From fd356c46c809c3cabbb20bc1872b0c8a7ff17163 Mon Sep 17 00:00:00 2001 From: Teingi Date: Sun, 30 Aug 2026 16:20:29 +0800 Subject: [PATCH 1/3] docs(rfc): define Handoff access control --- docs/en/rfcs/0000_handoff_access_control.md | 949 ++++++++++++++++++++ docs/zh/rfcs/0000_handoff_access_control.md | 872 ++++++++++++++++++ 2 files changed, 1821 insertions(+) create mode 100644 docs/en/rfcs/0000_handoff_access_control.md create mode 100644 docs/zh/rfcs/0000_handoff_access_control.md diff --git a/docs/en/rfcs/0000_handoff_access_control.md b/docs/en/rfcs/0000_handoff_access_control.md new file mode 100644 index 000000000..264b0771d --- /dev/null +++ b/docs/en/rfcs/0000_handoff_access_control.md @@ -0,0 +1,949 @@ +- Proposal Name: `handoff_access_control` +- Start Date: 2026-08-30 +- Status: Draft +- RFC PR: [oceanbase/powercontext#0000](https://github.com/oceanbase/powercontext/pull/0000) +- 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 0082](0082_handoff_report.md), and [RFC 1223](1223_human_agent_work_continuity.md) + +# Summary + +This RFC defines an independent Access Control boundary for the PowerContext Server and uses Handoff as the first +resource-level authorization profile. It 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? + +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 +``` + +User A can transfer work in two ways: + +- grant a Workstream role to a long-term collaborator; or +- grant B access to one exact committed Handoff Revision. + +The second option is the least-privilege path in the first version. B can read that Handoff, inspect only the evidence +explicitly cited by it through the Handoff resolver, and leave a Receipt for the same exact Revision. B does not gain +access to other Handoffs, Memory, or Sources in the scope. B also does not gain permission to commit a new Handoff, +record a Task Outcome, use tools, access the network, or read credentials. An `accepted` Receipt records the result of +the transfer; it does not grant authority. + +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, and Task +Outcomes. 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; +- 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 therefore needs an authorization layer independent of its 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. + +## 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": "handoff", + "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 reads the citation manifest from the immutable Handoff Revision, checks whether B has +`handoff.evidence.read` on that Handoff, and dereferences only 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. + +## 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, Sources, and read-only projections in the current scope; +- `scope.contributor` writes work evidence, Handoffs, and Outcomes 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. + +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 or a scope administrator can revoke an exact Handoff Binding created by A. 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; +- resolve evidence cited by an exact Handoff safely without opening the complete scope; +- 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. + +## 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 and Memory are `untrusted_history` 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. Public errors, logs, metrics, and traces do not contain credentials, Handoff content, Memory, Source bodies, 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 type | Identity | Parent | +| --- | --- | --- | +| `server` | Deployment identifier | None | +| `scope` | Exact `scope_id` | Server | +| `handoff` | Exact Handoff `ArtifactReference` plus `scope_id` | Scope | + +A Handoff resource includes `family`, `artifact_id`, and `revision`. 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 caller in an +already shared trust domain may still transmit a Prepared Handoff explicitly, but the receiver needs separate scope +authority to read its evidence. + +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, Handoff prose, 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 configuration | +| `scope.read` | scope | Read general resources and projections in a Workstream | +| `scope.contribute` | scope | Write Sources, Memory contributions, Handoffs, and Outcomes | +| `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 | +| `handoff.read` | exact handoff | Read one exact Handoff Revision | +| `handoff.evidence.read` | exact handoff | Resolve that Revision's citation manifest through the Handoff resolver | +| `handoff.acknowledge` | exact handoff | Create a Handoff Receipt for that Revision | + +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 `handoff.read` and `handoff.evidence.read` for Handoffs under the scope. +`scope.contribute` may imply acknowledge, prepare, commit, and Outcome writes. The reverse implication never holds: an +exact `handoff.receiver` does not gain `scope.read` or `scope.contribute`. + +## Built-in roles + +| Role | Granted actions | +| --- | --- | +| `handoff.viewer` | `handoff.read`, `handoff.evidence.read` on one exact Handoff | +| `handoff.receiver` | Viewer actions plus `handoff.acknowledge` on one exact Handoff | +| `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 action, including delegation and Binding administration | +| `server.observer` | `server.observe` | +| `server.admin` | Every server and scope action | + +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. + +## 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 list_resources( + self, + request: ResourceSearchRequest, + /, + ) -> AuthorizedResourcePage: ... +``` + +A normalized request is: + +```json +{ + "subject": { + "type": "user", + "issuer": "https://id.example.com/", + "id": "00u-bob" + }, + "action": {"name": "handoff.read"}, + "resource": { + "type": "handoff", + "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. + +`list_resources` is required for safe list operations. It obtains allowed resource identities from the authorization +system before passing a bounded identity set to a Repository. A Provider that offers only point checks and cannot +produce a safe resource filter must not query all Handoffs, Projects, or Scopes and filter them afterward. The +affected list operation returns 503, or configuration rejects the Provider as missing a required capability. + +## 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`, `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 an exact-Handoff 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 target subject so A can name B, but the caller can create only fixed roles on +resources it may administer. Before writing, the Server reads the exact Handoff identity after authorization and +confirms that it exists in the target scope. + +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)` | `scope.read` or `handoff.read` on the exact Revision | +| `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 ResourceRef 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. + +## 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. + +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 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, Report aggregate, and mutation +runs after allow. + +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` and batch checks to disable or hide actions but 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, Handoff objectives, or Candidate metadata. The safe order is: + +```text +AuthorizationProvider.list_resources + -> bounded authorized identity filter + -> Repository query restricted by that filter + -> 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. `total`, +cursors, and page boundaries describe only the authorized collection. + +An exact Handoff receiver discovers granted Revisions through `/v1/access/resources/list`; this does not place the +receiver in aggregate Project or Workstream lists. Only scope-level read permits Handoff Report aggregate queries. + +## 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 type, and opaque resource identity; +- allow or deny, stable reason code, and policy revision; +- for Binding creation or revocation, binding ID, grantor, target, 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, PreparedContext, or citation bodies; +- 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. + +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, +authorized resource listing, creation, revocation, and audit. It is the reference semantics for local deployments and +conformance tests. It 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 to the canonical scope resource namespace; +- object maps to a scope or exact Handoff resource key; +- 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. + +### OpenFGA adapter + +OpenFGA naturally represents relationships among users, groups, scopes, and exact Handoffs. A conceptual model is: + +```text +type user + +type scope + relations + 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 + define can_contribute: contributor or admin + define can_review: reviewer or admin + define can_delegate: delegator or admin + +type handoff + relations + define parent: [scope] + define viewer: [user] + define receiver: [user] + define can_read: viewer or receiver or can_read from parent + define can_acknowledge: receiver or can_contribute from parent +``` + +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. + +### 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 search 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. + +## 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 listing 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`. + +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. **Safe listing and UI**: authorized resource listing, Handoff inbox, Dashboard permission projection, and + authorization-aware pagination. +5. **MCP parity**: Principal propagation through the internal bridge, tool-discovery UX, and invocation-time + enforcement. +6. **External adapters**: implement Casbin or OpenFGA first, then validate an AuthZEN-compatible PDP with the same + conformance suite. +7. **Migration**: legacy static admin, configuration validation, 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 an existing exact Revision to B; without that action the request returns 403 and + writes no 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; +- built-in, Casbin/OpenFGA, and AuthZEN adapters return equivalent decisions for the same conformance vectors; +- Access Audit contains no token, Handoff content, Memory, Source body, or raw PDP error. + +Cross-component acceptance scenarios belong in `tests/e2e/` and assert through the public HTTP and MCP contracts. +Focused tests cover 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 resource search or a filter that can be pushed down, 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 containing highly sensitive material still need content minimization, external data classification, and +export controls. + +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 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 +Handoff Bindings serve one-off transfers. + +## 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. + +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 listing 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. + +Custom roles, organization hierarchy, cross-tenant export, anonymous share links, temporary elevation, approval +workflows, and general Source or Memory object-level ACLs 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; +- exact-resource grants for more Artifact Families; +- 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, Handoff content does not grant +authority, a Receipt does not elevate authority, and every transport fails closed at the Server PEP. diff --git a/docs/zh/rfcs/0000_handoff_access_control.md b/docs/zh/rfcs/0000_handoff_access_control.md new file mode 100644 index 000000000..053457fa4 --- /dev/null +++ b/docs/zh/rfcs/0000_handoff_access_control.md @@ -0,0 +1,872 @@ +- Proposal Name: `handoff_access_control` +- Start Date: 2026-08-30 +- Status: Draft +- RFC PR: [oceanbase/powercontext#0000](https://github.com/oceanbase/powercontext/pull/0000) +- 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 0082](0082_handoff_report.md)、[RFC 1223](1223_human_agent_work_continuity.md) + +# Summary + +本 RFC 为 PowerContext Server 定义独立的 Access Control 边界,并把 Handoff 作为第一种资源级授权场景。它回答一个 +具体问题:当用户 A 把一份 Handoff 交给用户 B 时,B 可以看到什么、可以做什么,以及这些权限如何撤销和审计。 + +Handoff 内容不保存用户、角色或 ACL。`scope_id` 继续表示 Workstream 的稳定业务分区,不是用户身份、tenant、角色或 +安全边界。身份认证和权限判定发生在 Server:认证层得到可信 Principal,Policy Enforcement Point(PEP)把 +Principal、action 和 resource 交给可替换的 `AuthorizationProvider`,得到允许或拒绝决定后,才调用现有 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 +``` + +用户 A 可以选择两种交接方式: + +- 为长期协作者授予 Workstream 级角色; +- 只把一个已提交的精确 Handoff Revision 授予 B。 + +第二种方式是首版的最小权限路径。B 可以读取该 Handoff、通过 Handoff resolver 检查其中明确引用的 evidence,并对 +同一个精确 Revision 留下 Receipt;B 不会因此看到同一 scope 的其他 Handoff、Memory 或 Source,也不会获得提交 +新 Handoff、记录 Task Outcome、使用工具、访问网络或读取凭据的权限。`accepted` Receipt 记录接收结果,不授予权限。 + +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,但现有 +Server 认证是可选的全局静态 Bearer。一个有效 token 可以访问所有受保护 operation,Server 无法表达: + +- A 可以管理 Workstream,而 B 只能看一份交接; +- B 可以确认接收,但不能提交新的里程碑; +- 团队成员可以查看 Handoff Report,但不能审批 Experience 或 Skill; +- 被撤销的接收方不能继续读取后续 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 或审计事件。 + +## 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": "handoff", + "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 先从不可变 Handoff +Revision 得到 citation manifest,再检查 B 是否对该 Handoff 拥有 `handoff.evidence.read`,最后只通过 Handoff +resolver 解引用 manifest 中的 exact citation。B 不能把任意 Source ID 填入通用读取 API 来复用这项权限。 + +如果一条 citation 已被删除、retire、损坏或因更高层策略被拒绝,Continue 把对应 evidence 标记为 unavailable。 +Handoff Binding 不覆盖 retention、legal hold、数据分类或显式 deny policy。 + +## 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、Source 和只读投影; +- `scope.contributor`:在 viewer 基础上写入工作 evidence、Handoff 和 Outcome; +- `scope.reviewer`:在 viewer 基础上评审 Artifact Candidate; +- `scope.delegator`:在 viewer 基础上把精确 Handoff 分享给接收方; +- `scope.admin`:管理该 scope 的全部角色和策略。 + +固定角色是 wire-contract vocabulary,不要求外部 PDP 使用相同内部存储。外部系统可以把企业角色、团队或关系映射为 +这些 action。 + +## 撤销和过期 + +A 或 scope admin 可以撤销 A 创建的精确 Handoff Binding。撤销后: + +- 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; +- 允许安全解引用精确 Handoff 已引用的 evidence,而不开放整个 scope; +- 提供可替换的判定接口和可选的关系写入接口; +- 提供自助检查、资源发现、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 产品。 + +## 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 是 `untrusted_history`,不能授予 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. Public error、log、metric 和 trace 不包含 credential、Handoff 正文、Memory、Source body 或 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 type | Identity | Parent | +| --- | --- | --- | +| `server` | deployment identifier | none | +| `scope` | exact `scope_id` | server | +| `handoff` | exact Handoff `ArtifactReference` plus `scope_id` | scope | + +Handoff resource 必须包含 `family`、`artifact_id` 和 `revision`。Prepared Handoff 没有持久化 identity,不能创建精确 +Access Binding。跨用户最小权限分享必须先 commit;Prepared Handoff 仍可由已经共享同一 trust domain 的调用方显式 +传输,但接收方需要独立的 scope 权限才能读取 evidence。 + +Adapter 负责把结构化 ResourceRef 映射成外部 PDP object ID。映射必须 canonical、可逆或稳定,并避免把 email、token、 +Handoff 文本或其他 PII 写入 Casbin policy、OpenFGA tuple 或 audit key。 + +## Action vocabulary + +首版 action 是稳定、小写、点分隔的字符串: + +| Action | Resource | Meaning | +| --- | --- | --- | +| `server.observe` | server | 读取服务级运行状态和观测数据 | +| `server.admin` | server | 管理 deployment access configuration | +| `scope.read` | scope | 读取该 Workstream 的通用只读资源和投影 | +| `scope.contribute` | scope | 写入 Source、Memory contribution、Handoff 和 Outcome | +| `scope.review` | scope | 评审该 scope 的 Artifact Candidate | +| `scope.delegate` | scope | 为精确 Handoff 创建 viewer 或 receiver Binding | +| `scope.admin` | scope | 管理该 scope 的角色、Binding 和 policy | +| `handoff.read` | exact handoff | 读取一个精确 Handoff Revision | +| `handoff.evidence.read` | exact handoff | 通过 Handoff resolver 解引用该 Revision 的 citation manifest | +| `handoff.acknowledge` | exact handoff | 对该 Revision 创建 Handoff Receipt | + +业务 operation 检查 action,不检查 role name。这样可以调整外部角色或关系模型,而不改 application code。 + +`scope.read` 可以通过策略蕴含 scope 下 Handoff 的 `handoff.read` 和 `handoff.evidence.read`; +`scope.contribute` 可以蕴含 acknowledge、prepare、commit 和 Outcome 写入。反向蕴含不成立:精确 `handoff.receiver` +不能得到 `scope.read` 或 `scope.contribute`。 + +## Built-in roles + +| Role | Granted actions | +| --- | --- | +| `handoff.viewer` | `handoff.read`, `handoff.evidence.read` on one exact Handoff | +| `handoff.receiver` | viewer actions plus `handoff.acknowledge` on one exact Handoff | +| `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 actions, including delegation and Binding administration | +| `server.observer` | `server.observe` | +| `server.admin` | all server and scope actions | + +首版不允许通过公共 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 都不能授予自己高于调用方管理边界的权限。 + +## 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 list_resources( + self, + request: ResourceSearchRequest, + /, + ) -> AuthorizedResourcePage: ... +``` + +规范化 request 示例: + +```json +{ + "subject": { + "type": "user", + "issuer": "https://id.example.com/", + "id": "00u-bob" + }, + "action": {"name": "handoff.read"}, + "resource": { + "type": "handoff", + "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 而允许整批资源。 + +`list_resources` 是安全列表功能的必要能力。它先从授权系统得到允许的 resource identity,再把有界 identity set 交给 +Repository 查询。只支持 point check、无法安全产生 resource filter 的 Provider 不得先查询全部 Handoff/Project/Scope +再逐项过滤;对应 list operation 应返回 503 或在配置阶段被判为不具备所需 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`、 +`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` | 创建精确 Handoff 或管理级 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` 必须接收目标 subject,因为分享需要指定 B;调用方仍然只能在自己拥有管理权限的 resource 上创建固定 +角色。Server 在写入前重新读取精确 Handoff identity,确认它存在并属于目标 scope。 + +公共 `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)` | `scope.read` or `handoff.read` on exact Revision | +| `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 +ResourceRef 并判定,再读取 Revision。它不能先解析 latest 再检查,也不能在 exact 缺失时回退到 latest。 + +Prepared Handoff 可以包含由调用方提交的完整内容,因此窄授权模式不接受 `selection=prepared`。只有已经拥有 +`scope.read` 的 Principal 才能用 prepared selection 解引用 scope evidence。 + +## 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 后才决定是否授权。 + +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 可以在判定前完成,以安全获得 resource identity;验证错误不得包含资源内容。任何 Repository lookup、 +Handoff resolution、Memory search、Report aggregate 或 mutation 都在 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` 和 batch check 禁用或隐藏操作,同时不能绕过 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、Handoff objective 或 Candidate metadata。安全顺序为: + +```text +AuthorizationProvider.list_resources + -> bounded authorized identity filter + -> Repository query restricted by that filter + -> stable pagination + -> response +``` + +禁止以下实现: + +```text +Repository.list_all -> page -> check each item -> remove denied rows +``` + +这种实现会泄漏总数、cursor、空洞和时序,也可能让授权用户永远看不到后面的记录。`total`、cursor 和 page boundary +必须只描述授权后的集合。 + +精确 Handoff receiver 通过 `/v1/access/resources/list` 发现授权 Revision;它不会因此出现在聚合 Project 或 Workstream +列表。只有 scope-level read 才允许进入 Handoff Report 聚合查询。 + +## Audit and diagnostics + +Access Audit 是 append-only Server security record,至少包含: + +- request ID、time、transport 和 operation ID; +- Principal opaque identifier 和可信 actor identifier(若存在); +- action、resource type 和 opaque resource identity; +- allow/deny、稳定 reason code 和 policy revision; +- Binding create/revoke 的 binding ID、grantor、target、role 和 expected/result version。 + +Audit 不包含: + +- Bearer token、cookie、client secret 或 PDP credential; +- Handoff objective/state/next action; +- Source、Memory、PreparedContext 或 citation body; +- 任意 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。 + +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、authorized resource listing、 +create、revoke 和 audit。它是本地部署和 conformance test 的参考语义,不提供用户密码、目录或自定义 policy language。 + +### Casbin adapter + +Casbin adapter 可以使用带 domain 的 RBAC: + +- subject 映射为 issuer-scoped opaque ID; +- domain 映射为 canonical scope resource namespace; +- object 映射为 scope 或 exact Handoff resource 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。 + +### OpenFGA adapter + +OpenFGA 适合表达用户、group、scope 和 exact Handoff 的关系。概念模型如下: + +```text +type user + +type scope + relations + 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 + define can_contribute: contributor or admin + define can_review: reviewer or admin + define can_delegate: delegator or admin + +type handoff + relations + define parent: [scope] + define viewer: [user] + define receiver: [user] + define can_read: viewer or receiver or can_read from parent + define can_acknowledge: receiver or can_contribute from parent +``` + +Adapter 使用固定 authorization model ID 执行 Check、ListObjects 和 tuple write。Tuple 只保存 opaque ID,不保存 email +或 Handoff 文本。Model migration 在 deployment configuration 中显式切换,不自动使用“latest model”。 + +### 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 search,不写 policy。部署必须明确 +`relationship_management=false`,Dashboard 不显示成功的 self-service share control。 + +## 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 listing。 + +`disabled` 只适用于调用方已经信任整个进程和 catalog 的本地场景。文档不能把它描述为多用户安全配置。远程、多用户或 +共享 Dashboard 部署应使用 `enforced`。 + +现有 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. **Safe listing and UI**:authorized resource listing、Handoff inbox、Dashboard permission projection、授权后分页。 +5. **MCP parity**:Principal 通过 internal bridge 传播、tool discovery UX 和调用时 enforcement。 +6. **External adapters**:先完成 Casbin 或 OpenFGA 之一,再用同一 conformance suite 验证 AuthZEN-compatible PDP。 +7. **Migration**:legacy static admin、configuration validation、readiness、operator documentation。 + +每个 slice 都保持 Server 可运行,不能先发布只隐藏 Dashboard 按钮或只保护 HTTP、不保护 MCP 的中间状态。 + +## Test and acceptance plan + +RFC 实现完成需要通过以下 observable scenarios: + +- 无身份访问受保护 operation 返回 401; +- A 有 `scope.delegate` 时可以把已存在的 exact Revision 授予 B,缺少该 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; +- Built-in、Casbin/OpenFGA 和 AuthZEN adapter 对同一 conformance vector 返回相同结果; +- Access Audit 不包含 token、Handoff 正文、Memory、Source body 或 PDP 原始错误。 + +Cross-component acceptance scenarios 放在 `tests/e2e/`,并通过公开 HTTP/MCP contract 断言行为。Focused tests 覆盖 +resource resolver、role mapping、Binding CAS、provider failure 和 citation membership,不冻结 private call order。 + +# Drawbacks + +每个业务请求增加一次授权判定,外部 PDP 还会增加网络依赖和延迟。安全列表要求 Provider 支持 resource search 或可下推 +filter,只有 point-check 的简单 adapter 无法支持全部 Dashboard 列表。 + +精确 Handoff 分享必须先 commit,因此不能把临时 Prepared Handoff 直接变成可撤销的跨用户资源。这增加一步持久化, +但避免为临时 payload 发明第二套 identity 和 ACL。 + +判定和关系管理分离使 adapter interface 比单一 `check()` 更复杂;另一方面,假设所有外部 PDP 都允许 PowerContext 写 +policy 会制造错误的可移植性承诺。 + +撤销只能阻止未来访问,无法删除接收方已经阅读、截图或导出的信息。包含高度敏感内容的 Handoff 仍需要最小化内容、 +外部数据分类和导出控制。 + +固定首版角色限制了组织自定义体验。企业可以在外部 PDP 映射自己的角色,但 PowerContext 公共 API 不立即提供自定义 +role editor。 + +# Rationale and alternatives + +## Chosen: independent Server PEP plus replaceable PDP + +该设计保持 Handoff 和 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 保留给长期协作,精确 Handoff Binding 负责一次性交接。 + +## 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。 + +[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 同时支持安全 resource listing,还是允许禁用相关 Dashboard 列表; +- `handoff.receiver` 的产品默认过期时间是否由 deployment policy 决定,还是 UI 必须每次显式选择; +- exact receiver 创建 Receipt 后,UI 是否建议管理员另行授予 `scope.contributor`,但不能自动执行该升级。 + +以下问题明确推迟:custom role、organization hierarchy、cross-tenant export、anonymous share link、temporary elevation、approval +workflow 和通用 Source/Memory object-level ACL。它们需要独立威胁模型和 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; +- 更多 Artifact Family 的 exact-resource grant; +- 在有明确 revocation-staleness guarantee 后增加 bounded decision cache。 + +这些扩展不能改变首版不变量:`scope_id` 不是 ACL,Handoff 内容不授予权限,Receipt 不升级权限,所有 transport 在 +Server PEP fail closed。 From 38b5c799a5e307a3dff969f65c75e681d1869a2a Mon Sep 17 00:00:00 2001 From: Teingi Date: Sun, 30 Aug 2026 16:22:38 +0800 Subject: [PATCH 2/3] docs(rfc): assign Handoff access control number --- ...andoff_access_control.md => 1396_handoff_access_control.md} | 3 ++- ...andoff_access_control.md => 1396_handoff_access_control.md} | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) rename docs/en/rfcs/{0000_handoff_access_control.md => 1396_handoff_access_control.md} (99%) rename docs/zh/rfcs/{0000_handoff_access_control.md => 1396_handoff_access_control.md} (99%) diff --git a/docs/en/rfcs/0000_handoff_access_control.md b/docs/en/rfcs/1396_handoff_access_control.md similarity index 99% rename from docs/en/rfcs/0000_handoff_access_control.md rename to docs/en/rfcs/1396_handoff_access_control.md index 264b0771d..48884c9b0 100644 --- a/docs/en/rfcs/0000_handoff_access_control.md +++ b/docs/en/rfcs/1396_handoff_access_control.md @@ -1,7 +1,8 @@ - Proposal Name: `handoff_access_control` +- RFC Number: 1396 - Start Date: 2026-08-30 - Status: Draft -- RFC PR: [oceanbase/powercontext#0000](https://github.com/oceanbase/powercontext/pull/0000) +- 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 0082](0082_handoff_report.md), and [RFC 1223](1223_human_agent_work_continuity.md) diff --git a/docs/zh/rfcs/0000_handoff_access_control.md b/docs/zh/rfcs/1396_handoff_access_control.md similarity index 99% rename from docs/zh/rfcs/0000_handoff_access_control.md rename to docs/zh/rfcs/1396_handoff_access_control.md index 053457fa4..fa33c3a48 100644 --- a/docs/zh/rfcs/0000_handoff_access_control.md +++ b/docs/zh/rfcs/1396_handoff_access_control.md @@ -1,7 +1,8 @@ - Proposal Name: `handoff_access_control` +- RFC Number: 1396 - Start Date: 2026-08-30 - Status: Draft -- RFC PR: [oceanbase/powercontext#0000](https://github.com/oceanbase/powercontext/pull/0000) +- 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 0082](0082_handoff_report.md)、[RFC 1223](1223_human_agent_work_continuity.md) From 28557fb6cb023089413591a27f5e44aba485dfad Mon Sep 17 00:00:00 2001 From: Teingi Date: Tue, 1 Sep 2026 19:20:09 +0800 Subject: [PATCH 3/3] docs(rfc): define artifact family access profiles --- docs/en/rfcs/1396_handoff_access_control.md | 851 +++++++++++++++++--- docs/zh/rfcs/1396_handoff_access_control.md | 805 +++++++++++++++--- 2 files changed, 1416 insertions(+), 240 deletions(-) diff --git a/docs/en/rfcs/1396_handoff_access_control.md b/docs/en/rfcs/1396_handoff_access_control.md index 48884c9b0..2fd28de93 100644 --- a/docs/en/rfcs/1396_handoff_access_control.md +++ b/docs/en/rfcs/1396_handoff_access_control.md @@ -5,13 +5,16 @@ - 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 for the PowerContext Server and uses Handoff as the first -resource-level authorization profile. It 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? +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 @@ -37,16 +40,29 @@ Identity Provider or static credential Existing application service ``` -User A can transfer work in two ways: +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 committed Handoff Revision. +- grant B access to one exact persisted or approved resource. -The second option is the least-privilege path in the first version. B can read that Handoff, inspect only the evidence -explicitly cited by it through the Handoff resolver, and leave a Receipt for the same exact Revision. B does not gain -access to other Handoffs, Memory, or Sources in the scope. B also does not gain permission to commit a new Handoff, -record a Task Outcome, use tools, access the network, or read credentials. An `accepted` Receipt records the result of -the transfer; it does not grant authority. +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, @@ -55,13 +71,19 @@ through adapters. # Motivation -PowerContext already has temporary Prepared Handoffs, immutable Handoff Revisions, Continue, Receipts, and Task -Outcomes. 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: +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. @@ -74,9 +96,9 @@ It answers whether the receiver currently appears able to continue. It does not natural-language `receiver`, `authorization_notes`, or an instruction such as “continue this work” cannot be an access credential either. -Handoff therefore needs an authorization layer independent of its 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. +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 @@ -98,6 +120,58 @@ Prepared Handoff -> Commit -> immutable Handoff Revision 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: @@ -134,7 +208,7 @@ An example Binding creation request is: "id": "00u-bob" }, "resource": { - "type": "handoff", + "type": "artifact", "scope_id": "project:payments", "reference": { "family": "handoff", @@ -168,14 +242,130 @@ The Server supplies `granted_by`, creation time, and policy revision. The caller | 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 reads the citation manifest from the immutable Handoff Revision, checks whether B has -`handoff.evidence.read` on that Handoff, and dereferences only 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. +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 @@ -198,18 +388,27 @@ or Receipt cannot enlarge those permissions. A stable team can receive scope roles instead of a new Binding for each Revision: -- `scope.viewer` reads Handoffs, Memory, Sources, and read-only projections in the current scope; -- `scope.contributor` writes work evidence, Handoffs, and Outcomes in addition to viewer access; +- `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 or a scope administrator can revoke an exact Handoff Binding created by A. After revocation: +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`; @@ -245,7 +444,10 @@ 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; @@ -259,7 +461,13 @@ This RFC does not define: - 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. +- 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 @@ -269,15 +477,30 @@ An implementation must preserve these invariants: 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 and Memory are `untrusted_history` and cannot grant an action. +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. Public errors, logs, metrics, and traces do not contain credentials, Handoff content, Memory, Source bodies, or raw - PDP responses. +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 @@ -312,19 +535,93 @@ for a non-accepted Receipt. It never treats the free-form `receiver` as a Princi Internal authorization requests use structured `ResourceRef` values. This avoids concatenating identifiers that may contain `:`, `/`, or user data into policy strings: -| Resource type | Identity | Parent | +| Resource Kind | Identity | Parent | | --- | --- | --- | | `server` | Deployment identifier | None | | `scope` | Exact `scope_id` | Server | -| `handoff` | Exact Handoff `ArtifactReference` plus `scope_id` | Scope | +| `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: -A Handoff resource includes `family`, `artifact_id`, and `revision`. 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 caller in an -already shared trust domain may still transmit a Prepared Handoff explicitly, but the receiver needs separate scope -authority to read its evidence. +| 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, Handoff prose, or other PII into Casbin policy, OpenFGA tuples, or audit keys. +must not write email addresses, tokens, resource content, publication target locators, or other PII into Casbin policy, +OpenFGA tuples, or audit keys. ## Action vocabulary @@ -333,36 +630,52 @@ 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 configuration | -| `scope.read` | scope | Read general resources and projections in a Workstream | -| `scope.contribute` | scope | Write Sources, Memory contributions, Handoffs, and Outcomes | +| `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 | -| `handoff.read` | exact handoff | Read one exact Handoff Revision | -| `handoff.evidence.read` | exact handoff | Resolve that Revision's citation manifest through the Handoff resolver | -| `handoff.acknowledge` | exact handoff | Create a Handoff Receipt for that Revision | +| `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 `handoff.read` and `handoff.evidence.read` for Handoffs under the scope. -`scope.contribute` may imply acknowledge, prepare, commit, and Outcome writes. The reverse implication never holds: an -exact `handoff.receiver` does not gain `scope.read` or `scope.contribute`. +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` | `handoff.read`, `handoff.evidence.read` on one exact Handoff | +| `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 action, including delegation and Binding administration | +| `scope.admin` | Every scope and child Artifact Family action, including delegation and Binding administration | | `server.observer` | `server.observe` | -| `server.admin` | Every server and scope action | +| `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 @@ -373,6 +686,21 @@ exact Handoff in that scope. Creating a scope role requires `scope.admin`. Creat `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 @@ -388,11 +716,11 @@ class AuthorizationProvider(Protocol): /, ) -> Sequence[AccessDecision]: ... - async def list_resources( + async def resolve_resource_filter( self, request: ResourceSearchRequest, /, - ) -> AuthorizedResourcePage: ... + ) -> AuthorizedResourceFilter: ... ``` A normalized request is: @@ -404,9 +732,9 @@ A normalized request is: "issuer": "https://id.example.com/", "id": "00u-bob" }, - "action": {"name": "handoff.read"}, + "action": {"name": "artifact.read"}, "resource": { - "type": "handoff", + "type": "artifact", "scope_id": "project:payments", "reference": { "family": "handoff", @@ -438,10 +766,56 @@ 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. -`list_resources` is required for safe list operations. It obtains allowed resource identities from the authorization -system before passing a bounded identity set to a Repository. A Provider that offers only point checks and cannot -produce a safe resource filter must not query all Handoffs, Projects, or Scopes and filter them afterward. The -affected list operation returns 503, or configuration rejects the Provider as missing a required capability. +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 @@ -494,8 +868,8 @@ idempotency key, and payload returns the original Binding. The same key with a d 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`, `handoff`, or `work` application object. It may share a deployment database with the -Server, but it owns an independent schema, migrations, and API. +`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 @@ -509,7 +883,7 @@ The OpenAPI source of truth adds these operations: | `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 an exact-Handoff or administrative Binding | Resource-specific administration action | +| `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` | @@ -517,9 +891,17 @@ The OpenAPI source of truth adds these operations: 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 target subject so A can name B, but the caller can create only fixed roles on -resources it may administer. Before writing, the Server reads the exact Handoff identity after authorization and -confirms that it exists in the target scope. +`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 @@ -534,7 +916,7 @@ The first-version Handoff mappings are: | `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)` | `scope.read` or `handoff.read` on the exact Revision | +| `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` | @@ -542,12 +924,49 @@ The first-version Handoff mappings are: | 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 ResourceRef and evaluates it before reading the Revision. It cannot resolve latest before +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 @@ -575,6 +994,27 @@ x-powercontext-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. @@ -592,9 +1032,10 @@ transport authentication -> response ``` -Schema validation 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, Report aggregate, and mutation -runs after allow. +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 @@ -611,7 +1052,8 @@ HTTP is the complete remote contract. MCP and the Dashboard reuse the same opera 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` and batch checks to disable or hide actions but cannot bypass API enforcement; +- 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. @@ -620,12 +1062,13 @@ conformance tests protect that guarantee. ## Listing and pagination -Lists can leak Project names, scope IDs, Handoff objectives, or Candidate metadata. The safe order is: +Lists can leak Project names, scope IDs, Artifact Family identities, Handoff objectives, or Candidate metadata. The +safe order is: ```text -AuthorizationProvider.list_resources - -> bounded authorized identity filter - -> Repository query restricted by that filter +AuthorizationProvider.resolve_resource_filter + -> validate bounded exact keys and parent constraints + -> Repository query applying their union -> stable pagination -> response ``` @@ -636,11 +1079,16 @@ This implementation is prohibited: 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. `total`, -cursors, and page boundaries describe only the authorized collection. +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 Handoff receiver discovers granted Revisions through `/v1/access/resources/list`; this does not place the -receiver in aggregate Project or Workstream lists. Only scope-level read permits Handoff Report aggregate queries. +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 @@ -648,15 +1096,16 @@ 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 type, and opaque resource identity; +- 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, target, role, and expected/result version. +- 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, PreparedContext, or citation bodies; +- 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. @@ -678,6 +1127,12 @@ When the Binding succeeded but the client lost the response, the same idempotenc 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 @@ -688,53 +1143,82 @@ window and records the decision revision. The first version does not cache allow ### Built-in provider The built-in profile uses fixed roles and a Server-owned Binding Store. It supports point checks, batch checks, -authorized resource listing, creation, revocation, and audit. It is the reference semantics for local deployments and -conformance tests. It does not provide passwords, a directory, or a custom policy language. +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 to the canonical scope resource namespace; -- object maps to a scope or exact Handoff resource key; +- 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. +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 Handoffs. A conceptual model is: +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 - define can_contribute: contributor or admin - define can_review: reviewer or admin - define can_delegate: delegator or admin + 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 handoff +type artifact relations define parent: [scope] define viewer: [user] - define receiver: [user] - define can_read: viewer or receiver or can_read from parent - define can_acknowledge: receiver or can_contribute from parent + 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 @@ -743,9 +1227,10 @@ decision back to `AccessDecision`. An OPA adapter can submit the same structure 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 search but +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. +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 @@ -759,12 +1244,51 @@ The Server provides three explicit modes: 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 listing are available. +`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. @@ -779,13 +1303,18 @@ Implementation proceeds in independently verifiable slices: audit. 3. **Exact Handoff receiver**: post-commit Binding creation, exact Continue, citation-manifest resolver, exact acknowledge, revocation, and expiration. -4. **Safe listing and UI**: authorized resource listing, Handoff inbox, Dashboard permission projection, and +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. -5. **MCP parity**: Principal propagation through the internal bridge, tool-discovery UX, and invocation-time +7. **MCP parity**: Principal propagation through the internal bridge, tool-discovery UX, and invocation-time enforcement. -6. **External adapters**: implement Casbin or OpenFGA first, then validate an AuthZEN-compatible PDP with the same +8. **External adapters**: implement Casbin or OpenFGA first, then validate an AuthZEN-compatible PDP with the same conformance suite. -7. **Migration**: legacy static admin, configuration validation, readiness, and operator documentation. +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. @@ -795,8 +1324,9 @@ the PEP, or hide only Dashboard controls without API enforcement. 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 an existing exact Revision to B; without that action the request returns 403 and - writes no Binding; +- 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; @@ -811,18 +1341,51 @@ The implementation of this RFC is complete only when these observable scenarios - 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; -- Access Audit contains no token, Handoff content, Memory, Source body, or raw PDP error. +- 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 resource resolvers, role mapping, Binding CAS, provider failure, and citation membership without -freezing private call order. +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 resource search or a filter that can be pushed down, so a point-check-only adapter cannot support every -Dashboard list. +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. @@ -831,8 +1394,21 @@ Separating decisions from relationship management makes the adapter surface more 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 containing highly sensitive material still need content minimization, external data classification, and -export controls. +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. @@ -841,9 +1417,9 @@ the PowerContext public API does not immediately provide a custom role editor. ## Chosen: independent Server PEP plus replaceable PDP -This design keeps Handoff 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. +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. @@ -857,8 +1433,30 @@ should not receive a new Revision whenever team membership changes. This alterna ## 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 -Handoff Bindings serve one-off transfers. +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 @@ -905,6 +1503,12 @@ evidence. [RFC 1223](1223_human_agent_work_continuity.md) defines Receipts and T 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. @@ -923,14 +1527,16 @@ The RFC must resolve these choices before merge, but they do not change the core - 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 listing or may disable the corresponding Dashboard lists; +- 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. + 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, and general Source or Memory object-level ACLs are explicitly deferred. They require separate threat models -and RFCs. +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 @@ -943,8 +1549,15 @@ The subject/action/resource contract can later support: - 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; -- exact-resource grants for more Artifact Families; +- 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, Handoff content does not grant -authority, a Receipt does not elevate authority, and every transport fails closed at the Server PEP. +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 index fa33c3a48..7406ee213 100644 --- a/docs/zh/rfcs/1396_handoff_access_control.md +++ b/docs/zh/rfcs/1396_handoff_access_control.md @@ -5,44 +5,75 @@ - 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 边界,并把 Handoff 作为第一种资源级授权场景。它回答一个 -具体问题:当用户 A 把一份 Handoff 交给用户 B 时,B 可以看到什么、可以做什么,以及这些权限如何撤销和审计。 +本 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,Policy Enforcement Point(PEP)把 -Principal、action 和 resource 交给可替换的 `AuthorizationProvider`,得到允许或拒绝决定后,才调用现有 Runtime -application service。 +安全边界。身份认证和权限判定发生在 Server:认证层得到可信 Principal;PowerContext Server 的策略执行点(PEP)把 +Principal、action 和 resource 交给作为策略决策点(PDP)的可替换 `AuthorizationProvider`。PDP 查询策略或关系存储并 +返回允许或拒绝决定;PEP 只有在允许时才调用现有 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 +身份提供方或静态凭据 + | + v + 已认证的主体 + | + v +PowerContext Server 策略执行点(PEP) + | + | 授权请求 + v + AuthorizationProvider(PDP) <----> 策略或关系存储 + | + | 允许或拒绝决定 + v +PowerContext Server 策略执行点(PEP) + | | + 允许 拒绝 + | | + v v + 现有应用服务 返回 403 ``` -用户 A 可以选择两种交接方式: +首版定义三种稳定 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 级角色; -- 只把一个已提交的精确 Handoff Revision 授予 B。 +- 只把一个已持久化或已批准的精确 resource 授予 B。 -第二种方式是首版的最小权限路径。B 可以读取该 Handoff、通过 Handoff resolver 检查其中明确引用的 evidence,并对 -同一个精确 Revision 留下 Receipt;B 不会因此看到同一 scope 的其他 Handoff、Memory 或 Source,也不会获得提交 -新 Handoff、记录 Task Outcome、使用工具、访问网络或读取凭据的权限。`accepted` Receipt 记录接收结果,不授予权限。 +第二种方式是首版的最小权限路径。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 @@ -50,12 +81,17 @@ Point(PDP)可以通过 adapter 接入。 # Motivation -PowerContext 已经拥有临时 Prepared Handoff、不可变 Handoff Revision、Continue、Receipt 和 Task Outcome,但现有 -Server 认证是可选的全局静态 Bearer。一个有效 token 可以访问所有受保护 operation,Server 无法表达: +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 得到相同判定。 @@ -66,8 +102,8 @@ RFC 1223 中 `acknowledge_handoff` 的 authorization check 是接收方对实时 条件”,不认证 B 的身份,也不是 ACL。自然语言里的 `receiver`、`authorization_notes` 或 “请继续执行”同样不能 成为权限凭据。 -因此,Handoff 需要一个独立于内容和 Runtime domain API 的授权层。这个层必须同时支持最小权限分享、团队角色、外部 -PDP、列表过滤、审计和 fail-closed 行为,而不能让 Agent、请求 body 或 `scope_id` 自行决定权限。 +因此,Handoff 和其他可共享资源需要一个独立于内容和 Runtime domain API 的授权层。这个层必须同时支持最小权限分享、 +团队角色、外部 PDP、列表过滤、审计和 fail-closed 行为,而不能让 Agent、请求 body 或 `scope_id` 自行决定权限。 # Guide-level explanation @@ -87,6 +123,53 @@ Prepared Handoff -> Commit -> immutable Handoff Revision 提交新 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,并已完成一份交接。正常流程如下: @@ -121,7 +204,7 @@ Prepared Handoff -> Commit -> immutable Handoff Revision "id": "00u-bob" }, "resource": { - "type": "handoff", + "type": "artifact", "scope_id": "project:payments", "reference": { "family": "handoff", @@ -154,13 +237,115 @@ Prepared Handoff -> Commit -> immutable Handoff Revision | Commit 新 Handoff 或记录 Task Outcome | 拒绝 | 需要 `scope.contribute` | | 审批 Candidate | 拒绝 | 需要独立的 `scope.review` | -Evidence 的最小权限不是逐条复制 Source 或 Memory,也不是让外部 PDP 保存全部 citation。Server 先从不可变 Handoff -Revision 得到 citation manifest,再检查 B 是否对该 Handoff 拥有 `handoff.evidence.read`,最后只通过 Handoff -resolver 解引用 manifest 中的 exact citation。B 不能把任意 Source ID 填入通用读取 API 来复用这项权限。 +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`: @@ -181,18 +366,25 @@ PowerContext 权限只控制 PowerContext 资源和 operation。修改 Git 仓 对固定团队,可以把用户或外部 group 绑定为 scope role,而不是为每个 Revision 创建 Binding: -- `scope.viewer`:读取当前 scope 的 Handoff、Memory、Source 和只读投影; -- `scope.contributor`:在 viewer 基础上写入工作 evidence、Handoff 和 Outcome; +- `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 或 scope admin 可以撤销 A 创建的精确 Handoff Binding。撤销后: +A、相应 grant administrator 或 scope admin 可以撤销其管理边界内的 exact Artifact Binding。对于 Handoff,撤销后: - B 的后续 read、Continue 和 acknowledge 返回 403; - B 不再从 `resources/list` 看到该 Handoff; @@ -226,7 +418,10 @@ A 或 scope admin 可以撤销 A 创建的精确 Handoff Binding。撤销后: - 在 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; @@ -240,7 +435,13 @@ A 或 scope admin 可以撤销 A 创建的精确 Handoff Binding。撤销后: - Git、文件系统、工具、网络、模型 Provider 或凭据授权; - 数据脱敏、cross-organization export、legal hold 或 retention policy; - 审批工作流、临时提权流程或 Agent 自动请求更高权限; -- 把 PowerContext 改造成通用 IAM 产品。 +- 把 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 @@ -249,13 +450,25 @@ A 或 scope admin 可以撤销 A 创建的精确 Handoff Binding。撤销后: 1. `scope_id` 是业务分区值,不是授权证明。 2. Principal 只来自认证 middleware 或可信 internal bridge context。 3. 请求 body 中的 `receiver`、`subject`、`actor`、role text 或 Handoff 自然语言不能替换当前 Principal。 -4. Handoff 和 Memory 是 `untrusted_history`,不能授予 action。 +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. Public error、log、metric 和 trace 不包含 credential、Handoff 正文、Memory、Source body 或 PDP 原始响应。 +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 @@ -288,18 +501,86 @@ Agent 名称、host、session ID 和模型名称属于 provenance,不默认成 内部授权 request 使用结构化 `ResourceRef`,避免把包含 `:`、`/` 或用户数据的标识直接拼成策略字符串: -| Resource type | Identity | Parent | +| Resource Kind | Identity | Parent | | --- | --- | --- | | `server` | deployment identifier | none | | `scope` | exact `scope_id` | server | -| `handoff` | exact Handoff `ArtifactReference` plus `scope_id` | scope | +| `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。 -Handoff resource 必须包含 `family`、`artifact_id` 和 `revision`。Prepared Handoff 没有持久化 identity,不能创建精确 -Access Binding。跨用户最小权限分享必须先 commit;Prepared Handoff 仍可由已经共享同一 trust domain 的调用方显式 -传输,但接收方需要独立的 scope 权限才能读取 evidence。 +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、 -Handoff 文本或其他 PII 写入 Casbin policy、OpenFGA tuple 或 audit key。 +资源正文、发布 target locator 或其他 PII 写入 Casbin policy、OpenFGA tuple 或 audit key。 ## Action vocabulary @@ -308,35 +589,49 @@ Handoff 文本或其他 PII 写入 Casbin policy、OpenFGA tuple 或 audit key | Action | Resource | Meaning | | --- | --- | --- | | `server.observe` | server | 读取服务级运行状态和观测数据 | -| `server.admin` | server | 管理 deployment access configuration | -| `scope.read` | scope | 读取该 Workstream 的通用只读资源和投影 | -| `scope.contribute` | scope | 写入 Source、Memory contribution、Handoff 和 Outcome | +| `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 | -| `handoff.read` | exact handoff | 读取一个精确 Handoff Revision | -| `handoff.evidence.read` | exact handoff | 通过 Handoff resolver 解引用该 Revision 的 citation manifest | -| `handoff.acknowledge` | exact handoff | 对该 Revision 创建 Handoff Receipt | +| `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 下 Handoff 的 `handoff.read` 和 `handoff.evidence.read`; -`scope.contribute` 可以蕴含 acknowledge、prepare、commit 和 Outcome 写入。反向蕴含不成立:精确 `handoff.receiver` -不能得到 `scope.read` 或 `scope.contribute`。 +`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` | `handoff.read`, `handoff.evidence.read` on one exact Handoff | +| `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 actions, including delegation and Binding administration | +| `scope.admin` | all scope and child Artifact Family actions, including delegation and Binding administration | | `server.observer` | `server.observe` | -| `server.admin` | all server and scope actions | +| `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。 @@ -345,6 +640,20 @@ conformance test 拥有稳定语义;企业 PDP 可以在外部把自定义组 精确 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 形状对齐,但 @@ -360,11 +669,11 @@ class AuthorizationProvider(Protocol): /, ) -> Sequence[AccessDecision]: ... - async def list_resources( + async def resolve_resource_filter( self, request: ResourceSearchRequest, /, - ) -> AuthorizedResourcePage: ... + ) -> AuthorizedResourceFilter: ... ``` 规范化 request 示例: @@ -376,9 +685,9 @@ class AuthorizationProvider(Protocol): "issuer": "https://id.example.com/", "id": "00u-bob" }, - "action": {"name": "handoff.read"}, + "action": {"name": "artifact.read"}, "resource": { - "type": "handoff", + "type": "artifact", "scope_id": "project:payments", "reference": { "family": "handoff", @@ -408,9 +717,52 @@ class AuthorizationProvider(Protocol): `check_batch` 必须保持输入顺序,并对每项返回独立决定。Adapter 不能因为一个 allow 而允许整批资源。 -`list_resources` 是安全列表功能的必要能力。它先从授权系统得到允许的 resource identity,再把有界 identity set 交给 -Repository 查询。只支持 point check、无法安全产生 resource filter 的 Provider 不得先查询全部 Handoff/Project/Scope -再逐项过滤;对应 list operation 应返回 503 或在配置阶段被判为不具备所需 capability。 +一个业务 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 @@ -460,7 +812,8 @@ Role、subject 或 resource 变化必须 revoke old + create new。相同 granto 原 Binding;同 key 不同 payload 返回 409。过期不删除记录,判定时视为 deny。 内置 Binding Repository 属于 Server access-control component,不加入 Runtime 的 `context`、`source`、`memory`、 -`handoff` 或 `work` application object。它可以与 Server 使用相同数据库部署,但拥有独立 schema、migration 和 API。 +`artifact`、`handoff` 或 `work` application object。它可以与 Server 使用相同数据库部署,但拥有独立 schema、 +migration 和 API。 ## Public Access API @@ -474,15 +827,22 @@ OpenAPI source of truth 增加以下 operation: | `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` | 创建精确 Handoff 或管理级 Binding | resource-specific administration action | +| `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` 必须接收目标 subject,因为分享需要指定 B;调用方仍然只能在自己拥有管理权限的 resource 上创建固定 -角色。Server 在写入前重新读取精确 Handoff identity,确认它存在并属于目标 scope。 +`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。 @@ -496,7 +856,7 @@ service。Access API 只用于解释和 UI preflight,不能替代业务请求 | `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)` | `scope.read` or `handoff.read` on exact Revision | +| `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` | @@ -504,11 +864,45 @@ service。Access API 只用于解释和 UI preflight,不能替代业务请求 | Handoff Report administration | `scope.admin` or appropriate server administration action | 当 exact receiver 调用 Continue 时,请求必须提供 `selection=exact` 和 exact `ArtifactReference`。Server 先建立 Handoff -ResourceRef 并判定,再读取 Revision。它不能先解析 latest 再检查,也不能在 exact 缺失时回退到 latest。 +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 生成到 @@ -535,6 +929,25 @@ x-powercontext-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。 @@ -552,8 +965,9 @@ transport authentication -> response ``` -Schema validation 可以在判定前完成,以安全获得 resource identity;验证错误不得包含资源内容。任何 Repository lookup、 -Handoff resolution、Memory search、Report aggregate 或 mutation 都在 allow 之后发生。 +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;需要安全边界 @@ -567,7 +981,8 @@ HTTP 是完整远程 contract,MCP 和 Dashboard 复用同一 operation 和 PEP - MCP internal ASGI bridge 把原 Principal、actor 和 request ID 放入 request-local context; - `is_internal_bridge()` 可以避免再次解析同一个外部 credential,但授权 wrapper仍执行; - MCP tool discovery 可以根据当前 Principal 过滤不可用工具,但隐藏工具只是 UX,调用时仍必须判定; -- Dashboard 根据 `access/me` 和 batch check 禁用或隐藏操作,同时不能绕过 API enforcement; +- 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 覆盖 @@ -575,12 +990,12 @@ HTTP 和 MCP 对同一 Principal、action、resource、policy revision 必须得 ## Listing and pagination -列表最容易泄漏 Project 名称、scope ID、Handoff objective 或 Candidate metadata。安全顺序为: +列表最容易泄漏 Project 名称、scope ID、Artifact Family identity、Handoff objective 或 Candidate metadata。安全顺序为: ```text -AuthorizationProvider.list_resources - -> bounded authorized identity filter - -> Repository query restricted by that filter +AuthorizationProvider.resolve_resource_filter + -> validate bounded exact keys and parent constraints + -> Repository query applying their union -> stable pagination -> response ``` @@ -591,11 +1006,13 @@ AuthorizationProvider.list_resources Repository.list_all -> page -> check each item -> remove denied rows ``` -这种实现会泄漏总数、cursor、空洞和时序,也可能让授权用户永远看不到后面的记录。`total`、cursor 和 page boundary -必须只描述授权后的集合。 +这种实现会泄漏总数、cursor、空洞和时序,也可能让授权用户永远看不到后面的记录。Repository 必须在同一个 query 中 +应用 exact key 与 parent constraint 的并集;`total`、cursor 和 page boundary 必须只描述授权后的集合。 -精确 Handoff receiver 通过 `/v1/access/resources/list` 发现授权 Revision;它不会因此出现在聚合 Project 或 Workstream -列表。只有 scope-level read 才允许进入 Handoff Report 聚合查询。 +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 @@ -603,15 +1020,16 @@ Access Audit 是 append-only Server security record,至少包含: - request ID、time、transport 和 operation ID; - Principal opaque identifier 和可信 actor identifier(若存在); -- action、resource type 和 opaque resource identity; +- action、Resource Kind、可选 Artifact Family 和 opaque resource identity; - allow/deny、稳定 reason code 和 policy revision; -- Binding create/revoke 的 binding ID、grantor、target、role 和 expected/result version。 +- 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、PreparedContext 或 citation body; +- 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 或不必要的目录属性。 @@ -631,6 +1049,11 @@ Commit Handoff 与创建外部授权关系不是跨系统原子事务。UI 中 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。 @@ -639,52 +1062,78 @@ Receipt 创建仍使用现有 exact-selection 和 evidence rules。授权判定 ### Built-in provider -内置 profile 使用固定角色和 Server-owned Binding Store,支持 point check、batch check、authorized resource listing、 -create、revoke 和 audit。它是本地部署和 conformance test 的参考语义,不提供用户密码、目录或自定义 policy language。 +内置 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 映射为 canonical scope resource namespace; -- object 映射为 scope 或 exact Handoff resource key; +- 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。 +ResourceRef 建立 domain。生成列表 filter 时,exact object policy 产生 canonical key,scope/server role assignment 产生 +对应 parent constraint;Casbin adapter 不需要枚举业务 Repository。 ### OpenFGA adapter -OpenFGA 适合表达用户、group、scope 和 exact Handoff 的关系。概念模型如下: +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 - define can_contribute: contributor or admin - define can_review: reviewer or admin - define can_delegate: delegator or admin + 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 handoff +type artifact relations define parent: [scope] define viewer: [user] - define receiver: [user] - define can_read: viewer or receiver or can_read from parent - define can_acknowledge: receiver or can_contribute from parent + 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 @@ -693,8 +1142,9 @@ AuthZEN adapter 把 `AccessRequest` 映射为 Authorization API 的 subject、ac 和 actions。 这些 adapter 的 decision interoperability 不代表 policy administration interoperability。若组织在 GitOps、IAM 或 -独立管理面维护 policy,PowerContext 只消费判定和安全 resource search,不写 policy。部署必须明确 -`relationship_management=false`,Dashboard 不显示成功的 self-service share control。 +独立管理面维护 policy,PowerContext 只消费判定和安全 resource filter,不写 policy。部署必须明确 +`relationship_management=false`,Dashboard 不显示成功的 self-service share control。若 adapter 不能从 PDP search 或 +可信关系数据产生 `AuthorizedResourceFilter`,还必须报告 `safe_resource_filtering=false`。 ## Configuration and compatibility @@ -707,11 +1157,48 @@ Server 提供三种显式 mode: | `enforced` | 认证 Provider 和 AuthorizationProvider 都是 required dependency,所有业务 operation 执行 PEP | 升级不能因为配置了外部身份但漏配 PDP 而回退到 `disabled`。Mode 必须显式,capabilities 和 readiness 报告当前 mode 与 -是否支持 relationship management、batch check 和 safe resource listing。 +是否支持 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 当作空结果。 @@ -724,10 +1211,15 @@ Server 提供三种显式 mode: 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. **Safe listing and UI**:authorized resource listing、Handoff inbox、Dashboard permission projection、授权后分页。 -5. **MCP parity**:Principal 通过 internal bridge 传播、tool discovery UX 和调用时 enforcement。 -6. **External adapters**:先完成 Casbin 或 OpenFGA 之一,再用同一 conformance suite 验证 AuthZEN-compatible PDP。 -7. **Migration**:legacy static admin、configuration validation、readiness、operator documentation。 +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 的中间状态。 @@ -736,7 +1228,8 @@ Server 提供三种显式 mode: RFC 实现完成需要通过以下 observable scenarios: - 无身份访问受保护 operation 返回 401; -- A 有 `scope.delegate` 时可以把已存在的 exact Revision 授予 B,缺少该 action 时返回 403 且不写 Binding; +- 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 调用通用读取接口; @@ -749,16 +1242,44 @@ RFC 实现完成需要通过以下 observable scenarios: - 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 返回相同结果; -- Access Audit 不包含 token、Handoff 正文、Memory、Source body 或 PDP 原始错误。 +- 请求不能提交独立的 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 覆盖 -resource resolver、role mapping、Binding CAS、provider failure 和 citation membership,不冻结 private call order。 +Family registry、selector/canonical key、resource resolver、role mapping、Binding CAS、provider failure 和 citation +membership,不冻结 private call order。 # Drawbacks -每个业务请求增加一次授权判定,外部 PDP 还会增加网络依赖和延迟。安全列表要求 Provider 支持 resource search 或可下推 -filter,只有 point-check 的简单 adapter 无法支持全部 Dashboard 列表。 +每个业务请求增加一次授权判定,外部 PDP 还会增加网络依赖和延迟。安全列表要求 Provider 产生有界、可下推的 +`AuthorizedResourceFilter`,只有 point-check 的简单 adapter 无法支持全部 Dashboard 列表。 精确 Handoff 分享必须先 commit,因此不能把临时 Prepared Handoff 直接变成可撤销的跨用户资源。这增加一步持久化, 但避免为临时 payload 发明第二套 identity 和 ACL。 @@ -766,8 +1287,19 @@ filter,只有 point-check 的简单 adapter 无法支持全部 Dashboard 列 判定和关系管理分离使 adapter interface 比单一 `check()` 更复杂;另一方面,假设所有外部 PDP 都允许 PowerContext 写 policy 会制造错误的可移植性承诺。 -撤销只能阻止未来访问,无法删除接收方已经阅读、截图或导出的信息。包含高度敏感内容的 Handoff 仍需要最小化内容、 -外部数据分类和导出控制。 +撤销只能阻止未来访问,无法删除接收方已经阅读、截图或导出的信息。包含高度敏感内容的 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。 @@ -776,8 +1308,9 @@ role editor。 ## Chosen: independent Server PEP plus replaceable PDP -该设计保持 Handoff 和 Runtime model 与身份系统解耦,同时让 HTTP、MCP 和 Dashboard 共用 enforcement。稳定 action -vocabulary 比稳定外部 role name 更容易跨 Casbin、OpenFGA、OPA、Cerbos 和企业 IAM 映射。 +该设计保持 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 统一。 @@ -790,7 +1323,27 @@ AuthZEN 统一。 ## Alternative: only use scope-level roles 只授予 `scope.viewer` 容易实现,但 B 会看到整个 Workstream 的 Memory、Source、历史和 Report。对于临时接力不符合最小 -权限原则。Scope roles 保留给长期协作,精确 Handoff Binding 负责一次性交接。 +权限原则。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 @@ -832,6 +1385,11 @@ policy service。 [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。 @@ -848,12 +1406,14 @@ subject、action、resource、context 和 decision contract。本 RFC 对齐其 - 首个外部 conformance adapter 选择 Casbin 还是 OpenFGA; - 内置 Provider 是否随默认 Server extra 安装,还是作为独立 optional extra; - Dashboard 如何从部署方的身份目录选择 canonical recipient;目录搜索本身不由本 RFC 的 Access API 提供; -- enforced deployment 是否要求 Provider 同时支持安全 resource listing,还是允许禁用相关 Dashboard 列表; +- enforced deployment 是否要求 Provider 同时支持 `safe_resource_filtering`,还是允许禁用相关 Dashboard 列表; - `handoff.receiver` 的产品默认过期时间是否由 deployment policy 决定,还是 UI 必须每次显式选择; -- exact receiver 创建 Receipt 后,UI 是否建议管理员另行授予 `scope.contributor`,但不能自动执行该升级。 +- 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/Memory object-level ACL。它们需要独立威胁模型和 RFC。 +workflow、通用 Source object-level ACL、动态 Memory collection、Artifact catalog 分享和自动跟随 future Revision。它们需要 +独立威胁模型和 RFC。 # Future possibilities @@ -866,8 +1426,11 @@ workflow 和通用 Source/Memory object-level ACL。它们需要独立威胁模 - AuthZEN Search API、obligation 和 richer decision metadata; - policy bundle、signed decision metadata 和跨服务 audit correlation; - 对 Handoff 导出的独立脱敏、watermark 和 data-loss-prevention policy; -- 更多 Artifact Family 的 exact-resource grant; +- 注册更多 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,Handoff 内容不授予权限,Receipt 不升级权限,所有 transport 在 -Server PEP fail closed。 +这些扩展不能改变首版不变量:`scope_id` 不是 ACL,资源内容不授予权限,exact grant 不跟随 future Revision,读取不自动 +进入 Context 或获得执行权,所有 transport 在 Server PEP fail closed。