docs(adr): ADR-0028 — scope Backstage publication as a downstream consumer, tiered on the entity-ownership mapping - #149
Merged
Conversation
…sumer ADR-0013 settled catalog composition for one direction of data flow, Backstage -> adrkit: a standalone offline snapshot generator emitting an interchange file the core consumes, with "the core still never learns an adapter exists" as the binding invariant. Feature 010 implements that direction. The demand actually on the table runs the other way — adrkit-originated ADRs surfaced inside Backstage — and no record in the corpus addresses it. Absent one, the default reading is that ADR-0013 already covers anything named "Backstage". That is a category error: 0013 constrains what adrkit may load, not what a downstream consumer may do with adrkit's published output. Two failure modes follow from the misreading — a plugin landed under `packages/adapters/catalog-*/**` inherits the offline-generator constraint by path, and `@backstage/*` enters a dependency graph that ADR-0007's `core-has-no-adapter-deps` and `clean-clone-builds` assertions keep clear. Scopes the publication direction on four clauses: the dependency edge is one-way (plugin -> adrkit, so 0013's invariant is preserved rather than excepted); it lives outside the catalog glob, separate repository preferred; it binds only to contracts already published at 0.7.0 — the ADR schema, `@adrkit/core` types, and the CLI's JSON outputs — and not to `@adrkit/catalog-envelope` or `@adrkit/catalog-backstage` while both sit at 0.0.0 outside `RELEASE_PACKAGES`; and it authorizes work, not a release, following ADR-0020's pattern. Does not amend or supersede ADR-0013, which remains authoritative and unchanged for the ingest direction. Recorded honestly: `status: proposed`, `authoredBy: agent-drafted`, no `ratifiedBy`. Per ADR-0013's own precedent against fabricating an acceptance that never happened, it does not govern until ratified — `adr explain` reports it under "Active proposals (not yet binding)". The trade-offs section states the cost the chosen option pays and Option B does not: cross-repository contract drift is unpoliced by any single CI run. Action item 4 records an unrelated defect found while tracing this: `packages/adapters/catalog-backstage/README.md` on main says six modules are "not implemented here" and that "no generator has run", while all six exist in `src/`. adr lint: 28 records, 0 errors, 0 warnings. typecheck clean, 2134 pass / 0 fail, check:doc-pins ok. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
There was a problem hiding this comment.
Pull request overview
Proposes ADR-0028 to distinguish adrkit-to-Backstage publication from the existing Backstage ingestion adapter architecture.
Changes:
- Defines a one-way downstream plugin boundary.
- Restricts integrations to published contracts.
- Documents placement, release, and governance constraints.
Suppressed comments (1)
docs/adr/0028-scope-backstage-publication-as-a-downstream-consumer-outside-the-catalog-adapter.md:199
- This repeats the version conflation from clause 3: the schema is published as schema version
0.1.0, not as a0.7.0package contract. The inventory criterion should distinguish the schema URL/version from the package and CLI release version; otherwise the action item cannot be completed accurately.
3. [ ] Inventory precisely what the plugin needs from adrkit and confirm each item is
already published at `0.7.0`. Anything that is not on that list is the
"how we would know this was wrong" signal firing before a line is written.
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Decisions governing this change
|
Two corrections to the draft, both found while planning the surface it scopes. **The prior art was missing entirely.** `@backstage-community/plugin-adr` already solves the document half — an entity ADR tab, a backend fetching markdown through Backstage's URL readers, search indexing, New Frontend System support, and extension points for file filtering, content decoration, and status rendering. A record proposing a Backstage surface that does not mention it invites rebuilding work that exists, and is the first thing a reviewer would ask about. Added as context, as Option E, and as an externalRef. Two details make it closer to adrkit than expected: adrkit's `NNNN-title-with-dashes.md` filenames match the plugin's own documented custom-filter example, and its sample status component switches on exactly the six statuses in `schema/adr.schema.json`. Whether its MADR-oriented parser reads adrkit frontmatter acceptably is *not* assumed — it is action item 4, to be observed and recorded either way. Option E is marked **partially chosen** rather than rejected, because clause 5 adopts it for the document layer. Listing it as refused would misrepresent the decision. **And the draft overstated the case.** It said feature 010's work does "not move this direction forward". The packages do not, but ADR-0012's `adrkit.io/owned-paths` annotation does: publication needs the same entity-to-path mapping ingest consumes, in reverse. That is now stated as shared ground and as an explicit decision not to invent a second ownership contract. Also adds clause 6 — resolution runs through `@adrkit/core`, never a reimplementation — since re-deriving `affects:` matching downstream would create the very drift the trade-offs section names as this option's main cost, in the one place it is avoidable. The new third-party extension- point dependency is recorded as a trade-off rather than left implicit. Still `proposed`, still `agent-drafted`, still no `ratifiedBy`. adr lint: 28 records, 0 errors, 0 warnings. 2134 pass / 0 fail. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
…nomy leaked A deep review (adversarial, architect, consumer, operator; 26 findings, 5 blocking, 4 verified against the tree) found the previous draft's central structure unsound. This replaces it rather than patching, because the defect was the structure. **The direction taxonomy leaked, and the leak was exploitable.** The draft said ADR-0013 governs Backstage -> adrkit and this record governs the reverse. But answering "which decisions govern this component" needs an entity-to-path mapping first, and obtaining it means reading `adrkit.io/owned-paths` off catalog entities — which is ingest. So the draft's own scope contained the direction it disclaimed, while asserting it "does not narrow" ADR-0013. Verified reachable: `CatalogSnapshot` is exported from `@adrkit/core` and `resolveAffects` accepts it via `snapshots.catalog`, so a consumer could read live entities, build that snapshot and resolve against it — satisfying every clause of the draft while performing the dynamic runtime catalog resolution ADR-0013 refused, and transporting a type ADR-0013 calls "not a wire format" across a process boundary. The fix is the tier split, and it closes two blocking findings at once. Tier 1 (corpus-scoped: queue, graph, status, explicit-path governance) needs no mapping and is authorized now. Tier 2 (entity-scoped) needs it, is routed through ADR-0013's envelope by clause 7, and is explicitly NOT authorized here. That also repairs the draft's unimplementable clause: it mandated reusing ADR-0012's annotation while clause 3 forbade the only packages implementing it — `grep -r 'adrkit.io/owned-paths' packages/core/src` returns nothing; all four implementations sit inside `@adrkit/catalog-backstage`. **The clause-2 attribution was false.** The draft called it "load-bearing" that `core-has-no-adapter-deps` and `clean-clone-builds` keep `@backstage/*` out. Neither does: `TOOLKIT_DEPENDENCY` is `/^(@actions\/|@octokit\/|octokit$)/`, `check-deps.ts:187` documents unknown packages as "silently unconstrained", and `catalog-backstage` already ships `picomatch` and `yaml` with both assertions green. Regrounded on Constitution Principle III's run-time clause, which does plausibly reach a plugin needing a Backstage instance. ADR-0007 Option C and its unmet revisit condition are now engaged rather than passed over, and Option B's two invented costs are recorded as not real: the allowlist is per-package, and `versioning: 'independent'` already exists. **And the count contradicted the enumeration** — "Four clauses, all binding:" above six. No count is stated now, and the record says unnumbered prose binds nothing. Also: repository home deferred with named criteria and in-repo work forbidden until decided (the draft offered a branch its own clause 2 foreclosed); clause 6 conditioned on ADR-0020 clause 9's undecided release rather than an editable `0.0.0`; clause 9 gains ADR-0019's pin discipline and names venue and acceptor; `affects:` extended to the contracts the clauses actually bind — verified surfacing on `packages/core/src/check/index.ts` and `scripts/release-pack.ts`, which it previously missed; and a new Enforcement posture section states plainly that nothing enforces this today and that CODEOWNERS is the only real gate. Still `proposed`, still `agent-drafted`, still no `ratifiedBy`. adr lint: 28 records, 0 errors, 0 warnings. typecheck clean, 2134 pass / 0 fail, check:doc-pins ok. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
A second independent four-lens panel over the redraft returned 30 findings, 3 blocking. All three are closed, plus every major finding either fixed or explicitly accepted and recorded. Ratified by @mbeacom. **Two false claims, both inside the Enforcement posture** — the section whose entire purpose is honesty about enforcement, which makes an overclaim there worse than the absence it discloses. The first cited ADR-0007's and ADR-0020's assertions as each pairing with "an identically named CI job." Verified false: ci.yml declares six jobs; `core-has-no-adapter-deps` is a *step* ("Verify dependency boundaries") inside `clean-clone-builds`; and `catalog-adapter-accept-path-needs-annotated-real-corpus` pairs with nothing — ADR-0020's own clause 8 calls it "currently inert", "never fails", "records the rule; it does not enforce it". The record cited as precedent an assertion documented as enforcing nothing. Now states the accurate picture: exactly one of the three demonstrates the pattern. The second claimed `affects:` surfaces this record for "the bound contracts". Clause 6 binds all of `@adrkit/core`'s published API, but `status/bucket.ts`, `parse/frontmatter.ts`, `load/corpus.ts` and the other re-exports of `index.ts` match no declared pattern. Declaring all of core was rejected as noise, so the posture now states where detection is silent rather than implying it is complete. **A second evasion route, same shape as the one the redraft closed.** Clause 1 admitted "a path the caller supplies explicitly" with "caller" undefined, so a plugin could read `backstage.io/source-location` off a live entity, call it explicit, and reach entity-scoped governance inside Tier 1. "Explicitly supplied" now means originating outside the plugin, any plugin-derived path is Tier 2, and where clauses 1 and 2 both admit a capability clause 2 governs. **The release limit did not bind.** The previous draft's rule that unnumbered prose "binds nothing" unbound its own blockquote, which was the only place forbidding a release — so a conformant Tier 1 plugin could have shipped to npm. That corpus-wide two-class rule is dropped (it also would have unbound ADR-0007's and ADR-0013's mostly-unnumbered decisions), and the limit is now clause 10. **Clause 7 named a mechanism that does not exist.** It said the `owned-paths` decode is "consumed, not copied", but `@adrkit/catalog-backstage` exports only `PACKAGE_NAME`, declares no entry point, and is unreachable from outside the workspace by design. Tier 2 consumes the envelope's already-normalized output and never needs the decode. Also: clause 11 adds adrkit's reciprocal additive-only obligation on the consumed shapes, which no clause carried (ADR-0025 precedent); clause 9 assigns the document layer to Tier 1 and states its version range is empty until the spike; clause 5 says Tier 1 work may begin out-of-repo as provisional placement; the ADR-0007 adapter-regime narrowing is stated rather than left implicit, with action item 7 to land the amendment note; blastRadius team -> org to match every org-scope ARB record; relatesTo corrected; action item 2 gains an owner and a date. adr lint: 28 records, 0 errors, 0 warnings. typecheck clean, 2134 pass / 0 fail. `adr explain` now reports it [accepted] and governing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
mbeacom
marked this pull request as ready for review
August 16, 2026 02:51
…ck wired Resolves ADR-0028 action item 2. The deferral rested on an obstacle the corpus had already dissolved. **Principle III's run-time clause does not reach this plugin.** ADR-0028 asserted it "plausibly reaches a plugin that requires a running Backstage instance." But `@adrkit/ci` lives at `packages/ci/`, ships at 0.1.0, declares `@actions/github`, and at run time calls `getOctokit(token)` against the authenticated GitHub REST API. If that clause reached what a package talks to when a user runs it, `@adrkit/ci` would violate it outright. It does not: the clause is read and enforced as build/test hermeticity plus dependency confinement, which is exactly what `CI_SURFACE_PACKAGE` and `TOOLKIT_DEPENDENCY` implement. A Backstage plugin talking to a Backstage backend is the same shape as an Action talking to GitHub. **And ADR-0007's own revisit condition is unmet.** It rejected separate repositories because they fragment "a project with one maintainer and no users," to be revisited "once an adapter acquires independent contributors." Separating now is premature by ADR-0007's own terms. The costs ADR-0028 attributed to staying in-repo are already solved: `isAdapterPackage()` is location-based and `packages/catalog-envelope/` is the precedent for a non-adapter sited by location; `versioning: 'independent'` already exists for `@adrkit/spec-kit`; and `check-deps.ts` is a per-package allowlist, so admitting a package weakens nothing elsewhere. Decision: `packages/backstage-plugin/`, confined on the `@adrkit/ci` pattern — `@backstage/*` admitted there and forbidden everywhere else. **Enforcement is wired now rather than deferred**, which is the point. Siting the plugin here converts ADR-0028 clause 4 from prose into a check; separation would have made it permanently unenforceable from this repository. The rule keys on the `@backstage/` prefix across every package rather than on the surface's allowlist entry, because `allowedDependenciesFor` returns `undefined` for an unrecognized package — which check-deps.ts itself documents as "silently unconstrained," so an allowlist-only rule would pass a brand-new directory smuggling the SDK in. That case is covered by a test. Observed failing before counted as coverage (ADR-0016): with the rule disabled, the two discriminating tests fail while the other 27 still pass. **ADR-0028's proposed narrowing of ADR-0007 is withdrawn** — `@adrkit/ci` already establishes that a first-party integration surface may live outside `packages/adapters/` under confinement, so no ADR-0007 clause is narrowed. ADR-0028 action items 2 and 7 are closed by reference, 7 as not required, following the mechanism ADR-0013 used on ADR-0007. adr lint: 29 records, 0 errors, 0 warnings. typecheck clean, 2140 pass / 0 fail, check:deps and check:doc-pins pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
This record's first draft decided in-repo. Measuring the dependency tree reversed it, and the argument that justified in-repo turned out to be overstated. Both are recorded rather than quietly replaced. **The measurement.** Installing the plugin's plausible dependency set: 94 packages / 65 MB / 0 lifecycle scripts today, against 607 / 480 MB for the runtime set alone and 1,274 / 1.0 GB / 2 lifecycle scripts with the community ADR plugin and @backstage/cli. Even the floor — runtime deps only, building with Bun per ADR-0010 — is 7.4x the disk and 6.5x the packages. `clean-clone-builds` pays it cold on every pull request, including documentation-only ones, so the measured 39s is a lower bound. Confinement does not help: an allowlist bounds which package may import the SDK, not what gets installed. **The overstatement.** The first draft's decisive claim was that separation makes ADR-0028 unenforceable. Broken down, exactly one mechanism is lost — contract drift caught in a single CI run. Clause 8 was never mechanically checkable either way (a dependency graph cannot detect re-derived `affects` matching), clause 11 governs this repository regardless, and the SDK prohibition is *stronger* out-of-repo than the confined form, because a blanket rule has no exception to get wrong. **The generalization, which is the actually useful part.** The distinguishing property was never plugin-ness. `@adrkit/spec-kit` is an extension surface that belongs here precisely because it declares no dependencies of any kind — asserted by its own packaging test — and ships no dist. So the rule is: a surface may live here only if it adds no install cost; otherwise it lives outside and consumes published contracts. Future surfaces get placed by that rule instead of a case-by-case argument. The check flips from confinement to blanket prohibition (`no-backstage-sdk-in-this-repository`), which is simpler and catches the case an allowlist-keyed rule would wave through: `allowedDependenciesFor` returns `undefined` for an unrecognized package, which check-deps.ts itself documents as "silently unconstrained". Observed failing per ADR-0016 — with the rule disabled all three discriminating tests fail while the other 26 pass. One of them names `@adrkit/backstage-plugin` specifically, since that is the single input the withdrawn confinement rule would have allowed. ADR-0007's Option C narrowing comes back, on better grounds than the first draft's taxonomy argument: ADR-0007 weighed fragmentation when no integration carried a dependency tree, and did not weigh one 15x the repository it joins. Its stated revisit condition (independent contributors) is still unmet and is not claimed. The accepted loss is stated with mitigations rather than merely admitted: the downstream repo pins and tests published @adrkit/* versions, and action item 5 builds a conformance fixture so a breaking change is detectable downstream. Without that fixture clause 5 is an intention, not a control, and the record says so. adr lint: 29 records, 0 errors, 0 warnings. typecheck clean, 2140 pass / 0 fail, check:deps ok. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
ADR-0029 accepted (ratified by @mbeacom). ADR-0030 proposed, and it amends ADR-0028 clauses 6 and 11 by reference, because clause 11 is measurably false as ratified. **The defect being fixed.** ADR-0028 clause 11 binds adrkit to additive-only change on "the consumed shapes," defined as `@adrkit/core`'s exported API. That surface is 173 symbols across 23 re-export lines. The only existing consumer, `@adrkit/ci`, imports three. A promise of that breadth resolves one of two ways: freeze the engine against ordinary refactoring, or break the promise silently. The second is what happens, and a governing clause routinely and invisibly violated is worse than none. The SDK's primary purpose is making that promise keepable by narrowing what it attaches to; reducing consumer boilerplate is secondary. **Why library-first rather than both.** Both modes already run here — `@adrkit/ci` consumes the library, `spec-kit` shells out to `adr check`. But the CLI's JSON is only sometimes a core shape: `queue` delegates to `formatQueueReportJson` and `check` serializes `CheckOutcome`, while `explain`, `lint`, and `new` assemble CLI-specific objects inline. So "one interface, two adapters" would additionally require a normalization layer for three commands plus a cross-mode equivalence property tested forever — widening the very surface the SDK exists to narrow. Process isolation, the honest argument for CLI-first, buys little here: core depends on picomatch, semver, yaml and zod, so a library consumer inherits almost nothing. The dependency asymmetry that pushed Backstage out (1,274 packages) does not exist in reverse. Both modes stay reachable. Clause 6 converges `explain`, `lint` and `new` onto core formatters as hygiene, after which a CLI adapter is nearly free — and the direction matters, because adding an adapter later is additive while withdrawing one is a break. The CLI JSON is kept as a documented sibling contract rather than an SDK adapter, since its consumers are language-agnostic and want a stable schema, not a JavaScript package. Clause 4 has the SDK declare its own types rather than re-export core's: a facade that re-exports is an alias and insulates nothing. That deliberate duplication follows `packages/catalog-envelope/`, whose `//boundary` note records the identical choice for the same reason. Marked `one-way-door`: a published surface cannot be withdrawn once depended on, only deprecated. Clause 8 therefore withholds the release until a real consumer has exercised the surface, so the first shape is not hardened against a hypothetical one. adr lint: 30 records, 0 errors, 0 warnings. 2140 pass / 0 fail. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
CI caught a real id collision that the local tree did not have: `main` merged a different ADR-0028 (portable agent plugin) while this branch was open, so `adr check` failed with `unique-id 0028 is used by multiple records`. self-dogfood, clean-clone-builds and both node-smoke jobs went red on it. This is the risk flagged when 0028 was first scaffolded — the number had already been used and reverted (#144/#145), so `adr new` offered it again and a concurrent branch took it first. The corpus is right to fail closed here; a duplicate id makes every `affects:` resolution ambiguous. Renumbered highest-first through placeholders so 0028 -> 0029 could not then be re-caught by the 0029 -> 0030 pass: 0028 scope-backstage-publication... -> 0029 0029 keep-extension-surfaces... -> 0030 0030 publish-a-narrow-consumer-sdk... -> 0031 Filenames, frontmatter ids, `# ADR-XXXX` headings, `relatesTo`, every cross-link between the three, the ADR-0031 amendment note on ADR-0029, and the ADR-0030 references in `scripts/check-deps.ts` and its test (comment, violation reason string, and the describe block) all moved together. Verified that no other record's number was touched: the only ADR references remaining in the three files are 0003, 0007, 0010, 0012, 0013, 0014, 0016, 0019, 0020, 0025 plus the new 0029/0030/0031. Ratification carries over unchanged — 0029 and 0030 remain accepted and ratified by @mbeacom, 0031 remains proposed. adr lint: 31 records, 0 errors, 0 warnings. typecheck clean, 2173 pass / 0 fail, check:deps ok. `adr check` over the branch diff now reports 0 changed-record errors, which is the failure CI reported. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scopes the adrkit → Backstage publication surface. Ratified by
@mbeacomafter two independent four-lens deep reviews.Review history
Round 1 found the direction taxonomy leaked: answering "which decisions govern this component" needs an entity→path mapping, and getting it means reading
adrkit.io/owned-pathsoff catalog entities — which is ingest. A consumer could satisfy every clause while doing the runtime catalog resolution ADR-0013 refused. The two-tier split fixes that and the unimplementable annotation-reuse clause together.What round 2 caught
Two false claims inside the Enforcement posture — the section whose purpose is honesty about enforcement, which makes an overclaim there worse than the absence it discloses.
ci.ymldeclares six jobs;core-has-no-adapter-depsis a step insideclean-clone-builds; andcatalog-adapter-accept-path-needs-annotated-real-corpuspairs with nothing — ADR-0020's own clause 8 calls it "currently inert… never fails… records the rule; it does not enforce it." The record cited as precedent an assertion documented as enforcing nothing.affects:surfaces the record for "the bound contracts." Clause 6 binds all of@adrkit/core's published API, butstatus/bucket.ts,parse/frontmatter.ts,load/corpus.tsand otherindex.tsre-exports match no pattern. Declaring all of core was rejected as noise, so the posture now states where detection is silent.A second evasion route, same shape as the one the redraft closed. Clause 1 admitted "a path the caller supplies explicitly" with caller undefined — so a plugin could read
backstage.io/source-locationoff a live entity, call it explicit, and reach entity-scoped governance inside Tier 1. Now: explicitly supplied means originating outside the plugin; any plugin-derived path is Tier 2; and where clauses 1 and 2 both admit a capability, clause 2 governs.The release limit did not bind. The redraft's rule that unnumbered prose "binds nothing" unbound its own blockquote — the only place forbidding a release — so a conformant Tier 1 plugin could have shipped to npm. That two-class rule is dropped (it would also have unbound ADR-0007's and ADR-0013's mostly-unnumbered decisions), and the limit is now clause 10.
Clause 7 named a mechanism that does not exist.
@adrkit/catalog-backstageexports onlyPACKAGE_NAMEand is unreachable from outside the workspace by design, so the decode could not be "consumed." Tier 2 now consumes the envelope's already-normalized output and never needs the decode.Also: clause 11 adds adrkit's reciprocal additive-only obligation on consumed shapes (ADR-0025 precedent) — no clause previously bound the producer; clause 9 assigns the document layer to Tier 1; clause 5 permits provisional out-of-repo work; the ADR-0007 adapter-regime narrowing is stated with action item 7 to land the amendment note;
blastRadiusteam → org;relatesTocorrected; action item 2 gains an owner and date.Honesty posture
status: accepted,authoredBy: agent-drafted,ratifiedBy: "@mbeacom"— both recorded, per ADR-0013's precedent that acceptance be stated rather than blurred.adr explainnow reports it[accepted]and governing.Not everything was fixed, and the record says so. Nothing mechanically enforces any clause;
affects:detection is partial and the gap is named;reviewBy: 2027-02-15has no mechanism once accepted (ratification removes it fromadr queue); clause 8 is unenforceable across a repo boundary. These are recorded in Enforcement posture and action items 4, 7 and 8 rather than closed silently.Two process disclosures: the community-plugin observations come from public docs, not from running it (action item 5), and in both rounds some lens reports arrived with a malformed frontmatter delimiter — adjudication failed closed, delimiters were repaired, originals retained in
findings-as-returned/, and both panels re-ran to clean 4/4 ledgers.Validation
adr lint: 28 records, 0 errors, 0 warnings. Typecheck clean.bun test2134 pass / 0 fail.check:dco4 signed. CI 12/12.Note: the local branch name is a leftover from the PR #98 fix earlier in the same session; the remote branch and this PR are named correctly.