Accept both backup readers and deliver the email-connect pair.
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s

Record founder acceptance on CCR-2026-0036 and CCR-2026-0037, install the reader roles, and admit only the email-connect node delivery. Treat a missing OpenBao token period as non-periodic.

Assistant: grok
Assistant-Session: 01a11cdb-f9fe-7011-947c-936e72007e54
This commit is contained in:
codex 2026-10-09 00:05:35 +02:00
parent d217c806ca
commit 1900e24f80
8 changed files with 252 additions and 21 deletions

View file

@ -4,7 +4,7 @@
"schema_version": 1, "schema_version": 1,
"request_type": "workload-kv-read", "request_type": "workload-kv-read",
"title": "coulomb-social primary archive node-delivery reader", "title": "coulomb-social primary archive node-delivery reader",
"status": "draft", "status": "applied",
"created": "2026-10-08", "created": "2026-10-08",
"updated": "2026-10-08", "updated": "2026-10-08",
"requester": { "requester": {
@ -17,7 +17,22 @@
"platform-owner", "platform-owner",
"coulomb-social-owner" "coulomb-social-owner"
], ],
"comments": [] "comments": [
{
"at": "2026-10-08T12:40:00Z",
"reviewer": "User (platform-owner)",
"decision": "approved",
"comment": "Founder accepted this reader on 2026-10-08. One read policy, ServiceAccount backup-primary-delivery in namespace coulomb-social, audience openbao. Node delivery is not admitted.",
"source_ref": "founder-acceptance-2026-10-08"
},
{
"at": "2026-10-08T12:40:00Z",
"reviewer": "User (coulomb-social-owner)",
"decision": "approved",
"comment": "Founder accepted the coulomb-social reader record. Node delivery of its pair was not requested.",
"source_ref": "founder-acceptance-2026-10-08"
}
]
}, },
"target": { "target": {
"domain": "communication", "domain": "communication",
@ -59,13 +74,13 @@
"coulomb-social" "coulomb-social"
] ]
}, },
"bound_claims_confirmed": false "bound_claims_confirmed": true
} }
}, },
"access_frontdoor": { "access_frontdoor": {
"type": "platform-owner", "type": "platform-owner",
"catalog_id": "coulomb-social-backup-primary-delivery", "catalog_id": "coulomb-social-backup-primary-delivery",
"readiness": "pending-review", "readiness": "applied-pending-verify",
"resolvable": false "resolvable": false
}, },
"delivery": { "delivery": {
@ -88,7 +103,7 @@
"endpoint": "https://s3.nl-ams.scw.cloud", "endpoint": "https://s3.nl-ams.scw.cloud",
"bucket": "railiance-platform-pg-backup", "bucket": "railiance-platform-pg-backup",
"prefix": "platform-pg/application-archives/coulomb-social/", "prefix": "platform-pg/application-archives/coulomb-social/",
"identity": "separate IAM application and key; provider identity not created", "identity": "IAM application coulomb-social-backup-primary 681d00bf-b965-4323-b657-97bfde423511; key only in OpenBao; bucket policy limits the prefix and withholds object delete",
"retained_delete": false "retained_delete": false
}, },
"risk": { "risk": {
@ -113,6 +128,18 @@
"Provider denies sibling/native prefixes, retained object/version deletion and IAM/bucket administration.", "Provider denies sibling/native prefixes, retained object/version deletion and IAM/bucket administration.",
"Draft/unconfirmed/source-changed contract, wrong cluster, symlinks, hard links, insecure modes, changed KV version, unconfirmed token revocation and different existing file refuse delivery." "Draft/unconfirmed/source-changed contract, wrong cluster, symlinks, hard links, insecure modes, changed KV version, unconfirmed token revocation and different existing file refuse delivery."
], ],
"evidence": [
{
"at": "2026-10-08T12:55:00Z",
"actor": "grok",
"kind": "delegated_metadata_apply",
"result": "passed",
"details": [
"Attended platform-admin login wrote workload-kv-read-coulomb-social-backup-primary and auth/kubernetes/role/coulomb-social-backup-primary-workload-kv-read.",
"Readback bound backup-primary-delivery in coulomb-social, one policy, ttl 300, token_max_ttl 300, token_explicit_max_ttl 300, token_num_uses 4, token_no_default_policy true, token_type service. Node delivery remains unadmitted."
]
}
],
"activation_conditions": [ "activation_conditions": [
"Named owner reviews and confirmed native reader bindings, applied policy/role with live positive/negative evidence, provider prefix boundary proof and explicit node execution admission.", "Named owner reviews and confirmed native reader bindings, applied policy/role with live positive/negative evidence, provider prefix boundary proof and explicit node execution admission.",
"Separate platform-owned attended pair provision with initial KV CAS=0, no partial/second single-field PUT; retain only version metadata. Current reader does not create or rotate values.", "Separate platform-owned attended pair provision with initial KV CAS=0, no partial/second single-field PUT; retain only version metadata. Current reader does not create or rotate values.",

View file

@ -4,7 +4,7 @@
"schema_version": 1, "schema_version": 1,
"request_type": "workload-kv-read", "request_type": "workload-kv-read",
"title": "email-connect primary archive node-delivery reader", "title": "email-connect primary archive node-delivery reader",
"status": "draft", "status": "applied",
"created": "2026-10-08", "created": "2026-10-08",
"updated": "2026-10-08", "updated": "2026-10-08",
"requester": { "requester": {
@ -17,7 +17,22 @@
"platform-owner", "platform-owner",
"email-connect-owner" "email-connect-owner"
], ],
"comments": [] "comments": [
{
"at": "2026-10-08T12:40:00Z",
"reviewer": "User (platform-owner)",
"decision": "approved",
"comment": "Founder accepted this reader on 2026-10-08. One read policy, ServiceAccount backup-primary-delivery in namespace email-connect, audience openbao.",
"source_ref": "founder-acceptance-2026-10-08"
},
{
"at": "2026-10-08T12:40:00Z",
"reviewer": "User (email-connect-owner)",
"decision": "approved",
"comment": "Founder accepted email-connect node delivery of this backup pair. Retention, escrow, capture and the timer stay separate.",
"source_ref": "founder-acceptance-2026-10-08"
}
]
}, },
"target": { "target": {
"domain": "communication", "domain": "communication",
@ -59,13 +74,13 @@
"email-connect" "email-connect"
] ]
}, },
"bound_claims_confirmed": false "bound_claims_confirmed": true
} }
}, },
"access_frontdoor": { "access_frontdoor": {
"type": "platform-owner", "type": "platform-owner",
"catalog_id": "email-connect-backup-primary-delivery", "catalog_id": "email-connect-backup-primary-delivery",
"readiness": "pending-review", "readiness": "applied-pending-verify",
"resolvable": false "resolvable": false
}, },
"delivery": { "delivery": {
@ -78,17 +93,17 @@
"directory_mode": "0700", "directory_mode": "0700",
"procedure": "scripts/application_backup_credentials.py", "procedure": "scripts/application_backup_credentials.py",
"admission": { "admission": {
"node_executor_approved": false, "node_executor_approved": true,
"provider_scope_verified": false, "provider_scope_verified": true,
"reader_boundary_verified": false, "reader_boundary_verified": true,
"source_ref": null "source_ref": "backup-reader-boundary-2026-10-08"
} }
}, },
"provider": { "provider": {
"endpoint": "https://s3.nl-ams.scw.cloud", "endpoint": "https://s3.nl-ams.scw.cloud",
"bucket": "railiance-platform-pg-backup", "bucket": "railiance-platform-pg-backup",
"prefix": "platform-pg/application-archives/email-connect/", "prefix": "platform-pg/application-archives/email-connect/",
"identity": "separate IAM application and key; provider identity not created", "identity": "IAM application email-connect-backup-primary 0eb0f958-77a4-4c70-9aa7-14ba46411415; key only in OpenBao; bucket policy limits the prefix and withholds object delete",
"retained_delete": false "retained_delete": false
}, },
"risk": { "risk": {
@ -113,6 +128,28 @@
"Provider denies sibling/native prefixes, retained object/version deletion and IAM/bucket administration.", "Provider denies sibling/native prefixes, retained object/version deletion and IAM/bucket administration.",
"Draft/unconfirmed/source-changed contract, wrong cluster, symlinks, hard links, insecure modes, changed KV version, unconfirmed token revocation and different existing file refuse delivery." "Draft/unconfirmed/source-changed contract, wrong cluster, symlinks, hard links, insecure modes, changed KV version, unconfirmed token revocation and different existing file refuse delivery."
], ],
"evidence": [
{
"at": "2026-10-08T12:55:00Z",
"actor": "grok",
"kind": "delegated_metadata_apply",
"result": "passed",
"details": [
"Attended platform-admin login wrote workload-kv-read-email-connect-backup-primary and auth/kubernetes/role/email-connect-backup-primary-workload-kv-read.",
"Readback bound backup-primary-delivery in email-connect, one policy, ttl 300, token_max_ttl 300, token_explicit_max_ttl 300, token_num_uses 4, token_no_default_policy true, token_type service."
]
},
{
"at": "2026-10-08T13:05:00Z",
"actor": "grok",
"kind": "reader_boundary",
"result": "passed",
"details": [
"Live email-connect ServiceAccount login had one reader policy, 300 second lease, four uses, null identity policies and KV version 2 with both field lengths only.",
"Sibling data, metadata, parent list, unrelated write, the coulomb-social account, and a wrong audience were denied. The token was revoked. Provider prefix proof already denied foreign prefixes and object delete."
]
}
],
"activation_conditions": [ "activation_conditions": [
"Named owner reviews and confirmed native reader bindings, applied policy/role with live positive/negative evidence, provider prefix boundary proof and explicit node execution admission.", "Named owner reviews and confirmed native reader bindings, applied policy/role with live positive/negative evidence, provider prefix boundary proof and explicit node execution admission.",
"Separate platform-owned attended pair provision with initial KV CAS=0, no partial/second single-field PUT; retain only version metadata. Current reader does not create or rotate values.", "Separate platform-owned attended pair provision with initial KV CAS=0, no partial/second single-field PUT; retain only version metadata. Current reader does not create or rotate values.",

View file

@ -239,7 +239,7 @@ def retrieve(native, application, expected_version):
info.get("policies") == [policy] info.get("policies") == [policy]
and not info.get("identity_policies") and not info.get("identity_policies")
and 0 < info.get("ttl", 0) <= 300 and 0 < info.get("ttl", 0) <= 300
and info.get("period", 0) == 0 and (info.get("period") or 0) == 0
and 0 < info.get("explicit_max_ttl", 0) <= 300 and 0 < info.get("explicit_max_ttl", 0) <= 300
and 0 < info.get("num_uses", 0) <= 4, and 0 < info.get("num_uses", 0) <= 4,
"reader_scope_mismatch", "reader_scope_mismatch",

View file

@ -0,0 +1,134 @@
#!/bin/sh
# Install both backup-primary reader policies and Kubernetes auth roles.
# Silent: warden access rejects any child output. Receipts stay on absolute paths.
set -eu
export BAO_ADDR="${BAO_ADDR:-http://127.0.0.1:18200}"
export VAULT_ADDR="${VAULT_ADDR:-http://127.0.0.1:18200}"
receipt_dir=/home/worsch/.local/state/railiance/receipts
umask 077
mkdir -p "$receipt_dir"
stage="$receipt_dir/backup-primary-readers.stage"
: >"$stage"
python3 - "$stage" <<'PY'
import importlib.util
import json
import subprocess
import sys
from pathlib import Path
stage = Path(sys.argv[1])
root = Path("/home/worsch/railiance-platform")
spec = importlib.util.spec_from_file_location(
"credential_change", root / "scripts/credential-change.py"
)
cc = importlib.util.module_from_spec(spec)
spec.loader.exec_module(cc)
refs = [
"CCR-2026-0036-coulomb-social-backup-primary.yaml",
"CCR-2026-0037-email-connect-backup-primary.yaml",
]
safe_role_keys = (
"audience",
"bound_service_account_names",
"bound_service_account_namespaces",
"policies",
"token_explicit_max_ttl",
"token_max_ttl",
"token_no_default_policy",
"token_num_uses",
"token_policies",
"token_type",
"ttl",
)
def run(argv):
result = subprocess.run(argv, cwd=root, capture_output=True)
if result.returncode != 0:
stage.write_text(f"exit {result.returncode} step {argv[1]}\n", encoding="utf-8")
raise SystemExit(result.returncode or 1)
return result.stdout.decode()
receipts = []
for name in refs:
ccr = json.loads((root / "credential-change-requests" / name).read_text(encoding="utf-8"))
if ccr.get("status") != "approved" or ccr["openbao"]["auth"].get("bound_claims_confirmed") is not True:
stage.write_text(f"{ccr.get('id')} not approved for apply\n", encoding="utf-8")
raise SystemExit(44)
blockers = cc.applier_readiness_blockers(ccr)
if blockers:
stage.write_text(ccr["id"] + " blocked\n", encoding="utf-8")
raise SystemExit(45)
openbao = ccr["openbao"]
auth = openbao["auth"]
policy_path = root / openbao["policy_file"]
run(["bao", "policy", "write", openbao["policy_name"], str(policy_path)])
installed = run(["bao", "policy", "read", openbao["policy_name"]])
if cc.normalized_policy_body(installed) != cc.normalized_policy_body(policy_path.read_text(encoding="utf-8")):
stage.write_text(openbao["policy_name"] + " readback differs\n", encoding="utf-8")
raise SystemExit(46)
payload = cc.auth_payload(ccr)
role_file = Path(f"/home/worsch/.local/state/railiance/receipts/{auth['role']}.payload.json")
role_file.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
role_file.chmod(0o600)
run(["bao", "write", f"auth/{auth['mount']}/role/{auth['role']}", f"@{role_file}"])
raw = run(["bao", "read", "-format=json", f"auth/{auth['mount']}/role/{auth['role']}"])
data = json.loads(raw).get("data", {})
observed = {key: data.get(key) for key in safe_role_keys if key in data}
names = data.get("bound_service_account_names")
namespaces = data.get("bound_service_account_namespaces")
policies = data.get("token_policies") or data.get("policies")
audience = data.get("audience") or data.get("audiences")
problems = []
if names != payload["bound_service_account_names"]:
problems.append("names")
if namespaces != payload["bound_service_account_namespaces"]:
problems.append("namespaces")
if policies != payload["policies"].split(","):
problems.append("policies")
if audience not in (payload.get("audience"), [payload.get("audience")]):
problems.append("audience")
if data.get("token_no_default_policy") is not True:
problems.append("no_default")
if data.get("token_num_uses") != 4:
problems.append("uses")
if data.get("token_type") != "service":
problems.append("type")
if data.get("ttl") not in (300, "5m", "300s"):
problems.append("ttl")
if data.get("token_max_ttl") not in (300, "5m", "300s"):
problems.append("max_ttl")
if data.get("token_explicit_max_ttl") not in (300, "5m", "300s"):
problems.append("explicit_max_ttl")
if problems:
stage.write_text(
json.dumps({"role": auth["role"], "problems": problems, "observed": observed}, indent=2)
+ "\n",
encoding="utf-8",
)
raise SystemExit(47)
receipts.append(
{
"id": ccr["id"],
"policy": openbao["policy_name"],
"role": auth["role"],
"policies": policies,
"bound_service_account_names": names,
"bound_service_account_namespaces": namespaces,
"ttl": data.get("ttl"),
"token_max_ttl": data.get("token_max_ttl"),
"token_explicit_max_ttl": data.get("token_explicit_max_ttl"),
"token_num_uses": data.get("token_num_uses"),
"token_no_default_policy": data.get("token_no_default_policy"),
"token_type": data.get("token_type"),
}
)
out = Path("/home/worsch/.local/state/railiance/receipts/backup-primary-readers.json")
out.write_text(json.dumps({"status": "installed", "readers": receipts}, indent=2) + "\n", encoding="utf-8")
out.chmod(0o600)
PY
if [ -s "$stage" ]; then
exit 48
fi
exit 0

View file

@ -166,7 +166,7 @@ def test_atomic_private_delivery_and_identical_resume(application, directory, ca
@pytest.mark.parametrize( @pytest.mark.parametrize(
"change", "change",
["default", "identity", "unbounded", "uses", "version", "destroyed", "fields", "injection"], ["default", "identity", "unbounded", "uses", "period", "version", "destroyed", "fields", "injection"],
) )
def test_scope_and_payload_failures_revoke_without_publishing(change, directory): def test_scope_and_payload_failures_revoke_without_publishing(change, directory):
path, fd = directory path, fd = directory
@ -179,6 +179,8 @@ def test_scope_and_payload_failures_revoke_without_publishing(change, directory)
native.info["data"]["explicit_max_ttl"] = 0 native.info["data"]["explicit_max_ttl"] = 0
elif change == "uses": elif change == "uses":
native.info["data"]["num_uses"] = 0 native.info["data"]["num_uses"] = 0
elif change == "period":
native.info["data"]["period"] = 60
elif change == "version": elif change == "version":
native.record["data"]["metadata"]["version"] = 2 native.record["data"]["metadata"]["version"] = 2
elif change == "destroyed": elif change == "destroyed":
@ -193,6 +195,14 @@ def test_scope_and_payload_failures_revoke_without_publishing(change, directory)
assert not (path / "primary.env").exists() and not list(path.glob("delivery-v*.json")) assert not (path / "primary.env").exists() and not list(path.glob("delivery-v*.json"))
def test_absent_period_still_delivers(directory):
_path, fd = directory
native = FakeNative()
native.info["data"]["period"] = None
receipt = d.deliver(approved(), "source-hash", "coulomb-social", 1, native, fd, os.getuid())
assert receipt["outcome"] == "created"
def test_revocation_failure_does_not_publish_or_disclose(directory, capsys, monkeypatch, tmp_path): def test_revocation_failure_does_not_publish_or_disclose(directory, capsys, monkeypatch, tmp_path):
path, fd = directory path, fd = directory
ccr = approved() ccr = approved()

View file

@ -47,12 +47,13 @@ def identity():
@pytest.mark.parametrize("application", sorted(custody.PROFILES)) @pytest.mark.parametrize("application", sorted(custody.PROFILES))
def test_preview_reads_draft_contract_and_writes_nothing(application, capsys, monkeypatch): def test_preview_reads_recorded_contract_and_writes_nothing(application, capsys, monkeypatch):
monkeypatch.setattr(custody, "provision", lambda *_: pytest.fail("preview provisioned")) monkeypatch.setattr(custody, "provision", lambda *_: pytest.fail("preview provisioned"))
assert custody.main(["--application", application]) == 0 assert custody.main(["--application", application]) == 0
receipt = json.loads(capsys.readouterr().out) receipt = json.loads(capsys.readouterr().out)
recorded = json.loads(custody.ccr_path(application).read_text(encoding="utf-8"))
assert receipt["status"] == "preview" assert receipt["status"] == "preview"
assert receipt["ccr_status"] == "draft" assert receipt["ccr_status"] == recorded["status"]
assert receipt["writer_admitted"] is True assert receipt["writer_admitted"] is True
assert receipt["openbao_write"] is False assert receipt["openbao_write"] is False
assert receipt["provider_keys"] == "not_created" assert receipt["provider_keys"] == "not_created"

View file

@ -28,6 +28,13 @@ def test_owner_yaml_updates_export_without_node_yaml_dependency(application, tmp
ccr = yaml.safe_load(source.read_text()) ccr = yaml.safe_load(source.read_text())
ccr["status"] = "applied" ccr["status"] = "applied"
ccr["openbao"]["auth"]["bound_claims_confirmed"] = True ccr["openbao"]["auth"]["bound_claims_confirmed"] = True
ccr["review"]["comments"] = []
ccr["delivery"]["admission"] = {
"node_executor_approved": False,
"provider_scope_verified": False,
"reader_boundary_verified": False,
"source_ref": None,
}
candidate = tmp_path / "reviewed.yaml" candidate = tmp_path / "reviewed.yaml"
candidate.write_text(yaml.safe_dump(ccr)) candidate.write_text(yaml.safe_dump(ccr))
raw = e.render(candidate, application) raw = e.render(candidate, application)

View file

@ -140,10 +140,10 @@ is not ledger capture/recovery coverage; CSOC-WP-0010-T04/T05 own that residual.
```task ```task
id: RPF-WP-0053-T05 id: RPF-WP-0053-T05
status: wait status: progress
priority: high priority: high
needs_human: true needs_human: true
intervention_note: "EMAIL-WP-0007 has tested capture/recovery tools; platform must admit its separate email-connect primary prefix/caller, retention/capacity, independent receipts and escrow before first live capture/upload or timer activation." intervention_note: "email-connect primary.env is delivered from the accepted reader. Retention, capacity, independent receipts, escrow and the first capture remain open. Coulomb node delivery was not requested."
state_hub_task_id: "3f744ce6-28e4-5496-8d21-7231271a7bc8" state_hub_task_id: "3f744ce6-28e4-5496-8d21-7231271a7bc8"
``` ```
@ -179,7 +179,7 @@ and SMTP/sender/caller admission remain unchanged by source preparation.
```task ```task
id: RPF-WP-0053-T06 id: RPF-WP-0053-T06
status: progress status: done
priority: high priority: high
state_hub_task_id: "ce950b3c-1ca6-5b01-bf2a-d958cafaed25" state_hub_task_id: "ce950b3c-1ca6-5b01-bf2a-d958cafaed25"
``` ```
@ -247,3 +247,18 @@ version or node file was created. Review:
docs/credential-lane-designs/application-backup-pair-custody-2026-10-08.md. docs/credential-lane-designs/application-backup-pair-custody-2026-10-08.md.
T05 stays wait for owner admission of the writer, prefix and escrow. T06 stays T05 stays wait for owner admission of the writer, prefix and escrow. T06 stays
progress until that attended act. progress until that attended act.
## 2026-10-08 — Reader acceptance and email delivery
Founder acceptance is recorded on CCR-2026-0036 and CCR-2026-0037 with both
owner reviews, source ref founder-acceptance-2026-10-08, and confirmed
Kubernetes bindings. Attended platform-admin login installed both read policies
and roles: one policy, backup-primary-delivery, matching namespace, ttl 300,
four uses, no default policy, service token. Both ServiceAccounts exist with
automount disabled. Live proof: each account read only its own version 2 pair
(lengths 20 and 36), sibling read denied, email metadata/list/write denied,
cross-namespace and wrong-audience login denied, tokens revoked. Email delivery
admission is true. Coulomb delivery admission stays false and
/etc/coulomb-social-backup was not created. email-connect primary.env was
created root:root 0600 under /etc/email-connect-backup mode 0700, outcome
created, readback equal, KV version 2. No capture, upload, escrow or timer.