fix: reject broken runtime launchers and preserve failed proof evidence
Some checks failed
Governed runtime contract / contract (push) Failing after 16s
Some checks failed
Governed runtime contract / contract (push) Failing after 16s
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e387-534d-70e3-ad53-4ea05676db8c
This commit is contained in:
parent
4e666d6fa3
commit
43e621439a
26 changed files with 1367 additions and 3 deletions
40
docs/evidence/2026-09-27-grant-aware-worker-rollout.json
Normal file
40
docs/evidence/2026-09-27-grant-aware-worker-rollout.json
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"observed_at": "2026-09-27T20:01:04.485643+00:00",
|
||||
"activity_revision": "428f2d71b0f5ade11457e16d7b6341f571f6735f",
|
||||
"platform_revision": "c3607fffeade70acd361a4757e72a062725cb83a",
|
||||
"deployment_diff": "worker image only; reuse already deployed API image; API/router/config/schedules unchanged",
|
||||
"tests": {
|
||||
"focused_grant_tests_passed": 21,
|
||||
"queue_repair_guard_tests_passed": 9,
|
||||
"native_binding_tests_passed": 5,
|
||||
"actual_image_rule_emission_queue_support": true
|
||||
},
|
||||
"argocd": {
|
||||
"sync": "Synced",
|
||||
"health": "Healthy",
|
||||
"phase": "Succeeded"
|
||||
},
|
||||
"deployments": [
|
||||
{
|
||||
"name": "actcore-api",
|
||||
"generation": 50,
|
||||
"observed_generation": 50,
|
||||
"ready": 1,
|
||||
"image": "forgejo.coulomb.social/coulomb/activity-core@sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd"
|
||||
},
|
||||
{
|
||||
"name": "actcore-worker",
|
||||
"generation": 63,
|
||||
"observed_generation": 63,
|
||||
"ready": 1,
|
||||
"image": "forgejo.coulomb.social/coulomb/activity-core@sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd"
|
||||
},
|
||||
{
|
||||
"name": "actcore-event-router",
|
||||
"generation": 33,
|
||||
"observed_generation": 33,
|
||||
"ready": 1,
|
||||
"image": "forgejo.coulomb.social/coulomb/activity-core@sha256:cd4e924c2809f0d1d319e53a9ddd20db2e88a1b543d62e6442702c184b7842f4"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
[
|
||||
{
|
||||
"memo_id": "metered-20260927-provider-apply",
|
||||
"disposition": "accept",
|
||||
"approval_id": "0c05bd0a-f81f-451d-840c-5565628e2edc",
|
||||
"native_submission_state": "confirmed",
|
||||
"approved_at": "2026-09-27T19:23:59+00:00"
|
||||
},
|
||||
{
|
||||
"memo_id": "metered-20260927-provider-exec",
|
||||
"disposition": "accept",
|
||||
"approval_id": "7b32443a-a817-400c-a130-01b9ef04c8ee",
|
||||
"native_submission_state": "confirmed",
|
||||
"approved_at": "2026-09-27T19:24:15+00:00"
|
||||
},
|
||||
{
|
||||
"memo_id": "metered-20260927-provider-verify",
|
||||
"disposition": "accept",
|
||||
"approval_id": "47f118a3-a86c-43ad-969d-42e09a0f45bb",
|
||||
"native_submission_state": "confirmed",
|
||||
"approved_at": "2026-09-27T19:24:32+00:00"
|
||||
},
|
||||
{
|
||||
"memo_id": "metered-20260927-worker-apply",
|
||||
"disposition": "accept",
|
||||
"approval_id": "2ce76d7f-01c3-4b63-8476-8d1230679769",
|
||||
"native_submission_state": "confirmed",
|
||||
"approved_at": "2026-09-27T19:24:43+00:00"
|
||||
},
|
||||
{
|
||||
"memo_id": "metered-20260927-worker-exec",
|
||||
"disposition": "accept",
|
||||
"approval_id": "dc22666a-d5d7-46bc-9490-ebe9fe09dc38",
|
||||
"native_submission_state": "confirmed",
|
||||
"approved_at": "2026-09-27T19:24:55+00:00"
|
||||
},
|
||||
{
|
||||
"memo_id": "metered-20260927-worker-verify",
|
||||
"disposition": "accept",
|
||||
"approval_id": "c2af4bcf-15b4-4305-aac1-acc7e4dcb099",
|
||||
"native_submission_state": "confirmed",
|
||||
"approved_at": "2026-09-27T19:25:10+00:00"
|
||||
}
|
||||
]
|
||||
12
docs/evidence/2026-09-27-metered-attended-execution.json
Normal file
12
docs/evidence/2026-09-27-metered-attended-execution.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"phase": "failed",
|
||||
"status": "failed",
|
||||
"credential_values_emitted": false,
|
||||
"reader_scope_verified": true,
|
||||
"kv_version": 1,
|
||||
"named_owner_images_verified": true,
|
||||
"remote_exit_code": 1,
|
||||
"owner_forwards_closed": true,
|
||||
"failure_type": "ValueError",
|
||||
"failure_code": "attended_execution_failed"
|
||||
}
|
||||
12
docs/evidence/2026-09-27-metered-attended-resume.json
Normal file
12
docs/evidence/2026-09-27-metered-attended-resume.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"phase": "failed",
|
||||
"status": "failed",
|
||||
"credential_values_emitted": false,
|
||||
"reader_scope_verified": true,
|
||||
"kv_version": 1,
|
||||
"named_owner_images_verified": true,
|
||||
"remote_exit_code": 1,
|
||||
"owner_forwards_closed": true,
|
||||
"failure_type": "ValueError",
|
||||
"failure_code": "attended_execution_failed"
|
||||
}
|
||||
32
docs/evidence/2026-09-27-metered-native-consumes.json
Normal file
32
docs/evidence/2026-09-27-metered-native-consumes.json
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"0c05bd0a-f81f-451d-840c-5565628e2edc": {
|
||||
"approval_id": "0c05bd0a-f81f-451d-840c-5565628e2edc",
|
||||
"valid_now": false,
|
||||
"consumed": true
|
||||
},
|
||||
"47f118a3-a86c-43ad-969d-42e09a0f45bb": {
|
||||
"approval_id": "47f118a3-a86c-43ad-969d-42e09a0f45bb",
|
||||
"valid_now": false,
|
||||
"consumed": true
|
||||
},
|
||||
"7b32443a-a817-400c-a130-01b9ef04c8ee": {
|
||||
"approval_id": "7b32443a-a817-400c-a130-01b9ef04c8ee",
|
||||
"valid_now": false,
|
||||
"consumed": true
|
||||
},
|
||||
"2ce76d7f-01c3-4b63-8476-8d1230679769": {
|
||||
"approval_id": "2ce76d7f-01c3-4b63-8476-8d1230679769",
|
||||
"valid_now": false,
|
||||
"consumed": true
|
||||
},
|
||||
"c2af4bcf-15b4-4305-aac1-acc7e4dcb099": {
|
||||
"approval_id": "c2af4bcf-15b4-4305-aac1-acc7e4dcb099",
|
||||
"valid_now": false,
|
||||
"consumed": true
|
||||
},
|
||||
"dc22666a-d5d7-46bc-9490-ebe9fe09dc38": {
|
||||
"approval_id": "dc22666a-d5d7-46bc-9490-ebe9fe09dc38",
|
||||
"valid_now": false,
|
||||
"consumed": true
|
||||
}
|
||||
}
|
||||
60
docs/evidence/2026-09-27-metered-native-execution.json
Normal file
60
docs/evidence/2026-09-27-metered-native-execution.json
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"status": "failed",
|
||||
"phase": "one_trigger_started",
|
||||
"observed_at": "2026-09-27T19:48:11.627308+00:00",
|
||||
"actions": [
|
||||
{
|
||||
"catalog": "glas-claude-agent-dev-anthropic",
|
||||
"action": "apply",
|
||||
"approval_id": "0c05bd0a-f81f-451d-840c-5565628e2edc",
|
||||
"exit_code": 0,
|
||||
"limits": {
|
||||
"token_ttl": 300,
|
||||
"token_max_ttl": 900,
|
||||
"secret_id_ttl": 300,
|
||||
"secret_id_num_uses": 1,
|
||||
"token_num_uses": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"catalog": "activity-core-metered-worker-token",
|
||||
"action": "apply",
|
||||
"approval_id": "2ce76d7f-01c3-4b63-8476-8d1230679769",
|
||||
"exit_code": 0,
|
||||
"limits": {
|
||||
"token_ttl": 300,
|
||||
"token_max_ttl": 900,
|
||||
"secret_id_ttl": 300,
|
||||
"secret_id_num_uses": 1,
|
||||
"token_num_uses": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"catalog": "glas-claude-agent-dev-anthropic",
|
||||
"action": "verify",
|
||||
"approval_id": "47f118a3-a86c-43ad-969d-42e09a0f45bb",
|
||||
"exit_code": 0,
|
||||
"sibling_denied": true,
|
||||
"metadata_denied": true,
|
||||
"session_revoked": true,
|
||||
"revoked_lookup_status": 403
|
||||
},
|
||||
{
|
||||
"catalog": "activity-core-metered-worker-token",
|
||||
"action": "verify",
|
||||
"approval_id": "c2af4bcf-15b4-4305-aac1-acc7e4dcb099",
|
||||
"exit_code": 0,
|
||||
"sibling_denied": true,
|
||||
"metadata_denied": true,
|
||||
"session_revoked": true,
|
||||
"revoked_lookup_status": 403
|
||||
}
|
||||
],
|
||||
"automatic_retry": false,
|
||||
"trigger": {
|
||||
"workflow_id": "activity-5bae5505-77f1-5ba3-8dfa-2e10ed15531e:manual-dab6c4c7-db03-4887-a17b-9d99b752482e",
|
||||
"trigger_key": "manual-dab6c4c7-db03-4887-a17b-9d99b752482e"
|
||||
},
|
||||
"failure_type": "ValueError",
|
||||
"failure_code": "natural_queue_binding_refused"
|
||||
}
|
||||
63
docs/evidence/2026-09-27-metered-native-resume.json
Normal file
63
docs/evidence/2026-09-27-metered-native-resume.json
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"status": "attempt_failed",
|
||||
"phase": "one_exec_returned",
|
||||
"observed_at": "2026-09-27T20:01:30.849242+00:00",
|
||||
"actions": [
|
||||
{
|
||||
"catalog": "glas-claude-agent-dev-anthropic",
|
||||
"action": "exec",
|
||||
"approval_id": "7b32443a-a817-400c-a130-01b9ef04c8ee",
|
||||
"exit_code": 1
|
||||
},
|
||||
{
|
||||
"catalog": "activity-core-metered-worker-token",
|
||||
"action": "exec",
|
||||
"approval_id": "dc22666a-d5d7-46bc-9490-ebe9fe09dc38",
|
||||
"exit_code": 1
|
||||
}
|
||||
],
|
||||
"automatic_retry": false,
|
||||
"prior_receipt": "execution.json",
|
||||
"completed_actions_not_replayed": [
|
||||
"provider/apply",
|
||||
"worker/apply",
|
||||
"provider/verify",
|
||||
"worker/verify",
|
||||
"queue/trigger"
|
||||
],
|
||||
"trigger": {
|
||||
"workflow_id": "activity-5bae5505-77f1-5ba3-8dfa-2e10ed15531e:manual-dab6c4c7-db03-4887-a17b-9d99b752482e",
|
||||
"trigger_key": "manual-dab6c4c7-db03-4887-a17b-9d99b752482e"
|
||||
},
|
||||
"queued_run": {
|
||||
"id": "6efa9436-6a91-47c0-94e5-b31b5a0e7e3a",
|
||||
"state": "open",
|
||||
"attempt": 0,
|
||||
"claim_owner": null,
|
||||
"target_repo": "hfact-glas-proof",
|
||||
"harness_profile_ref": "harness.agent-dev-local@1.1.1",
|
||||
"repository_grant": {
|
||||
"publish": false,
|
||||
"version": "1",
|
||||
"commit_count": {
|
||||
"max": 1,
|
||||
"min": 1
|
||||
},
|
||||
"allowed_paths": [
|
||||
"PROOF.md"
|
||||
]
|
||||
},
|
||||
"triggering_event_id": "manual-dab6c4c7-db03-4887-a17b-9d99b752482e"
|
||||
},
|
||||
"exec_exit_code": 1,
|
||||
"owner_results": [
|
||||
{
|
||||
"ok": false,
|
||||
"claimed": true,
|
||||
"empty": false,
|
||||
"run_id": "6efa9436-6a91-47c0-94e5-b31b5a0e7e3a",
|
||||
"ops_state": "failed"
|
||||
}
|
||||
],
|
||||
"private_runtime_removed": true
|
||||
}
|
||||
39
docs/evidence/2026-09-27-metered-postflight.json
Normal file
39
docs/evidence/2026-09-27-metered-postflight.json
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"runtime_sha256": "b6e4e8a429393d68831c996a65c0489664205df969ac9882e581983ec2da4969",
|
||||
"repository_head": "679d23e0517707ba63e25399b5262f0d2f37315d",
|
||||
"repository_clean": true,
|
||||
"repository_lock_available": true,
|
||||
"sandbox": {
|
||||
"sandbox_id": "b67907f1",
|
||||
"state": "destroyed",
|
||||
"created_at": "2026-09-27T20:01:43.835992Z",
|
||||
"destroyed_at": "2026-09-27T20:01:45.650937Z"
|
||||
},
|
||||
"removed_paths": {
|
||||
"workspace_dir": true
|
||||
},
|
||||
"private_credential_directories_remaining": 0,
|
||||
"close_outbox_pending": 0,
|
||||
"close_outbox_quarantined": 0,
|
||||
"spend_reservations": [
|
||||
{
|
||||
"run_id": "6efa9436-6a91-47c0-94e5-b31b5a0e7e3a",
|
||||
"definition_id": "5bae5505-77f1-5ba3-8dfa-2e10ed15531e",
|
||||
"idempotency_key": "5bae5505-77f1-5ba3-8dfa-2e10ed15531e:execute-hfact-glas-metered-proof:manual-dab6c4c7-db03-4887-a17b-9d99b752482e",
|
||||
"attempt": 1,
|
||||
"state": "held",
|
||||
"liability": 10000000,
|
||||
"start_day": "2026-09-27",
|
||||
"end_day": null,
|
||||
"observed_usd": null,
|
||||
"receipt": null
|
||||
}
|
||||
],
|
||||
"provider_request_reservations": [],
|
||||
"request_routes": [
|
||||
{
|
||||
"run_id": "6efa9436-6a91-47c0-94e5-b31b5a0e7e3a",
|
||||
"revoked": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"status": "applied",
|
||||
"observed_at": "2026-09-27T19:59:50.642053+00:00",
|
||||
"worker_pod": "actcore-worker-659497dcf9-rvf4h",
|
||||
"worker_image": "sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd",
|
||||
"run_id": "6efa9436-6a91-47c0-94e5-b31b5a0e7e3a",
|
||||
"definition_id": "5bae5505-77f1-5ba3-8dfa-2e10ed15531e",
|
||||
"definition_version": 1,
|
||||
"triggering_event_id": "manual-dab6c4c7-db03-4887-a17b-9d99b752482e",
|
||||
"source": "typed_existing_definition_rule",
|
||||
"grant_id": "656911f7dff83e871434b415f0cee685",
|
||||
"grant": {
|
||||
"version": "1",
|
||||
"allowed_paths": [
|
||||
"PROOF.md"
|
||||
],
|
||||
"commit_count": {
|
||||
"min": 1,
|
||||
"max": 1
|
||||
},
|
||||
"publish": false
|
||||
},
|
||||
"attempt": 0,
|
||||
"claim_owner": null,
|
||||
"new_trigger": false,
|
||||
"new_task": false
|
||||
}
|
||||
20
docs/evidence/2026-09-27-metered-renewal-installation.json
Normal file
20
docs/evidence/2026-09-27-metered-renewal-installation.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"status": "installed",
|
||||
"observed_at": "2026-09-27T19:39:35.079399+00:00",
|
||||
"old_counts": {
|
||||
"reservations": 0,
|
||||
"request_routes": 0,
|
||||
"request_reservations": 0
|
||||
},
|
||||
"old_ledger_preserved": true,
|
||||
"new_ledger": "/home/tegwick/hfact/owner-metered/spend-tool-proof-renewal-20260927.sqlite3",
|
||||
"old_dispatch_pins_retired": true,
|
||||
"owner_check": {
|
||||
"ok": true,
|
||||
"check_only": true,
|
||||
"dispatch_enabled": false,
|
||||
"policy_sha256": "de3d01c9f4753994e8f73c111cde328a649e05dbb2563c37b5c12d86a488b2fa",
|
||||
"runtime_sha256": "b6e4e8a429393d68831c996a65c0489664205df969ac9882e581983ec2da4969"
|
||||
},
|
||||
"dispatches": 0
|
||||
}
|
||||
109
docs/evidence/2026-09-27-metered-terminal-delivery.json
Normal file
109
docs/evidence/2026-09-27-metered-terminal-delivery.json
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
{
|
||||
"source": "railiance01/native-metered-20260927/native-evidence; allowlisted fields only",
|
||||
"native_records": [
|
||||
{
|
||||
"action": "apply",
|
||||
"catalog_id": "glas-claude-agent-dev-anthropic",
|
||||
"result": "applied",
|
||||
"record_id": "53136c39-5fb6-4f61-8d73-231c5d0fe57d",
|
||||
"detail": {
|
||||
"approval_consumed": true,
|
||||
"approval_id": "0c05bd0a-f81f-451d-840c-5565628e2edc",
|
||||
"approval_consume_idempotent": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "apply",
|
||||
"catalog_id": "activity-core-metered-worker-token",
|
||||
"result": "applied",
|
||||
"record_id": "f218efea-a990-4393-a99c-e405050a349e",
|
||||
"detail": {
|
||||
"approval_consumed": true,
|
||||
"approval_id": "2ce76d7f-01c3-4b63-8476-8d1230679769",
|
||||
"approval_consume_idempotent": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "verify",
|
||||
"catalog_id": "glas-claude-agent-dev-anthropic",
|
||||
"result": "pass",
|
||||
"record_id": "9a3347b1-9b1c-42a1-a504-ce791fdf1215",
|
||||
"detail": {
|
||||
"approval_consumed": true,
|
||||
"approval_id": "47f118a3-a86c-43ad-969d-42e09a0f45bb",
|
||||
"approval_consume_idempotent": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "verify",
|
||||
"catalog_id": "activity-core-metered-worker-token",
|
||||
"result": "pass",
|
||||
"record_id": "43517ef4-ab0e-47c4-ae96-4284a6f67b2e",
|
||||
"detail": {
|
||||
"approval_consumed": true,
|
||||
"approval_id": "c2af4bcf-15b4-4305-aac1-acc7e4dcb099",
|
||||
"approval_consume_idempotent": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "exec",
|
||||
"catalog_id": "activity-core-metered-worker-token",
|
||||
"result": "exit-1",
|
||||
"record_id": "7b3f492c-cf80-4e4d-8158-7e4504b136ca",
|
||||
"detail": {
|
||||
"approval_consumed": true,
|
||||
"approval_id": "dc22666a-d5d7-46bc-9490-ebe9fe09dc38",
|
||||
"approval_consume_idempotent": false,
|
||||
"session": {
|
||||
"established": true,
|
||||
"revocation_attempted": true,
|
||||
"revocation_succeeded": true,
|
||||
"session_handle": "a0f9a121b064"
|
||||
},
|
||||
"companion_of": "glas-claude-agent-dev-anthropic",
|
||||
"exec_owner_sha256": "310600eab467ed79fc3851178a065de12d57d3ada5bcf68d9c364ed11b3ee50f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "exec",
|
||||
"catalog_id": "glas-claude-agent-dev-anthropic",
|
||||
"result": "exit-1",
|
||||
"record_id": "1b6fabda-dbe5-4fe3-a74e-b9abf62050c1",
|
||||
"detail": {
|
||||
"approval_consumed": true,
|
||||
"approval_id": "7b32443a-a817-400c-a130-01b9ef04c8ee",
|
||||
"approval_consume_idempotent": false,
|
||||
"session": {
|
||||
"established": true,
|
||||
"revocation_attempted": true,
|
||||
"revocation_succeeded": true,
|
||||
"session_handle": "d7e5f59e383d"
|
||||
},
|
||||
"companions": [
|
||||
"activity-core-metered-worker-token"
|
||||
],
|
||||
"exec_owner_sha256": "310600eab467ed79fc3851178a065de12d57d3ada5bcf68d9c364ed11b3ee50f"
|
||||
}
|
||||
}
|
||||
],
|
||||
"terminal_queue": {
|
||||
"id": "6efa9436-6a91-47c0-94e5-b31b5a0e7e3a",
|
||||
"state": "failed",
|
||||
"attempt": 1,
|
||||
"claim_owner": "rein-aharness-metered@railiance01",
|
||||
"lease_until": null
|
||||
},
|
||||
"verification": {
|
||||
"full_suite": "406 passed, 9 skipped",
|
||||
"focused_owner_claim_spend_glas": "99 passed (includes new close-evidence test)",
|
||||
"native_procedure_and_queue_guards": "14 passed",
|
||||
"sandbox_builder": "12 passed",
|
||||
"activity_core_grant": "21 passed"
|
||||
},
|
||||
"remaining": {
|
||||
"workplan": "blocked",
|
||||
"parent_eur_reservation": "10.00 held; observed USD unknown",
|
||||
"retry_authorized": false,
|
||||
"corrected_artifact_admitted": false
|
||||
}
|
||||
}
|
||||
97
docs/native-metered-proof.md
Normal file
97
docs/native-metered-proof.md
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# Attended disposable proof — 2026-09-27
|
||||
|
||||
Existing work: REINAH-WP-0003-T05/T06 and SECRETS-WP-0009-T03.
|
||||
No new workplan, publication, automatic retry, or factory operating admission.
|
||||
|
||||
The operator accepted replacement spend memo `infd-20260927-b02`, confirmed
|
||||
USD 10 including tax/conversion fees fits EUR 10, and accepted all six
|
||||
`metered-20260927-{provider,worker}-{apply,verify,exec}` decisions. Native
|
||||
submission confirmations and replacement host installation are separate receipts
|
||||
in `docs/evidence/2026-09-27-metered-*.json`. The earlier expired spend grant is
|
||||
preserved and never used.
|
||||
|
||||
`scripts/metered-native-owner.py` checks the frozen six-request packet and exact
|
||||
recipient files before native claim/PDP checks. The provider's human-control
|
||||
flag and the worker companion's ordinary flag remain unchanged. Apply and
|
||||
verify each consume their own native approval before OpenBao effects. Exec
|
||||
uses the existing Secrets Engine primary/companion consume and delivery code;
|
||||
the procedure does not manufacture decisions, claims, or delivery state.
|
||||
|
||||
The attended workstation wrapper `scripts/attended-metered-proof.py` follows
|
||||
the existing scoped approval-client reader and nested platform-admin OIDC
|
||||
procedure. The operator-controlled SSH session runs the controller on Railiance
|
||||
because the approved command and owner-file bindings name that host. Client
|
||||
secret, short-lived operator/negative-test tokens and PDP caller token cross
|
||||
encrypted SSH stdin only. The host uses a fresh owner-only temporary directory
|
||||
on `/run/user/1000` tmpfs. Approval bearer tokens are minted in memory. No
|
||||
cluster reader, persistent service, credential rotation or new custody path is
|
||||
created. Warden self-revokes both attended sessions; private files and the
|
||||
named-pod forwarding processes are removed on normal exit, including failure.
|
||||
The delivered model child receives only its catalog-bound environment plus
|
||||
its separately approved provider and worker credentials.
|
||||
|
||||
Before activation, the installer locks and checks all three old ledger tables,
|
||||
refusing any prior reservation or liability. It preserves the old ledger and
|
||||
backs up both old configuration files. A new private ledger is initialized
|
||||
without resetting old evidence. Replacement file hashes retire dispatch using
|
||||
the old catalog pins. The immutable runtime's backend-free owner check passes.
|
||||
|
||||
The single trigger occurs after both lanes verify. The controller waits for
|
||||
exactly one open, unclaimed, attempt-zero task with the admitted profile and
|
||||
one-file/one-commit/no-publication grant. It records trigger and exec intent
|
||||
before either action. An existing execution receipt refuses all replays,
|
||||
including uncertain/failed attempts. The scheduled definition stays disabled.
|
||||
|
||||
The approved maximum request hold remains USD 4.64; at most two such holds fit
|
||||
the USD 10 liability cap. The [provider tariff](https://platform.claude.com/docs/en/about-claude/pricing)
|
||||
was rechecked immediately before activation: Sonnet 5 global standard output is
|
||||
USD 10/million tokens; the conservative input bound of USD 4/million covers
|
||||
one-hour cache writes. The proof has no authority to enlarge these limits.
|
||||
|
||||
After interruption, inspect the durable local/remote receipts and native
|
||||
consume state before any further action. Explicitly reconcile remaining
|
||||
`metered-native-*` / `metered-attended-*` private runtime directories and any
|
||||
open queue item; never rerun the command as a cleanup strategy. Hard-kill
|
||||
cleanup is not claimed by this wrapper. Provider-request reservations remain
|
||||
conservative until reconciled. T04's tenant migrations and broader T06
|
||||
acceptance requirements remain in the existing workplan.
|
||||
|
||||
## Actual execution and corrections
|
||||
|
||||
All six decisions were accepted and consumed once. Both lane apply/verify
|
||||
operations passed; exec delivered through two AppRole sessions whose revocation
|
||||
succeeded. The single definition trigger initially produced a row with no grant:
|
||||
the deployed Activity Core worker predated the API's grant-carriage support.
|
||||
Activity Core `428f2d7` and Platform `c3607ff` changed only the worker image to
|
||||
the already-deployed grant-aware API image through the existing GitOps parent
|
||||
and child applications. Argo reports Synced/Healthy/Succeeded.
|
||||
|
||||
The explicit repair script validates and locks the original disabled definition
|
||||
and original open, unclaimed, attempt-zero job. It copies only the exact typed
|
||||
grant from that definition; it creates no row or trigger. Nine negative/positive
|
||||
guard tests pass. This repair is recorded separately and is not represented as
|
||||
successful natural grant carriage by the old producer.
|
||||
|
||||
The `resume` mode is limited to that recorded pre-execution queue-binding failure.
|
||||
It verifies the four completed native actions, installed policies/role limits,
|
||||
zero prior request/reservation counts, and the same repaired job. It repeats
|
||||
neither apply/verify nor queue creation. Both remaining exec approvals were then
|
||||
consumed. There is no further resume path for the attempted job.
|
||||
|
||||
Job `6efa9436-6a91-47c0-94e5-b31b5a0e7e3a` closed `failed`, attempt 1, with its
|
||||
lease cleared. Its installed Python launchers still referenced a deleted build
|
||||
directory: uv's long-path shell trampoline had escaped the builder's direct
|
||||
shebang rewrite. A no-credential synthetic probe reproduced exit 127 for both
|
||||
launchers inside the actual pinned bwrap artifact, with zero provider forwards.
|
||||
The runtime builder correction is Sand-boxer `81b5fcf`; relocation tests execute
|
||||
after the old build directory disappears. Rein now also rejects these launchers
|
||||
before claim and retains bounded gateway stage/cleanup facts on accounting refusal.
|
||||
|
||||
The approved artifact itself remains unchanged. Sandbox `b67907f1` and its
|
||||
workspace are destroyed; the repository is clean at its original commit, the
|
||||
lock is available, and no close-outbox item or private credential directory is
|
||||
left over. The request route is revoked and there are no provider-request
|
||||
reservations. The parent EUR 10 reservation remains held; observed billing was
|
||||
not invented and the ledger was not reset. A corrected artifact and its changed
|
||||
pins need admission, held-liability reconciliation remains an owner action, and
|
||||
another attempt requires separate authority. REINAH-WP-0003 stays blocked.
|
||||
|
|
@ -47,6 +47,9 @@ profile/descriptor digests, operational readiness, model, empty-egress bwrap pro
|
|||
and runtime digest are checked before claim. Runtime, owner state and target checkout
|
||||
must not overlap. Provision parent and request ledgers as separate reviewed actions.
|
||||
The normal ACTIVITY_CORE/AGENT_HARNESS worker and state configuration still applies.
|
||||
The governed Python console launchers must be executable regular files naming
|
||||
`/opt/sandboxer/runtime/bin/python3`, rather than a build-host interpreter.
|
||||
The installed bwrap proof also runs their `--help` commands before any model request.
|
||||
|
||||
This config is not an authorization decision or a custody provenance proof. The
|
||||
invoking credential engine must already have passed its exact action approval,
|
||||
|
|
|
|||
|
|
@ -627,6 +627,8 @@ def _process_profiled_run_active(
|
|||
except GlasExecutionError as exc:
|
||||
execution_error = type(exc).__name__
|
||||
execution_reason = str(exc) if isinstance(exc, GlasSpendError) else "profiled execution failed (GlasExecutionError)"
|
||||
if isinstance(exc, GlasSpendError):
|
||||
safe_evidence = exc.execution_evidence
|
||||
|
||||
if tx is not None and tx.baseline is not None:
|
||||
tx_evidence = tx.evidence()
|
||||
|
|
|
|||
|
|
@ -66,6 +66,16 @@ class GlasExecutionError(RuntimeError):
|
|||
class GlasSpendError(GlasExecutionError):
|
||||
"""Spend refusal with bounded, operator-safe reason text."""
|
||||
|
||||
def __init__(self, message: str, *, execution_evidence: Any = None) -> None:
|
||||
super().__init__(message)
|
||||
evidence = normalise_execution_evidence_for_close(execution_evidence)
|
||||
# Accounting must remain fail-closed without erasing the gateway stage
|
||||
# and cleanup facts. Do not transport raw provider error/output here.
|
||||
self.execution_evidence = {
|
||||
key: value for key, value in evidence.items()
|
||||
if key not in {"error", "artifacts"}
|
||||
}
|
||||
|
||||
|
||||
def normalise_execution_evidence_for_close(raw: Any) -> dict[str, Any]:
|
||||
"""Retain only bounded Glas evidence fields safe for durable close state."""
|
||||
|
|
@ -85,6 +95,10 @@ def normalise_execution_evidence_for_close(raw: Any) -> dict[str, Any]:
|
|||
and value >= 0
|
||||
):
|
||||
result[key] = value
|
||||
for key in ("session_cleanup", "sandbox_destroy"):
|
||||
value = raw.get(key)
|
||||
if isinstance(value, str) and value in {"succeeded", "failed", "not_attempted"}:
|
||||
result[key] = value
|
||||
artifacts = raw.get("artifacts")
|
||||
if isinstance(artifacts, list):
|
||||
result["artifacts"] = [
|
||||
|
|
@ -220,7 +234,10 @@ def execute_profiled_run(
|
|||
if not spend.observe(run.id, raw):
|
||||
raise SpendAdmissionError("execution accounting requires reconciliation")
|
||||
except SpendAdmissionError as exc:
|
||||
raise GlasSpendError(f"spend accounting refused: {exc}") from None
|
||||
raise GlasSpendError(
|
||||
f"spend accounting refused: {exc}",
|
||||
execution_evidence=raw["evidence"],
|
||||
) from None
|
||||
if transfer is not None and raw["ok"]:
|
||||
evidence = raw["evidence"]
|
||||
if evidence.get("session_cleanup") != "succeeded" or evidence.get("sandbox_destroy") != "succeeded":
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class BootstrapRefused(RuntimeError):
|
|||
def prepare(path: Path, config: OpsRunConfig) -> tuple[MessagesPolicy, Path, str]:
|
||||
"""Validate value-free, owner-controlled pins without a key or queue claim."""
|
||||
from glas_harness.profiles import ProfileCatalog
|
||||
from sandboxer.extensions.runtime import verified_runtime
|
||||
from sandboxer.extensions.runtime import RUNTIME_MOUNT, verified_runtime
|
||||
try:
|
||||
_private_file(path)
|
||||
if not path.is_absolute() or path.resolve() != path or path.stat().st_size > 65536:
|
||||
|
|
@ -64,6 +64,15 @@ def prepare(path: Path, config: OpsRunConfig) -> tuple[MessagesPolicy, Path, str
|
|||
runtime = verified_runtime({"runtime": data["runtime"]})
|
||||
if runtime is None or not runtime.is_absolute() or runtime.resolve() != runtime:
|
||||
raise ValueError
|
||||
# A matching artifact digest does not prove its generated launchers
|
||||
# survived relocation. Refuse build-host interpreters before claiming.
|
||||
for name in ("rein-aharness", "glas-harness"):
|
||||
executable = runtime / "bin" / name
|
||||
if executable.is_symlink() or not executable.is_file() or not os.access(executable, os.X_OK):
|
||||
raise ValueError
|
||||
with executable.open("rb") as stream:
|
||||
if stream.readline(4096) != f"#!{RUNTIME_MOUNT}/bin/python3\n".encode():
|
||||
raise ValueError
|
||||
for private in (path.parent, spend.path.parent, Path(spend.policy.target_repo)):
|
||||
a, b = runtime.resolve(), private.resolve()
|
||||
if a.is_relative_to(b) or b.is_relative_to(a):
|
||||
|
|
|
|||
140
scripts/attended-metered-proof.py
Normal file
140
scripts/attended-metered-proof.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
"""Contained workstation login envelope for the exact Railiance native proof.
|
||||
|
||||
Run the reader through Warden. Both attended sessions self-revoke. Values stay
|
||||
in private tmpfs / process memory and encrypted SSH stdin; output is suppressed.
|
||||
"""
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from urllib.request import Request, build_opener, ProxyHandler, HTTPRedirectHandler
|
||||
|
||||
ROOT = Path("/home/worsch/rein-aharness")
|
||||
PLATFORM = Path("/home/worsch/railiance-platform")
|
||||
PYTHON = "/home/worsch/secrets-engine/.venv/bin/python"
|
||||
REMOTE = "/home/tegwick/hfact/native-metered-20260927"
|
||||
KUBE = ["kubectl", "--kubeconfig", "/home/worsch/.kube/config-railiance01"]
|
||||
RECEIPT = ROOT / "docs/evidence/2026-09-27-metered-attended-execution.json"
|
||||
RESUME = len(sys.argv) > 1 and sys.argv[1] in ("reader-resume", "admin-resume")
|
||||
if RESUME:
|
||||
RECEIPT = ROOT / "docs/evidence/2026-09-27-metered-attended-resume.json"
|
||||
spec = importlib.util.spec_from_file_location("native", ROOT / "scripts/metered-native-owner.py")
|
||||
native = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(native)
|
||||
require, private, run, write_new = native.require, native.private, native.run, native.write_new
|
||||
|
||||
|
||||
class NoRedirect(HTTPRedirectHandler):
|
||||
def redirect_request(self, *args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
def receipt(phase, **fields):
|
||||
data = json.loads(RECEIPT.read_text()) if RECEIPT.exists() else {}
|
||||
data.update(phase=phase, **fields)
|
||||
RECEIPT.write_text(json.dumps(data, indent=2) + "\n")
|
||||
|
||||
|
||||
def helper():
|
||||
require(Path.home().parent.name == ".warden-attended-login" and not os.getenv("BAO_TOKEN") and not os.getenv("VAULT_TOKEN"), "attended_containment_required")
|
||||
path = Path.home() / ".vault-token"
|
||||
private(path)
|
||||
return path
|
||||
|
||||
|
||||
def reader():
|
||||
require(not RECEIPT.exists(), "prior_session_requires_reconciliation")
|
||||
receipt("reader_preflight", status="in_progress", credential_values_emitted=False)
|
||||
token_file = helper()
|
||||
spec = importlib.util.spec_from_file_location("reader", PLATFORM / "scripts/approval-client-reader-preflight.py")
|
||||
pre = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(pre)
|
||||
pre.validate_identity(pre.bao("token", "lookup", "-format=json")["data"])
|
||||
for path, expected in pre.EXPECTED.items():
|
||||
require(sorted(pre.bao("token", "capabilities", "-format=json", path)) == expected, "reader_scope_failed")
|
||||
runtime = Path("/run/user") / str(os.getuid())
|
||||
private(runtime, True)
|
||||
require(run(["findmnt", "-n", "-o", "FSTYPE", "-T", str(runtime)]) == "tmpfs", "private_tmpfs_required")
|
||||
with tempfile.TemporaryDirectory(prefix="metered-attended-", dir=runtime) as name:
|
||||
directory = Path(name)
|
||||
req = Request("http://127.0.0.1:18200/v1/platform/data/workloads/secrets-engine/approval-client?version=1", headers={"X-Vault-Token": token_file.read_text().strip()})
|
||||
with build_opener(ProxyHandler({}), NoRedirect()).open(req, timeout=20) as response:
|
||||
raw = response.read(65537)
|
||||
require(len(raw) <= 65536, "credential_response_bound")
|
||||
data = json.loads(raw)
|
||||
require(data["data"]["metadata"]["version"] == 1, "custody_version_drift")
|
||||
value = data["data"]["data"]["CLIENT_SECRET"]
|
||||
require(isinstance(value, str) and 0 < len(value) <= 16384, "credential_invalid")
|
||||
write_new(directory / "client-secret", value)
|
||||
del value, raw, data, req
|
||||
receipt("attended_admin_login", reader_scope_verified=True, kv_version=1)
|
||||
result = subprocess.run(["python3", str(PLATFORM / "scripts/openbao-attended-exec.py"), "--", PYTHON, "-B", str(Path(__file__).resolve()), "admin-resume" if RESUME else "admin", str(directory), str(token_file)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=780)
|
||||
require(result.returncode == 0, "attended_execution_failed")
|
||||
receipt("attended_sessions_completed", status="completed", local_private_runtime_removed=True)
|
||||
|
||||
|
||||
def admin(directory, negative):
|
||||
admin_file = helper()
|
||||
private(directory, True)
|
||||
private(directory / "client-secret")
|
||||
private(negative)
|
||||
forwards = []
|
||||
payload = {}
|
||||
try:
|
||||
for namespace, label, image, port in (("approval-engine", "approval-engine", "251941a5cb2724b57cc32cff6b693b1ab0be695bee4f56f02d51961189c0fa49", 19281), ("flex-auth", "flex-auth-secrets-engine", "05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd", 19282)):
|
||||
pods = json.loads(run(KUBE + ["-n", namespace, "get", "pods", "-l", "app.kubernetes.io/name=" + label, "-o", "json"]))["items"]
|
||||
require(len(pods) == 1, "single_owner_pod_required")
|
||||
pod = pods[0]
|
||||
require(any(c.get("ready") and c.get("imageID", "").endswith("@sha256:" + image) for c in pod.get("status", {}).get("containerStatuses", [])), "native_image_pin_drift")
|
||||
forwards.append(subprocess.Popen(KUBE + ["-n", namespace, "port-forward", "pod/" + pod["metadata"]["name"], str(port) + ":8080", "--address=127.0.0.1"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL))
|
||||
for port in (19281, 19282):
|
||||
for _ in range(50):
|
||||
require(all(p.poll() is None for p in forwards), "native_forward_failed")
|
||||
try:
|
||||
with socket.create_connection(("127.0.0.1", port), timeout=.2):
|
||||
break
|
||||
except OSError:
|
||||
time.sleep(.1)
|
||||
else:
|
||||
raise ValueError("native_forward_not_ready")
|
||||
bridge = subprocess.Popen(["ssh", "-N", "-o", "ExitOnForwardFailure=yes", "-R", "127.0.0.1:28200:127.0.0.1:18200", "-R", "127.0.0.1:28281:127.0.0.1:19281", "-R", "127.0.0.1:28282:127.0.0.1:19282", "railiance01"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
forwards.append(bridge)
|
||||
time.sleep(1)
|
||||
require(bridge.poll() is None, "ssh_bridge_failed")
|
||||
payload = dict(client_secret=(directory / "client-secret").read_text(), negative_token=negative.read_text().strip(), backend_token=admin_file.read_text().strip(), pdp_token=run(KUBE + ["-n", "secrets-engine", "create", "token", "secrets-engine", "--audience=flex-auth", "--duration=10m"]))
|
||||
receipt("remote_native_execution_started", named_owner_images_verified=True)
|
||||
result = subprocess.run(["ssh", "railiance01", "env", "PATH=/home/tegwick/.local/bin:/usr/local/bin:/usr/bin:/bin", "PYTHONPATH=/home/tegwick/secrets-engine/src", "/home/tegwick/secrets-engine/.venv/bin/python", "-B", REMOTE + "/metered-native-owner.py", "resume" if RESUME else "execute", REMOTE], input=json.dumps(payload), text=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=650)
|
||||
payload.clear()
|
||||
receipt("remote_native_execution_returned", remote_exit_code=result.returncode)
|
||||
require(result.returncode == 0, "remote_native_execution_failed")
|
||||
finally:
|
||||
payload.clear()
|
||||
for process in reversed(forwards):
|
||||
process.terminate()
|
||||
try:
|
||||
process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
process.wait()
|
||||
receipt("native_transport_closed", owner_forwards_closed=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
if sys.argv[1:] in (["reader"], ["reader-resume"]):
|
||||
reader()
|
||||
elif len(sys.argv) == 4 and sys.argv[1] in ("admin", "admin-resume"):
|
||||
admin(Path(sys.argv[2]), Path(sys.argv[3]))
|
||||
else:
|
||||
raise ValueError("unsupported_mode")
|
||||
except Exception as error:
|
||||
fields = {"status": "failed", "failure_type": type(error).__name__}
|
||||
if type(error) is ValueError and str(error).replace("_", "").isalnum():
|
||||
fields["failure_code"] = str(error)
|
||||
receipt("failed", **fields)
|
||||
raise SystemExit(1) from None
|
||||
347
scripts/metered-native-owner.py
Normal file
347
scripts/metered-native-owner.py
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
"""Single attended, non-retrying Railiance proof under the six frozen approvals.
|
||||
|
||||
Non-secret bundle is staged separately. Credentials arrive only on SSH stdin,
|
||||
live in owner-only tmpfs for this process, and never enter the bound child except
|
||||
through Secrets Engine's approved provider/worker delivery.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import contextlib
|
||||
import copy
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from dataclasses import replace
|
||||
|
||||
PACKET_SHA = "22e640428e3a7ae8fc2363cf193db98381cd94e4be7ab009bc6703658d6fc544"
|
||||
PROVIDER = "glas-claude-agent-dev-anthropic"
|
||||
WORKER = "activity-core-metered-worker-token"
|
||||
IDS = {
|
||||
PROVIDER: dict(apply="0c05bd0a-f81f-451d-840c-5565628e2edc", verify="47f118a3-a86c-43ad-969d-42e09a0f45bb", exec="7b32443a-a817-400c-a130-01b9ef04c8ee"),
|
||||
WORKER: dict(apply="2ce76d7f-01c3-4b63-8476-8d1230679769", verify="c2af4bcf-15b4-4305-aac1-acc7e4dcb099", exec="dc22666a-d5d7-46bc-9490-ebe9fe09dc38"),
|
||||
}
|
||||
LIMITS = dict(token_ttl=300, token_max_ttl=900, secret_id_ttl=300, secret_id_num_uses=1, token_num_uses=8)
|
||||
OWNER = Path("/home/tegwick/hfact/owner-metered")
|
||||
TARGET = Path("/home/tegwick/hfact/targets/hfact-glas-proof")
|
||||
BASE_HEAD = "679d23e0517707ba63e25399b5262f0d2f37315d"
|
||||
OLD_FILES = {"owner.json": "e0d3fb84649fdca302eccd9415f3cc2beaee84bf07bd83205cdffbe93e5573bc", "spend-policy.json": "f31c585916de1ea8bd8e48c72421803dfde1015e6201704b9320f77d2c545d9c"}
|
||||
DEFINITION = "5bae5505-77f1-5ba3-8dfa-2e10ed15531e"
|
||||
RECEIPT_NAME = "execution.json"
|
||||
|
||||
|
||||
def require(value, code):
|
||||
if not value:
|
||||
raise ValueError(code)
|
||||
|
||||
|
||||
def private(path, directory=False):
|
||||
s = path.lstat()
|
||||
require(s.st_uid == os.getuid() and stat.S_IMODE(s.st_mode) == (0o700 if directory else 0o600) and (stat.S_ISDIR(s.st_mode) if directory else stat.S_ISREG(s.st_mode)), "private_path_required")
|
||||
|
||||
|
||||
def write_new(path, value):
|
||||
fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY | os.O_NOFOLLOW, 0o600)
|
||||
with os.fdopen(fd, "w") as stream:
|
||||
stream.write(value)
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
|
||||
|
||||
def run(args):
|
||||
p = subprocess.run(args, capture_output=True, text=True, timeout=30)
|
||||
require(p.returncode == 0, "metadata_command_failed")
|
||||
return p.stdout.strip()
|
||||
|
||||
|
||||
def save(bundle, receipt):
|
||||
path = bundle / RECEIPT_NAME
|
||||
temp = path.with_suffix(".tmp")
|
||||
temp.write_text(json.dumps(receipt, indent=2) + "\n")
|
||||
temp.chmod(0o600)
|
||||
temp.replace(path)
|
||||
|
||||
|
||||
def queue_rows():
|
||||
# Read-only owner metadata via the existing authenticated cluster transport.
|
||||
code = '''import asyncio,json
|
||||
from activity_core.db import make_engine
|
||||
from sqlalchemy import text
|
||||
async def main():
|
||||
e=make_engine()
|
||||
async with e.connect() as c:
|
||||
rows=await c.execute(text("SELECT id,state,attempt,claim_owner,target_repo,harness_profile_ref,repository_grant,triggering_event_id FROM ops_runs WHERE activity_definition_id='5bae5505-77f1-5ba3-8dfa-2e10ed15531e' OR labels @> '[\\\"hfact-metered\\\"]'::jsonb"))
|
||||
print(json.dumps([dict(x) for x in rows.mappings()],default=str))
|
||||
await e.dispose()
|
||||
asyncio.run(main())'''
|
||||
return json.loads(run(["/usr/local/bin/kubectl", "-n", "activity-core", "exec", "deploy/actcore-api", "--", "python", "-c", code]))
|
||||
|
||||
|
||||
def install_host(bundle):
|
||||
"""Run with the immutable admitted runtime; preserve every old ledger byte."""
|
||||
from rein_aharness.spend_admission import SpendLedger, SpendPolicy
|
||||
from rein_aharness.request_admission import RequestLedger
|
||||
import yaml
|
||||
private(OWNER, True)
|
||||
require(not (bundle / "installation.json").exists(), "prior_installation_requires_reconciliation")
|
||||
require(not queue_rows(), "metered_queue_not_empty")
|
||||
require(run(["git", "-C", str(TARGET), "rev-parse", "HEAD"]) == BASE_HEAD and not run(["git", "-C", str(TARGET), "status", "--porcelain"]), "target_not_pristine")
|
||||
for filename, expected in OLD_FILES.items():
|
||||
private(OWNER / filename)
|
||||
require(hashlib.sha256((OWNER / filename).read_bytes()).hexdigest() == expected, "old_host_pin_drift")
|
||||
candidate = yaml.safe_load((bundle / "catalog" / (PROVIDER + ".yaml")).read_text())["delivery_config"]["exec_owner"]
|
||||
for filename in OLD_FILES:
|
||||
require(hashlib.sha256((bundle / filename).read_bytes()).hexdigest() == candidate["files"][str(OWNER / filename)]["sha256"], "candidate_pin_drift")
|
||||
old = OWNER / "spend.sqlite3"
|
||||
private(old)
|
||||
db = sqlite3.connect(old)
|
||||
try:
|
||||
db.execute("BEGIN EXCLUSIVE")
|
||||
counts = {table: db.execute("SELECT count(*) FROM " + table).fetchone()[0] for table in ("reservations", "request_routes", "request_reservations")}
|
||||
require(not any(counts.values()), "prior_liabilities_require_reconciliation")
|
||||
policy = SpendPolicy.load(bundle / "spend-policy.json")
|
||||
ledger = SpendLedger(Path(candidate["environment"]["AGENT_HARNESS_SPEND_LEDGER"]), policy)
|
||||
require(not ledger.path.exists(), "new_ledger_already_exists")
|
||||
for filename in OLD_FILES:
|
||||
write_new(OWNER / (filename + ".pre-renewal-20260927"), (OWNER / filename).read_text())
|
||||
ledger.initialize()
|
||||
RequestLedger(ledger).initialize()
|
||||
for filename in OLD_FILES:
|
||||
staged = OWNER / (filename + ".renewal-staged")
|
||||
write_new(staged, (bundle / filename).read_text())
|
||||
staged.replace(OWNER / filename)
|
||||
# Changing both pins retires the old catalog's dispatch. There is no
|
||||
# active metered worker; old ledger and files remain preserved.
|
||||
check = subprocess.run([*candidate["command"], "--check"], env=candidate["environment"], cwd=candidate["cwd"], capture_output=True, text=True, timeout=30)
|
||||
require(check.returncode == 0, "installed_owner_check_failed")
|
||||
receipt = dict(status="installed", observed_at=datetime.now(timezone.utc).isoformat(), old_counts=counts, old_ledger_preserved=True, new_ledger=str(ledger.path), old_dispatch_pins_retired=True, owner_check=json.loads(check.stdout), dispatches=0)
|
||||
write_new(bundle / "installation.json", json.dumps(receipt, indent=2) + "\n")
|
||||
finally:
|
||||
db.rollback()
|
||||
db.close()
|
||||
|
||||
|
||||
def prepare_configs(bundle, private_dir, trust=None):
|
||||
import yaml
|
||||
from secrets_engine.config import Config
|
||||
from secrets_engine.catalog import get_entry
|
||||
from secrets_engine.approval_consume import _expected_request
|
||||
|
||||
packet = (bundle / "native-pdp-inputs.json").read_bytes()
|
||||
require(hashlib.sha256(packet).hexdigest() == PACKET_SHA, "frozen_packet_drift")
|
||||
data = json.loads(packet)
|
||||
rows = data if isinstance(data, list) else data["requests"]
|
||||
expected = {(r["catalog"], r["action"]): r["request"] for r in rows}
|
||||
require(set(expected) == {(lane, action) for lane in IDS for action in IDS[lane]}, "six_exact_requests_required")
|
||||
configs, entries = {}, {}
|
||||
for action in ("apply", "verify", "exec"):
|
||||
folder = private_dir / action
|
||||
folder.mkdir(mode=0o700)
|
||||
for lane in IDS:
|
||||
doc = yaml.safe_load((bundle / "catalog" / (lane + ".yaml")).read_text())
|
||||
doc = copy.deepcopy(doc)
|
||||
doc["approval"]["authorization_id"] = IDS[lane][action]
|
||||
write_new(folder / (lane + ".yaml"), yaml.safe_dump(doc, sort_keys=False))
|
||||
cfg = replace(Config.load(), catalog_dir=folder, evidence_dir=bundle / "native-evidence", hub_url="", bao_addr="http://127.0.0.1:28200", approval_url="http://127.0.0.1:28281", approval_token_file=None, approval_client_secret_file=private_dir / "client-secret", keycape_token_url="https://kc.coulomb.social/token", keycape_issuer="https://kc.coulomb.social", keycape_client_secret_file=None, openbao_jwt_login_file=None, authorization_subject_id="secrets-engine", authorization_subject_type="service", authorization_policy_package="secrets-engine.catalog-lane.lifecycle", authorization_policy_version="v2", authorization_min_approvals=1, pdp_url="http://127.0.0.1:28282", pdp_token_file=private_dir / "pdp-caller", clock_trust_file=trust)
|
||||
configs[action] = cfg
|
||||
for lane in IDS:
|
||||
entry = get_entry(folder, lane)
|
||||
actual = _expected_request(cfg, entry, action, fields=() if action == "apply" else tuple(entry.fields), policy_targets=(entry.policy_name,), auth_targets=(entry.role_name,))
|
||||
require(actual == expected[lane, action], "frozen_action_request_drift")
|
||||
entries[lane, action] = entry
|
||||
return configs, entries
|
||||
|
||||
|
||||
def clock_admission(bundle, directory):
|
||||
from railiance_clock.admission import admit
|
||||
from urllib.request import urlopen
|
||||
custody = json.loads((bundle / "clock-public.json").read_text())
|
||||
require(hashlib.sha256(custody["public_key_pem"].encode()).hexdigest() == "bd583446b5ed61d086806b2a0c5aaf33a875b751e45599e75335d1f415be609a", "clock_key_drift")
|
||||
with urlopen("http://127.0.0.1:8787/healthz", timeout=5) as response:
|
||||
epoch = json.load(response)["epoch"]
|
||||
require(epoch == "b1164ccb-a4c2-4cc8-adf8-1d5597de697b", "clock_epoch_requires_readmission")
|
||||
return admit(directory=directory, authority_id="railiance01", environment="prod", kid=custody["kid"], epoch=epoch, policy_id="railiance01-online-v1", public_key_pem=custody["public_key_pem"], endpoint="http://127.0.0.1:8787/v1/time-samples", limits=dict(max_age_ns=5000000000, max_rtt_ns=2000000000, max_width_ns=3000000000, max_server_error_ns=500000000, timer_ppm=1000, timer_resolution_ns=1000, suspend_tolerance_ns=5000000, trust_session_ns=900000000000), admission_ref="CCR-2026-0028; REINAH-WP-0003; attended proof 2026-09-27", transport="admitted-loopback")
|
||||
|
||||
|
||||
def verify_cleanup(client, entry, other):
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.error import HTTPError
|
||||
with client.approle_session(entry.role_name) as session:
|
||||
token = session.client.token
|
||||
for path in (f"platform/data/{other.path}", "platform/metadata/workloads", "platform/data/workloads/secrets-engine/approval-client"):
|
||||
require(session.client.token_capabilities(path, token=token) == ["deny"], "sibling_or_metadata_authority")
|
||||
require(session.revocation_succeeded, "session_revocation_failed")
|
||||
try:
|
||||
with urlopen(Request(client.addr + "/v1/auth/token/lookup-self", headers={"X-Vault-Token": token}), timeout=15):
|
||||
raise ValueError("revoked_token_still_usable")
|
||||
except HTTPError as error:
|
||||
require(error.code == 403, "revocation_not_definitive")
|
||||
finally:
|
||||
del token
|
||||
return dict(sibling_denied=True, metadata_denied=True, session_revoked=True, revoked_lookup_status=403)
|
||||
|
||||
|
||||
def validate_queued(rows, trigger):
|
||||
require(len(rows) == 1 and rows[0]["state"] == "open" and rows[0]["attempt"] == 0 and rows[0]["claim_owner"] is None and rows[0]["target_repo"] == "hfact-glas-proof" and rows[0]["harness_profile_ref"] == "harness.agent-dev-local@1.1.1" and rows[0]["repository_grant"] == {"version": "1", "allowed_paths": ["PROOF.md"], "commit_count": {"min": 1, "max": 1}, "publish": False} and rows[0]["triggering_event_id"] == trigger, "natural_queue_binding_refused")
|
||||
|
||||
|
||||
def execute(bundle, resume=False):
|
||||
from secrets_engine.openbao import OpenBaoClient
|
||||
from secrets_engine.approval_consume import authorize_action
|
||||
from secrets_engine.application_time import read_window
|
||||
from secrets_engine.cli import build_parser
|
||||
from secrets_engine.exec_owner import validate_delivery_target
|
||||
from secrets_engine.plan import build_plan
|
||||
|
||||
global RECEIPT_NAME
|
||||
prior = None
|
||||
if resume:
|
||||
prior = json.loads((bundle / "execution.json").read_text())
|
||||
require(prior.get("phase") == "one_trigger_started" and prior.get("failure_code") == "natural_queue_binding_refused" and prior.get("trigger", {}).get("trigger_key") == "manual-dab6c4c7-db03-4887-a17b-9d99b752482e", "unexpected_prior_failure")
|
||||
require([(r["catalog"], r["action"], r["approval_id"], r["exit_code"]) for r in prior["actions"]] == [(lane, action, IDS[lane][action], 0) for action in ("apply", "verify") for lane in IDS], "prior_native_actions_not_verified")
|
||||
RECEIPT_NAME = "execution-resume.json"
|
||||
require(not (bundle / RECEIPT_NAME).exists(), "prior_attempt_requires_reconciliation")
|
||||
receipt = dict(status="failed", phase="preflight", observed_at=datetime.now(timezone.utc).isoformat(), actions=[], automatic_retry=False)
|
||||
save(bundle, receipt)
|
||||
payload = {}
|
||||
directory = None
|
||||
try:
|
||||
require(run(["git", "-C", str(TARGET), "rev-parse", "HEAD"]) == BASE_HEAD and not run(["git", "-C", str(TARGET), "status", "--porcelain"]), "target_not_pristine")
|
||||
require(json.loads((bundle / "installation.json").read_text())["status"] == "installed", "installation_receipt_required")
|
||||
if resume:
|
||||
validate_queued(queue_rows(), prior["trigger"]["trigger_key"])
|
||||
with sqlite3.connect(f"file:{OWNER}/spend-tool-proof-renewal-20260927.sqlite3?mode=ro", uri=True) as db:
|
||||
require(all(db.execute("SELECT count(*) FROM " + table).fetchone()[0] == 0 for table in ("reservations", "request_routes", "request_reservations")), "prior_paid_dispatch_requires_reconciliation")
|
||||
receipt.update(prior_receipt="execution.json", completed_actions_not_replayed=["provider/apply", "worker/apply", "provider/verify", "worker/verify", "queue/trigger"], trigger=prior["trigger"])
|
||||
else:
|
||||
require(not queue_rows(), "metered_queue_not_empty")
|
||||
runtime = Path("/run/user") / str(os.getuid())
|
||||
private(runtime, True)
|
||||
require(run(["findmnt", "-n", "-o", "FSTYPE", "-T", str(runtime)]) == "tmpfs", "tmpfs_required")
|
||||
with tempfile.TemporaryDirectory(prefix="metered-native-", dir=runtime) as name:
|
||||
directory = Path(name)
|
||||
private(directory, True)
|
||||
payload = json.loads(sys.stdin.buffer.read(131073))
|
||||
require(set(payload) == {"client_secret", "negative_token", "backend_token", "pdp_token"}, "credential_envelope_invalid")
|
||||
for key, filename in (("client_secret", "client-secret"), ("negative_token", "negative-token"), ("pdp_token", "pdp-caller")):
|
||||
require(isinstance(payload[key], str) and 0 < len(payload[key]) < 32768, "credential_envelope_invalid")
|
||||
write_new(directory / filename, payload.pop(key))
|
||||
os.environ["BAO_TOKEN"] = payload.pop("backend_token")
|
||||
os.environ.pop("VAULT_TOKEN", None)
|
||||
os.environ["BAO_ADDR"] = "http://127.0.0.1:28200"
|
||||
trust = clock_admission(bundle, directory)
|
||||
configs, entries = prepare_configs(bundle, directory, trust)
|
||||
read_window(configs["exec"])
|
||||
command = entries[PROVIDER, "exec"].delivery_config["exec_owner"]["command"]
|
||||
validate_delivery_target(entries[PROVIDER, "exec"], "ANTHROPIC_API_KEY", command, "exec-env")
|
||||
client = OpenBaoClient.resolve(configs["exec"].bao_addr)
|
||||
identity = json.loads(run([client.bao_bin, "token", "lookup", "-format=json"]))["data"]
|
||||
policies = set(identity["policies"]) | set(identity.get("identity_policies", []))
|
||||
require("platform-admin" in policies and "root" not in policies and identity.get("entity_id") and 0 < identity["ttl"] <= 3600, "attended_operator_required")
|
||||
# Refuse drift/existing state before any native consume. An already
|
||||
# applied lane needs reconciliation, never a replay of this procedure.
|
||||
for lane in IDS:
|
||||
entry = entries[lane, "apply"]
|
||||
if resume:
|
||||
require(client.read_policy(entry.policy_name).strip() == build_plan(entry, "prod").policy_hcl.strip(), "applied_policy_drift")
|
||||
role = json.loads(run([client.bao_bin, "read", "-format=json", "auth/approle/role/" + entry.role_name]))["data"]
|
||||
require(all(role.get(k) == v for k, v in LIMITS.items()) and role.get("token_policies") == [entry.policy_name], "role_limits_drift")
|
||||
else:
|
||||
require(client.read_policy(entry.policy_name) is None and not client.approle_exists(entry.role_name), "existing_lane_requires_reconciliation")
|
||||
for lane in IDS:
|
||||
for action in (("exec",) if resume else IDS[lane]):
|
||||
entry = entries[lane, action]
|
||||
require(authorize_action(configs[action], entry, action, fields=() if action == "apply" else tuple(entry.fields), policy_targets=(entry.policy_name,), auth_targets=(entry.role_name,)) is not None, "native_authorization_missing")
|
||||
receipt["phase"] = "remaining_exec_checks_passed" if resume else "all_six_native_checks_passed"
|
||||
save(bundle, receipt)
|
||||
for action in (() if resume else ("apply", "verify")):
|
||||
for lane in IDS:
|
||||
receipt["phase"] = lane + "_" + action + "_started"
|
||||
save(bundle, receipt)
|
||||
argv = ["apply", lane, "--stage", "prod", "--auth", "env"] if action == "apply" else ["verify", lane, "--auth", "env", "--negative-token-file", str(directory / "negative-token")]
|
||||
args = build_parser().parse_args(argv)
|
||||
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
|
||||
code = args.func(configs[action], args)
|
||||
require(code == 0, "native_action_failed")
|
||||
row = dict(catalog=lane, action=action, approval_id=IDS[lane][action], exit_code=code)
|
||||
entry = entries[lane, action]
|
||||
if action == "apply":
|
||||
role = json.loads(run([client.bao_bin, "read", "-format=json", "auth/approle/role/" + entry.role_name]))["data"]
|
||||
require(all(role.get(k) == v for k, v in LIMITS.items()) and role.get("token_policies") == [entry.policy_name], "role_limits_drift")
|
||||
require(client.read_policy(entry.policy_name).strip() == build_plan(entry, "prod").policy_hcl.strip(), "applied_policy_drift")
|
||||
row["limits"] = LIMITS
|
||||
else:
|
||||
row.update(verify_cleanup(client, entry, entries[WORKER if lane == PROVIDER else PROVIDER, action]))
|
||||
receipt["actions"].append(row)
|
||||
save(bundle, receipt)
|
||||
# The owner triggers exactly once only after both lanes verify.
|
||||
# Persist intent first; any uncertainty blocks re-trigger/re-exec.
|
||||
if not resume:
|
||||
receipt["phase"] = "one_trigger_started"
|
||||
save(bundle, receipt)
|
||||
from urllib.request import Request, urlopen
|
||||
with urlopen(Request("http://127.0.0.1:8010/activity-definitions/" + DEFINITION + "/trigger", method="POST"), timeout=30) as response:
|
||||
require(response.status == 202, "trigger_refused")
|
||||
receipt["trigger"] = json.load(response)
|
||||
for _ in range(20):
|
||||
rows = queue_rows()
|
||||
if rows:
|
||||
break
|
||||
time.sleep(1)
|
||||
validate_queued(rows, receipt["trigger"]["trigger_key"])
|
||||
receipt["queued_run"] = rows[0]
|
||||
receipt["phase"] = "one_exec_started"
|
||||
save(bundle, receipt)
|
||||
args = build_parser().parse_args(["exec", "--catalog", PROVIDER, "--auth", "env", "--mode", "exec-env", "--", *command])
|
||||
args.command = args.command[1:]
|
||||
captured = io.StringIO()
|
||||
with contextlib.redirect_stdout(captured), contextlib.redirect_stderr(io.StringIO()):
|
||||
code = args.func(configs["exec"], args)
|
||||
# Only the closed owner result schema may leave this envelope.
|
||||
results = []
|
||||
for line in captured.getvalue().splitlines():
|
||||
try:
|
||||
row = json.loads(line)
|
||||
except ValueError:
|
||||
continue
|
||||
if isinstance(row, dict) and set(row) <= {"ok", "claimed", "empty", "run_id", "ops_state", "code"} and "ok" in row:
|
||||
results.append(row)
|
||||
receipt.update(exec_exit_code=code, owner_results=results, phase="one_exec_returned")
|
||||
receipt["actions"].extend(dict(catalog=lane, action="exec", approval_id=IDS[lane]["exec"], exit_code=code) for lane in IDS)
|
||||
receipt["status"] = "passed" if code == 0 and len(results) == 1 and results[0].get("claimed") and results[0].get("ok") else "attempt_failed"
|
||||
receipt["private_runtime_removed"] = True
|
||||
except Exception as error:
|
||||
receipt["failure_type"] = type(error).__name__
|
||||
if type(error) is ValueError and str(error).replace("_", "").isalnum():
|
||||
receipt["failure_code"] = str(error)
|
||||
finally:
|
||||
payload.clear()
|
||||
os.environ.pop("BAO_TOKEN", None)
|
||||
if directory is not None:
|
||||
receipt["private_runtime_removed"] = not directory.exists()
|
||||
save(bundle, receipt)
|
||||
return 0 if receipt["status"] == "passed" else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("mode", choices=("validate", "install", "execute", "resume"))
|
||||
parser.add_argument("bundle", type=Path)
|
||||
args = parser.parse_args()
|
||||
if args.mode == "validate":
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
prepare_configs(args.bundle, Path(directory))
|
||||
print("Six frozen request bindings verified")
|
||||
elif args.mode == "install":
|
||||
install_host(args.bundle)
|
||||
else:
|
||||
raise SystemExit(execute(args.bundle, resume=args.mode == "resume"))
|
||||
|
|
@ -230,6 +230,8 @@ except OSError: readonly=True
|
|||
else: readonly=False
|
||||
print(json.dumps({'runtime_readonly':readonly,'python_prefix':sys.prefix,
|
||||
'cli_version':subprocess.check_output(['claude','--version'],text=True).strip(),
|
||||
'entrypoints':{name:subprocess.run([name,'--help'],capture_output=True,timeout=15).returncode
|
||||
for name in ('rein-aharness','glas-harness')},
|
||||
'private_absent':not Path(sys.argv[1]).exists(),'source_absent':not Path(sys.argv[2]).exists(),
|
||||
'proxy_absent':not any('proxy' in k.lower() for k in os.environ),
|
||||
'interfaces':[x.split(':')[0].strip() for x in Path('/proc/net/dev').read_text().splitlines()[2:]]}))
|
||||
|
|
@ -239,6 +241,7 @@ print(json.dumps({'runtime_readonly':readonly,'python_prefix':sys.prefix,
|
|||
assert facts["runtime_readonly"] and facts["private_absent"] and facts["source_absent"]
|
||||
assert facts["proxy_absent"] and facts["interfaces"] == ["lo"]
|
||||
assert facts["cli_version"] == "2.1.266 (Claude Code)"
|
||||
assert all(code == 0 for code in facts["entrypoints"].values()), json.dumps({"entrypoint_check_failed": facts["entrypoints"], "provider_requests": server.provider_calls-before})
|
||||
adapter = AgenticClaudeCodeAdapter(workdir=Path(status.inputs["workspace_dir"]),
|
||||
cli_path="/opt/sandboxer/runtime/bin/claude",
|
||||
model=profile.model.model)
|
||||
|
|
|
|||
70
scripts/reconcile-metered-queue-grant.py
Normal file
70
scripts/reconcile-metered-queue-grant.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
"""Repair the one unclaimed proof row from its existing typed owner definition.
|
||||
|
||||
Run inside the Activity Core owner process context, first without --apply.
|
||||
No trigger, claim, inferred authority, retry, or new task is created.
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
RUN = "6efa9436-6a91-47c0-94e5-b31b5a0e7e3a"
|
||||
DEFINITION = "5bae5505-77f1-5ba3-8dfa-2e10ed15531e"
|
||||
TRIGGER = "manual-dab6c4c7-db03-4887-a17b-9d99b752482e"
|
||||
GRANT = {"version": "1", "allowed_paths": ["PROOF.md"], "commit_count": {"min": 1, "max": 1}, "publish": False}
|
||||
|
||||
|
||||
def validate(definition, row, live_worker_image):
|
||||
if live_worker_image != "sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd":
|
||||
raise ValueError("grant_aware_worker_required")
|
||||
if str(definition.id) != DEFINITION or definition.enabled or definition.version != 1:
|
||||
raise ValueError("definition_drift")
|
||||
rules = definition.rules_json
|
||||
if len(rules) != 1 or rules[0]["id"] != "execute-hfact-glas-metered-proof" or rules[0]["condition"] != "True":
|
||||
raise ValueError("rule_drift")
|
||||
action = rules[0]["action"]
|
||||
if action.get("repository_grant") != GRANT or action.get("target_repo") != "hfact-glas-proof" or action.get("harness_profile_ref") != "harness.agent-dev-local@1.1.1" or action.get("labels") != ["hfact-metered"]:
|
||||
raise ValueError("typed_authority_drift")
|
||||
expected = dict(id=RUN, activity_definition_id=DEFINITION, state="open", attempt=0, claim_owner=None, lease_until=None, target_repo="hfact-glas-proof", harness_profile_ref="harness.agent-dev-local@1.1.1", triggering_event_id=TRIGGER, source_type="rule", source_id=rules[0]["id"], repository_grant=None)
|
||||
for field, value in expected.items():
|
||||
actual = getattr(row, field)
|
||||
if field in ("id", "activity_definition_id"):
|
||||
actual = str(actual)
|
||||
if actual != value:
|
||||
raise ValueError("queue_row_drift")
|
||||
if row.description != action["description"] or row.title != action["task_template"] or row.labels != action["labels"]:
|
||||
raise ValueError("task_content_drift")
|
||||
return action["repository_grant"]
|
||||
|
||||
|
||||
async def main(apply, image):
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||
from activity_core.db import make_engine
|
||||
from activity_core.orm import ActivityDefinition, OpsRun
|
||||
from activity_core.repository_grant import RepositoryGrant
|
||||
engine = make_engine()
|
||||
try:
|
||||
async with async_sessionmaker(engine, expire_on_commit=False)() as session:
|
||||
async with session.begin():
|
||||
definition = (await session.execute(select(ActivityDefinition).where(ActivityDefinition.id == uuid.UUID(DEFINITION)).with_for_update())).scalar_one()
|
||||
row = (await session.execute(select(OpsRun).where(OpsRun.id == uuid.UUID(RUN)).with_for_update())).scalar_one()
|
||||
grant = RepositoryGrant.model_validate(validate(definition, row, image))
|
||||
if grant.grant_id != "656911f7dff83e871434b415f0cee685":
|
||||
raise ValueError("spend_grant_binding_mismatch")
|
||||
if apply:
|
||||
row.repository_grant = grant.payload()
|
||||
receipt = dict(status="applied" if apply else "dry_run_passed", run_id=RUN, definition_id=DEFINITION, definition_version=1, triggering_event_id=TRIGGER, source="typed_existing_definition_rule", grant_id=grant.grant_id, grant=grant.payload(), attempt=0, claim_owner=None, new_trigger=False, new_task=False)
|
||||
print(json.dumps(receipt, indent=2))
|
||||
finally:
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = sys.argv[1:]
|
||||
apply = args[:1] == ["--apply"]
|
||||
if apply:
|
||||
args = args[1:]
|
||||
if len(args) != 1:
|
||||
raise SystemExit("Supply the independently observed live worker image digest")
|
||||
asyncio.run(main(apply, args[0]))
|
||||
|
|
@ -535,6 +535,34 @@ def test_profile_refusal_fails_terminally_without_legacy_fallback(
|
|||
execute.assert_not_called()
|
||||
|
||||
|
||||
def test_spend_refusal_closes_with_gateway_cleanup_evidence(tmp_path: Path) -> None:
|
||||
from rein_aharness.glas_execution import GlasSpendError
|
||||
|
||||
_repo, run, client = _profiled_case(tmp_path)
|
||||
client.fail.return_value = OpsRun(
|
||||
id=run.id, activity_definition_id="def", idempotency_key="k",
|
||||
target_repo=run.target_repo, title=run.title, description="", state="failed",
|
||||
)
|
||||
refusal = GlasSpendError(
|
||||
"spend accounting refused: execution accounting requires reconciliation",
|
||||
execution_evidence={
|
||||
"outcome": "failed", "failure_stage": "execution",
|
||||
"session_cleanup": "succeeded", "sandbox_destroy": "succeeded",
|
||||
"error": "private response", "provider_response": "private payload",
|
||||
},
|
||||
)
|
||||
with patch("rein_aharness.claim_loop.execute_profiled_run", side_effect=refusal):
|
||||
result = process_one(client)
|
||||
assert result.ok is False
|
||||
assert client.fail.call_args.kwargs["reopen"] is False
|
||||
evidence = client.fail.call_args.kwargs["result"]["execution_evidence"]
|
||||
assert evidence == {
|
||||
"outcome": "failed", "failure_stage": "execution",
|
||||
"session_cleanup": "succeeded", "sandbox_destroy": "succeeded",
|
||||
}
|
||||
client.complete.assert_not_called()
|
||||
|
||||
|
||||
def test_profiled_signal_cancellation_releases_lock_and_durably_fails(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
|
|
|
|||
53
tests/test_metered_native_procedure.py
Normal file
53
tests/test_metered_native_procedure.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
"""Frozen action packet safety; requires the governed secrets-engine sibling."""
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("secrets_engine")
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SOURCE = ROOT.parent / "secrets-engine/docs/proposals/glas-metered-tool-renewal-20260927"
|
||||
spec = importlib.util.spec_from_file_location("metered_native", ROOT / "scripts/metered-native-owner.py")
|
||||
native = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(native)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bundle(tmp_path):
|
||||
path = tmp_path / "bundle"
|
||||
shutil.copytree(SOURCE, path)
|
||||
return path
|
||||
|
||||
|
||||
def test_all_six_action_bindings_match(bundle, tmp_path):
|
||||
private = tmp_path / "private"
|
||||
private.mkdir()
|
||||
configs, entries = native.prepare_configs(bundle, private)
|
||||
assert len(entries) == 6
|
||||
for (lane, action), entry in entries.items():
|
||||
assert entry.approval["authorization_id"] == native.IDS[lane][action]
|
||||
assert configs["exec"].clock_trust_file is None # validation is backend-free
|
||||
|
||||
|
||||
@pytest.mark.parametrize("lane", [native.PROVIDER, native.WORKER])
|
||||
def test_recipient_drift_refused_before_auth(bundle, tmp_path, lane):
|
||||
path = bundle / "catalog" / (lane + ".yaml")
|
||||
path.write_text(path.read_text().replace("token_max_ttl: 15m", "token_max_ttl: 16m"))
|
||||
private = tmp_path / "private"
|
||||
private.mkdir()
|
||||
with pytest.raises(ValueError, match="frozen_action_request_drift"):
|
||||
native.prepare_configs(bundle, private)
|
||||
|
||||
|
||||
def test_changed_packet_refused(bundle, tmp_path):
|
||||
path = bundle / "native-pdp-inputs.json"
|
||||
path.write_bytes(path.read_bytes() + b"\n")
|
||||
with pytest.raises(ValueError, match="frozen_packet_drift"):
|
||||
native.prepare_configs(bundle, tmp_path)
|
||||
|
||||
|
||||
def test_execution_never_replays_prior_attempt(bundle):
|
||||
(bundle / "execution.json").write_text('{"phase":"one_exec_started"}')
|
||||
with pytest.raises(ValueError, match="prior_attempt_requires_reconciliation"):
|
||||
native.execute(bundle)
|
||||
46
tests/test_metered_queue_reconciliation.py
Normal file
46
tests/test_metered_queue_reconciliation.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
"""The one-row repair cannot widen or synthesize mutation authority."""
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from copy import deepcopy
|
||||
|
||||
import pytest
|
||||
|
||||
spec = importlib.util.spec_from_file_location("repair", Path(__file__).resolve().parents[1] / "scripts/reconcile-metered-queue-grant.py")
|
||||
repair = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(repair)
|
||||
IMAGE = "sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def records():
|
||||
action = dict(repository_grant=deepcopy(repair.GRANT), target_repo="hfact-glas-proof", harness_profile_ref="harness.agent-dev-local@1.1.1", labels=["hfact-metered"], description="bounded task", task_template="proof")
|
||||
definition = SimpleNamespace(id=repair.DEFINITION, enabled=False, version=1, rules_json=[dict(id="execute-hfact-glas-metered-proof", condition="True", action=action)])
|
||||
row = SimpleNamespace(id=repair.RUN, activity_definition_id=repair.DEFINITION, state="open", attempt=0, claim_owner=None, lease_until=None, target_repo="hfact-glas-proof", harness_profile_ref="harness.agent-dev-local@1.1.1", triggering_event_id=repair.TRIGGER, source_type="rule", source_id="execute-hfact-glas-metered-proof", repository_grant=None, description="bounded task", title="proof", labels=["hfact-metered"])
|
||||
return definition, row
|
||||
|
||||
|
||||
def test_copies_only_typed_existing_authority(records):
|
||||
definition, row = records
|
||||
assert repair.validate(definition, row, IMAGE) is definition.rules_json[0]["action"]["repository_grant"]
|
||||
assert row.repository_grant is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field,value", [("state", "claimed"), ("attempt", 1), ("claim_owner", "worker"), ("repository_grant", repair.GRANT), ("triggering_event_id", "another-trigger"), ("description", "changed task")])
|
||||
def test_changed_or_used_row_refused(records, field, value):
|
||||
definition, row = records
|
||||
setattr(row, field, value)
|
||||
with pytest.raises(ValueError):
|
||||
repair.validate(definition, row, IMAGE)
|
||||
|
||||
|
||||
def test_changed_definition_cannot_grant_more(records):
|
||||
definition, row = records
|
||||
definition.rules_json[0]["action"]["repository_grant"]["allowed_paths"] = ["**"]
|
||||
with pytest.raises(ValueError, match="typed_authority_drift"):
|
||||
repair.validate(definition, row, IMAGE)
|
||||
|
||||
|
||||
def test_stale_worker_blocks_repair(records):
|
||||
with pytest.raises(ValueError, match="grant_aware_worker_required"):
|
||||
repair.validate(*records, "sha256:old")
|
||||
|
|
@ -42,6 +42,10 @@ def prepared(ledger, tmp_path, monkeypatch):
|
|||
runtime = tmp_path / "runtime"
|
||||
(runtime / "bin").mkdir(parents=True)
|
||||
(runtime / "bin/python3").write_bytes(b"not executed; fixture runtime structure")
|
||||
for name in ("rein-aharness", "glas-harness"):
|
||||
executable = runtime / "bin" / name
|
||||
executable.write_text("#!/opt/sandboxer/runtime/bin/python3\n# fixture only\n")
|
||||
executable.chmod(0o755)
|
||||
(runtime / "pyvenv.cfg").write_text("fixture only")
|
||||
messages = MessagesPolicy("fixture:upper-rate", profile.model.model, 1000, 1000, 1000, 1000)
|
||||
data = {"version": "1", "authority_ref": policy.authority_ref,
|
||||
|
|
@ -62,7 +66,8 @@ def test_prepare_pins_without_key_or_claim(prepared, monkeypatch):
|
|||
|
||||
|
||||
@pytest.mark.parametrize("case", ["authority", "policy_digest", "model", "version", "unknown_field",
|
||||
"duplicate", "public", "runtime_changed", "schema_missing"])
|
||||
"duplicate", "public", "runtime_changed", "schema_missing",
|
||||
"build_host_launcher", "missing_launcher", "nonexecutable_launcher"])
|
||||
def test_bad_bootstrap_refuses_before_claim(prepared, monkeypatch, capsys, case):
|
||||
path, config, data, runtime = prepared
|
||||
if case == "authority": data["authority_ref"] = "unrelated"
|
||||
|
|
@ -71,6 +76,16 @@ def test_bad_bootstrap_refuses_before_claim(prepared, monkeypatch, capsys, case)
|
|||
elif case == "version": data["version"] = "2"
|
||||
elif case == "unknown_field": data["upstream_url"] = "https://not-admitted.invalid"
|
||||
elif case == "runtime_changed": (runtime / "added-file").write_text("changed")
|
||||
elif case in {"build_host_launcher", "missing_launcher", "nonexecutable_launcher"}:
|
||||
executable = runtime / "bin/rein-aharness"
|
||||
if case == "build_host_launcher":
|
||||
executable.write_text("#!/bin/sh\nexec /old-build/bin/python3\n")
|
||||
elif case == "missing_launcher":
|
||||
executable.unlink()
|
||||
else:
|
||||
executable.chmod(0o644)
|
||||
# Even a correctly pinned malformed runtime must fail before claim.
|
||||
data["runtime"]["sha256"] = runtime_digest(runtime)
|
||||
elif case == "schema_missing":
|
||||
import sqlite3
|
||||
with sqlite3.connect(config.spend_ledger_path) as db: db.execute("DROP TABLE request_routes")
|
||||
|
|
|
|||
|
|
@ -419,6 +419,29 @@ def test_uncertain_gateway_never_imports_and_blocks_next_claim(dispatch_case, fa
|
|||
store.preflight()
|
||||
|
||||
|
||||
def test_accounting_refusal_preserves_stage_and_cleanup_without_raw_error(dispatch_case):
|
||||
from rein_aharness.glas_execution import GlasSpendError, execute_profiled_run
|
||||
|
||||
store, config, _catalog, transfer = dispatch_case
|
||||
result = success(store, run(), None)
|
||||
result["ok"] = False
|
||||
result["evidence"].update(
|
||||
outcome="failed", failure_stage="execution", sandbox_id="fixture-sandbox",
|
||||
error="private provider response", provider_response="private payload",
|
||||
)
|
||||
with pytest.raises(GlasSpendError) as caught:
|
||||
execute_profiled_run(run(), config, gateway=lambda *a, **k: result, report_to_hub=False)
|
||||
evidence = caught.value.execution_evidence
|
||||
assert evidence["failure_stage"] == "execution"
|
||||
assert evidence["sandbox_destroy"] == evidence["session_cleanup"] == "succeeded"
|
||||
assert "private" not in json.dumps(evidence)
|
||||
assert "error" not in evidence and "cost_usd" not in evidence
|
||||
transfer.import_after_teardown.assert_not_called()
|
||||
assert store.status()["reservations"][0]["state"] == "held"
|
||||
with pytest.raises(SpendAdmissionError):
|
||||
store.preflight()
|
||||
|
||||
|
||||
def test_cli_reconciliation_requires_termination_attestation(ledger, capsys):
|
||||
from rein_aharness.cli import main
|
||||
|
||||
|
|
|
|||
|
|
@ -985,3 +985,56 @@ backend delivery/consume, natural execution and recovery acceptance. T04's
|
|||
existing tenant-owner gates remain. The plan stays blocked; no new task or
|
||||
workplan was opened. The previous unsigned-spend/requester-mandate blockers above
|
||||
are superseded by the confirmed evidence in this return.
|
||||
|
||||
### Native activation, one failed attempt, and implemented fixes — 2026-09-27
|
||||
|
||||
All six human dispositions were accepted and confirmed. Both scoped credential
|
||||
lanes passed native apply and positive/negative verification, including sibling
|
||||
and metadata denial and revoked-token rejection. Each of the six exact native
|
||||
approvals was consumed once. Exec delivery sessions both revoked successfully;
|
||||
the attended Warden sessions exited and no private credential files remain.
|
||||
|
||||
Installed the approved renewed owner/spend pins after exclusively checking the
|
||||
old ledger's three tables were empty. The old ledger and configuration backups
|
||||
are preserved. No prior liability was discarded and old dispatch pins were retired.
|
||||
|
||||
The single trigger exposed a deployed Activity Core mismatch: its older worker
|
||||
dropped the definition's typed repository grant. Corrected only its image to
|
||||
the already-deployed grant-aware API artifact, using the existing GitOps parent
|
||||
and child. Source commits: Activity Core `428f2d7`, Platform `c3607ff`. Both
|
||||
applications are healthy/synced. A guarded, row-locked repair copied only the
|
||||
existing definition's exact grant to the same unclaimed attempt-zero job. It
|
||||
created no task/trigger and did not infer authority from text. Nine guard tests
|
||||
and 21 Activity Core carriage tests pass; actual image carriage checks pass.
|
||||
|
||||
Resumed only the two unused exec approvals. Job
|
||||
`6efa9436-6a91-47c0-94e5-b31b5a0e7e3a` reached attempt 1 and closed `failed`
|
||||
with its lease cleared. No provider request reservation was created. The
|
||||
installed `b6e4e8a4` artifact's uv shell launchers retained a vanished temporary
|
||||
build interpreter. The real bwrap no-credential probe reproduces exit 127 for
|
||||
rein and Glas with zero provider forwards. The prior direct-Claude fixture did
|
||||
not exercise these Python entrypoints and cannot stand in for this evidence.
|
||||
|
||||
Fixed Sand-boxer's builder to relocate direct and long-path/space-containing
|
||||
shell launchers (commit `81b5fcf`, 12 builder tests and focused lint pass).
|
||||
Added pre-claim launcher validation and actual in-sandbox entrypoint checks in
|
||||
rein. Accounting refusal now preserves bounded gateway stage/cleanup evidence
|
||||
without copying raw provider errors; it still holds the liability and prevents
|
||||
artifact import or another claim. The approved immutable artifact was not edited.
|
||||
|
||||
Postflight: original repository clean at `679d23e0`, lock available, sandbox
|
||||
`b67907f1`/workspace removed, request route revoked, no pending/quarantined close
|
||||
outbox records, and no private credential directories. The parent EUR 10
|
||||
reservation remains held for owner reconciliation; no billing figure or refund
|
||||
is inferred. There was no retry, publication, extra queue job, or new workplan.
|
||||
|
||||
Evidence: `docs/native-metered-proof.md` and
|
||||
`docs/evidence/2026-09-27-metered-{acceptance-confirmations,native-consumes,native-execution,native-resume,postflight,queue-grant-reconciliation,renewal-installation}.json`;
|
||||
worker rollout is `docs/evidence/2026-09-27-grant-aware-worker-rollout.json`.
|
||||
|
||||
T05/T06 remain `wait`, and the workplan remains `blocked`: a corrected immutable
|
||||
artifact/pin admission, reconciliation of the held reservation, and separately
|
||||
authorized successful model/tool/commit acceptance remain. The accepted decisions
|
||||
must not be requested again as though still pending; they are consumed historical
|
||||
authority. T04's FI/Binky owner gates remain unchanged. These residuals stay in
|
||||
the existing REINAH-WP-0003, SAND-WP-0015-T04 and SECRETS-WP-0009-T03 records.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue