openworkproof 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. openworkproof/__init__.py +1 -0
  2. openworkproof/acceptance.py +3006 -0
  3. openworkproof/cli.py +164 -0
  4. openworkproof/composition.py +832 -0
  5. openworkproof/evidence.py +8281 -0
  6. openworkproof/execution_adapter.py +256 -0
  7. openworkproof/external_acceptor.py +211 -0
  8. openworkproof/mcp_server.py +2939 -0
  9. openworkproof/mcp_transport.py +72 -0
  10. openworkproof/models.py +3380 -0
  11. openworkproof/policy.py +2494 -0
  12. openworkproof/predicates.py +333 -0
  13. openworkproof/repo_pipeline/__init__.py +74 -0
  14. openworkproof/repo_pipeline/analysis.py +152 -0
  15. openworkproof/repo_pipeline/errors.py +64 -0
  16. openworkproof/repo_pipeline/models.py +80 -0
  17. openworkproof/repo_pipeline/output.py +68 -0
  18. openworkproof/repo_pipeline/reader.py +47 -0
  19. openworkproof/repo_pipeline/sources.py +120 -0
  20. openworkproof/repo_pipeline/traversal.py +253 -0
  21. openworkproof/repo_tools.py +8382 -0
  22. openworkproof/runtime_context.py +189 -0
  23. openworkproof/schema_registry.py +623 -0
  24. openworkproof/schemas/v0.1/acceptance-receipt.schema.json +1 -0
  25. openworkproof/schemas/v0.1/acceptance-rejection-receipt.schema.json +1 -0
  26. openworkproof/schemas/v0.1/action-receipt.schema.json +1 -0
  27. openworkproof/schemas/v0.1/capability-grant.schema.json +1 -0
  28. openworkproof/schemas/v0.1/schema-registry.json +1 -0
  29. openworkproof/schemas/v0.1/work-order.schema.json +1 -0
  30. openworkproof/signing.py +421 -0
  31. openworkproof/state.py +765 -0
  32. openworkproof/team_network_client.py +419 -0
  33. openworkproof/trusted_helper.py +269 -0
  34. openworkproof-1.0.0.dist-info/METADATA +578 -0
  35. openworkproof-1.0.0.dist-info/RECORD +39 -0
  36. openworkproof-1.0.0.dist-info/WHEEL +5 -0
  37. openworkproof-1.0.0.dist-info/entry_points.txt +2 -0
  38. openworkproof-1.0.0.dist-info/licenses/LICENSE +202 -0
  39. openworkproof-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2494 @@
