Convention Bindings and Shared-Source Propagation — Declared Guidelines/CI/Config, Synced From a Shared Origin Through the Update Mechanism

Accepted

ontoref
A project's working conventions are NOT captured by ontoref today, even though

Context

A project's working conventions are NOT captured by ontoref today, even though they are load-bearing for every contributor and agent:

- GUIDELINES — per-language coding rules. In this ecosystem they are SHARED: .claude/guidelines/<lang>/ is a real directory whose individual *.md files are PER-FILE symlinks to /Users/Akasha/Tools/dev-system/languages/<lang>/guidelines/*.md (verified: e.g. .claude/guidelines/rust/ACTIX.md → dev-system/.../rust/guidelines/ACTIX.md). dev-system is the single origin; many projects symlink the same source. The same origin also feeds NON-Claude consumers (just-modules/, ci/, workflow.md), so the guidelines are project conventions of which Claude Code is only one consumer — not Claude-specific config. A project's OWN guideline is simply a real .md file sitting beside the symlinked ones in the same directory. - CI — already modeled: the workflow layer (ADR-038 et al.) declares validations/builds/distributions and generates .woodpecker/*, pre-commit, justfiles. CI is declared; it is just not cross-referenced from a single "what conventions does this project use" surface. - CONFIG conventions — how a project's config is structured (config.ncl shape, .cargo layout, env wiring). Partly surfaced by `describe config`.

ontoref already has adjacent surface: `describe guides` reads `language_guides` from the .claude/guidelines/*/ glob, `card.ncl` is the project beacon ("what IS this project"), and `describe config` reports the runtime config. What is missing is (a) a DECLARATION of which guideline/convention sources a project binds and at what version, and (b) a propagation/SYNC mechanism so a shared change — e.g. a new Rust version bumping the guideline — reaches every project, and a project's own refinement can flow back, without hand-managing symlinks per project.

The propagation need is the heart of this ADR. Symlinks propagate transparently but only on one machine, are invisible to ontoref's queryable surface, break on clone/CI/containers, and cannot be versioned or audited. The protocol already owns the right mechanism for "a change consumers must adopt": the migration system (ADR-010) plus the `update_ontoref` mode. Convention bindings should ride that mechanism, not a second ad-hoc symlink convention.

Two named Spiral Tensions from `.ontoref/ontology/core.ncl` are engaged:

- formalization-vs-adoption — declaring conventions raises formalization; forcing every project to vendor/copy them raises adoption cost. Its resolution ("optional layers, not mandatory gates") constrains the design. - ontology-vs-reflection — the binding declaration is Yin (what conventions the project IS bound to); the sync/update action is Yang (what BECOMES when the shared origin moves). Both must coexist: a declaration with no sync drifts silently; a sync with no declaration has nothing to reconcile against.

