diff --git a/docs/decisions/adrs/adr-104-runtime-control-plane-architecture.md b/docs/decisions/adrs/adr-104-runtime-control-plane-architecture.md index 24773008..b836fcdd 100644 --- a/docs/decisions/adrs/adr-104-runtime-control-plane-architecture.md +++ b/docs/decisions/adrs/adr-104-runtime-control-plane-architecture.md @@ -195,18 +195,24 @@ tenants. Multitenancy, cross-target stores, cross-run scheduling, and shared authorization namespaces are P3 nonclaims requiring a future ADR and explicit coordination, fencing, cache-coherence, and tenant-isolation contracts. -Participant clients use an organizational backend boundary, not P2 or the -in-process/store interfaces directly. P2 read roles are privileged: an identity -allowed to retrieve a governed participant projection can also read the full -snapshot, so a participant/audience binding does not make that credential safe -to delegate. The backend owns organizational authentication and entitlement, -binds each request to its selected target/run, participant, exact episode and -audience, and releases only a permitted participant projection after the -API-423/RUN-319 crossing. Participant-facing deployments require a configured -crossing resolver; the legacy projection path without one carries no such -assurance. Service credentials and raw control-plane outputs remain inside the -trusted backend. Authentication principals do not add SDL participants or -roles. The accepted route, authority, and deployment matrix is the +Participant-facing clients use a trusted host application, local or +organizational, which embeds P0/P1 SDK calls or uses the optional P2 HTTP +adapter. For SDK use, no RAES HTTP credential exists: direct Python methods +have no P2 role gate, `get_snapshot()` returns the full snapshot without a +caller identity, and the host keeps the control-plane object and store private. +For P2, deployment-configured bearer tokens or verified proxy identities are +privileged: an identity allowed to retrieve a governed participant projection +can also read the full snapshot, so a participant/audience binding does not +make that P2 identity safe to delegate. The host binds its caller to the +selected target/run, participant, exact episode, audience and operation, and +releases only a permitted projection after the API-423/RUN-319 crossing. +Participant-facing deployments require a configured crossing resolver; the +current legacy projection path without one can still return a view but has no +crossing assurance. Raw control-plane outputs stay inside the trusted host. +An organizational host owns organizational authentication and entitlement; a +local host applies its own caller boundary. Authentication principals do not +add SDL participants or roles. The accepted route, authority, and deployment +matrix is the [issue #1356 trust-boundary decision](../issue-1356-control-plane-participant-access-preflight.md). ### 8. Disposition of the incumbent surfaces @@ -349,3 +355,4 @@ demonstrated its lost-update and partial-state failures. | 2026-09-20 | #1189 | Made profile declarations runtime-owned composition metadata, separated provider facts from guarantees, and fixed P2 and recovery-observation boundaries. | | 2026-09-22 | #1348 | Defined shared-runtime supervision, effect reservations, evidence-based settlement and authored recovery choices while preserving profile and implementation nonclaims. | | 2026-09-24 | #1356 | Bound participant clients to an organizational backend and kept privileged P2 credentials and raw control-plane outputs inside that boundary. | +| 2026-09-25 | #1356 follow-up | Clarified SDK method access, optional P2 identity scope, host policy, and the legacy no-resolver projection gap. | diff --git a/docs/decisions/adrs/adr-index.yaml b/docs/decisions/adrs/adr-index.yaml index f9fc9f77..2716cce3 100644 --- a/docs/decisions/adrs/adr-index.yaml +++ b/docs/decisions/adrs/adr-index.yaml @@ -585,7 +585,7 @@ adrs: pin: a44acbc2db1b5349ba316ba0d09bd9b46310db016cb87937b08bc6bc107755aa - id: ADR-104 path: docs/decisions/adrs/adr-104-runtime-control-plane-architecture.md - pin: 131d0c0923dd72ad51a9b621019b09494142b0636e7e6a5dc2d50f2f9d7766fb + pin: c71f377900dce99190bc1cf08d0d7c93a80fedbb6fcbb0f887cf6b5dc6b49fc6 amendments: - date: 2026-09-03 ref: "#1151" @@ -602,6 +602,9 @@ adrs: - date: 2026-09-24 ref: "#1356" summary: "Bound participant clients to an organizational backend and kept privileged P2 credentials and raw control-plane outputs inside that boundary." + - date: 2026-09-25 + ref: "#1356 follow-up" + summary: "Clarified SDK method access, optional P2 identity scope, host policy, and the legacy no-resolver projection gap." - id: ADR-105 path: docs/decisions/adrs/adr-105-recursive-partial-description-semantics.md pin: e50538c4e03d12ea92ad584322dc8618835f1724ae0086c444fba0527949a01e diff --git a/docs/decisions/issue-1356-control-plane-participant-access-preflight.md b/docs/decisions/issue-1356-control-plane-participant-access-preflight.md index d880c0b7..eb7a6d7c 100644 --- a/docs/decisions/issue-1356-control-plane-participant-access-preflight.md +++ b/docs/decisions/issue-1356-control-plane-participant-access-preflight.md @@ -7,88 +7,110 @@ It changes no runtime behavior or executable authorization claim. ## Decision and authority -Participant clients reach an organizational backend, not RAE's HTTP adapter, -in-process `RuntimeControlPlane`, store, backend adapter, or host. The backend -authenticates the organization user/session, decides entitlement to a selected -run, participant, episode, audience and action, and returns only a participant -response it has authorized. Organizational users, groups, tenants, session -policy, and credential issuance remain backend responsibilities. An -authentication principal is not an SDL participant, controller, `agent` role, -or `deployment_tenant`; no new SDL declaration follows from this decision. - -The backend's RAE caller is a privileged **service principal**. Existing -`ControlPlaneIdentity` roles grant route families, not a participant-only read -capability: `BACKEND`, `OPERATOR`, and `AUDITOR` all pass `_ReadIdentity`, which -also admits `/snapshot`. A `ParticipantAudienceSubjectBinding` narrows governed -projection lookup; it does not narrow that principal's snapshot authority. -Consequently a bearer token or trusted proxy identity with a participant -binding must never be issued to a browser, participant agent, mobile client, -or participant-side plugin. A participant-facing backend must not expose a -generic RAE proxy or return raw RAE receipts, statuses, snapshots, diagnostic -payloads, or store records. Its outbound RAE credential stays in a trusted -server component and is never copied into a client process, URL, command line, -response, log, or cache key. - -Before a backend participant request reaches RAE, the backend binds its own -authenticated principal and session to **one** target/run, participant address, -exact episode, allowed audience, and permitted operation. It checks these -bindings again for every read, retry, poll, delivery, stream item, callback, -and mutation. RAE receives a target-bound service identity with the appropriate -existing role and, for governed views, exactly one matching -`ParticipantAudienceSubjectBinding`; RAE's one-run store supplies the run -boundary. The backend must not infer an audience from a URL, header, query, -view ref, or the fact that a service principal can read a snapshot. A status or -context request without an episode selector must be resolved against the -backend's selected exact episode before release. A history request's episode -path must equal that binding. An episode change, handoff, audience-policy -change, or run switch requires fresh authorization and a fresh projection; -cached bytes cannot be relabeled or reused across those coordinates. +RAES has two composition shapes. P0/P1 are SDK calls inside an embedding +application; P2 is an optional HTTP adapter over a P1 core. The trusted +**host application** owns participant-facing access in either shape. It may +be a local product such as LilRAE or an organizational service such as BigRAE. +Here “host” means that product, not a RAES realization-backend adapter. +Participant-facing UI or agent code receives an authorized result from the +host; it receives neither the privileged `RuntimeControlPlane` object and +store nor a P2 read identity. BigRAE retains organizational users, groups, +tenants, sessions, authentication and policy; a local host applies its own +caller boundary without inventing organizational accounts. An authentication +principal is not an SDL participant, controller, `agent` role, or +`deployment_tenant`. + +**P0/P1 SDK:** There is no RAES HTTP credential or listener. The embedding +process supplies typed actor context for mutations and controls which code +can call SDK methods and receive their results. `get_snapshot()` takes no +caller identity and returns the full snapshot; participant retrieval methods +accept optional identity context. HTTP role dependencies do not protect direct +Python calls. Giving untrusted participant code the control-plane object, +store, or an unrestricted SDK wrapper gives it privileged access. A host that +cannot keep that object private cannot safely share one run's state with +mutually restricted participants. In a single-user local application the owner +may call the SDK directly as the trusted host; this decision does not require +an HTTP service or organizational account layer for that use. + +**P2 HTTP:** `ControlPlaneSecurityConfig.strict_defaults()` contains no +tokens or trusted proxy identities. A deployment may configure bearer tokens +or verified proxy identities mapped to `ControlPlaneIdentity`; neither is an +RAES-issued universal credential. The configured identity has an exact target +binding and route roles. `BACKEND`, `OPERATOR`, and `AUDITOR` all pass +`_ReadIdentity`, which admits both API-408 participant view routes and +`/snapshot`. A `ParticipantAudienceSubjectBinding` narrows governed view +lookup only; it does not narrow snapshot access. “Service identity” is a +deployment use of these existing identities, not a distinct enforced role. +The host keeps its configured P2 token or proxy identity private and never +delegates it to a browser, participant agent, mobile client or plugin. + +For each participant-facing release, the host binds its own caller and +session to one target/run, participant address, exact episode, allowed +audience and permitted operation. It checks that selection for every read, +retry, poll, delivery, stream item, callback and mutation. In P2, RAE checks +the configured identity's target and route role; for governed views it also +requires one matching participant/audience binding and commits the crossing +decision. The one-run store supplies the run boundary, but P2 has no +episode-scoped credential. In P0/P1, the host supplies the actor and any +governed audience binding to the SDK; it remains responsible for caller +authentication and result release. Neither shape lets RAE infer the host's +user entitlement from a URL, operation ID, view ref or runtime actor. + +The host checks returned participant, episode and source state cut against +its selection before release. A status or context request without an episode +selector must be resolved against the host's selected exact episode; a +history request's path episode must match it. An episode change, handoff, +audience-policy change or run switch requires fresh authorization and a fresh +projection. Cached bytes cannot be relabeled across those coordinates. Participant-facing retrieval requires a configured RUN-319 crossing-policy -resolver and a permitted exact-cut API-423 egress. The current API-408 adapter -also has a legacy path when no resolver is configured; that path has no -crossing evidence and is **not** an accepted participant-facing deployment. -The backend may use RAE's governed status, history and context views only after -its own entitlement check, and must check the returned participant, episode, -and source state cut against its trusted binding before release. It must use -the governed RAE route with a configured resolver; the view schema alone is -not proof of a permitted crossing. Audience and policy authority come from -the backend's decision and RAE's governed crossing, not from interpreting a -view field as an entitlement. RAE enforces its -participant/audience candidate and crossing decision; it does not decide -organizational entitlement. Read-shaped governed egress is a mutation: it -commits crossing evidence before serialization through the existing mutation -authority. A denied, ambiguous, missing-evidence, or stale-cut result fails -closed. Administrative reads may remain available to trusted operators under -ADR-104, but they are never a participant-view fallback. +resolver and permitted exact-cut API-423 egress in the host's RAE composition. +The current SDK and HTTP adapter still have a legacy projection path when no +resolver is configured. That path can return a view without crossing evidence; +RAE does **not** reject it merely because the host intends participant-facing +use. The host must refuse such a deployment or result. It may release governed +status, history and context views only after its own entitlement check and +the applicable RAE crossing. A valid view schema alone is not proof of a +permitted crossing. Read-shaped governed egress commits crossing evidence +before serialization through the existing mutation authority. Administrative +snapshots, operation records, raw histories, receipts, diagnostics and errors +are never participant-view fallbacks. ## Runtime route and authority matrix -This inventories the reference P2 HTTP app; P0/P1 expose the corresponding -in-process methods only to a trusted embedder. `read` means the existing -`_ReadIdentity` role check, `mutate` means `_MutatingIdentity`, and `resolve` -means `_ResolutionIdentity`. All authenticated HTTP identities require the -exact RAE target binding. The backend applies its own user/session and -participant checks before any participant response. +This table inventories the optional reference P2 HTTP app. `read` is the +existing `_ReadIdentity` role check, `mutate` is `_MutatingIdentity`, and +`resolve` is `_ResolutionIdentity`. All authenticated P2 identities require +the exact target binding. These checks authenticate the host's P2 caller; +they do not authenticate the host's participant-facing user. The host applies +its own caller and participant checks before releasing any result. -| Surface | Current RAE gate and data | Authorized consumer and participant treatment | +| Surface | Current P2 gate and data | Caller and release rule | | --- | --- | --- | -| `GET /snapshot`; `GET /apparatus/operational-summary` | `read`; full runtime snapshot or operational summary, revision header, audit | Privileged backend, operator or auditor only. Never forward or derive a participant response from this route. | -| `GET /operations/{operation_id}` | `read`; core matches immutable operation actor **and authorization scope**, otherwise 404 | Submitting service actor/operator for operational polling. IDs and matching scopes are not participant authorization; backend reprojects any client-visible progress. | -| `POST /operations/provisioning`, `/orchestration`, `/evaluation` | `mutate`; planner authorization, typed plan validation, actor-scoped claim, receipt | Trusted backend/operator only. Plans and receipts are control-plane artifacts. | +| `GET /snapshot`; `GET /apparatus/operational-summary` | `read`; full runtime snapshot or operational summary, revision header, audit | Privileged host, operator or auditor identity. Never release these as participant views. | +| `GET /operations/{operation_id}` | `read`; core matches immutable operation actor **and authorization scope**, otherwise 404 | Submitting host actor/operator for operational polling. IDs and matching service scopes do not authorize the host's end user; the host governs any client-visible progress. | +| `POST /operations/provisioning`, `/orchestration`, `/evaluation` | `mutate`; planner authorization, typed plan validation, actor-scoped claim, receipt | Trusted host/operator only. Plans and receipts are control-plane artifacts. | | `POST /operations/{operation_id}/resolution` | `resolve` (operator); linked indeterminate resolution | Operator only; never a participant retry or recovery shortcut. | -| `POST /workflows/{workflow_address}/cancel`, `/workflows/reconcile-timeouts` | `mutate`; core workflow/run admission, receipt | Trusted backend/operator only; backend must authorize any user-driven cancellation. | +| `POST /workflows/{workflow_address}/cancel`, `/workflows/reconcile-timeouts` | `mutate`; core workflow/run admission, receipt | Trusted host/operator only; host must authorize any user-driven cancellation. | | `POST /participant-executions/{execution_scope_ref}/control`; `GET /participant-executions/{execution_scope_ref}` | `mutate`/`read`; backend execution control/readback, receipt or service state | Administrative service surface. Execution scope is not an audience-bound participant projection. | -| `POST /participants/{participant_address}/episodes/{initialize,reset,restart,terminate}` | `mutate`; typed lifecycle request, core episode/run state, receipt | Trusted backend/operator only. Path and optional episode body are request data, not entitlement. | +| `POST /participants/{participant_address}/episodes/{initialize,reset,restart,terminate}` | `mutate`; typed lifecycle request, core episode/run state, receipt | Trusted host/operator only. Path and optional episode body are request data, not entitlement. | | `POST /participants/{participant_address}/control-occurrences` | `mutate` plus core participant/controller binding and API-409 control admission | Authorized service controller only; control authority is distinct from view audience. | -| `GET /participants/{participant_address}/status`; `/episodes/{episode_id}/history`; `/context` | `read`; with resolver, exact participant/audience candidate, episode and API-423/RUN-319 crossing before serialized API-408 view; without resolver, legacy projection | Trusted backend service only. Backend releases a view solely for its separately bound user, run, participant, episode and audience. | +| `GET /participants/{participant_address}/status`; `/episodes/{episode_id}/history`; `/context` | `read`; with resolver, participant/audience candidate and API-423/RUN-319 crossing before serialized API-408 view; without resolver, legacy projection | Trusted host identity only. Host releases a governed view solely for its separately bound caller, run, participant, exact episode and audience. The route's role check alone does not supply those bindings. | | `GET /health/live`, `/health/ready`; framework `/openapi.json`, `/docs`, `/redoc` | No identity dependency; value-free health or API description | Deployment probe/administration network. No participant data authority; do not treat their reachability as permission to expose P2. | -| Audit log, snapshot/operation store, raw event and crossing histories, backend callbacks | No P2 HTTP route for raw audit or an event stream; in-process/store access belongs to the owner/embedder. Histories also appear inside privileged snapshots and governed participant views. | Owner/operator only. A future event, subscription, export, callback, or cache endpoint must inherit the same release gate for each item and cannot expose raw histories to participants. | -| `RuntimeControlPlane` in-process methods, `RuntimeManager` direct execution, and offline store check/backup/restore | Trusted embedder, direct backend authority, or owner-stopped maintenance; no HTTP participant authorization | Privileged host/operator path only. Store copies, direct snapshots and execution results must not become alternate participant reads or mutations. `RuntimeManager` does not coordinate with the control-plane owner on the same backend. | +| Audit log, snapshot/operation store, raw event and crossing histories, host callbacks | No P2 HTTP route for raw audit or an event stream; in-process/store access belongs to the owner/embedder. Histories also appear inside privileged snapshots and governed participant views. | Owner/operator only. A future event, subscription, export, callback, or cache endpoint must inherit the same release gate for each item and cannot expose raw histories to participants. | +| `RuntimeControlPlane` in-process methods, `RuntimeManager` direct execution, and offline store check/backup/restore | Trusted embedder, direct realization-backend authority, or owner-stopped maintenance; no HTTP participant authorization | Privileged host/operator path only. Store copies, direct snapshots and execution results must not become alternate participant reads or mutations. `RuntimeManager` does not coordinate with the control-plane owner on the same realization backend. | + +The P0/P1 boundary is method access inside the embedding process; the P2 +boundary also has transport authentication: + +| Composition | RAE-enforced boundary | Host-enforced boundary | +| --- | --- | --- | +| P0/P1 SDK | Runtime ownership, target/run store scope, actor-bound mutation and readback, and a participant crossing when configured. `get_snapshot()` has no caller-identity gate; the SDK does not authenticate a product user. | Keep the control-plane object and store private; authenticate or otherwise identify the local/product caller; authorize its participant, episode, audience and operation before calling or releasing a result. | +| P2 HTTP over P1 | Configured bearer token or verified proxy identity, route role and exact target checks; operation actor/scope readback; participant/audience crossing only with a resolver. There is no participant-only read role, run/episode-bound HTTP credential, or automatic rejection of the legacy no-resolver view path. | Keep the P2 identity and socket private from participant clients; authorize the product caller and exact result scope; reject ungoverned views and reauthorize cached or replayed results. | HTTP 4xx/5xx envelopes, validation errors, operation diagnostics, receipts, status readback, and `X-RAES-Snapshot-Revision` are also output surfaces. -They must carry no secret or cross-participant payload. The backend returns its +They must carry no secret or cross-participant payload. The host returns its own bounded, participant-safe errors and never uses a 403/404 distinction or raw RAE diagnostics to reveal another participant's existence. Participant progress or notifications derived from operational results still need the @@ -99,7 +121,8 @@ The in-process row also covers `snapshot`, `get_snapshot`, `audit_log`, `observation_execution`, participant action/control and decision-surface admission, and `deliver_participant_directed_view`. These are not additional participant transports: HTTP role dependencies do not wrap direct Python -calls. Directed delivery is a separate crossing, not proof of participant +calls, and possession of the SDK object grants access to its public methods. +Directed delivery is a separate crossing, not proof of participant consumption. The current `raes_mcp` authoring/inspection tools are not a live participant control-plane gateway; adding one would require the same boundary. @@ -110,13 +133,13 @@ participant control-plane gateway; adding one would require the same boundary. proxy headers, and select exactly one worker with reload disabled. Use `ControlPlaneSecurityConfig.strict_defaults()` until explicit identities and secrets are loaded. Unknown bearer credentials must fail without header - fallback. A reverse proxy route allowlist can constrain the participant - backend's outbound traffic, but does not make its RAE credential scoped at - the RAE auth layer. + fallback. A reverse proxy route allowlist can constrain the host's outbound + P2 traffic, but does not make a configured P2 identity narrower at the RAE + auth layer. - Reuse `ControlPlaneSecurityConfig`, `_ControlPlaneApiAuth`, the API request size and bounded offload guards, `ControlPlaneIdentity` and its subject bindings. RAE config currently has no episode- or run-scoped HTTP identity - field; the backend's selected run/episode must be checked against the pinned + field; the host's selected run/episode must be checked against the pinned RAE store and returned carrier. Do not silently add an env flag, query token, new role, or second principal model to simulate that binding. - Reuse the published `raes_contracts` operation, snapshot, API-408 view, @@ -130,29 +153,34 @@ participant control-plane gateway; adding one would require the same boundary. - Reuse `_responses._conflict_detail`, redacted FastAPI exception handlers, `RequestSizeLimitMiddleware`, `portable_diagnostic_payload`, and bounded rejection/operational audit. Log stable reason codes and actor/target - references only; never log bearer tokens, bodies, backend exception text, - source snapshot bytes, participant view payloads, or identifying paths. + references only; never log bearer tokens, bodies, realization-backend + exception text, source snapshot bytes, participant view payloads, or + identifying paths. Validate any future projection response at its final sink, including cache hits, retries and error branches. - Keep the P1 owner lease, private store directory/database permissions, - immutable target/run scope, snapshot revision/CAS and single worker rule. + immutable target/run scope and snapshot revision/CAS; the single-worker + server rule applies when P2 is used. Build on `control_plane_store_local`, its scope/carrier validation and the existing store-maintenance interface for operational copies; do not add a parallel participant store or direct reader. Load secrets through the deployment's protected secret channel, outside process arguments and published config. Do not put a bearer token in a URL, environment dump, CLI argument, SDL file, fixture, or versioned artifact. - Backend caches must be private, bounded by user/session plus target/run, + Host caches must be private, bounded by caller/session plus target/run, participant, episode, audience, policy and source revision, and invalidated on authorization or state-cut change. +- For P0/P1 SDK embedding, keep the control-plane object and store inside the + trusted process or trusted component. An in-process participant plugin with + access to that object can call privileged methods; a host must isolate such + code or avoid sharing one run with mutually restricted participants. -The extension seam is the backend-to-RAE identity/policy adapter: it maps a -backend-approved `(target, run, participant, episode, audience, operation)` -selection to existing RAE service identity and governed retrieval parameters. -It must permit a future route-scoped RAE credential or a new participant-safe -transport without changing SDL identities, the API-408/API-423 carriers, or -the backend's organizational policy model. Such a credential or transport -requires its own reviewed RAE authorization change; this decision grants none. +The extension seam is the host-to-RAE policy adapter: it maps a host-approved +`(target, run, participant, episode, audience, operation)` selection to SDK +actor and governed retrieval parameters, or to a configured P2 identity and +the same governed retrieval parameters. A future route-scoped P2 credential or +participant-safe transport requires its own reviewed RAE authorization change; +this decision grants none and changes no SDL identity or API-408/API-423 carrier. ## Composition and release checks @@ -162,16 +190,16 @@ schemas, or a proposed implementation sequence. Module names below are in | Layer and canonical incumbent | Required boundary | | --- | --- | -| Transport/config: `raes_runtime/control_plane_security.py`, `control_plane_api/_auth.py`, `control_plane_api_guards.py`, `control_plane_api/_offload.py` | Preserve immutable credential maps, valid distinct identity-header names, target equality, role checks, positive admission limits and bounded body reading before routing. Backend entitlement must precede trusted identity selection; caller-supplied headers or selectors cannot mint bindings. A shared service principal is not an end-user identity. | -| Runtime composition: `control_plane_configuration.py`, `control_plane_composition.py`, `control_plane_profiles.py`, `registry.py` | Use normalized `run:`, an explicitly selected P1 core/P2 adapter and admitted backend capabilities. Require the crossing resolver and its trusted `resolve_participant_view_evidence` hook for participant retrieval. Preserve explicit final-sink selection: selected SEM-233 enforcement requires `resolve_flow_sink_decision`; selected RUN-320 modular control requires its admitted binding/capability. Do not disable enforcement to make an incompatible resolver start, or confuse a manifest declaration with an installed enforcement provider. | +| P2 transport/config: `raes_runtime/control_plane_security.py`, `control_plane_api/_auth.py`, `control_plane_api_guards.py`, `control_plane_api/_offload.py` | Preserve immutable credential maps, valid distinct identity-header names, target equality, role checks, positive admission limits and bounded body reading before routing. The host must authorize its participant-facing caller before releasing results; caller-supplied headers or selectors cannot mint P2 bindings. A shared service identity is not an end-user identity. | +| Runtime composition: `control_plane_configuration.py`, `control_plane_composition.py`, `control_plane_profiles.py`, `registry.py` | Use normalized `run:`, an explicitly selected P0/P1 core with optional P2 adapter and admitted realization-backend capabilities. A participant-facing host requires the crossing resolver and its trusted `resolve_participant_view_evidence` hook for retrieval; the current legacy path does not enforce this requirement. Preserve explicit final-sink selection: selected SEM-233 enforcement requires `resolve_flow_sink_decision`; selected RUN-320 modular control requires its admitted binding/capability. Do not disable enforcement to make an incompatible resolver start, or confuse a manifest declaration with an installed enforcement provider. | | Portable shape and contextual authorization: `raes_contracts/contracts/participant_views.py`, `participant_crossing_validation.py`; `raes_runtime/participant_crossing_mediation.py`, `participant_flow_sink.py`, `participant_control_orchestration.py` | Reuse API-408 model validation and API-423 context validation plus operation-bound policy, audience, history-cut and selected final-sink checks. Validate transformed egress against the committed subject. A well-formed view, a visibility-projection ref or a successful HTTP status is not independently verifiable crossing evidence; deployment composition and the governed path supply that assurance. | | Secrets and environment: `raes_contracts/secret_references.py`; `raes/runtime_environment.py`, `runtime_generated_value.py`, `runtime_configuration.py`, `runtime_values.py` | Service credentials remain deployment secrets outside SDL. This decision introduces no env parser or secret loader. If deployment assets describe runtime env inputs, retain unique names, explicit sensitivity/provenance, redaction, literal/`value_from` exclusions and generated-output reference checks. `SecretReferenceId` is a logical reference, not a bearer value; generated values cannot masquerade as out-of-SDL `operator_secret` material. | -| Host/process and persistence: `control_plane_store_lease.py`, `control_plane_store_paths.py`, `control_plane_store_local.py`, `control_plane_store_records.py`, `control_plane_store_maintenance.py` | Enforce private owner-controlled paths, strict durable codecs, one target/run and one owner; keep `WEB_CONCURRENCY`/`UVICORN_WORKERS` at one and reload off. Proxy/network isolation must prevent direct socket access that bypasses verified-header authentication. Keep credentials out of argv, shell tracing, environment dumps, access logs, crash reports and exported backups/config. Store backups remain privileged even when diagnostics are redacted. | +| Host/process and persistence: `control_plane_store_lease.py`, `control_plane_store_paths.py`, `control_plane_store_local.py`, `control_plane_store_records.py`, `control_plane_store_maintenance.py` | Enforce private owner-controlled paths, strict durable codecs, one target/run and one owner. For P2 keep `WEB_CONCURRENCY`/`UVICORN_WORKERS` at one and reload off; proxy/network isolation must prevent direct socket access that bypasses verified-header authentication. Keep configured P2 credentials out of argv, shell tracing, environment dumps, access logs, crash reports and exported backups/config. Store backups remain privileged even when diagnostics are redacted. | | Errors and observability: `control_plane_api/_responses.py`, `_operation_routes.py`, `raes_contracts/diagnostics.py`, `raes_runtime/control_plane_audit.py`, `backend_result_diagnostics.py` | Use existing coarse conflict/422/500 envelopes and portable diagnostics; no raw validation input, exception text/type/chain or provider payload. Preserve the selected denial response when secondary audit fails. Keep terminal operational audit atomic with state; best-effort transport rejection audit is a separate guarantee. Backend, reverse-proxy and ASGI logging must obey the same disclosure boundary. | -Shared service identities need special care. `operation_actor_scope` and -`control_plane_admission` bind RAE claims/readback to the service actor and its -complete authorization scope, not the backend's user/session. The backend +Shared P2 service identities need special care. `operation_actor_scope` and +`control_plane_admission` bind RAE claims/readback to the supplied operation +actor and its complete authorization scope, not the host's user/session. The host must associate each client request and operation reference with its authorized selection and map client retry keys into that ownership context; forwarding arbitrary client keys into a shared service namespace is unsafe. Preserve the @@ -179,18 +207,19 @@ mapping for a genuine retry, reauthorize every release, and never generate a fresh key automatically after timeout, disconnect or an uncertain result. Changing subject bindings can change the immutable scope and legitimately make old readback unavailable; do not work around that by dropping the scope -check. Organization-user correlation belongs in backend audit, not a second -RAE actor model or credential-bearing operation context. +check. Product-user correlation belongs in host audit, not a second RAE actor +model or credential-bearing operation context. In P0/P1 the host supplies the +typed operation actor directly and owns its mapping to product callers. `RuntimeControlPlane._project_snapshot_read` returns the observed projection revision, not a promise about the latest state after the crossing commit. An idempotent egress can return its retained governed result; `participant_crossing_projection.stable_projection_subject` normalizes only runtime-owned revision paths. Do not rewrite retained source refs to a newer -header/revision or use that normalization as backend cache authorization. +header/revision or use that normalization as host cache authorization. Reauthorize against the selected episode/policy and obtain a fresh governed projection when a current view is required. Governed GETs must not be served -from a shared proxy/browser cache: backend delivery must pass its release gate +from a shared proxy/browser cache: host delivery must pass its release gate even for retained results, including conditional responses and stream items. ## Evidence and workflow boundaries @@ -204,10 +233,11 @@ transformation, stale-cut replay and atomic crossing), `test_issue_1003_final_sink_flow_enforcement.py` and the issue-1069 control tests (selected sink/control enforcement), and `test_issue_1185_api_404_profile_alignment.py` (profile/nonclaim drift), all in -`implementations/python/tests/`. They do not demonstrate an organizational -backend's entitlement, revocation, session isolation or cache policy. Those -need evidence at the backend boundary, including a shared service actor, -cross-participant/episode requests and authorization changes during delivery. +`implementations/python/tests/`. They do not demonstrate a host's caller +entitlement, revocation, session isolation or cache policy. Those need evidence +at the host boundary, including cross-participant/episode requests and +authorization changes during delivery; P2 hosts also need a shared service +identity case. Use `.ground-control.yaml`, `.gc/plan-rules.md`, `tools/policy/adr_policy.yaml` and `tools/check_repo_policy.py` for repository boundaries. ADR amendments use @@ -225,9 +255,9 @@ No direct participant-to-RAE authentication, RAE organizational IAM, new SDL participant/role, new P2 role, new published schema, or new HTTP route is authorized here. This design does not certify that current P2 deployments already have a crossing resolver, that legacy projection is safe for a -participant, or that a backend can delegate its broad RAE read credential. -Avoid filtering a full snapshot in the browser/backend as a substitute for +participant, or that a host can delegate its broad P2 read identity. +Avoid filtering a full snapshot in the browser/host as a substitute for governed projection, treating possession of an operation ID or a `ParticipantAudienceSubjectBinding` as global read authority, caching a view without its exact scope, and allowing a generic proxy, event stream, error or -retry path to bypass the backend gate. +retry path to bypass the host gate. diff --git a/docs/explain/sdl/runtime-architecture.md b/docs/explain/sdl/runtime-architecture.md index 9e635ae6..7c554faf 100644 --- a/docs/explain/sdl/runtime-architecture.md +++ b/docs/explain/sdl/runtime-architecture.md @@ -660,11 +660,16 @@ and backup policy. Profile metadata is in-process discovery, not a health signal, HTTP discovery endpoint, availability promise, or tenant-multiplexing contract. -Participant clients access an organizational backend rather than P2 directly. -P2 read credentials are privileged even when they carry participant/audience -bindings: they can also read the full snapshot. The backend keeps those -credentials private and binds each participant request to one run, participant, -episode and audience before releasing a governed API-408 view. The accepted +Participant clients access a trusted host application, which may embed P0/P1 +as an SDK or use the optional P2 HTTP adapter. The host keeps the SDK +control-plane object or P2 identity private and binds each caller to one +target/run, participant, exact episode, audience and operation before releasing +a governed API-408 view. SDK calls have no HTTP role gate; a P2 identity with +read access to participant views can also read the full snapshot. Neither +path authenticates the host's end user or turns a legacy view without a +configured crossing resolver into governed participant output. An +organizational host owns organizational identity and policy; a local host +applies its own caller boundary. The accepted [route and deployment boundary](../../decisions/issue-1356-control-plane-participant-access-preflight.md) also covers operation readback, histories, errors, caches and events. diff --git a/docs/requirements/API-404/requirement.md b/docs/requirements/API-404/requirement.md index 75fcd792..883074d7 100644 --- a/docs/requirements/API-404/requirement.md +++ b/docs/requirements/API-404/requirement.md @@ -34,6 +34,17 @@ discards operation state, idempotency records, and audit evidence held by the composition. P0 does not provide durability, restart recovery, or retained deduplication across process loss. +For P0/P1 SDK use, the host application owns participant-facing caller +authentication, entitlement and result release. Direct Python methods have no +P2 transport authentication: `get_snapshot()` returns the full snapshot +without a caller identity, and participant retrieval accepts optional identity +context. The host keeps the control-plane object and store private, binds a +participant request to its target/run, participant, exact episode, audience +and operation, and releases only a permitted governed projection after a +configured API-423/RUN-319 crossing. The current legacy no-resolver retrieval +path can return a view without that crossing; the host must reject it for +participant-facing use. Local hosts need no organizational account model. + ### API-404-C2 — Durable local control P1 and P2 shall add crash-consistent authoritative state, retained idempotency @@ -56,19 +67,21 @@ selected P1 core. The corresponding runtime guarantee identifiers are `owner-serialized-mutation`, and `revision-carrying-reads`. Only P2 authenticates transport callers; P0 and P1 rely on their trusted embedders. -P2's authenticated control-plane principal is a privileged service identity, -not a credential for a participant client. Read-role admission to an API-408 -participant projection also permits full snapshot and operational reads; the -participant/audience binding used for governed projection is not a route-wide -read restriction. Participant clients therefore use an organizational backend -that binds its own principal/session to target/run, participant, exact episode, -audience and permitted operation before every retrieval or mutation. A -participant-facing view requires configured API-423/RUN-319 crossing mediation -at the exact state cut; the legacy ungoverned projection path does not satisfy -this deployment boundary. The backend retains organizational identity/policy -and service credentials and must not disclose raw snapshot, operation, -history, event, error or cached control-plane outputs as participant views. -This interpretation adds no P2 profile guarantee or SDL participant role. +P2 identities are deployment-configured bearer tokens or verified proxy +identities, not RAES-issued participant credentials. A host may use one as a +service identity. Read-role admission to an API-408 participant projection +also permits full snapshot and operational reads; the participant/audience +binding used for governed projection is not a route-wide read restriction. +The host keeps its P2 identity private, binds its participant-facing caller to +target/run, participant, exact episode, audience and permitted operation, and +checks the returned view before release. P2 checks the configured identity's +target and role and, with a resolver, the governed crossing; it does not +authorize the host's end user or reject the legacy no-resolver path solely +because the result will be participant-facing. The host must not release raw +snapshot, operation, history, event, error or cached control-plane outputs as +participant views. Organizational identity and policy remain with an +organizational host such as BigRAE. This interpretation adds no P2 profile +guarantee or SDL participant role. ### API-404-C4 — Excluded stronger claims diff --git a/tools/policy/historical_identity_records.json b/tools/policy/historical_identity_records.json index 267cc673..edc74491 100644 --- a/tools/policy/historical_identity_records.json +++ b/tools/policy/historical_identity_records.json @@ -495,7 +495,7 @@ "record_class": "historical-index", "rationale": "Indexes immutable pre-cutover ADR titles, paths, pins, and amendment summaries without making them current identity surfaces.", "occurrences": 4, - "content_sha256": "319845da1c5381f10bf3013135eba2a067c5b9687e5f84647c744d7d5b82de2f" + "content_sha256": "aaf64f1f6c3fc2fc7283bf846a59b2620c47361a3e617e8f9087b4bf4d40ea2d" }, { "path": "docs/decisions/cage-2-replication-design.md",