63 lines
4.5 KiB
JavaScript
63 lines
4.5 KiB
JavaScript
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 }
|