Accept both backup readers and deliver the email-connect pair.
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:
parent
d217c806ca
commit
1900e24f80
8 changed files with 252 additions and 21 deletions
|
|
@ -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.",
|
||||||
|
|
|
||||||
|
|
@ -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.",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
134
scripts/install-backup-primary-readers.sh
Normal file
134
scripts/install-backup-primary-readers.sh
Normal 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
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue