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"),
|
|
|
|
|
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
|