Publish reviewed strict execution consumption confirmation client
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
parent
097a2f2834
commit
e864305d4d
5 changed files with 455 additions and 0 deletions
96
approval_engine/execution_confirmation.py
Normal file
96
approval_engine/execution_confirmation.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
"""Exact consumption evidence for an independently authenticated mediator.
|
||||
|
||||
This client supplies PIP facts, never an execution admission or PDP decision.
|
||||
The mediator still owns claim/PDP validation, deadlines and a one-shot journal.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from urllib.parse import quote
|
||||
|
||||
from .binding import DIGEST_RE, canonical_binding
|
||||
from .pep import ApprovalHTTPClient, ApprovalProtocolError, _ID_RE
|
||||
|
||||
|
||||
def _inputs(approval_id: str, request_digest: str, decision_id: str) -> None:
|
||||
if not isinstance(approval_id, str) or not _ID_RE.fullmatch(approval_id):
|
||||
raise ApprovalProtocolError("approval id is invalid")
|
||||
if not isinstance(request_digest, str) or not DIGEST_RE.fullmatch(request_digest):
|
||||
raise ApprovalProtocolError("canonical request digest is required")
|
||||
if not isinstance(decision_id, str) or not decision_id.strip():
|
||||
raise ApprovalProtocolError("exact decision id is required")
|
||||
|
||||
|
||||
def _evidence(payload: dict[str, Any], request_digest: str, decision_id: str) -> str:
|
||||
if payload.get("request_digest") != request_digest:
|
||||
raise ApprovalProtocolError("consumed request digest does not match")
|
||||
if payload.get("decision_id") != decision_id:
|
||||
raise ApprovalProtocolError("consumed decision id does not match")
|
||||
value = payload.get("consumed_at")
|
||||
try:
|
||||
stamp = datetime.fromisoformat(value) if isinstance(value, str) else None
|
||||
except ValueError:
|
||||
stamp = None
|
||||
if stamp is None or stamp.tzinfo is None or stamp.utcoffset() is None:
|
||||
raise ApprovalProtocolError("consumption time is missing or invalid")
|
||||
if any(key in payload for key in ("allow", "deny", "effect")):
|
||||
raise ApprovalProtocolError("consumption evidence is not a decision")
|
||||
return value
|
||||
|
||||
|
||||
class ExecutionConfirmationClient(ApprovalHTTPClient):
|
||||
def consume_exact(
|
||||
self, approval_id: str, request_digest: str, decision_id: str
|
||||
) -> dict[str, Any]:
|
||||
"""One CAS call, with exact response validation and no automatic retry."""
|
||||
_inputs(approval_id, request_digest, decision_id)
|
||||
result = self._request(
|
||||
"POST", f"/v1/approvals/{quote(approval_id, safe='')}/consume",
|
||||
{"request_digest": request_digest, "decision_id": decision_id},
|
||||
)
|
||||
if result.get("approval_id") != approval_id or result.get("status") != "consumed":
|
||||
raise ApprovalProtocolError("exact consumption was not confirmed")
|
||||
_evidence(result, request_digest, decision_id)
|
||||
if type(result.get("idempotent")) is not bool:
|
||||
raise ApprovalProtocolError("idempotent must be an explicit boolean")
|
||||
return {"approval_id": approval_id, "status": "consumed",
|
||||
"request_digest": request_digest, "decision_id": decision_id,
|
||||
"consumed_at": result["consumed_at"], "idempotent": result["idempotent"]}
|
||||
|
||||
def confirm_consumption(
|
||||
self, approval_id: str, request_digest: str, decision_id: str, *,
|
||||
expected_binding: dict[str, Any], expected_pdp_digest: str,
|
||||
) -> dict[str, str]:
|
||||
"""Read the authoritative object; return historical facts only.
|
||||
|
||||
Approval entries may be present on the wire but are not republished.
|
||||
This method is safe for reconciliation after an unknown CAS outcome;
|
||||
reconciliation cannot authorize retrying protected backend work.
|
||||
"""
|
||||
_inputs(approval_id, request_digest, decision_id)
|
||||
if not isinstance(expected_pdp_digest, str) or not DIGEST_RE.fullmatch(expected_pdp_digest):
|
||||
raise ApprovalProtocolError("exact approval-free PDP digest is required")
|
||||
try:
|
||||
expected = canonical_binding(expected_binding)
|
||||
except (TypeError, ValueError, KeyError):
|
||||
raise ApprovalProtocolError("expected act binding is invalid") from None
|
||||
obj = self._request("GET", f"/v1/approvals/{quote(approval_id, safe='')}")
|
||||
if obj.get("id") != approval_id or obj.get("status") != "consumed":
|
||||
raise ApprovalProtocolError("durable consumption was not confirmed")
|
||||
if any(key in obj for key in ("allow", "deny", "effect")):
|
||||
raise ApprovalProtocolError("consumption object is not a decision")
|
||||
served = obj.get("binding")
|
||||
try:
|
||||
same_act = isinstance(served, dict) and canonical_binding(served) == expected
|
||||
except (TypeError, ValueError, KeyError):
|
||||
same_act = False
|
||||
if not same_act or served.get("pdp_digest") != expected_pdp_digest:
|
||||
raise ApprovalProtocolError("consumed approval binding does not match")
|
||||
if served.get("pdp_path") is not True or served.get("human_control") is not True:
|
||||
raise ApprovalProtocolError("production execution requires declared PDP and human control")
|
||||
consumption = obj.get("consumption")
|
||||
if not isinstance(consumption, dict):
|
||||
raise ApprovalProtocolError("durable consumption evidence is missing")
|
||||
consumed_at = _evidence(consumption, request_digest, decision_id)
|
||||
return {"approval_id": approval_id, "request_digest": request_digest,
|
||||
"decision_id": decision_id, "consumed_at": consumed_at}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# Approval Engine task execution consumption source owner review
|
||||
|
||||
```yaml
|
||||
reviewer: approval-engine-owner (agent approval-engine, codex)
|
||||
decision: approved
|
||||
comment: >-
|
||||
Source-only provider client and consumer contract are consistent with
|
||||
approval-engine INTENT.md Purpose, What approval-engine Owns, The Claim Shape,
|
||||
and Consumption Ordering under GH-DEC-2026-003: they supply exact durable
|
||||
approval consumption facts, never a PDP verdict or execution admission.
|
||||
Reviewed from the approval-engine checkout with its AGENTS.md/INTENT.md loaded,
|
||||
after own work item APPROVAL-WP-ADHOC-2026-10-11-T01 was persisted in
|
||||
097a2f28340c0e768a327e3aff998835ff8226d0, anchor SHA256
|
||||
6765012ef84045aa02cd68782ce2182149eaae0afbdd277c86d8ba7a6a545ad9.
|
||||
Exact source revision: execution_confirmation.py SHA256
|
||||
81dd085908e2aa3cb516af8a2660f2625fe9b5d1a5f2209725c139b8b8587318;
|
||||
test_execution_confirmation.py SHA256
|
||||
a1d54e3a6bf1dff70d91cf1853280b9ae1c33e44e5f7c0227aa2d17188a246f2;
|
||||
task-execution-consumption-contract.md SHA256
|
||||
b5f0a7e0d0b7812a35c98dc31d852660ebffb23c6c657303063becb12b22ee11.
|
||||
Approval covers these immutable source artifacts only. Actual admitted
|
||||
independent Platform mediator approval:read scope, service/agent consume
|
||||
identity, credential custody and authenticated transport/ingress reachability
|
||||
are absent; this judgement supplies none of them. No grant, human approval,
|
||||
native execution, deployment, T-06 proof or broader lifecycle adoption.
|
||||
```
|
||||
|
||||
Existing source facts checked against api.py, auth.py, store.py and binding.py:
|
||||
|
||||
- POST /v1/approvals/{id}/consume is authenticated, tenant-isolated and
|
||||
service/agent-scoped. BEGIN IMMEDIATE plus status CAS emits use atomically.
|
||||
Engine validates request-digest syntax; it does not validate a PDP decision
|
||||
or independently equate request_digest to the stored approval-free digest.
|
||||
- GET /v1/approvals/{id} is the existing approval:read confirmation source.
|
||||
It exposes persisted consumption request/decision/time and native binding;
|
||||
the claim endpoint's consumed boolean alone does not prove that tuple.
|
||||
- Same-digest retries retain the original decision and consumed_at, including
|
||||
after expiry. A changed retry decision cannot be substituted by the client.
|
||||
Different digest conflicts. Consumed is terminal; it cannot be revoked or
|
||||
superseded to cancel further work. Historical confirmation renews no authority.
|
||||
- The new client rejects changed identifiers, mismatched original decision,
|
||||
malformed times and coerced booleans, rejects decision-shaped payloads,
|
||||
compares exact act and approval-free digest on read, and filters returned
|
||||
facts. It performs no protected callback, native mutation or automatic retry.
|
||||
|
||||
Execution requires the independent mediator to obtain and validate a fresh
|
||||
human-controlled claim and exact Access DecisionEnvelope before CAS, match
|
||||
original approval-free/full request digests and owner-compiled body/target/source
|
||||
context, and persist confirmed original consumption in a separate live one-shot
|
||||
execution journal before native work. Merely possessing provider output, a local
|
||||
file or idempotent success is insufficient. Unknown consume/read outcomes stop
|
||||
execution and permit only owner reconciliation; another protected action needs
|
||||
fresh exact recovery approval. Original approval/PDP/operation deadlines govern
|
||||
continuation; scoped cleanup is separate. The client does not attest its caller's
|
||||
identity or prove these mediator preconditions.
|
||||
|
||||
The native binding still covers only the five act fields; context execution body
|
||||
digests rely on Access's actual approval-free and enriched CheckRequest digests.
|
||||
A new execute-ccr action/package/principal representation must be owner-published
|
||||
and explicitly admitted. Existing apply/v2 package semantics cannot be assumed
|
||||
to recognize it. No other owner role is reviewed here.
|
||||
|
||||
Validation reviewed: unchanged source/tests match the exact hashes above;
|
||||
24 focused tests and 219 full staged tests passed with the existing JWT test-key
|
||||
warning. Existing authenticated in-process WSGI GET checks are source evidence,
|
||||
not a native admitted mediator proof. Source patch check passes. The original
|
||||
completed source-preparation outcome is preserved. APPROVAL-WP-0002-T01/T05 and
|
||||
all native/adoption owner tasks remain unchanged; no residual is promoted.
|
||||
143
docs/task-execution-consumption-contract.md
Normal file
143
docs/task-execution-consumption-contract.md
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
# Approval Engine metadata execution consumer contract — source proposal
|
||||
|
||||
Author: approval-engine source reviewer (Codex), staged only. Work record:
|
||||
APPROVAL-WP-ADHOC-2026-10-11-T01, bounded completed source preparation. Existing
|
||||
APPROVAL-WP-0002-T05 remains unchanged wait/residual adoption. This is an
|
||||
author-qualified technical proposal, not a formal delegated owner review,
|
||||
owner acknowledgement, founder approval, identity admission or runtime receipt.
|
||||
Source checkout: c8ae88057d5f2ff3743a37c158d7f692d90cce23, including pre-existing
|
||||
uncommitted owner files listed in source-state.json; none modified.
|
||||
|
||||
Reviewed proposals: SECRETS-EXEC-20261011-v1 and platform-task-execution/v1.
|
||||
Their exact source-file hashes are in source-state.json. Initial execute-ccr
|
||||
metadata pilot only; no generic grants, daemon, standing service permissions,
|
||||
raw bearer handoff or full Secrets adoption.
|
||||
|
||||
## Existing route and trust boundary
|
||||
|
||||
- GET /v1/approvals/{id}/claim requires approval:read and exact store tenant.
|
||||
It supplies fresh approval facts and native five-field act binding, approval-free
|
||||
PDP digest, PDP/human declarations. consumed=true lacks consumed request/decision
|
||||
correspondence and cannot confirm execution admission.
|
||||
- POST /v1/approvals/{id}/consume requires approval:consume, exact tenant and
|
||||
service/agent principal. Body is request_digest plus decision_id. It returns
|
||||
approval_id, status=consumed, request_digest, original decision_id when stored,
|
||||
consumed_at and explicit idempotent boolean. Direct response to an authenticated
|
||||
independent mediator is authoritative mutation evidence on its trusted transport.
|
||||
- GET /v1/approvals/{id} requires approval:read. Approval.as_dict returns id,
|
||||
persisted status, exact binding, validity, and consumption containing original
|
||||
request_digest/decision_id/consumed_at. This existing read is the independent
|
||||
consume-confirmation surface. It includes approver entries, so capture in memory
|
||||
and discard them; never serialize the raw object into a receipt or tool output.
|
||||
|
||||
There is no signed portable consume receipt or dedicated public confirmation
|
||||
endpoint. A caller boolean, ConsumedApproval dataclass, JSON receipt or local file
|
||||
is not trusted proof. The mediator fetches the authoritative object itself through
|
||||
its owner-pinned authenticated endpoint, with no caller-selected origin/opener.
|
||||
Bearer authentication identifies the client to the server; the Platform owner must
|
||||
also establish the server/transport identity. Generic HTTP support in the helper
|
||||
is not evidence of an admitted production transport.
|
||||
|
||||
Identity/read reachability is explicitly unproved. The admitted Secrets client
|
||||
does not grant a new Platform mediator approval:read/consume custody. KeyCape owns
|
||||
any exact client/audience/scope/tenant registration and Platform owns transport and
|
||||
credential custody; do not widen an identity profile or reuse Secrets credentials
|
||||
merely to make a read work. In-cluster callers require the existing reviewed
|
||||
NetworkPolicy namespace admission too. No credential was obtained in this review.
|
||||
|
||||
## Exact binding responsibilities
|
||||
|
||||
Source mechanisms: api.py ApiApplication.identity and consume route; auth.py
|
||||
RS256/JWKS claim verification; store.py Engine.consume/Approval.as_dict/claim;
|
||||
binding.py canonical_binding; execution_confirmation.py staged strict client.
|
||||
|
||||
Native binding digest covers action/actor/principal/purpose/target, preserving
|
||||
arbitrary nested target. Extra top-level execution context is not part of this
|
||||
native digest. It must be covered by the PDP's approval-free digest issued as
|
||||
pdp_digest and the full enriched decision binding.request_digest submitted to
|
||||
consume. Access owner reports ApprovalBindingDigest removes only top-level
|
||||
context.approval: execution_contract remains covered. Compute through Access's
|
||||
actual canonical CheckRequest implementation; never assume Python sorted-key JSON
|
||||
is the Go request digest. Full enriched request includes the served claim.
|
||||
|
||||
Engine.consume validates digest syntax, not equivalence to pdp_digest or act, and
|
||||
does not validate a DecisionEnvelope, decision_id existence or consuming sub
|
||||
against binding.actor. This is the PIP boundary, not a second policy decision
|
||||
point. A service holding consume scope can spend an approved object with an
|
||||
unrelated well-formed digest; that response cannot substitute for the mediator's
|
||||
prior independent exact claim/PDP/body/action/target/actor/principal validation.
|
||||
|
||||
Access's present v2 apply rules do not evaluate execution_contract. A distinct
|
||||
execute-ccr action, published exact package/version/digest and verified initiating
|
||||
principal/representation mapping are prerequisites, not inferred semantics.
|
||||
Create enforces binding.actor==verified requester sub; the design must resolve
|
||||
actual initiating principal vs execution service with Identity/Access owners,
|
||||
without guessing actor strings or treating environment subject text as evidence.
|
||||
All owner-compiled CCR/grant/catalog/source revisions, methods/paths/body hashes,
|
||||
operation UUID and deadlines must remain in the approved underlying context.
|
||||
Approval IDs and served claim additions must follow Access's explicit exclusion.
|
||||
|
||||
## Idempotency and lifecycle
|
||||
|
||||
BEGIN IMMEDIATE serializes consume; status compare-and-swap and local use outbox
|
||||
are one transaction. The first consume requires approved status, current validity
|
||||
window and declared human-control evidence. Different consumed digest conflicts.
|
||||
Same digest returns idempotent=true and the original decision_id even if a retry
|
||||
submits another decision_id, even after original expiry. No second use event is
|
||||
created. That success is historical consumption, never renewed execution power.
|
||||
The staged client requires exact original decision correspondence and rejects
|
||||
string/integer booleans, missing times and decision-shaped responses.
|
||||
|
||||
Revoked/superseded/expired objects reject a first consume. Consumed is terminal:
|
||||
revoke and supersede cannot cancel it, and expiry does not erase historical
|
||||
consumption. A spent claim has valid_now=false (after deadline its reason/state
|
||||
is expired while consumed remains true). Rechecking valid_now after consume
|
||||
cannot validate continuation. The original validated approval/PDP/operation
|
||||
deadlines bound every business write; failure stops further writes. In-flight
|
||||
cancellation requires a separately owned execution stop/cleanup mechanism, not
|
||||
post-consume approval revocation. Cleanup has its explicit original obligation.
|
||||
|
||||
## Independent mediator sequence
|
||||
|
||||
1. Independently reconstruct owner-approved operation bodies and binding; check
|
||||
identity/source/revisions/deadline and exact published PDP package. No native
|
||||
secret read/login, token mint or mutation during preparation.
|
||||
2. Atomically reserve a one-shot execution journal admission for the immutable
|
||||
approval/request/operation tuple; only creator proceeds. This journal is
|
||||
Platform execution state, distinct from approval-engine outbox/consumption.
|
||||
3. Fetch and validate a fresh human-controlled PDP-path claim, call Access Check,
|
||||
require exact underlying/full digest, original act and policy pin, then one
|
||||
CAS consume. Prefer consume at this mediator boundary to early CLI consumption.
|
||||
4. Validate direct consume evidence with consume_exact; if separate Secrets code
|
||||
returns a receipt, independently GET confirm_consumption with expected native
|
||||
act and approval-free PDP digest. Match original exact decision/full digest/time.
|
||||
Do not issue another mutating consume merely to discover whether one happened.
|
||||
5. Persist confirmed evidence in the existing live journal before protected
|
||||
calls. A successful idempotent response or GET without that same live one-shot
|
||||
admission never permits execution. A CAS/read timeout marks unknown; owner
|
||||
reconciliation may read confirmation but does not automatically resume work.
|
||||
6. Check original deadlines before every native call, record about-to-call then
|
||||
outcome, and maintain separately confirmed cleanup. Duplicate completed input
|
||||
returns existing receipt only. Any incomplete/unknown operation requires owner
|
||||
reconciliation and fresh exact recovery approval for another protected action.
|
||||
|
||||
The staged client implements only exact direct evidence and independent read
|
||||
confirmation. It does not implement the journal, authorization policy, server
|
||||
identity admission or execution. Existing Secrets consume_approval accepts wrong
|
||||
approval_id, does not require original decision match and coerces idempotent;
|
||||
Secrets owns its strict handoff refinement. Do not silently extend the generic
|
||||
ProtectedActionHarness into an at-most-once execution service.
|
||||
|
||||
## Gates and validation
|
||||
|
||||
Existing owners/tasks retained: APPROVAL-WP-0002-T05 served T-06 consumer proof;
|
||||
APPROVAL-WP-0002-T01 issuer/profile plus schema-v6 release evidence;
|
||||
SECRETS-WP-0013 implementation and SECRETS-WP-0008-T06 full adoption;
|
||||
RPF-WP-0035-T02 native lane adoption; exact Access/Identity action/representation
|
||||
admission under those owners' records. No dependency is marked closed.
|
||||
|
||||
24 focused tests pass, including authenticated WSGI GET with no mutation/outbox
|
||||
change, wrong act/target/actor/principal/purpose/digest/decision refusal, strict
|
||||
booleans/time, denied read, filtered evidence and idempotency after expiry with
|
||||
terminal revoke/supersede refusal. Staged full suite: 219 passed, one existing
|
||||
JWT short-test-key warning. No production HTTP/DB/OpenBao call or native action.
|
||||
128
tests/test_execution_confirmation.py
Normal file
128
tests/test_execution_confirmation.py
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import json
|
||||
from datetime import timedelta
|
||||
from urllib.parse import urlsplit
|
||||
from urllib.error import HTTPError
|
||||
|
||||
import pytest
|
||||
|
||||
from approval_engine.api import call
|
||||
from approval_engine.errors import Conflict
|
||||
from approval_engine.execution_confirmation import ExecutionConfirmationClient
|
||||
from approval_engine.pep import ApprovalProtocolError
|
||||
from tests.conftest import FROZEN, binding, validity
|
||||
from tests.test_pep import Response
|
||||
|
||||
FULL = "sha256:" + "ab" * 32
|
||||
FREE = "sha256:" + "cd" * 32
|
||||
|
||||
|
||||
def client(tmp_path, opener):
|
||||
token = tmp_path / "test-token"
|
||||
token.write_text("test-token")
|
||||
return ExecutionConfirmationClient("http://approval-engine", token, opener=opener)
|
||||
|
||||
|
||||
def consumed(engine):
|
||||
obj = engine.create(binding(), validity(), pdp_digest=FREE, pdp_path=True,
|
||||
human_control=True)
|
||||
engine.add_entry(obj.id, "human:founder", principal_type="human")
|
||||
engine.consume(obj.id, FULL, decision_id="decision:original")
|
||||
return obj
|
||||
|
||||
|
||||
def confirmer(c, obj):
|
||||
return c.confirm_consumption(obj.id, FULL, "decision:original",
|
||||
expected_binding=binding(), expected_pdp_digest=FREE)
|
||||
|
||||
|
||||
def test_confirmation_reads_authenticated_current_object_without_mutation(engine, app, tmp_path):
|
||||
obj = consumed(engine)
|
||||
before = list(engine.undrained())
|
||||
requests = []
|
||||
|
||||
def opener(req, timeout):
|
||||
requests.append(req)
|
||||
status, result = call(app, req.get_method(), urlsplit(req.full_url).path,
|
||||
authorization=req.get_header("Authorization"))
|
||||
assert status == 200
|
||||
return Response(result)
|
||||
|
||||
result = confirmer(client(tmp_path, opener), obj)
|
||||
assert result == {"approval_id": obj.id, "request_digest": FULL,
|
||||
"decision_id": "decision:original", "consumed_at": FROZEN.isoformat()}
|
||||
assert requests[0].get_method() == "GET"
|
||||
assert engine.undrained() == before
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field,value", [
|
||||
("id", "different"), ("status", "approved"),
|
||||
("consumption", {"request_digest": FULL, "decision_id": "different", "consumed_at": FROZEN.isoformat()}),
|
||||
("consumption", {"request_digest": FREE, "decision_id": "decision:original", "consumed_at": FROZEN.isoformat()}),
|
||||
("consumption", {"request_digest": FULL, "decision_id": "decision:original", "consumed_at": "naive"}),
|
||||
("consumption", None), ("effect", "ALLOW"),
|
||||
])
|
||||
def test_wrong_consumption_object_refuses(engine, tmp_path, field, value):
|
||||
obj = consumed(engine)
|
||||
result = engine.get(obj.id).as_dict()
|
||||
result[field] = value
|
||||
with pytest.raises(ApprovalProtocolError):
|
||||
confirmer(client(tmp_path, lambda *_a, **_k: Response(result)), obj)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field,value", [
|
||||
("action", "exec"), ("target", {"id": "sibling"}), ("actor", "forged"),
|
||||
("principal", "forged"), ("purpose", "different"), ("pdp_digest", FULL),
|
||||
("pdp_path", "true"), ("human_control", 1),
|
||||
])
|
||||
def test_wrong_act_or_digest_or_declaration_refuses(engine, tmp_path, field, value):
|
||||
obj = consumed(engine)
|
||||
result = engine.get(obj.id).as_dict()
|
||||
result["binding"][field] = value
|
||||
with pytest.raises(ApprovalProtocolError):
|
||||
confirmer(client(tmp_path, lambda *_a, **_k: Response(result)), obj)
|
||||
|
||||
|
||||
def test_retry_is_historical_after_expiry_and_retains_original_decision(engine, tmp_path):
|
||||
obj = consumed(engine)
|
||||
engine.clock = lambda: FROZEN + timedelta(days=1)
|
||||
retry = engine.consume(obj.id, FULL, decision_id="decision:new")
|
||||
assert retry["idempotent"] is True
|
||||
assert retry["decision_id"] == "decision:original"
|
||||
with pytest.raises(ApprovalProtocolError):
|
||||
client(tmp_path, lambda *_a, **_k: Response(retry)).consume_exact(obj.id, FULL, "decision:new")
|
||||
assert engine.claim(obj.id)["valid_now"] is False
|
||||
assert confirmer(client(tmp_path, lambda *_a, **_k: Response(engine.get(obj.id).as_dict())), obj)
|
||||
with pytest.raises(Conflict):
|
||||
engine.revoke(obj.id)
|
||||
with pytest.raises(Conflict):
|
||||
engine.supersede(obj.id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field,value", [
|
||||
("approval_id", "wrong"), ("decision_id", None), ("idempotent", "false"),
|
||||
("consumed_at", "2026-10-11T12:00:00"), ("effect", "ALLOW"),
|
||||
])
|
||||
def test_exact_consume_response_refuses_loose_receipt(engine, tmp_path, field, value):
|
||||
obj = consumed(engine)
|
||||
result = engine.consume(obj.id, FULL, decision_id="decision:original")
|
||||
result[field] = value
|
||||
with pytest.raises(ApprovalProtocolError):
|
||||
client(tmp_path, lambda *_a, **_k: Response(result)).consume_exact(obj.id, FULL, "decision:original")
|
||||
|
||||
|
||||
def test_exact_consume_filters_extraneous_payload(engine, tmp_path):
|
||||
obj = consumed(engine)
|
||||
result = engine.consume(obj.id, FULL, decision_id="decision:original")
|
||||
result["notes"] = "discard"
|
||||
confirmed = client(tmp_path, lambda *_a, **_k: Response(result)).consume_exact(obj.id, FULL, "decision:original")
|
||||
assert "notes" not in confirmed
|
||||
|
||||
|
||||
def test_denied_read_never_represents_confirmation(engine, app, tmp_path):
|
||||
obj = consumed(engine)
|
||||
def opener(req, timeout):
|
||||
status, result = call(app, "GET", urlsplit(req.full_url).path, authorization=None)
|
||||
assert status == 401
|
||||
raise HTTPError(req.full_url, status, "refused", {}, None)
|
||||
with pytest.raises(ApprovalProtocolError, match="unauthorized"):
|
||||
confirmer(client(tmp_path, opener), obj)
|
||||
|
|
@ -62,3 +62,23 @@ RPF-WP-0035-T02 retain their original statuses and authority boundaries.
|
|||
Formal review, if requested, must follow persistence of this work record in the
|
||||
actual owner checkout. This staged file is the proposed workrecord-before-review
|
||||
anchor; it does not establish a persisted anchor or approval by its presence.
|
||||
|
||||
|
||||
## Formal source owner review after persisted preparation anchor
|
||||
|
||||
Under the same bounded APPROVAL-WP-ADHOC-2026-10-11-T01 new-demand record, the
|
||||
approval-engine owner worker performed formal source judgement only after
|
||||
097a2f28340c0e768a327e3aff998835ff8226d0 persisted the original preparation
|
||||
record (SHA256 6765012ef84045aa02cd68782ce2182149eaae0afbdd277c86d8ba7a6a545ad9).
|
||||
The preceding proposed-anchor paragraph describes that earlier stage; the
|
||||
persisted preparation evidence now supplies the workrecord-before-review anchor.
|
||||
|
||||
Decision: approved for the unchanged exact provider sourceclient, tests and
|
||||
source-only consumption contract. Review: docs/reviews/2026-10-11-task-execution-
|
||||
consumption-source-owner-review.md. Exact hashes and INTENT basis are recorded
|
||||
there. Original task status, validation and source-preparation outcome retained.
|
||||
The review does not assert actual mediator identity/read scope, custody or
|
||||
transport admission. Fresh claim/PDP validation, returned original consumption
|
||||
correspondence and a separate one-shot journal remain caller obligations.
|
||||
No grant, native acceptance, deployment, human approval, residual promotion or
|
||||
full-adoption closure was recorded.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue