diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fc6e00..9b3940a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -166,10 +166,89 @@ measured against both files rather than read off the diff (`new=1/old=0` and `new=0/old=1`), then the canary body was **executed** against each: new → `rc=0 ok`, old → `rc=1` empty. -Still outstanding, and not fixable from this device: `isWithdrawn` has 17 -assertions and 4 independent mutation kills, but has **never been exercised on a -released image against the live logstream**. It is deployed and unproven — a -different state from untested, and a worse one to leave unlabelled. +**`isWithdrawn` is no longer deployed-and-unproven — and the suite that pins it +had been dark since the node 24 bump.** The rule was exercised on the released +image against the live logstream on 2026-09-14, from a container recreated onto +v1.9.1 (born 13:21:41Z, confirmed by entrypoint-written mtimes and docker-written +`/etc` files agreeing to the second; `/proc/uptime` and `ps -o lstart=` were not +used, per their retraction). Baked toolkit `e68ee20`, `grep -c isWithdrawn` = 3, +mempalace.ts sha256 `7c16fe14…`, and the file pi actually loads verified to be +that same path and hash rather than a stale copy. + +The instrument matters as much as the result: the shipped extractor was lifted +out of `scripts/test-owed-withdrawal.sh` and used to cut `TERMINAL_STATUS`, +`isStrictlyAfter`, `isAnswered` and `isWithdrawn` out of the **baked** +mempalace.ts by brace matching, then `deriveOwed`'s three queries were replayed +with their exact shipped parameters over the same `/mcp` transport the extension +uses. Shipped bytes, live data, no second copy of the logic. Baseline owed = 1, +agreed by three independent routes (the extension's own wake-up card; a hand +derivation of 23 candidates; the shipped predicates). Every expectation was +recorded before its measurement: + +| probe | predicted | observed | +|---|---|---| +| positive control planted | owed 1 → 2 | 2 | +| requester withdraws it | back to 1 | 1, and `isAnswered=false isWithdrawn=true` | +| **third party** retracts someone else's ask | no effect | still owed | +| requester withdraws in **prose**, no marker | no effect | still owed | +| cleanup by ordinary replies | owed == baseline | 1, then 0 | + +The count returning to baseline is only arithmetic; the per-predicate verdict is +what makes it a statement about mechanism. Probe A remained a raw candidate +throughout and no terminal reply of ours existed on its correlation, so its +removal is attributable to the withdrawal rule alone. Final attribution: A +cleared by `isWithdrawn` only, the two negative controls cleared by `isAnswered` +only — each probe retired by the predicate it was built to exercise. Both marker +spellings now have live witnesses (`metadata.withdraws` naming the ask's event id, +and naming its correlation). Unplanned and worth more than the probes: against +live data the rule also fires on the incident it was written for — seq 112 is +reported withdrawn-by-requester, i.e. mbp-m1-2020's seq 119 withdrawal now works, +so the 41h false obligation cannot recur. Full record in the coordination log at +`project/pi-devbox` seq 140; harness preserved as artifact +`art_20260914T141704_a7a9a294a4bb`. + +Not proven, and deliberately not claimed: the extension's **own in-process +mailbox poll** surfacing a withdrawn ask. That poll fires at `agent_settled` when +the agent is idle; two probes were left owed across the rest of the session to +give it a window and it did not fire. Calling that confirmed would be a claim +about the session's patience, not about the code. + +**Toolkit pickup `dab989b` — the owed-set suite could not run on this image, and +its own gate was why.** Reaching for the suite as corroboration exposed a second +defect: its precondition line `node --experimental-strip-types --check "$SRC"` +exits 2 on an unmodified mempalace.ts under node 24, and with `set -euo pipefail` +that skipped all 17 assertions and both regression guards. The cause is narrower +than the error suggests — it names the inline type-import, but `node --check` does +not type-strip at all: a file containing only `const x: number = 1` fails +identically, while executing the same file works. So the gate could never +validate TypeScript on any node; v1.9.1's bump from v22.23.2 to v24.21.0 is the +most likely trigger, though with no node 22 on the box that half stays labelled +inference rather than measurement. It failed **closed** — loud exit 2, never +vacuously green — which is the good direction, and the reason it went unnoticed is +that nothing in CI runs this suite. + +That matters more than a red test, because this suite is the only thing that makes +`isWithdrawn`'s failure mode visible: a wrong rule there does not throw and does +not log, it makes a real unanswered ask vanish from a mailbox forever. `dab989b` +strips first and syntax-checks the emitted JS, and splits the exit codes so that +`3` means the gate cannot run while `2` means the source does not parse — +collapsing those is how the defect disguised itself as "mempalace.ts does not +parse" while mempalace.ts was fine. Verified in six directions with expectations +written first: clean source `rc=0` 17/17; malformed TypeScript `rc=2`; stripper +made unavailable `rc=3` without the misleading message; and the four mutation +kills back at e2b060a's counts of 3/1/2/1, so sensitivity is restored rather than +asserted. + +> **Cost, and it is smaller than it looks:** `MEMPALACE_TOOLKIT_REF` is resolved +> by CI to the head of the toolkit's `main` and folded into the `base_tag` hash — +> verified at `docker-publish.yml:126-128`, whose own comment gives the reason +> ("otherwise a toolkit-only fix never lands"). So `dab989b` moves `base_tag` and +> the next tag rebuilds the base, with nothing to remember to trigger. But it adds +> no rebuild that was not already owed: `9aaff26` refreshed the vendored skill +> snapshot under `rootfs/`, which is also hashed into `base_tag`, so a base +> rebuild has been pending since before this fix existed. The toolkit pickup rides +> along with it, and the same rebuild is what finally bakes skillset `e9e45f7` and +> turns the snapshot canary above green against the image's own floor. ---