APPROVAL-WP-0007-T01 done: required_assurance floor; executor ingress; alignment answer

- Optional required_assurance {"level": aal1|aal2|aal3} at issue, schema v7.
  Enforced at add_entry (403), claim (assurance_unsatisfied) and consume (409)
  from the verified token level only; inherited on supersession; legacy rows
  NULL. Served only when declared so undeclared claims keep exact bytes.
- deploy/networkpolicies.yaml: pod-scoped ingress for sso +
  app.kubernetes.io/name=bounded-executor on 8080 (hub 39a3bffa). Not applied.
- docs/decision-services-alignment-answer.md: owner answer to the NetKingdom
  decision services review (hub 81cafbf3): roles accepted, T02 split proposed,
  presentation_ref at create declined with an entry-level counter-proposal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
This commit is contained in:
tegwick 2026-10-31 23:07:05 +01:00
parent e5f31477d4
commit e9e9f93317
14 changed files with 540 additions and 13 deletions

View file

@ -133,6 +133,7 @@ class App:
pdp_digest=data.get("pdp_digest"),
pdp_path=bool(data.get("pdp_path", False)),
human_control=data.get("human_control", False),
required_assurance=data.get("required_assurance"),
approval_id=data.get("id"),
)
return 201, obj.as_dict()

View file

@ -43,7 +43,47 @@ EVENT_CLASSES = ("issuance", "use", "supersession", "revocation", "heartbeat")
# class and says nothing about the quiet one — here that is `revocation`, which
# is the class whose silence actually matters.
HEARTBEAT_CLASSES = ("issuance", "use", "supersession", "revocation")
LATEST_SCHEMA_VERSION = 6
LATEST_SCHEMA_VERSION = 7
# APPROVAL-WP-0007-T01: authenticator assurance levels, weakest first. A level
# outside this list is unknown, and an unknown level never meets a floor.
ASSURANCE_LEVELS = ("aal1", "aal2", "aal3")
def assurance_rank(level: Any) -> int | None:
if isinstance(level, str) and level in ASSURANCE_LEVELS:
return ASSURANCE_LEVELS.index(level)
return None
def require_assurance_floor(value: Any) -> str | None:
"""Validate an issue-time `required_assurance` and return its level.
The shape mirrors the token's `assurance` claim: `{"level": "aal2"}`.
Absent or null means no floor (the pre-v7 behavior).
"""
if value is None:
return None
if not isinstance(value, dict) or set(value) != {"level"}:
raise Unprocessable('required_assurance must be {"level": "aal1|aal2|aal3"}')
if assurance_rank(value["level"]) is None:
raise Unprocessable("required_assurance.level must be one of aal1, aal2, aal3")
return value["level"]
def entry_meets_floor(assurance: str | None, floor: str | None) -> bool:
"""Whether a stored entry's verified assurance meets `floor`.
`assurance` is the JSON the HTTP boundary stored from the verified token.
A missing, unparsable or unknown level fails a declared floor.
"""
if floor is None:
return True
try:
level = json.loads(assurance).get("level") if assurance else None
except (TypeError, ValueError, AttributeError):
level = None
rank = assurance_rank(level)
return rank is not None and rank >= assurance_rank(floor)
SCHEMA = """
CREATE TABLE IF NOT EXISTS approvals (
@ -66,6 +106,7 @@ CREATE TABLE IF NOT EXISTS approvals (
consumed_at TEXT,
pdp_path INTEGER NOT NULL DEFAULT 0,
human_control INTEGER NOT NULL DEFAULT 0 CHECK (human_control IN (0, 1)),
required_assurance TEXT CHECK (required_assurance IS NULL OR required_assurance IN ('aal1', 'aal2', 'aal3')),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
@ -123,6 +164,7 @@ class Approval:
pdp_digest: str | None
pdp_path: bool
human_control: bool
required_assurance: str | None
actor: str
principal: str
action: str
@ -157,6 +199,10 @@ class Approval:
"created_at": self.created_at,
"updated_at": self.updated_at,
}
if self.required_assurance:
# APPROVAL-WP-0007-T01. Stated only when declared, so undeclared
# objects keep their exact pre-v7 shape for strict readers.
result["binding"]["required_assurance"] = {"level": self.required_assurance}
if self.consumed_digest:
result["consumption"] = {
"request_digest": self.consumed_digest,
@ -251,6 +297,14 @@ class Engine:
"ALTER TABLE approvals ADD COLUMN human_control "
"INTEGER NOT NULL DEFAULT 0 CHECK (human_control IN (0, 1))"
)
if "required_assurance" not in approval_columns:
# v7, APPROVAL-WP-0007-T01. Legacy rows stay NULL: no floor was
# declared at their issue, and inferring one from the entries'
# recorded levels would manufacture a declaration nobody made.
conn.execute(
"ALTER TABLE approvals ADD COLUMN required_assurance TEXT CHECK "
"(required_assurance IS NULL OR required_assurance IN ('aal1', 'aal2', 'aal3'))"
)
entry_columns = {
row["name"] for row in conn.execute("PRAGMA table_info(entries)").fetchall()
}
@ -372,6 +426,7 @@ class Engine:
pdp_digest: str | None = None,
pdp_path: bool = False,
human_control: bool = False,
required_assurance: dict[str, Any] | None = None,
approval_id: str | None = None,
) -> Approval:
if required_count < 1:
@ -380,6 +435,7 @@ class Engine:
raise Unprocessable("pdp_path must be a boolean")
if not isinstance(human_control, bool):
raise Unprocessable("human_control must be a boolean")
floor = require_assurance_floor(required_assurance)
canon = canonical_binding(binding)
digest = binding_digest(canon)
pdp = require_digest(pdp_digest)
@ -408,8 +464,8 @@ class Engine:
id, status, binding_json, binding_digest, pdp_digest,
actor, principal, action, purpose, target_json,
not_before, expires_at, required_count, superseded_by,
pdp_path, human_control, created_at, updated_at
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
pdp_path, human_control, required_assurance, created_at, updated_at
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(
aid,
"requested",
@ -427,6 +483,7 @@ class Engine:
None,
1 if pdp_path else 0,
1 if human_control else 0,
floor,
now,
now,
),
@ -485,6 +542,7 @@ class Engine:
pdp_digest=row["pdp_digest"],
pdp_path=bool(row["pdp_path"]),
human_control=bool(row["human_control"]),
required_assurance=row["required_assurance"],
actor=row["actor"],
principal=row["principal"],
action=row["action"],
@ -550,6 +608,12 @@ class Engine:
# This is the bind boundary. A service may draft a requested
# approval, but cannot contribute evidence for human judgment.
raise Forbidden("human_control requires a verified human approver")
if not entry_meets_floor(assurance, row["required_assurance"]):
# The level comes only from the verified token (api.py); a
# weaker or missing level cannot count toward this object.
raise Forbidden(
f"required_assurance {row['required_assurance']} not met by the approver's verified assurance"
)
try:
conn.execute(
"INSERT INTO entries "
@ -654,10 +718,13 @@ class Engine:
conn.rollback()
raise Conflict("supersession lost the compare-and-swap")
existing = conn.execute(
"SELECT id, human_control FROM approvals WHERE id=?", (successor_id,)
"SELECT id, human_control, required_assurance FROM approvals WHERE id=?",
(successor_id,),
).fetchone()
if existing is not None and existing["human_control"] != row["human_control"]:
raise Conflict("successor must inherit the human_control declaration")
if existing is not None and existing["required_assurance"] != row["required_assurance"]:
raise Conflict("successor must inherit the required_assurance declaration")
created_successor = False
if existing is None:
conn.execute(
@ -665,8 +732,8 @@ class Engine:
id, status, binding_json, binding_digest, pdp_digest,
actor, principal, action, purpose, target_json,
not_before, expires_at, required_count, superseded_by,
pdp_path, human_control, created_at, updated_at
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
pdp_path, human_control, required_assurance, created_at, updated_at
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(
successor_id,
"requested",
@ -684,6 +751,7 @@ class Engine:
None,
row["pdp_path"],
row["human_control"],
row["required_assurance"],
now,
now,
),
@ -808,6 +876,14 @@ class Engine:
).fetchone()
if counts["total"] < row["required_count"] or counts["humans"] != counts["total"]:
raise Conflict("cannot consume without the required human evidence")
if row["required_assurance"]:
levels = conn.execute(
"SELECT assurance FROM entries WHERE approval_id=?", (approval_id,)
).fetchall()
if len(levels) < row["required_count"] or not all(
entry_meets_floor(e["assurance"], row["required_assurance"]) for e in levels
):
raise Conflict("cannot consume without entries meeting required_assurance")
cur = conn.execute(
"UPDATE approvals SET status='consumed', consumed_digest=?, "
"consumed_decision_id=?, consumed_at=?, updated_at=? "
@ -928,10 +1004,13 @@ class Engine:
if approval_id:
details["approval_id"] = approval_id
row = conn.execute(
"SELECT human_control FROM approvals WHERE id=?", (approval_id,)
"SELECT human_control, required_assurance FROM approvals WHERE id=?",
(approval_id,),
).fetchone()
if row is not None:
details["human_control"] = bool(row["human_control"])
if row["required_assurance"]:
details["required_assurance"] = {"level": row["required_assurance"]}
payload = {
"schema_version": AUDIT_SCHEMA,
"event_id": event_id,
@ -977,6 +1056,11 @@ class Engine:
# happens to be present.
binding["pdp_path"] = obj.pdp_path
binding["human_control"] = obj.human_control
# APPROVAL-WP-0007-T01: stated only when a floor was declared. The
# access fact reader rejects unknown members, so undeclared claims keep
# their exact pre-v7 bytes; absence means no floor was declared.
if obj.required_assurance:
binding["required_assurance"] = {"level": obj.required_assurance}
return {
"schema_version": CLAIM_SCHEMA,
"kind": "approval-claim",
@ -1013,6 +1097,10 @@ class Engine:
return "approved", False, False, "not_yet_valid"
if obj.human_control and any(e["principal_type"] != "human" for e in obj.entries):
return "approved", False, False, "human_control_unsatisfied"
if obj.required_assurance and not all(
entry_meets_floor(e["assurance"], obj.required_assurance) for e in obj.entries
):
return "approved", False, False, "assurance_unsatisfied"
if len({e["subject_id"] for e in obj.entries}) < obj.required_count:
return "approved", False, False, "insufficient_approvers"
return "valid", True, False, "ok"