Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .specify/feature.json
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
{
"feature_directory": "specs/009-catalog-binding-viability"
}
{"feature_directory":"specs/010-catalog-backstage"}
125 changes: 125 additions & 0 deletions specs/010-catalog-backstage/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Specification Quality Checklist: Backstage Catalog Adapter — Offline Owned-Paths Snapshot Generator

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-08-04
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [ ] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
Comment on lines +17 to +18
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

### The one item deliberately left unchecked

**"No [NEEDS CLARIFICATION] markers remain" does not pass, and must not be forced to pass.**
Two markers remain, recorded under the spec's *Open questions* heading. Both are gaps at
the **ADR level**, not drafting omissions: each was traced to the point where the normative
records stop short, and each is quoted against the record that declines to decide it.

A third question — which package owns consumer-side envelope validation and
`CatalogSnapshot` derivation — was **resolved on 2026-08-04 by maintainer decision**: its
own workspace package, separate from both the adapter and `@adrkit/core`. It is recorded
as FR-044 and under the spec's *Resolved questions* heading. Because it was load-bearing
for FR-045 through FR-049, planning is no longer blocked on it.

| # | Question | Why it is unresolvable from the ADRs |
|---|---|---|
| 1 | How, if at all, is `allRefs` populated beyond the primary `canonicalId` in production? | Spike 009's `entity-identity.md` §2 sourced aliases from synthetic fixtures only and explicitly deferred the production mechanism as "an explicitly separate, later, out-of-scope design decision this contract does not make." No ADR makes it either. |
| 2 | What constitutes the "release evidence" component of ADR-0012 gate 4? | Gate 4 reads "clean-clone / offline / adapter-boundary / **release** evidence passing," but ADR-0020 clause 9 defers the release vehicle (publish target, tag, channel) to a later record. The first three components are specifiable now; the fourth has no decidable referent. |

Resolving these by guess would violate the specification's own binding honesty rules (and
ADR-0012's "production limits are **not** guessed now; they must be ratified from evidence").
They are therefore **carried forward for a maintainer decision**, which is the correct disposition
for an ADR-level gap, rather than closed by invention. Neither blocks planning: question 1
affects only whether `duplicate-canonical-ref` is reachable outside fixtures, and question 2
belongs with the release decision ADR-0020 clause 9 already defers.

### Validation notes for the checked items

