Fix founder publication login handoff and session reporting
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1abf1-7e95-7141-87c9-ee78332bf790
This commit is contained in:
parent
412787515c
commit
473187c114
7 changed files with 295 additions and 17 deletions
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"schema": "railiance.vergabe-pilot-provider-recovery.v1",
|
||||
"run_id": "20261105T233543Z-89bfbe04dece",
|
||||
"status": "failed",
|
||||
"provider_publication_verified": false,
|
||||
"decryption_verified": false,
|
||||
"restore_verified": false,
|
||||
"application_changed": false,
|
||||
"stage": "owner_custody",
|
||||
"failure": "contained_owner_login_required",
|
||||
"elapsed_seconds": 0.01,
|
||||
"warden_exit_code": 11,
|
||||
"receipt_file": "/home/worsch/.local/state/vergabe-pilot-recovery/20261105T233543Z-89bfbe04dece/provider-receipt-48cc9fb0018d.json",
|
||||
"session_cleanup_confirmed": false,
|
||||
"evidence_kind": "user_reported_terminal_output",
|
||||
"reported_on": "2026-11-06",
|
||||
"source_revision": "412787515c5ad662b8014347f9eb1bb3b8b8da0f",
|
||||
"workplan": "RAPPS-WP-0014",
|
||||
"task": "RAPPS-WP-0014-T03",
|
||||
"review": {
|
||||
"founder_login_succeeded": true,
|
||||
"session_revoked_user_reported": true,
|
||||
"provider_custody_reads_reached": false,
|
||||
"provider_calls_reached": false,
|
||||
"session_cleanup_false_is_publisher_reporting_defect": true,
|
||||
"original_guard_did_not_identify_which_condition_failed": true,
|
||||
"source_mismatch": "Publisher rejects OPENBAO_TOKEN, but the wrapped login only clears BAO_TOKEN and VAULT_TOKEN; an inherited alias reproduces this failure in the real Warden envelope with an offline fake Bao client.",
|
||||
"fix": "Clear all three token aliases before the fresh owner login; retain explicit isolated-home/tunnel/policy checks and safe per-condition diagnostics. Keep session lifecycle reporting distinct from child exit status."
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,15 @@ verifies ciphertext SHA256 `49152883…6bef76f`, 182521 bytes, for run
|
|||
`20261105T233543Z-89bfbe04dece`. The app remains running. The private encrypted
|
||||
kit is already on the founder's workstation. [Exact scope and source pins](plan.json).
|
||||
|
||||
First native attempt: [the founder return](../../evidence/2026-11-06-vergabe-provider-custody-failure-return.json)
|
||||
reports `contained_owner_login_required` before credential reads/provider calls.
|
||||
Warden logged in and reported session revocation; the publisher's old aggregate
|
||||
cleanup flag was misleading. Revision 2 clears all three token aliases before
|
||||
the fresh login and verifies Warden's private session-directory context. An
|
||||
offline test through the installed Warden envelope reproduces the inherited
|
||||
alias refusal and passes with the corrected preparation. Retry the same command
|
||||
below; the previous session was revoked. Provider scope and ciphertext are unchanged.
|
||||
|
||||
## Founder workstation command
|
||||
|
||||
```bash
|
||||
|
|
@ -46,6 +55,14 @@ boundary policies and ambient token variables refuse. The OpenBao tunnel remains
|
|||
`http://127.0.0.1:18200`, as in the existing owner wrapper. No fallback identity
|
||||
or changed control is introduced.
|
||||
|
||||
Before launching the wrapper, the publisher clears `BAO_TOKEN`, `VAULT_TOKEN`
|
||||
and `OPENBAO_TOKEN` and pins both address aliases to the tunnel. Warden's wrapper
|
||||
already clears the first two; leaving the third inherited conflicted with the
|
||||
publisher's own guard. The child verifies directory ownership/permissions and
|
||||
the `.warden-attended-login/session-*` HOME shape, without inspecting the token
|
||||
helper. Its receipt includes only boolean handoff checks. Self-lookup uses the
|
||||
owner's `auth/token/lookup-self` API form and combines token/identity policies.
|
||||
|
||||
## Exact provider change
|
||||
|
||||
One object in the existing `r01-platform-pg` backup folder:
|
||||
|
|
@ -84,6 +101,13 @@ the same deterministic object name. Warden output/cleanup errors remain failures
|
|||
even if data verification finished. Inspect only the allowed receipt, never
|
||||
Warden's captured output or token helper.
|
||||
|
||||
For failed children, exit 11 alone cannot distinguish confirmed revocation from
|
||||
an unconfirmed revocation. The new receipt records `null` for that uncertainty,
|
||||
with storage cleanup separately reported from Warden's documented exit contract;
|
||||
13 means revocation unconfirmed and 14 means private-storage cleanup failed.
|
||||
Only exit 0 confirms the entire envelope. The original failure receipt is kept
|
||||
unchanged as historical evidence, alongside its review of the reporting defect.
|
||||
|
||||
A new `provider-recovery-*` directory inside the private workstation kit retains
|
||||
the retrieved ciphertext, decrypted ZIP and unpacked recovery point for the next
|
||||
isolated restore. The directory is 0700 and files 0600, outside Git; do not open
|
||||
|
|
@ -97,10 +121,13 @@ No second application pause or recapture is needed for this point.
|
|||
|
||||
## Validation
|
||||
|
||||
Eleven synthetic checks cover real age encrypt/decrypt and complete packet/media/
|
||||
Fifteen publisher checks cover real age encrypt/decrypt and complete packet/media/
|
||||
SQLite verification with mocked custody/provider; separate upload/readback
|
||||
credentials; identical-object retry; different-object preservation; wrong escrow,
|
||||
quota and tampered-download refusal; credential-safe silent failure; ambient
|
||||
token refusal; pinned owner helpers; foreign-origin denial and bounded readback.
|
||||
Native provider publication/decryption has not executed until its founder receipt
|
||||
returns.
|
||||
The additional cases test all token aliases, API/effective-policy handling,
|
||||
separate session outcomes and the installed Warden envelope using an offline
|
||||
fake Bao client. They issue no native session and inspect no real helper or
|
||||
credential. All 45 pilot helper checks pass. Native provider publication and
|
||||
decryption remain unverified until a successful founder receipt returns.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"run_id": "20261105T233543Z-89bfbe04dece",
|
||||
"ciphertext_sha256": "4915288384e3ba8a87f1427b8e10106ee7241c04184adb97f439e57846bef76f",
|
||||
"ciphertext_bytes": 182521,
|
||||
"publisher_source_sha256": "845294c574692333b35e627f5d3194e6334136f14f4f699e27a680f9cd4604db",
|
||||
"publisher_source_sha256": "8815272ed09ffce91e2a1551c646285d8aea844f318fc188c986d15f4093f807",
|
||||
"platform_helper_pins": {
|
||||
"openbao-attended-exec.py": "53fb5fb38ad17c4b06d7969a4d8968d976c8ebc8f1abfeb9a191aa43dc54a5c3",
|
||||
"migrate_nextcloud_backup_account.py": "d3285f403f3c947f75c3288d989152e59ab9a8f5542bcf901bd0a06b0f3e050c",
|
||||
|
|
@ -53,10 +53,29 @@
|
|||
"recovered_data": "Private provider-recovery-* directory inside the existing workstation kit, outside Git, retained for the isolated restore rehearsal",
|
||||
"failure_handling": "Safe receipt with phase flags; different object bytes refuse without overwrite. Retry reads the deterministic object name. Warden exit and cleanup confirmation are recorded separately from data verification.",
|
||||
"validation": {
|
||||
"publisher_cases": 11,
|
||||
"publisher_cases": 15,
|
||||
"synthetic_only": true,
|
||||
"actual_age_round_trip": true,
|
||||
"provider_and_custody_mocked": true,
|
||||
"native_provider_publication_executed": false
|
||||
"native_provider_publication_executed": false,
|
||||
"total_pilot_cases": 45,
|
||||
"real_installed_warden_offline_fixture": true,
|
||||
"synthetic_bao_only": true,
|
||||
"native_login_used_by_tests": false
|
||||
},
|
||||
"previous_publisher_source_sha256": "845294c574692333b35e627f5d3194e6334136f14f4f699e27a680f9cd4604db",
|
||||
"revision": "r2-owner-handoff",
|
||||
"handoff_fix": {
|
||||
"clear_all_token_aliases_before_login": [
|
||||
"BAO_TOKEN",
|
||||
"VAULT_TOKEN",
|
||||
"OPENBAO_TOKEN"
|
||||
],
|
||||
"isolation": "Verify expected private Warden session HOME by directory metadata only; never inspect token-helper contents",
|
||||
"safe_handoff_booleans_in_receipt": true,
|
||||
"lookup_api": "auth/token/lookup-self",
|
||||
"effective_policies": "Union token and identity policies; root/agent boundary still refuse",
|
||||
"nonzero_child_exit_does_not_imply_revocation_failure": true,
|
||||
"same_provider_scope": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,6 +160,66 @@ class PublisherTests(unittest.TestCase):
|
|||
|
||||
|
||||
class BoundaryTests(unittest.TestCase):
|
||||
def test_owner_environment_discards_all_token_aliases_before_fresh_login(self):
|
||||
environment = {name: "synthetic" for name in publish.TOKEN_ENV}
|
||||
environment.update(HOME="/synthetic", PATH="/usr/bin", BAO_ADDR="wrong", VAULT_ADDR="wrong")
|
||||
result = publish.owner_login_environment(environment)
|
||||
self.assertFalse(set(result) & set(publish.TOKEN_ENV))
|
||||
self.assertEqual(result["BAO_ADDR"], publish.TUNNEL)
|
||||
self.assertEqual(result["VAULT_ADDR"], publish.TUNNEL)
|
||||
self.assertEqual(result["HOME"], "/synthetic")
|
||||
self.assertTrue(set(environment) & set(publish.TOKEN_ENV))
|
||||
|
||||
def test_real_warden_envelope_with_offline_synthetic_login(self):
|
||||
executable = shutil.which("warden")
|
||||
if not executable:
|
||||
self.skipTest("installed Warden is required for its real envelope fixture")
|
||||
first = Path(executable).read_text().splitlines()[0]
|
||||
if not first.startswith("#!/") or " " in first:
|
||||
self.skipTest("Warden Python interpreter could not be identified")
|
||||
result = subprocess.run([first[2:], str(Path(__file__).with_name("vergabe_pilot_warden_handoff_fixture.py"))],
|
||||
capture_output=True, timeout=30)
|
||||
self.assertEqual(result.returncode, 0, "offline Warden handoff fixture failed")
|
||||
evidence = json.loads(result.stdout)
|
||||
self.assertTrue(evidence["sanitized_handoff_passed"])
|
||||
self.assertFalse(evidence["real_credentials_used"])
|
||||
|
||||
def test_failed_child_exit_does_not_prove_revocation_failure(self):
|
||||
outcome = publish.session_outcome(11)
|
||||
self.assertIsNone(outcome["session_cleanup_confirmed"])
|
||||
self.assertIsNone(outcome["session_revocation_confirmed"])
|
||||
self.assertTrue(outcome["session_storage_cleanup_confirmed"])
|
||||
self.assertFalse(publish.session_outcome(13)["session_revocation_confirmed"])
|
||||
self.assertFalse(publish.session_outcome(14)["session_storage_cleanup_confirmed"])
|
||||
self.assertTrue(publish.session_outcome(0)["session_cleanup_confirmed"])
|
||||
|
||||
def test_owner_custody_uses_lookup_api_and_effective_identity_policies(self):
|
||||
from types import SimpleNamespace
|
||||
calls = []
|
||||
def read(args):
|
||||
calls.append(args)
|
||||
if args[2] == "auth/token/lookup-self":
|
||||
return {"data": {"policies": ["default", "platform-admin"],
|
||||
"identity_policies": ["operator-custody"]}}
|
||||
if args[2] == publish.LANE:
|
||||
return {"data": {"metadata": {"version": 1}, "data": {
|
||||
"NC_WEBDAV_TOKEN": "synthetic-upload",
|
||||
"NC_WEBDAV_URL": publish.HOST + "/public.php/dav/filesdrop/synthetic-upload",
|
||||
"AGE_PRIVATE_KEY": "synthetic-not-a-key"}}}
|
||||
if args[2] == publish.OPERATOR:
|
||||
return {"data": {"data": {"BACKUP_USERNAME": "Backup", "BACKUP_PASSWORD": "synthetic"}}}
|
||||
raise AssertionError("unexpected custody read")
|
||||
with tempfile.TemporaryDirectory(prefix="rapps-owner-context-") as temporary:
|
||||
root = Path(temporary) / ".warden-attended-login"
|
||||
root.mkdir(mode=0o700)
|
||||
home = root / "session-fixture"
|
||||
home.mkdir(mode=0o700)
|
||||
with patch.dict(os.environ, {"HOME": str(home), "WARDEN_ROUTING_CATALOG": "synthetic",
|
||||
"BAO_ADDR": publish.TUNNEL, "VAULT_ADDR": publish.TUNNEL}, clear=True):
|
||||
result = publish.custody(SimpleNamespace(bao=read, data=lambda value: value))
|
||||
self.assertEqual(result["kv_version"], 1)
|
||||
self.assertEqual([c[2] for c in calls], ["auth/token/lookup-self", publish.LANE, publish.OPERATOR])
|
||||
|
||||
def provider(self):
|
||||
from types import SimpleNamespace
|
||||
return publish.Provider(SimpleNamespace(HOST=publish.HOST, LANE=publish.LANE,
|
||||
|
|
@ -189,8 +249,9 @@ class BoundaryTests(unittest.TestCase):
|
|||
|
||||
def test_ambient_token_refuses_without_a_secret_read(self):
|
||||
from types import SimpleNamespace
|
||||
with patch.dict(os.environ, {"WARDEN_ROUTING_CATALOG": "synthetic", "BAO_ADDR": "http://127.0.0.1:18200",
|
||||
"BAO_TOKEN": "synthetic-not-real"}), self.assertRaises(stores.Refused):
|
||||
with patch.dict(os.environ, {"WARDEN_ROUTING_CATALOG": "synthetic", "BAO_ADDR": publish.TUNNEL,
|
||||
"VAULT_ADDR": publish.TUNNEL, "BAO_TOKEN": "synthetic-not-real"}), \
|
||||
self.assertRaisesRegex(stores.Refused, "ambient_owner_token_refused"):
|
||||
publish.custody(SimpleNamespace())
|
||||
|
||||
def test_owner_helper_pins_validate_without_native_access(self):
|
||||
|
|
|
|||
77
tests/vergabe_pilot_warden_handoff_fixture.py
Normal file
77
tests/vergabe_pilot_warden_handoff_fixture.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Exercise installed Warden's real envelope with a fake, offline Bao client.
|
||||
|
||||
Only synthetic files beneath a temporary HOME. No real token helper, login,
|
||||
OpenBao, provider, backup or operator credential is inspected or used.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from warden.proxy import AttendedLoginError, ResolvedFetch, proxy_attended_login_exec
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
|
||||
import vergabe_pilot_publish as publisher
|
||||
|
||||
|
||||
def main():
|
||||
with tempfile.TemporaryDirectory(prefix="rapps-warden-offline-", dir="/tmp") as temporary:
|
||||
root = Path(temporary)
|
||||
caller = root / "caller"
|
||||
caller.mkdir(mode=0o700)
|
||||
client = root / "bao"
|
||||
client.write_text("#!" + sys.executable + "\n" + '''
|
||||
import os,sys
|
||||
from pathlib import Path
|
||||
if sys.argv[1] == 'login':
|
||||
helper=Path.home()/'.vault-token'
|
||||
helper.write_text('SYNTHETIC-NO-AUTH')
|
||||
helper.chmod(0o600)
|
||||
elif sys.argv[1:] != ['token','revoke','-self']:
|
||||
raise SystemExit(2)
|
||||
''')
|
||||
client.chmod(0o700)
|
||||
proof = root / "handoff-proof.json"
|
||||
code = '''
|
||||
import json,sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0,sys.argv[1])
|
||||
import vergabe_pilot_publish as p
|
||||
try:
|
||||
p.require_owner_handoff()
|
||||
except p.Refused:
|
||||
raise SystemExit(1)
|
||||
Path(sys.argv[2]).write_text(json.dumps(p.owner_handoff_checks()))
|
||||
'''
|
||||
child = [sys.executable, "-c", code, str(Path(publisher.__file__).parent), str(proof)]
|
||||
environment = {"HOME": str(caller), "PATH": os.environ.get("PATH", "/usr/bin:/bin"),
|
||||
"WARDEN_ROUTING_CATALOG": str(root / "synthetic-catalog"),
|
||||
"BAO_ADDR": publisher.TUNNEL, "VAULT_ADDR": publisher.TUNNEL,
|
||||
"BAO_TOKEN": "synthetic-parent", "VAULT_TOKEN": "synthetic-parent",
|
||||
"OPENBAO_TOKEN": "synthetic-inherited-alias"}
|
||||
command = ResolvedFetch(argv=[str(client), "login"])
|
||||
with patch.dict(os.environ, environment, clear=True):
|
||||
try:
|
||||
proxy_attended_login_exec(command, child_argv=child)
|
||||
except AttendedLoginError as error:
|
||||
assert error.exit_code == 11 and error.outcome == "child_failed"
|
||||
else:
|
||||
raise AssertionError("unsanitized alias must refuse before custody")
|
||||
assert not proof.exists()
|
||||
assert not (caller / ".warden-attended-login").exists()
|
||||
with patch.dict(os.environ, publisher.owner_login_environment(environment), clear=True):
|
||||
assert proxy_attended_login_exec(command, child_argv=child) == 0
|
||||
assert all(json.loads(proof.read_text()).values())
|
||||
assert not (caller / ".warden-attended-login").exists()
|
||||
print(json.dumps({"status": "passed", "installed_warden_envelope": True,
|
||||
"unsanitized_alias_refused": True, "sanitized_handoff_passed": True,
|
||||
"synthetic_session_storage_removed": True, "native_login": False,
|
||||
"provider_access": False, "real_credentials_used": False}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -39,6 +39,8 @@ CIPHER_SHA256 = "4915288384e3ba8a87f1427b8e10106ee7241c04184adb97f439e57846bef76
|
|||
CIPHER_BYTES = 182521
|
||||
LANE = "platform/data/workloads/railiance/backup/offsite-lane"
|
||||
OPERATOR = "operators/data/nextcloud/backup"
|
||||
TUNNEL = "http://127.0.0.1:18200"
|
||||
TOKEN_ENV = ("BAO_TOKEN", "VAULT_TOKEN", "OPENBAO_TOKEN")
|
||||
PINS = {
|
||||
"openbao-attended-exec.py": "53fb5fb38ad17c4b06d7969a4d8968d976c8ebc8f1abfeb9a191aa43dc54a5c3",
|
||||
"migrate_nextcloud_backup_account.py": "d3285f403f3c947f75c3288d989152e59ab9a8f5542bcf901bd0a06b0f3e050c",
|
||||
|
|
@ -71,20 +73,52 @@ def source(directory):
|
|||
return directory / OBJECT
|
||||
|
||||
|
||||
def owner_login_environment(environ=None):
|
||||
env = dict(os.environ if environ is None else environ)
|
||||
for key in TOKEN_ENV:
|
||||
env.pop(key, None)
|
||||
env["BAO_ADDR"] = env["VAULT_ADDR"] = TUNNEL
|
||||
return env
|
||||
|
||||
|
||||
def owner_handoff_checks():
|
||||
home = Path.home()
|
||||
shape = home.parent.name == ".warden-attended-login" and home.name.startswith("session-")
|
||||
private = False
|
||||
if shape:
|
||||
try:
|
||||
private_directory(home)
|
||||
private_directory(home.parent)
|
||||
private = True
|
||||
except (OSError, Refused):
|
||||
pass
|
||||
return {"routing_marker_present": bool(os.environ.get("WARDEN_ROUTING_CATALOG")),
|
||||
"operator_tunnel_matches": os.environ.get("BAO_ADDR") == os.environ.get("VAULT_ADDR") == TUNNEL,
|
||||
"no_ambient_token_variables": not any(os.environ.get(k) for k in TOKEN_ENV),
|
||||
"isolated_home_shape": shape, "isolated_home_private": private}
|
||||
|
||||
|
||||
def require_owner_handoff():
|
||||
checks = owner_handoff_checks()
|
||||
for key, reason in [("routing_marker_present", "owner_routing_marker_missing"),
|
||||
("operator_tunnel_matches", "owner_tunnel_required"),
|
||||
("no_ambient_token_variables", "ambient_owner_token_refused"),
|
||||
("isolated_home_shape", "isolated_owner_home_required"),
|
||||
("isolated_home_private", "private_owner_home_required")]:
|
||||
require(checks[key], reason)
|
||||
|
||||
|
||||
def custody(helper):
|
||||
# Reuse the owner's contained OIDC session, never a token environment/file
|
||||
# handoff. Warden revokes and removes its private helper after the child.
|
||||
require(os.environ.get("WARDEN_ROUTING_CATALOG")
|
||||
and os.environ.get("BAO_ADDR") == "http://127.0.0.1:18200"
|
||||
and not any(os.environ.get(k) for k in ("BAO_TOKEN", "VAULT_TOKEN", "OPENBAO_TOKEN")),
|
||||
"contained_owner_login_required")
|
||||
require_owner_handoff()
|
||||
def read(args, reason):
|
||||
try:
|
||||
return helper.data(helper.bao(args))
|
||||
except Exception:
|
||||
raise Refused(reason) from None
|
||||
identity = read(["token", "lookup", "-format=json"], "owner_identity_lookup_failed")["data"]
|
||||
policies = set(identity.get("policies", []))
|
||||
identity = read(["read", "-format=json", "auth/token/lookup-self"], "owner_identity_lookup_failed")["data"]
|
||||
policies = set(identity.get("policies") or []) | set(identity.get("identity_policies") or [])
|
||||
require({"platform-admin", "operator-custody"} <= policies
|
||||
and not policies & {"root", "agent-high-risk-boundary"}, "owner_recovery_identity_required")
|
||||
lane = read(["read", "-format=json", LANE], "offsite_custody_read_failed")["data"]
|
||||
|
|
@ -269,6 +303,7 @@ def child(directory, output):
|
|||
"stage": "owner_custody"}
|
||||
try:
|
||||
helper = owner_helpers()
|
||||
receipt["owner_handoff_checks"] = owner_handoff_checks()
|
||||
publish(directory, custody(helper), Provider(helper), receipt)
|
||||
except BaseException as error:
|
||||
receipt["failure"] = str(error) if isinstance(error, Refused) else "provider_recovery_failed"
|
||||
|
|
@ -281,6 +316,14 @@ def child(directory, output):
|
|||
return 0 if receipt["status"] == "published_read_back_and_decrypted" else 1
|
||||
|
||||
|
||||
def session_outcome(exit_code):
|
||||
# Warden 11/12 can mean a revoked or revocation-unconfirmed failed child.
|
||||
# Only 0 confirms the whole envelope; 14 overrides any earlier phase.
|
||||
return {"session_cleanup_confirmed": True if exit_code == 0 else False if exit_code in {13, 14} else None,
|
||||
"session_revocation_confirmed": True if exit_code == 0 else False if exit_code == 13 else None,
|
||||
"session_storage_cleanup_confirmed": True if exit_code in {0, 11, 12, 13} else False if exit_code == 14 else None}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--execute", action="store_true")
|
||||
|
|
@ -310,14 +353,14 @@ def main():
|
|||
command = [sys.executable, str(PLATFORM / "scripts/openbao-attended-exec.py"), "--",
|
||||
sys.executable, str(Path(__file__).resolve()), "--contained", "--directory", str(directory),
|
||||
"--receipt", str(output)]
|
||||
result = subprocess.run(command, check=False)
|
||||
result = subprocess.run(command, check=False, env=owner_login_environment())
|
||||
if output.exists():
|
||||
export.regular(output, os.geteuid())
|
||||
receipt = json.loads(output.read_text())
|
||||
else:
|
||||
receipt = {"status": "owner_login_or_child_not_completed", "provider_outcome": "unconfirmed"}
|
||||
receipt.update(warden_exit_code=result.returncode, receipt_file=str(output),
|
||||
session_cleanup_confirmed=result.returncode == 0)
|
||||
**session_outcome(result.returncode))
|
||||
flags = os.O_WRONLY | os.O_NOFOLLOW | (os.O_TRUNC if output.exists() else os.O_CREAT | os.O_EXCL)
|
||||
fd = os.open(output, flags, 0o600)
|
||||
with os.fdopen(fd, "w") as stream:
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ id: RAPPS-WP-0014-T03
|
|||
status: wait
|
||||
needs_human: true
|
||||
decision_id: d06307e5-2737-461a-a2f4-a12c3b492a9a
|
||||
intervention_note: "Matched populated capture, resume and encrypted workstation copy pass. Both accounts work after restart, with an Übersicht omission under UI review. Next: founder-run tools/vergabe_pilot_publish.py --execute for the prepared contained owner publication/readback/decryption ceremony. Isolated restore, access/rollback and operating ownership remain open."
|
||||
intervention_note: "Capture/restart and encrypted workstation copy pass. First provider attempt logged in but the publisher handoff guard refused before credential/provider access; Warden reported revocation. Environment preparation and lifecycle reporting fixed and tested through the installed Warden envelope offline. Retry the same publisher --execute command with fresh founder login. Provider evidence, isolated restore, access/rollback and operating ownership remain open."
|
||||
priority: high
|
||||
assignee: the-custodian
|
||||
blocking_reason: "Matched populated capture, restart and encrypted workstation copy pass with overview UI follow-up. Await provider publication/independent readback, escrow decryption, isolated restored workflow, access checks, compatible rollback and operating ownership."
|
||||
|
|
@ -451,6 +451,27 @@ pass, including real age roundtrip with mocked provider/custody. Native executio
|
|||
awaits the founder; Warden cleanup is distinct from data verification. T03 stays
|
||||
waiting for provider evidence and the remaining restore/rollback/access/owner gates.
|
||||
|
||||
2026-11-06 first provider attempt: Warden login succeeded, but the publisher
|
||||
returned `contained_owner_login_required` at `owner_custody` (0.01 seconds,
|
||||
Warden exit 11). Source ordering places this before token metadata, credential
|
||||
reads or provider calls. Warden's terminal message confirms revocation. The
|
||||
publisher's old `session_cleanup_confirmed: false` conflated a failed child with
|
||||
cleanup failure; preserve the original receipt and this correction:
|
||||
`docs/evidence/2026-11-06-vergabe-provider-custody-failure-return.json`.
|
||||
|
||||
Revision 2 clears BAO_TOKEN, VAULT_TOKEN and OPENBAO_TOKEN before the existing
|
||||
wrapper, checks private Warden HOME-directory metadata (no helper inspection),
|
||||
records boolean handoff diagnostics, uses the owner's API self-lookup form and
|
||||
combines effective token/identity policies. Exit 11 no longer claims revocation
|
||||
failure from its code alone. The first receipt did not identify which guard
|
||||
condition failed; the source mismatch for the third inherited token alias is
|
||||
reproduced by an offline test through the installed real Warden envelope with a
|
||||
fake Bao client. The normalized handoff and session cleanup pass that test.
|
||||
Fifteen publisher / 45 total pilot checks pass; no native auth or provider call
|
||||
occurs in those tests. Retry the same reviewed provider command; the earlier
|
||||
go persists, while fresh login is needed because that session was revoked. No
|
||||
provider scope, role, policy, credential grant or live app change is introduced.
|
||||
|
||||
|
||||
## Validate synthetic sizing against the exact release and shared contention
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue