@aragon-agent/cli 0.6.44 → 0.6.46
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/CHANGELOG.md +44 -0
- package/README.md +120 -2
- package/dist/agent/controller.d.ts +69 -0
- package/dist/agent/controller.js +224 -11
- package/dist/agent/governance-view.d.ts +40 -0
- package/dist/agent/governance-view.js +40 -0
- package/dist/agent/governance-wiring.d.ts +162 -0
- package/dist/agent/governance-wiring.js +470 -0
- package/dist/agent/invocation-card.d.ts +77 -0
- package/dist/agent/invocation-card.js +169 -0
- package/dist/agent/plan-prompt.d.ts +7 -0
- package/dist/agent/plan-prompt.js +2 -1
- package/dist/agent/prompt-options.d.ts +6 -0
- package/dist/agent/queued-messages.d.ts +3 -0
- package/dist/agent/queued-messages.js +4 -1
- package/dist/agent/reducer.d.ts +38 -1
- package/dist/agent/reducer.js +57 -12
- package/dist/agent/system-prompt.d.ts +19 -0
- package/dist/agent/system-prompt.js +9 -1
- package/dist/cli.js +45 -1
- package/dist/commands/builtins.js +22 -4
- package/dist/commands/governance-command.d.ts +35 -0
- package/dist/commands/governance-command.js +304 -0
- package/dist/commands/governance-runs-table.d.ts +40 -0
- package/dist/commands/governance-runs-table.js +130 -0
- package/dist/commands/index-build.d.ts +8 -0
- package/dist/commands/index-build.js +11 -0
- package/dist/commands/registry.d.ts +35 -0
- package/dist/commands/registry.js +20 -7
- package/dist/commands/skills.js +36 -2
- package/dist/commands/subagent-command.js +3 -1
- package/dist/commands/team-command.d.ts +6 -0
- package/dist/commands/team-command.js +40 -3
- package/dist/config/app-paths.d.ts +10 -0
- package/dist/config/app-paths.js +12 -0
- package/dist/config/cli-commands.d.ts +18 -0
- package/dist/config/cli-commands.js +47 -1
- package/dist/config/env.d.ts +2 -1
- package/dist/config/env.js +10 -1
- package/dist/config/governance-config.d.ts +125 -0
- package/dist/config/governance-config.js +164 -0
- package/dist/config/governance-launch.d.ts +71 -0
- package/dist/config/governance-launch.js +58 -0
- package/dist/config/load.d.ts +15 -0
- package/dist/config/load.js +26 -1
- package/dist/config/model-profile-store.d.ts +4 -2
- package/dist/config/schema.d.ts +22 -0
- package/dist/config/schema.js +6 -0
- package/dist/config/store.js +14 -1
- package/dist/config/ui-state.d.ts +11 -0
- package/dist/config/ui-state.js +11 -0
- package/dist/diagnostics/doctor.js +20 -0
- package/dist/governance/card-normalize.d.ts +24 -0
- package/dist/governance/card-normalize.js +76 -0
- package/dist/governance/copy.d.ts +109 -0
- package/dist/governance/copy.js +223 -0
- package/dist/governance/engine/aftermath.d.ts +49 -0
- package/dist/governance/engine/aftermath.js +134 -0
- package/dist/governance/engine/artifact-persist.d.ts +35 -0
- package/dist/governance/engine/artifact-persist.js +86 -0
- package/dist/governance/engine/budget.d.ts +52 -0
- package/dist/governance/engine/budget.js +112 -0
- package/dist/governance/engine/cards.d.ts +18 -0
- package/dist/governance/engine/cards.js +186 -0
- package/dist/governance/engine/digest.d.ts +32 -0
- package/dist/governance/engine/digest.js +63 -0
- package/dist/governance/engine/engine-core.d.ts +219 -0
- package/dist/governance/engine/engine-core.js +562 -0
- package/dist/governance/engine/engine.d.ts +68 -0
- package/dist/governance/engine/engine.js +314 -0
- package/dist/governance/engine/estimate.d.ts +38 -0
- package/dist/governance/engine/estimate.js +46 -0
- package/dist/governance/engine/inspector.d.ts +16 -0
- package/dist/governance/engine/inspector.js +396 -0
- package/dist/governance/engine/integration.d.ts +35 -0
- package/dist/governance/engine/integration.js +220 -0
- package/dist/governance/engine/lifecycle.d.ts +29 -0
- package/dist/governance/engine/lifecycle.js +656 -0
- package/dist/governance/engine/pause.d.ts +56 -0
- package/dist/governance/engine/pause.js +107 -0
- package/dist/governance/engine/phase-members.d.ts +15 -0
- package/dist/governance/engine/phase-members.js +34 -0
- package/dist/governance/engine/phase-planner.d.ts +53 -0
- package/dist/governance/engine/phase-planner.js +282 -0
- package/dist/governance/engine/rebuild.d.ts +27 -0
- package/dist/governance/engine/rebuild.js +57 -0
- package/dist/governance/engine/report.d.ts +66 -0
- package/dist/governance/engine/report.js +220 -0
- package/dist/governance/engine/run-digest.d.ts +20 -0
- package/dist/governance/engine/run-digest.js +42 -0
- package/dist/governance/engine/spec-builder.d.ts +117 -0
- package/dist/governance/engine/spec-builder.js +321 -0
- package/dist/governance/engine/stage-runner.d.ts +22 -0
- package/dist/governance/engine/stage-runner.js +664 -0
- package/dist/governance/engine/start-input.d.ts +19 -0
- package/dist/governance/engine/start-input.js +111 -0
- package/dist/governance/engine/state-machine.d.ts +101 -0
- package/dist/governance/engine/state-machine.js +586 -0
- package/dist/governance/engine/submission-queries.d.ts +42 -0
- package/dist/governance/engine/submission-queries.js +97 -0
- package/dist/governance/engine/submission-render.d.ts +12 -0
- package/dist/governance/engine/submission-render.js +92 -0
- package/dist/governance/engine/submissions.d.ts +27 -0
- package/dist/governance/engine/submissions.js +455 -0
- package/dist/governance/engine/visibility.d.ts +104 -0
- package/dist/governance/engine/visibility.js +223 -0
- package/dist/governance/governed-dispatch.d.ts +19 -0
- package/dist/governance/governed-dispatch.js +17 -0
- package/dist/governance/limits.d.ts +107 -0
- package/dist/governance/limits.js +131 -0
- package/dist/governance/posture-transition.d.ts +38 -0
- package/dist/governance/posture-transition.js +44 -0
- package/dist/governance/posture.d.ts +35 -0
- package/dist/governance/posture.js +59 -0
- package/dist/governance/profiles.d.ts +48 -0
- package/dist/governance/profiles.js +140 -0
- package/dist/governance/prompts/orchestrator-block.d.ts +40 -0
- package/dist/governance/prompts/orchestrator-block.js +128 -0
- package/dist/governance/prompts/plan-addendum.d.ts +22 -0
- package/dist/governance/prompts/plan-addendum.js +60 -0
- package/dist/governance/prompts/refusals.d.ts +48 -0
- package/dist/governance/prompts/refusals.js +82 -0
- package/dist/governance/prompts/role-blocks.d.ts +40 -0
- package/dist/governance/prompts/role-blocks.js +255 -0
- package/dist/governance/report-reader.d.ts +31 -0
- package/dist/governance/report-reader.js +57 -0
- package/dist/governance/roles.d.ts +83 -0
- package/dist/governance/roles.js +142 -0
- package/dist/governance/rules/claims.d.ts +83 -0
- package/dist/governance/rules/claims.js +250 -0
- package/dist/governance/rules/elimination.d.ts +66 -0
- package/dist/governance/rules/elimination.js +105 -0
- package/dist/governance/rules/events-table.d.ts +46 -0
- package/dist/governance/rules/events-table.js +94 -0
- package/dist/governance/rules/failure-analysis.d.ts +28 -0
- package/dist/governance/rules/failure-analysis.js +93 -0
- package/dist/governance/rules/ledger-replay.d.ts +53 -0
- package/dist/governance/rules/ledger-replay.js +53 -0
- package/dist/governance/rules/ledger-state.d.ts +85 -0
- package/dist/governance/rules/ledger-state.js +175 -0
- package/dist/governance/rules/ledger.d.ts +228 -0
- package/dist/governance/rules/ledger.js +560 -0
- package/dist/governance/rules/rca.d.ts +68 -0
- package/dist/governance/rules/rca.js +220 -0
- package/dist/governance/rules/settlement.d.ts +129 -0
- package/dist/governance/rules/settlement.js +363 -0
- package/dist/governance/rules/suspicion.d.ts +66 -0
- package/dist/governance/rules/suspicion.js +169 -0
- package/dist/governance/store/artifacts.d.ts +81 -0
- package/dist/governance/store/artifacts.js +173 -0
- package/dist/governance/store/atomic.d.ts +79 -0
- package/dist/governance/store/atomic.js +119 -0
- package/dist/governance/store/events-log.d.ts +35 -0
- package/dist/governance/store/events-log.js +86 -0
- package/dist/governance/store/run-lock.d.ts +51 -0
- package/dist/governance/store/run-lock.js +122 -0
- package/dist/governance/store/run-store.d.ts +229 -0
- package/dist/governance/store/run-store.js +644 -0
- package/dist/governance/store/scoreboard-export.d.ts +53 -0
- package/dist/governance/store/scoreboard-export.js +65 -0
- package/dist/governance/tools/child-tools.d.ts +31 -0
- package/dist/governance/tools/child-tools.js +65 -0
- package/dist/governance/tools/guard.d.ts +71 -0
- package/dist/governance/tools/guard.js +194 -0
- package/dist/governance/tools/inspector-tools.d.ts +63 -0
- package/dist/governance/tools/inspector-tools.js +424 -0
- package/dist/governance/tools/lead-tools.d.ts +42 -0
- package/dist/governance/tools/lead-tools.js +226 -0
- package/dist/governance/tools/normalize-util.d.ts +101 -0
- package/dist/governance/tools/normalize-util.js +230 -0
- package/dist/governance/tools/scan-incomplete.d.ts +79 -0
- package/dist/governance/tools/scan-incomplete.js +486 -0
- package/dist/governance/tools/shared-tools.d.ts +44 -0
- package/dist/governance/tools/shared-tools.js +125 -0
- package/dist/governance/tools/submit-normalizers.d.ts +28 -0
- package/dist/governance/tools/submit-normalizers.js +474 -0
- package/dist/governance/tools/submit-tools.d.ts +26 -0
- package/dist/governance/tools/submit-tools.js +171 -0
- package/dist/governance/types.d.ts +661 -0
- package/dist/governance/types.js +27 -0
- package/dist/session/persist.js +10 -0
- package/dist/session/validate-session.js +36 -3
- package/dist/team/forced-mode.d.ts +19 -0
- package/dist/team/forced-mode.js +42 -20
- package/dist/team/runtime.d.ts +8 -0
- package/dist/team/runtime.js +9 -0
- package/dist/team/subagent.d.ts +23 -0
- package/dist/team/subagent.js +14 -3
- package/dist/team/types.d.ts +6 -0
- package/dist/tools/index.d.ts +30 -7
- package/dist/tools/index.js +14 -0
- package/dist/tools/plan-tools.d.ts +14 -2
- package/dist/tools/plan-tools.js +5 -2
- package/dist/ui/App.js +170 -31
- package/dist/ui/BottomStatusRow.js +1 -1
- package/dist/ui/Composer.d.ts +7 -1
- package/dist/ui/Composer.js +9 -6
- package/dist/ui/GovernancePanel.d.ts +50 -0
- package/dist/ui/GovernancePanel.js +77 -0
- package/dist/ui/GovernanceRun.d.ts +18 -0
- package/dist/ui/GovernanceRun.js +11 -0
- package/dist/ui/StatusBar.d.ts +15 -2
- package/dist/ui/StatusBar.js +10 -4
- package/dist/ui/Transcript.js +7 -1
- package/dist/ui/entries/GovernanceCard.d.ts +30 -0
- package/dist/ui/entries/GovernanceCard.js +32 -0
- package/dist/ui/entries/InvocationCard.d.ts +20 -0
- package/dist/ui/entries/InvocationCard.js +32 -0
- package/dist/ui/governance-copy.d.ts +69 -0
- package/dist/ui/governance-copy.js +104 -0
- package/dist/ui/governance-status.d.ts +145 -0
- package/dist/ui/governance-status.js +326 -0
- package/dist/ui/interaction-copy.d.ts +4 -0
- package/dist/ui/interaction-copy.js +5 -0
- package/dist/ui/interaction-hints.d.ts +17 -0
- package/dist/ui/interaction-hints.js +14 -2
- package/dist/ui/layout/governance-board.d.ts +121 -0
- package/dist/ui/layout/governance-board.js +382 -0
- package/dist/ui/layout/governance-card.d.ts +53 -0
- package/dist/ui/layout/governance-card.js +134 -0
- package/dist/ui/layout/governance-chip.d.ts +38 -0
- package/dist/ui/layout/governance-chip.js +64 -0
- package/dist/ui/layout/governance-panel.d.ts +85 -0
- package/dist/ui/layout/governance-panel.js +358 -0
- package/dist/ui/layout/governance-roster.d.ts +65 -0
- package/dist/ui/layout/governance-roster.js +275 -0
- package/dist/ui/layout/invocation-card.d.ts +41 -0
- package/dist/ui/layout/invocation-card.js +91 -0
- package/dist/ui/layout/status-detail-layout.js +3 -3
- package/dist/ui/layout/status-layout.d.ts +5 -2
- package/dist/ui/layout/status-layout.js +10 -6
- package/dist/ui/layout/virtual-window.js +22 -1
- package/dist/ui/overlays/GovernanceBoardOverlay.d.ts +41 -0
- package/dist/ui/overlays/GovernanceBoardOverlay.js +76 -0
- package/dist/ui/overlays/GovernanceSettingsPage.d.ts +48 -0
- package/dist/ui/overlays/GovernanceSettingsPage.js +93 -0
- package/dist/ui/overlays/HelpOverlay.js +4 -3
- package/dist/ui/overlays/QuestionOverlay.d.ts +9 -1
- package/dist/ui/overlays/QuestionOverlay.js +4 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
- package/dist/ui/overlays/SettingsScreen.js +21 -1
- package/dist/ui/overlays/governance-settings.d.ts +79 -0
- package/dist/ui/overlays/governance-settings.js +140 -0
- package/dist/ui/overlays/model-profile-state.d.ts +6 -1
- package/dist/ui/overlays/model-profile-state.js +10 -0
- package/dist/ui/palettes.d.ts +17 -0
- package/dist/ui/palettes.js +9 -0
- package/dist/ui/scrollback/live-preview.js +2 -0
- package/dist/ui/scrollback/settled-boundary.js +3 -0
- package/dist/ui/shortcuts.js +2 -2
- package/dist/ui/theme.d.ts +7 -0
- package/dist/ui/theme.js +1 -0
- package/dist/ui/transcript-text.js +13 -1
- package/dist/ui/use-wheel-routing.js +3 -1
- package/package.json +2 -2
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only queries over the accepted-submission cache (governance-mode 8.3).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
5
|
+
*
|
|
6
|
+
* The stage runner builds briefs from these, and the adjudication half uses them to find
|
|
7
|
+
* "the previous review of this WP". Everything is derived from `core.submissionCache`
|
|
8
|
+
* (artifact id -> submission), which the resume path repopulates from the artifact store, so
|
|
9
|
+
* nothing here needs a second copy of state that could drift (a "pending rework" list kept
|
|
10
|
+
* beside the submissions would have to be persisted AND rebuilt; deriving it cannot).
|
|
11
|
+
*/
|
|
12
|
+
import { settleTest } from '../rules/settlement.js';
|
|
13
|
+
/** All cached submissions of one kind for a WP, oldest first. */
|
|
14
|
+
export function submissionsOf(core, wpId, kind) {
|
|
15
|
+
const out = [];
|
|
16
|
+
for (const item of core.submissionCache.values()) {
|
|
17
|
+
if (item.kind === kind && 'wpId' in item && item.wpId === wpId) {
|
|
18
|
+
out.push(item);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return out.sort((a, b) => a.at - b.at || a.id.localeCompare(b.id));
|
|
22
|
+
}
|
|
23
|
+
export function latestWork(core, wpId) {
|
|
24
|
+
return submissionsOf(core, wpId, 'work').at(-1);
|
|
25
|
+
}
|
|
26
|
+
export function latestReview(core, wpId) {
|
|
27
|
+
return submissionsOf(core, wpId, 'review').at(-1);
|
|
28
|
+
}
|
|
29
|
+
export function latestTest(core, wpId) {
|
|
30
|
+
return submissionsOf(core, wpId, 'test').at(-1);
|
|
31
|
+
}
|
|
32
|
+
/** The review CONCLUSION a tester may see (never the comments, D-7). */
|
|
33
|
+
export function reviewConclusion(core, wpId) {
|
|
34
|
+
return latestReview(core, wpId)?.verdict;
|
|
35
|
+
}
|
|
36
|
+
/** Files the developer reported changing in the latest work submission. */
|
|
37
|
+
export function changeListOf(core, wpId) {
|
|
38
|
+
return latestWork(core, wpId)?.filesChanged ?? [];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The developer's open appeal, if the latest work submission appealed and no review has
|
|
42
|
+
* answered it yet. Reviewers MUST answer each finding id (L1, 5.6).
|
|
43
|
+
*/
|
|
44
|
+
export function pendingAppeal(core, wpId) {
|
|
45
|
+
const work = latestWork(core, wpId);
|
|
46
|
+
if (work?.appeal === undefined)
|
|
47
|
+
return undefined;
|
|
48
|
+
const review = latestReview(core, wpId);
|
|
49
|
+
if (review !== undefined && review.at >= work.at)
|
|
50
|
+
return undefined;
|
|
51
|
+
return work.appeal;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The rework items a developer must address: derived from whichever REJECTION came last,
|
|
55
|
+
* a review (its `requiredChanges`, the only feedback surface a DEV sees) or a test (the
|
|
56
|
+
* defect or gap descriptions). Empty when the WP has not been rejected since its last
|
|
57
|
+
* work submission.
|
|
58
|
+
*/
|
|
59
|
+
export function reworkItemsFor(core, wpId) {
|
|
60
|
+
const run = core.run;
|
|
61
|
+
const wp = run?.workPackages[wpId];
|
|
62
|
+
if (run === null || wp === undefined)
|
|
63
|
+
return [];
|
|
64
|
+
const work = latestWork(core, wpId);
|
|
65
|
+
const review = latestReview(core, wpId);
|
|
66
|
+
const test = latestTest(core, wpId);
|
|
67
|
+
const since = work?.at ?? 0;
|
|
68
|
+
const reviewRejected = review?.verdict === 'NEEDS_REWORK' && review.at >= since;
|
|
69
|
+
const testRejected = test !== undefined && test.verdict !== 'ALL_PASS' && test.at >= since;
|
|
70
|
+
if (reviewRejected && testRejected) {
|
|
71
|
+
return review.at >= test.at ? review.requiredChanges : testItems(core, wpId, test);
|
|
72
|
+
}
|
|
73
|
+
if (reviewRejected)
|
|
74
|
+
return review.requiredChanges;
|
|
75
|
+
if (testRejected)
|
|
76
|
+
return testItems(core, wpId, test);
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
function testItems(core, wpId, test) {
|
|
80
|
+
const wp = core.run?.workPackages[wpId];
|
|
81
|
+
if (wp === undefined)
|
|
82
|
+
return [];
|
|
83
|
+
const result = settleTest({
|
|
84
|
+
wp: {
|
|
85
|
+
id: wp.id,
|
|
86
|
+
assignee: wp.assignee ?? 'dev',
|
|
87
|
+
reviewer: wp.reviewer,
|
|
88
|
+
tester: wp.tester,
|
|
89
|
+
rejections: wp.rejections,
|
|
90
|
+
files: wp.files,
|
|
91
|
+
},
|
|
92
|
+
contracts: [],
|
|
93
|
+
phase: wp.phase,
|
|
94
|
+
at: test.at,
|
|
95
|
+
}, test);
|
|
96
|
+
return result.route?.to === 'dev' ? result.route.requiredChanges : [];
|
|
97
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-readable renderings of accepted submissions (written beside the JSON artifact, and
|
|
3
|
+
* what `gov_read` shows a role that is allowed to see them).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY in this source (`governance/` is glyph-scanned); the TEXT it renders is
|
|
6
|
+
* whatever the agent wrote, in whatever language the request used.
|
|
7
|
+
*/
|
|
8
|
+
import type { InspectorReport, ReviewSubmission, TestSubmission, WorkSubmission } from '../types.js';
|
|
9
|
+
export declare function renderWorkMarkdown(sub: WorkSubmission): string;
|
|
10
|
+
export declare function renderReviewMarkdown(sub: ReviewSubmission): string;
|
|
11
|
+
export declare function renderTestMarkdown(sub: TestSubmission): string;
|
|
12
|
+
export declare function renderInspectorMarkdown(report: InspectorReport): string;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-readable renderings of accepted submissions (written beside the JSON artifact, and
|
|
3
|
+
* what `gov_read` shows a role that is allowed to see them).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY in this source (`governance/` is glyph-scanned); the TEXT it renders is
|
|
6
|
+
* whatever the agent wrote, in whatever language the request used.
|
|
7
|
+
*/
|
|
8
|
+
function list(items) {
|
|
9
|
+
return items.length === 0 ? '- (none)' : items.map((i) => `- ${i}`).join('\n');
|
|
10
|
+
}
|
|
11
|
+
export function renderWorkMarkdown(sub) {
|
|
12
|
+
const lines = [
|
|
13
|
+
`# Work submission - ${sub.wpId} by ${sub.identity} (attempt ${sub.attempt}, ${sub.stage})`,
|
|
14
|
+
'',
|
|
15
|
+
sub.summary,
|
|
16
|
+
'',
|
|
17
|
+
'## Files changed',
|
|
18
|
+
list(sub.filesChanged),
|
|
19
|
+
'',
|
|
20
|
+
'## Interface contracts',
|
|
21
|
+
list(sub.interfaceContracts.map((c) => `${c.interface} [${c.role}] verified=${c.verified} peer=${c.peer || '-'}: ${c.method}`)),
|
|
22
|
+
'',
|
|
23
|
+
'## Verification',
|
|
24
|
+
list(sub.verification.commands.map((c) => `${c.cmd} -> ${c.result}${c.note ? ` (${c.note})` : ''}`)),
|
|
25
|
+
];
|
|
26
|
+
if (sub.appeal) {
|
|
27
|
+
lines.push('', '## Appeal', `Findings: ${sub.appeal.findingIds.join(', ')}`, sub.appeal.argument);
|
|
28
|
+
}
|
|
29
|
+
return `${lines.join('\n')}\n`;
|
|
30
|
+
}
|
|
31
|
+
export function renderReviewMarkdown(sub) {
|
|
32
|
+
const check = Object.entries(sub.checklist).map(([name, item]) => `${name}: ${item.status} - ${item.evidence}`);
|
|
33
|
+
const lines = [
|
|
34
|
+
`# Review - ${sub.wpId} by ${sub.identity} (attempt ${sub.attempt}): ${sub.verdict}`,
|
|
35
|
+
'',
|
|
36
|
+
'## Checklist',
|
|
37
|
+
list(check),
|
|
38
|
+
'',
|
|
39
|
+
'## Findings',
|
|
40
|
+
list(sub.findings.map((f) => `[${f.id}] ${f.severity}${f.mustFix ? ' MUST FIX' : ''} ${f.file}${f.line ? `:${f.line}` : ''}: ${f.issue} => ${f.requiredChange}`)),
|
|
41
|
+
'',
|
|
42
|
+
'## Fixes applied by the reviewer',
|
|
43
|
+
list(sub.fixesApplied.map((f) => `${f.file}: ${f.rationale}`)),
|
|
44
|
+
'',
|
|
45
|
+
'## Integration assessment',
|
|
46
|
+
sub.integrationAssessment,
|
|
47
|
+
];
|
|
48
|
+
if (sub.appealResponses.length > 0) {
|
|
49
|
+
lines.push('', '## Appeal responses', list(sub.appealResponses.map((r) => `${r.findingId}: ${r.decision}${r.note ? ` - ${r.note}` : ''}`)));
|
|
50
|
+
}
|
|
51
|
+
return `${lines.join('\n')}\n`;
|
|
52
|
+
}
|
|
53
|
+
export function renderTestMarkdown(sub) {
|
|
54
|
+
const lines = [
|
|
55
|
+
`# Test report - ${sub.wpId} by ${sub.identity} (attempt ${sub.attempt}): ${sub.verdict}`,
|
|
56
|
+
'',
|
|
57
|
+
'## Traceability',
|
|
58
|
+
list(sub.traceability.map((t) => `[${t.status}] ${t.requirement} - ${t.evidence}`)),
|
|
59
|
+
'',
|
|
60
|
+
'## Commands',
|
|
61
|
+
list(sub.commands.map((c) => `${c.cmd} (exit ${c.exit}): ${c.summary}`)),
|
|
62
|
+
'',
|
|
63
|
+
'## Issues',
|
|
64
|
+
list(sub.issues.map((i) => `${i.severity}: ${i.description}`)),
|
|
65
|
+
'',
|
|
66
|
+
`Boundary tests written: ${sub.boundaryTestsWritten ? 'yes' : 'no'}`,
|
|
67
|
+
];
|
|
68
|
+
if (sub.skeletonRatio !== undefined) {
|
|
69
|
+
lines.push(`Skeleton ratio: ${sub.skeletonRatio} (${sub.skeletonEvidence ?? 'no evidence'})`);
|
|
70
|
+
}
|
|
71
|
+
return `${lines.join('\n')}\n`;
|
|
72
|
+
}
|
|
73
|
+
export function renderInspectorMarkdown(report) {
|
|
74
|
+
const lines = [
|
|
75
|
+
`# Inspector report - phase ${report.phase + 1} ${report.stage}`,
|
|
76
|
+
'',
|
|
77
|
+
report.summary,
|
|
78
|
+
'',
|
|
79
|
+
'## Contract checks',
|
|
80
|
+
list(report.contractChecks.map((c) => `${c.contractId}: ${c.status} - ${c.note}`)),
|
|
81
|
+
];
|
|
82
|
+
if (report.integration) {
|
|
83
|
+
lines.push('', `## Integration: ${report.integration.result}`, list(report.integration.commands.map((c) => `${c.cmd} (exit ${c.exit}): ${c.summary}`)));
|
|
84
|
+
}
|
|
85
|
+
if (report.rca) {
|
|
86
|
+
lines.push('', '## Root cause analysis', `boundary=${report.rca.boundary} systemic=${report.rca.systemic}`, list(report.rca.failingChecks.map((f) => `${f.name}: ${f.files.join(', ')}`)), list(report.rca.attributions.map((a) => `${a.agentId} ${a.level} ${a.contract}: ${a.note}`)));
|
|
87
|
+
}
|
|
88
|
+
if (report.escalations.length > 0) {
|
|
89
|
+
lines.push('', '## Escalations', list(report.escalations.map((e) => `${e.kind}: ${e.topic}`)));
|
|
90
|
+
}
|
|
91
|
+
return `${lines.join('\n')}\n`;
|
|
92
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gov_submit_work` / `gov_submit_review` / `gov_submit_test` - the engine side
|
|
3
|
+
* (governance-mode 5.5, 7.1, 7.3, 8.4).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
6
|
+
*
|
|
7
|
+
* THE TOOL LAYER NORMALIZES AND THIS MODULE RE-CHECKS. A tool that is mocked, replaced or
|
|
8
|
+
* called by a future code path must not be able to push an unvalidated object into the
|
|
9
|
+
* ledger; re-running the (pure, cheap) normalizer here makes that a property of the engine
|
|
10
|
+
* and not of the caller.
|
|
11
|
+
*
|
|
12
|
+
* EVERY REFUSAL IS TEXT, NEVER AN ERROR, NEVER A PENALTY (D-15). A malformed submission is
|
|
13
|
+
* a tool-layer mistake, not a character flaw: `errorResult` would invite an identical
|
|
14
|
+
* retry loop, and a deduction would punish an agent for a typo. Refusals are counted per
|
|
15
|
+
* (identity, wp); past `GOV_LIMITS.maxValidationFailures` the submission is treated as
|
|
16
|
+
* absent (the stage runner then re-dispatches once, then blocks the WP - still no penalty).
|
|
17
|
+
*
|
|
18
|
+
* ALL STATE CHANGES ARE SYNCHRONOUS. The model's tool call returns text; persistence is
|
|
19
|
+
* queued behind `EngineCore`'s write chain.
|
|
20
|
+
*/
|
|
21
|
+
import type { Identity } from '../types.js';
|
|
22
|
+
import type { EngineCore } from './engine-core.js';
|
|
23
|
+
export declare function handleSubmitWork(core: EngineCore, identity: Identity, input: unknown): string;
|
|
24
|
+
export declare function handleSubmitReview(core: EngineCore, identity: Identity, input: unknown): string;
|
|
25
|
+
export declare function handleSubmitTest(core: EngineCore, identity: Identity, input: unknown): string;
|
|
26
|
+
/** For tests and the stage runner: every accepted submission of one WP, any kind. */
|
|
27
|
+
export declare function acceptedSubmissionIds(core: EngineCore, wpId: string): string[];
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gov_submit_work` / `gov_submit_review` / `gov_submit_test` - the engine side
|
|
3
|
+
* (governance-mode 5.5, 7.1, 7.3, 8.4).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
6
|
+
*
|
|
7
|
+
* THE TOOL LAYER NORMALIZES AND THIS MODULE RE-CHECKS. A tool that is mocked, replaced or
|
|
8
|
+
* called by a future code path must not be able to push an unvalidated object into the
|
|
9
|
+
* ledger; re-running the (pure, cheap) normalizer here makes that a property of the engine
|
|
10
|
+
* and not of the caller.
|
|
11
|
+
*
|
|
12
|
+
* EVERY REFUSAL IS TEXT, NEVER AN ERROR, NEVER A PENALTY (D-15). A malformed submission is
|
|
13
|
+
* a tool-layer mistake, not a character flaw: `errorResult` would invite an identical
|
|
14
|
+
* retry loop, and a deduction would punish an agent for a typo. Refusals are counted per
|
|
15
|
+
* (identity, wp); past `GOV_LIMITS.maxValidationFailures` the submission is treated as
|
|
16
|
+
* absent (the stage runner then re-dispatches once, then blocks the WP - still no penalty).
|
|
17
|
+
*
|
|
18
|
+
* ALL STATE CHANGES ARE SYNCHRONOUS. The model's tool call returns text; persistence is
|
|
19
|
+
* queued behind `EngineCore`'s write chain.
|
|
20
|
+
*/
|
|
21
|
+
import { getLogger } from '../../logging/logger.js';
|
|
22
|
+
import { GOV_LIMITS } from '../limits.js';
|
|
23
|
+
import { settleReview, settleTest, settleWork } from '../rules/settlement.js';
|
|
24
|
+
import { detectOwnershipViolations } from '../rules/suspicion.js';
|
|
25
|
+
import { formatNotAccepted } from '../tools/normalize-util.js';
|
|
26
|
+
import { normalizeReviewSubmission, normalizeTestSubmission, normalizeWorkSubmission, } from '../tools/submit-normalizers.js';
|
|
27
|
+
import { applyEvents, emitLedger } from './aftermath.js';
|
|
28
|
+
import { artifactIdFor, nextAttempt, persistArtifact, STORAGE_EXHAUSTED_TEXT, } from './artifact-persist.js';
|
|
29
|
+
import { escalationCard } from './cards.js';
|
|
30
|
+
import { renderReviewMarkdown, renderTestMarkdown, renderWorkMarkdown, } from './submission-render.js';
|
|
31
|
+
import { latestReview, pendingAppeal, submissionsOf } from './submission-queries.js';
|
|
32
|
+
import { openEscalation, recordRework } from './state-machine.js';
|
|
33
|
+
const TOOL = {
|
|
34
|
+
work: 'gov_submit_work',
|
|
35
|
+
review: 'gov_submit_review',
|
|
36
|
+
test: 'gov_submit_test',
|
|
37
|
+
};
|
|
38
|
+
const ROLE_OF = { work: 'DEV', review: 'REV', test: 'TEST' };
|
|
39
|
+
const STAGES_OF = {
|
|
40
|
+
work: ['dev', 'fix'],
|
|
41
|
+
review: ['review'],
|
|
42
|
+
test: ['test'],
|
|
43
|
+
};
|
|
44
|
+
const NOT_ACCEPTED = 'NOT ACCEPTED:';
|
|
45
|
+
const NOT_ACCEPTED_ALREADY_TEXT = `${NOT_ACCEPTED} already submitted:`;
|
|
46
|
+
function ownerOf(wp, kind) {
|
|
47
|
+
if (kind === 'work')
|
|
48
|
+
return wp.assignee;
|
|
49
|
+
return kind === 'review' ? wp.reviewer : wp.tester;
|
|
50
|
+
}
|
|
51
|
+
function failureKey(identity, wpId) {
|
|
52
|
+
return `${identity.id}|${wpId}`;
|
|
53
|
+
}
|
|
54
|
+
/** Record one refusal and append the running count; never books anything. */
|
|
55
|
+
function refuse(core, identity, wpId, text) {
|
|
56
|
+
const key = failureKey(identity, wpId);
|
|
57
|
+
const count = (core.validationFailures.get(key) ?? 0) + 1;
|
|
58
|
+
core.validationFailures.set(key, count);
|
|
59
|
+
const left = GOV_LIMITS.maxValidationFailures - count;
|
|
60
|
+
const tail = left > 0
|
|
61
|
+
? `(${left} correction attempt(s) left for ${wpId || 'this call'}; nothing was recorded ` +
|
|
62
|
+
`and no GT was deducted.)`
|
|
63
|
+
: `Too many invalid submissions for ${wpId || 'this call'}: it is now treated as NOT ` +
|
|
64
|
+
`submitted ` +
|
|
65
|
+
'(no GT was deducted).';
|
|
66
|
+
return `${text}\n${tail}`;
|
|
67
|
+
}
|
|
68
|
+
/** Refusals that are not the agent's mistake and so do not burn a correction attempt. */
|
|
69
|
+
function uncounted(text) {
|
|
70
|
+
return text.startsWith(NOT_ACCEPTED_ALREADY_TEXT) || text === STORAGE_EXHAUSTED_TEXT;
|
|
71
|
+
}
|
|
72
|
+
function tooManyFailures(core, identity, wpId) {
|
|
73
|
+
return ((core.validationFailures.get(failureKey(identity, wpId)) ?? 0) >=
|
|
74
|
+
GOV_LIMITS.maxValidationFailures);
|
|
75
|
+
}
|
|
76
|
+
function resolveWpId(run, raw) {
|
|
77
|
+
if (run.workPackages[raw] !== undefined)
|
|
78
|
+
return raw;
|
|
79
|
+
const lower = raw.toLowerCase();
|
|
80
|
+
return run.workPackages[lower] !== undefined ? lower : undefined;
|
|
81
|
+
}
|
|
82
|
+
/** The legality gate shared by all three tools. Returns the context or a refusal text. */
|
|
83
|
+
function admit(core, identity, kind, rawWpId) {
|
|
84
|
+
const run = core.run;
|
|
85
|
+
if (run === null)
|
|
86
|
+
return `${NOT_ACCEPTED} no governance run is active.`;
|
|
87
|
+
if (identity.role !== ROLE_OF[kind]) {
|
|
88
|
+
return (`${NOT_ACCEPTED} ${TOOL[kind]} is for ${ROLE_OF[kind]} agents; you are ` + `${identity.role}.`);
|
|
89
|
+
}
|
|
90
|
+
if (core.storageExhausted)
|
|
91
|
+
return STORAGE_EXHAUSTED_TEXT;
|
|
92
|
+
const stage = run.stage;
|
|
93
|
+
if (stage === null || stage.status !== 'running' || !STAGES_OF[kind].includes(stage.kind)) {
|
|
94
|
+
const now = stage ? `${stage.kind} (${stage.status})` : 'none';
|
|
95
|
+
return (`${NOT_ACCEPTED} no ${STAGES_OF[kind].join('/')} stage is running (current stage: ` +
|
|
96
|
+
`${now}).`);
|
|
97
|
+
}
|
|
98
|
+
const wpId = resolveWpId(run, rawWpId);
|
|
99
|
+
const wp = wpId === undefined ? undefined : run.workPackages[wpId];
|
|
100
|
+
if (wp === undefined) {
|
|
101
|
+
return (`${NOT_ACCEPTED} unknown work package "${rawWpId}". Yours: ` +
|
|
102
|
+
`${assignedTo(run, identity, kind)}.`);
|
|
103
|
+
}
|
|
104
|
+
if (!stage.wpIds.includes(wp.id)) {
|
|
105
|
+
return (`${NOT_ACCEPTED} work package ${wp.id} is not part of this stage. Yours: ` +
|
|
106
|
+
`${assignedTo(run, identity, kind)}.`);
|
|
107
|
+
}
|
|
108
|
+
if (ownerOf(wp, kind) !== identity.id) {
|
|
109
|
+
return (`${NOT_ACCEPTED} work package ${wp.id} is not assigned to you. Yours: ` +
|
|
110
|
+
`${assignedTo(run, identity, kind)}.`);
|
|
111
|
+
}
|
|
112
|
+
if (core.stageSubmitted.has(wp.id)) {
|
|
113
|
+
return `${NOT_ACCEPTED_ALREADY_TEXT} ${wp.id} (one submission per work package per stage).`;
|
|
114
|
+
}
|
|
115
|
+
return { run, wp, stage: stage.kind };
|
|
116
|
+
}
|
|
117
|
+
function assignedTo(run, identity, kind) {
|
|
118
|
+
const ids = (run.stage?.wpIds ?? []).filter((id) => ownerOf(run.workPackages[id], kind) === identity.id);
|
|
119
|
+
return ids.length > 0 ? ids.join(', ') : '(none)';
|
|
120
|
+
}
|
|
121
|
+
/** `OK:` tail telling a multi-WP agent what is still owed. */
|
|
122
|
+
function remainingText(core, identity, kind) {
|
|
123
|
+
const run = core.requireRun();
|
|
124
|
+
const mine = (run.stage?.wpIds ?? []).filter((id) => ownerOf(run.workPackages[id], kind) === identity.id);
|
|
125
|
+
const owed = mine.filter((id) => !core.stageSubmitted.has(id));
|
|
126
|
+
if (owed.length === 0) {
|
|
127
|
+
core.setAgentState(identity.id, 'submitted');
|
|
128
|
+
return `All ${mine.length} of your work package(s) are submitted. You are done.`;
|
|
129
|
+
}
|
|
130
|
+
return (`${mine.length - owed.length} of ${mine.length} submitted. Still owed: ` + `${owed.join(', ')}.`);
|
|
131
|
+
}
|
|
132
|
+
function ctxFor(run, wp, at, appeal) {
|
|
133
|
+
return {
|
|
134
|
+
wp: {
|
|
135
|
+
id: wp.id,
|
|
136
|
+
assignee: wp.assignee,
|
|
137
|
+
reviewer: wp.reviewer,
|
|
138
|
+
tester: wp.tester,
|
|
139
|
+
rejections: wp.rejections,
|
|
140
|
+
files: wp.files,
|
|
141
|
+
},
|
|
142
|
+
contracts: run.contracts,
|
|
143
|
+
phase: wp.phase,
|
|
144
|
+
at,
|
|
145
|
+
...(appeal !== undefined ? { appealFindingIds: appeal } : {}),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function bookedFor(booked, agentId) {
|
|
149
|
+
if (agentId === null)
|
|
150
|
+
return '';
|
|
151
|
+
const parts = booked
|
|
152
|
+
.filter((b) => b.agentId === agentId && typeof b.entry.code === 'string' && b.entry.delta !== 0)
|
|
153
|
+
.map((b) => {
|
|
154
|
+
const sign = b.entry.delta > 0 ? '+' : '';
|
|
155
|
+
const pool = b.entry.settlement === 'provisional' ? 'P' : '';
|
|
156
|
+
return `${sign}${b.entry.delta}${pool} ${b.entry.code}`;
|
|
157
|
+
});
|
|
158
|
+
return parts.join(' ');
|
|
159
|
+
}
|
|
160
|
+
/** Apply a settlement's side effects that are common to all three kinds. */
|
|
161
|
+
function applySettlement(core, wp, result, at) {
|
|
162
|
+
const run = core.requireRun();
|
|
163
|
+
const booked = applyEvents(core, result.events, { phase: wp.phase });
|
|
164
|
+
for (const r of result.reversals) {
|
|
165
|
+
const op = core.ledgerOp({
|
|
166
|
+
op: 'reverse',
|
|
167
|
+
request: { agentId: r.agentId, code: r.code, wpId: r.wpId, at, reason: r.reason },
|
|
168
|
+
});
|
|
169
|
+
core.noteBooked(op.booked);
|
|
170
|
+
booked.push(...op.booked);
|
|
171
|
+
}
|
|
172
|
+
for (const fp of result.newRejections) {
|
|
173
|
+
if (!wp.rejections.includes(fp))
|
|
174
|
+
wp.rejections.push(fp);
|
|
175
|
+
}
|
|
176
|
+
for (const claim of result.claims) {
|
|
177
|
+
if (!run.claims.some((c) => c.id === claim.id)) {
|
|
178
|
+
run.claims.push(claim);
|
|
179
|
+
core.log('claim', claim.raisedBy, { claim });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return booked;
|
|
183
|
+
}
|
|
184
|
+
function openRework(core, run, wp) {
|
|
185
|
+
const outcome = recordRework(run, wp.id);
|
|
186
|
+
if (outcome.escalation === null)
|
|
187
|
+
return `rework ${outcome.reworkCount}/${outcome.limit}`;
|
|
188
|
+
openEscalation(run, outcome.escalation);
|
|
189
|
+
core.log('escalation', 'engine', { escalation: outcome.escalation });
|
|
190
|
+
core.stageWarnings.push(`ESCALATION ${outcome.escalation.id}: ${wp.id} reached the rework limit and is blocked; ` +
|
|
191
|
+
`ask the user, then gov_run resolve.`);
|
|
192
|
+
core.emit((snapshot) => ({
|
|
193
|
+
type: 'escalation',
|
|
194
|
+
snapshot,
|
|
195
|
+
escalation: outcome.escalation,
|
|
196
|
+
card: escalationCard(core, outcome.escalation),
|
|
197
|
+
}));
|
|
198
|
+
return `blocked: rework limit ${outcome.limit} exceeded`;
|
|
199
|
+
}
|
|
200
|
+
function checkOwnership(core, identity, wp) {
|
|
201
|
+
const touched = core.touchedFiles.get(identity.id);
|
|
202
|
+
if (touched === undefined || touched.length === 0)
|
|
203
|
+
return;
|
|
204
|
+
const stray = detectOwnershipViolations(touched, wp.files);
|
|
205
|
+
if (stray.length === 0)
|
|
206
|
+
return;
|
|
207
|
+
core.log('ownership_violation', identity.id, { wpId: wp.id, files: stray });
|
|
208
|
+
core.stageWarnings.push(`${identity.id} touched files outside ${wp.id}: ${stray.slice(0, 5).join(', ')}.`);
|
|
209
|
+
}
|
|
210
|
+
function finish(core, kind, identity, wpId) {
|
|
211
|
+
core.stageSubmitted.add(wpId);
|
|
212
|
+
core.log('submission', identity.id, { kind, wpId });
|
|
213
|
+
core.checkpoint();
|
|
214
|
+
core.emit((snapshot) => ({ type: 'submission', snapshot, identity: identity.id, wpId, kind }));
|
|
215
|
+
emitLedger(core);
|
|
216
|
+
}
|
|
217
|
+
/** Wrap a handler so a bug becomes a `NOT ACCEPTED` text and a log line, never a throw. */
|
|
218
|
+
function guarded(tool, fn) {
|
|
219
|
+
try {
|
|
220
|
+
return fn();
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
224
|
+
getLogger().warn('agent', 'governance.submission_internal_error', { tool, message });
|
|
225
|
+
return (`${NOT_ACCEPTED} internal error while recording ${tool} (${message}). Nothing was ` +
|
|
226
|
+
`recorded; try once more.`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function normalizeOrRefuse(core, identity, kind, normalized, fallbackWpId) {
|
|
230
|
+
if (normalized.ok)
|
|
231
|
+
return normalized.value;
|
|
232
|
+
return refuse(core, identity, fallbackWpId, formatNotAccepted(TOOL[kind], normalized.errors));
|
|
233
|
+
}
|
|
234
|
+
/** Normalize, admit and rate-limit one call; a string result is the refusal to return. */
|
|
235
|
+
function intake(core, identity, kind, input, normalized) {
|
|
236
|
+
const value = normalizeOrRefuse(core, identity, kind, normalized, rawWpId(input));
|
|
237
|
+
if (typeof value === 'string')
|
|
238
|
+
return value;
|
|
239
|
+
const admitted = admit(core, identity, kind, value.wpId);
|
|
240
|
+
if (typeof admitted === 'string') {
|
|
241
|
+
return uncounted(admitted) ? admitted : refuse(core, identity, value.wpId, admitted);
|
|
242
|
+
}
|
|
243
|
+
if (tooManyFailures(core, identity, admitted.wp.id)) {
|
|
244
|
+
return (`${NOT_ACCEPTED} too many invalid submissions for ${admitted.wp.id}; ` +
|
|
245
|
+
'it is treated as not submitted.');
|
|
246
|
+
}
|
|
247
|
+
return { value, ...admitted };
|
|
248
|
+
}
|
|
249
|
+
function rawWpId(input) {
|
|
250
|
+
if (input !== null && typeof input === 'object') {
|
|
251
|
+
const v = input.wpId;
|
|
252
|
+
if (typeof v === 'string')
|
|
253
|
+
return v;
|
|
254
|
+
}
|
|
255
|
+
return '';
|
|
256
|
+
}
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Work
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
function validateAppeal(core, wp, sub) {
|
|
261
|
+
if (sub.appeal === undefined)
|
|
262
|
+
return null;
|
|
263
|
+
if (sub.stage === 'fix')
|
|
264
|
+
return (`${NOT_ACCEPTED} an appeal is only possible against a review rejection, not in a ` +
|
|
265
|
+
`fix stage.`);
|
|
266
|
+
if (wp.appealUsed)
|
|
267
|
+
return `${NOT_ACCEPTED} you already used your one appeal for ${wp.id}.`;
|
|
268
|
+
const review = latestReview(core, wp.id);
|
|
269
|
+
if (review === undefined || review.verdict !== 'NEEDS_REWORK') {
|
|
270
|
+
return `${NOT_ACCEPTED} there is no rejection of ${wp.id} to appeal.`;
|
|
271
|
+
}
|
|
272
|
+
const valid = new Set(review.findings.map((f) => f.id));
|
|
273
|
+
const unknown = sub.appeal.findingIds.filter((id) => !valid.has(id));
|
|
274
|
+
if (unknown.length > 0) {
|
|
275
|
+
return (`${NOT_ACCEPTED} unknown finding id(s) ${unknown.join(', ')}. Valid: ` +
|
|
276
|
+
`${[...valid].join(', ') || '(none)'}.`);
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
export function handleSubmitWork(core, identity, input) {
|
|
281
|
+
return guarded(TOOL.work, () => {
|
|
282
|
+
const got = intake(core, identity, 'work', input, normalizeWorkSubmission(input));
|
|
283
|
+
if (typeof got === 'string')
|
|
284
|
+
return got;
|
|
285
|
+
const { value: early, run, wp, stage } = got;
|
|
286
|
+
const at = core.now();
|
|
287
|
+
const target = { phase: wp.phase, wpId: wp.id, stage, identity: identity.id };
|
|
288
|
+
const sub = {
|
|
289
|
+
kind: 'work',
|
|
290
|
+
id: artifactIdFor(core, target),
|
|
291
|
+
identity: identity.id,
|
|
292
|
+
wpId: wp.id,
|
|
293
|
+
phase: wp.phase,
|
|
294
|
+
stage: stage === 'fix' ? 'fix' : 'dev',
|
|
295
|
+
attempt: nextAttempt(core, target),
|
|
296
|
+
at,
|
|
297
|
+
summary: early.summary,
|
|
298
|
+
filesChanged: early.filesChanged,
|
|
299
|
+
interfaceContracts: early.interfaceContracts,
|
|
300
|
+
verification: early.verification,
|
|
301
|
+
...(early.appeal ? { appeal: early.appeal } : {}),
|
|
302
|
+
...(early.escalate !== undefined ? { escalate: early.escalate } : {}),
|
|
303
|
+
};
|
|
304
|
+
const problem = validateAppeal(core, wp, sub);
|
|
305
|
+
if (problem !== null)
|
|
306
|
+
return refuse(core, identity, wp.id, problem);
|
|
307
|
+
const result = settleWork(ctxFor(run, wp, at), sub);
|
|
308
|
+
persistArtifact(core, target, sub.id, sub, renderWorkMarkdown(sub));
|
|
309
|
+
wp.submissions[stage].push(sub.id);
|
|
310
|
+
const booked = applySettlement(core, wp, result, at);
|
|
311
|
+
if (sub.appeal)
|
|
312
|
+
wp.appealUsed = true;
|
|
313
|
+
if (sub.escalate === true)
|
|
314
|
+
raiseDeveloperClaim(core, run, wp, sub);
|
|
315
|
+
if (stage === 'dev')
|
|
316
|
+
wp.status = 'awaiting_review';
|
|
317
|
+
checkOwnership(core, identity, wp);
|
|
318
|
+
core.stageResults.push(workResultLine(sub, booked));
|
|
319
|
+
finish(core, 'work', identity, wp.id);
|
|
320
|
+
const notes = result.notes.length > 0 ? `\n${result.notes.join('\n')}` : '';
|
|
321
|
+
return (`OK: work for ${wp.id} ` +
|
|
322
|
+
`recorded${stage === 'dev' ? '; it now goes to independent review' : ''}. ` +
|
|
323
|
+
`${remainingText(core, identity, 'work')}${notes}`);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function workResultLine(sub, booked) {
|
|
327
|
+
const what = sub.stage === 'fix' ? 'a fix' : 'work';
|
|
328
|
+
const files = `${sub.filesChanged.length} file(s)`;
|
|
329
|
+
const appeal = sub.appeal ? ', with an appeal' : '';
|
|
330
|
+
const delta = bookedFor(booked, sub.identity);
|
|
331
|
+
return `${sub.wpId}: ${sub.identity} submitted ${what} (${files})${appeal}${delta ? ` [${delta}]` : ''}`;
|
|
332
|
+
}
|
|
333
|
+
/** `escalate: true` after a lost appeal: the developer asks the inspector to arbitrate (L2). */
|
|
334
|
+
function raiseDeveloperClaim(core, run, wp, sub) {
|
|
335
|
+
const reviewer = wp.reviewer;
|
|
336
|
+
if (reviewer === null)
|
|
337
|
+
return;
|
|
338
|
+
const id = `cl-${wp.id}-e${sub.attempt}`;
|
|
339
|
+
if (run.claims.some((c) => c.id === id))
|
|
340
|
+
return;
|
|
341
|
+
run.claims.push({
|
|
342
|
+
id,
|
|
343
|
+
kind: 'false_positive',
|
|
344
|
+
raisedBy: sub.identity,
|
|
345
|
+
against: reviewer,
|
|
346
|
+
wpId: wp.id,
|
|
347
|
+
justification: sub.appeal?.argument ?? sub.summary,
|
|
348
|
+
status: 'open',
|
|
349
|
+
phase: wp.phase,
|
|
350
|
+
});
|
|
351
|
+
core.log('claim', sub.identity, { claimId: id, kind: 'false_positive', wpId: wp.id });
|
|
352
|
+
}
|
|
353
|
+
export function handleSubmitReview(core, identity, input) {
|
|
354
|
+
return guarded(TOOL.review, () => {
|
|
355
|
+
const got = intake(core, identity, 'review', input, normalizeReviewSubmission(input));
|
|
356
|
+
if (typeof got === 'string')
|
|
357
|
+
return got;
|
|
358
|
+
const { value: early, run, wp } = got;
|
|
359
|
+
const at = core.now();
|
|
360
|
+
const target = {
|
|
361
|
+
phase: wp.phase,
|
|
362
|
+
wpId: wp.id,
|
|
363
|
+
stage: 'review',
|
|
364
|
+
identity: identity.id,
|
|
365
|
+
};
|
|
366
|
+
const sub = {
|
|
367
|
+
kind: 'review',
|
|
368
|
+
id: artifactIdFor(core, target),
|
|
369
|
+
identity: identity.id,
|
|
370
|
+
wpId: wp.id,
|
|
371
|
+
phase: wp.phase,
|
|
372
|
+
attempt: nextAttempt(core, target),
|
|
373
|
+
at,
|
|
374
|
+
verdict: early.verdict,
|
|
375
|
+
checklist: early.checklist,
|
|
376
|
+
findings: early.findings,
|
|
377
|
+
fixesApplied: early.fixesApplied,
|
|
378
|
+
integrationAssessment: early.integrationAssessment,
|
|
379
|
+
appealResponses: early.appealResponses,
|
|
380
|
+
requiredChanges: early.requiredChanges,
|
|
381
|
+
...(early.excellence ? { excellence: early.excellence } : {}),
|
|
382
|
+
...(early.securityFinding ? { securityFinding: early.securityFinding } : {}),
|
|
383
|
+
};
|
|
384
|
+
const appeal = pendingAppeal(core, wp.id);
|
|
385
|
+
const result = settleReview(ctxFor(run, wp, at, appeal?.findingIds), sub);
|
|
386
|
+
if (!result.ok)
|
|
387
|
+
return refuse(core, identity, wp.id, result.refusal ?? `${NOT_ACCEPTED} invalid review.`);
|
|
388
|
+
persistArtifact(core, target, sub.id, sub, renderReviewMarkdown(sub));
|
|
389
|
+
wp.submissions.review.push(sub.id);
|
|
390
|
+
const booked = applySettlement(core, wp, result, at);
|
|
391
|
+
let routed = 'to test';
|
|
392
|
+
if (result.route?.to === 'dev')
|
|
393
|
+
routed = openRework(core, run, wp);
|
|
394
|
+
else
|
|
395
|
+
wp.status = 'awaiting_test';
|
|
396
|
+
core.stageResults.push(`${wp.id}: ${identity.id} ${sub.verdict} -> ${routed}${bookedFor(booked, wp.assignee) ? ` (${wp.assignee} ${bookedFor(booked, wp.assignee)})` : ''}`);
|
|
397
|
+
finish(core, 'review', identity, wp.id);
|
|
398
|
+
const notes = result.notes.length > 0 ? `\n${result.notes.join('\n')}` : '';
|
|
399
|
+
return (`OK: review of ${wp.id} recorded (${sub.verdict}). ` +
|
|
400
|
+
`${remainingText(core, identity, 'review')}${notes}`);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
export function handleSubmitTest(core, identity, input) {
|
|
404
|
+
return guarded(TOOL.test, () => {
|
|
405
|
+
const got = intake(core, identity, 'test', input, normalizeTestSubmission(input));
|
|
406
|
+
if (typeof got === 'string')
|
|
407
|
+
return got;
|
|
408
|
+
const { value: early, run, wp } = got;
|
|
409
|
+
const at = core.now();
|
|
410
|
+
const target = {
|
|
411
|
+
phase: wp.phase,
|
|
412
|
+
wpId: wp.id,
|
|
413
|
+
stage: 'test',
|
|
414
|
+
identity: identity.id,
|
|
415
|
+
};
|
|
416
|
+
const sub = {
|
|
417
|
+
kind: 'test',
|
|
418
|
+
id: artifactIdFor(core, target),
|
|
419
|
+
identity: identity.id,
|
|
420
|
+
wpId: wp.id,
|
|
421
|
+
phase: wp.phase,
|
|
422
|
+
attempt: nextAttempt(core, target),
|
|
423
|
+
at,
|
|
424
|
+
verdict: early.verdict,
|
|
425
|
+
traceability: early.traceability,
|
|
426
|
+
commands: early.commands,
|
|
427
|
+
issues: early.issues,
|
|
428
|
+
boundaryTestsWritten: early.boundaryTestsWritten,
|
|
429
|
+
claims: early.claims,
|
|
430
|
+
...(early.skeletonRatio !== undefined ? { skeletonRatio: early.skeletonRatio } : {}),
|
|
431
|
+
...(early.skeletonEvidence !== undefined ? { skeletonEvidence: early.skeletonEvidence } : {}),
|
|
432
|
+
};
|
|
433
|
+
const result = settleTest(ctxFor(run, wp, at), sub);
|
|
434
|
+
if (!result.ok)
|
|
435
|
+
return refuse(core, identity, wp.id, result.refusal ?? `${NOT_ACCEPTED} invalid test report.`);
|
|
436
|
+
persistArtifact(core, target, sub.id, sub, renderTestMarkdown(sub));
|
|
437
|
+
wp.submissions.test.push(sub.id);
|
|
438
|
+
const booked = applySettlement(core, wp, result, at);
|
|
439
|
+
let routed = 'passed';
|
|
440
|
+
if (result.route?.to === 'dev')
|
|
441
|
+
routed = openRework(core, run, wp);
|
|
442
|
+
else
|
|
443
|
+
wp.status = 'tested_ok';
|
|
444
|
+
checkOwnership(core, identity, wp);
|
|
445
|
+
core.stageResults.push(`${wp.id}: ${identity.id} ${sub.verdict} -> ${routed}${bookedFor(booked, wp.assignee) ? ` (${wp.assignee} ${bookedFor(booked, wp.assignee)})` : ''}`);
|
|
446
|
+
finish(core, 'test', identity, wp.id);
|
|
447
|
+
const notes = result.notes.length > 0 ? `\n${result.notes.join('\n')}` : '';
|
|
448
|
+
return (`OK: test report for ${wp.id} recorded (${sub.verdict}). ` +
|
|
449
|
+
`${remainingText(core, identity, 'test')}${notes}`);
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
/** For tests and the stage runner: every accepted submission of one WP, any kind. */
|
|
453
|
+
export function acceptedSubmissionIds(core, wpId) {
|
|
454
|
+
return ['work', 'review', 'test'].flatMap((k) => submissionsOf(core, wpId, k).map((s) => s.id));
|
|
455
|
+
}
|