WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
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
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
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const { execFileSync } = require('node:child_process')
|
||||
const { assertPublicVerification, publicVerificationReports, fixtureReconciliationCounts } = require('./publish_public_runtime.cjs')
|
||||
|
||||
const root = path.resolve(__dirname, '../..')
|
||||
const tenantID = 10059
|
||||
const reportNames = publicVerificationReports
|
||||
const memoryTables = ['memory_item_embeddings', 'memory_items', 'memory_tombstones', 'memory_topic_stats', 'memory_doc_affinity', 'memory_subjects']
|
||||
const fixtureIDFields = ['users', 'roles', 'agents', 'skills', 'humans', 'kbs', 'workbench', 'docs', 'tags', 'sessions', 'messages', 'resources']
|
||||
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
||||
const literal = value => `'${String(value).replaceAll("'", "''")}'`
|
||||
const idsSQL = values => values.length ? values.map(literal).join(',') : 'NULL'
|
||||
const completed = report => report.checked_at || report.time
|
||||
const runWindow = (alias, report) => `${alias}.created_at >= ${literal(report.started_at)}::timestamptz AND ${alias}.created_at <= ${literal(completed(report))}::timestamptz`
|
||||
const unique = values => [...new Set(values)]
|
||||
|
||||
function validateIDs(value, name, minimum = 0) {
|
||||
assert.ok(Array.isArray(value) && value.length >= minimum, `Missing fixture IDs: ${name}`)
|
||||
value.forEach(id => assert.match(id, uuid, `Invalid fixture ID: ${name}`))
|
||||
assert.equal(new Set(value).size, value.length, `Duplicate fixture IDs: ${name}`)
|
||||
return value
|
||||
}
|
||||
|
||||
function validateReports(manifest, reports, now = Date.now()) {
|
||||
assert.match(manifest.release, /^public-\d{8}-\d{6}$/)
|
||||
assert.equal(manifest.url, 'https://agent.pducv.cn')
|
||||
for (const name of reportNames) {
|
||||
assert.ok(reports[name], `Missing verification report: ${name}`)
|
||||
assertPublicVerification(manifest, reports[name], name, now)
|
||||
for (const field of fixtureIDFields) {
|
||||
if (reports[name].fixtures?.[field] !== undefined) validateIDs(reports[name].fixtures[field], `${name}.${field}`)
|
||||
}
|
||||
}
|
||||
const specifications = [
|
||||
['public-resource-isolation', 'resource-isolation', {users: 2, roles: 1, agents: 2, skills: 1, kbs: 1}],
|
||||
['public-resource-files', 'file-isolation', {users: 2, roles: 1, kbs: 2, docs: 1, sessions: 1, messages: 2, resources: 3}],
|
||||
['public-indexed-retrieval-isolation', 'indexed-isolation', {users: 2, roles: 1, kbs: 1, docs: 1, tags: 2}],
|
||||
]
|
||||
for (const [name, prefix, fields] of specifications) {
|
||||
const report = reports[name]
|
||||
assert.match(report.prefix, new RegExp(`^${prefix}-[0-9]+$`))
|
||||
for (const [field, minimum] of Object.entries(fields)) validateIDs(report.fixtures?.[field], `${name}.${field}`, minimum)
|
||||
}
|
||||
const resource = reports['public-resource-isolation']
|
||||
assert.deepEqual(resource.fixtures.memory_subjects, resource.fixtures.users.map(id => `web_user:${id}`))
|
||||
const file = reports['public-resource-files']
|
||||
for (const [field, count] of Object.entries({users: 2, sessions: 1, messages: 2, resources: 3})) assert.equal(file.fixtures[field].length, count, `Unexpected file fixture count: ${field}`)
|
||||
assert.equal(file.fixtures.message_scopes?.length, 1)
|
||||
const scope = file.fixtures.message_scopes[0]
|
||||
assert.equal(scope.tenant_id, tenantID)
|
||||
assert.equal(scope.session_id, file.fixtures.sessions[0])
|
||||
assert.equal(scope.user_id, file.fixtures.users[0])
|
||||
assert.equal(scope.title, file.prefix)
|
||||
assert.equal(scope.username, `${file.prefix}-alice`)
|
||||
assert.equal(scope.email, `${scope.username}@example.test`)
|
||||
assert.ok(Array.isArray(scope.messages), 'Missing file fixture messages')
|
||||
assert.deepEqual(scope.messages.map(message => message.id), file.fixtures.messages)
|
||||
assert.deepEqual(scope.messages.map(message => message.content), ['Disposable attachment fixture.', 'Unrelated message.'])
|
||||
const tenant = reports['public-tenant-isolation']
|
||||
assert.match(tenant.prefix, /^tenant-isolation-[0-9]+$/)
|
||||
assert.ok(Number.isSafeInteger(tenant.second_tenant) && tenant.second_tenant > tenantID)
|
||||
const access = reports['public-access-control']
|
||||
if (access.prefix !== undefined) assert.match(access.prefix, /^access-check-[0-9]+$/)
|
||||
}
|
||||
|
||||
function readOnlySQL(query) {
|
||||
assert.match(query.trimStart(), /^(SELECT|WITH)\b/)
|
||||
return `BEGIN READ ONLY;\nSET LOCAL statement_timeout='15s';\n${query}\nROLLBACK;`
|
||||
}
|
||||
|
||||
function executeReadOnly(query, run = execFileSync) {
|
||||
const output = run('docker', ['--context', 'colima-yawan', 'exec', '-i', 'WeKnora-postgres',
|
||||
'psql', '-X', '-q', '-U', 'postgres', '-d', 'yawan_native_acceptance_20260915', '-At', '-v', 'ON_ERROR_STOP=1'], {
|
||||
input: readOnlySQL(query), encoding: 'utf8', timeout: 45000, maxBuffer: 4 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'],
|
||||
}).trim()
|
||||
return JSON.parse(output)
|
||||
}
|
||||
|
||||
function identityQuery(reports) {
|
||||
const clauses = []
|
||||
for (const name of ['public-resource-isolation', 'public-resource-files', 'public-indexed-retrieval-isolation']) {
|
||||
clauses.push(`(u.id IN (${idsSQL(reports[name].fixtures.users)}))`)
|
||||
}
|
||||
const tenant = reports['public-tenant-isolation']
|
||||
clauses.push(`(${runWindow('u', tenant)} AND u.username IN (${literal(tenant.prefix + '-alice')},${literal(tenant.prefix + '-bob')}) AND u.email=u.username || '@example.test')`)
|
||||
const access = reports['public-access-control']
|
||||
const username = access.prefix ? `u.username=${literal(access.prefix)}` : "u.username ~ '^access-check-[0-9]+$'"
|
||||
clauses.push(`(${runWindow('u', access)} AND u.tenant_id=${tenantID} AND ${username} AND u.email IN (u.username || '@example.test','updated-' || u.username || '@example.test'))`)
|
||||
return `SELECT COALESCE(json_agg(json_build_object('id',u.id,'username',u.username,'email',u.email,'tenant_id',u.tenant_id,'created_at',u.created_at)), '[]'::json) FROM users u WHERE ${clauses.join(' OR ')};`
|
||||
}
|
||||
|
||||
function resolveIdentities(reports, rows) {
|
||||
assert.ok(Array.isArray(rows), 'User discovery did not return an array')
|
||||
const groups = [], used = new Set()
|
||||
const matchUser = (report, username, id, tenants, updatedEmail = false) => {
|
||||
const found = rows.filter(row => row.username === username && (!id || row.id === id))
|
||||
assert.equal(found.length, 1, `Expected one retained fixture identity: ${username}`)
|
||||
const row = found[0]
|
||||
assert.match(row.id, uuid)
|
||||
assert.ok(tenants.includes(Number(row.tenant_id)), `Unexpected fixture tenant: ${username}`)
|
||||
assert.ok([`${username}@example.test`, ...(updatedEmail ? [`updated-${username}@example.test`] : [])].includes(row.email), `Unexpected fixture email: ${username}`)
|
||||
assert.ok(Date.parse(row.created_at) >= Date.parse(report.started_at) && Date.parse(row.created_at) <= Date.parse(completed(report)), `Fixture identity outside report window: ${username}`)
|
||||
assert.ok(!used.has(row.id), `Fixture ID reused across reports: ${row.id}`)
|
||||
used.add(row.id)
|
||||
return row.id
|
||||
}
|
||||
for (const [name, labels] of [
|
||||
['public-resource-isolation', ['alice', 'bob']], ['public-resource-files', ['alice', 'bob']],
|
||||
['public-indexed-retrieval-isolation', ['owner', 'reader']],
|
||||
]) {
|
||||
const report = reports[name]
|
||||
assert.equal(report.fixtures.users.length, labels.length)
|
||||
groups.push({name, report, prefix: report.prefix, users: labels.map((label, index) => matchUser(report, `${report.prefix}-${label}`, report.fixtures.users[index], [tenantID]))})
|
||||
}
|
||||
const tenant = reports['public-tenant-isolation']
|
||||
groups.push({name: 'public-tenant-isolation', report: tenant, prefix: tenant.prefix, users: ['alice', 'bob'].map(label => matchUser(tenant, `${tenant.prefix}-${label}`, undefined, [tenantID, tenant.second_tenant]))})
|
||||
const access = reports['public-access-control']
|
||||
const candidates = rows.filter(row => /^access-check-[0-9]+$/.test(row.username) && (!access.prefix || access.prefix === row.username))
|
||||
assert.equal(candidates.length, 1, 'Expected one access-control fixture account in its exact report window')
|
||||
const accessPrefix = candidates[0].username
|
||||
groups.push({name: 'public-access-control', report: access, prefix: accessPrefix, users: [matchUser(access, accessPrefix, undefined, [tenantID], true)]})
|
||||
assert.equal(used.size, rows.length, 'Unexpected identity returned by fixture discovery')
|
||||
return groups
|
||||
}
|
||||
|
||||
function reconciliationQuery(reports, groups) {
|
||||
const allUsers = unique(groups.flatMap(group => group.users))
|
||||
const reported = key => unique(reportNames.flatMap(name => reports[name].fixtures?.[key] || []))
|
||||
const targetName = (alias, field, group) => `${alias}.${field} ~ ${literal(`^${group.prefix}($|-(invalid|agent|admin-agent|member-agent|human|knowledge|skill|flow|admin-edited|member-flow|shared-flow-task|blocked|viewer-blocked|forbidden|document|alice|bob)$)`)}`
|
||||
const discover = (alias, owner, field = 'name') => groups.map(group => `(${runWindow(alias, group.report)} AND (${owner ? `${alias}.${owner} IN (${idsSQL(group.users)}) OR ` : ''}${targetName(alias, field, group)}))`).join(' OR ')
|
||||
const scoped = (table, key, owner, field = 'name') => `SELECT t.id FROM ${table} t WHERE t.tenant_id=${tenantID} AND (t.id IN (${idsSQL(reported(key))}) OR ${discover('t', owner, field)})`
|
||||
const eventScope = groups.map(group => `(${runWindow('e', group.report)} AND ${targetName('e', 'message', {...group, prefix: '创建' + group.prefix})})`).join(' OR ')
|
||||
const ctes = [
|
||||
`fixture_roles AS (${scoped('workbench_access_roles', 'roles', null)})`,
|
||||
`fixture_agents AS (${scoped('custom_agents', 'agents', 'created_by')})`,
|
||||
`fixture_skills AS (${scoped('tenant_skill_catalog', 'skills', 'created_by')})`,
|
||||
`fixture_humans AS (${scoped('digital_humans', 'humans', 'owner_id')})`,
|
||||
`fixture_kbs AS (${scoped('knowledge_bases', 'kbs', 'creator_id')})`,
|
||||
`fixture_workbench AS (${scoped('workbench_resources', 'workbench', 'owner_id')} UNION SELECT e.resource_id FROM workbench_events e WHERE e.tenant_id=${tenantID} AND e.action='create' AND (${eventScope}))`,
|
||||
`fixture_sessions AS (${scoped('sessions', 'sessions', 'user_id', 'title')})`,
|
||||
`fixture_docs AS (SELECT id FROM knowledges WHERE tenant_id=${tenantID} AND (id IN (${idsSQL(reported('docs'))}) OR knowledge_base_id IN (SELECT id FROM fixture_kbs)))`,
|
||||
`fixture_tags AS (SELECT id FROM knowledge_tags WHERE tenant_id=${tenantID} AND (id IN (${idsSQL(reported('tags'))}) OR knowledge_base_id IN (SELECT id FROM fixture_kbs)))`,
|
||||
`fixture_resource_ids AS (SELECT id FROM fixture_agents UNION SELECT id FROM fixture_skills UNION SELECT id FROM fixture_humans UNION SELECT id FROM fixture_kbs UNION SELECT id FROM fixture_workbench)`,
|
||||
]
|
||||
// Include recorded IDs even when their resource row was physically removed.
|
||||
for (const [cte, key] of [['fixture_roles', 'roles'], ['fixture_agents', 'agents'], ['fixture_skills', 'skills'], ['fixture_humans', 'humans'], ['fixture_kbs', 'kbs'], ['fixture_workbench', 'workbench'], ['fixture_docs', 'docs'], ['fixture_tags', 'tags'], ['fixture_sessions', 'sessions']]) {
|
||||
const index = ctes.findIndex(value => value.startsWith(`${cte} AS (`))
|
||||
const ids = reported(key)
|
||||
if (ids.length) ctes[index] = ctes[index].slice(0, -1) + ` UNION SELECT unnest(ARRAY[${idsSQL(ids)}]::text[]))`
|
||||
}
|
||||
const inUsers = `IN (${idsSQL(allUsers)})`
|
||||
const fileIDs = idsSQL(reported('resources')), messageIDs = idsSQL(reported('messages'))
|
||||
const membershipTenants = `${tenantID},${reports['public-tenant-isolation'].second_tenant}`
|
||||
const counts = {
|
||||
active_users: `SELECT count(*) FROM users WHERE id ${inUsers} AND deleted_at IS NULL`,
|
||||
active_memberships: `SELECT count(*) FROM tenant_members WHERE user_id ${inUsers} AND deleted_at IS NULL`,
|
||||
roles: 'SELECT count(*) FROM workbench_access_roles WHERE id IN (SELECT id FROM fixture_roles)',
|
||||
assignments: `SELECT count(*) FROM workbench_access_assignments WHERE user_id ${inUsers} OR role_id IN (SELECT id FROM fixture_roles)`,
|
||||
grants: `SELECT count(*) FROM workbench_access_grants WHERE user_id ${inUsers} OR (tenant_id=${tenantID} AND resource_id IN (SELECT id FROM fixture_resource_ids))`,
|
||||
unrevoked_tokens: `SELECT count(*) FROM auth_tokens WHERE user_id ${inUsers} AND is_revoked=false`,
|
||||
active_agents: 'SELECT count(*) FROM custom_agents WHERE id IN (SELECT id FROM fixture_agents) AND deleted_at IS NULL',
|
||||
active_skills: 'SELECT count(*) FROM tenant_skill_catalog WHERE id IN (SELECT id FROM fixture_skills) AND deleted_at IS NULL',
|
||||
active_digital_humans: 'SELECT count(*) FROM digital_humans WHERE id IN (SELECT id FROM fixture_humans) AND deleted_at IS NULL',
|
||||
workbench_resources: 'SELECT count(*) FROM workbench_resources WHERE id IN (SELECT id FROM fixture_workbench)',
|
||||
active_sessions: 'SELECT count(*) FROM sessions WHERE id IN (SELECT id FROM fixture_sessions) AND deleted_at IS NULL',
|
||||
synthetic_messages: `SELECT count(*) FROM messages WHERE id IN (${messageIDs})`,
|
||||
file_resources: `SELECT count(*) FROM resources WHERE id IN (${fileIDs})`,
|
||||
file_bindings: `SELECT count(*) FROM resource_bindings WHERE resource_id IN (${fileIDs})`,
|
||||
file_access_grants: `SELECT count(*) FROM resource_access_grants WHERE resource_id IN (${fileIDs}) AND revoked_at IS NULL`,
|
||||
active_knowledge_bases: 'SELECT count(*) FROM knowledge_bases WHERE id IN (SELECT id FROM fixture_kbs) AND deleted_at IS NULL',
|
||||
active_documents: 'SELECT count(*) FROM knowledges WHERE id IN (SELECT id FROM fixture_docs) AND deleted_at IS NULL',
|
||||
active_chunks: `SELECT count(*) FROM chunks WHERE tenant_id=${tenantID} AND (knowledge_base_id IN (SELECT id FROM fixture_kbs) OR knowledge_id IN (SELECT id FROM fixture_docs)) AND deleted_at IS NULL`,
|
||||
tags: 'SELECT count(*) FROM knowledge_tags WHERE id IN (SELECT id FROM fixture_tags)',
|
||||
tag_relations: 'SELECT count(*) FROM knowledge_tag_relations WHERE tag_id IN (SELECT id FROM fixture_tags) OR knowledge_id IN (SELECT id FROM fixture_docs)',
|
||||
publications: `SELECT count(*) FROM workbench_access_publications WHERE tenant_id=${tenantID} AND resource_id IN (SELECT id FROM fixture_resource_ids)`,
|
||||
secondary_tenant: `SELECT count(*) FROM tenants WHERE id=${reports['public-tenant-isolation'].second_tenant}`,
|
||||
secondary_memberships: `SELECT count(*) FROM tenant_members WHERE tenant_id=${reports['public-tenant-isolation'].second_tenant} AND deleted_at IS NULL`,
|
||||
}
|
||||
for (const table of memoryTables) counts[table] = `SELECT count(*) FROM ${table} WHERE tenant_id IN (${membershipTenants}) AND subject_id IN (${idsSQL(allUsers.map(id => `web_user:${id}`))})`
|
||||
assert.deepEqual(Object.keys(counts), fixtureReconciliationCounts, 'Reconciliation queries must cover the publication contract')
|
||||
const retained = {
|
||||
soft_deleted_users: `SELECT count(*) FROM users WHERE id ${inUsers} AND deleted_at IS NOT NULL`,
|
||||
revoked_tokens: `SELECT count(*) FROM auth_tokens WHERE user_id ${inUsers} AND is_revoked=true`,
|
||||
soft_deleted_sessions: 'SELECT count(*) FROM sessions WHERE id IN (SELECT id FROM fixture_sessions) AND deleted_at IS NOT NULL',
|
||||
soft_deleted_documents: 'SELECT count(*) FROM knowledges WHERE id IN (SELECT id FROM fixture_docs) AND deleted_at IS NOT NULL',
|
||||
soft_deleted_chunks: `SELECT count(*) FROM chunks WHERE tenant_id=${tenantID} AND knowledge_base_id IN (SELECT id FROM fixture_kbs) AND deleted_at IS NOT NULL`,
|
||||
workbench_revisions: `SELECT count(*) FROM workbench_revisions WHERE tenant_id=${tenantID} AND resource_id IN (SELECT id FROM fixture_workbench)`,
|
||||
}
|
||||
const objectSQL = values => `json_build_object(${Object.entries(values).map(([name, query]) => `${literal(name)},(${query})`).join(',')})`
|
||||
const discovered = Object.fromEntries(['roles', 'agents', 'skills', 'humans', 'kbs', 'workbench', 'sessions', 'docs', 'tags'].map(key => [key, `SELECT COALESCE(json_agg(id), '[]'::json) FROM fixture_${key}`]))
|
||||
return {query: `WITH ${ctes.join(',\n')}\nSELECT json_build_object('counts',${objectSQL(counts)},'retained',${objectSQL(retained)},'discovered',${objectSQL(discovered)});`, countNames: Object.keys(counts)}
|
||||
}
|
||||
|
||||
function reconcile({manifest, reports, executeSQL = executeReadOnly, exists = fs.existsSync, directory = root, now = Date.now()}) {
|
||||
validateReports(manifest, reports, now)
|
||||
const identities = executeSQL(identityQuery(reports))
|
||||
const groups = resolveIdentities(reports, identities)
|
||||
const {query, countNames} = reconciliationQuery(reports, groups)
|
||||
const database = executeSQL(query)
|
||||
const checks = countNames.map(name => {
|
||||
const count = database.counts?.[name]
|
||||
assert.ok(Number.isSafeInteger(count) && count >= 0, `Invalid reconciliation count: ${name}`)
|
||||
return {name, count, passed: count === 0}
|
||||
})
|
||||
const fileDirectory = path.join(directory, 'acceptance', 'files', String(tenantID), 'exports', reports['public-resource-files'].prefix)
|
||||
checks.push({name: 'file_fixture_directory_removed', path: fileDirectory, passed: !exists(fileDirectory)})
|
||||
return {release: manifest.release, origin: manifest.url, checked_at: new Date(now).toISOString(), read_only: true,
|
||||
reports: Object.fromEntries(reportNames.map(name => [name, {started_at: reports[name].started_at, completed_at: completed(reports[name])}])),
|
||||
identities, discovered: database.discovered, retained: database.retained, checks,
|
||||
failures: checks.filter(check => !check.passed).map(check => check.name), passed: checks.every(check => check.passed),
|
||||
scope: 'PostgreSQL fixture records and exact file-test export directory. Soft-deleted history, revoked tokens and audits are retained. Milvus zero-vector cleanup is attested by the fresh indexed-retrieval report, not queried again here.'}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const release = process.argv[2]
|
||||
assert.match(release || '', /^public-\d{8}-\d{6}$/, 'Usage: node scripts/verify_public_fixture_reconciliation.cjs public-YYYYMMDD-HHMMSS')
|
||||
const reportPath = path.join(root, 'acceptance', 'public-fixture-reconciliation.json')
|
||||
// Invalidate a previous success before reading reports or contacting the database.
|
||||
fs.writeFileSync(reportPath, JSON.stringify({release, started_at: new Date().toISOString(), read_only: true,
|
||||
passed: false, checks: [], failures: ['Reconciliation has not completed.']}, null, 2) + '\n', {mode: 0o600})
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'releases', release, 'manifest.json'), 'utf8'))
|
||||
assert.equal(manifest.release, release)
|
||||
const reports = Object.fromEntries(reportNames.map(name => [name, JSON.parse(fs.readFileSync(path.join(root, 'acceptance', `${name}.json`), 'utf8'))]))
|
||||
const report = reconcile({manifest, reports})
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n', {mode: 0o600})
|
||||
console.log(JSON.stringify({release, passed: report.passed, checks: report.checks.length, failures: report.failures, retained: report.retained}))
|
||||
if (!report.passed) process.exitCode = 1
|
||||
}
|
||||
|
||||
module.exports = {reportNames, memoryTables, validateReports, readOnlySQL, executeReadOnly, identityQuery, resolveIdentities, reconciliationQuery, reconcile}
|
||||
if (require.main === module) {
|
||||
try { main() } catch (error) { console.error(error.message); process.exitCode = 1 }
|
||||
}
|
||||
Reference in New Issue
Block a user