Add recipient-scoped review discovery and settle initial lifecycle contracts
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
This commit is contained in:
parent
dfcef879d0
commit
da2c5a720f
9 changed files with 224 additions and 10 deletions
|
|
@ -163,8 +163,9 @@ document supersedes their exclusion of agent collaboration and human authoring
|
|||
for the new increment, while preserving human-only dispositions and the evidence
|
||||
and engine boundaries. L0 and L2 compatibility remain required.
|
||||
|
||||
Open implementation contracts: workload identity beyond cluster service accounts;
|
||||
publication audience policy and any later anonymous publication; source connector
|
||||
set and retention; non-approval L2 binding integration. T02 must make these
|
||||
explicit before implementation depends on them. None authorizes a production
|
||||
rollout or changes external owners' contracts by declaration.
|
||||
Initial implementation choices are explicit in `LifecycleAdmissionBoundary.md`:
|
||||
only the admitted cluster workload, authenticated publication, caller-supplied
|
||||
research provenance, append-only retention, organizational human approval, and
|
||||
no optional Hub outcome write-back. External identities, anonymous publication,
|
||||
source connectors/retention controls and non-approval binding are expansion work
|
||||
under INFD-WP-0008, not hidden prerequisites or enabled production capabilities.
|
||||
|
|
|
|||
33
docs/specs/LifecycleAdmissionBoundary.md
Normal file
33
docs/specs/LifecycleAdmissionBoundary.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Lifecycle admission and evidence boundary
|
||||
|
||||
2026-10-05; INFD-WP-0007-T02. This closes the implementation choices for the
|
||||
initial production audience. Expansion is tracked separately; these limits are
|
||||
observable behavior, not an implied grant to extend another owner's interface.
|
||||
|
||||
| Concern | Implemented contract |
|
||||
| --- | --- |
|
||||
| Human identity | KeyCape-authenticated net-kingdom-admins, tenant:platform; twelve-hour MFA authoring/list window, fifteen-minute review/read/act window |
|
||||
| Agent identity | Kubernetes TokenReview for the registered State Hub service account with a separate lifecycle audience; unknown and external bearer identities refused |
|
||||
| Publication | Explicit subject/group audience on an exact revision, authorized by Access Engine; not anonymous or indexed publicly; successor revisions require republication |
|
||||
| Research | Caller retrieves evidence externally and submits reference, retrieval time, digest and summary; the service does not fetch URLs or attest their truth |
|
||||
| Evaluation | Attributed assessment, uncertainty and source-event references constrained to the same record/revision/tenant |
|
||||
| Retention | Append-only revisions, sources, comments, evaluations and binding evidence in the durable store; no automatic purge, mutable source replacement or source connector credentials |
|
||||
| Documents | Digest-verified bytes supplied through the bounded API; the browser adds short text documents with optional required acknowledgments as new revisions |
|
||||
| Review | Organizational English approval profile using the existing shared schema and native Approval Engine; recipient from the configured review group, initially platform-root |
|
||||
| Discovery | Lifecycle workspace uses per-record lifecycle policy. Review overview uses its own list decision; attribute discovery requires the immutable named recipient plus verified native requester, human-control flag and digest |
|
||||
| Human acceptance | Existing protected review surface, fresh policy/read/act decisions, exact revision/presentation and required acknowledgments; no agent binding route |
|
||||
| Non-approval profiles | Discussion drafts may be authored and refined, but cannot create an approval review. L0/L2 core schema compatibility does not enable new production disposition profiles |
|
||||
| Hub write-back | Notification/dispatcher delivery is admitted; optional automatic resolution write-back remains disabled and cannot imply permission to execute |
|
||||
|
||||
Identity verification stays in KeyCape/TokenReview; authorization stays in Access
|
||||
Engine. The headless application service is shared by browser and private API.
|
||||
Client-supplied research descriptions and evaluation conclusions remain claims
|
||||
with attribution, not machine-verified facts. Packet bytes are retained so an
|
||||
external URL changing cannot alter a past presentation.
|
||||
|
||||
No finite retention duration, deletion mechanism, new connector, external agent
|
||||
identity or non-approval signing semantics is invented here. Those product
|
||||
extensions require their own owner contracts and delivery evidence under
|
||||
INFD-WP-0008. The existing source/evaluation and organizational approval workflows
|
||||
provide all nine initial lifecycle operations without per-decision infrastructure
|
||||
changes.
|
||||
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
2026-10-04; `INFD-WP-0007-T02/T03`. This is the internal persistence slice of
|
||||
`DecisionLifecycleService.md`. The protected application interface built on this
|
||||
slice is documented in `LifecycleServiceContract.md`; HTTP exposure is pending.
|
||||
slice is documented in `LifecycleServiceContract.md`; the deployed private HTTP
|
||||
adapter is specified in `LifecycleHTTPContract.md`.
|
||||
|
||||
## Implemented boundary
|
||||
|
||||
|
|
@ -16,8 +17,8 @@ Each call carries `Attribution(tenant, Actor(subject, kind))` from its trusted
|
|||
caller. This is audit attribution, not an authentication assertion. Like `Store`,
|
||||
the repository is an internal persistence interface: the application/API
|
||||
layer must authenticate callers and enforce current access-engine authorization
|
||||
before invoking it. Supplying a tenant string is not an access grant. No route
|
||||
or runtime configuration exposes these operations in this increment.
|
||||
before invoking it. Supplying a tenant string is not an access grant. The deployed application adapters authenticate and authorize these operations;
|
||||
this internal persistence interface is never exposed directly.
|
||||
|
||||
## Commands and reads
|
||||
|
||||
|
|
|
|||
|
|
@ -78,3 +78,12 @@ The lifecycle workspace discovers authorized records and exposes their outcomes.
|
|||
A later change to legacy overview discovery needs an Access Engine contract,
|
||||
not a consumer-side policy bypass. Broader binding profiles, external identities,
|
||||
source connectors and optional Hub write-back remain T02/T05 work.
|
||||
|
||||
## Named-recipient review discovery — source continuation
|
||||
|
||||
The adapter now carries retained intake facts and the immutable memo recipient
|
||||
for list decisions. It verifies native approval requester, human control and
|
||||
binding digest before the PDP call. Access Engine compact-sitting v13 checks the
|
||||
recipient and existing class/submitter/group admission with the twelve-hour list
|
||||
window; read and act remain separate fifteen-minute decisions. Missing evidence
|
||||
hides that row. This continuation requires the matching owner pin at release.
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ def build_request(session: HumanSession, memo, action, policy_version, *, intake
|
|||
extra = {} if intake_context is None else dict(intake_context)
|
||||
if extra and set(extra) != {"review_group", "memo_class", "intake_submitter", "approval_requester", "approval_human_control"}:
|
||||
raise ValueError("exact intake policy context required")
|
||||
if extra and action == "list":
|
||||
extra["review_recipient"] = memo.binding.principal.id
|
||||
return {"id": str(uuid.uuid4()), "tenant": "tenant:platform",
|
||||
"subject": {"id": session.subject, "type": "human", "tenant": session.tenant.value,
|
||||
"attributes": {"tenant_source": session.tenant.route.value,
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class ReviewController:
|
|||
|
||||
def _authorize(self, session, memo, action):
|
||||
self._memo(session, memo)
|
||||
context = self.store.intake_context(memo) if self.intake_attributes and action != 'list' else None
|
||||
context = self.store.intake_context(memo) if self.intake_attributes else None
|
||||
if context is not None:
|
||||
current = self._approval(session, memo)
|
||||
if (current['binding'].get('actor') != context['approval_requester']
|
||||
|
|
@ -167,6 +167,8 @@ class ReviewController:
|
|||
def _overview_row(self, session, memo):
|
||||
try:
|
||||
_, observation = self._authorize(session, memo, "list")
|
||||
except (ApprovalEngineError, ValueError, Conflict):
|
||||
return OverviewRow(memo.id, "unavailable", reason="review_evidence_unavailable")
|
||||
except ReviewError as exc:
|
||||
if exc.code == "session_expired":
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ import pytest
|
|||
from informed_decision.disposition import Actor, ActorKind, Verb, DispositionRefused
|
||||
from informed_decision.lifecycle import Attribution, LifecycleRepository
|
||||
from informed_decision.lifecycle_service import Caller, LifecycleService, LifecycleError
|
||||
from informed_decision.policy import build_request
|
||||
from informed_decision.provenance import Claim, Route
|
||||
from informed_decision.records import memo_from
|
||||
from informed_decision.store import Conflict, Store
|
||||
from test_intake import envelope
|
||||
from test_intake_service import setup, request
|
||||
from test_policy_client import policy
|
||||
from test_browser_auth import signing_key
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -376,3 +378,79 @@ def test_retained_handoff_api_is_revision_scoped_and_reauthorizes(handoff):
|
|||
assert request(intake, receipt['envelope'])[0] == 409
|
||||
app.policy.transport.change = lambda data: data.update(effect='deny')
|
||||
assert call(api, path, query='version=1')[:2] == (403, {'error': 'policy_denied'})
|
||||
|
||||
|
||||
def test_overview_uses_retained_intake_context_and_fails_closed_per_row(handoff):
|
||||
from informed_decision.review import ReviewController
|
||||
from informed_decision.approval_client import ApprovalEngineError
|
||||
from test_policy_client import human
|
||||
app, actor, memo, options, intake, calls = handoff
|
||||
receipt = prepare(handoff)
|
||||
assert request(intake, receipt['envelope'])[0] == 201
|
||||
store = app.repository.store
|
||||
stored = store.memo(receipt['review_memo_id'])
|
||||
session = replace(human(), subject=stored.binding.principal.id)
|
||||
class Engine:
|
||||
failure = False
|
||||
def get_approval(self, approval_id):
|
||||
if self.failure:
|
||||
raise ApprovalEngineError(503, 'unavailable')
|
||||
return {'status': 'requested', 'binding': {'digest': stored.approval_binding_digest,
|
||||
'actor': 'informed-decision', 'human_control': True}}
|
||||
engine = Engine()
|
||||
controller = ReviewController(store, policy(), lambda session: engine, intake_attributes=True)
|
||||
[row] = controller.overview(session)
|
||||
assert row.question == memo.question
|
||||
sent = controller.policy.transport.calls[-1][3]
|
||||
assert sent['action'] == 'list'
|
||||
assert sent['context']['review_recipient'] == session.subject
|
||||
assert sent['context']['intake_submitter'] == 'state-hub'
|
||||
assert 'review_recipient' not in build_request(
|
||||
session, stored, 'read', 'v1', intake_context=store.intake_context(stored))['context']
|
||||
assert store.evidence() == []
|
||||
engine.failure = True
|
||||
[row] = controller.overview(session)
|
||||
assert row.group == 'unavailable' and row.question is None
|
||||
assert len(controller.policy.transport.calls) == 1
|
||||
|
||||
|
||||
def test_real_discovery_policy_lists_only_recipient_and_keeps_fresh_read(handoff, tmp_path, signing_key):
|
||||
import os
|
||||
from pathlib import Path
|
||||
from informed_decision.policy import build_request
|
||||
from informed_decision.review import ReviewController, ReviewError
|
||||
from review_components import flex_component
|
||||
from test_policy_client import human
|
||||
binary, source = os.environ.get('INFD_FLEX_AUTH_BINARY'), os.environ.get('INFD_FLEX_AUTH_SOURCE')
|
||||
if not binary or not source:
|
||||
pytest.skip('actual owner discovery policy required')
|
||||
fixture = Path(source) / 'examples/informed-decision-lifecycle-review'
|
||||
if json.loads((fixture / 'pin.json').read_text())['version'] != 'v13':
|
||||
pytest.skip('owner discovery v13 required')
|
||||
app, actor, memo, options, intake, calls = handoff
|
||||
options['memo_class'] = 'decision-record'
|
||||
receipt = prepare(handoff)
|
||||
# Admit this class in the synthetic intake registry used by the fixture.
|
||||
registry = json.loads(intake.submitters_path.read_text())
|
||||
for value in registry.values():
|
||||
value['memo_classes'].append('decision-record')
|
||||
intake.submitters_path.write_text(json.dumps(registry))
|
||||
assert request(intake, receipt['envelope'])[0] == 201
|
||||
store = app.repository.store
|
||||
stored = store.memo(receipt['review_memo_id'])
|
||||
session = replace(human(), subject=stored.binding.principal.id,
|
||||
assurance={**human().assurance, 'at': int(time.time()) - 3600})
|
||||
class Engine:
|
||||
def get_approval(self, approval_id):
|
||||
return {'status': 'requested', 'binding': {'digest': stored.approval_binding_digest,
|
||||
'actor': 'informed-decision', 'human_control': True}}
|
||||
with flex_component(binary, tmp_path, signing_key, fixture=fixture) as (pdp, calls):
|
||||
controller = ReviewController(store, pdp, lambda session: Engine(), intake_attributes=True)
|
||||
[row] = controller.overview(session)
|
||||
assert row.question == memo.question and row.group == 'open'
|
||||
with pytest.raises(ReviewError, match='policy_denied'):
|
||||
controller.open(session, stored.id)
|
||||
req = build_request(session, stored, 'list', pdp.version, intake_context=store.intake_context(stored))
|
||||
req['context']['review_recipient'] = 'another-human'
|
||||
assert pdp.check(req).outcome == 'policy_denied'
|
||||
assert store.evidence() == []
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ boundary, revision/evidence semantics and acceptance without per-decision deploy
|
|||
|
||||
```task
|
||||
id: INFD-WP-0007-T02
|
||||
status: progress
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "cde0c932-afce-5bec-8cee-a2f377955b9f"
|
||||
```
|
||||
|
|
@ -389,3 +389,21 @@ synthetic identity, including mobile and desktop inspection. Evidence is retaine
|
|||
in docs/evidence/2026-10-05-lifecycle-authoring/. This source increment is not yet
|
||||
released; production remains on the accepted initial cutover image. T02/T05 stay
|
||||
in progress for broader contracts and attribute-based legacy review discovery.
|
||||
|
||||
## Authoring deployed; discovery continuation — 2026-10-05
|
||||
|
||||
Founder explicitly requested deployment and the remaining implementation.
|
||||
ADMINISTER @ realm:kubernetes/railiance01, activation=APPROVED. Authoring image
|
||||
79aabf37 is live through Argo source dfcef87 and Platform pin 0189965. Both health
|
||||
endpoints and Argo Synced/Healthy pass. Fresh backup/isolated restore preserved
|
||||
25 confirmed submissions, 174 delivered outbox rows, two records/nine events.
|
||||
Native selected-workload GET retrieves the exact retained handoff and confirms
|
||||
the prior human disposition. No new human acceptance is fabricated.
|
||||
|
||||
T02 now records explicit initial contracts in LifecycleAdmissionBoundary.md.
|
||||
Expansion residuals have live records INFD-WP-0008-T01 through T04: external
|
||||
identity/publication, connectors/retention controls, non-approval binding, optional
|
||||
Hub write-back. T05 implements named-recipient review discovery in the consumer
|
||||
and Access Engine FLEX-WP-0034 v13, with fresh read/act checks retained. Validation
|
||||
671 passed, one skipped; 32 owner embedded policy checks plus owner regressions.
|
||||
Discovery release and final native checks remain in progress.
|
||||
|
|
|
|||
70
workplans/INFD-WP-0008-lifecycle-expansion-contracts.md
Normal file
70
workplans/INFD-WP-0008-lifecycle-expansion-contracts.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
id: INFD-WP-0008
|
||||
type: workplan
|
||||
title: "Lifecycle expansion beyond the initial production audience"
|
||||
domain: infotech
|
||||
repo: informed-decision
|
||||
status: backlog
|
||||
owner: codex
|
||||
topic_slug: netkingdom
|
||||
created: "2026-10-05"
|
||||
updated: "2026-10-05"
|
||||
origin: residual
|
||||
origin_ref: INFD-WP-0007
|
||||
---
|
||||
|
||||
Initial production choices are recorded in docs/specs/LifecycleAdmissionBoundary.md.
|
||||
This workplan preserves expansion work without claiming it was delivered by the
|
||||
initial organizational approval service. These tasks do not authorize live changes.
|
||||
|
||||
## External workload identity and expanded publication
|
||||
|
||||
```task
|
||||
id: INFD-WP-0008-T01
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Contract with KeyCape and Access Engine for agents outside cluster TokenReview,
|
||||
additional tenants/audiences and any anonymous publication. Retain per-record
|
||||
policy and authentication-derived attribution; do not treat arbitrary JWT claims
|
||||
as admission. Initial authenticated publication remains the supported path.
|
||||
|
||||
## Source connector and retention controls
|
||||
|
||||
```task
|
||||
id: INFD-WP-0008-T02
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Select actual source connectors and define their retrieval/credential boundaries,
|
||||
network restrictions, content limits and provenance. Define retention/export/
|
||||
erasure requirements and owner review before introducing deletion. Existing
|
||||
caller-supplied evidence and append-only custody remain the default.
|
||||
|
||||
## Non-approval binding profiles
|
||||
|
||||
```task
|
||||
id: INFD-WP-0008-T03
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Define and implement non-approval L0/L2 presentation/disposition workflows against
|
||||
the shared schema with the identity, authorization and audit owners. Do not create
|
||||
an Approval Engine object solely to simulate an unrelated binding act. Preserve
|
||||
schema compatibility, human-only binding and revision-specific evidence.
|
||||
|
||||
## Optional State Hub outcome write-back
|
||||
|
||||
```task
|
||||
id: INFD-WP-0008-T04
|
||||
status: todo
|
||||
priority: low
|
||||
```
|
||||
|
||||
Independently admit and verify optional resolution write-back with State Hub.
|
||||
Keep explicit correlation, replay, and uncertain-outcome recovery; a human answer
|
||||
must never become an execution authorization. Current notification delivery and
|
||||
agent outcome retrieval remain available without automatic write-back.
|
||||
Loading…
Add table
Add a link
Reference in a new issue