Advance RPF-WP-0021 platform onboarding
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

This commit is contained in:
codex 2026-08-20 23:31:47 +02:00
parent 8caba51801
commit 07ed6217b7
8 changed files with 284 additions and 5 deletions

View file

@ -101,7 +101,7 @@
| task | RPF-WP-0020-T03 | todo | — | workplans/RPF-WP-0020-ccr-schema-drift.md |
| task | RPF-WP-0020-T04 | todo | — | workplans/RPF-WP-0020-ccr-schema-drift.md |
| task | RPF-WP-0021-T01 | done | — | workplans/RPF-WP-0021-core-hub-platform-onboarding.md |
| task | RPF-WP-0021-T02 | progress | — | workplans/RPF-WP-0021-core-hub-platform-onboarding.md |
| task | RPF-WP-0021-T02 | done | — | workplans/RPF-WP-0021-core-hub-platform-onboarding.md |
| task | RPF-WP-0021-T03 | progress | — | workplans/RPF-WP-0021-core-hub-platform-onboarding.md |
| task | RPF-WP-0021-T04 | progress | — | workplans/RPF-WP-0021-core-hub-platform-onboarding.md |
| task | RPF-WP-0021-T05 | wait | — | workplans/RPF-WP-0021-core-hub-platform-onboarding.md |

View file

@ -0,0 +1,60 @@
# RPF-WP-0021: three disjoint materialized Secrets for the Core Hub package.
# Apply only after the static CCR is approved/seeded and the database roles
# plus exact-scope renewable ESO token exist.
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: core-hub-api-token
namespace: core-hub
spec:
refreshInterval: 5m
secretStoreRef:
kind: ClusterSecretStore
name: openbao-core-hub-runtime
target:
name: core-hub-api-token
creationPolicy: Owner
deletionPolicy: Retain
data:
- secretKey: CORE_HUB_API_TOKEN
remoteRef:
key: workloads/core-hub/runtime
property: CORE_HUB_API_TOKEN
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: core-hub-runtime-database
namespace: core-hub
spec:
refreshInterval: 5m
secretStoreRef:
kind: ClusterSecretStore
name: openbao-core-hub-database
target:
name: core-hub-runtime-database
creationPolicy: Owner
deletionPolicy: Retain
# A single extract mints one lease and keeps its username/password paired.
dataFrom:
- extract:
key: creds/core-hub-runtime
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: core-hub-migration-database
namespace: core-hub
spec:
refreshInterval: 5m
secretStoreRef:
kind: ClusterSecretStore
name: openbao-core-hub-database
target:
name: core-hub-migration-database
creationPolicy: Owner
deletionPolicy: Retain
dataFrom:
- extract:
key: creds/core-hub-migration

View file

@ -0,0 +1,25 @@
# Railiance01-local database-engine store for RPF-WP-0021. The renewable
# orphan parent token is streamed into the referenced Secret by an attended
# platform operation; its value never enters this repository.
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: openbao-core-hub-database
labels:
app.kubernetes.io/part-of: railiance-gitops
railiance-platform/component: external-secrets
app.kubernetes.io/name: core-hub
spec:
provider:
vault:
server: http://openbao.openbao.svc:8200
path: database
version: v1
auth:
tokenSecretRef:
name: openbao-core-hub-eso-token
namespace: external-secrets
key: token
conditions:
- namespaces:
- core-hub

View file

@ -0,0 +1,67 @@
# Core Hub PostgreSQL capacity admission — 2026-08-20
Workplan: `RPF-WP-0021-T02`
Target: railiance01 `databases/platform-pg`
Mode: bounded live occupancy probe; no application data or credential value
was read or recorded.
## Question
Can Core Hub occupy the fourth and final declaration slot on `platform-pg`
without raising the current 1Gi pod limit or exceeding the explicit
100-connection aggregate?
The four declarations are `audit-core`, `tenant-engine`, `core-hub`, and the
counted isolation probe. Each workload declaration reserves 12 runtime plus
two migration connections; the probe's smaller allowance is deliberately
overstated here, making 56 a conservative declared-pool occupancy.
## Method
A temporary namespace labeled as a `platform-pg` client received the existing
bootstrap Secret by namespace-to-namespace projection without printing its
values. A separate client pod opened 56 sessions in four named groups of 14.
This kept client-process memory outside the database pod. The run then repeated
the same 56-session occupancy with 13 idle and one bounded active sort query
per group. The namespace and copied Secret were deleted after the run.
The active query materialized and sorted 100,000 generated integers, then held
the backend briefly for observation. It did not access an application table.
## Observations
| Signal | Before | Declared occupancy / observed peak |
| --- | ---: | ---: |
| Probe sessions | 0 | 56 (14 in each named group) |
| All PostgreSQL sessions | 9 | 65 |
| Active / idle sessions | 1 / 2 | 5 / 54 in the mixed run |
| PostgreSQL pod memory | 89Mi | **174Mi maximum observed** |
| PostgreSQL pod CPU | 7m | 82m during connection startup |
| Node memory | 6,210Mi / 16Gi | 6,342Mi / 16Gi (39%) |
| Node CPU | 470m | 2,002m transient startup peak |
The 85Mi database-pod increase across 56 opened backends is approximately
1.52Mi per connection at declared pool occupancy. That replaces ADR-0004's
unmeasured 10Mi literature estimate for idle/backend occupancy on this live
instance; it does not claim every query operator is limited to that figure.
The earlier quiet preflight observed 362Mi. Conservatively adding the entire
measured connection delta to that higher historical sample gives 447Mi, under
44% of the 1Gi limit. The four bounded active queries did not exceed the
174Mi maximum already observed during connection startup.
## Decision
Core Hub is accepted as the fourth declaration on `platform-pg` without a
memory-limit increase. It reaches the declared ceiling and does not authorize
a fifth declaration. The renderer must continue to reject any additional
consumer on this cell; the named overflow remains `platform-pg-2`.
This is capacity admission evidence, not a latency or workload-peak guarantee.
Core Hub is latency-critical beside batch `audit-core`, and community
PostgreSQL provides no service-class priority. The private shadow run must
therefore capture application health, query latency, pod memory, and backend
counts; material regression is a placement trigger, not a reason to reinterpret
this probe as stronger evidence.

