Skip to content

Commit c635eb1

Browse files
committed
Merge origin/main into claude/issue-15206-s3-doors-env-only
Claude-Session: https://claude.ai/code/session_01T33XAHecf6UARBkb45mecB Co-authored-by: Claude <noreply@anthropic.com>
2 parents 691f52d + 587bd96 commit c635eb1

27 files changed

Lines changed: 3292 additions & 418 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
"@objectstack/lint": patch
3+
---
4+
5+
fix(lint): 26 more author-time findings print one verdict line, and `os explain <rule-id>` carries their reasoning
6+
7+
Clause-②: no
8+
9+
- **Shorter verdicts.** Each finding of these 26 rule ids now prints a `message` of one verdict sentence. Every finding the rules' own test suites fire is at most 200 characters; before, the longest of each ran from 205 to 697 characters. The ids:
10+
- dashboard widgets: `widget-legacy-analytics-unrenderable`, `dashboard-filter-field-unknown`, `dashboard-filter-field-not-included`, `dashboard-filter-field-unprovisioned`, `widget-filter-field-unknown`, `widget-filter-field-not-included`, `widget-sortby-unselected`, `chart-field-unknown`, `chart-measures-missing`, `widget-measures-missing`, `chart-dimensions-missing`;
11+
- datasets: `dataset-include-unknown`, `dataset-field-unknown`, `dataset-field-not-included`, `dataset-filter-field-unknown`;
12+
- security posture: `security-controlled-by-parent-ambiguous-relation`, `security-fls-unknown-field`, `security-controlled-by-parent-no-relation`, `security-master-detail-ungranted`, `security-owd-alias`, `security-delegation-missing-reason`;
13+
- visibility predicates: `visibility-root-mislayered`, `visibility-predicate-over-budget`, `visibility-predicate-syntax`, `visibility-predicate-unknown-function`, `visibility-bare-identifier`.
14+
15+
The values the author wrote still close each verdict — the field path, the candidate roster, the selection list, the predicate excerpt (now at most 72 characters; `visibility-predicate-over-budget` no longer echoes the predicate at all, its `path` locates it) — so a verdict over a long authored value grows with it. The `fix` (the CLI's `fix:` line, the runtime issue's `hint`), every rule id, severity and `path`, and what each rule accepts or refuses are unchanged. A tool that matched the old message text should match on `rule` and `path` instead.
16+
- **`os explain <rule-id>` takes these 26 ids**, for example `os explain chart-field-unknown`. It prints the reasoning the verdicts no longer carry: how a dashboard filter reaches every widget, why the renderer ignores `chartConfig` binding keys, what an unresolved dataset path does to the analytics query, how the master relation of a `controlled_by_parent` object is chosen, how the runtime resolves a field-permission key, why a visibility predicate that cannot evaluate renders its element anyway. The `rule:` line under each of these findings now ends with `` — `os explain <rule-id>` for … ``. The no-argument listing and its `--json` `rules` array list the 26 ids, and so does the unknown-id error's `Rules with an explanation:` line. `RULE_EXPLANATIONS` in `@objectstack/lint` gains the 26 entries.
17+
- **Where the new text prints.** On the CLI, `os validate`, `os build` (and `os compile`, which `os dev` runs on every compile), `os lint`, `os verify` and `os init`'s scaffold check print the new `message` on the text face, and `os validate --json` and `os build --json` carry it in their `warnings` and author-time `issues`. `os doctor`'s dashboard widget check prints the 11 widget ids as `where: message`, with no `rule:` line. At the runtime publish gate (Studio, REST `/meta`, MCP), by write type: a `dashboard` write carries the widget ids, a `dataset` write the dataset ids, a `view` write the visibility ids, an `object` write the two `controlled_by_parent` ids and `security-master-detail-ungranted`, a `permission` write `security-fls-unknown-field` and `security-master-detail-ungranted`, a `seed` write `security-delegation-missing-reason`. An `error` changes the 422 issue's `message` and the refusal log line under `OS_ALLOW_UNLINTED_METADATA_WRITES`; a `warning` (`dashboard-filter-field-unprovisioned`, `chart-field-unknown`, `chart-measures-missing`, `widget-measures-missing`, `chart-dimensions-missing`, `security-master-detail-ungranted`, `visibility-root-mislayered`) changes the `message` in the 2xx response's `advisories` and the deduped `[Protocol] authoring advisory` server log line. Each issue's `hint` is unchanged.
18+
- **Never at the runtime gate:** `security-owd-alias`. The object schema's closed `sharingModel` / `externalSharingModel` enums refuse those values at parse, with their own message, before the gate runs; the rule speaks only on the unparsed doors (`os lint` on a raw config, a direct call).
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/core': patch
3+
'@objectstack/plugin-auth': patch
4+
---
5+
6+
perf(core,plugin-auth): an authenticated request resolves its caller's grants once, not twice
7+
8+
Clause-②: no
9+
10+
`resolveAuthzContext` learns who the caller is from the transport's `getSession`. Against `@objectstack/plugin-auth` that is better-auth's `getSession`, whose `customSession` hook resolves the principal's grants for the session payload's `positions[]` and `isPlatformAdmin`; `resolveAuthzContext` then resolved the same grants again, with the same arguments, for the request's envelope. Every authenticated request on a door that resolves identity through `resolveAuthzContext` with a better-auth session read (the runtime dispatcher and the REST server among them) paid every grant read twice: eight reads per resolution for a caller in an organization.
11+
12+
`resolveAuthzContext` now runs inside a request-scoped grants memo. A resolution that already completed inside the same call, with the same user, organization and seeds, is served to the next caller that asks for exactly that resolution, so the hook's resolution serves the resolver's own. The decision a request is authorised with is unchanged:
13+
14+
- The memo lives for one `resolveAuthzContext` call (an `AsyncLocalStorage` scope, closed when the call settles). Nothing is cached across requests; the cross-request grants cache keeps its own default-off switch, `OS_AUTHZ_GRANTS_CACHE_TTL_MS`.
15+
- An entry is served only when a fresh read would agree with it. No write may have started, been executed at the driver, or still be in flight on the engine since the first resolution opened: the engine's write epoch (bumped when a write starts) and a write observer the memo registers as an engine middleware (counting writes into the chain and, after the driver step, out of it) must both read what they read at the open, with nothing inside the chain. The observer sees statement execution, not commit visibility. A write inside an `engine.transaction()` becomes visible only at its COMMIT, outside every chain. On driver-sql (not on the Turso remote face, which opens no transaction), a request whose step 2 falls inside that one commit round trip is authorised as of its first resolution, and the next request reads fresh — the same answer as a write from another process. No grant validity boundary may lie between the two clocks. The organization must be the same (the session arm's dropped-claim re-resolution is its own entry). A `bypassGrantsCache` caller is never served, a failed resolution is never stored, and an engine without the write-epoch seam or `registerMiddleware` declines entirely.
16+
- `plugin-auth`'s hook now passes the session's email as its seed email, exactly as `resolveAuthzContext` does for the same session, so the two calls ask for the same resolution. The seed reaches only the envelope's `email`, which the hook does not read: the payload's `positions[]` and `isPlatformAdmin` are unchanged, and platform-admin standing still compares the stored `sys_user.email`, never a seed.
17+
18+
Where the saving does not apply, the request reads the grants twice, as before — always the safe direction:
19+
20+
- a request that meets a concurrent write on its engine;
21+
- a request whose session read itself writes: the first request on a fresh auth instance generates its signing key, and with an idle timeout configured `enforceSessionControls` stamps `sys_session.last_activity_at` about once a minute per session;
22+
- the first `resolveAuthzContext` call on an engine, which registers the write observer and stores nothing for it.

