hub-core/docs/repository-navigation-contract.md
tegwick 9e4d5ba23c HUB-WP-0006-T06: seed legacy topic aliases and add RM_SLICE_TOPICSPINE
- hub_core/runtime/data/legacy_topic_aliases.json: checked-in, versioned
  (2026-10-13.1) seed of the 16 State Hub topics from the STATE-WP-0098-T01
  capture -> primary_domain facet, residual owner STATE-WP-0079, expiry
  2027-03-31T23:59:59Z. Never fetched live; topic UUIDs kept verbatim.
- legacy_topic_aliases.py: loader validated against the 1.0.0
  legacyTopicAlias schema; per-response active/expired status; underscore
  slugs emitted hyphenated (1.0.0 pattern forbids '_').
- RM_SLICE_TOPICSPINE setting (off|shadow|on, default off). off keeps the
  navigation output unchanged (aliases []); shadow/on serve the aliases and
  mark responses with X-RM-Slice-Topicspine. Documented as the A5 rollback
  switch in docs/runtime.md.
- tests/test_legacy_topic_aliases.py: seed, flag and local A5 comparison
  cases (C01, C02/C08, C03, C10) against fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 356991@bnt-lap001
Assistant-Session: 5d99ea69-ed55-4a1e-8d97-f4f312db247f
2026-10-13 23:33:42 +02:00

108 lines
5.4 KiB
Markdown

# Repository navigation contract 1.0.0
Status: frozen for HUB-WP-0006 T02 on 2026-08-22.
The packaged contract is `helixforge.repository-navigation` version `1.0.0`.
It consumes only `helixforge.repository-classification-projection` version
`1.0.0`, representing `RMGR-CONTRACT-CLASSIFICATION-0001` version `1.0`.
Unsupported versions are rejected; hub-core does not coerce them.
## Authority and identity
Repository `.repo-classification.yaml` files remain authoritative. Repo Manager
validates and publishes them. Hub-core owns only rebuildable navigation rows,
facets, query cursors, diagnostics, and temporary compatibility aliases.
Every change carries the registrar's stable `repository_id` UUID. Slugs may
change and are display/routing labels, not identity. The Repo Manager adapter
adds that UUID when converting its local observation into this envelope.
`locations`, `repo_root`, work-record bodies, credentials, and other host-local
or authority-bearing data are forbidden by the closed schemas.
## Pages, snapshots, and changes
All pages in a transfer have the same `snapshot_id`, `mode`, `generated_at`,
`source_revision`, and `total_repository_count`. Entries are ordered by
`repository_id`; duplicate IDs in one transfer are invalid.
- The first page has a null `page_cursor`.
- `next_cursor` is null exactly when `final_page` is true.
- Cursors are opaque, signed or equivalently integrity-protected, bound to the
snapshot and normalized filter set, and never parsed by clients.
- A full snapshot consists of upserts. Absence means deletion only after every
page validates and the final page is accepted.
- An incremental snapshot contains explicit idempotent upsert/delete changes.
A change is newer only by its source observation/revision semantics; duplicate
delivery has no effect.
- `total_repository_count` is required for a full snapshot and may be null for
incremental delivery.
Activation is atomic. Hub-core builds into a new generation, derives all six
facet kinds, computes SHA-256 over canonical JSON repository rows ordered by
UUID, and swaps the active generation only after the complete transfer passes
schema and semantic validation. A failed full rebuild never deletes or partly
replaces the last accepted generation.
## Failure and diagnostics
An error diagnostic, malformed page, inconsistent snapshot metadata,
unsupported version, duplicate identity, invalid controlled value, or upstream
failure rejects that transfer. The last accepted projection remains readable
as `stale`, includes a diagnostic and `source_checked_at`, and readiness reports
the Repo Manager dependency as degraded. If no accepted generation exists, the
query port returns `503`. Successful acceptance restores `current`.
Provenance on every repository includes the Repo Manager URI, classification
contract version, source fingerprint, observed time, and repository revision.
The response also carries the source snapshot/revision and rebuild content
hash. Diagnostics must not include secrets or unbounded source content.
## Query semantics
Repositories are returned in ascending UUID order. Repeated values within one
filter family are ORed; different filter families are ANDed. The six families
are primary domain, secondary domain, category, capability tag, business stake,
and business mechanic. An unknown value produces `400`; an empty match is a
successful empty page.
Response cursors are bound to both the active content hash and normalized
filters. Reusing a cursor after a rebuild or with different filters returns
`409 cursor_snapshot_mismatch`, so a client restarts from the first page.
## Legacy topic aliases
`legacy_topic_aliases` is temporary, read-only compatibility metadata. Each
entry maps one State Hub topic UUID/slug to one derived facet and must name an
`expires_at` time and `residual_owner`. It cannot create, update, or delete
classification. Expired aliases return `410` until removed; there are no write
operations in the navigation OpenAPI surface. STATE-WP-0079 owns alias expiry
and the consumer route switch.
### Runtime alias source (HUB-WP-0006-T06)
The runtime serves aliases from one checked-in, versioned seed:
`hub_core/runtime/data/legacy_topic_aliases.json` (`alias_set_version`
`2026-10-13.1`). It holds the 16 active State Hub topics captured on
2026-10-13 by `GET /topics/?status=active` (State Hub packet
STATE-WP-0098-T01). Each alias maps `legacy_topic_id` to
`facet_kind = primary_domain` and `facet_value = topic.domain_slug`, with
`residual_owner = STATE-WP-0079` and `expires_at = 2027-03-31T23:59:59Z`.
hub-core never fetches topics from State Hub at runtime. Topic UUIDs are
identity and must not change while repositories carry `topic_id`. To renew
or retire aliases, edit the seed and bump `alias_set_version`.
The 1.0.0 `legacy_topic_slug` pattern forbids underscores. Three legacy
slugs (`inter_hub`, `vergabe_teilnahme`, `citation_evidence_mvp`) are
emitted with hyphens; the seed keeps the State Hub spelling. Comparisons
must normalise `_` to `-`. Emitting the exact spelling needs a contract
patch that widens the pattern.
`status` is computed per response: `expired` from `expires_at` onward.
Aliases are emitted unfiltered on every navigation response, so a facet
query still carries all of them. There is no per-alias route yet, so the
`410` behaviour above applies only once such a route exists.
Aliases are served only when `RM_SLICE_TOPICSPINE` is `shadow` or `on`
(see `docs/runtime.md`). With the default `off`, `legacy_topic_aliases`
stays `[]`.