@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.
@@ -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.73.0 `dist/acp-agent.js:3273-3297`). Until
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.73.0
346
- // `dist/acp-agent.js:2918-2924`). Both are measured, so backend.ts seals them.
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.73.0 `dist/acp-agent.js:5728-5748`). The narrower
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.73.0 `dist/acp-agent.js:3273-3297`) — #96.
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.73.0 `dist/acp-agent.js:3675-3682`), while claude-agent-sdk
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.73.0
64
- * `dist/acp-agent.js:2918-2933`), each carrying that result's current values.
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.73.0
350
- // `dist/acp-agent.js:3290-3297`), after constructing that scalar from the
351
- // latest assistant snapshot (`:3273-3289`). pi reads `usage.totalTokens` as
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 (version boundary: @0.84.40 must leak, @0.84.4 bare/peer-hash must pass); snapshot-safe qualification oracle, also run first inside check-pack-install
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.85+ at the current 0.84.4 pin) whose
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.84.4` an existing 0.84.3 host is upgraded, not kept.
1854
- // Expected shape: `>=<devDep> <0.<minor+1>` (e.g. `>=0.84.4 <0.85`).
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
- const BASELINE_DOCS = ['AGENTS.md', 'README.md', 'ROADMAP.md', 'docs/setup-clean-host.md', 'demo/README.md'];
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. The version BOUNDARY is load-bearing: a pnpm .pnpm entry is
3118
- # `<name>@<version>` followed by either `_<peer-hash>` or end-of-name (measured pnpm 11.20.0
3119
- # on this tree), so an unbounded substring match would bless a lookalike such as `@0.84.40`
3120
- # while announcing the pinned floor a false-green oracle (found by independent review,
3121
- # 2026-08-25).
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+pi-' | grep -Ev '@0\.84\.4(_|$)' || true
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 the
3127
- # false-green shape (it contains no 0.84.40), so the oracle is proven against a fixture
3128
- # listing. Expected: the two lookalikes leak, the pinned version passes bare and with a
3129
- # peer-hash suffix. Exposed as its own snapshot-safe subcommand because the heavy
3130
- # check-pack-install cannot run inside the qualification snapshot (no install environment),
3131
- # and a kill-proof needs a control-green gate there; check-pack-install still runs this
3132
- # first so the heavy gate cannot proceed on a broken oracle.
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+pi-ai@0.84.4' \
3137
- '@earendil-works+pi-ai@0.84.4_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
3138
- '@earendil-works+pi-ai@0.84.40' \
3139
- '@earendil-works+pi-agent-core@0.84.3' | pack_install_leaked_pi)
3140
- if [ "$matcher_probe" != '@earendil-works+pi-ai@0.84.40
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
- echo "[check-pack-pin-matcher] ok — the pin-leak matcher is version-bounded (lookalikes leak, pinned version passes bare and with a peer-hash)"
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.84.x`), so with no lockfile in this fresh temp project it
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 verified floor is 0.84.4 as of
3391
- # 2026-09-01, and the pin moved WITH it rather than being retired. The leak
3392
- # assertion below still covers every other pi package, including any package
3393
- # a future pi bump adds to the closure.
3394
- echo "[check-pack-install] pnpm add into $tmp (with 0.84.x peers + typebox)"
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.84.4" \
3399
- "@earendil-works/pi-coding-agent@0.84.4" \
3400
- "@earendil-works/pi-tui@0.84.4" \
3401
- "@earendil-works/pi-agent-core@0.84.4" \
3402
- "@earendil-works/pi-client@0.84.4" \
3403
- "@earendil-works/pi-protocol@0.84.4" \
3404
- "@earendil-works/pi-telemetry@0.84.4" \
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 pi package present — direct or transitive, top level or nested
3414
- # must be the pinned 0.84.4. Anything else means an unpinned caret floated and the
3415
- # rest of this gate would be exercising a runtime nobody verified, while still
3416
- # printing "pinned pi 0.84.4". Fail loud instead of proving the wrong floor.
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.84.4):"
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 pi package is 0.84.4"
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 PASS as
3723
- # npm-provided, core bridge boundary PASS, computed green, zero harness/auth
3724
- # writes. Harness probes are pinned absent via the PI_BIN/CLAUDE_BIN/AGY_BIN/
3725
- # COPILOT_BIN seams so the gate host's real harnesses never leak in.
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, 4x SKIP, bins npm-provided, core PASS, computed green, zero writes)"
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. check-gate-qualification left the
5462
- # default check chains (operator inner-loop cost), so release acceptance
5463
- # carries it explicitly as its own MUST step: a cut must re-prove the gates
5464
- # still kill what they claim to kill.
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.73.0 `dist/acp-agent.js:5750-5765`).
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.73.0 `dist/acp-agent.js:2918-2933`), then answers the
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.73.0
397
- // `dist/acp-agent.js:5732-5738`).
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.73.0
612
- // `dist/acp-agent.js:5728-5748`). Mixing the two scopes inflates the bound
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.73.0 `dist/acp-agent.js:2913-2924`), while other
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 `:3661-3671`). A live thinkpad ledger shows such turns
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.73.0 `dist/acp-agent.js:3675-3682`), but whether that
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[gate-qualification] self-test: ${passed} checks passed`);
794
+ console.log(`\n[${SURFACE}] self-test: ${passed} checks passed`);
779
795
 
780
- // ═══ Phase 2 — the real manifests against a snapshot of THIS repo ═══════════
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": 1,
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": 12,
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
- assert.deepEqual(
843
- laneTally,
844
- EXPECTED_LANE_MUTANTS,
845
- "mutant lane inventory drifted from the declared contract — extend EXPECTED_LANE_MUTANTS and the manifests together, never silently",
846
- );
847
- const allMutants = validateManifestSet(manifests, makeOriginChecks(REPO_DIR));
848
- const selected = allMutants;
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-qualification] ${selected.length} mutants across ${manifests.length} lanes (no tiers full set every run)`,
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