# 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 T01–T05 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.