- **Implementation details.** The spec names pinned external artifacts — `picomatch` with frozen
options, the Backstage validator commit `1121a4facd9e321179d0402c3f355e4a649e84d9`, SHA-256 —
as *frozen contract terms* inherited from ADR-0012, ADR-0015 and the 009 contracts, not as
design choices being made here. Under this repository's conventions a frozen, ratified
dependency pin is a requirement, not an implementation leak. No internal module layout,
function signature, or code structure is prescribed.
- **Testability.** Every functional requirement is stated as an observable input/output or
abort condition. Requirements imposing process obligations (FR-053 through FR-063) are
verifiable against recorded artifacts — frozen hashes, audit findings, observed-failing
records — as ADR-0016 requires.
- **Traceability.** Every requirement cites its normative source. Requirements derived rather
than quoted are marked as derived and carry the clause they are derived from.
- **Counts.** All quantities in the spec were counted in the cited source and the source is named
at the point of use. In particular the fatal trigger enumeration is **fifteen** for this
feature: the fourteen that `atomic-fail-closed.md` §4 requires ("MUST be one of exactly these
fourteen values"), **plus** `inadmissible-descriptor`, which ADR-0015 adds and which appears
nowhere under `specs/009-catalog-binding-viability/`. FR-035 states explicitly that fourteen is
correct for spike 009 and wrong for feature 010, and cites **ADR-0015's Condition of Acceptance
2** — binding "on any work that cites this record" — as the direct authority, this feature being
the follow-up that condition names. ADR-0020's separate "13 required trigger classes" figure is
deliberately never cross-mixed with the fourteen- and fifteen-value enumerations. Other counted
quantities: fifteen ordered glob rules; five consumer validation steps; four ADR-0012 production
gates; sixteen placeholder descriptors in three distinct forms; the corpus file and document
counts.
- **Verified during review.** All ten relative document links resolve. The ADR-0015 validator
table transcribed into FR-016 is byte-identical to ADR-0015 lines 120–123. FR numbering is
contiguous FR-001…FR-063, SC numbering contiguous SC-001…SC-017, and every internal `FR-`/`SC-`
cross-reference falls in range. A targeted sweep found no claim about Backstage-as-a-running-system,
no release-authorization language, and no ADR-0014 rung 2 or rung 3 claim.
- **Known correction made during review.** SC-007 originally required every one of the fifteen
glob rules to be exercised by a violating pattern. Rule 15's `invalid-glob-compile-failure` is
described by its own contract as a defensive backstop "expected to never occur in practice,"
so that criterion was unsatisfiable as written; it now requires rules 1–14 to be exercised by
violating patterns and rule 15's `accepted` outcome by valid ones, and explicitly states that a
run never producing `invalid-glob-compile-failure` is conformant.
- **Second correction, applied after maintainer review.** The `inadmissible-descriptor`
requirements were strengthened and two requirements added, on the authority of ADR-0015's
Condition of Acceptance 2 read in full:
- **FR-018** now states the consequence explicitly — abort, non-zero status, no usable partial
output, *identical to every other fatal trigger* — and quotes ADR-0015's "adds a failure
*class*; it removes no failure."
- **FR-019 (new)** requires that no inadmissible descriptor participate in a uniqueness
comparison, per ADR-0015's "Ordering" section.
- **FR-020** now requires **all three** record fields. ADR-0015's "Failure semantics" section
names the offending path, the failing field, **and the validator that rejected it** — three
items, where a two-item paraphrase would have dropped the validator.
- **FR-021 (new)** forbids treating duplicate detection as a proxy for validity, and requires
conformance evidence to include a descriptor that is inadmissible **and** canonically unique.
ADR-0015 records that `bulk-import` and `orchestrator` in `rhdh-plugins` "canonicalize
distinctly" and so "collide with nothing," yet "are exactly as invalid as the other fourteen";
it concludes "[d]uplicate detection is not a validity check."

Because the repository uses letter suffixes for tasks (`T014a`) but never for functional
requirements, the two additions were absorbed by renumbering rather than by inventing an
`FR-018a` convention: former FR-019 became FR-020, and former FR-020…FR-061 became
FR-022…FR-063. The rewrite was applied by script across definitions and prose references
together, then re-verified — numbering is contiguous FR-001…FR-063, no suffixed or sentinel
identifiers survive, and every internal reference resolves in range.

### Scope reminder for downstream phases

This specification is authorized by ADR-0020 toward **ADR-0014 rung 1 only**. It authorizes the
work, not the release. Planning and task generation must preserve that boundary, must not schedule
a release, and must not begin generator output before the fresh T014 → T014a oracle cycle
(ADR-0020 clause 6) and the clause 5(a) freeze/audit have both completed.
175 changes: 175 additions & 0 deletions specs/010-catalog-backstage/contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Phase 1 Contracts: Production Backstage Catalog Adapter

**Feature**: `010-catalog-backstage` | **Companion to**: `plan.md`,
`research.md`, `data-model.md`, `quickstart.md`

## What this directory is, and what it deliberately is not

Spike `009-catalog-binding-viability` already designed the contracts this
feature needs. Its `contracts/` directory holds **eleven** files (counted by
`ls specs/009-catalog-binding-viability/contracts/`). This feature's job is to
**carry them into production with citation**, not to redesign them.

So this directory is small on purpose. It contains:

- **This register** (§1–§3) — the authoritative record of which spike-009
contract is adopted, which is adopted with a named delta, and which is
deliberately excluded, with the authority for each.
- **Three normative files** — one restated because a **count changed**, and
two that are genuinely new because spike 009 had no counterpart.

| File | Why it exists here |
|---|---|
| `atomic-fail-closed.md` | The trigger set changed from **fourteen** to **fifteen**. A reader must not be sent to a document that names a different number. |
| `admissibility.md` | Genuinely new. ADR-0015 postdates the spike's contract set; there is no 009 counterpart. |
| `package-boundary.md` | Genuinely new. Spike 009 built no packages, so it could not specify a boundary between two. |

**Everything else is a citation, not a copy.** Where §2 marks a contract
`Adopted unchanged`, the spike-009 file **is** this feature's contract for
that surface, and it is authoritative in its original location. Copying it
here would create two texts that drift.

---

## §1. How to read the adoption register

Each row carries one of three statuses:

| Status | Meaning |
|---|---|
| **Adopted unchanged** | The 009 file is this feature's contract. Cite it by path and section. Do not restate it. |
| **Adopted with delta** | The 009 file is this feature's contract **except** for the named delta. Every delta names its authority. |
| **Excluded** | Deliberately not carried forward, with the reason. Not an oversight; do not reintroduce. |

A **delta** is a change this feature makes to the spike's design. Every delta
below names the record that authorizes it. A change with no named authority is
not a delta — it is an error.

---

## §2. The adoption register — all eleven spike-009 contracts

### Adopted unchanged (5)

| 009 contract | Sections this feature relies on | Where used |
|---|---|---|
| `glob-dialect.md` | §1 engine/options; §3 the **fifteen** ordered rules, first-match-wins; §4 dotfile policy; §5 migration; §6 compile-once-per-run | `data-model.md` §7.1 |
| `owned-paths-annotation.md` | §1 the **five** ordered decode steps; §3 the **three** ownership states; §4 empty-string edge case; §5 determinism | `data-model.md` §6, §7.2 |
| `entity-identity.md` | §1 canonicalization; §2 alias refs synthetic-only; §3 collisions; §4 overlap-is-not-collision; §5 case-sensitivity boundary | `data-model.md` §5 |
| `input-manifest.md` | §1 closed schema; §2 the **three** version/capability rejections; §3 repository identity and §3.1 scratch-repo; §4 digests and §4.1 two-stage path validation; §5 input boundary | `data-model.md` §1, §2 |
| `snapshot-envelope.md` | §1 envelope shape and the **five-field** entity record; §2 the **five** consumer validation steps; §3 digest; §4 staleness; §5/§6 identity and isolation | `data-model.md` §9–§14 |

**One clarification that is not a delta.** `glob-dialect.md` §1 records the
picomatch version as a fixed string. This feature **reads the version from the
resolved dependency at runtime** rather than transcribing it, so the envelope
cannot silently disagree with the matcher that actually ran. That is an
implementation instruction about how a recorded value is obtained, not a
change to the contract's content — the engine, the options, and the fifteen
rules are all untouched. `research.md` R3 verified the current resolution as
`picomatch@4.0.5` at `bun.lock` line 165.

### Adopted with delta (3)

#### D1 — `atomic-fail-closed.md`: fourteen → **fifteen** trigger classes

- **Delta**: add `inadmissible-descriptor` to the closed trigger set.
- **Authority**: ADR-0015 Condition of Acceptance 2, which requires the
trigger be carried onto the atomic surfaces; spec FR-035, which states
that fourteen "is correct for spike 009 and **wrong for this feature**; it
MUST NOT be copied across."
- **Verification**: `atomic-fail-closed.md` §4 says "exactly these **fourteen**
values" and lists them at lines 52–67. `inadmissible-descriptor` appears
**nowhere** under `specs/009-catalog-binding-viability/`.
- **Consequence**: because the count itself changed, this contract is
**restated in full** at `atomic-fail-closed.md` in this directory. Cite that
file, not the 009 one, for any trigger enumeration.
- Everything else in the 009 contract — the fail-closed semantics, the
no-partial-output rule, §5's grouping of the four manifest-request-level
rejections — is adopted unchanged and restated faithfully.

#### D2 — `composition-and-release-boundary.md`: §4 superseded for **work**, not for **release**

- **Delta**: §4 "No Shipping Artifact — Absolute Scope Boundary" is superseded
**only** to the extent that this feature is authorized to build two real
workspace packages. §5 "Release Vehicle Is Explicitly, Permanently
Undecided" is **not** superseded; it is reinforced.
- **Authority**: ADR-0020, which authorizes the **work** and not the
**release**; ADR-0020 clause 9, which holds this at ADR-0014 **rung 1**.
- **Precise boundary**: building a package is authorized. Publishing one is
not. Scheduling, designing toward, or implying a release is not. No artifact
of this feature may claim any rung beyond rung 1.
- §1 (standalone offline generator), §2 (core/CLI isolation unaffected), §3
(ADR-0007/ADR-0009 status unaffected) are adopted unchanged.
- §6 ("What This Plan Itself Adds to the Repository (None)") was true of the
spike's plan and is **not** true of this one: this plan's design artifacts
are followed by real package construction. Stated so no reader carries the
spike's zero-artifact claim forward.

#### D3 — `structural-fixtures-and-corpora.md`: pins and protocol carry; fixture inventory does not

- **Delta**: §1's three pinned commits and its **re-verify-before-execution,
fail-closed-on-drift, never-silently-substitute** protocol are adopted
unchanged. §§2–10's spike-specific fixture inventory is **not** carried
wholesale; this feature derives its own fixture set from its own FRs and SCs.
- **Authority**: the fixtures served spike FRs that this feature does not
restate; ADR-0020 clause 5 imposes a **different** corpus obligation (the
frozen, independently-audited accept corpus, `data-model.md` §17) that has
no spike counterpart.
- **Carried forward explicitly** because they remain true and are easy to lose:
§6 (absent annotation is the overwhelmingly common real-corpus case), §9
(overlap between distinct entities is not a failure), §10 (never a silent
skip).

### Excluded (3)

| 009 contract | Reason for exclusion | Authority |
|---|---|---|
| `comparison-heuristics.md` | Spike apparatus. The B/C/D option comparison is a **measurement instrument**, labelled `non-authoritative` by its own contract. This feature has no options to compare — ADR-0020 authorizes one design. | User instruction; ADR-0020 authorizes a single adapter, not a comparison |
| `evidence-bundle-and-verdict.md` | Spike apparatus. The evidence-bundle and three-way-verdict machinery existed to produce a **viability verdict**. That verdict was reached; ADR-0020 **is** its outcome. Re-running the machinery would re-decide a decided question. | User instruction; ADR-0020 is the record that closes it |
| `scale-and-security-measurement.md` | Its §§1–4 scale-measurement instrument is spike apparatus, and its §4 "Not Guessed Now" rule is already carried by ADR-0012 directly. | ADR-0012's own "production limits are not guessed now" |

**One partial retention from an excluded file, stated so it is not lost.**
`scale-and-security-measurement.md` §5 (network denial) describes the protocol
for evidencing that a run performs no network access, and `input-manifest.md`
§3.1 describes the standalone scratch repository. Those two remain relevant to
this feature's offline/clean-clone obligations (spec User Story 9). They are
cited from their original locations; the surrounding measurement instrument is
not carried.

---

## §3. What is genuinely new in this feature

Three surfaces have **no** spike-009 contract, because they did not exist when
the spike was designed:

1. **Admissibility** (`admissibility.md`) — ADR-0015 postdates the spike's
contract set. §4 of `data-model.md` and this contract are its only
specification here.
2. **The two-package boundary** (`package-boundary.md`) — spike 009 built no
packages (`composition-and-release-boundary.md` §6), so it could not
specify a boundary between two of them.
3. **The pre-output barrier** — ADR-0020 clauses 5 and 6. This is specified in
`plan.md` as **Barrier B**, because it is a **sequencing** constraint on the
build rather than a contract on a surface. `data-model.md` §16 and §17
define the artifacts it produces.

---

## §4. Standing honesty constraints on every file in this directory

These apply to this register and to all three normative files, and they are
repeated in each:

1. **Never assert what Backstage-as-a-running-system does.** The only warrant
available is **what a pure validator predicate returns when invoked** at the
pinned Backstage commit `1121a4facd9e321179d0402c3f355e4a649e84d9`.
2. **Never state a count that has not been verified in the cited source.** Every
count in this directory names where it was read.
3. **This feature has produced no evidence.** Every behavioural statement here
is a **requirement**, never a report of an observation.
4. **Genuine unknowns are marked** `[NEEDS CLARIFICATION: ...]`, never resolved
by guess.
5. **ADR-0014 rung 1 only.** Maintainer reference verification MUST NOT be
called external, third-party, or community. Only corpus *data* is
third-party — never the validation.
Loading