feat: freeze repository navigation contract
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a0230c-b06c-7641-808a-e191b6d1da49
This commit is contained in:
parent
b27de6e5bf
commit
a701dc89cf
15 changed files with 1053 additions and 4 deletions
|
|
@ -42,6 +42,12 @@ from hub_core.contracts import CONTRACT_VERSION, extension_contract_root
|
|||
contract_root = extension_contract_root()
|
||||
```
|
||||
|
||||
The wheel also includes the frozen `helixforge.repository-navigation` 1.0.0
|
||||
receiving and query contract. Its packaged schemas, fixtures, compatibility
|
||||
matrix, and read-only OpenAPI surface are located with
|
||||
`repository_navigation_contract_root()`; normative rebuild and cursor rules
|
||||
are in `docs/repository-navigation-contract.md`.
|
||||
|
||||
## Runtime scaffold
|
||||
|
||||
Install the runtime extra and start the API, MCP, or migration process through
|
||||
|
|
@ -105,6 +111,8 @@ are tracked in `HUB-WP-0006`.
|
|||
repo, DoI, TPSC/GDPR, risk/alert, and progress tools.
|
||||
- Packaged `helixforge.hub-extension` 0.1.0 Tier 1 schemas, OpenAPI port
|
||||
fragments, event catalog, compatibility matrix, and ops-hub fixture.
|
||||
- Packaged `helixforge.repository-navigation` 1.0.0 schemas, fixtures,
|
||||
compatibility policy, and read-only projection query contract.
|
||||
- Injectable primary runtime scaffold with five named ports, health/readiness,
|
||||
API/MCP/migration commands, and a locked non-root OCI image.
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
| task | HUB-WP-0005-T05 | done | — | workplans/HUB-WP-0005-core-hub-absorption-execution.md |
|
||||
| task | HUB-WP-0005-T06 | done | — | workplans/HUB-WP-0005-core-hub-absorption-execution.md |
|
||||
| task | HUB-WP-0006-T01 | done | — | workplans/HUB-WP-0006-repository-classification-navigation.md |
|
||||
| task | HUB-WP-0006-T02 | todo | — | workplans/HUB-WP-0006-repository-classification-navigation.md |
|
||||
| task | HUB-WP-0006-T02 | done | — | workplans/HUB-WP-0006-repository-classification-navigation.md |
|
||||
| task | HUB-WP-0006-T03 | todo | — | workplans/HUB-WP-0006-repository-classification-navigation.md |
|
||||
| task | HUB-WP-0006-T04 | todo | — | workplans/HUB-WP-0006-repository-classification-navigation.md |
|
||||
| task | HUB-WP-0006-T05 | todo | — | workplans/HUB-WP-0006-repository-classification-navigation.md |
|
||||
|
|
|
|||
79
docs/repository-navigation-contract.md
Normal file
79
docs/repository-navigation-contract.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# 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.
|
||||
|
|
@ -5,6 +5,8 @@ from importlib.resources.abc import Traversable
|
|||
|
||||
CONTRACT_ID = "helixforge.hub-extension"
|
||||
CONTRACT_VERSION = "0.1.0"
|
||||
REPOSITORY_NAVIGATION_CONTRACT_ID = "helixforge.repository-navigation"
|
||||
REPOSITORY_NAVIGATION_CONTRACT_VERSION = "1.0.0"
|
||||
|
||||
|
||||
def extension_contract_root() -> Traversable:
|
||||
|
|
@ -13,4 +15,17 @@ def extension_contract_root() -> Traversable:
|
|||
return files("hub_core.contracts.helixforge_hub_extension.v0_1_0")
|
||||
|
||||
|
||||
__all__ = ["CONTRACT_ID", "CONTRACT_VERSION", "extension_contract_root"]
|
||||
def repository_navigation_contract_root() -> Traversable:
|
||||
"""Return the packaged root for the repository-navigation contract."""
|
||||
|
||||
return files("hub_core.contracts.repository_navigation.v1_0_0")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"CONTRACT_ID",
|
||||
"CONTRACT_VERSION",
|
||||
"REPOSITORY_NAVIGATION_CONTRACT_ID",
|
||||
"REPOSITORY_NAVIGATION_CONTRACT_VERSION",
|
||||
"extension_contract_root",
|
||||
"repository_navigation_contract_root",
|
||||
]
|
||||
|
|
|
|||
1
hub_core/contracts/repository_navigation/__init__.py
Normal file
1
hub_core/contracts/repository_navigation/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
"""Versioned repository-navigation contracts."""
|
||||
24
hub_core/contracts/repository_navigation/v1_0_0/README.md
Normal file
24
hub_core/contracts/repository_navigation/v1_0_0/README.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# helixforge.repository-navigation 1.0.0
|
||||
|
||||
This package freezes hub-core's receiving and read contracts for Repo Manager
|
||||
classification projections:
|
||||
|
||||
- `schemas/repository-classification-page.schema.json` validates full and
|
||||
incremental input pages;
|
||||
- `schemas/repository-navigation-projection.schema.json` defines the derived
|
||||
repository, facet, provenance, diagnostic, cursor, and legacy-alias output;
|
||||
- `openapi/repository-navigation.openapi.json` defines the read-only
|
||||
`port.projection.query` routes;
|
||||
- `fixtures/` supplies a valid full input page and its representative output;
|
||||
- `compatibility-matrix.json` records the only accepted input version and the
|
||||
temporary State Hub topic adapter.
|
||||
|
||||
Repository files are classification authority and Repo Manager is the
|
||||
validator/publisher. Hub-core requires the registrar's stable repository UUID,
|
||||
derives indexes, and never accepts classification writes. Host-local paths and
|
||||
work-record contents do not cross this contract.
|
||||
|
||||
Normative cursor, rebuild, failure, filtering, provenance, and alias rules are
|
||||
documented in `docs/repository-navigation-contract.md`. Breaking authority,
|
||||
identity, deletion, rebuild, cursor, or facet changes require a new major
|
||||
contract and a dual-run window.
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""Repository-navigation contract version 1.0.0."""
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"contract_id": "helixforge.repository-navigation",
|
||||
"current_version": "1.0.0",
|
||||
"accepted_inputs": [
|
||||
{
|
||||
"contract_id": "helixforge.repository-classification-projection",
|
||||
"version": "1.0.0",
|
||||
"classification_contract_id": "RMGR-CONTRACT-CLASSIFICATION-0001",
|
||||
"classification_contract_version": "1.0",
|
||||
"status": "current",
|
||||
"coercion": false
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"status": "current",
|
||||
"compatible_min": "1.0.0",
|
||||
"compatible_max": "1.0.0",
|
||||
"breaking": false
|
||||
}
|
||||
],
|
||||
"legacy_adapters": [
|
||||
{
|
||||
"source": "state-hub.topic",
|
||||
"target": "legacy_topic_aliases",
|
||||
"status": "temporary-read-only",
|
||||
"authority": false,
|
||||
"required_metadata": ["expires_at", "residual_owner"],
|
||||
"notes": "An alias points to one derived facet. It cannot create or mutate repository classification and must be removed after its residual owner completes the cutover."
|
||||
}
|
||||
],
|
||||
"change_policy": {
|
||||
"patch": "Clarifications and compatible constraint corrections only.",
|
||||
"minor": "Additive optional fields, query filters, and diagnostics.",
|
||||
"major": "Changed authority, identity, cursor, rebuild, deletion, facet, or compatibility semantics; removed or renamed fields; or narrowed enums. Requires a dual-run window."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"contract_id": "helixforge.repository-classification-projection",
|
||||
"contract_version": "1.0.0",
|
||||
"source": {
|
||||
"system": "repo-manager",
|
||||
"classification_contract_id": "RMGR-CONTRACT-CLASSIFICATION-0001",
|
||||
"classification_contract_version": "1.0",
|
||||
"producer_version": "0.4.0"
|
||||
},
|
||||
"snapshot": {
|
||||
"snapshot_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"mode": "full",
|
||||
"generated_at": "2026-08-22T12:00:00Z",
|
||||
"source_revision": "0123456789abcdef0123456789abcdef01234567",
|
||||
"page_cursor": null,
|
||||
"next_cursor": null,
|
||||
"final_page": true,
|
||||
"total_repository_count": 2
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"operation": "upsert",
|
||||
"repository_id": "11111111-1111-4111-8111-111111111111",
|
||||
"slug": "hub-core",
|
||||
"lifecycle": "active",
|
||||
"classification": {
|
||||
"category": "tooling",
|
||||
"domain": "infotech",
|
||||
"secondary_domains": ["agents"],
|
||||
"capability_tags": ["hub-runtime", "repository-navigation"],
|
||||
"business_stake": ["technology"],
|
||||
"business_mechanics": ["coordination"]
|
||||
},
|
||||
"revision": {
|
||||
"head_sha": "1111111111111111111111111111111111111111",
|
||||
"source_fingerprint": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"observed_at": "2026-08-22T11:59:00Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"operation": "upsert",
|
||||
"repository_id": "22222222-2222-4222-8222-222222222222",
|
||||
"slug": "repo-manager",
|
||||
"lifecycle": "active",
|
||||
"classification": {
|
||||
"category": "tooling",
|
||||
"domain": "infotech",
|
||||
"secondary_domains": [],
|
||||
"capability_tags": ["repository-governance"],
|
||||
"business_stake": ["operations", "technology"],
|
||||
"business_mechanics": ["control", "coordination"]
|
||||
},
|
||||
"revision": {
|
||||
"head_sha": "2222222222222222222222222222222222222222",
|
||||
"source_fingerprint": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
"observed_at": "2026-08-22T11:59:30Z"
|
||||
}
|
||||
}
|
||||
],
|
||||
"diagnostics": []
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
{
|
||||
"contract_id": "helixforge.repository-navigation",
|
||||
"contract_version": "1.0.0",
|
||||
"projection_id": "repository_navigation",
|
||||
"projection_status": "current",
|
||||
"source_snapshot": {
|
||||
"snapshot_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"source_system": "repo-manager",
|
||||
"source_contract_id": "RMGR-CONTRACT-CLASSIFICATION-0001",
|
||||
"source_contract_version": "1.0",
|
||||
"source_revision": "0123456789abcdef0123456789abcdef01234567",
|
||||
"generated_at": "2026-08-22T12:00:00Z"
|
||||
},
|
||||
"source_checked_at": "2026-08-22T12:00:03Z",
|
||||
"rebuilt_at": "2026-08-22T12:00:02Z",
|
||||
"content_hash": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
"repositories": [
|
||||
{
|
||||
"repository_id": "11111111-1111-4111-8111-111111111111",
|
||||
"slug": "hub-core",
|
||||
"lifecycle": "active",
|
||||
"primary_domain": "infotech",
|
||||
"secondary_domains": ["agents"],
|
||||
"category": "tooling",
|
||||
"capability_tags": ["hub-runtime", "repository-navigation"],
|
||||
"business_stake": ["technology"],
|
||||
"business_mechanics": ["coordination"],
|
||||
"provenance": {
|
||||
"source_system": "repo-manager",
|
||||
"source_ref": "repo-manager://repositories/11111111-1111-4111-8111-111111111111",
|
||||
"classification_contract_version": "1.0",
|
||||
"repository_revision": "1111111111111111111111111111111111111111",
|
||||
"source_fingerprint": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"observed_at": "2026-08-22T11:59:00Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"repository_id": "22222222-2222-4222-8222-222222222222",
|
||||
"slug": "repo-manager",
|
||||
"lifecycle": "active",
|
||||
"primary_domain": "infotech",
|
||||
"secondary_domains": [],
|
||||
"category": "tooling",
|
||||
"capability_tags": ["repository-governance"],
|
||||
"business_stake": ["operations", "technology"],
|
||||
"business_mechanics": ["control", "coordination"],
|
||||
"provenance": {
|
||||
"source_system": "repo-manager",
|
||||
"source_ref": "repo-manager://repositories/22222222-2222-4222-8222-222222222222",
|
||||
"classification_contract_version": "1.0",
|
||||
"repository_revision": "2222222222222222222222222222222222222222",
|
||||
"source_fingerprint": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
"observed_at": "2026-08-22T11:59:30Z"
|
||||
}
|
||||
}
|
||||
],
|
||||
"facets": [
|
||||
{
|
||||
"kind": "primary_domain",
|
||||
"value": "infotech",
|
||||
"repository_count": 2,
|
||||
"repository_ids": [
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
"22222222-2222-4222-8222-222222222222"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "secondary_domain",
|
||||
"value": "agents",
|
||||
"repository_count": 1,
|
||||
"repository_ids": ["11111111-1111-4111-8111-111111111111"]
|
||||
},
|
||||
{
|
||||
"kind": "category",
|
||||
"value": "tooling",
|
||||
"repository_count": 2,
|
||||
"repository_ids": [
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
"22222222-2222-4222-8222-222222222222"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "capability_tag",
|
||||
"value": "repository-navigation",
|
||||
"repository_count": 1,
|
||||
"repository_ids": ["11111111-1111-4111-8111-111111111111"]
|
||||
},
|
||||
{
|
||||
"kind": "business_stake",
|
||||
"value": "technology",
|
||||
"repository_count": 2,
|
||||
"repository_ids": [
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
"22222222-2222-4222-8222-222222222222"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "business_mechanic",
|
||||
"value": "coordination",
|
||||
"repository_count": 2,
|
||||
"repository_ids": [
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
"22222222-2222-4222-8222-222222222222"
|
||||
]
|
||||
}
|
||||
],
|
||||
"legacy_topic_aliases": [
|
||||
{
|
||||
"legacy_topic_id": "33333333-3333-4333-8333-333333333333",
|
||||
"legacy_topic_slug": "infotech",
|
||||
"facet_kind": "primary_domain",
|
||||
"facet_value": "infotech",
|
||||
"status": "active",
|
||||
"expires_at": "2026-09-30T23:59:59Z",
|
||||
"residual_owner": "STATE-WP-0079"
|
||||
}
|
||||
],
|
||||
"diagnostics": [],
|
||||
"next_cursor": null,
|
||||
"total_repository_count": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "HelixForge repository navigation projection",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"paths": {
|
||||
"/ports/projections/repository-navigation/repositories": {
|
||||
"get": {
|
||||
"operationId": "queryRepositoryNavigation",
|
||||
"x-port-id": "port.projection.query",
|
||||
"x-direction": "out",
|
||||
"summary": "Query the derived repository navigation projection",
|
||||
"parameters": [
|
||||
{"$ref": "#/components/parameters/PrimaryDomain"},
|
||||
{"$ref": "#/components/parameters/SecondaryDomain"},
|
||||
{"$ref": "#/components/parameters/Category"},
|
||||
{"$ref": "#/components/parameters/CapabilityTag"},
|
||||
{"$ref": "#/components/parameters/BusinessStake"},
|
||||
{"$ref": "#/components/parameters/BusinessMechanic"},
|
||||
{"$ref": "#/components/parameters/Cursor"},
|
||||
{"$ref": "#/components/parameters/Limit"}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Snapshot-bound page in stable repository UUID order",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "../schemas/repository-navigation-projection.schema.json"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {"$ref": "#/components/responses/BadRequest"},
|
||||
"409": {"$ref": "#/components/responses/CursorMismatch"},
|
||||
"503": {"$ref": "#/components/responses/DependencyUnavailable"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ports/projections/repository-navigation/facets/{facet_kind}/{facet_value}": {
|
||||
"get": {
|
||||
"operationId": "getRepositoryNavigationFacet",
|
||||
"x-port-id": "port.projection.query",
|
||||
"x-direction": "out",
|
||||
"summary": "Resolve one derived navigation facet",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "facet_kind",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {"$ref": "#/components/schemas/FacetKind"}
|
||||
},
|
||||
{
|
||||
"name": "facet_value",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {"type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"}
|
||||
},
|
||||
{"$ref": "#/components/parameters/Cursor"},
|
||||
{"$ref": "#/components/parameters/Limit"}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Repositories in the selected derived facet",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "../schemas/repository-navigation-projection.schema.json"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {"$ref": "#/components/responses/BadRequest"},
|
||||
"404": {"description": "Facet not found"},
|
||||
"409": {"$ref": "#/components/responses/CursorMismatch"},
|
||||
"503": {"$ref": "#/components/responses/DependencyUnavailable"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ports/projections/repository-navigation/legacy-topic-aliases/{legacy_topic_id}": {
|
||||
"get": {
|
||||
"operationId": "resolveLegacyRepositoryTopicAlias",
|
||||
"x-port-id": "port.projection.query",
|
||||
"x-direction": "out",
|
||||
"summary": "Resolve temporary read-only State Hub topic compatibility metadata",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "legacy_topic_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {"type": "string", "format": "uuid"}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Active alias and its derived facet target",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "../schemas/repository-navigation-projection.schema.json#/$defs/legacyTopicAlias"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {"description": "Alias not found"},
|
||||
"410": {"description": "Alias expired; response identifies the residual owner"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"FacetKind": {
|
||||
"enum": [
|
||||
"primary_domain",
|
||||
"secondary_domain",
|
||||
"category",
|
||||
"capability_tag",
|
||||
"business_stake",
|
||||
"business_mechanic"
|
||||
]
|
||||
},
|
||||
"Error": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["code", "message"],
|
||||
"properties": {
|
||||
"code": {"type": "string"},
|
||||
"message": {"type": "string"},
|
||||
"retryable": {"type": "boolean"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"PrimaryDomain": {"name": "primary_domain", "in": "query", "schema": {"type": "array", "items": {"type": "string"}}, "explode": true},
|
||||
"SecondaryDomain": {"name": "secondary_domain", "in": "query", "schema": {"type": "array", "items": {"type": "string"}}, "explode": true},
|
||||
"Category": {"name": "category", "in": "query", "schema": {"type": "array", "items": {"type": "string"}}, "explode": true},
|
||||
"CapabilityTag": {"name": "capability_tag", "in": "query", "schema": {"type": "array", "items": {"type": "string"}}, "explode": true},
|
||||
"BusinessStake": {"name": "business_stake", "in": "query", "schema": {"type": "array", "items": {"type": "string"}}, "explode": true},
|
||||
"BusinessMechanic": {"name": "business_mechanic", "in": "query", "schema": {"type": "array", "items": {"type": "string"}}, "explode": true},
|
||||
"Cursor": {"name": "cursor", "in": "query", "schema": {"type": "string", "maxLength": 1000}},
|
||||
"Limit": {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 500, "default": 100}}
|
||||
},
|
||||
"responses": {
|
||||
"BadRequest": {
|
||||
"description": "Invalid filter or cursor",
|
||||
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
|
||||
},
|
||||
"CursorMismatch": {
|
||||
"description": "Cursor belongs to a superseded projection snapshot",
|
||||
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
|
||||
},
|
||||
"DependencyUnavailable": {
|
||||
"description": "No accepted projection is available",
|
||||
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://schemas.helixforge.local/repository-navigation/1.0.0/repository-classification-page.schema.json",
|
||||
"title": "Repo Manager classification projection page",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"contract_id",
|
||||
"contract_version",
|
||||
"source",
|
||||
"snapshot",
|
||||
"repositories",
|
||||
"diagnostics"
|
||||
],
|
||||
"properties": {
|
||||
"contract_id": {"const": "helixforge.repository-classification-projection"},
|
||||
"contract_version": {"const": "1.0.0"},
|
||||
"source": {"$ref": "#/$defs/source"},
|
||||
"snapshot": {"$ref": "#/$defs/snapshot"},
|
||||
"repositories": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/change"}
|
||||
},
|
||||
"diagnostics": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/diagnostic"}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"source": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"system",
|
||||
"classification_contract_id",
|
||||
"classification_contract_version",
|
||||
"producer_version"
|
||||
],
|
||||
"properties": {
|
||||
"system": {"const": "repo-manager"},
|
||||
"classification_contract_id": {"const": "RMGR-CONTRACT-CLASSIFICATION-0001"},
|
||||
"classification_contract_version": {"const": "1.0"},
|
||||
"producer_version": {"type": "string", "minLength": 1, "maxLength": 80}
|
||||
}
|
||||
},
|
||||
"snapshot": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"snapshot_id",
|
||||
"mode",
|
||||
"generated_at",
|
||||
"source_revision",
|
||||
"page_cursor",
|
||||
"next_cursor",
|
||||
"final_page",
|
||||
"total_repository_count"
|
||||
],
|
||||
"properties": {
|
||||
"snapshot_id": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
|
||||
"mode": {"enum": ["full", "incremental"]},
|
||||
"generated_at": {"type": "string", "format": "date-time"},
|
||||
"source_revision": {"type": "string", "pattern": "^[a-f0-9]{7,64}$"},
|
||||
"page_cursor": {"type": ["string", "null"], "maxLength": 1000},
|
||||
"next_cursor": {"type": ["string", "null"], "maxLength": 1000},
|
||||
"final_page": {"type": "boolean"},
|
||||
"total_repository_count": {"type": ["integer", "null"], "minimum": 0}
|
||||
}
|
||||
},
|
||||
"revision": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["head_sha", "source_fingerprint", "observed_at"],
|
||||
"properties": {
|
||||
"head_sha": {"type": ["string", "null"], "pattern": "^[a-f0-9]{40,64}$"},
|
||||
"source_fingerprint": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
|
||||
"observed_at": {"type": "string", "format": "date-time"}
|
||||
}
|
||||
},
|
||||
"classification": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["category", "domain"],
|
||||
"properties": {
|
||||
"category": {"enum": ["experimental", "research", "project", "tooling", "product", "business"]},
|
||||
"domain": {"$ref": "#/$defs/domain"},
|
||||
"secondary_domains": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/domain"},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"capability_tags": {
|
||||
"type": "array",
|
||||
"items": {"type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$", "maxLength": 120},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"business_stake": {
|
||||
"type": "array",
|
||||
"items": {"enum": ["execution", "intelligence", "finance", "legal", "sales", "experience", "technology", "operations", "product", "people", "procurement", "sustainability", "automation"]},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"business_mechanics": {
|
||||
"type": "array",
|
||||
"items": {"enum": ["intention", "control", "coordination", "operation", "adaptation"]},
|
||||
"uniqueItems": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"upsert": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["operation", "repository_id", "slug", "lifecycle", "classification", "revision"],
|
||||
"properties": {
|
||||
"operation": {"const": "upsert"},
|
||||
"repository_id": {"type": "string", "format": "uuid"},
|
||||
"slug": {"type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$", "maxLength": 120},
|
||||
"lifecycle": {"enum": ["active", "archived", "retired"]},
|
||||
"classification": {"$ref": "#/$defs/classification"},
|
||||
"revision": {"$ref": "#/$defs/revision"}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["operation", "repository_id", "slug", "revision"],
|
||||
"properties": {
|
||||
"operation": {"const": "delete"},
|
||||
"repository_id": {"type": "string", "format": "uuid"},
|
||||
"slug": {"type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$", "maxLength": 120},
|
||||
"revision": {"$ref": "#/$defs/revision"}
|
||||
}
|
||||
},
|
||||
"change": {"oneOf": [{"$ref": "#/$defs/upsert"}, {"$ref": "#/$defs/delete"}]},
|
||||
"diagnostic": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["severity", "code", "message"],
|
||||
"properties": {
|
||||
"severity": {"enum": ["info", "warning", "error"]},
|
||||
"code": {"type": "string", "pattern": "^[a-z][a-z0-9_.-]*$", "maxLength": 120},
|
||||
"message": {"type": "string", "minLength": 1, "maxLength": 1000},
|
||||
"repository_id": {"type": "string", "format": "uuid"},
|
||||
"field": {"type": "string", "maxLength": 160}
|
||||
}
|
||||
},
|
||||
"domain": {
|
||||
"enum": ["infotech", "financials", "communication", "consumer", "health", "industrials", "energy", "utilities", "materials", "realestate", "crypto", "agents", "space", "government"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://schemas.helixforge.local/repository-navigation/1.0.0/repository-navigation-projection.schema.json",
|
||||
"title": "Hub repository navigation projection",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"contract_id",
|
||||
"contract_version",
|
||||
"projection_id",
|
||||
"projection_status",
|
||||
"source_snapshot",
|
||||
"source_checked_at",
|
||||
"rebuilt_at",
|
||||
"content_hash",
|
||||
"repositories",
|
||||
"facets",
|
||||
"legacy_topic_aliases",
|
||||
"diagnostics",
|
||||
"next_cursor",
|
||||
"total_repository_count"
|
||||
],
|
||||
"properties": {
|
||||
"contract_id": {"const": "helixforge.repository-navigation"},
|
||||
"contract_version": {"const": "1.0.0"},
|
||||
"projection_id": {"const": "repository_navigation"},
|
||||
"projection_status": {"enum": ["current", "stale"]},
|
||||
"source_snapshot": {"$ref": "#/$defs/sourceSnapshot"},
|
||||
"source_checked_at": {"type": "string", "format": "date-time"},
|
||||
"rebuilt_at": {"type": "string", "format": "date-time"},
|
||||
"content_hash": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
|
||||
"repositories": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/repository"}
|
||||
},
|
||||
"facets": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/facet"}
|
||||
},
|
||||
"legacy_topic_aliases": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/legacyTopicAlias"}
|
||||
},
|
||||
"diagnostics": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/diagnostic"}
|
||||
},
|
||||
"next_cursor": {"type": ["string", "null"], "maxLength": 1000},
|
||||
"total_repository_count": {"type": "integer", "minimum": 0}
|
||||
},
|
||||
"$defs": {
|
||||
"sourceSnapshot": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"snapshot_id",
|
||||
"source_system",
|
||||
"source_contract_id",
|
||||
"source_contract_version",
|
||||
"source_revision",
|
||||
"generated_at"
|
||||
],
|
||||
"properties": {
|
||||
"snapshot_id": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
|
||||
"source_system": {"const": "repo-manager"},
|
||||
"source_contract_id": {"const": "RMGR-CONTRACT-CLASSIFICATION-0001"},
|
||||
"source_contract_version": {"const": "1.0"},
|
||||
"source_revision": {"type": "string", "pattern": "^[a-f0-9]{7,64}$"},
|
||||
"generated_at": {"type": "string", "format": "date-time"}
|
||||
}
|
||||
},
|
||||
"provenance": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"source_system",
|
||||
"source_ref",
|
||||
"classification_contract_version",
|
||||
"repository_revision",
|
||||
"source_fingerprint",
|
||||
"observed_at"
|
||||
],
|
||||
"properties": {
|
||||
"source_system": {"const": "repo-manager"},
|
||||
"source_ref": {"type": "string", "pattern": "^repo-manager://repositories/[0-9a-f-]{36}$"},
|
||||
"classification_contract_version": {"const": "1.0"},
|
||||
"repository_revision": {"type": ["string", "null"], "pattern": "^[a-f0-9]{40,64}$"},
|
||||
"source_fingerprint": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
|
||||
"observed_at": {"type": "string", "format": "date-time"}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"repository_id",
|
||||
"slug",
|
||||
"lifecycle",
|
||||
"primary_domain",
|
||||
"secondary_domains",
|
||||
"category",
|
||||
"capability_tags",
|
||||
"business_stake",
|
||||
"business_mechanics",
|
||||
"provenance"
|
||||
],
|
||||
"properties": {
|
||||
"repository_id": {"type": "string", "format": "uuid"},
|
||||
"slug": {"type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$", "maxLength": 120},
|
||||
"lifecycle": {"enum": ["active", "archived", "retired"]},
|
||||
"primary_domain": {"$ref": "#/$defs/domain"},
|
||||
"secondary_domains": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/domain"},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"category": {"$ref": "#/$defs/category"},
|
||||
"capability_tags": {
|
||||
"type": "array",
|
||||
"items": {"type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$", "maxLength": 120},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"business_stake": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/businessStake"},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"business_mechanics": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/$defs/businessMechanic"},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"provenance": {"$ref": "#/$defs/provenance"}
|
||||
}
|
||||
},
|
||||
"facetKind": {
|
||||
"enum": [
|
||||
"primary_domain",
|
||||
"secondary_domain",
|
||||
"category",
|
||||
"capability_tag",
|
||||
"business_stake",
|
||||
"business_mechanic"
|
||||
]
|
||||
},
|
||||
"facet": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["kind", "value", "repository_count", "repository_ids"],
|
||||
"properties": {
|
||||
"kind": {"$ref": "#/$defs/facetKind"},
|
||||
"value": {"type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$", "maxLength": 120},
|
||||
"repository_count": {"type": "integer", "minimum": 0},
|
||||
"repository_ids": {
|
||||
"type": "array",
|
||||
"items": {"type": "string", "format": "uuid"},
|
||||
"uniqueItems": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"legacyTopicAlias": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"legacy_topic_id",
|
||||
"legacy_topic_slug",
|
||||
"facet_kind",
|
||||
"facet_value",
|
||||
"status",
|
||||
"expires_at",
|
||||
"residual_owner"
|
||||
],
|
||||
"properties": {
|
||||
"legacy_topic_id": {"type": "string", "format": "uuid"},
|
||||
"legacy_topic_slug": {"type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$", "maxLength": 120},
|
||||
"facet_kind": {"$ref": "#/$defs/facetKind"},
|
||||
"facet_value": {"type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$", "maxLength": 120},
|
||||
"status": {"enum": ["active", "expired"]},
|
||||
"expires_at": {"type": "string", "format": "date-time"},
|
||||
"residual_owner": {"type": "string", "minLength": 1, "maxLength": 160}
|
||||
}
|
||||
},
|
||||
"diagnostic": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["severity", "code", "message"],
|
||||
"properties": {
|
||||
"severity": {"enum": ["info", "warning", "error"]},
|
||||
"code": {"type": "string", "pattern": "^[a-z][a-z0-9_.-]*$", "maxLength": 120},
|
||||
"message": {"type": "string", "minLength": 1, "maxLength": 1000},
|
||||
"repository_id": {"type": "string", "format": "uuid"},
|
||||
"field": {"type": "string", "maxLength": 160}
|
||||
}
|
||||
},
|
||||
"category": {"enum": ["experimental", "research", "project", "tooling", "product", "business"]},
|
||||
"domain": {
|
||||
"enum": ["infotech", "financials", "communication", "consumer", "health", "industrials", "energy", "utilities", "materials", "realestate", "crypto", "agents", "space", "government"]
|
||||
},
|
||||
"businessStake": {
|
||||
"enum": ["execution", "intelligence", "finance", "legal", "sales", "experience", "technology", "operations", "product", "people", "procurement", "sustainability", "automation"]
|
||||
},
|
||||
"businessMechanic": {"enum": ["intention", "control", "coordination", "operation", "adaptation"]}
|
||||
}
|
||||
}
|
||||
183
tests/test_repository_navigation_contract.py
Normal file
183
tests/test_repository_navigation_contract.py
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
import json
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import Any
|
||||
|
||||
from jsonschema import Draft202012Validator, FormatChecker
|
||||
|
||||
from hub_core.contracts import (
|
||||
REPOSITORY_NAVIGATION_CONTRACT_ID,
|
||||
REPOSITORY_NAVIGATION_CONTRACT_VERSION,
|
||||
repository_navigation_contract_root,
|
||||
)
|
||||
|
||||
|
||||
ROOT = repository_navigation_contract_root()
|
||||
SCHEMAS = ROOT.joinpath("schemas")
|
||||
INPUT_FIXTURE = ROOT.joinpath("fixtures", "repository-classification-page.json")
|
||||
OUTPUT_FIXTURE = ROOT.joinpath("fixtures", "repository-navigation-projection.json")
|
||||
OPENAPI = ROOT.joinpath("openapi", "repository-navigation.openapi.json")
|
||||
COMPATIBILITY = ROOT.joinpath("compatibility-matrix.json")
|
||||
|
||||
FACET_KINDS = {
|
||||
"primary_domain",
|
||||
"secondary_domain",
|
||||
"category",
|
||||
"capability_tag",
|
||||
"business_stake",
|
||||
"business_mechanic",
|
||||
}
|
||||
SECRET_OR_LOCAL_KEYS = {
|
||||
"api_key",
|
||||
"credential",
|
||||
"locations",
|
||||
"password",
|
||||
"passwd",
|
||||
"private_key",
|
||||
"repo_root",
|
||||
"secret",
|
||||
"token",
|
||||
"work_records",
|
||||
}
|
||||
|
||||
|
||||
def load_json(resource: Any) -> Any:
|
||||
return json.loads(resource.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def validate(instance: Any, schema_name: str) -> None:
|
||||
schema = load_json(SCHEMAS.joinpath(schema_name))
|
||||
Draft202012Validator.check_schema(schema)
|
||||
Draft202012Validator(schema, format_checker=FormatChecker()).validate(instance)
|
||||
|
||||
|
||||
def iter_keys(value: Any) -> set[str]:
|
||||
if isinstance(value, Mapping):
|
||||
keys = {str(key).lower() for key in value}
|
||||
for child in value.values():
|
||||
keys.update(iter_keys(child))
|
||||
return keys
|
||||
if isinstance(value, Sequence) and not isinstance(value, (str, bytes)):
|
||||
keys: set[str] = set()
|
||||
for child in value:
|
||||
keys.update(iter_keys(child))
|
||||
return keys
|
||||
return set()
|
||||
|
||||
|
||||
def test_packaged_repository_navigation_contract_identity_and_artifacts() -> None:
|
||||
assert REPOSITORY_NAVIGATION_CONTRACT_ID == "helixforge.repository-navigation"
|
||||
assert REPOSITORY_NAVIGATION_CONTRACT_VERSION == "1.0.0"
|
||||
for resource in (
|
||||
ROOT.joinpath("README.md"),
|
||||
INPUT_FIXTURE,
|
||||
OUTPUT_FIXTURE,
|
||||
OPENAPI,
|
||||
COMPATIBILITY,
|
||||
):
|
||||
assert resource.is_file()
|
||||
|
||||
|
||||
def test_input_and_output_fixtures_validate() -> None:
|
||||
validate(load_json(INPUT_FIXTURE), "repository-classification-page.schema.json")
|
||||
validate(load_json(OUTPUT_FIXTURE), "repository-navigation-projection.schema.json")
|
||||
|
||||
|
||||
def test_input_semantics_use_stable_unique_identity_and_consistent_final_page() -> None:
|
||||
page = load_json(INPUT_FIXTURE)
|
||||
repository_ids = [entry["repository_id"] for entry in page["repositories"]]
|
||||
|
||||
assert repository_ids == sorted(repository_ids)
|
||||
assert len(repository_ids) == len(set(repository_ids))
|
||||
assert page["snapshot"]["final_page"] is (page["snapshot"]["next_cursor"] is None)
|
||||
assert page["snapshot"]["total_repository_count"] == len(repository_ids)
|
||||
assert all(
|
||||
entry["classification"].get("domain")
|
||||
not in entry["classification"].get("secondary_domains", [])
|
||||
for entry in page["repositories"]
|
||||
if entry["operation"] == "upsert"
|
||||
)
|
||||
|
||||
|
||||
def test_output_has_every_derived_facet_and_per_repository_provenance() -> None:
|
||||
projection = load_json(OUTPUT_FIXTURE)
|
||||
repositories = projection["repositories"]
|
||||
repository_ids = [entry["repository_id"] for entry in repositories]
|
||||
|
||||
assert repository_ids == sorted(repository_ids)
|
||||
assert len(repository_ids) == len(set(repository_ids))
|
||||
assert {facet["kind"] for facet in projection["facets"]} == FACET_KINDS
|
||||
assert projection["total_repository_count"] == len(repositories)
|
||||
assert all(
|
||||
entry["provenance"]["source_ref"].endswith(entry["repository_id"])
|
||||
for entry in repositories
|
||||
)
|
||||
assert all(
|
||||
facet["repository_count"] == len(facet["repository_ids"])
|
||||
for facet in projection["facets"]
|
||||
)
|
||||
|
||||
|
||||
def test_contract_examples_exclude_secrets_and_host_local_authority_data() -> None:
|
||||
assert not (iter_keys(load_json(INPUT_FIXTURE)) & SECRET_OR_LOCAL_KEYS)
|
||||
assert not (iter_keys(load_json(OUTPUT_FIXTURE)) & SECRET_OR_LOCAL_KEYS)
|
||||
|
||||
|
||||
def test_compatibility_accepts_only_repo_manager_v1_without_coercion() -> None:
|
||||
matrix = load_json(COMPATIBILITY)
|
||||
accepted = matrix["accepted_inputs"]
|
||||
|
||||
assert matrix["contract_id"] == REPOSITORY_NAVIGATION_CONTRACT_ID
|
||||
assert matrix["current_version"] == REPOSITORY_NAVIGATION_CONTRACT_VERSION
|
||||
assert accepted == [
|
||||
{
|
||||
"contract_id": "helixforge.repository-classification-projection",
|
||||
"version": "1.0.0",
|
||||
"classification_contract_id": "RMGR-CONTRACT-CLASSIFICATION-0001",
|
||||
"classification_contract_version": "1.0",
|
||||
"status": "current",
|
||||
"coercion": False,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_navigation_openapi_is_read_only_and_exposes_all_filter_families() -> None:
|
||||
document = load_json(OPENAPI)
|
||||
operations = [
|
||||
(method, operation)
|
||||
for path_item in document["paths"].values()
|
||||
for method, operation in path_item.items()
|
||||
if method in {"get", "post", "put", "patch", "delete"}
|
||||
]
|
||||
query = document["paths"][
|
||||
"/ports/projections/repository-navigation/repositories"
|
||||
]["get"]
|
||||
parameter_names = {
|
||||
reference["$ref"].rsplit("/", 1)[-1]
|
||||
for reference in query["parameters"]
|
||||
if "$ref" in reference
|
||||
}
|
||||
|
||||
assert document["openapi"] == "3.1.0"
|
||||
assert all(method == "get" for method, _ in operations)
|
||||
assert all(operation["x-port-id"] == "port.projection.query" for _, operation in operations)
|
||||
assert {
|
||||
"PrimaryDomain",
|
||||
"SecondaryDomain",
|
||||
"Category",
|
||||
"CapabilityTag",
|
||||
"BusinessStake",
|
||||
"BusinessMechanic",
|
||||
} <= parameter_names
|
||||
assert set(document["components"]["schemas"]["FacetKind"]["enum"]) == FACET_KINDS
|
||||
|
||||
|
||||
def test_legacy_topic_alias_is_expiring_read_only_metadata() -> None:
|
||||
projection = load_json(OUTPUT_FIXTURE)
|
||||
aliases = projection["legacy_topic_aliases"]
|
||||
matrix = load_json(COMPATIBILITY)
|
||||
|
||||
assert aliases
|
||||
assert all(alias["expires_at"] and alias["residual_owner"] for alias in aliases)
|
||||
adapter = matrix["legacy_adapters"][0]
|
||||
assert adapter["authority"] is False
|
||||
assert adapter["status"] == "temporary-read-only"
|
||||
|
|
@ -52,7 +52,7 @@ single-authority boundary.
|
|||
|
||||
```task
|
||||
id: HUB-WP-0006-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "47d7d168-217f-45c6-b4d7-ab9cf4dd5329"
|
||||
```
|
||||
|
|
@ -62,6 +62,14 @@ identity, cursor/snapshot semantics, provenance, diagnostics, and the derived
|
|||
domain/category/tag/stake/mechanic views. Define legacy topic aliases as
|
||||
temporary compatibility metadata, not mutable topic authority.
|
||||
|
||||
Completed 2026-08-22. Packaged `helixforge.repository-navigation` 1.0.0
|
||||
defines the strict Repo Manager page envelope, stable registrar UUID identity,
|
||||
atomic snapshot/rebuild and cursor rules, per-repository provenance, failure
|
||||
diagnostics, all six derived facet kinds, read-only query routes, and expiring
|
||||
State Hub topic aliases with residual ownership. Both representative fixtures
|
||||
validate, the wheel contains all eight artifacts, and the full suite passes
|
||||
72 tests.
|
||||
|
||||
## Implement Repo Manager ingestion and rebuild
|
||||
|
||||
```task
|
||||
|
|
@ -121,7 +129,7 @@ expiry. Wait on T02-T05 and the State Hub cutover window.
|
|||
## Acceptance
|
||||
|
||||
- [x] Repo Manager v1.0 authority and projection contract accepted
|
||||
- [ ] Hub-side projection and compatibility alias contract versioned
|
||||
- [x] Hub-side projection and compatibility alias contract versioned
|
||||
- [ ] Rebuildable durable ingestion passes provenance and failure checks
|
||||
- [ ] HTTP/MCP navigation reads only the derived projection
|
||||
- [ ] A5/A4 consumer comparison and rollback evidence recorded
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue