@junghanacs/entwurf 0.18.0 → 0.18.1
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/AGENTS.md +4 -4
- package/CHANGELOG.md +172 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +9 -5
- package/VERIFY.md +3 -3
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +46 -12
- package/docs/setup-clean-host.md +35 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
- package/package.json +9 -9
- package/pi-extensions/entwurf-control.ts +12 -2
- package/pi-extensions/lib/acp/acp-client.ts +3 -3
- package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
- package/pi-extensions/lib/acp/backend.ts +3 -3
- package/pi-extensions/lib/acp/event-mapper.ts +5 -5
- package/run.sh +125 -56
- package/scripts/check-acp-usage-accounting.ts +9 -9
- package/scripts/check-gate-qualification.ts +62 -12
- package/scripts/check-release-gate-outcomes.ts +160 -3
- package/scripts/mutants/bridge-command-boot.json +3 -3
- package/scripts/mutants/gate-qualification.json +28 -0
- package/scripts/mutants/pack-install.json +14 -3
- package/scripts/mutants/release-gate.json +22 -0
- package/scripts/raw-acp-compaction-measure/README.md +77 -0
- package/scripts/raw-acp-compaction-measure/probe.ts +196 -0
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
|
@@ -150,7 +150,7 @@ export interface AcpAdapterSettingsParams {
|
|
|
150
150
|
*
|
|
151
151
|
* During stream handling the vendor keeps a per-message snapshot in
|
|
152
152
|
* `lastAssistantUsage`, but its `usage_update` publishes only the scalar `used`
|
|
153
|
-
* value (read at claude-agent-acp 0.
|
|
153
|
+
* value (read at claude-agent-acp 0.75.1 `dist/acp-agent.js:3853-3878`). Until
|
|
154
154
|
* that partition is carried on the wire, NOTHING honest can go in pi's four
|
|
155
155
|
* fields, so entwurf writes none of them.
|
|
156
156
|
*/
|
|
@@ -342,8 +342,8 @@ export const claudeAdapter: AcpBackendAdapter = {
|
|
|
342
342
|
|
|
343
343
|
// MEASURED (2026-09-02, #93): claude-agent-acp's `usage_update` carries a
|
|
344
344
|
// session-cumulative ESTIMATED cost (`cost.amount = message.total_cost_usd`)
|
|
345
|
-
// and a context-occupancy scalar (`used`; both read at 0.
|
|
346
|
-
// `dist/acp-agent.js:
|
|
345
|
+
// and a context-occupancy scalar (`used`; both read at 0.75.1
|
|
346
|
+
// `dist/acp-agent.js:3467-3482`). Both are measured, so backend.ts seals them.
|
|
347
347
|
// The token partition is NOT declared here: ACP's only token carrier is a
|
|
348
348
|
// per-turn round-trip aggregate, which is not what pi's four fields mean.
|
|
349
349
|
sealsTurnAccounting: true,
|
|
@@ -137,7 +137,7 @@ function finiteOrZero(value: unknown): number {
|
|
|
137
137
|
* "the accounting-grade figure per the SDK" — it also counts Task subagents,
|
|
138
138
|
* sidechains, and INTERNAL CALLS SUCH AS COMPACTION, so its rows "can total more
|
|
139
139
|
* than `token_count`" and are "the fuller picture, not a decomposition of it"
|
|
140
|
-
* (read at claude-agent-acp 0.
|
|
140
|
+
* (read at claude-agent-acp 0.75.1 `dist/acp-agent.js:6465-6485`). The narrower
|
|
141
141
|
* `PromptResponse.usage` (== `quota.token_count`) is the MAIN AGENT LOOP only.
|
|
142
142
|
*
|
|
143
143
|
* The wider one is the right numerator because the denominator already has that
|
|
@@ -1237,7 +1237,7 @@ export function streamAcpTurn(
|
|
|
1237
1237
|
// Writing zeros is not a placeholder for a better number we could compute:
|
|
1238
1238
|
// the per-request partition is genuinely absent from the wire. The vendor
|
|
1239
1239
|
// builds it in `lastAssistantUsage` and sends only its scalar sum (read at
|
|
1240
|
-
// claude-agent-acp 0.
|
|
1240
|
+
// claude-agent-acp 0.75.1 `dist/acp-agent.js:3853-3878`) — #96.
|
|
1241
1241
|
//
|
|
1242
1242
|
// But silence is NOT the resting state. A cache miss the operator never sees
|
|
1243
1243
|
// is a false reading, not a modest one: a session can run for hours believing
|
|
@@ -1356,7 +1356,7 @@ export function streamAcpTurn(
|
|
|
1356
1356
|
// between them is exactly why the notice below names a MECHANISM and
|
|
1357
1357
|
// never a cause: claude-agent-acp's `conversation_reset` handler only
|
|
1358
1358
|
// switches the SDK to a fresh conversation and touches no cost at all
|
|
1359
|
-
// (read at 0.
|
|
1359
|
+
// (read at 0.75.1 `dist/acp-agent.js:4282-4289`), while claude-agent-sdk
|
|
1360
1360
|
// separately documents that "a mid-session /clear resets the running
|
|
1361
1361
|
// total" (read at 0.3.257 `sdk.d.ts:4884`). A reset therefore PLAUSIBLY
|
|
1362
1362
|
// explains a backwards total, but nothing here has MEASURED that it did,
|
|
@@ -60,8 +60,8 @@ export type AcpPiStreamState = {
|
|
|
60
60
|
*
|
|
61
61
|
* Last write wins, never a sum: both are latest session-level observations, and
|
|
62
62
|
* one turn can legitimately see several (claude emits one per `result` message,
|
|
63
|
-
* including a sub-agent's own — read at claude-agent-acp 0.
|
|
64
|
-
* `dist/acp-agent.js:
|
|
63
|
+
* including a sub-agent's own — read at claude-agent-acp 0.75.1
|
|
64
|
+
* `dist/acp-agent.js:3467-3482`), each carrying that result's current values.
|
|
65
65
|
*/
|
|
66
66
|
observedSessionCostUsd?: number;
|
|
67
67
|
observedContextOccupancyTokens?: number;
|
|
@@ -346,9 +346,9 @@ export function applyAcpSessionUpdate(
|
|
|
346
346
|
case "usage_update": {
|
|
347
347
|
// `used` is OCCUPANCY-shaped — the backend's post-turn context size, not
|
|
348
348
|
// the prompt response's turn aggregate. Claude sends `lastAssistantTotalUsage`
|
|
349
|
-
// as `used` (read at claude-agent-acp 0.
|
|
350
|
-
// `dist/acp-agent.js:
|
|
351
|
-
// latest assistant snapshot (`:
|
|
349
|
+
// as `used` (read at claude-agent-acp 0.75.1
|
|
350
|
+
// `dist/acp-agent.js:3867-3878`), after constructing that scalar from the
|
|
351
|
+
// latest assistant snapshot (`:3853-3866`). pi reads `usage.totalTokens` as
|
|
352
352
|
// exactly that occupancy (`calculateContextTokens(usage) = usage.totalTokens
|
|
353
353
|
// || input + output + cacheRead + cacheWrite`, read at pi-coding-agent
|
|
354
354
|
// `dist/core/compaction/compaction.js:86-88`). The assignment below is the
|
package/run.sh
CHANGED
|
@@ -192,6 +192,7 @@ Usage:
|
|
|
192
192
|
./run.sh smoke-omp-receive-live # #87 bundle B acceptance: another harness wakes an ALREADY-OPEN omp citizen, which reads its inbox and answers in the same session. Decides its own outcome from the capability registry — omp without a drainable mailbox is a protocol SKIP (no receiver unit exists to arm), which an unattended release-gate reports and `--cut` reads as RED; a registry that claims a receive rail with no acceptance body here is a FAIL
|
|
193
193
|
./run.sh smoke-entwurf-chain-live # LIVE cross-harness delivery chain: native Claude Code -> pi GPT -> pi ACP Sonnet -> mailbox terminus, proving sender identity/replyable at every hop and a real read receipt at the end. Prerequisites (claude on PATH, pi credentials per backend) report protocol SKIP, never a pass
|
|
194
194
|
./run.sh check-release-gate-outcomes # release-gate STEP OUTCOME protocol (P1): one skip exit code shared by the shell + TS halves, classifier never rounds a skip up to a pass, `--cut` refuses a MUST SKIP while a bare diagnostic stays exit 0, no LIVE smoke keeps the old exit-0 skip shape, and both real skip surfaces are INVOKED and observed to propagate the code
|
|
195
|
+
./run.sh check-gate-manifests # the HEAD of that qualification, on its own: runner self-test + the committed manifests validated against the origin index + the declared lane inventory. Executes ZERO mutants and never snapshots this repo (~8s), which is why it lives in check:hermetic while the body stays on CI/release-gate — three of the five reds qualification ever produced in CI died right here, before a mutant gate ran (#99 B-3)
|
|
195
196
|
./run.sh check-gate-qualification # kill-proof qualification (the gate-of-gates): runner self-test (classifier truth table + synthetic negatives incl. wrong-reason/hang/control-red/impurity) + committed mutant manifests (scripts/mutants/*.json) run in an isolated snapshot repo under control→mutant→restore→control; the real checkout is never written. Evidence = claim IDs + killed mutant IDs, never assertion counts
|
|
196
197
|
./run.sh check-probe-ordering # §11-7 ordering-probe deterministic gate: raw-client SAMENESS pinned to backend.ts (sequence/args/timeouts/permission policy — the probe may never measure a lookalike), phase attribution incl. set-model, probe-mode fixture wire markers (delay honored, probeRunId REQUIRED, smoke-acp-mcp-live legacy compat), event-log door integrity (reserved keys refused at write; unknown marker name, broken sort axis, or a payload the classifier cannot judge on is MALFORMED, never a quiet event), and the §11-7 paired-verdict truth table (P0/I0 outside the space, phase-qualified D, B promotion ladder, C, A two-delay rule). Offline/deterministic; one product claim (no production prompt cutoff) is replant-qualified via scripts/mutants/probe-ordering.json, while the other assertions are direct [CHECK:*] contracts
|
|
197
198
|
./run.sh check-probe-cli-shim # §11-7-c B-name-snapshot PRODUCER gate: the CLI shim driven as a REAL process against fake CLIs (no API, no cost). Proves what a defect would buy — FABRICATED evidence (a malformed init is never reported as an empty name set; the boot report carries the true target path+sha256 the classifier verifies against the roster), a DESTROYED turn (byte transparency across mid-UTF8/CRLF/oversized/unterminated framing, exit-code fidelity, signal re-raise, inbound signal forwarding, stderr passthrough, stdout backpressure), and LEAKED operator state (exact-allowlist env scrub, no argv/env/prompt body in the log). Offline/deterministic; 20 direct [CHECK:*] contracts, deliberately no longer replant-qualified after #70 subtraction
|
|
@@ -253,7 +254,7 @@ Usage:
|
|
|
253
254
|
./run.sh check-dep-versions # local deterministic check that the pi pin agrees across package.json (devDeps + peer range), run.sh (peer-install pins), and the baseline docs (AGENTS/README/ROADMAP/setup-clean-host/demo)
|
|
254
255
|
./run.sh check-node-floor-coherence # binds the Node floor (24+, single axis) across engines.node, run.sh setup preflight, meta-bridge install/doctor judgment logic, clean-host docs, the bridge launcher header, and the CI runner node-version — engines.node is the SSOT, everything else is derived; sweeps tracked contract text for an unregistered declaration
|
|
255
256
|
./run.sh check-pack # publish gate (dry-run): npm pack --dry-run + tarball invariants (runtime-critical present, dev residue absent)
|
|
256
|
-
./run.sh check-pack-pin-matcher # pure self-test of check-pack-install's pin-leak matcher against synthetic .pnpm lookalikes
|
|
257
|
+
./run.sh check-pack-pin-matcher # pure self-test of check-pack-install's pin-leak matcher against synthetic .pnpm lookalikes, one cell per property: version boundary (@0.85.10 must leak, @0.85.1 bare or with either measured peer-hash must pass) and closure prefix (an off-pin @earendil-works/chord must leak — it carries no `pi-` prefix); snapshot-safe qualification oracle, also run first inside check-pack-install
|
|
257
258
|
./run.sh check-fresh-cut-gate # SOURCE cell of the generation-boundary proof (IN pnpm run check:full): drives real install/setup/fresh-cut in a sandbox; certification refusal is pre-write, quiescence is fail-closed, archives preserve bytes, and the #54 exit matrix distinguishes complete / no-move / usage / incomplete transition / complete-with-cleanup-residue. No model/network/cost
|
|
258
259
|
./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke with the pinned pi peers (pins derived from the package.json devDep; check-dep-versions binds them) + the npm-installed bridge BOOTS (tools/list) and DELIVERS (tools/call entwurf_v2 → .msg lands) + the installed all-absent and copilot-present (four-unit fake-vendor) `entwurf setup` rows + the INSTALLED generation lifecycle on a seeded previous-generation host (REFUSE before activation writes / zero Claude invocations → installed fresh-cut archives + opens empty → install-meta-bridge PASSES) + the INSTALLED-PACKAGE branch of the Copilot and OMP birth installers actually RUN (compiled entry selected, no raw .ts, and a real birth edge mints a citizen — the half a required-artifact list can never stand in for)
|
|
259
260
|
./run.sh check-install-container # 0.12.8 (#51 C): Linux artifact-CONSUMER gate — one candidate .tgz handed read-only to a checkout-invisible node:<engines-major>-bookworm cell. Default packs once to temp; ENTWURF_CANDIDATE_TGZ=/absolute/preserved.tgz consumes those exact bytes with no re-pack and prints canonical path+sha256 for release. Non-root global PATH install, frozen package, MCP tools/list, fake-Claude install-meta-bridge, path+sha256 fence, strict doctor, and the GENERATION host-state matrix (clean / v3-only store bytes unchanged / previous-generation REFUSE→fresh-cut→retry PASS) seeded inline. Docker missing = honest SKIP; ENTWURF_REQUIRE_DOCKER=1 makes that RED (required CI)
|
|
@@ -1845,13 +1846,13 @@ assert.equal(peerTui, piAi,
|
|
|
1845
1846
|
// floor tracks the devDep pin so a consumer can't install against a pi lacking
|
|
1846
1847
|
// the public trust exports the bridge imports at the pinned minor, AND an upper
|
|
1847
1848
|
// bound at the next minor stops a fresh install from silently pulling a future
|
|
1848
|
-
// pi (past the declared ceiling — 0.
|
|
1849
|
+
// pi (past the declared ceiling — 0.86+ at the current 0.85.1 pin) whose
|
|
1849
1850
|
// internal export surface has drifted from the one we typecheck against.
|
|
1850
1851
|
// pi moves its public surface every minor (the 0.79→0.80 getModels→provider-
|
|
1851
1852
|
// factory churn is exactly this), so an open `>=` floor is exactly how the next
|
|
1852
1853
|
// installer re-acquires the drift. The floor is also the HARD MINIMUM a consumer
|
|
1853
|
-
// install resolves: at `>=0.
|
|
1854
|
-
// Expected shape: `>=<devDep> <0.<minor+1>` (e.g. `>=0.
|
|
1854
|
+
// install resolves: at `>=0.85.1` an existing 0.85.0 host is upgraded, not kept.
|
|
1855
|
+
// Expected shape: `>=<devDep> <0.<minor+1>` (e.g. `>=0.85.1 <0.86`).
|
|
1855
1856
|
const [piMaj, piMin] = piAi.split('.').map(Number);
|
|
1856
1857
|
assert.equal(piMaj, 0,
|
|
1857
1858
|
`pi pin major must stay 0 for the next-minor ceiling rule (got ${piAi}); revisit check-dep-versions when pi reaches 1.x`);
|
|
@@ -1878,7 +1879,11 @@ assert.equal(peerDepTui, expectedPeer,
|
|
|
1878
1879
|
// Scope is deliberately narrow: only sentences that DECLARE the pi pin. History
|
|
1879
1880
|
// (CHANGELOG/NEXT) keeps its old versions, and a pi mention without a version
|
|
1880
1881
|
// (an uninstall line, a type import) is not a declaration.
|
|
1881
|
-
|
|
1882
|
+
// VERIFY.md joined on 2026-09-06: it declared the pi range in prose that no gate
|
|
1883
|
+
// read, and sat two bumps stale (`>=0.84.3 <0.85` at a 0.85.1 pin) precisely
|
|
1884
|
+
// because it was outside this list. Its declaration is a plain range, so the
|
|
1885
|
+
// range scan below binds it — no new PROSE_DECL is needed for it.
|
|
1886
|
+
const BASELINE_DOCS = ['AGENTS.md', 'README.md', 'ROADMAP.md', 'docs/setup-clean-host.md', 'demo/README.md', 'VERIFY.md'];
|
|
1882
1887
|
let rangeDecls = 0, exactDecls = 0;
|
|
1883
1888
|
for (const file of BASELINE_DOCS) {
|
|
1884
1889
|
const text = readFileSync(file, 'utf8');
|
|
@@ -3114,35 +3119,65 @@ check_pack() {
|
|
|
3114
3119
|
}
|
|
3115
3120
|
|
|
3116
3121
|
# The pin-leak filter for the install tree's .pnpm listing, shared by the real scan and its
|
|
3117
|
-
# self-test below.
|
|
3118
|
-
#
|
|
3119
|
-
#
|
|
3120
|
-
#
|
|
3121
|
-
#
|
|
3122
|
+
# self-test below. TWO properties are load-bearing and each has its own cell below.
|
|
3123
|
+
#
|
|
3124
|
+
# (1) The version BOUNDARY. A pnpm .pnpm entry is `<name>@<version>` followed by either
|
|
3125
|
+
# `_<peer-hash>` or end-of-name (measured pnpm 11.20.0 on this tree), so an unbounded
|
|
3126
|
+
# substring match would bless a lookalike such as `@0.85.10` while announcing the pinned
|
|
3127
|
+
# floor — a false-green oracle (found by independent review, 2026-08-25).
|
|
3128
|
+
# (2) The name PREFIX covers the whole @earendil-works closure, not the `pi-` families alone.
|
|
3129
|
+
# 0.85.0 added `@earendil-works/chord`, declared as a runtime `dependencies` entry by
|
|
3130
|
+
# pi-coding-agent, pi-agent-core, pi-client and pi-protocol — inside the closure, with a
|
|
3131
|
+
# name that carries no `pi-` prefix. `[측정 2026-09-06]` a real 0.85.1 install tree lists
|
|
3132
|
+
# `@earendil-works+chord@0.85.1` next to the seven pi entries, and the old
|
|
3133
|
+
# `^@earendil-works+pi-` filter did not see it: chord would have floated silently while
|
|
3134
|
+
# this gate printed a verified pin. The prefix is the closure's org, not one family in it.
|
|
3135
|
+
# (The measured peer-hash suffix moved too — `_ws@8.21.3` at 0.85.1 where 0.84.4 carried
|
|
3136
|
+
# `_@modelcontextprotocol+sdk@…` — which is exactly what property (1)'s `(_|$)` absorbs;
|
|
3137
|
+
# both shapes are fixtures below so a future suffix change cannot pass vacuously.)
|
|
3122
3138
|
pack_install_leaked_pi() {
|
|
3123
|
-
grep '^@earendil-works+
|
|
3139
|
+
grep '^@earendil-works+' | grep -Ev '@0\.85\.1(_|$)' || true
|
|
3124
3140
|
}
|
|
3125
3141
|
|
|
3126
|
-
# Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise
|
|
3127
|
-
# false-green shape (it contains no 0.
|
|
3128
|
-
#
|
|
3129
|
-
#
|
|
3130
|
-
# check-pack-install cannot run inside
|
|
3131
|
-
# and a kill-proof needs a control-green
|
|
3132
|
-
# first so the heavy gate cannot proceed on a
|
|
3142
|
+
# Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise either
|
|
3143
|
+
# false-green shape (it contains no 0.85.10 and no off-pin chord), so the oracle is proven
|
|
3144
|
+
# against fixture listings. One cell per property of pack_install_leaked_pi, so a regression
|
|
3145
|
+
# in either is named by its own signature rather than folded into a single verdict. Exposed
|
|
3146
|
+
# as its own snapshot-safe subcommand because the heavy check-pack-install cannot run inside
|
|
3147
|
+
# the qualification snapshot (no install environment), and a kill-proof needs a control-green
|
|
3148
|
+
# gate there; check-pack-install still runs this first so the heavy gate cannot proceed on a
|
|
3149
|
+
# broken oracle.
|
|
3133
3150
|
check_pack_pin_matcher() {
|
|
3134
3151
|
local matcher_probe
|
|
3152
|
+
|
|
3153
|
+
# Cell 1 — the version boundary. Both measured peer-hash shapes are fixtures: the
|
|
3154
|
+
# 0.84.4-era `_@modelcontextprotocol+sdk@…` and the `_ws@8.21.3` observed on a real
|
|
3155
|
+
# 0.85.1 tree. Neither may leak; the two lookalikes must.
|
|
3156
|
+
matcher_probe=$(printf '%s\n' \
|
|
3157
|
+
'@earendil-works+pi-ai@0.85.1' \
|
|
3158
|
+
'@earendil-works+pi-ai@0.85.1_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
|
|
3159
|
+
'@earendil-works+pi-ai@0.85.1_ws@8.21.3' \
|
|
3160
|
+
'@earendil-works+pi-ai@0.85.10' \
|
|
3161
|
+
'@earendil-works+pi-agent-core@0.85.0' | pack_install_leaked_pi)
|
|
3162
|
+
if [ "$matcher_probe" != '@earendil-works+pi-ai@0.85.10
|
|
3163
|
+
@earendil-works+pi-agent-core@0.85.0' ]; then
|
|
3164
|
+
fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the 0.85.10/0.85.0 lookalikes and pass 0.85.1 bare or with either measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
|
|
3165
|
+
return 1
|
|
3166
|
+
fi
|
|
3167
|
+
|
|
3168
|
+
# Cell 2 — the closure prefix. chord is a non-`pi-` member of the same runtime closure
|
|
3169
|
+
# (0.85.0 onward). An off-pin chord MUST leak; the pinned one must not. A matcher narrowed
|
|
3170
|
+
# back to `^@earendil-works+pi-` sees nothing here and dies at this signature.
|
|
3135
3171
|
matcher_probe=$(printf '%s\n' \
|
|
3136
|
-
'@earendil-works+
|
|
3137
|
-
'@earendil-works+
|
|
3138
|
-
'@earendil-works+pi-ai@0.
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
@earendil-works+pi-agent-core@0.84.3' ]; then
|
|
3142
|
-
fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the 0.84.40/0.84.3 lookalikes and pass 0.84.4 bare or with a peer-hash — got: ${matcher_probe:-<nothing leaked>}"
|
|
3172
|
+
'@earendil-works+chord@0.85.1' \
|
|
3173
|
+
'@earendil-works+chord@0.85.0' \
|
|
3174
|
+
'@earendil-works+pi-ai@0.85.1' | pack_install_leaked_pi)
|
|
3175
|
+
if [ "$matcher_probe" != '@earendil-works+chord@0.85.0' ]; then
|
|
3176
|
+
fail "[QK:PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE] the pin-leak matcher must cover every @earendil-works closure member, not just the pi-* families — an off-pin @earendil-works/chord has to leak (it is a runtime dependency of pi-coding-agent/pi-agent-core/pi-client/pi-protocol at 0.85.x) — got: ${matcher_probe:-<nothing leaked>}"
|
|
3143
3177
|
return 1
|
|
3144
3178
|
fi
|
|
3145
|
-
|
|
3179
|
+
|
|
3180
|
+
echo "[check-pack-pin-matcher] ok — the pin-leak matcher is version-bounded (lookalikes leak, pinned version passes bare and with either measured peer-hash) and covers the whole @earendil-works closure (chord included)"
|
|
3146
3181
|
}
|
|
3147
3182
|
|
|
3148
3183
|
_check_pack_install_impl() {
|
|
@@ -3369,7 +3404,7 @@ _check_pack_install_impl() {
|
|
|
3369
3404
|
printf '%s\n' '{ "name": "entwurf-install-smoke", "version": "0.0.0", "private": true }' > "$tmp/package.json"
|
|
3370
3405
|
|
|
3371
3406
|
# pi-agent-core is pinned even though we never import it: pi-coding-agent depends
|
|
3372
|
-
# on it by CARET (`^0.
|
|
3407
|
+
# on it by CARET (`^0.85.x`), so with no lockfile in this fresh temp project it
|
|
3373
3408
|
# floats to whatever pi published last — and that newer core then drags a NESTED
|
|
3374
3409
|
# pi-ai of its own. Measured 2026-07-21: pinning only the three we import left
|
|
3375
3410
|
# pi-agent-core@0.80.10 + pi-ai@0.80.10 in the tree while the gate still announced
|
|
@@ -3387,21 +3422,30 @@ _check_pack_install_impl() {
|
|
|
3387
3422
|
# `^0.84.x` carets on it), and upstream's 0.84.4 patch publish (2026-08-28
|
|
3388
3423
|
# 22:04Z) floated that caret in this lockfile-less temp install, turning CI
|
|
3389
3424
|
# red through the leak assertion below. That explicit pin is what held the
|
|
3390
|
-
# line until the bump lane ran; the
|
|
3391
|
-
#
|
|
3392
|
-
#
|
|
3393
|
-
#
|
|
3394
|
-
|
|
3425
|
+
# line until the bump lane ran; the pin moved WITH each verified floor rather
|
|
3426
|
+
# than being retired.
|
|
3427
|
+
#
|
|
3428
|
+
# At 0.85.1 the constellation GREW AGAIN, and this time outside the `pi-`
|
|
3429
|
+
# naming: `@earendil-works/chord` is new in 0.85.0 and is a runtime
|
|
3430
|
+
# `dependencies` entry of pi-coding-agent, pi-agent-core, pi-client and
|
|
3431
|
+
# pi-protocol (`[측정 2026-09-06]` `npm view <pkg>@0.85.1 dependencies`), so an
|
|
3432
|
+
# unpinned `^0.85.x` caret on it floats exactly like pi-agent-core did in 2026-07-21.
|
|
3433
|
+
# It is pinned here for the first time. The leak assertion below covers every
|
|
3434
|
+
# other @earendil-works package too — but only because the matcher's prefix was
|
|
3435
|
+
# widened from `pi-` to the org in the same change; before that it was blind to
|
|
3436
|
+
# precisely this class of new member. The verified floor is 0.85.1 as of 2026-09-06.
|
|
3437
|
+
echo "[check-pack-install] pnpm add into $tmp (with 0.85.x peers + chord + typebox)"
|
|
3395
3438
|
local install_log
|
|
3396
3439
|
install_log=$(cd "$tmp" && pnpm add \
|
|
3397
3440
|
"$tgz_path" \
|
|
3398
|
-
"@earendil-works/pi-ai@0.
|
|
3399
|
-
"@earendil-works/pi-coding-agent@0.
|
|
3400
|
-
"@earendil-works/pi-tui@0.
|
|
3401
|
-
"@earendil-works/pi-agent-core@0.
|
|
3402
|
-
"@earendil-works/pi-client@0.
|
|
3403
|
-
"@earendil-works/pi-protocol@0.
|
|
3404
|
-
"@earendil-works/pi-telemetry@0.
|
|
3441
|
+
"@earendil-works/pi-ai@0.85.1" \
|
|
3442
|
+
"@earendil-works/pi-coding-agent@0.85.1" \
|
|
3443
|
+
"@earendil-works/pi-tui@0.85.1" \
|
|
3444
|
+
"@earendil-works/pi-agent-core@0.85.1" \
|
|
3445
|
+
"@earendil-works/pi-client@0.85.1" \
|
|
3446
|
+
"@earendil-works/pi-protocol@0.85.1" \
|
|
3447
|
+
"@earendil-works/pi-telemetry@0.85.1" \
|
|
3448
|
+
"@earendil-works/chord@0.85.1" \
|
|
3405
3449
|
"typebox@latest" \
|
|
3406
3450
|
--ignore-workspace --ignore-scripts 2>&1) || {
|
|
3407
3451
|
fail "[check-pack-install] pnpm add failed:"
|
|
@@ -3410,18 +3454,18 @@ _check_pack_install_impl() {
|
|
|
3410
3454
|
}
|
|
3411
3455
|
|
|
3412
3456
|
# A pin is a wish until the resolved tree is read back. Assert it: EVERY
|
|
3413
|
-
# @earendil-works
|
|
3414
|
-
# must be the pinned 0.
|
|
3415
|
-
# rest of this gate would be exercising a runtime nobody verified,
|
|
3416
|
-
# printing "pinned pi 0.
|
|
3457
|
+
# @earendil-works package present — direct or transitive, top level or nested,
|
|
3458
|
+
# chord included — must be the pinned 0.85.1. Anything else means an unpinned caret
|
|
3459
|
+
# floated and the rest of this gate would be exercising a runtime nobody verified,
|
|
3460
|
+
# while still printing "pinned pi 0.85.1". Fail loud instead of proving the wrong floor.
|
|
3417
3461
|
local leaked_pi
|
|
3418
3462
|
leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
|
|
3419
3463
|
if [ -n "$leaked_pi" ]; then
|
|
3420
|
-
fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.
|
|
3464
|
+
fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.85.1):"
|
|
3421
3465
|
printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
|
|
3422
3466
|
return 1
|
|
3423
3467
|
fi
|
|
3424
|
-
echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works
|
|
3468
|
+
echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 0.85.1 (chord included)"
|
|
3425
3469
|
|
|
3426
3470
|
# Resolve the installed package.json and confirm pi.extensions
|
|
3427
3471
|
# arrived intact. If pi.extensions is empty or missing, the
|
|
@@ -3719,10 +3763,23 @@ sys.exit(0 if any(isinstance(s,str) and s.rstrip('/')== '$npm2_pkg' for s in src
|
|
|
3719
3763
|
# SAME installed candidate, on a FRESH sandbox home with every harness absent.
|
|
3720
3764
|
# This is the first living consumer of package-installed `entwurf setup`:
|
|
3721
3765
|
# mode=installed decided by name before anything else, NO pnpm bootstrap inside
|
|
3722
|
-
# node_modules, pi/claude/agy explicit zero-state SKIPs, stable bins
|
|
3723
|
-
# npm-provided, core bridge boundary PASS, computed green, zero
|
|
3724
|
-
# writes. Harness probes are pinned absent via the
|
|
3725
|
-
# COPILOT_BIN seams so the gate host's real
|
|
3766
|
+
# node_modules, pi/claude/agy/copilot/omp explicit zero-state SKIPs, stable bins
|
|
3767
|
+
# PASS as npm-provided, core bridge boundary PASS, computed green, zero
|
|
3768
|
+
# harness/auth writes. Harness probes are pinned absent via the FIVE
|
|
3769
|
+
# PI_BIN/CLAUDE_BIN/AGY_BIN/COPILOT_BIN/OMP_BIN seams so the gate host's real
|
|
3770
|
+
# harnesses never leak in.
|
|
3771
|
+
#
|
|
3772
|
+
# OMP_BIN was MISSING here from the 0.16.0 OMP admission until 2026-09-06, while
|
|
3773
|
+
# the two sibling fixtures (`smoke-setup-verdict.sh:70`,
|
|
3774
|
+
# `check-setup-qualification.sh:76`) both carried it. On a host that HAS omp on
|
|
3775
|
+
# PATH the row's own premise was false: setup detected omp, and the omp config /
|
|
3776
|
+
# receiver installers then refused — correctly — because this row also exports
|
|
3777
|
+
# `PI_CODING_AGENT_DIR`, which omp reads too, so the target directory is
|
|
3778
|
+
# ambiguous. Nothing was wrong with the product; the fixture was. It stayed
|
|
3779
|
+
# invisible because CI runners carry no omp AND `release_gate()` does not run
|
|
3780
|
+
# this gate (it is prepublishOnly + the CI install-surface job). The skip probe
|
|
3781
|
+
# below now requires FIVE zero-states, so a sixth harness cannot be admitted
|
|
3782
|
+
# while this seam is quietly left behind again.
|
|
3726
3783
|
local setup_home="$npm_tmp/setuphome" setup_proj="$npm_tmp/setupproj" setup_out setup_rc setup_auth
|
|
3727
3784
|
mkdir -p "$setup_home/.pi/agent" "$setup_proj"
|
|
3728
3785
|
setup_auth="$setup_home/.pi/agent/auth.json"
|
|
@@ -3733,7 +3790,7 @@ sys.exit(0 if any(isinstance(s,str) and s.rstrip('/')== '$npm2_pkg' for s in src
|
|
|
3733
3790
|
setup_out=$(HOME="$setup_home" XDG_DATA_HOME="$setup_home/.local/share" XDG_STATE_HOME="$setup_home/.local/state" \
|
|
3734
3791
|
XDG_CACHE_HOME="$setup_home/.cache" XDG_CONFIG_HOME="$setup_home/.config" PI_CODING_AGENT_DIR="$setup_home/.pi/agent" \
|
|
3735
3792
|
PI_BIN="$npm_tmp/definitely-absent" CLAUDE_BIN="$npm_tmp/definitely-absent" AGY_BIN="$npm_tmp/definitely-absent" \
|
|
3736
|
-
COPILOT_BIN="$npm_tmp/definitely-absent" \
|
|
3793
|
+
COPILOT_BIN="$npm_tmp/definitely-absent" OMP_BIN="$npm_tmp/definitely-absent" \
|
|
3737
3794
|
"$npmroot/node_modules/.bin/entwurf" setup "$setup_proj" 2>&1)
|
|
3738
3795
|
setup_rc=$?
|
|
3739
3796
|
set -e
|
|
@@ -3753,7 +3810,7 @@ sys.exit(0 if any(isinstance(s,str) and s.rstrip('/')== '$npm2_pkg' for s in src
|
|
|
3753
3810
|
return 1
|
|
3754
3811
|
fi
|
|
3755
3812
|
local skip_probe
|
|
3756
|
-
for skip_probe in "pi: SKIP" "claude: SKIP" "agy: SKIP" "copilot: SKIP"; do
|
|
3813
|
+
for skip_probe in "pi: SKIP" "claude: SKIP" "agy: SKIP" "copilot: SKIP" "omp: SKIP"; do
|
|
3757
3814
|
if ! grep -q "$skip_probe" <<<"$setup_out"; then
|
|
3758
3815
|
fail "[check-pack-install] installed all-absent setup missing explicit zero-state '$skip_probe':"
|
|
3759
3816
|
echo "$setup_out" | tail -25 | sed 's/^/ /' >&2
|
|
@@ -3778,7 +3835,7 @@ sys.exit(0 if any(isinstance(s,str) and s.rstrip('/')== '$npm2_pkg' for s in src
|
|
|
3778
3835
|
fail "[check-pack-install] installed setup touched the credential store (byte drift or .bak)"
|
|
3779
3836
|
return 1
|
|
3780
3837
|
fi
|
|
3781
|
-
echo "[check-pack-install] installed all-absent setup pass (mode-first, no bootstrap,
|
|
3838
|
+
echo "[check-pack-install] installed all-absent setup pass (mode-first, no bootstrap, 5x SKIP, bins npm-provided, core PASS, computed green, zero writes)"
|
|
3782
3839
|
|
|
3783
3840
|
# Installed copilot-present aggregate `setup` row (#86 C3b): the SAME consumer
|
|
3784
3841
|
# bin on a fresh sandbox home, with the shared fake vendor
|
|
@@ -3804,6 +3861,7 @@ sys.exit(0 if any(isinstance(s,str) and s.rstrip('/')== '$npm2_pkg' for s in src
|
|
|
3804
3861
|
setup_out=$(HOME="$cop_setup_home" XDG_DATA_HOME="$cop_setup_home/.local/share" XDG_STATE_HOME="$cop_setup_home/.local/state" \
|
|
3805
3862
|
XDG_CACHE_HOME="$cop_setup_home/.cache" XDG_CONFIG_HOME="$cop_setup_home/.config" PI_CODING_AGENT_DIR="$cop_setup_home/.pi/agent" \
|
|
3806
3863
|
PI_BIN="$npm_tmp/definitely-absent" CLAUDE_BIN="$npm_tmp/definitely-absent" AGY_BIN="$npm_tmp/definitely-absent" \
|
|
3864
|
+
OMP_BIN="$npm_tmp/definitely-absent" \
|
|
3807
3865
|
COPILOT_BIN="$cop_fake/copilot" PATH="$cop_fake:$npmroot/node_modules/.bin:$PATH" \
|
|
3808
3866
|
"$npmroot/node_modules/.bin/entwurf" setup "$cop_setup_proj" 2>&1)
|
|
3809
3867
|
setup_rc=$?
|
|
@@ -5458,10 +5516,11 @@ release_gate() {
|
|
|
5458
5516
|
results+=("FAIL static (pnpm run check:full)"); failc=$((failc + 1))
|
|
5459
5517
|
fi
|
|
5460
5518
|
|
|
5461
|
-
# 1b. Discriminating power of that floor.
|
|
5462
|
-
# default check chains (operator inner-loop cost)
|
|
5463
|
-
#
|
|
5464
|
-
#
|
|
5519
|
+
# 1b. Discriminating power of that floor. The mutant-EXECUTING body left the
|
|
5520
|
+
# default check chains (operator inner-loop cost) — only its head rides along,
|
|
5521
|
+
# as check-gate-manifests inside check:hermetic — so release acceptance carries
|
|
5522
|
+
# the body explicitly as its own MUST step: a cut must re-prove the gates still
|
|
5523
|
+
# kill what they claim to kill.
|
|
5465
5524
|
section "release-gate step: check-gate-qualification"
|
|
5466
5525
|
if (cd "$REPO_DIR" && bash "$self" check-gate-qualification); then
|
|
5467
5526
|
ok "check-gate-qualification: PASS"
|
|
@@ -6036,6 +6095,16 @@ case "$cmd" in
|
|
|
6036
6095
|
# per cell; offline + deterministic (deps: python3).
|
|
6037
6096
|
(cd "$REPO_DIR" && python3 scripts/check-agy-permission-matrix.py)
|
|
6038
6097
|
;;
|
|
6098
|
+
check-gate-manifests)
|
|
6099
|
+
# The HEAD of check-gate-qualification, exposed on its own so the deterministic
|
|
6100
|
+
# floor can pay for it: the runner self-test (classifier truth table + synthetic
|
|
6101
|
+
# negatives) plus the REAL manifests — schema, global claim uniqueness, subjects
|
|
6102
|
+
# tracked and lstat-regular in the origin index, claim tokens exactly once in their
|
|
6103
|
+
# gate sources — plus the declared lane inventory. It executes ZERO mutants and
|
|
6104
|
+
# makes NO snapshot of this repo; the body (check-gate-qualification) owns that and
|
|
6105
|
+
# is unchanged. Cheap enough for check:hermetic (~8s).
|
|
6106
|
+
run_ts scripts/check-gate-qualification.ts --manifests-only
|
|
6107
|
+
;;
|
|
6039
6108
|
check-gate-qualification)
|
|
6040
6109
|
# Kill-proof qualification — the gate-of-gates. Proves the committed defect mutants
|
|
6041
6110
|
# (scripts/mutants/*.json) make their gates fail BOUNDED and FOR THE CLAIMED REASON
|
|
@@ -72,7 +72,7 @@ interface WireUsage {
|
|
|
72
72
|
* One `_meta.quota.token_count` row. The field NAMES deliberately differ from
|
|
73
73
|
* `PromptResponse.usage`: cache reads are `cachedInputTokens` here because the
|
|
74
74
|
* shape is shared with codex-acp, and `cachedWriteTokens` is Claude's extra
|
|
75
|
-
* sibling (read at claude-agent-acp 0.
|
|
75
|
+
* sibling (read at claude-agent-acp 0.75.1 `dist/acp-agent.js:6493-6502`).
|
|
76
76
|
* Reading a quota row with the `usage` field names silently yields zeros, so the
|
|
77
77
|
* fixture below spells the vendor's names out rather than reusing `WireUsage`.
|
|
78
78
|
*/
|
|
@@ -137,7 +137,7 @@ function makeFakeChild() {
|
|
|
137
137
|
*
|
|
138
138
|
* Each `prompt` call consumes the next TurnScript: it first pushes that turn's
|
|
139
139
|
* `usage_update` notification (the wire the running cost total actually arrives
|
|
140
|
-
* on — read at claude-agent-acp 0.
|
|
140
|
+
* on — read at claude-agent-acp 0.75.1 `dist/acp-agent.js:3467-3482`), then answers the
|
|
141
141
|
* prompt with that turn's `PromptResponse.usage` (the wire the turn aggregate
|
|
142
142
|
* arrives on). Both orderings are the real one: the notification precedes the
|
|
143
143
|
* response, because the SDK emits it from the `result` message that ENDS the turn.
|
|
@@ -393,8 +393,8 @@ try {
|
|
|
393
393
|
// AGENT LOOP only. `_meta.quota.model_usage` comes from `result.modelUsage` and
|
|
394
394
|
// also counts Task subagents, sidechains and INTERNAL CALLS SUCH AS COMPACTION;
|
|
395
395
|
// the vendor states its rows "can total more than `token_count`" and are "the
|
|
396
|
-
// fuller picture, not a decomposition of it" (read at claude-agent-acp 0.
|
|
397
|
-
// `dist/acp-agent.js:
|
|
396
|
+
// fuller picture, not a decomposition of it" (read at claude-agent-acp 0.75.1
|
|
397
|
+
// `dist/acp-agent.js:6468-6474`).
|
|
398
398
|
//
|
|
399
399
|
// The wide one is required, not merely nicer, because the DENOMINATOR already
|
|
400
400
|
// has that scope: turn cost is the adjacent diff of the backend's running total,
|
|
@@ -608,8 +608,8 @@ try {
|
|
|
608
608
|
// identity that produces the bound is a property of the MAIN AGENT LOOP's
|
|
609
609
|
// cache breakpoints. `_meta.quota.model_usage` is a WIDER scope: the vendor
|
|
610
610
|
// states those rows also count Task subagents, sidechains, and INTERNAL
|
|
611
|
-
// CALLS SUCH AS COMPACTION (read at claude-agent-acp 0.
|
|
612
|
-
// `dist/acp-agent.js:
|
|
611
|
+
// CALLS SUCH AS COMPACTION (read at claude-agent-acp 0.75.1
|
|
612
|
+
// `dist/acp-agent.js:6465-6485`). Mixing the two scopes inflates the bound
|
|
613
613
|
// through both remaining terms that mention cacheWrite:
|
|
614
614
|
// max(0, occupancy − cacheWrite) shrinks as wide cacheWrite grows, so
|
|
615
615
|
// less is subtracted; min(rawBound, cacheWrite) rises with it.
|
|
@@ -749,9 +749,9 @@ try {
|
|
|
749
749
|
// CELL 3 — a turn with NO cost notification holds the baseline.
|
|
750
750
|
//
|
|
751
751
|
// Measured upstream: the result-path `usage_update` carries cost (read at
|
|
752
|
-
// claude-agent-acp 0.
|
|
752
|
+
// claude-agent-acp 0.75.1 `dist/acp-agent.js:3467-3482`), while other
|
|
753
753
|
// `usage_update` paths can carry `used` without cost (for example the
|
|
754
|
-
// rate-limit path at `:
|
|
754
|
+
// rate-limit path at `:4269-4277`). A live thinkpad ledger shows such turns
|
|
755
755
|
// really occur. The honest handling is to HOLD the baseline so the amount lands
|
|
756
756
|
// in the NEXT diff: misattributed by turn, exact by session. Rebaselining to 0
|
|
757
757
|
// there would double-count the whole prefix.
|
|
@@ -831,7 +831,7 @@ try {
|
|
|
831
831
|
// CELL 4 — a DECREASING session total is never silently absorbed.
|
|
832
832
|
//
|
|
833
833
|
// `conversation_reset` switches the session to a fresh transcript (read at
|
|
834
|
-
// claude-agent-acp 0.
|
|
834
|
+
// claude-agent-acp 0.75.1 `dist/acp-agent.js:4282-4289`), but whether that
|
|
835
835
|
// changes `total_cost_usd` is an SDK-internal value we cannot observe here.
|
|
836
836
|
// A diff can therefore go negative in a session we are still holding.
|
|
837
837
|
// Absorbing it quietly would both misreport the turn and destroy the only
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* check-gate-qualification — kill-proof qualification of the shipped gates.
|
|
3
3
|
*
|
|
4
|
+
* TWO ENTRYPOINTS, ONE FILE. `--manifests-only` (shipped as `run.sh
|
|
5
|
+
* check-gate-manifests`) stops after the HEAD — the runner self-test plus the real
|
|
6
|
+
* manifests' validation and lane inventory — having executed ZERO mutants from
|
|
7
|
+
* `scripts/mutants/` and having made NO snapshot of this repo. The default
|
|
8
|
+
* entrypoint is unchanged: it runs the same head first and then the body. The head
|
|
9
|
+
* is separable because of what it has actually caught: across 549 CI runs the
|
|
10
|
+
* qualification step went red five times, and three of those died in the head in
|
|
11
|
+
* 4-5 seconds, before a single mutant gate was invoked (#99 B-3). The head costs
|
|
12
|
+
* ~8s, so it belongs in the deterministic floor every push already pays for; the
|
|
13
|
+
* body's contract — which mutants run, how they are classified, what it prints —
|
|
14
|
+
* is untouched by the split.
|
|
15
|
+
*
|
|
4
16
|
* Two phases, in a fixed order:
|
|
5
17
|
*
|
|
6
18
|
* 1. RUNNER SELF-TEST. The runner is itself a SUT: a qualification harness that
|
|
@@ -58,6 +70,10 @@ import {
|
|
|
58
70
|
const REPO_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
59
71
|
const MUTANTS_DIR = path.join(REPO_DIR, "scripts", "mutants");
|
|
60
72
|
|
|
73
|
+
/** Head-only mode: validate, count, and stop — no mutant is executed, no repo snapshot is made. */
|
|
74
|
+
const MANIFESTS_ONLY = process.argv.includes("--manifests-only");
|
|
75
|
+
const SURFACE = MANIFESTS_ONLY ? "check-gate-manifests" : "gate-qualification";
|
|
76
|
+
|
|
61
77
|
let passed = 0;
|
|
62
78
|
function ok(label: string, cond: boolean): void {
|
|
63
79
|
assert.ok(cond, label);
|
|
@@ -775,10 +791,16 @@ const quiet = (): void => {};
|
|
|
775
791
|
}
|
|
776
792
|
}
|
|
777
793
|
|
|
778
|
-
console.log(`\n[
|
|
794
|
+
console.log(`\n[${SURFACE}] self-test: ${passed} checks passed`);
|
|
779
795
|
|
|
780
|
-
// ═══ Phase
|
|
796
|
+
// ═══ Phase 1d — the real manifests, VALIDATED but not yet run ═══════════════
|
|
797
|
+
//
|
|
798
|
+
// The HEAD ends here. Everything above and in this block is pure reading and
|
|
799
|
+
// checking: no mutant from `scripts/mutants/` is executed and no snapshot of THIS
|
|
800
|
+
// repo exists yet. `--manifests-only` returns from exactly this point.
|
|
781
801
|
|
|
802
|
+
let selected: MutantSpec[];
|
|
803
|
+
let manifestCount: number;
|
|
782
804
|
{
|
|
783
805
|
const manifests: MutantManifest[] = fs
|
|
784
806
|
.readdirSync(MUTANTS_DIR)
|
|
@@ -813,6 +835,7 @@ console.log(`\n[gate-qualification] self-test: ${passed} checks passed`);
|
|
|
813
835
|
"copilot-launch": 14,
|
|
814
836
|
"copilot-receive": 18,
|
|
815
837
|
"fresh-cut": 1,
|
|
838
|
+
"gate-qualification": 2,
|
|
816
839
|
"meta-facts": 4,
|
|
817
840
|
"meta-hook-session-switch": 17,
|
|
818
841
|
"meta-identity": 4,
|
|
@@ -821,14 +844,14 @@ console.log(`\n[gate-qualification] self-test: ${passed} checks passed`);
|
|
|
821
844
|
"mux-fresh-call": 37,
|
|
822
845
|
"mux-launcher-fence": 7,
|
|
823
846
|
"mux-parent-artifact": 3,
|
|
824
|
-
"pack-install":
|
|
847
|
+
"pack-install": 2,
|
|
825
848
|
"pi-package-ownership": 6,
|
|
826
849
|
"mux-resume-call": 12,
|
|
827
850
|
"omp-birth": 11,
|
|
828
851
|
"omp-fresh": 24,
|
|
829
852
|
"omp-receive": 11,
|
|
830
853
|
"probe-ordering": 1,
|
|
831
|
-
"release-gate":
|
|
854
|
+
"release-gate": 14,
|
|
832
855
|
"resume-args": 6,
|
|
833
856
|
"resume-launch-identity": 6,
|
|
834
857
|
"self-address": 5,
|
|
@@ -839,17 +862,44 @@ console.log(`\n[gate-qualification] self-test: ${passed} checks passed`);
|
|
|
839
862
|
};
|
|
840
863
|
const laneTally: Record<string, number> = {};
|
|
841
864
|
for (const man of manifests) laneTally[man.lane] = (laneTally[man.lane] ?? 0) + man.mutants.length;
|
|
842
|
-
|
|
843
|
-
laneTally,
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
865
|
+
try {
|
|
866
|
+
assert.deepEqual(laneTally, EXPECTED_LANE_MUTANTS);
|
|
867
|
+
} catch {
|
|
868
|
+
assert.fail(
|
|
869
|
+
"[QK:LANE-INVENTORY-DECLARED] the mutant lane inventory drifted from the declared contract — extend " +
|
|
870
|
+
"EXPECTED_LANE_MUTANTS and the manifests together, never silently. A lane that lost its manifest in a " +
|
|
871
|
+
"merge, or gained mutants nobody declared, would otherwise pass as `n/n killed` over whatever set " +
|
|
872
|
+
`happened to be on disk. declared=${JSON.stringify(EXPECTED_LANE_MUTANTS)} onDisk=${JSON.stringify(laneTally)}`,
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
try {
|
|
876
|
+
selected = validateManifestSet(manifests, makeOriginChecks(REPO_DIR));
|
|
877
|
+
} catch (err) {
|
|
878
|
+
assert.fail(
|
|
879
|
+
"[QK:MANIFEST-SET-INTEGRITY-REFUSED] the committed manifest set broke its cross-manifest contract (global " +
|
|
880
|
+
"claim uniqueness, subject tracked + lstat-regular in the ORIGIN index, signatureSource on the origin work " +
|
|
881
|
+
"surface, claim token present exactly once in its gate source). Every one of those is what makes a KILLED " +
|
|
882
|
+
"verdict mean something, so the set is refused rather than run. Refusal: " +
|
|
883
|
+
(err instanceof Error ? err.message : String(err)),
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
manifestCount = manifests.length;
|
|
887
|
+
console.log(`[${SURFACE}] ${selected.length} mutants across ${manifestCount} lanes (no tiers — full set every run)`);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (MANIFESTS_ONLY) {
|
|
849
891
|
console.log(
|
|
850
|
-
`[gate-
|
|
892
|
+
`[check-gate-manifests] ok — runner self-test green, ${selected.length} committed mutants across ` +
|
|
893
|
+
`${manifestCount} lanes validated against the origin index, and the lane inventory matches its declared ` +
|
|
894
|
+
"contract. ZERO mutants were executed and this repo was never snapshotted: the body " +
|
|
895
|
+
"(check-gate-qualification) owns that, unchanged.",
|
|
851
896
|
);
|
|
897
|
+
process.exit(0);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// ═══ Phase 2 — the real manifests against a snapshot of THIS repo ═══════════
|
|
852
901
|
|
|
902
|
+
{
|
|
853
903
|
const headBefore = originHead(REPO_DIR);
|
|
854
904
|
const workSurfaceBefore = originWorkSurfaceSha(REPO_DIR);
|
|
855
905
|
|