@sabaiway/agent-workflow-kit 5.2.0 → 5.3.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 +40 -0
- package/SKILL.md +1 -1
- package/bridges/codex-cli-bridge/SKILL.md +10 -3
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +153 -34
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +228 -4
- package/bridges/codex-cli-bridge/bin/codex-review.sh +1 -1
- package/bridges/codex-cli-bridge/capability.json +3 -2
- package/bridges/codex-cli-bridge/references/driving-codex.md +5 -3
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +24 -10
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/scripts/archive-decisions.mjs +340 -15
- package/references/scripts/archive-decisions.test.mjs +522 -2
- package/tools/detect-backends.mjs +1 -0
- package/tools/dispatch-record.mjs +926 -0
- package/tools/flow-check.mjs +48 -12
package/tools/flow-check.mjs
CHANGED
|
@@ -281,7 +281,33 @@ export const evaluateVetoOverride = ({ records, vetoReceipt, tree }) => {
|
|
|
281
281
|
// and their records remain forgeable (each store's own header says so) — a backdated or
|
|
282
282
|
// backwards-clock instant can move a red across this boundary; the cross-store arming-fence
|
|
283
283
|
// hardening is queued (FLOW-ARMING-FENCE-CROSS-STORE), never pretended here.
|
|
284
|
-
|
|
284
|
+
//
|
|
285
|
+
// The lane split (FLOW-FINAL-RED-DEADLOCK) — the same reasoning the D10 arm already applies one arm
|
|
286
|
+
// away (see the `consumer` paragraph in computeFlowDecision's own header; a line reference there
|
|
287
|
+
// would rot on the next insertion). The strict rule demands a receipt the gate's OWN run has not
|
|
288
|
+
// written yet: run-gates appends the final receipt only AFTER every gate has run, so an in-matrix
|
|
289
|
+
// flow-check can never see the completed retry that would answer the newest red, and each --final
|
|
290
|
+
// mints red N+1 — no number of rerun-causes converges. On the 'gate' lane a current-base red is
|
|
291
|
+
// therefore ALSO answered by a provable IN-PROGRESS retry: an authoritative rerun-cause naming its
|
|
292
|
+
// attempt AND binding the CURRENT fingerprint, plus a final-start at that fingerprint ordered
|
|
293
|
+
// STRICTLY AFTER that red whose attempt carries no completed final — the shape run-gates creates
|
|
294
|
+
// before any gate runs (run-gates.mjs:636-658), so inside a real final run the conjunction holds by
|
|
295
|
+
// construction while a standalone check on a quiet tree still refuses. The relaxation carries the
|
|
296
|
+
// SAME base correlation the strict rule demands of the retry tree: a fingerprint is not unique to a
|
|
297
|
+
// base (a clean tree hashes identically under every HEAD), so without it a cause minted at another
|
|
298
|
+
// base would answer the gate and leave a green final the commit boundary is guaranteed to reject —
|
|
299
|
+
// the relaxation must only ever admit a state a completed retry could actually clear. It is opt-in
|
|
300
|
+
// by EXACT match: any other consumer — unknown, misspelled, absent — evaluates the strict rule, and
|
|
301
|
+
// a tree whose fingerprint is unresolvable or ambiguously correlated never relaxes. The
|
|
302
|
+
// 'commit-guard' lane is unchanged; the commit boundary still demands a real completed retry.
|
|
303
|
+
// Stated residual: an INTERRUPTED final run leaves exactly that record shape with no live run
|
|
304
|
+
// behind it, so a standalone check reads PASS in that window. It authorizes nothing — commit-guard
|
|
305
|
+
// refuses the dangling start independently (commit-guard.mjs:213-223) and consults the STRICT lane
|
|
306
|
+
// (commit-guard.mjs:253) — and the window closes on the next completed final run. The real fix is a
|
|
307
|
+
// runner-attested capability (a one-time unpublished nonce over stdin or an inherited FD, verified
|
|
308
|
+
// against a one-way commitment recorded in the final-start); it needs its own IPC contract and is
|
|
309
|
+
// QUEUED, never pretended here.
|
|
310
|
+
export const collectUnansweredRedRefusals = ({ flowRecords, coreRecords, currentBase, owner, consumer = 'commit-guard', currentFingerprint = null }) => {
|
|
285
311
|
if (!hasOwnAdoption(flowRecords, owner)) return [];
|
|
286
312
|
const adoptionInstants = flowRecords
|
|
287
313
|
.filter((r) => r.kind === CHAIN_KIND && r.purpose === 'adoption' && r.owner === owner)
|
|
@@ -302,6 +328,13 @@ export const collectUnansweredRedRefusals = ({ flowRecords, coreRecords, current
|
|
|
302
328
|
&& basesAt(g.fingerprintBefore).length === 1 && basesAt(g.fingerprintBefore)[0] === currentBase
|
|
303
329
|
&& firstFinalByAttempt.get(g.attempt) === gi
|
|
304
330
|
&& c.fingerprint === g.fingerprintBefore));
|
|
331
|
+
const completedAttempts = new Set(finals.map(({ r }) => r.attempt));
|
|
332
|
+
const relaxes = consumer === 'gate' && typeof currentFingerprint === 'string' && currentFingerprint !== ''
|
|
333
|
+
&& basesAt(currentFingerprint).length === 1 && basesAt(currentFingerprint)[0] === currentBase;
|
|
334
|
+
const inProgressRetryFor = (red, redAt) => rerunCauses.some((c) => c.attempt === red.attempt
|
|
335
|
+
&& c.fingerprint === currentFingerprint
|
|
336
|
+
&& coreRecords.some((s, si) => si > redAt && s.kind === 'final-start'
|
|
337
|
+
&& s.fingerprint === currentFingerprint && !completedAttempts.has(s.attempt)));
|
|
305
338
|
for (const { r, i } of finals) {
|
|
306
339
|
if (r.status !== 'red') continue;
|
|
307
340
|
if (armingInstant !== null && isCanonicalInstant(r.timestamp) && Date.parse(r.timestamp) < armingInstant) continue;
|
|
@@ -315,7 +348,7 @@ export const collectUnansweredRedRefusals = ({ flowRecords, coreRecords, current
|
|
|
315
348
|
continue;
|
|
316
349
|
}
|
|
317
350
|
if (bases[0] !== currentBase) continue;
|
|
318
|
-
if (!answeredBy(r, i)) {
|
|
351
|
+
if (!answeredBy(r, i) && !(relaxes && inProgressRetryFor(r, i))) {
|
|
319
352
|
refusals.push(`a red final (attempt "${r.attempt}") on the CURRENT base (${bases[0] == null ? 'null' : short(bases[0])}) has no later completed retry cleared by a rerun-cause — an unanswered red never passes an armed flow (#65). recovery (edit the quoted cause, then paste; mint on the RETRY tree): ${writerCommand(`rerun-cause --attempt=${shellQuote(r.attempt)} --cause='<the stated cause>'`)}`);
|
|
320
353
|
}
|
|
321
354
|
}
|
|
@@ -568,13 +601,14 @@ export const computeAllPathWorktreeSurface = (cwd) => {
|
|
|
568
601
|
return { ok: true, paths: [...new Set([...tracked, ...untracked])] };
|
|
569
602
|
};
|
|
570
603
|
|
|
571
|
-
// decideFlowCheck({ flowRead, coreRead, owner, motion?, evidence? }) → { refusals,
|
|
572
|
-
// Pure — consumes the FULL read-results of both stores; store health fails closed
|
|
573
|
-
// content judgment. `motion` ({ currentBase, resolveBaseDelta, resolveChangedSurface })
|
|
574
|
-
// Step-1.4 base-motion refusals; `evidence` ({ receipts, tree, backends }) arms the three
|
|
575
|
-
// rungs (#65/#25/#42 — each self-gates on an OWN adoption). Absent inputs keep the decision
|
|
576
|
-
// byte-identical to the Plan-2 checker.
|
|
577
|
-
|
|
604
|
+
// decideFlowCheck({ flowRead, coreRead, owner, motion?, evidence?, consumer? }) → { refusals,
|
|
605
|
+
// advisories }. Pure — consumes the FULL read-results of both stores; store health fails closed
|
|
606
|
+
// BEFORE any content judgment. `motion` ({ currentBase, resolveBaseDelta, resolveChangedSurface })
|
|
607
|
+
// arms the Step-1.4 base-motion refusals; `evidence` ({ receipts, tree, backends }) arms the three
|
|
608
|
+
// Phase-1 rungs (#65/#25/#42 — each self-gates on an OWN adoption). Absent inputs keep the decision
|
|
609
|
+
// byte-identical to the Plan-2 checker. `consumer` rides through to the #65 lane split and defaults
|
|
610
|
+
// to the STRICT lane, so a caller that forgets to thread it inherits strictness.
|
|
611
|
+
export const decideFlowCheck = ({ flowRead, coreRead, owner, flowPath = 'the flow store', corePath = 'the core evidence store', motion = null, evidence = null, consumer = 'commit-guard' }) => {
|
|
578
612
|
const refusals = [];
|
|
579
613
|
const advisories = [];
|
|
580
614
|
if (flowRead.readError) refusals.push(`the flow store is unreadable (${flowRead.readError}) — the checker consumes the FULL read-result; inspect ${flowPath} (fail closed)`);
|
|
@@ -594,7 +628,7 @@ export const decideFlowCheck = ({ flowRead, coreRead, owner, flowPath = 'the flo
|
|
|
594
628
|
refusals.push(...deltaRefusals(records, owner));
|
|
595
629
|
refusals.push(...degradeOrderingRefusals(coreRead.records));
|
|
596
630
|
if (evidence != null) {
|
|
597
|
-
refusals.push(...collectUnansweredRedRefusals({ flowRecords: records, coreRecords: coreRead.records, currentBase: evidence.tree.base, owner }));
|
|
631
|
+
refusals.push(...collectUnansweredRedRefusals({ flowRecords: records, coreRecords: coreRead.records, currentBase: evidence.tree.base, owner, consumer, currentFingerprint: evidence.tree.fingerprint }));
|
|
598
632
|
refusals.push(...collectDegradeCoverageRefusals({ flowRecords: records, coreRecords: coreRead.records, tree: evidence.tree, owner, backends: evidence.degradeBackends }));
|
|
599
633
|
refusals.push(...collectReceiptCoverageRefusals({ flowRecords: records, receipts: evidence.receipts, tree: evidence.tree, owner, backends: evidence.receiptBackends, declaredPaths: evidence.declaredPaths, refreshCap: evidence.refreshCap }));
|
|
600
634
|
}
|
|
@@ -611,7 +645,9 @@ export const decideFlowCheck = ({ flowRead, coreRead, owner, flowPath = 'the flo
|
|
|
611
645
|
// #42 never falls open). `consumer` (Plan 4 Decision 2): the D10 flow→final comparison runs ONLY
|
|
612
646
|
// on the 'commit-guard' lane — the default 'gate' lane (the in-matrix flow-check --check gate)
|
|
613
647
|
// stays inert on it, because during a final run the "latest final" is by construction the
|
|
614
|
-
// PREVIOUS one and an in-matrix comparison would make a new green final unreachable.
|
|
648
|
+
// PREVIOUS one and an in-matrix comparison would make a new green final unreachable. The SAME
|
|
649
|
+
// distinction reaches the #65 unanswered-red rung (its own header states the split): the 'gate'
|
|
650
|
+
// lane also answers a red under a provable in-progress retry, for the same reason.
|
|
615
651
|
export const computeFlowDecision = ({ cwd = process.cwd(), consumer = 'gate', probes = {} } = {}) => {
|
|
616
652
|
const fingerprintProbe = probes.fingerprint ?? computeTreeFingerprint;
|
|
617
653
|
const owner = deriveFlowOwner(cwd);
|
|
@@ -745,7 +781,7 @@ export const computeFlowDecision = ({ cwd = process.cwd(), consumer = 'gate', pr
|
|
|
745
781
|
}
|
|
746
782
|
}
|
|
747
783
|
}
|
|
748
|
-
const { refusals, advisories } = decideFlowCheck({ flowRead, coreRead, owner, flowPath, corePath, motion, evidence });
|
|
784
|
+
const { refusals, advisories } = decideFlowCheck({ flowRead, coreRead, owner, flowPath, corePath, motion, evidence, consumer });
|
|
749
785
|
// Semantic refusals bind only an ARMED store; the D10 binding refusals ride the commit-guard
|
|
750
786
|
// lane UNCONDITIONALLY — a deleted or truncated store must never un-arm the binding.
|
|
751
787
|
const effectiveRefusals = healthBroken ? refusals : [...(armed ? [...refusals, ...evidenceRefusals] : []), ...bindingRefusals];
|