8.4 KiB
8.4 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ops-foundation | the ops module's substrate — the token layer, the namespace binding, and the fanout registry | pending | moderate | medium | component | implementation |
|
Description
The ops feature's state substrate in src/ops.rs (currently the
ADR-001 stub doc comment) — everything the five op handlers ride, and
none of the handlers themselves (they land in the following tasks).
The module is a store-facade consumer (ADR-001 §4): its store
access is the public facade plus the crate-internal renewal seam named
below — no backend side-door, no engine types.
- Module structure + the ops context: the feature-gated module
shape (
#[cfg(feature = "ops")], re-exported fromlib.rsper the one-module-per-file convention); the ops-layer context type binding theArc<Store>, the module's state (the token registry, the namespace bindings, the fanout registry), and the module config. This context is what the handler pairs (later tasks) capture. - The pin-token layer (ADR-010 §4; ADR-012 §2): opaque token
minting over a landed put's pin; the token↔(key, owner) in-memory
mapping with process lifetime (outlives the minting handler —
ADR-012 §2's correction of the original handler-lifetime wording);
the validity domain is the minting node — the registry is
per-process state, there is no cross-node form, and fleet peers
refuse tokens they did not mint (
present: false, not an error). Named design points: the token's minting form (a CSPRNG id — e.g.getrandomgated behindops; a counter or derivable form is rejected as a guessable capability — decide and record), and the mapping's retention discipline (when a mapping retires: conversion-confirmed or process end; the mapping carries no expiry machinery of its own — the pin row's expiry is the fleet liveness signal, and nothing expires non-fleet). - The guard-custody design (the fleet/non-fleet split ADR-010 §4
pins): under fleet, the put handler's
Pinguard drops at response completion — the pin row's expiry is the only protection from that moment, which is exactly why the TTL/3 renewal duty exists (a guard held for the token's whole life would make the TTL machinery dead code on the ops path); under non-fleet, the token registry holds the guard — the token is the in-process guard's handle (ADR-012 §2's generalization: sqlite, single-instance postgres, pg-lo,local,mem— nothing expires outside fleet mode) until the embedder's registration confirms and the guard converts. The custody split is the token layer's core branch. - The namespace→liveness-source binding (ops-surface.md §Op
family, hand-over contract step 3): the embedder's converter seam —
per-namespace registration of the converter the embedder plugs its
registry into ("A
Pinconversion on the server side (token → registered liveness source) is the mechanism the embedder plugs its registry into"). The conversion rides the crate-internalRegistryCore::convertmachinery (wave 2). Named design point: ** the conversion trigger** (an embedder-called confirm verb vs a probe-triggered conversion — decide and record; the hand-over contract's duty text is the embedder's, the mechanism is this module's). - The crate-internal store-core renewal access (ADR-010 §4: "the
ops module is in-crate and crosses the seam by construction, the
same way the put handler holds the
Pinguard"): the renewal probe's access topin_renew(key, owner)andpin_check(key)through the fleet host — thepin_checkdeliberately-unconsumed note from the wave-6 review (W6-3) names this consumer. The exact probe composition (verify-the-mapping-then-renew vs renew-answers-the-verification) is a named design point against ADR-012 §2's three-step handler duty. What never happens: a * consumer* reaching the seam, or a public-API widening for this. - The fanout registry (ops-surface.md's fetch fanout seam; POC #3
finding A3): per-digest broadcast state — one reader, store arm +
subscriber arms; bounded subscriber channels (the backpressure
discipline: a slow subscriber stalls the broadcast, which is
correct for a put — subscriber count bounded, slow-subscriber
handling is policy); the late-joiner signal (broadcast ended →
the degrade-to-verified-get path); the drop-and-late-join
slow-subscriber policy as ops-layer policy. The producer wiring
lands with
ops-put, the consumer wiring withops-fetch; the registry's mechanics land here, unit-tested standalone. - The renewal-cadence boundary (ADR-013 §1): the module ships the renewal mechanism (the have form's handler); the TTL/3 cadence — the putter's renewal worker — is embedder composition above the facade (scheduler/queues), never in-crate machinery. Documented on the module, not implemented.
- Dependency posture:
serde/serde_json(payload handling) and the token CSPRNG enter asops-gated dependencies — the base crate stays lean (convention 8; ADR-013 §4's engine table unchanged —opspulls alkcall plus these, default-off). - Unit tests: token mint/lookup/retirement and the mapping's process-lifetime semantics; the custody split (the fleet and non-fleet forms branch as designed); the namespace binding's converter invocation; the fanout registry's subscribe / late-joiner-degrade / slow-subscriber-drop mechanics.
Acceptance Criteria
src/ops.rscarries the module structure under featureops: the ops context, the token layer, the namespace binding, the fanout registry — no op handlers yet (they land in ops-json-ops/ops-fetch/ops-put)- The token layer: opaque, unguessable minting (the CSPRNG design point decided and recorded); the token↔(key, owner) mapping with process lifetime; validity domain = the minting node (a per-process registry; no cross-node form exists)
- The guard-custody design decided and recorded: fleet — the guard drops at response completion (the row's expiry is the liveness signal); non-fleet — the registry holds the guard (nothing expires) until conversion
- The namespace→liveness-source binding: per-namespace converter
registration riding
RegistryCore::convert; the conversion trigger design decided and recorded - The crate-internal renewal access: the ops module reaches
pin_renew/pin_checkthrough the fleet host (in-crate seam crossing; no public-API widening; no backend side-door) - The fanout registry: per-digest broadcast, bounded subscriber channels, the late-joiner degrade signal, the drop-and-late-join policy point recorded
- New dependencies (
serde,serde_json, the CSPRNG) areops-gated; the default build's dependency tree is unchanged - Unit tests green;
cargo test(default) green with the module compiled out;cargo test --features opsgreen;cargo clippy --all-targets -- -D warningsandcargo fmt --checkgreen on both shapes;cargo doc --no-depsclean
References
- docs/architecture/ops-surface.md (§Op family — the hand-over contract; §ACL mapping; §Store-facing relationship)
- docs/architecture/decisions/001-substrate-posture-and-ops-placement.md §3/§4 (placement; the facade-consumer rule)
- docs/architecture/decisions/010-backend-io-seams-and-fleet-state-home.md §4 (the token contract: minting, custody, validity domain, the in-crate seam crossing)
- docs/architecture/decisions/012-pre-decomposition-consistency-rulings.md §2 (the wire shapes: {token, digest}; the have renewal form; the validity domain; the non-fleet generalization)
- docs/architecture/decisions/013-family-composition-reactive-boundary-and-feature-graph.md §1 (the renewal cadence is embedder composition), §4 (the feature graph)
- docs/research/poc-largeblob-findings.md finding A3 (the fanout seam shape)
- src/ops.rs (the ADR-001 stub this task fills)
- src/gc.rs (
EngineFleetState::pin_renew/pin_check— the seam the renewal access crosses;RegistryCore::convert) - src/store.rs (
Store::gc()— the crate-internal access path) - tasks/review-wave-6.md (W6-3:
pin_checkdeliberately unconsumed — this wave's renewal probe is the named consumer)
Notes
To be filled by implementation agent
Summary
To be filled on completion