Declarative Constellation Surface and the Reconciling Sync Mode — Reversible Auto, Human-Gated Remotes

Accepted

ontoref
ADR-062 fixed the constellation TAXONOMY (Primary | Addon | Projection) and the

Context

ADR-062 fixed the constellation TAXONOMY (Primary | Addon | Projection) and the forge rule (Forgejo canonical · GitHub one-way mirror · rad sovereign · vault never mirrored) as prose plus Soft/Hard constraints on ontoref-self's own repos. What it did NOT provide is a mechanism: how a project DECLARES its constellation and how the declared topology is RECONCILED against the filesystem — for first setup and, equally, for updating an existing partial constellation.

The gap surfaced concretely. ADR-013 already ships a per-project `vcs` block (manifest.ncl: primary/direction/mirrors) and a `vcs.nu` module (push --mirror, cross-project catalog, drift status) plus the `adopt_repo_distribution` mode — but all per-PROJECT. The constellation has multiple members, most of them NOT individually onboarded (outreach, vault, desktop are plain repos or not yet repos), so per-member manifests do not exist to aggregate. CI (Woodpecker) and the private dev-internal overlay (.coder/.claude, ADR-062 dev-internal-overlay-never-mirrored) were declarable nowhere at the constellation level. And the operator-facing question — "given the declared topology, what is done and what must I still do?" — had only hand-written runbooks as an answer.

The forces: a reconciler must (a) read a single declared source of the whole constellation, (b) converge the parts that are cheap to reverse without a human in the loop, yet (c) never silently perform the parts that are expensive to reverse or external — creating a Forgejo repo, pushing, wiring a mirror. A mode that created repos and pushed on its own would be convenient and wrong: it would mutate remotes from an inferred plan, exactly the failure `governed-delivery` (ADR-041) was built to prevent for Work Orders.

Decision

Add a declarative constellation surface and a reconciling mode that applies the reversible half itself and emits the irreversible half to a human.

DECLARATION (the single editable source of the topology) - reflection/schemas/constellation.ncl — a member is {name, path, category (Primary|Addon|Projection), canonical, sovereign+rid, mirror (Auto|Never), ci (None|Woodpecker|GithubActions), overlay, overlay_url}. The forge rule is a TYPED contract, not prose: a 'Projection owns no repo (canonical_url / mirror_url must be empty), a 'Never member carries no mirror, a 'Radicle sovereign requires a rid. Illegal combinations fail at nickel-export time. - .ontoref/ontology/constellation.ncl — the project's instance. Editing it is how the topology changes; the mode is how the change is applied.

RECONCILER (reflection/bin/constellation.nu — offline, install-independent) - plan — per member, declared-vs-actual + the [auto]/[you] next actions. - apply-auto — applies ONLY the reversible half: sets up the private overlay (never mirrored) for members that declare one and have content. Never inits a repo, never touches a remote. - check [--auto-only] — exit 1 on drift; --auto-only gates the reversible half. It NEVER mutates a remote. Server-side facts (does the forge repo exist? is the push-mirror wired?) are REPORTED as pending human steps, never asserted.

MODE (reflection/modes/constellation-sync.ncl) guard(declaration parses) → plan(report) → apply_overlays(reversible, agent) → emit_remote_steps(actor = 'Human: create repo / push / wire mirror). A postcheck gates that the reversible overlay job is done; full convergence includes the human steps and the mode is re-run idempotently to reconverge.

BOUNDARY (the load-bearing rule). The mode's automated steps are exactly the reversible ones. Every remote-creating or remote-pushing action is actor 'Human and emitted as instruction — the same human-gated boundary as governed-delivery's witnessed signing. Adoption is opt-in: a single-repo project declares nothing and the mode is inert.

Constraints

  • Soft A constellation's topology is a declared source (.ontoref/ontology/constellation.ncl conforming to the Constellation schema), not inferred from the filesystem.
  • Hard The forge rule (Projection owns no repo, Never carries no mirror, Radicle needs a rid) is enforced by a typed schema contract that fails nickel-export on violation, not by prose.
  • Hard The constellation-sync mode never creates a repo or pushes to a remote in an automated step; every remote-creating/pushing action is actor 'Human and emitted as instruction.
  • Soft The reconciler (constellation.nu) inspects git remotes/dirs locally and reports server-side facts as pending; it issues no remote-mutating git command.

Alternatives considered

  • Extend adopt_repo_distribution to iterate over members.rejected: Conflates two responsibilities: adopt_repo_distribution declares ONE member's backend; constellation-sync reconciles the WHOLE constellation and emits human remote steps. Growing one mode to do both muddies its contract; a separate mode keeps each single-purpose.
  • A full-auto mode that creates Forgejo repos and pushes via API tokens.rejected: Mutating remotes from an inferred plan is exactly the failure governed-delivery (ADR-041) prevents. Creating a repo and pushing are expensive to reverse and external; automating them trades a real sovereignty/safety risk for convenience. Human-gated emission preserves the boundary.
  • Keep the pattern as static runbooks / a QA entry only.rejected: Docs cannot enforce the forge rule or report per-member drift. The typed contract + reconciler make the rule executable and the state queryable; the runbook survives as the mode's template payload, not the mechanism.
  • Infer the constellation by scanning the filesystem for repos.rejected: Inference cannot express intent (a member declared but not yet materialized, a Projection that must NOT become a repo, a vault that must NOT be mirrored). A declaration carries intent the filesystem lacks.

Anti-patterns

  • A Sync Mode That Creates Repos and Pushes On Its Own — Letting a reconciling mode create Forgejo repos, push, or wire mirrors from an inferred plan for convenience. Mutates external, expensive-to-reverse state without a human deciding — the failure governed-delivery prevents for Work Orders, reintroduced at the constellation layer.
  • Reconstructing the Constellation From the Filesystem — Deriving the constellation's membership and forge policy by scanning for repos instead of reading a declaration. Loses intent: a member declared but not yet materialized, a Projection that must NOT be a repo, a vault that must NOT be mirrored — none are inferable from the tree.
  • Merging the Member Backend and the Constellation Policy — Treating the per-project manifest `vcs` block (ADR-013, a member's working backend) and the constellation declaration (this ADR, the constellation's forge policy) as one surface. They describe different scopes; collapsing them re-couples a member's release cadence to the constellation topology.

Related ADRs

ADR-062 · ADR-048 · ADR-013 · ADR-041 · ADR-028 · ADR-054 · ADR-026 · ADR-014

Was this useful? Rate it
Got something to add? Tell me what you think, what you'd suggest, or whether we should keep exploring this topic.
· reads

We use cookies to help this site function, understand service usage, and support marketing efforts. Cookie Policy for more info.