Files
alkblobs/docs/architecture/ops-surface.md
T
glm-5.3-flash b18521a0e6 docs(architecture): dissolve OQ-09's self-referential deferral
- OQ-09 resolved closed-by-default (deny-unlisted), decided on risk
  asymmetry in hand, not on a first deployment the crate itself must
  create; named reopen condition instead of a parked wait
- delete oq-09-ops-visibility-tracker.md (watch-task for an event the
  crate itself gates — never collapses)
- fix doc-lifecycle circularity: externally-owned OQs no longer block
  a spec's `reviewed` promotion
- gc-and-namespaces: "until a consumer names a requirement" reframed
  as re-entry via a new ADR, not a parked question
- README: state the corollary explicitly — facts this crate must
  create (its own first deployment/consumer) are never deciding inputs

Verified: full-text sweep for stale OQ-09 deferral/tracker references
comes up clean across docs/ and tasks/.
2026-10-02 06:14:09 +00:00

8.0 KiB

status, last_updated
status last_updated
draft 2026-10-02

Ops surface (alkcall-backed network operations)

What this is

The network-facing operation family over the store: have/need announcements, verified fetch, and streaming put — riding the alkcall substrate behind a feature-gated ops module in this crate (feature ops, default-off; ADR-001). The base crate stays lean and alkcall-free; enabling ops adds the alkcall dependency and registers the op family.

Why alkcall (ADR-001, the short form)

The op family is exactly alkcall's mixed-shape sweet spot (validated machinery, not parallel invention):

  • JSON control plane — have/need announcements, stat-shaped probes, offer/carry lengths: alkcall OperationSpec + JSON Schema validation (alkcall ADR-016) + typed error schemas + from_call discovery + External/Internal visibility.
  • Binary data plane — bulk bytes one way (fetch: Sub, alkcall ADR-021) or the other (put: Pub/Sink, alkcall ADR-046), allocated via the channel_open marker (alkcall ADR-047); established pump patterns (pump_bidi, alkcall ADR-050).
  • ACL is solved there — AccessControl + ownership checks (alkcall ADR-011) under the ADR-017 privilege model map onto namespace gating directly; an in-band invented scheme would be a second, unreviewed authorization story in the family (AGENTS convention 6).

Producer/consumer vocabulary throughout; no transport enters either layer — the caller (an embedder, alkgit's replicator, a future alkfs sync) dials and hands an established Connection in (alkcall is a pure protocol crate; ADR-012 there).

Op family (WHAT is exposed)

Namespaces on the wire. Every ops payload carries an explicit namespace field — the consumer-scoped identifier the embedder's registry recognizes (syntax: non-empty UTF-8 string, embedder-validated; the minting of namespaces is the embedder's act — e.g. alkgit's registry maps repo ids to namespaces, alkfs maps workspace roots). The store core never sees this string (backends are namespace-blind, ADR-005); it selects the liveness source and the ACL resource. The ops module's job is to bind namespace → the embedder-registered liveness source for pin conversion, and to present the namespace as the alkcall ACL resource.

JSON control ops (Visibility per ADR-001 §Decision):

  • blobs/stat {namespace, digest} → {len} — probe before offering; ACL-gated like fetch (read action on the namespace)
  • blobs/have {namespace, digests[]} → {present[]} — the have half of have/need set diffing (hashes only — content never traverses this op); read-gated: an existence probe over arbitrary digests is a discovery surface, so it is gated exactly like fetch, not public
  • blobs/delete {namespace, digests[]} — operator machinery; Visibility::Internal, evaluated under the internal authority context per alkcall ADR-017 (internal calls switch authority context, never skip ACL): requires the embedder's operator/admin authority context, not a namespace grant. The namespace field exists for audit/triage scoping (which slice of the pool the deletion targets), not for gating.

Binary channel ops (registered via the channel_open marker):

  • blobs/fetch (Sub — the server→client streaming op shape) — {namespace, digest, ranges?} in; verified bytes out: the consumer hash-checks each received chunk/whole against the carried digest (ADR-006). Broadcast fanout above the store: one reader, store arm + subscriber arms (POC #3 finding A3); late joiners degrade to a normal post-commit get — identical bytes under CAS. Slow-subscriber policy (drop-and-late-join) is ops-layer policy, not a store concern.
  • blobs/put (Sink — the client→server streaming op shape) — {namespace, digest?, len?} offer + byte stream in; server verifies against the canonical derivation before commit. A known-length offer is the encouraged path (one- pass); unknown-length rides the store's pre-threshold buffering path (ADR-003). Need half of have/need: a fetch miss is the need announcement — the consumer computes its need set by diffing blobs/have results and fetches the absent digests; no separate need op exists (the diff is caller-side; the wire carries only concrete fetch requests).

Pin hand-over for remote puts (the cross-wire contract). The server-side put handler owns the Pin guard; the offering side's reference registration happens in the remote process, invisible to the server. The hand-over contract:

  1. the put lands pinned (ADR-005) — the entry cannot be swept while the handler holds the pin;
  2. the blobs/put response returns a pin token (opaque handle); the putter can later confirm its registration landed by observing the digest present via blobs/have;
  3. the embedder's registered liveness source is the actual root of record: the contract on the embedder (documented on the ops module) is that it registers the namespace's roots such that a digest referenced by a namespace's manifest is either (a) already registered before the caller's next sweep could run on the serving node, or (b) protected by holding the pin token until its registration is confirmed (blobs/have returns present after registration). In short: the remote putter may not rely on an unconfirmed put surviving the next sweep — it holds its pin token until its registration shows up in blobs/have. A Pin conversion on the server side (token → registered liveness source) is the mechanism the embedder plugs its registry into.

Placement of the op registration (which embedders wire where they want them exposed) matches the alkgit ops pattern (its git/repo/* call ops): the module exports spec/handler pairs; the embedder assembles.

ACL mapping

Resources and actions, evaluated by alkcall's AccessControl machinery at op entry — this module never re-implements an authorization check (convention 6; ADR-001):

  • Namespace = resource. resource_type: "blob-namespace", resource_id_path selecting the payload's namespace field; actions map onto read/put/manage:
    • blobs/fetch, blobs/stat, blobs/have — the read action; unlisted namespaces (no ACL entries) deny by default (OQ-09's resolution; a grant per namespace is the deferred cost, deliberately paid over accidental exposure)
    • blobs/put — the write action
    • blobs/delete — not namespace-gated at all; internal authority context only (alkcall ADR-017: internal switches context, never skips ACL)

Store-facing relationship

The ops module is a consumer of the store core (store-api.md), not a side channel: fetch handlers call get/read_range/stat; put handlers call put; both honor pins and delete windows. There is no ops-internal path around the store facade (ADR-001 §Consequences — this is what makes "re-homing the ops surface later loses nothing" true).

Design Decisions

ADR Decision Summary
001 Substrate posture & placement alkcall-backed, feature-gated ops module; conformance-not-deviation rationale
006 Verification verified-fetch via carried digests; no chunk trees

Open Questions

None owned by this document. OQ-09 (default namespace visibility) resolved closed-by-default — recorded in open-questions.md with its reopen condition (a named requirement for open-read-by-default, never mere existence of a deployment).

References

  • docs/research/phase-0.md OQ-BL-01 (the substrate settlement + its justification, promoted verbatim into ADR-001 §Context)
  • alkcall docs/architecture — ADR-011/012/016/017/021/046/047/050 (the machinery this module rides)
  • docs/research/poc-largeblob-findings.md finding A3 (fanout seam)
  • ADR-001; store-api.md; gc-and-namespaces.md (namespace-as-resource)