Files

63 lines
4.5 KiB
JavaScript
Raw Permalink Normal View History

const { attemptOf, describeReadCoverage, durableRecoveryStatus } = require('./wiki_recovery_policy.cjs')
// These exact pre-full-read receipts recovered pages only. Never requeue or
// promote them to full-read success merely because later releases add coverage.
const historicalPageOnlyReceipts = [
[2, 2, 3099, '1d8a8896-f23b-4e98-b58a-f3d359ef5caf', '20b9280e-ed73-4700-8fa5-944d6ae6dd5f', '2026-09-18T19:39:08.680099+00:00'],
[5, 1, 3401, '2ea280cf-501b-4a8e-b35b-2feab2d859d7', '5d28a7e5-e00e-44a7-a0e1-c72342cdcf68', '2026-09-18T19:52:41.921422+00:00'],
[6, 1, 1989, '4f92148f-9333-461d-8f94-631fb5b941ce', '5d28a7e5-e00e-44a7-a0e1-c72342cdcf68', '2026-09-18T18:20:43.837222+00:00'],
[15, 1, 2959, '69152d3a-d413-42c6-b26c-883510c73760', '5d28a7e5-e00e-44a7-a0e1-c72342cdcf68', '2026-09-18T19:14:32.337324+00:00'],
[16, 2, 2831, '0506b4b7-161c-4290-9066-eaaa5a549393', '5d28a7e5-e00e-44a7-a0e1-c72342cdcf68', '2026-09-18T18:51:04.464164+00:00'],
[17, 1, 1991, 'c74a943f-0b7f-4bd9-a5ad-45a583c213b7', '5d28a7e5-e00e-44a7-a0e1-c72342cdcf68', '2026-09-18T18:20:43.837222+00:00'],
[28, 1, 1992, 'dabe1cf2-2707-4499-8afa-1c028ba7050b', 'badd084b-f229-4005-8fb3-196fbe139fe0', '2026-09-18T18:20:43.837222+00:00'],
[30, 1, 4026, 'bf6b5e01-77b6-4a27-a4a2-c9575f443d2a', 'badd084b-f229-4005-8fb3-196fbe139fe0', '2026-09-18T21:17:11.116135+00:00'],
[31, 1, 1965, '0901d6b1-903a-4d2c-b609-774752d2164e', 'badd084b-f229-4005-8fb3-196fbe139fe0', '2026-09-18T18:09:21.442301+00:00'],
[32, 1, 1993, '7d301f18-f296-4680-a01a-d606ac22a5e0', 'badd084b-f229-4005-8fb3-196fbe139fe0', '2026-09-18T18:20:43.837222+00:00'],
[33, 1, 3816, 'd24e2e57-c54e-407f-8217-099809a07811', 'badd084b-f229-4005-8fb3-196fbe139fe0', '2026-09-18T20:11:50.828516+00:00'],
].map(([dead_letter_id, attempt_number, pending_op_id, knowledge_id, knowledge_base_id, queued_at]) => Object.freeze({
tenant_id: 10059, model_id: '404e005b-83ae-5e2e-a3b3-999f9d5cd47e',
dead_letter_id, attempt_number, pending_op_id, knowledge_id, knowledge_base_id, queued_at,
}))
Object.freeze(historicalPageOnlyReceipts)
function isHistoricalPageOnlyReceipt(row) {
return historicalPageOnlyReceipts.some(receipt => Object.entries(receipt).every(([key, value]) =>
(key === 'attempt_number' ? attemptOf(row) : row[key]) === value))
}
function hasSourceReadingProof(row) {
// Require the reported status AND its raw, versioned map proof. Window or
// summary progress alone does not establish completed source processing.
return row.full_read_status === 'source_processed' && describeReadCoverage(row) === 'source_processed'
}
function recoverySchedulingGate(report) {
const rows = report.recoveries
const pending = row => row.pending || row.pending_finalize > 0
const pageFailures = rows.filter(row => durableRecoveryStatus(row) || ['failed', 'partial', 'unverified', 'skipped', 'stopped_budget', 'stopped_invalid'].includes(row.recovery_status)
|| row.new_failures > 0 || row.span_status === 'failed' || row.failed_page_spans > 0
|| (!pending(row) && row.recovery_status !== 'wiki_recovered'))
const missingRead = rows.filter(row => !pending(row) && row.recovery_status === 'wiki_recovered'
&& !isHistoricalPageOnlyReceipt(row) && !hasSourceReadingProof(row))
const historicalUnverified = rows.filter(row => isHistoricalPageOnlyReceipt(row) && !hasSourceReadingProof(row))
const details = {
missing_full_read_ids: missingRead.map(row => row.dead_letter_id),
historical_full_read_unverified_ids: historicalUnverified.map(row => row.dead_letter_id),
source_reading_complete: report.remaining_not_attempted === 0 && rows.length > 0 && rows.every(hasSourceReadingProof),
}
if (pageFailures.length || missingRead.length) return { state: 'stopped_for_review', ...details,
unresolved_ids: [...new Set([...pageFailures, ...missingRead].map(row => row.dead_letter_id))] }
if (rows.some(pending)) return { state: 'waiting_for_current_batch', ...details }
if (report.remaining_not_attempted === 0) return {
state: details.source_reading_complete ? 'all_original_pages_and_source_reading_complete' : 'all_original_pages_recovered', ...details,
}
return { state: 'ready_for_next_batch', ...details }
}
function remainingRecoveryCandidates(report) {
return report.candidates.filter(row => row.eligible && row.chunks > 0 && row.pending === 0
&& !report.recoveries.some(done => done.dead_letter_id === row.id))
}
module.exports = { historicalPageOnlyReceipts, isHistoricalPageOnlyReceipt, hasSourceReadingProof, recoverySchedulingGate, remainingRecoveryCandidates }