# NAMEPLATE-JIT — milestone releases#181 (todlando build lane)

Brief: doyle 2026-08-17 (spt message), minted from the operator grill of 2026-08-17. Recon-first;
REQ mints ride the build PR; thin lane; doyle gates. Base for every reading below: main @`27d40b9`
(local == origin at recon time), Cargo.toml 0.55.0.

## Scope (sub-issues of #181)
- **#180 flagship** — DISCOVER default-enabled under `closed` posture.
- **#163** — ER/DISCOVER conjunction notice at accept time.
- **#76** — write-time consequence when a FORK rule's subject lacks DISCOVER; knock prescription
  prescribes the pair.
- **#169** — knock events do not notify the instigator (approve→knocker, redeem→minter).
- **#184** — remove the two UNLISTED footer explainer lines from `endpoint list`.

## Rulings in force (operator + doyle, binding — not re-litigated here)
1. Broad reading: `closed` at ANY tier (endpoint mode / node mode / captured subnet mode) no longer
   implies DISCOVER-deny. The only off-switch is an EXPLICIT DISCOVER-naming deny — a rule row, or a
   per-surface mode set from the engine-room — honored at every tier.
2. ADR-0053 degrade unchanged: a degraded store still refuses DISCOVER (`DiscoverGate` returns false
   on `store: None`, spt-daemon/access.rs:624-626). Preserve + pin with a unit.
3. REQ-ER-NOT-ADVERTISED (ER pre-chain carve-out, posture beats whitelist) stays exactly as shipped.
4. CONTEXT.md mode-tier glossary gains the DISCOVER carve-out as ratified vocabulary (doc stage,
   same change).
5. Redemption stays DISCOVER-free by design (knock.rs:413-417, node-tier-only routing
   wansend.rs:1357-1393) — #180 must NOT touch the redeem path.

## Recon findings (todlando 2026-08-17, all from source at `27d40b9`)

### F1 — the mode tiers are surface-blind at exactly three sites, and so is their sibling reader
`AccessStore::decide` (spt-store/src/access.rs:1288) decides the three mode tiers with
`allow = m == Mode::Open`: endpoint mode :1310-1315, node mode :1318-1323, captured subnet mode
:1328-1345; chain bottom is unconditional `ImplicitOpen` :1347-1351. Each tier sources its mode from
`Modes::for_surface` (:596-607), which resolves `per_surface` exact → case-folded → **falls back to
the blanket `self.all`** (:606).

**That last `.or(self.all)` IS the defect seat.** "Explicit DISCOVER-naming deny" vs "a blanket
closed posture" is exactly the `per_surface`-hit vs `all`-fallback distinction the type already
carries. No new storage shape is needed.

`effective_posture` (:1364-1386) re-reads the same three tiers through the same `for_surface` — by
deliberate design ("a separate reading of what the posture is here would be a second source for a
fact the chain already owns"). It feeds `may_grant_node_subject` (:1397-1402). **So any carve-out
placed in the chain alone would put those two readings out of agreement** — see Q2.

### F2 — knock initiation cannot reach an undiscoverable target, while `send` can (doyle's arm, confirmed)
`knock_send_remote` (spt/src/wansend.rs:527) resolves via `resolve_across_visible` and returns
`KnockSendOutcome::NotFound` outright at :555. The message leg at the same resolution point falls
through to the node-tier evidence route (`unlisted::route_node_for`, :434-442,
REQ-UNLISTED-EVIDENCE-ROUTE). Result: an undiscoverable target is **unknockable while still
sendable** — and knocks are precisely how a stranger asks to be admitted.

The fallback is a straight parallel: the knock leg consumes only `instance.node` from the resolution
(two uses — :569 `seed_first_addr`, :579 `let node = instance.node`), the same field the message leg
takes. The asker's own exclusion must be re-imposed at the arm as the message leg does with
`excl.anywhere()` (:438).

**This is also the N-1 interop answer** and it is load-bearing, not a nice-to-have: a pre-#180 peer
still applies its blanket-closed posture to DISCOVER and will not advertise, so flipping OUR default
cannot make THEM knockable. Without F2, #180 buys nothing against the installed fleet.

### F3 — #76's stated premise stops being true inside this same milestone
#76 reasons: "per-surface isolation closes every surface an endpoint does not list, so a FORK-only
grant also DENIES DISCOVER." Read against the code, the deny does not come from the rule at all — a
FORK rule simply does not `cover` DISCOVER (`AccessRule::covers` :540-546), so DISCOVER falls THROUGH
to the mode tiers, and it was the blanket-closed posture there that denied it. #180 removes exactly
that. So after #180 the field failure #76 was filed from no longer reproduces on a blanket-closed
node, and only reproduces where DISCOVER is explicitly denied.

⇒ #76's condition must be **the chain's own DISCOVER verdict for that subject** (`AccessStore::decide`
with `surface::DISCOVER`), never rule-row presence. Same for the prescription half: prescribe
DISCOVER only where the binary would otherwise refuse it — which is the existing
REQ-KNOCK-PRESCRIBED-REMEDY-HONORED doctrine, and cli.rs:15953 already states the prescription is
composed from the SAME predicate the approve path uses, so the seam to reuse exists.

### F4 — #169 is one mechanism at three sites, plus a design gap the ratified set never had
All three consented notifications are produced by the SAME shape — recipient passed into the
`from_id` slot, `subnet = ""`, `NotifScope::Node`, written straight to the store:
- `cli.rs::knock_notify` :15246-15264 (local approve),
- `spt-daemon/src/answerop.rs::notify_knocker` :136-158 (cross-node approve, on the knocker's node),
- `spt-daemon/src/redeemop.rs::notify_minter` :278-300 (redeem, on the minter's node).

**F4a — nothing can ever surface those rows.** Two independent gates, both structural:
- They bypass the daemon's produce-and-fire front door (`produce_scoped_and_first_fire`,
  spt-daemon/src/notif.rs:361) and call `NotifStore::produce_scoped` directly ⇒ no live delivery.
- `subnet = ""` is unreachable from BOTH surfacing paths. `resurface_at_boundary` (:616-675) walks
  only `policy.subnets`, built from `SubnetStore::load().subnets` (:97-107) — no member subnet is
  named `""`. And `first_fire_at` resolves its target with
  `most_recently_active_on_node(.., &row.subnet)` (:288), which filters candidates through
  `policy.visible(id, "")` (presence.rs:185-199) — false for every endpoint ⇒ `NoTarget`.

  So the redeem row lands on the RIGHT node (the minter's) and still never appears: that is the #149
  field observation (lia never notified) with a mechanism under it.

**F4b — the cross-node approval notification is gated behind `--mutual`.** `serve_one_answer`
(answerop.rs:45) runs on the knocker's node for EVERY approval (the receipt is sent unconditionally
to a remote knocker — cli.rs:16573, and deliberately on refusals too), but `notify_knocker` sits
after the `ReceiptOutcome::Consumed(grant)` arm (:77, :130). A plain knock arms no pre-authorization,
so its receipt returns `Ignored` at :66 and no notification is even produced. The ordinary
(non-mutual) knocker is structurally un-notifiable today.

**F4c — the design gap (Q1).** `NotifRow` has **no addressee field** — which is why the recipient is
riding `from_id`. Delivery is by presence-MRA, so even a correctly-subnet'd node-scoped row goes to
whichever endpoint was most recently active on the node (14 endpoints on this box), not to the
knocker. An *addressed courtesy* is not expressible in the notif primitive as ratified.

### F5 — #184 is a two-line deletion with a live test surface
The footer text sits in `cli.rs` around :6667 and :6769 ("…nobody to ask"), with the distinction
between "we asked and heard nothing" and "there was nobody to ask" carried in a comment at :6667.
Per-row evidence labels stay. Expect assertions on the footer strings in the cli test module — the
retraction sweeps STRINGS, so the phrases must be hunted, not just the render site.

## Proposed mechanism (recon's recommendation — doyle rules)

### #180 — the default lives in the surface vocabulary, and one method reads it
1. The surface table already carries a per-surface discriminant (`Surface { id, attributable, desc }`,
   access.rs:179 for DISCOVER). Add `default_on: bool` to that SAME row — single-source discriminant:
   the vocabulary states its own default instead of a predicate scattered beside the chain.
2. `Modes` grows `for_surface_governing(surface)`: for a `default_on` surface, resolve `per_surface`
   only (exact → case-folded, keeping the releases#66 precedence rule verbatim); otherwise today's
   behavior. `for_surface` itself is left alone so every non-chain reader keeps its meaning.
3. All THREE mode tiers in `decide` route through it — one change honored at every tier, which is
   the ruling's own wording.
4. `MatchedTier` gains `SurfaceDefaultOpen` so the pass-reason is nameable rather than conflated with
   "nothing said anything" (ImplicitOpen). #163's notice and the trust-warning composer's
   pass-reason both need to distinguish them. Ripple is small and additive: 22 `MatchedTier::` sites
   in access.rs (mostly tests) + 8 elsewhere.
5. Degrade untouched (ruling 2) + a unit that pins it.

Units: blanket-closed node ⇒ DISCOVER allow, MSG deny (the whole flagship in one cell); explicit
`per_surface[DISCOVER] = closed` denies at each of the three tiers separately; a DISCOVER-naming deny
ROW still denies; case-folded `discover` key still honored; degraded store still refuses.

### #163 — pure classifier at the accept site
`classify_er_discover_conjunction(target_is_er, covers_discover, whitelist_len, posture) -> Option<Notice>`,
printed after a SUCCESSFUL mutation (never a refusal — ruled). Reads the ER record's
`advertises_to`/posture that `DiscoverGate::engine_room_discloses_to` (spt-daemon/access.rs:593-602)
already reads; the notice names the whitelist as the other lever and its current posture.

### #76 — one verdict, two consumers
Compute the subject's DISCOVER verdict through `AccessStore::decide` at write time (F3). If it is
Deny, state the consequence (the rule still lands). Same verdict feeds the knock prescription so the
pair is prescribed only where it is needed — correct by construction, and no second classifier
(cli.rs:16123-16129 records what a second copy of that classifier cost last time).

### #169 — recommend an addressed notif, narrow (Q1 is doyle's)
Preferred: `NotifRow` grows an optional addressee (additive serde, N-1-safe — the `SubscribeReq.code`
precedent), honored by `first_fire_at` (address wins over MRA) and by `resurface_at_boundary`
(skip rows addressed to another endpoint). Then all three producers move onto
`produce_scoped_and_first_fire` with a real member subnet, and F4b's producer moves ahead of the
`Consumed` arm so a plain knocker is notified too. ADR-0046 amendment rides the PR.

Rejected in recon, with reasons: (b) re-home the three notifications onto the message spool
(`spool_message_at`, the ER-briefing precedent) — addressed and cross-node by construction, but it
silently retires a ratified notif design and splits knock courtesies from every other notice;
(c) just give node-scoped rows a real subnet — smallest diff, but delivery stays presence-MRA, so on
a 14-endpoint node the approval notice lands on a bystander. (c) is not a fix, it is a louder miss.

### #184 — delete the two footer lines; sweep the phrases in tests.

## REQ mints (ride the build PR; doc/impl/unit active, int at gate)
`REQ-ACL-DISCOVER-DEFAULT-ON` · `REQ-KNOCK-EVIDENCE-ROUTE` (F2 leg; sibling of
REQ-UNLISTED-EVIDENCE-ROUTE, own gate) · `REQ-ACL-ER-DISCOVER-CONJUNCTION-NOTICE` ·
`REQ-ACL-FORK-WITHOUT-DISCOVER-CONSEQUENCE` · `REQ-KNOCK-PRESCRIBES-FORK-PAIR` ·
`REQ-KNOCK-NOTIFY-INSTIGATOR` (both arms + the delivery seam) · `REQ-UNLISTED-FOOTER-OMITTED`.
Names final at mint. Existing ids to tag against rather than re-mint: REQ-ACL-DISCOVER-GATE,
REQ-ACL-FAIL-CLOSED, REQ-ER-NOT-ADVERTISED, REQ-KNOCK-PRESCRIBED-REMEDY-HONORED.

## RULED — doyle 2026-08-17 (recon accepted whole; nothing is blocked)
- **Q1 RULED, recon's recommendation adopted.** `NotifRow` grows an optional addressee (additive
  serde, the `SubscribeReq.code` precedent), honored at BOTH surfacing paths: `first_fire_at` —
  address beats MRA; `resurface_at_boundary` — skip rows addressed elsewhere. All three producers
  move onto the produce-and-fire front door with a REAL member subnet — **the knock's or sealed
  code's own correlation subnet** (the one it rode), never `""`. Lift the cross-node approval
  producer AHEAD of the `Consumed` arm (F4b): the ratified set says approval → knocker
  unconditionally, not mutual-only. ADR-0046 amendment rides the PR. Both recon rejections ruled
  correct: the spool re-home would silently retire a ratified W4 grill decision (those three were
  deliberately put on the notif spool AGAINST inbox delivery), and subnet-only keeps MRA delivery.
  Units: per notification produce → fire → surface, plus one pinning that an addressed row never
  lands on a bystander (the 14-endpoints-on-a-box case IS the test).
- **Q2 RULED YES — `effective_posture` follows the carve-out.** Grounds: single-source (two readings
  of one fact must not diverge; #163's notice and ENDPOINTS_CAN_GRANT_NODES both read the posture
  side), and the `may_grant_node_subject` consequence is CONSISTENT rather than a hole — post-#180 a
  node-subject DISCOVER allow admits nothing the default already doesn't, and the
  standing-row-vs-later-ER-close shape is the same class every implicit-open surface (MSG today)
  already has. Existing answers: the ER's node-wide row-edit authority (W4) + the FF-T3 views.
  Pin with the both-readings unit.
- **Q3 CONFIRMED.** #76's condition derives from the chain's actual DISCOVER verdict; prescription
  prescribes DISCOVER only where the binary would otherwise refuse. **Note the re-derivation in
  #76's close-out comment** so the stale premise is not re-litigated later.
- **Q4 RULED — mint `MatchedTier::SurfaceDefaultOpen`.** A pass by surface default and a pass by
  chain bottom are different facts; cheap at mint, expensive at retrofit. Additive ripple accepted.
- **Q5 — my call from the render.** Two constraints: the deletion must not strand the UNLISTED
  section into ambiguity, and the retraction sweeps the STRINGS (hunt the phrases in test
  assertions, not just the render site).
- **F1 mechanism ACCEPTED as proposed. F2 ACCEPTED as #180's in-lane N-1 leg** — and the PR body must
  state why the leg is there (without it #180 buys nothing against the installed fleet).

## Order (approved)
#180 + F2 as one lane → #163 (reads #180's tier vocabulary) → #76 (condition derived from #180) →
#169 → #184 independent, any time.
Docs (CONTEXT.md carve-out, ADR-0046 amendment if Q1 lands that way, reference regen) ride the same
change as their code. Pre-flight discipline: expected suite scope + duration stated before any test
run. Pool claimed from the lane worktree; worktree under `.worktrees/` only.

## Lane notes — rulings taken DURING the build (todlando, 2026-08-17)

- **W1b (the ER off-switch verb) was ruled INTO the W1 lane** as greenlit FORM, not scope growth:
  #180's greenlit text promises a node owner can disable DISCOVER from their engine room, and
  post-carve-out no verb could. Shape ruled as proposed: `spt api access-node-surface-mode
  <SURFACE> <open|closed|unset> <id>`, `spt api` family (the gate, the auth proof and the ER
  capability vocabulary all live there), shared `gate()` verbatim, node tier only, `unset` a real
  third state. Endpoint-tier per-surface verb NOT minted — that is #154/CONCIERGE's beat.
- **Rider on `unset`:** the pinned-open vs default-open distinction must be VISIBLE in access
  views, with a unit. Followed into the views, where it turned out to be bigger than the pin: a
  blanket-closed summary that said only `closed` was asserting the reading the carve-out removed,
  so the default clause is rendered too. Two shipped view cells were asserting the now-false
  summary.
- **#163's redundant-grant notice: DO NOT ADD — the premise is FALSE, not merely out of scope**
  (doyle, 2026-08-17). An explicit `DISCOVER` allow row is NOT redundant under default-on: ROWS
  PRECEDE THE MODE TIERS, so the row keeps admitting its subject through a FUTURE explicit close
  that the default would not survive. A notice calling it redundant would teach operators the
  wrong model of their own rules. Do not re-raise this in #76 or later waves.
- **PR shape for the milestone:** thin lanes, each PR based on its predecessor branch so the diff
  is one wave only, landing via the golden chain (W3 #125 precedent) — never by merging a lane
  branch into its predecessor. #145 = W1+W1b (base main), #146 = W2/#163 (base build/nameplate-w1).
- **Blind-consumer review is where the setter defect was caught** (doyle on #145): a per-surface
  write bare-inserted while its clear folded case, so a legacy `discover` key plus a `DISCOVER`
  write left BOTH governing by asker's spelling. Fixed with one shared `Modes::set_named`/
  `clear_named` body for both tiers. The pattern worth keeping from that fix: a refusal cell needs
  an accepted write FIRST in the same rig, or "nothing was written" is unfalsifiable.
- **BUILD SIDE COMPLETE (2026-08-17).** Five PRs on `spt-bs-core`, each based on its predecessor,
  all gated PASS by doyle: #145 = W1+W1b (#180 + F2, base main) · #146 = W2 (#163) · #147 = W3
  (#76) · #148 = W4 (#169) · #149 = W5 (#184). doyle assembles once hertz's discover-repin lands.
  Lane worktree `.worktrees/nameplate-w1` sits on `build/nameplate-w5` @`2bd36f1` and still HOLDS
  the pool claim (label `nameplate-w5`) — the lanes are unlanded, so the claim correctly refuses a
  takeover until assembly lands them.
- **#184's kept legend is a RULING now, not a lane choice** (doyle RATIFIED the Q5 call): the two
  filed footer lines are gone, and the conditional `unknown` legend STAYS because the filing does
  not name it and its word cannot carry its own meaning at the point an operator acts on it (a
  relay-hosted peer cannot tell busy from offline). Do not "finish the job" by deleting it later.
  The sweep's own lesson beside it: the reader-parity cell's `contains("not asked")` asserts the
  per-ROW cell, and a string sweep that did not separate row vocabulary from footer text would have
  either red-ed it or, worse, left a green riding a substring the row still carries.
- **Echo/notice condition doctrine (now used twice):** a line that asserts a fact about the store
  must ASK the store — via the chain's own verdict, never a scan of rule rows. #76 reuses this
  read; the legacy-allow echo at `cli.rs` is the working example.