This ADR is the meta-convention sibling of ADR-039 (which made the project's OWN recipes self-describing). ADR-039 is about intent the project authors; ADR-040 is about conventions the project BINDS from a shared origin and keeps in sync.

Decision

Make a project DECLARE its convention bindings, hold the convention CONTENT authoritatively under `.ontoref/` (protocol-first, harness-independent), expose both as a queryable surface, and SYNC from the shared origin through the existing update/migration mechanism — never through hand-managed symlinks as the source of truth.

DESIGN PROPERTIES (each is a constraint below):

1. Declaration in card.ncl. The beacon gains a `conventions` block:

conventions = { guidelines = [ { lang = "rust", source = "dev-system", version = "2024" }, { lang = "nushell", source = "dev-system", version = "0.111" }, ], ci = { workflow_layer = "ci" }, # reference, not a copy (ADR-038) config = { schema = ".ontoref/config.ncl", profile = "default" }, }

card.ncl is chosen over a new catalog because conventions are part of "what IS this project" (the beacon already answers that and is migration-versioned), and `describe guides`/`describe config` already read this neighborhood. The declaration NAMES sources and versions; it does NOT inline guideline content.

2. Content authoritative under .ontoref/, harness as materialized view (protocol-first). The guideline CONTENT lives at .ontoref/conventions/<lang>/ — the protocol surface, addressable independently of any harness (cf. ontology-decoupled-from-project). .claude/guidelines/<lang>/ becomes a MATERIALIZED VIEW: a symlink pointing INTO .ontoref/conventions/<lang>/, so Claude Code keeps reading its expected path while ontoref owns the content. The conventions survive with no .claude/ at all (CI, other agents, plain tooling read .ontoref/ directly). Within .ontoref/conventions/<lang>/, each file is either a symlink to the shared origin (dev-system) or a real project-OWN file beside them — the per-file model the current layout already uses, just relocated to the protocol root.

3. CI is referenced, never re-declared. The `ci` binding points at a workflow layer id (ADR-038's model). ontoref does not duplicate CI definition into the conventions surface; it cross-links so `describe conventions` can show "CI = workflow layer 'ci'" and jump to the workflow model.

4. Shared origin, resolvable PER-FILE mode (symlink | vendored | local-own). Inside .ontoref/conventions/<lang>/ each file resolves explicitly: `symlink` to the shared origin (transparent dev-loop), `vendored` (a copied snapshot, clone/CI/container-safe), or `local-own` (a real project-authored file with no origin). The per-file granularity is what lets a project mix shared guidelines with its own in one directory. The DECLARATION (card.ncl) plus the .ontoref/ content are the source of truth; the .claude/ symlink is a derived view.

5. Propagation rides the update/migration mechanism, not bespoke symlink edits. `ontoref conventions sync` (and the `update_ontoref` mode) reconcile each declared binding against the shared origin: detect version drift (origin moved, e.g. new Rust guideline), report it, and — opt-in — re-link or re-vendor to the declared version. A shared change reaches every binding project through this one path. Project-local refinement flows back by updating the origin and bumping the declared version, never by editing a vendored copy in place and losing the link to origin.

6. Bidirectional but origin-authoritative. Sync is two-directional in intent (origin → projects for shared bumps; project → origin for refinements) but the ORIGIN is authoritative: a project does not silently fork shared conventions. A divergence is reported by audit; promoting a local change means writing it to the origin and re-syncing, so all binding projects can adopt it.

7. Optional, audit-not-gate. A project may declare zero conventions and remain valid. `ontoref conventions audit` reports: bindings whose origin is unreachable, version drift (declared != origin), and resolution-mode mismatches (declared symlink but found vendored, or vice versa). It REPORTS; it does not block — the formalization-vs-adoption resolution.

These are MODES/utilities, not new stores: per the user's framing, guidelines/CI/ config bindings are implementation-framework utilities (templates, examples, sync). `ontoref conventions sync|audit|describe` and the `update_ontoref` extension are the operational surface; card.ncl carries the declaration.

Constraints

  • Hard A project's convention bindings (guidelines per language, CI reference, config conventions) MUST be declared in .ontoref/card.ncl as a `conventions` block naming source + version. Guideline CONTENT MUST NOT be inlined; the declaration names sources, content resolves from the shared origin.
  • Hard Convention CONTENT MUST live authoritatively under .ontoref/conventions/<lang>/ (protocol-first). .claude/guidelines/<lang>/ MUST be a materialized view (a symlink into .ontoref/conventions/), never the authoritative location. The conventions MUST resolve with no .claude/ present. The card.ncl declaration plus the .ontoref/ content are the source of truth; per-file resolution mode (symlink to origin | vendored | local-own) is recorded, and a project's own guideline is a real file beside the shared ones.
  • Hard Convention propagation/sync MUST be implemented as an extension of the existing migration system (ADR-010) and the update_ontoref mode — e.g. `ontoref conventions sync` and an update_ontoref step — NOT as a separate symlink-specific propagation path. A shared-origin change reaching consumers MUST be expressible as / accompanied by a migration.
  • Hard The shared origin MUST be authoritative for shared conventions. A project MUST NOT silently fork a bound convention by editing a vendored copy in place. Local refinements are promoted by writing to the origin and bumping the declared version; divergence MUST be reported by audit, not auto-merged.
  • Soft Convention bindings MUST be optional: a project may declare none and remain valid. `ontoref conventions audit` MUST report unreachable origins, version drift, and resolution-mode mismatches, but MUST NOT block builds or commands.
  • Soft The `ci` convention binding MUST reference a workflow layer id (ADR-038 model), not re-declare CI steps in the conventions surface. `describe conventions` cross-links to the workflow model rather than duplicating it.

Alternatives considered

  • Keep symlinks as the mechanism, add nothingrejected: Symlinks are invisible to ontoref, unversioned, and break on clone/CI/container. They cannot answer 'what version' or 'is this in sync', and a shared bump requires manual per-project symlink work. The whole point is to make bindings declared, queryable, and synced.
  • Declare conventions in a new .ontoref/catalog/conventions.nclrejected: Fragments 'what the project is bound to' away from the card.ncl beacon and duplicates the read surface that describe guides/config already provide. A catalog suits kind-extensible operation inventories (ADR-034), not the small, identity-level binding declaration. Chosen against, but the catalog remains the right home if bindings later grow rich enough to need their own kind system.
  • A bespoke convention-sync tool independent of the migration systemrejected: Duplicates ADR-010's propagation mechanism and update_ontoref, and stays outside `ontoref migrate`/`describe`. Convention propagation is the same event shape as protocol propagation; it must reuse that one path.
  • Vendor (copy) conventions only, drop symlinks entirelyrejected: Loses the transparent dev-loop where editing the origin is instantly reflected. Both modes have a place: symlink for local dev, vendored for clone/CI/container. The declaration records which mode applies; forcing one collapses a useful choice.
  • Inline guideline content into card.ncl / the projectrejected: Bloats the beacon, defeats sharing, and guarantees drift from the origin. Bindings must name sources+versions and resolve content from the shared origin, never inline it.

Anti-patterns

  • Treating .claude Symlinks as the Authoritative Content — Convention content lives authoritatively under .claude/guidelines/* (harness-specific) with no card.ncl declaration and nothing under .ontoref/. The binding is invisible to ontoref, unversioned, harness-coupled, and vanishes on clone/CI/container or when there is no Claude Code.
  • A Separate Sync Path Outside the Migration System — Convention propagation is built as its own tool/script independent of the migration system and update_ontoref, so it is invisible to `ontoref migrate` and duplicates the protocol's propagation mechanism.
  • Editing a Vendored Convention In Place — A project edits its vendored copy of a shared guideline directly, forking the shared origin silently. Other binding projects never see the change and the origin fractures.
  • Failing Builds on Convention Drift — `ontoref conventions audit` (or a hook) blocks commands/builds when a binding is drifted or undeclared, turning an optional layer into a mandatory gate.
  • Re-Declaring CI in the Conventions Surface — CI steps are copied into card.ncl's conventions block instead of referencing the workflow layer, creating a second drifting CI source.

Related ADRs

ADR-010 · ADR-038 · ADR-039

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.