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

184 lines
11 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:** accepted for `CORE-WP-0010-T02` / `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 Core Hub record is aligned with the canonical joint plan at
`hub-core/docs/core-hub-absorption-plan.md` and State Hub decision
`12514947-6cc0-42a0-98ca-9aacc9d875b0`. It 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.
## Runtime and database shape
The candidate uses a distinct hub-core-owned schema, provisionally
`hub_runtime`, beside retained legacy schema `core_hub` in the existing
platform PostgreSQL allocation. This does not consume another application
database slot. The target adds its own audit ledger and migration lineage;
Core Hub models and migrations are not imported. Deployment remains owned by
`rapp-core-hub` during dual-run, with final package naming/ownership recorded
before this repository is archived.
## 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, audit
ledger, and migrations, and fails readiness when durable storage or its
authorization dependency 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 |
| --- | --- | --- | --- |
| S0 — candidate foundation | image, API/MCP processes, migrations, durable store/audit, auth adapter, group controls, health/readiness | All entry gates above; all seven source tables representable; no public route | hub-core conformance plus Core Hub contract suite |
| S1 — system/discovery | health/readiness; widget/event/annotation/policy catalogs; compatibility OpenAPI/docs and aliases | Response status, headers, and canonical JSON match; dependency-aware readiness | public probes, ops-hub public gate, activity-core resolver |
| S2 — registry/manifests | hubs, capability manifests/activation, hub registry | IDs and legacy serialization preserved; counts/digests match; activation audited | ops-hub can resolve/update its manifest; reverse delta proven |
| S3 — consumers/credentials | API consumers, key issuance, `/token` | Hashes/prefixes only; auth deny cases and custody separation pass; rate/quota residual explicit | existing ops-hub and activity-core credentials; reverse key-hash delta proven |
| S4 — widgets/interaction | widgets; interaction-event append/list mapped to `port.events.interaction` | Historical IDs/timestamps retained; event families remain separate; projection/reverse replay pass | ops-hub and activity-core correlated append/readback |
| S5 — deferred/console | empty annotation, requirement, decision, deployment and outcome collections; `/console` | Exact empty/error behavior; residual disposition per collection; console has no source-schema reads | operator CLI, protected console, visual smoke |
| S6 — whole-host authority | all `/api/v2`, API/MCP entrypoints and remaining traffic | Final backup/delta has zero unexplained drift; operator approves; seven consecutive stable days before retirement | hub-core conformance and Core Hub, ops-hub, activity-core gates together |
Related calls within a slice move as one traffic group. In particular, public
traffic must not mix old and new authorities inside registry, credential, or
widget/interaction workflows whose identifiers cross resource boundaries.
## 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 `legacy`,
`shadow`, and `candidate`, plus independently observable legacy/candidate
write-group sets. The concrete carrier may use the joint plan's
`HUB_CORE_V2_GROUPS`, `HUB_CORE_V2_WRITE_GROUPS`, and
`CORE_HUB_V2_WRITE_GROUPS` or equivalent Helm values. Configuration must
reject overlapping writer sets, and 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 every production writer change, S6 public authority, and later repository archive |
## Next executable work
After hub-core publishes its completed T01T06 revision, start S0. 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.