Implement attended root custody admission and crash-safe publisher
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1029d-2c93-7500-a0e0-e3e089668b29
This commit is contained in:
parent
3ee800a0fe
commit
fa958cc051
5 changed files with 490 additions and 38 deletions
|
|
@ -6,7 +6,7 @@ Scoped made decision receipt: `c2cff929-91b9-4018-bfd6-fa04f7da8280`.
|
|||
This selects railiance-platform and the exact source below. Identity admission,
|
||||
attended live provisioning, custody provenance and live readiness remain pending.
|
||||
NetKingdom canon remains authoritative; reviewed baseline is net-kingdom commit
|
||||
`de4c4f84f5043b97a205f1a72a26e77f8750d628`. Deployments must pin the validator
|
||||
`db319335b27b61b855382ee5af5d690f06eeb010`. Deployments must pin the validator
|
||||
and its schema together, and reconcile amendments before publication.
|
||||
|
||||
## Approved source and transport
|
||||
|
|
@ -16,10 +16,12 @@ and its schema together, and reconcile amendments before publication.
|
|||
- `record_id`: `nk-custody:platform-root`; `owner_system`: `net-kingdom-custody`.
|
||||
- API response unwrap: `data.data.document` is the complete canon document.
|
||||
`data.metadata.version` is KV concurrency state, distinct from canon revision.
|
||||
- Reader: user-engine only. Its repository currently has no deployed workload
|
||||
namespace/ServiceAccount, so no Kubernetes identity binding is claimed.
|
||||
User-engine must name its real runtime identity and an existing supported auth
|
||||
route. Platform admits it only after review. Reader policy is exact-path read;
|
||||
- Reader: owner manifests identify namespace `user-engine`, ServiceAccount
|
||||
`user-engine`. Role `user-engine-root-custody-reader` on `auth/kubernetes`
|
||||
binds exactly those values and audience `openbao`. Tokens last at most 60s,
|
||||
with no default policy. Dedicated projected JWT is owned by rapp-user-engine;
|
||||
the existing flex-auth JWT and ESO AppRole are not reused. Live admission
|
||||
remains unexecuted. Reader policy is exact-path read;
|
||||
no list, sibling read or write. Never distribute an attended writer token.
|
||||
- Writer: separate human custody lane, bounded attended MFA operator login,
|
||||
reviewed exact-path policy. This packet does not grant a credential or role.
|
||||
|
|
@ -37,26 +39,64 @@ refuses missing existing baselines, same-revision changes and rollback. CAS=0
|
|||
is bootstrap-only, CAS=current-version updates; a CAS conflict requires a fresh
|
||||
owner read and revalidation, never blind retry. It never prints documents.
|
||||
|
||||
The attended executor still needs implementation after actual reader/writer
|
||||
identity confirmation: suppress output, read document and KV version together,
|
||||
verify retained high-water mark, call preparation helper, write one complete payload, read back
|
||||
and compare, and emit non-secret revision/digest receipts. An unvalidated direct
|
||||
writer bypass is not an accepted delivery path. Policies alone cannot enforce
|
||||
the human ceremony, validator or global revision monotonicity.
|
||||
The attended executor is implemented in `scripts/root_grant_attended.py`:
|
||||
|
||||
## Exact later live approval packet
|
||||
- `provision` checks existing Kubernetes auth and active audit, admits only the
|
||||
exact reviewed reader policy/role, enables CAS on this key and verifies all
|
||||
readbacks. Differing existing policies/roles are refused without overwrite.
|
||||
- `publish` reads current document and KV version together, validates pinned
|
||||
canon and the persisted high-water baseline, fsyncs a pending journal before
|
||||
CAS write, verifies exact readback, retains immutable local history and
|
||||
atomically advances state. Exact replay creates no new version. Ambiguous
|
||||
writes recover only after exact matching owner readback and durable history.
|
||||
- `restore-check` requires the independently retained publisher baseline to
|
||||
match the restored source exactly. Missing/different baseline never resets
|
||||
authority or permits rollback. Off-host history backup is still an execution
|
||||
dependency; local files alone cannot prove safe disaster restoration.
|
||||
- The silent child requires the existing warden attended-login envelope and
|
||||
exact loopback OpenBao tunnel. No raw process response/error is emitted.
|
||||
Every invocation requires a new receipt path: old success receipts are refused.
|
||||
|
||||
After real consumer identity confirmation and final source preparation, request
|
||||
one explicit attended approval naming the reviewed platform and NetKingdom
|
||||
commits, exact mount/path/record ID, real reader auth binding, separate writer
|
||||
binding, audit owner, abort operator, window and provenance reference. Its
|
||||
bounded actions are: inspect existing mount and identity without values;
|
||||
configure exact-path admission and KV per-key CAS requirement; publish a
|
||||
human-reviewed initial document only from real ceremony evidence (or an
|
||||
explicitly approved empty baseline); verify authenticated reader success,
|
||||
anonymous/wrong-identity/parent-list/sibling/write denial and failed-read behavior;
|
||||
return only source ID, revision, content digest, actor/time/provenance references
|
||||
and pass/fail checks. No synthetic fixture can seed the authoritative record.
|
||||
## Authorized attended execution packet
|
||||
|
||||
Founder approval to "go forward" authorizes continued implementation and bounded
|
||||
provisioning of the approved design. No additional generic design approval is
|
||||
requested. Attended OIDC/MFA must still run in the founder's own terminal:
|
||||
|
||||
```bash
|
||||
BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 \
|
||||
warden access openbao-platform-admin-login --exec -- \
|
||||
/home/worsch/railiance-platform/scripts/root-grant-attended.sh provision \
|
||||
--receipt /tmp/platform-root-admission-UNIQUE-RUN.json
|
||||
```
|
||||
|
||||
Replace UNIQUE-RUN with a fresh run identifier. The child is silent. Inspect the
|
||||
new JSON receipt and warden's printed result; a shell exit code or previous
|
||||
receipt cannot establish success. Provisioning does not create a custody grant.
|
||||
Existing operator platform-admin identity provides the human writer lane;
|
||||
this packet does not create another privileged identity or reuse its token in
|
||||
user-engine. Deployment/network policy/consumer activation belongs to
|
||||
rapp-user-engine and follows source image publication and admission receipts.
|
||||
|
||||
Publication requires an owner state directory outside the checkout, off-host
|
||||
history backup, a bounded non-secret initial document and ceremony reference.
|
||||
Do not infer a custodian from the legacy planned roster or LLDAP account. Real
|
||||
bootstrap requires the human's exact issuer/subject and signed ceremony; an
|
||||
empty initial record can be used only if chosen by the founder. Example child
|
||||
arguments after those inputs exist:
|
||||
|
||||
```text
|
||||
publish --candidate /owner/candidate.json --state /owner/history/baseline.json
|
||||
--provenance-ref <human-ceremony-or-approved-empty-baseline-reference>
|
||||
--net-kingdom /home/worsch/net-kingdom
|
||||
--validator-digest 9156e399c8d92a45b817bfc3738c89843487ce57e1718ed747f804f95cc719bb
|
||||
--schema-digest bb6cd6da5dc48eddcf118ae78c2cacfafd5f860098fd8a8368fe6bc845776d07
|
||||
--receipt /tmp/platform-root-publish-UNIQUE-RUN.json
|
||||
```
|
||||
|
||||
Run via the same attended wrapper. `restore-check` takes the same source pins
|
||||
and state but no candidate or provenance argument. Source digests enforce the
|
||||
reviewed NetKingdom validator and schema without importing an unreviewed path.
|
||||
|
||||
Acceptance also needs revocation/supersession exercise and rollback/restore
|
||||
receipts. Exercise real grant changes only under their own custody ceremony
|
||||
|
|
|
|||
3
scripts/root-grant-attended.sh
Executable file
3
scripts/root-grant-attended.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
exec python3 "$(dirname "$0")/root_grant_attended.py" "$@" >/dev/null 2>&1
|
||||
255
scripts/root_grant_attended.py
Normal file
255
scripts/root_grant_attended.py
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Silent attended OpenBao admission/publisher; never emits server responses."""
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import fcntl
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from root_grant_publish import MAX_BYTES, RECORD_ID, prepare_payload
|
||||
from openbao_platform_admin_check import require_attended
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PATH = "platform/data/custody/net-kingdom/platform-root"
|
||||
META = "platform/metadata/custody/net-kingdom/platform-root"
|
||||
ROLE_PATH = "auth/kubernetes/role/user-engine-root-custody-reader"
|
||||
POLICY = "user-engine-root-custody-reader"
|
||||
ROLE = {"bound_service_account_names": ["user-engine"],
|
||||
"bound_service_account_namespaces": ["user-engine"], "audience": "openbao",
|
||||
"token_policies": [POLICY], "token_no_default_policy": True,
|
||||
"token_ttl": 60, "token_max_ttl": 60, "token_explicit_max_ttl": 60}
|
||||
|
||||
|
||||
class Refused(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def digest(document):
|
||||
return hashlib.sha256(json.dumps(document, sort_keys=True, separators=(",", ":"), allow_nan=False).encode()).hexdigest()
|
||||
|
||||
|
||||
def atomic_json(path, document):
|
||||
path = Path(path)
|
||||
if path.is_symlink():
|
||||
raise Refused("unsafe_state_path")
|
||||
with tempfile.NamedTemporaryFile(dir=path.parent, delete=False) as out:
|
||||
temporary = Path(out.name)
|
||||
os.chmod(temporary, 0o600)
|
||||
out.write(json.dumps(document, sort_keys=True).encode())
|
||||
out.flush()
|
||||
os.fsync(out.fileno())
|
||||
try:
|
||||
os.replace(temporary, path)
|
||||
fd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
|
||||
try:
|
||||
os.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
finally:
|
||||
temporary.unlink(missing_ok=True)
|
||||
|
||||
|
||||
class Bao:
|
||||
def call(self, command, payload=None, optional=False):
|
||||
result = subprocess.run(["bao", *command], input=None if payload is None else json.dumps(payload).encode(),
|
||||
capture_output=True, timeout=15)
|
||||
if result.returncode:
|
||||
# Distinguish a documented missing-path message from auth/outage.
|
||||
if optional and result.returncode == 2 and result.stderr.strip() == ("No value found at " + command[-1]).encode():
|
||||
return None
|
||||
raise Refused("openbao_request_failed")
|
||||
if len(result.stdout) > MAX_BYTES * 2:
|
||||
raise Refused("oversize_owner_response")
|
||||
return json.loads(result.stdout) if result.stdout.strip() else {}
|
||||
|
||||
def read(self, path, optional=False):
|
||||
response = self.call(["read", "-format=json", path], optional=optional)
|
||||
return None if response is None else response["data"]
|
||||
|
||||
def write(self, path, payload):
|
||||
return self.call(["write", "-format=json", path, "-"], payload)
|
||||
|
||||
|
||||
def check_baseline(actual, state, validator):
|
||||
if state is None:
|
||||
if actual is not None:
|
||||
raise Refused("controlled_baseline_import_required")
|
||||
return
|
||||
previous = state["document"]
|
||||
if validator.validate(previous) or previous["record_id"] != RECORD_ID or state["digest"] != digest(previous):
|
||||
raise Refused("invalid_retained_baseline")
|
||||
if actual is None or validator.validate(actual["data"]["document"]):
|
||||
raise Refused("missing_or_invalid_source")
|
||||
document = actual["data"]["document"]
|
||||
if digest(document) != state["digest"] or actual["metadata"]["version"] != state["kv_version"]:
|
||||
raise Refused("source_differs_from_retained_baseline")
|
||||
|
||||
|
||||
def retain_history(state_path, receipt):
|
||||
history = state_path.parent / "history"
|
||||
history.mkdir(mode=0o700, exist_ok=True)
|
||||
target = history / (str(receipt["revision"]) + "-" + receipt["digest"] + ".json")
|
||||
if target.exists():
|
||||
if json.loads(target.read_text()) != receipt:
|
||||
raise Refused("history_receipt_differs")
|
||||
return
|
||||
with target.open("x") as out:
|
||||
os.chmod(target, 0o600)
|
||||
json.dump(receipt, out, sort_keys=True)
|
||||
out.flush()
|
||||
os.fsync(out.fileno())
|
||||
fd = os.open(history, os.O_RDONLY | os.O_DIRECTORY)
|
||||
try:
|
||||
os.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def publish(bao, candidate, state_path, validator, provenance):
|
||||
state_path = Path(state_path)
|
||||
if not isinstance(provenance, str) or not provenance.strip() or len(provenance) > 300 or validator.SECRET_VALUE.search(provenance):
|
||||
raise Refused("nonsecret_human_provenance_required")
|
||||
if state_path.is_symlink():
|
||||
raise Refused("unsafe_state_path")
|
||||
state = json.loads(state_path.read_text()) if state_path.exists() else None
|
||||
actual = bao.read(PATH, optional=True)
|
||||
# Pending state is fsynced before network mutation. Recover only exact readback;
|
||||
# ambiguous network failures are never retried or cleared automatically.
|
||||
if state is not None and state.get("pending"):
|
||||
pending = state["pending"]
|
||||
if actual is None or digest(actual["data"]["document"]) != pending["digest"] or actual["metadata"]["version"] != pending["expected_kv_version"]:
|
||||
raise Refused("pending_publication_requires_owner_review")
|
||||
if validator.validate(pending["document"]) or pending["document"]["record_id"] != RECORD_ID:
|
||||
raise Refused("invalid_pending_document")
|
||||
state = {key: value for key, value in pending.items() if key != "expected_kv_version"}
|
||||
retain_history(state_path, state)
|
||||
atomic_json(state_path, state)
|
||||
check_baseline(actual, state, validator)
|
||||
previous = None if state is None else state["document"]
|
||||
version = 0 if actual is None else actual["metadata"]["version"]
|
||||
if previous is not None and digest(candidate) == state["digest"]:
|
||||
# Exact replay is an idempotent readback, not a new custody/KV revision.
|
||||
return {**{key: value for key, value in state.items() if key != "document"}, "unchanged": True}
|
||||
payload = prepare_payload(candidate, previous, version, validator)
|
||||
now = dt.datetime.now(dt.timezone.utc)
|
||||
times = [candidate["updated_at"]]
|
||||
for entry in candidate["entries"]:
|
||||
times.append(entry["granted"]["at"])
|
||||
if entry.get("revoked"):
|
||||
times.append(entry["revoked"]["at"])
|
||||
if any(validator.parse_time(value) > now for value in times):
|
||||
raise Refused("future_custody_action")
|
||||
receipt = {"record_id": RECORD_ID, "revision": candidate["revision"], "digest": digest(candidate),
|
||||
"kv_version": version + 1, "document": candidate, "provenance_ref": provenance,
|
||||
"recorded_at": dt.datetime.now(dt.timezone.utc).isoformat()}
|
||||
pending = dict(receipt, expected_kv_version=version + 1)
|
||||
atomic_json(state_path, {**(state or {}), "pending": pending})
|
||||
bao.write(PATH, payload)
|
||||
returned = bao.read(PATH)
|
||||
if returned["metadata"]["version"] != version + 1 or digest(returned["data"]["document"]) != receipt["digest"]:
|
||||
raise Refused("publication_readback_failed")
|
||||
# Independent immutable receipts precede advancing local state. They must be
|
||||
# backed up off-host; restoring only this directory is not independent proof.
|
||||
retain_history(state_path, receipt)
|
||||
atomic_json(state_path, receipt)
|
||||
return {key: value for key, value in receipt.items() if key != "document"}
|
||||
|
||||
|
||||
def provision(bao):
|
||||
# Require existing Kubernetes auth; no auth mount or cluster reviewer mutation.
|
||||
if bao.read("auth/kubernetes/config") is None:
|
||||
raise Refused("kubernetes_auth_unavailable")
|
||||
audit = bao.read("sys/audit")
|
||||
if not audit:
|
||||
raise Refused("audit_required")
|
||||
policy = (ROOT / "openbao/root-grant/reader.hcl").read_text()
|
||||
existing = bao.read("sys/policies/acl/" + POLICY, optional=True)
|
||||
if existing is not None and existing.get("policy", existing.get("rules", "")).strip() != policy.strip():
|
||||
raise Refused("existing_reader_policy_differs")
|
||||
role = bao.read(ROLE_PATH, optional=True)
|
||||
if role is not None and any(role.get(key) != value for key, value in ROLE.items()):
|
||||
raise Refused("existing_reader_role_differs")
|
||||
if existing is None:
|
||||
bao.write("sys/policies/acl/" + POLICY, {"policy": policy})
|
||||
if role is None:
|
||||
bao.write(ROLE_PATH, ROLE)
|
||||
fresh = bao.read(ROLE_PATH)
|
||||
if any(fresh.get(key) != value for key, value in ROLE.items()):
|
||||
raise Refused("role_readback_failed")
|
||||
fresh_policy = bao.read("sys/policies/acl/" + POLICY)
|
||||
if fresh_policy.get("policy", fresh_policy.get("rules", "")).strip() != policy.strip():
|
||||
raise Refused("policy_readback_failed")
|
||||
# Require CAS on this key without altering retention or destroying versions.
|
||||
metadata = bao.read(META, optional=True)
|
||||
if not metadata or not metadata.get("cas_required"):
|
||||
bao.write(META, {"cas_required": True})
|
||||
if bao.read(META).get("cas_required") is not True:
|
||||
raise Refused("cas_required_readback_failed")
|
||||
return {"reader_role": ROLE_PATH, "reader_namespace": "user-engine", "reader_service_account": "user-engine",
|
||||
"audience": "openbao", "record_id": RECORD_ID, "cas_required": True, "audit_enabled": True}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("action", choices=["provision", "publish", "restore-check"])
|
||||
parser.add_argument("--receipt", type=Path, required=True)
|
||||
parser.add_argument("--state", type=Path)
|
||||
parser.add_argument("--candidate", type=Path)
|
||||
parser.add_argument("--net-kingdom", type=Path)
|
||||
parser.add_argument("--provenance-ref")
|
||||
parser.add_argument("--validator-digest")
|
||||
parser.add_argument("--schema-digest")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
if args.receipt.exists() or args.receipt.is_symlink():
|
||||
raise Refused("unique_run_receipt_required")
|
||||
require_attended()
|
||||
if os.getenv("BAO_ADDR") != "http://127.0.0.1:18200":
|
||||
raise Refused("approved_operator_tunnel_required")
|
||||
bao = Bao()
|
||||
if args.action == "provision":
|
||||
receipt = provision(bao)
|
||||
else:
|
||||
if not args.state or not args.net_kingdom or not args.validator_digest or not args.schema_digest:
|
||||
raise Refused("pinned_source_and_state_required")
|
||||
source = args.net_kingdom / "tools/custody-record/validate.py"
|
||||
schema = args.net_kingdom / "canon/schemas/custody-record_v0.1.schema.json"
|
||||
for path, expected in [(source, args.validator_digest), (schema, args.schema_digest)]:
|
||||
if hashlib.sha256(path.read_bytes()).hexdigest() != expected:
|
||||
raise Refused("canon_pin_mismatch")
|
||||
spec = importlib.util.spec_from_file_location("custody_validator", source)
|
||||
validator = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(validator)
|
||||
if not args.state.parent.is_dir():
|
||||
raise Refused("owner_state_directory_required")
|
||||
# Serialize this publisher, including reads and pending-journal recovery.
|
||||
with (args.state.parent / "publisher.lock").open("a") as lock:
|
||||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||||
if args.action == "restore-check":
|
||||
if not args.state.exists():
|
||||
raise Refused("retained_baseline_required")
|
||||
state = json.loads(args.state.read_text())
|
||||
if state.get("pending"):
|
||||
raise Refused("pending_publication_requires_owner_review")
|
||||
check_baseline(bao.read(PATH, optional=True), state, validator)
|
||||
receipt = {"record_id": RECORD_ID, "baseline_matches": True, "revision": state["document"]["revision"]}
|
||||
else:
|
||||
if not args.candidate or not args.provenance_ref:
|
||||
raise Refused("candidate_and_human_provenance_required")
|
||||
candidate = json.loads(args.candidate.read_bytes()[:MAX_BYTES + 1])
|
||||
receipt = publish(bao, candidate, args.state, validator, args.provenance_ref)
|
||||
atomic_json(args.receipt, {"ok": True, **receipt})
|
||||
return 0
|
||||
except Exception:
|
||||
# CLI/process/validation errors may embed secrets. Never emit them.
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
140
tests/test_root_grant_attended.py
Normal file
140
tests/test_root_grant_attended.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import copy
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "scripts"))
|
||||
import root_grant_attended as attended
|
||||
|
||||
spec = importlib.util.spec_from_file_location("custody_validator", ROOT.parent / "net-kingdom/tools/custody-record/validate.py")
|
||||
validator = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(validator)
|
||||
|
||||
|
||||
class FakeBao:
|
||||
def __init__(self, document=None, version=1):
|
||||
self.actual = None if document is None else {"data": {"document": copy.deepcopy(document)}, "metadata": {"version": version}}
|
||||
self.metadata = {"max_versions": 123, "cas_required": False}
|
||||
self.config = {"auth/kubernetes/config": {}, "sys/audit": {"file/": {"type": "file"}}, attended.META: self.metadata}
|
||||
self.writes = []
|
||||
self.fail_after_write = False
|
||||
|
||||
def read(self, path, optional=False):
|
||||
return copy.deepcopy(self.actual if path == attended.PATH else self.config.get(path))
|
||||
|
||||
def write(self, path, payload):
|
||||
self.writes.append((path, copy.deepcopy(payload)))
|
||||
if path == attended.PATH:
|
||||
version = 0 if self.actual is None else self.actual["metadata"]["version"]
|
||||
if payload["options"]["cas"] != version:
|
||||
raise attended.Refused("cas_conflict")
|
||||
self.actual = {"data": payload["data"], "metadata": {"version": version + 1}}
|
||||
if self.fail_after_write:
|
||||
raise attended.Refused("ambiguous_timeout")
|
||||
else:
|
||||
self.config[path] = payload
|
||||
if path == attended.META:
|
||||
self.metadata.update(payload)
|
||||
self.config[path] = self.metadata
|
||||
|
||||
|
||||
class AttendedTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.state = Path(self.temp.name) / "baseline.json"
|
||||
self.first = json.loads((ROOT.parent / "net-kingdom/tools/custody-record/fixtures/valid/grant.json").read_text())
|
||||
self.first["record_id"] = attended.RECORD_ID
|
||||
self.next = dict(copy.deepcopy(self.first), revision=self.first["revision"] + 1)
|
||||
|
||||
def tearDown(self):
|
||||
self.temp.cleanup()
|
||||
|
||||
def test_first_publication_and_update_keep_history(self):
|
||||
bao = FakeBao()
|
||||
receipt = attended.publish(bao, self.first, self.state, validator, "human-receipt")
|
||||
self.assertNotIn("document", receipt)
|
||||
attended.publish(bao, self.next, self.state, validator, "human-receipt-2")
|
||||
self.assertEqual(len(list((self.state.parent / "history").glob("*.json"))), 2)
|
||||
self.assertEqual(bao.writes[1][1]["options"], {"cas": 1})
|
||||
|
||||
def test_exact_replay_creates_no_kv_version_or_history(self):
|
||||
bao = FakeBao()
|
||||
attended.publish(bao, self.first, self.state, validator, "human-receipt")
|
||||
result = attended.publish(bao, self.first, self.state, validator, "human-receipt")
|
||||
self.assertTrue(result["unchanged"])
|
||||
self.assertEqual(len(bao.writes), 1)
|
||||
self.assertEqual(len(list((self.state.parent / "history").glob("*.json"))), 1)
|
||||
|
||||
def test_existing_receipt_prevents_any_live_call(self):
|
||||
from unittest.mock import patch
|
||||
receipt = Path(self.temp.name) / "receipt.json"
|
||||
receipt.write_text('{"ok":true}')
|
||||
with patch.object(sys, "argv", ["executor", "provision", "--receipt", str(receipt)]), patch.object(attended, "require_attended") as check:
|
||||
self.assertEqual(attended.main(), 1)
|
||||
check.assert_not_called()
|
||||
self.assertEqual(receipt.read_text(), '{"ok":true}')
|
||||
|
||||
def test_existing_source_without_baseline_is_not_bootstrap(self):
|
||||
bao = FakeBao(self.first)
|
||||
with self.assertRaises(attended.Refused):
|
||||
attended.publish(bao, self.next, self.state, validator, "human-receipt")
|
||||
self.assertEqual(bao.writes, [])
|
||||
|
||||
def test_rollback_restore_denied(self):
|
||||
bao = FakeBao()
|
||||
attended.publish(bao, self.first, self.state, validator, "human-receipt")
|
||||
attended.publish(bao, self.next, self.state, validator, "human-receipt")
|
||||
state = json.loads(self.state.read_text())
|
||||
with self.assertRaises(attended.Refused):
|
||||
attended.check_baseline(FakeBao(self.first).actual, state, validator)
|
||||
|
||||
def test_ambiguous_network_failure_leaves_durable_pending(self):
|
||||
bao = FakeBao()
|
||||
bao.fail_after_write = True
|
||||
with self.assertRaises(attended.Refused):
|
||||
attended.publish(bao, self.first, self.state, validator, "human-receipt")
|
||||
self.assertIn("pending", json.loads(self.state.read_text()))
|
||||
bao.fail_after_write = False
|
||||
attended.publish(bao, self.next, self.state, validator, "human-receipt-2")
|
||||
self.assertNotIn("pending", json.loads(self.state.read_text()))
|
||||
self.assertEqual(len(bao.writes), 2)
|
||||
self.assertEqual(len(list((self.state.parent / "history").glob("*.json"))), 2)
|
||||
|
||||
def test_pending_mismatch_requires_review_and_no_retry(self):
|
||||
bao = FakeBao()
|
||||
bao.fail_after_write = True
|
||||
with self.assertRaises(attended.Refused):
|
||||
attended.publish(bao, self.first, self.state, validator, "human-receipt")
|
||||
bao.actual = None
|
||||
with self.assertRaises(attended.Refused):
|
||||
attended.publish(bao, self.next, self.state, validator, "human-receipt")
|
||||
self.assertEqual(len(bao.writes), 1)
|
||||
|
||||
def test_exact_admission_is_idempotent_and_retention_unchanged(self):
|
||||
bao = FakeBao()
|
||||
attended.provision(bao)
|
||||
self.assertEqual(bao.config[attended.ROLE_PATH], attended.ROLE)
|
||||
self.assertTrue(bao.metadata["cas_required"])
|
||||
self.assertEqual(bao.metadata["max_versions"], 123)
|
||||
writes = len(bao.writes)
|
||||
attended.provision(bao)
|
||||
self.assertEqual(writes, len(bao.writes))
|
||||
|
||||
def test_different_existing_role_and_missing_audit_are_refused_before_write(self):
|
||||
for mode in ["role", "audit"]:
|
||||
bao = FakeBao()
|
||||
if mode == "role":
|
||||
bao.config[attended.ROLE_PATH] = dict(attended.ROLE, bound_service_account_names=["other"])
|
||||
else:
|
||||
bao.config["sys/audit"] = {}
|
||||
with self.assertRaises(attended.Refused):
|
||||
attended.provision(bao)
|
||||
self.assertEqual(bao.writes, [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -41,36 +41,39 @@ These are preparation artifacts, not an admitted or operational source.
|
|||
|
||||
```task
|
||||
id: RPF-WP-0049-T02
|
||||
status: wait
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [NK-WP-0045-T01, NK-WP-0045-T04]
|
||||
blocked_on: message-from:user-engine
|
||||
blocking_reason: "Design selection is approved; user-engine must still supply its actual runtime reader identity and supported authentication route. No deployed ServiceAccount is known."
|
||||
state_hub_task_id: "16096f84-6232-5d97-8fe3-b4683c79f4fb"
|
||||
```
|
||||
|
||||
Approved source: platform/data/custody/net-kingdom/platform-root, record ID
|
||||
nk-custody:platform-root and exact-path read using the existing owner-routed
|
||||
workload admission. user-engine confirmed nested document compatibility;
|
||||
its adapter is transport-neutral and runtime identity remains unconfirmed.
|
||||
its adapter is transport-neutral. Owner rapp-user-engine manifests confirm
|
||||
namespace user-engine / ServiceAccount user-engine. Selected admission role
|
||||
is user-engine-root-custody-reader, auth/kubernetes, audience openbao, 60s
|
||||
maximum token TTL and no default policy; dedicated projection is owned by
|
||||
rapp-user-engine. This resolves source identity, not live admission.
|
||||
|
||||
## Implement attended executor, recovery and audit for the approved source
|
||||
|
||||
```task
|
||||
id: RPF-WP-0049-T03
|
||||
status: wait
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [RPF-WP-0049-T02]
|
||||
blocking_reason: "Surface is approved; actual reader identity and separate attended writer custody bindings remain required for final executor/admission/restore configuration."
|
||||
state_hub_task_id: "0bc4bc1f-e279-5c92-97bb-cf76d2e33749"
|
||||
```
|
||||
|
||||
Implement reviewed value-safe executor with owner-read baseline + CAS, exact
|
||||
identity admission configuration, per-key required CAS, retention/independent
|
||||
high-water history, audit and rollback-safe restoration. Pin canon source.
|
||||
Do not let direct writer capability bypass transition or ceremony requirements.
|
||||
Implemented silent attended provision/publish/restore-check executor with exact
|
||||
reader admission, per-key required CAS, active-audit check, pinned canon, retained
|
||||
high-water baseline, fsynced pending journal and immutable local history.
|
||||
Readback is mandatory; replay is idempotent; conflicting existing role/policy,
|
||||
missing state and rollback fail closed. Off-host history placement and actual
|
||||
recovery evidence remain execution/acceptance dependencies in T04/T05.
|
||||
|
||||
## Approve bounded live provisioning and custody bootstrap
|
||||
## Execute authorized attended provisioning and establish initial baseline
|
||||
|
||||
```task
|
||||
id: RPF-WP-0049-T04
|
||||
|
|
@ -78,13 +81,16 @@ status: wait
|
|||
priority: high
|
||||
depends_on: [RPF-WP-0049-T03]
|
||||
needs_human: true
|
||||
blocking_reason: "Separate founder go for named commits, exact read/writer bindings, attended window and real custody provenance; decision on design alone is not live approval."
|
||||
blocking_reason: "Founder go-forward is recorded; execution needs founder terminal OIDC/MFA, owner state/history location and real ceremony inputs or founder choice of an empty baseline. No real custodian can be inferred."
|
||||
state_hub_task_id: "88b932e8-1010-56c6-be27-ad46f47e1e80"
|
||||
```
|
||||
|
||||
Assemble exact later approval packet from producer README. Source-only draft
|
||||
policies are not approval to apply or create entries. Bootstrap ceremony facts
|
||||
are supplied by human custodian, never inferred from IAM or test fixtures.
|
||||
Founder continued approval: "Good, i approve go forward please." This authorizes
|
||||
bounded execution of the reviewed design; generic permission is not pending.
|
||||
The concrete attended provisioning command is in the producer README. The
|
||||
founder runs existing operator OIDC/MFA in their own terminal. Human supplies
|
||||
real ceremony issuer/subject/reference or chooses a no-grant empty baseline;
|
||||
legacy planned roster and bootstrap setup operator are not grant evidence.
|
||||
|
||||
## Return producer delivery receipts
|
||||
|
||||
|
|
@ -116,3 +122,11 @@ bindings/final executor/recovery preparation, T04 on separate attended live
|
|||
approval and ceremony provenance, and T05 on actual producer delivery receipts.
|
||||
The workplan remains blocked on those residual dependencies. No live mutation
|
||||
or real custody entry is authorized by this design acceptance.
|
||||
|
||||
## Continued implementation — 2026-10-03
|
||||
|
||||
T02/T03 source identity and executor implementation complete. No further
|
||||
generic approval requested; T04 is attended execution/information dependency.
|
||||
T05 still requires live producer read/denial/outage/restore/audit receipts.
|
||||
NetKingdom canon pin db319335b27b61b855382ee5af5d690f06eeb010. No real
|
||||
root grant, privileged login, production admission or readiness claimed.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue