@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,664 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gov_dispatch`: run one stage of the current phase (governance-mode 7.2, 8.2-8.4).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
5
|
+
*
|
|
6
|
+
* THE DIVISION OF LABOUR. This module owns everything BEFORE and AFTER the children run:
|
|
7
|
+
* legality, budget, assignment, briefs, the stage record, the single automatic redispatch of
|
|
8
|
+
* whatever came back without a submission, the pause on abort, and the digest. It owns NONE
|
|
9
|
+
* of the judgement: when a child calls `gov_submit_*` / `ledger_*` the adjudication half
|
|
10
|
+
* (`submissions.ts`, `inspector.ts`) books the ledger and routes the work package
|
|
11
|
+
* synchronously inside that tool call. That is why a stage that is aborted half way keeps
|
|
12
|
+
* every submission already made, and why "resume re-runs only what is missing" needs no
|
|
13
|
+
* bookkeeping here: a routed WP is simply no longer eligible for the stage.
|
|
14
|
+
*
|
|
15
|
+
* A MISSING SUBMISSION IS NOT A MISDEED (8.4). Infrastructure failures, a model that
|
|
16
|
+
* wandered off and a context that ran out all look the same from here, so the first answer
|
|
17
|
+
* is one reminder-bearing redispatch of only the missing WPs, and the second is `blocked`
|
|
18
|
+
* with `blockedAt` set to this stage - never a deduction.
|
|
19
|
+
*/
|
|
20
|
+
import { GOV_LIMITS } from '../limits.js';
|
|
21
|
+
import { assignRoundRobin } from '../roles.js';
|
|
22
|
+
import { detectOwnershipViolations } from '../rules/suspicion.js';
|
|
23
|
+
import { activeAgents } from '../rules/ledger-state.js';
|
|
24
|
+
import { formatNotAccepted, isRecord, Issues } from '../tools/normalize-util.js';
|
|
25
|
+
import { STAGE_KINDS } from '../types.js';
|
|
26
|
+
import { accumulateUsage, shouldStopNewStages } from './budget.js';
|
|
27
|
+
import { composeDigest, refusal } from './digest.js';
|
|
28
|
+
import { openBudgetEscalation } from './lifecycle.js';
|
|
29
|
+
import { applyPendingPause, pausedByUserRefusal } from './pause.js';
|
|
30
|
+
import { recoverInterruptedStage } from './rebuild.js';
|
|
31
|
+
import { renderLedgerLine } from './report.js';
|
|
32
|
+
import { buildSubagentSpec } from './spec-builder.js';
|
|
33
|
+
import { checkStage, markBlocked } from './state-machine.js';
|
|
34
|
+
import { changeListOf, latestReview, latestTest, latestWork, pendingAppeal, reviewConclusion, reworkItemsFor, } from './submission-queries.js';
|
|
35
|
+
const INSPECTOR_IDENTITY = 'insp';
|
|
36
|
+
const PAUSE_RESUME_HINT = 'PAUSED: the stage was interrupted. Completed submissions were kept.';
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Input
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
function parseDispatchInput(raw) {
|
|
41
|
+
const issues = new Issues();
|
|
42
|
+
const args = isRecord(raw) ? raw : {};
|
|
43
|
+
const stage = issues.oneOf(args.stage, 'stage', STAGE_KINDS, { required: true });
|
|
44
|
+
const wps = issues
|
|
45
|
+
.list(args.wps, 'wps', 16, GOV_LIMITS.wpIdChars * 4)
|
|
46
|
+
.map((w) => w.toLowerCase());
|
|
47
|
+
const notes = {};
|
|
48
|
+
if (isRecord(args.notes)) {
|
|
49
|
+
for (const [key, value] of Object.entries(args.notes)) {
|
|
50
|
+
const text = typeof value === 'string' ? value.trim() : '';
|
|
51
|
+
if (text !== '')
|
|
52
|
+
notes[key.toLowerCase()] = text.slice(0, GOV_LIMITS.noteChars);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (issues.errors.length > 0 || stage === undefined) {
|
|
56
|
+
return { ok: false, errors: issues.errors.length > 0 ? issues.errors : ['stage is required.'] };
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
ok: true,
|
|
60
|
+
value: {
|
|
61
|
+
stage,
|
|
62
|
+
...(wps.length > 0 ? { wps } : {}),
|
|
63
|
+
...(Object.keys(notes).length > 0 ? { notes } : {}),
|
|
64
|
+
},
|
|
65
|
+
notes: issues.notes,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Assignment
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
/** Follow `replacedBy` links so a stale assignee id still resolves to a live identity. */
|
|
72
|
+
function liveIdentity(core, id) {
|
|
73
|
+
let current = id;
|
|
74
|
+
for (let hops = 0; hops < 8; hops += 1) {
|
|
75
|
+
const record = core.ledger.agents[current];
|
|
76
|
+
if (record === undefined || record.replacedBy === null)
|
|
77
|
+
return current;
|
|
78
|
+
current = record.replacedBy;
|
|
79
|
+
}
|
|
80
|
+
return current;
|
|
81
|
+
}
|
|
82
|
+
function takeoverMap(core) {
|
|
83
|
+
const out = {};
|
|
84
|
+
for (const agent of Object.values(core.ledger.agents)) {
|
|
85
|
+
if (agent.replacedBy !== null)
|
|
86
|
+
out[agent.id] = agent.replacedBy;
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
function groupDevs(core, run, wpIds) {
|
|
91
|
+
const byIdentity = new Map();
|
|
92
|
+
for (const id of wpIds) {
|
|
93
|
+
const wp = run.workPackages[id];
|
|
94
|
+
if (wp === undefined)
|
|
95
|
+
continue;
|
|
96
|
+
const identity = liveIdentity(core, wp.assignee ?? 'dev1');
|
|
97
|
+
wp.assignee = identity;
|
|
98
|
+
byIdentity.set(identity, [...(byIdentity.get(identity) ?? []), id]);
|
|
99
|
+
}
|
|
100
|
+
return [...byIdentity.entries()].map(([identity, ids]) => ({
|
|
101
|
+
identity,
|
|
102
|
+
role: 'DEV',
|
|
103
|
+
wpIds: ids,
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
function groupByRole(core, run, role, wpIds) {
|
|
107
|
+
const ceiling = core.deps.getTeamCeiling();
|
|
108
|
+
const identities = activeAgents(core.ledger, role)
|
|
109
|
+
.map((a) => a.id)
|
|
110
|
+
.slice(0, Math.max(1, ceiling));
|
|
111
|
+
const sticky = {};
|
|
112
|
+
for (const id of wpIds) {
|
|
113
|
+
const wp = run.workPackages[id];
|
|
114
|
+
const prior = role === 'REV' ? wp?.reviewer : wp?.tester;
|
|
115
|
+
if (prior)
|
|
116
|
+
sticky[id] = prior;
|
|
117
|
+
}
|
|
118
|
+
const assignments = assignRoundRobin({
|
|
119
|
+
wpIds,
|
|
120
|
+
identities,
|
|
121
|
+
sticky,
|
|
122
|
+
takeover: takeoverMap(core),
|
|
123
|
+
});
|
|
124
|
+
for (const a of assignments) {
|
|
125
|
+
for (const id of a.wpIds) {
|
|
126
|
+
const wp = run.workPackages[id];
|
|
127
|
+
if (wp === undefined)
|
|
128
|
+
continue;
|
|
129
|
+
if (role === 'REV')
|
|
130
|
+
wp.reviewer = a.identity;
|
|
131
|
+
else
|
|
132
|
+
wp.tester = a.identity;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return assignments.map((a) => ({ identity: a.identity, role, wpIds: a.wpIds }));
|
|
136
|
+
}
|
|
137
|
+
/** The newest integrate report of `phase` that carries an RCA (from the artifact cache). */
|
|
138
|
+
function latestRca(core, phase) {
|
|
139
|
+
let best;
|
|
140
|
+
for (const item of core.submissionCache.values()) {
|
|
141
|
+
if (item.kind !== 'inspector' || item.stage !== 'integrate')
|
|
142
|
+
continue;
|
|
143
|
+
if (item.phase !== phase.index || item.rca === undefined)
|
|
144
|
+
continue;
|
|
145
|
+
if (best === undefined || item.at >= best.at)
|
|
146
|
+
best = item;
|
|
147
|
+
}
|
|
148
|
+
return best?.rca;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The WPs a `fix` stage works on: the ones whose DEV the last RCA names as primary or
|
|
152
|
+
* contributing (oversight reviewers and testers do not write code), else the whole phase.
|
|
153
|
+
*/
|
|
154
|
+
function fixTargets(core, run, phase, requested) {
|
|
155
|
+
if (requested.length > 0)
|
|
156
|
+
return [...requested];
|
|
157
|
+
const live = phase.wpIds.filter((id) => run.workPackages[id]?.status !== 'dropped');
|
|
158
|
+
const rca = latestRca(core, phase);
|
|
159
|
+
if (rca === undefined)
|
|
160
|
+
return live;
|
|
161
|
+
const devs = new Set(rca.attributions
|
|
162
|
+
.filter((a) => a.level !== 'oversight')
|
|
163
|
+
.map((a) => liveIdentity(core, a.agentId)));
|
|
164
|
+
const named = live.filter((id) => devs.has(liveIdentity(core, run.workPackages[id]?.assignee ?? '')));
|
|
165
|
+
return named.length > 0 ? named : live;
|
|
166
|
+
}
|
|
167
|
+
/** The RCA as prose for the fix brief (the failing checks, the boundary, who is attributed). */
|
|
168
|
+
function rcaText(core, phase) {
|
|
169
|
+
const rca = latestRca(core, phase);
|
|
170
|
+
if (rca === undefined)
|
|
171
|
+
return undefined;
|
|
172
|
+
const checks = rca.failingChecks.map((c) => `${c.name} (${c.files.join(', ') || 'no files'})`);
|
|
173
|
+
const who = rca.attributions.map((a) => `${a.agentId} ${a.level}/${a.contract}: ${a.note}`);
|
|
174
|
+
return [
|
|
175
|
+
`Failing checks: ${checks.join('; ') || '(none recorded)'}`,
|
|
176
|
+
`Module boundary failure: ${rca.boundary ? 'yes' : 'no'}; ` +
|
|
177
|
+
`systemic: ${rca.systemic ? 'yes' : 'no'}`,
|
|
178
|
+
`Attribution: ${who.join(' | ') || '(none)'}`,
|
|
179
|
+
]
|
|
180
|
+
.join('\n')
|
|
181
|
+
.slice(0, 1800);
|
|
182
|
+
}
|
|
183
|
+
function buildGroups(core, run, stage, phase, wpIds) {
|
|
184
|
+
switch (stage) {
|
|
185
|
+
case 'dev':
|
|
186
|
+
return groupDevs(core, run, wpIds);
|
|
187
|
+
case 'fix':
|
|
188
|
+
return groupDevs(core, run, fixTargets(core, run, phase, wpIds));
|
|
189
|
+
case 'review':
|
|
190
|
+
return groupByRole(core, run, 'REV', wpIds);
|
|
191
|
+
case 'test':
|
|
192
|
+
return groupByRole(core, run, 'TEST', wpIds);
|
|
193
|
+
case 'settle':
|
|
194
|
+
case 'integrate':
|
|
195
|
+
return [
|
|
196
|
+
{
|
|
197
|
+
identity: INSPECTOR_IDENTITY,
|
|
198
|
+
role: 'INSP',
|
|
199
|
+
wpIds: phase.wpIds.filter((id) => run.workPackages[id]?.status !== 'dropped'),
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Briefs
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
function predecessorOf(core, identity) {
|
|
208
|
+
return Object.values(core.ledger.agents).find((a) => a.replacedBy === identity);
|
|
209
|
+
}
|
|
210
|
+
function digestFindings(wp, core) {
|
|
211
|
+
const review = latestReview(core, wp.id);
|
|
212
|
+
if (review === undefined)
|
|
213
|
+
return 'no review on record';
|
|
214
|
+
const findings = review.findings
|
|
215
|
+
.slice(0, 6)
|
|
216
|
+
.map((f) => `[${f.severity}] ${f.file}${f.line ? `:${f.line}` : ''} ${f.issue}`);
|
|
217
|
+
return `${review.verdict} by ${review.identity}; ${review.findings.length} finding(s)${findings.length > 0 ? `: ${findings.join(' | ')}` : ''}`.slice(0, 900);
|
|
218
|
+
}
|
|
219
|
+
function digestTest(wp, core) {
|
|
220
|
+
const test = latestTest(core, wp.id);
|
|
221
|
+
if (test === undefined)
|
|
222
|
+
return 'no test report on record';
|
|
223
|
+
const gaps = test.traceability.filter((t) => t.status !== 'covered').length;
|
|
224
|
+
return (`${test.verdict} by ${test.identity}; ${test.traceability.length} requirement(s), ` +
|
|
225
|
+
`${gaps} gap/unverified; ${test.issues.length} issue(s); ${test.claims.length} claim(s); ` +
|
|
226
|
+
`boundary tests ${test.boundaryTestsWritten ? 'written' : 'not written'}`).slice(0, 600);
|
|
227
|
+
}
|
|
228
|
+
function autoEventLines(core, phaseIndex) {
|
|
229
|
+
const rows = [];
|
|
230
|
+
for (const agent of Object.values(core.ledger.agents)) {
|
|
231
|
+
for (const entry of agent.history) {
|
|
232
|
+
if (entry.phase !== phaseIndex || entry.by !== 'engine')
|
|
233
|
+
continue;
|
|
234
|
+
if (typeof entry.code !== 'string' || entry.delta === 0)
|
|
235
|
+
continue;
|
|
236
|
+
rows.push({ seq: entry.seq, text: renderLedgerLine(entry, agent.id) });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return rows
|
|
240
|
+
.sort((a, b) => a.seq - b.seq)
|
|
241
|
+
.slice(-40)
|
|
242
|
+
.map((r) => r.text);
|
|
243
|
+
}
|
|
244
|
+
function inspectorMaterial(core, run, ctx) {
|
|
245
|
+
const wps = ctx.groups[0]?.wpIds.map((id) => run.workPackages[id]).filter((w) => w !== undefined) ?? [];
|
|
246
|
+
const reviewDigest = {};
|
|
247
|
+
const testDigest = {};
|
|
248
|
+
for (const wp of wps) {
|
|
249
|
+
reviewDigest[wp.id] = digestFindings(wp, core);
|
|
250
|
+
testDigest[wp.id] = digestTest(wp, core);
|
|
251
|
+
}
|
|
252
|
+
const health = core.health();
|
|
253
|
+
return {
|
|
254
|
+
autoEvents: autoEventLines(core, ctx.phase.index),
|
|
255
|
+
openClaims: run.claims
|
|
256
|
+
.filter((c) => c.status === 'open')
|
|
257
|
+
.map((c) => ({
|
|
258
|
+
id: c.id,
|
|
259
|
+
kind: c.kind,
|
|
260
|
+
raisedBy: c.raisedBy,
|
|
261
|
+
against: c.against,
|
|
262
|
+
wpId: c.wpId,
|
|
263
|
+
text: c.justification.slice(0, 300),
|
|
264
|
+
})),
|
|
265
|
+
flags: core.phaseFlags.get(ctx.phase.index) ?? [],
|
|
266
|
+
reviewDigest,
|
|
267
|
+
testDigest,
|
|
268
|
+
integrationAttempt: ctx.phase.integrationAttempts,
|
|
269
|
+
healthWarning: health !== null && health < GOV_LIMITS.healthWarnThreshold,
|
|
270
|
+
viewEvents: core.viewLog.get(ctx.phase.index) ?? [],
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function briefFor(core, run, ctx, group, missingOnly) {
|
|
274
|
+
const wps = group.wpIds
|
|
275
|
+
.map((id) => run.workPackages[id])
|
|
276
|
+
.filter((w) => w !== undefined);
|
|
277
|
+
const record = core.ledger.agents[group.identity];
|
|
278
|
+
const predecessor = predecessorOf(core, group.identity);
|
|
279
|
+
const failure = predecessor ? core.ledger.failureAnalyses[predecessor.id] : undefined;
|
|
280
|
+
const peers = ctx.groups.filter((g) => g.identity !== group.identity).map((g) => g.identity);
|
|
281
|
+
const brief = {
|
|
282
|
+
run: {
|
|
283
|
+
runId: run.runId,
|
|
284
|
+
goal: run.goal,
|
|
285
|
+
profile: run.profile,
|
|
286
|
+
spec: ctx.specText,
|
|
287
|
+
contracts: run.contracts,
|
|
288
|
+
integrationCommands: run.integrationCommands,
|
|
289
|
+
},
|
|
290
|
+
identity: group.identity,
|
|
291
|
+
role: group.role,
|
|
292
|
+
generation: record?.generation ?? 0,
|
|
293
|
+
tokens: record ? record.confirmed + record.provisional : 0,
|
|
294
|
+
phase: ctx.phase.index + 1,
|
|
295
|
+
stage: ctx.stage,
|
|
296
|
+
wps,
|
|
297
|
+
peers,
|
|
298
|
+
...(failure ? { predecessor: failure } : {}),
|
|
299
|
+
...(missingOnly && missingOnly.length > 0 ? { missingOnly: [...missingOnly] } : {}),
|
|
300
|
+
};
|
|
301
|
+
return addRoleMaterial(core, run, ctx, brief);
|
|
302
|
+
}
|
|
303
|
+
function withRca(text) {
|
|
304
|
+
return text === undefined ? {} : { rcaText: text };
|
|
305
|
+
}
|
|
306
|
+
function addRoleMaterial(core, run, ctx, brief) {
|
|
307
|
+
const ids = brief.wps.map((w) => w.id);
|
|
308
|
+
switch (ctx.stage) {
|
|
309
|
+
case 'dev':
|
|
310
|
+
case 'fix': {
|
|
311
|
+
const requiredChanges = {};
|
|
312
|
+
const previousOwn = {};
|
|
313
|
+
for (const id of ids) {
|
|
314
|
+
const items = ctx.stage === 'dev' ? reworkItemsFor(core, id) : [];
|
|
315
|
+
if (items.length > 0)
|
|
316
|
+
requiredChanges[id] = items;
|
|
317
|
+
const own = latestWork(core, id);
|
|
318
|
+
if (own)
|
|
319
|
+
previousOwn[id] = own.summary.slice(0, 600);
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
...brief,
|
|
323
|
+
...(Object.keys(requiredChanges).length > 0 ? { requiredChanges } : {}),
|
|
324
|
+
...(Object.keys(previousOwn).length > 0 ? { previousOwn } : {}),
|
|
325
|
+
...(Object.keys(ctx.notes).length > 0 ? { notes: ctx.notes } : {}),
|
|
326
|
+
...(ctx.stage === 'fix' ? withRca(rcaText(core, ctx.phase)) : {}),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
case 'review': {
|
|
330
|
+
const workSubmissions = {};
|
|
331
|
+
const appeals = {};
|
|
332
|
+
for (const id of ids) {
|
|
333
|
+
const work = latestWork(core, id);
|
|
334
|
+
if (work)
|
|
335
|
+
workSubmissions[id] = work;
|
|
336
|
+
const appeal = pendingAppeal(core, id);
|
|
337
|
+
if (appeal)
|
|
338
|
+
appeals[id] = appeal;
|
|
339
|
+
}
|
|
340
|
+
return { ...brief, workSubmissions, ...(Object.keys(appeals).length > 0 ? { appeals } : {}) };
|
|
341
|
+
}
|
|
342
|
+
case 'test': {
|
|
343
|
+
const changeLists = {};
|
|
344
|
+
const reviewVerdicts = {};
|
|
345
|
+
for (const id of ids) {
|
|
346
|
+
changeLists[id] = changeListOf(core, id);
|
|
347
|
+
const verdict = reviewConclusion(core, id);
|
|
348
|
+
if (verdict)
|
|
349
|
+
reviewVerdicts[id] = verdict;
|
|
350
|
+
}
|
|
351
|
+
return { ...brief, changeLists, reviewVerdicts };
|
|
352
|
+
}
|
|
353
|
+
case 'settle':
|
|
354
|
+
case 'integrate':
|
|
355
|
+
return { ...brief, inspector: inspectorMaterial(core, run, ctx) };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
// Stage bookkeeping
|
|
360
|
+
// ---------------------------------------------------------------------------
|
|
361
|
+
const RUNNING_STATUS = {
|
|
362
|
+
dev: 'dev_running',
|
|
363
|
+
review: 'review_running',
|
|
364
|
+
test: 'test_running',
|
|
365
|
+
};
|
|
366
|
+
function markRunning(core, run, ctx) {
|
|
367
|
+
const status = RUNNING_STATUS[ctx.stage];
|
|
368
|
+
for (const group of ctx.groups) {
|
|
369
|
+
core.setAgentState(group.identity, 'queued');
|
|
370
|
+
if (status === undefined)
|
|
371
|
+
continue;
|
|
372
|
+
for (const id of group.wpIds) {
|
|
373
|
+
const wp = run.workPackages[id];
|
|
374
|
+
if (wp === undefined)
|
|
375
|
+
continue;
|
|
376
|
+
wp.status = status;
|
|
377
|
+
delete wp.blockedAt;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
run.dispatches += 1;
|
|
381
|
+
run.status = 'executing';
|
|
382
|
+
run.stage = {
|
|
383
|
+
kind: ctx.stage,
|
|
384
|
+
phase: ctx.phase.index,
|
|
385
|
+
wpIds: ctx.groups.flatMap((g) => g.wpIds),
|
|
386
|
+
startedAt: core.now(),
|
|
387
|
+
status: 'running',
|
|
388
|
+
};
|
|
389
|
+
core.log('stage_started', 'engine', {
|
|
390
|
+
stage: ctx.stage,
|
|
391
|
+
phase: ctx.phase.index,
|
|
392
|
+
groups: ctx.groups.map((g) => ({ identity: g.identity, wps: g.wpIds })),
|
|
393
|
+
});
|
|
394
|
+
core.checkpoint();
|
|
395
|
+
core.emit((snapshot) => ({ type: 'stage_started', snapshot }));
|
|
396
|
+
}
|
|
397
|
+
/** WPs of the stage that still lack a submission (or `['ledger_settle']` for the inspector). */
|
|
398
|
+
function missingOf(core, ctx) {
|
|
399
|
+
if (ctx.stage === 'settle' || ctx.stage === 'integrate') {
|
|
400
|
+
return core.inspectorOutcome === null ? ['ledger_settle'] : [];
|
|
401
|
+
}
|
|
402
|
+
return ctx.groups.flatMap((g) => g.wpIds).filter((id) => !core.stageSubmitted.has(id));
|
|
403
|
+
}
|
|
404
|
+
function restrictGroups(groups, missing) {
|
|
405
|
+
const wanted = new Set(missing);
|
|
406
|
+
return groups
|
|
407
|
+
.map((g) => ({ ...g, wpIds: g.wpIds.filter((id) => wanted.has(id)) }))
|
|
408
|
+
.filter((g) => g.wpIds.length > 0 || g.role === 'INSP');
|
|
409
|
+
}
|
|
410
|
+
function noteOwnership(core, run, ctx, out) {
|
|
411
|
+
if (ctx.stage !== 'dev' && ctx.stage !== 'fix' && ctx.stage !== 'test')
|
|
412
|
+
return;
|
|
413
|
+
for (const child of out.runs) {
|
|
414
|
+
const group = ctx.groups.find((g) => g.identity === child.label);
|
|
415
|
+
if (group === undefined)
|
|
416
|
+
continue;
|
|
417
|
+
const owned = group.wpIds.flatMap((id) => run.workPackages[id]?.files ?? []);
|
|
418
|
+
const bad = detectOwnershipViolations(child.filesTouched, owned);
|
|
419
|
+
if (bad.length === 0)
|
|
420
|
+
continue;
|
|
421
|
+
const text = `${child.label} touched files outside its work package ` +
|
|
422
|
+
`(${group.wpIds.join(', ')}): ${bad.slice(0, 5).join(', ')}`;
|
|
423
|
+
core.phaseFlags.set(ctx.phase.index, [...(core.phaseFlags.get(ctx.phase.index) ?? []), text]);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function noteChildFailures(core, out) {
|
|
427
|
+
for (const child of out.runs) {
|
|
428
|
+
if (child.phase !== 'failed' || child.error === undefined)
|
|
429
|
+
continue;
|
|
430
|
+
core.stageWarnings.push(`${child.label} failed: ${child.error.slice(0, 160)}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
async function dispatchGroups(core, run, ctx, groups, options) {
|
|
434
|
+
const specs = groups.map((g) => buildSubagentSpec(briefFor(core, run, { ...ctx, groups }, g, options.missingOnly ? g.wpIds : undefined)));
|
|
435
|
+
for (const g of groups)
|
|
436
|
+
core.setAgentState(g.identity, 'queued');
|
|
437
|
+
const outcome = await core.deps.withPausedWatchdog(() => core.deps.runtime.dispatch(specs, specs.length, options.controller.signal));
|
|
438
|
+
run.usage = accumulateUsage(run.usage, outcome, core.deps.modelCost?.());
|
|
439
|
+
noteOwnership(core, run, ctx, outcome);
|
|
440
|
+
noteChildFailures(core, outcome);
|
|
441
|
+
return outcome;
|
|
442
|
+
}
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
// Entry point
|
|
445
|
+
// ---------------------------------------------------------------------------
|
|
446
|
+
/** `gov_dispatch`. Always returns text; an interrupted stage is `PAUSED:`, not an error. */
|
|
447
|
+
export async function runStage(core, raw, signal) {
|
|
448
|
+
const run = core.run;
|
|
449
|
+
if (run === null)
|
|
450
|
+
return refusal(core, 'no governance run is active; call gov_run start first.');
|
|
451
|
+
const parsed = parseDispatchInput(raw);
|
|
452
|
+
if (!parsed.ok)
|
|
453
|
+
return formatNotAccepted('gov_dispatch', parsed.errors);
|
|
454
|
+
const { stage, wps, notes } = parsed.value;
|
|
455
|
+
// A pause the user asked for outranks a new stage, whatever the orchestrator wants:
|
|
456
|
+
// without this gate a parallel tool call could start one between the request and the
|
|
457
|
+
// stage end and quietly eat the pause. Its twin is the redeem point below; they land
|
|
458
|
+
// together or not at all (governance-mode-v1-1 V-3).
|
|
459
|
+
if (core.pauseRequested || core.userPaused)
|
|
460
|
+
return pausedByUserRefusal(core);
|
|
461
|
+
run.limits.budgetTokens = core.config().budgetTokens;
|
|
462
|
+
const legal = checkStage(run, stage, wps, { teamCeiling: core.deps.getTeamCeiling() });
|
|
463
|
+
if (!legal.ok)
|
|
464
|
+
return legal.message;
|
|
465
|
+
if (shouldStopNewStages(run.usage, run.limits.budgetTokens)) {
|
|
466
|
+
const escalation = openBudgetEscalation(core);
|
|
467
|
+
const flush = await core.flush();
|
|
468
|
+
const text = composeDigest(core, { headline: `ESCALATION: ${escalation.topic}` });
|
|
469
|
+
return flush === null ? text : `${text}\n${flush}`;
|
|
470
|
+
}
|
|
471
|
+
if (signal?.aborted) {
|
|
472
|
+
return composeDigest(core, {
|
|
473
|
+
headline: 'PAUSED: the call was interrupted before the stage started, so nothing was dispatched.',
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
const phase = run.phases[legal.phase];
|
|
477
|
+
const dropped = stage !== 'dev' && stage !== 'fix' && notes !== undefined;
|
|
478
|
+
core.resetStageScratch();
|
|
479
|
+
if (dropped) {
|
|
480
|
+
core.stageWarnings.push('NOTE: orchestrator notes are accepted for dev and fix only; they were discarded ' +
|
|
481
|
+
'(each role sees only what it is allowed to see).');
|
|
482
|
+
}
|
|
483
|
+
const controller = core.beginStage(signal);
|
|
484
|
+
try {
|
|
485
|
+
const ctx = {
|
|
486
|
+
stage,
|
|
487
|
+
phase,
|
|
488
|
+
groups: buildGroups(core, run, stage, phase, legal.wpIds),
|
|
489
|
+
notes: stage === 'dev' || stage === 'fix' ? (notes ?? {}) : {},
|
|
490
|
+
specText: (await core.deps.store.readRunFile(run.runId, 'spec.md')) ?? run.goal,
|
|
491
|
+
};
|
|
492
|
+
const text = await executeStage(core, run, ctx, controller);
|
|
493
|
+
// Redeem a pending `/gov pause` HERE, while the stage is still "active", so a request
|
|
494
|
+
// that arrives in the tail of the stage waits for this hook instead of racing it.
|
|
495
|
+
return await applyPendingPause(core, text);
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
// An unexpected throw must not eat the user's pause (the run itself is intact).
|
|
499
|
+
await applyPendingPause(core, '').catch(() => undefined);
|
|
500
|
+
throw error;
|
|
501
|
+
}
|
|
502
|
+
finally {
|
|
503
|
+
core.endStage();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
async function executeStage(core, run, ctx, controller) {
|
|
507
|
+
if (ctx.groups.length === 0) {
|
|
508
|
+
return refusal(core, `stage '${ctx.stage}' has no work package to run.`);
|
|
509
|
+
}
|
|
510
|
+
markRunning(core, run, ctx);
|
|
511
|
+
let aborted = false;
|
|
512
|
+
try {
|
|
513
|
+
let missing = missingOf(core, ctx);
|
|
514
|
+
let groups = ctx.groups;
|
|
515
|
+
for (let attempt = 0; attempt <= GOV_LIMITS.maxAutoRedispatch && !aborted; attempt += 1) {
|
|
516
|
+
const outcome = await dispatchGroups(core, run, ctx, groups, {
|
|
517
|
+
controller,
|
|
518
|
+
missingOnly: attempt > 0,
|
|
519
|
+
});
|
|
520
|
+
aborted = outcome.aborted || controller.signal.aborted;
|
|
521
|
+
missing = missingOf(core, ctx);
|
|
522
|
+
if (aborted || missing.length === 0)
|
|
523
|
+
break;
|
|
524
|
+
groups = restrictGroups(ctx.groups, missing);
|
|
525
|
+
}
|
|
526
|
+
if (aborted)
|
|
527
|
+
return await pauseAfterAbort(core, run, ctx);
|
|
528
|
+
return await finishStage(core, run, ctx, missing);
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
return await failStage(core, run, ctx, error);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async function pauseAfterAbort(core, run, ctx) {
|
|
535
|
+
recoverInterruptedStage(run);
|
|
536
|
+
if (run.stage)
|
|
537
|
+
run.stage.status = 'aborted';
|
|
538
|
+
run.status = 'paused';
|
|
539
|
+
core.log('stage_ended', 'engine', {
|
|
540
|
+
stage: ctx.stage,
|
|
541
|
+
phase: ctx.phase.index,
|
|
542
|
+
status: 'aborted',
|
|
543
|
+
});
|
|
544
|
+
core.log('run_paused', 'engine', { reason: 'aborted' });
|
|
545
|
+
core.clearActivity();
|
|
546
|
+
for (const g of ctx.groups)
|
|
547
|
+
core.setAgentState(g.identity, 'idle');
|
|
548
|
+
core.checkpoint();
|
|
549
|
+
core.emit((snapshot) => ({ type: 'stage_ended', snapshot, card: null }));
|
|
550
|
+
core.emit((snapshot) => ({ type: 'run_paused', snapshot, reason: 'aborted' }));
|
|
551
|
+
const flush = await core.flush();
|
|
552
|
+
const text = composeDigest(core, {
|
|
553
|
+
headline: PAUSE_RESUME_HINT,
|
|
554
|
+
results: core.stageResults,
|
|
555
|
+
ledger: core.stageLedgerLines,
|
|
556
|
+
warnings: core.stageWarnings,
|
|
557
|
+
standings: true,
|
|
558
|
+
});
|
|
559
|
+
return flush === null ? text : `${text}\n${flush}`;
|
|
560
|
+
}
|
|
561
|
+
async function failStage(core, run, ctx, error) {
|
|
562
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
563
|
+
recoverInterruptedStage(run);
|
|
564
|
+
if (run.stage)
|
|
565
|
+
run.stage.status = 'aborted';
|
|
566
|
+
core.log('stage_ended', 'engine', {
|
|
567
|
+
stage: ctx.stage,
|
|
568
|
+
phase: ctx.phase.index,
|
|
569
|
+
status: 'failed',
|
|
570
|
+
error: message.slice(0, 200),
|
|
571
|
+
});
|
|
572
|
+
core.clearActivity();
|
|
573
|
+
for (const g of ctx.groups)
|
|
574
|
+
core.setAgentState(g.identity, 'failed');
|
|
575
|
+
core.checkpoint();
|
|
576
|
+
core.emit((snapshot) => ({ type: 'stage_ended', snapshot, card: null }));
|
|
577
|
+
await core.flush();
|
|
578
|
+
return composeDigest(core, {
|
|
579
|
+
headline: `NOT ACCEPTED: the team could not run the ${ctx.stage} stage (${message.slice(0, 200)}). ` +
|
|
580
|
+
'Nothing was recorded for this stage and no GT was deducted.',
|
|
581
|
+
results: core.stageResults,
|
|
582
|
+
warnings: core.stageWarnings,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
function blockMissing(core, run, ctx, missing) {
|
|
586
|
+
if (ctx.stage === 'fix') {
|
|
587
|
+
core.stageWarnings.push(`No fix submission from: ${missing.join(', ')} after one retry (no GT deducted). ` +
|
|
588
|
+
'Integrate again to see where things stand.');
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (ctx.stage === 'settle' || ctx.stage === 'integrate') {
|
|
592
|
+
core.stageWarnings.push(`The inspector finished without calling ledger_settle after one retry, so nothing was ` +
|
|
593
|
+
`recorded for the ${ctx.stage} stage (no GT changed). Dispatch ${ctx.stage} again.`);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
for (const id of missing) {
|
|
597
|
+
markBlocked(run, id, ctx.stage);
|
|
598
|
+
core.stageWarnings.push(`WP ${id} is BLOCKED at ${ctx.stage}: no submission after one retry (no GT deducted). ` +
|
|
599
|
+
`Dispatch ${ctx.stage} for it again.`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function settleAgentStates(core, ctx, missing) {
|
|
603
|
+
const lacking = new Set(missing);
|
|
604
|
+
for (const g of ctx.groups) {
|
|
605
|
+
const own = g.wpIds.filter((id) => lacking.has(id)).length;
|
|
606
|
+
const gaps = g.role === 'INSP' ? missing.length : own;
|
|
607
|
+
core.setAgentState(g.identity, gaps > 0 ? 'failed' : 'submitted');
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async function finishStage(core, run, ctx, missing) {
|
|
611
|
+
if (missing.length > 0)
|
|
612
|
+
blockMissing(core, run, ctx, missing);
|
|
613
|
+
if (ctx.stage === 'fix')
|
|
614
|
+
ctx.phase.fixCycles += 1;
|
|
615
|
+
if (run.stage)
|
|
616
|
+
run.stage.status = 'done';
|
|
617
|
+
settleAgentStates(core, ctx, missing);
|
|
618
|
+
core.log('stage_ended', 'engine', {
|
|
619
|
+
stage: ctx.stage,
|
|
620
|
+
phase: ctx.phase.index,
|
|
621
|
+
status: 'done',
|
|
622
|
+
missing,
|
|
623
|
+
});
|
|
624
|
+
core.clearActivity();
|
|
625
|
+
core.checkpoint();
|
|
626
|
+
// The integrate verdict emits its OWN `stage_ended` carrying the phase card; every other
|
|
627
|
+
// stage (and an integrate that produced no verdict) is announced here, exactly once.
|
|
628
|
+
const verdictEmitted = ctx.stage === 'integrate' && core.inspectorOutcome?.kind === 'integrate';
|
|
629
|
+
if (!verdictEmitted)
|
|
630
|
+
core.emit((snapshot) => ({ type: 'stage_ended', snapshot, card: null }));
|
|
631
|
+
core.exportScoreboard();
|
|
632
|
+
const flush = await core.flush();
|
|
633
|
+
const text = composeDigest(core, stageDigest(core, run, ctx, missing));
|
|
634
|
+
return flush === null ? text : `${text}\n${flush}`;
|
|
635
|
+
}
|
|
636
|
+
function stageDigest(core, run, ctx, missing) {
|
|
637
|
+
const open = run.escalations.filter((e) => e.status === 'open');
|
|
638
|
+
const outcome = core.inspectorOutcome;
|
|
639
|
+
const total = ctx.groups.flatMap((g) => g.wpIds).length;
|
|
640
|
+
const done = total - (ctx.stage === 'settle' || ctx.stage === 'integrate' ? 0 : missing.length);
|
|
641
|
+
let headline;
|
|
642
|
+
if (open.length > 0) {
|
|
643
|
+
headline = `ESCALATION: ${open.map((e) => `${e.id}: ${e.topic}`).join(' | ')}`;
|
|
644
|
+
}
|
|
645
|
+
else if ((ctx.stage === 'settle' || ctx.stage === 'integrate') && outcome === null) {
|
|
646
|
+
headline = `NOT ACCEPTED: the ${ctx.stage} stage produced no result. Nothing was recorded.`;
|
|
647
|
+
}
|
|
648
|
+
else if (outcome !== null) {
|
|
649
|
+
headline = `OK: ${outcome.headline}`;
|
|
650
|
+
}
|
|
651
|
+
else if (ctx.stage === 'fix') {
|
|
652
|
+
headline = `OK: fix stage finished (cycle ${ctx.phase.fixCycles}).`;
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
headline = `OK: ${ctx.stage} stage finished: ${done}/${total} submission(s) recorded.`;
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
headline,
|
|
659
|
+
results: core.stageResults,
|
|
660
|
+
ledger: core.stageLedgerLines,
|
|
661
|
+
warnings: core.stageWarnings,
|
|
662
|
+
standings: true,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gov_run start` argument normalization (governance-mode 7.2).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY (`governance/` is glyph-scanned).
|
|
5
|
+
*
|
|
6
|
+
* REPAIR FIRST, REJECT SECOND, and never create anything on a rejection: a malformed
|
|
7
|
+
* `start` must leave no run directory, no ledger and no lock behind, because the model's
|
|
8
|
+
* natural response to `NOT ACCEPTED` is to call `start` again with a corrected payload.
|
|
9
|
+
* Every problem is listed at once - a model that learns about one defect per call burns a
|
|
10
|
+
* round trip per defect, and each round trip is an LLM turn.
|
|
11
|
+
*
|
|
12
|
+
* The schema in `lead-tools.ts` declares SHAPE only (plan-tools.ts P0-4: `ajv` is an
|
|
13
|
+
* optional dependency, so a numeric bound in the schema would behave differently depending
|
|
14
|
+
* on whether it installed). Every bound is enforced HERE, deterministically.
|
|
15
|
+
*/
|
|
16
|
+
import type { StartInput } from '../types.js';
|
|
17
|
+
import { type Normalized } from '../tools/normalize-util.js';
|
|
18
|
+
/** Validate a raw `gov_run start` payload. Pure; never throws. */
|
|
19
|
+
export declare function normalizeStartInput(raw: unknown): Normalized<StartInput>;
|