Integrate verified State Hub dispatcher into lifecycle release
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
This commit is contained in:
parent
4898c9b764
commit
c084600082
10 changed files with 361 additions and 16 deletions
|
|
@ -15,7 +15,8 @@ humans and system:serviceaccount:state-hub:state-hub. **Not deployed.**
|
|||
sibling JSON registries. The policy YAML is Helm-rendered from Access Engine's
|
||||
FLEX-WP-0033 candidate values; it is a review artifact, not a second deployment
|
||||
owner. Existing Helm releases must be updated through their owning workflow.
|
||||
All 25 proposed objects passed Kubernetes server dry-run against railiance01.
|
||||
The original 25 objects and the three dispatcher objects passed Kubernetes
|
||||
server dry-run against railiance01.
|
||||
No Secret is included. Dry-run does not test reachability, credential delivery,
|
||||
policy caller identity, backup restoration or a human disposition.
|
||||
|
||||
|
|
@ -46,10 +47,11 @@ contract; neither is presented as a newly enforced control.
|
|||
under its separate live go, with exact identity/path negative proofs and
|
||||
mounted-file refresh/rotation evidence. Only the audit ExternalSecret was
|
||||
present in the live namespace at inspection.
|
||||
2. INFD-WP-0005-T08: State Hub owner supplies production forwarding and separate
|
||||
lifecycle/intake audience token projections. Its current dev fixture is not
|
||||
an activated forwarder. Confirm notification receiver reachability; keep
|
||||
resolution write-back disabled until its owner admission.
|
||||
2. INFD-WP-0005-T08 / STATE-WP-0096: the private dispatcher and separate
|
||||
lifecycle/intake token projections are prepared in dispatcher.candidate.json.
|
||||
Deploy after intake prerequisites. Explicit enqueue requires trusted workload
|
||||
execution; there is no new public delegate endpoint. Confirm notification
|
||||
receiver reachability; keep resolution write-back disabled.
|
||||
3. Refresh live image/config pins and unresolved-entry counts; complete the
|
||||
backup/restore steps below; obtain explicit approval of this concrete rollout.
|
||||
INFD-WP-0007-T07 and CCR-2026-0032 require this distinct go.
|
||||
|
|
@ -98,3 +100,22 @@ broader browser packet/binding profiles remain explicit workplan tasks.
|
|||
The final Access Engine pin is the Forgejo image built from commit d2d0b6f
|
||||
(run https://forgejo.coulomb.social/coulomb/access-engine/actions/runs/316).
|
||||
It supersedes the locally built candidate for the proposed deployment.
|
||||
|
||||
|
||||
## Private State Hub dispatcher continuation
|
||||
|
||||
The dispatcher is a separate Recreate workload with 5m CPU / 24Mi memory requests,
|
||||
one private 1Gi PVC, no Service/Ingress, and the existing state-hub service account.
|
||||
Its only egress is DNS and the exact informed-decision peer on 8081. Projected
|
||||
lifecycle and intake tokens each expire after 600 seconds. The dispatcher reads
|
||||
only the intake token and never binds. Its pod omits app=state-hub, so it is not
|
||||
selected by the existing Hub API Service.
|
||||
|
||||
Image: forgejo.coulomb.social/coulomb/state-hub-memo-dispatch@sha256:410fb6b6c9fd7a7ee0b63d727fd3249fb3ae13943421d6a5ce88b7ad2520bd2f
|
||||
Source: State Hub 4a59b23, built by the dedicated Forgejo workflow.
|
||||
Source integration proves queue/restart/lost-reply handling (one create, replayed
|
||||
receipt), subsequent human acceptance and independent audit delivery. All identity
|
||||
and credential inputs in that test are synthetic. Native acceptance remains open.
|
||||
|
||||
There is no new service-image build for this continuation. The service suite now
|
||||
passes 663 tests (1 skipped); State Hub forwarding/notification tests pass 28.
|
||||
|
|
|
|||
214
deploy/lifecycle/dispatcher.candidate.json
Normal file
214
deploy/lifecycle/dispatcher.candidate.json
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "List",
|
||||
"items": [
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "PersistentVolumeClaim",
|
||||
"metadata": {
|
||||
"name": "state-hub-memo-dispatch",
|
||||
"namespace": "state-hub",
|
||||
"annotations": {
|
||||
"state-hub.coulomb.social/admission": "proposal; STATE-WP-0096 / INFD-WP-0007 approved rollout required"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"accessModes": [
|
||||
"ReadWriteOnce"
|
||||
],
|
||||
"storageClassName": "local-path",
|
||||
"resources": {
|
||||
"requests": {
|
||||
"storage": "1Gi"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"name": "state-hub-memo-dispatch",
|
||||
"namespace": "state-hub",
|
||||
"annotations": {
|
||||
"state-hub.coulomb.social/admission": "proposal; STATE-WP-0096 / INFD-WP-0007 approved rollout required"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"replicas": 1,
|
||||
"strategy": {
|
||||
"type": "Recreate"
|
||||
},
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app.kubernetes.io/name": "state-hub",
|
||||
"app.kubernetes.io/component": "memo-dispatch"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app.kubernetes.io/name": "state-hub",
|
||||
"app.kubernetes.io/component": "memo-dispatch"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"serviceAccountName": "state-hub",
|
||||
"automountServiceAccountToken": false,
|
||||
"terminationGracePeriodSeconds": 15,
|
||||
"securityContext": {
|
||||
"runAsNonRoot": true,
|
||||
"runAsUser": 10001,
|
||||
"runAsGroup": 10001,
|
||||
"fsGroup": 10001,
|
||||
"fsGroupChangePolicy": "OnRootMismatch",
|
||||
"seccompProfile": {
|
||||
"type": "RuntimeDefault"
|
||||
}
|
||||
},
|
||||
"containers": [
|
||||
{
|
||||
"name": "dispatcher",
|
||||
"image": "forgejo.coulomb.social/coulomb/state-hub-memo-dispatch@sha256:410fb6b6c9fd7a7ee0b63d727fd3249fb3ae13943421d6a5ce88b7ad2520bd2f",
|
||||
"securityContext": {
|
||||
"allowPrivilegeEscalation": false,
|
||||
"readOnlyRootFilesystem": true,
|
||||
"capabilities": {
|
||||
"drop": [
|
||||
"ALL"
|
||||
]
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"requests": {
|
||||
"cpu": "5m",
|
||||
"memory": "24Mi"
|
||||
},
|
||||
"limits": {
|
||||
"cpu": "100m",
|
||||
"memory": "96Mi"
|
||||
}
|
||||
},
|
||||
"volumeMounts": [
|
||||
{
|
||||
"name": "queue",
|
||||
"mountPath": "/data"
|
||||
},
|
||||
{
|
||||
"name": "intake-caller",
|
||||
"mountPath": "/var/run/secrets/state-hub/informed-decision",
|
||||
"readOnly": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"volumes": [
|
||||
{
|
||||
"name": "queue",
|
||||
"persistentVolumeClaim": {
|
||||
"claimName": "state-hub-memo-dispatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "intake-caller",
|
||||
"projected": {
|
||||
"defaultMode": 288,
|
||||
"sources": [
|
||||
{
|
||||
"serviceAccountToken": {
|
||||
"audience": "informed-decision-intake",
|
||||
"expirationSeconds": 600,
|
||||
"path": "intake-token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"serviceAccountToken": {
|
||||
"audience": "informed-decision-lifecycle",
|
||||
"expirationSeconds": 600,
|
||||
"path": "lifecycle-token"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "networking.k8s.io/v1",
|
||||
"kind": "NetworkPolicy",
|
||||
"metadata": {
|
||||
"name": "state-hub-memo-dispatch",
|
||||
"namespace": "state-hub",
|
||||
"annotations": {
|
||||
"state-hub.coulomb.social/admission": "proposal; STATE-WP-0096 / INFD-WP-0007 approved rollout required"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"podSelector": {
|
||||
"matchLabels": {
|
||||
"app.kubernetes.io/name": "state-hub",
|
||||
"app.kubernetes.io/component": "memo-dispatch"
|
||||
}
|
||||
},
|
||||
"policyTypes": [
|
||||
"Ingress",
|
||||
"Egress"
|
||||
],
|
||||
"ingress": [],
|
||||
"egress": [
|
||||
{
|
||||
"to": [
|
||||
{
|
||||
"namespaceSelector": {
|
||||
"matchLabels": {
|
||||
"kubernetes.io/metadata.name": "kube-system"
|
||||
}
|
||||
},
|
||||
"podSelector": {
|
||||
"matchLabels": {
|
||||
"k8s-app": "kube-dns"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ports": [
|
||||
{
|
||||
"protocol": "UDP",
|
||||
"port": 53
|
||||
},
|
||||
{
|
||||
"protocol": "TCP",
|
||||
"port": 53
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"to": [
|
||||
{
|
||||
"namespaceSelector": {
|
||||
"matchLabels": {
|
||||
"kubernetes.io/metadata.name": "informed-decision"
|
||||
}
|
||||
},
|
||||
"podSelector": {
|
||||
"matchLabels": {
|
||||
"app.kubernetes.io/name": "informed-decision"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ports": [
|
||||
{
|
||||
"protocol": "TCP",
|
||||
"port": 8081
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"status": "passed",
|
||||
"image_id": "sha256:410fb6b6c9fd7a7ee0b63d727fd3249fb3ae13943421d6a5ce88b7ad2520bd2f",
|
||||
"checks": [
|
||||
"non-root read-only worker starts without external network",
|
||||
"explicit enqueue persists a lifecycle envelope without inventing a Hub relationship",
|
||||
"SIGTERM stops without forced kill",
|
||||
"restart retains the exact pending envelope; missing custody does not drop it",
|
||||
"private queue and database modes enforced"
|
||||
],
|
||||
"fixture_only": true,
|
||||
"external_network": "none",
|
||||
"native_identity_proven": false,
|
||||
"envelope_sha256": "ce0b8e1c6e861fde2da584363b695932bd0c74f923d1688211cc8b3d9a2f8c2e",
|
||||
"cleanup_complete": true
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"image": "forgejo.coulomb.social/coulomb/state-hub-memo-dispatch@sha256:410fb6b6c9fd7a7ee0b63d727fd3249fb3ae13943421d6a5ce88b7ad2520bd2f",
|
||||
"source_commit": "4a59b23",
|
||||
"scanner": "Trivy 0.74.0",
|
||||
"created_at": "2026-10-04T20:38:44.831801093Z",
|
||||
"high": 0,
|
||||
"critical": 0,
|
||||
"scope": "Image vulnerabilities in the scanner database at scan time; not universal security attestation."
|
||||
}
|
||||
|
|
@ -3,11 +3,12 @@
|
|||
"status": "prepared-not-deployed",
|
||||
"server_dry_run": {
|
||||
"cluster": "railiance01",
|
||||
"objects": 25,
|
||||
"objects": 28,
|
||||
"passed": true
|
||||
},
|
||||
"sha256": {
|
||||
"deploy/lifecycle/admission.candidate.json": "e3054aebb2338146b912121198eba0232973574b9f045710abc802e524fe43f6",
|
||||
"deploy/lifecycle/dispatcher.candidate.json": "e64bdaabfc307caf5f65894ce7e37fd66abaaf9328fd35cfd9103e14bbf739c8",
|
||||
"deploy/lifecycle/intake-submitters.json": "58ba5ef87ecfd40bc7f0eaf2beb5e50f0c1f0e47315e79dbfa9e06411a17eba9",
|
||||
"deploy/lifecycle/lifecycle-callers.json": "0b66382a64bdf81ee75d14a70ba3b19290084922a1e0bc8a873af0f039dc7041",
|
||||
"deploy/lifecycle/policy.candidate.yaml": "850d96aa7807b0e81e28278d380543770012a006b5dc3ebb06a5a8f5fcb43c4a",
|
||||
|
|
@ -15,5 +16,12 @@
|
|||
"deploy/lifecycle/review-groups.json": "1d61d2b82230981130936651a7fc4230e06ecaa51416a8a1be2bb2eb6ec4c353",
|
||||
"deploy/lifecycle/review-policy.candidate.yaml": "1f680eb1184c4a26ae2638b7e0829e5df42237ee09550e7554a3e965edbc8674",
|
||||
"deploy/lifecycle/service.candidate.json": "bf36ecb0160bf893a60fe059a3baf27a63668e2bb7b9f17c9582d6de8e842545"
|
||||
},
|
||||
"dispatcher_verification": {
|
||||
"owner_tests_passed": 28,
|
||||
"cross_service_tests_passed": 663,
|
||||
"cross_service_tests_skipped": 1,
|
||||
"installed_container_checks": 5,
|
||||
"high_critical_findings": 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,3 +86,15 @@ No repeated design decision is needed for this accepted boundary. The remaining
|
|||
human action is separate founder approval of the final concrete live packet and
|
||||
named verification/abort attendance, after these technical gaps are closed.
|
||||
No live apply, credential read, rotation, issuer mutation or deployment occurred.
|
||||
|
||||
|
||||
## Runtime acceptance update — 2026-10-04
|
||||
|
||||
The private listener, CA-verified TokenReview, rotating file-backed requester,
|
||||
attribute consumer and durable State Hub dispatch are now composed and tested.
|
||||
The service image remains the scanned 2768bd1 candidate in deploy/lifecycle;
|
||||
State Hub worker is the CI-built 410fb6b candidate. This closes the source/runtime
|
||||
composition gap only. The live CCR remains proposed; its current SHA256 is
|
||||
ab4cd7c15192ebe98e17b0c40a188ae67616c63cc796d9b807adba7c39f2edb3.
|
||||
No new design decision is requested. Exact live delivery approval, attended
|
||||
OpenBao login, native custody/rotation proof and cutover acceptance remain.
|
||||
|
|
|
|||
|
|
@ -56,11 +56,16 @@ live Approval Engine object, checks its binding pin, requester informed-decision
|
|||
and human_control=true before the PDP call. Store guards recheck retained intake
|
||||
metadata at the mutation boundary. A valid-looking UUID/digest alone is not proof.
|
||||
|
||||
State Hub forwarding currently exists as a dev fixture, not a production worker.
|
||||
The selected identity still needs projected lifecycle/intake tokens and an owner
|
||||
runtime integration. State Hub notifications/write-back require admitted receiver
|
||||
routes; `INFD_HUB_WRITEBACK` stays off in this candidate. Pending rows remain
|
||||
explicit. These are live INFD-WP-0005 dependencies, not claimed release evidence.
|
||||
State Hub now has a private explicitly queued dispatcher (STATE-WP-0096), with a
|
||||
separate standard-library-only image and PVC. It uses the selected existing
|
||||
service account and distinct projected lifecycle/intake tokens; its transport
|
||||
reads only the intake token. The Hub public API does not gain a credentialed
|
||||
forwarding route. Queueing is trusted workload execution. New review envelopes
|
||||
are data operations and do not change deployments. The exact lifecycle snapshot
|
||||
id/key is preserved without inventing a Hub relationship when serves is absent.
|
||||
Lost replies replay the same envelope; native-create uncertainty blocks the row.
|
||||
Native deployment/acceptance still remains open. Notification/write-back receiver
|
||||
admission is unchanged; INFD_HUB_WRITEBACK remains off.
|
||||
|
||||
The image migrates schema 2 to schema 5 on open. Existing records, presentations,
|
||||
dispositions, submissions and outbox evidence must survive. Rollback to the old
|
||||
|
|
@ -76,7 +81,8 @@ must be reconciled, never retried or discarded to make rollback convenient.
|
|||
evidence remain with RPF-WP-0051.
|
||||
- FLEX-WP-0033: publish scanned policy artifact; approve exact policy/RBAC/network
|
||||
objects and native caller/subject allow/refusal tests.
|
||||
- INFD-WP-0005: production State Hub forwarding/receiver and token projections.
|
||||
- INFD-WP-0005 / STATE-WP-0096: activate and verify the prepared private State Hub
|
||||
dispatcher and its projected tokens; retain gated resolution write-back.
|
||||
- INFD-WP-0007-T06/T07: backup/restore, exact rollout approval, native human review
|
||||
and independent audit receipts across two decisions and a revision with unchanged
|
||||
release/configuration pins. Browser packet authoring and broader binding profiles
|
||||
|
|
|
|||
|
|
@ -164,13 +164,26 @@ from test_approval_component import component, signing_key
|
|||
from test_durable_component import receiver
|
||||
|
||||
|
||||
def test_actual_engine_human_entry_and_independent_audit_link_to_source_revision(handoff, component, receiver):
|
||||
@pytest.mark.parametrize("via_dispatch", [False, True])
|
||||
def test_actual_engine_human_entry_and_independent_audit_link_to_source_revision(handoff, component, receiver, tmp_path, via_dispatch):
|
||||
import jwt
|
||||
from informed_decision.intake_approval import IntakeApprovalClient
|
||||
from informed_decision.approval_http import ApprovalHTTPClient
|
||||
from informed_decision.review import ReviewController
|
||||
from informed_decision.runtime import AuditPump
|
||||
app, actor, memo, options, intake, calls = handoff
|
||||
if via_dispatch:
|
||||
import os, sys
|
||||
source = os.environ.get('INFD_STATE_HUB_SOURCE')
|
||||
if not source:
|
||||
pytest.skip('State Hub source required for private dispatcher integration')
|
||||
sys.path.insert(0, source)
|
||||
from api.services.memo_dispatch import Dispatcher
|
||||
options = {**options, 'memo_class': 'decision-record'}
|
||||
grants = json.loads(intake.submitters_path.read_text())
|
||||
for row in grants.values():
|
||||
row['memo_classes'] = ['decision-record']
|
||||
intake.submitters_path.write_text(json.dumps(grants))
|
||||
client, engine, transport, human, signing = component
|
||||
groups = json.loads(intake.groups_path.read_text())
|
||||
groups[options['review_group']]['subject'] = human.subject
|
||||
|
|
@ -183,8 +196,31 @@ def test_actual_engine_human_entry_and_independent_audit_link_to_source_revision
|
|||
summary='Responded to research', key='revise')
|
||||
app.publish(actor, memo.id, 2, audience={'subjects': [human.subject], 'groups': []}, key='publish-second')
|
||||
receipt = app.request_review(actor, memo.id, 2, options=options, key='review-second')
|
||||
status, loaded = request(intake, receipt['envelope'])
|
||||
assert status == 201, loaded
|
||||
if via_dispatch:
|
||||
attempts = []
|
||||
now = [time.time()]
|
||||
def send(method, url, body, headers):
|
||||
assert headers['Authorization'] == 'Bearer projected-token'
|
||||
result = request(intake, body)
|
||||
attempts.append(result[0])
|
||||
if len(attempts) == 1:
|
||||
raise OSError('lost reply after native creation')
|
||||
return result
|
||||
worker = Dispatcher(tmp_path / 'private-dispatch', send=send,
|
||||
read_token=lambda: 'projected-token', clock=lambda: now[0])
|
||||
assert worker.enqueue(receipt['envelope']).status == 'pending'
|
||||
assert not attempts
|
||||
assert worker.once().status == 'pending'
|
||||
now[0] += 10
|
||||
worker = Dispatcher(tmp_path / 'private-dispatch', send=send,
|
||||
read_token=lambda: 'projected-token', clock=lambda: now[0])
|
||||
forwarded = worker.once()
|
||||
assert forwarded.status == 'delivered' and attempts == [201, 200]
|
||||
loaded = forwarded.receipt
|
||||
assert worker.once() is None
|
||||
else:
|
||||
status, loaded = request(intake, receipt['envelope'])
|
||||
assert status == 201, loaded
|
||||
controller = ReviewController(app.repository.store, policy(),
|
||||
lambda session: ApprovalHTTPClient('https://approval.test', session, transport=transport), intake_attributes=True)
|
||||
page = controller.open(human, receipt['review_memo_id'])
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ id: INFD-WP-0005-T08
|
|||
status: wait
|
||||
priority: medium
|
||||
blocked_on: message-from:state-hub
|
||||
blocking_reason: "Founder selected system:serviceaccount:state-hub:state-hub; production token projection and runtime forwarding remain, alongside T05/T06"
|
||||
blocking_reason: "STATE-WP-0096 implements private queue/runtime and projected audience proposals; native forwarding acceptance waits on requester custody and approved rollout"
|
||||
state_hub_task_id: "ce2b36b4-ef82-53b7-aee7-e0063855370b"
|
||||
```
|
||||
|
||||
|
|
@ -271,3 +271,14 @@ registries and live runtime remain unchanged. T05/T06 remain waiting on native
|
|||
custody and policy deployment; T08 still needs the State Hub production forwarder,
|
||||
not merely its dev fixture. See docs/specs/LifecycleProductionRuntime.md and
|
||||
FLEX-WP-0033 for exact boundaries.
|
||||
|
||||
|
||||
## Private State Hub runtime delivered in source — 2026-10-04
|
||||
|
||||
STATE-WP-0096 adds the private explicit queue dispatcher, immutable CI image,
|
||||
separate lifecycle/intake token projections, and isolated PVC/network proposal.
|
||||
Cross-service proof covers retained lifecycle envelope forwarding, lost reply
|
||||
following one native approval create, restart, replay receipt, human acceptance
|
||||
and Audit Core delivery. 663 service tests pass (1 skipped), including this path;
|
||||
28 State Hub dispatch/contract/notification tests pass. T08 remains waiting only
|
||||
for approved native delivery/acceptance, not implementation of its runtime.
|
||||
|
|
|
|||
|
|
@ -274,3 +274,15 @@ CCR-2026-0032 / RPF-WP-0051 owns requester delivery; INFD-WP-0005-T08 owns State
|
|||
Hub forwarding/token projections. The exact chosen identity is no longer an open
|
||||
question. Rollback must retain new evidence and must not run a schema-2 reader
|
||||
on the migrated schema-5 database.
|
||||
|
||||
|
||||
## Private dispatcher production continuation — 2026-10-04
|
||||
|
||||
STATE-WP-0096 now supplies the prepared private State Hub worker and audience
|
||||
projections. No credentialed forwarding route is exposed through the unauthenticated
|
||||
Hub API. Fixed a consumer contract mismatch: immutable lifecycle review envelopes
|
||||
may omit serves and retain their review-UUID idempotency key. Integration proves
|
||||
lost-reply/restart recovery without duplicate native approval, followed by human
|
||||
review and independent audit. Validation: 663 passed, 1 skipped; 28 Hub tests.
|
||||
The exact three worker objects are added to deploy/lifecycle. Native credentials,
|
||||
rollout approval and human acceptance remain the next gates; no live mutation.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue