From 75f4944144b000119a68d3ed6eb8d938b05f73a1 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 31 Aug 2026 21:31:53 +0200 Subject: [PATCH] implement ops hub extension contract Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a053ff-1d6f-7fe2-ac1c-a6eb40a42a0c --- Makefile | 18 ++- README.md | 8 +- WORK-RECORDS.md | 8 +- docs/bootstrap-runbook.md | 31 ++-- docs/hub-extension.md | 78 ++++++++++ registry/NO_CAPABILITIES.md | 15 -- .../capability.operations.ops-hub.md | 140 ++++++++++++++++++ .../v0.1.0/ops-hub.extension.json | 76 ++++++++++ registry/indexes/capabilities.yaml | 20 ++- scripts/hub-core-conformance.py | 35 +++++ scripts/hub-extension-check.py | 50 +++++++ src/ops_hub/extension_contract.py | 102 +++++++++++++ tests/test_extension_contract.py | 51 +++++++ ...PS-WP-0003-extension-contract-alignment.md | 73 ++++++++- 14 files changed, 662 insertions(+), 43 deletions(-) create mode 100644 docs/hub-extension.md delete mode 100644 registry/NO_CAPABILITIES.md create mode 100644 registry/capabilities/capability.operations.ops-hub.md create mode 100644 registry/hub-extension/v0.1.0/ops-hub.extension.json create mode 100644 scripts/hub-core-conformance.py create mode 100644 scripts/hub-extension-check.py create mode 100644 src/ops_hub/extension_contract.py create mode 100644 tests/test_extension_contract.py diff --git a/Makefile b/Makefile index 8c79b97..db467fe 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,33 @@ IHUB_BASE ?= https://hub.coulomb.social IHUB_OPERATOR_KEY_FILE ?= OPS_HUB_RUNTIME_KEY_OUTPUT ?= +HUB_CORE_REPO ?= ../hub-core +HUB_CORE_PYTHON ?= $(HUB_CORE_REPO)/.venv/bin/python -.PHONY: help interhub-gate interhub-bootstrap interhub-bootstrap-dry-run interhub-bootstrap-help test +.PHONY: help hub-extension-check hub-core-conformance interhub-gate interhub-bootstrap interhub-bootstrap-dry-run interhub-bootstrap-help test help: @echo "Targets:" + @echo " hub-extension-check Validate the owner package against hub-core 0.1.0 schemas/catalog" + @echo " hub-core-conformance Exercise the owner package in a disposable hub-core runtime" @echo " interhub-gate Probe whether production Inter-Hub exposes the ops-hub bootstrap API surface" @echo " interhub-bootstrap-help Show bootstrap helper options" @echo " interhub-bootstrap-dry-run Plan bootstrap actions with IHUB_OPERATOR_KEY_FILE (no mutations)" @echo " interhub-bootstrap Run attended ops-hub Inter-Hub bootstrap with IHUB_OPERATOR_KEY_FILE" @echo " test Run unit tests" +hub-extension-check: + @test -x "$(HUB_CORE_PYTHON)" || \ + (echo "hub-core environment missing at $(HUB_CORE_PYTHON)" >&2; exit 2) + PYTHONPATH=src "$(HUB_CORE_PYTHON)" scripts/hub-extension-check.py \ + --hub-core-repo "$(HUB_CORE_REPO)" + +hub-core-conformance: + @test -x "$(HUB_CORE_PYTHON)" || \ + (echo "hub-core environment missing at $(HUB_CORE_PYTHON)" >&2; exit 2) + PYTHONPATH=src "$(HUB_CORE_PYTHON)" scripts/hub-core-conformance.py \ + --hub-core-repo "$(HUB_CORE_REPO)" + interhub-gate: IHUB_BASE="$(IHUB_BASE)" python3 scripts/interhub-gate-probe.py diff --git a/README.md b/README.md index 75ed6d6..c236e39 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ -Operations / System 1 extension for Core Hub. +Operations / System 1 extension for Hub Core. + +The versioned `helixforge.hub-extension` descriptor, manifest, validation, and +State Hub retirement boundary are documented in +[`docs/hub-extension.md`](docs/hub-extension.md). `ops-hub` is the operational truth surface for environments, hosts, clusters, services, endpoints, releases, backups, incidents, risks, runbooks, readiness -gates, and migration waves. Generic hub framework work stays in `core-hub`; +gates, and migration waves. Generic hub framework work stays in `hub-core`; operations-specific extension code belongs here. Historical Haskell Inter-Hub is `inter-hub-haskell` (retired 2026-07-08). diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index d79ef21..bb40fb6 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -8,7 +8,7 @@ | Kind | ID | Status | Lane | Source | | --- | --- | --- | --- | --- | -| workplan | OPS-WP-0003 | proposed | — | workplans/OPS-WP-0003-extension-contract-alignment.md | -| task | OPS-WP-0003-T01 | todo | — | workplans/OPS-WP-0003-extension-contract-alignment.md | -| task | OPS-WP-0003-T02 | todo | — | workplans/OPS-WP-0003-extension-contract-alignment.md | -| task | OPS-WP-0003-T03 | todo | — | workplans/OPS-WP-0003-extension-contract-alignment.md | +| workplan | OPS-WP-0003 | finished | — | workplans/OPS-WP-0003-extension-contract-alignment.md | +| task | OPS-WP-0003-T01 | done | — | workplans/OPS-WP-0003-extension-contract-alignment.md | +| task | OPS-WP-0003-T02 | done | — | workplans/OPS-WP-0003-extension-contract-alignment.md | +| task | OPS-WP-0003-T03 | done | — | workplans/OPS-WP-0003-extension-contract-alignment.md | diff --git a/docs/bootstrap-runbook.md b/docs/bootstrap-runbook.md index 08d1815..34c64e5 100644 --- a/docs/bootstrap-runbook.md +++ b/docs/bootstrap-runbook.md @@ -1,18 +1,20 @@ # Ops Hub Bootstrap Runbook -Date: 2026-06-17 +Date: 2026-08-31 ## Purpose This runbook is the operator-ready path for activating `ops-hub` in production -Inter-Hub. It covers the preferred API bootstrap, key custody expectations, the -ops-warden remote execution lane, and the explicit SQL fallback. +Hub Core's `/api/v2` compatibility surface. It covers the preferred API +bootstrap, key custody expectations, the ops-warden remote execution lane, and +the explicit SQL fallback. Use this when finishing `CUST-WP-0047-T05` or any later ops-hub Inter-Hub activation task. ## Inputs +- Hub-extension package: `registry/hub-extension/v0.1.0/ops-hub.extension.json` - Manifest draft: `seeds/ops-hub-manifest.draft.json` - Widget seed: `seeds/ops-hub-widgets.seed.json` - API helper: `scripts/ops-hub-bootstrap-api.py` @@ -167,14 +169,21 @@ curl -s https://hub.coulomb.social/api/v2/hub-registry Expected: `ops-hub` is visible, and the operator can see the seeded widgets in the authenticated UI. -## Current Live-Execution Blocker +## Authenticated rerun boundary -The repo-side helper, dry-run path, and runbook are ready. The remaining step is -an operator-attended production run with: +Ops Hub bootstrap and consumer gates passed during the hub-core candidate +cutover (`RAPPCOREHUB-WP-0002-T04`), and the 2026-08-31 post-cutover public +gate passes against `https://hub.coulomb.social`. There is no pending bootstrap +mutation merely to finish `OPS-WP-0003`. -- an operator-provided `IHUB_OPERATOR_KEY_FILE`, -- an OpenBao-materialized key on a trusted host, or -- an explicitly approved deployment-side migration/bootstrap path. +An authenticated rerun remains a high-risk attended operation because it may +create a consumer key or production records. Use it only for a real registration +change, start with `warden route show inter-hub-bootstrap-ssh --json`, and +record non-secret evidence. For routine validation use: -Until one of those is available, run `make interhub-gate`, `make test`, and -`make interhub-bootstrap-dry-run` for local validation. +```bash +make test +make hub-extension-check +make hub-core-conformance +make interhub-gate +``` diff --git a/docs/hub-extension.md b/docs/hub-extension.md new file mode 100644 index 0000000..1b9dbdd --- /dev/null +++ b/docs/hub-extension.md @@ -0,0 +1,78 @@ +# Ops Hub extension package + +Ops Hub implements the owner side of `helixforge.hub-extension` 0.1.0. Hub +Core owns and runs the framework ports; this repository owns the operations +descriptor, capability declarations, event boundary, compatibility bootstrap, +and evidence used to move service-catalog responsibility out of State Hub. + +The canonical package is +`registry/hub-extension/v0.1.0/ops-hub.extension.json`. Its stable identity is +`capability.operations.ops-hub`; it also declares +`capability.operations.service-catalog` as an owner capability. + +## Ownership boundary + +| Surface | Owner | +| --- | --- | +| Descriptor, manifest, operational vocabulary, service-catalog records | `ops-hub` | +| `port.*` schemas, event catalog, registry and projection runtime | `hub-core` | +| Runtime and operator credentials | OpenBao/platform custody; never this package | +| State Hub compatibility routes during retirement | `state-hub` until their evidenced cutover | + +Ops Hub is an aspect hub, not a second hub-core deployment. The API endpoint in +the manifest is a discovery reference to the hub-core-hosted compatibility +surface at `https://hub.coulomb.social/api/v2`. + +## Events and legacy compatibility + +The 0.1.0 port contract emits `ops.endpoint.verified` and consumes +`hub.progress.recorded`. The older `/api/v2` bootstrap seed uses +`ops-endpoint-verified`; hub-core's compatibility adapter retains that legacy +spelling. New port-native evidence uses the dotted catalog event and must not +silently rewrite historical events. + +## Validation + +```bash +make test +make hub-extension-check +make hub-core-conformance +make interhub-gate +``` + +`hub-extension-check` validates the owner package against hub-core's packaged +Draft 2020-12 descriptor and manifest schemas, checks every declared event +against hub-core's catalog, and emits a non-secret checksum receipt. + +`hub-core-conformance` injects this repository's package into a disposable +hub-core reference runtime. Contract version 0.1.0 automates C1, C3, C4, C5, +C6, and C8 plus Tier-3 F2 and F3. C2 registry resolution, C7 raw-port policy, +C9 dependency-aware readiness, and C10 version negotiation are not automated +by the current hub-core harness and are not represented as passing. + +`interhub-gate` is the safe post-cutover production check. It proves that the +hub-core-hosted `/api/v2` surface exposes the bootstrap resources without +requiring a credential or mutating production. + +## Authenticated bootstrap + +The existing `scripts/ops-hub-bootstrap-api.py` remains the idempotent +compatibility bootstrap client. It creates or reuses the hub, manifest, API +consumer, widgets, and verification event. Follow `docs/bootstrap-runbook.md`. + +Operator and runtime keys are never arguments, logs, Git content, or extension +fields. Credential routing starts with: + +```bash +warden route show inter-hub-bootstrap-ssh --json +``` + +The current route is high-risk and attended. A source change or public gate +does not authorize another live bootstrap mutation. + +## State Hub retirement handoff + +This package satisfies the receiving-side contract for `STATE-WP-0079` Wave +D1. Removing State Hub's service-catalog routes still requires a separate +caller inventory, projection/count comparison, rollback receipt, and explicit +production writer-cutover authorization. Owner readiness is not cutover proof. diff --git a/registry/NO_CAPABILITIES.md b/registry/NO_CAPABILITIES.md deleted file mode 100644 index 31d3276..0000000 --- a/registry/NO_CAPABILITIES.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -repo: ops-hub -reason: > - Only a single diagnostic bootstrap-API probe script exists; the described operational-truth surface (hosts/services/incidents/runbooks) isn't implemented yet. -reviewed: "2026-07-06" -reviewed_by: claude-code -revisit: > - Revisit once the operational-truth surface has real implementation beyond the probe script. ---- - -# No reusable capability - -This repo was reviewed for the `reuse-surface` capability registry -(REUSE-WP-0017 coverage campaign) and has no capability to register at this -time. See the `reason` field above. diff --git a/registry/capabilities/capability.operations.ops-hub.md b/registry/capabilities/capability.operations.ops-hub.md new file mode 100644 index 0000000..79d0254 --- /dev/null +++ b/registry/capabilities/capability.operations.ops-hub.md @@ -0,0 +1,140 @@ +--- +id: capability.operations.ops-hub +name: Operations Hub Extension Contract +summary: Versioned hub-extension package and bootstrap client for operational truth, service-catalog records, readiness evidence, and migration gates. +owner: ops-hub +status: reviewed +domain: operations +tags: + - operations + - service-catalog + - readiness + - hub-extension + +maturity: + discovery: + current: D3 + target: D4 + confidence: high + rationale: > + The owner boundary, versioned descriptor/manifest, hub-core ports, event + families, compatibility bootstrap, and State Hub retirement handoff are + explicit and tested. + availability: + current: A1 + target: A3 + confidence: high + rationale: > + The contract package and idempotent bootstrap client are available from + source; the framework runtime is hosted by hub-core rather than this repo. + +external_evidence: + completeness: + level: C1 + confidence: medium + basis: scope_vs_intent_and_consumer_expectations + satisfied_expectations: + - versioned extension descriptor and manifest + - hub-core schema and event-catalog validation + - disposable Tier 2/3 conformance profile + - production compatibility API public gate + broken_expectations: [] + out_of_scope_expectations: + - independent framework runtime + - secret custody + reliability: + level: R1 + confidence: high + basis: consumer_quality_signals + known_reliability_risks: + - authenticated re-bootstrap requires the attended high-risk access lane + +discovery: + intent: > + Let hub-core host an Ops Hub extension without transferring ownership of + operational records or credentials into the framework. + includes: + - descriptor and capability manifest + - operations service-catalog declaration + - readiness evidence event boundary + - compatibility bootstrap client and public gate + excludes: + - hub-core framework implementation + - credential storage or vending + - State Hub compatibility routes after cutover + assumptions: + - hub-core remains the surviving framework runtime + use_cases: + - register Ops Hub against hub-core + - publish and project service-catalog records + - attach non-secret readiness evidence + research_memos: [] + +availability: + current_level: A1 + target_level: A3 + current_artifacts: + - registry/hub-extension/v0.1.0/ops-hub.extension.json + - scripts/ops-hub-bootstrap-api.py + - scripts/hub-extension-check.py + - scripts/hub-core-conformance.py + target_artifacts: + - policy-bound unattended registration lane + consumption_modes: + - contract package + - command-line bootstrap + +relations: + depends_on: + - capability.infotech.hub-core-library + supports: + - capability.operations.service-catalog + related_to: [] + +evidence: + documentation: + - docs/hub-extension.md + - docs/bootstrap-runbook.md + tests: + - tests/test_extension_contract.py + - make hub-core-conformance + consumer_feedback: + - RAPPCOREHUB-WP-0002-T04 + bug_reports: [] + incidents: [] + +consumer_guidance: + recommended_for: + - registering and validating the Ops Hub extension on hub-core + - migrating State Hub service-catalog consumers to an owner-backed contract + not_recommended_for: + - treating ops-hub as another hub-core runtime + - retrieving or storing credentials + known_limitations: + - C2, C7, C9, and C10 are not automated by hub-core contract version 0.1.0 + - authenticated live bootstrap requires the governed attended access lane + +promotion_history: + - date: "2026-08-31" + dimension: discovery + from: D0 + to: D3 + rationale: Published the owner-side 0.1.0 package and conformance evidence. + author: codex + - date: "2026-08-31" + dimension: availability + from: A0 + to: A1 + rationale: Published source-level validation and bootstrap consumers. + author: codex +--- + +# Operations Hub Extension Contract + +The capability is the owner-side contract package and bootstrap client. Hub +Core supplies the framework runtime; Ops Hub retains the operational vocabulary, +service-catalog model, readiness evidence, and migration-gate ownership. + +The package contains no credentials. Authenticated bootstrap follows +`docs/bootstrap-runbook.md` and emits only non-secret identifiers and key +prefixes as evidence. diff --git a/registry/hub-extension/v0.1.0/ops-hub.extension.json b/registry/hub-extension/v0.1.0/ops-hub.extension.json new file mode 100644 index 0000000..8345113 --- /dev/null +++ b/registry/hub-extension/v0.1.0/ops-hub.extension.json @@ -0,0 +1,76 @@ +{ + "descriptor": { + "contract_id": "helixforge.hub-extension", + "contract_version": "0.1.0", + "hub_slug": "ops-hub", + "display_name": "Operations Hub", + "description": "Operations aspect hub for operational truth, service-catalog records, readiness evidence, and migration gates.", + "domain": "operations", + "hub_kind": "aspect", + "status": "active", + "reuse_surface_id": "capability.operations.ops-hub", + "contract_version_min": "0.1.0", + "contract_version_max": "0.1.0", + "vsm_system": "operations", + "vsm_function": "coordination" + }, + "manifest": { + "manifest_version": "0.1.0", + "schema_version": "0.1.0", + "reuse_surface_id": "capability.operations.ops-hub", + "provides": [ + "capability.operations.ops-hub", + "capability.operations.service-catalog" + ], + "consumes": [ + "port.registry", + "port.messaging", + "port.events.interaction", + "port.projection.query", + "port.policy" + ], + "events_emitted": [ + "ops.endpoint.verified" + ], + "events_consumed": [ + "hub.progress.recorded" + ], + "endpoints": [ + { + "id": "api", + "discovery_key": "service.ops-hub.http" + }, + { + "id": "framework-api", + "url": "https://hub.coulomb.social/api/v2" + }, + { + "id": "docs", + "url": "https://forgejo.coulomb.social/coulomb/ops-hub/src/branch/main/docs/hub-extension.md" + } + ], + "policy_scopes": [ + "ops.catalog.read", + "ops.evidence.write" + ], + "widgets": [ + { + "id": "ops-overview", + "kind": "widget", + "description": "Non-secret operations overview surface." + } + ], + "operator_surfaces": [ + { + "id": "ops-console", + "kind": "console", + "description": "Operator-facing operations console hosted by hub-core projections." + }, + { + "id": "ops-bootstrap", + "kind": "cli", + "description": "Attended, idempotent registration and compatibility bootstrap." + } + ] + } +} diff --git a/registry/indexes/capabilities.yaml b/registry/indexes/capabilities.yaml index f944e47..a592a65 100644 --- a/registry/indexes/capabilities.yaml +++ b/registry/indexes/capabilities.yaml @@ -1,4 +1,20 @@ version: 1 -updated: '2026-06-16' +updated: '2026-08-31' domain: helix_forge -capabilities: [] +capabilities: +- id: capability.operations.ops-hub + name: Operations Hub Extension Contract + summary: Versioned hub-extension package and bootstrap client for operational truth, service-catalog records, readiness evidence, and migration gates. + vector: D3 / A1 / C1 / R1 + domain: operations + status: reviewed + owner: ops-hub + path: registry/capabilities/capability.operations.ops-hub.md + tags: + - operations + - service-catalog + - readiness + - hub-extension + consumption_modes: + - contract package + - command-line bootstrap diff --git a/scripts/hub-core-conformance.py b/scripts/hub-core-conformance.py new file mode 100644 index 0000000..fc3f2b4 --- /dev/null +++ b/scripts/hub-core-conformance.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Run Ops Hub's package through a disposable hub-core reference runtime.""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--hub-core-repo", type=Path, default=Path("../hub-core")) + args = parser.parse_args() + sys.path.insert(0, str(args.hub_core_repo.resolve())) + + from fastapi.testclient import TestClient + from hub_core.conformance import ConformanceHarness + from hub_core.runtime.app import create_app + from hub_core.runtime.config import RuntimeSettings + from hub_core.runtime.store import InMemoryPortStore + from ops_hub.extension_contract import load_extension_package + + settings = RuntimeSettings(environment="test", backend="memory", allow_ephemeral=True) + target = TestClient(create_app(settings=settings, port_store=InMemoryPortStore())) + harness = ConformanceHarness(target) + harness.package = load_extension_package() + report = harness.run() + print(json.dumps(report.to_dict(), indent=2, sort_keys=True)) + return 0 if report.passed else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/hub-extension-check.py b/scripts/hub-extension-check.py new file mode 100644 index 0000000..64547cf --- /dev/null +++ b/scripts/hub-extension-check.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""Validate the owner package against hub-core's versioned schemas.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from jsonschema import Draft202012Validator, FormatChecker + +from ops_hub.extension_contract import load_extension_package, validation_receipt + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--hub-core-repo", type=Path, default=Path("../hub-core")) + args = parser.parse_args() + + root = ( + args.hub_core_repo + / "hub_core" + / "contracts" + / "helixforge_hub_extension" + / "v0_1_0" + ) + schemas = root / "schemas" + package = load_extension_package() + for key, schema_name in ( + ("descriptor", "hub-descriptor.schema.json"), + ("manifest", "hub-manifest.schema.json"), + ): + schema = json.loads((schemas / schema_name).read_text(encoding="utf-8")) + Draft202012Validator.check_schema(schema) + Draft202012Validator(schema, format_checker=FormatChecker()).validate(package[key]) + + catalog = json.loads((root / "catalogs" / "event-types.json").read_text(encoding="utf-8")) + known_events = {entry["type"] for entry in catalog["event_types"]} + declared_events = set(package["manifest"]["events_emitted"]) + declared_events.update(package["manifest"]["events_consumed"]) + unknown = sorted(declared_events - known_events) + if unknown: + raise SystemExit("events absent from hub-core catalog: " + ", ".join(unknown)) + + print(json.dumps(validation_receipt(), indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/ops_hub/extension_contract.py b/src/ops_hub/extension_contract.py new file mode 100644 index 0000000..8e30f2f --- /dev/null +++ b/src/ops_hub/extension_contract.py @@ -0,0 +1,102 @@ +"""Ops Hub's owner-side ``helixforge.hub-extension`` package helpers.""" + +from __future__ import annotations + +import hashlib +import json +import re +from collections.abc import Mapping, Sequence +from pathlib import Path +from typing import Any + + +CONTRACT_ID = "helixforge.hub-extension" +CONTRACT_VERSION = "0.1.0" +REUSE_SURFACE_ID = "capability.operations.ops-hub" +PACKAGE_PATH = ( + Path(__file__).resolve().parents[2] + / "registry" + / "hub-extension" + / "v0.1.0" + / "ops-hub.extension.json" +) + +_SECRET_KEY = re.compile( + r"(?:^|_)(?:api_?(?:key|token)|access_?token|auth_?token|client_?secret|" + r"credential|passwd|password|private_?key|secret)(?:$|_)", + re.IGNORECASE, +) +_SECRET_VALUE = re.compile( + r"(?:postgres(?:ql)?|mysql|mariadb|mongodb(?:\+srv)?|redis)://" + r"[^\s/:]+:[^\s/@]+@|-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----", + re.IGNORECASE, +) + + +def load_extension_package(path: Path = PACKAGE_PATH) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def package_sha256(path: Path = PACKAGE_PATH) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def find_secret_violations(value: Any, path: str = "$") -> list[str]: + """Return paths with secret-shaped keys/values without returning values.""" + + violations: list[str] = [] + if isinstance(value, Mapping): + for key, child in value.items(): + child_path = f"{path}.{key}" + if _SECRET_KEY.search(str(key)): + violations.append(child_path) + violations.extend(find_secret_violations(child, child_path)) + elif isinstance(value, Sequence) and not isinstance(value, (str, bytes)): + for index, child in enumerate(value): + violations.extend(find_secret_violations(child, f"{path}[{index}]")) + elif isinstance(value, str) and _SECRET_VALUE.search(value): + violations.append(path) + return violations + + +def validate_owner_invariants(package: Mapping[str, Any]) -> None: + descriptor = package.get("descriptor") + manifest = package.get("manifest") + if not isinstance(descriptor, Mapping) or not isinstance(manifest, Mapping): + raise ValueError("package must contain descriptor and manifest objects") + if descriptor.get("contract_id") != CONTRACT_ID: + raise ValueError("unexpected extension contract id") + if descriptor.get("contract_version") != CONTRACT_VERSION: + raise ValueError("unexpected extension contract version") + if descriptor.get("hub_slug") != "ops-hub": + raise ValueError("descriptor is not owned by ops-hub") + if descriptor.get("reuse_surface_id") != REUSE_SURFACE_ID: + raise ValueError("descriptor reuse_surface_id is not canonical") + if manifest.get("reuse_surface_id") != REUSE_SURFACE_ID: + raise ValueError("descriptor and manifest reuse_surface_id differ") + if REUSE_SURFACE_ID not in manifest.get("provides", []): + raise ValueError("manifest does not provide its canonical capability") + if "capability.operations.service-catalog" not in manifest.get("provides", []): + raise ValueError("manifest omits the service-catalog retirement handoff") + if "ops.endpoint.verified" not in manifest.get("events_emitted", []): + raise ValueError("manifest omits its governed verification event") + violations = find_secret_violations(package) + if violations: + raise ValueError("secret-like material at " + ", ".join(violations)) + + +def validation_receipt(path: Path = PACKAGE_PATH) -> dict[str, Any]: + package = load_extension_package(path) + validate_owner_invariants(package) + return { + "ok": True, + "contract_id": CONTRACT_ID, + "contract_version": CONTRACT_VERSION, + "package": str(path), + "sha256": package_sha256(path), + "reuse_surface_id": REUSE_SURFACE_ID, + "provides": package["manifest"]["provides"], + "events_emitted": package["manifest"]["events_emitted"], + "events_consumed": package["manifest"]["events_consumed"], + "secret_violations": [], + } diff --git a/tests/test_extension_contract.py b/tests/test_extension_contract.py new file mode 100644 index 0000000..5ff0ae6 --- /dev/null +++ b/tests/test_extension_contract.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import json +import unittest + +from ops_hub.extension_contract import ( + CONTRACT_ID, + CONTRACT_VERSION, + PACKAGE_PATH, + REUSE_SURFACE_ID, + find_secret_violations, + load_extension_package, + package_sha256, + validate_owner_invariants, + validation_receipt, +) + + +class ExtensionContractTests(unittest.TestCase): + def test_owner_package_is_valid_and_versioned(self) -> None: + package = load_extension_package() + validate_owner_invariants(package) + self.assertEqual(package["descriptor"]["contract_id"], CONTRACT_ID) + self.assertEqual(package["descriptor"]["contract_version"], CONTRACT_VERSION) + self.assertEqual(package["manifest"]["reuse_surface_id"], REUSE_SURFACE_ID) + + def test_package_is_canonical_json_and_receipt_is_non_secret(self) -> None: + package = load_extension_package() + self.assertEqual(package, json.loads(PACKAGE_PATH.read_text(encoding="utf-8"))) + receipt = validation_receipt() + self.assertTrue(receipt["ok"]) + self.assertEqual(len(package_sha256()), 64) + self.assertEqual(receipt["secret_violations"], []) + + def test_secret_detection_reports_paths_without_values(self) -> None: + value = { + "nested": {"api_token": "do-not-return"}, + "database": "postgresql://runtime:do-not-return@example.invalid/hub", + } + self.assertEqual(find_secret_violations(value), ["$.nested.api_token", "$.database"]) + + def test_service_catalog_handoff_and_event_boundary_are_declared(self) -> None: + manifest = load_extension_package()["manifest"] + self.assertIn("capability.operations.service-catalog", manifest["provides"]) + self.assertEqual(manifest["events_emitted"], ["ops.endpoint.verified"]) + self.assertEqual(manifest["events_consumed"], ["hub.progress.recorded"]) + self.assertIn("port.events.interaction", manifest["consumes"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/workplans/OPS-WP-0003-extension-contract-alignment.md b/workplans/OPS-WP-0003-extension-contract-alignment.md index 3670094..3e780a4 100644 --- a/workplans/OPS-WP-0003-extension-contract-alignment.md +++ b/workplans/OPS-WP-0003-extension-contract-alignment.md @@ -4,11 +4,25 @@ type: workplan title: "Extension contract alignment" domain: infotech repo: ops-hub -status: proposed +status: finished owner: codex topic_slug: infotech created: "2026-08-09" -updated: "2026-08-09" +updated: "2026-08-31" +reviewed_at: "2026-08-31" +reviewed_by: codex +reviewed_against_commit: "9bbbf2032daf257d786446217bad79dc051a529c" +quality_dor: DoR-Ok +quality_dor_at: "2026-08-31" +quality_dor_by: codex +quality_dod: DoD-Ok +quality_dod_at: "2026-08-31" +quality_dod_by: codex +quality_dod_note: >- + The owner-side 0.1.0 package is versioned and federated, passes hub-core's + schemas/catalog and all eight implemented conformance checks, and the + post-cutover production compatibility gate is green. Unimplemented framework + checks are live residual HUB-WP-0009 rather than reported as passing. parent_project: prj-state-hub-retirement parent_workplan: SHR-WP-0001 related: @@ -30,7 +44,7 @@ registration without secrets. ```task id: OPS-WP-0003-T01 -status: todo +status: done priority: high state_hub_task_id: "a938b0e2-912e-5126-aa6f-d63bdeebca5d" ``` @@ -39,22 +53,50 @@ Produce hub descriptor + capability manifest referencing `capability.infotech.ops-hub` (or current registry id), events emitted/consumed, and non-secret endpoints. +**Completed 2026-08-31.** Published the owner-side +`helixforge.hub-extension` 0.1.0 package at +`registry/hub-extension/v0.1.0/ops-hub.extension.json`. It uses the current +hub-core fixture identity `capability.operations.ops-hub`, declares the +operations service-catalog handoff, named framework ports, emitted +`ops.endpoint.verified`, consumed `hub.progress.recorded`, and non-secret +discovery/documentation endpoints. The package checksum is +`d48bb22c88598af482a3a779b930d1269e5bd8fd9e245681e278f14c4bd064d0`. + +The capability is now a reviewed federation entry (`D3 / A1 / C1 / R1`) rather +than an empty index. `docs/hub-extension.md` records that hub-core owns the +runtime while ops-hub owns operational vocabulary and records; the legacy +dash-separated event remains a compatibility spelling, not a rewrite target. + ## Pass Tier 2 conformance against hub-core ```task id: OPS-WP-0003-T02 -status: todo +status: done priority: high state_hub_task_id: "1d1c422d-892c-5404-8d7f-a1f78c5cd87a" ``` When HUB-WP-0004 harness exists, run C1–C10 checks; fix gaps in ops-hub. +**Completed 2026-08-31 with framework residuals.** The owner package validates +against hub-core's packaged Draft 2020-12 descriptor/manifest schemas and event +catalog with zero secret-shaped fields. Injecting that package into a disposable +hub-core runtime passed the complete implemented profile: Tier-2 C1, C3, C4, +C5, C6, C8 and Tier-3 F2/F3 (8/8). + +Hub Core contract 0.1.0 explicitly does not automate C2, C7, C9, or C10; those +cannot be fixed honestly in an extension repository. They are residual-owned by +new proposed owner workplan `HUB-WP-0009`, covering registry resolution, +raw-port configuration policy, dependency-aware readiness, version negotiation, +and any remaining tenant-isolation split. It is registered as State Hub +workplan `0d6e94f3-fd15-5f57-af41-60f0b862da5e`. The report does not count +unsupported checks as passing. + ## Bootstrap dual-run on consolidated runtime ```task id: OPS-WP-0003-T03 -status: todo +status: done priority: high state_hub_task_id: "947306d4-8a00-5cd6-8c6d-1f266d45055c" ``` @@ -62,8 +104,23 @@ state_hub_task_id: "947306d4-8a00-5cd6-8c6d-1f266d45055c" Verify bootstrap/smokes against hub-core-hosted `/api/v2` during CORE-WP-0010 dual-run and after cutover. +**Completed 2026-08-31.** `RAPPCOREHUB-WP-0002-T04` records the ops-hub +consumer gate passing against the public candidate during the S1–S5 dual-run. +The production cutover and exercised rollback/forward recovery repeated the +consumer gates. A fresh post-cutover `make interhub-gate` now passes against +`https://hub.coulomb.social`: `/api/v2/hubs` fails closed with 401 for an +anonymous caller, `/api/v2/openapi.json` returns 200, and all four required +bootstrap resources are present. + +No new authenticated bootstrap mutation was needed or performed. The routed +Inter-Hub operator-key lane is high-risk and attended +(`inter-hub-bootstrap-ssh`); repeating a successful bootstrap merely to obtain +fresh evidence would create credentials and production records without a +functional change. Existing idempotent bootstrap behavior remains covered by +the source tests, while the current production check is non-mutating. + ## Acceptance -- [ ] Hub package artefacts present and versioned -- [ ] Tier 2 conformance green (or residual-owned gaps) -- [ ] Bootstrap works on consolidated runtime +- [x] Hub package artefacts present and versioned +- [x] Tier 2 conformance green (or residual-owned gaps) +- [x] Bootstrap works on consolidated runtime