Skip to content

ADR-0010/0011: cross-model references and the network boundary - #32

Merged
jbeda merged 3 commits into
mainfrom
adr-cross-model-references
Jul 26, 2026
Merged

ADR-0010/0011: cross-model references and the network boundary#32
jbeda merged 3 commits into
mainfrom
adr-cross-model-references

Conversation

@jbeda

@jbeda jbeda commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Design output from a grilling session on #25. Nothing is implemented — these are forward-looking decision records plus one guard test.

ADR-0010 — cross-model references by vendoring

A model declares a scope: slug and references another model's items as slug.Name, resolving against a whole-file copy committed to this repo.

  • imports: is a list of paths; each listed file's own scope: owns the slug, so nothing is repeated and nothing can disagree. Resolution does not recurse.
  • Whole-file vendoring, so referencing something new from an already-imported model is a local edit rather than another fetch.
  • Provenance is a header comment, not schema and not a lock file. An origin: key would put a vendoring concept into the format that every single-model user meets in the schema reference; a root lock file would sit several directories from models that commonly live as peers in docs/. The format already carries machine-read header comments (# yaml-language-server:).
  • The surface digest covers structure and normative content — including invariant statements, since those are the rule itself — but excludes description, definition, note and other documentation, so upstream prose polish doesn't page anyone.
  • Offline verify on by default; upstream freshness behind a flag, mirroring --completeness error so posture is chosen per repo.
  • Render documents the dependency and never expands it, so output stays a pure function of one model and the golden-fixture invariant holds.
  • Commands nest under modelith deps: import, update, check. Origins are assumed to be GitHub.
  • Qualified entity references stay deferred. They already fail schema validation via the existing name patterns; lint gains a friendly error ahead of the raw violation.

ADR-0011 — the network boundary

Replaces the blanket "no network calls" rule in go-style.md with the property it was standing in for: lint and render never perform network I/O, and commands that do are named, opt-in, and delegate transport to git/gh rather than implementing it. Split out of ADR-0010 because it governs decisions well beyond imports.

Test

TestADR_0011_OfflinePackages walks transitive imports of lint, render/*, schema and model, failing on net, net/http or os/exec. Banning os/exec is deliberate — without it the rule could be evaded by shelling out to curl. Verified to fail when it should, not merely to pass today.

ADR-0010 has no test because none of it is built; it names what will pin it.

Notes for review

  • task check is green. The one //nolint:gosec is the first in the repo, documented per go-style.md's carve-out for false positives.
  • Schema, structs, docs/06-schema-reference.md and go-style.md are deliberately untouched — they are test-coupled and move together at implementation time.
  • Draft until the review pass clears it.

Refs #25

🤖 Generated with Claude Code

jbeda and others added 3 commits July 26, 2026 13:05
… ADR-0011)

Design output from a grilling session on #25. Nothing is implemented; both
ADRs record intent so the format work has a settled shape to build against.

ADR-0010 decides that a model declares a scope slug and references another
model's items as slug.Name, resolving against a whole-file copy committed to
this repo. An explicit imports: path list locates copies, each file's own
scope: owns its slug, and resolution does not recurse. Provenance lives in a
header comment rather than in the schema, because an origin: key would put a
vendoring concept into the format that every single-model user would meet in
the schema reference, and rather than a lock file, because models commonly sit
as peer files in docs/ where a root lock would be several directories away.
The surface digest covers structure and normative content but not
documentation prose. Qualified entity references stay deferred; they already
fail schema validation, and lint gains a friendly error ahead of it.

ADR-0011 replaces the blanket no-network rule in go-style.md with the property
it was standing in for: lint and render never perform network I/O, and the
commands that do are named, opt-in, and delegate transport to git/gh rather
than implementing it.

ADR-0011 lands with TestADR_0011_OfflinePackages, which walks transitive
imports of the offline packages and fails on net, net/http or os/exec.
os/exec is banned there deliberately: without it the rule could be evaded by
shelling out to curl. Verified to fail when it should, by temporarily banning
a package that is genuinely in the graph. ADR-0010 has no test yet because
none of it is built; the ADR names what will pin it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joe Beda <joe@stacklok.com>
Review pass on PR #32 returned 2 blocking and 8 should-fix findings.

Blocking. ADR-0010 described the freshness check two incompatible ways: as a
flag on lint mirroring --completeness error, and as a command under modelith
deps. The second is the decision; the flag wording was left behind when the
command group was added, and ADR-0011 repeated it. lint never asks whether
origin moved.

Blocking. A vendored model is not the repo's work, but nothing said so
operationally. The Action lints every globbed file with --completeness and
optionally renders each one (action.yml:90-99), so a vendored model would turn
a user's CI red for gaps in someone else's document. Ownership-scoped
diagnostics now skip a file carrying a provenance header.

Local imports. A model in the same repo, canonical where it sits, has no
origin and no header. Provenance-header presence is the signal that separates a
copy from a canonical file, and it does the same job three times: what to
verify, what completeness applies to, and whether an unresolvable import is an
error. Peers in one repo import each other with no ceremony.

Also: go-style.md is actually updated rather than merely declared updated, and
ADR-0011 no longer claims precedence over a rule file, which inverted CLAUDE.md;
the TestSchemaStructSync claim is corrected, since it does not cover
docs/06-schema-reference.md; duplicate and absent scopes among imports are
decided; the narrowing of ADR-0002 that lossy imported Markdown represents is
stated rather than left implicit; qualified names in prose are deferred
explicitly; and ADR-0011 no longer implies its test proves the no-flag claim
end to end, since cmd/modelith reaches net through pflag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joe Beda <joe@stacklok.com>
render --check has the same shape as the completeness problem: it fails when a
model's .md is absent or stale, and a vendored model owes neither -- its
rendered form belongs to its home repo. Vendored files are skipped by
render --check; naming one explicitly still renders it, which is how a deep
link gets a target.

The scope-collision text said there was no local remedy, which stopped being
true when the same change introduced local imports: two local models collide
and you rename one. Only vendored copies of two upstreams that chose the same
slug are stuck, because there is no alias layer. Also names lint as where the
error is raised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joe Beda <joe@stacklok.com>
@jbeda

jbeda commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Review loop record

Two rounds, per .claude/rules/agent-workflow.md. Full evidence in .scratch/reviews/adr-0010-0011.md (gitignored, local).

Round 1 — independent reviewer (Opus), full scope

2 BLOCKING, 8 SHOULD-FIX, 6 NIT.

Finding Outcome
F1 freshness described as both a lint flag and a deps command fixed — deps check owns it; lint never asks whether origin moved
F8 vendored models fire completeness diagnostics; the shipped Action lints every globbed file (action.yml:90-99) fixed — ownership-scoped diagnostics skip a file with a provenance header
F2 go-style.md declared updated but wasn't; ADR claimed precedence over a rule file, inverting CLAUDE.md fixed — file actually updated, claim dropped
F3 overclaimed TestSchemaStructSync covers the schema reference fixed — it covers schema↔structs only
F4 lossy imported Markdown narrows ADR-0002 silently fixed — stated, with rationale
F9 vendored file linted directly, own imports dangling fixed
F10 duplicate / absent scope: among imports undecided fixed
F11 backticked slug.Name in prose unchecked fixed — deferred explicitly
F12 anchor collisions, no test pins the scheme fixed — named; test listed as an implementation obligation
F14 test guards libraries, not commands; cmd/modelith reaches net via pflag fixed — claim scoped to what the test proves

F1 was self-inflicted: the flag wording survived the switch to a deps command group and ADR-0011 repeated it.

Round 1 also missed something a human caught — a model in the same repo, canonical where it sits, has no origin and no header. Local imports are now first-class, and provenance-header presence became the single signal doing three jobs: what to verify, what ownership diagnostics apply to, and whether an unresolvable import is an error.

Round 2 — delta only (Sonnet)

6 fixed, 4 partial, 1 new. Two partials were real design gaps and are now closed:

  • render --check had the same shape as F8 — it would demand a committed .md for a model whose rendered form belongs to its home repo. Vendored files are skipped.
  • "No local remedy" for a scope collision stopped being true once local imports existed. Two local models collide and you rename one; only two upstreams choosing the same slug are stuck.

The remaining partials (which specific checks stop running, where exactly the collision error surfaces) are implementation-time detail, not open design questions.

Verification

All factual claims in both ADRs were checked against source with file:line — lint.go:864-877, markdown.go:91/121, action.yml:90-99, sync_test.go, pflag→net. None fabricated.

TestADR_0011_OfflinePackages was verified to fail when it should, by temporarily banning a package genuinely in the graph — not merely to pass today.

task check green. No blocking findings remain; moving out of draft.

@jbeda
jbeda marked this pull request as ready for review July 26, 2026 20:28
@jbeda
jbeda merged commit a51b156 into main Jul 26, 2026
2 checks passed
jbeda added a commit that referenced this pull request Jul 26, 2026
Review pass on PR #32 returned 2 blocking and 8 should-fix findings.

Blocking. ADR-0010 described the freshness check two incompatible ways: as a
flag on lint mirroring --completeness error, and as a command under modelith
deps. The second is the decision; the flag wording was left behind when the
command group was added, and ADR-0011 repeated it. lint never asks whether
origin moved.

Blocking. A vendored model is not the repo's work, but nothing said so
operationally. The Action lints every globbed file with --completeness and
optionally renders each one (action.yml:90-99), so a vendored model would turn
a user's CI red for gaps in someone else's document. Ownership-scoped
diagnostics now skip a file carrying a provenance header.

Local imports. A model in the same repo, canonical where it sits, has no
origin and no header. Provenance-header presence is the signal that separates a
copy from a canonical file, and it does the same job three times: what to
verify, what completeness applies to, and whether an unresolvable import is an
error. Peers in one repo import each other with no ceremony.

Also: go-style.md is actually updated rather than merely declared updated, and
ADR-0011 no longer claims precedence over a rule file, which inverted CLAUDE.md;
the TestSchemaStructSync claim is corrected, since it does not cover
docs/06-schema-reference.md; duplicate and absent scopes among imports are
decided; the narrowing of ADR-0002 that lossy imported Markdown represents is
stated rather than left implicit; qualified names in prose are deferred
explicitly; and ADR-0011 no longer implies its test proves the no-flag claim
end to end, since cmd/modelith reaches net through pflag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joe Beda <joe@stacklok.com>
@jbeda
jbeda deleted the adr-cross-model-references branch July 26, 2026 22:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant