2026-09-01 23:45:48 +02:00
|
|
|
"""HTTP surface. Introspection and lifecycle mutation; never a decision."""
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
from typing import Any, Callable
|
|
|
|
|
|
2026-09-02 00:52:04 +02:00
|
|
|
from .auth import Authenticator, DenyAllAuthenticator, Identity
|
|
|
|
|
from .errors import ApprovalError, Forbidden
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
from .store import Engine
|
|
|
|
|
|
|
|
|
|
FORBIDDEN_DECISION_KEYS = frozenset({"effect", "decision", "allow", "deny"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _read_json(environ: dict[str, Any]) -> dict[str, Any]:
|
2026-09-02 00:52:04 +02:00
|
|
|
try:
|
|
|
|
|
length = int(environ.get("CONTENT_LENGTH") or 0)
|
|
|
|
|
except (TypeError, ValueError) as exc:
|
|
|
|
|
raise ApprovalError("invalid content length") from exc
|
|
|
|
|
if length < 0 or length > 256 * 1024:
|
|
|
|
|
raise ApprovalError("request body is too large")
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
if length == 0:
|
|
|
|
|
return {}
|
|
|
|
|
raw = environ["wsgi.input"].read(length)
|
2026-09-02 00:52:04 +02:00
|
|
|
if len(raw) != length:
|
|
|
|
|
raise ApprovalError("truncated request body")
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
if not raw:
|
|
|
|
|
return {}
|
|
|
|
|
try:
|
|
|
|
|
data = json.loads(raw.decode("utf-8"))
|
|
|
|
|
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
|
|
|
raise ApprovalError("invalid json") from exc
|
|
|
|
|
if not isinstance(data, dict):
|
|
|
|
|
raise ApprovalError("json object required")
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _assert_not_decision(payload: Any) -> None:
|
|
|
|
|
if isinstance(payload, dict):
|
|
|
|
|
bad = FORBIDDEN_DECISION_KEYS & set(payload)
|
|
|
|
|
if bad:
|
|
|
|
|
raise RuntimeError(f"decision-shaped keys leaked: {sorted(bad)}")
|
|
|
|
|
for value in payload.values():
|
|
|
|
|
_assert_not_decision(value)
|
|
|
|
|
elif isinstance(payload, list):
|
|
|
|
|
for item in payload:
|
|
|
|
|
_assert_not_decision(item)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class App:
|
2026-09-02 00:52:04 +02:00
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
engine: Engine,
|
|
|
|
|
authenticator: Authenticator | None = None,
|
|
|
|
|
*,
|
|
|
|
|
require_persistent: bool = False,
|
|
|
|
|
) -> None:
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
self.engine = engine
|
2026-09-02 00:52:04 +02:00
|
|
|
self.authenticator = authenticator or DenyAllAuthenticator()
|
|
|
|
|
self.require_persistent = require_persistent
|
|
|
|
|
|
|
|
|
|
def identity(self, environ: dict[str, Any], scope: str) -> Identity:
|
|
|
|
|
identity = self.authenticator.authenticate(
|
|
|
|
|
environ.get("HTTP_AUTHORIZATION")
|
|
|
|
|
).require(scope)
|
|
|
|
|
if identity.tenant != self.engine.tenant:
|
|
|
|
|
raise Forbidden("caller tenant does not match this approval store")
|
|
|
|
|
return identity
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
|
|
|
|
|
def __call__(self, environ: dict[str, Any], start_response: Callable) -> list[bytes]:
|
|
|
|
|
method = environ.get("REQUEST_METHOD", "GET").upper()
|
|
|
|
|
path = environ.get("PATH_INFO") or "/"
|
|
|
|
|
try:
|
|
|
|
|
status, body = self.dispatch(method, path, environ)
|
|
|
|
|
except ApprovalError as exc:
|
|
|
|
|
status, body = exc.http_status, {"error": exc.reason_code, "message": str(exc)}
|
|
|
|
|
except ValueError as exc:
|
|
|
|
|
status, body = 422, {"error": "unprocessable", "message": str(exc)}
|
|
|
|
|
_assert_not_decision(body)
|
|
|
|
|
payload = json.dumps(body, sort_keys=True).encode("utf-8")
|
|
|
|
|
start_response(
|
|
|
|
|
f"{status} {'OK' if status < 400 else 'ERROR'}",
|
|
|
|
|
[
|
|
|
|
|
("Content-Type", "application/json"),
|
|
|
|
|
("Content-Length", str(len(payload))),
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
return [payload]
|
|
|
|
|
|
|
|
|
|
def dispatch(self, method: str, path: str, environ: dict[str, Any]) -> tuple[int, dict[str, Any]]:
|
2026-09-02 00:52:04 +02:00
|
|
|
if path in ("/healthz", "/v1/healthz") and method == "GET":
|
|
|
|
|
return 200, {"status": "ok"}
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
if path in ("/readyz", "/v1/readyz") and method == "GET":
|
2026-09-02 00:52:04 +02:00
|
|
|
storage = self.engine.storage_status()
|
|
|
|
|
ready = storage["schema_current"] and (
|
|
|
|
|
storage["persistent"] or not self.require_persistent
|
|
|
|
|
)
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
self.engine.outbox_stats()
|
2026-09-02 00:52:04 +02:00
|
|
|
return (200 if ready else 503), {
|
|
|
|
|
"status": "ok" if ready else "unavailable",
|
|
|
|
|
"store": "ok" if ready else "not-production-ready",
|
|
|
|
|
}
|
|
|
|
|
if path == "/v1/storage/status" and method == "GET":
|
|
|
|
|
self.identity(environ, "approval:observe")
|
|
|
|
|
return 200, self.engine.storage_status(integrity=True)
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
if path == "/v1/cadence" and method == "GET":
|
2026-09-02 00:52:04 +02:00
|
|
|
self.identity(environ, "approval:observe")
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
return 200, self.engine.transition_counts() | {"form": "heartbeat-or-reconciliation"}
|
|
|
|
|
if path == "/v1/outbox/stats" and method == "GET":
|
2026-09-02 00:52:04 +02:00
|
|
|
self.identity(environ, "approval:observe")
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
return 200, self.engine.outbox_stats()
|
|
|
|
|
if path == "/v1/heartbeat" and method == "POST":
|
2026-09-02 00:52:04 +02:00
|
|
|
self.identity(environ, "approval:emit")
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
return 200, self.engine.emit_heartbeat()
|
|
|
|
|
if path == "/v1/approvals" and method == "POST":
|
2026-09-02 00:52:04 +02:00
|
|
|
identity = self.identity(environ, "approval:create")
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
data = _read_json(environ)
|
2026-09-02 00:52:04 +02:00
|
|
|
binding = data.get("binding") or {}
|
|
|
|
|
if binding.get("actor") != identity.subject:
|
|
|
|
|
raise Forbidden("binding.actor must match the authenticated subject")
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
obj = self.engine.create(
|
2026-09-02 00:52:04 +02:00
|
|
|
binding,
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
data.get("validity") or {},
|
|
|
|
|
int(data.get("required_count") or 1),
|
|
|
|
|
pdp_digest=data.get("pdp_digest"),
|
Implement GH-DEC-2026-008: declared PDP-path intent, enforced at issue
Gate House ruled binding.pdp_digest is the binding correspondence on the
GH-DEC-2026-003 path and is required there, having rejected a vocabulary
mapping for the reasons we gave. It asked this engine to record the PDP
digest at issue for approvals intended for that path, and to have the
claim state which approvals those are rather than leaving it to the
requester's memory.
Schema v3 adds approvals.pdp_path. create() refuses pdp_path true without
a pdp_digest, so an approval that would be unusable on the path fails at
issue rather than at the protected side effect. The claim exposes
binding.pdp_path, which makes it a guarantee rather than a hint: pdp_path
true implies pdp_digest is non-null.
Intent is declared and never inferred. A pdp_digest that happens to be
present is not a declaration anybody made, so a recorded digest alone
leaves pdp_path false, legacy rows migrate to false rather than being
back-filled from their digests, and a successor inherits its
predecessor's declaration. Approvals issued before the ruling stay usable
by consumers in this engine's own vocabulary and are simply not usable on
the PDP path -- the ruling's intended cost, stated as such.
Schema, both published examples, a v2-to-v3 migration test asserting
survivors keep their digest while declaring no path intent, and tests for
refusal at issue, claim exposure, non-inference, and successor
inheritance. 102 tests pass (8 new).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TvyJPAaVCGsVheVhcCwNND
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 411227@bnt-lap001
Assistant-Session: d566f6d3-bcaf-43c3-bc5e-3ddd0f64b535
2026-09-06 14:51:23 +02:00
|
|
|
pdp_path=bool(data.get("pdp_path", False)),
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
approval_id=data.get("id"),
|
|
|
|
|
)
|
|
|
|
|
return 201, obj.as_dict()
|
|
|
|
|
parts = path.strip("/").split("/")
|
|
|
|
|
if len(parts) >= 3 and parts[0] == "v1" and parts[1] == "approvals":
|
|
|
|
|
approval_id = parts[2]
|
|
|
|
|
rest = parts[3:]
|
|
|
|
|
if not rest and method == "GET":
|
2026-09-02 00:52:04 +02:00
|
|
|
self.identity(environ, "approval:read")
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
return 200, self.engine.get(approval_id).as_dict()
|
|
|
|
|
if rest == ["claim"] and method == "GET":
|
2026-09-02 00:52:04 +02:00
|
|
|
self.identity(environ, "approval:read")
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
return 200, self.engine.claim(approval_id)
|
|
|
|
|
if rest == ["entries"] and method == "POST":
|
2026-09-02 00:52:04 +02:00
|
|
|
identity = self.identity(environ, "approval:approve")
|
|
|
|
|
_read_json(environ)
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
obj = self.engine.add_entry(
|
|
|
|
|
approval_id,
|
2026-09-02 00:52:04 +02:00
|
|
|
identity.subject,
|
|
|
|
|
assurance=json.dumps(identity.assurance, sort_keys=True),
|
|
|
|
|
evidence_ref=identity.evidence_ref,
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
)
|
|
|
|
|
return 200, obj.as_dict()
|
|
|
|
|
if rest == ["revoke"] and method == "POST":
|
2026-09-02 00:52:04 +02:00
|
|
|
self.identity(environ, "approval:revoke")
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
return 200, self.engine.revoke(approval_id).as_dict()
|
|
|
|
|
if rest == ["supersede"] and method == "POST":
|
2026-09-02 00:52:04 +02:00
|
|
|
self.identity(environ, "approval:supersede")
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
data = _read_json(environ)
|
|
|
|
|
return 200, self.engine.supersede(approval_id, data.get("successor_id"))
|
2026-09-01 23:45:48 +02:00
|
|
|
if rest == ["consume"] and method == "POST":
|
2026-09-02 00:52:04 +02:00
|
|
|
identity = self.identity(environ, "approval:consume")
|
|
|
|
|
if identity.principal_type not in {"service", "agent"}:
|
|
|
|
|
raise Forbidden("consume requires a service or agent principal")
|
2026-09-01 23:45:48 +02:00
|
|
|
data = _read_json(environ)
|
|
|
|
|
return 200, self.engine.consume(
|
|
|
|
|
approval_id,
|
|
|
|
|
data.get("request_digest"),
|
|
|
|
|
decision_id=data.get("decision_id"),
|
|
|
|
|
)
|
|
|
|
|
if "check" in path or path.endswith("/authorize"):
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
return 404, {"error": "not_found", "message": "no such surface"}
|
|
|
|
|
return 404, {"error": "not_found", "message": path}
|
|
|
|
|
|
|
|
|
|
|
2026-09-02 00:52:04 +02:00
|
|
|
def call(
|
|
|
|
|
app: App,
|
|
|
|
|
method: str,
|
|
|
|
|
path: str,
|
|
|
|
|
body: dict[str, Any] | None = None,
|
|
|
|
|
*,
|
|
|
|
|
authorization: str | None = "Bearer test-token",
|
|
|
|
|
) -> tuple[int, dict[str, Any]]:
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
"""In-process WSGI helper for tests."""
|
|
|
|
|
raw = json.dumps(body or {}).encode("utf-8") if body is not None else b""
|
|
|
|
|
environ = {
|
|
|
|
|
"REQUEST_METHOD": method,
|
|
|
|
|
"PATH_INFO": path,
|
|
|
|
|
"wsgi.input": _Bytes(raw),
|
|
|
|
|
"CONTENT_LENGTH": str(len(raw)) if body is not None else "0",
|
|
|
|
|
"QUERY_STRING": "",
|
|
|
|
|
}
|
2026-09-02 00:52:04 +02:00
|
|
|
if authorization is not None:
|
|
|
|
|
environ["HTTP_AUTHORIZATION"] = authorization
|
Implement the engine spine: claim, outbox, machine, API
Contracts first (T02–T04): approval claim schema with issuer, freshness,
and binding digest; local transactional outbox wire; load-bearing cadence
as heartbeat or reconciliation (layer.yaml declared).
Then the object (T06–T08): SQLite closed state machine, CAS supersession,
distinct-approver fail-closed, revocation without holder cooperation,
outbox insert in the same transaction. Tests fail the mutation when
emission fails, and revoke while the drain sink is down.
Introspection GET /v1/approvals/{id}/claim is a PIP fact, not a decision.
No public consume (T05 waits on GH-WP-0002-T06). Canon T-06 coverage for
wrong binding, expiry, revoke, and supersede.
FLEX-WP-0017 T03 is unblocked on this object; T05 remains blocked only on
consumption ordering.
Assistant: grok
Assistant-Session: 01a04ceb-2057-7e20-b0f9-c282964d5dd9
2026-08-29 12:52:49 +02:00
|
|
|
status_headers: list[tuple[str, list]] = []
|
|
|
|
|
|
|
|
|
|
def start_response(status: str, headers: list[tuple[str, str]]) -> None:
|
|
|
|
|
status_headers.append((status, headers))
|
|
|
|
|
|
|
|
|
|
result = b"".join(app(environ, start_response))
|
|
|
|
|
code = int(status_headers[0][0].split()[0])
|
|
|
|
|
return code, json.loads(result.decode("utf-8"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _Bytes:
|
|
|
|
|
def __init__(self, data: bytes) -> None:
|
|
|
|
|
self._data = data
|
|
|
|
|
|
|
|
|
|
def read(self, n: int = -1) -> bytes:
|
|
|
|
|
if n < 0:
|
|
|
|
|
out, self._data = self._data, b""
|
|
|
|
|
return out
|
|
|
|
|
out, self._data = self._data[:n], self._data[n:]
|
|
|
|
|
return out
|