§22.4a SLICE-1 of docs/design/2026-06-06-configurable-collection-metadata.md (§7.2). Entry metadata can live in a per-entry `<slug>.meta.yaml` sidecar with the `.md` kept as pure prose (INV-2). Additive and non-breaking — with no sidecars present every corpus stays on the legacy frontmatter path, byte-identical (N=1 unchanged). - Dual-read (app/metadata.py `read_entry`) — sidecar-else-legacy-frontmatter, identical records (INV-6); unknown/forward-compat keys ride along through parse→serialize and migration (INV-7, `Entry.extra`). A degenerate sidecar (malformed/empty/slug-less) never drops the entry — slug backstopped from the filename stem, flagged not lost (INV-3). - Migration tool (`metadata.migrate_collection`) — idempotent, one ChangeFiles commit per collection (new `gitea.change_files`). Tested as a function; its Owner-gated operator trigger is DEFERRED to SLICE-4 (write paths must become sidecar-aware first — see the design's SLICE-4 note + INV-8). No production trigger ships here, so no corpus is rewritten. - Malformed flag — migration 033 adds `cached_rfcs.metadata_malformed` (additive); the corpus mirror derives it; catalog list + entry-detail APIs surface `metadata_malformed`. - INV-7 at graduation — graduation now carries `Entry.extra` through the rebuild instead of dropping forward-compat keys. Gate: backend 575 passed (28 new: test_metadata / _migration / _cache + graduation extra-preservation). Frontend untouched. CHANGELOG 0.47.0 + upgrade-steps; VERSION + frontend/package.json -> 0.47.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
33 KiB
Solution Design: Configurable Collection Metadata (clean-doc tagging)
| Author(s) | Ben Stull |
| Reviewers / approvers | Ben Stull |
| Status | draft |
| Version | v0.1.6 |
| Source artifacts | Reference modeled: retired BDD Release Planner (wiggleverse/wiggleverse-ecomm-bdd-release-planner-app, RETIRED 2026-06-04) · Related: 2026-06-05-three-tier-projects-collections.md (§22) · Corpus: ecomm Shopify-modeled BDD (wiggleverse-ecomm-meta/research/shopify, ~1,238 scenarios) · Supersedes: 2026-06-06-per-type-surfaces.md |
Change log
| Date | Version | Change | By |
|---|---|---|---|
| 2026-06-06 | v0.1.0 | Initial draft from discovery session OHM-0079.0 | Ben Stull |
| 2026-06-06 | v0.1.1 | Value-only Executive Summary; add Pain Points | Ben Stull |
| 2026-06-06 | v0.1.2 | Business Outcomes restated as business (adoption/diversity); Business Use Cases → solution-agnostic | Ben Stull |
| 2026-06-06 | v0.1.3 | Supersede per-type-surfaces draft (harvest patterns; bdd coverage future; §22.4a amendment); split Business Actors / Product Personas | Ben Stull |
| 2026-06-06 | v0.1.4 | Two-part restructure: §1 Business Context (solution-agnostic, 1.1–1.9) + §2 Solution Proposal; renumber | Ben Stull |
| 2026-06-06 | v0.1.5 | Move Business Actors to §1.3 (define roles before Problem/Pain reference them) | Ben Stull |
| 2026-06-06 | v0.1.6 | §7.1 execution convention — each slice is its own writing-plans→executing-plans coding session, plans just-in-time | Ben Stull |
1. Business Context
The business lens — solution-agnostic throughout. No mechanism is proposed until §2.
1.1 Executive Summary
A deployment's corpus is only as valuable as the ability of the people running it to prioritise it, navigate it, and act on it — and as valuable as the downstream tools that can read structured signal out of it. Today that value is stranded: operators and contributors can't rank what matters or find content by what matters, and the tools meant to plan and build from the corpus have nothing structured to consume. The value at stake is lower-friction corpus planning for operators and contributors, broader adoption by teams whose document types the platform couldn't previously serve, and a corpus external tooling can consume without bespoke glue. (Value summary; the solution is proposed in §2.)
1.2 Background
The framework hosts RFC standardization for multiple deployments. One deployment hosts the ecomm BDD corpus — ~1,238 Shopify-modeled scenarios, one markdown file per scenario, slugged by feature ID (DD-FF-NNNN-slug). A standalone BDD Release Planner previously let operators search that corpus, attach metadata (priority P0–P3, owner, status), cluster scenarios into named releases, and emit each release as a roadmap phase. §22 (three-tier projects/collections) absorbed the planner's corpus hosting into rfc-app (the corpus now runs as a bdd project on the RFC deployment) and the planner was retired — but its annotation half (priority/tags on scenarios, filtering, bulk assignment) was never rebuilt. Teams evaluating rfc-app for other document types often need structured attributes (a priority, a status, domain tags) the platform can't yet express — so they go elsewhere.
1.3 Business Actors / Roles
Real-world roles, solution-agnostic — they exist whether or not rfc-app does. They are defined here, before the Problem (§1.4) and Pain Points (§1.5) reference them; the Business Use Cases (§1.9) are about these roles, and the Product Personas (§3) map onto them.
| Role | Responsible for (in the business) |
|---|---|
| Standards owner | Owns an organization's RFC / standards / requirements process; decides what's tracked and how |
| Release planner | Decides what work belongs in upcoming releases |
| Requirements author | Proposes and curates the requirements (e.g. BDD scenarios) |
| Requirements consumer | A person or downstream tool that plans or builds from the requirements |
| Reader | Anyone navigating the corpus to find what's relevant to them |
1.4 Problem Statement
rfc-app cannot express or surface structured signal about its content. Tags are free-form strings with no filtering; there is no notion of priority or any other collection-defined attribute; the catalog is a flat list; and what little metadata exists is mixed into the top of every document. As a result, a corpus cannot be prioritised, navigated by attribute, planned in bulk, or cleanly consumed by downstream tools — and teams whose workflows depend on such attributes cannot adopt the platform at all.
1.5 Pain Points
| # | Pain | Who feels it | Cost / frequency today |
|---|---|---|---|
| PP-1 | Scenarios carry no priority, so triage and planning happen off-platform, in spreadsheets and memory | Release planner, contributor | Every planning cycle; signal lives off-platform and goes stale |
| PP-2 | The catalog is a flat, unfilterable list — at ~1,200 scenarios, "show me the P0 checkout scenarios" is impractical | Reader, release planner | Every browse/triage; finding the right work is slow and error-prone |
| PP-3 | Tags are free-form with no filtering payoff, so they're decorative and go unmaintained | Contributor | Ongoing; the one existing affordance rots |
| PP-4 | Annotating many scenarios means opening many PRs, so bulk planning has no home in the tool | Release planner | Every batch; the core planning gesture is effectively impossible |
| PP-5 | rfc-app metadata clutters the top of every document, hurting readability and making the corpus awkward to consume cleanly | Reader, downstream consumer | Every read; every downstream integration |
| PP-6 | Downstream tools have no structured signal to read — the retired planner's capability left a gap | Downstream consumer | Continuous since the planner's retirement |
| PP-7 | Teams whose document types need structured attributes can't model them, so they don't adopt rfc-app | Prospective adopter (org/team) | Every evaluation that ends in "not yet" |
1.6 Targeted Business Outcomes
Business outcomes for rfc-app as a platform — adoption, reach, and diversity of use — not solution outputs. (Whether documents carry a priority is a solution output, tracked as a slice's Definition of Done in §7, not here.)
| Outcome | Success metric | Baseline → Target | Guardrail (must not regress) | How / when measured |
|---|---|---|---|---|
| Teams blocked by missing structured attributes now adopt rfc-app | # organizations on rfc-app; # active users | internal deployments only → external orgs onboard | existing deployments don't churn | deployment registry + usage analytics; quarterly |
| The platform hosts a wider variety of workflows and document types | # distinct document/collection types & field schemas in use | today's handful → broader mix | existing types' experience unchanged | type/schema census; quarterly |
| Corpus planning happens on-platform rather than in side tools | share of prioritisation/planning done in rfc-app vs spreadsheets | largely off-platform → on-platform | — | operator interviews + usage signals; quarterly |
1.7 Scope (business)
- In scope: the corpus can carry per-item importance and categorisation; people can find items by those attributes; the signal is captured durably and is consumable by other people and tools; teams with new document types can express the attributes their workflow needs.
- Out of scope (business): deciding what a given deployment's priorities or categories should be (that's the deployment's editorial choice); release sequencing and ship tracking as a business process (stays a downstream/operator concern).
- Non-goals: modelling "releases" as a first-class business object inside the platform.
(Solution-specific scope/non-goals are in §2.)
1.8 Assumptions · Constraints · Dependencies
- Assumptions: git remains the content source of truth and downstream consumers can read the corpus from git; the BDD grain is one markdown file per scenario (already true for the ecomm corpus).
- Constraints: rfc-app is a framework hosting multiple deployments — any change must be mechanical and non-breaking, with §20 changelog/upgrade-steps; the hard secrets rule (§6.3) holds; edits must respect scope-role authorization (§22 Part B / S3); the §22.4a "engine unchanged" rule holds (INV-8).
- Dependencies: the S3 scope-role resolver (
auth.effective_scope_role); the existing git write-through used byedit-meta(§9.5); the §22 collection model; the bindingSPEC.md§22.4a contract, which §2's solution amends (§7 SLICE-0).
1.9 Business Use Cases
Solution-agnostic: what an actor (§1.3) wants to accomplish, why (value), and what success looks like — no reference to any product. Each could be satisfied by a person by hand before any software. Form: "As a … I can … so that …".
BUC-1 — As a release planner, I can prioritise the requirements in a body of work, so that I can decide what belongs in upcoming releases.
Scenario: BUC-1 — Prioritise to plan releases
Given a body of requirements of varying importance
When the planner weighs which matter most
Then they hold a ranking of those requirements by importance
And can decide a release's contents from it
- Acceptance: the planner can select and justify the next release's contents from the relative importance of the work.
BUC-2 — As a planner facing a large body of requirements, I can organise and triage it within a normal working session, so that planning actually gets done rather than deferred or improvised.
Scenario: BUC-2 — Triage at scale
Given more requirements than can be weighed one at a time
When the planner ranks and groups them in bulk
Then the body of work reflects those decisions without per-item drudgery
- Acceptance: a planner moves from an unsorted corpus to a prioritised plan in one sitting.
BUC-3 — As a team, I want the importance and categorisation of our requirements captured durably and shareably, so that other people and tools can plan from it without re-deriving it.
Scenario: BUC-3 — Durable, shareable signal
Given requirements that have been weighed and categorised
When someone or something else needs to plan from them
Then they can read what matters and why without asking the original author
- Acceptance: a second party — person or tool — can pick up the work and plan from it unaided.
BUC-4 — As a team with a specialised body of documents, I can capture the attributes that make them actionable (importance, status, category), so that I can manage that work the way my domain requires.
Scenario: BUC-4 — Manage a domain's work on its own terms
Given documents whose usefulness depends on domain-specific attributes
When the team records and works with those attributes
Then they can run their workflow with the distinctions it depends on
- Acceptance: the team can capture and act on the distinctions their domain requires — success is them choosing to manage the work this way.
BUC-5 — As someone consuming a large corpus, I can find the items that matter to my current purpose, so that I act on the right things instead of wading through everything.
Scenario: BUC-5 — Find what matters
Given a large body of items
When the consumer looks for the important ones for their task
Then they can locate them quickly
- Acceptance: a person narrows a large corpus to the relevant, important subset for their task.
2. Solution Proposal
The solution is to build it into rfc-app. Give every collection a small, declared field schema (in its .collection.yaml) so it can carry structured metadata — priority, tags, and any custom fields the deployment defines. Store each entry's values in a clean sidecar file so the document body stays pure prose. rfc-app then renders those fields as forms, filters the catalog by them (faceted, with counts), and lets authorized users tag in single and bulk gestures committed straight to git; downstream tools read the values from the sidecars directly. It is one generic mechanism — tags and priority are just fields — not per-type special-casing and not a bespoke "release" entity.
Why a software solution (and not a manual one). A non-build alternative — operators maintaining priorities/tags in a shared spreadsheet — was considered and rejected: it leaves the corpus unfilterable in-tool (PP-2), keeps documents and the side-sheet out of sync, produces no durable git-readable signal for downstream tools (PP-5/PP-6), and does nothing for the adoption outcome (§1.6, PP-7). The value only lands if the structure lives with the content.
Solution-specific scope. Out: release ordering, ship status, roadmap emission, the specification release-planning surface — all downstream, reading sidecars from git. In-app management of field definitions (edit .collection.yaml in git for v1); corpus-wide tag rename/merge/delete; sub-document grain; a whole-corpus export endpoint. Future (recorded, not v1): a bdd coverage surface — a verifies-style ref field type plus a read-derived view mapping features to the spec sections they exercise (harvested from the superseded per-type-surfaces draft); deferred pending §9 Q4. This solution amends the binding SPEC.md §22.4a contract (§7 SLICE-0).
(The Product and Engineering sections below — §§3–7 — elaborate this build. They would be replaced by an operational plan if the chosen solution were non-software.)
3. Product Personas
rfc-app's user types — each an embodiment of one or more Business Roles (§1.3). The Product Use Cases (§4) are about these personas.
| Product persona | In rfc-app | Maps to business role(s) |
|---|---|---|
| Collection Owner | scope-role Owner; declares the collection's fields: schema (edits .collection.yaml) |
Standards owner |
| Contributor | scope-role contributor; sets metadata (single + bulk), proposes/curates entries | Requirements author; Release planner |
| Reader | viewer; browses and filters the catalog | Reader |
| Downstream consumer | an external system reading sidecars + .collection.yaml from git |
Requirements consumer |
4. Product Use Cases
Scenario: PUC-1 — Set priority/tags on a scenario (realizes BUC-1, BUC-4)
Given I am a Contributor viewing a scenario whose collection defines priority and tags
When I choose P0 in the priority control and add the tag "checkout"
Then the metadata panel reflects P0 and the checkout tag
And the change is committed directly to the scenario's sidecar
Scenario: PUC-2 — Bulk tag/untag from the catalog (realizes BUC-2)
Given I have multi-selected several scenarios in the catalog
When I choose "Set priority → P1" from the bulk action bar
Then every selected scenario shows P1
And the bulk change is one commit
Scenario: PUC-3 — Filter the catalog by facet (realizes BUC-5, BUC-1)
Given the left pane shows faceted filters generated from the collection schema
When I check Priority P0 and tag "checkout"
Then the catalog shows only scenarios matching both
And each facet value shows its result count
Scenario: PUC-4 — A Collection Owner declares fields (realizes BUC-4)
Given a Collection Owner edits .collection.yaml to add a priority enum field
When the collection is re-ingested
Then the priority filter and the priority form control appear automatically
Scenario: PUC-5 — Migrate a collection to clean docs (product-only; enables BUC-3)
Given a collection whose docs still carry top-of-doc frontmatter
When the operator runs the frontmatter→sidecar migration
Then each doc body becomes pure prose and a sidecar holds its metadata
And rfc-app reads the collection identically before and after
Scenario: PUC-6 — A malformed entry is visibly fixable (realizes BUC-3)
Given a stored entry whose metadata fails its collection's schema
When the catalog renders
Then the entry still loads (read never hard-fails)
And it is flagged "malformed metadata" so a Contributor can fix it
5. UX Layout
5.1 Screen: Catalog (left pane) (serves PUC-3, PUC-6)
- Purpose: browse and filter a collection's entries.
- Layout (top → bottom): full-text search (existing); faceted filter groups (one per schema field + state): each a collapsible group with per-value result counts and multi-select checkboxes;
tags-type fields include a "filter values…" search box to stay usable at 30+ values. - States: happy: facets with counts · empty: "no entries match" + clear-filters · loading: skeleton facets · error: retry · malformed: entries failing their schema carry a fixable marker (parallel to §22.4c
unreviewed) and are filterable.
5.2 Screen: Scenario detail — metadata panel (serves PUC-1)
- Purpose: view/edit one entry's metadata.
- Layout: one control per schema field —
enum→ single-select;tags→ removable chips + add-tag input (with existing AI suggest);text→ text input. The body renders below as pure prose; metadata never appears inline. - States: read (no edit role) shows values · edit (authorized) shows controls · saving: spinner · error: field-level validation message.
5.3 Screen: Catalog — bulk action bar (serves PUC-2)
- Purpose: apply a field value to many entries at once.
- Layout: selecting ≥1 row reveals a sticky bar: "N selected · Set priority ▾ · Add tag ▾ · Remove tag ▾ · Clear". Applying commits once.
- States: none selected: hidden · applying: progress · partial failure: toast naming entries that failed validation, others applied.
6. Technical Design
6.1 Invariants
- INV-1: The sidecar (
<slug>.meta.yaml) is the source of truth for entry metadata;cached_rfcsis a derived index, fully rebuildable from git. - INV-2: A document body (
.md) never contains rfc-app metadata once migrated; metadata lives only in the sidecar. - INV-3: Reading a collection never hard-fails on bad metadata — an invalid value surfaces as a warning, the entry still loads, and the catalog flags it (§5.1).
- INV-4: Metadata writes are authorized by scope-role (contributor+ on the collection) and validated at the write boundary; content-body edits keep their existing PR-review path.
- INV-5: A collection with no
fields:block behaves exactly as today (free-formtagsonly). The §22.13 generated default collection isdocumentwith no fields → N=1 deployments see zero change. - INV-6: Dual-read: parser reads the sidecar if present, else legacy top-of-doc frontmatter, with identical resulting in-memory records.
- INV-7: Unknown / forward-compat keys in a sidecar ride along untouched — never dropped on read or rewrite, never reported as malformed.
- INV-8: Engine unchanged (§22.4a) — additive and read-mostly; never forks the content write path, the propose→branch→PR→graduate lifecycle, threads/flags/chat, or the storage model. Metadata edits reuse the existing
edit-metagit write-through.
6.2 High-level architecture
flowchart LR
subgraph Git[content repo]
CY[.collection.yaml<br/>fields: schema]
MD[slug.md<br/>prose body]
SC[slug.meta.yaml<br/>values]
end
CY --> ING[ingest / parser<br/>lenient, type-agnostic]
MD --> ING
SC --> ING
ING --> VAL[metadata_schema.validate<br/>advisory at read]
VAL --> DB[(cached_rfcs<br/>values + facet counts + malformed)]
DB --> API[API: schema · list+filter · facets · edit]
API --> FILT[left-pane faceted filters]
API --> PANEL[detail metadata panel]
API --> BULK[bulk select bar]
PANEL -->|validate + direct commit| SC
BULK -->|validate + 1 commit| SC
SC -.read from git.-> CONS[downstream consumers]
- ingest/parser — reads
.collection.yamlschema + sidecars (or legacy frontmatter), stays lenient/type-agnostic (INV-7); rebuildscached_rfcs; never authoritative. metadata_schema.validate(values, fields) → [problems]— the one place that knows a collection's required/forbidden fields and each field's shape (modeled onregistry.py). Advisory at ingest (warn + malformed flag, INV-3); enforced at the write boundary (INV-4).- API — serves the schema, filtered lists with facet counts + malformed flag, and metadata edits; never writes metadata anywhere but the sidecar.
6.3 Data model & ownership
| Entity | Owned by | Key fields | System of record |
|---|---|---|---|
| Collection field schema | Collection Owner | fields: {name → {type, values?, label}} in .collection.yaml |
git |
| Entry metadata values | Contributor | sidecar <slug>.meta.yaml: lifecycle + schema fields + forward-compat keys (INV-7) |
git (sidecar) |
| Derived index | ingest | per-entry values + facet aggregations + malformed flag |
cached_rfcs (SQLite, derived) |
Field types (v1): enum (single-select; controlled by required values:), tags (multi-value; free-form unless values: given), text (free string). Future: ref (a typed cross-entry link — basis for the deferred bdd verifies/coverage surface; §2, §9 Q4). Unknown types ignored with a warning.
Sidecar example:
slug: 01-01-0001-view-today-s-key-performance-metrics-at-a-glance
title: View today's key performance metrics at a glance
state: active
owners: [ben.stull]
priority: P1
tags: [dashboard, analytics]
owner: hasan
6.4 Interfaces & contracts
GET …/collections/<c>— out: collection incl.fieldsschema.GET …/collections/<c>/rfcs— in: filter params (?priority=P0&tags=checkout&state=active; OR within a field, AND across fields;?malformed=true) · out: entries with values + per-entrymalformed+facets: {field → {value → count}}. Errors: 400 unknown field.POST …/rfcs/<slug>/meta— in:{field: value}· effect: validate → write sidecar → direct commit → re-ingest. Errors: 403, 422.POST …/collections/<c>/meta/bulk— in:{slugs, op: set|add|remove, field, value}· out:{applied, rejected}· effect: validate → write N sidecars → one commit → re-ingest. Errors: 403, 422.
6.5 Per–Product-Use-Case design
PUC-2 — Bulk tag/untag
sequenceDiagram
actor U as Contributor
participant C as Catalog UI
participant A as API
participant V as metadata_schema
participant G as Git
participant D as cached_rfcs
U->>C: select rows, "Set priority P1"
C->>A: POST /meta/bulk {slugs, set, priority, P1}
A->>A: authz (scope-role)
A->>V: validate values vs schema
A->>G: write N sidecars, 1 commit
A->>D: re-ingest affected entries
A-->>C: {applied, rejected}
C-->>U: rows show P1; toast on any rejected
- Implementation: reuse the
edit-metagit write-through, extended to target the sidecar and batch N files into one commit. Honors INV-1/INV-4/INV-8.
PUC-5 — Migration
- Implementation: a tool walks a collection; for each entry with legacy frontmatter it writes
<slug>.meta.yamland rewrites<slug>.mdto the body only — one commit per collection, idempotent, preserving unknown keys (INV-7). Dual-read (INV-6) lets it run anytime; lazy migration converts stragglers on first metadata edit.
6.6 Non-functional requirements & cross-cutting concerns
- Security & privacy: edits gated by
auth.effective_scope_role; no secrets in sidecars; git history records authorship. - Performance & scale: facet counts from the derived DB; responsive at ~1.2k entries with dozens of tag values.
- Availability & resilience: bad metadata never blocks read (INV-3); failed re-ingest leaves git authoritative, recoverable by rebuild.
- Observability: log each metadata commit; warn-log + count schema-validation failures on ingest.
- Accessibility: facet groups and form controls keyboard-navigable; checkboxes labelled value + count.
6.7 Key decisions & alternatives considered
| Decision | Chosen | Alternatives | Why |
|---|---|---|---|
| Solution type | Build into rfc-app | Manual (shared spreadsheet) | Manual leaves corpus unfilterable, out of sync, no git-readable signal (§2) |
| Release modeling | Metadata only; releases downstream | First-class release entity | Operator pulled ordering/ship-status out of rfc-app |
| Tag system shape | One generic typed-field system | Releases first-class + simple tags; namespaced facets | Tags/priority/custom are all just fields |
| Schema model (D9) | Pure collection-config | Type-driven hard-coded schemas (per-type-surfaces draft) | Flexible, data-driven |
| Metadata storage | Sidecar per entry | Frontmatter; end-of-doc; index file; DB-only | Clean docs + git-visible + locality |
| Left-pane filtering | Faceted groups with counts | Flat facet chips | Scales to ~1.2k-scenario, many-tag corpus |
| Edit governance | Direct commit for authorized roles | PR per change | Bulk planning impractical via PR-per-toggle |
| bdd coverage (D10) | Future per-type surface over a ref field |
Build now; drop | Valuable but not v1; needs Q4 |
6.8 Testing strategy
Unit: schema parsing (all types, missing block); sidecar round-trip incl. unknown-key preservation (INV-7); dual-read equivalence (INV-6); validation; malformed-flag; facet aggregation; bulk op (single commit, partial-rejection). Two-tier local-Docker→PPE for API + git write-through. "Tested" = PUC acceptance scenarios pass + migration proven idempotent and reversible-on-read.
6.9 Failure modes, rollback & flags
- Invalid value committed out-of-band → ingest warns + loads with the value flagged malformed (INV-3).
- Re-ingest fails after commit → git authoritative; full rebuild recovers.
- Migration rollback: dual-read keeps an un-/partly-migrated corpus working; the migration commit is revertible.
- Feature flag: inherently opt-in per collection (INV-5) — no global flag.
7. Delivery Plan
7.1 Approach / strategy
Amend the binding contract first, then build storage/compat, then schema, then read, then write. Each build slice is shippable and non-breaking.
Execution convention. Each slice is taken as its own coding session — writing-plans → executing-plans → verify → ship/deploy → merge + version bump — in dependency order, with the slice's implementation plan written just-in-time at the start of that session, not up front (later slices' plans depend on the code earlier slices land). brainstorming ran once to produce this spec and recurs only if a slice proves the spec wrong. A slice's Definition of Done (§7.2) is the signal to advance the Next /goal: cursor to the next slice. SLICE-0 is doc-only (no implementation plan).
7.2 Slicing plan
SLICE-0 — Amend SPEC.md §22.4a (contract) → unblocks the rest
- Depends on: —
- Definition of done: §22.4a reframed — item 1 (entry schema) is collection-configured sidecar fields, not type-driven frontmatter; item 3 (type surfaces) deferred to a future design (bdd coverage recorded); per-type-surfaces draft marked superseded; §20 changelog. Doc-only; no code.
SLICE-1 — Sidecar storage + dual-read + migration → completes PUC-5, PUC-6
- Depends on: SLICE-0
- DoD: parser reads sidecar-else-legacy (INV-6), preserves unknown keys (INV-7); migration tool idempotent; existing collections load byte-identically; malformed flag derived; tests green.
SLICE-2 — Collection field schema + central validation → completes PUC-4
- Depends on: SLICE-1
- DoD:
.collection.yaml fields:parsed;metadata_schema.validateadvisory at read / enforced at write; schema served via the collection API; no-fields:collections unchanged (INV-5).
SLICE-3 — Faceted left-pane filtering (read) → completes PUC-3
- Depends on: SLICE-2
- DoD: list endpoint returns facet counts + honors filter params (incl.
malformed); left pane renders faceted groups with counts + tag-value search; filters compose.
SLICE-4 — Single-entry metadata edit → completes PUC-1
- Depends on: SLICE-2
- DoD: detail panel renders schema controls;
POST …/metavalidates, direct-commits, re-ingests; scope-role gated (INV-4); lazy-migrates a legacy entry on first edit. - Carried from SLICE-1 (deferred there): make the write paths
sidecar-aware — every site that today does
entry.parse(<slug>.md)and serializes back into the.mdmust read/write metadata via the sidecar so a migrated (body-only) entry doesn't crash or re-grow frontmatter. The known sites: graduation + claim +_read_meta_entry(api_graduation.py),mark_entry_reviewed(bot.py), body-edit / accept-change wrappers (api_branches.py_wrap_body/_extract_body), and the PR-replay wrappers (api_prs.py). Only once these are sidecar-aware should the operator trigger formetadata.migrate_collection(the Owner-gated migrate endpoint) ship.
SLICE-5 — Bulk tag/untag → completes PUC-2
- Depends on: SLICE-3, SLICE-4
- DoD: multi-select + bulk bar;
POST …/meta/bulkapplies set/add/remove as one commit; partial-rejection reported.
7.3 Rollout / launch plan
Pre-v1, single production: ship slices in order; each minor bump carries §20 changelog + upgrade steps. Opt-in per collection (INV-5): a deployment adopts it only by declaring a fields: block and (optionally) running the migration.
7.4 Risks & mitigations
| Risk | L/I | Mitigation |
|---|---|---|
| Amending binding §22.4a destabilises a shipped contract | M/M | SLICE-0 doc-only, reviewed; dual-read keeps runtime non-breaking; supersede note preserves rationale |
| Frontmatter→sidecar migration corrupts content | L/H | Dual-read; idempotent, revertible migration; body-byte-identity + unknown-key tests |
| Doubling file count (sidecars) clutters corpus | M/L | Docs stay clean; sidecars small/co-located |
| Direct-commit metadata edits bypass review | M/M | Scope-role gate (INV-4); content-body edits still PR'd; git audit trail |
| Facet aggregation slow at scale | L/M | Compute from indexed derived DB; measure at ~1.2k entries |
8. Traceability matrix
| Pain | Business UC | Product UC | Slice | Tests |
|---|---|---|---|---|
| — (contract) | — | — | SLICE-0 | (doc review) |
| PP-5 | BUC-3 | PUC-5, PUC-6 | SLICE-1 | test_dual_read_equiv, test_migration_idempotent, test_unknown_keys_preserved |
| PP-7 | BUC-4 | PUC-4 | SLICE-2 | test_schema_parse, test_validate |
| PP-2 | BUC-5, BUC-1 | PUC-3 | SLICE-3 | test_facet_counts, test_filter_compose |
| PP-1, PP-3 | BUC-1, BUC-4 | PUC-1 | SLICE-4 | test_single_meta_commit, test_authz |
| PP-4 | BUC-2 | PUC-2 | SLICE-5 | test_bulk_one_commit, test_partial_reject |
| PP-6 | BUC-3 | (consumer reads git) | — | test_sidecar_schema_stable |
9. Open Questions & Decisions log
Open
| # | Question | Owner | Blocks |
|---|---|---|---|
| Q1 | Do downstream consumers read sidecars from git, via API, or both? (leaning git) | Ben | nothing v1 |
| Q2 | Ship multi-enum (multi-select controlled) in v1 or later? |
Ben | SLICE-2 scope |
| Q3 | Exact §22.4a amendment wording + the future-surfaces home | Ben | SLICE-0 |
| Q4 | bdd coverage: ref field grammar + a coverage view honoring §22's no-cross-collection-join rule as hyperlinks |
Ben | future surface |
Resolved
| # | Decision | Resolution | Date |
|---|---|---|---|
| D1 | Release behaviors | Out of rfc-app; downstream | 2026-06-06 |
| D2 | Tag system shape | Approach A — one generic typed-field system | 2026-06-06 |
| D3 | Metadata grain | Per entry (corpus already one file per scenario) | 2026-06-06 |
| D4 | Schema location | .collection.yaml fields: block |
2026-06-06 |
| D5 | Value storage | Sidecar <slug>.meta.yaml; doc body pure prose |
2026-06-06 |
| D6 | Left-pane filtering | Faceted groups with counts | 2026-06-06 |
| D7 | Edit governance | Direct commit for authorized roles; bulk = 1 commit | 2026-06-06 |
| D8 | Management scope | Deferred; edit .collection.yaml in git for v1 |
2026-06-06 |
| D9 | Schema model | Pure collection-config; not type-driven | 2026-06-06 |
| D10 | bdd coverage | Future per-type surface over a ref field; not v1 |
2026-06-06 |
| D11 | per-type-surfaces draft | Superseded; §22.4a to be amended (SLICE-0) | 2026-06-06 |
10. Glossary & References
- Sidecar —
<slug>.meta.yaml, the per-entry metadata file that is the source of truth; keeps the.mdbody pure prose. - Field schema — the
fields:block in.collection.yamldeclaring a collection's typed metadata fields. - Facet — a schema field surfaced as a left-pane filter group with per-value counts.
- Malformed metadata — stored values that fail their collection's schema; flagged in the catalog, never a hard read failure (INV-3).
- Downstream consumer — an external tool that reads corpus metadata from git; rfc-app does not model releases.
- References: retired BDD Release Planner; superseded per-type-surfaces draft (
2026-06-06-per-type-surfaces.md); §22 three-tier design;SPEC.md§7.1 (left-pane filter), §9.5 (edit-meta), §20 (versioning), §22.4a (per-type contract — to be amended), §22 Part B / S3 (scope-role).