docs(spec): open feature 010 — production Backstage catalog adapter - #83
Merged
Conversation
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>
There was a problem hiding this comment.
🟡 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.locklater 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 |
…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>
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.
Opens the production feature specification for
packages/adapters/catalog-backstage/. Specification only — no package, no source, noplan.md, notasks.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) fromadrkit.io/owned-pathsalone, enforces whole-operation atomic fail-closed semantics, and writes only the versioned envelope — never aCatalogSnapshot-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-descriptoronto 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-importandorchestratorcarry 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
diffclean) rather than paraphrased — it is the highest-risk transcription in the document.rung 2/rung 3/reference-verified/externally validatedoccurrence is a prohibition, never a claim.invalid-glob-compile-failureis 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:
This is load-bearing for FR-044–FR-048 and needs a maintainer decision before planning. The other two concern
allRefspopulation in production (makingduplicate-canonical-refpossibly 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 test857 pass / 0 fail ·typecheckclean ·check:deps→core-has-no-adapter-deps: ok·adr lint→ 20 records, 0 errors, 0 warnings.Nothing under
specs/009-catalog-binding-viability/,docs/adr/, orpackages/was modified.