View file

@ -0,0 +1,42 @@
# Core Hub private-shadow preflight — 2026-08-20
This records non-secret source/target metadata gathered for
`RPF-WP-0021-T05`. It is preparation only: no source data was dumped or
restored because the governed credential projections under T04 are not yet
live.
## Target admission
At `2026-08-20T21:31:13Z` on railiance01:
- namespace `core-hub` was Active and carried
`railiance.io/postgres-client=platform-pg` plus the package identity labels;
- CNPG `Database/core-hub` reported `status.applied=true` against
`platform-pg`, with reclaim policy `retain` and owner `core_hub_owner`;
- the `core_hub` database existed but intentionally contained no application
tables before the rehearsal restore; and
- the KV store, database store and three ExternalSecret manifests all passed
Kubernetes server-side dry-run after namespace creation.
## Source snapshot inventory
A read-only query ran through the existing `coulombcore` SSH route against
`apps-pg/core_hub`. No Secret or connection value was read. The live source
writer remained enabled, so these counts are a rehearsal baseline and must be
captured again with the eventual consistent dump.
| Object | Rows |
| --- | ---: |
| `public.alembic_version` | 1 |
| `public.api_consumers` | 3 |
| `public.api_keys` | 10 |
| `public.hub_capability_manifests` | 2 |
| `public.hubs` | 2 |
| `public.interaction_events` | 3 |
| `public.migration_runs` | 2 |
| `public.widgets` | 16 |
The source Alembic revision was `20260627_0002`. No user-defined sequence was
reported. The table set and counts are small enough for a bounded logical-copy
rehearsal, but the restore remains gated on T04 so that migration and runtime
checks use the intended distinct dynamic leases rather than a bootstrap login.

View file

@ -0,0 +1,11 @@
# Exact-scope parent policy for the renewable ESO token that projects Core Hub
# PostgreSQL leases. Static API-token custody uses the separate
# workload-kv-read-core-hub-runtime policy and Kubernetes-auth store.
path "database/creds/core-hub-runtime" {
capabilities = ["read"]
}
path "database/creds/core-hub-migration" {
capabilities = ["read"]
}

View file

@ -104,6 +104,48 @@ class CredentialChangeTests(unittest.TestCase):
self.assertEqual(store["spec"]["provider"]["vault"]["path"], "platform")
self.assertEqual(store["spec"]["conditions"][0]["namespaces"], ["core-hub"])
database_policy = (
REPO_DIR / "openbao/policies/external-secrets-core-hub-database.hcl"
).read_text()
self.assertIn('path "database/creds/core-hub-runtime"', database_policy)
self.assertIn('path "database/creds/core-hub-migration"', database_policy)
self.assertNotIn("platform/data/", database_policy)
database_store = credential_change.load_yaml(
REPO_DIR
/ "argocd/platform-addons/openbao-secretstore/openbao-core-hub-database.clustersecretstore.yaml"
)
self.assertEqual(database_store["spec"]["provider"]["vault"]["path"], "database")
self.assertEqual(database_store["spec"]["provider"]["vault"]["version"], "v1")
self.assertEqual(database_store["spec"]["conditions"][0]["namespaces"], ["core-hub"])
with (
REPO_DIR
/ "argocd/platform-addons/openbao-secretstore/core-hub.externalsecrets.yaml"
).open() as stream:
external_secrets = list(credential_change.yaml.safe_load_all(stream))
self.assertEqual(
{item["metadata"]["name"] for item in external_secrets},
{
"core-hub-api-token",
"core-hub-runtime-database",
"core-hub-migration-database",
},
)
for item in external_secrets:
self.assertEqual(item["metadata"]["namespace"], "core-hub")
self.assertEqual(item["spec"]["refreshInterval"], "5m")
runtime = next(
item for item in external_secrets
if item["metadata"]["name"] == "core-hub-runtime-database"
)
migration = next(
item for item in external_secrets
if item["metadata"]["name"] == "core-hub-migration-database"
)
self.assertEqual(runtime["spec"]["dataFrom"][0]["extract"]["key"], "creds/core-hub-runtime")
self.assertEqual(migration["spec"]["dataFrom"][0]["extract"]["key"], "creds/core-hub-migration")
def test_target_rapp_rejects_non_slug(self) -> None:
path = self.unapproved_ccr()
data = credential_change.load_yaml(path)

