@allocator-one/rcl 4.5.8 → 4.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/consensus/semantic-deduper.d.ts +2 -1
- package/dist/consensus/semantic-deduper.js +21 -10
- package/dist/consensus/semantic-deduper.js.map +1 -1
- package/dist/converge/attempt-budget.d.ts +11 -0
- package/dist/converge/attempt-budget.js +119 -61
- package/dist/converge/attempt-budget.js.map +1 -1
- package/dist/converge/delivery-reconciliation.d.ts +2 -1
- package/dist/converge/delivery-reconciliation.js +17 -9
- package/dist/converge/delivery-reconciliation.js.map +1 -1
- package/dist/converge/launch-guard.d.ts +3 -0
- package/dist/converge/launch-guard.js +53 -17
- package/dist/converge/launch-guard.js.map +1 -1
- package/dist/converge/recovered-production.d.ts +14 -0
- package/dist/converge/recovered-production.js +61 -0
- package/dist/converge/recovered-production.js.map +1 -0
- package/dist/converge/recovery-launch.js +82 -35
- package/dist/converge/recovery-launch.js.map +1 -1
- package/dist/converge/run-state.d.ts +2 -0
- package/dist/converge/run-state.js +26 -12
- package/dist/converge/run-state.js.map +1 -1
- package/dist/converge/semantic-state.js +18 -4
- package/dist/converge/semantic-state.js.map +1 -1
- package/dist/coordination/registry-lock.d.ts +2 -0
- package/dist/coordination/registry-lock.js +42 -13
- package/dist/coordination/registry-lock.js.map +1 -1
- package/dist/evidence/claim-recovery/delivery.d.ts +25 -0
- package/dist/evidence/claim-recovery/delivery.js +100 -0
- package/dist/evidence/claim-recovery/delivery.js.map +1 -0
- package/dist/evidence/claim-recovery/public-adoption.d.ts +31 -0
- package/dist/evidence/claim-recovery/public-adoption.js +177 -0
- package/dist/evidence/claim-recovery/public-adoption.js.map +1 -0
- package/dist/evidence/claim-recovery/public-manifest.d.ts +114 -0
- package/dist/evidence/claim-recovery/public-manifest.js +11 -0
- package/dist/evidence/claim-recovery/public-manifest.js.map +1 -0
- package/dist/evidence/claim-recovery/public-model.d.ts +85 -0
- package/dist/evidence/claim-recovery/public-model.js +177 -0
- package/dist/evidence/claim-recovery/public-model.js.map +1 -0
- package/dist/evidence/original-run/journal.d.ts +10 -0
- package/dist/evidence/original-run/journal.js +55 -3
- package/dist/evidence/original-run/journal.js.map +1 -1
- package/dist/evidence/original-run/lock.js +22 -9
- package/dist/evidence/original-run/lock.js.map +1 -1
- package/dist/evidence/recover-claim.d.ts +21 -0
- package/dist/evidence/recover-claim.js +504 -0
- package/dist/evidence/recover-claim.js.map +1 -0
- package/dist/index.js +228 -136
- package/dist/index.js.map +1 -1
- package/dist/report/assembly.d.ts +2 -0
- package/dist/report/assembly.js +1 -0
- package/dist/report/assembly.js.map +1 -1
- package/dist/report/consensus-assembly.d.ts +2 -0
- package/dist/report/consensus-assembly.js +6 -3
- package/dist/report/consensus-assembly.js.map +1 -1
- package/dist/report/reviewer-artifact.js +3 -1
- package/dist/report/reviewer-artifact.js.map +1 -1
- package/dist/report/run-header.js +2 -0
- package/dist/report/run-header.js.map +1 -1
- package/dist/telemetry/deliver.d.ts +8 -0
- package/dist/telemetry/deliver.js +55 -8
- package/dist/telemetry/deliver.js.map +1 -1
- package/dist/telemetry/events.js +2 -0
- package/dist/telemetry/events.js.map +1 -1
- package/dist/telemetry/outbox.d.ts +20 -0
- package/dist/telemetry/outbox.js +324 -103
- package/dist/telemetry/outbox.js.map +1 -1
- package/dist/telemetry/reviewer-delivery.d.ts +95 -3
- package/dist/telemetry/reviewer-delivery.js +600 -12
- package/dist/telemetry/reviewer-delivery.js.map +1 -1
- package/dist/telemetry/sink.d.ts +14 -0
- package/dist/telemetry/sink.js +44 -2
- package/dist/telemetry/sink.js.map +1 -1
- package/dist/telemetry/terminal-reviewer-delivery.d.ts +154 -5
- package/dist/telemetry/terminal-reviewer-delivery.js +196 -21
- package/dist/telemetry/terminal-reviewer-delivery.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -47,7 +47,9 @@ import { resolveFinding, buildDiscussPrompts, runDiscussion, loadContextDocs, }
|
|
|
47
47
|
import { DEFAULT_CONVERGE_ATTEMPT_CAP, claimConvergeAttempt, ConvergeAttemptBudgetExceededError, convergeAttemptErrorExitCode, ConvergeAttemptStateError, resolveGitCommonDir, } from './converge/attempt-budget.js';
|
|
48
48
|
import { DEFAULT_CONVERGE_ROUND_CAP, HARD_CONVERGE_ROUND_CAP, processRoundReport, recordVerdicts, findingGatingReason, ConvergeRoundCapError, ConvergeRunStateError, } from './converge/run-state.js';
|
|
49
49
|
import { applyRoundGap, previewRoundGap } from './converge/round-gap.js';
|
|
50
|
+
import { withNativeTarget } from './converge/target-ownership.js';
|
|
50
51
|
import { guardReviewLaunch, ReviewLaunchRefused } from './converge/launch-guard.js';
|
|
52
|
+
import { selectCurrentRecoveredProduction } from './converge/recovered-production.js';
|
|
51
53
|
import { createBoundFixRecovery } from './converge/bound-fix-recovery.js';
|
|
52
54
|
import { openReadSink } from './telemetry/read-sink.js';
|
|
53
55
|
import { reconcileFlushedRun } from './converge/delivery-reconciliation.js';
|
|
@@ -110,7 +112,7 @@ program.hook('preAction', async (_thisCommand, actionCommand) => {
|
|
|
110
112
|
// Reads and explicit repairs must not flush unrelated evidence, even in preview.
|
|
111
113
|
if (actionCommand.parent?.name() === 'evidence' && (name === 'show' || name === 'status'))
|
|
112
114
|
return;
|
|
113
|
-
if (name === 'converge-rejected' || name === 'converge-stale' || name === 'converge-gap' || name === 'converge-reconcile-history' || name === 'recover-run' || name === 'recover-finding' || name === 'retriage-finding' || name === 'telemetry' || actionCommand.parent?.name() === 'telemetry' || name.includes('worker'))
|
|
115
|
+
if (name === 'converge-rejected' || name === 'converge-stale' || name === 'converge-gap' || name === 'converge-reconcile-history' || name === 'recover-run' || name === 'recover-claim' || name === 'recover-finding' || name === 'retriage-finding' || name === 'telemetry' || actionCommand.parent?.name() === 'telemetry' || name.includes('worker'))
|
|
114
116
|
return;
|
|
115
117
|
const flags = actionCommand.opts();
|
|
116
118
|
if (flags.telemetry === false || (process.env['RCL_TELEMETRY'] ?? '').trim().toLowerCase() === 'off')
|
|
@@ -234,7 +236,7 @@ program
|
|
|
234
236
|
.option('--round <n>', 'Converge round number (or RCL_CONVERGE_ROUND)')
|
|
235
237
|
.option('--attempt <n>', 'Converge attempt number (or RCL_CONVERGE_ATTEMPT)')
|
|
236
238
|
.option('--start-over', 'Start an explicitly requested fresh review with a new normal budget; retain all prior evidence and spending')
|
|
237
|
-
.option('--guarded-converge', 'Validate and claim inside this review process; derive the round from native state')
|
|
239
|
+
.option('--guarded-converge', 'Validate and claim inside this review process, or consume an exact recovered-v3 --attempt; derive the round from native state')
|
|
238
240
|
.option('--bound-fix-recovery <run-id>', 'Review unchanged inputs once live Harness evidence proves a retained bound fix obligation after this native dismissal-only run')
|
|
239
241
|
.option('--launch-intent <intent>', 'Guarded intent: review, stop-upstream, stop-review, or retry-delivery')
|
|
240
242
|
.option('--retry-report <path>', 'Original legacy report proving an inconclusive launch; the new inputs may differ; requires --retry-reason')
|
|
@@ -329,24 +331,31 @@ program
|
|
|
329
331
|
throw new ConvergeAttemptStateError('maxAttempts (--max-attempts) must be a positive safe integer.');
|
|
330
332
|
}
|
|
331
333
|
}
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
const gitCommonDir = await resolveGitCommonDir();
|
|
335
|
+
const claim = await withNativeTarget(gitCommonDir, opts.target, async (ownership) => {
|
|
336
|
+
const committed = await claimConvergeAttempt({
|
|
337
|
+
gitCommonDir,
|
|
338
|
+
target: opts.target,
|
|
339
|
+
maxAttempts,
|
|
340
|
+
ownership,
|
|
341
|
+
});
|
|
342
|
+
await reportConvergeEvents([
|
|
343
|
+
buildEvent({
|
|
344
|
+
kind: 'attempt_claimed',
|
|
345
|
+
convergeTarget: committed.target,
|
|
346
|
+
attempt: committed.attempt,
|
|
347
|
+
// The claim's local state path and process id stay on this machine.
|
|
348
|
+
payload: { attempt: committed.attempt, cap: committed.cap,
|
|
349
|
+
...(committed.cycle ? { cycle_id: committed.cycle.id } : {}) },
|
|
350
|
+
}),
|
|
351
|
+
// An explicit --max-attempts is consent evidence, whatever it was before.
|
|
352
|
+
...(maxAttempts !== undefined
|
|
353
|
+
? [buildEvent({ kind: 'cap_changed', convergeTarget: committed.target, attempt: committed.attempt,
|
|
354
|
+
payload: { kind: 'attempts', to: committed.cap } })]
|
|
355
|
+
: []),
|
|
356
|
+
]);
|
|
357
|
+
return committed;
|
|
336
358
|
});
|
|
337
|
-
await reportConvergeEvents([
|
|
338
|
-
buildEvent({
|
|
339
|
-
kind: 'attempt_claimed',
|
|
340
|
-
convergeTarget: claim.target,
|
|
341
|
-
attempt: claim.attempt,
|
|
342
|
-
// The claim's local state path and process id stay on this machine.
|
|
343
|
-
payload: { attempt: claim.attempt, cap: claim.cap, ...(claim.cycle ? { cycle_id: claim.cycle.id } : {}) },
|
|
344
|
-
}),
|
|
345
|
-
// An explicit --max-attempts is consent evidence, whatever it was before.
|
|
346
|
-
...(maxAttempts !== undefined
|
|
347
|
-
? [buildEvent({ kind: 'cap_changed', convergeTarget: claim.target, attempt: claim.attempt, payload: { kind: 'attempts', to: claim.cap } })]
|
|
348
|
-
: []),
|
|
349
|
-
]);
|
|
350
359
|
if (opts.json) {
|
|
351
360
|
console.log(JSON.stringify(claim));
|
|
352
361
|
}
|
|
@@ -596,10 +605,12 @@ program
|
|
|
596
605
|
}
|
|
597
606
|
}
|
|
598
607
|
let report;
|
|
608
|
+
let reportJson;
|
|
599
609
|
let reportSha256;
|
|
600
610
|
try {
|
|
601
611
|
const source = await readFile(opts.report);
|
|
602
|
-
|
|
612
|
+
reportJson = source.toString('utf8');
|
|
613
|
+
report = JSON.parse(reportJson);
|
|
603
614
|
reportSha256 = sha256(source);
|
|
604
615
|
}
|
|
605
616
|
catch (err) {
|
|
@@ -626,48 +637,69 @@ program
|
|
|
626
637
|
if (reportRunId !== undefined && runId === undefined) {
|
|
627
638
|
console.error(chalk.yellow(`Report run ${reportRunId} ${reportTarget === undefined ? 'carries no converge target' : `belongs to converge target ${reportTarget}`}, not ${opts.target}; the round keeps no run id.`));
|
|
628
639
|
}
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
...(runId !== undefined ? { runId } : {}),
|
|
638
|
-
});
|
|
639
|
-
const classified = result.findings.map((f) => ({
|
|
640
|
-
identity: f.identity,
|
|
641
|
-
status: f.status,
|
|
642
|
-
gating: findingGatingReason(f.finding),
|
|
643
|
-
severity: f.finding.severity,
|
|
644
|
-
file: f.finding.file,
|
|
645
|
-
startLine: f.finding.startLine,
|
|
646
|
-
endLine: f.finding.endLine,
|
|
647
|
-
title: f.finding.title,
|
|
648
|
-
...(f.suppressReason ? { suppressReason: f.suppressReason } : {}),
|
|
649
|
-
}));
|
|
650
|
-
const actionable = classified.filter((f) => (f.status === 'new' || f.status === 'regating') && f.gating !== 'none');
|
|
651
|
-
await reportConvergeEvents([
|
|
652
|
-
buildEvent({
|
|
653
|
-
kind: 'round_processed',
|
|
654
|
-
convergeTarget: opts.target,
|
|
640
|
+
const gitCommonDir = await resolveGitCommonDir();
|
|
641
|
+
const target = opts.target;
|
|
642
|
+
const { result, classified, actionable } = await withNativeTarget(gitCommonDir, target, async (ownership) => {
|
|
643
|
+
const existing = await loadConvergeRunState(gitCommonDir, target);
|
|
644
|
+
const recoveredReport = existing?.version === 3 || report.run?.gating?.bound_classification_protocol !== undefined;
|
|
645
|
+
const result = await processRoundReport({
|
|
646
|
+
gitCommonDir,
|
|
647
|
+
target,
|
|
655
648
|
round,
|
|
649
|
+
findings: recoveredReport
|
|
650
|
+
? [...report.findings, ...(report.belowThresholdFindings ?? [])]
|
|
651
|
+
: report.findings,
|
|
652
|
+
...(recoveredReport ? { evidence: { reportJson } } : {}),
|
|
653
|
+
reportSha256,
|
|
654
|
+
cycleId: report.run?.cycle_id,
|
|
655
|
+
ownership,
|
|
656
|
+
...(maxRounds !== undefined ? { maxRounds } : {}),
|
|
656
657
|
...(runId !== undefined ? { runId } : {}),
|
|
657
|
-
|
|
658
|
+
});
|
|
659
|
+
const classified = result.findings.map((f) => ({
|
|
660
|
+
identity: f.identity,
|
|
661
|
+
status: f.status,
|
|
662
|
+
gating: f.sighting?.gating ?? findingGatingReason(f.finding),
|
|
663
|
+
severity: f.finding.severity,
|
|
664
|
+
file: f.finding.file,
|
|
665
|
+
startLine: f.finding.startLine,
|
|
666
|
+
endLine: f.finding.endLine,
|
|
667
|
+
title: f.finding.title,
|
|
668
|
+
...(f.suppressReason ? { suppressReason: f.suppressReason } : {}),
|
|
669
|
+
}));
|
|
670
|
+
const actionable = classified.filter((f) => (f.status === 'new' || f.status === 'regating') && f.gating !== 'none');
|
|
671
|
+
await reportConvergeEvents([
|
|
672
|
+
buildEvent({
|
|
673
|
+
kind: 'round_processed',
|
|
674
|
+
convergeTarget: target,
|
|
658
675
|
round,
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
676
|
+
...(runId !== undefined ? { runId } : {}),
|
|
677
|
+
payload: {
|
|
678
|
+
round,
|
|
679
|
+
round_cap: result.roundCap,
|
|
680
|
+
counts: result.counts,
|
|
681
|
+
actionable_gating: result.actionableIdentities?.length ?? actionable.length,
|
|
682
|
+
...(result.classificationVersion
|
|
683
|
+
? {
|
|
684
|
+
classification_version: result.classificationVersion,
|
|
685
|
+
report_json_sha256: result.reportBinding.reportSha256,
|
|
686
|
+
...(result.legacyPendingIdentities
|
|
687
|
+
? { legacy_pending_identities: result.legacyPendingIdentities }
|
|
688
|
+
: {}),
|
|
689
|
+
}
|
|
690
|
+
: {}),
|
|
691
|
+
// Which identity each sighting was matched to, so the server
|
|
692
|
+
// can apply standing verdicts to keys that moved (IO-12601).
|
|
693
|
+
identities: roundIdentities(result.findings),
|
|
694
|
+
},
|
|
695
|
+
}),
|
|
696
|
+
...(maxRounds !== undefined
|
|
697
|
+
? [buildEvent({ kind: 'cap_changed', convergeTarget: target, round,
|
|
698
|
+
payload: { kind: 'rounds', to: result.roundCap } })]
|
|
699
|
+
: []),
|
|
700
|
+
]);
|
|
701
|
+
return { result, classified, actionable };
|
|
702
|
+
});
|
|
671
703
|
if (opts.json) {
|
|
672
704
|
console.log(JSON.stringify({
|
|
673
705
|
target: opts.target,
|
|
@@ -675,8 +707,11 @@ program
|
|
|
675
707
|
roundCap: result.roundCap,
|
|
676
708
|
reviewerHealth: blockingHealthJson(health),
|
|
677
709
|
counts: result.counts,
|
|
678
|
-
actionableGating: actionable.length,
|
|
710
|
+
actionableGating: result.actionableIdentities?.length ?? actionable.length,
|
|
679
711
|
findings: classified,
|
|
712
|
+
...(result.recoveryProjection
|
|
713
|
+
? { recoveryProjection: result.recoveryProjection }
|
|
714
|
+
: {}),
|
|
680
715
|
}, null, 2));
|
|
681
716
|
return;
|
|
682
717
|
}
|
|
@@ -684,7 +719,7 @@ program
|
|
|
684
719
|
console.log(`Round ${round}/${result.roundCap} for ${opts.target}: ` +
|
|
685
720
|
`${result.counts.new} new, ${result.counts.repeat} repeat, ` +
|
|
686
721
|
`${result.counts.suppressed} suppressed, ${result.counts.regating} regating · ` +
|
|
687
|
-
`${actionable.length} actionable gating finding(s)`);
|
|
722
|
+
`${result.actionableIdentities?.length ?? actionable.length} actionable gating finding(s)`);
|
|
688
723
|
for (const f of actionable) {
|
|
689
724
|
console.log(` [${f.status}] ${f.identity} ${f.file}:${f.startLine} — ${f.title}`);
|
|
690
725
|
}
|
|
@@ -778,68 +813,74 @@ program
|
|
|
778
813
|
if (new Set(verdicts.map(({ key }) => key)).size !== verdicts.length) {
|
|
779
814
|
throw new ConvergeRunStateError('Pass each finding identity only once, as either fixed or dismissed.');
|
|
780
815
|
}
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
});
|
|
788
|
-
// Feed the cross-run precision history (RCL-27) — fail-soft, the
|
|
789
|
-
// verdicts above are already durably recorded.
|
|
790
|
-
try {
|
|
791
|
-
const ts = new Date().toISOString();
|
|
792
|
-
await appendOutcomes(updated
|
|
793
|
-
.filter((e) => e.verdict !== undefined && e.models.length > 0)
|
|
794
|
-
.map((e) => ({
|
|
795
|
-
ts,
|
|
796
|
-
verdict: e.verdict,
|
|
797
|
-
models: e.models,
|
|
798
|
-
severity: e.verdictSeverity ?? e.severity,
|
|
799
|
-
target: opts.target,
|
|
800
|
-
findingKey: e.key,
|
|
801
|
-
source: 'live',
|
|
802
|
-
})));
|
|
803
|
-
}
|
|
804
|
-
catch (err) {
|
|
805
|
-
// Advisory history; verdict recording must not fail over it —
|
|
806
|
-
// but say so, or a broken store silently stops learning.
|
|
807
|
-
console.warn(`Model-stats store unavailable (outcomes not recorded): ${String(err)}`);
|
|
808
|
-
}
|
|
809
|
-
await reportConvergeEvents([
|
|
810
|
-
buildEvent({
|
|
811
|
-
kind: 'verdicts_recorded',
|
|
812
|
-
convergeTarget: opts.target,
|
|
816
|
+
const target = opts.target;
|
|
817
|
+
const gitCommonDir = await resolveGitCommonDir();
|
|
818
|
+
const { entries: updated, resolution, runId: roundRun } = await withNativeTarget(gitCommonDir, target, async (ownership) => {
|
|
819
|
+
const recorded = await recordVerdicts({
|
|
820
|
+
gitCommonDir,
|
|
821
|
+
target,
|
|
813
822
|
round,
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
823
|
+
verdicts,
|
|
824
|
+
runId: opts.runId,
|
|
825
|
+
ownership,
|
|
826
|
+
});
|
|
827
|
+
// Feed the cross-run precision history (RCL-27) — fail-soft, the
|
|
828
|
+
// verdicts above are already durably recorded.
|
|
829
|
+
try {
|
|
830
|
+
const ts = new Date().toISOString();
|
|
831
|
+
await appendOutcomes(recorded.entries
|
|
832
|
+
.filter((e) => e.verdict !== undefined && e.models.length > 0)
|
|
833
|
+
.map((e) => ({
|
|
834
|
+
ts,
|
|
835
|
+
verdict: e.verdict,
|
|
836
|
+
models: e.models,
|
|
837
|
+
severity: e.verdictSeverity ?? e.severity,
|
|
838
|
+
target,
|
|
839
|
+
findingKey: e.key,
|
|
840
|
+
source: 'live',
|
|
841
|
+
})));
|
|
842
|
+
}
|
|
843
|
+
catch (err) {
|
|
844
|
+
// Advisory history; verdict recording must not fail over it —
|
|
845
|
+
// but say so, or a broken store silently stops learning.
|
|
846
|
+
console.warn(`Model-stats store unavailable (outcomes not recorded): ${String(err)}`);
|
|
847
|
+
}
|
|
848
|
+
await reportConvergeEvents([
|
|
849
|
+
buildEvent({
|
|
850
|
+
kind: 'verdicts_recorded',
|
|
851
|
+
convergeTarget: target,
|
|
852
|
+
round,
|
|
853
|
+
...(recorded.runId !== undefined ? { runId: recorded.runId } : {}),
|
|
854
|
+
payload: {
|
|
855
|
+
verdicts: recorded.entries.map((e) => ({
|
|
856
|
+
identity_key: e.key,
|
|
857
|
+
verdict: e.verdict,
|
|
858
|
+
// Verdict reasons are user-authored prose: scrubbed like every other free text that leaves the machine.
|
|
859
|
+
...(e.verdictReason !== undefined ? { reason: scrubText(e.verdictReason, 500) } : {}),
|
|
860
|
+
severity: e.verdictSeverity ?? e.severity,
|
|
861
|
+
models: e.models,
|
|
862
|
+
})),
|
|
863
|
+
},
|
|
864
|
+
}),
|
|
865
|
+
...(recorded.resolution
|
|
866
|
+
? [
|
|
867
|
+
buildEvent({
|
|
868
|
+
kind: 'resolution',
|
|
869
|
+
convergeTarget: target,
|
|
870
|
+
round,
|
|
871
|
+
...(recorded.runId !== undefined ? { runId: recorded.runId } : {}),
|
|
872
|
+
payload: {
|
|
873
|
+
status: recorded.resolution.status,
|
|
874
|
+
actionable: recorded.resolution.actionable,
|
|
875
|
+
unresolved: recorded.resolution.unresolved.length,
|
|
876
|
+
fixed_this_round: recorded.resolution.fixedThisRound,
|
|
877
|
+
},
|
|
878
|
+
}),
|
|
879
|
+
]
|
|
880
|
+
: []),
|
|
881
|
+
]);
|
|
882
|
+
return recorded;
|
|
883
|
+
});
|
|
843
884
|
if (opts.json) {
|
|
844
885
|
console.log(JSON.stringify({
|
|
845
886
|
target: opts.target,
|
|
@@ -886,19 +927,31 @@ const telemetry = program
|
|
|
886
927
|
.description('Evidence delivery to Harness: credentials, queued deliveries and retained rejected reports');
|
|
887
928
|
telemetry
|
|
888
929
|
.command('recover-reviewer')
|
|
889
|
-
.description('
|
|
890
|
-
.
|
|
891
|
-
.
|
|
930
|
+
.description('Recover one terminal reviewer delivery without restarting reviewers')
|
|
931
|
+
.option('--preview', 'Write a pinned manifest without changing the server')
|
|
932
|
+
.option('--apply', 'Apply one exact reviewed recovery manifest')
|
|
933
|
+
.option('--resume', 'Resume the same journaled recovery operation')
|
|
934
|
+
.option('--manifest <path>', 'Immutable recovery manifest path; required for preview, apply or resume')
|
|
935
|
+
.option('--manifest-sha256 <sha256>', 'Required exact manifest digest for apply or resume')
|
|
936
|
+
.option('--target <target>', 'Exact guarded convergence target (preview or legacy no-outbox delivery)')
|
|
937
|
+
.option('--run <id>', 'Exact retained terminal run UUID (preview or legacy no-outbox delivery)')
|
|
892
938
|
.option('--json', 'Output JSON')
|
|
893
939
|
.action(async (opts) => {
|
|
894
940
|
try {
|
|
895
941
|
const runtime = await createTelemetryRuntime({ rclVersion: RCL_VERSION });
|
|
896
|
-
const result = await deliverTerminalReviewerRun(runtime, {
|
|
942
|
+
const result = await deliverTerminalReviewerRun(runtime, { preview: opts.preview, apply: opts.apply, resume: opts.resume,
|
|
943
|
+
manifest: opts.manifest, manifestSha256: opts.manifestSha256, target: opts.target, runId: opts.run });
|
|
897
944
|
if (opts.json)
|
|
898
945
|
console.log(JSON.stringify(result, null, 2));
|
|
946
|
+
else if (!('status' in result)) {
|
|
947
|
+
const legacy = result;
|
|
948
|
+
console.log(`${legacy.outcome.line}\nReport ${legacy.reportSha256}; reviewer artifact ${legacy.reviewerArtifactSha256}`);
|
|
949
|
+
}
|
|
950
|
+
else if (result.status === 'prepared')
|
|
951
|
+
console.log(`Recovery manifest ${result.manifest}\nSHA-256 ${result.manifest_sha256}\nOperation ${result.operation_id}`);
|
|
899
952
|
else
|
|
900
|
-
console.log(
|
|
901
|
-
process.exitCode = result.outcome.exitCode;
|
|
953
|
+
console.log(`Reviewer recovery complete for ${result.run_id}\nJournal ${result.journal}\nAcknowledgement ${result.recovery_acknowledgement}`);
|
|
954
|
+
process.exitCode = !('status' in result) ? result.outcome.exitCode : 0;
|
|
902
955
|
}
|
|
903
956
|
catch (error) {
|
|
904
957
|
const message = scrubText(error instanceof Error ? error.message : String(error), 300);
|
|
@@ -1054,6 +1107,22 @@ evidenceCmd
|
|
|
1054
1107
|
.action(async (runId, opts) => {
|
|
1055
1108
|
process.exitCode = await runEvidenceShow(runId ?? '', opts, evidenceDeps());
|
|
1056
1109
|
});
|
|
1110
|
+
evidenceCmd
|
|
1111
|
+
.command('recover-claim')
|
|
1112
|
+
.description('Preview, apply or resume an evidence-backed claim correction on the same target')
|
|
1113
|
+
.option('--preview', 'Read immutable evidence and write an exclusive preview manifest')
|
|
1114
|
+
.option('--apply', 'Apply the explicitly pinned manifest')
|
|
1115
|
+
.option('--resume', 'Resume the same operation after independently reading accepted receipts')
|
|
1116
|
+
.option('--selection <path>', 'Explicit source and claim selection for preview')
|
|
1117
|
+
.option('--adopt-manifest <path>', 'Existing operation manifest to adopt during preview')
|
|
1118
|
+
.option('--adopt-manifest-sha256 <sha256>', 'Exact digest of the existing operation manifest')
|
|
1119
|
+
.requiredOption('--manifest <path>', 'Exclusive preview manifest or existing operation manifest')
|
|
1120
|
+
.option('--manifest-sha256 <sha256>', 'Exact reviewed manifest digest for apply or resume')
|
|
1121
|
+
.option('--json', 'Print machine-readable operation status')
|
|
1122
|
+
.action(async (opts) => {
|
|
1123
|
+
const { runPublicClaimRecovery } = await import('./evidence/recover-claim.js');
|
|
1124
|
+
process.exitCode = await runPublicClaimRecovery(opts, evidenceDeps());
|
|
1125
|
+
});
|
|
1057
1126
|
evidenceCmd
|
|
1058
1127
|
.command('recover-run')
|
|
1059
1128
|
.description('Preview, apply or resume delivery of one immutable original asserted run; no review, events or native accounting')
|
|
@@ -1360,7 +1429,20 @@ async function prepareCouncil(spinner, opts, fallbackRoles, attestation) {
|
|
|
1360
1429
|
const startedAt = new Date();
|
|
1361
1430
|
// Validate the converge context first: a bad --round must fail before any
|
|
1362
1431
|
// model time is spent, not after the council has run.
|
|
1363
|
-
|
|
1432
|
+
let converge = resolveConvergeContext({ convergeTarget: opts.convergeTarget, round: opts.round, attempt: opts.attempt }, reviewConvergeEnvironment(opts));
|
|
1433
|
+
if (!opts.startOver && opts.guardedConverge && converge && converge.round === undefined) {
|
|
1434
|
+
const state = await loadConvergeRunState(await resolveGitCommonDir(), converge.target);
|
|
1435
|
+
converge = { ...converge, round: Math.max(0, ...(state?.rounds.map(entry => entry.round) ?? [])) + 1 };
|
|
1436
|
+
}
|
|
1437
|
+
const recoveredProduction = converge && !opts.startOver
|
|
1438
|
+
? await selectCurrentRecoveredProduction(converge)
|
|
1439
|
+
: undefined;
|
|
1440
|
+
if (recoveredProduction && !opts.guardedConverge) {
|
|
1441
|
+
throw new ReviewLaunchRefused('recovery_guard_required', 'Recovered-v3 production requires --guarded-converge so the provider call and report admission remain bound to one durable attempt.');
|
|
1442
|
+
}
|
|
1443
|
+
if (opts.guardedConverge && converge?.attempt !== undefined && !recoveredProduction) {
|
|
1444
|
+
throw new ReviewLaunchRefused('preclaimed_attempt_ineligible', 'An explicit guarded attempt may only hand off the exact standalone claim for recovered-v3 production.');
|
|
1445
|
+
}
|
|
1364
1446
|
if (!opts.exportPendingPackage)
|
|
1365
1447
|
await fetchHarnessKeys(spinner, attestation?.credential);
|
|
1366
1448
|
const config = await loadConfig(opts.config, undefined, { preserveDefaultRoster: opts.guardedConverge });
|
|
@@ -1554,7 +1636,10 @@ async function prepareCouncil(spinner, opts, fallbackRoles, attestation) {
|
|
|
1554
1636
|
...(pendingClaimPlan ? { pendingClaimPlan } : {}),
|
|
1555
1637
|
explicit: explicitReviewers !== undefined,
|
|
1556
1638
|
coreModels: models,
|
|
1557
|
-
...(converge ? { converge
|
|
1639
|
+
...(converge ? { converge: { ...converge, ...(recoveredProduction ? { recovery_source: {
|
|
1640
|
+
version: 1, native_sha256: recoveredProduction.nativeSha256,
|
|
1641
|
+
} } : {}) } } : {}),
|
|
1642
|
+
...(recoveredProduction ? { recoveredProduction } : {}),
|
|
1558
1643
|
startedAt,
|
|
1559
1644
|
};
|
|
1560
1645
|
}
|
|
@@ -1585,8 +1670,10 @@ async function discoverCycleReview(target, opts) {
|
|
|
1585
1670
|
const active = opts.startOver ? undefined : await loadConvergeRunState(common, targetKey);
|
|
1586
1671
|
if (!opts.startOver && !active?.cycle)
|
|
1587
1672
|
return opts;
|
|
1588
|
-
|
|
1673
|
+
const recoveredPreclaim = active?.version === 3 && opts.guardedConverge && opts.attempt !== undefined;
|
|
1674
|
+
if ((opts.attempt !== undefined && !recoveredPreclaim) || (opts.startOver && opts.round !== undefined) || opts.attest) {
|
|
1589
1675
|
throw new Error('A fresh review assigns its own ordinals and needs an ordinary actor credential');
|
|
1676
|
+
}
|
|
1590
1677
|
if (opts.telemetry === false)
|
|
1591
1678
|
throw new Error('Fresh review cycles require Harness evidence');
|
|
1592
1679
|
const paths = opts.jsonFile ? {} : await freshReviewOutputPaths(common);
|
|
@@ -1681,8 +1768,8 @@ async function runReview(target, opts) {
|
|
|
1681
1768
|
const converge = resolveConvergeContext(opts, reviewConvergeEnvironment(opts));
|
|
1682
1769
|
if (!converge)
|
|
1683
1770
|
throw new ReviewLaunchRefused('target_required', 'A guarded launch requires --converge-target.');
|
|
1684
|
-
if (
|
|
1685
|
-
throw new ReviewLaunchRefused('incompatible_launch', 'Guarded review
|
|
1771
|
+
if (opts.attest) {
|
|
1772
|
+
throw new ReviewLaunchRefused('incompatible_launch', 'Guarded review cannot be combined with --attest.');
|
|
1686
1773
|
}
|
|
1687
1774
|
}
|
|
1688
1775
|
// Exactly one review source: a positional target, --staged, or --working-tree
|
|
@@ -2122,6 +2209,7 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
2122
2209
|
target: prepared.converge.target,
|
|
2123
2210
|
startOver: opts.startOver, cycleRemote,
|
|
2124
2211
|
boundFixRecovery,
|
|
2212
|
+
recoverySource: prepared.converge.recovery_source,
|
|
2125
2213
|
headSha: extra.target.headSha ?? '',
|
|
2126
2214
|
inputSha256: guardedInputSha256(guardedInput),
|
|
2127
2215
|
round: prepared.converge.round,
|
|
@@ -2130,6 +2218,7 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
2130
2218
|
...(opts.retryReport ? { legacyRetry: { reportPath: opts.retryReport, config, roster,
|
|
2131
2219
|
historicalPlan: prepared.historicalRetryPlan === true } } : {}),
|
|
2132
2220
|
maxAttempts: opts.maxAttempts === undefined ? undefined : Number(opts.maxAttempts),
|
|
2221
|
+
consumeAttempt: prepared.recoveredProduction ? prepared.converge.attempt : undefined,
|
|
2133
2222
|
maxRounds: opts.maxRounds === undefined ? undefined : Number(opts.maxRounds),
|
|
2134
2223
|
validate: async () => {
|
|
2135
2224
|
validateLaunchProviders(roster.map(entry => entry.provider));
|
|
@@ -2147,7 +2236,7 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
2147
2236
|
baseSha: retained.baseSha } };
|
|
2148
2237
|
},
|
|
2149
2238
|
onClaim: async (claim) => {
|
|
2150
|
-
if (opts.telemetry !== false)
|
|
2239
|
+
if (!claim.preclaimed && opts.telemetry !== false)
|
|
2151
2240
|
await reportConvergeEvents([buildEvent({
|
|
2152
2241
|
kind: 'attempt_claimed', convergeTarget: claim.target, attempt: claim.attempt,
|
|
2153
2242
|
payload: { attempt: claim.attempt, cap: claim.cap, ...(claim.cycle ? { cycle_id: claim.cycle.id } : {}),
|
|
@@ -2444,7 +2533,10 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
2444
2533
|
startedAt: extra.pendingResume
|
|
2445
2534
|
? new Date(extra.pendingResume.launch.startedAtMs)
|
|
2446
2535
|
: prepared.startedAt,
|
|
2447
|
-
...(prepared.converge ? { converge: {
|
|
2536
|
+
...(prepared.converge ? { converge: {
|
|
2537
|
+
target: prepared.converge.target, round: prepared.converge.round, attempt: prepared.converge.attempt,
|
|
2538
|
+
...(prepared.converge.recovery_source ? { recovery_source: prepared.converge.recovery_source } : {}),
|
|
2539
|
+
}, cycleId: prepared.converge.cycleId } : {}),
|
|
2448
2540
|
};
|
|
2449
2541
|
const assemblyDependencies = {
|
|
2450
2542
|
onStage: postReviewStage,
|
|
@@ -2501,7 +2593,7 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
2501
2593
|
else
|
|
2502
2594
|
result = await assembleCompletedReview({
|
|
2503
2595
|
chunkReviews, arrivedAsync, asyncLaunched, startTime, roleMap, config, diff,
|
|
2504
|
-
gatingConfig: prepared.gatingConfig, modelWeights, run: runInput,
|
|
2596
|
+
gatingConfig: prepared.gatingConfig, recoveredProduction: prepared.recoveredProduction, modelWeights, run: runInput,
|
|
2505
2597
|
}, assemblyDependencies);
|
|
2506
2598
|
const { run } = result;
|
|
2507
2599
|
// The same derivation converge-report and the server apply to this report.
|