@aperant/framework 0.17.0 → 0.19.0
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 +152 -0
- package/dist/cli/commands/ci-watch.mjs +49 -2
- package/dist/cli/commands/ci-watch.mjs.map +1 -1
- package/dist/cli/commands/commit.mjs +3 -3
- package/dist/cli/commands/commit.mjs.map +1 -1
- package/dist/cli/commands/event.mjs +16 -16
- package/dist/cli/commands/event.mjs.map +1 -1
- package/dist/cli/commands/merge-integrate.mjs +3 -3
- package/dist/cli/commands/merge-integrate.mjs.map +1 -1
- package/dist/cli/commands/produce.d.mts +9 -0
- package/dist/cli/commands/produce.d.mts.map +1 -0
- package/dist/cli/commands/produce.mjs +1345 -0
- package/dist/cli/commands/produce.mjs.map +1 -0
- package/dist/cli/commands/task.d.mts +16 -1
- package/dist/cli/commands/task.d.mts.map +1 -1
- package/dist/cli/commands/task.mjs +434 -266
- package/dist/cli/commands/task.mjs.map +1 -1
- package/dist/cli/coordination/auto-emit-artifact-ready.mjs +5 -5
- package/dist/cli/coordination/auto-emit-artifact-ready.mjs.map +1 -1
- package/dist/cli/coordination/event-schema.d.mts +5 -3
- package/dist/cli/coordination/event-schema.d.mts.map +1 -1
- package/dist/cli/coordination/event-schema.mjs +245 -21
- package/dist/cli/coordination/event-schema.mjs.map +1 -1
- package/dist/cli/coordination/store.d.mts +2 -2
- package/dist/cli/coordination/store.mjs +4 -4
- package/dist/cli/coordination/store.mjs.map +1 -1
- package/dist/cli/dispatch.d.mts.map +1 -1
- package/dist/cli/dispatch.mjs +2 -0
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/help.d.mts.map +1 -1
- package/dist/cli/help.mjs +30 -0
- package/dist/cli/help.mjs.map +1 -1
- package/dist/cli/install/legacy-paths.d.mts.map +1 -1
- package/dist/cli/install/legacy-paths.mjs +2 -0
- package/dist/cli/install/legacy-paths.mjs.map +1 -1
- package/dist/cli/produce/blind-probe.d.mts +85 -0
- package/dist/cli/produce/blind-probe.d.mts.map +1 -0
- package/dist/cli/produce/blind-probe.mjs +217 -0
- package/dist/cli/produce/blind-probe.mjs.map +1 -0
- package/dist/cli/produce/claim.d.mts +188 -0
- package/dist/cli/produce/claim.d.mts.map +1 -0
- package/dist/cli/produce/claim.mjs +518 -0
- package/dist/cli/produce/claim.mjs.map +1 -0
- package/dist/cli/produce/done-gate.d.mts +87 -0
- package/dist/cli/produce/done-gate.d.mts.map +1 -0
- package/dist/cli/produce/done-gate.mjs +200 -0
- package/dist/cli/produce/done-gate.mjs.map +1 -0
- package/dist/cli/produce/events.d.mts +77 -0
- package/dist/cli/produce/events.d.mts.map +1 -0
- package/dist/cli/produce/events.mjs +126 -0
- package/dist/cli/produce/events.mjs.map +1 -0
- package/dist/cli/produce/evidence-oracle.d.mts +63 -0
- package/dist/cli/produce/evidence-oracle.d.mts.map +1 -0
- package/dist/cli/produce/evidence-oracle.mjs +122 -0
- package/dist/cli/produce/evidence-oracle.mjs.map +1 -0
- package/dist/cli/produce/ledger.d.mts +140 -0
- package/dist/cli/produce/ledger.d.mts.map +1 -0
- package/dist/cli/produce/ledger.mjs +272 -0
- package/dist/cli/produce/ledger.mjs.map +1 -0
- package/dist/cli/produce/probe-family.d.mts +53 -0
- package/dist/cli/produce/probe-family.d.mts.map +1 -0
- package/dist/cli/produce/probe-family.mjs +160 -0
- package/dist/cli/produce/probe-family.mjs.map +1 -0
- package/dist/cli/produce/projection.d.mts +55 -0
- package/dist/cli/produce/projection.d.mts.map +1 -0
- package/dist/cli/produce/projection.mjs +97 -0
- package/dist/cli/produce/projection.mjs.map +1 -0
- package/dist/cli/produce/run-id.d.mts +42 -0
- package/dist/cli/produce/run-id.d.mts.map +1 -0
- package/dist/cli/produce/run-id.mjs +79 -0
- package/dist/cli/produce/run-id.mjs.map +1 -0
- package/dist/cli/produce/saga.d.mts +180 -0
- package/dist/cli/produce/saga.d.mts.map +1 -0
- package/dist/cli/produce/saga.mjs +290 -0
- package/dist/cli/produce/saga.mjs.map +1 -0
- package/dist/cli/produce/scheduler.d.mts +165 -0
- package/dist/cli/produce/scheduler.d.mts.map +1 -0
- package/dist/cli/produce/scheduler.mjs +399 -0
- package/dist/cli/produce/scheduler.mjs.map +1 -0
- package/dist/cli/produce/setpoint.d.mts +52 -0
- package/dist/cli/produce/setpoint.d.mts.map +1 -0
- package/dist/cli/produce/setpoint.mjs +113 -0
- package/dist/cli/produce/setpoint.mjs.map +1 -0
- package/dist/cli/produce/verification-ttl.d.mts +75 -0
- package/dist/cli/produce/verification-ttl.d.mts.map +1 -0
- package/dist/cli/produce/verification-ttl.mjs +169 -0
- package/dist/cli/produce/verification-ttl.mjs.map +1 -0
- package/dist/cli/roadmap/{conductor-view.d.mts → showrunner-view.d.mts} +3 -3
- package/dist/cli/roadmap/showrunner-view.d.mts.map +1 -0
- package/dist/cli/roadmap/{conductor-view.mjs → showrunner-view.mjs} +7 -7
- package/dist/cli/roadmap/showrunner-view.mjs.map +1 -0
- package/dist/plugin/.claude-plugin/plugin.json +2 -1
- package/dist/plugin/skills/apt/SKILL.md +112 -38
- package/dist/plugin/skills/apt-debug/SKILL.md +14 -24
- package/dist/plugin/skills/apt-fan-out/SKILL.md +4 -4
- package/dist/plugin/skills/apt-handoff/SKILL.md +1 -1
- package/dist/plugin/skills/apt-plan/SKILL.md +5 -5
- package/dist/plugin/skills/apt-plan/adapters/{conductor.md → showrunner.md} +16 -16
- package/dist/plugin/skills/apt-produce/SKILL.md +606 -0
- package/dist/plugin/skills/apt-quick/SKILL.md +14 -22
- package/dist/plugin/skills/apt-run/SKILL.md +126 -3
- package/dist/plugin/skills/apt-ship/SKILL.md +2 -0
- package/dist/plugin/skills/apt-spar/SKILL.md +5 -3
- package/dist/plugin/skills/apt-watch-ci/SKILL.md +4 -4
- package/package.json +4 -4
- package/prompts/{conductor-framework-context.md → showrunner-framework-context.md} +1 -1
- package/prompts/{conductor-status-check.md → showrunner-status-check.md} +1 -1
- package/prompts/{conductor-sub-agent.md → showrunner-sub-agent.md} +6 -6
- package/prompts/{conductor-system.md → showrunner-system.md} +8 -8
- package/skills/apt/SKILL.md +112 -38
- package/skills/apt-debug/SKILL.md +14 -24
- package/skills/apt-fan-out/SKILL.md +4 -4
- package/skills/apt-handoff/SKILL.md +1 -1
- package/skills/apt-plan/SKILL.md +5 -5
- package/skills/apt-plan/adapters/{conductor.md → showrunner.md} +16 -16
- package/skills/apt-produce/SKILL.md +606 -0
- package/skills/apt-quick/SKILL.md +14 -22
- package/skills/apt-run/SKILL.md +126 -3
- package/skills/apt-ship/SKILL.md +2 -0
- package/skills/apt-spar/SKILL.md +5 -3
- package/skills/apt-watch-ci/SKILL.md +4 -4
- package/src/cli/commands/ci-watch.mjs +51 -2
- package/src/cli/commands/commit.mjs +3 -3
- package/src/cli/commands/event.mjs +16 -16
- package/src/cli/commands/merge-integrate.mjs +3 -3
- package/src/cli/commands/produce.mjs +1466 -0
- package/src/cli/commands/task.mjs +482 -285
- package/src/cli/coordination/auto-emit-artifact-ready.mjs +5 -5
- package/src/cli/coordination/event-schema.d.ts +4 -2
- package/src/cli/coordination/event-schema.mjs +276 -21
- package/src/cli/coordination/store.mjs +4 -4
- package/src/cli/dispatch.mjs +2 -0
- package/src/cli/help.mjs +30 -0
- package/src/cli/install/legacy-paths.mjs +2 -0
- package/src/cli/produce/blind-probe.mjs +245 -0
- package/src/cli/produce/claim.mjs +543 -0
- package/src/cli/produce/done-gate.mjs +238 -0
- package/src/cli/produce/events.mjs +131 -0
- package/src/cli/produce/evidence-oracle.mjs +133 -0
- package/src/cli/produce/ledger.mjs +284 -0
- package/src/cli/produce/probe-family.mjs +168 -0
- package/src/cli/produce/projection.mjs +105 -0
- package/src/cli/produce/run-id.mjs +84 -0
- package/src/cli/produce/saga.mjs +303 -0
- package/src/cli/produce/scheduler.mjs +423 -0
- package/src/cli/produce/setpoint.mjs +122 -0
- package/src/cli/produce/verification-ttl.mjs +191 -0
- package/src/cli/roadmap/showrunner-view.d.ts +10 -0
- package/src/cli/roadmap/{conductor-view.mjs → showrunner-view.mjs} +6 -6
- package/dist/cli/roadmap/conductor-view.d.mts.map +0 -1
- package/dist/cli/roadmap/conductor-view.mjs.map +0 -1
- package/src/cli/roadmap/conductor-view.d.ts +0 -10
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal
|
|
3
|
+
* produce/scheduler.mjs — the deterministic WSJF/WIP "what-next-under-budget"
|
|
4
|
+
* brain (M5, ID-M5-01/ID-M5-02).
|
|
5
|
+
*
|
|
6
|
+
* A NEW CONSUMER that FOLDS the existing M1/M3/M4 projections into a WSJF
|
|
7
|
+
* (Weighted Shortest Job First) ranking of the READY milestones + a WIP-limited
|
|
8
|
+
* pick. It reads `projectRoadmap` (M1 status/order/title), `projectLedger` (M3
|
|
9
|
+
* open acceptance criteria per milestone), `listRegressions` + the run's latest
|
|
10
|
+
* `produce.ttl.reverified` result (M4 unresolved regression), and its OWN small
|
|
11
|
+
* meta-fold over `produce.roadmap.milestone_added` (the additive depends_on /
|
|
12
|
+
* priority / size fields). It NEVER writes to those modules — projection.mjs /
|
|
13
|
+
* ledger.mjs / saga.mjs / verification-ttl.mjs stay BYTE-UNTOUCHED (ID-M5-02).
|
|
14
|
+
*
|
|
15
|
+
* The math is pure integer arithmetic over recorded folds — NO LLM, NO model id
|
|
16
|
+
* (scheduler.mjs is in the produce-no-hardcoded-models.guard scan list, AC11),
|
|
17
|
+
* NO daemon, NO loop. `computeSchedule` emits NO event (a pure read); the wake
|
|
18
|
+
* `tick` is the ONLY writer of `produce.schedule.computed` (ID-M5-06).
|
|
19
|
+
*
|
|
20
|
+
* WSJF(m) = CostOfDelay(m) / JobSize(m)
|
|
21
|
+
* CostOfDelay(m) = W_REGRESS·regression_flag(m) // 5·{0|1}
|
|
22
|
+
* + W_BLOCK ·blocking_count(m) // 3·(# milestones depending on m)
|
|
23
|
+
* + W_VALUE ·open_criteria(m) // 2·(M3 open criteria bound to m)
|
|
24
|
+
* + priority(m) // + optional declared priority (default 0)
|
|
25
|
+
* JobSize(m) = declared_size(m) > 0 ? declared_size(m) : max(open_criteria(m), 1)
|
|
26
|
+
*
|
|
27
|
+
* open_criteria deliberately appears in BOTH the CoD value term AND (as the
|
|
28
|
+
* fallback) in JobSize — standard WSJF (prefer small, high-value jobs); a
|
|
29
|
+
* declared --size decouples them (ID-M5-01). ready_queue is sorted WSJF
|
|
30
|
+
* descending → roadmap first-seen order → milestone_id, a total stable order so
|
|
31
|
+
* two folds of the same log are byte-identical.
|
|
32
|
+
*/
|
|
33
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
34
|
+
import { join } from 'node:path';
|
|
35
|
+
import { readProduceRunEventsMatching } from './events.mjs';
|
|
36
|
+
import { projectLedger } from './ledger.mjs';
|
|
37
|
+
import { projectRoadmap } from './projection.mjs';
|
|
38
|
+
import { listRegressions } from './verification-ttl.mjs';
|
|
39
|
+
/**
|
|
40
|
+
* WSJF cost-of-delay weights (ID-M5-01). Fixed integer constants — the ordering
|
|
41
|
+
* is intentional: a shipped-but-regressed milestone is user-facing breakage
|
|
42
|
+
* (most urgent) > a milestone blocking many downstream ones > a milestone with
|
|
43
|
+
* more open value locked behind it > a human-declared priority nudge.
|
|
44
|
+
*/
|
|
45
|
+
export const W_REGRESS = 5;
|
|
46
|
+
export const W_BLOCK = 3;
|
|
47
|
+
export const W_VALUE = 2;
|
|
48
|
+
/** The default WIP cap when neither a flag nor config supplies one (ID-M5-05). */
|
|
49
|
+
export const DEFAULT_WIP_LIMIT = 1;
|
|
50
|
+
/**
|
|
51
|
+
* Load `.aperant/config.json` defensively (mirrors roadmap/backend-adapter.mjs).
|
|
52
|
+
* Returns `{}` on any read/parse failure so the WIP resolver never throws.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} projectDir
|
|
55
|
+
* @returns {Record<string, unknown>}
|
|
56
|
+
*/
|
|
57
|
+
function loadConfig(projectDir) {
|
|
58
|
+
const p = join(projectDir, '.aperant', 'config.json');
|
|
59
|
+
if (!existsSync(p))
|
|
60
|
+
return {};
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(readFileSync(p, 'utf-8'));
|
|
63
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the WIP cap deterministically (ID-M5-05):
|
|
71
|
+
* `--wip <n>` flag → `.aperant/config.json :: produce.wip` → default 1. A
|
|
72
|
+
* non-numeric / negative / bare `--wip` value is ignored (falls through). The
|
|
73
|
+
* cap is the max number of CONCURRENT in_progress milestones the loop will run.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} projectDir
|
|
76
|
+
* @param {Map<string, string | true>} flags
|
|
77
|
+
* @returns {number}
|
|
78
|
+
*/
|
|
79
|
+
export function resolveWipLimit(projectDir, flags) {
|
|
80
|
+
const flagVal = flags && typeof flags.get === 'function' ? flags.get('wip') : undefined;
|
|
81
|
+
if (typeof flagVal === 'string') {
|
|
82
|
+
const n = Number.parseInt(flagVal, 10);
|
|
83
|
+
if (Number.isFinite(n) && n >= 0)
|
|
84
|
+
return n;
|
|
85
|
+
}
|
|
86
|
+
const cfg = loadConfig(projectDir);
|
|
87
|
+
const produceCfg = cfg && typeof cfg.produce === 'object' && cfg.produce !== null ? cfg.produce : null;
|
|
88
|
+
if (produceCfg && Number.isFinite(produceCfg.wip) && Number(produceCfg.wip) >= 0) {
|
|
89
|
+
return Math.trunc(Number(produceCfg.wip));
|
|
90
|
+
}
|
|
91
|
+
return DEFAULT_WIP_LIMIT;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Fold the run's `produce.roadmap.milestone_added` events into the per-milestone
|
|
95
|
+
* scheduling metadata carried as ADDITIVE optional fields on that event
|
|
96
|
+
* (ID-M5-03): `depends_on` (string[]), `priority` (int, default 0), `size`
|
|
97
|
+
* (positive int | null). An M1-style milestone (added with no new flags) folds
|
|
98
|
+
* to `{ depends_on: [], priority: 0, size: null }` — byte-identical behavior.
|
|
99
|
+
* First-seen wins (idempotent re-adds never clobber).
|
|
100
|
+
*
|
|
101
|
+
* @param {string} projectDir
|
|
102
|
+
* @param {string} runId
|
|
103
|
+
* @returns {Map<string, { depends_on: string[], priority: number, size: number | null }>}
|
|
104
|
+
*/
|
|
105
|
+
export function readMilestoneMeta(projectDir, runId) {
|
|
106
|
+
const events = readProduceRunEventsMatching(projectDir, runId, (op) => op === 'produce.roadmap.milestone_added');
|
|
107
|
+
const meta = new Map();
|
|
108
|
+
for (const e of events) {
|
|
109
|
+
const d = e.data || {};
|
|
110
|
+
const id = typeof d.milestone_id === 'string' ? d.milestone_id : null;
|
|
111
|
+
if (!id || meta.has(id))
|
|
112
|
+
continue;
|
|
113
|
+
const dependsOn = Array.isArray(d.depends_on)
|
|
114
|
+
? d.depends_on.filter((x) => typeof x === 'string' && x.length > 0)
|
|
115
|
+
: [];
|
|
116
|
+
const priority = Number.isFinite(d.priority) ? Math.trunc(Number(d.priority)) : 0;
|
|
117
|
+
const size = Number.isFinite(d.size) && Number(d.size) > 0 ? Math.trunc(Number(d.size)) : null;
|
|
118
|
+
meta.set(id, { depends_on: dependsOn, priority, size });
|
|
119
|
+
}
|
|
120
|
+
return meta;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* CostOfDelay = 5·regression + 3·blocking + 2·open_criteria + priority (all
|
|
124
|
+
* integer). Absent fields default to 0.
|
|
125
|
+
*
|
|
126
|
+
* @param {{ regressionFlag?: number, blockingCount?: number, openCriteria?: number, priority?: number }} args
|
|
127
|
+
* @returns {number}
|
|
128
|
+
*/
|
|
129
|
+
export function computeCostOfDelay({ regressionFlag = 0, blockingCount = 0, openCriteria = 0, priority = 0, } = {}) {
|
|
130
|
+
return (W_REGRESS * (regressionFlag ? 1 : 0) +
|
|
131
|
+
W_BLOCK * blockingCount +
|
|
132
|
+
W_VALUE * openCriteria +
|
|
133
|
+
priority);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* JobSize = declared_size when positive, else the open-criteria count floored at
|
|
137
|
+
* 1 (never 0 — a criteria-less, size-less milestone is the smallest job = 1, so
|
|
138
|
+
* WSJF never divides by zero). ID-M5-01.
|
|
139
|
+
*
|
|
140
|
+
* @param {{ declaredSize?: number | null, openCriteria?: number }} args
|
|
141
|
+
* @returns {number}
|
|
142
|
+
*/
|
|
143
|
+
export function computeJobSize({ declaredSize = null, openCriteria = 0 } = {}) {
|
|
144
|
+
if (Number.isFinite(declaredSize) && Number(declaredSize) > 0)
|
|
145
|
+
return Math.trunc(Number(declaredSize));
|
|
146
|
+
return Math.max(openCriteria, 1);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* WSJF = CostOfDelay / JobSize. JobSize is guaranteed ≥ 1 (computeJobSize) so
|
|
150
|
+
* this never divides by zero.
|
|
151
|
+
*
|
|
152
|
+
* @param {number} costOfDelay
|
|
153
|
+
* @param {number} jobSize
|
|
154
|
+
* @returns {number}
|
|
155
|
+
*/
|
|
156
|
+
export function computeWsjf(costOfDelay, jobSize) {
|
|
157
|
+
return costOfDelay / (jobSize > 0 ? jobSize : 1);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* True iff milestone `milestoneId` has an UNRESOLVED M4 verification-TTL
|
|
161
|
+
* regression — it appears in `listRegressions` AND its latest recorded
|
|
162
|
+
* `produce.ttl.reverified` result is not `pass` (ID-M5-07). A subsequent
|
|
163
|
+
* `--result pass` re-verify resolves it (latest result becomes `pass`).
|
|
164
|
+
*
|
|
165
|
+
* @param {string} projectDir
|
|
166
|
+
* @param {string} runId
|
|
167
|
+
* @param {string} milestoneId
|
|
168
|
+
* @returns {boolean}
|
|
169
|
+
*/
|
|
170
|
+
export function hasUnresolvedRegression(projectDir, runId, milestoneId) {
|
|
171
|
+
const regressed = listRegressions(projectDir, runId).some((r) => r.milestone_id === milestoneId);
|
|
172
|
+
if (!regressed)
|
|
173
|
+
return false;
|
|
174
|
+
const reverifies = readProduceRunEventsMatching(projectDir, runId, (op) => op === 'produce.ttl.reverified');
|
|
175
|
+
let latestResult = null;
|
|
176
|
+
for (const e of reverifies) {
|
|
177
|
+
const d = e.data || {};
|
|
178
|
+
if (d.milestone_id === milestoneId && typeof d.result === 'string')
|
|
179
|
+
latestResult = d.result;
|
|
180
|
+
}
|
|
181
|
+
return latestResult !== 'pass';
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* A ranked ready-queue entry with its WSJF terms exposed.
|
|
185
|
+
*
|
|
186
|
+
* @typedef {Object} ScheduleEntry
|
|
187
|
+
* @property {string} milestone_id
|
|
188
|
+
* @property {string} title
|
|
189
|
+
* @property {string} status
|
|
190
|
+
* @property {number} wsjf
|
|
191
|
+
* @property {number} cost_of_delay
|
|
192
|
+
* @property {number} job_size
|
|
193
|
+
* @property {number} open_criteria
|
|
194
|
+
* @property {number} blocking_count
|
|
195
|
+
* @property {number} priority
|
|
196
|
+
* @property {boolean} regressed
|
|
197
|
+
*/
|
|
198
|
+
/**
|
|
199
|
+
* The pure schedule combiner (ID-M5-02). Folds the existing projections + the
|
|
200
|
+
* meta-fold + regressions into a WSJF-ranked `ready_queue`, a `blocked` list
|
|
201
|
+
* (with `blocked_by` = the not-done deps), the `in_progress` accounting, and a
|
|
202
|
+
* WIP-limited `pick` of the top `wip_available` ready milestones. Emits NO
|
|
203
|
+
* event and mutates no module (a pure read).
|
|
204
|
+
*
|
|
205
|
+
* Ready-set membership: a `planned` milestone is READY iff every `depends_on`
|
|
206
|
+
* id resolves to a roadmap milestone with status `done` (an unknown dep fails
|
|
207
|
+
* closed = unmet); otherwise it goes to `blocked`. A `done` milestone with an
|
|
208
|
+
* unresolved M4 regression re-enters the ready set tagged regressed:true.
|
|
209
|
+
*
|
|
210
|
+
* @param {string} projectDir
|
|
211
|
+
* @param {string} runId
|
|
212
|
+
* @param {{ wipLimit?: number }} [opts]
|
|
213
|
+
* @returns {{
|
|
214
|
+
* run_id: string,
|
|
215
|
+
* wip_limit: number,
|
|
216
|
+
* wip_in_use: number,
|
|
217
|
+
* wip_available: number,
|
|
218
|
+
* ready_queue: ScheduleEntry[],
|
|
219
|
+
* blocked: Array<{ milestone_id: string, title: string, blocked_by: string[] }>,
|
|
220
|
+
* in_progress: Array<{ milestone_id: string, title: string }>,
|
|
221
|
+
* pick: ScheduleEntry[],
|
|
222
|
+
* reason: string,
|
|
223
|
+
* cycle: string[] | null,
|
|
224
|
+
* }}
|
|
225
|
+
*/
|
|
226
|
+
export function computeSchedule(projectDir, runId, { wipLimit = 1 } = {}) {
|
|
227
|
+
const roadmap = projectRoadmap(projectDir, runId);
|
|
228
|
+
const ledger = projectLedger(projectDir, runId);
|
|
229
|
+
const meta = readMilestoneMeta(projectDir, runId);
|
|
230
|
+
// M3 open acceptance criteria per milestone.
|
|
231
|
+
const openByMilestone = new Map();
|
|
232
|
+
for (const c of ledger.criteria) {
|
|
233
|
+
if (c.status === 'open' && typeof c.milestone_id === 'string') {
|
|
234
|
+
openByMilestone.set(c.milestone_id, (openByMilestone.get(c.milestone_id) || 0) + 1);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// blocking_count(m) = # of milestones whose depends_on includes m.
|
|
238
|
+
const blockingCount = new Map();
|
|
239
|
+
for (const m of roadmap.milestones) {
|
|
240
|
+
const dm = meta.get(m.id);
|
|
241
|
+
for (const dep of dm ? dm.depends_on : []) {
|
|
242
|
+
blockingCount.set(dep, (blockingCount.get(dep) || 0) + 1);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const statusById = new Map(roadmap.milestones.map((m) => [m.id, m.status]));
|
|
246
|
+
const firstSeenOrder = new Map(roadmap.milestones.map((m, i) => [m.id, i]));
|
|
247
|
+
const inProgress = roadmap.milestones
|
|
248
|
+
.filter((m) => m.status === 'in_progress')
|
|
249
|
+
.map((m) => ({ milestone_id: m.id, title: m.title }));
|
|
250
|
+
const wipInUse = inProgress.length;
|
|
251
|
+
const wipAvailable = Math.max(0, wipLimit - wipInUse);
|
|
252
|
+
/** Build a ranked ready-queue entry with its WSJF terms exposed. */
|
|
253
|
+
const buildEntry = (m, regressed) => {
|
|
254
|
+
const dm = meta.get(m.id) || { depends_on: [], priority: 0, size: null };
|
|
255
|
+
const openCriteria = openByMilestone.get(m.id) || 0;
|
|
256
|
+
const blocking = blockingCount.get(m.id) || 0;
|
|
257
|
+
const costOfDelay = computeCostOfDelay({
|
|
258
|
+
regressionFlag: regressed ? 1 : 0,
|
|
259
|
+
blockingCount: blocking,
|
|
260
|
+
openCriteria,
|
|
261
|
+
priority: dm.priority,
|
|
262
|
+
});
|
|
263
|
+
const jobSize = computeJobSize({ declaredSize: dm.size, openCriteria });
|
|
264
|
+
return {
|
|
265
|
+
milestone_id: m.id,
|
|
266
|
+
title: m.title,
|
|
267
|
+
status: m.status,
|
|
268
|
+
wsjf: computeWsjf(costOfDelay, jobSize),
|
|
269
|
+
cost_of_delay: costOfDelay,
|
|
270
|
+
job_size: jobSize,
|
|
271
|
+
open_criteria: openCriteria,
|
|
272
|
+
blocking_count: blocking,
|
|
273
|
+
priority: dm.priority,
|
|
274
|
+
regressed,
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
// Ready set = { planned with all deps done } ∪ { done with an unresolved M4
|
|
278
|
+
// regression } (ID-M5-07). A regressed `done` milestone re-enters ready_queue
|
|
279
|
+
// tagged regressed:true; its regression_flag gives it the W_REGRESS=5 CoD
|
|
280
|
+
// weight, so it ranks toward the top and competes for a WIP slot — the loop
|
|
281
|
+
// defends what it shipped. WIP still counts roadmap `in_progress` ONLY (a
|
|
282
|
+
// regressed done milestone is a candidate, not counted as in_progress).
|
|
283
|
+
const readyQueue = [];
|
|
284
|
+
const blocked = [];
|
|
285
|
+
for (const m of roadmap.milestones) {
|
|
286
|
+
if (m.status === 'planned') {
|
|
287
|
+
const dm = meta.get(m.id) || { depends_on: [], priority: 0, size: null };
|
|
288
|
+
// An unknown dep fails closed = unmet (ID-M5-04).
|
|
289
|
+
const notDone = dm.depends_on.filter((dep) => statusById.get(dep) !== 'done');
|
|
290
|
+
if (notDone.length === 0) {
|
|
291
|
+
readyQueue.push(buildEntry(m, false));
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
blocked.push({ milestone_id: m.id, title: m.title, blocked_by: notDone });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else if (m.status === 'done' && hasUnresolvedRegression(projectDir, runId, m.id)) {
|
|
298
|
+
readyQueue.push(buildEntry(m, true));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
sortReadyQueue(readyQueue, firstSeenOrder);
|
|
302
|
+
const pick = readyQueue.slice(0, wipAvailable);
|
|
303
|
+
// A dependency cycle among not-done milestones means none can EVER become
|
|
304
|
+
// ready — surface it as a diagnostic rather than a misleading silent idle
|
|
305
|
+
// (ID-M5-04). The cyclic milestones simply never enter ready_queue.
|
|
306
|
+
const cycle = detectDependencyCycle(roadmap, meta);
|
|
307
|
+
let reason;
|
|
308
|
+
if (wipAvailable === 0)
|
|
309
|
+
reason = 'wip_saturated';
|
|
310
|
+
else if (readyQueue.length === 0)
|
|
311
|
+
reason = cycle ? 'dependency_cycle_detected' : 'no_ready_milestones';
|
|
312
|
+
else
|
|
313
|
+
reason = 'scheduled';
|
|
314
|
+
return {
|
|
315
|
+
run_id: runId,
|
|
316
|
+
wip_limit: wipLimit,
|
|
317
|
+
wip_in_use: wipInUse,
|
|
318
|
+
wip_available: wipAvailable,
|
|
319
|
+
ready_queue: readyQueue,
|
|
320
|
+
blocked,
|
|
321
|
+
in_progress: inProgress,
|
|
322
|
+
pick,
|
|
323
|
+
reason,
|
|
324
|
+
cycle,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Minimal inline DFS over the NOT-DONE dependency sub-graph (ID-M5-04). An edge
|
|
329
|
+
* m → dep exists when m depends on dep AND dep is itself a not-done roadmap
|
|
330
|
+
* milestone (a `done` dep is satisfied; an UNKNOWN dep is a leaf — it can't form
|
|
331
|
+
* a cycle). Returns the offending cycle's milestone ids (the recursion-stack
|
|
332
|
+
* slice from the re-entered gray node), or `null` when the sub-graph is acyclic.
|
|
333
|
+
* Deterministic — iterates in roadmap first-seen order.
|
|
334
|
+
*
|
|
335
|
+
* @param {{ milestones: Array<{ id: string, status: string }> }} roadmap
|
|
336
|
+
* @param {Map<string, { depends_on: string[] }>} meta
|
|
337
|
+
* @returns {string[] | null}
|
|
338
|
+
*/
|
|
339
|
+
function detectDependencyCycle(roadmap, meta) {
|
|
340
|
+
const nodes = roadmap.milestones.filter((m) => m.status !== 'done').map((m) => m.id);
|
|
341
|
+
const nodeSet = new Set(nodes);
|
|
342
|
+
const depsOf = (id) => (meta.get(id)?.depends_on ?? []).filter((dep) => nodeSet.has(dep));
|
|
343
|
+
const WHITE = 0;
|
|
344
|
+
const GRAY = 1;
|
|
345
|
+
const BLACK = 2;
|
|
346
|
+
const color = new Map(nodes.map((n) => [n, WHITE]));
|
|
347
|
+
const stack = [];
|
|
348
|
+
let found = null;
|
|
349
|
+
const visit = (n) => {
|
|
350
|
+
if (found)
|
|
351
|
+
return;
|
|
352
|
+
color.set(n, GRAY);
|
|
353
|
+
stack.push(n);
|
|
354
|
+
for (const dep of depsOf(n)) {
|
|
355
|
+
if (found)
|
|
356
|
+
return;
|
|
357
|
+
const c = color.get(dep);
|
|
358
|
+
if (c === GRAY) {
|
|
359
|
+
found = stack.slice(stack.indexOf(dep));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (c === WHITE)
|
|
363
|
+
visit(dep);
|
|
364
|
+
}
|
|
365
|
+
stack.pop();
|
|
366
|
+
color.set(n, BLACK);
|
|
367
|
+
};
|
|
368
|
+
for (const n of nodes) {
|
|
369
|
+
if (found)
|
|
370
|
+
break;
|
|
371
|
+
if (color.get(n) === WHITE)
|
|
372
|
+
visit(n);
|
|
373
|
+
}
|
|
374
|
+
return found;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Sort a ready-queue in place: WSJF descending → roadmap first-seen order →
|
|
378
|
+
* milestone_id lexicographic. A total, stable order so two folds of the same
|
|
379
|
+
* log are byte-identical (ID-M5-01 tie-break).
|
|
380
|
+
*
|
|
381
|
+
* @param {Array<{ wsjf: number, milestone_id: string }>} queue
|
|
382
|
+
* @param {Map<string, number>} firstSeenOrder
|
|
383
|
+
*/
|
|
384
|
+
function sortReadyQueue(queue, firstSeenOrder) {
|
|
385
|
+
queue.sort((a, b) => {
|
|
386
|
+
if (b.wsjf !== a.wsjf)
|
|
387
|
+
return b.wsjf - a.wsjf;
|
|
388
|
+
const ao = firstSeenOrder.get(a.milestone_id) ?? 0;
|
|
389
|
+
const bo = firstSeenOrder.get(b.milestone_id) ?? 0;
|
|
390
|
+
if (ao !== bo)
|
|
391
|
+
return ao - bo;
|
|
392
|
+
if (a.milestone_id < b.milestone_id)
|
|
393
|
+
return -1;
|
|
394
|
+
if (a.milestone_id > b.milestone_id)
|
|
395
|
+
return 1;
|
|
396
|
+
return 0;
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
//# sourceMappingURL=scheduler.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.mjs","sourceRoot":"","sources":["../../../src/cli/produce/scheduler.mjs"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sha256 of the canonical JSON of the setpoint's immutable core.
|
|
3
|
+
*
|
|
4
|
+
* @param {{ run_id: string, outcome: string, frozen_at: string }} core
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export function computeSetpointHash(core: {
|
|
8
|
+
run_id: string;
|
|
9
|
+
outcome: string;
|
|
10
|
+
frozen_at: string;
|
|
11
|
+
}): string;
|
|
12
|
+
/**
|
|
13
|
+
* The setpoint.json path for a given run dir.
|
|
14
|
+
* @param {string} runDir
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
export function setpointPathFor(runDir: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Exclusive-create write of the setpoint. Uses `openSync(path, 'wx')` on the
|
|
20
|
+
* FINAL path (NOT a tmp+rename) so a SECOND write throws EEXIST — the setpoint
|
|
21
|
+
* is immutable. Any attempt to overwrite an existing setpoint throws.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} setpointPath
|
|
24
|
+
* @param {object} setpoint
|
|
25
|
+
*/
|
|
26
|
+
export function writeSetpointExclusive(setpointPath: string, setpoint: object): void;
|
|
27
|
+
/**
|
|
28
|
+
* Read + parse the frozen setpoint for a run dir. Returns null when absent or
|
|
29
|
+
* malformed (never throws).
|
|
30
|
+
*
|
|
31
|
+
* @param {string} runDir
|
|
32
|
+
* @returns {object | null}
|
|
33
|
+
*/
|
|
34
|
+
export function readSetpoint(runDir: string): object | null;
|
|
35
|
+
/**
|
|
36
|
+
* Freeze the setpoint for a run, idempotently. If already frozen, returns the
|
|
37
|
+
* byte-identical stored setpoint with `reused: true`. Otherwise writes it once
|
|
38
|
+
* via exclusive create and returns `reused: false`.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} runDir
|
|
41
|
+
* @param {{ run_id: string, outcome: string }} args
|
|
42
|
+
* @param {Date} [now]
|
|
43
|
+
* @returns {{ reused: boolean, setpoint: object }}
|
|
44
|
+
*/
|
|
45
|
+
export function freezeSetpoint(runDir: string, { run_id, outcome }: {
|
|
46
|
+
run_id: string;
|
|
47
|
+
outcome: string;
|
|
48
|
+
}, now?: Date): {
|
|
49
|
+
reused: boolean;
|
|
50
|
+
setpoint: object;
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=setpoint.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setpoint.d.mts","sourceRoot":"","sources":["../../../src/cli/produce/setpoint.mjs"],"names":[],"mappings":"AAoCA;;;;;GAKG;AACH,0CAHW;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACpD,MAAM,CAIlB;AAED;;;;GAIG;AACH,wCAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;;;GAOG;AACH,qDAHW,MAAM,YACN,MAAM,QAYhB;AAED;;;;;;GAMG;AACH,qCAHW,MAAM,GACJ,MAAM,GAAG,IAAI,CAUzB;AAED;;;;;;;;;GASG;AACH,uCALW,MAAM,uBACN;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,QACnC,IAAI,GACF;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAqBjD"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal
|
|
3
|
+
* produce/setpoint.mjs — the FROZEN acceptance setpoint (M1, ID-02).
|
|
4
|
+
*
|
|
5
|
+
* The setpoint is the fixed external reference the parent spar requires
|
|
6
|
+
* instead of the Showrunner's moving opinion. It is written ONCE via
|
|
7
|
+
* `openSync(path, 'wx')` (exclusive create) — a second write throws EEXIST, so
|
|
8
|
+
* the reference cannot drift. `produce start` on a run whose setpoint already
|
|
9
|
+
* exists is idempotent-reuse: it returns the byte-identical stored setpoint,
|
|
10
|
+
* NEVER an overwrite.
|
|
11
|
+
*
|
|
12
|
+
* File shape: `{ run_id, outcome, frozen_at, setpoint_hash }` where
|
|
13
|
+
* `setpoint_hash = sha256(canonical JSON of { run_id, outcome, frozen_at })`.
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from 'node:crypto';
|
|
16
|
+
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, writeSync, } from 'node:fs';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
/** Stable-key-order JSON of the hashable setpoint core. */
|
|
19
|
+
function canonicalSetpointJson(core) {
|
|
20
|
+
return JSON.stringify({
|
|
21
|
+
run_id: core.run_id,
|
|
22
|
+
outcome: core.outcome,
|
|
23
|
+
frozen_at: core.frozen_at,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* sha256 of the canonical JSON of the setpoint's immutable core.
|
|
28
|
+
*
|
|
29
|
+
* @param {{ run_id: string, outcome: string, frozen_at: string }} core
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
export function computeSetpointHash(core) {
|
|
33
|
+
return createHash('sha256').update(canonicalSetpointJson(core)).digest('hex');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The setpoint.json path for a given run dir.
|
|
37
|
+
* @param {string} runDir
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
export function setpointPathFor(runDir) {
|
|
41
|
+
return join(runDir, 'setpoint.json');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Exclusive-create write of the setpoint. Uses `openSync(path, 'wx')` on the
|
|
45
|
+
* FINAL path (NOT a tmp+rename) so a SECOND write throws EEXIST — the setpoint
|
|
46
|
+
* is immutable. Any attempt to overwrite an existing setpoint throws.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} setpointPath
|
|
49
|
+
* @param {object} setpoint
|
|
50
|
+
*/
|
|
51
|
+
export function writeSetpointExclusive(setpointPath, setpoint) {
|
|
52
|
+
mkdirSync(dirname(setpointPath), { recursive: true });
|
|
53
|
+
const payload = `${JSON.stringify(setpoint, null, 2)}\n`;
|
|
54
|
+
const fd = openSync(setpointPath, 'wx'); // throws EEXIST when already frozen
|
|
55
|
+
try {
|
|
56
|
+
writeSync(fd, payload, 0, 'utf-8');
|
|
57
|
+
fsyncSync(fd);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
closeSync(fd);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Read + parse the frozen setpoint for a run dir. Returns null when absent or
|
|
65
|
+
* malformed (never throws).
|
|
66
|
+
*
|
|
67
|
+
* @param {string} runDir
|
|
68
|
+
* @returns {object | null}
|
|
69
|
+
*/
|
|
70
|
+
export function readSetpoint(runDir) {
|
|
71
|
+
const p = setpointPathFor(runDir);
|
|
72
|
+
if (!existsSync(p))
|
|
73
|
+
return null;
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(readFileSync(p, 'utf-8'));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Freeze the setpoint for a run, idempotently. If already frozen, returns the
|
|
83
|
+
* byte-identical stored setpoint with `reused: true`. Otherwise writes it once
|
|
84
|
+
* via exclusive create and returns `reused: false`.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} runDir
|
|
87
|
+
* @param {{ run_id: string, outcome: string }} args
|
|
88
|
+
* @param {Date} [now]
|
|
89
|
+
* @returns {{ reused: boolean, setpoint: object }}
|
|
90
|
+
*/
|
|
91
|
+
export function freezeSetpoint(runDir, { run_id, outcome }, now = new Date()) {
|
|
92
|
+
const existing = readSetpoint(runDir);
|
|
93
|
+
if (existing)
|
|
94
|
+
return { reused: true, setpoint: existing };
|
|
95
|
+
const frozen_at = now.toISOString();
|
|
96
|
+
const core = { run_id, outcome, frozen_at };
|
|
97
|
+
const setpoint = { ...core, setpoint_hash: computeSetpointHash(core) };
|
|
98
|
+
try {
|
|
99
|
+
writeSetpointExclusive(setpointPathFor(runDir), setpoint);
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
// Lost a concurrent race — the winner's file is authoritative; re-read
|
|
103
|
+
// it byte-identically rather than overwriting (ID-02).
|
|
104
|
+
if (e && e.code === 'EEXIST') {
|
|
105
|
+
const winner = readSetpoint(runDir);
|
|
106
|
+
if (winner)
|
|
107
|
+
return { reused: true, setpoint: winner };
|
|
108
|
+
}
|
|
109
|
+
throw e;
|
|
110
|
+
}
|
|
111
|
+
return { reused: false, setpoint };
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=setpoint.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setpoint.mjs","sourceRoot":"","sources":["../../../src/cli/produce/setpoint.mjs"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select a DETERMINISTIC rotating sample of already-`done` milestones STALE
|
|
3
|
+
* against HEAD (ID-M4-03 / AC7). REUSES `selectSweepSample` for the rotation
|
|
4
|
+
* order (fewest/oldest last-probe-run first), then annotates each candidate with
|
|
5
|
+
* staleness against `headSha` and FILTERS OUT the FRESH ones (last re-verified
|
|
6
|
+
* against the current HEAD). Returns the first `sample` STALE candidates. An
|
|
7
|
+
* absent HEAD sha fails closed (nothing selected).
|
|
8
|
+
*
|
|
9
|
+
* @param {string} projectDir
|
|
10
|
+
* @param {string} runId
|
|
11
|
+
* @param {{ headSha: string, sample?: number }} args
|
|
12
|
+
* @returns {{ run_id: string, head_sha: string | null, sample_size: number,
|
|
13
|
+
* selected: Array<{ milestone_id: string, title: string, last_reverify_head_sha: string | null, last_reverify_at: string | null, stale: boolean }> }}
|
|
14
|
+
*/
|
|
15
|
+
export function selectTtlReverifySample(projectDir: string, runId: string, { headSha, sample }?: {
|
|
16
|
+
headSha: string;
|
|
17
|
+
sample?: number;
|
|
18
|
+
}): {
|
|
19
|
+
run_id: string;
|
|
20
|
+
head_sha: string | null;
|
|
21
|
+
sample_size: number;
|
|
22
|
+
selected: Array<{
|
|
23
|
+
milestone_id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
last_reverify_head_sha: string | null;
|
|
26
|
+
last_reverify_at: string | null;
|
|
27
|
+
stale: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Record a scheduled TTL re-verify RUN against HEAD (ID-M4-03). REUSES the M2
|
|
32
|
+
* cross-family guard (refuses a same-family re-verify, or when no implementer
|
|
33
|
+
* family is recorded). Appends `produce.ttl.reverified {run_id, milestone_id,
|
|
34
|
+
* head_sha, result}`; on `result === 'fail'` ALSO appends
|
|
35
|
+
* `produce.ttl.regression_detected` AND fires the FORWARD compensator via
|
|
36
|
+
* saga.mjs::compensatePhase({phase:'verify', reason:'ttl_regression@<sha>'}) — a
|
|
37
|
+
* revert-branch + reopen + re-plan, NEVER a merged-PR rollback. A `pass`
|
|
38
|
+
* refreshes verification against the new HEAD and logs no regression.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} projectDir
|
|
41
|
+
* @param {{ runId: string, milestoneId: string, result: string, probeFamily: string, headSha: string }} args
|
|
42
|
+
* @returns {{ ok: true, result: string, probe_family: string, head_sha: string, regression: boolean, compensator: object | null }
|
|
43
|
+
* | { ok: false, reason: string }}
|
|
44
|
+
*/
|
|
45
|
+
export function recordTtlReverify(projectDir: string, { runId, milestoneId, result, probeFamily, headSha }: {
|
|
46
|
+
runId: string;
|
|
47
|
+
milestoneId: string;
|
|
48
|
+
result: string;
|
|
49
|
+
probeFamily: string;
|
|
50
|
+
headSha: string;
|
|
51
|
+
}): {
|
|
52
|
+
ok: true;
|
|
53
|
+
result: string;
|
|
54
|
+
probe_family: string;
|
|
55
|
+
head_sha: string;
|
|
56
|
+
regression: boolean;
|
|
57
|
+
compensator: object | null;
|
|
58
|
+
} | {
|
|
59
|
+
ok: false;
|
|
60
|
+
reason: string;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Read-only fold of the run's `produce.ttl.regression_detected` events (for saga
|
|
64
|
+
* status / canary context). Chronological order.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} projectDir
|
|
67
|
+
* @param {string} runId
|
|
68
|
+
* @returns {Array<{ milestone_id: string | null, head_sha: string | null, t: string | null }>}
|
|
69
|
+
*/
|
|
70
|
+
export function listRegressions(projectDir: string, runId: string): Array<{
|
|
71
|
+
milestone_id: string | null;
|
|
72
|
+
head_sha: string | null;
|
|
73
|
+
t: string | null;
|
|
74
|
+
}>;
|
|
75
|
+
//# sourceMappingURL=verification-ttl.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verification-ttl.d.mts","sourceRoot":"","sources":["../../../src/cli/produce/verification-ttl.mjs"],"names":[],"mappings":"AAiEA;;;;;;;;;;;;;GAaG;AACH,oDANW,MAAM,SACN,MAAM,wBACN;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IACvE,QAAQ,EAAE,KAAK,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CAAE,CAiCtJ;AAED;;;;;;;;;;;;;;GAcG;AACH,8CALW,MAAM,wDACN;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC1F;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC3H;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CA4CnC;AAED;;;;;;;GAOG;AACH,4CAJW,MAAM,SACN,MAAM,GACJ,KAAK,CAAC;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAa7F"}
|