View file

@ -108,7 +108,7 @@ State Hub; workplan UUID assignment remains with the production registrar.
```task
id: RPF-WP-0021-T02
status: progress
status: done
priority: high
```
@ -125,8 +125,7 @@ baseline. The untracked `consumers/issue-core.yaml` is not a real consumer —
`ISSUE-WP-0007` explicitly removed Postgres from that package — and must not
silently consume a slot.
Live apply, database role creation, namespace labeling, and OpenBao database
role configuration remain attended operations after declaration review.
OpenBao database role configuration remains an attended operation under T04.
Read-only preflight 2026-08-20: `platform-pg-1` used 9m CPU and 362Mi memory
against a 1Gi limit, with only `audit_core` and operator sessions present;
@ -134,6 +133,23 @@ against a 1Gi limit, with only `audit_core` and operator sessions present;
does not satisfy ADR-0004's representative multi-consumer peak measurement.
Evidence: `docs/evidence/core-hub-postgres-capacity-preflight-2026-08-20.md`.
Capacity admission completed 2026-08-20. A bounded live probe opened 56
sessions in four declaration groups and repeated the occupancy with one active
bounded sort per group. `platform-pg` peaked at 174Mi/1Gi; the 85Mi increase is
about 1.52Mi per opened backend. Even adding that delta to the earlier 362Mi
sample yields 447Mi, under 44% of the limit. Core Hub is accepted as the fourth
and final declaration without raising the pod limit. Evidence:
`docs/evidence/core-hub-postgres-capacity-admission-2026-08-20.md`.
Completed 2026-08-20. `rapp-postgres` commit `4f316cc` accepts and publishes
the Core Hub declaration as the fourth `platform-pg` consumer. All 31 package
tests and consumer rendering passed; the railiance01 server-side dry-run
accepted the generated resource. The idempotent package provisioner then
created `core_hub`, its `core_hub` schema, distinct NOLOGIN owner/migration/
runtime roles and bounded role settings. CNPG `Database/core-hub` reports
`status.applied=true`, targets `platform-pg`, retains the database on resource
deletion, and names `core_hub_owner` as owner.
## T03 — Correct the runtime credential delivery contract
```task
@ -207,6 +223,16 @@ high-risk boundary to deny direct data reads on the new path. Database-engine
policy/store manifests wait for the accepted Postgres declaration and the T03
consumer correction.
Progress 2026-08-20 after T02 admission: added an exact two-path database ESO
policy, a namespace-limited `openbao-core-hub-database` store, and three
disjoint ExternalSecrets. Runtime and migration each use one `dataFrom.extract`
so a username/password pair comes from one lease; both refresh every five
minutes. The database store and all projections passed railiance01 server-side
dry-run. The package namespace is live with the reviewed `platform-pg` client
label. Live role/store activation correctly stopped because the current
OpenBao session receives 403 and CCR-2026-0013 is still proposed; no authority
or placeholder Secret was improvised.
## T05 — Support the private shadow restore and verification
```task
@ -224,11 +250,17 @@ the private endpoint.
This task ends with repeatable shadow evidence. It does not flip
`hub.coulomb.social`, freeze production writes, or remove CoulombCore.
Preflight 2026-08-20: the source has Alembic revision `20260627_0002`, eight
public tables and 39 total rows. The admitted target intentionally has no
application tables before restore. No dump or restore was attempted ahead of
the T04 credential gate. Evidence:
`docs/evidence/core-hub-shadow-preflight-2026-08-20.md`.
## Acceptance
- [x] Private Forgejo repo exists, validated commit `4403c9b` is preserved,
and the registered workplan prefix is consistent.
- [ ] Core Hub is an accepted, capacity-reviewed `rapp-postgres` consumer.
- [x] Core Hub is an accepted, capacity-reviewed `rapp-postgres` consumer.
- [ ] Static API-token and dynamic database credentials have distinct,
least-privilege delivery paths.
- [ ] Database credential rotation cannot leave a running pod using an expired