hub-core/docs/core-hub-absorption-plan.md
tegwick 7e1ec03f0c
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / pytest-smoke (push) Failing after 0s
feat: add hub runtime and extension contract
2026-08-21 10:58:03 +02:00

14 KiB
Raw Permalink Blame History

Core Hub absorption and /api/v2 cutover plan

Status: accepted joint input for HUB-WP-0004-T06 and CORE-WP-0010-T02 Date: 2026-08-21 Runtime source: hub-core Rollback runtime: core-hub on railiance01

Decision

Absorb Core Hub into hub-core by capability and data boundary. There is no big-bang route move and no period with two production writers for the same boundary.

Hub-core owns the surviving wheel, runtime image, extension contracts, compatibility routes, durable framework store, and migrations. Core Hub stays deployable as the rollback runtime until every /api/v2 route group, durable record family, consumer gate, and rollback exercise has passed. Its Python package is never imported by hub-core.

The current production baseline is Core Hub chart 0.3.2 on railiance01, backed by the core_hub PostgreSQL schema. Its source database on CoulombCore is already frozen and is historical rollback evidence, not a participant in this absorption.

Non-negotiable invariants

  1. A route group has exactly one production writer at any moment.
  2. A write freeze, final export, count/content comparison, and explicit operator approval precede every production writer change.
  3. Shadow traffic consists of reads and synthetic fixture writes only. Do not mirror normal writes to both runtimes.
  4. Hub-core and Core Hub keep separate SQLAlchemy metadata and migration lineages. Hub-core may copy legacy rows but must not import core_hub models or migrations.
  5. IDs, timestamps, relationships, source revision, source table, and import bundle hash survive migration as provenance.
  6. Only API-key hashes and prefixes move. Raw keys, static operator tokens, database credentials, and secret values never enter bundles or evidence.
  7. Authorization fails closed when its dependency is unavailable. The compatibility API-key validator is temporary; authorization decisions remain owned through the identity/policy boundary.
  8. Core Hub remains a tested rollback target until the stabilization and rollback-retirement gates close.

Runtime and database shape

The hub-core image selected by ADR-0001 is the candidate runtime. Its current in-memory PortStore is not a migration target. The first absorption slice must add a PostgreSQL implementation, production readiness checks, an audit ledger, and migrations owned entirely by hub-core.

Use the existing platform PostgreSQL allocation currently assigned to Core Hub; do not consume a fifth application slot. Add a distinct target schema (provisionally hub_runtime) beside retained legacy schema core_hub, then transfer the consumer declaration and dynamic credential identity as part of the final package transition. Separate schemas make source/target counts and rollback observable without mixing ORM metadata.

The deployment package currently named rapp-core-hub owns the verified railiance01 chart, policies, secret projections, live verifier, and rollback surface. During dual-run it must deploy both immutable image revisions or a candidate release beside the legacy release. Before Core Hub is archived, the package must either be renamed/replaced by a hub-core-owned rapp package or recorded explicitly as the surviving package with updated ownership and intent. No application source moves into the deployment package.

Traffic controls

Implement route-group controls before moving data:

  • HUB_CORE_V2_GROUPS: compatibility groups served by hub-core;
  • HUB_CORE_V2_WRITE_GROUPS: subset allowed to mutate hub-core authority;
  • CORE_HUB_V2_WRITE_GROUPS: legacy groups still allowed to mutate Core Hub;
  • an ingress/gateway route map selecting core-hub or hub-core per group;
  • a shadow comparator that calls both read targets, normalizes ordering and volatile fields, and records hashes without response bodies containing sensitive data.

The exact configuration carrier may be Helm values rather than environment variables, but the three states are mandatory and independently observable:

State Public owner Candidate behavior Write rule
legacy Core Hub health plus synthetic fixtures Core Hub only
shadow Core Hub normalized read comparison Core Hub only
candidate hub-core public read/write for the group hub-core only

Direct service access is network-policy restricted. The route map is the normal traffic authority; application write-group checks are defense in depth. A configuration assertion must reject overlap between legacy and candidate write sets.

Route and module move order

Each slice is independently buildable, reversible, and evidenced. Later slices do not excuse a failed earlier gate.

S0 — Durable runtime, compatibility shell, and migration tooling

No public route moves.

  • Add hub-core PostgreSQL PortStore, session lifecycle, readiness, audit ledger, import/export commands, and a migration bundle schema.
  • Add /api/v2 router composition, legacy response serializers, bearer-token dependency, per-group write controls, and normalized shadow comparison.
  • Import the static operator token through the existing OpenBao/ExternalSecret lane and copy API-key hashes into the isolated compatibility auth store.
  • Preserve current error/status semantics and unprefixed OpenAPI aliases.
  • Extend the conformance suite with durable restart/rebuild, authorization allow/deny/unavailable, metadata isolation, and migration idempotence.

Gate: frozen source fixture imports twice without duplicates; all seven source table counts can be represented; production readiness fails when PostgreSQL or authorization is unavailable; no traffic has moved.

S1 — System and read-only discovery

Routes:

  • /healthz, /readyz;
  • /api/v2/widget-types, /api/v2/event-types;
  • /api/v2/annotation-categories, /api/v2/policy-scopes;
  • /api/v2/openapi.json, /api/v2/openapi.yaml, /api/v2/docs;
  • compatibility aliases published by the current OpenAPI surface.

Move the catalogs, schemas, OpenAPI snapshot/export behavior, and public consumer fixtures. Static catalog responses are compared as canonical JSON. Readiness must reflect the dependencies required by enabled groups rather than reporting healthy from process liveness alone.

Gate: public catalog and documentation smoke passes against both targets; normalized payload hashes match; ops-hub public gate and activity-core resolver pass; rollback route-map change is rehearsed.

S2 — Registry and manifest authority

Routes:

  • GET/POST /api/v2/hubs;
  • GET/POST /api/v2/hub-capability-manifests;
  • PATCH /api/v2/hub-capability-manifests/{manifest_id};
  • POST /api/v2/hub-capability-manifests/{manifest_id}/activate;
  • GET /api/v2/hub-registry.

Modules/data:

  • copy hubs and hub_capability_manifests into hub-core registry authority;
  • preserve legacy UUIDs and camel-case compatibility serialization;
  • validate new writes against helixforge.hub-extension, while a named legacy adapter reads migrated 0.x records without silently rewriting them;
  • add activation audit records and expose registry projections with provenance.

Gate: table counts and order-independent row hashes match; manifest activation is idempotent and audited; protected allow/deny checks pass; ops-hub bootstrap can resolve and update its manifest; rollback delta replay is proven.

S3 — API consumers and compatibility credentials

Routes:

  • GET/POST /api/v2/api-consumers;
  • POST /api/v2/api-consumers/{consumer_id}/api-keys;
  • POST /api/v2/token.

Modules/data:

  • copy api_consumers and api_keys, including status, scopes, quotas, hash, and prefix but never raw key material;
  • preserve one-time key issuance semantics and ensure generated secrets are absent from logs, projections, migration bundles, and evidence;
  • keep the static operator credential and dynamic consumer keys in distinct custody lanes;
  • record a residual owner and follow-on task for replacing compatibility keys with the platform identity/authorization ports. The shim may outlive Core Hub briefly, but it is not a permanent hub-core authority.

Gate: existing ops-hub and activity-core credentials authenticate against the candidate; invalid, revoked, missing, and dependency-unavailable cases deny; rate/quota behavior is either preserved or explicitly residual-owned; reverse delta replay for newly issued key hashes is rehearsed without exposing a key.

S4 — Widgets and interaction evidence

Routes:

  • GET/POST /api/v2/widgets;
  • GET/POST /api/v2/interaction-events.

Modules/data:

  • copy widgets into the operator-surface registry/projection boundary;
  • copy interaction_events into the physically separate interaction-event authority while retaining legacy widgetId, event type, context, metadata, IDs, and timestamps in the compatibility view;
  • route new writes through port.events.interaction and build /api/v2 reads from the same authority rather than a second compatibility-only store.

Gate: counts and hashes match; projection rebuild fixture and migrated sample produce provenance-equivalent views; progress events never appear in the interaction family; ops-hub and activity-core each post and read back unique correlated evidence; rollback delta replay is proven.

S5 — Empty compatibility collections and operator console

