From 6df969d5c4217c7f87e508d84e833e0d0cdf231d Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Fri, 4 Sep 2026 16:35:09 -0500 Subject: [PATCH 1/7] docs(webconsole): rewrite the seam-refresh procedure onto the derived digest (BACKLOG #1443) The "Bumping the seam" procedure still described the pre-#1220 world, where ENGINE_UI_SEAM was a hand-picked incrementing integer over five hand-maintained tuples. Three of its seven steps had no meaning left, and the steps could not be executed in any order: a derived digest does not exist until the change is made and regenerated, so old step 2 asked the reader to write a value old step 5 had not yet computed. Steps 2 and 4 are gone. Nobody picks the value and there is no curated list to update. Step 5's shell redirect is replaced by --write, which rewrites the constant and the golden together. The redirect turned out to be wrong in every shell, not only under PowerShell: a redirect writes one of the two files --write writes, so it leaves the constant stale even where the encoding survives. Measured on Windows: PowerShell 5.1 emits UTF-16LE with a BOM, which the test cannot decode as UTF-8 at all; pwsh 7 emits CRLF, which diffs every line of a golden written with \n; Git Bash writes a clean file and silently does half the job. The fence was tagged bash, which advertised that last case. The console-side edit stays by hand, and the page now points at --write's own output for its literal text rather than restating it (SDS-3.5). The page keeps the reasoning the tool does not print: one value per #279, a test that reds CI on the same commit, and what re-widening the set would cost. Fixing only the numbered list would have left the page contradicting itself two screens earlier, so the same pass corrects the section that defines the term: the heading called the seam an integer, the prose typed it int and the console's set frozenset[int], and the implementation list said "three files" while describing a curated surface. It is four files with seam_discovery.py, the surface is discovered, and the generator has captured enum members and Literal value sets since #1220 -- an omission that matters, because the console indexes _SCOPE_NOTES[data.scope], so a renamed literal KeyErrors at runtime while a field-name-only snapshot stays byte-identical. Found 2026-09-03 while building #1439, which deliberately left it alone so a sys.path fix and a documentation rewrite stayed separately reviewable. That item's closing paragraph is updated to cite this one. No engine effect and no deployment axis (CLAUDE.md sec. 0): this is a developer procedure for a repository gate. The cost it removes is a corrupted golden and then debugging the gate that reported it. Verification: 8 passed in tests/test_webconsole_seam_snapshot.py; the derived digest 266cbfd342b22819 matches both ENGINE_UI_SEAM and SUPPORTED_ENGINE_SEAMS; backlog_status_check parses #1443 as open; link_check resolves 4839 links across 284 files under docs/. Co-Authored-By: Claude Opus 5 --- docs/WEBCONSOLE-PACKAGE.md | 114 ++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 39 deletions(-) diff --git a/docs/WEBCONSOLE-PACKAGE.md b/docs/WEBCONSOLE-PACKAGE.md index 5782a24e0..3c908363d 100644 --- a/docs/WEBCONSOLE-PACKAGE.md +++ b/docs/WEBCONSOLE-PACKAGE.md @@ -56,10 +56,14 @@ console package, so `import messagefoundry.api.app` still succeeds with the cons (`add_auth_routes` runs unconditionally and returns an `AdminHandlers` built from this leaf, so its concrete type must live engine-side). -### `ENGINE_UI_SEAM` — the handshake integer - -`api/_ui_seam.ENGINE_UI_SEAM: int` is the contract version the engine ships — **read the current value from that module**; it is deliberately not restated here, because a number quoted in prose goes stale silently (this line said "currently 1" until seam 11). The -console declares `messagefoundry_webconsole.SUPPORTED_ENGINE_SEAMS: frozenset[int]` and refuses a skew +### `ENGINE_UI_SEAM` — the handshake digest + +`api/_ui_seam.ENGINE_UI_SEAM: str` is the contract identity the engine ships: a 16-hex-character +SHA-256 digest of the contract surface, **derived and never chosen** (BACKLOG #1220). **Read the +current value from that module**; it is deliberately not restated here, because a value quoted in +prose goes stale silently (this line said "currently 1" back when the seam was a hand-picked +integer). The console declares `messagefoundry_webconsole.SUPPORTED_ENGINE_SEAMS: frozenset[str]` +and refuses a skew at startup via `assert_engine_seam(engine_seam)`, which raises `UiSeamMismatch` with a clear message rather than a raw `TypeError`. The handshake is **three-layered** and fails loud at every layer: @@ -70,7 +74,7 @@ rather than a raw `TypeError`. The handshake is **three-layered** and fails loud surfaces as `UiSeamMismatch`, not a kwargs `TypeError`. A second identical assert at the top of `mount_ui` is belt-and-suspenders. 3. **CI** — the package suite runs against the supported engine seam(s); the engine repo's snapshot gate - (below) fails on an unbumped incompatible change. + (below) fails on an incompatible change the seam did not follow. ### `mount_ui(app, deps)` and the injected bundle @@ -91,8 +95,8 @@ Handler fields are typed `Callable[..., Awaitable[Any]]` (engine/gate params ins The auth dep factories — `require`, `require_step_up`, `require_reauth_only`, `get_auth`, `authorize_ws`, `ws_token` — are **not** injected; the console imports them directly from `messagefoundry.api.security` -(leaf-safe). Their public surface is part of the seam's compat scope even so (a re-signature there is -seam-bumping) and is captured by the snapshot gate. +(leaf-safe). Their public surface is part of the seam's compat scope even so (a re-signature there +moves the seam) and is captured by the snapshot gate. ### The `app.state` hooks @@ -116,56 +120,88 @@ Independent versioning is exactly what makes runtime skew possible, so the engin **contract-snapshot gate**. Its purpose: a silent, incompatible change to the injected contract — a renamed handler field, a re-signatured `api.security` dep or `AuthService` method, or a renamed field on a **DTO the console renders** (which breaks render, not import, so `mypy` alone misses it) — must fail -CI until `ENGINE_UI_SEAM` is bumped. +CI until `ENGINE_UI_SEAM` is regenerated. + +**The surface is DISCOVERED, not enumerated** (BACKLOG #1220). It used to be five hand-maintained +tuples in the generator, and three of them had drifted: a list like that cannot detect its own +omissions, because the gate's coverage *is* the list. Nothing below asks you to keep one current. -Three files implement it: +Four files implement it: +- [`scripts/seam_discovery.py`](../scripts/seam_discovery.py) — walks the console's own imports and + uses to derive the surface it depends on: the `api.security` deps, the `AuthService` members + (methods **and** properties), the `app.state` attributes, and the DTOs it renders, closed over + nested models. An idiom the walk cannot resolve exactly raises `SeamDiscoveryError` rather than + being skipped, so a new blind spot is loud instead of silent. - [`scripts/webconsole_seam_snapshot.py`](../scripts/webconsole_seam_snapshot.py) — emits a stable, - deterministic text serialization of the contract: `ENGINE_UI_SEAM`; the `UiDeps` / `CoreHandlers` / - `AdminHandlers` dataclass field names; a **curated** list of the cross-seam surface consumed outside - the bundle (the `api.security` deps' names+signatures, the `AuthService` methods, the `app.state` - attributes); and the **live-introspected** field sets of the `api.models` / `api.auth_models` DTOs the - console renders (so a rename on exactly those DTOs changes the snapshot). + deterministic text serialization of that surface: `ENGINE_UI_SEAM`; the `UiDeps` / `CoreHandlers` / + `AdminHandlers` dataclass field names; the discovered cross-seam surface consumed outside the + bundle; the DTO field sets, closed over nested models; and the enum member sets and `Literal` value + sets those DTOs expose. Field names alone are not the contract — the console indexes a dict by + `UploadedFileList.scope`, so a renamed literal would `KeyError` at runtime while a + field-name-only snapshot stayed byte-identical. - [`tests/golden/webconsole_seam.snapshot`](../tests/golden/webconsole_seam.snapshot) — the checked-in golden. - [`tests/test_webconsole_seam_snapshot.py`](../tests/test_webconsole_seam_snapshot.py) — regenerates the - snapshot and diffs it against the golden, failing with an actionable hint on any drift. + snapshot and diffs it against the golden, failing with an actionable hint on any drift. It also + pins the digest itself: `test_the_stored_seam_equals_the_derived_digest` fails any value that was + not derived from the current surface. -This gate is the **sole backstop** against a *future* engine's unbumped, render-breaking DTO rename -(the package CI matrix only covers engines that exist at package-CI time), so it must stay comprehensive -and blocking. +This gate is the **sole backstop** against a *future* engine's render-breaking DTO rename that the +seam did not follow (the package CI matrix only covers engines that exist at package-CI time), so it +must stay comprehensive and blocking. -### Bumping the seam on an intentional contract change +### Refreshing the seam on an intentional contract change + +**You cannot know the new value in advance, and you never type one.** The seam is a digest of the +surface `seam_discovery.py` finds, so it exists only once the change is made and regenerated, and +`test_the_stored_seam_equals_the_derived_digest` fails any hand-written value. When you deliberately change the injected contract (add/rename a `CoreHandlers`/`AdminHandlers` field, -change an `api.security` dep signature, rename a rendered DTO field, add/remove an `app.state` hook or a -consumed `AuthService` method): +change an `api.security` dep signature, rename a rendered DTO field or one of its `Literal` values, +add/remove an `app.state` hook or a consumed `AuthService` member): 1. Make the contract change in the engine (and the matching consumer change in the package). -2. Bump `ENGINE_UI_SEAM` in [`messagefoundry/api/_ui_seam.py`](../messagefoundry/api/_ui_seam.py) - (e.g. `1` → `2`). -3. Update `messagefoundry_webconsole.SUPPORTED_ENGINE_SEAMS` in - [`messagefoundry_webconsole/__init__.py`](../messagefoundry_webconsole/__init__.py) to the NEW seam - alone — it holds exactly one value (BACKLOG #279). A test asserts - `SUPPORTED_ENGINE_SEAMS == {ENGINE_UI_SEAM}`, so forgetting this step fails CI on the bump commit - rather than at a deployment's startup. Widening it back to a range requires landing the cross-seam - CI matrix in the same change. -4. If the change touched the *curated* surface (an `api.security` symbol, an `AuthService` method, an - `app.state` attribute, or which DTOs the console renders), update the corresponding list in - `scripts/webconsole_seam_snapshot.py`. -5. Refresh the golden (write UTF-8, no BOM): +2. Regenerate the engine side: ```bash - python scripts/webconsole_seam_snapshot.py > tests/golden/webconsole_seam.snapshot + python scripts/webconsole_seam_snapshot.py --write ``` -6. Confirm green: `python -m pytest tests/test_webconsole_seam_snapshot.py -q`. -7. At release, update the compat range on both sides (the engine `[webconsole]` extra and the package's + That rewrites **both** `ENGINE_UI_SEAM` in + [`messagefoundry/api/_ui_seam.py`](../messagefoundry/api/_ui_seam.py) and the golden + [`tests/golden/webconsole_seam.snapshot`](../tests/golden/webconsole_seam.snapshot). + + **Use `--write`, never a shell redirect over the golden.** A redirect writes the golden alone and + leaves the constant stale, so the gate still reds no matter which shell you use; and the shell + then damages the file on top of that. Measured: Windows PowerShell 5.1 emits UTF-16LE with a BOM, + which the test cannot decode as UTF-8 at all, and `pwsh` 7 emits CRLF, which diffs every line of + a golden written with `\n`. +3. Set the console side **by hand, in the same commit**. `--write` deliberately leaves it alone — a + tool that wrote both halves would turn the handshake into a self-consistent tautology, removing + the one place a human states that this console build matches this engine contract. Its output + prints the exact one-line edit, with the new value already in it; paste that. + + `messagefoundry_webconsole.SUPPORTED_ENGINE_SEAMS` in + [`messagefoundry_webconsole/__init__.py`](../messagefoundry_webconsole/__init__.py) holds exactly + one value (BACKLOG #279), and a test asserts `SUPPORTED_ENGINE_SEAMS == {ENGINE_UI_SEAM}`, so + forgetting this step reds CI on the same commit rather than becoming a hard startup refusal at a + deploying site. Widening it back to a range requires landing the cross-seam CI matrix in the same + change. +4. Confirm green: `python -m pytest tests/test_webconsole_seam_snapshot.py -q`. +5. At release, update the compat range on both sides (the engine `[webconsole]` extra and the package's `messagefoundry>=X, Date: Fri, 4 Sep 2026 16:41:08 -0500 Subject: [PATCH 2/7] docs(webconsole): correct the redirect's failure mode, and the RELEASE.md line it lands on An adversarial verification pass caught a false claim in the preceding commit, and finding the real one made the warning much stronger. WRONG, now removed: that pwsh 7's CRLF output "diffs every line of a golden written with \n". It does not. Path.read_text performs universal-newline translation, so a CRLF golden and an LF golden compare equal -- measured directly. Only Windows PowerShell 5.1 damages the file, via UTF-16LE and a BOM. RIGHT, and it is worse than an encoding bug: the old procedure walks a developer into a state where the HEADLINE gate passes on a fabricated seam. build_snapshot() prints whatever ENGINE_UI_SEAM is imported, while contract_digest() never reads it, so hand-writing the constant and then regenerating the golden by redirect produces a golden that AGREES with the hand-written value. Reproduced in an isolated copy of messagefoundry/, messagefoundry_webconsole/, scripts/ and the golden, with an assertion that the generator read the copy and not the real tree: hand-written seam in source : deadbeefdeadbeef seam recorded in the golden : deadbeefdeadbeef test_webconsole_seam_snapshot_matches_golden : PASS test_the_stored_seam_equals_the_derived_digest : FAIL Exactly one test refuses it. The page now says so. RELEASE.md is the terminus of the procedure's last step, and its opening told the reader to keep "the seam integers" honest -- the retired vocabulary, one line after leaving it. It now says digests and names #1220. A repo-wide sweep with a positive control found the shell redirect surviving in exactly one place, the page fixed here. There is no second copy, .github/ included. Co-Authored-By: Claude Opus 5 --- docs/WEBCONSOLE-PACKAGE.md | 12 +++++++----- packaging/messagefoundry-webconsole/RELEASE.md | 5 +++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/WEBCONSOLE-PACKAGE.md b/docs/WEBCONSOLE-PACKAGE.md index 3c908363d..62882caef 100644 --- a/docs/WEBCONSOLE-PACKAGE.md +++ b/docs/WEBCONSOLE-PACKAGE.md @@ -172,11 +172,13 @@ add/remove an `app.state` hook or a consumed `AuthService` member): [`messagefoundry/api/_ui_seam.py`](../messagefoundry/api/_ui_seam.py) and the golden [`tests/golden/webconsole_seam.snapshot`](../tests/golden/webconsole_seam.snapshot). - **Use `--write`, never a shell redirect over the golden.** A redirect writes the golden alone and - leaves the constant stale, so the gate still reds no matter which shell you use; and the shell - then damages the file on top of that. Measured: Windows PowerShell 5.1 emits UTF-16LE with a BOM, - which the test cannot decode as UTF-8 at all, and `pwsh` 7 emits CRLF, which diffs every line of - a golden written with `\n`. + **Use `--write`, never a shell redirect over the golden**, and note that this fails in the worst + direction. A redirect writes the golden and never the constant, in every shell. The golden *embeds* + whatever `ENGINE_UI_SEAM` currently says, so redirecting makes the golden agree with a stale or + hand-typed value and `test_webconsole_seam_snapshot_matches_golden` goes **green** — measured + against a fabricated `deadbeefdeadbeef`. Exactly one test refuses it, + `test_the_stored_seam_equals_the_derived_digest`. (Windows PowerShell 5.1 also emits UTF-16LE with + a BOM there, which the test cannot decode as UTF-8 at all; `pwsh` 7 does not.) 3. Set the console side **by hand, in the same commit**. `--write` deliberately leaves it alone — a tool that wrote both halves would turn the handshake into a self-consistent tautology, removing the one place a human states that this console build matches this engine contract. Its output diff --git a/packaging/messagefoundry-webconsole/RELEASE.md b/packaging/messagefoundry-webconsole/RELEASE.md index 9de9fd1d3..edcbdc05e 100644 --- a/packaging/messagefoundry-webconsole/RELEASE.md +++ b/packaging/messagefoundry-webconsole/RELEASE.md @@ -17,8 +17,9 @@ Context: the console is a separately-versioned second distribution mounted same- (Option B, [ADR 0065](../../docs/adr/0065-web-ops-dashboard.md)). Architecture, the seam, and the version-skew gate are documented in [`docs/WEBCONSOLE-PACKAGE.md`](../../docs/WEBCONSOLE-PACKAGE.md). The **`ENGINE_UI_SEAM` handshake means the engine and console versions can move independently within a -compat range** — you do not have to release them lockstep; you must only keep the range and the seam -integers honest. +compat range** — you do not have to release them lockstep; you must only keep the range and the two +seam digests honest. Nobody chooses a seam value: it is derived (BACKLOG #1220), and +`docs/WEBCONSOLE-PACKAGE.md` carries the procedure for moving it. The console's own `release-webconsole` job already exists in [`.github/workflows/release.yml`](../../.github/workflows/release.yml), modelled on `release-harness` From 9f5cca101008b87b8509dbd97cdbb78d2e8f2cc4 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Fri, 4 Sep 2026 16:41:16 -0500 Subject: [PATCH 3/7] docs(backlog): file #1443, the stale seam-refresh procedure, and cite it from #1439 Records what the preceding commit fixed and how each claim was measured: the three retired steps, the ordering that made them unfollowable, the redirect measured in three shells, and the drift outside the numbered list. Also updates #1439's closing "Adjacent and NOT fixed here, named rather than numbered" paragraph, which asked for exactly this item. Two of its three descriptions were sharpened by measurement while building the fix, and the paragraph now says so rather than being silently superseded. Kept in its own commit, last, so the Lander gets a scripted row-merge rather than a conflict tangled with prose changes. Co-Authored-By: Claude Opus 5 --- docs/BACKLOG.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 987a97699..66f5f4fdc 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -21997,3 +21997,67 @@ So `test_the_script_prefers_its_own_repo_over_an_earlier_path_entry` supplies th **Verification:** 8 passed in `tests/test_webconsole_seam_snapshot.py`. Mutation check run rather than argued -- with the `sys.path.insert` line deleted, the decoy test reds naming the decoy import, and the by-path digest test **stays green**, which is the luck described above measured rather than predicted. Anchor restored, 8 passed again. **Adjacent and NOT fixed here, named rather than numbered.** `docs/WEBCONSOLE-PACKAGE.md`'s seam-refresh procedure is stale in three steps left behind by #1220: it says to bump `ENGINE_UI_SEAM` by hand (`1` to `2`) when the value is a derived digest, it says to update curated lists in this script that #1220 retired, and its step 5 prescribes `python scripts/webconsole_seam_snapshot.py > tests/golden/...`, the shell redirect this script's own docstring forbids because PowerShell's `>` writes UTF-16LE with a BOM into a file the test reads as UTF-8. That is doc drift with its own cause and it wants its own item; folding a documentation rewrite into a `sys.path` fix would make both harder to review. + +> **It got one: #1443**, filed and fixed 2026-09-04, in the commit that carries this line. Two of the three descriptions above were sharpened by measurement while it was built: the redirect is wrong in every shell rather than only under PowerShell (a redirect can write just one of the two files `--write` writes, so it leaves the constant stale even where the encoding survives), and the drift was not confined to the numbered steps -- the section heading two screens earlier called the seam an integer and typed it `int`. See #1443's LIMB 2 and LIMB 4. + +## 1443. WEBCONSOLE-PACKAGE.md's seam-refresh procedure teaches three steps #1220 retired, one of which corrupts the golden + +> 🔢 **Filed 2026-09-04 - FIXED IN THIS COMMIT, not yet landed.** Found 2026-09-03 while building #1439, which deliberately left it alone so a `sys.path` fix and a documentation rewrite stayed separately reviewable. The wider census in LIMB 4 is why the fix is not confined to the three steps the finding named. + +**Cluster:** repository tooling. **Priority:** P3. **Verdict:** build. +**Severity:** no engine effect, no PHI axis, and **no deployment axis (sec. 0)** -- this is a developer procedure for a repository gate, and `docs/` and `scripts/` reach no wheel. The cost is bounded to a developer's session and it is the same shape #1439 recorded: **a corrupted golden, then debugging the gate that reported it.** Nothing is mis-shipped, because the seam the engine actually ships is derived by the tool and pinned by the tests either way. + +**What:** [`docs/WEBCONSOLE-PACKAGE.md`](WEBCONSOLE-PACKAGE.md)'s "Bumping the seam on an intentional contract change" still described the pre-#1220 world, in which `ENGINE_UI_SEAM` was a hand-picked incrementing integer over five hand-maintained tuples. #1220 made it a **16-hex-character SHA-256 digest of a DISCOVERED surface**. Three of the procedure's seven steps had no meaning left. + +| Step | Said | Shipped behaviour | +|---|---|---| +| 2 | bump `ENGINE_UI_SEAM` by hand, "e.g. `1` -> `2`" | the value is derived; `test_the_stored_seam_equals_the_derived_digest` fails any hand-written one | +| 4 | if the change touched the *curated* surface, update the list in `webconsole_seam_snapshot.py` | #1220 retired all five tuples; `scripts/seam_discovery.py` discovers the surface and there is no list | +| 5 | `python scripts/webconsole_seam_snapshot.py > tests/golden/webconsole_seam.snapshot` | the script's own docstring forbids exactly this; `--write` is the command, and it rewrites the constant AND the golden together | + +**LIMB 1 -- THE PROCEDURE IS UNFOLLOWABLE, NOT MERELY MISLABELLED, AND THAT IS THE STRONGEST THING AGAINST IT.** Under a derived seam the value does not exist until the change has been made and regenerated. Old step 2 asked the reader to write it, and old step 3 asked them to copy it into the console before old step 5 ever ran the generator. **There is no order in which the old steps can be executed**, so a reader who follows them faithfully cannot finish, and the first thing they will doubt is the gate rather than the page. + +**LIMB 2 -- STEP 5 FAILS IN THE WORST DIRECTION: FOLLOW THE OLD PROCEDURE AND THE HEADLINE GATE GOES GREEN ON A FABRICATED SEAM.** This is the finding that matters, and it is not the one the script's docstring names. + +`build_snapshot()` prints whatever `ENGINE_UI_SEAM` is imported, while `contract_digest()` never reads it. So old step 2 (hand-write the value) followed by old step 5 (regenerate the golden by redirect) produces a golden that AGREES with the hand-written value. Reproduced 2026-09-04 in an isolated copy of `messagefoundry/`, `messagefoundry_webconsole/`, `scripts/` and the golden, with the repository untouched and an assertion that the generator read the copy rather than the real tree: + +``` +hand-written seam in source : deadbeefdeadbeef +seam recorded in the golden : deadbeefdeadbeef +test_webconsole_seam_snapshot_matches_golden : PASS +test_the_stored_seam_equals_the_derived_digest : FAIL (derived=266cbfd342b22819 stored=deadbeefdeadbeef) +``` + +**The gate whose name says it compares the snapshot accepts `deadbeefdeadbeef`.** Exactly one test refuses it. A procedure that walks a developer into that state is worse than one that simply does not work. + +**THE ENCODING STORY IS REAL BUT NARROWER THAN THE DOCSTRING CLAIMS, AND I HAD IT WRONG ONCE BEFORE CORRECTING IT.** Measured on this box, one string redirected by each shell, then read back the way `tests/test_webconsole_seam_snapshot.py:94` reads the golden: + +| Shell | Bytes written | `read_text(encoding="utf-8")` | +|---|---|---| +| Windows PowerShell 5.1 | `ff fe 78 00 0d 00 0a 00` -- UTF-16LE with a BOM | `UnicodeDecodeError: invalid start byte` | +| `pwsh` 7 | UTF-8, no BOM, CRLF | **decodes, and compares EQUAL** | +| Git Bash | UTF-8, no BOM, LF | decodes clean | + +The `pwsh` 7 row is the correction: an earlier draft of this item and of the page claimed CRLF would diff every line. It does not. `read_text` performs universal-newline translation, so a CRLF golden and an LF golden compare equal, verified directly. The claim was plausible, wrong, and would have sent a reader hunting line endings; an adversarial verification pass caught it before it landed. + +So only the 5.1 arm damages the file, and that arm is still the nastiest: the golden is read as UTF-8 with no guard, so the developer gets a decode traceback instead of `_FAILURE_HINT` -- the gate's one mechanism for teaching the repair, bypassed exactly when it is needed. **But the encoding is not the common failure.** The common failure, in every shell including the `bash` the fence advertised, is that a redirect writes one of the two files `--write` writes. + +**LIMB 3 -- THE REMAINING BY-HAND EDIT IS STATED BY THE TOOL, SO THE PAGE LINKS TO IT RATHER THAN RESTATING IT (SDS-3.5).** `--write` deliberately does **not** touch `messagefoundry_webconsole.SUPPORTED_ENGINE_SEAMS`; its own docstring gives the reason, that a tool writing both halves would turn the handshake into a self-consistent tautology and remove the one place a human states this console build matches this engine contract. So the console-side edit stays a step, but its **literal text is printed by `--write` with the new value already in it**, and the page now points at that output. The page keeps the *why* -- one value (#279), a test that reds CI on the same commit, and what widening the set would cost -- because none of that is in the tool's output. + +**LIMB 4 -- THE DRIFT WAS WIDER THAN THE THREE STEPS, AND THE REST OF IT WAS IN THE SECTION THAT DEFINES THE TERM.** Fixing only the numbered list would have left the page contradicting itself two screens earlier: + +- the heading read **"`ENGINE_UI_SEAM` -- the handshake integer"**, and the paragraph under it typed the constant `int` and the console's set `frozenset[int]`. Shipped: `ENGINE_UI_SEAM: str` (`api/_ui_seam.py`) and `SUPPORTED_ENGINE_SEAMS: frozenset[str]` (`messagefoundry_webconsole/__init__.py:48`). A reader who trusted the annotation would write an integer and be refused by mypy before the gate ever spoke. +- the implementation list said **"Three files implement it"** and described the generator as emitting "a **curated** list" plus "**live-introspected**" DTO fields. `scripts/seam_discovery.py` is a fourth file and the whole surface is discovered. The description also predated two capture classes the generator has carried since #1220: enum member sets and `Literal` value sets. That omission is load-bearing rather than tidy -- the console indexes `_SCOPE_NOTES[data.scope]`, so a renamed literal `KeyError`s at runtime while a field-name-only snapshot stays byte-identical, which is precisely the failure the section claims to be the sole backstop against. +- three further sites carried "unbumped" / "seam bump" / "seam-bumping", each implying a number somebody types. + +**ONE RULE WAS ADDED THAT NO VERSION OF THE PAGE EVER CARRIED: on a merge conflict over the seam, NEITHER SIDE IS CORRECT.** When two branches both move the contract surface, all three files holding the value conflict, and the merged surface derives a **third** digest matching neither. Taking either side ships a value describing a tree that does not exist, and it looks exactly like an ordinary conflict resolution. This is #1220's central property read from the other direction -- `api/_ui_seam.py` states it as the reason a digest cannot collide the way the old integer did, but nothing told a developer standing in the conflict what to do. The page now does: clear the markers to anything, rerun `--write`, then set the console side from its output. Reported by a peer session that hit it live on 2026-09-04 (branch `d4ae2ce03d5723a7`, main `266cbfd342b22819`, merged `767521d4399d1ef4`); the mechanism is deterministic from the digest's own construction, so it is recorded here as design rather than as one session's luck. + +**Two things were DELIBERATELY not added.** A `PYTHONPATH` pin before the generator runs, because #1439 anchored the script on `__file__` and that test's `_ANCHOR_HINT` forbids the call-site pin by name, citing this very by-hand procedure as the reason. And the new digest's value, anywhere in prose -- the section's own rule, older than this item, is that a value quoted in prose goes stale silently, which is how it came to say "currently 1" until seam 11. + +**Verification:** `python scripts/webconsole_seam_snapshot.py --digest` returns `266cbfd342b22819`, matching both `ENGINE_UI_SEAM` and `SUPPORTED_ENGINE_SEAMS`, so the tree is self-consistent and the rewritten page describes the state it is in. The redirect table above was measured rather than argued, in a scratch directory outside the repository, with the repository untouched. + +**ONE SIBLING WAS FIXED, BECAUSE IT IS THE TERMINUS OF THIS PROCEDURE.** The page's last step sends the reader to `packaging/messagefoundry-webconsole/RELEASE.md`, whose opening said to "keep the range and the seam **integers** honest". A reader who follows the corrected procedure to its end would have landed on the retired vocabulary one line after leaving it. That file now says digests, and names #1220. A repo-wide sweep found the shell redirect surviving in exactly ONE place -- the page this item fixes -- so there is no second copy of the procedure anywhere, `.github/` included (checked with a positive control, since a pattern that finds nothing is indistinguishable from a clean tree). + +**Adjacent and NOT fixed here, named rather than numbered.** `tests/test_webconsole_seam_snapshot.py:106-127` still says "A seam bump has always required editing BOTH constants" and "on the commit that bumps the engine", and its assertion message opens "Bumping ENGINE_UI_SEAM requires updating...". That prose is not false -- the constant does change -- but it carries the same chosen-number connotation this item removed from the page, and `_FAILURE_HINT` in the same file already teaches the correct repair. It is test prose rather than the procedure a developer follows, so it is left for whoever next edits that file. + +Four more, all in the #1220 class and all deliberately out of this diff so the procedure stays separately reviewable: `scripts/webconsole_seam_snapshot.py:41` and `:303` state the redirect's reason as the PowerShell encoding twice, which is true of 5.1, silent about the one-of-two-files failure, and a restatement of a load-bearing fact (SDS-3.5); `messagefoundry_webconsole/__init__.py:29` is a truncated leftover comment ending mid-sentence on the word "the" and naming an integer seam identity, sitting directly above the constant it purports to explain; `packaging/messagefoundry-webconsole/CHANGELOG.md:16` and `:25` make a PRESENT-state claim in `[Unreleased]` that the supported seam is `2, 3`, which is wrong on both the type and on #279's one-value rule; and `docs/adr/0143`'s "curated into the webconsole seam snapshot" names the retired mechanism, though an accepted ADR records what was true when written and is better left dated than edited. Also surfaced and entirely unrelated: `docs/LEDGER-GATE.md:132` credits `install-git-hooks.ps1` with installing the ledger gate, which its own synopsis says it no longer does, and `:164`'s two anchors into `ledger_check.py` have drifted to `:376` and `:465`. From c2f549f4256c9d33d3e113c6e0980612c8cd969d Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Fri, 4 Sep 2026 16:47:02 -0500 Subject: [PATCH 4/7] docs(scripts): give the redirect warning its real reason, not the shell-specific one The generator's docstring said a shell redirect is forbidden because PowerShell writes UTF-16LE with a BOM. Accurate, and the wrong thing to publish: a reader on Git Bash consults it, correctly concludes it does not apply to their shell, runs the redirect and silently does half the job. The warning licensed the failure it exists to prevent. SDS-3.4 asks what a reader would DO with a sentence, not whether it is true, and this one fails that test. The real failure is shell-independent and worse. A redirect writes the golden and never the constant, and build_snapshot() prints whatever ENGINE_UI_SEAM is imported, so the redirected golden AGREES with a hand-typed value: test_webconsole_seam_snapshot_matches_golden goes green on a fabricated deadbeefdeadbeef, and only test_the_stored_seam_equals_the_derived_digest refuses it. The docstring now leads with that and demotes the encoding to a second, shell-specific hazard, naming what pwsh 7 and Git Bash actually do. The duplicate of the same fact at the write site becomes a pointer rather than a second copy (SDS-3.5). Comment-only: ruff clean, mypy clean, 8 passed, and the derived digest is 266cbfd342b22819 before and after. Raised by the session that authored the line under BACKLOG #1439, which declined to open a competing branch for two comment lines and handed it over. Taken here because this is the change that cites the behaviour. Co-Authored-By: Claude Opus 5 --- scripts/webconsole_seam_snapshot.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/scripts/webconsole_seam_snapshot.py b/scripts/webconsole_seam_snapshot.py index 53a5cdec3..4a10e41b4 100644 --- a/scripts/webconsole_seam_snapshot.py +++ b/scripts/webconsole_seam_snapshot.py @@ -38,8 +38,17 @@ ``python scripts/webconsole_seam_snapshot.py --digest`` print the derived seam value only ``python scripts/webconsole_seam_snapshot.py --write`` rewrite ENGINE_UI_SEAM AND the golden -Use ``--write``, never a shell redirect over the golden: PowerShell's ``>`` emits UTF-16LE with a -BOM, which corrupts a file the test reads as UTF-8. +Use ``--write``, never a shell redirect over the golden. A redirect writes the golden and never +``ENGINE_UI_SEAM``, in EVERY shell, and it fails GREEN rather than red: ``build_snapshot`` prints +whatever seam is imported, so a redirected golden AGREES with a stale or hand-typed constant and +``test_webconsole_seam_snapshot_matches_golden`` passes. Measured against a fabricated +``deadbeefdeadbeef``, only ``test_the_stored_seam_equals_the_derived_digest`` refused it. + +The encoding damage is a SECOND hazard layered on that one, and it is shell-specific, so it must not +be read as the reason: Windows PowerShell 5.1's ``>`` emits UTF-16LE with a BOM, which the test +cannot decode as UTF-8 at all, while ``pwsh`` 7 and Git Bash write a clean file and still leave the +constant stale. An earlier version of this note gave only the encoding reason, which licensed the +redirect for anyone not on 5.1 -- the failure it was warning against. """ from __future__ import annotations @@ -300,8 +309,8 @@ def write_seam_and_golden() -> str: # Rebuild with the NEW value in scope. The digest itself does not depend on the seam (see # contract_sections), but the snapshot's first section prints it. globals()["ENGINE_UI_SEAM"] = digest - # Written explicitly rather than via a shell redirect: PowerShell's `>` emits UTF-16LE with a - # BOM, which corrupts a golden the test reads as UTF-8. + # Written explicitly rather than via a shell redirect, which would write this file and never the + # constant rewritten above it. See the module docstring for why that fails green. _GOLDEN_FILE.write_text(build_snapshot(), encoding="utf-8", newline="\n") return digest From 8252826f6a2d31265245e1ec1b48ef75f2ff8444 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Fri, 4 Sep 2026 16:47:18 -0500 Subject: [PATCH 5/7] docs(backlog): score #1443 at filing, and record the docstring handover Scores the item as it is filed rather than leaving it for a later pass. Nothing in CI requires a score at filing, so an item enters the ledger unranked and stays that way until a pass reaches it; five items filed since the 2026-09-03 pass already carry none, and this would have been the sixth. Value 3, difficulty 2. Value is above the bottom of the table because the defect is not cosmetic staleness: the old procedure walks a reader into a state where the headline gate PASSES on a fabricated seam. Difficulty prices the two controls that made the prose honest, not the prose. Also moves scripts/webconsole_seam_snapshot.py out of the item's not-fixed list, since the preceding commit fixed it, and records why that handover was accepted. Co-Authored-By: Claude Opus 5 --- docs/BACKLOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 66f5f4fdc..6bf241667 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -22003,6 +22003,7 @@ So `test_the_script_prefers_its_own_repo_over_an_earlier_path_entry` supplies th ## 1443. WEBCONSOLE-PACKAGE.md's seam-refresh procedure teaches three steps #1220 retired, one of which corrupts the golden > 🔢 **Filed 2026-09-04 - FIXED IN THIS COMMIT, not yet landed.** Found 2026-09-03 while building #1439, which deliberately left it alone so a `sys.path` fix and a documentation rewrite stayed separately reviewable. The wider census in LIMB 4 is why the fix is not confined to the three steps the finding named. +> **Scored at filing 2026-09-04.** Value **3/10** · Difficulty **2/10** · _fill-in_. Scored here rather than left for a later pass, because nothing in CI requires a score at filing and an item that enters unranked stays unranked until a pass reaches it -- five items filed since 2026-09-03 already carry none. Value is above the bottom of the table but not by much: there is no engine, PHI or deployment axis, and the whole population is developers changing the console contract, which is rare. It is not a 1 because the defect is not cosmetic staleness -- LIMB 2 measures the old procedure walking a reader into a state where the headline gate PASSES on a fabricated seam, and a procedure that manufactures a false green is worth more to remove than one that merely fails. Difficulty 2 prices the prose plus the two controls that made it honest (reproducing the false green in an isolated tree, and measuring three shells); the writing alone would have been a 1, and scoring it a 1 would hide that the measurement is what corrected a wrong claim in the first draft. **Cluster:** repository tooling. **Priority:** P3. **Verdict:** build. **Severity:** no engine effect, no PHI axis, and **no deployment axis (sec. 0)** -- this is a developer procedure for a repository gate, and `docs/` and `scripts/` reach no wheel. The cost is bounded to a developer's session and it is the same shape #1439 recorded: **a corrupted golden, then debugging the gate that reported it.** Nothing is mis-shipped, because the seam the engine actually ships is derived by the tool and pinned by the tests either way. @@ -22060,4 +22061,6 @@ So only the 5.1 arm damages the file, and that arm is still the nastiest: the go **Adjacent and NOT fixed here, named rather than numbered.** `tests/test_webconsole_seam_snapshot.py:106-127` still says "A seam bump has always required editing BOTH constants" and "on the commit that bumps the engine", and its assertion message opens "Bumping ENGINE_UI_SEAM requires updating...". That prose is not false -- the constant does change -- but it carries the same chosen-number connotation this item removed from the page, and `_FAILURE_HINT` in the same file already teaches the correct repair. It is test prose rather than the procedure a developer follows, so it is left for whoever next edits that file. -Four more, all in the #1220 class and all deliberately out of this diff so the procedure stays separately reviewable: `scripts/webconsole_seam_snapshot.py:41` and `:303` state the redirect's reason as the PowerShell encoding twice, which is true of 5.1, silent about the one-of-two-files failure, and a restatement of a load-bearing fact (SDS-3.5); `messagefoundry_webconsole/__init__.py:29` is a truncated leftover comment ending mid-sentence on the word "the" and naming an integer seam identity, sitting directly above the constant it purports to explain; `packaging/messagefoundry-webconsole/CHANGELOG.md:16` and `:25` make a PRESENT-state claim in `[Unreleased]` that the supported seam is `2, 3`, which is wrong on both the type and on #279's one-value rule; and `docs/adr/0143`'s "curated into the webconsole seam snapshot" names the retired mechanism, though an accepted ADR records what was true when written and is better left dated than edited. Also surfaced and entirely unrelated: `docs/LEDGER-GATE.md:132` credits `install-git-hooks.ps1` with installing the ledger gate, which its own synopsis says it no longer does, and `:164`'s two anchors into `ledger_check.py` have drifted to `:376` and `:465`. +**ONE MORE SIBLING WAS TAKEN, ON A HANDOVER, AND THE ARGUMENT FOR IT IS SDS-3.4 RATHER THAN TIDINESS.** `scripts/webconsole_seam_snapshot.py:41` gave the redirect's reason as the PowerShell encoding alone. That is accurate and it is the wrong thing to publish: a reader on Git Bash consults it, correctly concludes it does not apply to their shell, runs the redirect and silently does half the job -- the exact case measured in LIMB 2, licensed by the warning meant to prevent it. Asking what a reader would DO with the sentence is the SDS-3.4 test, and it fails. The docstring now leads with the one-of-two-files failure and the false green, and demotes the encoding to a shell-specific second hazard; the duplicate at `:303` becomes a pointer rather than a second copy (SDS-3.5). Raised by the session that authored the line under #1439, which declined to open a competing branch for two comment lines and handed it over; taken because this diff is the one that cites the behaviour. It changes no code path -- the derived digest is `266cbfd342b22819` before and after. + +Three more, all in the #1220 class and all deliberately out of this diff so the procedure stays separately reviewable: `messagefoundry_webconsole/__init__.py:29` is a truncated leftover comment ending mid-sentence on the word "the" and naming an integer seam identity, sitting directly above the constant it purports to explain; `packaging/messagefoundry-webconsole/CHANGELOG.md:16` and `:25` make a PRESENT-state claim in `[Unreleased]` that the supported seam is `2, 3`, which is wrong on both the type and on #279's one-value rule; and `docs/adr/0143`'s "curated into the webconsole seam snapshot" names the retired mechanism, though an accepted ADR records what was true when written and is better left dated than edited. Also surfaced and entirely unrelated: `docs/LEDGER-GATE.md:132` credits `install-git-hooks.ps1` with installing the ledger gate, which its own synopsis says it no longer does, and `:164`'s two anchors into `ledger_check.py` have drifted to `:376` and `:465`. From 90e75890de964bbe5181cbaf02d6417988207100 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Fri, 4 Sep 2026 16:51:14 -0500 Subject: [PATCH 6/7] docs(backlog): price #1443's difficulty on the remainder, not on work already done The digit stays 2. The reasoning behind it was wrong, and the reasoning is what a later scoring pass reads. It said difficulty priced "the prose plus the two controls that made it honest". The table prices only the remainder -- docs/BACKLOG.md:203 says so outright, and every difficulty-1 row in it reads "the remainder is a doc edit". Investigation already run is not remainder, and counting it would make a well-investigated item cost more than a careless one, inverting the scale. Restated on remainder grounds: four small prose edits on existing seams, no code path, no test, no CI leg, which is where #1381 sits at the same digit. What the controls bought is recorded in LIMB 2, which is where it belongs. The error is recorded in the line rather than quietly overwritten, because an item about prose that teaches a wrong method should not contain one. Raised by a peer session that also argued 4/1 over 3/2 and then showed the two are within noise -- tier and quadrant are pure functions of the pair and both give _fill-in_ / P3 -- so only the reasoning changed. Co-Authored-By: Claude Opus 5 --- docs/BACKLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 6bf241667..3c502c527 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -22003,7 +22003,7 @@ So `test_the_script_prefers_its_own_repo_over_an_earlier_path_entry` supplies th ## 1443. WEBCONSOLE-PACKAGE.md's seam-refresh procedure teaches three steps #1220 retired, one of which corrupts the golden > 🔢 **Filed 2026-09-04 - FIXED IN THIS COMMIT, not yet landed.** Found 2026-09-03 while building #1439, which deliberately left it alone so a `sys.path` fix and a documentation rewrite stayed separately reviewable. The wider census in LIMB 4 is why the fix is not confined to the three steps the finding named. -> **Scored at filing 2026-09-04.** Value **3/10** · Difficulty **2/10** · _fill-in_. Scored here rather than left for a later pass, because nothing in CI requires a score at filing and an item that enters unranked stays unranked until a pass reaches it -- five items filed since 2026-09-03 already carry none. Value is above the bottom of the table but not by much: there is no engine, PHI or deployment axis, and the whole population is developers changing the console contract, which is rare. It is not a 1 because the defect is not cosmetic staleness -- LIMB 2 measures the old procedure walking a reader into a state where the headline gate PASSES on a fabricated seam, and a procedure that manufactures a false green is worth more to remove than one that merely fails. Difficulty 2 prices the prose plus the two controls that made it honest (reproducing the false green in an isolated tree, and measuring three shells); the writing alone would have been a 1, and scoring it a 1 would hide that the measurement is what corrected a wrong claim in the first draft. +> **Scored at filing 2026-09-04.** Value **3/10** · Difficulty **2/10** · _fill-in_. Scored here rather than left for a later pass, because nothing in CI requires a score at filing and an item that enters unranked stays unranked until a pass reaches it -- five items filed since 2026-09-03 already carry none. Value is above the bottom of the table but not by much: there is no engine, PHI or deployment axis, and the whole population is developers changing the console contract, which is rare. It is not a 1 because the defect is not cosmetic staleness -- LIMB 2 measures the old procedure walking a reader into a state where the headline gate PASSES on a fabricated seam, and a procedure that manufactures a false green is worth more to remove than one that merely fails. Difficulty 2 prices the REMAINDER, which is what this table prices and nothing else: a section rewrite in one page, a docstring and its duplicate in the generator, one line in the package release checklist, and this row -- four small prose edits on existing seams, no code path, no test, no CI leg, which is where #1381 sits at the same digit for five citation rewrites plus an ADR amendment. **The measurement that made the prose honest is deliberately NOT priced here.** An earlier draft of this line did price it, and that was a rubric error worth recording rather than silently fixing: investigation already done is not remainder, and counting it would make a well-investigated item cost more than a careless one, inverting the scale. What the controls bought belongs in LIMB 2, which carries it. Correction raised by a peer session on 2026-09-04 and verified against the table's own rule at docs/BACKLOG.md:203, "the scores below price only the remainder". **Cluster:** repository tooling. **Priority:** P3. **Verdict:** build. **Severity:** no engine effect, no PHI axis, and **no deployment axis (sec. 0)** -- this is a developer procedure for a repository gate, and `docs/` and `scripts/` reach no wheel. The cost is bounded to a developer's session and it is the same shape #1439 recorded: **a corrupted golden, then debugging the gate that reported it.** Nothing is mis-shipped, because the seam the engine actually ships is derived by the tool and pinned by the tests either way. From c41176f68d331651e76889361393f80040c68e5c Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Fri, 4 Sep 2026 18:25:22 -0500 Subject: [PATCH 7/7] docs(webconsole): widen the seam merge rule from conflicts to every merge The rule was headed "on a merge conflict over the seam", so a reader whose merge came out clean skipped it. That is the wrong half to skip. The three files do not conflict together. The constant is one line and collides visibly; the golden is long and its sections auto-merge. That is the #1220 origin story exactly, and _ui_seam.py already records it: two branches, a cosmetic conflict in the constant's comment block, a golden that merged clean carrying both changes, and resolving the visible conflict correctly still shipped the fault. Nothing raises its hand for the half that merged quietly. So the rule now leads with "after any merge that moved the contract surface, regenerate; a clean merge is not evidence of a correct digest", and the conflict case becomes one arm of it. Also states the consequence, because it is not what a reader guesses. test_the_stored_seam_equals_the_derived_digest RECOMPUTES from the surface rather than comparing two stored copies, so a stale digest reds CI and main stays clean. It presents as an unrelated-looking test failure on a merge somebody just resolved by hand, which is how a reader ends up debugging the gate instead of rerunning --write. Raised by a peer session that hit the conflict case live earlier today, and that corrected its own first framing of the consequence from a startup refusal to a red CI leg before offering it. Verified here against the test and against _ui_seam.py:33-37 rather than taken from the summary. Co-Authored-By: Claude Opus 5 --- docs/WEBCONSOLE-PACKAGE.md | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/WEBCONSOLE-PACKAGE.md b/docs/WEBCONSOLE-PACKAGE.md index 62882caef..10e11ece7 100644 --- a/docs/WEBCONSOLE-PACKAGE.md +++ b/docs/WEBCONSOLE-PACKAGE.md @@ -194,12 +194,27 @@ add/remove an `app.state` hook or a consumed `AuthService` member): 5. At release, update the compat range on both sides (the engine `[webconsole]` extra and the package's `messagefoundry>=X,