@allocator-one/rcl 4.5.7 → 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/guarded-input-retention.d.ts +23 -0
- package/dist/converge/guarded-input-retention.js +179 -0
- package/dist/converge/guarded-input-retention.js.map +1 -0
- 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/ordinary-pending-export.js +74 -40
- package/dist/converge/ordinary-pending-export.js.map +1 -1
- package/dist/converge/ordinary-pending-package.d.ts +16 -2
- package/dist/converge/ordinary-pending-package.js +77 -4
- package/dist/converge/ordinary-pending-package.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 +234 -139
- 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';
|
|
@@ -79,6 +81,7 @@ import { parseRepoName } from './evidence/target.js';
|
|
|
79
81
|
import { text } from './evidence/format.js';
|
|
80
82
|
import { loadConvergeRunState } from './converge/run-state.js';
|
|
81
83
|
import { finalizeOrdinaryPendingLaunch, previewOrdinaryPendingLaunch, resumePendingLegacyLaunch } from './converge/pending-legacy-resume.js';
|
|
84
|
+
import { ordinaryPendingGuardedInput } from './converge/ordinary-pending-package.js';
|
|
82
85
|
import { exportOrdinaryPendingPackage, retainOrdinaryLaunchInputs } from './converge/ordinary-pending-export.js';
|
|
83
86
|
import { capturePreparedCouncil } from './dispatch/capture-council.js';
|
|
84
87
|
import { executeCapturedOriginal } from './dispatch/original-execution.js';
|
|
@@ -109,7 +112,7 @@ program.hook('preAction', async (_thisCommand, actionCommand) => {
|
|
|
109
112
|
// Reads and explicit repairs must not flush unrelated evidence, even in preview.
|
|
110
113
|
if (actionCommand.parent?.name() === 'evidence' && (name === 'show' || name === 'status'))
|
|
111
114
|
return;
|
|
112
|
-
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'))
|
|
113
116
|
return;
|
|
114
117
|
const flags = actionCommand.opts();
|
|
115
118
|
if (flags.telemetry === false || (process.env['RCL_TELEMETRY'] ?? '').trim().toLowerCase() === 'off')
|
|
@@ -233,7 +236,7 @@ program
|
|
|
233
236
|
.option('--round <n>', 'Converge round number (or RCL_CONVERGE_ROUND)')
|
|
234
237
|
.option('--attempt <n>', 'Converge attempt number (or RCL_CONVERGE_ATTEMPT)')
|
|
235
238
|
.option('--start-over', 'Start an explicitly requested fresh review with a new normal budget; retain all prior evidence and spending')
|
|
236
|
-
.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')
|
|
237
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')
|
|
238
241
|
.option('--launch-intent <intent>', 'Guarded intent: review, stop-upstream, stop-review, or retry-delivery')
|
|
239
242
|
.option('--retry-report <path>', 'Original legacy report proving an inconclusive launch; the new inputs may differ; requires --retry-reason')
|
|
@@ -328,24 +331,31 @@ program
|
|
|
328
331
|
throw new ConvergeAttemptStateError('maxAttempts (--max-attempts) must be a positive safe integer.');
|
|
329
332
|
}
|
|
330
333
|
}
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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;
|
|
335
358
|
});
|
|
336
|
-
await reportConvergeEvents([
|
|
337
|
-
buildEvent({
|
|
338
|
-
kind: 'attempt_claimed',
|
|
339
|
-
convergeTarget: claim.target,
|
|
340
|
-
attempt: claim.attempt,
|
|
341
|
-
// The claim's local state path and process id stay on this machine.
|
|
342
|
-
payload: { attempt: claim.attempt, cap: claim.cap, ...(claim.cycle ? { cycle_id: claim.cycle.id } : {}) },
|
|
343
|
-
}),
|
|
344
|
-
// An explicit --max-attempts is consent evidence, whatever it was before.
|
|
345
|
-
...(maxAttempts !== undefined
|
|
346
|
-
? [buildEvent({ kind: 'cap_changed', convergeTarget: claim.target, attempt: claim.attempt, payload: { kind: 'attempts', to: claim.cap } })]
|
|
347
|
-
: []),
|
|
348
|
-
]);
|
|
349
359
|
if (opts.json) {
|
|
350
360
|
console.log(JSON.stringify(claim));
|
|
351
361
|
}
|
|
@@ -595,10 +605,12 @@ program
|
|
|
595
605
|
}
|
|
596
606
|
}
|
|
597
607
|
let report;
|
|
608
|
+
let reportJson;
|
|
598
609
|
let reportSha256;
|
|
599
610
|
try {
|
|
600
611
|
const source = await readFile(opts.report);
|
|
601
|
-
|
|
612
|
+
reportJson = source.toString('utf8');
|
|
613
|
+
report = JSON.parse(reportJson);
|
|
602
614
|
reportSha256 = sha256(source);
|
|
603
615
|
}
|
|
604
616
|
catch (err) {
|
|
@@ -625,48 +637,69 @@ program
|
|
|
625
637
|
if (reportRunId !== undefined && runId === undefined) {
|
|
626
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.`));
|
|
627
639
|
}
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
...(runId !== undefined ? { runId } : {}),
|
|
637
|
-
});
|
|
638
|
-
const classified = result.findings.map((f) => ({
|
|
639
|
-
identity: f.identity,
|
|
640
|
-
status: f.status,
|
|
641
|
-
gating: findingGatingReason(f.finding),
|
|
642
|
-
severity: f.finding.severity,
|
|
643
|
-
file: f.finding.file,
|
|
644
|
-
startLine: f.finding.startLine,
|
|
645
|
-
endLine: f.finding.endLine,
|
|
646
|
-
title: f.finding.title,
|
|
647
|
-
...(f.suppressReason ? { suppressReason: f.suppressReason } : {}),
|
|
648
|
-
}));
|
|
649
|
-
const actionable = classified.filter((f) => (f.status === 'new' || f.status === 'regating') && f.gating !== 'none');
|
|
650
|
-
await reportConvergeEvents([
|
|
651
|
-
buildEvent({
|
|
652
|
-
kind: 'round_processed',
|
|
653
|
-
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,
|
|
654
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 } : {}),
|
|
655
657
|
...(runId !== undefined ? { runId } : {}),
|
|
656
|
-
|
|
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,
|
|
657
675
|
round,
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
+
});
|
|
670
703
|
if (opts.json) {
|
|
671
704
|
console.log(JSON.stringify({
|
|
672
705
|
target: opts.target,
|
|
@@ -674,8 +707,11 @@ program
|
|
|
674
707
|
roundCap: result.roundCap,
|
|
675
708
|
reviewerHealth: blockingHealthJson(health),
|
|
676
709
|
counts: result.counts,
|
|
677
|
-
actionableGating: actionable.length,
|
|
710
|
+
actionableGating: result.actionableIdentities?.length ?? actionable.length,
|
|
678
711
|
findings: classified,
|
|
712
|
+
...(result.recoveryProjection
|
|
713
|
+
? { recoveryProjection: result.recoveryProjection }
|
|
714
|
+
: {}),
|
|
679
715
|
}, null, 2));
|
|
680
716
|
return;
|
|
681
717
|
}
|
|
@@ -683,7 +719,7 @@ program
|
|
|
683
719
|
console.log(`Round ${round}/${result.roundCap} for ${opts.target}: ` +
|
|
684
720
|
`${result.counts.new} new, ${result.counts.repeat} repeat, ` +
|
|
685
721
|
`${result.counts.suppressed} suppressed, ${result.counts.regating} regating · ` +
|
|
686
|
-
`${actionable.length} actionable gating finding(s)`);
|
|
722
|
+
`${result.actionableIdentities?.length ?? actionable.length} actionable gating finding(s)`);
|
|
687
723
|
for (const f of actionable) {
|
|
688
724
|
console.log(` [${f.status}] ${f.identity} ${f.file}:${f.startLine} — ${f.title}`);
|
|
689
725
|
}
|
|
@@ -777,68 +813,74 @@ program
|
|
|
777
813
|
if (new Set(verdicts.map(({ key }) => key)).size !== verdicts.length) {
|
|
778
814
|
throw new ConvergeRunStateError('Pass each finding identity only once, as either fixed or dismissed.');
|
|
779
815
|
}
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
});
|
|
787
|
-
// Feed the cross-run precision history (RCL-27) — fail-soft, the
|
|
788
|
-
// verdicts above are already durably recorded.
|
|
789
|
-
try {
|
|
790
|
-
const ts = new Date().toISOString();
|
|
791
|
-
await appendOutcomes(updated
|
|
792
|
-
.filter((e) => e.verdict !== undefined && e.models.length > 0)
|
|
793
|
-
.map((e) => ({
|
|
794
|
-
ts,
|
|
795
|
-
verdict: e.verdict,
|
|
796
|
-
models: e.models,
|
|
797
|
-
severity: e.verdictSeverity ?? e.severity,
|
|
798
|
-
target: opts.target,
|
|
799
|
-
findingKey: e.key,
|
|
800
|
-
source: 'live',
|
|
801
|
-
})));
|
|
802
|
-
}
|
|
803
|
-
catch (err) {
|
|
804
|
-
// Advisory history; verdict recording must not fail over it —
|
|
805
|
-
// but say so, or a broken store silently stops learning.
|
|
806
|
-
console.warn(`Model-stats store unavailable (outcomes not recorded): ${String(err)}`);
|
|
807
|
-
}
|
|
808
|
-
await reportConvergeEvents([
|
|
809
|
-
buildEvent({
|
|
810
|
-
kind: 'verdicts_recorded',
|
|
811
|
-
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,
|
|
812
822
|
round,
|
|
813
|
-
|
|
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
|
-
|
|
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
|
+
});
|
|
842
884
|
if (opts.json) {
|
|
843
885
|
console.log(JSON.stringify({
|
|
844
886
|
target: opts.target,
|
|
@@ -885,19 +927,31 @@ const telemetry = program
|
|
|
885
927
|
.description('Evidence delivery to Harness: credentials, queued deliveries and retained rejected reports');
|
|
886
928
|
telemetry
|
|
887
929
|
.command('recover-reviewer')
|
|
888
|
-
.description('
|
|
889
|
-
.
|
|
890
|
-
.
|
|
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)')
|
|
891
938
|
.option('--json', 'Output JSON')
|
|
892
939
|
.action(async (opts) => {
|
|
893
940
|
try {
|
|
894
941
|
const runtime = await createTelemetryRuntime({ rclVersion: RCL_VERSION });
|
|
895
|
-
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 });
|
|
896
944
|
if (opts.json)
|
|
897
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}`);
|
|
898
952
|
else
|
|
899
|
-
console.log(
|
|
900
|
-
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;
|
|
901
955
|
}
|
|
902
956
|
catch (error) {
|
|
903
957
|
const message = scrubText(error instanceof Error ? error.message : String(error), 300);
|
|
@@ -1053,6 +1107,22 @@ evidenceCmd
|
|
|
1053
1107
|
.action(async (runId, opts) => {
|
|
1054
1108
|
process.exitCode = await runEvidenceShow(runId ?? '', opts, evidenceDeps());
|
|
1055
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
|
+
});
|
|
1056
1126
|
evidenceCmd
|
|
1057
1127
|
.command('recover-run')
|
|
1058
1128
|
.description('Preview, apply or resume delivery of one immutable original asserted run; no review, events or native accounting')
|
|
@@ -1359,7 +1429,20 @@ async function prepareCouncil(spinner, opts, fallbackRoles, attestation) {
|
|
|
1359
1429
|
const startedAt = new Date();
|
|
1360
1430
|
// Validate the converge context first: a bad --round must fail before any
|
|
1361
1431
|
// model time is spent, not after the council has run.
|
|
1362
|
-
|
|
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
|
+
}
|
|
1363
1446
|
if (!opts.exportPendingPackage)
|
|
1364
1447
|
await fetchHarnessKeys(spinner, attestation?.credential);
|
|
1365
1448
|
const config = await loadConfig(opts.config, undefined, { preserveDefaultRoster: opts.guardedConverge });
|
|
@@ -1553,7 +1636,10 @@ async function prepareCouncil(spinner, opts, fallbackRoles, attestation) {
|
|
|
1553
1636
|
...(pendingClaimPlan ? { pendingClaimPlan } : {}),
|
|
1554
1637
|
explicit: explicitReviewers !== undefined,
|
|
1555
1638
|
coreModels: models,
|
|
1556
|
-
...(converge ? { converge
|
|
1639
|
+
...(converge ? { converge: { ...converge, ...(recoveredProduction ? { recovery_source: {
|
|
1640
|
+
version: 1, native_sha256: recoveredProduction.nativeSha256,
|
|
1641
|
+
} } : {}) } } : {}),
|
|
1642
|
+
...(recoveredProduction ? { recoveredProduction } : {}),
|
|
1557
1643
|
startedAt,
|
|
1558
1644
|
};
|
|
1559
1645
|
}
|
|
@@ -1584,8 +1670,10 @@ async function discoverCycleReview(target, opts) {
|
|
|
1584
1670
|
const active = opts.startOver ? undefined : await loadConvergeRunState(common, targetKey);
|
|
1585
1671
|
if (!opts.startOver && !active?.cycle)
|
|
1586
1672
|
return opts;
|
|
1587
|
-
|
|
1673
|
+
const recoveredPreclaim = active?.version === 3 && opts.guardedConverge && opts.attempt !== undefined;
|
|
1674
|
+
if ((opts.attempt !== undefined && !recoveredPreclaim) || (opts.startOver && opts.round !== undefined) || opts.attest) {
|
|
1588
1675
|
throw new Error('A fresh review assigns its own ordinals and needs an ordinary actor credential');
|
|
1676
|
+
}
|
|
1589
1677
|
if (opts.telemetry === false)
|
|
1590
1678
|
throw new Error('Fresh review cycles require Harness evidence');
|
|
1591
1679
|
const paths = opts.jsonFile ? {} : await freshReviewOutputPaths(common);
|
|
@@ -1680,8 +1768,8 @@ async function runReview(target, opts) {
|
|
|
1680
1768
|
const converge = resolveConvergeContext(opts, reviewConvergeEnvironment(opts));
|
|
1681
1769
|
if (!converge)
|
|
1682
1770
|
throw new ReviewLaunchRefused('target_required', 'A guarded launch requires --converge-target.');
|
|
1683
|
-
if (
|
|
1684
|
-
throw new ReviewLaunchRefused('incompatible_launch', 'Guarded review
|
|
1771
|
+
if (opts.attest) {
|
|
1772
|
+
throw new ReviewLaunchRefused('incompatible_launch', 'Guarded review cannot be combined with --attest.');
|
|
1685
1773
|
}
|
|
1686
1774
|
}
|
|
1687
1775
|
// Exactly one review source: a positional target, --staged, or --working-tree
|
|
@@ -1906,6 +1994,8 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
1906
1994
|
throw new ReviewLaunchRefused('pending_async_binding_invalid', 'Retained async bindings must be comma-separated lowercase SHA-256 digests, or "none" for an empty cycle history.');
|
|
1907
1995
|
}
|
|
1908
1996
|
const migrationPackage = opts.ordinaryPendingPackage === undefined ? undefined : JSON.parse((await readStable(opts.ordinaryPendingPackage)).text);
|
|
1997
|
+
const migrationGuardedInput = migrationPackage
|
|
1998
|
+
? ordinaryPendingGuardedInput(migrationPackage) : undefined;
|
|
1909
1999
|
if (opts.expectPrHeadSha !== undefined && migrationPackage?.version !== 2) {
|
|
1910
2000
|
throw new ReviewLaunchRefused('pending_finalize_incompatible', '--expect-pr-head-sha requires a cycle-backed version-2 pending package.');
|
|
1911
2001
|
}
|
|
@@ -1955,7 +2045,7 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
1955
2045
|
return [...found.values()].sort((a, b) => a.sha256.localeCompare(b.sha256));
|
|
1956
2046
|
};
|
|
1957
2047
|
if (opts.previewPending && migrationPackage) {
|
|
1958
|
-
const inputSha256 = guardedInputSha256(
|
|
2048
|
+
const inputSha256 = guardedInputSha256(migrationGuardedInput);
|
|
1959
2049
|
const preview = await previewOrdinaryPendingLaunch({ gitCommonDir: common,
|
|
1960
2050
|
target: prepared.converge.target, headSha: extra.target.headSha ?? '', baseSha: extra.target.baseSha ?? '',
|
|
1961
2051
|
pendingInputSha256: inputSha256, recoveryInputSha256: inputSha256,
|
|
@@ -1974,7 +2064,7 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
1974
2064
|
return undefined;
|
|
1975
2065
|
}
|
|
1976
2066
|
if (opts.finalizePendingOnly && migrationPackage) {
|
|
1977
|
-
const inputSha256 = guardedInputSha256(
|
|
2067
|
+
const inputSha256 = guardedInputSha256(migrationGuardedInput);
|
|
1978
2068
|
const result = await finalizeOrdinaryPendingLaunch({ gitCommonDir: common,
|
|
1979
2069
|
target: prepared.converge.target, headSha: extra.target.headSha ?? '',
|
|
1980
2070
|
baseSha: extra.target.baseSha ?? '', pendingInputSha256: inputSha256,
|
|
@@ -2017,7 +2107,7 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
2017
2107
|
.slice(0, MAX_ASYNC_CALLS_PER_ROUND);
|
|
2018
2108
|
const pendingClaimAsyncPrompts = await Promise.all(pendingClaimAsyncChunks.map(({ assignment, chunk }) => buildPrompt(chunk, assignment.role, { contextDocs: pendingClaimContext, plan: planContext })));
|
|
2019
2109
|
const claimedInputSha256 = migrationPackage
|
|
2020
|
-
? guardedInputSha256(
|
|
2110
|
+
? guardedInputSha256(migrationGuardedInput)
|
|
2021
2111
|
: guardedInputSha256({
|
|
2022
2112
|
head: extra.target.headSha, kind: extra.target.kind, repo: extra.target.repo, pr: extra.target.prNumber,
|
|
2023
2113
|
diff: diffDigest(diff.files), config: configDigest(config), roster: pendingClaimRoster,
|
|
@@ -2119,6 +2209,7 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
2119
2209
|
target: prepared.converge.target,
|
|
2120
2210
|
startOver: opts.startOver, cycleRemote,
|
|
2121
2211
|
boundFixRecovery,
|
|
2212
|
+
recoverySource: prepared.converge.recovery_source,
|
|
2122
2213
|
headSha: extra.target.headSha ?? '',
|
|
2123
2214
|
inputSha256: guardedInputSha256(guardedInput),
|
|
2124
2215
|
round: prepared.converge.round,
|
|
@@ -2127,6 +2218,7 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
2127
2218
|
...(opts.retryReport ? { legacyRetry: { reportPath: opts.retryReport, config, roster,
|
|
2128
2219
|
historicalPlan: prepared.historicalRetryPlan === true } } : {}),
|
|
2129
2220
|
maxAttempts: opts.maxAttempts === undefined ? undefined : Number(opts.maxAttempts),
|
|
2221
|
+
consumeAttempt: prepared.recoveredProduction ? prepared.converge.attempt : undefined,
|
|
2130
2222
|
maxRounds: opts.maxRounds === undefined ? undefined : Number(opts.maxRounds),
|
|
2131
2223
|
validate: async () => {
|
|
2132
2224
|
validateLaunchProviders(roster.map(entry => entry.provider));
|
|
@@ -2144,7 +2236,7 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
2144
2236
|
baseSha: retained.baseSha } };
|
|
2145
2237
|
},
|
|
2146
2238
|
onClaim: async (claim) => {
|
|
2147
|
-
if (opts.telemetry !== false)
|
|
2239
|
+
if (!claim.preclaimed && opts.telemetry !== false)
|
|
2148
2240
|
await reportConvergeEvents([buildEvent({
|
|
2149
2241
|
kind: 'attempt_claimed', convergeTarget: claim.target, attempt: claim.attempt,
|
|
2150
2242
|
payload: { attempt: claim.attempt, cap: claim.cap, ...(claim.cycle ? { cycle_id: claim.cycle.id } : {}),
|
|
@@ -2441,7 +2533,10 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
2441
2533
|
startedAt: extra.pendingResume
|
|
2442
2534
|
? new Date(extra.pendingResume.launch.startedAtMs)
|
|
2443
2535
|
: prepared.startedAt,
|
|
2444
|
-
...(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 } : {}),
|
|
2445
2540
|
};
|
|
2446
2541
|
const assemblyDependencies = {
|
|
2447
2542
|
onStage: postReviewStage,
|
|
@@ -2498,7 +2593,7 @@ async function executeCouncil(spinner, prepared, diff, opts, extra, preparedWork
|
|
|
2498
2593
|
else
|
|
2499
2594
|
result = await assembleCompletedReview({
|
|
2500
2595
|
chunkReviews, arrivedAsync, asyncLaunched, startTime, roleMap, config, diff,
|
|
2501
|
-
gatingConfig: prepared.gatingConfig, modelWeights, run: runInput,
|
|
2596
|
+
gatingConfig: prepared.gatingConfig, recoveredProduction: prepared.recoveredProduction, modelWeights, run: runInput,
|
|
2502
2597
|
}, assemblyDependencies);
|
|
2503
2598
|
const { run } = result;
|
|
2504
2599
|
// The same derivation converge-report and the server apply to this report.
|