@aragon-agent/cli 0.6.43 → 0.6.45
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 +69 -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/plan-prompt.d.ts +7 -0
- package/dist/agent/plan-prompt.js +2 -1
- package/dist/agent/reducer.d.ts +33 -1
- package/dist/agent/reducer.js +45 -9
- 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 +10 -4
- package/dist/commands/governance-command.d.ts +35 -0
- package/dist/commands/governance-command.js +292 -0
- package/dist/commands/governance-runs-table.d.ts +40 -0
- package/dist/commands/governance-runs-table.js +130 -0
- package/dist/commands/registry.d.ts +12 -0
- 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/proc/kill-tree.d.ts +19 -1
- package/dist/proc/kill-tree.js +32 -5
- package/dist/proc/limits.d.ts +19 -0
- package/dist/proc/limits.js +19 -0
- package/dist/proc/supervisor.js +8 -1
- package/dist/session/validate-session.js +32 -1
- 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/bounded-spawn.d.ts +56 -0
- package/dist/tools/bounded-spawn.js +76 -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/tools/search-tools.js +52 -44
- package/dist/ui/App.js +145 -25
- 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/ScrollbackShell.d.ts +35 -7
- package/dist/ui/ScrollbackShell.js +1 -1
- package/dist/ui/StatusBar.d.ts +15 -2
- package/dist/ui/StatusBar.js +10 -4
- package/dist/ui/Transcript.js +3 -0
- package/dist/ui/entries/GovernanceCard.d.ts +30 -0
- package/dist/ui/entries/GovernanceCard.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/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 +11 -0
- 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/LiveTail.d.ts +3 -1
- package/dist/ui/scrollback/budget.d.ts +12 -6
- package/dist/ui/scrollback/budget.js +11 -4
- package/dist/ui/scrollback/live-preview.js +2 -0
- package/dist/ui/scrollback/settled-boundary.js +3 -0
- package/dist/ui/scrollback/tail-anchor.d.ts +5 -2
- package/dist/ui/scrollback/tail-anchor.js +3 -1
- 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 +9 -0
- package/dist/ui/use-wheel-routing.js +3 -1
- package/package.json +2 -2
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage legality, `NEXT` generation and escalation handling (governance-mode 8.2, I-G6).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY (`governance/` is glyph-scanned). Pure apart from the explicitly named
|
|
5
|
+
* mutators (`recordRework`, `markBlocked`, `openEscalation`, `closeEscalation`,
|
|
6
|
+
* `applyResolveDisposition`), which edit the `RunState` they are handed IN PLACE and do
|
|
7
|
+
* no I/O, so the engine can replay or persist around them.
|
|
8
|
+
*
|
|
9
|
+
* WHY A TABLE AND NOT TRUST. The orchestrator is an LLM; "do not skip review" in a prompt
|
|
10
|
+
* drifts over a multi-hour run. Here the stage order is a hard fact: `checkStage` is the
|
|
11
|
+
* only gate `gov_dispatch` passes through, and every refusal teaches (it names the
|
|
12
|
+
* blocking WPs and ends with the legal next calls) so a refused call is one cheap
|
|
13
|
+
* correction, not a dead end. Refusals are plain text, never errors (D-15).
|
|
14
|
+
*
|
|
15
|
+
* The current phase is the FIRST phase with `integrated === false`; stages only ever act
|
|
16
|
+
* on it. `dropped` and `integrated` WPs never block anything.
|
|
17
|
+
*/
|
|
18
|
+
import { TEAM_LIMITS } from '../../team/limits.js';
|
|
19
|
+
import { GOV_LIMITS } from '../limits.js';
|
|
20
|
+
import { STAGE_KINDS, } from '../types.js';
|
|
21
|
+
export const NOT_ACCEPTED = 'NOT ACCEPTED:';
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Basic queries
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/** Index of the first phase that is not integrated, or null when all are. */
|
|
26
|
+
export function currentPhaseIndex(run) {
|
|
27
|
+
const found = run.phases.find((phase) => !phase.integrated);
|
|
28
|
+
return found ? found.index : null;
|
|
29
|
+
}
|
|
30
|
+
export function currentPhase(run) {
|
|
31
|
+
const index = currentPhaseIndex(run);
|
|
32
|
+
return index === null ? null : (run.phases[index] ?? null);
|
|
33
|
+
}
|
|
34
|
+
export function wpsInPhase(run, phase) {
|
|
35
|
+
return phase.wpIds
|
|
36
|
+
.map((id) => run.workPackages[id])
|
|
37
|
+
.filter((wp) => wp !== undefined);
|
|
38
|
+
}
|
|
39
|
+
/** `maxRework` plus L3 grants for one WP, never above the structural ceiling. */
|
|
40
|
+
export function reworkLimit(run, wp) {
|
|
41
|
+
return Math.min(run.limits.maxRework + wp.reworkGrants, GOV_LIMITS.hardMaxRework);
|
|
42
|
+
}
|
|
43
|
+
/** Fix cycles allowed in a phase: `maxFixCycles + extraFixGrants`, capped structurally. */
|
|
44
|
+
export function fixCycleLimit(run, phase) {
|
|
45
|
+
return Math.min(run.limits.maxFixCycles + phase.extraFixGrants, GOV_LIMITS.hardMaxFixCycles);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The last integrate failed, every allowed fix has been spent, and no newer integrate
|
|
49
|
+
* attempt is pending: the engine must open an `integration_exhausted` escalation.
|
|
50
|
+
*/
|
|
51
|
+
export function isIntegrationExhausted(run, phase) {
|
|
52
|
+
return (phase.lastIntegrationFailed &&
|
|
53
|
+
phase.fixCycles >= fixCycleLimit(run, phase) &&
|
|
54
|
+
phase.fixCycles < phase.integrationAttempts);
|
|
55
|
+
}
|
|
56
|
+
/** `gov_run close` precondition: every phase integrated (accepted failing counts). */
|
|
57
|
+
export function canClose(run) {
|
|
58
|
+
return run.phases.length > 0 && run.phases.every((phase) => phase.integrated);
|
|
59
|
+
}
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Per-stage eligibility
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
function dependenciesIntegrated(run, wp) {
|
|
64
|
+
return wp.dependsOn.every((id) => run.workPackages[id]?.status === 'integrated');
|
|
65
|
+
}
|
|
66
|
+
function isEligible(run, wp, stage) {
|
|
67
|
+
switch (stage) {
|
|
68
|
+
case 'dev':
|
|
69
|
+
return ((wp.status === 'pending' && dependenciesIntegrated(run, wp)) ||
|
|
70
|
+
wp.status === 'rework' ||
|
|
71
|
+
(wp.status === 'blocked' && wp.blockedAt === 'dev'));
|
|
72
|
+
case 'review':
|
|
73
|
+
return (wp.status === 'awaiting_review' || (wp.status === 'blocked' && wp.blockedAt === 'review'));
|
|
74
|
+
case 'test':
|
|
75
|
+
return wp.status === 'awaiting_test' || (wp.status === 'blocked' && wp.blockedAt === 'test');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const SETTLE_BLOCKING = new Set([
|
|
79
|
+
'pending',
|
|
80
|
+
'dev_running',
|
|
81
|
+
'awaiting_review',
|
|
82
|
+
'review_running',
|
|
83
|
+
'awaiting_test',
|
|
84
|
+
'test_running',
|
|
85
|
+
'rework',
|
|
86
|
+
'blocked',
|
|
87
|
+
]);
|
|
88
|
+
const STATUS_LABEL = {
|
|
89
|
+
pending: 'pending',
|
|
90
|
+
dev_running: 'in development',
|
|
91
|
+
awaiting_review: 'awaiting review',
|
|
92
|
+
review_running: 'in review',
|
|
93
|
+
awaiting_test: 'awaiting test',
|
|
94
|
+
test_running: 'in testing',
|
|
95
|
+
rework: 'in rework',
|
|
96
|
+
tested_ok: 'tested ok',
|
|
97
|
+
integrated: 'integrated',
|
|
98
|
+
blocked: 'blocked',
|
|
99
|
+
dropped: 'dropped',
|
|
100
|
+
};
|
|
101
|
+
function labelOf(wp) {
|
|
102
|
+
if (wp.status === 'blocked' && wp.blockedAt)
|
|
103
|
+
return `blocked at ${wp.blockedAt}`;
|
|
104
|
+
return STATUS_LABEL[wp.status];
|
|
105
|
+
}
|
|
106
|
+
/** "WPs awaiting review: a, b; pending: c" over the not-yet-finished WPs of a phase. */
|
|
107
|
+
export function describePhaseStatus(run, phase) {
|
|
108
|
+
const groups = new Map();
|
|
109
|
+
for (const wp of wpsInPhase(run, phase)) {
|
|
110
|
+
if (wp.status === 'integrated' || wp.status === 'dropped')
|
|
111
|
+
continue;
|
|
112
|
+
const label = labelOf(wp);
|
|
113
|
+
groups.set(label, [...(groups.get(label) ?? []), wp.id]);
|
|
114
|
+
}
|
|
115
|
+
return [...groups.entries()].map(([label, ids]) => `WPs ${label}: ${ids.join(', ')}`).join('; ');
|
|
116
|
+
}
|
|
117
|
+
function settleIssue(run, phase) {
|
|
118
|
+
if (phase.settled)
|
|
119
|
+
return 'this phase is already settled';
|
|
120
|
+
const wps = wpsInPhase(run, phase);
|
|
121
|
+
const blockers = wps.filter((wp) => SETTLE_BLOCKING.has(wp.status));
|
|
122
|
+
if (blockers.length > 0) {
|
|
123
|
+
return `settle needs every WP finished: ${describePhaseStatus(run, phase)}`;
|
|
124
|
+
}
|
|
125
|
+
if (!wps.some((wp) => wp.status === 'tested_ok'))
|
|
126
|
+
return 'no work package has passed testing';
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
function integrateIssue(run, phase) {
|
|
130
|
+
if (!phase.settled)
|
|
131
|
+
return 'the phase is not settled yet';
|
|
132
|
+
if (phase.lastIntegrationFailed && phase.fixCycles < phase.integrationAttempts) {
|
|
133
|
+
return isIntegrationExhausted(run, phase)
|
|
134
|
+
? `the last integration failed and all ${fixCycleLimit(run, phase)} fix cycles are spent`
|
|
135
|
+
: 'the last integration failed; run a fix stage first';
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
function fixIssue(run, phase) {
|
|
140
|
+
if (!phase.lastIntegrationFailed)
|
|
141
|
+
return 'there is no failed integration to fix';
|
|
142
|
+
if (phase.fixCycles >= phase.integrationAttempts) {
|
|
143
|
+
return 'a fix already ran since the last failure; integrate again';
|
|
144
|
+
}
|
|
145
|
+
const limit = fixCycleLimit(run, phase);
|
|
146
|
+
if (phase.fixCycles >= limit)
|
|
147
|
+
return `fix cycles are exhausted (${phase.fixCycles}/${limit})`;
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
function evaluateWpStage(run, phase, stage, requested) {
|
|
151
|
+
const eligible = wpsInPhase(run, phase).filter((wp) => isEligible(run, wp, stage));
|
|
152
|
+
if (requested === undefined) {
|
|
153
|
+
if (eligible.length > 0)
|
|
154
|
+
return { ok: true, phase, wpIds: eligible.map((wp) => wp.id) };
|
|
155
|
+
return { ok: false, reason: describePhaseStatus(run, phase) || 'no work package is eligible' };
|
|
156
|
+
}
|
|
157
|
+
const problems = requested.flatMap((id) => requestedProblem(run, phase, id, eligible));
|
|
158
|
+
if (problems.length > 0)
|
|
159
|
+
return { ok: false, reason: problems.join('; ') };
|
|
160
|
+
return { ok: true, phase, wpIds: [...new Set(requested)] };
|
|
161
|
+
}
|
|
162
|
+
function requestedProblem(run, phase, id, eligible) {
|
|
163
|
+
const wp = run.workPackages[id];
|
|
164
|
+
if (!wp)
|
|
165
|
+
return [`unknown work package '${id}'`];
|
|
166
|
+
if (wp.phase !== phase.index) {
|
|
167
|
+
return [`'${id}' belongs to phase ${wp.phase + 1}, the current phase is ${phase.index + 1}`];
|
|
168
|
+
}
|
|
169
|
+
if (eligible.some((candidate) => candidate.id === id))
|
|
170
|
+
return [];
|
|
171
|
+
return [`'${id}' is ${labelOf(wp)}`];
|
|
172
|
+
}
|
|
173
|
+
function evaluateStage(run, stage, requested) {
|
|
174
|
+
const phase = currentPhase(run);
|
|
175
|
+
if (!phase)
|
|
176
|
+
return { ok: false, reason: 'every phase is already integrated' };
|
|
177
|
+
if (stage === 'dev' || stage === 'review' || stage === 'test') {
|
|
178
|
+
return evaluateWpStage(run, phase, stage, requested);
|
|
179
|
+
}
|
|
180
|
+
const issue = stage === 'settle'
|
|
181
|
+
? settleIssue(run, phase)
|
|
182
|
+
: stage === 'integrate'
|
|
183
|
+
? integrateIssue(run, phase)
|
|
184
|
+
: fixIssue(run, phase);
|
|
185
|
+
if (issue)
|
|
186
|
+
return { ok: false, reason: issue };
|
|
187
|
+
// `fix` defaults to no WPs: the engine picks the DEVs the RCA attributes, and counting
|
|
188
|
+
// every WP of the phase here would inflate the stage width past the team ceiling.
|
|
189
|
+
const live = wpsInPhase(run, phase).filter((wp) => wp.status !== 'dropped');
|
|
190
|
+
const defaults = stage === 'fix' ? [] : live.map((wp) => wp.id);
|
|
191
|
+
return { ok: true, phase, wpIds: (requested ?? defaults).slice() };
|
|
192
|
+
}
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Stage width and chunking (D-24)
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
/** Number of children a stage over `wpCount` WPs launches. */
|
|
197
|
+
export function stageWidth(stage, wpCount) {
|
|
198
|
+
switch (stage) {
|
|
199
|
+
case 'dev':
|
|
200
|
+
return wpCount;
|
|
201
|
+
case 'fix':
|
|
202
|
+
return Math.max(1, wpCount);
|
|
203
|
+
case 'review':
|
|
204
|
+
case 'test':
|
|
205
|
+
return Math.min(3, Math.max(1, Math.ceil(wpCount / 2)));
|
|
206
|
+
case 'settle':
|
|
207
|
+
case 'integrate':
|
|
208
|
+
return 1;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** Most WPs one call may carry so that `stageWidth` stays within `ceiling`. */
|
|
212
|
+
export function stageChunkSize(stage, ceiling) {
|
|
213
|
+
const c = Math.max(1, Math.floor(ceiling));
|
|
214
|
+
switch (stage) {
|
|
215
|
+
case 'dev':
|
|
216
|
+
case 'fix':
|
|
217
|
+
return c;
|
|
218
|
+
case 'review':
|
|
219
|
+
case 'test':
|
|
220
|
+
return c >= 3 ? Number.POSITIVE_INFINITY : c * 2;
|
|
221
|
+
case 'settle':
|
|
222
|
+
case 'integrate':
|
|
223
|
+
return Number.POSITIVE_INFINITY;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function chunk(items, size) {
|
|
227
|
+
if (!Number.isFinite(size) || size >= items.length)
|
|
228
|
+
return [items.slice()];
|
|
229
|
+
const out = [];
|
|
230
|
+
for (let i = 0; i < items.length; i += size)
|
|
231
|
+
out.push(items.slice(i, i + size));
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
function ceilingOf(options) {
|
|
235
|
+
return options?.teamCeiling ?? TEAM_LIMITS.hardMaxSubagents;
|
|
236
|
+
}
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// NEXT generation
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
/** `gov_dispatch {"stage":"dev","wps":["a","b"]}` */
|
|
241
|
+
export function dispatchCall(stage, wps) {
|
|
242
|
+
const body = { stage };
|
|
243
|
+
if (wps && wps.length > 0)
|
|
244
|
+
body.wps = [...wps];
|
|
245
|
+
return `gov_dispatch ${JSON.stringify(body)}`;
|
|
246
|
+
}
|
|
247
|
+
/** Render actions as the trailing `NEXT:` block every `gov_*` result ends with (D-16). */
|
|
248
|
+
export function formatNext(actions) {
|
|
249
|
+
if (actions.length === 0)
|
|
250
|
+
return 'NEXT: (none)';
|
|
251
|
+
return `NEXT:\n${actions.map((action) => `- ${action}`).join('\n')}`;
|
|
252
|
+
}
|
|
253
|
+
const DISPOSITIONS_BY_KIND = {
|
|
254
|
+
wp_rework_cap: ['retry', 'accept_as_is', 'drop'],
|
|
255
|
+
integration_exhausted: ['retry', 'accept_failing'],
|
|
256
|
+
dispute: [],
|
|
257
|
+
};
|
|
258
|
+
function escalationActions(escalations) {
|
|
259
|
+
const actions = [];
|
|
260
|
+
for (const esc of escalations.filter((e) => e.status === 'open')) {
|
|
261
|
+
const allowed = DISPOSITIONS_BY_KIND[esc.kind];
|
|
262
|
+
const disposition = allowed.length > 0 ? `,"disposition":"${allowed.join('|')}"` : '';
|
|
263
|
+
actions.push(`ask_user: put ${esc.id} (${esc.kind}) to the user: ${esc.topic}`);
|
|
264
|
+
const ruling = '"ruling":"<user ruling>"';
|
|
265
|
+
actions.push(`gov_run {"action":"resolve","escalationId":"${esc.id}",${ruling}${disposition}}`);
|
|
266
|
+
}
|
|
267
|
+
return actions;
|
|
268
|
+
}
|
|
269
|
+
function stageCalls(run, stage, ceiling) {
|
|
270
|
+
const evaluation = evaluateStage(run, stage, undefined);
|
|
271
|
+
if (!evaluation.ok)
|
|
272
|
+
return [];
|
|
273
|
+
if (stage === 'settle' || stage === 'integrate' || stage === 'fix')
|
|
274
|
+
return [dispatchCall(stage)];
|
|
275
|
+
const groups = chunk(evaluation.wpIds, stageChunkSize(stage, ceiling));
|
|
276
|
+
return groups.map((ids) => dispatchCall(stage, ids));
|
|
277
|
+
}
|
|
278
|
+
function dropHints(run) {
|
|
279
|
+
const phase = currentPhase(run);
|
|
280
|
+
if (!phase)
|
|
281
|
+
return [];
|
|
282
|
+
const stuck = wpsInPhase(run, phase).filter((wp) => wp.status === 'pending' &&
|
|
283
|
+
wp.dependsOn.some((id) => run.workPackages[id]?.status === 'dropped'));
|
|
284
|
+
if (stuck.length === 0)
|
|
285
|
+
return [];
|
|
286
|
+
return [
|
|
287
|
+
`WPs ${stuck.map((wp) => wp.id).join(', ')} depend on a dropped WP and cannot start: ` +
|
|
288
|
+
'gov_run {"action":"abandon","reason":"..."} and restart with a revised plan',
|
|
289
|
+
];
|
|
290
|
+
}
|
|
291
|
+
function executingActions(run, ceiling) {
|
|
292
|
+
if (canClose(run))
|
|
293
|
+
return ['gov_run {"action":"close","summary":"<what was delivered>"}'];
|
|
294
|
+
const calls = STAGE_KINDS.flatMap((stage) => stageCalls(run, stage, ceiling));
|
|
295
|
+
const phase = currentPhase(run);
|
|
296
|
+
if (calls.length === 0 && phase && isIntegrationExhausted(run, phase)) {
|
|
297
|
+
return [
|
|
298
|
+
'integration fix cycles are exhausted: gov_run {"action":"status"} ' +
|
|
299
|
+
'(an escalation is opened)',
|
|
300
|
+
];
|
|
301
|
+
}
|
|
302
|
+
const hints = dropHints(run);
|
|
303
|
+
if (calls.length === 0 && hints.length === 0)
|
|
304
|
+
return ['gov_run {"action":"status"}'];
|
|
305
|
+
return [...calls, ...hints];
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Ordered legal next calls for the run's current state. Pure. Stage width beyond
|
|
309
|
+
* `teamCeiling` is split into chunk calls (D-24), each independently legal.
|
|
310
|
+
*/
|
|
311
|
+
export function nextActions(run, options) {
|
|
312
|
+
const ceiling = ceilingOf(options);
|
|
313
|
+
switch (run.status) {
|
|
314
|
+
case 'closed':
|
|
315
|
+
case 'abandoned':
|
|
316
|
+
return ['gov_run {"action":"start","goal":"...","spec":"...","workPackages":[...]}'];
|
|
317
|
+
case 'paused':
|
|
318
|
+
return ['gov_run {"action":"resume"}'];
|
|
319
|
+
case 'escalated':
|
|
320
|
+
return escalationActions(run.escalations);
|
|
321
|
+
case 'executing':
|
|
322
|
+
return run.stage?.status === 'running'
|
|
323
|
+
? ['a stage is running; wait for it to finish, then gov_run {"action":"status"}']
|
|
324
|
+
: executingActions(run, ceiling);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
// checkStage
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
function refuse(run, reason, options, head) {
|
|
331
|
+
const first = head ?? reason;
|
|
332
|
+
const next = formatNext(nextActions(run, options));
|
|
333
|
+
return { ok: false, message: `${NOT_ACCEPTED} ${first}\n${next}` };
|
|
334
|
+
}
|
|
335
|
+
function runLevelRefusal(run) {
|
|
336
|
+
switch (run.status) {
|
|
337
|
+
case 'closed':
|
|
338
|
+
return 'the run is closed.';
|
|
339
|
+
case 'abandoned':
|
|
340
|
+
return 'the run was abandoned.';
|
|
341
|
+
case 'paused':
|
|
342
|
+
return 'the run is paused; resume it before dispatching.';
|
|
343
|
+
case 'escalated': {
|
|
344
|
+
const open = run.escalations.filter((e) => e.status === 'open').map((e) => e.id);
|
|
345
|
+
const ids = open.join(', ') || 'open escalation';
|
|
346
|
+
return `the run is escalated (${ids}); the user must rule first.`;
|
|
347
|
+
}
|
|
348
|
+
case 'executing':
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
if (run.stage?.status === 'running')
|
|
352
|
+
return `a ${run.stage.kind} stage is already running.`;
|
|
353
|
+
if (run.dispatches >= GOV_LIMITS.hardMaxDispatches) {
|
|
354
|
+
return `the dispatch limit of ${GOV_LIMITS.hardMaxDispatches} per run is reached.`;
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* The single legality gate for `gov_dispatch` (I-G6). `wps` omitted means "every eligible
|
|
360
|
+
* WP of the current phase". On success `wpIds` is the validated list the stage will run.
|
|
361
|
+
*/
|
|
362
|
+
export function checkStage(run, stage, wps, options) {
|
|
363
|
+
const blocked = runLevelRefusal(run);
|
|
364
|
+
if (blocked)
|
|
365
|
+
return refuse(run, blocked, options);
|
|
366
|
+
const evaluation = evaluateStage(run, stage, wps);
|
|
367
|
+
if (!evaluation.ok) {
|
|
368
|
+
const head = `stage '${stage}' is not legal now (${evaluation.reason}).`;
|
|
369
|
+
return refuse(run, evaluation.reason, options, head);
|
|
370
|
+
}
|
|
371
|
+
const ceiling = ceilingOf(options);
|
|
372
|
+
const width = stageWidth(stage, evaluation.wpIds.length);
|
|
373
|
+
if (width > ceiling) {
|
|
374
|
+
const size = stageChunkSize(stage, ceiling);
|
|
375
|
+
const calls = chunk(evaluation.wpIds, size).map((ids) => dispatchCall(stage, ids));
|
|
376
|
+
return {
|
|
377
|
+
ok: false,
|
|
378
|
+
message: `${NOT_ACCEPTED} stage width ${width} exceeds team.maxSubagents (${ceiling}); ` +
|
|
379
|
+
`dispatch at most ${size} WPs per call.\n${formatNext(calls)}`,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
return { ok: true, phase: evaluation.phase.index, wpIds: evaluation.wpIds };
|
|
383
|
+
}
|
|
384
|
+
/** Stages that are legal right now (ignores `teamCeiling`; chunking is not legality). */
|
|
385
|
+
export function legalStages(run) {
|
|
386
|
+
return STAGE_KINDS.filter((stage) => checkStage(run, stage).ok);
|
|
387
|
+
}
|
|
388
|
+
// ---------------------------------------------------------------------------
|
|
389
|
+
// Rework, blocking and escalations (mutators)
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
export function nextEscalationId(run) {
|
|
392
|
+
return `ESC-${run.escalations.length + 1}`;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Count one rejection against `wpId`. Beyond `maxRework + reworkGrants` the WP becomes
|
|
396
|
+
* `blocked` (retry stage `dev`, where the rework would have run) and an L3 escalation is
|
|
397
|
+
* proposed; otherwise it goes to `rework`.
|
|
398
|
+
*/
|
|
399
|
+
export function recordRework(run, wpId) {
|
|
400
|
+
const wp = run.workPackages[wpId];
|
|
401
|
+
if (!wp)
|
|
402
|
+
throw new Error(`recordRework: unknown work package '${wpId}'`);
|
|
403
|
+
wp.reworkCount += 1;
|
|
404
|
+
const limit = reworkLimit(run, wp);
|
|
405
|
+
if (wp.reworkCount <= limit) {
|
|
406
|
+
wp.status = 'rework';
|
|
407
|
+
return { outcome: 'rework', reworkCount: wp.reworkCount, limit, escalation: null };
|
|
408
|
+
}
|
|
409
|
+
wp.status = 'blocked';
|
|
410
|
+
wp.blockedAt = 'dev';
|
|
411
|
+
const escalation = {
|
|
412
|
+
id: nextEscalationId(run),
|
|
413
|
+
level: 'L3',
|
|
414
|
+
kind: 'wp_rework_cap',
|
|
415
|
+
wpId,
|
|
416
|
+
phase: wp.phase,
|
|
417
|
+
topic: `Work package '${wpId}' was sent back ${wp.reworkCount} times (limit ${limit}).`,
|
|
418
|
+
positions: [],
|
|
419
|
+
status: 'open',
|
|
420
|
+
};
|
|
421
|
+
return { outcome: 'blocked', reworkCount: wp.reworkCount, limit, escalation };
|
|
422
|
+
}
|
|
423
|
+
/** A stage produced no submission for this WP: block it at that stage, no penalty. */
|
|
424
|
+
export function markBlocked(run, wpId, stage) {
|
|
425
|
+
const wp = run.workPackages[wpId];
|
|
426
|
+
if (!wp)
|
|
427
|
+
throw new Error(`markBlocked: unknown work package '${wpId}'`);
|
|
428
|
+
wp.status = 'blocked';
|
|
429
|
+
wp.blockedAt = stage;
|
|
430
|
+
}
|
|
431
|
+
export function openEscalation(run, escalation) {
|
|
432
|
+
run.escalations.push(escalation);
|
|
433
|
+
run.status = 'escalated';
|
|
434
|
+
}
|
|
435
|
+
/** Mark resolved; the run leaves `escalated` once no escalation remains open. */
|
|
436
|
+
export function closeEscalation(run, escalation, ruling) {
|
|
437
|
+
escalation.status = 'resolved';
|
|
438
|
+
escalation.ruling = ruling;
|
|
439
|
+
const stillOpen = run.escalations.some((e) => e.status === 'open');
|
|
440
|
+
if (!stillOpen && run.status === 'escalated')
|
|
441
|
+
run.status = 'executing';
|
|
442
|
+
}
|
|
443
|
+
function resolveFailure(message) {
|
|
444
|
+
return { ok: false, message: `${NOT_ACCEPTED} ${message}` };
|
|
445
|
+
}
|
|
446
|
+
/** Not-yet-delivered WPs that depend (transitively) on `wpId`. */
|
|
447
|
+
function dependentsOf(run, wpId) {
|
|
448
|
+
const hit = new Set();
|
|
449
|
+
let grew = true;
|
|
450
|
+
while (grew) {
|
|
451
|
+
grew = false;
|
|
452
|
+
for (const wp of Object.values(run.workPackages)) {
|
|
453
|
+
if (hit.has(wp.id) || wp.status === 'integrated' || wp.status === 'dropped')
|
|
454
|
+
continue;
|
|
455
|
+
if (wp.dependsOn.some((dep) => dep === wpId || hit.has(dep))) {
|
|
456
|
+
hit.add(wp.id);
|
|
457
|
+
grew = true;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return run.wpOrder.filter((id) => hit.has(id));
|
|
462
|
+
}
|
|
463
|
+
function applyReworkDisposition(run, escalation, disposition) {
|
|
464
|
+
const wp = escalation.wpId ? run.workPackages[escalation.wpId] : undefined;
|
|
465
|
+
if (!wp)
|
|
466
|
+
return resolveFailure(`escalation ${escalation.id} names no known work package.`);
|
|
467
|
+
if (disposition === 'retry')
|
|
468
|
+
return grantRework(run, wp);
|
|
469
|
+
if (disposition === 'accept_as_is')
|
|
470
|
+
return acceptAsIs(wp);
|
|
471
|
+
return dropWorkPackage(run, wp);
|
|
472
|
+
}
|
|
473
|
+
function grantRework(run, wp) {
|
|
474
|
+
if (wp.reworkGrants >= GOV_LIMITS.maxReworkGrantsPerWp) {
|
|
475
|
+
return resolveFailure(`'${wp.id}' already used its ${GOV_LIMITS.maxReworkGrantsPerWp} rework grants; ` +
|
|
476
|
+
'choose accept_as_is or drop.');
|
|
477
|
+
}
|
|
478
|
+
if (run.limits.maxRework + wp.reworkGrants + 1 > GOV_LIMITS.hardMaxRework) {
|
|
479
|
+
return resolveFailure(`one more rework would exceed the structural limit of ${GOV_LIMITS.hardMaxRework}; ` +
|
|
480
|
+
'choose accept_as_is or drop.');
|
|
481
|
+
}
|
|
482
|
+
wp.reworkGrants += 1;
|
|
483
|
+
wp.status = 'rework';
|
|
484
|
+
delete wp.blockedAt;
|
|
485
|
+
return {
|
|
486
|
+
ok: true,
|
|
487
|
+
message: `'${wp.id}' gets one more rework (limit now ${reworkLimit(run, wp)}).`,
|
|
488
|
+
affectedDependents: [],
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function acceptAsIs(wp) {
|
|
492
|
+
if (wp.submissions.review.length < 1) {
|
|
493
|
+
return resolveFailure(`'${wp.id}' has no review on record; accept_as_is needs at least one.`);
|
|
494
|
+
}
|
|
495
|
+
wp.status = 'tested_ok';
|
|
496
|
+
wp.acceptedWithIssues = true;
|
|
497
|
+
delete wp.blockedAt;
|
|
498
|
+
return {
|
|
499
|
+
ok: true,
|
|
500
|
+
message: `'${wp.id}' accepted as is; its unresolved required changes go into the final report.`,
|
|
501
|
+
affectedDependents: [],
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function dropWorkPackage(run, wp) {
|
|
505
|
+
wp.status = 'dropped';
|
|
506
|
+
delete wp.blockedAt;
|
|
507
|
+
const voided = [];
|
|
508
|
+
for (const contract of run.contracts) {
|
|
509
|
+
const consumersGone = contract.consumers.every((id) => run.workPackages[id]?.status === 'dropped');
|
|
510
|
+
if (!contract.voided && (contract.producer === wp.id || consumersGone)) {
|
|
511
|
+
contract.voided = true;
|
|
512
|
+
voided.push(contract.id);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const phase = run.phases[wp.phase];
|
|
516
|
+
const skipped = phase !== undefined && wpsInPhase(run, phase).every((w) => w.status === 'dropped');
|
|
517
|
+
if (phase && skipped) {
|
|
518
|
+
phase.settled = true;
|
|
519
|
+
phase.integrated = true;
|
|
520
|
+
}
|
|
521
|
+
const dependents = dependentsOf(run, wp.id);
|
|
522
|
+
const parts = [`'${wp.id}' dropped; it is excluded from settle and integrate.`];
|
|
523
|
+
if (voided.length > 0)
|
|
524
|
+
parts.push(`Voided contracts: ${voided.join(', ')}.`);
|
|
525
|
+
if (skipped)
|
|
526
|
+
parts.push(`Phase ${wp.phase + 1} has no work left and is skipped.`);
|
|
527
|
+
if (dependents.length > 0) {
|
|
528
|
+
parts.push(`Affected work packages: ${dependents.join(', ')}. ` +
|
|
529
|
+
'Decide: drop them as well or abandon the run.');
|
|
530
|
+
}
|
|
531
|
+
return { ok: true, message: parts.join(' '), affectedDependents: dependents };
|
|
532
|
+
}
|
|
533
|
+
function applyIntegrationDisposition(run, escalation, disposition) {
|
|
534
|
+
const index = escalation.phase ?? currentPhaseIndex(run);
|
|
535
|
+
const phase = index === null ? undefined : run.phases[index];
|
|
536
|
+
if (!phase)
|
|
537
|
+
return resolveFailure(`escalation ${escalation.id} names no known phase.`);
|
|
538
|
+
if (disposition === 'retry') {
|
|
539
|
+
if (phase.extraFixGrants >= GOV_LIMITS.maxFixGrantsPerPhase) {
|
|
540
|
+
return resolveFailure('this phase already received its extra fix cycle; choose accept_failing.');
|
|
541
|
+
}
|
|
542
|
+
if (run.limits.maxFixCycles + phase.extraFixGrants + 1 > GOV_LIMITS.hardMaxFixCycles) {
|
|
543
|
+
return resolveFailure(`one more fix cycle would exceed the structural limit of ${GOV_LIMITS.hardMaxFixCycles}; ` +
|
|
544
|
+
'choose accept_failing.');
|
|
545
|
+
}
|
|
546
|
+
phase.extraFixGrants += 1;
|
|
547
|
+
return { ok: true, message: 'one extra fix cycle granted.', affectedDependents: [] };
|
|
548
|
+
}
|
|
549
|
+
phase.integrated = true;
|
|
550
|
+
phase.integrationAccepted = false;
|
|
551
|
+
phase.lastIntegrationFailed = false;
|
|
552
|
+
for (const wp of wpsInPhase(run, phase))
|
|
553
|
+
if (wp.status === 'tested_ok')
|
|
554
|
+
wp.status = 'integrated';
|
|
555
|
+
return {
|
|
556
|
+
ok: true,
|
|
557
|
+
message: `phase ${phase.index + 1} accepted although integration failed (reported in red).`,
|
|
558
|
+
affectedDependents: [],
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Apply the user's `disposition` for an escalation (spec 7.2 table). Mismatched or
|
|
563
|
+
* over-cap dispositions are refused with a reason; no disposition changes nothing.
|
|
564
|
+
* Mutates `run`; does NOT mark the escalation resolved (`closeEscalation` does).
|
|
565
|
+
*/
|
|
566
|
+
export function applyResolveDisposition(run, escalation, disposition) {
|
|
567
|
+
if (escalation.status === 'resolved') {
|
|
568
|
+
return resolveFailure(`escalation ${escalation.id} is already resolved.`);
|
|
569
|
+
}
|
|
570
|
+
if (disposition === undefined) {
|
|
571
|
+
return {
|
|
572
|
+
ok: true,
|
|
573
|
+
message: 'ruling recorded; no work package or phase was changed.',
|
|
574
|
+
affectedDependents: [],
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
const allowed = DISPOSITIONS_BY_KIND[escalation.kind];
|
|
578
|
+
if (!allowed.includes(disposition)) {
|
|
579
|
+
const list = allowed.length > 0 ? allowed.join(', ') : 'none';
|
|
580
|
+
return resolveFailure(`disposition '${disposition}' does not apply to a ${escalation.kind} escalation ` +
|
|
581
|
+
`(allowed: ${list}).`);
|
|
582
|
+
}
|
|
583
|
+
return escalation.kind === 'wp_rework_cap'
|
|
584
|
+
? applyReworkDisposition(run, escalation, disposition)
|
|
585
|
+
: applyIntegrationDisposition(run, escalation, disposition);
|
|
586
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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 { type RequiredChange } from '../rules/settlement.js';
|
|
13
|
+
import type { ReviewSubmission, ReviewVerdict, Submission, TestSubmission, WorkSubmission } from '../types.js';
|
|
14
|
+
import type { EngineCore } from './engine-core.js';
|
|
15
|
+
type Kind = Submission['kind'];
|
|
16
|
+
/** All cached submissions of one kind for a WP, oldest first. */
|
|
17
|
+
export declare function submissionsOf<K extends Kind>(core: EngineCore, wpId: string, kind: K): Array<Extract<Submission, {
|
|
18
|
+
kind: K;
|
|
19
|
+
}>>;
|
|
20
|
+
export declare function latestWork(core: EngineCore, wpId: string): WorkSubmission | undefined;
|
|
21
|
+
export declare function latestReview(core: EngineCore, wpId: string): ReviewSubmission | undefined;
|
|
22
|
+
export declare function latestTest(core: EngineCore, wpId: string): TestSubmission | undefined;
|
|
23
|
+
/** The review CONCLUSION a tester may see (never the comments, D-7). */
|
|
24
|
+
export declare function reviewConclusion(core: EngineCore, wpId: string): ReviewVerdict | undefined;
|
|
25
|
+
/** Files the developer reported changing in the latest work submission. */
|
|
26
|
+
export declare function changeListOf(core: EngineCore, wpId: string): string[];
|
|
27
|
+
/**
|
|
28
|
+
* The developer's open appeal, if the latest work submission appealed and no review has
|
|
29
|
+
* answered it yet. Reviewers MUST answer each finding id (L1, 5.6).
|
|
30
|
+
*/
|
|
31
|
+
export declare function pendingAppeal(core: EngineCore, wpId: string): {
|
|
32
|
+
findingIds: string[];
|
|
33
|
+
argument: string;
|
|
34
|
+
} | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* The rework items a developer must address: derived from whichever REJECTION came last,
|
|
37
|
+
* a review (its `requiredChanges`, the only feedback surface a DEV sees) or a test (the
|
|
38
|
+
* defect or gap descriptions). Empty when the WP has not been rejected since its last
|
|
39
|
+
* work submission.
|
|
40
|
+
*/
|
|
41
|
+
export declare function reworkItemsFor(core: EngineCore, wpId: string): RequiredChange[];
|
|
42
|
+
export {};
|