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
267 lines
15 KiB
JavaScript
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)
|
|
})
|