INFD-WP-0010: present_existing intake profile (S1) and owner answer to decision services alignment
A registered submitter with a present_existing profile may pose a memo that pins an approval it created itself in approval-engine. Intake verifies the object read-only (requested, no entries, human_control, actor == profile actor, digest == memo, binding == intent, required_count 1, unexpired, one memo per approval) and never creates, enters or consumes. The recorded requester reaches review and the access-engine context. Existing submitters are unchanged. Answer doc accepts S1 (profile, not route; packet carries the admission), S4 in principle (waits on gate-house classes and policy) and S3 (exists behind INFD_HUB_WRITEBACK). Workplan INFD-WP-0010 proposed. 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:
parent
d5cb527d4c
commit
0e66a33389
8 changed files with 731 additions and 24 deletions
|
|
@ -20,7 +20,10 @@ class RequesterError(Exception):
|
|||
|
||||
|
||||
class RequesterTokenProvider:
|
||||
def __init__(self, issuer, secret_file, *, transport=None, clock=time.time):
|
||||
def __init__(self, issuer, secret_file, *, transport=None, clock=time.time, scope="approval:create"):
|
||||
if scope not in ("approval:create", "approval:read"):
|
||||
raise ValueError("requester scope is create or read only")
|
||||
self.scope = scope
|
||||
self.issuer = https_origin(issuer)
|
||||
self.secret_file = Path(secret_file)
|
||||
if not self.secret_file.is_absolute():
|
||||
|
|
@ -44,7 +47,7 @@ class RequesterTokenProvider:
|
|||
headers={"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Authorization": "Basic " + credentials},
|
||||
body=urlencode({"grant_type": "client_credentials",
|
||||
"scope": "approval:create"}).encode())
|
||||
"scope": self.scope}).encode())
|
||||
if (status != 200 or not isinstance(result.get("token_type"), str)
|
||||
or result["token_type"].lower() != "bearer"
|
||||
or "refresh_token" in result):
|
||||
|
|
@ -71,7 +74,7 @@ class RequesterTokenProvider:
|
|||
or claims.get("tenant") != "tenant:platform"
|
||||
or claims.get("principal_type") != "service"
|
||||
or claims.get("roles") != [CLIENT_ID]
|
||||
or claims.get("scope") != "approval:create"
|
||||
or claims.get("scope") != self.scope
|
||||
or type(claims["iat"]) is not int or type(claims["exp"]) is not int
|
||||
or claims["iat"] > now or claims["exp"] <= now
|
||||
or not 0 < claims["exp"] - claims["iat"] <= 900):
|
||||
|
|
@ -88,9 +91,13 @@ def compose_intake(store, token_review_call, *, issuer, secret_file,
|
|||
This factory grants no submitter and starts no socket. The separate private
|
||||
listener and deployment remain reviewed owner configuration.
|
||||
"""
|
||||
from .intake_approval import IntakeApprovalClient
|
||||
from .intake_approval import IntakeApprovalClient, IntakeApprovalReader
|
||||
from .intake_service import IntakeService, TokenReview
|
||||
provider = RequesterTokenProvider(issuer, secret_file, transport=transport)
|
||||
creator = IntakeApprovalClient(approval_origin, provider, allow_internal_http=True)
|
||||
# present_existing only; needs approval:read on the requester client (KeyCape registration).
|
||||
# Until granted, that exchange is refused and only present_existing submitters fail closed.
|
||||
read_provider = RequesterTokenProvider(issuer, secret_file, transport=transport, scope="approval:read")
|
||||
reader = IntakeApprovalReader(approval_origin, read_provider, allow_internal_http=True)
|
||||
return IntakeService(store, TokenReview(token_review_call), creator,
|
||||
submitters_path=submitters_path, groups_path=groups_path)
|
||||
submitters_path=submitters_path, groups_path=groups_path, reader=reader)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue