core-hub/docs/specs/runtime-absorption-plan.md
tegwick 4a9df801c3
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / pytest-smoke (push) Failing after 3s
docs: define hub-core absorption plan
2026-08-21 10:57:13 +02:00

168 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Core Hub to hub-core absorption plan
**Status:** recorded for `CORE-WP-0010-T02`; joint artifact for `HUB-WP-0004-T06`
**Reviewed:** 2026-08-21
**Strategy:** route-group migration with Core Hub retained as rollback; no
client dual-write and no big-bang cutover
This plan turns the inventory in `runtime-absorption-inventory.md` into ordered,
testable migration slices. Hub-core owns the surviving runtime selected by its
ADR-0001. Core Hub remains the public `/api/v2` authority until each route group
passes its own data, contract, consumer, and rollback gates.
## Decisions
1. **One target runtime.** Hub-core ships the importable wheel and primary OCI
image. Core Hub is a temporary source and rollback runtime, not a permanent
host for hub-core.
2. **Internal candidate first.** A hub-core candidate deployment receives no
public traffic until it has a published source revision, immutable image
digest, durable PostgreSQL backend, migrations, compatible authentication,
and green readiness.
3. **Route groups, not percentages.** Traffic moves by coherent API group.
Random percentage canaries would send related bootstrap calls to different
authorities and are not permitted.
4. **One writer per route group.** Read-only requests may be mirrored for
comparison. Production clients never dual-write. Write cutover uses a short
drain, final delta replay, route switch, and one active writer.
5. **Isolated metadata during dual-run.** Core Hub keeps the `core_hub` schema;
hub-core uses its own migration lineage and schema. Cross-importing either
service's SQLAlchemy metadata is prohibited.
6. **Stable identity and safe key migration.** Durable IDs are preserved.
API-key prefixes, hashes, scopes, and status may move; raw bearer tokens or
one-time full keys must never enter migration bundles or evidence.
7. **Archive last.** Core Hub stays deployable until the final stabilization
gate, reverse-delta requirement, and residual ownership record are closed.
## Entry gates
Absorption implementation does not begin until all of these are evidenced:
- the completed `HUB-WP-0004-T01``T05` work is committed and pushed;
- a hub-core wheel and OCI image identify the same revision and version;
- `rapp-core-hub` can deploy a second, cluster-internal candidate without
changing `hub.coulomb.social`;
- hub-core has a production PostgreSQL `PortStore`/compatibility store and
fails readiness when durable storage is unavailable;
- the existing operator token and copied API-key hashes authenticate through
the candidate without exposing credential values;
- Core Hub export, hub-core import, row-count, canonical-digest, and delta
replay commands are repeatable and record non-secret migration metadata;
- the hub-core conformance profile and Core Hub compatibility tests pass
against the candidate.
The current in-memory hub-core backend is a conformance implementation, not an
absorption target. Its deliberate production-readiness failure is a useful
gate and must not be bypassed.
## Ordered slices
| Slice | Surface | Move condition | Consumer gate |
| --- | --- | --- | --- |
| 0 — candidate foundation | image, API/MCP processes, migrations, durable store, auth adapter, health/readiness | All entry gates above; no public route | hub-core conformance plus Core Hub contract suite |
| 1 — stateless compatibility | widget/event/annotation/policy catalogs; empty annotation, requirement, decision, deployment, and outcome collections; compatibility OpenAPI/docs | Response status, headers, and canonical JSON match; unprefixed OpenAPI aliases retained | unauthenticated catalog and OpenAPI probes |
| 2 — protected bootstrap | hubs, capability manifests and activation, API consumers/key issuance, widgets, hub registry | IDs/FKs preserved; source and target counts/digests match; copied key hashes authenticate; isolated create/read fixture passes | complete ops-hub bootstrap/gate probe |
| 3 — interaction evidence | interaction-event append/list and mapping to `port.events.interaction` | Historical IDs/timestamps retained; adapter mapping is reversible; append/readback fixture passes | activity-core resolver and evidence-sink probe |
| 4 — operator surface | `/console`, readiness summary, compatibility redirects and remaining aliases | Console reads target projections; no source-only API calls remain | operator CLI, protected console, visual smoke |
| 5 — public authority | all `/api/v2`, health/readiness, API/MCP entrypoints | Earlier slices stable; rollback and residual-owner evidence current | ops-hub and activity-core gates together |
Slice 2 is intentionally atomic at the traffic boundary. The ops-hub bootstrap
creates records whose identifiers cross hubs, manifests, consumers, keys, and
widgets; splitting those calls across two authorities would manufacture
referential drift. Its code and data migration may still land in smaller
reviewable commits before the route group switches.
## Compatibility adapters
Hub-core's named ports are not themselves the legacy `/api/v2` contract. The
target needs an explicit compatibility composition layer:
- hub and manifest resources map to `port.registry` while preserving legacy
IDs, body fields, activation behavior, pagination envelope, and hub-registry
projection;
- interaction events map to `port.events.interaction` with a stable
correlation ID, original event ID, widget reference, view context, event
type, metadata, and occurrence/recording timestamps;
- widgets and API consumers/keys remain compatibility resources until a named
replacement port is accepted; they must not be forced into registry records;
- health/readiness retain the response shape expected by deployed Core Hub
smoke tooling until consumers adopt the hub-core-native shape;
- `/api/v2/openapi.json`, YAML/docs discovery, and required unprefixed aliases
remain versioned compatibility artifacts through final cutover.
## Data movement and comparison
For each durable slice:
1. Export source records in dependency order with a schema version, source
revision, high-water mark, and bundle SHA-256.
2. Validate without writing; reject unknown fields, missing parents, raw secret
shapes, duplicate identities, or a lower target schema version.
3. Upsert into the target by stable ID. Re-running the same bundle must be
idempotent and recorded as such.
4. Compare table/resource counts, sorted identity sets, and canonical row
digests. Timestamp formatting and JSON key order are normalized before
hashing; semantic field differences are not ignored.
5. Mirror only safe GET requests and compare status plus canonical response.
Exercise writes with isolated migration fixtures, never by duplicating a
production client request.
6. At route cutover, drain writes for that group, capture the final high-water
delta, import/compare it, switch the route group, and enable target writes.
Migration evidence may contain IDs, counts, hashes, versions, timestamps, and
pass/fail results. It must not contain raw API keys, bearer tokens, database
credentials, Authorization headers, or copied Kubernetes Secrets.
## Traffic controls and rollback
`rapp-core-hub` owns one logical switch per slice with the states `source`,
`shadow-read`, and `target`. The concrete gateway or Helm key names are a
deployment implementation detail, but every rendered release must reveal the
selected state as non-secret evidence.
Rollback remains possible while a slice is target-owned:
1. stop new target writes for the affected route group;
2. export and verify the target-only delta by stable ID;
3. replay that delta to Core Hub when the old source must become writable;
4. compare counts/digests and run the consumer gate;
5. switch the whole route group back to `source`.
If reverse replay is not implemented for a write slice, rollback is limited to
the period before target writes are enabled. That limitation blocks public
cutover and must be visible in the evidence report.
## Required evidence per slice
- hub-core source revision, package version, and OCI digest;
- Core Hub source revision and deployed Helm revision;
- source/target schema versions and migration bundle hash;
- before/after counts, identity-set comparison, and canonical digest result;
- compatibility/OpenAPI diff result;
- auth positive and negative checks without credential disclosure;
- named consumer smoke reports and correlation IDs;
- selected route state, switch time, observer, and rollback result or reason it
was not invoked.
Any schema mismatch, unexplained count/digest drift, secret-shaped evidence,
auth broadening, consumer failure, or non-green readiness stops the slice and
keeps public authority on Core Hub.
## Ownership
| Owner | Responsibility |
| --- | --- |
| hub-core | compatibility modules, durable store/migrations, import/delta/reverse-delta, conformance |
| core-hub | source export, legacy fixtures/OpenAPI, canonical response comparison, rollback service |
| rapp-core-hub | candidate deployment, immutable image pin, route switches, rollout/rollback evidence |
| railiance-platform / rapp-postgres | PostgreSQL, credential delivery, backup/restore readiness |
| ops-hub | bootstrap and registry consumer gate |
| activity-core | interaction resolver, append, and readback gate |
| operator | approval for Slice 5 public-authority cutover and later repository archive |
## Next executable work
After hub-core publishes its completed T01T05 revision, start Slice 0. The
first implementation commit should add the durable PostgreSQL boundary and
legacy auth/health compatibility in hub-core, plus a candidate deployment mode
in `rapp-core-hub`. It must not change public DNS or ingress.