diff --git a/docs/en/rfcs/1396_handoff_access_control.md b/docs/en/rfcs/1396_handoff_access_control.md new file mode 100644 index 000000000..2fd28de93 --- /dev/null +++ b/docs/en/rfcs/1396_handoff_access_control.md @@ -0,0 +1,1563 @@ +- Proposal Name: `handoff_access_control` +- RFC Number: 1396 +- Start Date: 2026-08-30 +- Status: Draft +- RFC PR: [oceanbase/powercontext#1396](https://github.com/oceanbase/powercontext/pull/1396) +- Tracking Issue: [oceanbase/powercontext#1395](https://github.com/oceanbase/powercontext/issues/1395) +- Related RFCs: [RFC 0011](0011_remote_access_architecture.md), [RFC 0048](0048_handoff_artifact.md), + [RFC 0050](0050_artifact_candidate_review_inbox.md), [RFC 0051](0051_experience_skill_artifact_families.md), + [RFC 0082](0082_handoff_report.md), and [RFC 1223](1223_human_agent_work_continuity.md) + +# Summary + +This RFC defines an independent Access Control boundary, stable Resource Kinds, and an Artifact Family-driven Access +Profile contract for the PowerContext Server. Handoff is the first complete resource-level authorization scenario. +The RFC answers one concrete question—when user A transfers a Handoff to user B, what may B see and do, and how can +that access be revoked and audited—and specifies how later Artifact Families reuse the same Principal, action, +ResourceRef, Binding, PEP/PDP, listing, and audit semantics. + +Handoff content does not store users, roles, or ACLs. `scope_id` remains the stable business partition for a +Workstream; it is not a user identity, tenant, role, or security boundary. Authentication and authorization happen at +the Server. Authentication establishes a trusted Principal. A Policy Enforcement Point (PEP) sends that Principal, +the action, and the resource to a replaceable `AuthorizationProvider` before it calls the existing Runtime application +service. + +```text +Identity Provider or static credential + | + v + Authenticated Principal + | + v + PowerContext Server PEP + | + v + AuthorizationProvider <----> Policy or relationship store + | + allow or deny + | + v + Existing application service +``` + +The first version defines three stable Resource Kinds: + +- `server`: the current PowerContext deployment; +- `scope`: one exact Workstream scope; +- `artifact`: an exact Artifact Revision or Family-owned selector interpreted by an Artifact Family Access Profile. + +The `artifact` Resource Kind initially registers Artifact Family Access Profiles for `handoff`, `memory`, +`experience`, `skill`, and `prompt`. `ArtifactReference.family` is the only Profile discriminator. A client does not +submit a second content type that could conflict with it. + +User A can collaborate in two ways: + +- grant a Workstream role to a long-term collaborator; or +- grant B access to one exact persisted or approved resource. + +The second option is the least-privilege path in the first version. B may read the shared exact resource and perform +only the actions defined by its Artifact Family Access Profile. An exact Handoff receiver may inspect the evidence +explicitly cited by that Handoff through its resolver and leave a Receipt for the same Revision. An exact Memory, +Artifact, or Prompt grant does +not open the rest of the scope, the current head, later Revisions, search results, or resources referenced by lineage. +Reading a Skill, publishing it to a target, and allowing a host to load or execute it are separate authorization +boundaries. An `accepted` Receipt, Artifact approval, Prompt read, or Skill publication never grants tools, network, +filesystem, model Provider, or credential access. + +PowerContext defines a stable authorization request and decision, built-in roles, an Access API, and an OpenAPI +extension without requiring one policy engine. The first version provides a built-in Role Binding Store. Casbin, +OpenFGA, and Policy Decision Points (PDPs) compatible with the OpenID AuthZEN Authorization API can be integrated +through adapters. + +# Motivation + +PowerContext already has temporary Prepared Handoffs, immutable Handoff Revisions, Continue, Receipts, Task Outcomes, +Memory Entry Versions, approved Experience and managed Skill Revisions, and host-local Skill projections. The current +Server authentication model, however, is an optional global static Bearer token. A valid token can call every protected +operation. The Server cannot express that: + +- A administers a Workstream while B can see only one transfer; +- B may acknowledge a transfer but may not publish another milestone; +- a team member may view a Handoff Report but may not approve an Experience or Skill; +- B may read one shared Memory Entry Version but may not search the scope or follow later versions; +- B may read an approved Experience or managed Skill Revision but may not review a Candidate; +- B may use one exact Prompt but may not silently promote it to a host system or developer instruction; +- a publisher may publish one exact managed Skill but cannot thereby modify its source Revision or gain host execution + authority; +- a revoked receiver may not read later Revisions; +- HTTP, MCP, and the Dashboard make the same decision for the same Principal. + +RFC 0048 requires a receiver to be able to read the Handoff's scope and evidence. Adding B to the complete scope meets +that requirement but exposes unrelated Memory, Sources, and history. Copying only the Handoff body to B loses exact +evidence, Receipts, and revocation. + +The authorization check in RFC 1223's `acknowledge_handoff` is the receiver's observation about the live environment. +It answers whether the receiver currently appears able to continue. It does not authenticate B and is not an ACL. The +natural-language `receiver`, `authorization_notes`, or an instruction such as “continue this work” cannot be an access +credential either. + +Handoff and other shareable resources therefore need an authorization layer independent of their content and the +Runtime domain API. That layer must support least-privilege sharing, team roles, external PDPs, safe listing, audit, and +fail-closed behavior without allowing an Agent, a request body, or `scope_id` to establish authority. + +# Guide-level explanation + +## Mental model: transfer content and transfer access are different + +A Handoff answers “where is the work?” An Access Binding answers “who may do what with this transfer now?” They have +different lifecycles: + +```text +Prepared Handoff -> Commit -> immutable Handoff Revision + | + +-> Access Binding for user B + | + read / inspect / acknowledge + | + expire or revoke +``` + +Committing a new Handoff does not share it automatically. Sharing does not change the Handoff content or Revision. +Revoking a Binding does not delete the Handoff, Receipt, or audit events. + +## One Access Plane with Artifact Family-driven Profiles + +The Access Control core answers only whether the current Principal may perform an action on an exact resource. A +Resource Kind defines the shape of an authorization object. An Artifact Family Access Profile defines the +authorization semantics for one kind of content: + +```text +Protected Resource +├── server +├── scope +├── artifact +│ ├── family=handoff +│ ├── family=memory +│ ├── family=experience +│ ├── family=skill +│ └── family=prompt +``` + +Each Artifact Family Access Profile must define: + +| Family profile contract | Required definition | +| --- | --- | +| share unit | Whether the grant covers an exact Revision or a Family-owned exact selector | +| shareable state | Which lifecycle states, such as committed, approved, or retained, allow Binding creation | +| parent | How scope- or server-level roles imply child-resource actions in one direction | +| actions | Stable actions for reading, using, acknowledging, publishing, and administration | +| grantable roles | Fixed roles that may bind to the resource and who may create those Bindings | +| resolution | Operations that can resolve the resource from a validated request and what they may not read first | +| listing | How an exact grant is discovered and which aggregate lists still require scope or server authority | +| transitivity | Whether reading the resource also reads lineage, citations, or other related resources | + +All Families reuse the same `/v1/access/*` API. They do not add parallel authorization endpoints such as +`/memory/share`, `/experience/share`, `/skill/share`, or `/prompt/share`. A new exact-read Family that reuses +`artifact.read` does not require another ResourceRef variant, but it must be registered explicitly. A Family that +introduces a semantic action, selector, or role must update OpenAPI, the fixed action and role vocabulary, Server-owned +resolvers, Provider conformance vectors, and generated transport artifacts together. Unknown Families are not +shareable by default. + +Resource visibility, context selection, and external execution authority are separate planes: + +```text +Access Plane: Which exact resource the Principal may read or use +Context Plane: Which authorized content enters bounded PreparedContext after explicit selection +Execution Plane: Whether a host installs, loads, or executes a Skill or Prompt and which tools it may use +``` + +An allow decision does not propagate across planes. An exact Memory, Artifact, or Prompt grant does not place content +in normal scope recall automatically. A receiver first discovers it in a “Shared with me” view, then explicitly reads +it, attaches it to the current task, or forks it into a scope where the receiver may contribute. Shared content remains +`untrusted_history` or untrusted instruction; Context builders and hosts still enforce their own budgets, precedence, +approval, and sandbox policy. + +## A transfers one exact Handoff to B + +Assume A administers the `project:payments` Workstream and has prepared a transfer. The normal flow is: + +1. A inspects and commits the Prepared Handoff, producing an immutable `ArtifactReference`: + + ```json + { + "family": "handoff", + "artifact_id": "project:payments", + "revision": 12 + } + ``` + +2. A explicitly selects B. The Dashboard or integration resolves B through the deployment's identity directory to a + trusted canonical Principal. Model output, a display name, or email text cannot replace this resolution. +3. The Server checks whether A has `scope.delegate` on `project:payments`. +4. The Server creates an Access Binding with the `handoff.receiver` role for that exact Revision and optionally sets an + expiration time. +5. B signs in using B's own credential. `resources/list` returns exact Handoffs B may read. B never receives A's token + or a new bearer share link. +6. B calls Continue with an exact selection. The Server reads the same Revision and resolves only the evidence it + explicitly cites. +7. After checking the live workspace, capability, and authorization state, B may leave an `accepted`, + `needs_clarification`, or `declined` Receipt for the same Revision. + +An example Binding creation request is: + +```json +{ + "subject": { + "type": "user", + "issuer": "https://id.example.com/", + "id": "00u-bob" + }, + "resource": { + "type": "artifact", + "scope_id": "project:payments", + "reference": { + "family": "handoff", + "artifact_id": "project:payments", + "revision": 12 + } + }, + "role": "handoff.receiver", + "expires_at": "2026-09-06T12:00:00Z", + "reason": "Continue the payment retry investigation", + "idempotency_key": "transfer-payments-12-to-bob" +} +``` + +The Server supplies `granted_by`, creation time, and policy revision. The caller cannot assert them. + +## What B can see + +`handoff.receiver` is an exact-resource role, not a scope role: + +| Operation | Result | Reason | +| --- | --- | --- | +| Read Handoff Revision 12 | Allowed | The Binding identifies this exact Revision | +| Inspect the citations of Revision 12 through Continue | Allowed | `handoff.evidence.read` covers only this Revision's citation manifest | +| Acknowledge Revision 12 | Allowed | A receiver may leave a Receipt for the exact Handoff it inspected | +| Request `latest` | Denied | Latest may be a later Revision that was never granted to B | +| Read Revision 11 or 13 | Denied | An exact Binding does not inherit to adjacent Revisions | +| Open the aggregate Handoff Report | Denied | The Report contains scope-level history and statistics | +| Search scope Memory or list Sources | Denied | A Handoff Binding does not grant general scope read | +| Commit a Handoff or record a Task Outcome | Denied | Those operations require `scope.contribute` | +| Approve a Candidate | Denied | Approval requires independent `scope.review` authority | + +Least-privilege evidence access does not copy each Source or Memory item, and it does not require an external PDP to +store every citation. The Server first builds the exact Handoff `ArtifactResourceRef` from the validated request and +checks both `artifact.read` and `handoff.evidence.read` for B. Only when both decisions allow access may it read the +immutable Handoff Revision, obtain its citation manifest, and dereference exact citations in that manifest through the +Handoff resolver. B cannot reuse that permission by placing an arbitrary Source ID in a general read API. + +If a citation has been deleted, retired, corrupted, or denied by a higher-order policy, Continue marks the +corresponding evidence unavailable. A Handoff Binding does not override retention, legal hold, data classification, or +an explicit deny policy. + +## Sharing other Artifact Families + +Other Artifact Families use the same exact-share flow without inheriting Handoff evidence or Receipt semantics: + +1. A selects an exact persisted resource that can be authorized. Memory uses a complete `MemoryCitation`; Experience, + managed Skill, and Prompt use an `ArtifactReference` with a positive integer Revision. +2. The Server checks whether A may create the relevant Binding in the resource's scope, then verifies that the resource + exists and is in a shareable state. +3. B discovers the exact resource through `access/resources/list` and reads or explicitly uses it as B's own Principal. +4. To modify or maintain the content, B explicitly forks or proposes a Candidate in a scope where B has + `scope.contribute`. The original resource and Binding do not change. + +First-version exact grants behave as follows: + +| Family role | Allows | Does not allow | +| --- | --- | --- | +| `artifact.viewer` on a `family=memory` selector | Exact get of one `entry_version_id` | Search, list, changes, current head, revise, retire, or another entry/version | +| `artifact.viewer` | Exact get of one approved Experience or managed Skill Revision | Candidate read/review, later Revisions, publication, or lineage bodies | +| `artifact.viewer` on `family=prompt` | Exact get of one approved Prompt Revision | Render/use, later Revisions, or automatic injection | +| `prompt.user` | `artifact.viewer` plus explicit render/use | Changing instruction priority, enabling tools, or reading credentials | + +Ordinary user input remains Source evidence; the word “prompt” in its content does not make it a Prompt Artifact. A later +Prompt Artifact lifecycle may define reusable parameterized task templates. Internal prompts for Memory extraction, +Experience or Skill generation, and Handoff generation are Server implementation or configuration managed by +`server.admin`; they are not shared through `family=prompt` Artifact Bindings. Content that tells an Agent when to +apply a capability, how to perform it, and how to validate it should be a managed Skill rather than a duplicate Prompt +Artifact. + +An exact resource response may return lineage or citation identities defined by its schema, but the grant does not +propagate to those referenced resources. A general Source, Memory, or Artifact get still requires an independent +decision for the target. A Provider must not create `can_read` inheritance merely because “A references B.” + +## Sharing is a read-only snapshot, not collaborative editing + +An exact-resource Binding grants only read, explicit use, or a controlled publication operation to a Server-configured +target. It does not transfer content authority over the original resource. The Binding itself cannot authorize the +receiver to revise, retire, replace, commit a later Revision, or overwrite the shared content in place. If the receiver +separately has `scope.contribute` or stronger authority in the original scope, that write authority comes from the +independent scope role, not from the share. + +State produced by the receiver remains separate from the shared original: + +| Receiver operation | Constraint | +| --- | --- | +| Acknowledge a Handoff | Creates a separate Receipt and does not modify the Handoff Revision | +| Submit feedback or a change request | Creates separate feedback or a change request and does not modify shared content | +| Publish a managed Skill | Writes projection or state to a Server-configured target and does not modify the source Skill Revision | +| Fork, import, or copy | Requires `scope.contribute` on the destination scope; creates a new identity or Candidate with lineage to the original | + +Product surfaces should offer actions such as “View,” “Use,” “Acknowledge,” “Request changes,” “Copy to my scope,” or +“Publish to configured target.” They should not present an exact share as “Edit shared content.” Ongoing co-maintenance +requires a separate scope role. For an Artifact Family with Review, a contributor still creates a Candidate and uses +the Review lifecycle to produce a new Revision instead of editing an approved Revision in place. Revocation prevents +later access, but it cannot erase content already seen by the receiver or automatically revoke a Receipt, projection, +or fork that was previously created under independent authority. + +## Publishing a managed Skill + +Reading Skill content and publishing it to a configured host-local Agent target are different operations. A +publication request accepts only an exact managed Skill `ArtifactReference` and an opaque Server-configured +`target_id`. It does not accept a destination path, Agent home, SSH credential, or arbitrary filesystem locator. +Before it reads the Skill body, resolves `target_id`, inspects target host state, or writes a projection, the Server +must obtain both allow decisions on the same exact Skill Artifact: + +```text +artifact.read AND skill.publish on exact family=skill Artifact +``` + +`skill.publisher` binds only to one exact managed Skill Revision and grants both actions. `target_id` is an opaque +operation parameter configured by `server.admin`, not a `ResourceRef`, Access Binding, or authorization resource in +`/access/resources/list`. Only after authorization may the Server confirm that `target_id` is registered and resolve it +to host-local Agent projection configuration. An unknown or disabled target rejects publication. Host IDs, +destination paths, Agent homes, credential references, and locators do not enter the request, Binding, ordinary audit, +or public errors. + +An ordinary publisher selects a target through `POST /v1/skills/publication-targets/list`. The request contains the +`scope_id` and exact Skill `ArtifactReference`, and the Server reuses the two requirements above. It reads the Skill +Repository and target registry only after every decision allows access. The response lists only enabled targets and +their opaque `target_id`, Agent kind, installation scope, and safe capabilities. It does not return desired or applied +state, host paths, Agent homes, credential references, or underlying errors. This operation belongs to the Skill +publication domain contract; it is not Access Resource listing and creates no target Binding. + +```json +{ + "scope_id": "project:payments", + "artifact": {"family": "skill", "artifact_id": "retry-runbook", "revision": 4} +} +``` + +```json +{ + "artifact": {"family": "skill", "artifact_id": "retry-runbook", "revision": 4}, + "targets": [ + { + "target_id": "codex-project", + "agent_kind": "codex", + "installation_scope": "project", + "capabilities": ["publish"] + } + ] +} +``` + +The first version does not support per-target delegation. A Principal with `skill.publisher` on an exact Skill may +publish that Revision to any enabled configured target in the current deployment. Only `server.admin` may configure, +change, or remove targets. Target status is operational information protected by `server.observe` or `server.admin`. +If the product must express “B may publish to X but not Y,” a separate distribution RFC introduces a generic +`execution_target` Resource instead of mixing a Skill-specific target into the Artifact sharing model. + +Successful publication means only that the configured host-local target projection received the exact Revision. It +does not authorize the host to load or execute the Skill or to access tools, networks, filesystems, or secrets. +External Skill registrations and host-local locators are not cross-host shareable Artifact Family Access Profiles. +Collaboration requires an explicit import or fork into a managed Skill. Remote Receiver distribution is outside the +first version. + +## B takes over the Workstream + +Seeing a transfer does not grant execution authority. If B will work on the Workstream over time, A or an administrator +must separately grant `scope.contributor`: + +```text +handoff.receiver + = read one exact Handoff + inspect its citations + acknowledge it + +scope.contributor + = read the Workstream + contribute Sources + prepare/commit Handoffs + + acknowledge Handoffs + record Task Outcomes +``` + +PowerContext authorization governs only PowerContext resources and operations. The host, operating system, and +external services still govern Git changes, cloud APIs, production access, and credentials. A Handoff, Role Binding, +or Receipt cannot enlarge those permissions. + +## Long-term team collaboration + +A stable team can receive scope roles instead of a new Binding for each Revision: + +- `scope.viewer` reads Handoffs, Memory, approved Artifacts, Prompts, Sources, and read-only projections in the current + scope and may explicitly use approved Prompts; +- `scope.contributor` writes work evidence, Memory contributions, Handoffs, and Outcomes and proposes Artifact or Prompt + Candidates in addition to viewer access; +- `scope.reviewer` reviews Artifact Candidates in addition to viewer access; +- `scope.delegator` shares exact Handoffs with receivers in addition to viewer access; +- `scope.admin` administers all roles and policies for the scope. + +`scope.delegate` continues to authorize only viewer or receiver Bindings for `family=handoff` Artifacts in this RFC. In +the first version, only `scope.admin` may create exact Bindings for other Artifact Families. An existing Handoff +delegator does not silently gain a wider sharing boundary. A later resource-specific delegation action is an explicit +wire-contract change. `server.admin` manages publication targets through deployment configuration; targets do not +receive Access Bindings. + +These fixed roles are wire-contract vocabulary. An external PDP does not have to persist the same role names. It may +map organization roles, teams, or relationships to these actions. + +## Revocation and expiration + +A, the applicable grant administrator, or a scope administrator can revoke an exact Artifact Binding within its +administration boundary. For a Handoff, after revocation: + +- B's later read, Continue, and acknowledge requests return 403; +- B no longer sees the Handoff in `resources/list`; +- the saved Handoff, Receipt, and Access Audit remain intact; +- content already displayed, exported, or copied by B cannot be recalled remotely. + +The PDP evaluates expiration against trusted Server time. If an adapter cannot enforce conditions or expiration, it +must reject creation of an expiring Binding instead of silently creating permanent access. + +A role change uses revoke + create rather than updating `handoff.viewer` in place to `handoff.receiver`. Revocation +uses `expected_version`; a concurrent change returns 409. + +## The authorization service is unavailable + +Authorization is a security dependency. In enforced mode: + +- a missing or unverifiable identity returns 401; +- a valid identity with insufficient authority returns 403; +- an unavailable PDP, Binding Store, or safe resource filter returns 503; +- the Server does not fall back to a global token, an empty Principal, or allow-all when a PDP fails; +- `/health/live` still reports process liveness while `/health/ready` reports the required authorization dependency as + not ready. + +A 403 response does not distinguish “the resource does not exist” from “the resource exists but is not visible.” The +Repository may return 404 only after authorization succeeds, preventing resource enumeration. + +# Reference-level explanation + +## Goals and non-goals + +This RFC aims to: + +- establish one Server PEP in front of HTTP, MCP, and the Dashboard; +- establish a Principal from a credential without allowing the request to override it; +- support scope-level RBAC and exact Handoff receiver Bindings; +- define stable Resource Kinds and an Artifact Family Access Profile contract, with exact authorization for Handoff, + Memory, Experience, Skill, and Prompt resources; +- resolve evidence cited by an exact Handoff safely without opening the complete scope; +- separate resource reads, context selection, Skill publication, and host execution authority; +- provide a replaceable decision interface and an optional relationship mutation interface; +- provide APIs for self-checks, resource discovery, Binding administration, and audit; +- fail closed for direct reads, lists, pagination, the internal MCP bridge, and background operations; +- preserve the domain purity of the current Runtime, Source, Memory, Handoff, and Work application APIs. + +This RFC does not define: + +- user registration, passwords, MFA, an OIDC Provider, or token issuance; +- a custom role DSL, wildcard scopes, organization hierarchy, or a group directory; +- anonymous bearer share links or authority embedded in Handoff content; +- authorization for Git, filesystems, tools, networks, model Providers, or credentials; +- redaction, cross-organization export, legal hold, or retention policy; +- approval workflows, temporary elevation, or an Agent requesting more authority automatically; +- PowerContext as a general-purpose IAM product; +- multi-writer collaborative editing of an exact shared resource or ownership transfer through a Binding; +- dynamic subscription sharing for Memory collections, Artifact catalogs, or resources that follow `latest`; +- the Prompt Artifact content schema, variable language, Review lifecycle, or host instruction-precedence policy; +- per-target publication delegation or a general `execution_target` Resource; +- remote managed Skill projection or a Receiver distribution contract; or +- cross-host locators, automatic installation, or package distribution for External Skills. + +## Trust model and invariants + +An implementation must preserve these invariants: + +1. `scope_id` is a business partition value, not proof of authority. +2. A Principal comes only from authentication middleware or trusted internal bridge context. +3. A `receiver`, `subject`, `actor`, role string, or Handoff prose in a request body cannot replace the current + Principal. +4. Handoff, Memory, Artifact, and Prompt content is `untrusted_history` or untrusted instruction and cannot grant an + action. +5. `is_internal_bridge()` may skip repeated transport authentication but never authorization. +6. Every protected operation receives a decision before it accesses a Repository or application service. +7. An exact Handoff grant does not allow `latest` and does not cover other Revisions of the same Artifact. +8. An `accepted` Receipt does not create, update, or inherit an Access Binding. +9. A model may suggest a receiver or explain a denial, but it cannot choose a canonical Principal or invoke an + allow-all fallback. +10. An exact Memory Entry grant consists of an exact `family=memory` `ArtifactReference` and a complete `memory_entry` + selector. Every other exact Artifact grant contains a positive integer Revision; none allows `latest` or inherits + to later Revisions. The Server derives the Access Profile only from `ArtifactReference.family`; it rejects an + independent content profile, an unknown Family, or a selector mismatch. +11. Reading Memory, Artifact, or Prompt content does not grant its lineage or citation targets and does not place it in + PreparedContext automatically. +12. An exact-resource Binding does not grant revise, retire, replace, commit-next-Revision, or any other mutation of + shared content. Receipts, feedback, projections, and forks are separate resources or operations that require + independent authorization and do not modify the original resource identity, content, or Revision. +13. `prompt.use` does not change host instruction precedence. `skill.publish` does not authorize host loading, + execution, tools, networks, filesystems, or secrets. +14. Skill publication requires both `artifact.read` and `skill.publish` on the exact `family=skill` Artifact before + resolving `target_id` or performing any host or filesystem inspection. `target_id` is not an authorization + resource, and the first version resolves only configured host-local targets. +15. Public errors, logs, metrics, and traces do not contain credentials, Handoff, Memory, Artifact, or Prompt content, + Source bodies, target locators, or raw PDP responses. + +## Principal model + +`PrincipalRef` uses the stable opaque identity established by an authentication Provider: + +```json +{ + "type": "user", + "issuer": "https://id.example.com/", + "id": "00u-bob" +} +``` + +The fields mean: + +| Field | Semantics | +| --- | --- | +| `type` | `user`, `service`, or a later registered Principal type | +| `issuer` | The trusted issuer that established the identity; local credentials use a deployment-specific issuer | +| `id` | A stable opaque subject within that issuer, not a display name or email address | + +Agent names, hosts, session IDs, and model names are provenance, not Principals by default. When an enterprise token +proves an on-behalf-of actor, an authentication adapter may add that actor to trusted request context; a PDP may then +constrain both subject and actor. A client cannot assert that actor in a JSON body. + +The existing Handoff Receipt `receiver` remains record content. The Server separately records the authenticated +Principal that produced the Receipt. If they differ, the Server rejects `accepted` or explicitly records the mismatch +for a non-accepted Receipt. It never treats the free-form `receiver` as a Principal. + +## Resource model + +Internal authorization requests use structured `ResourceRef` values. This avoids concatenating identifiers that may +contain `:`, `/`, or user data into policy strings: + +| Resource Kind | Identity | Parent | +| --- | --- | --- | +| `server` | Deployment identifier | None | +| `scope` | Exact `scope_id` | Server | +| `artifact` | Exact `ArtifactReference`, optional Family-owned selector, and `scope_id` | Scope | + +`ResourceRef` is an OpenAPI discriminated union. Each variant uses `additionalProperties: false` and accepts only these +fields: + +| `type` | Required identity fields | +| --- | --- | +| `server` | `deployment_id` | +| `scope` | `scope_id` | +| `artifact` | `scope_id`, `reference`, and optional `selector` | + +An ordinary Artifact Revision has no selector: + +```json +{ + "type": "artifact", + "scope_id": "project:payments", + "reference": {"family": "experience", "artifact_id": "exp-retry-budget", "revision": 3} +} +``` + +Memory Entry uses an exact selector owned by the `memory` Family. The combination of `reference` and `selector` is a +complete `MemoryCitation`: + +```json +{ + "type": "artifact", + "scope_id": "project:payments", + "reference": {"family": "memory", "artifact_id": "memory", "revision": 18}, + "selector": { + "type": "memory_entry", + "entry_id": "retry-policy", + "entry_version_id": "01K..." + } +} +``` + +`ArtifactResourceRef.reference.family` is the only Artifact Family Access Profile discriminator. A request contains no +separate `profile` field. The Server derives the Profile from the validated exact `ArtifactReference`, avoiding +conflicts such as `profile=prompt` with `family=skill`. Each Family declares its selector required, forbidden, or one +specific discriminated-union variant. The first version requires a `memory_entry` selector for `memory` and forbids a +selector for `handoff`, `experience`, `skill`, and `prompt`. + +The Family registry is a fixed Server-owned contract, not an administrator-editable policy DSL. Every registration +contains at least: + +| Field | Requirement | +| --- | --- | +| `family` | Stable name that exactly matches `ArtifactReference.family` | +| `share_unit` | `revision` or one explicit Family-owned selector type | +| `shareable_states` | Lifecycle states in which a Binding may be created | +| `base_action` | `artifact.read` in the first version | +| `additional_actions` | Family-specific use, acknowledge, or publish actions | +| `grantable_roles` | Fixed exact roles compatible with the Family | +| `parent_implications` | Child actions implied by scope roles in one direction | +| `transitivity` | Whether lineage, citations, or other related resources need separate decisions; the default is none | +| `resolver` | How to resolve the exact resource after authorization and which safe identity to return | + +The first-version registry is: + +| Artifact Family | Share unit | Shareable state | Exact actions | Grantable exact roles | +| --- | --- | --- | --- | --- | +| `handoff` | Revision | committed | `artifact.read`, `handoff.evidence.read`, `handoff.acknowledge` | `handoff.viewer`, `handoff.receiver` | +| `memory` | `memory_entry` selector | active in the referenced Revision | `artifact.read` | `artifact.viewer` | +| `experience` | Revision | approved | `artifact.read` | `artifact.viewer` | +| `skill` | Revision | approved | `artifact.read`, `skill.publish` | `artifact.viewer`, `skill.publisher` | +| `prompt` | Revision | approved | `artifact.read`, `prompt.use` | `artifact.viewer`, `prompt.user` | + +A Prepared Handoff has no persistent identity and cannot receive an exact Access Binding. A least-privilege cross-user +transfer must be committed first. A pending or rejected Candidate likewise cannot receive an Artifact Binding. Even a +new Family that reuses only `artifact.read` must be registered explicitly as shareable. Unknown, disabled, or +selector-incompatible Families are denied by default. `revision=latest`, an `entry_id` alone, a Memory current head, or +a search query is not a stable authorization identity. Later Artifact Revisions and Memory Entry Versions do not +inherit an exact Binding. + +Each Resource Kind defines a stable canonical serialization for adapter object IDs. An Artifact key includes +`scope_id`, `family`, `artifact_id`, a positive integer `revision`, and the complete selector. The same business +identity produces the same key over HTTP, MCP, and the Dashboard. Different Families or selectors cannot share a +Binding through string collisions. + +An adapter maps a structured ResourceRef to an external PDP object ID. The mapping must be canonical and stable, and +must not write email addresses, tokens, resource content, publication target locators, or other PII into Casbin policy, +OpenFGA tuples, or audit keys. + +## Action vocabulary + +First-version actions are stable lowercase dotted strings: + +| Action | Resource | Meaning | +| --- | --- | --- | +| `server.observe` | server | Read service-level operations and observability data | +| `server.admin` | server | Administer deployment access and publication-target configuration | +| `scope.read` | scope | Read general resources, approved content, and projections in a Workstream | +| `scope.contribute` | scope | Write Sources, Memory contributions, Handoffs/Outcomes, and propose Artifact/Prompt Candidates | +| `scope.review` | scope | Review Artifact Candidates in the scope | +| `scope.delegate` | scope | Create viewer or receiver Bindings for exact Handoffs | +| `scope.admin` | scope | Administer roles, Bindings, and policy for the scope | +| `artifact.read` | exact artifact | Read the exact Revision or selector defined by its Family Profile | +| `handoff.evidence.read` | `family=handoff` artifact | Resolve that Revision's citation manifest through the Handoff resolver | +| `handoff.acknowledge` | `family=handoff` artifact | Create a Handoff Receipt for that Revision | +| `prompt.use` | `family=prompt` artifact | Explicitly render or attach an authorized Prompt without deciding host instruction precedence | +| `skill.publish` | `family=skill` artifact | Discover safe target choices and select one exact managed Skill Revision for publication | + +`artifact.read` has one meaning across every Family: read only the exact Revision or selector named by the Binding. It +does not include Handoff evidence, Prompt use, Skill publication, lineage bodies, or any mutation. A Family adds a +semantic action only for an operation with a genuinely different security effect. + +Business operations check actions rather than role names. External role and relationship models can therefore evolve +without changing application code. + +Policy may make `scope.read` imply `artifact.read` for every registered Family, `handoff.evidence.read` for Handoffs, +and `prompt.use` for Prompts under the scope. `scope.contribute` may imply acknowledge, prepare, commit, Memory +contribution, Artifact or Prompt Candidate proposal, and Outcome writes. The reverse implication never holds: an exact +viewer or user role does not gain `scope.read` or `scope.contribute`. +`scope.read` does not imply `skill.publish`. + +## Built-in roles + +| Role | Granted actions | +| --- | --- | +| `handoff.viewer` | `artifact.read`, `handoff.evidence.read` on one exact `family=handoff` Artifact | +| `handoff.receiver` | Viewer actions plus `handoff.acknowledge` on one exact Handoff | +| `artifact.viewer` | `artifact.read` on one compatible exact Artifact Revision or selector | +| `prompt.user` | `artifact.read`, `prompt.use` on one exact `family=prompt` Artifact | +| `skill.publisher` | `artifact.read`, `skill.publish` on one exact managed Skill Revision | +| `scope.viewer` | `scope.read` | +| `scope.contributor` | `scope.read`, `scope.contribute` | +| `scope.reviewer` | `scope.read`, `scope.review` | +| `scope.delegator` | `scope.read`, `scope.delegate` | +| `scope.admin` | Every scope and child Artifact Family action, including delegation and Binding administration | +| `server.observer` | `server.observe` | +| `server.admin` | Every server, scope, and Artifact Family action | + +Every exact-resource role is read-only with respect to its bound content. `handoff.receiver` adds only the creation of +a separate Receipt. `skill.publisher` adds only a projection write to a Server-configured target. Neither +role may modify the source Handoff or Skill Revision. Mutation of the original resource requires an independent scope +role and the relevant domain lifecycle. + +The first version does not allow the public API to create roles or change role-to-action mappings. Fixed roles give +OpenAPI, the Dashboard, and adapter conformance tests stable semantics. An enterprise PDP may map custom organization +roles to the actions externally. + +A Principal with `scope.delegate` may create only `handoff.viewer` or `handoff.receiver`, and only for an existing +exact Handoff in that scope. Creating a scope role requires `scope.admin`. Creating `server.admin` requires an existing +`server.admin` and permission from deployment policy. A Principal cannot grant itself authority beyond the caller's +administration boundary. + +In the first version, only `scope.admin` may create `artifact.viewer`, `prompt.user`, or `skill.publisher` Bindings in +an administered scope. `artifact.viewer` may bind only to an exact Revision or selector declared compatible by the +Family registry. `prompt.user` and `skill.publisher` may bind only to approved `family=prompt` and `family=skill` +Artifacts, respectively. A role and Artifact Family Access Profile or Resource Kind mismatch returns 422; insufficient +authority returns 403. The +Server must not forward an incompatible role string unchanged to an external RelationshipWriter. + +| Resource or Artifact Family Profile | Grantable exact roles | Binding administrator | +| --- | --- | --- | +| `artifact` with `family=handoff` | `handoff.viewer`, `handoff.receiver` | `scope.delegate`, `scope.admin`, or `server.admin` | +| `artifact` with `family=memory` and a `memory_entry` selector | `artifact.viewer` | `scope.admin` or `server.admin` | +| `artifact` with `family=experience` | `artifact.viewer` | `scope.admin` or `server.admin` | +| `artifact` with `family=skill` | `artifact.viewer`, `skill.publisher` | `scope.admin` or `server.admin` | +| `artifact` with `family=prompt` | `artifact.viewer`, `prompt.user` | `scope.admin` or `server.admin` | + +## Authorization request and decision + +The PowerContext decision model aligns with the subject, action, resource, and context shape of the OpenID AuthZEN +Authorization API, but the Python protocol does not require an HTTP PDP: + +```python +class AuthorizationProvider(Protocol): + async def check(self, request: AccessRequest, /) -> AccessDecision: ... + + async def check_batch( + self, + requests: Sequence[AccessRequest], + /, + ) -> Sequence[AccessDecision]: ... + + async def resolve_resource_filter( + self, + request: ResourceSearchRequest, + /, + ) -> AuthorizedResourceFilter: ... +``` + +A normalized request is: + +```json +{ + "subject": { + "type": "user", + "issuer": "https://id.example.com/", + "id": "00u-bob" + }, + "action": {"name": "artifact.read"}, + "resource": { + "type": "artifact", + "scope_id": "project:payments", + "reference": { + "family": "handoff", + "artifact_id": "project:payments", + "revision": 12 + } + }, + "context": { + "request_id": "pc-01K...", + "transport": "mcp" + } +} +``` + +`AccessDecision` contains at least: + +```json +{ + "allowed": true, + "reason_code": "role_binding", + "policy_revision": "42" +} +``` + +`reason_code` is a stable, low-sensitivity enum for audit and diagnostics. A business 403 response does not expose a +provider rule, tuple, URL, stack, or raw body. `policy_revision` correlates audit and cache behavior to a defined +policy; it is not an authorization token. + +`check_batch` preserves input order and returns one decision for each item. An adapter cannot use one allowed item to +permit a complete batch. + +A business operation may resolve to one or more `ResolvedAccessRequirement` values. The first version supports only +the `all` combination. The PEP uses one `check_batch`, or semantically equivalent point checks, and calls no Repository, +application service, target adapter, or filesystem unless every decision allows access. This is not a client-authored +Boolean policy DSL. + +For example, managed Skill publication resolves to: + +```json +{ + "combination": "all", + "requirements": [ + { + "action": {"name": "artifact.read"}, + "resource": { + "type": "artifact", + "scope_id": "project:payments", + "reference": {"family": "skill", "artifact_id": "retry-runbook", "revision": 4} + } + }, + { + "action": {"name": "skill.publish"}, + "resource": { + "type": "artifact", + "scope_id": "project:payments", + "reference": {"family": "skill", "artifact_id": "retry-runbook", "revision": 4} + } + } + ] +} +``` + +The business request's `target_id` does not enter these requirements. The Server resolves that parameter only after +both decisions allow access. + +Alternatives such as “scope role or exact role” do not require an `any` expression. The PEP requests the child-resource +action. A Provider uses a trusted parent relationship to decide whether a scope role implies that action, while an exact +Binding applies directly to the child. Providers therefore do not need an arbitrary nested policy expression language. + +`resolve_resource_filter` is required for safe list operations. An `AuthorizedResourceFilter` is specific to the +current Principal and action. It contains bounded canonical resource keys produced by exact Bindings and bounded +server or scope constraints produced by parent roles. A parent constraint means that a Repository may query only +within that parent, requested Resource Kind, and Family; it is not a client-authored wildcard. The filter also carries +the policy revision. The Server validates its structure and bounds, then pushes the union of exact keys and parent +constraints into one Repository query before totals, ordering, or pagination are computed. + +The built-in Provider derives exact keys and parent constraints directly from its Binding Store, so it does not mirror +the complete Artifact catalog. An external Provider returns an equivalent authorization filter, or its adapter builds +one from trusted relationship search. A point-check-only Provider that cannot produce this filter must not query all +Artifacts, Projects, or Scopes and filter them afterward. The affected list operation returns 503, or configuration +reports `safe_resource_filtering=false`. + +## Relationship administration + +AuthZEN defines decision interoperability, not the relationship mutation interface for every PDP. Administration is +therefore separate from decisions: + +```python +class RelationshipWriter(Protocol): + async def create_binding( + self, + request: CreateAccessBinding, + /, + ) -> AccessBinding: ... + + async def revoke_binding( + self, + binding_id: str, + /, + *, + expected_version: int, + ) -> AccessBinding: ... +``` + +The built-in Provider and Casbin or OpenFGA adapters may implement both `AuthorizationProvider` and +`RelationshipWriter`. An OPA, Cerbos, or generic AuthZEN adapter may provide decisions only. Its PowerContext Binding +mutation endpoint then returns `relationship_management_unavailable`, and administrators configure relationships in +the external system. The Server must not report a successful grant and then write only a local shadow record. + +## Access Binding model + +The built-in Binding Store records at least: + +| Field | Requirement | +| --- | --- | +| `binding_id` | Server-generated opaque ID | +| `subject` | Canonical `PrincipalRef` | +| `resource` | Canonical exact `ResourceRef` | +| `role` | One fixed role name | +| `granted_by` | Authenticated Principal recorded by the Server | +| `reason` | Optional bounded human explanation | +| `created_at` | Trusted Server time | +| `expires_at` | Optional trusted expiration | +| `state` | `active` or `revoked` | +| `version` | Monotonically increasing CAS version | +| `policy_revision` | Policy version after mutation when available | +| `idempotency_key` | Bounded caller key scoped to grantor and resource | + +A role, subject, or resource change revokes the old Binding and creates a new one. A retry with the same grantor, +idempotency key, and payload returns the original Binding. The same key with a different payload returns 409. +Expiration does not delete a record; the decision treats it as denied. + +The built-in Binding Repository belongs to a Server access-control component. It is not added to the Runtime +`context`, `source`, `memory`, `artifact`, `handoff`, or `work` application object. It may share a deployment +database with the Server, but it owns an independent schema, migrations, and API. + +## Public Access API + +The OpenAPI source of truth adds these operations: + +| Operation | Purpose | Authorization | +| --- | --- | --- | +| `GET /v1/access/me` | Return the current Principal and access-control capabilities | Authenticated Principal | +| `POST /v1/access/check` | Check one action/resource for the current Principal | Current Principal only | +| `POST /v1/access/check-batch` | Batch checks for the current Principal | Current Principal only | +| `POST /v1/access/resources/list` | List resource identities available to the current Principal | Current Principal only | +| `POST /v1/access/roles/list` | Return fixed roles and action vocabulary | Authenticated Principal | +| `POST /v1/access/bindings/list` | List Bindings the caller may administer | `scope.delegate`, `scope.admin`, or `server.admin` | +| `POST /v1/access/bindings/create` | Create a Family-compatible exact-resource or administrative Binding | Resource-specific administration action | +| `POST /v1/access/bindings/revoke` | Revoke a Binding using CAS | Same administration boundary | +| `POST /v1/access/audit/list` | Query security audit events | `scope.admin` or `server.admin` | + +`check`, `check-batch`, and `resources/list` do not accept a client-selected subject. They evaluate only the current +authenticated Principal, preventing ordinary users from using the API as a personnel permission oracle. +Administrator checks for another Principal, subject search, and directory integration are deferred. + +`bindings/create` necessarily accepts a recipient subject so A can name B, but the caller can create only fixed roles +on resources it may administer. The Server validates structure and role compatibility through the Resource Kind and +Artifact Family registry, performs the grant-administration check, and only then reads a Repository +to confirm that the resource exists, belongs to the declared parent, and is in an authorizable state. A nonexistent +and an invisible resource both return 403 to an unauthorized caller. A 404 or Family-specific conflict is available +only after the administration decision allows access. + +The Access API does not create, modify, fork, render, or publish business resources. Memory, Artifact, Prompt, and +managed Skill publication operations retain their own contracts. Publisher-safe target selection belongs to the Skill +publication contract; target configuration and operator status are Server operations. None enters the Access API or +creates a target Binding. A Binding expresses only who may perform which action on an existing resource. + +The public `check` operation may return HTTP 200 with `allowed=false`. The same denial on a business operation returns +403 and does not call the application service. The Access API supports explanation and UI preflight; it never replaces +enforcement when the business request runs. + +## Handoff operation requirements + +The first-version Handoff mappings are: + +| Operation | Required authorization | +| --- | --- | +| `prepare_handoff`, `finalize_handoff`, `handoff_current_work` | `scope.contribute` on request `scope_id` | +| `commit_handoff` | `scope.contribute` on request `scope_id` | +| `continue_handoff(selection=latest)` | `scope.read` on request `scope_id` | +| `continue_handoff(selection=exact)` | `artifact.read` and `handoff.evidence.read` on the exact `family=handoff` Artifact, directly or through parent `scope.read` | +| `continue_handoff(selection=prepared)` | `scope.read` on request `scope_id` | +| `acknowledge_handoff` with an exact Receipt | `scope.contribute` or `handoff.acknowledge` on the exact Revision | +| `record_task_outcome` | `scope.contribute` on request `scope_id` | +| Aggregate Handoff Report queries | Scope-level read; an exact Handoff grant is insufficient | +| Handoff Report administration | `scope.admin` or an appropriate server administration action | + +When an exact receiver calls Continue, the request provides `selection=exact` and an exact `ArtifactReference`. The +Server builds the Handoff ArtifactResourceRef and evaluates it before reading the Revision. It cannot resolve latest before +the check or fall back to latest when the exact Revision is absent. + +A Prepared Handoff may contain complete caller-supplied content, so the narrow grant path does not accept +`selection=prepared`. Only a Principal with `scope.read` may use a prepared selection to resolve scope evidence. + +## Artifact Family operation requirements + +Family operations map as follows. “Scope or exact” behavior is implemented by Provider parent relationships, not by a +client-selected bypass path: + +| Operation family | Required authorization | +| --- | --- | +| Memory search/list/changes | `scope.read` on request `scope_id`; an exact Memory grant is insufficient | +| Exact Memory get | `artifact.read` on an exact `family=memory` Artifact plus complete `memory_entry` selector, directly or through parent `scope.read` | +| Memory flush/remember/revise/retire | `scope.contribute`; an exact viewer grant is insufficient | +| Approved Experience/managed Skill exact get | `artifact.read` on an exact `ArtifactReference`, directly or through parent `scope.read` | +| Experience/Skill propose or generate | `scope.contribute` | +| Candidate list/get | `scope.read`; an exact Artifact grant does not expose Candidates | +| Candidate revise/approve/reject | `scope.review` | +| Approved Prompt exact get | `artifact.read` on an exact `family=prompt` Artifact, directly or through parent `scope.read` | +| Approved Prompt render/use | `prompt.use`, directly or through parent `scope.read` | +| Prompt propose/revise | Candidate operation defined by the Prompt lifecycle plus `scope.contribute` | +| List enabled publication targets for an exact managed Skill | `artifact.read` **and** `skill.publish` on the same exact `family=skill` Artifact | +| Publish managed Skill | `artifact.read` **and** `skill.publish` on the same exact `family=skill` Artifact | + +An exact-get resolver obtains the complete identity directly from a validated request. A Memory `entry_id`, Artifact +`artifact_id`, or Prompt name alone is not an authorization key. Search, current-head selection, aggregate projections, +and the Candidate Inbox remain collection operations; an exact grant cannot enter them. + +The Prompt Family Access Profile specifies authorization vocabulary and resolver behavior only. A deployment reports +that Family as enabled only after it registers an immutable approved `family=prompt` Artifact lifecycle and exposes +exact get and use operations consistent with this section. A version without Prompt domain operations may implement +other Families, but it must reject `family=prompt` Bindings and must not claim `prompt.user` is usable in `roles/list`. + +`target_id` is a Server-configured publication operation parameter, not an authorization key or Resource. Only +`server.admin` may configure, modify, or remove a target; `server.observe` or `server.admin` protects detailed target +status. An operator status response contains only target ID, Agent kind, capabilities, desired and applied exact +Revisions, a stable state, and a safe reason code. It does not expose host paths, Agent homes, credentials, or raw OS +errors. For publication and publisher target-list requests, the Server must allow both requirements on the exact Skill +before resolving `target_id` or reading the target registry. A standalone operator status request first checks the +server-level action. + +## OpenAPI access metadata + +Every protected operation declares `x-powercontext-access` in `openapi/powercontext.yaml`. The generator includes the +extension as `Operation.access`; Server `_add_route()` uses it to assemble the PEP wrapper. For example: + +```yaml +/v1/handoff/commit: + post: + operationId: commit_handoff + x-powercontext-access: + action: scope.contribute + resource: + type: scope + scope-id-from: body.scope_id +``` + +An operation whose policy depends on selection names a registered resolver rather than embedding executable +expressions in YAML: + +```yaml +x-powercontext-access: + resolver: continue_handoff_access +``` + +A resolver is deterministic, Server-owned, and unit-tested. It builds an AccessRequest only from the validated request +model and route metadata. It cannot read a business Repository before deciding what to authorize. + +Operations that need multiple requirements use a resolver. Publisher target selection and publication reuse the same +exact Skill resolver: + +```yaml +/v1/skills/publication-targets/list: + post: + operationId: list_skill_publication_targets + x-powercontext-access: + resolver: publish_managed_skill_access + +/v1/skills/publish: + post: + operationId: publish_managed_skill + x-powercontext-access: + resolver: publish_managed_skill_access +``` + +Generated `Operation.access` represents either one static requirement or a named resolver. The Server-side resolver +return type supports multiple `all` requirements. Generated transports do not duplicate policy logic; they carry the +current Principal and invoke the same Server operation. + +Health endpoints, static page shells, and authentication callbacks may be explicitly public. A new business operation +without access metadata fails contract generation or contract tests; it never defaults to public. + +## Server PEP + +Request order is fixed: + +```text +transport authentication + -> bind Principal and trusted request context + -> validate request schema + -> resolve action and resource + -> AuthorizationProvider decision + -> application service + -> response +``` + +Schema validation and Family/selector compatibility validation that does not access a Repository may run before the +decision to establish a resource identity safely, but validation errors do not expose resource content. Every +Repository lookup, Handoff resolution, Memory search, Artifact Family read, target lookup, host inspection, Report +aggregate, and mutation runs only after all required decisions allow access. + +The PEP lives in the Server adapter. It does not add `principal`, role, or permission parameters to +`application.context.for_scope(...)` or to Source, Memory, Handoff, Work, or Review domain methods. Local in-process +Runtime calls do not gain Server authentication automatically. A local integration that needs a security boundary +uses the same Access Control service or calls through the Server. + +## HTTP, MCP, and Dashboard parity + +HTTP is the complete remote contract. MCP and the Dashboard reuse the same operations and PEP: + +- HTTP authentication establishes a Principal before the authorization wrapper runs for each operation; +- the MCP internal ASGI bridge propagates the original Principal, actor, and request ID in request-local context; +- `is_internal_bridge()` can avoid parsing the same external credential twice, but the authorization wrapper still + runs; +- MCP tool discovery may filter unavailable tools for the current Principal, but hiding a tool is only UX and each + invocation still receives a decision; +- the Dashboard uses `access/me`, authorized resource listing, and batch checks to show a Handoff inbox or “Shared with + me” view and disable or hide unavailable actions, but it cannot bypass API enforcement; +- a background job carries the service Principal bound when it was created or an explicit system Principal, never an + empty identity. + +HTTP and MCP return the same allow or deny for the same Principal, action, resource, and policy revision. Adapter +conformance tests protect that guarantee. + +## Listing and pagination + +Lists can leak Project names, scope IDs, Artifact Family identities, Handoff objectives, or Candidate metadata. The +safe order is: + +```text +AuthorizationProvider.resolve_resource_filter + -> validate bounded exact keys and parent constraints + -> Repository query applying their union + -> stable pagination + -> response +``` + +This implementation is prohibited: + +```text +Repository.list_all -> page -> check each item -> remove denied rows +``` + +It leaks totals, cursors, holes, and timing, and can prevent an authorized user from ever reaching later rows. The +Repository applies the union of exact keys and parent constraints in one query. `total`, cursors, and page boundaries +describe only the authorized collection. + +An exact Artifact receiver discovers granted resources through Resource Kind and Family filters on +`/v1/access/resources/list`. This does not place those resources in aggregate Project, Workstream, Memory search, +Artifact catalog, or Candidate Inbox results. Only scope-level read permits the corresponding aggregate query. A +publication target is not an authorization resource and does not appear in this list. A Principal authorized to +publish the exact Skill obtains redacted target choices through the Skill-domain preflight. Detailed operational +status is queried through a Server operation protected by `server.observe` or `server.admin`. + +## Audit and diagnostics + +Access Audit is an append-only Server security record. It contains at least: + +- request ID, time, transport, and operation ID; +- the Principal's opaque identifier and trusted actor identifier, if present; +- action, Resource Kind, optional Artifact Family, and opaque resource identity; +- allow or deny, stable reason code, and policy revision; +- for Binding creation or revocation, binding ID, grantor, recipient subject, role, and expected/result version. + +Audit does not contain: + +- Bearer tokens, cookies, client secrets, or PDP credentials; +- Handoff objectives, state, or next action; +- Source, Memory, Artifact, Prompt, PreparedContext, or citation bodies; +- publication-target locators, host paths, credential references, or raw Receiver or OS errors; +- arbitrary exception fields, configured PDP URLs, or raw provider responses; +- email addresses, display names, or unnecessary directory attributes. + +Ordinary logs, metrics, and traces use the same data-minimization boundary. Public readiness returns only stable +component states and safe reasons. Detailed provider diagnostics stay in a protected operator channel. + +## Consistency and failure recovery + +Committing a Handoff and creating an external authorization relationship are not a disguised cross-system +transaction. A “send to B” UI performs recoverable steps: + +1. commit or reuse the same exact Handoff Revision; +2. create the Binding using a stable idempotency key; +3. display “shared” only after both steps succeed; +4. if the second step fails, display “Handoff saved, but not yet visible to B” and retry only Binding creation; +5. do not prepare, commit, or create another Revision. + +When the Binding succeeded but the client lost the response, the same idempotency key returns the original Binding. +If an external RelationshipWriter cannot provide equivalent idempotency, its adapter performs a safe exact +relationship lookup first or declares self-service mutation unsupported. + +Every Artifact Family follows the same “persist or approve first, bind second” sharing rule. A failed Binding creation +does not roll back or recreate a business Revision; the client retries only the same idempotent Binding mutation. +Skill publication is a projection operation protected by two decisions. It creates no content Revision and +creates no target Binding or change to target authorization state. A failed target apply retains retryable +desired/applied state and a safe reason without placing local paths or underlying errors in public audit. + +Receipt creation retains the existing exact-selection and evidence rules. The decision occurs before the Receipt +transaction. If authority is revoked concurrently immediately after the check, a colocated Provider and Binding Store +use a policy revision or transaction fence to avoid an obvious stale write. A remote PDP has a bounded residual TOCTOU +window and records the decision revision. The first version does not cache allowed decisions. + +## Provider profiles + +### Built-in provider + +The built-in profile uses fixed roles and a Server-owned Binding Store. It supports point checks, batch checks, +pushdown `AuthorizedResourceFilter` generation from exact, scope, and server Bindings, creation, revocation, and audit. +It does not need a business-resource inventory. It is the reference semantics for local deployments and conformance +tests and does not provide passwords, a directory, or a custom policy language. + +### Casbin adapter + +A Casbin adapter can use RBAC with domains: + +- subject maps to an issuer-scoped opaque ID; +- domain maps a server resource to the deployment access namespace and a scope or Artifact resource to its canonical + scope resource namespace; +- object maps to a canonical server key, scope key, or Artifact key containing Family and selector; +- action uses this RFC's action vocabulary; +- role assignment and policy mutation use the Casbin management API and a persistence adapter. + +The Casbin domain is an adapter policy namespace. It does not turn `scope_id` into authentication or tenant proof. The +adapter derives the domain from a trusted ResourceRef supplied by the Server. For list filtering, exact-object policy +produces canonical keys while scope or server role assignments produce parent constraints; the Casbin adapter does not +enumerate the business Repository. + +### OpenFGA adapter + +OpenFGA naturally represents relationships among users, groups, scopes, and exact child resources. Every Artifact +Family uses one `artifact` object type. The object ID contains the canonical Family, Revision, and selector; the Server +validates relation compatibility through the Family registry before a tuple write. A new read-only Family therefore +does not require a new OpenFGA type: + +```text +type user + +type server + relations + define observer: [user] + define admin: [user] + define can_observe: observer or admin + define can_admin: admin + +type scope + relations + define parent: [server] + define viewer: [user] + define contributor: [user] + define reviewer: [user] + define delegator: [user] + define admin: [user] + define can_read: viewer or contributor or reviewer or delegator or admin or admin from parent + define can_contribute: contributor or admin or admin from parent + define can_review: reviewer or admin or admin from parent + define can_delegate: delegator or admin or admin from parent + define can_admin: admin or admin from parent + +type artifact + relations + define parent: [scope] + define viewer: [user] + define handoff_viewer: [user] + define handoff_receiver: [user] + define prompt_user: [user] + define skill_publisher: [user] + define can_read: viewer or handoff_viewer or handoff_receiver or prompt_user or skill_publisher or can_read from parent + define can_read_handoff_evidence: handoff_viewer or handoff_receiver or can_read from parent + define can_acknowledge_handoff: handoff_receiver or can_contribute from parent + define can_use_prompt: prompt_user or can_read from parent + define can_publish_skill: skill_publisher or can_admin from parent +``` + +The adapter maps `server.observe` to `server#can_observe` and `server.admin` to `server#can_admin`. `admin from parent` +continues to make deployment `server.admin` imply scope administration and child Artifact Family actions in one +direction. `server.observer` gains none of those permissions. + +The adapter uses an explicit authorization model ID for Check, ListObjects, and tuple writes. Tuples contain only +opaque IDs, never email addresses or Handoff content. Model migration switches the configured model ID explicitly; it +does not use an implicit latest model. +For lists, exact relations may produce canonical keys through ListObjects, while scope or server roles produce trusted +parent constraints directly. The adapter does not require an object tuple for every business Artifact that has no +exact Binding. + +### AuthZEN, OPA, and Cerbos adapters + +An AuthZEN adapter maps `AccessRequest` to the Authorization API subject, action, resource, and context and maps the +decision back to `AccessDecision`. An OPA adapter can submit the same structure as its input document. A Cerbos adapter +can map it to principal, resource, and actions. + +Decision interoperability does not imply policy administration interoperability. If an organization manages policy +through GitOps, IAM, or a separate administration plane, PowerContext consumes decisions and safe resource filters but +does not write policy. The deployment declares `relationship_management=false`, and the Dashboard does not present a +self-service share control that could report false success. An adapter that cannot build an `AuthorizedResourceFilter` +from PDP search or trusted relationship data also reports `safe_resource_filtering=false`. + +## Configuration and compatibility + +The Server provides three explicit modes: + +| Mode | Behavior | +| --- | --- | +| `disabled` | Preserve existing single-user, single-trust-domain behavior; Access API unavailable; no multi-user isolation claim | +| `legacy-static-admin` | Map the current static Bearer to a deployment-local `server.admin` Principal | +| `enforced` | Require both an authentication Provider and AuthorizationProvider; run the PEP for every business operation | + +An upgrade cannot fall back to `disabled` because external identity is configured but a PDP is missing. Mode is +explicit. Capabilities and readiness report the current mode and whether relationship management, batch checks, and +`safe_resource_filtering` are available. + +`disabled` is suitable only for a local environment whose caller already trusts the whole process and catalog. +Documentation cannot describe it as a secure multi-user configuration. Remote, multi-user, or shared-Dashboard +deployments use `enforced`. + +`access/me` and readiness also report enabled Resource Kinds and an `artifact_families` capability map. Each Family +entry contains at least `enabled`, `share_unit`, available actions, and grantable roles. For example, a deployment +without the Prompt lifecycle reports `prompt.enabled=false`. `operation_capabilities.skill_publication` separately +reports whether host-local managed Skill publication and publisher-safe target selection are available. It is true +only when the Skill Family, both domain operations, and at least one enabled host-local target are available; it is +not a Resource Kind or bindable profile. When a Provider lacks `safe_resource_filtering`, multi-requirement checks, or +relationship mutation, the relevant capability is false. The Server must not accept a Binding it cannot subsequently +enforce or revoke. + +```json +{ + "resource_kinds": ["server", "scope", "artifact"], + "provider_capabilities": { + "safe_resource_filtering": true, + "multi_requirement_check": true, + "relationship_management": true + }, + "artifact_families": [ + { + "family": "memory", + "enabled": true, + "share_unit": "memory_entry", + "actions": ["artifact.read"], + "grantable_roles": ["artifact.viewer"] + }, + { + "family": "prompt", + "enabled": false, + "share_unit": "revision", + "actions": [], + "grantable_roles": [] + } + ], + "operation_capabilities": { + "skill_publication": {"enabled": true} + } +} +``` + +Adding authorization metadata to an existing OpenAPI operation does not change its domain request or response schema, +but it adds a 403 response and changes unauthorized behavior. The generated Client maps 401, 403, and 503 to stable, +distinct exceptions; it does not treat 403 as an empty result. + +## Implementation slices + +Implementation proceeds in independently verifiable slices: + +1. **Contract and Principal**: OpenAPI Access models, operation metadata, generated `Operation.access`, trusted request + Principal, and stable errors. +2. **Built-in PEP/PDP**: fixed roles, Binding Store, `_add_route()` authorization wrapper, point/batch checks, and + audit. +3. **Exact Handoff receiver**: post-commit Binding creation, exact Continue, citation-manifest resolver, exact + acknowledge, revocation, and expiration. +4. **Artifact Family Access Profiles**: unified ArtifactResourceRef, Family registry, Memory selector, exact read/use + resolvers, role compatibility, and non-transitive lineage. +5. **Skill publication**: a Server-configured host-local target registry, publisher-safe selection, operator status, + read-plus-publish requirements on the same exact Skill, and redacted failure state. +6. **Safe listing and UI**: authorized resource listing, Handoff inbox, “Shared with me,” Dashboard permission projection, and + authorization-aware pagination. +7. **MCP parity**: Principal propagation through the internal bridge, tool-discovery UX, and invocation-time + enforcement. +8. **External adapters**: implement Casbin or OpenFGA first, then validate an AuthZEN-compatible PDP with the same + conformance suite. +9. **Migration**: legacy static admin, configuration validation, Family capabilities, readiness, and operator + documentation. + +Every slice leaves the Server in a coherent state. An intermediate release cannot protect only HTTP while MCP bypasses +the PEP, or hide only Dashboard controls without API enforcement. + +## Test and acceptance plan + +The implementation of this RFC is complete only when these observable scenarios pass: + +- an unauthenticated request to a protected operation returns 401; +- A with `scope.delegate` can grant B only an existing committed exact Handoff Revision in that scope, using + `handoff.viewer` or `handoff.receiver`; another Artifact Family or role returns 422, while a missing action returns + 403, and neither failure writes a Binding; +- B can read, Continue, and acknowledge the granted exact Revision; +- B is denied latest, adjacent Revisions, the aggregate Handoff Report, Memory lists, Source lists, and Task Outcome + writes; +- B reads manifest citations only through the authorized Handoff resolver and cannot submit an arbitrary citation to + a general read endpoint; +- `handoff.viewer` cannot acknowledge while `handoff.receiver` can; +- an `accepted` Receipt creates no Binding or scope role; +- after revocation or expiration, B's later access is denied and authorized resource listing omits the Revision; +- Binding creation and revocation have stable CAS, idempotency, and audit behavior; +- 403 does not leak resource existence, and list cursors and totals describe only the authorized collection; +- an unavailable PDP returns 503 without calling an application service, Repository, or mutation; +- the MCP internal bridge uses the original Principal and returns the same denial as HTTP; +- the API denies a request even when Dashboard controls are bypassed or fail to hide it; +- a legacy static token becomes local admin only in the explicit compatibility mode; +- `server.observer` can read protected service and publication status but cannot modify access or target configuration; + `server.admin` can perform both classes of operation, with equivalent Built-in, Casbin, and OpenFGA results; +- built-in, Casbin/OpenFGA, and AuthZEN adapters return equivalent decisions for the same conformance vectors; +- a request cannot submit an independent content profile; an unknown or disabled Family, `revision=latest`, a missing + or extra selector, or a Family-role mismatch returns 422 and writes no Binding; +- `artifact.viewer` always maps only to `artifact.read` for Experience, Skill, Prompt, and a `memory_entry` selector; + the Family never adds use, publish, acknowledge, or mutation implicitly; +- `artifact.viewer` can get an authorized Memory Entry through `family=memory` and a complete `memory_entry` selector, + but cannot search, list, select current, revise, retire, or read adjacent versions; +- an exact Artifact viewer can read an approved Experience or managed Skill Revision but cannot see Candidates, later + Revisions, or dereference lineage bodies; +- `artifact.viewer` may only read a Prompt while `prompt.user` may use it explicitly; neither role changes host + instruction precedence or places the Prompt in normal recall automatically; +- an exact-resource role cannot revise, retire, replace, or commit a later Revision of the shared original, even when + the request supplies the expected version; +- a Receipt created by acknowledgement and a target projection created by publication do not change the source + identity, content, Revision, or digest; +- a fork, import, or copy is denied without `scope.contribute` on the destination scope; when allowed, it creates a new + identity or Candidate and leaves the original unchanged; +- managed Skill publication runs only when both `artifact.read` and `skill.publish` allow access on the same exact + Skill; any denial or unavailable decision prevents `target_id` resolution, host-path inspection, and projection + writes; after authorization, an unknown or disabled target still rejects publication; +- the publisher target list reads the registry only after both requirements on the same exact Skill allow access and + returns only safe identities and capabilities for enabled targets; detailed status still requires `server.observe` + or `server.admin`; +- the first version rejects a remote Receiver target without reading remote credentials or opening a network + connection; +- `skill.publisher` may publish its authorized exact Skill to any enabled target in the deployment; the first version + has no target Binding or per-target delegation; +- `resources/list` totals, cursors, and rows describe only the selected Resource Kind and Artifact Family resources + discoverable by the current Principal; +- a deployment without a Prompt lifecycle rejects `family=prompt` Bindings; one without an available publication + operation reports `operation_capabilities.skill_publication.enabled=false`; and +- Access Audit contains no token, Handoff, Memory, Artifact, or Prompt content, Source body, target locator, or raw PDP + error. + +Cross-component acceptance scenarios belong in `tests/e2e/` and assert through the public HTTP and MCP contracts. +Focused tests cover the Family registry, selectors and canonical keys, resource resolvers, role mapping, Binding CAS, +provider failure, and citation membership without freezing private call order. + +# Drawbacks + +Every business request adds an authorization decision. A remote PDP adds a network dependency and latency. Safe lists +require a bounded pushdown `AuthorizedResourceFilter`, so a point-check-only adapter cannot support every Dashboard +list. + +An exact Handoff transfer must be committed first. A temporary Prepared Handoff cannot become a revocable cross-user +resource. That adds a persistence step but avoids inventing a second identity and ACL model for temporary payloads. + +Separating decisions from relationship management makes the adapter surface more complex than a single `check()`. +Assuming every external PDP lets PowerContext write policy would, however, make a false portability promise. + +Revocation blocks future access but cannot erase information a receiver has already read, captured, or exported. +Handoffs, Memory, Artifacts, or Prompts containing highly sensitive material still need content minimization, external +data classification, and export controls. + +Artifact Family Access Profiles add a registry, selectors, a role compatibility matrix, and conformance vectors. Skill +publication also checks `artifact.read` and `skill.publish` on the same exact Artifact. A remote PDP without an atomic +multi-requirement decision adds latency and a bounded TOCTOU risk whose policy revision must be recorded. + +The first version does not place targets in authorization policy. A Principal with `skill.publisher` on an exact Skill +may publish it to any enabled target in the deployment. A deployment that needs target-specific isolation must defer +the capability, isolate deployments, or wait for a separate RFC to define a generic `execution_target` Resource. This +RFC does not prematurely encode that model as a Skill-specific resource. + +The Prompt Family Access Profile defines only an authorization boundary. It cannot replace the Prompt Artifact +lifecycle or host instruction-precedence contract. A deployment reports that Family unavailable until those business +capabilities exist, so the RFC can deliver other Families first without claiming the complete product experience. + +Fixed first-version roles limit organization-specific UX. An enterprise can map custom roles in its external PDP, but +the PowerContext public API does not immediately provide a custom role editor. + +# Rationale and alternatives + +## Chosen: independent Server PEP plus replaceable PDP + +This design keeps Handoff, Memory, Artifact, Prompt, and Runtime models independent of the identity system +while giving HTTP, MCP, and the Dashboard one enforcement path. Stable action vocabulary maps across Casbin, OpenFGA, +OPA, Cerbos, and enterprise IAM more reliably than stable external role names. + +An AuthZEN-compatible request shape gives remote PDPs a standard integration point. A separate RelationshipWriter +accurately reflects that AuthZEN does not standardize all grant mutations. + +## Alternative: put ACL fields on Handoff or scope + +Adding `allowed_users` to Handoff or encoding owner and tenant into `scope_id` looks direct but mixes identity +lifecycle, group expansion, revocation, external policy revision, and audit into domain data. An immutable Handoff +should not receive a new Revision whenever team membership changes. This alternative is rejected. + +## Alternative: scope-level roles only + +Granting only `scope.viewer` is easy, but B then sees the complete Workstream's Memory, Sources, history, and Report. +That violates least privilege for a temporary relay. Scope roles remain available for long-term collaboration; +exact-resource Bindings serve one-off transfers or asset sharing. + +## Alternative: add one share API per domain + +`/memory/share`, `/experience/share`, `/skill/share`, and `/prompt/share` would duplicate Principal, Binding, expiration, +revocation, audit, and external-PDP semantics and make transport behavior likely to diverge. This RFC uses one Access +API with one ArtifactResourceRef, Family role compatibility, and resolvers. Each domain still owns its business API. + +## Alternative: one Resource Kind per Artifact Family + +Separate `ResourceRef.type` values for `handoff`, `memory_entry`, `experience`, `skill`, and `prompt` would duplicate +scope parentage, exact Revision identity, canonical keys, and read-only sharing structure. Every new Family would also +extend the OpenAPI discriminator and external PDP object types. More importantly, `ResourceRef.type` and +`ArtifactReference.family` would become two potentially conflicting content discriminators. This RFC uses one +`artifact` Resource Kind and lets the Server derive the Access Profile from `ArtifactReference.family`. Only a Family +such as Memory that needs a narrower authorization unit adds an explicit selector. + +## Alternative: recall every shared resource automatically + +Adding every exact grant to PreparedContext conflates visibility with relevance, expands token budgets, and lets an +untrusted Prompt or Skill affect a receiver's model without explicit selection. The first version provides authorized +discovery and explicit attachment only. A later shared collection or subscription still passes through an independent +Context selection policy. + +## Alternative: send an anonymous capability URL + +A bearer share link treats knowledge of a URL as identity. Links can enter chat, logs, browser history, or model +context. They make it hard to identify the actual receiver or apply enterprise group policy and individual audit. The +first version requires B's own authenticated identity and does not provide anonymous capability URLs. + +## Alternative: copy a redacted Handoff document + +Copying Markdown avoids Server authorization work but loses exact Revision, evidence availability, Receipt, +concurrency, and revocation semantics. Export may become an explicit external publication feature, but it cannot +replace a PowerContext-internal transfer. + +## Alternative: hide unauthorized Dashboard controls + +UI hiding improves experience but an HTTP or MCP caller can bypass it. Enforcement always occurs at the Server PEP; +the Dashboard only consumes the same decisions. + +## Alternative: require one policy engine + +Casbin fits embedded RBAC, OpenFGA fits relationships and groups, and OPA or Cerbos fits an existing policy platform. +Requiring one implementation either increases deployment cost or restricts enterprise integration. PowerContext +defines semantics and a conformance contract rather than one engine. + +## Alternative: store roles in access tokens + +Token roles are simple but poorly suited to exact Handoff grants, revocation, large resource sets, and policy updates. +A token may carry trusted identity and group claims, but the PDP still makes the final resource decision. + +## Alternative: authorize inside every Runtime method + +Passing a Principal into Context, Source, Memory, Handoff, and Work spreads transport policy through the domain, +encourages divergent HTTP and MCP implementations, and changes local domain APIs. The Server PEP is the single remote +trust-boundary enforcement point. + +# Prior art + +PowerContext [RFC 0011](0011_remote_access_architecture.md) defines HTTP as the complete contract with the generated +Client and MCP projection sharing Server application semantics. This RFC adds authentication and authorization at the +same Server boundary rather than creating a parallel MCP policy service. + +[RFC 0048](0048_handoff_artifact.md) defines Prepared Handoffs, immutable Handoff Revisions, Continue, and exact +evidence. [RFC 1223](1223_human_agent_work_continuity.md) defines Receipts and Task Outcomes and states that a transfer +does not grant tools, network access, or credentials. [RFC 0082](0082_handoff_report.md) provides scope- and +Project-level aggregate views. This RFC adds Principal-aware visibility to those reads and writes. + +[RFC 0050](0050_artifact_candidate_review_inbox.md) defines Experience and Skill Candidates and their Review gate; a +pending or rejected Candidate is not a shareable Artifact. [RFC 0051](0051_experience_skill_artifact_families.md) +defines exact Experience and managed Skill Revisions, host-local External Skill authority, and the boundary that +approval or publication does not grant execution. This RFC adds Principal-aware visibility and managed Skill +publication authorization without changing that content authority. + +The [OpenID AuthZEN Authorization API 1.0](https://openid.net/specs/authorization-api-1_0.html) defines the subject, +action, resource, context, and decision contract between PEPs and PDPs. This RFC aligns with that information model +while retaining an embedded Provider option. + +[Casbin RBAC with Domains](https://casbin.apache.org/docs/rbac-with-domains/) demonstrates domain-scoped role +assignment. [OpenFGA concepts](https://openfga.dev/docs/concepts) use user, relation, and object tuples for object-level +authorization. [OPA](https://www.openpolicyagent.org/docs/integration) provides a general policy decision integration. +[Cerbos CheckResources](https://docs.cerbos.dev/cerbos/latest/api/index.html) provides batch decisions over principals, +resources, and actions. These systems are adapter targets; they do not change the PowerContext Handoff lifecycle. + +# Unresolved questions + +The RFC must resolve these choices before merge, but they do not change the core security boundary: + +- whether the first external conformance adapter is Casbin or OpenFGA; +- whether the built-in Provider ships with the default Server extra or a separate optional extra; +- how the Dashboard selects a canonical recipient from the deployment identity directory; the Access API in this RFC + does not provide directory search; +- whether an enforced deployment requires `safe_resource_filtering` or may disable the corresponding Dashboard lists; +- whether deployment policy sets a default expiration for `handoff.receiver` or the UI requires an explicit choice; +- whether the UI suggests a separate `scope.contributor` grant after an exact receiver creates a Receipt, without ever + performing that upgrade automatically; +- whether the later Prompt Artifact lifecycle uses one fixed Review policy or distinguishes private personal templates + from organization-approved templates. + +Custom roles, organization hierarchy, cross-tenant export, anonymous share links, temporary elevation, approval +workflows, general Source object-level ACLs, dynamic Memory collections, Artifact catalog sharing, and automatic +following of future Revisions are explicitly deferred. They require separate threat models and RFCs. + +# Future possibilities + +The subject/action/resource contract can later support: + +- group, team, and organization relationships; +- Project-to-Workstream inheritance and explicit deny; +- administrator checks, subject/resource search, and access-review campaigns; +- approval-backed temporary scope elevation; +- AuthZEN Search APIs, obligations, and richer decision metadata; +- policy bundles, signed decision metadata, and cross-service audit correlation; +- separate redaction, watermarking, and data-loss-prevention policy for Handoff export; +- registration of more approved Artifact Families under the existing `artifact` Resource Kind and base + `artifact.read` action; +- a generic `execution_target` Resource Kind and per-target grants shared by Skill, Prompt, or other execution content, + defined in a separate RFC; +- remote managed Skill targets after a separate Receiver distribution contract and trust-boundary review; +- shared collections with explicit membership and Revision manifests, plus subscription selection through Context + policy; +- a bounded decision cache after a clear revocation-staleness guarantee exists. + +These extensions cannot change the first-version invariants: `scope_id` is not an ACL, resource content does not grant +authority, exact grants do not follow later Revisions, reads do not enter Context or grant execution automatically, and +every transport fails closed at the Server PEP. diff --git a/docs/zh/rfcs/1396_handoff_access_control.md b/docs/zh/rfcs/1396_handoff_access_control.md new file mode 100644 index 000000000..7406ee213 --- /dev/null +++ b/docs/zh/rfcs/1396_handoff_access_control.md @@ -0,0 +1,1436 @@ +- Proposal Name: `handoff_access_control` +- RFC Number: 1396 +- Start Date: 2026-08-30 +- Status: Draft +- RFC PR: [oceanbase/powercontext#1396](https://github.com/oceanbase/powercontext/pull/1396) +- Tracking Issue: [oceanbase/powercontext#1395](https://github.com/oceanbase/powercontext/issues/1395) +- Related RFCs: [RFC 0011](0011_remote_access_architecture.md)、[RFC 0048](0048_handoff_artifact.md)、 + [RFC 0050](0050_artifact_candidate_review_inbox.md)、[RFC 0051](0051_experience_skill_artifact_families.md)、 + [RFC 0082](0082_handoff_report.md)、[RFC 1223](1223_human_agent_work_continuity.md) + +# Summary + +本 RFC 为 PowerContext Server 定义独立的 Access Control 边界、稳定的 Resource Kind,以及由 Artifact Family 驱动的 +Access Profile contract,并把 Handoff 作为第一种完整的资源级授权场景。它既回答一个具体问题——当用户 A 把一份 +Handoff 交给用户 B 时,B 可以看到什么、可以做什么,以及这些权限如何撤销和审计——也规范后续 Artifact Family +如何复用同一套 Principal、action、ResourceRef、Binding、PEP(Policy Enforcement Point,策略执行点)/PDP +(Policy Decision Point,策略决策点)、列表和审计语义。 + +Handoff 内容不保存用户、角色或 ACL。`scope_id` 继续表示 Workstream 的稳定业务分区,不是用户身份、tenant、角色或 +安全边界。身份认证和权限判定发生在 Server:认证层得到可信 Principal;PowerContext Server 的策略执行点(PEP)把 +Principal、action 和 resource 交给作为策略决策点(PDP)的可替换 `AuthorizationProvider`。PDP 查询策略或关系存储并 +返回允许或拒绝决定;PEP 只有在允许时才调用现有 Runtime application service。 + +```text +身份提供方或静态凭据 + | + v + 已认证的主体 + | + v +PowerContext Server 策略执行点(PEP) + | + | 授权请求 + v + AuthorizationProvider(PDP) <----> 策略或关系存储 + | + | 允许或拒绝决定 + v +PowerContext Server 策略执行点(PEP) + | | + 允许 拒绝 + | | + v v + 现有应用服务 返回 403 +``` + +首版定义三种稳定 Resource Kind: + +```text +├── server 管理资源 +├── scope 管理资源 +├── artifact 内容资源 +│ ├── family=handoff +│ ├── family=memory +│ ├── family=experience +│ ├── family=skill +│ └── family=prompt +``` + +- `server`:当前 PowerContext deployment; +- `scope`:一个精确 Workstream scope; +- `artifact`:一个由 Artifact Family Access Profile 解释的精确 Artifact Revision 或 Family-owned selector。 + +`artifact` Resource Kind 首版注册 `handoff`、`memory`、`experience`、`skill` 和 `prompt` 五个 Artifact Family Access +Profile。`ArtifactReference.family` 是唯一的 Profile discriminator;客户端不再提交第二个可能与它冲突的内容类型。 + +用户 A 可以选择两种协作方式: + +- 为长期协作者授予 Workstream 级角色; +- 只把一个已持久化或已批准的精确 resource 授予 B。 + +第二种方式是首版的最小权限路径。B 可以读取被分享的精确资源,并只能执行对应 Artifact Family Access Profile 明确 +授予的 action。精确 Handoff receiver 可以通过 Handoff resolver 检查其中明确引用的 evidence,并对同一个 Revision 留下 Receipt;精确 +Memory、Artifact 或 Prompt grant 不自动开放同一 scope、current head、未来 Revision、搜索结果或 lineage 中引用的 +其他资源。Skill 的读取、发布到一个 target,以及宿主最终加载或执行是彼此独立的授权边界。`accepted` Receipt、Artifact +approval、Prompt read 或 Skill publication 都不会授予工具、网络、文件系统、模型 Provider 或凭据权限。 + +PowerContext 定义稳定的授权 request/decision、内置角色、Access API 和 OpenAPI extension,但不绑定一个策略引擎。 +首版提供内置 Role Binding Store;Casbin、OpenFGA 和兼容 OpenID AuthZEN Authorization API 的 Policy Decision +Point(PDP)可以通过 adapter 接入。 + +# Motivation + +PowerContext 已经拥有临时 Prepared Handoff、不可变 Handoff Revision、Continue、Receipt 和 Task Outcome,也拥有 +Memory Entry Version、approved Experience/managed Skill Revision 和 host-local Skill projection;但现有 Server 认证是 +可选的全局静态 Bearer。一个有效 token 可以访问所有受保护 operation,Server 无法表达: + +- A 可以管理 Workstream,而 B 只能看一份交接; +- B 可以确认接收,但不能提交新的里程碑; +- 团队成员可以查看 Handoff Report,但不能审批 Experience 或 Skill; +- B 只能读取一条被分享的 Memory Entry Version,不能搜索整个 scope 或跟随它的未来版本; +- B 可以读取一个 approved Experience 或 managed Skill Revision,但不能评审 Candidate; +- B 可以使用一个精确 Prompt,但不能把它静默提升为宿主的 system/developer instruction; +- 发布者可以发布一个精确 managed Skill,但不能借此修改源 Revision 或获得宿主执行权限; +- 被撤销的接收方不能继续读取后续 Revision; +- HTTP、MCP 和 Dashboard 对同一个 Principal 得到相同判定。 + +RFC 0048 要求接收方能够读取 Handoff 所属 scope 及其 evidence。直接把 B 加入整个 scope 虽然满足该要求,却会暴露 +与这次交接无关的 Memory、Source 和历史。只把 Handoff 正文复制给 B 又会丢失 exact evidence、Receipt 和撤销能力。 + +RFC 1223 中 `acknowledge_handoff` 的 authorization check 是接收方对实时环境的观察。它用于判断“当前是否具备继续 +条件”,不认证 B 的身份,也不是 ACL。自然语言里的 `receiver`、`authorization_notes` 或 “请继续执行”同样不能 +成为权限凭据。 + +因此,Handoff 和其他可共享资源需要一个独立于内容和 Runtime domain API 的授权层。这个层必须同时支持最小权限分享、 +团队角色、外部 PDP、列表过滤、审计和 fail-closed 行为,而不能让 Agent、请求 body 或 `scope_id` 自行决定权限。 + +# Guide-level explanation + +## 建立直觉:交接内容和交接权限是两件事 + +Handoff 回答“工作到了哪里”;Access Binding 回答“谁现在可以对这份交接做什么”。两者具有不同生命周期: + +```text +Prepared Handoff -> Commit -> immutable Handoff Revision + | + +-> Access Binding for user B + | + read / inspect / acknowledge + | + expire or revoke +``` + +提交新 Handoff 不会自动分享,分享也不修改 Handoff 内容或 Revision。撤销 Binding 不删除 Handoff、Receipt 或审计事件。 + +## 同一 Access Plane,Artifact Family 驱动的 Profile + +Access Control 核心只回答“当前 Principal 是否可以对这个精确资源执行这个 action”。Resource Kind 定义授权对象的结构; +Artifact Family Access Profile 定义一种内容的授权语义: + +```text +Protected Resource +├── server +├── scope +├── artifact +│ ├── family=handoff +│ ├── family=memory +│ ├── family=experience +│ ├── family=skill +│ └── family=prompt +``` + +每一种 Artifact Family Access Profile 必须固定回答以下问题: + +| Family profile contract | 必须定义的内容 | +| --- | --- | +| share unit | 分享整个精确 Revision,还是一个 Family-owned exact selector | +| shareable state | committed、approved、retained 等哪些 lifecycle state 可以创建 Binding | +| parent | scope 或 server 级角色如何单向蕴含子资源 action | +| actions | 读取、使用、确认、发布和管理分别使用什么稳定 action | +| grantable roles | 哪些固定角色可以绑定到该资源,以及谁可以创建这些 Binding | +| resolution | 哪些 operation 可以从已验证 request 确定资源,不得在授权前读取什么 | +| listing | exact grant 如何被发现,以及哪些聚合列表仍要求 scope 或 server 权限 | +| transitivity | 读取资源是否同时允许读取 lineage、citation 或其他关联资源 | + +所有 Family 复用同一个 `/v1/access/*` API,不增加 `/memory/share`、`/experience/share`、`/skill/share` 或 +`/prompt/share` 等平行授权接口。新增 Family 必须显式注册;只复用 `artifact.read` 的 exact-read Family 不需要增加新的 +ResourceRef variant。若 Family 引入新的 semantic action、selector 或 role,则必须同步 OpenAPI、固定 action/role +vocabulary、Server-owned resolver、Provider conformance vector 和生成的 transport artifact。未知 Family 默认不可分享。 + +资源可读、进入上下文和获得外部执行能力是三个不同平面: + +```text +Access Plane: Principal 可以读取或使用哪个 exact resource +Context Plane: 哪些已授权内容经显式选择进入有界 PreparedContext +Execution Plane: 宿主是否安装、加载或执行 Skill/Prompt,以及能使用哪些工具和凭据 +``` + +一个 allow decision 不能跨平面传播。精确 Memory、Artifact 或 Prompt grant 不会让内容自动进入普通 scope recall;接收方 +先在 “Shared with me” 视图发现资源,再显式读取、附加到当前任务或 fork 到自己可贡献的 scope。共享内容继续视为 +`untrusted_history` 或不可信 instruction,Context builder 和宿主仍执行各自的预算、优先级、approval 与 sandbox policy。 + +## A 把一份精确 Handoff 交给 B + +假设 A 负责 `project:payments` Workstream,并已完成一份交接。正常流程如下: + +1. A 检查并提交 Prepared Handoff,得到不可变 `ArtifactReference`: + + ```json + { + "family": "handoff", + "artifact_id": "project:payments", + "revision": 12 + } + ``` + +2. A 明确选择接收方 B。Dashboard 或集成层把 B 从企业身份目录解析为可信的 canonical Principal;模型输出、显示名或 + 邮箱文本不能替代该解析。 +3. Server 检查 A 对 `project:payments` 是否拥有 `scope.delegate`。 +4. Server 创建角色为 `handoff.receiver` 的 Access Binding,资源是上面的精确 Revision,可选设置过期时间。 +5. B 使用自己的凭据登录。`resources/list` 返回 B 有权读取的精确 Handoff,B 不需要知道 A 的 token,也不接收新的 + bearer share link。 +6. B 使用 exact selection 调用 Continue。Server 读取同一 Revision,并只解析它明确引用的 evidence。 +7. B 检查当前 workspace、能力和授权状态后,可以对同一 Revision 留下 `accepted`、`needs_clarification` 或 + `declined` Receipt。 + +创建 Binding 的请求示例为: + +```json +{ + "subject": { + "type": "user", + "issuer": "https://id.example.com/", + "id": "00u-bob" + }, + "resource": { + "type": "artifact", + "scope_id": "project:payments", + "reference": { + "family": "handoff", + "artifact_id": "project:payments", + "revision": 12 + } + }, + "role": "handoff.receiver", + "expires_at": "2026-09-06T12:00:00Z", + "reason": "Continue the payment retry investigation", + "idempotency_key": "transfer-payments-12-to-bob" +} +``` + +`granted_by`、创建时间和 policy revision 由 Server 填充,调用方不能伪造。 + +## B 能看到什么 + +`handoff.receiver` 是精确资源角色,不是 scope role: + +| 操作 | 结果 | 原因 | +| --- | --- | --- | +| 读取 Handoff Revision 12 | 允许 | Binding 指向该精确 Revision | +| 通过 Continue 检查 Revision 12 的引用 | 允许 | `handoff.evidence.read` 只覆盖该 Revision 的 citation manifest | +| Acknowledge Revision 12 | 允许 | receiver 可以为已检查的 exact Handoff 留 Receipt | +| 请求 `latest` | 拒绝 | latest 可能是 B 未获授权的后续 Revision | +| 读取 Revision 11 或 13 | 拒绝 | 精确 Binding 不继承到其他 Revision | +| 打开聚合 Handoff Report | 拒绝 | Report 包含 scope 级历史和统计 | +| 搜索 scope Memory 或列出 Source | 拒绝 | Handoff Binding 不授予通用 scope read | +| Commit 新 Handoff 或记录 Task Outcome | 拒绝 | 需要 `scope.contribute` | +| 审批 Candidate | 拒绝 | 需要独立的 `scope.review` | + +Evidence 的最小权限不是逐条复制 Source 或 Memory,也不是让外部 PDP 保存全部 citation。Server 先从已验证请求构造 +exact Handoff `ArtifactResourceRef`,同时检查 B 的 `artifact.read` 和 `handoff.evidence.read`;只有两个 decision 都允许后, +才能读取不可变 Handoff Revision、取得 citation manifest,并通过 Handoff resolver 解引用其中的 exact citation。B 不能把 +任意 Source ID 填入通用读取 API 来复用这项权限。 + +如果一条 citation 已被删除、retire、损坏或因更高层策略被拒绝,Continue 把对应 evidence 标记为 unavailable。 +Handoff Binding 不覆盖 retention、legal hold、数据分类或显式 deny policy。 + +## 分享其他 Artifact Family + +其他 Artifact Family 使用相同的 exact-share 流程,但不会继承 Handoff 的 evidence 和 Receipt 语义: + +1. A 选择一个已经持久化且可授权的精确资源;Memory 使用完整 `MemoryCitation`,Experience、managed Skill 和 Prompt + 使用带正整数 Revision 的 `ArtifactReference`。 +2. Server 先检查 A 是否可以在该资源所属 scope 创建对应 Binding,再验证资源存在且处于可分享状态。 +3. B 通过 `access/resources/list` 发现 exact resource,并使用自己的 Principal 读取或显式使用它。 +4. B 若要修改或长期维护内容,需要在自己拥有 `scope.contribute` 的 scope 中显式 fork 或提出新 Candidate;原资源和 + Binding 不被修改。 + +首版 exact grant 的行为如下: + +| Family role | 允许 | 不允许 | +| --- | --- | --- | +| `artifact.viewer` on `family=memory` selector | exact get 一个 `entry_version_id` | search、list、changes、current head、revise、retire、其他 entry/version | +| `artifact.viewer` | exact get 一个 approved Experience 或 managed Skill Revision | Candidate read/review、future Revision、publication、lineage body | +| `artifact.viewer` on `family=prompt` | exact get 一个 approved Prompt Revision | render/use、future Revision、自动注入 | +| `prompt.user` | `artifact.viewer` 加显式 render/use | 改变 instruction priority、自动启用工具或读取凭据 | + +普通用户输入仍是 Source evidence,不因包含文字 “prompt” 就成为 Prompt Artifact。可复用、参数化的任务模板可以由后续 +Prompt Artifact lifecycle 定义;Memory extraction、Experience/Skill generation 和 Handoff generation 使用的内部 prompt +属于 Server implementation/configuration,由 `server.admin` 管理,不通过 `family=prompt` Artifact Binding 分享。如果一个 +内容描述 Agent 何时使用、如何执行和如何验证一项能力,它应建模为 managed Skill,而不是重复创建 Prompt Artifact。 + +精确资源响应可以返回 schema 已定义的 lineage/citation identity,但 grant 不向引用目标传递。调用通用 Source、Memory 或 +Artifact get operation 仍需对目标资源独立判定;Provider 不得因为 “A references B” 自动创建 `can_read` 继承。 + +## 分享是只读快照,不是共同编辑 + +Exact-resource Binding 只授予读取、显式使用或向 Server-configured target 执行受控发布 operation 的权限,不转移原资源的 +content authority。 +Binding 本身不能授权接收方 revise、retire、replace、提交下一 Revision,或原地覆盖共享内容。即使接收方另外拥有原 scope +的 `scope.contribute` 或更高权限,其写入能力也来自该独立的 scope role,而不是这次分享。 + +接收方产生的状态必须与共享原件分离: + +| 接收方操作 | 约束 | +| --- | --- | +| acknowledge Handoff | 创建独立 Receipt,不修改 Handoff Revision | +| 提交 feedback 或变更建议 | 创建独立 feedback/change request,不修改共享内容 | +| 发布 managed Skill | 写入 Server 配置目标的 projection/state,不修改源 Skill Revision | +| fork、import 或 copy | 必须对目标 scope 拥有 `scope.contribute`;创建新的 identity 或 Candidate,并保留到原资源的 lineage | + +产品界面应使用“查看”“使用”“确认接收”“请求变更”“复制到我的 scope”或“发布到配置目标”等动作,不应把 exact share +呈现为“编辑共享内容”。持续共同维护需要单独授予 scope role;对于需要 Review 的 Artifact Family,贡献者仍通过 Candidate +和 Review lifecycle 产生新 Revision,而不是原地改写 approved Revision。撤销分享会阻止后续访问,但不能删除接收方已经 +看到的内容,也不能自动撤销此前经独立授权创建的 Receipt、projection 或 fork。 + +## 发布 managed Skill + +读取 Skill 内容和把 Skill 发布到配置的 host-local Agent target 是不同 operation。发布请求只接受 exact managed Skill +`ArtifactReference` 和 Server 配置的 opaque `target_id`,不接受 destination path、Agent home、SSH credential 或任意 +filesystem locator。Server 必须在读取 Skill body、解析 `target_id`、检查 target host 状态或写入 projection 前同时得到两个 +关于同一个 exact Skill Artifact 的 allow decision: + +```text +artifact.read AND skill.publish on exact family=skill Artifact +``` + +`skill.publisher` 只绑定到一个 exact managed Skill Revision,并同时授予这两个 action。`target_id` 是由 `server.admin` +配置的 opaque operation parameter,不是 `ResourceRef`、Access Binding 或 `/access/resources/list` 中的授权资源。授权通过后, +Server 才能确认 `target_id` 已注册并把它解析为 host-local Agent projection configuration;未注册或 disabled target 拒绝 +发布。Host ID、destination path、Agent home、credential reference 和 locator 不进入请求、Binding、普通 audit 或公共错误。 + +普通 publisher 通过 `POST /v1/skills/publication-targets/list` 选择 target。请求携带 `scope_id` 和 exact Skill +`ArtifactReference`,Server 复用上述两个 requirement;只有全部 allow 后才读取 Skill Repository 和 target registry。响应 +只列出 enabled target 的 opaque `target_id`、Agent kind、installation scope 和安全 capability,不返回 desired/applied +state、host path、Agent home、credential reference 或底层错误。该 operation 是 Skill publication domain contract,不是 +Access Resource listing,也不为 target 创建 Binding。 + +```json +{ + "scope_id": "project:payments", + "artifact": {"family": "skill", "artifact_id": "retry-runbook", "revision": 4} +} +``` + +```json +{ + "artifact": {"family": "skill", "artifact_id": "retry-runbook", "revision": 4}, + "targets": [ + { + "target_id": "codex-project", + "agent_kind": "codex", + "installation_scope": "project", + "capabilities": ["publish"] + } + ] +} +``` + +首版不提供 per-target delegation:获得一个 exact Skill 的 `skill.publisher` 后,可以把该 Revision 发布到当前 deployment +中任意 enabled configured target。只有 `server.admin` 能配置、修改或删除 target;target 状态属于受 `server.observe` 或 +`server.admin` 保护的运维信息。若产品需要表达“B 可以发布到 X,但不能发布到 Y”,后续由独立分发 RFC 定义通用 +`execution_target` Resource,而不把 Skill 专用 target 混入 Artifact 分享模型。 + +发布成功只表示配置的 host-local target projection 接收到该 exact Revision,不授予宿主加载、执行、工具、网络、文件系统 +或 secret 权限。External Skill registration 和 host-local locator 不是可跨主机分享的 Artifact Family Access Profile; +需要协作时应显式 import/fork 为 managed Skill。Remote Receiver distribution 不属于首版。 + +## B 真正接手 Workstream + +查看交接不等于获得执行权。若 B 将长期推进该 Workstream,A 或管理员需要另行授予 `scope.contributor`: + +```text +handoff.receiver + = read one exact Handoff + inspect its citations + acknowledge it + +scope.contributor + = read the Workstream + contribute Sources + prepare/commit Handoffs + + acknowledge Handoffs + record Task Outcomes +``` + +PowerContext 权限只控制 PowerContext 资源和 operation。修改 Git 仓库、调用云 API、访问生产环境或读取凭据仍由宿主、 +操作系统和外部服务授权。Handoff、Role Binding 和 Receipt 都不能扩大这些权限。 + +## 长期团队协作 + +对固定团队,可以把用户或外部 group 绑定为 scope role,而不是为每个 Revision 创建 Binding: + +- `scope.viewer`:读取当前 scope 的 Handoff、Memory、approved Artifact、Prompt、Source 和只读投影,并显式使用 approved + Prompt; +- `scope.contributor`:在 viewer 基础上写入工作 evidence、Memory contribution、Handoff 和 Outcome,并提出 Artifact/Prompt + Candidate; +- `scope.reviewer`:在 viewer 基础上评审 Artifact Candidate; +- `scope.delegator`:在 viewer 基础上把精确 Handoff 分享给接收方; +- `scope.admin`:管理该 scope 的全部角色和策略。 + +`scope.delegate` 在本 RFC 中继续只允许为 `family=handoff` Artifact 创建 viewer/receiver Binding。首版其他 Artifact +Family 的 exact Binding 只能由 `scope.admin` 创建,不能因为已有 Handoff delegator 就静默扩大分享边界。后续可以增加 +资源级 delegation action,但必须作为显式 wire-contract 变更。发布 target 由 `server.admin` 通过 deployment configuration +管理,不创建 Access Binding。 + +固定角色是 wire-contract vocabulary,不要求外部 PDP 使用相同内部存储。外部系统可以把企业角色、团队或关系映射为 +这些 action。 + +## 撤销和过期 + +A、相应 grant administrator 或 scope admin 可以撤销其管理边界内的 exact Artifact Binding。对于 Handoff,撤销后: + +- B 的后续 read、Continue 和 acknowledge 返回 403; +- B 不再从 `resources/list` 看到该 Handoff; +- 已保存的 Handoff、Receipt 和 Access Audit 不被删除; +- 已经展示、导出或复制给 B 的内容无法被远程收回。 + +过期时间由 PDP 使用可信 Server time 判断。Adapter 不支持条件或 expiration 时必须拒绝创建带过期时间的 Binding, +不能静默创建永久授权。 + +角色变更使用 revoke + create,不原地把 `handoff.viewer` 升级为 `handoff.receiver`。撤销使用 `expected_version`,并发 +修改返回 409。 + +## 授权服务不可用 + +授权是安全依赖。配置为 enforced mode 时: + +- 没有或无法验证身份返回 401; +- 身份有效但权限不足返回 403; +- PDP、Binding Store 或安全资源过滤不可用返回 503; +- Server 不会因为 PDP 故障而回退到全局 token、空 Principal 或 allow-all; +- `/health/live` 仍反映进程存活,`/health/ready` 报告 required authorization dependency 未就绪。 + +403 不区分“资源不存在”和“资源存在但不可见”。只有通过授权后,Repository 才可以返回 404,避免资源枚举。 + +# Reference-level explanation + +## Goals and non-goals + +本 RFC 的目标是: + +- 在 HTTP、MCP 和 Dashboard 前建立同一个 Server PEP; +- 从认证凭据建立不可由请求覆盖的 Principal; +- 支持 scope 级 RBAC 和精确 Handoff receiver Binding; +- 定义稳定 Resource Kind 和 Artifact Family Access Profile contract,并规范 Handoff、Memory、Experience、Skill 和 Prompt + 的精确授权; +- 允许安全解引用精确 Handoff 已引用的 evidence,而不开放整个 scope; +- 区分资源读取、上下文选择、Skill 发布与宿主执行权限; +- 提供可替换的判定接口和可选的关系写入接口; +- 提供自助检查、资源发现、Binding 管理和审计 API; +- 对直接读取、列表、分页、内部 MCP bridge 和后台 operation fail closed; +- 保留当前 Runtime、Source、Memory、Handoff 和 Work application API 的领域纯度。 + +本 RFC 不定义: + +- 用户注册、密码、MFA、OIDC Provider 或 token issuance; +- 自定义 role DSL、wildcard scope、组织层级或 group directory; +- 匿名 bearer share link 或把授权嵌入 Handoff 内容; +- Git、文件系统、工具、网络、模型 Provider 或凭据授权; +- 数据脱敏、cross-organization export、legal hold 或 retention policy; +- 审批工作流、临时提权流程或 Agent 自动请求更高权限; +- 把 PowerContext 改造成通用 IAM 产品; +- 对 exact shared resource 进行 multi-writer collaborative editing,或通过 Binding 转移 ownership; +- Memory collection、Artifact catalog 或 “自动跟随 latest” 的动态订阅分享; +- Prompt Artifact 的内容 schema、变量语言、Review lifecycle 或宿主 instruction-priority policy; +- per-target publication delegation 或通用 `execution_target` Resource; +- remote managed Skill projection 或 Receiver distribution contract; +- External Skill 的跨主机 locator、自动安装或 package distribution contract。 + +## Trust model and invariants + +实现必须维持以下不变量: + +1. `scope_id` 是业务分区值,不是授权证明。 +2. Principal 只来自认证 middleware 或可信 internal bridge context。 +3. 请求 body 中的 `receiver`、`subject`、`actor`、role text 或 Handoff 自然语言不能替换当前 Principal。 +4. Handoff、Memory、Artifact 和 Prompt 内容是 `untrusted_history` 或不可信 instruction,不能授予 action。 +5. `is_internal_bridge()` 只能跳过重复 transport authentication,不能跳过 authorization。 +6. 每个受保护的 operation 在访问 Repository 或 application service 前完成判定。 +7. 精确 Handoff grant 不允许 `latest`,不自动覆盖同 Artifact 的其他 Revision。 +8. `accepted` Receipt 不创建、更新或继承 Access Binding。 +9. 模型可以建议接收方或解释拒绝原因,但不能自行确定 canonical Principal 或调用 allow-all fallback。 +10. Exact Memory Entry grant 必须由 `family=memory` 的精确 `ArtifactReference` 和完整 `memory_entry` selector 组成;其他 + exact Artifact grant 必须包含正整数 Revision,不允许 `latest` 或自动继承到未来 Revision。Server 只从 + `ArtifactReference.family` 派生 Access Profile;独立 content profile、未知 Family 或 selector mismatch 必须拒绝。 +11. 读取 Memory、Artifact 或 Prompt 不自动授予其 lineage/citation target,也不自动进入 PreparedContext。 +12. Exact-resource Binding 本身不授予 revise、retire、replace、提交下一 Revision 或其他修改共享内容的 operation; + Receipt、feedback、projection 和 fork 是独立资源或 operation,必须分别授权,并且不能修改原资源的 identity、content + 或 Revision。 +13. `prompt.use` 不改变宿主 instruction priority;`skill.publish` 不授予宿主加载、执行、工具、网络、文件系统或 secret + 权限。 +14. Skill publish 必须同时允许 exact `family=skill` Artifact 的 `artifact.read` 和 `skill.publish`,且授权发生在解析 + `target_id` 或任何 host/filesystem inspection 前;`target_id` 不是授权资源,首版只解析已配置的 host-local target。 +15. Public error、log、metric 和 trace 不包含 credential、Handoff/Memory/Artifact/Prompt 正文、Source body、target locator + 或 PDP 原始响应。 + +## Principal model + +`PrincipalRef` 使用认证 Provider 给出的稳定 opaque identity: + +```json +{ + "type": "user", + "issuer": "https://id.example.com/", + "id": "00u-bob" +} +``` + +字段语义如下: + +| Field | Semantics | +| --- | --- | +| `type` | `user`、`service` 或后续注册的 Principal type | +| `issuer` | 建立该 identity 的可信 issuer;本地凭据使用 deployment-specific issuer | +| `id` | issuer 内稳定 opaque subject,不使用显示名或 email | + +Agent 名称、host、session ID 和模型名称属于 provenance,不默认成为 Principal。若企业 token 明确证明 on-behalf-of actor, +认证 adapter 可以在可信 request context 中附加 `actor`;PDP 可以同时约束 subject 和 actor。客户端不能通过 JSON body +声明该 actor。 + +现有 Handoff Receipt 的 `receiver` 字段继续作为记录内容。Server 另外记录产生 Receipt 的 authenticated Principal, +两者不一致时拒绝 `accepted` 或在非 accepted Receipt 中明确标记 mismatch;绝不能把自由文本 `receiver` 当作 Principal。 + +## Resource model + +内部授权 request 使用结构化 `ResourceRef`,避免把包含 `:`、`/` 或用户数据的标识直接拼成策略字符串: + +| Resource Kind | Identity | Parent | +| --- | --- | --- | +| `server` | deployment identifier | none | +| `scope` | exact `scope_id` | server | +| `artifact` | exact `ArtifactReference`、可选 Family-owned selector 和 `scope_id` | scope | + +`ResourceRef` 是 OpenAPI discriminated union。每个 variant 使用 `additionalProperties: false`,并且只接受下表字段: + +| `type` | Required identity fields | +| --- | --- | +| `server` | `deployment_id` | +| `scope` | `scope_id` | +| `artifact` | `scope_id`, `reference`, and optional `selector` | + +普通 Artifact Revision 不包含 selector: + +```json +{ + "type": "artifact", + "scope_id": "project:payments", + "reference": {"family": "experience", "artifact_id": "exp-retry-budget", "revision": 3} +} +``` + +Memory Entry 使用 `memory` Family 拥有的 exact selector。`reference` 和 `selector` 合在一起等价于完整 +`MemoryCitation`: + +```json +{ + "type": "artifact", + "scope_id": "project:payments", + "reference": {"family": "memory", "artifact_id": "memory", "revision": 18}, + "selector": { + "type": "memory_entry", + "entry_id": "retry-policy", + "entry_version_id": "01K..." + } +} +``` + +`ArtifactResourceRef.reference.family` 是唯一的 Artifact Family Access Profile discriminator。请求不包含独立 `profile` +字段;Server 从已验证的 exact `ArtifactReference` 派生 Profile,避免 `profile=prompt` 与 `family=skill` 等不一致组合。 +每个 Family 声明 selector 为 required、forbidden 或某个固定 discriminated union variant。首版 `memory` 要求 +`memory_entry` selector,`handoff`、`experience`、`skill` 和 `prompt` 禁止 selector。 + +Family registry 是 Server-owned 固定 contract,不是管理员可编辑的 policy DSL。每个注册项至少包含: + +| Field | Requirement | +| --- | --- | +| `family` | 与 `ArtifactReference.family` 完全匹配的稳定名称 | +| `share_unit` | `revision` 或一个明确的 Family-owned selector type | +| `shareable_states` | 允许创建 Binding 的 lifecycle state | +| `base_action` | 首版统一为 `artifact.read` | +| `additional_actions` | Family 特有的 use、acknowledge 或 publish action | +| `grantable_roles` | 与该 Family 兼容的固定 exact roles | +| `parent_implications` | scope role 可以单向蕴含哪些 child action | +| `transitivity` | lineage、citation 或其他关联资源是否需要独立判定;未声明时为 none | +| `resolver` | 授权后如何解析 exact resource 以及返回什么安全 identity | + +首版 registry 为: + +| Artifact Family | Share unit | Shareable state | Exact actions | Grantable exact roles | +| --- | --- | --- | --- | --- | +| `handoff` | Revision | committed | `artifact.read`, `handoff.evidence.read`, `handoff.acknowledge` | `handoff.viewer`, `handoff.receiver` | +| `memory` | `memory_entry` selector | active in the referenced Revision | `artifact.read` | `artifact.viewer` | +| `experience` | Revision | approved | `artifact.read` | `artifact.viewer` | +| `skill` | Revision | approved | `artifact.read`, `skill.publish` | `artifact.viewer`, `skill.publisher` | +| `prompt` | Revision | approved | `artifact.read`, `prompt.use` | `artifact.viewer`, `prompt.user` | + +Prepared Handoff 没有持久化 identity,不能创建精确 Access Binding。跨用户最小权限分享必须先 commit;pending/rejected +Candidate 同样不能创建 Artifact Binding。普通新 Family 即使只复用 `artifact.read`,也必须先显式注册为 shareable; +未知、disabled 或 selector 不匹配的 Family 默认拒绝。`revision=latest`、只有 `entry_id`、Memory current head 或 search query +都不是稳定授权身份。后续 Revision 或 Memory Entry Version 不继承 exact Binding。 + +每个 Resource Kind 都定义稳定的 canonical serialization 供 adapter 建立 object ID。Artifact key 必须包含 `scope_id`、 +`family`、`artifact_id`、正整数 `revision` 和完整 selector;相同业务身份在 HTTP、MCP 和 Dashboard 必须得到同一个 key。 +不同 Family 或 selector 不得因字符串碰撞共享 Binding。 + +Adapter 负责把结构化 ResourceRef 映射成外部 PDP object ID。映射必须 canonical、可逆或稳定,并避免把 email、token、 +资源正文、发布 target locator 或其他 PII 写入 Casbin policy、OpenFGA tuple 或 audit key。 + +## Action vocabulary + +首版 action 是稳定、小写、点分隔的字符串: + +| Action | Resource | Meaning | +| --- | --- | --- | +| `server.observe` | server | 读取服务级运行状态和观测数据 | +| `server.admin` | server | 管理 deployment access configuration 和 publication target configuration | +| `scope.read` | scope | 读取该 Workstream 的通用只读资源、approved content 和投影 | +| `scope.contribute` | scope | 写入 Source、Memory contribution、Handoff/Outcome,并提出 Artifact/Prompt Candidate | +| `scope.review` | scope | 评审该 scope 的 Artifact Candidate | +| `scope.delegate` | scope | 为精确 Handoff 创建 viewer 或 receiver Binding | +| `scope.admin` | scope | 管理该 scope 的角色、Binding 和 policy | +| `artifact.read` | exact artifact | 读取 Family Profile 定义的 exact Revision 或 selector | +| `handoff.evidence.read` | `family=handoff` artifact | 通过 Handoff resolver 解引用该 Revision 的 citation manifest | +| `handoff.acknowledge` | `family=handoff` artifact | 对该 Revision 创建 Handoff Receipt | +| `prompt.use` | `family=prompt` artifact | 显式 render 或附加一个已授权 Prompt;不决定宿主 instruction priority | +| `skill.publish` | `family=skill` artifact | 发现安全 target 选项,并选择一个 exact managed Skill Revision 用于发布 | + +`artifact.read` 的含义在所有 Family 中保持固定:只读取 Binding 标识的 exact Revision 或 selector。它不自动包含 Handoff +evidence、Prompt use、Skill publish、lineage body 或任何 mutation。只有确实具有不同安全效果的 Family operation 才新增 +semantic action。 + +业务 operation 检查 action,不检查 role name。这样可以调整外部角色或关系模型,而不改 application code。 + +`scope.read` 可以通过策略蕴含 scope 下所有已注册 Family 的 `artifact.read`、Handoff 的 `handoff.evidence.read` 和 +Prompt 的 `prompt.use`;`scope.contribute` 可以蕴含 acknowledge、prepare、commit、Memory contribution、Artifact/Prompt +Candidate proposal 和 Outcome 写入。反向蕴含不成立:任何 exact viewer/user role 都不能得到 `scope.read` 或 +`scope.contribute`。`scope.read` 不蕴含 `skill.publish`。 + +## Built-in roles + +| Role | Granted actions | +| --- | --- | +| `handoff.viewer` | `artifact.read`, `handoff.evidence.read` on one exact `family=handoff` Artifact | +| `handoff.receiver` | viewer actions plus `handoff.acknowledge` on one exact Handoff | +| `artifact.viewer` | `artifact.read` on one compatible exact Artifact Revision or selector | +| `prompt.user` | `artifact.read`, `prompt.use` on one exact `family=prompt` Artifact | +| `skill.publisher` | `artifact.read`, `skill.publish` on one exact managed Skill Revision | +| `scope.viewer` | `scope.read` | +| `scope.contributor` | `scope.read`, `scope.contribute` | +| `scope.reviewer` | `scope.read`, `scope.review` | +| `scope.delegator` | `scope.read`, `scope.delegate` | +| `scope.admin` | all scope and child Artifact Family actions, including delegation and Binding administration | +| `server.observer` | `server.observe` | +| `server.admin` | all server, scope, and Artifact Family actions | + +所有 exact-resource role 对其绑定内容都是只读的。`handoff.receiver` 只额外允许创建独立 Receipt;`skill.publisher` 只允许 +向 Server 配置的 target 写 projection。两者都不能修改源 Handoff 或 Skill Revision。原资源的 mutation 必须由独立的 +scope role 和对应领域 lifecycle 授权。 + +首版不允许通过公共 API 创建新 role 或修改 role-to-action mapping。固定角色让 OpenAPI、Dashboard 和 adapter +conformance test 拥有稳定语义;企业 PDP 可以在外部把自定义组织角色映射为这些 action。 + +拥有 `scope.delegate` 的 Principal 只能创建 `handoff.viewer` 或 `handoff.receiver`,且只能针对该 scope 中已经存在的 +精确 Handoff。创建 scope role 需要 `scope.admin`;创建 `server.admin` 需要现有 `server.admin` 和 deployment policy +允许。任何 Principal 都不能授予自己高于调用方管理边界的权限。 + +首版只有 `scope.admin` 可以在所管理的 scope 中创建 `artifact.viewer`、`prompt.user` 或 `skill.publisher` Binding。 +`artifact.viewer` 只能绑定到 Family registry 声明兼容的 exact Revision 或 selector;`prompt.user` 和 `skill.publisher` 分别 +只能绑定 approved `family=prompt` 和 `family=skill` Artifact。Role 与 Artifact Family Access Profile 或 Resource Kind +不匹配时返回 422, +授权不足时返回 403;Server 不能把不匹配的 role text 原样交给外部 RelationshipWriter。 + +| Resource or Artifact Family Profile | Grantable exact roles | Binding administrator | +| --- | --- | --- | +| `artifact` with `family=handoff` | `handoff.viewer`, `handoff.receiver` | `scope.delegate`, `scope.admin`, or `server.admin` | +| `artifact` with `family=memory` and `memory_entry` selector | `artifact.viewer` | `scope.admin` or `server.admin` | +| `artifact` with `family=experience` | `artifact.viewer` | `scope.admin` or `server.admin` | +| `artifact` with `family=skill` | `artifact.viewer`, `skill.publisher` | `scope.admin` or `server.admin` | +| `artifact` with `family=prompt` | `artifact.viewer`, `prompt.user` | `scope.admin` or `server.admin` | + +## Authorization request and decision + +PowerContext 的判定模型与 OpenID AuthZEN Authorization API 的 subject、action、resource、context 形状对齐,但 +Python protocol 不要求 PDP 使用 HTTP: + +```python +class AuthorizationProvider(Protocol): + async def check(self, request: AccessRequest, /) -> AccessDecision: ... + + async def check_batch( + self, + requests: Sequence[AccessRequest], + /, + ) -> Sequence[AccessDecision]: ... + + async def resolve_resource_filter( + self, + request: ResourceSearchRequest, + /, + ) -> AuthorizedResourceFilter: ... +``` + +规范化 request 示例: + +```json +{ + "subject": { + "type": "user", + "issuer": "https://id.example.com/", + "id": "00u-bob" + }, + "action": {"name": "artifact.read"}, + "resource": { + "type": "artifact", + "scope_id": "project:payments", + "reference": { + "family": "handoff", + "artifact_id": "project:payments", + "revision": 12 + } + }, + "context": { + "request_id": "pc-01K...", + "transport": "mcp" + } +} +``` + +`AccessDecision` 至少包含: + +```json +{ + "allowed": true, + "reason_code": "role_binding", + "policy_revision": "42" +} +``` + +`reason_code` 是稳定、低敏感度枚举,用于 audit 和诊断;business 403 response 不返回 provider rule、tuple、URL、堆栈或 +原始 body。`policy_revision` 允许审计和缓存关联到确定策略,但它不是授权 token。 + +`check_batch` 必须保持输入顺序,并对每项返回独立决定。Adapter 不能因为一个 allow 而允许整批资源。 + +一个业务 operation 可以解析出 1..N 个 `ResolvedAccessRequirement`。首版只支持 `all` 组合:PEP 使用一次 +`check_batch` 或语义等价的 point checks,并且只有全部 decision 都为 allow 才能调用 Repository、application service、 +target adapter 或 filesystem。它不提供 client-authored Boolean policy DSL。 + +例如 managed Skill 发布解析为: + +```json +{ + "combination": "all", + "requirements": [ + { + "action": {"name": "artifact.read"}, + "resource": { + "type": "artifact", + "scope_id": "project:payments", + "reference": {"family": "skill", "artifact_id": "retry-runbook", "revision": 4} + } + }, + { + "action": {"name": "skill.publish"}, + "resource": { + "type": "artifact", + "scope_id": "project:payments", + "reference": {"family": "skill", "artifact_id": "retry-runbook", "revision": 4} + } + } + ] +} +``` + +业务请求中的 `target_id` 不进入 requirements。只有上述两个 decision 都 allow 后,Server 才解析该参数。 + +“scope role 或 exact role” 这类替代关系不需要 `any` 表达式。PEP 请求 child-resource action,Provider 根据可信 parent +relation 判断 scope role 是否蕴含该 action;exact Binding 则直接作用于 child resource。这样不同 Provider 不必实现任意 +嵌套策略表达式。 + +`resolve_resource_filter` 是安全列表功能的必要能力。`AuthorizedResourceFilter` 是当前 Principal 和 action 专属的 +Server-consumable filter,由两类约束组成:exact Binding 产生的有界 canonical resource key,以及父级角色产生的有界 +server/scope constraint。父级 constraint 表示“Repository 可以在该 parent、请求的 Resource Kind 和 Family 内查询”,不是 +客户端可提交的 wildcard。Filter 还携带 policy revision;Server 必须校验其结构和上限,再把 exact key 与 parent +constraint 的并集下推到同一次 Repository query,在计算 total、排序和分页前完成过滤。 + +内置 Provider 可以直接从 Binding Store 产生 exact key 和 parent constraint,因此不需要镜像整个 Artifact catalog。 +外部 Provider 可以返回等价的授权 filter,或由 adapter 根据可信 relationship search 生成。只支持 point check、无法安全 +产生该 filter 的 Provider 不得先查询全部 Artifact、Project 或 Scope 再逐项过滤;对应 list operation 应返回 503,或在 +配置阶段被判为不具备 `safe_resource_filtering` capability。 + +## Relationship administration + +AuthZEN 定义判定接口,不定义所有 PDP 的关系写入方式。因此管理能力与判定能力分开: + +```python +class RelationshipWriter(Protocol): + async def create_binding( + self, + request: CreateAccessBinding, + /, + ) -> AccessBinding: ... + + async def revoke_binding( + self, + binding_id: str, + /, + *, + expected_version: int, + ) -> AccessBinding: ... +``` + +内置 Provider、Casbin adapter 和 OpenFGA adapter 可以同时提供 `AuthorizationProvider` 与 `RelationshipWriter`。 +OPA、Cerbos 或通用 AuthZEN adapter 可以只提供 decision;此时 PowerContext 的 Binding mutation endpoint 明确返回 +`relationship_management_unavailable`,管理员通过外部系统配置关系。Server 不能声称 grant 成功后再只写本地影子记录。 + +## Access Binding model + +内置 Binding Store 至少保存: + +| Field | Requirement | +| --- | --- | +| `binding_id` | Server-generated opaque ID | +| `subject` | canonical `PrincipalRef` | +| `resource` | canonical exact `ResourceRef` | +| `role` | one fixed role name | +| `granted_by` | authenticated Principal recorded by Server | +| `reason` | optional bounded human explanation | +| `created_at` | trusted Server time | +| `expires_at` | optional trusted expiration | +| `state` | `active` or `revoked` | +| `version` | monotonically increasing CAS version | +| `policy_revision` | policy version after mutation when available | +| `idempotency_key` | bounded caller key scoped to grantor and resource | + +Role、subject 或 resource 变化必须 revoke old + create new。相同 grantor、idempotency key 和相同 payload 的重试返回 +原 Binding;同 key 不同 payload 返回 409。过期不删除记录,判定时视为 deny。 + +内置 Binding Repository 属于 Server access-control component,不加入 Runtime 的 `context`、`source`、`memory`、 +`artifact`、`handoff` 或 `work` application object。它可以与 Server 使用相同数据库部署,但拥有独立 schema、 +migration 和 API。 + +## Public Access API + +OpenAPI source of truth 增加以下 operation: + +| Operation | Purpose | Authorization | +| --- | --- | --- | +| `GET /v1/access/me` | 返回当前 Principal 和 access-control capability | authenticated Principal | +| `POST /v1/access/check` | 检查当前 Principal 的一个 action/resource | current Principal only | +| `POST /v1/access/check-batch` | 批量检查当前 Principal | current Principal only | +| `POST /v1/access/resources/list` | 列出当前 Principal 可访问的资源 identity | current Principal only | +| `POST /v1/access/roles/list` | 返回固定角色及 action vocabulary | authenticated Principal | +| `POST /v1/access/bindings/list` | 列出调用方可管理的 Binding | `scope.delegate`, `scope.admin`, or `server.admin` | +| `POST /v1/access/bindings/create` | 创建 Family-compatible exact-resource 或管理级 Binding | resource-specific administration action | +| `POST /v1/access/bindings/revoke` | CAS revoke 一个 Binding | same administration boundary | +| `POST /v1/access/audit/list` | 查询安全审计事件 | `scope.admin` or `server.admin` | + +`check`、`check-batch` 和 `resources/list` 不接受 client-specified subject,只检查当前 authenticated Principal,防止普通 +用户把 API 当作人员权限枚举器。管理员代查其他 Principal、subject search 和 directory integration 留给后续 RFC。 + +`bindings/create` 必须接收 recipient subject,因为分享需要指定 B;调用方仍然只能在自己拥有管理权限的 resource 上创建 +固定角色。Server 先根据 Resource Kind 和 Artifact Family registry 校验结构与 role compatibility,再执行 grant +administration check,最后才读取 Repository,确认 Artifact 存在、属于声明的 parent 且处于可授权状态。 +不存在与不可见的资源对未授权调用方返回相同 403;只有管理判定通过后才能返回 404 或 family-specific conflict。 + +Access API 不负责创建、修改、fork、render 或发布业务资源。Memory、Artifact、Prompt 和 managed Skill publication 的 +业务 operation 继续使用各自 contract;Binding 只表达谁能对已存在资源执行哪些 action。Publisher-safe target selection +属于 Skill publication contract;target configuration 和 operator status 属于 Server operation。三者都不进入 Access API, +也不创建 target Binding。 + +公共 `check` 可以用 HTTP 200 返回 `allowed=false`。业务 operation 的相同拒绝返回 403,并且不调用 application +service。Access API 只用于解释和 UI preflight,不能替代业务请求时的实时 enforcement。 + +## Handoff operation requirements + +首版 Handoff 映射如下: + +| Operation | Required authorization | +| --- | --- | +| `prepare_handoff`, `finalize_handoff`, `handoff_current_work` | `scope.contribute` on request `scope_id` | +| `commit_handoff` | `scope.contribute` on request `scope_id` | +| `continue_handoff(selection=latest)` | `scope.read` on request `scope_id` | +| `continue_handoff(selection=exact)` | `artifact.read` and `handoff.evidence.read` on exact `family=handoff` Artifact, directly or through parent `scope.read` | +| `continue_handoff(selection=prepared)` | `scope.read` on request `scope_id` | +| `acknowledge_handoff` with exact receipt | `scope.contribute` or `handoff.acknowledge` on exact Revision | +| `record_task_outcome` | `scope.contribute` on request `scope_id` | +| aggregated Handoff Report queries | scope-level read; exact Handoff grant is insufficient | +| Handoff Report administration | `scope.admin` or appropriate server administration action | + +当 exact receiver 调用 Continue 时,请求必须提供 `selection=exact` 和 exact `ArtifactReference`。Server 先建立 Handoff +ArtifactResourceRef 并判定,再读取 Revision。它不能先解析 latest 再检查,也不能在 exact 缺失时回退到 latest。 + +Prepared Handoff 可以包含由调用方提交的完整内容,因此窄授权模式不接受 `selection=prepared`。只有已经拥有 +`scope.read` 的 Principal 才能用 prepared selection 解引用 scope evidence。 + +## Artifact Family operation requirements + +Family operation 映射如下。表中的 “scope or exact” 由 Provider 的 parent relation 实现,不让客户端选择绕过路径: + +| Operation family | Required authorization | +| --- | --- | +| Memory search/list/changes | `scope.read` on request `scope_id`;exact Memory grant 不足 | +| exact Memory get | `artifact.read` on exact `family=memory` Artifact plus complete `memory_entry` selector, directly or through parent `scope.read` | +| Memory flush/remember/revise/retire | `scope.contribute`; exact viewer grant 不足 | +| approved Experience/managed Skill exact get | `artifact.read` on exact `ArtifactReference`, directly or through parent `scope.read` | +| Experience/Skill propose or generate | `scope.contribute` | +| Candidate list/get | `scope.read`; exact Artifact grant 不暴露 Candidate | +| Candidate revise/approve/reject | `scope.review` | +| approved Prompt exact get | `artifact.read` on exact `family=prompt` Artifact, directly or through parent `scope.read` | +| approved Prompt render/use | `prompt.use`, directly or through parent `scope.read` | +| Prompt propose/revise | Prompt lifecycle 定义的 Candidate operation plus `scope.contribute` | +| list enabled publication targets for an exact managed Skill | `artifact.read` **and** `skill.publish` on the same exact `family=skill` Artifact | +| publish managed Skill | `artifact.read` **and** `skill.publish` on the same exact `family=skill` Artifact | + +Exact get resolver 必须从已验证 request 中直接取得完整 identity。Memory `entry_id`、Artifact `artifact_id` 或 Prompt name +都不能单独作为授权 key。Search、current-head selection、aggregated projection 和 Candidate Inbox 仍是 collection +operation,不能通过一个 exact grant 进入。 + +Prompt Family Access Profile 只规范 authorization vocabulary 和 resolver contract。部署只有在注册 `family=prompt` 的 +immutable approved Artifact lifecycle,并提供与本节一致的 exact get/use operation 后,才能报告该 Family enabled。 +不支持 Prompt domain operation 的版本仍可实现其他 Family,但不能接受 `family=prompt` Binding 或在 `roles/list` 中声称 +`prompt.user` 可用。 + +`target_id` 是 Server 配置的发布 operation parameter,不是授权 key 或 Resource。只有 `server.admin` 可以配置、修改或 +移除 target;详细 target status 由 `server.observe` 或 `server.admin` 保护。Operator status response 只能返回 target ID、 +Agent kind、capability、desired/applied exact Revision、稳定 state 和安全 reason code,不能返回 host path、Agent home、 +credential 或原始 OS error。在发布和 publisher target-list 请求中,Server 必须先允许 exact Skill 的两个 requirement,再 +解析 `target_id` 或读取 target registry;独立的 operator status 请求则先判定 server-level action。 + +## OpenAPI access metadata + +每个受保护 operation 在 `openapi/powercontext.yaml` 中声明 `x-powercontext-access`。生成器把该 extension 生成到 +`Operation.access`,Server `_add_route()` 使用它组装 PEP wrapper。示例: + +```yaml +/v1/handoff/commit: + post: + operationId: commit_handoff + x-powercontext-access: + action: scope.contribute + resource: + type: scope + scope-id-from: body.scope_id +``` + +具有 selection-dependent policy 的 operation 使用已注册 resolver name,而不是在 YAML 中嵌入可执行表达式: + +```yaml +x-powercontext-access: + resolver: continue_handoff_access +``` + +Resolver 是 Server-owned、经过单元测试的确定性函数。它只能从已验证 request model 和 route metadata 建立 +AccessRequest,不能读取业务 Repository 后才决定是否授权。 + +需要多个 requirement 的 operation 使用 resolver。Publisher target selection 和 publish 复用同一个 exact Skill resolver: + +```yaml +/v1/skills/publication-targets/list: + post: + operationId: list_skill_publication_targets + x-powercontext-access: + resolver: publish_managed_skill_access + +/v1/skills/publish: + post: + operationId: publish_managed_skill + x-powercontext-access: + resolver: publish_managed_skill_access +``` + +生成的 `Operation.access` 必须能够表示 static single requirement 或 named resolver。Resolver 的 Server-side return type +支持多个 `all` requirements;生成 transport 不复制 policy 逻辑,只携带当前 Principal 并调用同一 Server operation。 + +Health endpoint、静态 page shell 和认证 callback 可以显式声明 public。没有 access metadata 的新增业务 operation +使 contract generation 或 contract test 失败,不能默认 public。 + +## Server PEP + +请求顺序固定为: + +```text +transport authentication + -> bind Principal and trusted request context + -> validate request schema + -> resolve action and resource + -> AuthorizationProvider decision + -> application service + -> response +``` + +Schema validation 和不访问 Repository 的 Family/selector compatibility validation 可以在判定前完成,以安全获得 resource +identity;验证错误不得包含资源内容。任何 Repository lookup、Handoff resolution、Memory search、Artifact Family read、 +target lookup、host inspection、Report aggregate 或 mutation 都在全部必要 requirement allow 之后发生。 + +PEP 位于 Server adapter,不向 `application.context.for_scope(...)`、Source、Memory、Handoff、Work 或 Review domain method +添加 `principal`、role 或 permission 参数。Local in-process Runtime 调用不自动获得 Server authentication;需要安全边界 +的本地集成应调用同一 Access Control service 或通过 Server。 + +## HTTP, MCP, and Dashboard parity + +HTTP 是完整远程 contract,MCP 和 Dashboard 复用同一 operation 和 PEP: + +- HTTP authentication 建立 Principal 后,授权 wrapper 对每个 operation 执行; +- MCP internal ASGI bridge 把原 Principal、actor 和 request ID 放入 request-local context; +- `is_internal_bridge()` 可以避免再次解析同一个外部 credential,但授权 wrapper仍执行; +- MCP tool discovery 可以根据当前 Principal 过滤不可用工具,但隐藏工具只是 UX,调用时仍必须判定; +- Dashboard 根据 `access/me`、authorized resource list 和 batch check 展示 Handoff inbox 或 “Shared with me”,并禁用或隐藏 + 不可用操作,同时不能绕过 API enforcement; +- background job 必须携带创建 job 时绑定的 service Principal 或显式 system Principal,不使用空 identity。 + +HTTP 和 MCP 对同一 Principal、action、resource、policy revision 必须得到相同 allow/deny。Adapter conformance test 覆盖 +这一保证。 + +## Listing and pagination + +列表最容易泄漏 Project 名称、scope ID、Artifact Family identity、Handoff objective 或 Candidate metadata。安全顺序为: + +```text +AuthorizationProvider.resolve_resource_filter + -> validate bounded exact keys and parent constraints + -> Repository query applying their union + -> stable pagination + -> response +``` + +禁止以下实现: + +```text +Repository.list_all -> page -> check each item -> remove denied rows +``` + +这种实现会泄漏总数、cursor、空洞和时序,也可能让授权用户永远看不到后面的记录。Repository 必须在同一个 query 中 +应用 exact key 与 parent constraint 的并集;`total`、cursor 和 page boundary 必须只描述授权后的集合。 + +Artifact exact receiver 通过 `/v1/access/resources/list` 的 Resource Kind 和 Family filter 发现授权资源;这些资源不会因此 +出现在聚合 Project、Workstream、Memory search、Artifact catalog 或 Candidate Inbox。只有 scope-level read 才允许进入 +对应聚合查询。发布 target 不是授权资源,不出现在该列表中。拥有 exact Skill 发布权限的 Principal 通过 Skill domain +preflight 取得脱敏 target 选项;详细运维状态通过受 `server.observe` 或 `server.admin` 保护的 Server operation 查询。 + +## Audit and diagnostics + +Access Audit 是 append-only Server security record,至少包含: + +- request ID、time、transport 和 operation ID; +- Principal opaque identifier 和可信 actor identifier(若存在); +- action、Resource Kind、可选 Artifact Family 和 opaque resource identity; +- allow/deny、稳定 reason code 和 policy revision; +- Binding create/revoke 的 binding ID、grantor、recipient subject、role 和 expected/result version。 + +Audit 不包含: + +- Bearer token、cookie、client secret 或 PDP credential; +- Handoff objective/state/next action; +- Source、Memory、Artifact、Prompt、PreparedContext 或 citation body; +- publication target locator、host path、credential reference 或原始 Receiver/OS error; +- 任意 exception fields、configured PDP URL 或 provider 原始 response; +- email、display name 或不必要的目录属性。 + +普通 log、metric 和 trace 使用同样的数据最小化边界。Public readiness 只返回稳定 component state 和安全 reason,详细 +provider diagnostics 留在受保护的 operator channel。 + +## Consistency and failure recovery + +Commit Handoff 与创建外部授权关系不是跨系统原子事务。UI 中的“发送给 B”按以下可恢复步骤执行: + +1. commit 或复用同一精确 Handoff Revision; +2. 使用稳定 idempotency key 创建 Binding; +3. 只有两步都成功才显示“已分享”; +4. 第二步失败时显示“交接已保存,但 B 尚不可见”,并只重试 Binding create; +5. 不重新 prepare、commit 或创建另一个 Revision。 + +Binding 已成功而客户端丢失响应时,同一 idempotency key 返回原 Binding。外部 RelationshipWriter 无法提供等价幂等 +保证时,adapter 必须先执行安全的 exact relationship lookup,或声明不支持 self-service mutation。 + +所有 Artifact Family 分享遵循相同的 “persist/approve first, bind second” 原则。Binding create 失败不回滚或重建业务 +Revision;客户端只重试同一个 idempotent Binding mutation。Skill publish 则是一次受双重授权保护的 projection +operation,不创建内容 Revision,也不创建 target Binding 或改变 target authorization state。Target apply 失败保留可重试的 +desired/applied 状态和安全 reason,不把本地路径或底层错误写入公共 audit。 + +Receipt 创建仍使用现有 exact-selection 和 evidence rules。授权判定发生在 Receipt transaction 前;授权在判定后立即 +被并发撤销时,Provider 和 Binding Store 应在同一 deployment 中使用 policy revision 或 transaction fence 防止明显 +越权。跨网络 PDP 的剩余 TOCTOU 窗口必须有界并记录 decision revision;首版不缓存 allow decision。 + +## Provider profiles + +### Built-in provider + +内置 profile 使用固定角色和 Server-owned Binding Store,支持 point check、batch check、从 exact/scope/server Binding +生成可下推 `AuthorizedResourceFilter`、create、revoke 和 audit。它不需要保存业务 resource inventory,是本地部署和 +conformance test 的参考语义;它不提供用户密码、目录或自定义 policy language。 + +### Casbin adapter + +Casbin adapter 可以使用带 domain 的 RBAC: + +- subject 映射为 issuer-scoped opaque ID; +- domain 对 server resource 映射为 deployment access namespace,对 scope/artifact resource 映射为 canonical scope + resource namespace; +- object 映射为 canonical server key、scope key 或包含 Family/selector 的 canonical Artifact key; +- action 使用本 RFC 的 action vocabulary; +- role assignment 和 policy mutation 通过 Casbin management API 与持久化 adapter 完成。 + +Casbin domain 是 adapter policy namespace,不把 `scope_id` 变成认证或 tenant 证明。Adapter 仍从 Server 传入的可信 +ResourceRef 建立 domain。生成列表 filter 时,exact object policy 产生 canonical key,scope/server role assignment 产生 +对应 parent constraint;Casbin adapter 不需要枚举业务 Repository。 + +### OpenFGA adapter + +OpenFGA 适合表达用户、group、scope 和 exact child resource 的关系。所有 Artifact Family 使用一个 `artifact` object type; +object ID 包含 canonical Family、Revision 和 selector,Server 在 tuple write 前用 Family registry 校验 relation compatibility。 +这样新增只读 Family 不需要新增 OpenFGA type: + +```text +type user + +type server + relations + define observer: [user] + define admin: [user] + define can_observe: observer or admin + define can_admin: admin + +type scope + relations + define parent: [server] + define viewer: [user] + define contributor: [user] + define reviewer: [user] + define delegator: [user] + define admin: [user] + define can_read: viewer or contributor or reviewer or delegator or admin or admin from parent + define can_contribute: contributor or admin or admin from parent + define can_review: reviewer or admin or admin from parent + define can_delegate: delegator or admin or admin from parent + define can_admin: admin or admin from parent + +type artifact + relations + define parent: [scope] + define viewer: [user] + define handoff_viewer: [user] + define handoff_receiver: [user] + define prompt_user: [user] + define skill_publisher: [user] + define can_read: viewer or handoff_viewer or handoff_receiver or prompt_user or skill_publisher or can_read from parent + define can_read_handoff_evidence: handoff_viewer or handoff_receiver or can_read from parent + define can_acknowledge_handoff: handoff_receiver or can_contribute from parent + define can_use_prompt: prompt_user or can_read from parent + define can_publish_skill: skill_publisher or can_admin from parent +``` + +Adapter 把 `server.observe` 映射到 `server#can_observe`,把 `server.admin` 映射到 `server#can_admin`。`admin from parent` +继续使 deployment `server.admin` 单向蕴含 scope administration 和 child Artifact Family action;`server.observer` 不获得 +这些权限。 + +Adapter 使用固定 authorization model ID 执行 Check、ListObjects 和 tuple write。Tuple 只保存 opaque ID,不保存 email +或 Handoff 文本。Model migration 在 deployment configuration 中显式切换,不自动使用“latest model”。 +列表中,exact relation 可以通过 ListObjects 产生 canonical key;scope/server role 直接产生可信 parent constraint,不要求 +为每一个没有 exact Binding 的业务 Artifact 预先写入 object tuple。 + +### AuthZEN, OPA, and Cerbos adapters + +AuthZEN adapter 把 `AccessRequest` 映射为 Authorization API 的 subject、action、resource、context,把 decision 映射回 +`AccessDecision`。OPA adapter 可以把相同结构作为 input document;Cerbos adapter 可以映射为 principal、resource +和 actions。 + +这些 adapter 的 decision interoperability 不代表 policy administration interoperability。若组织在 GitOps、IAM 或 +独立管理面维护 policy,PowerContext 只消费判定和安全 resource filter,不写 policy。部署必须明确 +`relationship_management=false`,Dashboard 不显示成功的 self-service share control。若 adapter 不能从 PDP search 或 +可信关系数据产生 `AuthorizedResourceFilter`,还必须报告 `safe_resource_filtering=false`。 + +## Configuration and compatibility + +Server 提供三种显式 mode: + +| Mode | Behavior | +| --- | --- | +| `disabled` | 保持单用户、单 trust-domain 的现有行为;Access API 不可用,不宣称多用户隔离 | +| `legacy-static-admin` | 现有静态 Bearer 映射为 deployment-local `server.admin` Principal | +| `enforced` | 认证 Provider 和 AuthorizationProvider 都是 required dependency,所有业务 operation 执行 PEP | + +升级不能因为配置了外部身份但漏配 PDP 而回退到 `disabled`。Mode 必须显式,capabilities 和 readiness 报告当前 mode 与 +是否支持 relationship management、batch check 和 `safe_resource_filtering`。 + +`disabled` 只适用于调用方已经信任整个进程和 catalog 的本地场景。文档不能把它描述为多用户安全配置。远程、多用户或 +共享 Dashboard 部署应使用 `enforced`。 + +`access/me` 和 readiness 还必须报告启用的 Resource Kind,以及 `artifact_families` capability map。每个 Family 条目至少 +包含 `enabled`、`share_unit`、可用 action 和 grantable role;例如未实现 Prompt lifecycle 时 `prompt.enabled=false`。 +`operation_capabilities.skill_publication` 单独报告 host-local managed Skill 发布及其 publisher-safe target selection 是否 +可用;只有 Skill Family、两个 domain operation 和至少一个 enabled host-local target 都可用时才能为 true。它不是 +Resource Kind 或可绑定 profile。Provider 不支持 `safe_resource_filtering`、多 requirement check 或 relationship mutation +时,相应 capability 必须为 false;Server 不能接受随后无法 enforce 或撤销的 Binding。 + +```json +{ + "resource_kinds": ["server", "scope", "artifact"], + "provider_capabilities": { + "safe_resource_filtering": true, + "multi_requirement_check": true, + "relationship_management": true + }, + "artifact_families": [ + { + "family": "memory", + "enabled": true, + "share_unit": "memory_entry", + "actions": ["artifact.read"], + "grantable_roles": ["artifact.viewer"] + }, + { + "family": "prompt", + "enabled": false, + "share_unit": "revision", + "actions": [], + "grantable_roles": [] + } + ], + "operation_capabilities": { + "skill_publication": {"enabled": true} + } +} +``` + +现有 OpenAPI operation 首次增加 authorization metadata 不改变 request/response domain schema,但会增加 403 response +并改变未授权行为。Generated Client 把 401、403 和 503 映射为稳定、不同的 exception;不能把 403 当作空结果。 + +## Implementation slices + +实现按以下可独立验证的 slice 推进: + +1. **Contract and Principal**:OpenAPI Access model、operation metadata、generated `Operation.access`、可信 request + Principal 和 stable errors。 +2. **Built-in PEP/PDP**:固定角色、Binding Store、`_add_route()` authorization wrapper、point/batch check、audit。 +3. **Handoff exact receiver**:commit 后创建 Binding、exact Continue、citation-manifest resolver、exact acknowledge、 + revoke 和 expiration。 +4. **Artifact Family Access Profiles**:统一 ArtifactResourceRef、Family registry、Memory selector、exact read/use resolver、 + 角色兼容性与非传递 lineage。 +5. **Skill publication**:Server-configured host-local target registry、publisher-safe selection、operator status、同一 + exact Skill 上的 read plus publish requirement,以及脱敏失败状态。 +6. **Safe listing and UI**:authorized resource listing、Handoff inbox、“Shared with me”、Dashboard permission projection、 + 授权后分页。 +7. **MCP parity**:Principal 通过 internal bridge 传播、tool discovery UX 和调用时 enforcement。 +8. **External adapters**:先完成 Casbin 或 OpenFGA 之一,再用同一 conformance suite 验证 AuthZEN-compatible PDP。 +9. **Migration**:legacy static admin、configuration validation、Family capability、readiness、operator documentation。 + +每个 slice 都保持 Server 可运行,不能先发布只隐藏 Dashboard 按钮或只保护 HTTP、不保护 MCP 的中间状态。 + +## Test and acceptance plan + +RFC 实现完成需要通过以下 observable scenarios: + +- 无身份访问受保护 operation 返回 401; +- A 有 `scope.delegate` 时只能把所属 scope 中已存在、committed 的 exact Handoff Revision 以 `handoff.viewer` 或 + `handoff.receiver` 授予 B;其他 Artifact Family 或 role 返回 422,缺少该 action 时返回 403,且都不写 Binding; +- B 可以读取、Continue 和 acknowledge 被授予的 exact Revision; +- B 请求 latest、相邻 Revision、聚合 Handoff Report、Memory list、Source list 和 Task Outcome write 均被拒绝; +- B 只能通过被授权 Handoff 的 resolver 读取 manifest citation,不能用任意 citation 调用通用读取接口; +- `handoff.viewer` 不能 acknowledge,`handoff.receiver` 可以; +- `accepted` Receipt 不产生新的 Binding 或 scope role; +- revoke 或 expiration 后,B 的后续 access 被拒绝,authorized resource list 不再包含该 Revision; +- Binding create/revoke 的 CAS、idempotency 和 audit 行为稳定; +- 403 不泄漏资源是否存在,list cursor 和 total 只描述授权集合; +- PDP unavailable 返回 503,且 application service、Repository 和 mutation 未被调用; +- MCP internal bridge 使用原 Principal 并执行与 HTTP 相同的 deny; +- Dashboard 隐藏控制失效或被绕过时,API 仍拒绝请求; +- legacy static token 只在显式 mode 中映射为 local admin; +- `server.observer` 可以读取受保护的服务和 publication status,但不能修改 access 或 target configuration; + `server.admin` 可以执行两类 operation,且 Built-in、Casbin 和 OpenFGA 的结果一致; +- Built-in、Casbin/OpenFGA 和 AuthZEN adapter 对同一 conformance vector 返回相同结果; +- 请求不能提交独立的 content profile;未知/disabled Family、`revision=latest`、缺失或多余 selector,以及 + Family-role mismatch 返回 422 且不写 Binding; +- `artifact.viewer` 在 Experience、Skill、Prompt 和 `memory_entry` selector 上始终只映射为 `artifact.read`,不会因 Family + 不同隐式增加 use、publish、acknowledge 或 mutation action; +- `artifact.viewer` 可以通过 `family=memory` 和完整 `memory_entry` selector get 被授权的 Memory Entry,但不能 + search/list/current/revise/retire 或读取相邻版本; +- exact Artifact viewer 可以读取 approved Experience/managed Skill Revision,但不能看到 Candidate、future Revision 或 + 解引用 lineage body; +- `artifact.viewer` 只能读取 Prompt,`prompt.user` 可以显式 use;两者都不能改变宿主 instruction priority 或自动进入 + 普通 recall; +- exact-resource role 即使知道 expected version,也不能 revise、retire、replace 或提交共享原件的下一 Revision; +- acknowledge 创建的 Receipt 和 publish 创建的 target projection 不改变源资源的 identity、content、Revision 或 digest; +- fork、import 或 copy 在没有目标 scope 的 `scope.contribute` 时被拒绝;授权后创建新的 identity 或 Candidate,并保持原资源 + 不变; +- managed Skill publish 只有在同一个 exact Skill 的 `artifact.read` 和 `skill.publish` 均 allow 时执行,任一 + deny/unavailable 都不得解析 `target_id`、检查 host path 或写 projection;授权通过后,unknown 或 disabled target 仍必须 + 拒绝发布; +- publisher target-list 只有在同一个 exact Skill 的两个 requirement 均 allow 后才能读取 registry,并且只返回 enabled + target 的 safe identity/capability;详细 status 仍要求 `server.observe` 或 `server.admin`; +- 首版拒绝 remote Receiver target,并且不得尝试读取 remote credential 或建立网络连接; +- `skill.publisher` 可以把被授权的 exact Skill 发布到 deployment 中任一 enabled target;首版没有 target Binding 或 + per-target delegation; +- `resources/list` 的 total、cursor 和 rows 只描述当前 Principal 对所选 Resource Kind 和 Artifact Family 有权发现的集合; +- 不支持 Prompt lifecycle 的部署拒绝 `family=prompt` Binding;没有可用发布 operation 的部署准确报告 + `operation_capabilities.skill_publication.enabled=false`; +- Access Audit 不包含 token、Handoff/Memory/Artifact/Prompt 正文、Source body、target locator 或 PDP 原始错误。 + +Cross-component acceptance scenarios 放在 `tests/e2e/`,并通过公开 HTTP/MCP contract 断言行为。Focused tests 覆盖 +Family registry、selector/canonical key、resource resolver、role mapping、Binding CAS、provider failure 和 citation +membership,不冻结 private call order。 + +# Drawbacks + +每个业务请求增加一次授权判定,外部 PDP 还会增加网络依赖和延迟。安全列表要求 Provider 产生有界、可下推的 +`AuthorizedResourceFilter`,只有 point-check 的简单 adapter 无法支持全部 Dashboard 列表。 + +精确 Handoff 分享必须先 commit,因此不能把临时 Prepared Handoff 直接变成可撤销的跨用户资源。这增加一步持久化, +但避免为临时 payload 发明第二套 identity 和 ACL。 + +判定和关系管理分离使 adapter interface 比单一 `check()` 更复杂;另一方面,假设所有外部 PDP 都允许 PowerContext 写 +policy 会制造错误的可移植性承诺。 + +撤销只能阻止未来访问,无法删除接收方已经阅读、截图或导出的信息。包含高度敏感内容的 Handoff、Memory、Artifact 或 +Prompt 仍需要最小化内容、外部数据分类和导出控制。 + +Artifact Family Access Profile 增加了 registry、selector、角色兼容矩阵和 conformance vector。Skill publish 还需要在 +同一个 exact Artifact 上判定 `artifact.read` 和 `skill.publish`;外部 PDP 不提供原子 multi-requirement decision 时会增加 +延迟,并留下必须记录 policy revision 的有界 TOCTOU 风险。 + +首版不把 target 纳入授权策略。拥有某个 exact Skill 的 `skill.publisher` 可以把它发布到 deployment 中任一 enabled +target。需要按 target 隔离发布权限的部署必须暂缓该能力、隔离 deployment,或等待独立 RFC 定义通用 +`execution_target` Resource;本 RFC 不用一个 Skill 专属资源提前固化这套模型。 + +Prompt Family Access Profile 只定义授权边界,不能代替 Prompt Artifact lifecycle 和宿主 instruction-priority contract。 +部署在这些业务能力完成前必须报告该 Family 不可用,因此 RFC 可以先落地其他 Family,但产品不会同时获得全部用户体验。 + +固定首版角色限制了组织自定义体验。企业可以在外部 PDP 映射自己的角色,但 PowerContext 公共 API 不立即提供自定义 +role editor。 + +# Rationale and alternatives + +## Chosen: independent Server PEP plus replaceable PDP + +该设计保持 Handoff、Memory、Artifact、Prompt 和 Runtime model 与身份系统解耦,同时让 HTTP、MCP 和 +Dashboard 共用 enforcement。稳定 action vocabulary 比稳定外部 role name 更容易跨 Casbin、OpenFGA、OPA、Cerbos 和 +企业 IAM 映射。 + +AuthZEN-compatible request shape 使网络 PDP 有标准接入点;独立 RelationshipWriter 则诚实表达 grant mutation 并未被 +AuthZEN 统一。 + +## Alternative: put ACL fields on Handoff or scope + +在 Handoff 增加 `allowed_users`,或把 owner/tenant 编入 `scope_id`,实现看似直接,但会把身份生命周期、group expansion、 +撤销、外部 policy revision 和审计塞进领域数据。不可变 Handoff 也不适合随成员变更而创建新 Revision。该方案被拒绝。 + +## Alternative: only use scope-level roles + +只授予 `scope.viewer` 容易实现,但 B 会看到整个 Workstream 的 Memory、Source、历史和 Report。对于临时接力不符合最小 +权限原则。Scope roles 保留给长期协作,exact-resource Binding 负责一次性交接或资产分享。 + +## Alternative: add one share API per domain + +`/memory/share`、`/experience/share`、`/skill/share` 和 `/prompt/share` 会重复 Principal、Binding、expiration、revoke、audit 与 +external PDP semantics,还容易让不同 transport 出现不一致。本 RFC 选择一个 Access API、统一 ArtifactResourceRef、 +Family role compatibility 和 resolver;业务 API 仍由各 domain 拥有。 + +## Alternative: 每个 Artifact Family 使用一个 Resource Kind + +为 `handoff`、`memory_entry`、`experience`、`skill` 和 `prompt` 分别增加 `ResourceRef.type`,会重复 scope parent、exact +Revision、canonical key 和只读分享结构;每新增一个 Family 还必须扩展 OpenAPI discriminator 和外部 PDP object type。 +它也会让 `ResourceRef.type` 与 `ArtifactReference.family` 成为两个可能冲突的内容 discriminator。本 RFC 选择统一 +`artifact` Resource Kind,由 Server 从 `ArtifactReference.family` 派生 Access Profile;只有 Memory 等需要更细授权单元的 +Family 增加显式 selector。 + +## Alternative: automatically recall every shared resource + +把所有 exact grant 自动加入 PreparedContext 会混淆可见性与相关性,扩大 token budget,并让不可信 Prompt 或 Skill 在接收方 +没有显式选择时影响模型。首版只提供授权发现与显式附加;后续若增加 shared collection 或 subscription,仍必须经过独立的 +Context selection policy。 + +## Alternative: send an anonymous capability URL + +Bearer share link 把“知道 URL”变成身份。链接可能进入聊天、日志、浏览器历史或模型上下文,难以确认实际接收者,也难以 +执行企业 group policy 和个人审计。首版要求 B 使用自己的认证凭据,不提供匿名 capability URL。 + +## Alternative: copy a redacted Handoff document + +复制 Markdown 可以减少 Server 权限工作,但会失去 exact Revision、evidence availability、Receipt、并发和撤销语义。 +导出仍可作为显式的外部发布功能,不能替代 PowerContext 内部交接。 + +## Alternative: hide unauthorized Dashboard controls + +UI 隐藏只能改善体验,HTTP 或 MCP 调用仍可绕过。所有 enforcement 必须发生在 Server PEP,Dashboard 仅消费相同判定。 + +## Alternative: require one policy engine + +Casbin 适合 embedded RBAC,OpenFGA 适合关系和 group,OPA/Cerbos 适合已有 policy platform。强制一个实现会增加部署成本或 +限制企业集成。PowerContext 定义语义和 conformance contract,不选择唯一 engine。 + +## Alternative: store roles in access token + +Token role 简单但对 exact Handoff grant、撤销、large resource set 和 policy update 不友好。Token 可以携带可信 identity +和 group claims,最终 resource decision 仍由 PDP 完成。 + +## Alternative: authorize inside every Runtime method + +把 Principal 参数传入 Context、Source、Memory、Handoff 和 Work 会扩散 transport policy,容易让 HTTP 与 MCP 产生不同 +实现,也破坏本地 domain API。Server PEP 是当前远程 trust boundary 的单一 enforcement point。 + +# Prior art + +PowerContext [RFC 0011](0011_remote_access_architecture.md) 已定义 HTTP 完整 contract、generated Client 和 MCP 投影共享 +Server application semantics。本 RFC在同一 Server boundary 增加 authentication 和 authorization,不创建平行 MCP +policy service。 + +[RFC 0048](0048_handoff_artifact.md) 定义 Prepared Handoff、不可变 Handoff Revision、Continue 和 exact evidence; +[RFC 1223](1223_human_agent_work_continuity.md) 定义 Receipt 和 Task Outcome,并明确交接不能授予工具、网络或凭据权限; +[RFC 0082](0082_handoff_report.md) 提供 scope 和 Project 级聚合视图。本 RFC 为这些读取和写入补充 Principal-aware +visibility。 + +[RFC 0050](0050_artifact_candidate_review_inbox.md) 定义 Experience/Skill Candidate 与 Review gate;pending/rejected +Candidate 不是可分享 Artifact。[RFC 0051](0051_experience_skill_artifact_families.md) 定义 exact Experience/managed Skill +Revision、External Skill host-local authority,以及 approval/publication 不等于执行授权。本 RFC 只增加这些资源的 +Principal-aware visibility 和 managed Skill publication authorization,不改变其内容权威。 + +[OpenID AuthZEN Authorization API 1.0](https://openid.net/specs/authorization-api-1_0.html) 定义 PEP 与 PDP 之间的 +subject、action、resource、context 和 decision contract。本 RFC 对齐其信息模型,但保留 embedded Provider。 + +[Casbin RBAC with Domains](https://casbin.apache.org/docs/rbac-with-domains/) 展示 domain-scoped role assignment; +[OpenFGA concepts](https://openfga.dev/docs/concepts) 使用 user、relation、object tuple 表达 object-level authorization; +[OPA](https://www.openpolicyagent.org/docs/integration) 提供通用 policy decision integration; +[Cerbos CheckResources](https://docs.cerbos.dev/cerbos/latest/api/index.html) 提供 principal、resource 和 action 的批量判定。 +这些系统是 adapter 目标,不改变 PowerContext 的 Handoff lifecycle。 + +# Unresolved questions + +以下问题需要在 RFC 合并前确认,但不改变核心安全边界: + +- 首个外部 conformance adapter 选择 Casbin 还是 OpenFGA; +- 内置 Provider 是否随默认 Server extra 安装,还是作为独立 optional extra; +- Dashboard 如何从部署方的身份目录选择 canonical recipient;目录搜索本身不由本 RFC 的 Access API 提供; +- enforced deployment 是否要求 Provider 同时支持 `safe_resource_filtering`,还是允许禁用相关 Dashboard 列表; +- `handoff.receiver` 的产品默认过期时间是否由 deployment policy 决定,还是 UI 必须每次显式选择; +- exact receiver 创建 Receipt 后,UI 是否建议管理员另行授予 `scope.contributor`,但不能自动执行该升级; +- Prompt Artifact 的后续 lifecycle 采用固定 Review policy,还是区分个人私有模板与组织 approved template。 + +以下问题明确推迟:custom role、organization hierarchy、cross-tenant export、anonymous share link、temporary elevation、approval +workflow、通用 Source object-level ACL、动态 Memory collection、Artifact catalog 分享和自动跟随 future Revision。它们需要 +独立威胁模型和 RFC。 + +# Future possibilities + +后续可以在不改变 subject/action/resource contract 的前提下增加: + +- group、team 和 organization relation; +- Project 到 Workstream 的继承策略和显式 deny; +- 管理员代查、subject/resource search 和 access review campaign; +- 带审批的临时 scope elevation; +- AuthZEN Search API、obligation 和 richer decision metadata; +- policy bundle、signed decision metadata 和跨服务 audit correlation; +- 对 Handoff 导出的独立脱敏、watermark 和 data-loss-prevention policy; +- 注册更多 approved Artifact Family 使用现有 `artifact` Resource Kind 和基础 `artifact.read` action; +- 用独立 RFC 定义可供 Skill、Prompt 或其他 execution content 共用的 `execution_target` Resource Kind 和 per-target grant; +- 在独立 Receiver distribution contract 和 trust-boundary review 完成后增加 remote managed Skill target; +- 带显式成员和 Revision manifest 的共享 collection,以及经过 Context policy 的订阅式选择; +- 在有明确 revocation-staleness guarantee 后增加 bounded decision cache。 + +这些扩展不能改变首版不变量:`scope_id` 不是 ACL,资源内容不授予权限,exact grant 不跟随 future Revision,读取不自动 +进入 Context 或获得执行权,所有 transport 在 Server PEP fail closed。