Bitspark constellation
accepted (2026-10-09, seat:ccca@atlas standing in for the unheld architecture seat, with the release-inventory addition in section 3) source ↗

ADR 0037 — membership is a contract of obligations, each with an enforcement point; a central profile says how a member meets it

  • Status: accepted (2026-10-09, seat:ccca@atlas standing in for the unheld architecture seat, with the release-inventory addition in section 3)
  • Date: 2026-10-09
  • Builds on: ADR 0014 (the unified development process and its .atlas/process.json pin), ADR 0019 (private members in public projections: what private means), ADR 0027 (Gate A before merge, Gate B before catalog acceptance)
  • Relates: research 0005 (research-docs/0005-public-member-conformance.md, rows 1, 7 and 12, accepted by the owner in #995), #1066 (this record), #1067 (obligation-level evidence in the collector), #1068 / #1094 (central convergence for public members), #1148 (Gate A's self-identity comes from the PR)

A member is a member because it meets every obligation at that obligation's enforcement point, with evidence. A typed profile held in atlas's registry says HOW it meets them — which gate transport, which runners, which convergence transport, which release-evidence route. Visibility chooses the HOW, never the WHETHER, and a member's own pull request cannot choose or change its profile.

Context

Until October 2026 every member was private, and "a conformant member" was measured as "carries the files": the thin caller of atlas's private reusable gate, the vendored scaffold, the process pin, a fresh docs manifest. That encoding broke the day a member went public. A public repository:

  • cannot call a reusable workflow in a private repository;
  • receives no organization secrets;
  • may not run on the shared self-hosted pool (the baci runner group excludes public repositories);
  • and therefore cannot fetch the private CLI every scaffold receiver downloads.

archon, then deixis, then ontos went public. Each one, measured by files, read "not wired" while it was in fact gated: by an organization ruleset requiring Bitspark/atlas-gate's gate.yml at an approved commit (#1065, #1083). Each first needed an owned onboarding exception (#1060) to stop reddening the dashboard. Two further problems surfaced:

  • private was carrying meaning it was never given. ADR 0019 defines it as a presentation flag: render the name unlinked. It was never an enforcement switch, and treating it as one would let a cosmetic edit change how a member is gated.
  • The member's own files were the only source of who it is. Gate A takes the repository's identity from the PR's atlas.json (#1148), and nothing says centrally which gate a member must run. So a PR that swaps the thin caller for a no-op, renames itself, or deletes a check can only be caught by whatever happens to notice later.

research 0005 asked how to govern a public member when the governance machinery is private. Its answer, graded and accepted, is that one contract does not need one execution location, one credential model, or identical files. It needs equivalent obligations, explicit enforcement points, and trustworthy evidence. The contract already mixed blocking PR checks, advisory checks, central observations and release-ingestion checks; it had just never written down which was which.

Decision

1. The membership contract is a list of obligations, each with an enforcement point

obligation enforcement point evidence central responsibility
Gate A: exact critical pins, one version per language, in every resolved graph before merge, on every candidate an effective required check whose result proves Gate A ran (a skipped or tokenless run is not a pass, #1069) the catalog classification; auditing execution and coverage (#1070)
a valid atlas.json before merge: deletion, a malformed schema and an unauthorized identity change are blocked the manifest check at the head; the identity the registry holds global topology and cross-member relationships
an effective, authorized PR gate (replaces "carries the literal thin caller") before merge: a change that disables or substitutes the gate must not bypass it the gate rule GitHub actually enforces on the default branch (rules API), matched to the profile's gate transport observing the actual rule and the approved checker commit
gate and security configuration before merge the same rule, plus protected paths —
ordinary scaffold repaired centrally; drift is not a merge blocker conformity to the member's pinned bundle, and freshness against the current one, reported separately rendering the bundle and converging it (#1068)
process pin and agent instructions after onboarding, the pin's loss blocks; instruction-file presence is a cheap check where practical .atlas/process.json at the head; the agent docs' presence and claims approving content and migrating versions within a deadline
current documentation index verified centrally; local checks may help but need not block code the docs manifest's freshness against the member's docs detecting stale indexes, broken cross-repository links and figure drift
Gate B: release coherence and verified hashes before catalog acceptance, not on every PR the ingest verification of the captured release; the release inventory's record that the release is owed ingestion; explicit ecosystem coverage; for a registry-capture member, deriving the obligation itself (section 3)

A platform limit must never quietly move an obligation's enforcement point. If a member cannot run a check where the table puts it, a different mechanism must enforce it there. A public member cannot run the private gate before merge, so a public, commit-pinned gate runs before merge instead. "Verified centrally later" is not that. Equally, an obligation the table places centrally (docs freshness, ordinary scaffold drift) does not become a merge blocker merely because it could be one.

Conformity and freshness are different facts. A member can conform exactly to the bundle, contract version or gate commit it pins while a newer one awaits adoption. Both are reported; neither is called "current" on its own.

2. A typed membership profile, held in atlas's registry, says how a member meets the contract

Each member's entry in topology/registry.json carries a profile:

field values today meaning
gate private-reusable · public-ruleset how the before-merge gate is selected: a SHA-pinned call of atlas's private reusable substrate-member.yml, or the organization ruleset requiring Bitspark/atlas-gate's gate.yml at an approved commit
runner shared-self-hosted · github-hosted where the member's gate runs
convergence receivers · central-pr how centrally owned files reach it: dispatched receivers inside the member, or pull requests atlas opens (atlas public converge)
releaseEvidence recorder · registry-capture how a release reaches the catalog: the member's own release recorder, or atlas capturing the published coordinates from the public registries

The profile is policy: what atlas requires. It is kept apart from two other things that are easy to conflate with it:

  • private stays a presentation flag (ADR 0019). It never selects enforcement.
  • observed GitHub visibility is a fact the collector reads. A profile that disagrees with the observed facts (a public-ruleset member with no such rule enforced, a private-reusable member that turns out to be public) is a finding, not a silent re-classification.

A member pull request cannot select or change its profile. It lives in atlas, so changing it is a reviewed atlas change. For the same reason, the registry, not the member's atlas.json, is the authority for a member's identity: its name, its repository, and the GitHub repository id the collector verifies its checkout against (#1067). Gate A's self-exclusion is bound to it (#1148), so a PR cannot rename itself to hide a sibling's pins.

New values are added by amending this record's table, never by a member-specific exception. The next public member is a profile selection, an identity record, a shadow run and an activation, not another onboarding exception.

3. The collector measures obligations against the profile

atlas constellation collect reads each member's profile and judges the gate obligation against it:

  • private-reusable: the thin caller is present and pins an approved commit;
  • public-ruleset: an active rule on the default branch requires atlas-gate's gate.yml at the approved commit (what #1083 already measures).

A member with no profile reads unprofiled and is reported, never silently treated as private.

The release inventory reads the profile too (added at acceptance by seat:ccca@atlas, measured). The reconciler (#1076) learns that a release is owed from substrate/release-facts*.json at each tag. A registry-capture member has no private recorder, so it carries no facts, and every one of its releases yields zero obligations. On 2026-10-09 archon v0.15.0 was tagged and released while the BOM held v0.14.0, and the reconciler reported owed=0: nothing knew the release existed. That is a platform limit quietly moving an obligation, which section 1 forbids. So:

For releaseEvidence: registry-capture, the release inventory derives the obligation from the member's release tags against the BOM, with no member-declared facts. It is owned centrally (seat:ccca@atlas, the capturer), not by the member's seat. Obligation-level evidence records (result, applicability, freshness, subject commit, checker identity, evidence location) are #1067's to build; this record fixes what they are evidence of.

Consequences

  • Onboarding exceptions stop being the way a public member gets measured. The ledger (substrate/onboarding-exceptions.json) returns to what it is for: a time-bounded, owned gap. It is not a second, implicit profile.
  • _members in governance/ruleset-public-members.json becomes derived. It must equal the set of members whose profile says public-ruleset. A disagreement is a finding: the ruleset's repository list and the profiles are two records of one fact until the record is generated from the profiles.
  • Every enforcement point is now a claim someone can check. "Before merge" means a required check whose result proves the gate ran. That is why #1069 (a tokenless run must fail) and #1070 (Gate A must read every graph) were preconditions, not side quests.
  • Cost: the registry carries policy. A profile edit is a governance change and is reviewed as one. It also re-stales the generated figures through the topology digest, like any registry edit.
  • Cost: two gate transports are maintained. The private reusable gate and the public export run the same Gate A functions (atlas gate export assembles them verbatim), but the public one only advances when it is re-exported and its ruleset commit moves. Its drift is real and measured: on 2026-10-09 atlas-gate still carried the catalog of 2026-10-04 (#1146).
  • Not decided here: which identity opens convergence PRs and approves releases (#1064), and the shape of the evidence records (#1067).

The Bitspark constellation — how the systems are built and relate.

GitHub