Skip to content

Commit 37335ca

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-24579-v18-sweep-round7-integration
2 parents f73b68d + 1c1144f commit 37335ca

42 files changed

Lines changed: 1548 additions & 15 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
---
4+
5+
fix(plugin-security): a delegated agent's own leg does not collect an object's declared default row scope (#24341)
6+
7+
Clause-②: no
8+
9+
An agent acting on behalf of a user (ADR-0090 D10) is judged on two legs that the read door ANDs together: its own sets, which are a capability ceiling and carry no row policy, and its delegator's sets. Since the row-policy collection began applying an object's declared `defaultRowScope` where the held sets carry no row policy, the ceiling's silent leg collected that default too. Where the delegator's own leg is wider than the default, the agent then read fewer rows than its delegator. The platform administrator is the case in point: the administrator's own leg skips row policies on the identity objects, so an agent acting for the administrator would have read only the administrator's own rows there, where it read every row before.
10+
11+
The collection no longer adds an object's default on a delegated principal's own leg, so a ceiling's silence is not read as the object's default. The delegator's leg collects the default exactly as any principal's does, and a principal that acts for no one keeps the default whenever its sets are silent.
12+
13+
What moves, against `18.0.0-next.5`: on an object that declares `defaultRowScope`, an agent acting for a user reads the rows its delegator's side admits, and its explain output names no object default on the agent's side. Before this release's reader, that was already so.
14+
15+
**Unchanged.**
16+
17+
- A principal that acts for no one, a human or otherwise, collects an object's default when its sets carry no row policy for the object and operation, as before.
18+
- The delegator's side, the superuser bypass, and every authored policy are judged as before.
19+
- No export, option or type moves.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@objectstack/objectql': minor
3+
---
4+
5+
fix(objectql)!: the registry refuses an `extend` contribution that carries `defaultRowScope` (#24341)
6+
7+
Clause-②: no (narrowing)
8+
9+
An object's default row scope is declared by the object's owner alone. `SchemaRegistry.registerObject` now refuses an `extend` contribution that carries `defaultRowScope`, whatever its value and whatever the target, with `INVALID_METADATA` (`status: 422`) and the error name `ExtensionDefaultRowScopeRefusedError`, naming the object and the extending package. It is refused before the contribution is recorded, so nothing of it lands, and the owner's declaration stays in force.
10+
11+
Every value is refused because no narrower line holds. Whether one row predicate admits more rows than another cannot be decided in general, so a "loosening" cannot be told from a "narrowing". And the extension merge never carried the key onto the object, so an accepted one restricted nothing while its author believed it did.
12+
13+
**BREAKING — what moves for consumers.** This is an accept-set narrowing on the registry's `extend` door, shipped as `minor` under the launch-window convention for accept-set narrowings.
14+
15+
- FROM: `registerObject(def, packageId, undefined, 'extend')` with `def.defaultRowScope` set registered the contribution and dropped the key.
16+
- TO: it throws `INVALID_METADATA` (`422`, `ExtensionDefaultRowScopeRefusedError`).
17+
- Fix: remove `defaultRowScope` from the extension. To scope the rows the holders of a permission set read on the object, give that set a `rowLevelSecurity` policy for it.
18+
19+
**Unchanged.**
20+
21+
- An `extend` contribution that leaves the key out, or sets it to `undefined`, registers and folds as before. The one shipped `extend` of a platform object in this repository (`@objectstack/plugin-auth`'s `sys_sso_provider` extension) registers, measured on a booted composition.
22+
- A manifest's `objectExtensions` entry never reaches this refusal: its schema is strict and refuses the key at authoring, and `ObjectQL.registerApp` copies a closed key list off each entry.
23+
- The owner's own `defaultRowScope` registers and re-registers as before.
24+
- No export moves: the error class is not exported, and its code is the existing `INVALID_METADATA`.
25+
26+
<!-- adr-0087: not-required (no-migration-prescription) a refusal at the registry's extend door of a key the extension merge never carried: no authorable key, spelling, export or stored shape moves, and the manifest path already refuses the key at authoring. The repair is removing the key from a programmatic extend call, which no ledger entry can derive. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id covers this door (not already-registered); and the change is a door verdict, not an interface, a type or an export (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
'@objectstack/platform-objects': minor
3+
'@objectstack/plugin-audit': minor
4+
---
5+
6+
feat(platform-objects)!: the platform identity objects declare their default row scope (#24341)
7+
8+
Clause-②: no (narrowing)
9+
10+
Twenty-three platform objects now declare `defaultRowScope`, each with the row policy the shipped permission sets of `@objectstack/plugin-security` carry for it, moved verbatim: the seven SCIM projections (`sys_scim_user`, `sys_scim_subject`, `sys_scim_projection_grant`, `sys_scim_identity_tombstone`, `sys_scim_group`, `sys_scim_group_member`, `sys_scim_connection_binding`), `sys_verification` and `sys_jwks`, `sys_organization`, `sys_user`, `sys_session`, `sys_account`, `sys_team_member`, `sys_two_factor`, `sys_user_preference`, `sys_api_key`, `sys_device_code`, `sys_oauth_access_token`, `sys_oauth_refresh_token`, `sys_oauth_consent`, `sys_oauth_application` (`@objectstack/platform-objects`) and `sys_audit_log` (`@objectstack/plugin-audit`, which owns the ledger). `sys_user` is the one merge: the key holds one policy, so its two shipped policies are joined into `id == current_user.id || id in current_user.org_user_ids`, a `select` policy. The platform-global objects (`sys_metadata*`, `sys_job*`, `sys_migration*`, `sys_platform_setting`, `sys_presence`, `sys_flow_dispatch`) declare none.
11+
12+
The security plugin's row-policy collection applies an object's declared default when the permission sets the caller holds carry no row policy for that object and operation. The shipped sets still carry the same policies, so a holder of them reads exactly what it read before.
13+
14+
**BREAKING — what moves for consumers.** This narrows what a principal reads on these objects when its sets carry no row policy for them, shipped as `minor` under the launch-window convention for accept-set narrowings.
15+
16+
- FROM: a principal holding no platform baseline (a deployment that sets the security plugin's `fallbackPermissionSet: null`) whose sets grant read on an identity object and carry no row policy for it read every row of that object.
17+
- TO: it reads the object's declared default: its own rows (`user_id == current_user.id`); on `sys_user`, its own row plus the members of the organisation it acts in; on `sys_organization`, its own organisation; no row on `sys_verification`, `sys_jwks`, `sys_scim_group`, `sys_scim_group_member` and `sys_scim_connection_binding`; on `sys_audit_log`, its organisation's entries where organisation scoping is on (the platform's tenant policies, this one included, are stripped where it is off).
18+
- Fix: to give such a principal more rows, give one of its permission sets a `rowLevelSecurity` policy for the object. A policy a held set carries is used as authored, and the default is not added to it.
19+
20+
**Unchanged.**
21+
22+
- A deployment with the platform baseline (`member_default`, the default) serves every human exactly what it served before, on every one of the 23 objects and every operation: each human holds the baseline, and the baseline carries a policy for each object and operation. Measured on a booted composition with a member, a viewer, an organisation admin, a user in no organisation and the platform administrator, before and after the declarations.
23+
- An MCP agent acting for a user reads the same rows, and gets the same explain output, as before. Its ceiling set carries no row policy, and the security plugin does not collect an object's declared default on a delegated principal's own side of the intersection (see the `@objectstack/plugin-security` entry), so the agent's rows stay bounded by its delegator's side alone. Measured on a booted composition, before and after the declarations, for the agents of a member, a viewer, an organisation admin and the platform administrator; the platform administrator's agent still reads every row.
24+
- The platform administrator's superuser bypass is decided before any row policy is collected.
25+
- No export, option or stored shape moves.
26+
27+
<!-- adr-0087: not-required (no-migration-prescription) declarations of a row scope on platform objects, each the policy the shipped permission sets already carry: no authorable key, spelling, export or stored shape moves, and no stored row is read, rewritten or converted. What narrows is the rows a principal with no baseline and no row policy of its own reads, and the repair is a deployer's choice of a `rowLevelSecurity` policy, which no ledger entry can derive. The other categories are closed on facts: both packages publish (not unpublished); no ADR-0087 id covers row scopes (not already-registered); and the change is a runtime row scope, not an interface, a type or an export (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
Automation: two help sentences on `try_catch` now match what the engine binds. The error value's `nodeId` is described as the id of the Try / Catch node itself (not the failing inner step, which is normally named inside `message`), and `errorVariable` says in words what the bound value carries instead of naming a code type. Schema and runtime behaviour are unchanged; the generated reference page and the shipped JSON schema carry the new text.

‎.claude/skills/pm-dispatch/references/cross-repo-coordination.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
- 规则 3:联动杂事立单,不靠记忆。
3535
- 已验收 PR 的产物流向另一仓,接受该 PR 的执行席立即在消费仓立后续单,带 `Blocked-by:`。
3636
- 规则 4:纵向拆分,一个分诊 PM + N 个执行 PM;一人恰一车道,一车道可坐多席。
37-
- 分诊座位唯一生产:定级/路由/type/查重/shadow/`duplicate_of`;执行席永不定级或路由裸卡。
37+
- 多车道仓:分诊唯一生产定级/路由/type/查重/shadow/`duplicate_of`;执行席永不定级或路由裸卡。
3838
- 执行座位信任标签,只在本车道认领;误标 ⛔ 不自行改,挂 `pm:retriage` + 异议评论同笔。
3939
- 突发积压调频率或 `batch`,持续积压按仓拆席走 PR;⛔ 不跨车道借调。
4040
- 维护者直派通道:当面指挥的 PM 会话直接路由,只对明示指挥的卡成立。

‎.claude/skills/pm-dispatch/references/filing-gate.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
- ⛔ 不是卡:既有卡一条评论能承载的指针/知会/请求;那条评论即其记录。
2424
- UI 卡写明所需通用块或 spec 契约门;要产品命名 widget/dialog/hook/命名空间的卡退回重立。
2525
- POST 前正文答三件:类别 ①–④、动手的读者(哪席哪步)、查重查询与命中数(含 closed)。
26-
- 缺件卡由分诊首触补齐能从卡与一次检索推导的三件,写进定级评论并定级。
27-
- 补不齐(四类皆非,或 `finding` 无 `reach:`)者才因缺件首触即关 not_planned,一行点名所缺。
26+
- 缺件卡由分诊(单车道仓 `repo:*` 席)首触补齐卡与一次检索可推三件,写进定级评论并定级。
27+
- 补不齐(四类皆非)者才因缺件首触即关 not_planned,一行点名所缺。
2828
- 「动手的读者」与 Acceptance notes 同一问:说得出具体 PR 或人 ⇒ 写进去,说不出 ⇒ 不立卡。
2929
- 说不出的形态:`.changeset/*`、无人在改的文档页、生成物、已扫完的批次,兜底不成立。
3030
- 配额:一席每 fire 至多立三张;sweep 超出的折进一张带清单的卡,或折进座位贴。

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,6 @@
4848
## 席内判断
4949

5050
- 平台缺口卡是本席最常见的联动产出:立卡在平台仓、无 assignee、普通卡待分诊。
51-
- `domain:*` 与 type 留给分诊;催办不代裁。
51+
- 平台仓卡的 `domain:*` 与 type 归分诊;本仓卡 type 随机械三务自打;催办不代裁。
5252
- 展品价值判据:一张卡若既不展现平台能力、也不修真实缺陷,默认不做。
5353
- 拿不准升决策箱,⛔ 不自裁。

‎content/docs/references/automation/control-flow.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ const result = FlowRegionSchema.parse(data);
244244
| :--- | :--- | :--- | :--- |
245245
| **try** | `{ nodes: object[]; edges?: object[] }` | ✅ | Protected region |
246246
| **catch** | `{ nodes: object[]; edges?: object[] }` | optional | Handler region run when the try region fails |
247-
| **errorVariable** | `string` | optional (default: `"$error"`) | Variable holding the caught error in the catch region — `$error` (the default), or a name without a leading `$` read as `{{ name.message }}`; any other `$` name is the flow engine's own and refused. The value is a `TryCatchErrorValue`: `nodeId`, `message`, `code` when the failing node carried a platform-classified error code (ADR-0112 — branch on `$error.code` to tell "the row is already there" from "the store is down"), and `iteration` / `item` when the failure happened inside a loop body |
247+
| **errorVariable** | `string` | optional (default: `"$error"`) | Variable holding the caught error in the catch region — `$error` (the default), or a name without a leading `$` read as `{{ name.message }}`; any other `$` name is the flow engine's own and refused. It carries the id of this Try / Catch node (`nodeId`), the failure message, which normally names the step that failed (`message`), the platform-classified error code when that step reported one (`code`, ADR-0112 — branch on `$error.code` to tell "the row is already there" from "the store is down"), and, when the failure happened inside a loop body, the zero-based pass number (`iteration`) and the item being processed (`item`) |
248248
| **retry** | `{ maxRetries?: integer; backoffMs?: integer; backoffMultiplier?: number; maxRetryDelayMs?: integer; … }` | optional | Optional retry policy for the try region |
249249

250250
### Nested Shape: `TryCatchConfig.try`
@@ -281,7 +281,7 @@ const result = FlowRegionSchema.parse(data);
281281

282282
| Property | Type | Required | Description |
283283
| :--- | :--- | :--- | :--- |
284-
| **nodeId** | `string` | ✅ | Node the failure is attributed to |
284+
| **nodeId** | `string` | ✅ | Id of this Try / Catch node, not of the step that failed inside its try region; that step is normally named inside `message` |
285285
| **message** | `string` | ✅ | Message of the error that ended the try region, after any retries |
286286
| **code** | `string` | optional | Platform-classified error code (ADR-0112) of the failure that ended the try region, e.g. `create_record`'s `DUPLICATE_RECORD`; present only when the failing node's own result carried one, so a catch region branching on `$error.code` treats "unset" as "no classified code", never as "nothing failed". An open `string`, not a closed enum: the vocabulary is `StandardErrorCode` plus registered ledger codes plus tenant-authored codes, and third-party node executors bind their own |
287287
| **iteration** | `integer` | optional | Zero-based iteration of the enclosing loop when the failure happened inside a loop body; absent outside a loop |

0 commit comments

Comments
 (0)