1
+ """Immutable authorization policy replay for bounded signed histories."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable, Mapping
6
+ from dataclasses import dataclass
7
+ from datetime import datetime, timedelta, timezone
8
+ import hashlib
9
+ from itertools import islice
10
+ import json
11
+ import re
12
+
13
+ import rfc8785
14
+
15
+ from openworkproof.composition import (
16
+ AuthorizationCausalSnapshot,
17
+ AuthorizationCausalState,
18
+ AuthorizationCausalityError,
19
+ replay_authorization_causality,
20
+ validate_authorization_causal_bindings,
21
+ )
22
+ from openworkproof.models import (
23
+ ActionReceiptEnvelope,
24
+ AgentRequest,
25
+ ApplyPatchArguments,
26
+ ApprovalHumanDecision,
27
+ ApprovalDecisionReceipt,
28
+ ApprovalRequestedReceipt,
29
+ CapabilityGrant,
30
+ EvidenceRef,
31
+ GrantConsumedReceipt,
32
+ GrantIssuedReceipt,
33
+ GrantRevokedReceipt,
34
+ KeyBinding,
35
+ PatchResultEvidence,
36
+ PolicyDecision,
37
+ RepoReadArguments,
38
+ RollbackReceipt,
39
+ RunTestsArguments,
40
+ SystemEventReceipt,
41
+ TerminationHumanDecision,
42
+ TerminationDecisionReceipt,
43
+ ToolCallReceipt,
44
+ ToolRequestArguments,
45
+ WorkOrder,
46
+ ComposeProofArguments,
47
+ CreatePrProposalArguments,
48
+ request_arguments_digest,
49
+ )
50
+ from openworkproof.repo_tools import (
51
+ ReplayCheckpoint,
52
+ workspace_manifest_digest,
53
+ )
54
+ from openworkproof.predicates import (
55
+ EvaluationContext,
56
+ evaluate_required_predicates,
57
+ select_required_predicates,
58
+ )
59
+ from openworkproof.signing import (
60
+ decode_and_verify_key_binding,
61
+ verify_payload,
62
+ )
63
+ from openworkproof.state import (
64
+ TaskState,
65
+ _agent_direct_call_is_authorized,
66
+ _validate_termination,
67
+ append_receipt,
68
+ apply_state_transition,
69
+ )
70
+
71
+
72
+ MAX_EFFECTIVE_GRANTS = 8
73
+ MAX_GRANT_ATTEMPTS = 8
74
+ MAX_AUTHORIZATION_RECEIPTS = 64
75
+ DENIAL_PRECEDENCE = (
76
+ "STATE_DENIED",
77
+ "ROLE_DENIED",
78
+ "CAPABILITY_DENIED",
79
+ "APPROVAL_DENIED",
80
+ "PREDICATE_DENIED",
81
+ "QUOTA_EXHAUSTED",
82
+ )
83
+
84
+
85
+ class AuthorizationPolicyError(RuntimeError):
86
+ """The signed history cannot satisfy the frozen authorization policy."""
87
+
88
+
89
+ @dataclass(frozen=True, slots=True)
90
+ class AuthorizationLedgerPrefix:
91
+ """Canonical bounded inputs for one live authorization transaction."""
92
+
93
+ effective_grants: tuple[CapabilityGrant, ...]
94
+ grant_attempts: tuple[CapabilityGrant, ...]
95
+ receipts: tuple[ActionReceiptEnvelope, ...]
96
+
97
+ def __post_init__(self) -> None:
98
+ if any(
99
+ type(value) is not tuple
100
+ for value in (
101
+ self.effective_grants,
102
+ self.grant_attempts,
103
+ self.receipts,
104
+ )
105
+ ):
106
+ raise AuthorizationPolicyError(
107
+ "live policy collections must be exact tuples"
108
+ )
109
+ if (
110
+ len(self.effective_grants) > MAX_EFFECTIVE_GRANTS
111
+ or len(self.grant_attempts) > MAX_GRANT_ATTEMPTS
112
+ or len(self.receipts) > MAX_AUTHORIZATION_RECEIPTS
113
+ ):
114
+ raise AuthorizationPolicyError(
115
+ "live policy collection exceeds its bounded capacity"
116
+ )
117
+ if any(
118
+ not isinstance(grant, CapabilityGrant)
119
+ for grant in self.effective_grants + self.grant_attempts
120
+ ) or any(
121
+ not isinstance(receipt, ActionReceiptEnvelope)
122
+ for receipt in self.receipts
123
+ ):
124
+ raise AuthorizationPolicyError(
125
+ "live policy collection contains a malformed entry"
126
+ )
127
+ grant_ids = tuple(
128
+ grant.grant_id for grant in self.effective_grants
129
+ )
130
+ attempt_digests = tuple(
131
+ grant.digest for grant in self.grant_attempts
132
+ )
133
+ if (
134
+ grant_ids != tuple(sorted(grant_ids))
135
+ or len(set(grant_ids)) != len(grant_ids)
136
+ or attempt_digests != tuple(sorted(attempt_digests))
137
+ or len(set(attempt_digests)) != len(attempt_digests)
138
+ ):
139
+ raise AuthorizationPolicyError(
140
+ "live policy Grants are not canonical and unique"
141
+ )
142
+
143
+
144
+ @dataclass(frozen=True, slots=True)
145
+ class CommittedEvidence:
146
+ """Exact committed bytes paired with their signed evidence reference."""
147
+
148
+ reference: EvidenceRef
149
+ payload: bytes
150
+
151
+ def __post_init__(self) -> None:
152
+ if not isinstance(self.reference, EvidenceRef) or type(
153
+ self.payload
154
+ ) is not bytes:
155
+ raise AuthorizationPolicyError(
156
+ "committed evidence entry is malformed"
157
+ )
158
+
159
+
160
+ @dataclass(frozen=True)
161
+ class AuthorizationReplayState:
162
+ """Immutable policy facts reconstructed from the signed receipt history."""
163
+
164
+ active_patch_receipt_id: str | None
165
+ balances: tuple[tuple[str, str, int], ...]
166
+ revoked_grant_ids: tuple[str, ...]
167
+ used_single_use_grant_ids: tuple[str, ...]
168
+ approval_decision_by_request: tuple[tuple[str, str], ...]
169
+
170
+
171
+ @dataclass(frozen=True, slots=True)
172
+ class AuthorizationContext:
173
+ """Verified immutable facts used by one live authorization decision."""
174
+
175
+ work_order: WorkOrder
176
+ ledger_prefix: AuthorizationLedgerPrefix
177
+ committed_evidence: tuple[CommittedEvidence, ...]
178
+ replay_checkpoint: ReplayCheckpoint
179
+ transaction_time: datetime
180
+ causal_state: AuthorizationCausalState
181
+ replay_state: AuthorizationReplayState
182
+ current_state: str
183
+ routine_capacity_remaining: int
184
+ denial_count: int
185
+ independent_failure_terminal: bool
186
+
187
+ @property
188
+ def active_patch_receipt_id(self) -> str | None:
189
+ return self.causal_state.active_patch_receipt_id
190
+
191
+
192
+ @dataclass(frozen=True, slots=True)
193
+ class ProspectiveExecutionFacts:
194
+ """Sidecar-observed execution identity for a prospective test call."""
195
+
196
+ execution_context_id: str
197
+ container_instance_id_digest: str
198
+ controller_id: str
199
+
200
+ def __post_init__(self) -> None:
201
+ digest = re.compile(r"^[0-9a-f]{64}$")
202
+ key = re.compile(r"^ed25519:[0-9a-f]{64}$")
203
+ if (
204
+ type(self.execution_context_id) is not str
205
+ or digest.fullmatch(self.execution_context_id) is None
206
+ or type(self.container_instance_id_digest) is not str
207
+ or digest.fullmatch(self.container_instance_id_digest) is None
208
+ or type(self.controller_id) is not str
209
+ or key.fullmatch(self.controller_id) is None
210
+ ):
211
+ raise AuthorizationPolicyError(
212
+ "prospective execution facts are malformed"
213
+ )
214
+
215
+
216
+ @dataclass(frozen=True)
217
+ class _GrantReplayState:
218
+ remaining_tool_calls: int
219
+ remaining_repair_rounds: int
220
+ use_count: int
221
+ revoked: bool
222
+
223
+
224
+ @dataclass(frozen=True)
225
+ class _GrantReplayContext:
226
+ states_before_receipt: tuple[
227
+ tuple[str, tuple[tuple[str, _GrantReplayState], ...]],
228
+ ...,
229
+ ]
230
+ final_states: tuple[tuple[str, _GrantReplayState], ...]
231
+
232
+ def state_before(
233
+ self,
234
+ receipt_id: str,
235
+ grant_id: str,
236
+ ) -> _GrantReplayState | None:
237
+ for candidate_id, states in self.states_before_receipt:
238
+ if candidate_id != receipt_id:
239
+ continue
240
+ return dict(states).get(grant_id)
241
+ return None
242
+
243
+ def final_by_grant(self) -> dict[str, _GrantReplayState]:
244
+ return dict(self.final_states)
245
+
246
+
247
+ def _fail(message: str) -> AuthorizationPolicyError:
248
+ return AuthorizationPolicyError(message)
249
+
250
+
251
+ def _validate_tool_authorization_inputs(
252
+ context: AuthorizationContext,
253
+ request: AgentRequest,
254
+ request_arguments: ToolRequestArguments,
255
+ execution_facts: ProspectiveExecutionFacts | None,
256
+ ) -> tuple[CapabilityGrant, KeyBinding]:
257
+ """Verify signed prospective inputs before any policy decision."""
258
+
259
+ argument_tools = {
260
+ RepoReadArguments: "owp.repo_read",
261
+ ApplyPatchArguments: "owp.apply_patch",
262
+ RunTestsArguments: "owp.run_tests",
263
+ CreatePrProposalArguments: "owp.create_pr_proposal",
264
+ ComposeProofArguments: "owp.compose_proof",
265
+ }
266
+ if type(context) is not AuthorizationContext:
267
+ raise _fail("authorization context is malformed")
268
+ if type(request) is not AgentRequest:
269
+ raise _fail("Agent request is malformed")
270
+ tool_name = argument_tools.get(type(request_arguments))
271
+ if tool_name is None:
272
+ raise _fail("tool request arguments are malformed")
273
+ if execution_facts is not None and type(
274
+ execution_facts
275
+ ) is not ProspectiveExecutionFacts:
276
+ raise _fail("prospective execution facts are malformed")
277
+ if isinstance(request_arguments, RunTestsArguments):
278
+ if execution_facts is None:
279
+ raise _fail("test execution facts are required")
280
+ elif execution_facts is not None:
281
+ raise _fail("execution facts are forbidden for this tool")
282
+ if (
283
+ request.work_order_digest != context.work_order.digest
284
+ or request.tool_name != tool_name
285
+ or request.arguments_digest
286
+ != request_arguments_digest(tool_name, request_arguments)
287
+ ):
288
+ raise _fail("Agent request integrity binding is invalid")
289
+
290
+ grants = {
291
+ grant.grant_id: grant
292
+ for grant in context.ledger_prefix.effective_grants
293
+ }
294
+ grant = grants.get(request.grant_id)
295
+ bindings = {
296
+ binding.key_id: binding
297
+ for binding in context.work_order.key_bindings
298
+ }
299
+ binding = bindings.get(request.actor_key_id)
300
+ if (
301
+ grant is None
302
+ or binding is None
303
+ or request.actor_id != binding.subject_id
304
+ or grant.subject_key_id != binding.key_id
305
+ or grant.subject_agent_id != binding.subject_id
306
+ ):
307
+ raise _fail("Agent request Grant or actor binding is invalid")
308
+ try:
309
+ public_key = decode_and_verify_key_binding(binding)
310
+ except Exception as error:
311
+ raise _fail("Agent request key binding is invalid") from error
312
+ if not verify_payload(
313
+ "agent-request",
314
+ request.model_dump(mode="json"),
315
+ public_key,
316
+ ):
317
+ raise _fail("Agent request signature is invalid")
318
+
319
+ request_age = context.transaction_time - request.requested_at
320
+ if request_age < timedelta(0) or request_age > timedelta(seconds=300):
321
+ raise _fail("Agent request is outside the freshness window")
322
+
323
+ if execution_facts is not None:
324
+ sidecar_key_ids = {
325
+ candidate.key_id
326
+ for candidate in context.work_order.key_bindings
327
+ if candidate.role == "Sidecar"
328
+ }
329
+ if execution_facts.controller_id not in sidecar_key_ids:
330
+ raise _fail("prospective execution controller is not the Sidecar")
331
+ return grant, binding
332
+
333
+
334
+ def _denial_code(
335
+ *,
336
+ state_allowed: bool,
337
+ role_allowed: bool,
338
+ capability_allowed: bool,
339
+ approval_allowed: bool = True,
340
+ predicate_allowed: bool = True,
341
+ quota_allowed: bool = True,
342
+ ) -> str | None:
343
+ allowed = (
344
+ state_allowed,
345
+ role_allowed,
346
+ capability_allowed,
347
+ approval_allowed,
348
+ predicate_allowed,
349
+ quota_allowed,
350
+ )
351
+ return next(
352
+ (
353
+ error_code
354
+ for error_code, condition in zip(
355
+ DENIAL_PRECEDENCE,
356
+ allowed,
357
+ strict=True,
358
+ )
359
+ if not condition
360
+ ),
361
+ None,
362
+ )
363
+
364
+
365
+ def _remaining_tool_calls(
366
+ context: AuthorizationContext,
367
+ grant_id: str,
368
+ ) -> int:
369
+ balances = {
370
+ (candidate_id, metric): remaining
371
+ for candidate_id, metric, remaining in context.replay_state.balances
372
+ }
373
+ try:
374
+ return balances[(grant_id, "tool_calls")]
375
+ except KeyError as error:
376
+ raise _fail("effective Grant balance is unavailable") from error
377
+
378
+
379
+ def _policy_decision(error_code: str | None) -> PolicyDecision:
380
+ return PolicyDecision(
381
+ allowed=error_code is None,
382
+ decision="allow" if error_code is None else "deny",
383
+ error_code=error_code,
384
+ reason=(
385
+ "TOOL_CALL_AUTHORIZED"
386
+ if error_code is None
387
+ else "TOOL_CALL_DENIED"
388
+ ),
389
+ )
390
+
391
+
392
+ def _prospective_state_allowed(
393
+ context: AuthorizationContext,
394
+ tool_name: str,
395
+ arguments: ToolRequestArguments,
396
+ ) -> bool:
397
+ if tool_name == "owp.repo_read":
398
+ return context.current_state in {"running", "retrying"}
399
+ if tool_name == "owp.apply_patch":
400
+ return (
401
+ context.current_state in {"running", "retrying"}
402
+ and context.active_patch_receipt_id is None
403
+ )
404
+ if tool_name == "owp.run_tests":
405
+ return context.current_state in {"running", "retrying"} or (
406
+ context.current_state == "evidence_incomplete"
407
+ and isinstance(arguments, RunTestsArguments)
408
+ and arguments.test_mode == "verifier"
409
+ )
410
+ if tool_name == "owp.create_pr_proposal":
411
+ return (
412
+ context.current_state in {"running", "retrying"}
413
+ and context.active_patch_receipt_id is not None
414
+ )
415
+ if tool_name == "owp.compose_proof":
416
+ return (
417
+ context.current_state
418
+ in {"locally_verified", "evidence_incomplete"}
419
+ and context.active_patch_receipt_id is not None
420
+ )
421
+ return False
422
+
423
+
424
+ def _prospective_role_allowed(
425
+ role: str,
426
+ tool_name: str,
427
+ arguments: ToolRequestArguments,
428
+ ) -> bool:
429
+ if role == "Manager":
430
+ return tool_name in {
431
+ "owp.compose_proof",
432
+ "owp.create_pr_proposal",
433
+ }
434
+ if role == "Developer":
435
+ return tool_name in {"owp.apply_patch", "owp.repo_read"} or (
436
+ tool_name == "owp.run_tests"
437
+ and isinstance(arguments, RunTestsArguments)
438
+ and arguments.test_mode == "developer"
439
+ )
440
+ if role == "Verifier":
441
+ return (
442
+ tool_name == "owp.run_tests"
443
+ and isinstance(arguments, RunTestsArguments)
444
+ and arguments.test_mode == "verifier"
445
+ )
446
+ return False
447
+
448
+
449
+ def _static_predicates_allowed(
450
+ context: AuthorizationContext,
451
+ grant: CapabilityGrant,
452
+ arguments: ToolRequestArguments,
453
+ execution_facts: ProspectiveExecutionFacts | None,
454
+ ) -> bool:
455
+ if isinstance(arguments, RepoReadArguments):
456
+ return _roots_within((arguments.path,), grant.allowed_read_roots)
457
+ if isinstance(arguments, ApplyPatchArguments):
458
+ return _roots_within(
459
+ arguments.target_paths,
460
+ grant.allowed_write_roots,
461
+ )
462
+ if not isinstance(arguments, RunTestsArguments):
463
+ return True
464
+ if execution_facts is None or context.active_patch_receipt_id is None:
465
+ return False
466
+ profiles = tuple(
467
+ profile
468
+ for profile in context.work_order.test_profiles
469
+ if profile.test_mode == arguments.test_mode
470
+ )
471
+ if len(profiles) != 1:
472
+ return False
473
+ profile = profiles[0]
474
+ if (
475
+ arguments.command_digest != profile.command_digest
476
+ or arguments.source_commit != context.work_order.source_commit
477
+ or arguments.candidate_commit
478
+ != context.replay_checkpoint.head_commit
479
+ or arguments.workspace_manifest_digest
480
+ != context.replay_checkpoint.workspace_manifest_digest
481
+ or arguments.container_image_digest
482
+ != profile.container_image_digest
483
+ or arguments.fixed_test_source_digest
484
+ != profile.fixed_test_source_digest
485
+ ):
486
+ return False
487
+ used_execution_contexts: set[str] = set()
488
+ used_container_instances: set[str] = set()
489
+ for receipt in context.ledger_prefix.receipts:
490
+ if not isinstance(receipt, ToolCallReceipt):
491
+ continue
492
+ if receipt.tool_name != "owp.run_tests":
493
+ continue
494
+ factors = receipt.correlation_factors
495
+ if factors.execution_context_id is not None:
496
+ used_execution_contexts.add(factors.execution_context_id)
497
+ if factors.container_instance_id_digest is not None:
498
+ used_container_instances.add(
499
+ factors.container_instance_id_digest
500
+ )
501
+ if (
502
+ execution_facts.execution_context_id in used_execution_contexts
503
+ or execution_facts.container_instance_id_digest
504
+ in used_container_instances
505
+ ):
506
+ return False
507
+ return not (
508
+ arguments.test_mode == "verifier"
509
+ and context.causal_state.independent_result_receipt_id is not None
510
+ )
511
+
512
+
513
+ def _prospective_approval_allowed(
514
+ context: AuthorizationContext,
515
+ tool_name: str,
516
+ arguments: ToolRequestArguments,
517
+ ) -> bool:
518
+ if tool_name != "owp.create_pr_proposal":
519
+ return True
520
+ if not isinstance(arguments, CreatePrProposalArguments):
521
+ return False
522
+ by_id = {
523
+ receipt.receipt_id: receipt
524
+ for receipt in context.ledger_prefix.receipts
525
+ }
526
+ patch = by_id.get(context.active_patch_receipt_id)
527
+ decision = by_id.get(arguments.approval_receipt_id)
528
+ request = (
529
+ by_id.get(decision.request_receipt_id)
530
+ if isinstance(decision, ApprovalDecisionReceipt)
531
+ else None
532
+ )
533
+ decisions = dict(context.replay_state.approval_decision_by_request)
534
+ return (
535
+ isinstance(patch, ToolCallReceipt)
536
+ and patch.tool_name == "owp.apply_patch"
537
+ and isinstance(decision, ApprovalDecisionReceipt)
538
+ and isinstance(request, ApprovalRequestedReceipt)
539
+ and decisions.get(request.receipt_id) == decision.receipt_id
540
+ and arguments.target_patch_digest == patch.digest
541
+ and arguments.approval_receipt_digest == decision.digest
542
+ and decision.request_receipt_digest == request.digest
543
+ and decision.decision == "approved"
544
+ and decision.policy_decision == "allow"
545
+ and decision.execution_status == "succeeded"
546
+ and request.request_kind == "high_risk_action"
547
+ and request.requested_scope["work_order_digest"]
548
+ == context.work_order.digest
549
+ and request.requested_scope["target_patch_digest"]
550
+ == patch.digest
551
+ and decision.approved_scope == request.requested_scope
552
+ and context.transaction_time <= request.expires_at
553
+ and context.transaction_time <= decision.expires_at
554
+ )
555
+
556
+
557
+ def _compose_arguments_allowed(
558
+ context: AuthorizationContext,
559
+ tool_name: str,
560
+ arguments: ToolRequestArguments,
561
+ ) -> bool:
562
+ if tool_name != "owp.compose_proof":
563
+ return True
564
+ if not isinstance(arguments, ComposeProofArguments):
565
+ return False
566
+ from openworkproof import evidence as _evidence
567
+
568
+ expected_version = _evidence._derive_protocol_transaction_version(
569
+ action_receipts=context.ledger_prefix.receipts,
570
+ acceptance_receipts=(),
571
+ )
572
+ if arguments.expected_state_version != expected_version:
573
+ return False
574
+ if context.current_state == "locally_verified":
575
+ return arguments.previous_report_digest is None
576
+ if context.current_state != "evidence_incomplete":
577
+ return False
578
+ by_id = {
579
+ receipt.receipt_id: receipt
580
+ for receipt in context.ledger_prefix.receipts
581
+ }
582
+ trigger = by_id.get(
583
+ context.causal_state.latest_composition_trigger_id
584
+ )
585
+ independent = by_id.get(
586
+ context.causal_state.independent_result_receipt_id
587
+ )
588
+ return (
589
+ isinstance(trigger, SystemEventReceipt)
590
+ and trigger.system_event_name == "proof_composed"
591
+ and isinstance(independent, ToolCallReceipt)
592
+ and independent.tool_name == "owp.run_tests"
593
+ and arguments.previous_report_digest
594
+ == trigger.cause.composition_report_digest
595
+ )
596
+
597
+
598
+ def authorize_tool_call(
599
+ context: AuthorizationContext,
600
+ request: AgentRequest,
601
+ request_arguments: ToolRequestArguments,
602
+ execution_facts: ProspectiveExecutionFacts | None = None,
603
+ ) -> PolicyDecision:
604
+ """Decide prospective handler eligibility without mutating authority."""
605
+
606
+ grant, binding = _validate_tool_authorization_inputs(
607
+ context,
608
+ request,
609
+ request_arguments,
610
+ execution_facts,
611
+ )
612
+ if context.transaction_time > context.work_order.deadline:
613
+ raise _fail("contract expired before tool authorization")
614
+ if context.routine_capacity_remaining <= 0:
615
+ raise _fail("routine Receipt capacity is exhausted")
616
+ if context.independent_failure_terminal:
617
+ raise _fail("EVIDENCE_FAILURE_SEALED")
618
+
619
+ capability_allowed = (
620
+ request.tool_name in context.work_order.allowed_tools
621
+ and request.tool_name in grant.allowed_tools
622
+ and grant.grant_id not in context.replay_state.revoked_grant_ids
623
+ and grant.valid_from
624
+ <= context.transaction_time
625
+ <= min(grant.expires_at, context.work_order.deadline)
626
+ )
627
+ error_code = _denial_code(
628
+ state_allowed=_prospective_state_allowed(
629
+ context,
630
+ request.tool_name,
631
+ request_arguments,
632
+ ),
633
+ role_allowed=_prospective_role_allowed(
634
+ binding.role,
635
+ request.tool_name,
636
+ request_arguments,
637
+ ),
638
+ capability_allowed=capability_allowed,
639
+ approval_allowed=_prospective_approval_allowed(
640
+ context,
641
+ request.tool_name,
642
+ request_arguments,
643
+ ),
644
+ predicate_allowed=(
645
+ _static_predicates_allowed(
646
+ context,
647
+ grant,
648
+ request_arguments,
649
+ execution_facts,
650
+ )
651
+ and _compose_arguments_allowed(
652
+ context,
653
+ request.tool_name,
654
+ request_arguments,
655
+ )
656
+ ),
657
+ quota_allowed=_remaining_tool_calls(context, grant.grant_id) > 0,
658
+ )
659
+ return _policy_decision(error_code)
660
+
661
+
662
+ def validate_human_decision(
663
+ context: AuthorizationContext,
664
+ claim: ApprovalHumanDecision | TerminationHumanDecision,
665
+ ) -> PolicyDecision:
666
+ """Decide a signed human claim without mutating authority."""
667
+
668
+ if type(context) is not AuthorizationContext or type(claim) not in {
669
+ ApprovalHumanDecision,
670
+ TerminationHumanDecision,
671
+ }:
672
+ raise _fail("human decision inputs are malformed")
673
+ if claim.work_order_digest != context.work_order.digest:
674
+ raise _fail("human decision WorkOrder binding is invalid")
675
+
676
+ bindings = tuple(
677
+ binding
678
+ for binding in context.work_order.key_bindings
679
+ if binding.key_id == claim.actor_key_id
680
+ )
681
+ if (
682
+ len(bindings) != 1
683
+ or claim.actor_id != bindings[0].subject_id
684
+ or claim.signer_key_id != bindings[0].key_id
685
+ ):
686
+ raise _fail("human decision actor binding is invalid")
687
+ binding = bindings[0]
688
+ try:
689
+ public_key = decode_and_verify_key_binding(binding)
690
+ except Exception as error:
691
+ raise _fail("human decision key binding is invalid") from error
692
+ if not verify_payload(
693
+ "human-decision",
694
+ claim.model_dump(mode="json"),
695
+ public_key,
696
+ ):
697
+ raise _fail("human decision signature is invalid")
698
+
699
+ ingestion_age = context.transaction_time - claim.decided_at
700
+ if ingestion_age < timedelta(0) or ingestion_age > timedelta(seconds=300):
701
+ raise _fail("human decision freshness window is invalid")
702
+
703
+ if isinstance(claim, TerminationHumanDecision):
704
+ if (
705
+ claim.target_work_order_digest != context.work_order.digest
706
+ or claim.decided_at < context.work_order.issued_at
707
+ ):
708
+ raise _fail("human termination binding is invalid")
709
+ error_code = _denial_code(
710
+ state_allowed=context.current_state
711
+ not in {"accepted", "rejected"},
712
+ role_allowed=binding.role == "Maintainer",
713
+ capability_allowed=True,
714
+ )
715
+ return _policy_decision(error_code)
716
+
717
+ request = next(
718
+ (
719
+ receipt
720
+ for receipt in context.ledger_prefix.receipts
721
+ if receipt.receipt_id == claim.request_receipt_id
722
+ ),
723
+ None,
724
+ )
725
+ if (
726
+ not isinstance(request, ApprovalRequestedReceipt)
727
+ or request.policy_decision != "allow"
728
+ or request.execution_status != "succeeded"
729
+ or request.request_kind != "high_risk_action"
730
+ or request.digest != claim.request_receipt_digest
731
+ or request.requested_scope != claim.approved_scope
732
+ or request.expires_at != claim.expires_at
733
+ or claim.decided_at < request.occurred_at
734
+ or claim.decided_at > request.expires_at
735
+ ):
736
+ raise _fail("human approval binding is invalid")
737
+ if claim.request_receipt_id in dict(
738
+ context.replay_state.approval_decision_by_request
739
+ ):
740
+ raise _fail("human approval request already has a decision")
741
+
742
+ error_code = _denial_code(
743
+ state_allowed=context.current_state in {"running", "retrying"},
744
+ role_allowed=binding.role == request.required_role,
745
+ capability_allowed=True,
746
+ approval_allowed=(
747
+ claim.decision == "approved"
748
+ and context.transaction_time <= claim.expires_at
749
+ ),
750
+ )
751
+ return _policy_decision(error_code)
752
+
753
+
754
+ def validate_rollback(
755
+ context: AuthorizationContext,
756
+ request: AgentRequest,
757
+ ) -> PolicyDecision:
758
+ """Decide prospective rollback eligibility without mutating authority."""
759
+
760
+ if (
761
+ type(context) is not AuthorizationContext
762
+ or type(request) is not AgentRequest
763
+ ):
764
+ raise _fail("rollback authorization inputs are malformed")
765
+ grants = {
766
+ grant.grant_id: grant
767
+ for grant in context.ledger_prefix.effective_grants
768
+ }
769
+ bindings = {
770
+ binding.key_id: binding
771
+ for binding in context.work_order.key_bindings
772
+ }
773
+ grant = grants.get(request.grant_id)
774
+ binding = bindings.get(request.actor_key_id)
775
+ if (
776
+ request.tool_name != "owp.rollback_patch"
777
+ or request.work_order_digest != context.work_order.digest
778
+ or grant is None
779
+ or binding is None
780
+ or request.actor_id != binding.subject_id
781
+ or request.signer_key_id != binding.key_id
782
+ or grant.subject_agent_id != binding.subject_id
783
+ or grant.subject_key_id != binding.key_id
784
+ ):
785
+ raise _fail("rollback request Grant or actor binding is invalid")
786
+ try:
787
+ public_key = decode_and_verify_key_binding(binding)
788
+ except Exception as error:
789
+ raise _fail("rollback request key binding is invalid") from error
790
+ if not verify_payload(
791
+ "agent-request",
792
+ request.model_dump(mode="json"),
793
+ public_key,
794
+ ):
795
+ raise _fail("rollback request signature is invalid")
796
+ request_age = context.transaction_time - request.requested_at
797
+ if request_age < timedelta(0) or request_age > timedelta(seconds=300):
798
+ raise _fail("rollback request is outside the freshness window")
799
+ target = next(
800
+ (
801
+ receipt
802
+ for receipt in context.ledger_prefix.receipts
803
+ if receipt.receipt_id == context.active_patch_receipt_id
804
+ ),
805
+ None,
806
+ )
807
+ if (
808
+ not isinstance(target, ToolCallReceipt)
809
+ or target.tool_name != "owp.apply_patch"
810
+ or target.policy_decision != "allow"
811
+ or target.execution_status != "succeeded"
812
+ ):
813
+ raise _fail("rollback target patch is unavailable")
814
+ expected_arguments = {
815
+ "target_patch_receipt_id": target.receipt_id,
816
+ "target_patch_digest": target.digest,
817
+ "before_commit": context.replay_checkpoint.head_commit,
818
+ }
819
+ if request.arguments_digest != request_arguments_digest(
820
+ "owp.rollback_patch",
821
+ expected_arguments,
822
+ ):
823
+ raise _fail("rollback target binding is invalid")
824
+ if context.transaction_time > context.work_order.deadline:
825
+ raise _fail("contract expired before rollback authorization")
826
+ if context.routine_capacity_remaining <= 0:
827
+ raise _fail("routine Receipt capacity is exhausted")
828
+ capability_allowed = (
829
+ request.tool_name in context.work_order.allowed_tools
830
+ and request.tool_name in grant.allowed_tools
831
+ and grant.grant_id not in context.replay_state.revoked_grant_ids
832
+ and (
833
+ grant.usage_mode != "single_use"
834
+ or grant.grant_id
835
+ not in context.replay_state.used_single_use_grant_ids
836
+ )
837
+ and grant.valid_from
838
+ <= context.transaction_time
839
+ <= min(grant.expires_at, context.work_order.deadline)
840
+ )
841
+ return _policy_decision(
842
+ _denial_code(
843
+ state_allowed=(
844
+ context.current_state == "needs_rework"
845
+ and context.causal_state.failure_receipt_id is not None
846
+ and context.causal_state.rollback_receipt_id is None
847
+ ),
848
+ role_allowed=binding.role == "Developer",
849
+ capability_allowed=capability_allowed,
850
+ quota_allowed=_remaining_tool_calls(
851
+ context,
852
+ grant.grant_id,
853
+ )
854
+ > 0,
855
+ )
856
+ )
857
+
858
+
859
+ def _bounded_mapping(
860
+ value: Mapping[str, CapabilityGrant],
861
+ *,
862
+ cap: int,
863
+ label: str,
864
+ ) -> dict[str, CapabilityGrant]:
865
+ if not isinstance(value, Mapping):
866
+ raise _fail(f"{label} must be a mapping")
867
+ try:
868
+ items = tuple(islice(value.items(), cap + 1))
869
+ except AuthorizationPolicyError:
870
+ raise
871
+ except Exception as error:
872
+ raise _fail(f"{label} input is unavailable") from error
873
+ if len(items) > cap:
874
+ raise _fail(f"{label} exceeds its bounded capacity")
875
+ snapshot: dict[str, CapabilityGrant] = {}
876
+ for key, grant in items:
877
+ if (
878
+ type(key) is not str
879
+ or not isinstance(grant, CapabilityGrant)
880
+ or key in snapshot
881
+ ):
882
+ raise _fail(f"{label} contains a malformed entry")
883
+ snapshot[key] = grant
884
+ return snapshot
885
+
886
+
887
+ def _bounded_receipts(
888
+ receipts: Iterable[ActionReceiptEnvelope],
889
+ ) -> tuple[ActionReceiptEnvelope, ...]:
890
+ try:
891
+ bounded = tuple(
892
+ islice(receipts, MAX_AUTHORIZATION_RECEIPTS + 1)
893
+ )
894
+ except AuthorizationPolicyError:
895
+ raise
896
+ except Exception as error:
897
+ raise _fail("receipt history input is unavailable") from error
898
+ if len(bounded) > MAX_AUTHORIZATION_RECEIPTS:
899
+ raise _fail("receipt history exceeds its bounded capacity")
900
+ if any(
901
+ not isinstance(receipt, ActionReceiptEnvelope)
902
+ for receipt in bounded
903
+ ):
904
+ raise _fail("receipt history contains a malformed receipt")
905
+ return bounded
906
+
907
+
908
+ def _validate_grant_signatures(
909
+ work_order: WorkOrder,
910
+ grants: Mapping[str, CapabilityGrant],
911
+ attempts: Mapping[str, CapabilityGrant],
912
+ ) -> None:
913
+ try:
914
+ bindings_by_key = {
915
+ binding.key_id: (
916
+ binding,
917
+ decode_and_verify_key_binding(binding),
918
+ )
919
+ for binding in work_order.key_bindings
920
+ }
921
+ except Exception as error:
922
+ raise _fail("Grant signature key bindings are unavailable") from error
923
+ if len(bindings_by_key) != len(work_order.key_bindings):
924
+ raise _fail("Grant signature key bindings are not unique")
925
+
926
+ def validate_grant(
927
+ grant: CapabilityGrant,
928
+ *,
929
+ issuer_roles: set[str],
930
+ subject_roles: set[str],
931
+ ) -> None:
932
+ issuer_entry = bindings_by_key.get(grant.issuer_key_id)
933
+ subject_entry = bindings_by_key.get(grant.subject_key_id)
934
+ if (
935
+ grant.work_order_digest != work_order.digest
936
+ or issuer_entry is None
937
+ or issuer_entry[0].role not in issuer_roles
938
+ or grant.signer_key_id != grant.issuer_key_id
939
+ or subject_entry is None
940
+ or subject_entry[0].subject_id
941
+ != grant.subject_agent_id
942
+ or subject_entry[0].role not in subject_roles
943
+ or not verify_payload(
944
+ "capability-grant",
945
+ grant.model_dump(mode="json"),
946
+ issuer_entry[1],
947
+ )
948
+ ):
949
+ raise _fail("Grant signature or role binding is invalid")
950
+
951
+ for grant in grants.values():
952
+ validate_grant(
953
+ grant,
954
+ issuer_roles={
955
+ "Maintainer"
956
+ if grant.parent_grant_id is None
957
+ else "Manager"
958
+ },
959
+ subject_roles=(
960
+ {"Manager"}
961
+ if grant.parent_grant_id is None
962
+ else {"Developer", "Verifier"}
963
+ ),
964
+ )
965
+ for grant in attempts.values():
966
+ validate_grant(
967
+ grant,
968
+ issuer_roles=(
969
+ {"Maintainer"}
970
+ if grant.parent_grant_id is None
971
+ else {"Manager", "Developer", "Verifier"}
972
+ ),
973
+ subject_roles=(
974
+ {"Manager"}
975
+ if grant.parent_grant_id is None
976
+ else {"Developer", "Verifier"}
977
+ ),
978
+ )
979
+
980
+
981
+ def _root_contains(root: str, candidate: str) -> bool:
982
+ return candidate == root or candidate.startswith(f"{root}/")
983
+
984
+
985
+ def _roots_within(
986
+ candidates: tuple[str, ...],
987
+ allowed: tuple[str, ...],
988
+ ) -> bool:
989
+ return all(
990
+ any(_root_contains(root, candidate) for root in allowed)
991
+ for candidate in candidates
992
+ )
993
+
994
+
995
+ def _child_signing_binding(
996
+ work_order: WorkOrder,
997
+ candidate: CapabilityGrant,
998
+ ):
999
+ matches = tuple(
1000
+ binding
1001
+ for binding in work_order.key_bindings
1002
+ if binding.key_id == candidate.signer_key_id
1003
+ )
1004
+ if len(matches) != 1:
1005
+ raise _fail("child Grant signer binding is unavailable")
1006
+ return matches[0]
1007
+
1008
+
1009
+ def _request_matches_candidate_issuer(request, binding) -> bool:
1010
+ return (
1011
+ request.actor_id == binding.subject_id
1012
+ and request.actor_key_id == binding.key_id
1013
+ and request.signer_key_id == binding.key_id
1014
+ )
1015
+
1016
+
1017
+ def _child_policy_decision(
1018
+ work_order: WorkOrder,
1019
+ parent: CapabilityGrant,
1020
+ parent_state: _GrantReplayState | None,
1021
+ candidate: CapabilityGrant,
1022
+ request,
1023
+ signing_binding,
1024
+ bindings: Mapping[str, object],
1025
+ now,
1026
+ *,
1027
+ state_allowed: bool,
1028
+ ) -> tuple[bool, str | None]:
1029
+ manager = bindings["Manager"]
1030
+ developer = bindings["Developer"]
1031
+ verifier = bindings["Verifier"]
1032
+ matching_subjects = tuple(
1033
+ binding
1034
+ for binding in (developer, verifier)
1035
+ if (
1036
+ candidate.subject_agent_id == binding.subject_id
1037
+ and candidate.subject_key_id == binding.key_id
1038
+ )
1039
+ )
1040
+ authority_valid = (
1041
+ signing_binding.role == "Manager"
1042
+ and signing_binding.key_id == manager.key_id
1043
+ and parent.subject_agent_id == manager.subject_id
1044
+ and parent.subject_key_id == manager.key_id
1045
+ and request.actor_id == manager.subject_id
1046
+ and request.actor_key_id == manager.key_id
1047
+ )
1048
+ role_allowed = authority_valid and len(matching_subjects) == 1
1049
+ tools_valid = (
1050
+ set(candidate.allowed_tools) <= set(parent.allowed_tools)
1051
+ and set(candidate.allowed_tools) <= set(work_order.allowed_tools)
1052
+ )
1053
+ roots_valid = (
1054
+ _roots_within(
1055
+ candidate.allowed_read_roots,
1056
+ parent.allowed_read_roots,
1057
+ )
1058
+ and _roots_within(
1059
+ candidate.allowed_read_roots,
1060
+ work_order.allowed_read_roots,
1061
+ )
1062
+ and _roots_within(
1063
+ candidate.allowed_write_roots,
1064
+ parent.allowed_write_roots,
1065
+ )
1066
+ and _roots_within(
1067
+ candidate.allowed_write_roots,
1068
+ work_order.allowed_write_roots,
1069
+ )
1070
+ )
1071
+ subject = matching_subjects[0] if len(matching_subjects) == 1 else None
1072
+ role_tools = (
1073
+ {"owp.run_tests"}
1074
+ if subject is not None and subject.role == "Verifier"
1075
+ else {
1076
+ "owp.apply_patch",
1077
+ "owp.repo_read",
1078
+ "owp.rollback_patch",
1079
+ *(
1080
+ ("owp.run_tests",)
1081
+ if any(
1082
+ profile.test_mode == "developer"
1083
+ for profile in work_order.test_profiles
1084
+ )
1085
+ else ()
1086
+ ),
1087
+ }
1088
+ )
1089
+ time_valid = (
1090
+ work_order.issued_at
1091
+ <= candidate.issued_at
1092
+ <= candidate.valid_from
1093
+ < candidate.expires_at
1094
+ == work_order.deadline
1095
+ and parent.valid_from
1096
+ <= candidate.issued_at
1097
+ <= candidate.valid_from
1098
+ < candidate.expires_at
1099
+ <= parent.expires_at
1100
+ and parent.valid_from <= now <= parent.expires_at
1101
+ and 0 <= (now - candidate.issued_at).total_seconds() <= 300
1102
+ )
1103
+ rights_valid = (
1104
+ candidate.may_delegate is False
1105
+ and candidate.usage_mode in {"single_use", "metered"}
1106
+ )
1107
+ quota_valid = (
1108
+ parent_state is not None
1109
+ and candidate.quota.tool_calls
1110
+ <= parent_state.remaining_tool_calls
1111
+ and candidate.quota.repair_rounds
1112
+ <= parent_state.remaining_repair_rounds
1113
+ )
1114
+ capability_valid = (
1115
+ parent_state is not None
1116
+ and not parent_state.revoked
1117
+ and subject is not None
1118
+ and tools_valid
1119
+ and set(candidate.allowed_tools) <= role_tools
1120
+ and roots_valid
1121
+ and not (
1122
+ subject is not None
1123
+ and subject.role == "Verifier"
1124
+ and candidate.allowed_write_roots
1125
+ )
1126
+ and time_valid
1127
+ and rights_valid
1128
+ )
1129
+ error_code = _denial_code(
1130
+ state_allowed=state_allowed,
1131
+ role_allowed=role_allowed,
1132
+ capability_allowed=capability_valid,
1133
+ quota_allowed=quota_valid,
1134
+ )
1135
+ return error_code is None, error_code
1136
+
1137
+
1138
+ def _replay_grant_context(
1139
+ work_order: WorkOrder,
1140
+ receipts: Iterable[ActionReceiptEnvelope],
1141
+ grants: Mapping[str, CapabilityGrant],
1142
+ ) -> _GrantReplayContext:
1143
+ mutable: dict[str, dict[str, int | bool]] = {}
1144
+ states_before_receipt: list[
1145
+ tuple[str, tuple[tuple[str, _GrantReplayState], ...]]
1146
+ ] = []
1147
+
1148
+ def frozen_states() -> tuple[tuple[str, _GrantReplayState], ...]:
1149
+ return tuple(
1150
+ (
1151
+ grant_id,
1152
+ _GrantReplayState(
1153
+ remaining_tool_calls=int(state["tool_calls"]),
1154
+ remaining_repair_rounds=int(
1155
+ state["repair_rounds"]
1156
+ ),
1157
+ use_count=int(state["use_count"]),
1158
+ revoked=bool(state["revoked"]),
1159
+ ),
1160
+ )
1161
+ for grant_id, state in sorted(mutable.items())
1162
+ )
1163
+
1164
+ def add_grant(grant: CapabilityGrant) -> None:
1165
+ if grant.grant_id in mutable:
1166
+ raise _fail("Grant quota replay encountered duplicate authority")
1167
+ mutable[grant.grant_id] = {
1168
+ "tool_calls": grant.quota.tool_calls,
1169
+ "repair_rounds": grant.quota.repair_rounds,
1170
+ "use_count": 0,
1171
+ "revoked": False,
1172
+ }
1173
+
1174
+ def consume_authority(grant: CapabilityGrant) -> None:
1175
+ state = mutable.get(grant.grant_id)
1176
+ if state is None or state["revoked"] is True:
1177
+ raise _fail("Grant quota replay references inactive authority")
1178
+ if grant.usage_mode == "single_use" and state["use_count"] != 0:
1179
+ raise _fail("single-use Grant was consumed more than once")
1180
+ state["use_count"] = int(state["use_count"]) + 1
1181
+
1182
+ def validate_direct_call(receipt, *, require_active: bool):
1183
+ grant = grants.get(receipt.grant_id)
1184
+ state = mutable.get(receipt.grant_id)
1185
+ request = receipt.nested_claim
1186
+ elapsed = (
1187
+ receipt.occurred_at - request.requested_at
1188
+ ).total_seconds()
1189
+ if (
1190
+ grant is None
1191
+ or state is None
1192
+ or receipt.actor_id != grant.subject_agent_id
1193
+ or receipt.actor_key_id != grant.subject_key_id
1194
+ or request.actor_id != grant.subject_agent_id
1195
+ or request.actor_key_id != grant.subject_key_id
1196
+ or request.signer_key_id != grant.subject_key_id
1197
+ or request.grant_id != grant.grant_id
1198
+ or request.work_order_digest != work_order.digest
1199
+ or request.requested_at < work_order.issued_at
1200
+ or request.requested_at > work_order.deadline
1201
+ or receipt.occurred_at < work_order.issued_at
1202
+ or receipt.occurred_at > work_order.deadline
1203
+ or elapsed < 0
1204
+ or elapsed > 300
1205
+ ):
1206
+ raise _fail("Grant call binding failed semantic replay")
1207
+ role_authorized = _agent_direct_call_is_authorized(
1208
+ receipt,
1209
+ work_order,
1210
+ )
1211
+ if require_active and not role_authorized:
1212
+ raise _fail("Grant direct-call role failed semantic replay")
1213
+ if require_active and (
1214
+ state["revoked"] is True
1215
+ or request.tool_name not in grant.allowed_tools
1216
+ or request.requested_at < grant.valid_from
1217
+ or request.requested_at > grant.expires_at
1218
+ or receipt.occurred_at < grant.valid_from
1219
+ or receipt.occurred_at > grant.expires_at
1220
+ ):
1221
+ raise _fail("Grant charge authority failed semantic replay")
1222
+ return grant, state, role_authorized
1223
+
1224
+ charge_types = (
1225
+ GrantConsumedReceipt,
1226
+ ToolCallReceipt,
1227
+ ApprovalRequestedReceipt,
1228
+ RollbackReceipt,
1229
+ )
1230
+ for receipt in receipts:
1231
+ states_before_receipt.append(
1232
+ (receipt.receipt_id, frozen_states())
1233
+ )
1234
+ if isinstance(receipt, GrantIssuedReceipt):
1235
+ if (
1236
+ receipt.policy_decision != "allow"
1237
+ or receipt.execution_status != "succeeded"
1238
+ ):
1239
+ continue
1240
+ grant = grants.get(receipt.issued_grant_id)
1241
+ if grant is None:
1242
+ raise _fail("Grant quota replay cannot resolve issuance")
1243
+ if grant.parent_grant_id is None:
1244
+ add_grant(grant)
1245
+ continue
1246
+ parent = grants.get(grant.parent_grant_id)
1247
+ parent_state = mutable.get(grant.parent_grant_id)
1248
+ if parent is None or parent_state is None:
1249
+ raise _fail(
1250
+ "Grant quota replay references non-prior parent"
1251
+ )
1252
+ consume_authority(parent)
1253
+ for metric in ("tool_calls", "repair_rounds"):
1254
+ remaining = int(parent_state[metric]) - getattr(
1255
+ grant.quota,
1256
+ metric,
1257
+ )
1258
+ if remaining < 0:
1259
+ raise _fail("Grant quota replay over-allocates parent")
1260
+ parent_state[metric] = remaining
1261
+ add_grant(grant)
1262
+ continue
1263
+ if isinstance(receipt, GrantRevokedReceipt):
1264
+ if (
1265
+ receipt.policy_decision != "allow"
1266
+ or receipt.execution_status != "succeeded"
1267
+ ):
1268
+ if receipt.quota_charge is not None:
1269
+ raise _fail("denied Grant receipt cannot charge quota")
1270
+ continue
1271
+ authorizer = grants.get(receipt.authorizing_grant_id)
1272
+ target_state = mutable.get(receipt.revoked_grant_id)
1273
+ if authorizer is None or target_state is None:
1274
+ raise _fail(
1275
+ "Grant quota replay cannot resolve revocation"
1276
+ )
1277
+ consume_authority(authorizer)
1278
+ target_state["revoked"] = True
1279
+ continue
1280
+ if not isinstance(receipt, charge_types):
1281
+ if (
1282
+ receipt.policy_decision == "deny"
1283
+ and receipt.quota_charge is not None
1284
+ ):
1285
+ raise _fail("denied Grant receipt cannot charge quota")
1286
+ continue
1287
+ if receipt.policy_decision == "deny":
1288
+ if receipt.quota_charge is not None:
1289
+ raise _fail("denied Grant receipt cannot charge quota")
1290
+ if (
1291
+ isinstance(receipt, GrantConsumedReceipt)
1292
+ and receipt.amount != 1
1293
+ ):
1294
+ raise _fail(
1295
+ "start_retry must consume exactly one repair round"
1296
+ )
1297
+ validate_direct_call(
1298
+ receipt,
1299
+ require_active=False,
1300
+ )
1301
+ continue
1302
+ charge = receipt.quota_charge
1303
+ if charge is None:
1304
+ raise _fail("started Grant receipt must charge quota")
1305
+ if (
1306
+ isinstance(receipt, GrantConsumedReceipt)
1307
+ and receipt.amount != 1
1308
+ ):
1309
+ raise _fail("start_retry must consume exactly one repair round")
1310
+ grant, state, _ = validate_direct_call(
1311
+ receipt,
1312
+ require_active=True,
1313
+ )
1314
+ if charge.grant_id != grant.grant_id:
1315
+ raise _fail("Grant charge does not match direct call")
1316
+ remaining = int(state[charge.metric])
1317
+ if charge.amount > remaining:
1318
+ raise _fail("Grant quota is exhausted")
1319
+ expected_after = remaining - charge.amount
1320
+ if charge.remaining_after != expected_after:
1321
+ raise _fail(
1322
+ "Grant quota remaining_after failed semantic replay"
1323
+ )
1324
+ consume_authority(grant)
1325
+ state[charge.metric] = expected_after
1326
+ if set(mutable) != set(grants):
1327
+ raise _fail("Grant quota replay is incomplete")
1328
+ return _GrantReplayContext(
1329
+ states_before_receipt=tuple(states_before_receipt),
1330
+ final_states=frozen_states(),
1331
+ )
1332
+
1333
+
1334
+ def _replay_grant_quota_history(
1335
+ work_order: WorkOrder,
1336
+ receipts: Iterable[ActionReceiptEnvelope],
1337
+ grants: Mapping[str, CapabilityGrant],
1338
+ ) -> dict[str, _GrantReplayState]:
1339
+ return _replay_grant_context(
1340
+ work_order,
1341
+ receipts,
1342
+ grants,
1343
+ ).final_by_grant()
1344
+
1345
+
1346
+ def _grant_history_replay_context(
1347
+ work_order: WorkOrder,
1348
+ receipts: Iterable[ActionReceiptEnvelope],
1349
+ grants: Mapping[str, CapabilityGrant],
1350
+ attempts: Mapping[str, CapabilityGrant],
1351
+ ) -> _GrantReplayContext:
1352
+ history = tuple(receipts)
1353
+ replay_context = _replay_grant_context(
1354
+ work_order,
1355
+ history,
1356
+ grants,
1357
+ )
1358
+ bindings = {
1359
+ binding.role: binding for binding in work_order.key_bindings
1360
+ }
1361
+ manager = bindings["Manager"]
1362
+ prior_effective: dict[str, CapabilityGrant] = {}
1363
+ seen_attempts: set[str] = set()
1364
+ revoked_grants: set[str] = set()
1365
+ root_seen = False
1366
+ for receipt in history:
1367
+ if isinstance(receipt, GrantRevokedReceipt):
1368
+ authorizer = prior_effective.get(
1369
+ receipt.authorizing_grant_id
1370
+ )
1371
+ target = prior_effective.get(receipt.revoked_grant_id)
1372
+ request = receipt.nested_claim
1373
+ binding_invalid = (
1374
+ authorizer is None
1375
+ or authorizer.parent_grant_id is not None
1376
+ or target is None
1377
+ or target.parent_grant_id != authorizer.grant_id
1378
+ or target.grant_id in revoked_grants
1379
+ or receipt.state_before != receipt.state_after
1380
+ or receipt.actor_id != manager.subject_id
1381
+ or receipt.actor_key_id != manager.key_id
1382
+ or request.actor_id != manager.subject_id
1383
+ or request.actor_key_id != manager.key_id
1384
+ or request.signer_key_id != manager.key_id
1385
+ or request.work_order_digest != work_order.digest
1386
+ or request.grant_id != authorizer.grant_id
1387
+ or request.tool_name != "owp.revoke_grant"
1388
+ or request.requested_at < work_order.issued_at
1389
+ or request.requested_at > work_order.deadline
1390
+ or receipt.occurred_at < request.requested_at
1391
+ or (
1392
+ receipt.occurred_at - request.requested_at
1393
+ ).total_seconds()
1394
+ > 300
1395
+ or receipt.occurred_at > work_order.deadline
1396
+ )
1397
+ allowed_invalid = (
1398
+ receipt.policy_decision == "allow"
1399
+ and (
1400
+ receipt.policy_error_code is not None
1401
+ or receipt.execution_status != "succeeded"
1402
+ or receipt.execution_error_code is not None
1403
+ or request.requested_at < authorizer.valid_from
1404
+ or request.requested_at > authorizer.expires_at
1405
+ or receipt.occurred_at < authorizer.valid_from
1406
+ or receipt.occurred_at > authorizer.expires_at
1407
+ )
1408
+ )
1409
+ denied_invalid = (
1410
+ receipt.policy_decision == "deny"
1411
+ and (
1412
+ receipt.policy_error_code is None
1413
+ or receipt.execution_status != "denied"
1414
+ or receipt.execution_error_code is not None
1415
+ or receipt.quota_charge is not None
1416
+ )
1417
+ )
1418
+ if (
1419
+ binding_invalid
1420
+ or allowed_invalid
1421
+ or denied_invalid
1422
+ or receipt.policy_decision not in {"allow", "deny"}
1423
+ ):
1424
+ raise _fail(
1425
+ "Grant revocation history failed semantic replay"
1426
+ )
1427
+ if receipt.policy_decision == "allow":
1428
+ revoked_grants.add(target.grant_id)
1429
+ continue
1430
+ if not isinstance(receipt, GrantIssuedReceipt):
1431
+ continue
1432
+ if receipt.parent_grant_id is None:
1433
+ root = grants.get(receipt.issued_grant_id)
1434
+ request = receipt.nested_claim
1435
+ if (
1436
+ root_seen
1437
+ or receipt.sequence != 1
1438
+ or root is None
1439
+ or root.parent_grant_id is not None
1440
+ or receipt.candidate_grant_digest != root.digest
1441
+ or receipt.actor_id != manager.subject_id
1442
+ or receipt.actor_key_id != manager.key_id
1443
+ or not _request_matches_candidate_issuer(
1444
+ request,
1445
+ manager,
1446
+ )
1447
+ or request.work_order_digest != work_order.digest
1448
+ or request.grant_id != root.grant_id
1449
+ or request.tool_name != "owp.activate_root_grant"
1450
+ or request.requested_at < work_order.issued_at
1451
+ or request.requested_at > work_order.deadline
1452
+ or request.requested_at < root.valid_from
1453
+ or request.requested_at > root.expires_at
1454
+ or receipt.occurred_at < request.requested_at
1455
+ or (
1456
+ receipt.occurred_at - request.requested_at
1457
+ ).total_seconds()
1458
+ > 300
1459
+ or receipt.occurred_at < root.valid_from
1460
+ or receipt.occurred_at > root.expires_at
1461
+ ):
1462
+ raise _fail(
1463
+ "root activation history failed semantic replay"
1464
+ )
1465
+ root_seen = True
1466
+ prior_effective[root.grant_id] = root
1467
+ continue
1468
+ parent = prior_effective.get(receipt.parent_grant_id)
1469
+ candidate = (
1470
+ grants.get(receipt.issued_grant_id)
1471
+ if receipt.policy_decision == "allow"
1472
+ else attempts.get(receipt.candidate_grant_digest)
1473
+ )
1474
+ if parent is None or candidate is None:
1475
+ raise _fail("Grant history references non-prior authority")
1476
+ signing_binding = _child_signing_binding(
1477
+ work_order,
1478
+ candidate,
1479
+ )
1480
+ request = receipt.nested_claim
1481
+ if (
1482
+ receipt.actor_id != signing_binding.subject_id
1483
+ or receipt.actor_key_id != signing_binding.key_id
1484
+ or not _request_matches_candidate_issuer(
1485
+ request,
1486
+ signing_binding,
1487
+ )
1488
+ or candidate.parent_grant_id != parent.grant_id
1489
+ or request.work_order_digest != work_order.digest
1490
+ or request.grant_id != parent.grant_id
1491
+ or request.tool_name != "owp.delegate_grant"
1492
+ or request.requested_at < work_order.issued_at
1493
+ or request.requested_at > work_order.deadline
1494
+ or receipt.occurred_at < request.requested_at
1495
+ or (
1496
+ receipt.occurred_at - request.requested_at
1497
+ ).total_seconds()
1498
+ > 300
1499
+ ):
1500
+ raise _fail(
1501
+ "Grant history request binding failed semantic replay"
1502
+ )
1503
+ allowed, error_code = _child_policy_decision(
1504
+ work_order,
1505
+ parent,
1506
+ replay_context.state_before(
1507
+ receipt.receipt_id,
1508
+ parent.grant_id,
1509
+ ),
1510
+ candidate,
1511
+ request,
1512
+ signing_binding,
1513
+ bindings,
1514
+ receipt.occurred_at,
1515
+ state_allowed=(
1516
+ receipt.state_before == receipt.state_after
1517
+ and receipt.state_before
1518
+ in {"running", "retrying", "needs_rework"}
1519
+ ),
1520
+ )
1521
+ if receipt.policy_decision == "allow":
1522
+ if (
1523
+ not allowed
1524
+ or error_code is not None
1525
+ or receipt.policy_error_code is not None
1526
+ or receipt.execution_status != "succeeded"
1527
+ or receipt.issued_grant_id != candidate.grant_id
1528
+ ):
1529
+ raise _fail(
1530
+ "allowed Grant history fails policy replay"
1531
+ )
1532
+ prior_effective[candidate.grant_id] = candidate
1533
+ else:
1534
+ if (
1535
+ allowed
1536
+ or error_code is None
1537
+ or receipt.policy_error_code != error_code
1538
+ or receipt.execution_status != "denied"
1539
+ ):
1540
+ raise _fail(
1541
+ "denied Grant history fails policy replay"
1542
+ )
1543
+ seen_attempts.add(candidate.digest)
1544
+ if (
1545
+ not root_seen
1546
+ or set(prior_effective) != set(grants)
1547
+ or seen_attempts != set(attempts)
1548
+ ):
1549
+ raise _fail("Grant history replay is incomplete")
1550
+ return replay_context
1551
+
1552
+
1553
+ def _validate_grant_history_semantics(
1554
+ work_order: WorkOrder,
1555
+ receipts: Iterable[ActionReceiptEnvelope],
1556
+ grants: Mapping[str, CapabilityGrant],
1557
+ attempts: Mapping[str, CapabilityGrant],
1558
+ ) -> dict[str, _GrantReplayState]:
1559
+ return _grant_history_replay_context(
1560
+ work_order,
1561
+ receipts,
1562
+ grants,
1563
+ attempts,
1564
+ ).final_by_grant()
1565
+
1566
+
1567
+ def _validate_state_history(
1568
+ work_order: WorkOrder,
1569
+ receipts: tuple[ActionReceiptEnvelope, ...],
1570
+ causal_state: AuthorizationCausalState,
1571
+ ) -> None:
1572
+ try:
1573
+ public_keys = {
1574
+ binding.key_id: decode_and_verify_key_binding(binding)
1575
+ for binding in work_order.key_bindings
1576
+ }
1577
+ except Exception as error:
1578
+ raise _fail(
1579
+ "WorkOrder key bindings are unavailable for state replay"
1580
+ ) from error
1581
+ for receipt in receipts:
1582
+ receipt_causal_state = causal_state.snapshot_for(
1583
+ receipt.receipt_id
1584
+ )
1585
+ state_before = TaskState(receipt.state_before)
1586
+ state_after = TaskState(receipt.state_after)
1587
+ decision = (
1588
+ append_receipt(
1589
+ work_order=work_order,
1590
+ state=state_before,
1591
+ receipt=receipt,
1592
+ public_keys=public_keys,
1593
+ now=receipt.occurred_at,
1594
+ allow_independent_verifier_rerun=True,
1595
+ )
1596
+ if state_before is state_after
1597
+ else apply_state_transition(
1598
+ work_order=work_order,
1599
+ state_before=state_before,
1600
+ state_after=state_after,
1601
+ trigger_receipt=receipt,
1602
+ acceptance_receipt=None,
1603
+ public_keys=public_keys,
1604
+ now=receipt.occurred_at,
1605
+ )
1606
+ )
1607
+ if decision.allowed:
1608
+ continue
1609
+ if decision.error_code in {
1610
+ "COMPOSITION_VALIDATOR_UNAVAILABLE",
1611
+ "TRANSITION_CONTEXT_UNAVAILABLE",
1612
+ }:
1613
+ if (
1614
+ isinstance(receipt, ToolCallReceipt)
1615
+ and _tool_state_context_exception(
1616
+ work_order,
1617
+ receipt,
1618
+ receipt_causal_state,
1619
+ )
1620
+ ):
1621
+ continue
1622
+ if isinstance(
1623
+ receipt,
1624
+ (
1625
+ GrantConsumedReceipt,
1626
+ RollbackReceipt,
1627
+ SystemEventReceipt,
1628
+ ),
1629
+ ):
1630
+ continue
1631
+ if (
1632
+ isinstance(receipt, TerminationDecisionReceipt)
1633
+ and _validate_termination(
1634
+ receipt,
1635
+ work_order,
1636
+ state_before,
1637
+ receipt.occurred_at,
1638
+ )
1639
+ ):
1640
+ continue
1641
+ raise _fail("receipt is denied by the frozen state machine")
1642
+
1643
+
1644
+ def _tool_predicates(
1645
+ work_order: WorkOrder,
1646
+ receipt: ToolCallReceipt,
1647
+ grant: CapabilityGrant,
1648
+ state: _GrantReplayState,
1649
+ ) -> tuple[tuple, bool]:
1650
+ test_mode = (
1651
+ receipt.request_arguments.test_mode
1652
+ if isinstance(receipt.request_arguments, RunTestsArguments)
1653
+ else "developer"
1654
+ )
1655
+ selected = select_required_predicates(
1656
+ work_order=work_order,
1657
+ tool_name=receipt.tool_name,
1658
+ policy_decision=receipt.policy_decision,
1659
+ execution_status=receipt.execution_status,
1660
+ test_mode=test_mode,
1661
+ )
1662
+ supplied = {
1663
+ result.predicate_id: result
1664
+ for result in receipt.predicate_results
1665
+ }
1666
+ authoritative: dict[str, object] = {}
1667
+ child_scope_allowed = True
1668
+ for spec in selected:
1669
+ result = supplied.get(spec.predicate_id)
1670
+ if result is None:
1671
+ raise _fail("tool predicate set is incomplete")
1672
+ if spec.name == "tool_allowed":
1673
+ value: object = {"actual_tool_name": receipt.tool_name}
1674
+ elif spec.name == "quota_remaining":
1675
+ value = {
1676
+ "grant_id": grant.grant_id,
1677
+ "metric": "tool_calls",
1678
+ "amount": 1,
1679
+ "grant_remaining_before": state.remaining_tool_calls,
1680
+ "ledger_prefix_digest": receipt.previous_receipt_digest,
1681
+ }
1682
+ elif spec.name == "path_allowed":
1683
+ path_input = result.input
1684
+ if isinstance(receipt.request_arguments, ApplyPatchArguments):
1685
+ requested = receipt.request_arguments.target_paths
1686
+ child_roots = grant.allowed_write_roots
1687
+ elif isinstance(receipt.request_arguments, RepoReadArguments):
1688
+ requested = (receipt.request_arguments.path,)
1689
+ child_roots = grant.allowed_read_roots
1690
+ else:
1691
+ raise _fail("path predicate request type is unavailable")
1692
+ resolved = tuple(
1693
+ item.resolved_relative_path
1694
+ for item in path_input.resolved_entries
1695
+ )
1696
+ child_scope_allowed = (
1697
+ path_input.requested_paths == tuple(requested)
1698
+ and tuple(
1699
+ item.requested_path
1700
+ for item in path_input.resolved_entries
1701
+ )
1702
+ == tuple(requested)
1703
+ and path_input.resolution_manifest_digest is not None
1704
+ and all(
1705
+ any(_root_contains(root, path) for root in child_roots)
1706
+ for path in requested
1707
+ )
1708
+ and all(
1709
+ path is not None
1710
+ and any(
1711
+ _root_contains(root, path)
1712
+ for root in child_roots
1713
+ )
1714
+ for path in resolved
1715
+ )
1716
+ )
1717
+ value = {
1718
+ "requested_paths": list(requested),
1719
+ "resolved_entries": [
1720
+ item.model_dump(mode="json")
1721
+ for item in path_input.resolved_entries
1722
+ ],
1723
+ "resolution_manifest_digest": (
1724
+ path_input.resolution_manifest_digest
1725
+ ),
1726
+ }
1727
+ elif spec.name == "tests_passed":
1728
+ arguments = receipt.request_arguments
1729
+ profile = next(
1730
+ (
1731
+ item
1732
+ for item in work_order.test_profiles
1733
+ if item.test_mode == "verifier"
1734
+ ),
1735
+ None,
1736
+ )
1737
+ if (
1738
+ not isinstance(arguments, RunTestsArguments)
1739
+ or profile is None
1740
+ ):
1741
+ raise _fail(
1742
+ "Verifier predicate authority is unavailable"
1743
+ )
1744
+ value = {
1745
+ **result.input.model_dump(mode="json"),
1746
+ "command_digest": profile.command_digest,
1747
+ "expected_exit_code": profile.expected_exit_code,
1748
+ "source_commit": arguments.source_commit,
1749
+ "candidate_commit": arguments.candidate_commit,
1750
+ "workspace_manifest_digest": (
1751
+ arguments.workspace_manifest_digest
1752
+ ),
1753
+ "container_image_digest": profile.container_image_digest,
1754
+ "fixed_test_source_digest": (
1755
+ profile.fixed_test_source_digest
1756
+ ),
1757
+ }
1758
+ else:
1759
+ raise _fail("tool predicate has no offline authority rule")
1760
+ authoritative[spec.predicate_id] = value
1761
+ evaluated = evaluate_required_predicates(
1762
+ selected,
1763
+ EvaluationContext(
1764
+ inputs={
1765
+ result.predicate_id: result.input
1766
+ for result in receipt.predicate_results
1767
+ },
1768
+ authoritative_inputs=authoritative,
1769
+ authoritative_ledger_prefix_digests={
1770
+ grant.grant_id: receipt.previous_receipt_digest,
1771
+ },
1772
+ ),
1773
+ )
1774
+ if evaluated != receipt.predicate_results:
1775
+ raise _fail(
1776
+ "tool PredicateResults failed exact historical replay"
1777
+ )
1778
+ return evaluated, child_scope_allowed
1779
+
1780
+
1781
+ def _approval_allowed(
1782
+ receipt: ToolCallReceipt,
1783
+ prior: Mapping[str, ActionReceiptEnvelope],
1784
+ decisions: Mapping[str, str],
1785
+ ) -> bool:
1786
+ if receipt.tool_name != "owp.create_pr_proposal":
1787
+ return True
1788
+ decision = prior.get(receipt.approval_receipt_id)
1789
+ request = (
1790
+ prior.get(decision.request_receipt_id)
1791
+ if isinstance(decision, ApprovalDecisionReceipt)
1792
+ else None
1793
+ )
1794
+ requested_at = receipt.nested_claim.requested_at
1795
+ return (
1796
+ isinstance(decision, ApprovalDecisionReceipt)
1797
+ and isinstance(request, ApprovalRequestedReceipt)
1798
+ and decisions.get(request.receipt_id) == decision.receipt_id
1799
+ and receipt.approval_receipt_digest == decision.digest
1800
+ and decision.request_receipt_digest == request.digest
1801
+ and decision.decision == "approved"
1802
+ and decision.policy_decision == "allow"
1803
+ and decision.execution_status == "succeeded"
1804
+ and request.request_kind == "high_risk_action"
1805
+ and request.requested_scope["work_order_digest"]
1806
+ == receipt.work_order_digest
1807
+ and request.requested_scope["target_patch_digest"]
1808
+ == receipt.request_arguments.target_patch_digest
1809
+ and decision.approved_scope == request.requested_scope
1810
+ and requested_at <= decision.expires_at
1811
+ and requested_at <= request.expires_at
1812
+ and receipt.occurred_at <= decision.expires_at
1813
+ and receipt.occurred_at <= request.expires_at
1814
+ )
1815
+
1816
+
1817
+ def _tool_state_allowed(
1818
+ work_order: WorkOrder,
1819
+ receipt: ToolCallReceipt,
1820
+ causal_state: AuthorizationCausalSnapshot,
1821
+ ) -> bool:
1822
+ state_before = receipt.state_before
1823
+ if receipt.tool_name == "owp.compose_proof":
1824
+ if receipt.state_after != state_before:
1825
+ return False
1826
+ if state_before == "locally_verified":
1827
+ return (
1828
+ causal_state.active_patch_receipt_id
1829
+ in receipt.parent_receipt_ids
1830
+ )
1831
+ if state_before != "evidence_incomplete":
1832
+ return False
1833
+ return (
1834
+ causal_state.latest_composition_trigger_id
1835
+ in receipt.parent_receipt_ids
1836
+ and causal_state.independent_result_receipt_id
1837
+ in receipt.parent_receipt_ids
1838
+ )
1839
+ if (
1840
+ receipt.tool_name == "owp.run_tests"
1841
+ and state_before == "evidence_incomplete"
1842
+ ):
1843
+ if receipt.state_after != state_before:
1844
+ return False
1845
+ role = next(
1846
+ (
1847
+ binding.role
1848
+ for binding in work_order.key_bindings
1849
+ if (
1850
+ binding.subject_id == receipt.actor_id
1851
+ and binding.key_id == receipt.actor_key_id
1852
+ )
1853
+ ),
1854
+ None,
1855
+ )
1856
+ test_results = tuple(
1857
+ result
1858
+ for result in receipt.predicate_results
1859
+ if result.name == "tests_passed"
1860
+ )
1861
+ tests_passed = bool(test_results) and all(
1862
+ result.passed for result in test_results
1863
+ )
1864
+ result_binding_valid = (
1865
+ causal_state.independent_result_receipt_id
1866
+ == receipt.receipt_id
1867
+ if tests_passed
1868
+ else causal_state.independent_result_receipt_id is None
1869
+ )
1870
+ return (
1871
+ role == "Verifier"
1872
+ and causal_state.active_patch_receipt_id
1873
+ in receipt.parent_receipt_ids
1874
+ and causal_state.latest_composition_trigger_id
1875
+ in receipt.parent_receipt_ids
1876
+ and result_binding_valid
1877
+ )
1878
+ return state_before in {"running", "retrying"}
1879
+
1880
+
1881
+ def _tool_state_context_exception(
1882
+ work_order: WorkOrder,
1883
+ receipt: ToolCallReceipt,
1884
+ causal_state: AuthorizationCausalSnapshot,
1885
+ ) -> bool:
1886
+ is_context_dependent = (
1887
+ receipt.tool_name == "owp.compose_proof"
1888
+ and receipt.state_before
1889
+ in {"locally_verified", "evidence_incomplete"}
1890
+ ) or (
1891
+ receipt.tool_name == "owp.run_tests"
1892
+ and receipt.state_before == "evidence_incomplete"
1893
+ )
1894
+ return (
1895
+ is_context_dependent
1896
+ and receipt.state_after == receipt.state_before
1897
+ and _tool_state_allowed(work_order, receipt, causal_state)
1898
+ )
1899
+
1900
+
1901
+ def _validate_policy_history(
1902
+ work_order: WorkOrder,
1903
+ receipts: tuple[ActionReceiptEnvelope, ...],
1904
+ grants: Mapping[str, CapabilityGrant],
1905
+ replay_context: _GrantReplayContext,
1906
+ causal_state: AuthorizationCausalState,
1907
+ ) -> None:
1908
+ prior: dict[str, ActionReceiptEnvelope] = {}
1909
+
1910
+ for receipt in receipts:
1911
+ receipt_causal_state = causal_state.snapshot_for(
1912
+ receipt.receipt_id
1913
+ )
1914
+ decisions = dict(
1915
+ receipt_causal_state.approval_decision_by_request
1916
+ )
1917
+ if isinstance(receipt, GrantIssuedReceipt):
1918
+ prior[receipt.receipt_id] = receipt
1919
+ continue
1920
+ if isinstance(receipt, GrantRevokedReceipt):
1921
+ grant = grants.get(receipt.authorizing_grant_id)
1922
+ state = replay_context.state_before(
1923
+ receipt.receipt_id,
1924
+ receipt.authorizing_grant_id,
1925
+ )
1926
+ target = replay_context.state_before(
1927
+ receipt.receipt_id,
1928
+ receipt.revoked_grant_id,
1929
+ )
1930
+ request = receipt.nested_claim
1931
+ expected = _denial_code(
1932
+ state_allowed=receipt.state_before
1933
+ in {"running", "retrying", "needs_rework"},
1934
+ role_allowed=(
1935
+ grant is not None
1936
+ and state is not None
1937
+ and receipt.actor_id == grant.subject_agent_id
1938
+ and receipt.actor_key_id == grant.subject_key_id
1939
+ and _agent_direct_call_is_authorized(
1940
+ receipt,
1941
+ work_order,
1942
+ )
1943
+ ),
1944
+ capability_allowed=(
1945
+ grant is not None
1946
+ and state is not None
1947
+ and target is not None
1948
+ and state.revoked is False
1949
+ and request.tool_name in grant.allowed_tools
1950
+ and grant.valid_from
1951
+ <= request.requested_at
1952
+ <= receipt.occurred_at
1953
+ <= grant.expires_at
1954
+ ),
1955
+ )
1956
+ if receipt.policy_decision == "deny":
1957
+ if receipt.policy_error_code != expected:
1958
+ raise _fail(
1959
+ "revocation denial failed historical policy replay"
1960
+ )
1961
+ else:
1962
+ if expected is not None:
1963
+ raise _fail(
1964
+ "allowed revocation failed historical policy replay"
1965
+ )
1966
+ prior[receipt.receipt_id] = receipt
1967
+ continue
1968
+ if isinstance(
1969
+ receipt,
1970
+ (GrantConsumedReceipt, ApprovalRequestedReceipt, RollbackReceipt),
1971
+ ):
1972
+ grant = grants.get(receipt.grant_id)
1973
+ state = replay_context.state_before(
1974
+ receipt.receipt_id,
1975
+ receipt.grant_id,
1976
+ )
1977
+ request = receipt.nested_claim
1978
+ if (
1979
+ isinstance(receipt, ApprovalRequestedReceipt)
1980
+ and receipt.request_kind == "high_risk_action"
1981
+ and grant is not None
1982
+ ):
1983
+ gate = work_order.approval_gates[0]
1984
+ maximum_expiry = min(
1985
+ receipt.occurred_at
1986
+ + timedelta(seconds=gate.max_validity_seconds),
1987
+ grant.expires_at,
1988
+ work_order.deadline,
1989
+ )
1990
+ if receipt.expires_at > maximum_expiry:
1991
+ raise _fail(
1992
+ "approval request validity exceeds protocol cap"
1993
+ )
1994
+ if isinstance(receipt, GrantConsumedReceipt):
1995
+ state_allowed = receipt.state_before == "needs_rework"
1996
+ quota_metric = "repair_rounds"
1997
+ elif isinstance(receipt, ApprovalRequestedReceipt):
1998
+ state_allowed = (
1999
+ receipt.state_before in {"running", "retrying"}
2000
+ if receipt.request_kind == "high_risk_action"
2001
+ else receipt.state_before == "proof_ready"
2002
+ )
2003
+ quota_metric = "tool_calls"
2004
+ else:
2005
+ state_allowed = receipt.state_before == "needs_rework"
2006
+ quota_metric = "tool_calls"
2007
+ expected = _denial_code(
2008
+ state_allowed=state_allowed,
2009
+ role_allowed=(
2010
+ grant is not None
2011
+ and state is not None
2012
+ and receipt.actor_id == grant.subject_agent_id
2013
+ and receipt.actor_key_id == grant.subject_key_id
2014
+ and _agent_direct_call_is_authorized(
2015
+ receipt,
2016
+ work_order,
2017
+ )
2018
+ ),
2019
+ capability_allowed=(
2020
+ grant is not None
2021
+ and state is not None
2022
+ and state.revoked is False
2023
+ and request.tool_name in grant.allowed_tools
2024
+ and grant.valid_from
2025
+ <= request.requested_at
2026
+ <= receipt.occurred_at
2027
+ <= grant.expires_at
2028
+ ),
2029
+ quota_allowed=(
2030
+ state is not None
2031
+ and (
2032
+ state.remaining_repair_rounds
2033
+ if quota_metric == "repair_rounds"
2034
+ else state.remaining_tool_calls
2035
+ )
2036
+ > 0
2037
+ ),
2038
+ )
2039
+ if receipt.policy_decision == "deny":
2040
+ if receipt.policy_error_code != expected:
2041
+ raise _fail(
2042
+ "non-tool denial failed historical policy replay"
2043
+ )
2044
+ else:
2045
+ if expected is not None:
2046
+ raise _fail(
2047
+ "allowed non-tool action failed policy replay"
2048
+ )
2049
+ prior[receipt.receipt_id] = receipt
2050
+ continue
2051
+ if isinstance(receipt, ToolCallReceipt):
2052
+ grant = grants.get(receipt.grant_id)
2053
+ state = replay_context.state_before(
2054
+ receipt.receipt_id,
2055
+ receipt.grant_id,
2056
+ )
2057
+ if grant is None or state is None:
2058
+ raise _fail(
2059
+ "tool receipt references a never-effective Grant"
2060
+ )
2061
+ evaluated, child_scope_allowed = _tool_predicates(
2062
+ work_order,
2063
+ receipt,
2064
+ grant,
2065
+ state,
2066
+ )
2067
+ request = receipt.nested_claim
2068
+ role_allowed = (
2069
+ receipt.actor_id == grant.subject_agent_id
2070
+ and receipt.actor_key_id == grant.subject_key_id
2071
+ and _agent_direct_call_is_authorized(
2072
+ receipt,
2073
+ work_order,
2074
+ )
2075
+ )
2076
+ capability_allowed = (
2077
+ state.revoked is False
2078
+ and receipt.tool_name in grant.allowed_tools
2079
+ and child_scope_allowed
2080
+ and grant.valid_from
2081
+ <= request.requested_at
2082
+ <= receipt.occurred_at
2083
+ <= grant.expires_at
2084
+ )
2085
+ expected = _denial_code(
2086
+ state_allowed=_tool_state_allowed(
2087
+ work_order,
2088
+ receipt,
2089
+ receipt_causal_state,
2090
+ ),
2091
+ role_allowed=role_allowed,
2092
+ capability_allowed=capability_allowed,
2093
+ approval_allowed=_approval_allowed(
2094
+ receipt,
2095
+ prior,
2096
+ decisions,
2097
+ ),
2098
+ predicate_allowed=all(
2099
+ item.passed
2100
+ for item in evaluated
2101
+ if item.name != "tests_passed"
2102
+ ),
2103
+ quota_allowed=state.remaining_tool_calls > 0,
2104
+ )
2105
+ if receipt.policy_decision == "deny":
2106
+ if receipt.policy_error_code != expected:
2107
+ raise _fail(
2108
+ "tool denial failed historical policy replay"
2109
+ )
2110
+ else:
2111
+ if expected is not None:
2112
+ raise _fail(
2113
+ "allowed tool failed historical policy replay"
2114
+ )
2115
+ prior[receipt.receipt_id] = receipt
2116
+ continue
2117
+ if isinstance(receipt, ApprovalDecisionReceipt):
2118
+ request = prior.get(receipt.request_receipt_id)
2119
+ approved = receipt.decision == "approved"
2120
+ expected_decision_id = (
2121
+ decisions.get(request.receipt_id)
2122
+ if isinstance(request, ApprovalRequestedReceipt)
2123
+ else None
2124
+ )
2125
+ if (
2126
+ not isinstance(request, ApprovalRequestedReceipt)
2127
+ or expected_decision_id != receipt.receipt_id
2128
+ or request.digest != receipt.request_receipt_digest
2129
+ or request.requested_scope != receipt.approved_scope
2130
+ or request.expires_at != receipt.expires_at
2131
+ or approved
2132
+ != (
2133
+ receipt.policy_decision == "allow"
2134
+ and receipt.execution_status == "succeeded"
2135
+ )
2136
+ or (
2137
+ not approved
2138
+ and (
2139
+ receipt.policy_decision != "deny"
2140
+ or receipt.execution_status != "denied"
2141
+ )
2142
+ )
2143
+ or receipt.decided_at < request.occurred_at
2144
+ or receipt.decided_at > request.expires_at
2145
+ or receipt.occurred_at < receipt.decided_at
2146
+ or (
2147
+ receipt.occurred_at - receipt.decided_at
2148
+ ).total_seconds()
2149
+ > 300
2150
+ ):
2151
+ raise _fail("Human approval failed historical replay")
2152
+ elif isinstance(receipt, TerminationDecisionReceipt):
2153
+ if (
2154
+ receipt.target_work_order_digest != work_order.digest
2155
+ or receipt.decided_at < work_order.issued_at
2156
+ or receipt.occurred_at < receipt.decided_at
2157
+ or (
2158
+ receipt.occurred_at - receipt.decided_at
2159
+ ).total_seconds()
2160
+ > 300
2161
+ ):
2162
+ raise _fail("Human termination failed historical replay")
2163
+ prior[receipt.receipt_id] = receipt
2164
+
2165
+
2166
+ def replay_authorization_policy(
2167
+ work_order: WorkOrder,
2168
+ grants: Mapping[str, CapabilityGrant],
2169
+ attempts: Mapping[str, CapabilityGrant],
2170
+ receipts: Iterable[ActionReceiptEnvelope],
2171
+ causal_state: AuthorizationCausalState,
2172
+ ) -> AuthorizationReplayState:
2173
+ """Replay the bounded authorization policy without external state."""
2174
+
2175
+ if (
2176
+ not isinstance(work_order, WorkOrder)
2177
+ or not isinstance(causal_state, AuthorizationCausalState)
2178
+ ):
2179
+ raise _fail("authorization policy inputs are unavailable")
2180
+ effective = _bounded_mapping(
2181
+ grants,
2182
+ cap=MAX_EFFECTIVE_GRANTS,
2183
+ label="effective Grants",
2184
+ )
2185
+ denied = _bounded_mapping(
2186
+ attempts,
2187
+ cap=MAX_GRANT_ATTEMPTS,
2188
+ label="Grant attempts",
2189
+ )
2190
+ bounded = _bounded_receipts(receipts)
2191
+ _validate_grant_signatures(work_order, effective, denied)
2192
+ try:
2193
+ validate_authorization_causal_bindings(
2194
+ work_order,
2195
+ bounded,
2196
+ causal_state,
2197
+ )
2198
+ except AuthorizationCausalityError as error:
2199
+ raise _fail(str(error)) from error
2200
+ decision_pairs = causal_state.approval_decision_by_request
2201
+ replay_context = _grant_history_replay_context(
2202
+ work_order,
2203
+ bounded,
2204
+ effective,
2205
+ denied,
2206
+ )
2207
+ quota_states = replay_context.final_by_grant()
2208
+ _validate_policy_history(
2209
+ work_order,
2210
+ bounded,
2211
+ effective,
2212
+ replay_context,
2213
+ causal_state,
2214
+ )
2215
+ _validate_state_history(work_order, bounded, causal_state)
2216
+ balances = tuple(
2217
+ entry
2218
+ for grant_id, state in sorted(quota_states.items())
2219
+ for entry in (
2220
+ (grant_id, "repair_rounds", state.remaining_repair_rounds),
2221
+ (grant_id, "tool_calls", state.remaining_tool_calls),
2222
+ )
2223
+ )
2224
+ return AuthorizationReplayState(
2225
+ active_patch_receipt_id=causal_state.active_patch_receipt_id,
2226
+ balances=balances,
2227
+ revoked_grant_ids=tuple(
2228
+ sorted(
2229
+ grant_id
2230
+ for grant_id, state in quota_states.items()
2231
+ if state.revoked
2232
+ )
2233
+ ),
2234
+ used_single_use_grant_ids=tuple(
2235
+ sorted(
2236
+ grant_id
2237
+ for grant_id, state in quota_states.items()
2238
+ if effective[grant_id].usage_mode == "single_use"
2239
+ and state.use_count > 0
2240
+ )
2241
+ ),
2242
+ approval_decision_by_request=decision_pairs,
2243
+ )
2244
+
2245
+
2246
+ def _validate_committed_evidence(
2247
+ work_order: WorkOrder,
2248
+ ledger_prefix: AuthorizationLedgerPrefix,
2249
+ committed_evidence: tuple[CommittedEvidence, ...],
2250
+ ) -> dict[str, CommittedEvidence]:
2251
+ committed_paths = tuple(
2252
+ item.reference.path for item in committed_evidence
2253
+ )
2254
+ if (
2255
+ committed_paths
2256
+ != tuple(sorted(committed_paths, key=lambda value: value.encode()))
2257
+ or len(set(committed_paths)) != len(committed_paths)
2258
+ ):
2259
+ raise _fail("committed evidence is not canonical and unique")
2260
+
2261
+ expected_by_path: dict[str, EvidenceRef] = {}
2262
+ for receipt in ledger_prefix.receipts:
2263
+ for reference in receipt.evidence_refs:
2264
+ if reference.path in expected_by_path:
2265
+ raise _fail("receipt evidence coverage is not one-to-one")
2266
+ expected_by_path[reference.path] = reference
2267
+ committed_by_path = {
2268
+ item.reference.path: item for item in committed_evidence
2269
+ }
2270
+ if set(committed_by_path) != set(expected_by_path):
2271
+ raise _fail("committed evidence coverage is not exact")
2272
+
2273
+ artifacts = {
2274
+ f"{work_order.evidence_policy.evidence_root}/{artifact.path}": artifact
2275
+ for artifact in work_order.evidence_policy.artifacts
2276
+ }
2277
+ for path, expected_reference in expected_by_path.items():
2278
+ item = committed_by_path[path]
2279
+ artifact = artifacts.get(path)
2280
+ if artifact is None:
2281
+ raise _fail("committed evidence path is outside the allowlist")
2282
+ if item.reference != expected_reference:
2283
+ raise _fail("committed evidence reference does not match receipt")
2284
+ if item.reference.media_type != artifact.media_type:
2285
+ raise _fail("committed evidence media type is invalid")
2286
+ if item.reference.size_bytes > artifact.max_size_bytes:
2287
+ raise _fail("committed evidence exceeds its artifact limit")
2288
+ if len(item.payload) != item.reference.size_bytes:
2289
+ raise _fail("committed evidence size does not match reference")
2290
+ if (
2291
+ hashlib.sha256(item.payload).hexdigest()
2292
+ != item.reference.sha256
2293
+ ):
2294
+ raise _fail("committed evidence digest does not match bytes")
2295
+ if item.reference.media_type == "application/json":
2296
+ try:
2297
+ decoded = json.loads(item.payload)
2298
+ canonical = rfc8785.dumps(decoded)
2299
+ except (TypeError, ValueError, UnicodeDecodeError) as error:
2300
+ raise _fail("committed JSON evidence is invalid") from error
2301
+ if canonical != item.payload:
2302
+ raise _fail("committed JSON evidence is not RFC 8785 canonical")
2303
+ return committed_by_path
2304
+
2305
+
2306
+ def _validate_replay_checkpoint(
2307
+ work_order: WorkOrder,
2308
+ ledger_prefix: AuthorizationLedgerPrefix,
2309
+ committed_by_path: Mapping[str, CommittedEvidence],
2310
+ replay_checkpoint: ReplayCheckpoint,
2311
+ active_patch_receipt_id: str | None,
2312
+ ) -> None:
2313
+ try:
2314
+ actual_manifest_digest = workspace_manifest_digest(
2315
+ replay_checkpoint.workspace_manifest
2316
+ )
2317
+ except Exception as error:
2318
+ raise _fail("replay checkpoint manifest is invalid") from error
2319
+ if (
2320
+ replay_checkpoint.workspace_manifest.head_commit
2321
+ != replay_checkpoint.head_commit
2322
+ or replay_checkpoint.workspace_manifest_digest
2323
+ != actual_manifest_digest
2324
+ ):
2325
+ raise _fail("replay checkpoint manifest binding is invalid")
2326
+ if active_patch_receipt_id is None:
2327
+ if replay_checkpoint.head_commit != work_order.source_commit:
2328
+ raise _fail("source replay checkpoint head is invalid")
2329
+ return
2330
+
2331
+ active_patch = next(
2332
+ (
2333
+ receipt
2334
+ for receipt in ledger_prefix.receipts
2335
+ if receipt.receipt_id == active_patch_receipt_id
2336
+ ),
2337
+ None,
2338
+ )
2339
+ if (
2340
+ not isinstance(active_patch, ToolCallReceipt)
2341
+ or active_patch.tool_name != "owp.apply_patch"
2342
+ or active_patch.policy_decision != "allow"
2343
+ or active_patch.execution_status != "succeeded"
2344
+ or not isinstance(active_patch.request_arguments, ApplyPatchArguments)
2345
+ ):
2346
+ raise _fail("active patch checkpoint origin is invalid")
2347
+ artifacts = {
2348
+ f"{work_order.evidence_policy.evidence_root}/{artifact.path}": artifact
2349
+ for artifact in work_order.evidence_policy.artifacts
2350
+ }
2351
+ result_refs = tuple(
2352
+ reference
2353
+ for reference in active_patch.evidence_refs
2354
+ if artifacts.get(reference.path) is not None
2355
+ and artifacts[reference.path].purpose == "patch_result"
2356
+ )
2357
+ if len(result_refs) != 1:
2358
+ raise _fail("active patch evidence has no unique result")
2359
+ result_ref = result_refs[0]
2360
+ committed_result = committed_by_path.get(result_ref.path)
2361
+ if committed_result is None:
2362
+ raise _fail("active patch result evidence is unavailable")
2363
+ try:
2364
+ patch_result = PatchResultEvidence.model_validate_json(
2365
+ committed_result.payload
2366
+ )
2367
+ except (TypeError, ValueError) as error:
2368
+ raise _fail("active patch result evidence is invalid") from error
2369
+ if (
2370
+ active_patch.output_digest != result_ref.sha256
2371
+ or patch_result.patch_digest
2372
+ != active_patch.request_arguments.patch_digest
2373
+ or patch_result.patch_size_bytes
2374
+ != active_patch.request_arguments.patch_size_bytes
2375
+ or patch_result.replay_profile_digest
2376
+ != work_order.replay_profile_digest
2377
+ or patch_result.candidate_commit != replay_checkpoint.head_commit
2378
+ or patch_result.workspace_manifest_digest
2379
+ != replay_checkpoint.workspace_manifest_digest
2380
+ ):
2381
+ raise _fail("active patch checkpoint binding is invalid")
2382
+
2383
+
2384
+ def derive_authorization_context(
2385
+ work_order: WorkOrder,
2386
+ ledger_prefix: AuthorizationLedgerPrefix,
2387
+ committed_evidence: tuple[CommittedEvidence, ...],
2388
+ replay_checkpoint: ReplayCheckpoint,
2389
+ transaction_time: datetime,
2390
+ ) -> AuthorizationContext:
2391
+ """Derive live policy facts from one closed, signed ledger snapshot."""
2392
+
2393
+ if (
2394
+ not isinstance(work_order, WorkOrder)
2395
+ or not isinstance(ledger_prefix, AuthorizationLedgerPrefix)
2396
+ or type(committed_evidence) is not tuple
2397
+ or any(
2398
+ not isinstance(item, CommittedEvidence)
2399
+ for item in committed_evidence
2400
+ )
2401
+ or not isinstance(replay_checkpoint, ReplayCheckpoint)
2402
+ ):
2403
+ raise _fail("live authorization context inputs are unavailable")
2404
+ if (
2405
+ not isinstance(transaction_time, datetime)
2406
+ or transaction_time.tzinfo is None
2407
+ or transaction_time.utcoffset() != timedelta(0)
2408
+ or transaction_time.microsecond != 0
2409
+ ):
2410
+ raise _fail("transaction time must be a trusted UTC second")
2411
+ canonical_time = transaction_time.astimezone(timezone.utc)
2412
+ earliest_time = (
2413
+ ledger_prefix.receipts[-1].occurred_at
2414
+ if ledger_prefix.receipts
2415
+ else work_order.issued_at
2416
+ )
2417
+ if canonical_time < earliest_time:
2418
+ raise _fail("transaction time precedes the signed ledger prefix")
2419
+
2420
+ effective_grants = {
2421
+ grant.grant_id: grant
2422
+ for grant in ledger_prefix.effective_grants
2423
+ }
2424
+ grant_attempts = {
2425
+ grant.digest: grant
2426
+ for grant in ledger_prefix.grant_attempts
2427
+ }
2428
+ try:
2429
+ causal_state = replay_authorization_causality(
2430
+ work_order,
2431
+ ledger_prefix.receipts,
2432
+ )
2433
+ except AuthorizationCausalityError as error:
2434
+ raise _fail(str(error)) from error
2435
+ replay_state = replay_authorization_policy(
2436
+ work_order,
2437
+ effective_grants,
2438
+ grant_attempts,
2439
+ ledger_prefix.receipts,
2440
+ causal_state,
2441
+ )
2442
+ committed_by_path = _validate_committed_evidence(
2443
+ work_order,
2444
+ ledger_prefix,
2445
+ committed_evidence,
2446
+ )
2447
+ _validate_replay_checkpoint(
2448
+ work_order,
2449
+ ledger_prefix,
2450
+ committed_by_path,
2451
+ replay_checkpoint,
2452
+ causal_state.active_patch_receipt_id,
2453
+ )
2454
+ current_state = (
2455
+ ledger_prefix.receipts[-1].state_after
2456
+ if ledger_prefix.receipts
2457
+ else "issued"
2458
+ )
2459
+ return AuthorizationContext(
2460
+ work_order=work_order,
2461
+ ledger_prefix=ledger_prefix,
2462
+ committed_evidence=committed_evidence,
2463
+ replay_checkpoint=replay_checkpoint,
2464
+ transaction_time=canonical_time,
2465
+ causal_state=causal_state,
2466
+ replay_state=replay_state,
2467
+ current_state=current_state,
2468
+ routine_capacity_remaining=max(
2469
+ 0,
2470
+ 61 - len(ledger_prefix.receipts),
2471
+ ),
2472
+ denial_count=sum(
2473
+ receipt.policy_decision == "deny"
2474
+ for receipt in ledger_prefix.receipts
2475
+ ),
2476
+ independent_failure_terminal=(
2477
+ causal_state.independent_failure_terminal
2478
+ ),
2479
+ )
2480
+
2481
+
2482
+ __all__ = [
2483
+ "AuthorizationContext",
2484
+ "AuthorizationLedgerPrefix",
2485
+ "AuthorizationPolicyError",
2486
+ "AuthorizationReplayState",
2487
+ "CommittedEvidence",
2488
+ "ProspectiveExecutionFacts",
2489
+ "authorize_tool_call",
2490
+ "derive_authorization_context",
2491
+ "replay_authorization_policy",
2492
+ "validate_human_decision",
2493
+ "validate_rollback",
2494
+ ]