Skip to content

MST/2 T05: composed-scope publication identity and real writer-epoch fencing #15

Description

@Ivanbeethoven

Context

Two pieces of the publication model (spec 09) are still placeholders.

1. scope → namespace mapping

MST2 resolve currently derives the namespace it reports a sequence for by
normalising the request scope:

// src/api/router/snapshot_router.rs (resolve)
let namespace = state.storage.mono_storage().normalize_namespace(&req.scope);
let durable = state.storage.mono_storage().publication_sequence(&namespace).await?;

normalize_namespace (src/jupiter/storage/mono_storage.rs) is just
"leading slash, no trailing slash". That is correct only while a scope names
one native namespace, which is true for the default scope and for deployments
without composite bindings — but not once a scope is composed from several
sources (import bindings, aggregates, subdirectory checkouts). A composed scope
has to report a publication identity that reflects the set of sources it was
resolved from, otherwise two different composed views can share a sequence
number.

2. writer epoch

mst2_publication.writer_epoch exists and the coordinator fences on it
(PublicationCoordinator::prepare rejects a writer whose epoch is behind the
namespace's), but nothing assigns or advances an epoch: the column is
written as a constant 1. Spec 09 §9 requires that once a deployment starts
publishing, an old binary that does not participate in the plan must be fenced
out rather than allowed to write refs directly.

Scope

  1. Namespace map. Introduce an explicit mapping from a resolved view to the
    publication namespace(s) it must be checked against:

    • native scope → its own namespace (today's behaviour);
    • composed scope → a deterministic identity derived from the ordered set of
      source identities and their publication sequences, stored so a later
      resolve can compare like with like;
    • resolve must return publication_sequence such that an identical
      composition yields an identical value and a different composition cannot
      collide.
      The mapping belongs next to the descriptor construction
      (src/ceres/snapshot/descriptor.rs), not in the router.
  2. Writer epoch storage and fencing.

    • Persist the current epoch per namespace (the mst2_namespace_seq.epoch
      column already exists).
    • Accept an epoch from each writer (config value read at startup is the
      simplest honest source) and record it on the receipt.
    • When an epoch is advanced, writers below it must fail their next prepare
      with CONFLICT, and the failure must be visible in logs/metrics rather
      than looking like an ordinary CAS race.
    • Document the cutover procedure (stop old writers → advance epoch → start
      new writers) in the spec 09 §9 rollout runbook.

Acceptance

  • A test where a composed scope built from sources A+B and one built from A+C
    report different publication identities even when A's sequence is equal.
  • A test where epoch is advanced and a stale-epoch writer is refused with no
    ref movement, plus a test that a stale reader is unaffected.
  • resolve with mst2.publication_enabled=false must keep reporting the
    provisional per-tip sequence (no behaviour change for deployments that have
    not opted in).

References

  • Spec 09 §2 (canonical plan, expected head/epoch), §7 (data structures), §9
    (enablement gates)
  • src/ceres/snapshot/publication.rs, src/api/router/snapshot_router.rs,
    src/jupiter/storage/mono_storage.rs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions