Skip to content

docs(spec): open feature 010 — production Backstage catalog adapter - #83

Merged
mbeacom merged 4 commits into
mainfrom
mbeacom-supreme-guacamole
Aug 5, 2026
Merged

docs(spec): open feature 010 — production Backstage catalog adapter#83
mbeacom merged 4 commits into
mainfrom
mbeacom-supreme-guacamole

Conversation

@mbeacom

@mbeacom mbeacom commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Opens the production feature specification for packages/adapters/catalog-backstage/. Specification only — no package, no source, no plan.md, no tasks.md.

This is ADR-0020 action item 1, the first work authorized by that record.

Scope

The minimal viable production slice ADR-0020 clause 7 describes: an offline generator that reads one local Backstage catalog checkout via a closed-schema single-repository input manifest, validates admissibility before canonicalizing identity, resolves the three-state ownership discriminator (explicit-paths / explicit-empty / annotation-absent) from adrkit.io/owned-paths alone, enforces whole-operation atomic fail-closed semantics, and writes only the versioned envelope — never a CatalogSnapshot-shaped artifact directly.

9 user stories · 62 functional requirements · 17 success criteria.

Two details worth reviewing closely

The fatal trigger enumeration is fifteen, not fourteen. Spike 009 enumerates exactly fourteen and says so in those words. ADR-0015's Condition of Acceptance 2 requires "the follow-up" to carry inadmissible-descriptor onto the atomic surfaces — and this feature is that follow-up. The spec states explicitly that fourteen is correct for 009 and wrong here, so the number cannot be copied across.

It also requires conformance evidence to include a descriptor that is inadmissible and canonically unique. Duplicate detection is not a validity check: in the pinned corpora, bulk-import and orchestrator carry unsubstituted placeholders that canonicalize uniquely and collide with nothing, yet are exactly as invalid as the colliding cohort. Without that case the suite could pass on duplicate detection firing incidentally and prove nothing.

ADR-0020 clause 5's accept-path gate is specified as two distinct recorded steps — a pre-output freeze and independent audit of the accept corpus, its overlay, its expected paths, and its selection basis and size with an explicit adequacy finding; then a post-output diff of derived ownership against those frozen expectations at zero false positives and zero false negatives. A populated, digest-verified envelope proves integrity, not correctness.

Verification performed on this spec

  • ADR-0015's four-row validator table is reproduced byte-identically from ADR-0015 lines 120–123 (diff clean) rather than paraphrased — it is the highest-risk transcription in the document.
  • FR numbering contiguous 001–062; SC numbering contiguous 001–017.
  • All 10 cross-document links resolve.
  • Every rung 2 / rung 3 / reference-verified / externally validated occurrence is a prohibition, never a claim.
  • Descriptor file counts vs entity document counts are distinguished (assumption A5), per the evidence-index named finding.
  • SC-007 was corrected during authoring: it originally required all fifteen glob rules be exercised by violating patterns, but rule 15's invalid-glob-compile-failure is a backstop its own contract says is "expected to never occur in practice." Now scoped to rules 1–14, with rule 15's non-firing declared conformant.

Three open clarifications, deliberately left

All are ADR-level gaps, not drafting gaps. The load-bearing one:

Which package owns consumer-side envelope validation and CatalogSnapshot derivation?
ADR-0020 clause 7 puts it outside the generator ("the generator writes the envelope and nothing else"); ADR-0007 and Constitution III put it outside core and CLI (which "receive only an already-validated CatalogSnapshot-shaped artifact"); ADR-0012 keeps the envelope out of any published schema. No record names where it does live.

This is load-bearing for FR-044–FR-048 and needs a maintainer decision before planning. The other two concern allRefs population in production (making duplicate-canonical-ref possibly unreachable outside fixtures) and what "release evidence" means in ADR-0012 gate 4 while the release vehicle is deferred.

Status

Authorizes work toward ADR-0014 rung 1 only. The spec claims no rung 2 or rung 3 status and no release authorization; both remain deferred to a later record per ADR-0020 clause 9. ADR-0012 gate 3 remains open and is not waived.

Checks

bun test 857 pass / 0 fail · typecheck clean · check:depscore-has-no-adapter-deps: ok · adr lint → 20 records, 0 errors, 0 warnings.

Nothing under specs/009-catalog-binding-viability/, docs/adr/, or packages/ was modified.

ADR-0020 action item 1. Opens the production feature spec for
packages/adapters/catalog-backstage/, citing ADR-0020 for the SC-010
rescope. Specification only — no package, no source, no plan or tasks.

Scope is the minimal viable production slice: an offline generator that
reads one local catalog checkout via a closed-schema single-repository
input manifest, validates admissibility before canonicalizing identity,
resolves the three-state ownership discriminator from
adrkit.io/owned-paths alone, enforces whole-operation atomic fail-closed
semantics, and writes only the versioned envelope — never a
CatalogSnapshot-shaped artifact directly.

9 user stories, 62 functional requirements, 17 success criteria.

Two details worth calling out:

- The fatal trigger enumeration is FIFTEEN, not the fourteen spike 009
  enumerates. ADR-0015's Condition of Acceptance 2 requires the follow-up
  to carry inadmissible-descriptor onto the atomic surfaces, and this
  feature is that follow-up. The spec says explicitly that fourteen is
  correct for 009 and wrong here, so the number cannot be copied across.
  Conformance evidence must include a descriptor that is inadmissible AND
  canonically unique, because duplicate detection is not a validity check
  and would otherwise appear to cover the case incidentally.

- ADR-0020 clause 5's accept-path gate is specified as two distinct
  recorded steps: a pre-output freeze and independent audit of the accept
  corpus, its overlay, its expected paths and its selection basis and
  size with an explicit adequacy finding; and a post-output diff of
  derived ownership against those frozen expectations at zero false
  positives and zero false negatives. A populated, digest-verified
  envelope proves integrity, not correctness.

The ADR-0015 validator table is reproduced byte-identically from
ADR-0015 lines 120-123 (verified by diff) rather than paraphrased.

Three [NEEDS CLARIFICATION] markers are left deliberately, all
ADR-level gaps rather than drafting gaps. The load-bearing one: no record
names which package owns consumer-side envelope validation and
CatalogSnapshot derivation — ADR-0020 clause 7 puts it outside the
generator, ADR-0007 and Constitution III put it outside core and CLI, and
ADR-0012 keeps the envelope out of any published schema. It needs a
maintainer decision before planning.

Authorizes work toward ADR-0014 rung 1 only. The spec claims no rung 2 or
rung 3 status and no release authorization; both remain deferred to a
later record per ADR-0020 clause 9.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings August 5, 2026 01:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

The specification contains conflicting contracts, an unsafe consumer path-validation gap, and unresolved wire-format ambiguities.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

Opens the draft production specification for the offline Backstage catalog adapter.

Changes:

  • Defines generator, envelope, validation, and atomicity requirements.
  • Adds success criteria and readiness checklist.
  • Selects feature 010 as the active Spec Kit feature.
File summaries
File Description
specs/010-catalog-backstage/spec.md Adds the production feature specification.
specs/010-catalog-backstage/checklists/requirements.md Adds specification-quality checks.
.specify/feature.json Activates feature 010.
Review details

Suppressed comments (4)

specs/010-catalog-backstage/spec.md:903

  • As written, FR-047 requires a different-repository envelope to be both rejected and accepted. The cited §§5–6 make the outcome depend on consumer mode: rejection for a consumer configured with exactly one expected repository, versus acceptance/filtering for an index deliberately configured for multiple repositories. State those preconditions here so implementations do not choose opposite behavior for the same input.
- **FR-047**: An envelope whose `repository.id` does not match the consuming repository MUST be
  rejected as misidentified
  ([`snapshot-envelope.md`](../009-catalog-binding-viability/contracts/snapshot-envelope.md) §5).
  Distinctly, a **valid** envelope for a different repository MUST be **accepted** as valid, with
  repository isolation expressed as the query returning no matches — isolation is a property of

specs/010-catalog-backstage/spec.md:1134

  • This success criterion again omits the consumer-mode precondition, making the wrong-repository rejection and different-repository acceptance indistinguishable. Mirror the cited contract's single-repository versus multi-repository setup so SC-014 is measurable rather than internally contradictory.
- **SC-014** *(consumer rejection and isolation)*: Each of the five ordered consumer validation
  steps rejects at its own step for its own malformation kind; a mutated envelope is rejected on
  digest recomputation; an envelope whose revision is not exactly equal to the consuming
  checkout's is rejected on exact inequality; an envelope whose repository id does not match is
  rejected as misidentified; and — as the contrasting acceptance case — a **valid** envelope for a

specs/010-catalog-backstage/spec.md:1048

  • The reject path intentionally produces no envelope, so “byte-identical output … including every array's ordering” is undefined there. Define determinism separately for successful envelope bytes and rejected runs' observable classification/status.
- **SC-001** *(determinism)*: Running the generator three or more times over identical inputs
  produces byte-identical output on every run, including every array's ordering — on the accept
  path and on the reject path alike.

specs/010-catalog-backstage/spec.md:1208

  • This repeats the floating-version problem from FR-029: if bun.lock later resolves another version, the generator would emit it while FR-044's inherited exact-value check rejects it. State that 4.0.5 is the frozen contract and the lockfile is verification evidence, not the source of a mutable requirement.
- **A6 — The frozen glob engine version is the lockfile's resolution, not a declared range.**
  `packages/core/package.json` declares `picomatch` as a range; the frozen value is what
  `bun.lock` resolves, which at this document's writing is `picomatch@4.0.5`. The implementation
  MUST read and record the actual resolved version rather than trust this document's
  transcription of it.
  • Files reviewed: 3/3 changed files
  • Comments generated: 8
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +835 to +839
The authority for the fifteenth is **ADR-0015's Condition of Acceptance 2**, whose preamble
states that its three conditions "were attached at ratification and are binding on any work
that cites this record." Condition 2 requires that "the follow-up must carry
`inadmissible-descriptor` onto the atomic surfaces," that "[a]dding the class to the identity
contract alone is insufficient," and that the atomic-fail-closed and data-model surfaces "must
Comment on lines +879 to +881
- **FR-043**: `provenance` MUST distinguish upstream-authored descriptor content from
maintainer-authored annotation overlay, so that **ADR-0020 clause 5**'s "only the corpus data
is third-party, never the validation" boundary is legible from the artifact.
Comment on lines +528 to +534
8. **Given** an envelope whose `repository.id` does not match the consuming repository, **When**
the consumer evaluates repository identity, **Then** the envelope is rejected as
misidentified.
9. **Given** a wholly valid envelope generated for a *different* repository, **When** this
repository's paths are queried against it, **Then** the envelope is **accepted** as valid and
the query simply returns no matches — repository isolation is a property of the query, not a
rejection.
Comment on lines +210 to +213
**Independent Test**: Point the generator at a real checkout via one manifest; run it three or
more times; confirm each run terminates in exactly one of the two permitted outcomes; confirm
repeated runs over identical inputs are byte-identical; confirm that on the rejection path the
process exit status is non-zero and no envelope file — complete or partial — is left on disk.
Comment on lines +794 to +800
- **FR-029**: Every derived pattern MUST be validated against the restricted glob dialect, whose
engine and options are **frozen**: `picomatch`, at the exact version resolved in the
repository's committed `bun.lock`, with options `{ dot: false, nocase: false, nonegate: true }`
([`glob-dialect.md`](../009-catalog-binding-viability/contracts/glob-dialect.md) §1;
**ADR-0012**). At this document's writing `bun.lock` resolves `picomatch@4.0.5`; the frozen
value is the lockfile's resolution, which the implementation MUST record in the envelope's
`globDialect` and MUST verify rather than assume.
Comment on lines +667 to +672
- **FR-009**: The generator MUST verify the manifest's declared repository identity and revision
against the **actual checkout**, reading those two values through separate git tooling, and
MUST abort on any mismatch in either. Repository identity MUST NOT be read from a descriptor
annotation
([`input-manifest.md`](../009-catalog-binding-viability/contracts/input-manifest.md) §3;
**ADR-0012**).
Comment on lines +889 to +892
list): (1) valid JSON; (2) complete shape at every nesting level; (3) frozen matcher contract
by **exact value** — `schemaVersion`, `globDialect`, `capabilities`; (4) every `sources[]`
digest present, correctly typed, and matching the actual bytes; (5)
`completeness.identityOnly === false`.
Comment on lines +17 to +18
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
mbeacom and others added 3 commits August 4, 2026 21:54
…kage

Maintainer decision, 2026-08-04. The envelope validator and CatalogSnapshot
deriver live in their own workspace package (working name
@adrkit/catalog-envelope), separate from both the adapter and @adrkit/core.

This was the load-bearing one of the spec's three open clarifications:
three records each pushed the consumer out of a different home and none
named the remainder. Verified before recording it:

- ADR-0020 clause 7 puts it outside the generator.
- ADR-0007 and Constitution Principle III put it outside core and the CLI,
  which "receive only an already-validated CatalogSnapshot-shaped artifact".
- ADR-0012's published-schema constraint is about schema/adr.schema.json
  specifically — the canonical ADR JSON Schema hosted at its $id per
  ADR-0011 — so a separate package validating envelopes does not touch it.
  There is no conflict here, which was worth checking rather than assuming.

Constitution Principle III permits the placement directly: core and the CLI
may depend on "their own workspace packages". Because the package sits
outside packages/adapters/**, core-has-no-adapter-deps is satisfied by
construction rather than by exception.

Recorded as FR-044, which also requires the dependency direction and the
package's absence from packages/adapters/** both be enforced by the
dependency-graph check, each observed failing first (ADR-0016). The adapter
and the consumer depend on neither each other nor anything but the envelope
file, per composition-and-release-boundary.md §2.

Deliberately NOT decided, and marked out of scope: whether the package is
published at all, and if so whether it versions in lockstep or
independently. Both belong with the release decision ADR-0020 clause 9
defers.

FR-044 through FR-062 renumbered to FR-045 through FR-063 to make room,
following the convention this spec already set of renumbering rather than
suffixing FR identifiers. Numbering re-verified contiguous 001-063 with no
suffixed identifiers and no out-of-range references; checklist references
renumbered and its open-question table resynced from three items to two.

Two ADR-level clarifications remain, neither blocking planning: allRefs
population in production, and what "release evidence" means in ADR-0012
gate 4 while the release vehicle is deferred.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
ADR-0020 action item 1 continued. Adds plan.md, quickstart.md, and the two
010-specific contracts; research.md and data-model.md were written in an
earlier session and are unchanged.

The organizing constraint is Barrier B. ADR-0020 clause 6 and clause 5(a)
require the fresh T014 -> T014a oracle cycle and the clause-5 accept-corpus
freeze/audit to complete before any generator-derived output exists. The
plan makes that structural rather than procedural, per research.md R5:

- Input absence. input-manifest.md §5 already forbids recursive walking or
  glob-expansion to discover descriptors, so the only route to a corpus is
  a manifest naming its files explicitly. Absent manifest, absent corpus,
  absent output.
- Hash match, re-derived in CI, which is what makes "the expectations were
  never amended to fit the output" checkable rather than asserted.
- Ordering of the comparison harness, written only after the freeze and
  its audit, so clause 5's two steps stay two steps.

This matters because ADR-0020 clause 6 concedes the repository holds no
existing control here beyond repetition.

Seven phases A-G, each stating its barrier side, discharging all 63 FRs and
all 17 SCs exactly once.

Maintainer decision 2026-08-04, recorded here: clause 6's bar on "generator
output" does NOT reach unit-level execution of the adapter's pure
validators against fixtures whose expected values come from frozen
contracts. Backfilling is the thing clause 6 prevents, and that control has
no purchase on a test the oracle never sourced. Two limits travel with it —
where R4's definition and its distinguishing test disagree the plan takes
the definition, so all assembled-generator work stays behind the barrier;
and only Phase D's barrier side changes. research.md R4 keeps its original
open marker as the historical record of what was undecided when the
research ran.

Adopted sequence: [A || B] -> [C || D] -> E -> F -> G. The stricter
reading's sequence is retained as a documented counterfactual.

Two ADR-level clarifications remain open, neither blocking: allRefs
population in production, and gate 4's release-evidence component.

Still zero code and no package. ADR-0012 gate 3 remains open, gate 4 unmet
and not yet testable, and this feature has produced no evidence.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
100 tasks, T001-T100, across plan.md's seven phases. Nothing is checked;
generating the list performs no implementation.

Barrier B is enforced by the dependency graph rather than by prose. T024 is
the hard gate, and all 32 behind-barrier tasks (T069-T100) name T024 in
their Depends line — verified by script, not by reading. A dispatcher
cannot schedule behind-barrier work while T024 is unchecked. Every task
carries an explicit Barrier line.

20 of 100 tasks are marked [P]. Zero in phases B, E and F, which are serial
by construction per plan.md's three anti-verdicts: nothing runs alongside
E; F may not be authored early, since writing the comparison harness before
the freeze collapses ADR-0020 clause 5's two steps into one; and B may not
be split freeze-now/audit-later, because SC-010 requires both in the same
cycle.

One correction to the brief this list was generated from, worth recording
rather than smoothing over. "All 63 FRs and 17 SCs are discharged exactly
once" is not literally true of plan.md: six identifiers name two genuinely
separable obligations each and are assigned to two phases —

  FR-005  A + C   locality / consumer
  FR-044  A + C   placement and direction / behavioural
  FR-057  B + F   clause-5 step (a) / step (b)
  FR-058  C + F   consumer framing / report
  FR-063  A + F   documentation / report
  SC-012  C + F   framing / demonstration

These are represented as split discharges with named halves rather than
deduplicated to manufacture a clean 1:1. The other 57 FRs and 16 SCs are
each discharged exactly once. Full FR/SC coverage was verified against
spec.md by expanding range notation.

ADR-0016 is honoured with 12 explicit observed-failing steps: each check is
deliberately violated, watched failing, and restored, as a step distinct
from writing the check. The observation is the coverage.

Counting traps held: "fourteen" appears only as spike 009's count, with an
explicit prohibition on stating it as this feature's; glob rule 15's
non-firing is recorded as conformant rather than a coverage gap; file
counts stay distinct from entity-document counts.

Both open clarifications are carried unresolved — allRefs population at
T071/T078, and ADR-0012 gate 4's release-evidence component at T099,
recorded unmet rather than passed or failed.

No task schedules or prepares a release, and nothing claims above rung 1.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@mbeacom
mbeacom merged commit 891165f into main Aug 5, 2026
9 checks passed
@mbeacom
mbeacom deleted the mbeacom-supreme-guacamole branch August 5, 2026 03:24
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.

2 participants