Skip to content

#74 ticket half: generation = keyed permutation of the slot counter; a never-minted generation is a forgery (stacked on #1007) - #1018

Draft
MattJackson wants to merge 8 commits into
predevfrom
lane-proof-ticket-prp
Draft

MattJackson wants to merge 8 commits into
predevfrom
lane-proof-ticket-prp

Conversation

@MattJackson

@MattJackson MattJackson commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Coordinator ruling C. DRAFT, stacked on #1007 (rule 19).

  • Generation. A ticket's generation is now PRP_k(per-slot count): a 6-round Feistel network on 16-bit halves, SipHash-2-4 rounds under the #74 ABI half: HostCtx is a kernel-tagged handle verified before use (no host pointer crosses) #1007 key, tweak ("bb.tick1", worker, slot), mapped to nonzero values by cycle-walking.
    • It is a bijection, so per-slot uniqueness stays exact and the contract line is unchanged.
    • The layout is unchanged.
    • The instance is not in the tweak, because a slab ticket is minted before any instance uses it.
  • The key. It is drawn at the dispatcher's boot (handle::ticket_key(), a TicketKey only that function makes) and held in the dispatcher's Env. Every worker gets it at construction. There is no fallback key: a failed draw refuses Dispatcher::try_new / try_with_services with LoadError::Entropy.
  • Counter wrap. A slot whose counter reaches u32::MAX is retired. It is never put back on the free list, and the next mint takes a fresh slot. Worker replacement carries a retired slot as retired. A wrapped counter would re-mint generations the slot already handed out.
  • Judgement. The worker's equality check is the MAC check. On a mismatch, the inverse permutation names the mint the generation would be:
    • A count the slot has reached is a late wake (H2): dropped and counted stale, as before. This is exact, with no ring.
    • Anything else is a forgery, and is blamed on the ticket's supplier.
  • Supplier attribution. The supplier is carried explicitly with the wake (Msg::Wake(ticket, supplier)), never read off the thread the wake fires on.
    • host_wake names the instance its context resolved to.
    • A pended service's Completer, the connector's TicketWaker and the I/O Routed waker each carry the instance whose context created them (ticket::Waking).
    • The connection table's wakes name the caller the table registered the ticket for. The contract gains conn::ConnWake = Arc<dyn Fn(InstanceId, Ticket)>; the connector's WakeTicket is that type, and its TicketWake carries the owner. The dispatcher's conn_waker maps the owner to its handle entry, which becomes the supplier.
    • There is one numbering: an instance's connection-table identity is now its handle's id (handle::instance_id). The separate NEXT_INSTANCE counter is gone, so no second registry exists. The id is never 0, which stays the root's own (ROOT_OWNER). A wake under 0 names no supplier, and only such host-own wakes count to #74 ABI half: HostCtx is a kernel-tagged handle verified before use (no host pointer crosses) #1007's latch.
  • No quarantine under the worker's lock. apply collects each supplier; the worker quarantines after the lock is dropped.
  • Inline tickets (the calling-thread ops' slots, worker index INLINE_WORKER) are permuted as a worker slot's are: ticket_generation(key, INLINE_WORKER, slot, count), with a per-slot counter, retirement at u32::MAX (generation 0, never re-issued), and the same judgement.
    • A wake whose generation the slot minted is a late wake.
    • Any other wake, including a slot that does not exist, is Judged::Forged. The pool then calls forged_ticket(supplier, latch) with the inline table's lock already dropped.
    • One per-slot section. Each inline slot keeps its generation and its registered waker under one per-slot lock.
      • A recycle bumps or retires the generation and clears the waker inside it.
      • A wake checks the generation and takes the waker inside it, then wakes once the lock is released. A wake only schedules, and the waker taken belongs to that generation's task.
      • waker_of (what io.* hands the reactor) returns a waker bound to (slot, generation) that performs the same check. If it fires after a recycle and re-mint, it wakes nothing and blames no one.
  • A worker the dispatcher does not run. A wake for encode(w, _) with workers <= w < INLINE_WORKER is a forgery blamed on its supplier, judged lock-free in Pool::wake. It is no longer just a stale wake.

What this claims, and what it does not:

  • No false accusation. An instance is quarantined only for a generation its own context handed the host that the slot never minted. A wake that fires inside another instance's crossing does not blame that instance. A late wake for a generation the slot really minted is never a forgery, including on a retired slot.
  • Judged after the answer. The worker judges the forgery when it applies the wake, which is after the crossing that supplied the ticket returned. The forging op's own answer is therefore judged before the forgery is found, and that answer stands. Quarantine faults every subsequent op of the instance; it does not reach back to that op.
  • Every ticket a plugin can reach is either live or never-minted. A never-minted ticket is judged against its supplier: worker slot, inline slot, or a worker index nobody runs. A plugin cannot trip the process latch with tickets: every ticket it supplies, through a host service, its own wake or a connection slot, is blamed on it.

RED tests:

  • The permutation exhausted at 8-bit width under three keys/tweaks: bijection, nonzero, inverse.
  • Consecutive generations hide the counter.
  • Worker-tweak separation: the same slot index on two workers shares no sequence, and worker 0's generations do not pass as minted on worker 1.
  • A forged generation quarantines its supplier (honest control).
  • Attribution: A's completion fires inside B's crossing; A is quarantined and B is not.
  • Off-crossing: a host-own forged wake (root identity) counts toward the latch and blames no one. A plugin's own off-crossing wake quarantines its supplier and leaves the latch alone.
  • Connection slot: a plugin's read slot is handed a forged ticket. The table parks it under (caller, ticket) and wakes it through conn_waker. The supplier is quarantined; the latch has forged == 1, unattributed == 0.
  • Connector: every wake of a read parked by OWNER on ticket 7 is (OWNER, 7).
  • One numbering: instance identities are never 0, never repeat, and name their own handle entry. The root's identity and an unminted one name none.
  • Inline guess: a plugin wakes encode(INLINE_WORKER, i) at generation 1, which the old counter handed out first. No held inline generation is in 1..=1024. The miss quarantines the guesser and leaves another instance alone. A never-used inline slot is a forgery too, and the latch has forged == 2, unattributed == 0.
  • Inline wrap: an inline slot seeded at u32::MAX - 1 is handed out at u32::MAX, then retired and never re-issued. Late wakes for both of its generations are not forgeries.
  • I/O-held waker: the I/O holds a ticket's waker. The ticket is dropped and the slot re-minted to a new task, then the old waker fires: neither task is woken. Control: the new generation's own waker wakes the new task.
  • Wake/recycle race: a test hook drops and re-mints the slot between a wake's lookup and its section. The wake reaches no task and is judged Stale, not a forgery.
  • Unrun worker: a wake for encode(workers, 0) quarantines its supplier, and the latch has (1, 0).
  • Counter wrap: seeded at u32::MAX - 1, the slot is handed out at u32::MAX, then retired, and never re-issued. A late wake on its last generation is not a forgery.
  • A stale wake for a minted generation is counted stale and not as a forgery.
  • The fixture's stale mode wakes recycled real tickets.

…lot's counter; a never-minted one is a forgery

Coordinator ruling C. A ticket's generation was a bare per-slot counter: predictable, so a plugin
could name a generation it was never handed. It is now PRP_k(count): a six-round balanced Feistel
network of 16-bit halves whose rounds are SipHash-2-4 under the handle key, tweaked by
("bb.tick1", worker, slot), mapped onto the nonzero values by cycle-walking. A permutation of the
counter is a bijection, so uniqueness per slot is exact, as the counter's was, and the contract line
("unique per instance across all workers") is unchanged; the layout does not move (Ticket stays
{slot u32, generation u32}). The instance is not part of the tweak: a slab ticket is minted before
any instance is chosen for it.

The worker's equality check is the MAC check. A wake whose generation does not match the live one
is judged by the inverse: a generation the slot really minted (its count reached) is a late wake
(H2), dropped and counted stale as before -- exact, no ring needed; any other is a FORGERY:
handle::forged_ticket counts it, and the instance that woke it from inside its crossing (carried on
the wake message: Msg::Wake(ticket, culprit)) is quarantined (plugin::quarantine_entry, via the
entry's back-reference to its instance); with no crossing to blame it counts to #1007's latch.
Worker replacement carries the slot COUNTERS. Inline and connection tickets are unchanged.

REDs: handle_tests -- the permutation exhausted at an 8-bit width (bijection, nonzero, inverse) under
three keys/tweaks; consecutive generations hide their counter and invert exactly. dispatch_tests --
a forged ticket generation quarantines the instance that woke it (honest control); a stale wake for a
generation the slot minted is counted stale and not as a forgery; the stale-wake script mode now
wakes two recycled tickets the plugin really held.
…e counter

Per the #1007 re-review (no test reads the global forgery counter): the stale-wake RED wakes from
inside a crossing of its own instance, so a misjudged forgery would quarantine it, and asserts it is
not; the forged-generation RED asserts the quarantine and a revoked handle on a latch of its own.
…quarantine outside the worker's lock

- A slot whose mint counter reaches u32::MAX is retired: never back on the free list, carried
  retired across a worker replacement; the next mint takes a fresh slot.
- The supplier of a ticket is carried with its wake (ticket::Waking): host_wake names its resolved
  instance; Completer, TicketWaker and Routed carry the instance whose context made them. No wake
  reads the thread's crossing; the host's own wakes carry none and count to the latch.
- apply collects forged suppliers; the worker quarantines once its lock is dropped.
- The ticket key is a TicketKey drawn at the dispatcher's boot and held in Env; no zero fallback.
- REDs: counter wrap, attribution across crossings, worker-tweak separation, a supplier-less
  forged ticket counting toward the latch.
…cket's supplier

- conn::ConnWake = Arc<dyn Fn(InstanceId, Ticket)>: the connector's TicketWake carries the owner
  the read, wait, accept or upgrade registered under; TcpConns and the test doubles name it too.
- One numbering: an instance's connection-table identity is its handle's id (handle::instance_id);
  NEXT_INSTANCE is gone. conn_waker maps the owner to its entry (handle::supplier_of); the root's
  own identity 0 names none, and only its wakes count to the latch.
- REDs: a forged ticket through the read slot quarantines its supplier with the latch unmoved; the
  connector's wake names the caller it parked for; instance identities name their own entry.
… worker no one runs is a forgery

- Inline slots carry a mint counter; their generation is ticket_generation(key, INLINE_WORKER,
  slot, count), retired at u32::MAX (generation 0, never back on the free list). A wake that
  matches no held inline ticket is judged by the inverse: minted = late wake, never minted (or no
  such slot) = Judged::Forged, and the pool calls forged_ticket(supplier, latch) with the inline
  table's lock let go.
- Pool::wake: a slot naming a worker index past the dispatcher's workers (short of the inline
  worker) is a forgery blamed on its supplier, judged there, lock-free.
- recycled_generation is gone: no ticket generation is a plain counter any more.
- REDs: a guessed small inline generation quarantines its guesser, a never-used inline slot is a
  forgery; an inline slot is retired at its counter's end and its late wakes are not forgeries; a
  wake for encode(workers, 0) is a forgery.
…a held waker is bound to its generation

- Slot keeps (generation, waker) under one Mutex: recycle bumps or retires the generation and
  clears the waker there; a wake checks the generation and takes the waker there, waking once the
  section is let go, so a wake never takes a later generation's task's waker.
- waker_of hands out a GenerationWaker bound to (slot, generation): fired after a recycle and
  re-mint it wakes nothing and blames no one.
- REDs: an I/O-held waker fired after recycle + re-mint wakes neither task; a wake interleaved
  with recycle + re-mint (test hook between lookup and section) reaches no task, judged Stale.
Base automatically changed from lane-proof-abi-mac to predev October 15, 2026 05:33
MattJackson added a commit that referenced this pull request Oct 15, 2026
…eversing an unbounded zip; PB-29 cites its four tests by path; the deleted cold lane's paths are declared to qa-names

- plugin-loader dispatch worker: zip(0u32..) is not double-ended, so the free list is collected and
  reversed (lowest index popped first, as before). Compile red on every check (#1018).
- qa/design-bindings.json, qa/DESIGN-BINDINGS.md: PB-29's four 503-literal tests are declared in both
  busbar-llm and busbar-plane-llm's on_driver seam (#912's rehome), so each is cited as path::name.
- xtask abi_freeze CONDEMNED and one_memory_abi COLD_HOME keep naming abi/cold after #961 deleted it;
  each carries its qa-names declaration.
MattJackson added a commit that referenced this pull request Oct 15, 2026
…onns wrapper follow the merged shapes

From the full logs of 2382599:
- root door_steps PresentingConns: bind_auth/auth_of take and hand on the registration `member`
  (the connector's per-member binding, DeclaredConns; E0050/E0061 x2 each).
- root tests: the datagram cell's Got states fault/declared_len/end (#893 over #837); the two
  upgrade-row builds take the settings alone (HostWire build, #932 over #997); the two userinfo
  cells hand provider_routes a ResolvedProvider (#957 over #876/#936).
- tests/plane_blackbox.rs: the step's (key, text) pairs are a type (clippy type_complexity).
- config-schema snapshot: `type WakeTicket` is now an alias of ConnWake (#1018), recorded.
- Law 0: busbar x instance:export 25 -> 24 (measure); kernel x plane: a test doc names a plane's
  own webhooks section, not the llm plane's (1050 measured against w2's 1049).
MattJackson added a commit that referenced this pull request Oct 15, 2026
…ippy build named (bundle of d543b25)

- busbar-kernel tests: the rehomed lane-caps cells (#912) and the tenant merge cell read the
  resolved entry's table through LegacyLaneKeys, as #957's resolved provider carries the plane keys
  (E0599 lane_caps_for x15, E0609 organization/project x3).
- busbar-core-connector tests: the wake closures take (instance, ticket) (ConnWake since #1018,
  E0593 x3); the datagram head piece states fault/declared_len (E0063).
- busbar-plane-llm tests/conformance.rs: hydrate and start are the plane kind's slots (E0425).
- busbar-contract tests/object_safety.rs: StreamId is no longer imported (unused since #932).
- voice-conformance harness twilio_legs: std::slice::from_ref (clippy cloned_ref_to_slice_refs).

This branch has not been deployed

No deployments
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