Skip to content

Commit 63e1e48

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-23357-page-component-api-data
2 parents 7309ef0 + c857a93 commit 63e1e48

51 files changed

Lines changed: 1574 additions & 6559 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/plugin-security': minor
3+
'@objectstack/spec': patch
4+
---
5+
6+
feat(plugin-security)!: the permission-set projection, its seed-name lookup and refusal sink are deleted (ADR-0131 D3, C3 stage 7a)
7+
8+
Clause-②: no (narrowing)
9+
10+
<!-- adr-0087: not-required (runtime-export-retired @objectstack/plugin-security#permissionSetRowFields, @objectstack/plugin-security#permissionSetBodyFromRow, @objectstack/plugin-security#upsertEnvPermissionSet, @objectstack/plugin-security#projectPermissionMutation, @objectstack/plugin-security#registerPermissionSetProjection, @objectstack/plugin-security#createPermissionSetWriteThrough, @objectstack/plugin-security#reconcilePermissionSetProjection, @objectstack/plugin-security#PermissionSeedOutcome, @objectstack/plugin-security#ProjectionDeps, @objectstack/plugin-security#ProjectionReconcileOutcome, @objectstack/plugin-security#WriteThroughDeps) The retired names are runtime functions and types of the projector that mirrored a permission-set definition into a sys_permission_set row. No metadata moves: no spec key, authorable spelling or stored shape changes, and no stored row is rewritten. Stage 8 already stopped registering the projector and the data-door write-through, so nothing in a running deployment called them. -->
11+
12+
**BREAKING** (retired exports of `@objectstack/plugin-security`), graded `minor` on the v18 prerelease line.
13+
14+
Stage 8 stopped registering the `sys_permission_set` row object, the `permission` mutation projector and the data-door write-through. This change deletes the modules behind them: `permission-set-projection`, `seed-name-lookup` and `seed-refusal-sink`. A permission set is written through the metadata door alone (`PUT /api/v1/meta/permission/:name`), and the security catalog serves it by name.
15+
16+
What a consumer sees:
17+
18+
- FROM `createPermissionSetWriteThrough(deps)` TO nothing: no `sys_permission_set` data-door write exists to redirect. Write the set through the metadata door. Delete the call.
19+
- FROM `registerPermissionSetProjection(protocol, deps)` TO nothing: no row mirrors a definition. Delete the call.
20+
- FROM `reconcilePermissionSetProjection(...)` TO nothing: there is no projection to reconcile at boot. Delete the call.
21+
- FROM `projectPermissionMutation(...)` TO nothing. Delete the call.
22+
- FROM `upsertEnvPermissionSet(...)` TO `PUT /api/v1/meta/permission/:name`, or the metadata protocol's `saveMetaItem({ type: 'permission', name, item })`.
23+
- FROM `permissionSetBodyFromRow(row)` TO `permissionSetDefinitionFromRow(row)` from `@objectstack/core`: the same mapping, for converting a stored `sys_permission_set` row into its definition.
24+
- FROM `permissionSetRowFields(definition)` TO nothing: no row is written. Delete the call.
25+
- FROM the types `PermissionSeedOutcome`, `ProjectionDeps`, `ProjectionReconcileOutcome` and `WriteThroughDeps` TO nothing. Delete the import.
26+
27+
Unchanged: the security catalog's echo of a metadata-door permission set or position on an environment kernel, which lives in its own module since #23308. The packaged-permission-set lock still refuses a metadata-door save of a set a code package ships (`403 NOT_OVERRIDABLE`), and the boot still reports environment overlays that shadow such a set. `OverlayDetectionOptions.logger` now asks only for `warn`, the one channel the reading reports on.
28+
29+
**`@objectstack/spec`** (patch): the `permission.tabPermissions` liveness entry no longer cites the deleted projection, and the liveness README's registered-but-unbound `permission-set-projection` proof row is retired with the invariant it described (no `@proof` tag carries it any more).
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/metadata': minor
4+
'@objectstack/metadata-protocol': minor
5+
---
6+
7+
feat(spec)!: publish refuses an anonymous flow endpoint whose target flow runs as `system`, in both directions, with the prescription (ADR-0138 D2b)
8+
9+
Clause-②: yes (narrowing)
10+
11+
<!-- adr-0087: registered api-endpoint-anonymous-elevated-flow-refused -->
12+
13+
**BREAKING**: an accept-set narrowing on a published authoring surface, shipped as `minor` under the launch-window convention for breaking changes.
14+
15+
ADR-0138 D2b decides that no anonymous door triggers an elevated flow directly. An `authRequired: false` endpoint of `type: 'flow'` whose `target` flow declares `runAs: 'system'` ran every anonymous request's data steps as the system principal, past row-level security, with the endpoint's rate limit as the only brake. The runtime's own elevated-start refusal covers only self-triggered flow types (`autolaunched`, `record_change`, `schedule`), so a `system` flow of type `api` or `screen` behind such an endpoint executed elevated. Publish now refuses the pair, in both directions, because either item can be the last one edited: the endpoint's publish reads its target's `runAs`, and a flow's publish that sets `runAs: 'system'` reads the anonymous endpoints that target it. Each refusal names the endpoint and the flow and carries the prescription.
16+
17+
**What is refused, and where.** Every publish door that holds the pair calls one judge, `anonymousElevatedFlowPairs` in `@objectstack/spec/api`:
18+
19+
- **The stack parse** (`defineStack`, `objectstack validate`, `objectstack build`, the artifact load): a stack whose anonymous flow endpoint names one of its own flows declaring `runAs: 'system'`. One zod issue per pair, `code: 'custom'` at `apis[i].authRequired`; `defineStack` throws its schema refusal, `STACK_SCHEMA_INVALID` (422), carrying the issue.
20+
- **The package publish** (`MetadataManager.publishPackage`, `POST /api/v1/packages/:id/publish`): an `api` item of the package naming a `system` flow anywhere in the manager's index, reported at the endpoint; and a `flow` item of the package declaring `runAs: 'system'` that an anonymous endpoint outside the package names, reported at the flow. The package publishes nothing (`success: false`, the pair in `validationErrors`), and `validate: false` does not turn it off.
21+
- **A flow published through the metadata API** (`saveMetaItem` in publish mode, so `PUT /api/v1/meta/flow/:name`; a draft's publish, `POST /api/v1/meta/flow/:name/publish`; and `POST /api/v1/packages/:id/publish-drafts`): a flow going `active` with `runAs: 'system'` while a declared anonymous flow endpoint names it. `INVALID_METADATA` (422), `issues[0].path: 'runAs'`, nothing written; a publish-drafts batch aborts on it. A draft save is not judged (a draft does not execute; its publish is), and neither is a `migrate-stored` re-save of a row that already exists.
22+
23+
**Unchanged.** Each item alone publishes as before: an authenticated endpoint on a `system` flow, an anonymous endpoint on a `user` flow (or one that leaves `runAs` at its default), and an anonymous `object_operation` endpoint are all accepted. A stored flow row that already pairs this way keeps its bytes and keeps executing as published; its next publish is refused. A guest-written row that fires a record-change flow is outside the refusal: the record leaves that indirect path undecided, because its trigger is the write, not the door.
24+
25+
## FROM → TO
26+
27+
| door | FROM | TO |
28+
|:--|:--|:--|
29+
| `defineStack` / `objectstack validate` | `apis: [{ name: 'acme_intake_api', type: 'flow', target: 'acme_intake', authRequired: false, rateLimit: { enabled: true, windowMs: 60000, maxRequests: 100 }, … }]` beside `flows: [{ name: 'acme_intake', runAs: 'system', … }]` accepted; every anonymous request ran `acme_intake` as the system principal | refused, `STACK_SCHEMA_INVALID` 422, the issue at `apis[0].authRequired` naming both items and the prescription |
30+
| package publish | a package carrying either half of the pair, with the other half anywhere in the manager's index, published | refused, `success: false`, the pair reported at the package's own item |
31+
| metadata API flow publish | `PUT /api/v1/meta/flow/acme_intake` with `runAs: 'system'` while an anonymous endpoint names it saved and armed it | refused, `INVALID_METADATA` 422, the issue at `runAs`, nothing written |
32+
33+
**The one-line fix: serve the caller another way — an authenticated endpoint (`authRequired: true`) for a partner that can hold a credential, the flow's signed inbound hook (ADR-0041: a start node with `triggerType: 'api'`, a `hookId` and a `secret`, which may keep `runAs: 'system'`) for a partner webhook, or a public form (a form view whose `sharing` sets `allowAnonymous: true`) for anonymous intake.** `runAs: 'automation'` joins that list when ADR-0073 M2 adds the value.
34+
35+
No D2 conversion rewrites it: which channel replaces the anonymous endpoint depends on who the caller is, and whether the flow needs elevation is a judgment about its data steps, so no conversion can choose a posture for the author. Its D3 record is the semantic entry `api-endpoint-anonymous-elevated-flow-refused` (protocol 18).
36+
37+
**`@objectstack/spec`.** The judge and its two refusal sentences join `@objectstack/spec/api`: `anonymousElevatedFlowPairs`, `anonymousFlowDoorTarget`, `anonymousEndpointElevatedFlowMessage` (the endpoint direction), `elevatedFlowAnonymousEndpointsMessage` (the flow direction) and `validateAnonymousElevatedFlows` (the stack's issues), with the types `AnonymousFlowDoorCandidate`, `ElevatedFlowCandidate` and `AnonymousElevatedFlowPair`. `ObjectStackDefinitionSchema` runs the judge beside the per-endpoint gates. The `runAs` description on the flow schema, which prescribed `system` for every user-less trigger, `api` included, now names the exception and what serves that caller.
38+
39+
**`@objectstack/metadata`.** `MetadataManager.publishPackage` runs the judge over the package's `api` and `flow` items against the index the endpoint matcher serves from.
40+
41+
**`@objectstack/metadata-protocol`.** The flow publish doors run the judge against the declared endpoints: the engine registry's `api` items and the `metadata` service's.
42+
43+
**Who is affected, measured.** No in-repository stack pairs an anonymous flow endpoint with a `system` flow: `examples/` declares no `authRequired: false` endpoint, and every anonymous flow endpoint in the test fixtures names a `runAs: 'user'` flow. The partner-webhook example, `showcase_inbound_task_webhook`, takes the signed inbound hook with `runAs: 'system'` and is untouched.

‎.changeset/23233-permission-echo-names-runtime-package.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ Clause-②: no
99
- **What was wrong.** A writable runtime package's permission set marked `isDefault: true`, published through the package door with no `packageId` in its body, recorded no audience-binding suggestion on an environment kernel, so ADR-0090 D5's "packages suggest, admins confirm" never happened for it. On that kernel the protocol hydrates no stored row into the engine registry, so the security catalog finds the set in the metadata service. The only body there is the permission-set projection's in-memory copy, and that copy carried no `_packageId`. The catalog served the set as unowned while `GET /api/v1/meta/permission/NAME` named the package, and the suggestion collector skipped it.
1010
- **What changed.** The projection's copy of a tenant-authored set now names the package its stored row is bound to. It reads that binding from the protocol's by-name read (the `_packageId` that read serves) and derives none of its own. The catalog, the by-name read and the layered read (`?layers=true`, whose `packageId` comes from the same copy) now name the same owner. `GET /api/v1/security/suggested-bindings` lists one pending suggestion under that package. A `manage_metadata` holder still has to accept it: the publish grants nothing.
1111
- **When the body's `packageId` disagrees.** A set whose body declares a `packageId` other than the package it is published into is now owned, for the catalog and the suggestions, by the package it is published into, which is what the by-name read already served. On an environment kernel, a suggestion recorded under the body's package is pruned at the next sync while pending, and withheld from `everyone` with its reason logged if it was accepted. A pending suggestion under the publishing package replaces it.
12-
- **Unchanged.** The `sys_permission_set` row (still `managed_by: 'admin'`, with no `package_id`), the packaged-permission-set lock and every edit door. A code package's set, whose owner the catalog reads from its artifact. A set saved into no package, which stays unowned and records no suggestion. A copy of a code-shipped set's legacy overlay, which carries no owner, as before.
12+
- **Unchanged.** The packaged-permission-set lock and every edit door. (No `sys_permission_set` row is written for the set: C3 stage 7a retires the last writer.) A code package's set, whose owner the catalog reads from its artifact. A set saved into no package, which stays unowned and records no suggestion. A copy of a code-shipped set's legacy overlay, which carries no owner, as before.
1313
- **If the binding cannot be read**, the copy is registered without an owner, as before, and one warning says so. It still enforces the new body, and the next save, publish or boot reads the binding again.

‎.claude/agents/os-dev.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ model: opus
351351
- issue 对塑造公开契约的决定欠规格(spec/Zod schema、API 形状、命名、元数据语义)时:不猜。
352352
- 两种读法通向两种架构时同此;⛔ 不写投机代码。
353353
- 返回 `status: "needs_decision"`,每个问题带选项、真实代价与工作量、推荐,写进 `open_questions`。
354-
- 升级分析的四轴决策框架由派发词携带,PM 从自己那份副本填入。
354+
- 派发词标只读读数 ⇒ 不建闭包、不跑测试、不开 PR;报 `status: "reading"`,读数贴决策卡。
355355
- 框架唯一副本在 pm-dispatch SKILL.md 〈升级与决策〉;派发词逐字粘贴,dev ⛔ 不留副本。
356356
- 每个方案逐轴分析,推荐也按那些轴给理由;派发词没带,停下向 PM 索取,⛔ 不自拟一套轴。
357357
- `main` 碎了、依赖未合并、CI 基础设施故障 ⇒ 报 `blocked` 附证据,重试到排除你的改动。
@@ -361,7 +361,7 @@ model: opus
361361
```json
362362
{
363363
"issue": <n>,
364-
"status": "done | rework | blocked | needs_decision",
364+
"status": "done | rework | blocked | needs_decision | reading",
365365
"branch": "claude/issue-<n>-<slug>",
366366
"pr": "<url or null>",
367367
"session": "session_<id> — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)",

‎.claude/skills/pm-dispatch/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
105105
| `pm:dispatched` | 已派发(派发评论记轮次),恒带 assignee;与摘 `pm:queue` 同一次标签写入成对落地 |
106106
| `needs-user-decision` | 决定待做:永不派发、除代裁通道外永不代答;维护者的收件箱 |
107107
| `pm:on-hold` | 决定已做且答案是暂不做:不派发不催;仅当带机器可读 `Restart-when:` 行才合法 |
108-
| `pm:blocked` + 正文行 `Blocked-by: #N` | 等上游:选择期跳过,#N 关闭时由解锁扫描放回;工已完、PR 被外部门禁卡住的同用本态 |
108+
| `pm:blocked` + 正文行 `Blocked-by: #N` | 等上游:选择期跳过,#N 关闭时由解锁扫描放回;工已完、PR 被外部门禁卡住的同用本态;正文或线程行首 `Blocked-by:` 指向无 `Ruled:` 的 open `needs-user-decision` 卡 ⇒ ⛔ 不派发,挂本态直至其 `Ruled:` |
109109
| `pm:awaiting-maintainer` | 决定已做,只剩一次 GitHub 之外的人工动作:不派发不催;与其它 pm 状态标签互斥;入态恒带行首 `Maintainer-action:` 行(细则见 `references/state-machine.md`),无行即半态 |
110110
| `pm:blocking` | 有 open 下游依赖者(自 `Blocked-by:` 索引推导的缓存,⛔ 不手工挂);进选择全序 |
111111
| `pm:retriage` | 向分诊提问(改判、跨域 PR 指定车道、改路由、拆卡、裁 dev 报告留下的分叉),定车道与改路由限未派发卡,异议评论写明所求;与现行 `pm:*` 并存、⛔ 不摘原标;带本标签的 `pm:queue` 卡跳过派发 |

‎.claude/skills/pm-dispatch/references/decision-analysis.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
## 适用面
66

77
- 只约束 `needs-user-decision` 卡与决策箱讨论的四维分析。
8-
- 内部工具卡(dev 派发用)四维照旧,⛔ 不强加行业类比;只适用新记录,存量 ⛔ 不回改。
98

109
## 常设决裁批流程
1110

1211
- 每批恰好 5 张,末批不足照呈;呈完即停等回批,⛔ 永不一条消息投多批。
13-
- 批序:在飞被阻塞 > 运营阻塞 > 用户可见 > 结构性;每卡 = 六项写法 + 四棱块。
12+
- 批序:在飞被阻塞 > 运营阻塞 > 用户可见 > 结构性;卡面分析只一层 = 立卡席六项 + 四棱。
13+
- 总监批前卡评论是增量:命令复核前提,逐棱同异,推荐/回退/口径;不同意字母才展开四维。
1414
- 回批一行式逐卡对应字母选项;带的附加条件随执行落地,⛔ 不为它另起请示轮。
1515
- 呈递详解 = 逐卡散文按正文六项;⛔ 表格行、判词、栅格不构成详解,压缩批次是违规。
1616
- 受管草稿批例外:一 PR 一行(改了什么/为什么/席位意见/一个动作),⛔ 不套六项;速读住 PR。

‎.claude/skills/pm-dispatch/references/dispatch-runbook.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,7 @@
202202
- 点名单当天现取仍会漏,dev 对实际改动路径重取补跑。
203203
- ② 去重或看板读数取一次(查询式加时间戳)整批共用。
204204
- 派发令写:当既有事实用,只复核其后的增量,⛔ 不重跑;dev 侧对价住 os-dev 定义。
205-
- 四轴框架整段现取现粘、逐单随派发词下发。
206-
- 内部 PM 副本 = `.claude/skills/pm-dispatch/SKILL.md` 升级与决策 节的四轴框架块。
205+
- 四轴框架块(SKILL.md 升级与决策 节)整段现取现粘、逐单随派发词下发。
207206
- 该块自黑体句起、至推荐约束句止,整段逐字粘贴,⛔ 不重打、⛔ 不摘句。
208207
- 派发那刻现取,把该副本的 commit 与 md5 记进派发词。
209208
- ⛔ 仓内任何文件不得另立第三份,`check:skill-frame-sync` 全树扫;os-dev 定义没带就停下索取。
@@ -232,7 +231,8 @@
232231
- ⛔ 不凭记忆;并带一句:每一面都必须在 PR 正文有一个结论。
233232
- 结论三选一:已改、本就合规(证据)、明确不在范围(理由)。
234233
- ⛔ 静默略过:评审把没提到的面读作漏掉的面。
235-
- 测量先行卡(修复由测量结果有条件授权)⇒ 派发令写明 ⛔ 测量存在之前不写修复。
234+
- 测量先行卡(修复由测量结果有条件授权)⇒ 只派只读读数 dev 或不派等裁;建设 dev 待裁。
235+
- 只读读数 dev 不建闭包、不跑测试、不开 PR;只读树与记录,读数贴决策卡或立卡。
236236
- 探针做在 dev 要留的分支上,⛔ 不用一次性分支:建得出删不掉,遗留分支被预检读成认领。
237237

238238
## security-object 无判决枚举(座位贴常设 ⑦)的方法

‎.claude/skills/pm-dispatch/references/lanes/director.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
## 基本原则
2323

2424
- 三要素:每批 5 张;每卡从业务角度讲清问题、选项与代价;每卡带四维分析加一个推荐。
25-
- 呈报的代价列旁恒带工作量列(车道 · 阶段/PR 数 · 实测先例或「无先例」),与卡面同列。
25+
- 聊天呈批逐卡六项,取材 = 卡面分析 + 本席增量;增量点名复核了什么,⛔ 不复述所同意的。
2626
- 创业阶段不渐进:废弃别名与能力退役默认立即退休,无窗口、无双拼写宽限。
2727
- 一次裁清:子问题、分叉与执行参数(字母、车道、级别)同笔裁定,⛔ 不留 dev 另立决策卡。
2828

0 commit comments

Comments
 (0)