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,330 @@
|
||||
const { test } = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
const crypto = require('node:crypto')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const vm = require('node:vm')
|
||||
const cp = require('node:child_process')
|
||||
const policy = require('./wiki_recovery_policy.cjs')
|
||||
const sixth = require('./wiki_sixth_recovery_review.cjs')
|
||||
const sha = value => crypto.createHash('sha256').update(value).digest('hex')
|
||||
const reason = 'Reviewed successful exact-window diagnostic and published bounded candidate correction'
|
||||
|
||||
function fixture() {
|
||||
const now = Date.now()
|
||||
const scope = sixth.sixthRecoveryScope, pins = sixth.diagnosticPins
|
||||
const source = { ...scope, id: 10, eligible: true, pending: 0, chunks: 624 }
|
||||
const evidence = { run_id: '00000000-0000-4000-8000-000000000003', status: 'finished', candidate_source_only: true,
|
||||
source_tree_hash: 'a'.repeat(64), protected_state_unchanged: true, previous_diagnostics_unchanged: true,
|
||||
started_at: '2026-09-20T16:20:00Z', actual_send_reservations: 2,
|
||||
pins: { tenant: 10059, deadLetter: 10, priorAttempt: 5, priorOperation: 4144, document: scope.knowledge_id,
|
||||
kb: scope.knowledge_base_id, model: scope.model_id, embedding: scope.embedding_model_id,
|
||||
sourceFingerprint: scope.source_fingerprint, modelFingerprint: scope.model_fingerprint,
|
||||
release: pins.release, image: pins.image, maxSends: 21, durationMs: 900000 },
|
||||
outcome: { status: 'exact_window_candidates_validated', source_hash: pins.source,
|
||||
window: { start_rune: pins.start, end_rune: pins.end, sha256: pins.window }, candidate_count: 193,
|
||||
actual_send_reservations: 2, leaves: [{ start_rune: pins.start, end_rune: pins.end, sha256: pins.window }] } }
|
||||
const bytes = JSON.stringify(evidence)
|
||||
const review = { ...scope, budget_version: 1, max_attempt: 6, previous_attempt: 5, previous_pending_op_id: 4144,
|
||||
previous_error_code: 'EXTRACT_FAILED', fix_release: 'public-20260921-010000', api_image_id: `sha256:${'b'.repeat(64)}`,
|
||||
published_at: '2026-09-20T17:00:00Z', public_gates_passed: true, max_provider_requests: 1000,
|
||||
budget_started_at: new Date(now - 1000).toISOString(), deadline_at: new Date(now + 7199000).toISOString(),
|
||||
candidate_diagnostic: { kind: 'candidate_repair_window_v3', run_id: evidence.run_id,
|
||||
evidence_sha256: sha(bytes), source_tree_hash: evidence.source_tree_hash, semantic_reviewed: true } }
|
||||
const previous = { ...scope, attempt_number: 5, pending_op_id: 4144, recovery_status: 'stopped_budget', new_failures: 1,
|
||||
error_code: 'EXTRACT_FAILED', queued_at: '2026-09-20T08:58:56Z', pending: false, pending_finalize: 0,
|
||||
recovery_budget: { ...scope, attempt_number: 5, pending_op_id: 4144, status: 'stopped_budget' } }
|
||||
const pilot = { ...scope, dead_letter_id: 31, pending: false, recovery_status: 'wiki_recovered', span_status: 'done',
|
||||
pages_dropped: 0, failed_slug_writes: 0, missing_expected_pages: 0, failed_page_spans: 0, current_pages: 1,
|
||||
published_summaries: 1, expected_pages: 1, pages_written: 1, pages_total: 1 }
|
||||
const receipt = { ...scope, attempt_number: 6, pending_op_id: 6000, pending: true, pending_finalize: 0,
|
||||
deployed_fix_review: review, retry_reason: reason, new_failures: 0,
|
||||
recovery_budget: { ...scope, attempt_number: 6, pending_op_id: 6000, status: 'active',
|
||||
max_provider_requests: 1000, reserved_provider_requests: 0, deadline_at: review.deadline_at,
|
||||
reviewed_release: review.fix_release, reviewed_api_image: review.api_image_id } }
|
||||
return { source, evidence, bytes, review, previous, pilot, receipt }
|
||||
}
|
||||
|
||||
function replayFixture() {
|
||||
const data = fixture()
|
||||
data.evidence = { ...sixth.replayPins, run_id: '00000000-0000-4000-8000-000000000006',
|
||||
source_tree_hash: 'a'.repeat(64), status: 'passed', model_calls: 0, production_writes: 0,
|
||||
entity_count: 22, concept_count: 67, candidate_count: 89, rejected_count: 0,
|
||||
source_preserved: true, original_receipt_preserved: true,
|
||||
started_at: '2026-09-20T16:20:00Z', completed_at: '2026-09-20T16:21:00Z' }
|
||||
data.bytes = JSON.stringify(data.evidence)
|
||||
data.review.candidate_diagnostic = { kind: sixth.replayPins.kind, run_id: data.evidence.run_id,
|
||||
evidence_sha256: sha(data.bytes), source_tree_hash: data.evidence.source_tree_hash, semantic_reviewed: true }
|
||||
return data
|
||||
}
|
||||
|
||||
function guardPausedFixture() {
|
||||
const data = replayFixture()
|
||||
Object.assign(data.previous, { new_failures: 0, span_status: 'failed' })
|
||||
Object.assign(data.previous.recovery_budget, { stop_reason: 'guard_rejected', last_phase: 'wiki_candidate_slug',
|
||||
reserved_provider_requests: 4, paused_payload: JSON.stringify([{ ...sixth.sixthGuardPausedOperation,
|
||||
enqueued_at: data.previous.queued_at, claimed_at: '2026-09-20T08:58:59Z' }]) })
|
||||
return data
|
||||
}
|
||||
|
||||
test('reviewed guard pause permits zero new dead letters without changing the terminal receipt', () => {
|
||||
const { source, previous, pilot, receipt, review } = guardPausedFixture()
|
||||
const saved = JSON.stringify(previous)
|
||||
assert.deepEqual(policy.planRecovery([source], [pilot, previous], 6, reason, review), [source])
|
||||
assert.doesNotThrow(() => policy.validateFinalRecoveryTrigger(receipt, { ...source, pending: 1 }, [pilot, previous, receipt], review))
|
||||
assert.equal(JSON.stringify(previous), saved)
|
||||
})
|
||||
|
||||
test('zero-deadletter exception rejects missing or changed failed span and terminal guard evidence', () => {
|
||||
const { source, previous, pilot, review } = guardPausedFixture()
|
||||
for (const change of [{ span_status: undefined }, { span_status: 'done' }, { span_status: 'running' },
|
||||
{ error_code: undefined }, { error_code: 'other' }, { pending: true }, { pending_finalize: 1 },
|
||||
{ new_failures: -1 }, { new_failures: undefined }, { new_failures: '0' }]) {
|
||||
assert.throws(() => policy.planRecovery([source], [pilot, { ...previous, ...change }], 6, reason, review))
|
||||
}
|
||||
for (const change of [{ status: 'active' }, { stop_reason: undefined }, { stop_reason: 'deadline' },
|
||||
{ last_phase: undefined }, { last_phase: 'wiki_summary' }, { reserved_provider_requests: 3 },
|
||||
{ paused_payload: undefined }, { paused_payload: 'invalid' }, { paused_payload: '{}' },
|
||||
{ paused_payload: '[]' }, { paused_payload: '[null]' },
|
||||
{ paused_payload: JSON.stringify([sixth.sixthGuardPausedOperation, sixth.sixthGuardPausedOperation]) }]) {
|
||||
const changed = { ...previous, recovery_budget: { ...previous.recovery_budget, ...change } }
|
||||
assert.throws(() => policy.planRecovery([source], [pilot, changed], 6, reason, review))
|
||||
}
|
||||
})
|
||||
|
||||
test('zero-deadletter exception checks every paused operation and nested recovery scope field', () => {
|
||||
const { source, previous, pilot, review } = guardPausedFixture()
|
||||
for (const key of Object.keys(sixth.sixthGuardPausedOperation)) {
|
||||
if (key === 'payload') continue
|
||||
for (const value of [undefined, typeof sixth.sixthGuardPausedOperation[key] === 'number' ? -1 : 'changed']) {
|
||||
const paused = { ...sixth.sixthGuardPausedOperation, [key]: value }
|
||||
const changed = { ...previous, recovery_budget: { ...previous.recovery_budget, paused_payload: JSON.stringify([paused]) } }
|
||||
assert.throws(() => policy.planRecovery([source], [pilot, changed], 6, reason, review), key)
|
||||
}
|
||||
}
|
||||
for (const key of Object.keys(sixth.sixthGuardPausedOperation.payload)) {
|
||||
for (const value of [undefined, 'changed']) {
|
||||
const paused = { ...sixth.sixthGuardPausedOperation, payload: { ...sixth.sixthGuardPausedOperation.payload, [key]: value } }
|
||||
const changed = { ...previous, recovery_budget: { ...previous.recovery_budget, paused_payload: JSON.stringify([paused]) } }
|
||||
assert.throws(() => policy.planRecovery([source], [pilot, changed], 6, reason, review), key)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('saved-response replay preserves distinct identities and original failure without a new provider call', () => {
|
||||
const { evidence, review, bytes } = replayFixture()
|
||||
assert.doesNotThrow(() => sixth.validateSixthReplayEvidence(review, bytes, evidence.source_tree_hash))
|
||||
assert.throws(() => sixth.validateSixthReplayEvidence(review, bytes + '\n', evidence.source_tree_hash))
|
||||
assert.throws(() => sixth.validateSixthReplayEvidence(review, bytes, 'c'.repeat(64)))
|
||||
const changes = [{ status: 'failed' }, { model_calls: 1 }, { production_writes: 1 },
|
||||
{ source_preserved: false }, { original_receipt_preserved: false }, { rejected_count: 1 },
|
||||
{ entity_count: 21 }, { concept_count: 66 }, { candidate_count: 88 },
|
||||
{ input_run_id: 'other' }, { source_window_sha256: 'c'.repeat(64) },
|
||||
{ response_sha256: 'c'.repeat(64) }, { input_evidence_sha256: 'c'.repeat(64) },
|
||||
{ run_id: 'other' }, { completed_at: 'invalid' }, { completed_at: '2026-09-21T16:20:00Z' }]
|
||||
for (const change of changes) {
|
||||
const changed = JSON.stringify({ ...evidence, ...change })
|
||||
const changedReview = { ...review, candidate_diagnostic: { ...review.candidate_diagnostic, evidence_sha256: sha(changed) } }
|
||||
assert.throws(() => sixth.validateSixthReplayEvidence(changedReview, changed, evidence.source_tree_hash))
|
||||
}
|
||||
assert.throws(() => sixth.validateSixthDiagnosticEvidence(review, bytes, evidence.source_tree_hash))
|
||||
})
|
||||
|
||||
test('exact sixth scope uses a new receipt while attempt five and the default limit stay unchanged', () => {
|
||||
const { source, previous, pilot, receipt, review } = fixture()
|
||||
const original = JSON.stringify(previous)
|
||||
assert.equal(policy.maxReviewedAttempt, 4)
|
||||
assert.deepEqual(policy.planRecovery([source], [pilot, previous], 6, reason, review), [source])
|
||||
assert.deepEqual(policy.planRecovery([source], [pilot, previous, receipt], 6, reason, review), [])
|
||||
assert.doesNotThrow(() => policy.validateFinalRecoveryTrigger(receipt, { ...source, pending: 1 }, [pilot, previous, receipt], review))
|
||||
assert.equal(JSON.stringify(previous), original)
|
||||
assert.throws(() => policy.planRecovery([source], [pilot, previous], 7, reason, { ...review, max_attempt: 7 }))
|
||||
assert.equal(policy.durableRecoveryStatus({ ...receipt, recovery_budget: undefined }), 'stopped_invalid')
|
||||
for (const key of Object.keys(sixth.sixthRecoveryScope)) {
|
||||
const sourceKey = key === 'dead_letter_id' ? 'id' : key
|
||||
assert.throws(() => policy.planRecovery([{ ...source, [sourceKey]: 'changed' }], [pilot, previous], 6, reason, review))
|
||||
assert.throws(() => policy.planRecovery([source], [pilot, previous], 6, reason, { ...review, [key]: 'changed' }))
|
||||
}
|
||||
for (const change of [{ attempt_number: 4 }, { pending_op_id: 4143 }, { recovery_status: 'failed' },
|
||||
{ pending: true }, { pending_finalize: 1 }, { error_code: 'other' }, { new_failures: 0 }, { recovery_budget: undefined },
|
||||
{ recovery_budget: { ...previous.recovery_budget, status: 'active' } },
|
||||
{ recovery_budget: { ...previous.recovery_budget, pending_op_id: 4143 } }]) {
|
||||
assert.throws(() => policy.planRecovery([source], [pilot, { ...previous, ...change }], 6, reason, review))
|
||||
}
|
||||
})
|
||||
|
||||
test('sixth budgets, semantic review and published candidate are bounded independently', () => {
|
||||
const { review, source, previous, pilot, receipt } = fixture()
|
||||
for (const change of [{ max_provider_requests: 1001 }, { max_provider_requests: 0 }, { budget_version: 0 },
|
||||
{ previous_pending_op_id: 4143 }, { public_gates_passed: false }, { candidate_diagnostic: undefined },
|
||||
{ candidate_diagnostic: { ...review.candidate_diagnostic, semantic_reviewed: false } },
|
||||
{ api_image_id: sixth.diagnosticPins.image }, { fix_release: sixth.diagnosticPins.release },
|
||||
{ budget_started_at: 'invalid' }, { budget_started_at: new Date(Date.now() + 50000).toISOString() },
|
||||
{ deadline_at: new Date(Date.parse(review.budget_started_at) + 7200001).toISOString() },
|
||||
{ deadline_at: review.budget_started_at }]) {
|
||||
assert.throws(() => policy.planRecovery([source], [pilot, previous], 6, reason, { ...review, ...change }))
|
||||
}
|
||||
for (const change of [{ status: 'stopped_budget' }, { status: 'completed' }, { reserved_provider_requests: 1000 },
|
||||
{ max_provider_requests: 1001 }, { deadline_at: new Date(Date.parse(review.deadline_at) + 1).toISOString() }]) {
|
||||
const changed = { ...receipt, recovery_budget: { ...receipt.recovery_budget, ...change } }
|
||||
assert.throws(() => policy.validateFinalRecoveryTrigger(changed, { ...source, pending: 1 }, [pilot, previous, changed], review))
|
||||
}
|
||||
})
|
||||
|
||||
test('sixth recovery checks successful V3 bytes and exact candidate source rather than a summary claim', () => {
|
||||
const { evidence, bytes, review } = fixture()
|
||||
assert.doesNotThrow(() => sixth.validateSixthDiagnosticEvidence(review, bytes, evidence.source_tree_hash))
|
||||
assert.throws(() => sixth.validateSixthDiagnosticEvidence(review, bytes + '\n', evidence.source_tree_hash), /changed/)
|
||||
assert.throws(() => sixth.validateSixthDiagnosticEvidence(review, bytes, 'c'.repeat(64)), /Current candidate/)
|
||||
const changes = [value => { value.status = 'stopped' }, value => { value.protected_state_unchanged = false },
|
||||
value => { value.previous_diagnostics_unchanged = false }, value => { value.candidate_source_only = false },
|
||||
value => { value.run_id = 'different' }, value => { value.pins.maxSends = 22 },
|
||||
value => { value.outcome.status = 'failed' }, value => { value.outcome.candidate_count = 0 },
|
||||
value => { value.outcome.window.start_rune++ }, value => { value.outcome.source_hash = 'c'.repeat(64) },
|
||||
value => { value.outcome.actual_send_reservations++ }, value => { value.actual_send_reservations = 22 },
|
||||
value => { value.outcome.leaves[0].start_rune++ }, value => { value.outcome.leaves[0].end_rune-- }]
|
||||
for (const change of changes) {
|
||||
const changed = structuredClone(evidence); change(changed)
|
||||
const changedBytes = JSON.stringify(changed)
|
||||
const changedReview = { ...review, candidate_diagnostic: { ...review.candidate_diagnostic, evidence_sha256: sha(changedBytes) } }
|
||||
assert.throws(() => sixth.validateSixthDiagnosticEvidence(changedReview, changedBytes, evidence.source_tree_hash))
|
||||
}
|
||||
})
|
||||
|
||||
function runCLI(data, trigger = false, corruptEvidence = false) {
|
||||
let transaction, triggered = 0
|
||||
const errors = []
|
||||
const context = { __dirname,
|
||||
process: { argv: ['node', 'recover_wiki_deadletters.cjs', ...(trigger ? ['--trigger', '--ids=10'] :
|
||||
['--apply', '--ids=10', '--attempt=6', `--reason=${reason}`, '--review-file=/fixture/review.json'])] },
|
||||
console: { log() {}, error(value) { errors.push(value) } },
|
||||
require(name) {
|
||||
if (name === 'node:fs') return { mkdirSync() {}, writeFileSync() {}, readFileSync(file) {
|
||||
if (file === '/fixture/review.json') return JSON.stringify(data.review)
|
||||
if (path.basename(file) === 'public-release.json') return JSON.stringify({ stage: 'published',
|
||||
release: data.review.fix_release, api_image_id: data.review.api_image_id, published_at: data.review.published_at })
|
||||
assert.equal(path.basename(file), 'evidence.json')
|
||||
return data.bytes + (corruptEvidence ? '\n' : '')
|
||||
} }
|
||||
if (name === './wiki_sixth_recovery_review.cjs') return { ...sixth,
|
||||
candidateSourceTreeHash: () => data.evidence.source_tree_hash,
|
||||
verifyReplayInputFiles: () => { assert.ok(!data.inputDrift, 'Original replay input changed') } }
|
||||
if (name === './wiki_recovery_budget_sql.cjs') return { recoveryFingerprintsSQL: () => ({
|
||||
source: `'${data.source.source_fingerprint}'::text`, model: `'${data.source.model_fingerprint}'::text` }) }
|
||||
if (name === 'node:child_process') return { execFileSync(command, args, options = {}) {
|
||||
assert.equal(command, 'docker')
|
||||
if (args.includes('inspect')) return JSON.stringify({ running: true, release: data.review.fix_release, image_id: data.review.api_image_id })
|
||||
if (args.includes('cp')) return ''
|
||||
if (args.includes('/tmp/wiki-backfill-trigger')) { triggered++; return '{}' }
|
||||
assert.ok(args.includes('psql'))
|
||||
if (options.input.startsWith('BEGIN;')) { transaction = options.input; throw new Error('Captured transaction') }
|
||||
if (options.input.includes('to_regclass')) return 't'
|
||||
if (options.input.includes('FROM (SELECT ledger.*')) return JSON.stringify([data.pilot, data.previous, ...(trigger ? [data.receipt] : [])])
|
||||
if (options.input.includes('SELECT coalesce(json_agg(row_to_json(d))')) return '[]'
|
||||
return JSON.stringify([{ ...data.source, pending: trigger ? 1 : 0 }])
|
||||
} }
|
||||
return require(name)
|
||||
} }
|
||||
vm.runInNewContext(fs.readFileSync(path.join(__dirname, 'recover_wiki_deadletters.cjs'), 'utf8'), context)
|
||||
return { transaction, triggered, errors }
|
||||
}
|
||||
|
||||
test('actual CLI checks immutable V3 evidence before enqueue and every explicit trigger', () => {
|
||||
const data = fixture()
|
||||
const good = runCLI(data)
|
||||
assert.ok(good.transaction)
|
||||
for (const required of ['p.attempt_number=5 AND p.pending_op_id=4144', "b.status='stopped_budget'", 'later.attempt_number>5',
|
||||
"interval '120 minutes'", '1000 BETWEEN 1 AND 1000', 'b.source_fingerprint=', 'b.model_fingerprint=']) assert.ok(good.transaction.includes(required))
|
||||
assert.equal(runCLI(data, true).triggered, 1)
|
||||
assert.equal(runCLI(data, false, true).transaction, undefined)
|
||||
assert.equal(runCLI(data, true, true).triggered, 0)
|
||||
})
|
||||
|
||||
test('actual CLI rechecks replay and immutable original inputs before enqueue or trigger', () => {
|
||||
const data = replayFixture()
|
||||
assert.ok(runCLI(data).transaction)
|
||||
assert.equal(runCLI(data, true).triggered, 1)
|
||||
assert.equal(runCLI(data, false, true).transaction, undefined)
|
||||
assert.equal(runCLI(data, true, true).triggered, 0)
|
||||
data.inputDrift = true
|
||||
assert.equal(runCLI(data).transaction, undefined)
|
||||
assert.equal(runCLI(data, true).triggered, 0)
|
||||
})
|
||||
|
||||
test('actual CLI validates the reviewed paused operation before enqueue and trigger', () => {
|
||||
const data = guardPausedFixture()
|
||||
const good = runCLI(data)
|
||||
assert.ok(good.transaction)
|
||||
assert.equal(runCLI(data, true).triggered, 1)
|
||||
for (const required of ["b.stop_reason='guard_rejected'", "b.last_phase='wiki_candidate_slug'", 'b.reserved_provider_requests=4',
|
||||
'jsonb_array_length(b.paused_payload::jsonb)=1', 'b.paused_payload::jsonb @>', "span.status='failed' AND span.error_code='EXTRACT_FAILED'",
|
||||
'ORDER BY span.started_at DESC,span.id DESC LIMIT 1']) assert.ok(good.transaction.includes(required), required)
|
||||
data.previous.recovery_budget.paused_payload = '[]'
|
||||
assert.equal(runCLI(data).transaction, undefined)
|
||||
assert.equal(runCLI(data, true).triggered, 0)
|
||||
})
|
||||
|
||||
test('actual sixth enqueue SQL preserves old receipts, refuses drift and rolls back atomically', { skip: process.env.VERIFY_SIXTH_SQL !== '1' }, async () => {
|
||||
const data = guardPausedFixture(), scope = sixth.sixthRecoveryScope, quote = value => `'${String(value).replaceAll("'", "''")}'`
|
||||
const transaction = runCLI(data).transaction
|
||||
assert.ok(transaction)
|
||||
const name = 'yawan-wiki-sixth-sql-test-' + crypto.randomUUID()
|
||||
const docker = (args, input) => cp.execFileSync('docker', ['--context', 'colima-yawan', ...args], { input, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 30000 })
|
||||
const sql = input => docker(['exec', '-i', name, 'psql', '-X', '-qAt', '-U', 'postgres', '-v', 'ON_ERROR_STOP=1'], input).trim()
|
||||
const setup = `SET search_path=pg_temp;
|
||||
CREATE TEMP TABLE task_pending_ops(id bigserial PRIMARY KEY,tenant_id bigint,task_type text,scope text,scope_id text,op text,dedup_key text,payload jsonb,fail_count int,enqueued_at timestamptz);
|
||||
CREATE TEMP TABLE task_dead_letters(id bigint PRIMARY KEY,tenant_id bigint,task_type text,scope text,scope_id text,related_id text,payload jsonb);
|
||||
CREATE TEMP TABLE knowledges(id text,tenant_id bigint,knowledge_base_id text,parse_status text,pending_subtasks_count int,deleted_at timestamptz);
|
||||
CREATE TEMP TABLE knowledge_bases(id text,tenant_id bigint,wiki_config jsonb,indexing_strategy jsonb,embedding_model_id text,deleted_at timestamptz);
|
||||
CREATE TEMP TABLE chunks(tenant_id bigint,knowledge_id text,content text,deleted_at timestamptz);
|
||||
CREATE TEMP TABLE models(id text);
|
||||
CREATE TEMP TABLE wiki_pages(tenant_id bigint,knowledge_base_id text,source_refs jsonb,deleted_at timestamptz);
|
||||
CREATE TEMP TABLE knowledge_processing_spans(id bigint,knowledge_id text,name text,status text,error_code text,started_at timestamptz);
|
||||
CREATE TEMP TABLE wiki_dead_letter_recoveries(tenant_id bigint NOT NULL,dead_letter_id bigint NOT NULL,knowledge_id varchar NOT NULL,knowledge_base_id varchar NOT NULL,model_id varchar NOT NULL,pending_op_id bigint NOT NULL,queued_at timestamptz NOT NULL,baseline_pages bigint NOT NULL,baseline_chunks bigint NOT NULL,chunk_chars bigint NOT NULL,dead_letter_highwater bigint NOT NULL,attempt_number integer NOT NULL,retry_reason text NOT NULL DEFAULT '',deployed_fix_review jsonb,PRIMARY KEY(tenant_id,dead_letter_id,attempt_number));
|
||||
${fs.readFileSync(path.join(__dirname, '../migrations/versioned/000214_wiki_recovery_budgets.up.sql'), 'utf8').replace('CREATE TABLE IF NOT EXISTS', 'CREATE TEMP TABLE')}
|
||||
INSERT INTO task_dead_letters VALUES(10,10059,'wiki:ingest','knowledge_base',${quote(scope.knowledge_base_id)},${quote(scope.knowledge_id)},jsonb_build_object('op','ingest','knowledge_id',${quote(scope.knowledge_id)}));
|
||||
INSERT INTO knowledges VALUES(${quote(scope.knowledge_id)},10059,${quote(scope.knowledge_base_id)},'completed',0,NULL);
|
||||
INSERT INTO knowledge_bases VALUES(${quote(scope.knowledge_base_id)},10059,jsonb_build_object('synthesis_model_id',${quote(scope.model_id)}),'{"wiki_enabled":true}',${quote(scope.embedding_model_id)},NULL);
|
||||
INSERT INTO chunks VALUES(10059,${quote(scope.knowledge_id)},'fixture',NULL);
|
||||
INSERT INTO wiki_dead_letter_recoveries VALUES(10059,10,${quote(scope.knowledge_id)},${quote(scope.knowledge_base_id)},${quote(scope.model_id)},4144,'2026-09-20T08:58:56Z',23,624,227094,45,5,'retained original',NULL);
|
||||
INSERT INTO wiki_recovery_budgets(tenant_id,dead_letter_id,attempt_number,pending_op_id,knowledge_base_id,knowledge_id,model_id,embedding_model_id,source_fingerprint,model_fingerprint,reviewed_release,reviewed_api_image,max_provider_requests,reserved_provider_requests,deadline_at,status,paused_payload,verified_progress)
|
||||
VALUES(10059,10,5,4144,${quote(scope.knowledge_base_id)},${quote(scope.knowledge_id)},${quote(scope.model_id)},${quote(scope.embedding_model_id)},${quote(scope.source_fingerprint)},${quote(scope.model_fingerprint)},'retained-release','retained-image',1000,4,'2026-09-20T15:00:00Z','stopped_budget','[]','{}');
|
||||
UPDATE wiki_recovery_budgets SET stop_reason='guard_rejected',last_phase='wiki_candidate_slug',paused_payload=${quote(data.previous.recovery_budget.paused_payload)};
|
||||
INSERT INTO knowledge_processing_spans VALUES(1,${quote(scope.knowledge_id)},'postprocess.wiki','failed','EXTRACT_FAILED','2026-09-20T08:59:00Z');
|
||||
CREATE TEMP TABLE prior_receipt AS SELECT to_jsonb(r) AS body FROM wiki_dead_letter_recoveries r WHERE attempt_number=5;
|
||||
CREATE TEMP TABLE prior_budget AS SELECT to_jsonb(b) AS body FROM wiki_recovery_budgets b WHERE attempt_number=5;
|
||||
`
|
||||
const counts = `SELECT json_build_object('ops',(SELECT count(*) FROM task_pending_ops),'new_budgets',(SELECT count(*) FROM wiki_recovery_budgets WHERE attempt_number=6),'new_receipts',(SELECT count(*) FROM wiki_dead_letter_recoveries WHERE attempt_number=6),'old_receipt_unchanged',(SELECT to_jsonb(r)=(SELECT body FROM prior_receipt) FROM wiki_dead_letter_recoveries r WHERE attempt_number=5),'old_budget_unchanged',(SELECT to_jsonb(b)=(SELECT body FROM prior_budget) FROM wiki_recovery_budgets b WHERE attempt_number=5));`
|
||||
const inspect = input => JSON.parse(sql(input + counts).split('\n').at(-1))
|
||||
try {
|
||||
docker(['run', '-d', '--name', name, '--network', 'none', '--cpus', '1', '--memory', '768m', '--tmpfs', '/var/lib/postgresql/data:rw', '-e', 'POSTGRES_HOST_AUTH_METHOD=trust', 'paradedb/paradedb:v0.22.2-pg17'])
|
||||
let ready = false
|
||||
for (let i = 0; i < 60; i++) {
|
||||
try { docker(['exec', name, 'pg_isready', '-U', 'postgres']); ready = true; break } catch {}
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
}
|
||||
assert.ok(ready, 'Disposable PostgreSQL did not become ready')
|
||||
assert.deepEqual(inspect(setup + transaction + transaction), { ops: 1, new_budgets: 1, new_receipts: 1, old_receipt_unchanged: true, old_budget_unchanged: true })
|
||||
for (const drift of ["UPDATE wiki_recovery_budgets SET status='active';", 'UPDATE wiki_dead_letter_recoveries SET pending_op_id=4143;',
|
||||
"UPDATE wiki_recovery_budgets SET source_fingerprint='changed';", "UPDATE wiki_recovery_budgets SET stop_reason='deadline';",
|
||||
"UPDATE wiki_recovery_budgets SET last_phase='wiki_summary';", 'UPDATE wiki_recovery_budgets SET reserved_provider_requests=3;',
|
||||
"UPDATE wiki_recovery_budgets SET paused_payload='[]';",
|
||||
"UPDATE wiki_recovery_budgets SET paused_payload=jsonb_set(paused_payload::jsonb,'{0,id}','4143')::text;",
|
||||
"UPDATE wiki_recovery_budgets SET paused_payload=jsonb_set(paused_payload::jsonb,'{0,scope_id}','\"changed\"')::text;",
|
||||
"UPDATE wiki_recovery_budgets SET paused_payload=jsonb_set(paused_payload::jsonb,'{0,payload,recovery_attempt}','4')::text;",
|
||||
'DELETE FROM knowledge_processing_spans;', "UPDATE knowledge_processing_spans SET status='done';",
|
||||
"UPDATE knowledge_processing_spans SET error_code='other';",
|
||||
`INSERT INTO knowledge_processing_spans VALUES(2,${quote(scope.knowledge_id)},'postprocess.wiki','done','','2026-09-20T09:00:00Z');`]) {
|
||||
const result = inspect(setup + drift + transaction)
|
||||
assert.equal(result.ops, 0); assert.equal(result.new_budgets, 0); assert.equal(result.new_receipts, 0)
|
||||
}
|
||||
const legacyFailure = `UPDATE wiki_recovery_budgets SET paused_payload='[]',stop_reason='',last_phase='';
|
||||
INSERT INTO task_dead_letters VALUES(46,10059,'wiki:ingest','knowledge_base',${quote(scope.knowledge_base_id)},${quote(scope.knowledge_id)},'{}');`
|
||||
const legacyResult = inspect(setup + legacyFailure + transaction)
|
||||
assert.equal(legacyResult.ops, 1); assert.equal(legacyResult.new_budgets, 1); assert.equal(legacyResult.new_receipts, 1)
|
||||
assert.deepEqual(inspect(setup + `ALTER TABLE wiki_dead_letter_recoveries ADD CONSTRAINT reject_new CHECK(attempt_number<>6);\n\\set ON_ERROR_STOP off\n` + transaction + '\n\\set ON_ERROR_STOP on\n'),
|
||||
{ ops: 0, new_budgets: 0, new_receipts: 0, old_receipt_unchanged: true, old_budget_unchanged: true })
|
||||
} finally {
|
||||
docker(['rm', '-f', name])
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user