Build the T08 domain core with the engine behind a seam
approval-engine APPROVAL-WP-0002-T01 is still progress and its namespace has no
pods, so the live end-to-end proof cannot run. Built everything that does not
depend on it, with the engine behind a Protocol plus a fake carrying its real
refusal semantics, so its arrival is a wiring change rather than a build.
- memo.py: the Decision Memo, versions, binding document. Principal, Scope,
Awareness and Hat are dataclasses rather than dicts because the canonicalizer
requires a shape and a missing key should fail at construction rather than
deep inside hashing — which is exactly how it failed twice while building
this. Field names follow the governed canonicalizer (item_id, severity,
locator): the published vectors are the contract, so the object was aligned to
them rather than the reverse.
- presentation.py: the sole writer of view_hash. One writer, one canonicalizer,
one place to audit. Acknowledgment is an explicit method call and nothing
infers it from scroll, dwell or focus.
- disposition.py: verbs and guards G_NOAGENT, G_STEP, G_PRES, G_ACK, G_REASONS,
G_SEALED. accept is ABSENT from weak steps rather than present-and-disabled,
because a greyed-out accept still teaches the wrong model. Only accept reaches
the engine; a memo return is not represented there at all.
- provenance.py: claim routes per A-16. assert_human_control_dischargeable
refuses a registration-supplied human, so PR-11's limitation fires at the
point of use instead of sitting in a document.
- evidence.py: local outbox, commitment-only records carrying the
GH-DEC-2026-014 §4 existence assertion, per-class reconciliation counts, and a
custody-locator guard that rejects credentialed URLs (PR-12).
- approval_client.py: 409 duplicate_approver is success, 409 conflict terminal,
503 fail-closed, approval:consume refused before a token is requested.
87 tests pass, including every negative case in the Use Case Catalog and that a
fail-closed outcome is recorded as a stance application with no verb field —
never as a decline, because the human did not make one.
T08 stays progress: the live proof is the remainder.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3W1dQG7GFFM9d94jFx7iR
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1565372@bnt-lap001
Assistant-Session: 16bb2f25-b34c-49ef-8e94-5fec3567a568
2026-09-10 20:38:20 +02:00
|
|
|
"""Claim provenance — A-16 applied to claims this surface consumes.
|
|
|
|
|
|
2026-09-10 22:05:17 +02:00
|
|
|
`key-cape` emits `tenant_source` alongside `tenant`. Its authorization-code
|
|
|
|
|
handler derives `principal_type=human` from the authenticated user flow; its
|
|
|
|
|
client-credentials handler emits `service`. These are distinct provenance
|
|
|
|
|
contracts, not two properties inferred from a client registration.
|
Build the T08 domain core with the engine behind a seam
approval-engine APPROVAL-WP-0002-T01 is still progress and its namespace has no
pods, so the live end-to-end proof cannot run. Built everything that does not
depend on it, with the engine behind a Protocol plus a fake carrying its real
refusal semantics, so its arrival is a wiring change rather than a build.
- memo.py: the Decision Memo, versions, binding document. Principal, Scope,
Awareness and Hat are dataclasses rather than dicts because the canonicalizer
requires a shape and a missing key should fail at construction rather than
deep inside hashing — which is exactly how it failed twice while building
this. Field names follow the governed canonicalizer (item_id, severity,
locator): the published vectors are the contract, so the object was aligned to
them rather than the reverse.
- presentation.py: the sole writer of view_hash. One writer, one canonicalizer,
one place to audit. Acknowledgment is an explicit method call and nothing
infers it from scroll, dwell or focus.
- disposition.py: verbs and guards G_NOAGENT, G_STEP, G_PRES, G_ACK, G_REASONS,
G_SEALED. accept is ABSENT from weak steps rather than present-and-disabled,
because a greyed-out accept still teaches the wrong model. Only accept reaches
the engine; a memo return is not represented there at all.
- provenance.py: claim routes per A-16. assert_human_control_dischargeable
refuses a registration-supplied human, so PR-11's limitation fires at the
point of use instead of sitting in a document.
- evidence.py: local outbox, commitment-only records carrying the
GH-DEC-2026-014 §4 existence assertion, per-class reconciliation counts, and a
custody-locator guard that rejects credentialed URLs (PR-12).
- approval_client.py: 409 duplicate_approver is success, 409 conflict terminal,
503 fail-closed, approval:consume refused before a token is requested.
87 tests pass, including every negative case in the Use Case Catalog and that a
fail-closed outcome is recorded as a stance application with no verb field —
never as a decline, because the human did not make one.
T08 stays progress: the live proof is the remainder.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3W1dQG7GFFM9d94jFx7iR
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1565372@bnt-lap001
Assistant-Session: 16bb2f25-b34c-49ef-8e94-5fec3567a568
2026-09-10 20:38:20 +02:00
|
|
|
|
2026-09-10 22:05:17 +02:00
|
|
|
`GH-DEC-2026-013` §5 requires the claim to carry its provenance. This surface
|
|
|
|
|
records which route the value arrived by rather than storing an
|
Build the T08 domain core with the engine behind a seam
approval-engine APPROVAL-WP-0002-T01 is still progress and its namespace has no
pods, so the live end-to-end proof cannot run. Built everything that does not
depend on it, with the engine behind a Protocol plus a fake carrying its real
refusal semantics, so its arrival is a wiring change rather than a build.
- memo.py: the Decision Memo, versions, binding document. Principal, Scope,
Awareness and Hat are dataclasses rather than dicts because the canonicalizer
requires a shape and a missing key should fail at construction rather than
deep inside hashing — which is exactly how it failed twice while building
this. Field names follow the governed canonicalizer (item_id, severity,
locator): the published vectors are the contract, so the object was aligned to
them rather than the reverse.
- presentation.py: the sole writer of view_hash. One writer, one canonicalizer,
one place to audit. Acknowledgment is an explicit method call and nothing
infers it from scroll, dwell or focus.
- disposition.py: verbs and guards G_NOAGENT, G_STEP, G_PRES, G_ACK, G_REASONS,
G_SEALED. accept is ABSENT from weak steps rather than present-and-disabled,
because a greyed-out accept still teaches the wrong model. Only accept reaches
the engine; a memo return is not represented there at all.
- provenance.py: claim routes per A-16. assert_human_control_dischargeable
refuses a registration-supplied human, so PR-11's limitation fires at the
point of use instead of sitting in a document.
- evidence.py: local outbox, commitment-only records carrying the
GH-DEC-2026-014 §4 existence assertion, per-class reconciliation counts, and a
custody-locator guard that rejects credentialed URLs (PR-12).
- approval_client.py: 409 duplicate_approver is success, 409 conflict terminal,
503 fail-closed, approval:consume refused before a token is requested.
87 tests pass, including every negative case in the Use Case Catalog and that a
fail-closed outcome is recorded as a stance application with no verb field —
never as a decline, because the human did not make one.
T08 stays progress: the live proof is the remainder.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3W1dQG7GFFM9d94jFx7iR
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1565372@bnt-lap001
Assistant-Session: 16bb2f25-b34c-49ef-8e94-5fec3567a568
2026-09-10 20:38:20 +02:00
|
|
|
undifferentiated string (PR-09), and never discharges a human-in-the-loop
|
|
|
|
|
control on a registration-supplied assertion of humanity (PR-11,
|
|
|
|
|
`GH-DEC-2026-016` §5).
|
|
|
|
|
|
|
|
|
|
A-16's rider applies to this module: where the route marker is written by the
|
|
|
|
|
party whose conduct the route describes, it constrains a defect but not an
|
|
|
|
|
adversary. These markers are written by us about claims we received, which is
|
|
|
|
|
the semi-independent case — we gain nothing by mislabelling them.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
from enum import Enum
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Route(str, Enum):
|
|
|
|
|
"""How a claim value reached us. Never collapsed, never defaulted."""
|
|
|
|
|
|
|
|
|
|
#: Asserted by the directory about the person. Strong.
|
|
|
|
|
DIRECTORY = "directory-asserted"
|
|
|
|
|
#: Supplied by the client registration. The GH-DEC-2026-013 bounded gap.
|
|
|
|
|
REGISTRATION = "registration-supplied"
|
|
|
|
|
#: Derived from the authentication event itself.
|
|
|
|
|
AUTHENTICATION = "authentication-derived"
|
|
|
|
|
#: Present, but its route is not determinable. Never treated as any of the above.
|
|
|
|
|
INDETERMINATE = "indeterminate"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class Claim:
|
|
|
|
|
"""A claim value with the route it arrived by.
|
|
|
|
|
|
|
|
|
|
There is no constructor that takes a value without a route. A claim whose
|
|
|
|
|
provenance is unknown is ``INDETERMINATE``, explicitly — not defaulted to
|
|
|
|
|
the strongest reading.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
value: str
|
|
|
|
|
route: Route
|
|
|
|
|
|
|
|
|
|
def __post_init__(self) -> None:
|
|
|
|
|
if not self.value:
|
|
|
|
|
raise ValueError("claim value must be non-empty")
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def is_verified_about_the_person(self) -> bool:
|
|
|
|
|
"""True only where the claim was asserted about the *person*.
|
|
|
|
|
|
|
|
|
|
A registration-supplied claim says something about the client the person
|
|
|
|
|
came through, not about the person. `GH-DEC-2026-016` §5: refusing a
|
|
|
|
|
service principal while accepting an unverified assertion of humanity
|
|
|
|
|
moves the defect rather than closing it.
|
|
|
|
|
"""
|
|
|
|
|
return self.route in (Route.DIRECTORY, Route.AUTHENTICATION)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class HumanControlNotDischargeable(Exception):
|
|
|
|
|
"""Raised when a human-in-the-loop control cannot be discharged.
|
|
|
|
|
|
|
|
|
|
Not an authorization decision. This surface is not saying the actor may not
|
|
|
|
|
act; it is saying *this claim cannot carry that weight*.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def assert_human_control_dischargeable(principal_type: Claim) -> None:
|
|
|
|
|
"""Guard for `GH-DEC-2026-016` §5 / PR-11.
|
|
|
|
|
|
2026-09-10 22:05:17 +02:00
|
|
|
The KeyCape login adapter supplies AUTHENTICATION only after verifying the
|
|
|
|
|
issuer's code-flow tokens. A bare or registration-supplied assertion still
|
|
|
|
|
cannot discharge the control.
|
Build the T08 domain core with the engine behind a seam
approval-engine APPROVAL-WP-0002-T01 is still progress and its namespace has no
pods, so the live end-to-end proof cannot run. Built everything that does not
depend on it, with the engine behind a Protocol plus a fake carrying its real
refusal semantics, so its arrival is a wiring change rather than a build.
- memo.py: the Decision Memo, versions, binding document. Principal, Scope,
Awareness and Hat are dataclasses rather than dicts because the canonicalizer
requires a shape and a missing key should fail at construction rather than
deep inside hashing — which is exactly how it failed twice while building
this. Field names follow the governed canonicalizer (item_id, severity,
locator): the published vectors are the contract, so the object was aligned to
them rather than the reverse.
- presentation.py: the sole writer of view_hash. One writer, one canonicalizer,
one place to audit. Acknowledgment is an explicit method call and nothing
infers it from scroll, dwell or focus.
- disposition.py: verbs and guards G_NOAGENT, G_STEP, G_PRES, G_ACK, G_REASONS,
G_SEALED. accept is ABSENT from weak steps rather than present-and-disabled,
because a greyed-out accept still teaches the wrong model. Only accept reaches
the engine; a memo return is not represented there at all.
- provenance.py: claim routes per A-16. assert_human_control_dischargeable
refuses a registration-supplied human, so PR-11's limitation fires at the
point of use instead of sitting in a document.
- evidence.py: local outbox, commitment-only records carrying the
GH-DEC-2026-014 §4 existence assertion, per-class reconciliation counts, and a
custody-locator guard that rejects credentialed URLs (PR-12).
- approval_client.py: 409 duplicate_approver is success, 409 conflict terminal,
503 fail-closed, approval:consume refused before a token is requested.
87 tests pass, including every negative case in the Use Case Catalog and that a
fail-closed outcome is recorded as a stance application with no verb field —
never as a decline, because the human did not make one.
T08 stays progress: the live proof is the remainder.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3W1dQG7GFFM9d94jFx7iR
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1565372@bnt-lap001
Assistant-Session: 16bb2f25-b34c-49ef-8e94-5fec3567a568
2026-09-10 20:38:20 +02:00
|
|
|
"""
|
|
|
|
|
if principal_type.value != "human":
|
|
|
|
|
raise HumanControlNotDischargeable(
|
|
|
|
|
f"principal_type is {principal_type.value!r}, not 'human'"
|
|
|
|
|
)
|
|
|
|
|
if not principal_type.is_verified_about_the_person:
|
|
|
|
|
raise HumanControlNotDischargeable(
|
|
|
|
|
"principal_type 'human' arrived by "
|
|
|
|
|
f"{principal_type.route.value}; a human-in-the-loop control must not "
|
|
|
|
|
"be discharged on a claim that describes the client rather than the "
|
|
|
|
|
"person (GH-DEC-2026-016 §5, A-16)"
|
|
|
|
|
)
|