184 lines
11 KiB
Markdown
184 lines
11 KiB
Markdown
# 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 T01–T06 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.
|