‎.claude/skills/pm-dispatch/references/landing-operations.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
- 这种 PR 停在 draft,正文写精确的预期红清单:逐条失败测试名 + 报错签名。
8888
- 正文同写解除条件:点名所依赖的那张 PR 合入。
8989
- 每个 CI-failure 事件与清单比对:签名匹配静默跳过,新签名才是真问题。
90-
- 依赖合入后:最后一轮同步 → 红清零 → 转 ready → 入队。
90+
- 依赖合入后:末轮同步走中继 `pr_update_branch`(`rest-channel.md`)→ 红清零 → 转 ready → 入队。
9191

9292
## D. 串行接力:多个已实现 PR 全碰生成物时,一次只放行一个
9393

‎.claude/skills/pm-dispatch/references/platform-readings.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
- 摘标签与翻 ready 是状态写,同 head 重起标签敏感检查 ⇒ ③ 在入队前末次状态写之后读。
8787
- 检查全部完成的 PR 挂 auto-merge 即入队,本仓 28–60 秒 ⇒ 挂载与落地之间无窗口。
8888
- `mergeable_state` 未落定时挂上的是经典 auto-merge、不入队,落定后再挂才入队。
89-
- `behind` 的 PR 照常入队:落后于 main 不是入队否决,⛔ 不为它先跑 update-branch。
89+
- `behind` 的 PR 照常入队:落后于 main 不是入队否决,⛔ 不为它先发 `pr_update_branch`。
9090
- `check_suite.completed` 会命名过期 head,check-run 也只属最后一次 push ⇒ 用前先重读当前 head。
9191
- 落地相邻的写侧一则:GitHub 标签描述上限 100 字符,超长写回 422,133 字的原文即不可存。
9292

‎.claude/skills/pm-dispatch/references/rest-channel.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@
4646
- ✓ 该 `PATCH` 带 `state` 关卡/重开,`state_reason` 交付 `completed`、撤单 `not_planned`,走裸 REST。
4747
- 请求体走文件(`-d @file`)或引号定界 heredoc(`<<'EOF'`),⛔ 永不内联双引号串。
4848
- ✓ 请求复审 `POST .../pulls/{n}/requested_reviewers` · 开 PR `POST .../pulls` 带 `draft=true`。
49-
- ✓ `origin/main` 合进 PR head:`PUT .../pulls/{n}/update-branch`,PM 席位、零文件写、真合并提交。
50-
- `expected_head_sha` 须完整 40 字符 SHA(短 SHA 回 422);base 未动回 422 = 无事可做,不是失败。
49+
- ✓ `origin/main` 合进 PR head:中继 op `pr_update_branch`(`pull` + 40 位 `expected_head_sha`),真合并提交。
50+
- ⛔ 裸 `PUT .../pulls/{n}/update-branch` 落个人账号;短 SHA 派发前即拒;base 未动 422 = op 报 no-op。
5151
- ✓ 落地经中继 `node scripts/pm/fleet-write/dispatch.mjs --repo O/N --actions-file F`;`--via dispatch` 同载。
5252
- ⛔ 裸 `PATCH /pulls/{n}` 带 `{"draft": false}` 回 200 零改;状态码不作数,`GET /pulls/{n}` 才作数。
5353
- 线程自己建:`POST .../pulls/{n}/comments` 带 `commit_id`·`path`·`line`,回读看 `review_threads`。
@@ -71,7 +71,7 @@
7171

7272
- `merged_by` 是入队者,⛔ 不是绕队证据:队列合并归属给入队的账户,对队列与直合零分辨力。
7373
- 判据 ①:直接合并 `PUT .../pulls/{n}/merge` 在强制队列 ruleset 下回 405。
74-
- ②:PR 上的 `added_to_merge_queue` timeline 事件。③:对已入队 PR 调 update-branch 回不能更新。
74+
- ②:PR 上的 `added_to_merge_queue` timeline 事件。③:对已入队 PR 发 `pr_update_branch` 回不能更新。
7575
- ①② 的拼写与边界住 `platform-readings.md` 队列段,本条只归拢判据。
7676
- 队列 required 集按 job 与 check-run 名匹配,workflow 名从不作为 check context 出现,搜也搜不到。
7777
- 改任一 job 名或 test 分片矩阵的形状,必须同一笔更新队列 required 集,否则队列静默挂起。

0 commit comments

Comments
 (0)