Files
xinmi/scripts/verify_public_fixture_reconciliation.test.cjs
yawan f7fc992f57
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
2026-09-22 16:19:06 +08:00

267 lines
15 KiB
JavaScript

const assert = require('node:assert/strict')
const test = require('node:test')
const path = require('node:path')
const fs = require('node:fs')
const vm = require('node:vm')
const {assertFixtureReconciliation} = require('./publish_public_runtime.cjs')
const {
reportNames, memoryTables, validateReports, readOnlySQL, executeReadOnly,
identityQuery, resolveIdentities, reconciliationQuery, reconcile,
} = require('./verify_public_fixture_reconciliation.cjs')
function fixture() {
let nextID = 0
const id = () => `00000000-0000-4000-8000-${String(++nextID).padStart(12, '0')}`
const now = Date.parse('2026-09-20T02:00:00.000Z')
const manifest = {release: 'public-20260919-223000', url: 'https://agent.pducv.cn',
activated_at: '2026-09-20T00:00:00.000Z', api_activated_at: '2026-09-20T00:01:00.000Z',
frontend_built_at: '2026-09-20T00:02:00.000Z', frontend_activated_at: '2026-09-20T00:03:00.000Z'}
const reports = Object.fromEntries(reportNames.map((name, index) => [name, {
started_at: `2026-09-20T01:0${index}:00.000Z`, time: `2026-09-20T01:0${index}:50.000Z`,
origin: manifest.url, api_origin: manifest.url, passed: true, checks: [{name: 'fixture_checked', passed: true}], failures: [],
}]))
const specifications = [
['public-resource-isolation', 'resource-isolation-1001', {users: 2, roles: 1, agents: 2, skills: 1, kbs: 1}],
['public-resource-files', 'file-isolation-1002', {users: 2, roles: 1, kbs: 2, docs: 1, sessions: 1, messages: 2, resources: 3}],
['public-indexed-retrieval-isolation', 'indexed-isolation-1003', {users: 2, roles: 1, kbs: 1, docs: 1, tags: 2}],
]
for (const [name, prefix, counts] of specifications) Object.assign(reports[name], {prefix,
fixtures: Object.fromEntries(Object.entries(counts).map(([field, count]) => [field, Array.from({length: count}, id)]))})
const indexed = reports['public-indexed-retrieval-isolation']
indexed.cleanup = indexed.fixtures.kbs.map(knowledgeID => ({name: `knowledge ${knowledgeID}`, passed: true}))
const resource = reports['public-resource-isolation']
resource.fixtures.memory_subjects = resource.fixtures.users.map(user => `web_user:${user}`)
const file = reports['public-resource-files']
file.fixtures.message_scopes = [{tenant_id: 10059, session_id: file.fixtures.sessions[0],
user_id: file.fixtures.users[0], title: file.prefix, username: `${file.prefix}-alice`,
email: `${file.prefix}-alice@example.test`, messages: file.fixtures.messages.map((message, index) => ({
id: message, content: ['Disposable attachment fixture.', 'Unrelated message.'][index],
}))}]
const tenant = reports['public-tenant-isolation']
Object.assign(tenant, {prefix: 'tenant-isolation-1004', second_tenant: 10060})
const identities = []
const addUser = (report, username, userID = id(), email = `${username}@example.test`, tenantID = 10059) => {
identities.push({id: userID, username, email, tenant_id: tenantID, created_at: report.started_at})
}
for (const [name, labels] of [
['public-resource-isolation', ['alice', 'bob']], ['public-resource-files', ['alice', 'bob']],
['public-indexed-retrieval-isolation', ['owner', 'reader']],
]) labels.forEach((label, index) => addUser(reports[name], `${reports[name].prefix}-${label}`, reports[name].fixtures.users[index]))
for (const label of ['alice', 'bob']) addUser(tenant, `${tenant.prefix}-${label}`)
addUser(reports['public-access-control'], 'access-check-1005', id(), 'updated-access-check-1005@example.test')
const groups = resolveIdentities(reports, identities)
const {countNames} = reconciliationQuery(reports, groups)
const database = {counts: Object.fromEntries(countNames.map(name => [name, 0])),
retained: {soft_deleted_users: 9, revoked_tokens: 10, soft_deleted_sessions: 2, soft_deleted_documents: 4,
soft_deleted_chunks: 2, workbench_revisions: 5}, discovered: {agents: resource.fixtures.agents}}
const queries = [], paths = []
const options = {manifest, reports, now, directory: '/fixture-runtime',
executeSQL: query => {queries.push(query); return queries.length === 1 ? identities : database},
exists: value => {paths.push(value); return false}}
return {options, reports, identities, groups, database, queries, paths, id}
}
test('reconciles all fresh reports and accepts retained audit and soft-delete history', () => {
const value = fixture()
const report = reconcile(value.options)
assert.equal(report.passed, true)
assert.equal(report.read_only, true)
assert.equal(report.checks.length, 30)
assert.deepEqual(report.failures, [])
assert.equal(value.queries.length, 2)
assert.deepEqual(report.retained, value.database.retained)
assert.equal(report.identities.length, 9)
assert.match(report.scope, /Milvus.*not queried again/)
assert.deepEqual(value.paths, [path.join('/fixture-runtime', 'acceptance/files/10059/exports/file-isolation-1002')])
})
test('generated reconciliation satisfies the publication gate contract', () => {
const {options} = fixture()
options.directory = path.resolve(__dirname, '../..')
const report = reconcile(options)
assert.doesNotThrow(() => assertFixtureReconciliation(options.manifest, report, options.reports, options.now))
})
for (const name of reportNames) {
test(`rejects stale ${name} before any database query`, () => {
const {options, reports, queries} = fixture()
reports[name].started_at = '2026-09-20T00:02:59.999Z'
assert.throws(() => reconcile(options), /started before this runtime/)
assert.deepEqual(queries, [])
})
}
for (const [label, mutate] of [
['missing report', reports => {delete reports['public-navigation']}],
['wrong origin', reports => {reports['public-navigation'].origin = 'http://127.0.0.1:18088'}],
['failed report', reports => {reports['public-navigation'].passed = false}],
['hidden failed check', reports => {reports['public-navigation'].checks.push({name: 'failed', passed: false})}],
['reported failures', reports => {reports['public-navigation'].failures.push('failed cleanup')}],
['failed cleanup result', reports => {reports['public-indexed-retrieval-isolation'].cleanup = [{name: 'knowledge fixture', passed: false}]}],
['invalid cleanup result', reports => {reports['public-indexed-retrieval-isolation'].cleanup = [null]}],
['missing indexed cleanup', reports => {delete reports['public-indexed-retrieval-isolation'].cleanup}],
['mismatched indexed cleanup', reports => {reports['public-indexed-retrieval-isolation'].cleanup[0].name = 'knowledge other'}],
['empty indexed fixtures', reports => {reports['public-indexed-retrieval-isolation'].fixtures.kbs = []}],
['missing start time', reports => {delete reports['public-navigation'].started_at}],
['future completion', reports => {reports['public-navigation'].time = '2026-09-21T01:00:00.000Z'}],
['unsafe file prefix', reports => {reports['public-resource-files'].prefix = '../../uploads'}],
['invalid optional ID', reports => {reports['public-tenant-isolation'].fixtures = {agents: ['bad ID']}}],
['duplicate UUID', reports => {reports['public-resource-isolation'].fixtures.users.fill(reports['public-resource-isolation'].fixtures.users[0])}],
['missing IDs', reports => {delete reports['public-resource-files'].fixtures.resources}],
['changed message ID', reports => {reports['public-resource-files'].fixtures.message_scopes[0].messages[0].id = reports['public-resource-files'].fixtures.docs[0]}],
['changed message content', reports => {reports['public-resource-files'].fixtures.message_scopes[0].messages[0].content = 'Real user message.'}],
['different message tenant', reports => {reports['public-resource-files'].fixtures.message_scopes[0].tenant_id = 10060}],
['different message owner', reports => {reports['public-resource-files'].fixtures.message_scopes[0].user_id = reports['public-resource-files'].fixtures.users[1]}],
['different fixture email', reports => {reports['public-resource-files'].fixtures.message_scopes[0].email = 'real@example.test'}],
['extra synthetic message', reports => {reports['public-resource-files'].fixtures.messages.push(reports['public-resource-files'].fixtures.docs[0])}],
['wrong memory subjects', reports => {reports['public-resource-isolation'].fixtures.memory_subjects = ['web_user:other']}],
['unsafe secondary tenant', reports => {reports['public-tenant-isolation'].second_tenant = 10059}],
['invalid access prefix', reports => {reports['public-access-control'].prefix = 'access-check-1005-extra'}],
]) {
test(`rejects ${label} before database access`, () => {
const {options, reports, queries} = fixture()
mutate(reports)
assert.throws(() => reconcile(options))
assert.deepEqual(queries, [])
})
}
for (const [label, mutate] of [
['email mismatch', rows => {rows[0].email = 'real@example.test'}],
['tenant mismatch', rows => {rows[0].tenant_id = 1}],
['creation outside the run', rows => {rows[0].created_at = '2026-09-19T01:00:00.000Z'}],
['missing user', rows => {rows.pop()}],
['ambiguous access account', rows => {rows.push({...rows.at(-1), id: 'ffffffff-0000-4000-8000-000000000001'})}],
['unrelated returned account', rows => {rows.push({...rows[0], username: 'unrelated', id: 'ffffffff-0000-4000-8000-000000000002'})}],
]) {
test(`rejects discovered ${label} before reconciliation query`, () => {
const {options, identities, queries} = fixture()
mutate(identities)
assert.throws(() => reconcile(options))
assert.equal(queries.length, 1)
})
}
test('can identify the tenant fixture while its restoration is incomplete', () => {
const value = fixture()
value.identities.find(row => row.username === 'tenant-isolation-1004-bob').tenant_id = 10060
assert.doesNotThrow(() => resolveIdentities(value.reports, value.identities))
})
test('applies read-only mode, timeout and rollback to both generated SQL queries', () => {
const value = fixture()
reconcile(value.options)
const calls = []
for (const query of value.queries) {
const output = executeReadOnly(query, (command, args, options) => {
calls.push({command, args, options})
return '{"injected":true}\n'
})
assert.deepEqual(output, {injected: true})
}
for (const {command, args, options} of calls) {
assert.equal(command, 'docker')
assert.ok(args.includes('colima-yawan'))
assert.ok(args.includes('yawan_native_acceptance_20260915'))
assert.ok(args.includes('ON_ERROR_STOP=1'))
assert.equal(options.timeout, 45000)
assert.match(options.input, /^BEGIN READ ONLY;\nSET LOCAL statement_timeout='15s';\n(?:WITH|SELECT)\b/)
assert.match(options.input, /;\nROLLBACK;$/)
assert.doesNotMatch(options.input, /(?:^|;)\s*(?:DELETE|UPDATE|INSERT|COMMIT|DROP|ALTER|TRUNCATE)\b/im)
assert.doesNotMatch(options.input, /password|access_token|refresh_token/i)
assert.deepEqual(options.stdio, ['pipe', 'pipe', 'pipe'])
}
})
test('rejects a direct mutation query and invalid database output', () => {
assert.throws(() => readOnlySQL('DELETE FROM users;'))
assert.throws(() => executeReadOnly('SELECT 1;', () => 'not JSON'))
})
test('uses recorded IDs and exact bounded discovery for missing fixture IDs', () => {
const {reports, groups} = fixture()
const discovery = identityQuery(reports)
assert.match(discovery, /u\.username ~ '\^access-check-\[0-9\]\+\$'/)
assert.match(discovery, /u\.tenant_id=10059/)
assert.match(discovery, /u\.email IN \(u\.username \|\| '@example.test','updated-'/)
assert.match(discovery, /u\.username IN \('tenant-isolation-1004-alice','tenant-isolation-1004-bob'\)/)
reports['public-access-control'].prefix = 'access-check-1005'
assert.match(identityQuery(reports), /u\.username='access-check-1005'/)
const {query} = reconciliationQuery(reports, groups)
for (const group of groups) {
assert.ok(query.includes(group.report.started_at))
assert.ok(query.includes(group.report.time))
for (const userID of group.users) assert.ok(query.includes(userID))
}
assert.match(query, /UNION SELECT unnest\(ARRAY\[/)
assert.match(query, /FROM workbench_events e WHERE e\.tenant_id=10059 AND e\.action='create'/)
assert.match(query, /e\.message ~ '\^创建access-check-1005/)
assert.doesNotMatch(query, /LIKE|ILIKE/)
for (const table of memoryTables) assert.match(query, new RegExp(`FROM ${table} WHERE tenant_id IN \\(10059,10060\\) AND subject_id IN \\('web_user:`))
for (const id of reports['public-resource-files'].fixtures.messages) assert.ok(query.includes(id))
for (const id of reports['public-resource-files'].fixtures.resources) assert.ok(query.includes(id))
assert.match(query, /FROM chunks WHERE tenant_id=10059 AND \(knowledge_base_id IN/)
assert.match(query, /FROM workbench_access_publications WHERE tenant_id=10059 AND resource_id IN/)
})
for (const name of ['active_users', 'grants', 'unrevoked_tokens', 'synthetic_messages', 'file_resources', 'file_bindings', 'active_chunks', 'publications', ...memoryTables]) {
test(`reports leftover ${name} without removing it`, () => {
const {options, database} = fixture()
database.counts[name] = 1
const report = reconcile(options)
assert.equal(report.passed, false)
assert.deepEqual(report.failures, [name])
assert.equal(database.counts[name], 1)
})
}
test('fails when the exact attachment fixture directory remains', () => {
const {options} = fixture()
options.exists = () => true
assert.deepEqual(reconcile(options).failures, ['file_fixture_directory_removed'])
})
for (const count of [undefined, null, '0', -1, 0.5, Infinity, NaN, Number.MAX_SAFE_INTEGER + 1]) {
test(`fails closed on invalid counts (${String(count)})`, () => {
const {options, database} = fixture()
database.counts.active_users = count
assert.throws(() => reconcile(options), /Invalid reconciliation count: active_users/)
})
}
test('does not accept an unvalidated release or origin', () => {
const {options} = fixture()
options.manifest.release = '../old-release'
assert.throws(() => validateReports(options.manifest, options.reports, options.now))
options.manifest.release = 'public-20260919-223000'
options.manifest.url = 'http://127.0.0.1:18088'
assert.throws(() => validateReports(options.manifest, options.reports, options.now))
})
test('CLI invalidates a stale successful artifact before a report read can fail', () => {
const writes = [], errors = []
const module = {exports: {}}
const process = {argv: ['node', 'script.cjs', 'public-20260919-223000']}
const injectedRequire = name => {
if (name === 'node:fs') return {
writeFileSync: (...args) => writes.push(args),
readFileSync: () => {throw new Error('Missing verification report')},
}
if (name === 'node:child_process') return {execFileSync: () => assert.fail('No database command is allowed')}
return require(name)
}
injectedRequire.main = module
vm.runInNewContext(fs.readFileSync(path.join(__dirname, 'verify_public_fixture_reconciliation.cjs'), 'utf8'), {
require: injectedRequire, module, process, __dirname, console: {error: value => errors.push(value)},
})
assert.equal(writes.length, 1)
assert.match(writes[0][0], /acceptance\/public-fixture-reconciliation\.json$/)
const invalidated = JSON.parse(writes[0][1])
assert.equal(invalidated.passed, false)
assert.equal(invalidated.read_only, true)
assert.deepEqual(invalidated.failures, ['Reconciliation has not completed.'])
assert.equal(writes[0][2].mode, 0o600)
assert.deepEqual(errors, ['Missing verification report'])
assert.equal(process.exitCode, 1)
})