backlog: record the corrected owner rulings for #341 (WIDEN) and #326 (REFUSE) - #184
Merged
Merged
Conversation
Both items reserved a fork that changes BEHAVIOUR, not style, and both were blocking a Wave-1 lane. The owner ruled 2026-08-04; recording it in the item bodies so it outlives the exchange -- a decision that lives only in a session message is one nobody can find, and a Workflow dispatched at either item would otherwise have made the call itself. #341 -> RAISE, do not widen. A Handler returning a tuple/set of Sends must raise, so the message lands ERROR/dead-letter. Widening would start DELIVERING messages the engine drops today: every live Handler returning a tuple begins flowing on upgrade, silently, including PHI currently dropped. An accept-and-drop is what CLAUDE.md §12 forbids, and turning it into an accept-and-send nobody asked for is the worse of the two. A loud dead-letter is visible and recoverable; a quiet new delivery path is neither. Recorded with two build constraints, because they decide how the fix lands rather than whether: test_partition_parity_table pins the current [0,0,0] behaviour deliberately and its docstring argues against normalising -- that rationale is superseded, so the test is rewritten and its reasoning replaced, not extended around. And _sandbox_codec.py preserves the container shape on purpose, so in-process and subprocess modes must agree or the fix creates a mode-dependent disposition. #326 -> WARN FIRST, with a DATED flip to refuse. The fix is unchanged (re-key admin_exposed off the serve_ui-independent predicate that already exists in the file, correct the two exposure_desc else-branches); what is settled is the upgrade behaviour. Because it makes a currently-starting configuration refuse, against this repo's own rule at docs/CONFIGURATION.md:1439, the refusal is deferred. The date is not decoration. A deferred refusal with no date is a refusal that never happens -- it decays into a warning everyone scrolls past, which is precisely how the inert control this item is about came to exist. The gap is real and measured (arm C starts rc=0 with single-factor admin on production PHI) but has been latent a while; breaking a running production instance on upgrade with no notice trades one silent failure for a loud one nobody scheduled. Neither item is closed -- these are rulings, not completions, and both keep their open banner. The first draft led each blockquote with ✅ and the status gate correctly rejected it as a closed banner contradicting an open one; they now use the file's glyph-free AMENDED convention. backlog_status_check.py --min-items 277: OK, 278 items.
… :1475, not :1439 The #326 ruling justified deferring the refusal by citing "this repo's own rule at docs/CONFIGURATION.md:1439". A sibling session checked the anchor and it is wrong. At HEAD, :1439 sits in the [security] desugaring section, whose rule is "no shipped refusal is LOOSENED" (the No-loosen rule, ADR 0092 §5). That is about not weakening an existing refusal -- the OPPOSITE direction from adding a new one that breaks a starting config. Cited as written, the ruling rested on a rule that says nothing about its case. The real rule is at :1475 and the quoted text was right all along: "a new refusal fires only on a new opt-in". I inherited the wrong line number from #326's own Proposed point 3 without re-checking it -- the same defect this session catalogued five times in other people's work, in a ruling other sessions are about to build from. The correction also surfaces something the implementer needs. The real rule's stated reasoning IS this case -- "a refusal by default would stop working dev/staging/prod deployments from booting on upgrade over something they cannot change" -- and the house pattern it names (require_memory_encryption_declaration, ADR 0152 rung 2; allowed_client_networks, ADR 0151) is a refusal gated behind an OPT-IN FLAG, not merely a dated flip. The owner's ruling stands as given; whether the eventual refusal also sits behind an opt-in is a build detail that should follow those two precedents rather than be invented at the keyboard. Recorded as a dated correction inside the ruling rather than a silent edit, because the wrong anchor was already pushed and a reader who saw it should be able to see that it moved and why. backlog_status_check.py --min-items 277: OK, 278 items.
…sm question Second correction to the same citation, because the first was still imprecise. Both catches came from sibling sessions reading the line verbatim; neither came from me. WHAT :1439 ACTUALLY IS. Read at HEAD it is "...[ai].production) is **rejected at load** with a" -- mid-sentence in the [security] desugaring section, about rejecting MOVED LEGACY KEYS. It is not a rule about upgrade behaviour in either direction. My first correction called it the No-loosen rule; that rule is real but sits at :1445, six lines later, and concerns not WEAKENING an existing refusal -- still the opposite direction from adding one. I corrected a wrong anchor to a different wrong description of the same line, which is the same error one layer down. THE THREE REAL ANCHORS, each verified verbatim rather than inherited: :1475 "a new refusal fires only on a new opt-in" :88, :1020 "The refuse/warn split is [security].enforcement, not the production tier" THE SECOND FIND CHANGES THE BUILD, and is the more valuable half. [security].enforcement is the repo's FIRST-CLASS refuse/warn split -- refuse under enforce, warn under warn -- and every sibling gate uses it (require_managed_identity, security_notifications_required). A bespoke dated flip for #326 alone would stand up a second, parallel mechanism for the same decision: two ways to answer one question, and no rule about which applies. The lane building #326 proposed a reading that satisfies both, now recorded and flagged to the owner: warn irrespective of enforcement until the dated flip, then obey [security].enforcement like its siblings. That preserves the deferral the owner asked for and lands the gate on the existing mechanism rather than beside it. The owner's ruling stands and is not reopened. What was open is only HOW the eventual refusal is expressed -- and that is a question the ruling never claimed to settle. Worth stating plainly, since this ledger is read as authority: a wrong anchor in a RULING carries more weight than the same wrong anchor in the item it came from, because a ruling is what other sessions build against without re-deriving. That is the whole reason both corrections are dated notes rather than silent edits. backlog_status_check.py --min-items 277: OK, 278 items.
… not exist Owner-confirmed 2026-08-04: MessageFoundry is a NOT-DEPLOYED beta with ZERO production instances. Nobody is running it. Published to PyPI is not deployed. That makes an entire class of argument vacuous, and both rulings I recorded were built on it. Reversed, as dated supersessions rather than edits, because the wrong versions were pushed and two lanes were already building against them. #341: RAISE -> WIDEN. The first ruling said widening "would start delivering messages the engine drops today -- every live Handler returning a tuple would begin flowing on upgrade, including PHI currently being dropped." That requires live Handlers to exist. None do. The cost it priced has no referent. Three arguments for widening that never touched deployment, and would have carried it regardless -- all three surfaced by the lane doing recon, not by me: 1. _handler_names (dryrun.py:96-99) already accepts a tuple/set/generator from a ROUTER. Only the HANDLER path rejects it. Widening removes an internal inconsistency; raising entrenches a split with no stated rationale. 2. `return ()` is a documented filter idiom (lens.py:678, SHALL'd at ADR 0108:60). It composes under widen; under raise it needs a carve-out. 3. It does what the author plainly meant. #326: WARN-FIRST -> REFUSE OUTRIGHT. The first ruling deferred the refusal because the fix "makes a currently-starting configuration refuse on upgrade", citing CONFIGURATION.md:1475. That rule exists in as many words to protect working dev/staging/prod deployments from booting on upgrade. There are none. Worth recording plainly: a sibling session and I then spent an afternoon carefully distinguishing a bespoke dated flip from the house [security].enforcement split from the opt-in-flag pattern. BOTH MECHANISMS WERE SOLVING A PROBLEM THAT DOES NOT EXIST. The analysis is preserved in the item for whoever revisits it if an adopter goes live; it is not a live design question now. Also recorded, verified at HEAD and independent of the rulings: - #328: api/app.py is IN scope. The multisession plan said DROP it, but every [integrity] setting reaches the Engine ONLY via create_managed_app -- audit_verify_on_start at api/app.py:5467. Building to the plan's scope would ship a dead setting: configurable, documented, never read. - #341's ADR 0087 citation: AC-11 is at :232, and :238 is AC-12 (code-set tables). A builder following the wrong range rewrites the wrong SHALL. The premise is now in project memory (mf-not-deployed-beta), written by a sibling session that hit the same gap the same day. It is not derivable from the tree -- I searched README, POSITIONING, SUPPORT-POLICY, EARLY-ADOPTER-GUIDE and the backlog and found no statement of it; the nearest signal is the value rubric's 9 = "a named adopter is waiting", which is only consistent with it. backlog_status_check.py --min-items 277: OK, 278 items.
wshallwshall
enabled auto-merge (squash)
August 4, 2026 18:33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Authored by the a52335 session; opened by the coordinator on the owner's push/merge authority, at the Wave-1 session's request and after independent verification.
This must merge BEFORE two ready lanes, and the reason is a self-contradicting main
The Wave-1 session flagged an ordering hazard and it checks out mechanically:
claude/owner-rulings-341-3264a3a8687claude/owner-rulings-341-326-anchorfix2cb825eb4a3a8687holds the superseded rulings — RAISE for #341, WARN-FIRST for #326. The amendments reversing them to WIDEN and REFUSE exist only on this branch. Verified:git merge-base --is-ancestor 4a3a8687 2cb825ebreturns yes, so this branch carries both the original ruling and its correction.Two ready lanes (
plan-cli-exposurefor #326+#328, andplan-dryrun-fanout) each carry4a3a8687and neither carries2cb825eb. Merging either first would land a ledger blockquote ordering warn-first directly above a banner recording an outright refusal that shipped — the code and the record contradicting each other on main, in the same file, inches apart. That is the exact defect class this repo has spent the day removing.Landing this first puts the ruling and its correction on main together, after which the lanes rebase and their inherited copy becomes redundant.
Verified before opening
docs/BACKLOG.mdonly.24c27812despite being 5 commits behind — checked withgit merge-tree, not assumed from the branch being behind. Being behind is not a conflict and not a revert.Note on what it does not do
It records rulings. It does not close #328 — the Wave-1 session was explicit that #328's Proposed 3 is deferred for a measured reason (a stored startup anchor would fire a false positive against an exact point-in-time seal), so #328's banner correctly stays open and no title or changelog should imply otherwise.
Gates
Docs-only, so the pytest legs are skipped pre-merge and fire only on the push to main — a live gap, not specific to this change. The ledger gate and the leak gate both run regardless. I did not re-run the full local suite against this branch because it adds no code and touches no ranked-table or census line; the ledger gate is the binding check here and it is not skipped.