@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,1345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands/produce.mjs — `/apt:produce` M1 CLI substrate.
|
|
3
|
+
*
|
|
4
|
+
* Subcommands (mirrors commands/roadmap.mjs's (subcommand, projectDir, extraArgs)
|
|
5
|
+
* shape): start | add-milestone | set-milestone-status | status | tick.
|
|
6
|
+
*
|
|
7
|
+
* M1 is the outer-loop SKELETON: it freezes an immutable acceptance setpoint,
|
|
8
|
+
* seeds + projects the roadmap crash-safely from the append-only event log, and
|
|
9
|
+
* advances one wake tick per invocation. It does NOT adjudicate "done" — that
|
|
10
|
+
* is external-oracle-gated and deferred to M2 (ID-03).
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
13
|
+
import { createCoordinationStore } from '../coordination/store.mjs';
|
|
14
|
+
import { freezeProbeSet, recordProbeRun, selectSweepSample } from '../produce/blind-probe.mjs';
|
|
15
|
+
import { consumeClaims, freezeClaim, projectClaims } from '../produce/claim.mjs';
|
|
16
|
+
import { adjudicateDone, computeCanary } from '../produce/done-gate.mjs';
|
|
17
|
+
import { appendProduceEvent, readImplementerFamily, readProduceRunEventsMatching, } from '../produce/events.mjs';
|
|
18
|
+
import { prepareOracleInput, recordOracleVerdict } from '../produce/evidence-oracle.mjs';
|
|
19
|
+
import { closeCriteriaForShippedMilestone, computeOpenCriteria, computeProgress, evaluateDeclareGuard, mintCriterionId, } from '../produce/ledger.mjs';
|
|
20
|
+
import { resolveProbeFamily } from '../produce/probe-family.mjs';
|
|
21
|
+
import { projectRoadmap, writeProjectionCache } from '../produce/projection.mjs';
|
|
22
|
+
import { mintRunId, relativeSetpointPath, resolveRunDir, slugFromOutcome, } from '../produce/run-id.mjs';
|
|
23
|
+
import { compensatePhase, completePhase, enterPhase, projectSaga, recordReview, resolveReviewer, SAGA_PHASES, } from '../produce/saga.mjs';
|
|
24
|
+
import { computeSchedule, resolveWipLimit } from '../produce/scheduler.mjs';
|
|
25
|
+
import { freezeSetpoint, readSetpoint } from '../produce/setpoint.mjs';
|
|
26
|
+
import { listRegressions, recordTtlReverify, selectTtlReverifySample, } from '../produce/verification-ttl.mjs';
|
|
27
|
+
import { parseFlags } from '../util/args.mjs';
|
|
28
|
+
import { err, ok } from '../util/result.mjs';
|
|
29
|
+
/**
|
|
30
|
+
* Top-level dispatcher.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} subcommand
|
|
33
|
+
* @param {string} projectDir
|
|
34
|
+
* @param {string[]} extraArgs
|
|
35
|
+
*/
|
|
36
|
+
export async function cmdProduce(subcommand, projectDir, extraArgs) {
|
|
37
|
+
if (!subcommand)
|
|
38
|
+
return err('Usage: apt-tools produce <start|add-milestone|set-milestone-status|status|tick|declare-criteria|progress|record-implementer|resolve-probe-family|oracle|probe|canary|saga|ttl|schedule|claim> <project-dir>');
|
|
39
|
+
if (!projectDir)
|
|
40
|
+
return err(`Usage: apt-tools produce ${subcommand} <project-dir>`);
|
|
41
|
+
const flags = parseFlags(extraArgs);
|
|
42
|
+
switch (subcommand) {
|
|
43
|
+
case 'start':
|
|
44
|
+
return cmdStart(projectDir, flags);
|
|
45
|
+
case 'add-milestone':
|
|
46
|
+
return cmdAddMilestone(projectDir, flags);
|
|
47
|
+
case 'set-milestone-status':
|
|
48
|
+
return cmdSetMilestoneStatus(projectDir, flags);
|
|
49
|
+
case 'status':
|
|
50
|
+
return cmdStatus(projectDir, flags);
|
|
51
|
+
case 'tick':
|
|
52
|
+
return cmdTick(projectDir, flags);
|
|
53
|
+
// M3 — the Lyapunov acceptance-criteria ledger: declare an open criterion
|
|
54
|
+
// behind the write-time anti-windup guard (ID-M3-03 / ID-M3-04), and the
|
|
55
|
+
// read-time monotone-progress report (ID-M3-02 — flags, never rejects).
|
|
56
|
+
case 'declare-criteria':
|
|
57
|
+
return cmdDeclareCriteria(projectDir, flags);
|
|
58
|
+
case 'progress':
|
|
59
|
+
return cmdProgress(projectDir, flags);
|
|
60
|
+
// M2 — the cross-family disjointness anchor (ID-M2-02) + resolver (ST2).
|
|
61
|
+
case 'record-implementer':
|
|
62
|
+
return cmdRecordImplementer(projectDir, flags);
|
|
63
|
+
case 'resolve-probe-family':
|
|
64
|
+
return cmdResolveProbeFamily(projectDir, flags);
|
|
65
|
+
// M2 — two-level subcommands: `produce oracle <prepare|record>` (ST3) +
|
|
66
|
+
// `produce probe <freeze|record|sweep>` (ST4). `projectDir` here holds the
|
|
67
|
+
// sub-subcommand; the real dir is extraArgs[0].
|
|
68
|
+
case 'oracle':
|
|
69
|
+
return cmdOracle(projectDir, extraArgs);
|
|
70
|
+
case 'probe':
|
|
71
|
+
return cmdProbe(projectDir, extraArgs);
|
|
72
|
+
// M2 — the reward-hacking canary (ID-M2-06 / ST5).
|
|
73
|
+
case 'canary':
|
|
74
|
+
return cmdCanary(projectDir, flags);
|
|
75
|
+
// M4 — the saga phase model + FORWARD compensators + provider-diverse
|
|
76
|
+
// review, a two-level subcommand (same shape as oracle/probe: the
|
|
77
|
+
// sub-subcommand arrives as `projectDir`, the real dir is extraArgs[0]).
|
|
78
|
+
case 'saga':
|
|
79
|
+
return cmdSaga(projectDir, extraArgs);
|
|
80
|
+
// M4 — the verification-TTL: a rotating re-verify of OLD done milestones
|
|
81
|
+
// against HEAD (`ttl select`) + the cross-family re-verify RUN that flags +
|
|
82
|
+
// compensates regressions (`ttl reverify`). Two-level (same shape as saga).
|
|
83
|
+
case 'ttl':
|
|
84
|
+
return cmdTtl(projectDir, extraArgs);
|
|
85
|
+
// M5 — the WSJF/WIP scheduler: a read-only ranking of the ready milestones
|
|
86
|
+
// + a WIP-limited pick (dependency-aware, regression-aware). Single-level
|
|
87
|
+
// (mirrors cmdStatus/cmdCanary read-only shape); emits nothing (ID-M5-06).
|
|
88
|
+
case 'schedule':
|
|
89
|
+
return cmdSchedule(projectDir, flags);
|
|
90
|
+
// M6 — the Claim Envelope lifecycle, a two-level subcommand (same shape as
|
|
91
|
+
// oracle/probe/saga/ttl: the sub-subcommand arrives as `projectDir`, the
|
|
92
|
+
// real dir is extraArgs[0]).
|
|
93
|
+
case 'claim':
|
|
94
|
+
return cmdClaim(projectDir, extraArgs);
|
|
95
|
+
default:
|
|
96
|
+
return err(`Unknown produce subcommand: ${subcommand}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Require a frozen run; returns { setpoint } or an err() envelope. */
|
|
100
|
+
function requireRun(projectDir, runId, cmd) {
|
|
101
|
+
if (!runId)
|
|
102
|
+
return { error: err(`produce ${cmd} requires --run-id <id>`) };
|
|
103
|
+
const setpoint = readSetpoint(resolveRunDir(projectDir, runId));
|
|
104
|
+
if (!setpoint)
|
|
105
|
+
return {
|
|
106
|
+
error: err(`produce ${cmd}: no frozen setpoint for run ${runId} — run produce start first`),
|
|
107
|
+
};
|
|
108
|
+
return { setpoint };
|
|
109
|
+
}
|
|
110
|
+
/** Resolve a string flag or null. */
|
|
111
|
+
function strFlag(flags, name) {
|
|
112
|
+
const v = flags.get(name);
|
|
113
|
+
return typeof v === 'string' ? v : null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* `produce start . --outcome "<o>" [--run-id <id>]` — freeze the acceptance
|
|
117
|
+
* setpoint and, on a fresh freeze, emit produce.run.started +
|
|
118
|
+
* produce.setpoint.frozen. Re-running with the same --run-id is idempotent
|
|
119
|
+
* reuse (never overwrites — ID-02).
|
|
120
|
+
*/
|
|
121
|
+
function cmdStart(projectDir, flags) {
|
|
122
|
+
const outcome = strFlag(flags, 'outcome');
|
|
123
|
+
const explicitRunId = strFlag(flags, 'run-id');
|
|
124
|
+
let runId = explicitRunId;
|
|
125
|
+
if (!runId) {
|
|
126
|
+
if (!outcome)
|
|
127
|
+
return err('produce start requires --outcome "<text>" (or --run-id <id> to reuse an existing run)');
|
|
128
|
+
runId = mintRunId(outcome);
|
|
129
|
+
}
|
|
130
|
+
const runDir = resolveRunDir(projectDir, runId);
|
|
131
|
+
const existing = readSetpoint(runDir);
|
|
132
|
+
if (!existing && !outcome)
|
|
133
|
+
return err(`produce start --run-id ${runId} has no frozen setpoint yet; pass --outcome "<text>" to freeze it`);
|
|
134
|
+
const outcomeForFreeze = existing ? existing.outcome : outcome;
|
|
135
|
+
const { reused, setpoint } = freezeSetpoint(runDir, { run_id: runId, outcome: outcomeForFreeze });
|
|
136
|
+
const setpointPath = relativeSetpointPath(runId);
|
|
137
|
+
// Emit the run-start + setpoint-frozen events only on a FRESH freeze;
|
|
138
|
+
// idempotent reuse never re-logs (ID-02).
|
|
139
|
+
if (!reused) {
|
|
140
|
+
const store = createCoordinationStore(projectDir);
|
|
141
|
+
store.appendEvent({
|
|
142
|
+
op: 'produce.run.started',
|
|
143
|
+
task: runId,
|
|
144
|
+
data: { run_id: runId, setpoint_hash: setpoint.setpoint_hash },
|
|
145
|
+
});
|
|
146
|
+
store.appendEvent({
|
|
147
|
+
op: 'produce.setpoint.frozen',
|
|
148
|
+
task: runId,
|
|
149
|
+
data: {
|
|
150
|
+
run_id: runId,
|
|
151
|
+
setpoint_hash: setpoint.setpoint_hash,
|
|
152
|
+
setpoint_path: setpointPath,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return ok({
|
|
157
|
+
status: 'ok',
|
|
158
|
+
command: 'produce-start',
|
|
159
|
+
reused,
|
|
160
|
+
run_id: runId,
|
|
161
|
+
outcome: setpoint.outcome,
|
|
162
|
+
frozen_at: setpoint.frozen_at,
|
|
163
|
+
setpoint_path: setpointPath,
|
|
164
|
+
setpoint_hash: setpoint.setpoint_hash,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* `produce add-milestone . --run-id <id> --title "<t>" [--id <mid>]
|
|
169
|
+
* [--depends-on a,b,c] [--priority <int>] [--size <positive int>]` — append
|
|
170
|
+
* produce.roadmap.milestone_added, then refresh the derived roadmap.json cache.
|
|
171
|
+
* The milestone id is minted with a random suffix when not supplied so N
|
|
172
|
+
* concurrent auto-id writers never collide (parent TD-06 / subtask 4).
|
|
173
|
+
*
|
|
174
|
+
* M5 (ID-M5-03): --depends-on / --priority / --size ride as ADDITIVE OPTIONAL
|
|
175
|
+
* data fields on the EXISTING event (no schema change — run_id/milestone_id/title
|
|
176
|
+
* stay the only required fields). An M1-style add (no new flags) is
|
|
177
|
+
* byte-identical: the event carries none of the extra fields.
|
|
178
|
+
*/
|
|
179
|
+
function cmdAddMilestone(projectDir, flags) {
|
|
180
|
+
const runId = strFlag(flags, 'run-id');
|
|
181
|
+
const title = strFlag(flags, 'title');
|
|
182
|
+
const gate = requireRun(projectDir, runId, 'add-milestone');
|
|
183
|
+
if (gate.error)
|
|
184
|
+
return gate.error;
|
|
185
|
+
if (!title)
|
|
186
|
+
return err('produce add-milestone requires --title "<text>"');
|
|
187
|
+
const milestoneId = strFlag(flags, 'id') || `${slugFromOutcome(title)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
188
|
+
const data = { run_id: runId, milestone_id: milestoneId, title };
|
|
189
|
+
// Additive scheduling meta (M5) — only stamped when the flag is present, so an
|
|
190
|
+
// M1-style add stays byte-identical (ID-M5-03).
|
|
191
|
+
const dependsOnRaw = strFlag(flags, 'depends-on');
|
|
192
|
+
if (dependsOnRaw) {
|
|
193
|
+
const deps = dependsOnRaw
|
|
194
|
+
.split(',')
|
|
195
|
+
.map((s) => s.trim())
|
|
196
|
+
.filter(Boolean);
|
|
197
|
+
if (deps.length > 0)
|
|
198
|
+
data.depends_on = deps;
|
|
199
|
+
}
|
|
200
|
+
const priorityRaw = strFlag(flags, 'priority');
|
|
201
|
+
if (priorityRaw !== null) {
|
|
202
|
+
const p = Number.parseInt(priorityRaw, 10);
|
|
203
|
+
if (Number.isFinite(p))
|
|
204
|
+
data.priority = p;
|
|
205
|
+
}
|
|
206
|
+
const sizeRaw = strFlag(flags, 'size');
|
|
207
|
+
if (sizeRaw !== null) {
|
|
208
|
+
const s = Number.parseInt(sizeRaw, 10);
|
|
209
|
+
if (Number.isFinite(s) && s > 0)
|
|
210
|
+
data.size = s;
|
|
211
|
+
}
|
|
212
|
+
appendProduceEvent(projectDir, 'produce.roadmap.milestone_added', data);
|
|
213
|
+
// The event log is the authority; the cache is a derived convenience,
|
|
214
|
+
// folded + persisted under a single-writer lock (never lost-update).
|
|
215
|
+
const roadmap = writeProjectionCache(projectDir, runId);
|
|
216
|
+
return ok({
|
|
217
|
+
status: 'ok',
|
|
218
|
+
command: 'produce-add-milestone',
|
|
219
|
+
run_id: runId,
|
|
220
|
+
milestone_id: milestoneId,
|
|
221
|
+
title,
|
|
222
|
+
roadmap,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* `produce set-milestone-status . --run-id <id> --milestone <mid> --status <s>`
|
|
227
|
+
* — append produce.roadmap.milestone_status_changed. `--status done` is the M2
|
|
228
|
+
* two-gate adjudication (ID-M2-04) — accepted ONLY when the visible oracle AND
|
|
229
|
+
* the blind probe both agree; a bare done with no recorded external evidence is
|
|
230
|
+
* STILL refused (the Showrunner never self-attests, ID-03). `planned` /
|
|
231
|
+
* `in_progress` are byte-identical to M1.
|
|
232
|
+
*/
|
|
233
|
+
function cmdSetMilestoneStatus(projectDir, flags) {
|
|
234
|
+
const runId = strFlag(flags, 'run-id');
|
|
235
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
236
|
+
const status = strFlag(flags, 'status');
|
|
237
|
+
const gate = requireRun(projectDir, runId, 'set-milestone-status');
|
|
238
|
+
if (gate.error)
|
|
239
|
+
return gate.error;
|
|
240
|
+
if (!milestoneId)
|
|
241
|
+
return err('produce set-milestone-status requires --milestone <mid>');
|
|
242
|
+
if (!status)
|
|
243
|
+
return err('produce set-milestone-status requires --status <planned|in_progress|done>');
|
|
244
|
+
// M2 — `done` is the two-gate adjudication over recorded external evidence,
|
|
245
|
+
// replacing M1's blanket reject (ID-M2-04). See cmdSetMilestoneDone.
|
|
246
|
+
if (status === 'done')
|
|
247
|
+
return cmdSetMilestoneDone(projectDir, runId, milestoneId, flags);
|
|
248
|
+
if (status !== 'planned' && status !== 'in_progress')
|
|
249
|
+
return err(`produce set-milestone-status: invalid --status "${status}". Allowed: planned | in_progress | done.`);
|
|
250
|
+
const before = projectRoadmap(projectDir, runId);
|
|
251
|
+
if (!before.milestones.some((m) => m.id === milestoneId))
|
|
252
|
+
return err(`produce set-milestone-status: milestone ${milestoneId} not found in run ${runId}`);
|
|
253
|
+
appendProduceEvent(projectDir, 'produce.roadmap.milestone_status_changed', {
|
|
254
|
+
run_id: runId,
|
|
255
|
+
milestone_id: milestoneId,
|
|
256
|
+
status,
|
|
257
|
+
});
|
|
258
|
+
const roadmap = writeProjectionCache(projectDir, runId);
|
|
259
|
+
return ok({
|
|
260
|
+
status: 'ok',
|
|
261
|
+
command: 'produce-set-milestone-status',
|
|
262
|
+
run_id: runId,
|
|
263
|
+
milestone_id: milestoneId,
|
|
264
|
+
milestone_status: status,
|
|
265
|
+
roadmap,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* `produce status . --run-id <id>` — read-only re-projection of the roadmap
|
|
270
|
+
* from the append-only event log (no in-memory authority — ID-01 / AC2). Does
|
|
271
|
+
* NOT write the cache (pure read).
|
|
272
|
+
*/
|
|
273
|
+
function cmdStatus(projectDir, flags) {
|
|
274
|
+
const runId = strFlag(flags, 'run-id');
|
|
275
|
+
const gate = requireRun(projectDir, runId, 'status');
|
|
276
|
+
if (gate.error)
|
|
277
|
+
return gate.error;
|
|
278
|
+
const roadmap = projectRoadmap(projectDir, runId);
|
|
279
|
+
return ok({
|
|
280
|
+
status: 'ok',
|
|
281
|
+
command: 'produce-status',
|
|
282
|
+
run_id: runId,
|
|
283
|
+
setpoint_hash: gate.setpoint.setpoint_hash,
|
|
284
|
+
roadmap,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* `produce tick . --run-id <id> [--wip N]` — run EXACTLY ONE wake tick: re-project
|
|
289
|
+
* the roadmap from disk (ID-01), compute the M5 WSJF/WIP schedule, emit
|
|
290
|
+
* produce.tick + ONE produce.schedule.computed, set next_action to the scheduler's
|
|
291
|
+
* top pick, and return a reschedule hint, then EXIT. There is no polling loop and
|
|
292
|
+
* no self-scheduling timer — the recurring beat is delivered by the host (ID-05).
|
|
293
|
+
*
|
|
294
|
+
* next_action is scheduler-driven but SHAPE-PRESERVING (ID-M5-08): an in_progress
|
|
295
|
+
* milestone → continue_milestone (unconditional — always continue in-flight work,
|
|
296
|
+
* regardless of WIP); else the top WSJF ready pick under the WIP budget →
|
|
297
|
+
* start_milestone; else idle (carrying the schedule's reason). It is advisory;
|
|
298
|
+
* the Showrunner never self-attests a milestone done (external-oracle-gated, M2 —
|
|
299
|
+
* ID-03).
|
|
300
|
+
*/
|
|
301
|
+
function cmdTick(projectDir, flags) {
|
|
302
|
+
const runId = strFlag(flags, 'run-id');
|
|
303
|
+
const gate = requireRun(projectDir, runId, 'tick');
|
|
304
|
+
if (gate.error)
|
|
305
|
+
return gate.error;
|
|
306
|
+
// Re-read the roadmap from the append-only log — no in-memory authority.
|
|
307
|
+
const roadmap = projectRoadmap(projectDir, runId);
|
|
308
|
+
// M5 — fold the WSJF/WIP schedule (dependency-aware, regression-aware) under
|
|
309
|
+
// the resolved WIP budget. A pure read; the tick is the ONLY writer below.
|
|
310
|
+
const wipLimit = resolveWipLimit(projectDir, flags);
|
|
311
|
+
const schedule = computeSchedule(projectDir, runId, { wipLimit });
|
|
312
|
+
appendProduceEvent(projectDir, 'produce.tick', { run_id: runId });
|
|
313
|
+
// One recorded schedule fact per tick (ID-M5-06) — content-blind (picked ids +
|
|
314
|
+
// wip_limit only; the roadmap projection never folds produce.schedule.*).
|
|
315
|
+
appendProduceEvent(projectDir, 'produce.schedule.computed', {
|
|
316
|
+
run_id: runId,
|
|
317
|
+
picked: schedule.pick.map((p) => p.milestone_id).join(','),
|
|
318
|
+
wip_limit: String(wipLimit),
|
|
319
|
+
});
|
|
320
|
+
// Scheduler-driven next_action, shape-preserving (ID-M5-08). Continue an
|
|
321
|
+
// in-flight milestone unconditionally; else start the top WSJF ready pick under
|
|
322
|
+
// the WIP budget; else idle with the schedule's reason.
|
|
323
|
+
const inProgress = roadmap.milestones.find((m) => m.status === 'in_progress');
|
|
324
|
+
let nextAction;
|
|
325
|
+
if (inProgress) {
|
|
326
|
+
nextAction = {
|
|
327
|
+
kind: 'continue_milestone',
|
|
328
|
+
milestone_id: inProgress.id,
|
|
329
|
+
title: inProgress.title,
|
|
330
|
+
status: inProgress.status,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
else if (schedule.pick.length > 0) {
|
|
334
|
+
const top = schedule.pick[0];
|
|
335
|
+
nextAction = {
|
|
336
|
+
kind: 'start_milestone',
|
|
337
|
+
milestone_id: top.milestone_id,
|
|
338
|
+
title: top.title,
|
|
339
|
+
status: top.status,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
nextAction = { kind: 'idle', reason: schedule.reason };
|
|
344
|
+
}
|
|
345
|
+
const envelope = {
|
|
346
|
+
status: 'ok',
|
|
347
|
+
command: 'produce-tick',
|
|
348
|
+
run_id: runId,
|
|
349
|
+
roadmap,
|
|
350
|
+
next_action: nextAction,
|
|
351
|
+
// M5 (ID-M5-08) — the full WSJF/WIP schedule object, an ADDITIVE field.
|
|
352
|
+
schedule,
|
|
353
|
+
next_tick_hint: 'One tick per invocation — reschedule via the host beat (cron-capable host → CronCreate; ' +
|
|
354
|
+
`non-cron host → re-run \`apt-tools produce tick . --run-id ${runId}\` when you return). No in-skill loop (ID-05).`,
|
|
355
|
+
};
|
|
356
|
+
// M4 (ID-M4-05) — an OPTIONAL --head-sha surfaces the additive per-tick TTL
|
|
357
|
+
// re-verify sample (the rotating STALE done milestones to re-verify this beat,
|
|
358
|
+
// AC13 "each tick"). When ABSENT the tick output is BYTE-IDENTICAL to M1/M2/M3
|
|
359
|
+
// (no field) and next_action is unchanged. Still exactly one tick, no loop.
|
|
360
|
+
const headSha = strFlag(flags, 'head-sha');
|
|
361
|
+
if (headSha) {
|
|
362
|
+
const sampleRaw = strFlag(flags, 'sample');
|
|
363
|
+
const sample = sampleRaw ? Number.parseInt(sampleRaw, 10) : 1;
|
|
364
|
+
envelope.ttl_reverify = selectTtlReverifySample(projectDir, runId, { headSha, sample });
|
|
365
|
+
}
|
|
366
|
+
return ok(envelope);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* `produce declare-criteria . --run-id <id> --milestone <mid> --criterion "<text>"
|
|
370
|
+
* [--criterion-id <cid>] [--scope-change "<reason>"]` — open one acceptance
|
|
371
|
+
* criterion bound to a milestone (V += 1), gated by the write-time Lyapunov
|
|
372
|
+
* anti-windup guard (ID-M3-03). Before descent, declaration is FREE (the baseline
|
|
373
|
+
* scope-definition window). After descent (≥1 criterion already closed), it is
|
|
374
|
+
* REJECTED fail-closed (exit 1, nothing appended) unless --scope-change
|
|
375
|
+
* "<reason>" justifies the increase — the ONLY sanctioned post-descent V rise
|
|
376
|
+
* (ID-M3-04). When --scope-change is present, the command appends
|
|
377
|
+
* produce.scope.changed FIRST, then produce.criteria.declared — atomic
|
|
378
|
+
* self-justifying growth on the trail.
|
|
379
|
+
*/
|
|
380
|
+
function cmdDeclareCriteria(projectDir, flags) {
|
|
381
|
+
const runId = strFlag(flags, 'run-id');
|
|
382
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
383
|
+
const criterion = strFlag(flags, 'criterion');
|
|
384
|
+
const gate = requireRun(projectDir, runId, 'declare-criteria');
|
|
385
|
+
if (gate.error)
|
|
386
|
+
return gate.error;
|
|
387
|
+
if (!milestoneId)
|
|
388
|
+
return err('produce declare-criteria requires --milestone <mid>');
|
|
389
|
+
if (!criterion)
|
|
390
|
+
return err('produce declare-criteria requires --criterion "<text>"');
|
|
391
|
+
// The milestone must exist in the run's roadmap (fail-closed).
|
|
392
|
+
const roadmap = projectRoadmap(projectDir, runId);
|
|
393
|
+
if (!roadmap.milestones.some((m) => m.id === milestoneId))
|
|
394
|
+
return err(`produce declare-criteria: milestone ${milestoneId} not found in run ${runId}`);
|
|
395
|
+
const scopeChangeReason = strFlag(flags, 'scope-change');
|
|
396
|
+
const criterionId = strFlag(flags, 'criterion-id') || mintCriterionId(criterion);
|
|
397
|
+
// Write-time Lyapunov anti-windup guard — the ONLY hard enforcement (ID-M3-03).
|
|
398
|
+
const guard = evaluateDeclareGuard(projectDir, runId, { hasScopeChange: !!scopeChangeReason });
|
|
399
|
+
if (!guard.allowed)
|
|
400
|
+
return err('Lyapunov violation: adding an open acceptance criterion after the run began closing criteria requires --scope-change "<reason>" (ID-M3-03/ID-M3-04)', 1, {
|
|
401
|
+
command: 'produce-declare-criteria',
|
|
402
|
+
run_id: runId,
|
|
403
|
+
milestone_id: milestoneId,
|
|
404
|
+
in_descent: guard.in_descent,
|
|
405
|
+
});
|
|
406
|
+
// Self-justifying growth: the scope-change lands FIRST, then the criterion
|
|
407
|
+
// (atomic — every post-descent increase carries its own logged justification).
|
|
408
|
+
if (scopeChangeReason)
|
|
409
|
+
appendProduceEvent(projectDir, 'produce.scope.changed', {
|
|
410
|
+
run_id: runId,
|
|
411
|
+
milestone_id: milestoneId,
|
|
412
|
+
reason: scopeChangeReason,
|
|
413
|
+
});
|
|
414
|
+
appendProduceEvent(projectDir, 'produce.criteria.declared', {
|
|
415
|
+
run_id: runId,
|
|
416
|
+
milestone_id: milestoneId,
|
|
417
|
+
criterion_id: criterionId,
|
|
418
|
+
text: criterion,
|
|
419
|
+
});
|
|
420
|
+
return ok({
|
|
421
|
+
status: 'ok',
|
|
422
|
+
command: 'produce-declare-criteria',
|
|
423
|
+
run_id: runId,
|
|
424
|
+
milestone_id: milestoneId,
|
|
425
|
+
criterion_id: criterionId,
|
|
426
|
+
open_count: computeOpenCriteria(projectDir, runId),
|
|
427
|
+
scope_changed: !!scopeChangeReason,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Read the LATEST recorded implementer family for a run across ALL milestones
|
|
432
|
+
* (used by `resolve-probe-family` when neither --implementer-family nor
|
|
433
|
+
* --milestone is supplied). Returns null when none is recorded (fail-closed).
|
|
434
|
+
*
|
|
435
|
+
* @param {string} projectDir
|
|
436
|
+
* @param {string} runId
|
|
437
|
+
* @returns {string | null}
|
|
438
|
+
*/
|
|
439
|
+
function latestImplementerFamilyForRun(projectDir, runId) {
|
|
440
|
+
const events = readProduceRunEventsMatching(projectDir, runId, (op) => op === 'produce.milestone.implemented');
|
|
441
|
+
let family = null;
|
|
442
|
+
for (const e of events) {
|
|
443
|
+
const d = e.data || {};
|
|
444
|
+
if (typeof d.implementer_family === 'string' && d.implementer_family.length > 0) {
|
|
445
|
+
family = d.implementer_family;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return family;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* `produce record-implementer . --run-id <id> --milestone <mid>
|
|
452
|
+
* --implementer-family <fam>` — record who implemented a milestone (ID-M2-02).
|
|
453
|
+
* This is the recorded disjointness anchor every cross-family check reads. The
|
|
454
|
+
* Showrunner records it once per milestone at implementation time; the CLI
|
|
455
|
+
* never dispatches execute in M2 (that is the post-M2 closed loop).
|
|
456
|
+
*/
|
|
457
|
+
function cmdRecordImplementer(projectDir, flags) {
|
|
458
|
+
const runId = strFlag(flags, 'run-id');
|
|
459
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
460
|
+
const implementerFamily = strFlag(flags, 'implementer-family');
|
|
461
|
+
const gate = requireRun(projectDir, runId, 'record-implementer');
|
|
462
|
+
if (gate.error)
|
|
463
|
+
return gate.error;
|
|
464
|
+
if (!milestoneId)
|
|
465
|
+
return err('produce record-implementer requires --milestone <mid>');
|
|
466
|
+
if (!implementerFamily)
|
|
467
|
+
return err('produce record-implementer requires --implementer-family <fam>');
|
|
468
|
+
appendProduceEvent(projectDir, 'produce.milestone.implemented', {
|
|
469
|
+
run_id: runId,
|
|
470
|
+
milestone_id: milestoneId,
|
|
471
|
+
implementer_family: implementerFamily,
|
|
472
|
+
});
|
|
473
|
+
return ok({
|
|
474
|
+
status: 'ok',
|
|
475
|
+
command: 'produce-record-implementer',
|
|
476
|
+
run_id: runId,
|
|
477
|
+
milestone_id: milestoneId,
|
|
478
|
+
implementer_family: implementerFamily,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* `produce resolve-probe-family . --run-id <id> [--implementer-family <fam>]
|
|
483
|
+
* [--milestone <mid>] [--host-family <fam>] [--available-families a,b,c]` —
|
|
484
|
+
* resolve a probe/oracle family DISJOINT from the implementer, with the model
|
|
485
|
+
* resolved LIVE via the reused spar primitive (no baked model id — ID-M2-07).
|
|
486
|
+
*
|
|
487
|
+
* The implementer family is taken from --implementer-family, else read from the
|
|
488
|
+
* recorded `produce.milestone.implemented` trail (by --milestone, else the
|
|
489
|
+
* latest for the run). If neither is present, adjudication is impossible — you
|
|
490
|
+
* cannot enforce disjointness against an unknown implementer (ID-M2-02,
|
|
491
|
+
* fail-closed).
|
|
492
|
+
*/
|
|
493
|
+
function cmdResolveProbeFamily(projectDir, flags) {
|
|
494
|
+
const runId = strFlag(flags, 'run-id');
|
|
495
|
+
const gate = requireRun(projectDir, runId, 'resolve-probe-family');
|
|
496
|
+
if (gate.error)
|
|
497
|
+
return gate.error;
|
|
498
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
499
|
+
let implementerFamily = strFlag(flags, 'implementer-family');
|
|
500
|
+
if (!implementerFamily) {
|
|
501
|
+
implementerFamily = milestoneId
|
|
502
|
+
? readImplementerFamily(projectDir, runId, milestoneId)
|
|
503
|
+
: latestImplementerFamilyForRun(projectDir, runId);
|
|
504
|
+
}
|
|
505
|
+
if (!implementerFamily)
|
|
506
|
+
return err('produce resolve-probe-family: no implementer family — pass --implementer-family <fam> or record it first via `produce record-implementer` (ID-M2-02, fail-closed).', 1, { command: 'produce-resolve-probe-family', run_id: runId });
|
|
507
|
+
const hostFamily = strFlag(flags, 'host-family');
|
|
508
|
+
const availRaw = strFlag(flags, 'available-families');
|
|
509
|
+
const availableFamilies = availRaw
|
|
510
|
+
? availRaw
|
|
511
|
+
.split(',')
|
|
512
|
+
.map((s) => s.trim())
|
|
513
|
+
.filter(Boolean)
|
|
514
|
+
: undefined;
|
|
515
|
+
const resolution = resolveProbeFamily({
|
|
516
|
+
implementerFamily,
|
|
517
|
+
hostFamily,
|
|
518
|
+
availableFamilies,
|
|
519
|
+
});
|
|
520
|
+
return ok({
|
|
521
|
+
status: 'ok',
|
|
522
|
+
command: 'produce-resolve-probe-family',
|
|
523
|
+
run_id: runId,
|
|
524
|
+
milestone_id: milestoneId ?? null,
|
|
525
|
+
implementer_family: implementerFamily,
|
|
526
|
+
host_family: hostFamily ?? null,
|
|
527
|
+
...resolution,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* `produce oracle <prepare|record> <project-dir> ...` — two-level dispatch. The
|
|
532
|
+
* sub-subcommand arrives as the outer `projectDir` param (see cmdProduce); the
|
|
533
|
+
* real project dir is `args[0]` and the flags follow it.
|
|
534
|
+
*
|
|
535
|
+
* @param {string} subsub
|
|
536
|
+
* @param {string[]} args
|
|
537
|
+
*/
|
|
538
|
+
function cmdOracle(subsub, args) {
|
|
539
|
+
const realProjectDir = Array.isArray(args) ? args[0] : undefined;
|
|
540
|
+
if (!realProjectDir || realProjectDir.startsWith('--'))
|
|
541
|
+
return err('Usage: apt-tools produce oracle <prepare|record> <project-dir> [flags]');
|
|
542
|
+
const flags = parseFlags(args.slice(1));
|
|
543
|
+
switch (subsub) {
|
|
544
|
+
case 'prepare':
|
|
545
|
+
return cmdOraclePrepare(realProjectDir, flags);
|
|
546
|
+
case 'record':
|
|
547
|
+
return cmdOracleRecord(realProjectDir, flags);
|
|
548
|
+
default:
|
|
549
|
+
return err(`Unknown produce oracle subcommand: ${subsub}. Expected prepare | record.`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* `produce oracle prepare . --run-id <id> --milestone <mid> --verdict-path <path>`
|
|
554
|
+
* — extract verify-proof's v2 verdict + tests[] evidence into a cross-family
|
|
555
|
+
* judge payload. Rejects (exit 1) when the verdict is not EXACTLY `approved`
|
|
556
|
+
* (ID-M2-05) so the skill never judges evidence a failed/unreadable gate never
|
|
557
|
+
* produced. The skill drives the actual cross-family judge LLM call; this only
|
|
558
|
+
* extracts.
|
|
559
|
+
*/
|
|
560
|
+
function cmdOraclePrepare(projectDir, flags) {
|
|
561
|
+
const runId = strFlag(flags, 'run-id');
|
|
562
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
563
|
+
const verdictPath = strFlag(flags, 'verdict-path');
|
|
564
|
+
const gate = requireRun(projectDir, runId, 'oracle prepare');
|
|
565
|
+
if (gate.error)
|
|
566
|
+
return gate.error;
|
|
567
|
+
if (!milestoneId)
|
|
568
|
+
return err('produce oracle prepare requires --milestone <mid>');
|
|
569
|
+
if (!verdictPath)
|
|
570
|
+
return err('produce oracle prepare requires --verdict-path <path>');
|
|
571
|
+
const prepared = prepareOracleInput(verdictPath);
|
|
572
|
+
if (!prepared.approved)
|
|
573
|
+
return err(`produce oracle prepare: verify-proof verdict is not 'approved' (got: ${prepared.verdict ?? 'missing/unreadable'}) — not adjudicable (ID-M2-05, fail-closed).`, 1, {
|
|
574
|
+
command: 'produce-oracle-prepare',
|
|
575
|
+
run_id: runId,
|
|
576
|
+
milestone_id: milestoneId,
|
|
577
|
+
verdict: prepared.verdict,
|
|
578
|
+
reason: prepared.reason,
|
|
579
|
+
});
|
|
580
|
+
return ok({
|
|
581
|
+
status: 'ok',
|
|
582
|
+
command: 'produce-oracle-prepare',
|
|
583
|
+
run_id: runId,
|
|
584
|
+
milestone_id: milestoneId,
|
|
585
|
+
verdict: prepared.verdict,
|
|
586
|
+
tests: prepared.tests,
|
|
587
|
+
judge_input: prepared.judge_input,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* `produce oracle record . --run-id <id> --milestone <mid> --verdict <pass|fail>
|
|
592
|
+
* --oracle-family <fam>` — record the cross-family evidence-quality judge's
|
|
593
|
+
* verdict. Refused fail-closed when the oracle family equals the recorded
|
|
594
|
+
* implementer family, or when no implementer family is recorded (ID-08 / ID-M2-02).
|
|
595
|
+
*/
|
|
596
|
+
function cmdOracleRecord(projectDir, flags) {
|
|
597
|
+
const runId = strFlag(flags, 'run-id');
|
|
598
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
599
|
+
const verdict = strFlag(flags, 'verdict');
|
|
600
|
+
const oracleFamily = strFlag(flags, 'oracle-family');
|
|
601
|
+
const gate = requireRun(projectDir, runId, 'oracle record');
|
|
602
|
+
if (gate.error)
|
|
603
|
+
return gate.error;
|
|
604
|
+
if (!milestoneId)
|
|
605
|
+
return err('produce oracle record requires --milestone <mid>');
|
|
606
|
+
if (!verdict)
|
|
607
|
+
return err('produce oracle record requires --verdict <pass|fail>');
|
|
608
|
+
if (!oracleFamily)
|
|
609
|
+
return err('produce oracle record requires --oracle-family <fam>');
|
|
610
|
+
const res = recordOracleVerdict(projectDir, { runId, milestoneId, verdict, oracleFamily });
|
|
611
|
+
if (!res.ok)
|
|
612
|
+
return err(`produce oracle record refused: ${res.reason}`, 1, {
|
|
613
|
+
command: 'produce-oracle-record',
|
|
614
|
+
run_id: runId,
|
|
615
|
+
milestone_id: milestoneId,
|
|
616
|
+
reason: res.reason,
|
|
617
|
+
implementer_family: res.implementer_family ?? null,
|
|
618
|
+
});
|
|
619
|
+
return ok({
|
|
620
|
+
status: 'ok',
|
|
621
|
+
command: 'produce-oracle-record',
|
|
622
|
+
run_id: runId,
|
|
623
|
+
milestone_id: milestoneId,
|
|
624
|
+
verdict: res.verdict,
|
|
625
|
+
oracle_family: res.oracle_family,
|
|
626
|
+
implementer_family: res.implementer_family,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* `produce probe <freeze|record|sweep> <project-dir> ...` — two-level dispatch
|
|
631
|
+
* (same shape as cmdOracle: the sub-subcommand arrives as `projectDir`).
|
|
632
|
+
*
|
|
633
|
+
* @param {string} subsub
|
|
634
|
+
* @param {string[]} args
|
|
635
|
+
*/
|
|
636
|
+
function cmdProbe(subsub, args) {
|
|
637
|
+
const realProjectDir = Array.isArray(args) ? args[0] : undefined;
|
|
638
|
+
if (!realProjectDir || realProjectDir.startsWith('--'))
|
|
639
|
+
return err('Usage: apt-tools produce probe <freeze|record|sweep> <project-dir> [flags]');
|
|
640
|
+
const flags = parseFlags(args.slice(1));
|
|
641
|
+
switch (subsub) {
|
|
642
|
+
case 'freeze':
|
|
643
|
+
return cmdProbeFreeze(realProjectDir, flags);
|
|
644
|
+
case 'record':
|
|
645
|
+
return cmdProbeRecord(realProjectDir, flags);
|
|
646
|
+
case 'sweep':
|
|
647
|
+
return cmdProbeSweep(realProjectDir, flags);
|
|
648
|
+
default:
|
|
649
|
+
return err(`Unknown produce probe subcommand: ${subsub}. Expected freeze | record | sweep.`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* `produce probe freeze . --run-id <id> --milestone <mid> --probe-file <path>
|
|
654
|
+
* --author-family <fam>` — freeze an immutable held-out probe set authored by a
|
|
655
|
+
* family ≠ the implementer. The probe CONTENT is read from --probe-file (the
|
|
656
|
+
* skill wrote it in the disjoint author family) and stored in the frozen file
|
|
657
|
+
* ONLY — never the event log (ID-M2-03). Reads the recorded implementer family
|
|
658
|
+
* to enforce the cross-family rule (fail-closed if absent).
|
|
659
|
+
*/
|
|
660
|
+
function cmdProbeFreeze(projectDir, flags) {
|
|
661
|
+
const runId = strFlag(flags, 'run-id');
|
|
662
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
663
|
+
const probeFile = strFlag(flags, 'probe-file');
|
|
664
|
+
const authorFamily = strFlag(flags, 'author-family');
|
|
665
|
+
const gate = requireRun(projectDir, runId, 'probe freeze');
|
|
666
|
+
if (gate.error)
|
|
667
|
+
return gate.error;
|
|
668
|
+
if (!milestoneId)
|
|
669
|
+
return err('produce probe freeze requires --milestone <mid>');
|
|
670
|
+
if (!probeFile)
|
|
671
|
+
return err('produce probe freeze requires --probe-file <path>');
|
|
672
|
+
if (!authorFamily)
|
|
673
|
+
return err('produce probe freeze requires --author-family <fam>');
|
|
674
|
+
if (!existsSync(probeFile))
|
|
675
|
+
return err(`produce probe freeze: --probe-file not found: ${probeFile}`);
|
|
676
|
+
// Read the probe content the skill authored. JSON when parseable, else raw
|
|
677
|
+
// text — the content is stored verbatim and never enters the event log.
|
|
678
|
+
const raw = readFileSync(probeFile, 'utf-8');
|
|
679
|
+
let content;
|
|
680
|
+
try {
|
|
681
|
+
content = JSON.parse(raw);
|
|
682
|
+
}
|
|
683
|
+
catch {
|
|
684
|
+
content = raw;
|
|
685
|
+
}
|
|
686
|
+
const implementerFamily = readImplementerFamily(projectDir, runId, milestoneId);
|
|
687
|
+
if (!implementerFamily)
|
|
688
|
+
return err('produce probe freeze: no recorded implementer family — record it first via `produce record-implementer` (ID-M2-02, fail-closed).', 1, { command: 'produce-probe-freeze', run_id: runId, milestone_id: milestoneId });
|
|
689
|
+
const res = freezeProbeSet(projectDir, runId, milestoneId, {
|
|
690
|
+
content,
|
|
691
|
+
authorFamily,
|
|
692
|
+
implementerFamily,
|
|
693
|
+
});
|
|
694
|
+
if (!res.ok)
|
|
695
|
+
return err(`produce probe freeze refused: ${res.reason}`, 1, {
|
|
696
|
+
command: 'produce-probe-freeze',
|
|
697
|
+
run_id: runId,
|
|
698
|
+
milestone_id: milestoneId,
|
|
699
|
+
reason: res.reason,
|
|
700
|
+
});
|
|
701
|
+
return ok({
|
|
702
|
+
status: 'ok',
|
|
703
|
+
command: 'produce-probe-freeze',
|
|
704
|
+
run_id: runId,
|
|
705
|
+
milestone_id: milestoneId,
|
|
706
|
+
reused: res.reused,
|
|
707
|
+
probe_set_hash: res.probe_set_hash,
|
|
708
|
+
author_family: res.author_family,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* `produce probe record . --run-id <id> --milestone <mid> --result <pass|fail>
|
|
713
|
+
* --probe-family <fam> --head-sha <sha>` — record a blind-probe run result
|
|
714
|
+
* (result/family/head_sha only — never content). Refused fail-closed when the
|
|
715
|
+
* probe family equals the recorded implementer family (ID-08).
|
|
716
|
+
*/
|
|
717
|
+
function cmdProbeRecord(projectDir, flags) {
|
|
718
|
+
const runId = strFlag(flags, 'run-id');
|
|
719
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
720
|
+
const result = strFlag(flags, 'result');
|
|
721
|
+
const probeFamily = strFlag(flags, 'probe-family');
|
|
722
|
+
const headSha = strFlag(flags, 'head-sha');
|
|
723
|
+
const gate = requireRun(projectDir, runId, 'probe record');
|
|
724
|
+
if (gate.error)
|
|
725
|
+
return gate.error;
|
|
726
|
+
if (!milestoneId)
|
|
727
|
+
return err('produce probe record requires --milestone <mid>');
|
|
728
|
+
if (!result)
|
|
729
|
+
return err('produce probe record requires --result <pass|fail>');
|
|
730
|
+
if (!probeFamily)
|
|
731
|
+
return err('produce probe record requires --probe-family <fam>');
|
|
732
|
+
if (!headSha)
|
|
733
|
+
return err('produce probe record requires --head-sha <sha>');
|
|
734
|
+
const res = recordProbeRun(projectDir, { runId, milestoneId, result, probeFamily, headSha });
|
|
735
|
+
if (!res.ok)
|
|
736
|
+
return err(`produce probe record refused: ${res.reason}`, 1, {
|
|
737
|
+
command: 'produce-probe-record',
|
|
738
|
+
run_id: runId,
|
|
739
|
+
milestone_id: milestoneId,
|
|
740
|
+
reason: res.reason,
|
|
741
|
+
});
|
|
742
|
+
return ok({
|
|
743
|
+
status: 'ok',
|
|
744
|
+
command: 'produce-probe-record',
|
|
745
|
+
run_id: runId,
|
|
746
|
+
milestone_id: milestoneId,
|
|
747
|
+
result: res.result,
|
|
748
|
+
probe_family: res.probe_family,
|
|
749
|
+
head_sha: res.head_sha,
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* `produce probe sweep . --run-id <id> [--sample N]` — return a deterministic
|
|
754
|
+
* rotating sample of already-done milestones selected for blind re-probing
|
|
755
|
+
* against HEAD (US-6). Read-only — selection, not a run.
|
|
756
|
+
*/
|
|
757
|
+
function cmdProbeSweep(projectDir, flags) {
|
|
758
|
+
const runId = strFlag(flags, 'run-id');
|
|
759
|
+
const gate = requireRun(projectDir, runId, 'probe sweep');
|
|
760
|
+
if (gate.error)
|
|
761
|
+
return gate.error;
|
|
762
|
+
const sampleRaw = strFlag(flags, 'sample');
|
|
763
|
+
const sample = sampleRaw ? Number.parseInt(sampleRaw, 10) : 1;
|
|
764
|
+
const swept = selectSweepSample(projectDir, runId, { sample });
|
|
765
|
+
return ok({
|
|
766
|
+
status: 'ok',
|
|
767
|
+
command: 'produce-probe-sweep',
|
|
768
|
+
...swept,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* The M2 `--status done` path — the two-gate adjudication (ID-M2-04). Delegates
|
|
773
|
+
* to `adjudicateDone`: `done` flips ONLY when the visible oracle (verify-proof
|
|
774
|
+
* `approved` + recorded cross-family oracle pass) AND the blind probe (recorded
|
|
775
|
+
* cross-family probe pass + a cross-family frozen set) both agree. A
|
|
776
|
+
* visible-pass + blind-fail logs `produce.oracle.probe_gap` and stays open; a
|
|
777
|
+
* bare done with no recorded external evidence is refused — the Showrunner never
|
|
778
|
+
* self-attests (ID-03). `--verdict-path` supplies verify-proof's verdict file.
|
|
779
|
+
*/
|
|
780
|
+
function cmdSetMilestoneDone(projectDir, runId, milestoneId, flags) {
|
|
781
|
+
const verdictPath = strFlag(flags, 'verdict-path');
|
|
782
|
+
const res = adjudicateDone({ projectDir, runId, milestoneId, verdictPath });
|
|
783
|
+
if (res.ok) {
|
|
784
|
+
// M3 (ID-M3-05) — the shipped milestone's still-open acceptance criteria
|
|
785
|
+
// close as a CONSEQUENCE of the ship, so V strictly decreases. Wired
|
|
786
|
+
// ADDITIVELY here (done-gate.mjs is untouched): a criteria-less milestone
|
|
787
|
+
// closes zero and is byte-identical to M2.
|
|
788
|
+
const closed = closeCriteriaForShippedMilestone(projectDir, runId, milestoneId);
|
|
789
|
+
return ok({
|
|
790
|
+
status: 'ok',
|
|
791
|
+
command: 'produce-set-milestone-status',
|
|
792
|
+
run_id: runId,
|
|
793
|
+
milestone_id: milestoneId,
|
|
794
|
+
milestone_status: 'done',
|
|
795
|
+
roadmap: res.roadmap,
|
|
796
|
+
closed_criteria: closed,
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
// Refused — the milestone stays open. Frame the reason honestly (done is
|
|
800
|
+
// external-oracle-gated: it requires recorded external evidence + a blind
|
|
801
|
+
// cross-family probe; the Showrunner never self-attests, ID-03).
|
|
802
|
+
const messages = {
|
|
803
|
+
no_recorded_implementer: 'no recorded implementer family — done is external-oracle-gated: record the implementer + a cross-family oracle + blind probe first (the Showrunner never self-attests, ID-03).',
|
|
804
|
+
not_adjudicable: 'not adjudicable — done is external-oracle-gated: it requires verify-proof `approved` AND a cross-family oracle pass (ID-03).',
|
|
805
|
+
probe_gap: 'probe_gap — the visible oracle passed but the held-out blind cross-family probe FAILED; the milestone stays open and a produce.oracle.probe_gap canary event was logged (AC9).',
|
|
806
|
+
};
|
|
807
|
+
return err(`produce set-milestone-status --status done refused: ${messages[res.reason] ?? res.reason}`, 1, {
|
|
808
|
+
command: 'produce-set-milestone-status',
|
|
809
|
+
run_id: runId,
|
|
810
|
+
milestone_id: milestoneId,
|
|
811
|
+
rejected_reason: res.reason,
|
|
812
|
+
visible_gate: res.visible_gate,
|
|
813
|
+
blind_gate: res.blind_gate,
|
|
814
|
+
detail: res.detail ?? null,
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* `produce progress . --run-id <id>` — the read-time monotone-progress report
|
|
819
|
+
* (M3, ID-M3-02 / AC6). Re-reads the ledger from the append-only log on every
|
|
820
|
+
* invocation (no in-memory authority — ID-01) and returns the V-series across
|
|
821
|
+
* shipped-milestone checkpoints, `strictly_decreasing`, and any hard
|
|
822
|
+
* `lyapunov_violations` (an un-sanctioned post-descent increase, detected
|
|
823
|
+
* defensively from the fold). It FLAGS — it never rejects (the rejection is the
|
|
824
|
+
* write-time guard in `declare-criteria`); a flat ship is a soft stall.
|
|
825
|
+
*/
|
|
826
|
+
function cmdProgress(projectDir, flags) {
|
|
827
|
+
const runId = strFlag(flags, 'run-id');
|
|
828
|
+
const gate = requireRun(projectDir, runId, 'progress');
|
|
829
|
+
if (gate.error)
|
|
830
|
+
return gate.error;
|
|
831
|
+
return ok({
|
|
832
|
+
status: 'ok',
|
|
833
|
+
command: 'produce-progress',
|
|
834
|
+
...computeProgress(projectDir, runId),
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* `produce canary . --run-id <id>` — the reward-hacking canary (ID-M2-06):
|
|
839
|
+
* green-on-visible MINUS green-on-blind over the run's milestones (from recorded
|
|
840
|
+
* seals), with a per-milestone breakdown. Each `produce.oracle.probe_gap` is one
|
|
841
|
+
* visible-pass + blind-fail near-miss. Read-only.
|
|
842
|
+
*/
|
|
843
|
+
function cmdCanary(projectDir, flags) {
|
|
844
|
+
const runId = strFlag(flags, 'run-id');
|
|
845
|
+
const gate = requireRun(projectDir, runId, 'canary');
|
|
846
|
+
if (gate.error)
|
|
847
|
+
return gate.error;
|
|
848
|
+
const canary = computeCanary(projectDir, runId);
|
|
849
|
+
return ok({
|
|
850
|
+
status: 'ok',
|
|
851
|
+
command: 'produce-canary',
|
|
852
|
+
...canary,
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* `produce schedule . --run-id <id> [--wip N]` — the M5 WSJF/WIP scheduler
|
|
857
|
+
* (ID-M5-01/AC1). READ-ONLY: re-projects the roadmap + ledger + regressions from
|
|
858
|
+
* the append-only log, folds them into a WSJF-ranked ready_queue, a dependency-
|
|
859
|
+
* aware blocked list, the in_progress WIP accounting, and a WIP-limited pick of
|
|
860
|
+
* the top wip_available ready milestones. Emits NO event (a pure read, mirroring
|
|
861
|
+
* status/progress/canary). WIP resolves --wip flag → config produce.wip →
|
|
862
|
+
* default 1.
|
|
863
|
+
*/
|
|
864
|
+
function cmdSchedule(projectDir, flags) {
|
|
865
|
+
const runId = strFlag(flags, 'run-id');
|
|
866
|
+
const gate = requireRun(projectDir, runId, 'schedule');
|
|
867
|
+
if (gate.error)
|
|
868
|
+
return gate.error;
|
|
869
|
+
const wipLimit = resolveWipLimit(projectDir, flags);
|
|
870
|
+
const schedule = computeSchedule(projectDir, runId, { wipLimit });
|
|
871
|
+
return ok({
|
|
872
|
+
status: 'ok',
|
|
873
|
+
command: 'produce-schedule',
|
|
874
|
+
...schedule,
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* `produce saga <enter|complete|compensate|status> <project-dir> ...` — two-level
|
|
879
|
+
* dispatch (same shape as cmdOracle/cmdProbe: the sub-subcommand arrives as the
|
|
880
|
+
* outer `projectDir` param; the real dir is `args[0]` and the flags follow it).
|
|
881
|
+
*
|
|
882
|
+
* @param {string} subsub
|
|
883
|
+
* @param {string[]} args
|
|
884
|
+
*/
|
|
885
|
+
function cmdSaga(subsub, args) {
|
|
886
|
+
const realProjectDir = Array.isArray(args) ? args[0] : undefined;
|
|
887
|
+
if (!realProjectDir || realProjectDir.startsWith('--'))
|
|
888
|
+
return err('Usage: apt-tools produce saga <enter|complete|compensate|review|resolve-reviewer|status> <project-dir> [flags]');
|
|
889
|
+
const flags = parseFlags(args.slice(1));
|
|
890
|
+
switch (subsub) {
|
|
891
|
+
case 'enter':
|
|
892
|
+
return cmdSagaEnter(realProjectDir, flags);
|
|
893
|
+
case 'complete':
|
|
894
|
+
return cmdSagaComplete(realProjectDir, flags);
|
|
895
|
+
case 'compensate':
|
|
896
|
+
return cmdSagaCompensate(realProjectDir, flags);
|
|
897
|
+
case 'review':
|
|
898
|
+
return cmdSagaReview(realProjectDir, flags);
|
|
899
|
+
case 'resolve-reviewer':
|
|
900
|
+
return cmdSagaResolveReviewer(realProjectDir, flags);
|
|
901
|
+
case 'status':
|
|
902
|
+
return cmdSagaStatus(realProjectDir, flags);
|
|
903
|
+
default:
|
|
904
|
+
return err(`Unknown produce saga subcommand: ${subsub}. Expected enter | complete | compensate | review | resolve-reviewer | status.`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
/** Require the milestone to exist in the run's roadmap (fail-closed). */
|
|
908
|
+
function requireSagaMilestone(projectDir, runId, milestoneId, cmd) {
|
|
909
|
+
const roadmap = projectRoadmap(projectDir, runId);
|
|
910
|
+
if (!roadmap.milestones.some((m) => m.id === milestoneId))
|
|
911
|
+
return err(`produce saga ${cmd}: milestone ${milestoneId} not found in run ${runId}`);
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* `produce saga enter . --run-id <id> --milestone <mid> --phase <p>` — append
|
|
916
|
+
* produce.saga.phase_entered for a milestone's saga phase (phase ∈ SAGA_PHASES,
|
|
917
|
+
* validated fail-closed). A SEPARATE fold from the roadmap projection (blindness).
|
|
918
|
+
*/
|
|
919
|
+
function cmdSagaEnter(projectDir, flags) {
|
|
920
|
+
const runId = strFlag(flags, 'run-id');
|
|
921
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
922
|
+
const phase = strFlag(flags, 'phase');
|
|
923
|
+
const gate = requireRun(projectDir, runId, 'saga enter');
|
|
924
|
+
if (gate.error)
|
|
925
|
+
return gate.error;
|
|
926
|
+
if (!milestoneId)
|
|
927
|
+
return err('produce saga enter requires --milestone <mid>');
|
|
928
|
+
if (!phase)
|
|
929
|
+
return err(`produce saga enter requires --phase <${SAGA_PHASES.join('|')}>`);
|
|
930
|
+
const missing = requireSagaMilestone(projectDir, runId, milestoneId, 'enter');
|
|
931
|
+
if (missing)
|
|
932
|
+
return missing;
|
|
933
|
+
const res = enterPhase(projectDir, { runId, milestoneId, phase });
|
|
934
|
+
if (!res.ok)
|
|
935
|
+
return err(`produce saga enter: invalid --phase "${phase}". Allowed: ${SAGA_PHASES.join(' | ')}.`, 1, { command: 'produce-saga-enter', run_id: runId, milestone_id: milestoneId });
|
|
936
|
+
return ok({
|
|
937
|
+
status: 'ok',
|
|
938
|
+
command: 'produce-saga-enter',
|
|
939
|
+
run_id: runId,
|
|
940
|
+
milestone_id: milestoneId,
|
|
941
|
+
phase,
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* `produce saga complete . --run-id <id> --milestone <mid> --phase <p>` — append
|
|
946
|
+
* produce.saga.phase_completed for a milestone's saga phase.
|
|
947
|
+
*/
|
|
948
|
+
function cmdSagaComplete(projectDir, flags) {
|
|
949
|
+
const runId = strFlag(flags, 'run-id');
|
|
950
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
951
|
+
const phase = strFlag(flags, 'phase');
|
|
952
|
+
const gate = requireRun(projectDir, runId, 'saga complete');
|
|
953
|
+
if (gate.error)
|
|
954
|
+
return gate.error;
|
|
955
|
+
if (!milestoneId)
|
|
956
|
+
return err('produce saga complete requires --milestone <mid>');
|
|
957
|
+
if (!phase)
|
|
958
|
+
return err(`produce saga complete requires --phase <${SAGA_PHASES.join('|')}>`);
|
|
959
|
+
const missing = requireSagaMilestone(projectDir, runId, milestoneId, 'complete');
|
|
960
|
+
if (missing)
|
|
961
|
+
return missing;
|
|
962
|
+
const res = completePhase(projectDir, { runId, milestoneId, phase });
|
|
963
|
+
if (!res.ok)
|
|
964
|
+
return err(`produce saga complete: invalid --phase "${phase}". Allowed: ${SAGA_PHASES.join(' | ')}.`, 1, { command: 'produce-saga-complete', run_id: runId, milestone_id: milestoneId });
|
|
965
|
+
return ok({
|
|
966
|
+
status: 'ok',
|
|
967
|
+
command: 'produce-saga-complete',
|
|
968
|
+
run_id: runId,
|
|
969
|
+
milestone_id: milestoneId,
|
|
970
|
+
phase,
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* `produce saga compensate . --run-id <id> --milestone <mid> --phase <p>
|
|
975
|
+
* [--reason "<r>"]` — fire the FORWARD compensator for a failed saga phase
|
|
976
|
+
* (ID-M4-02). Records produce.saga.compensated carrying the descriptor KIND. The
|
|
977
|
+
* verify/review/ship compensator SHIPS A REVERT + reopens + re-plans; EVERY
|
|
978
|
+
* compensator is `rolls_back_merged_pr: false` — never a merged-PR rollback. The
|
|
979
|
+
* CLI records the descriptor; it does NOT execute the revert (OOS dispatch) and
|
|
980
|
+
* NEVER mutates the roadmap milestone status.
|
|
981
|
+
*/
|
|
982
|
+
function cmdSagaCompensate(projectDir, flags) {
|
|
983
|
+
const runId = strFlag(flags, 'run-id');
|
|
984
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
985
|
+
const phase = strFlag(flags, 'phase');
|
|
986
|
+
const reason = strFlag(flags, 'reason');
|
|
987
|
+
const gate = requireRun(projectDir, runId, 'saga compensate');
|
|
988
|
+
if (gate.error)
|
|
989
|
+
return gate.error;
|
|
990
|
+
if (!milestoneId)
|
|
991
|
+
return err('produce saga compensate requires --milestone <mid>');
|
|
992
|
+
if (!phase)
|
|
993
|
+
return err(`produce saga compensate requires --phase <${SAGA_PHASES.join('|')}>`);
|
|
994
|
+
const missing = requireSagaMilestone(projectDir, runId, milestoneId, 'compensate');
|
|
995
|
+
if (missing)
|
|
996
|
+
return missing;
|
|
997
|
+
const res = compensatePhase(projectDir, { runId, milestoneId, phase, reason });
|
|
998
|
+
if (!res.ok)
|
|
999
|
+
return err(`produce saga compensate: invalid --phase "${phase}". Allowed: ${SAGA_PHASES.join(' | ')}.`, 1, { command: 'produce-saga-compensate', run_id: runId, milestone_id: milestoneId });
|
|
1000
|
+
return ok({
|
|
1001
|
+
status: 'ok',
|
|
1002
|
+
command: 'produce-saga-compensate',
|
|
1003
|
+
run_id: runId,
|
|
1004
|
+
milestone_id: milestoneId,
|
|
1005
|
+
phase,
|
|
1006
|
+
reason: reason ?? null,
|
|
1007
|
+
compensator: res.compensator,
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* `produce saga resolve-reviewer . --run-id <id> --milestone <mid>
|
|
1012
|
+
* [--host-family <fam>] [--available-families a,b,c]` — resolve a REVIEW-phase
|
|
1013
|
+
* reviewer family DISJOINT from the recorded implementer (ID-M4-04 / ID-08), a
|
|
1014
|
+
* THIN wrapper over the reused resolveProbeFamily (no duplicated cross-family or
|
|
1015
|
+
* model-resolution logic). The model is resolved LIVE (never a hardcoded id);
|
|
1016
|
+
* available:false when no disjoint family exists (single-family setup) or when no
|
|
1017
|
+
* implementer family is recorded (fail-closed).
|
|
1018
|
+
*/
|
|
1019
|
+
function cmdSagaResolveReviewer(projectDir, flags) {
|
|
1020
|
+
const runId = strFlag(flags, 'run-id');
|
|
1021
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
1022
|
+
const gate = requireRun(projectDir, runId, 'saga resolve-reviewer');
|
|
1023
|
+
if (gate.error)
|
|
1024
|
+
return gate.error;
|
|
1025
|
+
if (!milestoneId)
|
|
1026
|
+
return err('produce saga resolve-reviewer requires --milestone <mid>');
|
|
1027
|
+
const hostFamily = strFlag(flags, 'host-family');
|
|
1028
|
+
const availRaw = strFlag(flags, 'available-families');
|
|
1029
|
+
const availableFamilies = availRaw
|
|
1030
|
+
? availRaw
|
|
1031
|
+
.split(',')
|
|
1032
|
+
.map((s) => s.trim())
|
|
1033
|
+
.filter(Boolean)
|
|
1034
|
+
: undefined;
|
|
1035
|
+
const resolution = resolveReviewer(projectDir, {
|
|
1036
|
+
runId,
|
|
1037
|
+
milestoneId,
|
|
1038
|
+
hostFamily,
|
|
1039
|
+
availableFamilies,
|
|
1040
|
+
});
|
|
1041
|
+
return ok({
|
|
1042
|
+
status: 'ok',
|
|
1043
|
+
command: 'produce-saga-resolve-reviewer',
|
|
1044
|
+
run_id: runId,
|
|
1045
|
+
milestone_id: milestoneId,
|
|
1046
|
+
host_family: hostFamily ?? null,
|
|
1047
|
+
...resolution,
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* `produce saga review . --run-id <id> --milestone <mid> --verdict <pass|fail>
|
|
1052
|
+
* --reviewer-family <fam>` — record the cross-family review verdict
|
|
1053
|
+
* (produce.saga.reviewed). REFUSED fail-closed when the reviewer family equals
|
|
1054
|
+
* the recorded implementer family, or when no implementer family is recorded
|
|
1055
|
+
* (ID-M4-04 / ID-08 — the review-path analog of `oracle record`).
|
|
1056
|
+
*/
|
|
1057
|
+
function cmdSagaReview(projectDir, flags) {
|
|
1058
|
+
const runId = strFlag(flags, 'run-id');
|
|
1059
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
1060
|
+
const verdict = strFlag(flags, 'verdict');
|
|
1061
|
+
const reviewerFamily = strFlag(flags, 'reviewer-family');
|
|
1062
|
+
const gate = requireRun(projectDir, runId, 'saga review');
|
|
1063
|
+
if (gate.error)
|
|
1064
|
+
return gate.error;
|
|
1065
|
+
if (!milestoneId)
|
|
1066
|
+
return err('produce saga review requires --milestone <mid>');
|
|
1067
|
+
if (!verdict)
|
|
1068
|
+
return err('produce saga review requires --verdict <pass|fail>');
|
|
1069
|
+
if (!reviewerFamily)
|
|
1070
|
+
return err('produce saga review requires --reviewer-family <fam>');
|
|
1071
|
+
const res = recordReview(projectDir, { runId, milestoneId, verdict, reviewerFamily });
|
|
1072
|
+
if (!res.ok)
|
|
1073
|
+
return err(`produce saga review refused: ${res.reason}`, 1, {
|
|
1074
|
+
command: 'produce-saga-review',
|
|
1075
|
+
run_id: runId,
|
|
1076
|
+
milestone_id: milestoneId,
|
|
1077
|
+
reason: res.reason,
|
|
1078
|
+
implementer_family: res.implementer_family ?? null,
|
|
1079
|
+
});
|
|
1080
|
+
return ok({
|
|
1081
|
+
status: 'ok',
|
|
1082
|
+
command: 'produce-saga-review',
|
|
1083
|
+
run_id: runId,
|
|
1084
|
+
milestone_id: milestoneId,
|
|
1085
|
+
verdict: res.verdict,
|
|
1086
|
+
reviewer_family: res.reviewer_family,
|
|
1087
|
+
implementer_family: res.implementer_family,
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* `produce saga status . --run-id <id>` — read-only re-projection of the saga
|
|
1092
|
+
* fold from the append-only log (no in-memory authority — AC4). Reports the
|
|
1093
|
+
* per-milestone phase state + any pending compensation, WITHOUT mutating the
|
|
1094
|
+
* roadmap milestone status. Also reports V read-only via computeProgress for
|
|
1095
|
+
* context (light M3 ledger reuse — no structural coupling).
|
|
1096
|
+
*/
|
|
1097
|
+
function cmdSagaStatus(projectDir, flags) {
|
|
1098
|
+
const runId = strFlag(flags, 'run-id');
|
|
1099
|
+
const gate = requireRun(projectDir, runId, 'saga status');
|
|
1100
|
+
if (gate.error)
|
|
1101
|
+
return gate.error;
|
|
1102
|
+
const saga = projectSaga(projectDir, runId);
|
|
1103
|
+
return ok({
|
|
1104
|
+
status: 'ok',
|
|
1105
|
+
command: 'produce-saga-status',
|
|
1106
|
+
run_id: runId,
|
|
1107
|
+
saga,
|
|
1108
|
+
v_open_now: computeProgress(projectDir, runId).v_open_now,
|
|
1109
|
+
// M4 — the TTL regression trail for context (read-only; no coupling).
|
|
1110
|
+
ttl_regressions: listRegressions(projectDir, runId),
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* `produce ttl <select|reverify> <project-dir> ...` — two-level dispatch (same
|
|
1115
|
+
* shape as cmdSaga/cmdOracle/cmdProbe: the sub-subcommand arrives as the outer
|
|
1116
|
+
* `projectDir` param; the real dir is `args[0]` and the flags follow it).
|
|
1117
|
+
*
|
|
1118
|
+
* @param {string} subsub
|
|
1119
|
+
* @param {string[]} args
|
|
1120
|
+
*/
|
|
1121
|
+
function cmdTtl(subsub, args) {
|
|
1122
|
+
const realProjectDir = Array.isArray(args) ? args[0] : undefined;
|
|
1123
|
+
if (!realProjectDir || realProjectDir.startsWith('--'))
|
|
1124
|
+
return err('Usage: apt-tools produce ttl <select|reverify> <project-dir> [flags]');
|
|
1125
|
+
const flags = parseFlags(args.slice(1));
|
|
1126
|
+
switch (subsub) {
|
|
1127
|
+
case 'select':
|
|
1128
|
+
return cmdTtlSelect(realProjectDir, flags);
|
|
1129
|
+
case 'reverify':
|
|
1130
|
+
return cmdTtlReverify(realProjectDir, flags);
|
|
1131
|
+
default:
|
|
1132
|
+
return err(`Unknown produce ttl subcommand: ${subsub}. Expected select | reverify.`);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* `produce ttl select . --run-id <id> --head-sha <sha> [--sample N]` — return a
|
|
1137
|
+
* deterministic rotating sample of already-done milestones STALE against HEAD
|
|
1138
|
+
* (reuses selectSweepSample; fresh milestones excluded). Read-only — selection,
|
|
1139
|
+
* not a run. Requires --head-sha (staleness fails closed without it).
|
|
1140
|
+
*/
|
|
1141
|
+
function cmdTtlSelect(projectDir, flags) {
|
|
1142
|
+
const runId = strFlag(flags, 'run-id');
|
|
1143
|
+
const headSha = strFlag(flags, 'head-sha');
|
|
1144
|
+
const gate = requireRun(projectDir, runId, 'ttl select');
|
|
1145
|
+
if (gate.error)
|
|
1146
|
+
return gate.error;
|
|
1147
|
+
if (!headSha)
|
|
1148
|
+
return err('produce ttl select requires --head-sha <sha>');
|
|
1149
|
+
const sampleRaw = strFlag(flags, 'sample');
|
|
1150
|
+
const sample = sampleRaw ? Number.parseInt(sampleRaw, 10) : 1;
|
|
1151
|
+
const selected = selectTtlReverifySample(projectDir, runId, { headSha, sample });
|
|
1152
|
+
return ok({
|
|
1153
|
+
status: 'ok',
|
|
1154
|
+
command: 'produce-ttl-select',
|
|
1155
|
+
...selected,
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* `produce ttl reverify . --run-id <id> --milestone <mid> --result <pass|fail>
|
|
1160
|
+
* --probe-family <fam> --head-sha <sha>` — record a cross-family TTL re-verify
|
|
1161
|
+
* run. Refused fail-closed when the probe family equals the recorded implementer
|
|
1162
|
+
* family (ID-08). On `fail` it appends produce.ttl.reverified +
|
|
1163
|
+
* produce.ttl.regression_detected AND fires the FORWARD compensator (a revert +
|
|
1164
|
+
* reopen + re-plan; NEVER a merged-PR rollback — ID-M4-03). A `pass` refreshes
|
|
1165
|
+
* verification against the new HEAD and logs no regression.
|
|
1166
|
+
*/
|
|
1167
|
+
function cmdTtlReverify(projectDir, flags) {
|
|
1168
|
+
const runId = strFlag(flags, 'run-id');
|
|
1169
|
+
const milestoneId = strFlag(flags, 'milestone');
|
|
1170
|
+
const result = strFlag(flags, 'result');
|
|
1171
|
+
const probeFamily = strFlag(flags, 'probe-family');
|
|
1172
|
+
const headSha = strFlag(flags, 'head-sha');
|
|
1173
|
+
const gate = requireRun(projectDir, runId, 'ttl reverify');
|
|
1174
|
+
if (gate.error)
|
|
1175
|
+
return gate.error;
|
|
1176
|
+
if (!milestoneId)
|
|
1177
|
+
return err('produce ttl reverify requires --milestone <mid>');
|
|
1178
|
+
if (!result)
|
|
1179
|
+
return err('produce ttl reverify requires --result <pass|fail>');
|
|
1180
|
+
if (!probeFamily)
|
|
1181
|
+
return err('produce ttl reverify requires --probe-family <fam>');
|
|
1182
|
+
if (!headSha)
|
|
1183
|
+
return err('produce ttl reverify requires --head-sha <sha>');
|
|
1184
|
+
const res = recordTtlReverify(projectDir, { runId, milestoneId, result, probeFamily, headSha });
|
|
1185
|
+
if (!res.ok)
|
|
1186
|
+
return err(`produce ttl reverify refused: ${res.reason}`, 1, {
|
|
1187
|
+
command: 'produce-ttl-reverify',
|
|
1188
|
+
run_id: runId,
|
|
1189
|
+
milestone_id: milestoneId,
|
|
1190
|
+
reason: res.reason,
|
|
1191
|
+
});
|
|
1192
|
+
return ok({
|
|
1193
|
+
status: 'ok',
|
|
1194
|
+
command: 'produce-ttl-reverify',
|
|
1195
|
+
run_id: runId,
|
|
1196
|
+
milestone_id: milestoneId,
|
|
1197
|
+
result: res.result,
|
|
1198
|
+
probe_family: res.probe_family,
|
|
1199
|
+
head_sha: res.head_sha,
|
|
1200
|
+
regression: res.regression,
|
|
1201
|
+
compensator: res.compensator,
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* `produce claim <declare|list|expire|consume> <project-dir> ...` — two-level
|
|
1206
|
+
* dispatch (same shape as cmdSaga/cmdTtl: the sub-subcommand arrives as the outer
|
|
1207
|
+
* `subsub` param; the real dir is `args[0]` and the flags follow it). The Claim
|
|
1208
|
+
* Envelope lifecycle (M6): declare freezes + logs a validated envelope, list
|
|
1209
|
+
* re-projects the ledger, expire logs `produce.claim.expired`, consume emits the
|
|
1210
|
+
* structured delta.
|
|
1211
|
+
*
|
|
1212
|
+
* @param {string} subsub
|
|
1213
|
+
* @param {string[]} args
|
|
1214
|
+
*/
|
|
1215
|
+
function cmdClaim(subsub, args) {
|
|
1216
|
+
const realProjectDir = Array.isArray(args) ? args[0] : undefined;
|
|
1217
|
+
if (!realProjectDir || realProjectDir.startsWith('--'))
|
|
1218
|
+
return err('Usage: apt-tools produce claim <declare|list|expire|consume> <project-dir> [flags]');
|
|
1219
|
+
const flags = parseFlags(args.slice(1));
|
|
1220
|
+
switch (subsub) {
|
|
1221
|
+
case 'declare':
|
|
1222
|
+
return cmdClaimDeclare(realProjectDir, flags);
|
|
1223
|
+
case 'list':
|
|
1224
|
+
return cmdClaimList(realProjectDir, flags);
|
|
1225
|
+
case 'expire':
|
|
1226
|
+
return cmdClaimExpire(realProjectDir, flags);
|
|
1227
|
+
case 'consume':
|
|
1228
|
+
return cmdClaimConsume(realProjectDir, flags);
|
|
1229
|
+
default:
|
|
1230
|
+
return err(`Unknown produce claim subcommand: ${subsub}. Expected declare | list | expire | consume.`);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* `produce claim declare . --run-id <id> --profile <p> --claim-file <path>
|
|
1235
|
+
* [--claim-id <id>]` — read + parse the claim file, VALIDATE + freeze it to
|
|
1236
|
+
* claims/<claim-id>.json exclusive-create, and log `produce.claim.declared`
|
|
1237
|
+
* (content_hash only). An invalid envelope is REFUSED fail-closed with its reason.
|
|
1238
|
+
*/
|
|
1239
|
+
function cmdClaimDeclare(projectDir, flags) {
|
|
1240
|
+
const runId = strFlag(flags, 'run-id');
|
|
1241
|
+
const profile = strFlag(flags, 'profile');
|
|
1242
|
+
const claimFile = strFlag(flags, 'claim-file');
|
|
1243
|
+
const claimId = strFlag(flags, 'claim-id');
|
|
1244
|
+
const gate = requireRun(projectDir, runId, 'claim declare');
|
|
1245
|
+
if (gate.error)
|
|
1246
|
+
return gate.error;
|
|
1247
|
+
if (!profile)
|
|
1248
|
+
return err('produce claim declare requires --profile <id>');
|
|
1249
|
+
if (!claimFile)
|
|
1250
|
+
return err('produce claim declare requires --claim-file <path>');
|
|
1251
|
+
if (!existsSync(claimFile))
|
|
1252
|
+
return err(`produce claim declare: claim file not found: ${claimFile}`);
|
|
1253
|
+
let envelope;
|
|
1254
|
+
try {
|
|
1255
|
+
envelope = JSON.parse(readFileSync(claimFile, 'utf-8'));
|
|
1256
|
+
}
|
|
1257
|
+
catch {
|
|
1258
|
+
return err(`produce claim declare: claim file is not valid JSON: ${claimFile}`);
|
|
1259
|
+
}
|
|
1260
|
+
const res = freezeClaim(projectDir, runId, { profile, envelope, claimId: claimId ?? undefined });
|
|
1261
|
+
if (!res.ok)
|
|
1262
|
+
return err(`produce claim declare refused: ${res.reason}`, 1, {
|
|
1263
|
+
command: 'produce-claim-declare',
|
|
1264
|
+
run_id: runId,
|
|
1265
|
+
reason: res.reason,
|
|
1266
|
+
});
|
|
1267
|
+
return ok({
|
|
1268
|
+
status: 'ok',
|
|
1269
|
+
command: 'produce-claim-declare',
|
|
1270
|
+
run_id: runId,
|
|
1271
|
+
claim_id: res.claim_id,
|
|
1272
|
+
profile: res.profile,
|
|
1273
|
+
content_hash: res.content_hash,
|
|
1274
|
+
reused: res.reused,
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* `produce claim list . --run-id <id>` — read-only re-projection of the claim
|
|
1279
|
+
* ledger (declared − expired) folded from the append-only log (no in-memory
|
|
1280
|
+
* authority). The `produce.claim.*` ops never enter the roadmap projection.
|
|
1281
|
+
*/
|
|
1282
|
+
function cmdClaimList(projectDir, flags) {
|
|
1283
|
+
const runId = strFlag(flags, 'run-id');
|
|
1284
|
+
const gate = requireRun(projectDir, runId, 'claim list');
|
|
1285
|
+
if (gate.error)
|
|
1286
|
+
return gate.error;
|
|
1287
|
+
const ledger = projectClaims(projectDir, runId);
|
|
1288
|
+
return ok({ status: 'ok', command: 'produce-claim-list', ...ledger });
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* `produce claim expire . --run-id <id> --claim-id <id> [--reason "<r>"]` —
|
|
1292
|
+
* append `produce.claim.expired`; the claim leaves the ledger's active set
|
|
1293
|
+
* (anti-corrosion manual expiry). Records the descriptor; the content file stays.
|
|
1294
|
+
*/
|
|
1295
|
+
function cmdClaimExpire(projectDir, flags) {
|
|
1296
|
+
const runId = strFlag(flags, 'run-id');
|
|
1297
|
+
const claimId = strFlag(flags, 'claim-id');
|
|
1298
|
+
const reason = strFlag(flags, 'reason') || 'manual_expiry';
|
|
1299
|
+
const gate = requireRun(projectDir, runId, 'claim expire');
|
|
1300
|
+
if (gate.error)
|
|
1301
|
+
return gate.error;
|
|
1302
|
+
if (!claimId)
|
|
1303
|
+
return err('produce claim expire requires --claim-id <id>');
|
|
1304
|
+
appendProduceEvent(projectDir, 'produce.claim.expired', {
|
|
1305
|
+
run_id: runId,
|
|
1306
|
+
claim_id: claimId,
|
|
1307
|
+
reason,
|
|
1308
|
+
});
|
|
1309
|
+
return ok({
|
|
1310
|
+
status: 'ok',
|
|
1311
|
+
command: 'produce-claim-expire',
|
|
1312
|
+
run_id: runId,
|
|
1313
|
+
claim_id: claimId,
|
|
1314
|
+
reason,
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* `produce claim consume . --run-id <id> [--milestone <mid>]` — emit the
|
|
1319
|
+
* STRUCTURED delta `{claim_id, targets:[{kind, requirement, ref?}]}` for each
|
|
1320
|
+
* consumable active claim (derived from resolution_verifier — never a lesson-text
|
|
1321
|
+
* paste), skipping expired / HEAD-invalidated / precondition-unmatched claims.
|
|
1322
|
+
* Appends one `produce.claim.consumed {run_id, claim_id, target_kind, target_ref?}`
|
|
1323
|
+
* per emitted target for the forensic trail (target_kind only, never content).
|
|
1324
|
+
*/
|
|
1325
|
+
function cmdClaimConsume(projectDir, flags) {
|
|
1326
|
+
const runId = strFlag(flags, 'run-id');
|
|
1327
|
+
const gate = requireRun(projectDir, runId, 'claim consume');
|
|
1328
|
+
if (gate.error)
|
|
1329
|
+
return gate.error;
|
|
1330
|
+
const milestone = strFlag(flags, 'milestone');
|
|
1331
|
+
const ctx = milestone ? { milestone, milestone_id: milestone } : {};
|
|
1332
|
+
const result = consumeClaims(projectDir, runId, ctx);
|
|
1333
|
+
// Forensic trail: one produce.claim.consumed per emitted target (ID-M6-07 —
|
|
1334
|
+
// target_kind only, never claim content).
|
|
1335
|
+
for (const c of result.consumed) {
|
|
1336
|
+
for (const t of c.targets) {
|
|
1337
|
+
const data = { run_id: runId, claim_id: c.claim_id, target_kind: t.kind };
|
|
1338
|
+
if (typeof t.ref === 'string' && t.ref)
|
|
1339
|
+
data.target_ref = t.ref;
|
|
1340
|
+
appendProduceEvent(projectDir, 'produce.claim.consumed', data);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
return ok({ status: 'ok', command: 'produce-claim-consume', run_id: runId, ...result });
|
|
1344
|
+
}
|
|
1345
|
+
//# sourceMappingURL=produce.mjs.map
|