Routes:

  • GET/POST /api/v2/annotations;
  • GET/POST /api/v2/requirement-candidates;
  • GET/POST /api/v2/decision-records;
  • GET/POST /api/v2/deployment-records;
  • GET/POST /api/v2/outcome-signals;
  • /console.

The five collections have no Core Hub durable models and currently return empty data. Preserve that behavior only as a compatibility adapter; do not claim or create authority. Each endpoint needs an explicit retirement or future-owner record before the adapter is removed. Rebuild the console from hub-core projections and public APIs; it must not query the retained core_hub schema.

Gate: exact empty/error compatibility passes, the console authorization boundary and visual smoke pass, and every deferred collection has a residual disposition.

S6 — Whole-host cutover and Core Hub retirement

After S1S5 are in candidate state:

  1. Freeze all remaining Core Hub writes and capture a final encrypted backup, table counts, content hashes, schema revision, image digest, and correlation ID.
  2. Import any remaining deltas and require zero unexplained count/hash drift.
  3. Run hub-core conformance, the Core Hub deployed smoke, ops-hub bootstrap, and activity-core resolver/write-readback against the candidate image.
  4. With explicit operator approval, point hub.coulomb.social entirely at hub-core and set every Core Hub write group empty.
  5. Observe at least seven consecutive days with no unexplained 5xx, no failed consumer gate, no data drift, and no normal request to Core Hub. Record any exception with an owner and expiry.
  6. Exercise rollback once during rehearsal. After the stabilization window and explicit retirement approval, scale Core Hub to zero while retaining its immutable image, database backup, deployment revision, and route-map rollback instructions until the agreed expiry.
  7. Move the final OpenAPI/contracts/smoke fixtures into hub-core history, record residual owners, archive the core-hub repository read-only, and update the deployment package identity.

Evidence required for every durable slice

Evidence Pass condition
Source identity Core Hub image/source revision and schema revision recorded
Target identity hub-core wheel/image version, revision, and contract version recorded
Backup Encrypted location and checksum recorded; no secret value logged
Counts Per-table source/import/target counts with zero unexplained delta
Content Order-independent canonical row hashes match after documented transforms
Provenance Source table/ID/revision, import bundle hash, and target record IDs queryable
Contract Static OpenAPI diff has no unexplained removed route/status/schema behavior
Authorization Missing/invalid/revoked deny; allowed roles pass; dependency outage denies
Failure mode Database, policy/auth, and candidate unavailability behavior recorded
Consumers Owned ops-hub and activity-core gates pass against the candidate
Rollback Reverse delta export/import and route-map reversal exercised

An unexplained row, response, authorization, or provenance mismatch fails the slice. A waiver requires a human progress note and a live residual task.

Smoke and change ownership

Surface Owner during absorption
Hub-core unit, contract, conformance, migration, and image tests hub-core
Legacy OpenAPI and deployed-smoke oracle core-hub until archive, then historical fixture in hub-core
ops-hub public/bootstrap gate ops-hub
activity-core resolver and interaction write/readback activity-core
Helm render, policy, secret projection, live verification, traffic map, rollback rapp package (currently rapp-core-hub) + platform owners
Production writer flip, rollback, scale-to-zero, archive operator approval required

Rollback procedure

For a failed candidate slice:

  1. Stop candidate writes for the affected group; do not enable legacy writes yet.
  2. Export the candidate-only delta with IDs and hashes, validate it against the reverse adapter, and import it into the retained legacy schema.
  3. Compare counts/content and run the legacy group smoke.
  4. Route the group to Core Hub, enable only its legacy writer, and confirm the candidate writer remains disabled.
  5. Record the failure correlation ID, data interval, revisions, evidence, and next owner. Do not continue to the next slice.

If candidate data cannot be replayed safely, keep both writers disabled and invoke the operator recovery path. Availability pressure does not authorize discarding acknowledged writes or enabling concurrent writers.

Completion criteria

HUB-WP-0004-T06 is complete when this plan is linked to CORE-WP-0010 and its handoff is recorded. HUB-WP-0004 may then finish; it does not claim production absorption.

CORE-WP-0010 completes only when all slices have evidence, production is on hub-core, the stabilization and rollback gates close, residuals have live owners, the deployment package names the surviving runtime, and Core Hub is archived read-only.