Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docs/en/docs/workflows/scopes-and-access.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,45 @@ target's tags follows that target's write permission. Use [Tags](manage-artifact
Teams use the API, MCP, or host integrations with their own identities and permissions. The Dashboard is limited to
personal and demonstration deployments, with optional static token authentication. It is not a team RBAC interface; leave
`POWERCONTEXT_SERVER_DASHBOARD_ENABLED=false` in team deployments.

## Recover external ownership writes

When `create_server_app` composes the built-in Runtime with relationship/audit stores outside its business transaction,
supported creation hooks persist ownership intents in `pc_access_owner_recovery`. A failed delivery returns 503 even
when the domain object has committed. Keep the original database: the Server retries pending intents after restart.
Do not repeat a non-idempotent create blindly, assign the retrying user as owner, or edit an intent's payload.

An operator with access to the embedded application can inspect metadata and explicitly retry an accepted intent:

```python
recovery = app.state.owner_recovery
if recovery is not None:
pending = await recovery.pending(scope_id=scope_id, limit=100)
for operation in pending:
await recovery.replay(operation.operation_id)

after = None
while True:
page = await recovery.inventory_gaps(scope_id, kind="artifact", after=after, limit=100)
# Export page.items only to the trusted operator's destination.
after = page.after
if after is None:
break
```

`pending` also accepts an `after` operation ID for pagination. Use `kind="candidate"` to inventory proposed-owner
attestations. Inventory returns identity metadata, never Artifact or Candidate bodies. Its `after` cursor follows
scanned identities, so an empty `items` page may still have a continuation. `unknown` means no trusted intent exists;
`delivered_relation_missing` means a completed receipt no longer has its relation; `legacy_selector_required` means a
Memory collection needs an explicit entry inventory. These require an operator policy based on trusted original
evidence; replay does not accept a new owner or restore completed receipts whose external relation was deleted.
An existing independent read grant remains valid and does not fill the ownership gap.

External Artifact recovery requires event-idempotent `append_audit_once`; missing capability fails before the domain
commit. Relationship writers must enforce their existing immutable owner/key and exact Candidate target contracts.
Conflicts remain pending for investigation. Completed receipts are retained. Keep them with the business database's
backup and retention policy. No public recovery HTTP/MCP endpoint or CLI is provided.

The recovery tests use two separate real SQLite databases with injected delivery/acknowledgement failures, process
lifecycle restart, and concurrent replay. They do not establish acceptance for a remote relationship service,
OceanBase/MySQL, multiple Server nodes, or custom Runtime assemblies without commit hooks.
46 changes: 34 additions & 12 deletions docs/en/rfcs/1396_handoff_access_control.md
Original file line number Diff line number Diff line change
Expand Up @@ -620,30 +620,52 @@ logical resource, one `PrincipalRef`, trusted creation time, policy revision, an
contains no Artifact Revision. The owner relation is immutable; creating it again is idempotent only for the same
owner and key, and a different owner returns a conflict. Ownership transfer is not part of this RFC.

In enforced mode, Artifact authorization fails closed with `artifact_owner_pending` until this relation exists. New
In enforced mode, a write requiring ownership establishment fails closed with `artifact_owner_pending` until this relation exists. New
Memory entries and first Handoff commits are owned by the creating Principal. A new Experience or Skill Candidate
records a Server-side proposed-owner attestation; approval establishes that Principal as owner. A Candidate targeting
an existing identity must retain its existing owner. Cross-Scope publication establishes the publisher as owner of
the new target identity.

The first version assumes that a deployment enables enforced mode before it persists its first Artifact. It does not
backfill or infer owners for a catalog populated while access control was disabled, and it exposes no general owner
repair workflow. Switching such a catalog to enforced mode without a separate operator migration leaves those
Artifacts unavailable by design.
Enable enforced mode before persisting the first Artifact when possible. The Server does not backfill or infer owners
for a catalog populated while access control was disabled. Historical records without trusted ownership evidence remain
unknown and require an explicit operator policy; current administrator status does not establish their original owner.
An existing independent read grant continues to authorize reads without becoming ownership.

When the canonical relational repository owns relationships and audit in the business database, Artifact creation, first Handoff
commit, cross-Scope publication, and Experience/Skill Candidate creation commit their owner relation or proposed-owner
attestation in the business transaction. This includes generated, uploaded, imported, and scheduled Candidates.
An ownership failure rolls back the associated write; scheduled incubation also retains its Source cursor for retry.
Atomic Memory, Profile generation, Dream, and supervised worker writes retain their domain-owned transaction hooks.

External relationship writers and separate audit stores cannot join that transaction. If domain persistence succeeds
but owner establishment fails in those compositions, the request still fails closed; only a business flow that
explicitly supports idempotent replay may repair the relation on retry. Local existing ownerless records are not attributed
to the current caller or reconstructed from content. They remain unavailable until an operator migration can establish
the original identity from trusted persisted evidence. Local publication replay and Handoff no-op replay report
`artifact_owner_pending` for those records without creating an owner. A general transactional outbox and operator
recovery procedure are future work outside this RFC.
External relationship writers and separate audit stores cannot join that transaction. The composed Server persists
`pc_access_owner_recovery` in the business database for creation paths with domain commit hooks: base Artifact creation,
first Handoff commit, publication, Candidate proposal/generation/upload/import, scheduled incubation, and Profile flush.
The immutable intent commits alongside the domain object and retains only the trusted initiating Principal type/ID,
exact logical resource or Candidate identity and target, idempotency key, and data-minimized audit context. It contains
no Artifact/Source bodies, model output, tokens, or credentials.

The Server delivers accepted intents after the business transaction and before returning success. A delivery failure
returns 503 and leaves the intent durable. A background task retries bounded pages every five seconds, advancing past
failed entries. Restart and concurrent replay preserve the original identity; a different owner/key/target conflicts
rather than transferring ownership. Artifact recovery requires the external audit store's
`append_audit_once(AccessAuditEvent)` capability: identical event ID and fields return the original event, while changed
fields conflict. The canonical relational audit store implements it. If absent, the commit hook fails before the domain
commit. Candidate intents retain the immutable proposed-owner receipt; they do not establish ownership of an existing
target. Completed recovery receipts are retained and must not be independently purged while their identities remain.

Operators can inspect `app.state.owner_recovery.pending()`, page through `inventory_gaps(scope_id)`, and call
`replay(operation_id)` from trusted embedding code; there is no owner-assignment HTTP/MCP endpoint or recovery CLI.
Inventory reads only head identities and classifies pending intents, unknown history, completed receipts whose
relations are missing, and legacy Memory collections requiring entry selectors. Replay accepts an existing intent ID,
never a caller-supplied Principal. Unknown history is not replayable. Local publication and Handoff no-op replay do not
attribute historical ownerless records to the current caller. Existing grants retain their independent read semantics.

Candidate approval already retains its original proposed-owner attestation and approved result; retrying the same
terminal approval can establish that proposed owner without approving twice. Custom Runtime/RecordService assemblies
without transactional commit hooks retain their explicit business-retry guarantees and do not gain durable recovery
merely by injecting an external writer. Atomic Memory, Dream, and supervised worker security retain their domain-owned
transaction requirements. See [operator recovery](../docs/workflows/scopes-and-access.md#recover-external-ownership-writes)
for inspection and validation limits.

### Built-in relational persistence

Expand Down
39 changes: 39 additions & 0 deletions docs/zh/docs/workflows/scopes-and-access.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,42 @@ Scope 选择项目上下文边界。其不透明 ID 用于标识数据,不负

团队通过 API、MCP 或宿主集成使用各自的身份与权限。Dashboard 仅用于个人和演示部署,可选用静态 token 鉴权,
不作为 RBAC 团队入口;团队部署保持 `POWERCONTEXT_SERVER_DASHBOARD_ENABLED=false`。

## 恢复外部所有权写入

`create_server_app` 将内置 Runtime 与业务事务之外的 relationship/audit store 组合时,
受支持的创建回调在 `pc_access_owner_recovery` 保存所有权 intent。交付失败返回 503,此时业务对象可能已提交。
保留原业务库,Server 重启后会继续重试 pending intent。不要盲目重复非幂等创建、把重试者指定为 owner,或修改 intent payload。

能访问嵌入应用的运营者可以查看元数据,并显式重试已有 intent:

```python
recovery = app.state.owner_recovery
if recovery is not None:
pending = await recovery.pending(scope_id=scope_id, limit=100)
for operation in pending:
await recovery.replay(operation.operation_id)

after = None
while True:
page = await recovery.inventory_gaps(scope_id, kind="artifact", after=after, limit=100)
# 仅向可信运营目标导出 page.items。
after = page.after
if after is None:
break
```

`pending` 同样支持用 operation ID 作为 `after` 分页。使用 `kind="candidate"` 盘点拟定归属凭证。
清单只返回 identity 元数据,不返回 Artifact 或 Candidate 正文。`after` 跟随扫描过的 identity,
因此 `items` 为空时也可能有下一页。`unknown` 表示没有可信 intent;`delivered_relation_missing` 表示完成 receipt
对应的外部 relation 已缺失;`legacy_selector_required` 表示旧 Memory collection 需要显式 entry 清单。
这些情况需要基于可信原始证据确定 operator policy;重放不接受新 owner,也不会恢复已完成 receipt 对应的被删除 relation。
已有独立读取授权仍有效,但不会填补所有权缺口。

外部 Artifact 恢复要求支持按事件幂等的 `append_audit_once`;缺少该能力会在业务提交前失败。
Relationship writer 必须遵循原有不可变 owner/key 以及精确 Candidate target 契约。
冲突保持 pending,供运营者调查。完成 receipt 持续保留,应纳入业务库备份和保留策略。
没有新增公共 recovery HTTP/MCP 接口或 CLI。

恢复测试使用两个独立真实 SQLite 数据库,注入交付/确认失败,验证应用生命周期重启与并发重放。
这不等于远程权限服务、OceanBase/MySQL、多 Server 节点或不具备 commit hook 的自组装 Runtime 已通过验收。
36 changes: 27 additions & 9 deletions docs/zh/rfcs/1396_handoff_access_control.md
Original file line number Diff line number Diff line change
Expand Up @@ -579,25 +579,43 @@ Server 把 ownership 与普通 `AccessBinding` 分开保存。`ArtifactOwnerRela
`PrincipalRef`、可信创建时间、policy revision 和 idempotency key,刻意不包含 Artifact Revision。Owner relation
不可变;只有相同 owner 和 key 的重复建立才是幂等操作,不同 owner 返回 conflict。本 RFC 不定义 ownership transfer。

在 enforced mode 下,owner relation 建立前的 Artifact authorization 以 `artifact_owner_pending` fail closed。新 Memory
在 enforced mode 下,要求建立所有权的写入在 owner relation 建立前以 `artifact_owner_pending` fail closed。新 Memory
Entry 和首次 Handoff commit 由创建者拥有。新 Experience/Skill Candidate 记录 Server-side proposed-owner attestation,
批准后再建立 ownership;以已有 identity 为 target 的 Candidate 必须保留原 owner。跨 Scope publication 的新 target
identity 归 publisher 所有。

首版假设 deployment 在持久化第一个 Artifact 前就已启用 enforced mode。它不会为 access control disabled 期间已写入的
catalog 回填或推断 owner,也不提供通用 owner repair 流程。未经过独立 operator migration 就把这类 catalog 切换到
enforced mode 时,其中的 Artifact 按设计保持不可用。
建议在持久化第一个 Artifact 前启用 enforced mode。Server 不会为 access control disabled 期间已写入的 catalog
回填或推断 owner。没有可信归属证据的历史记录保持 unknown,需要显式 operator policy;当前管理员身份不能证明历史 owner。
独立的读取授权仍可允许读取,但不会因此成为所有权。

标准关系型 repository 同时负责 relationship 和 audit,且与业务共用数据库时,Artifact 创建、首次 Handoff commit、跨 Scope
publication,以及 Experience/Skill Candidate 创建,会在业务事务内提交 owner relation 或 proposed-owner attestation。
这包括生成、上传、导入和定时处理产生的 Candidate。所有权写入失败时,相关业务写入一起回滚;定时 incubation 的
Source cursor 也保持不变,供后续重试。Atomic Memory、Profile 生成、Dream 和 supervisor worker 保留各自领域已有的事务回调。

外部 relationship writer 和独立 audit store 无法加入该事务。这类组合中,若 domain persistence 已成功但 owner
establishment 失败,请求仍 fail closed;只有明确支持幂等重放的业务流程才能通过重试修复 relation。同库已有的 owner 缺失记录
不会被归给当前调用者,也不会根据内容重建 owner;它们保持不可用,直到独立 operator migration 能根据可信持久化证据确认
原始身份。同库 publication 重放和 Handoff no-op 重放会对这类记录返回 `artifact_owner_pending`,不会创建 owner。
通用 transactional outbox 和 operator recovery 流程属于本 RFC 之外的未来工作。
外部 relationship writer 和独立 audit store 无法加入业务事务。标准组合的 Server 为具备 domain commit hook 的创建路径,
在业务库保存 `pc_access_owner_recovery`:包括基础 Artifact 创建、首次 Handoff commit、publication、Candidate
提交/生成/上传/导入、定时 incubation 和 Profile flush。不可变 intent 与业务对象同事务提交,仅保存可信发起者的
Principal type/ID、精确逻辑 resource 或 Candidate identity/target、幂等键和最小化审计上下文;
不保存 Artifact/Source 正文、模型输出、token 或凭据。

Server 在业务事务结束后交付 intent,全部成功后才返回成功。交付失败返回 503,intent 持久保留。
后台任务每五秒重试有界分页,并越过失败项。重启或并发重放保留原始身份;不同 owner/key/target 返回冲突,不转移所有权。
Artifact 恢复要求外部 audit store 提供 `append_audit_once(AccessAuditEvent)`:相同 event ID 和字段返回原事件,
同 ID 不同字段返回冲突;标准关系型 audit store 已实现该能力。缺少该能力时,commit hook 在业务提交前失败回滚。
Candidate intent 保留不可变拟定归属凭证,不会建立已有 target 的新所有权。完成的恢复 receipt 持续保留,
关联 identity 尚存在时不能独立清理这些 receipt。

运营者可通过可信嵌入代码调用 `app.state.owner_recovery.pending()`、分页 `inventory_gaps(scope_id)` 和
`replay(operation_id)`;没有新增 owner 赋权 HTTP/MCP 接口或 recovery CLI。清单仅读取 head identity,
区分 pending、unknown、已完成 receipt 对应 relation 缺失,以及需要 entry selector 的旧 Memory collection。
重放只接受已有 intent ID,不接受调用者指定 Principal;unknown 历史不能重放。同库 publication 和 Handoff no-op
重放不会把历史缺失 owner 归给当前调用者;已有独立授权保留原来的读取语义。

Candidate approval 已保存原拟定归属凭证及 approved result;对同一终态 approval 的重试可恢复拟定 owner,不会再次批准。
不具备事务 commit hook 的自组装 Runtime/RecordService 保留各自明确的业务重试保证,
不会仅因注入外部 writer 就获得持久恢复能力。Atomic Memory、Dream 和 supervisor worker security 保留各自领域的事务要求。
操作方法和验证边界见[外部所有权写入恢复](../docs/workflows/scopes-and-access.md#恢复外部所有权写入)。

### 内置关系型存储

Expand Down
Loading
Loading