Skip to content

backlog: record the corrected owner rulings for #341 (WIDEN) and #326 (REFUSE) - #184

Merged
wshallwshall merged 5 commits into
mainfrom
claude/owner-rulings-341-326-anchorfix
Aug 4, 2026
Merged

backlog: record the corrected owner rulings for #341 (WIDEN) and #326 (REFUSE)#184
wshallwshall merged 5 commits into
mainfrom
claude/owner-rulings-341-326-anchorfix

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

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:

ref tip on main
claude/owner-rulings-341-326 4a3a8687 no
claude/owner-rulings-341-326-anchorfix 2cb825eb no

4a3a8687 holds 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 2cb825eb returns yes, so this branch carries both the original ruling and its correction.

Two ready lanes (plan-cli-exposure for #326+#328, and plan-dryrun-fanout) each carry 4a3a8687 and neither carries 2cb825eb. 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

  • Content matches the claim. Both reversals are present with explicit supersession language, and each records why the first ruling was reversed rather than silently replacing it.
  • Purely additive against current main: three-dot diff is 58 insertions, 0 deletions, docs/BACKLOG.md only.
  • Merges clean into 24c27812 despite being 5 commits behind — checked with git merge-tree, not assumed from the branch being behind. Being behind is not a conflict and not a revert.
  • It does not touch the ranked table or the census, both of which were just reconciled in backlog: archive #335, file #1002 for #105's renumber, re-derive the census #183 (101 items, 101 open, bijection holding in both directions). This branch adds banner blockquotes only, so it cannot disturb that.

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.

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
wshallwshall enabled auto-merge (squash) August 4, 2026 18:33
@wshallwshall
wshallwshall merged commit baddaba into main Aug 4, 2026
31 checks passed
@wshallwshall
wshallwshall deleted the claude/owner-rulings-341-326-anchorfix branch August 4, 2026 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant