Skip to content

Commit 33ade70

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-16095-hook-rules-reach-handler-authored-hooks
2 parents fdcc85b + f48f3f1 commit 33ade70

44 files changed

Lines changed: 2318 additions & 221 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: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
"@objectstack/core": minor
3+
---
4+
5+
fix(core): `ResolvedAuthzContext.authRefusal` is removed — a published member nothing ever read (#14273)
6+
7+
**BREAKING** published-type narrowing, shipped as `minor` under the repo's
8+
launch-window convention for breaking changes. `ResolvedAuthzContext` — the
9+
envelope `resolveAuthzContext` answers, exported from `@objectstack/core`'s
10+
root entry — loses its optional `authRefusal?: { reason; message }` member.
11+
Maintainer ruling 2026-09-02 (option A, ADR-0049 enforce-or-remove),
12+
re-affirmed 2026-09-03 as A1 with the carriers a published narrowing owes
13+
once the type was measured as public API: the member was written by the two
14+
posture-conditional API-key refusals (`organization_required` at admission,
15+
`organization_membership_ended` after grants) since #8287 and read by nothing
16+
— zero runtime readers across every transport and consumer in the repo for
17+
its whole life; only test assertions ever looked at it.
18+
19+
What changes:
20+
21+
- `ResolvedAuthzContext` no longer declares `authRefusal`. Code that reads
22+
`ctx.authRefusal` stops compiling (`TS2339`); at runtime the property was
23+
already absent from every resolved context except the two refused ones.
24+
- The two refusals themselves are UNCHANGED: they still fire, still fail
25+
closed (no `userId`, empty grants), and every transport still answers the
26+
generic anonymous `401 UNAUTHENTICATED`. No status code, body or header
27+
moves — a holder of someone else's key learns nothing, exactly as before.
28+
- The refusal REASON is observable on exactly one surface, and it is not the
29+
envelope: the server-side `[security] API key refused (reason) ...` `warn`
30+
line at the decision point (#15256 / 2A), which names the key row id,
31+
principal and organization for the operator. The pins that kept the two
32+
reasons distinguishable through the field now read that line.
33+
- `ApiKeyRefusalReason` and `ApiKeyAdmission` are unchanged — the reason
34+
vocabulary still exists; it just no longer has a copy on the resolved
35+
context.
36+
37+
**Migration.** A consumer that read `ctx.authRefusal` deletes the read; there
38+
is no replacement on the envelope, by design — disclosing the reason to a
39+
caller (option B) was ruled out as a security-boundary question, and the
40+
recorded fallback if a reader ever appears is an audit-side outlet (option C),
41+
never the wire. Fail-closed handling keys on the absent `userId`, as every
42+
in-repo transport already did. An operator who needs the reason reads the
43+
server log line.
44+
45+
<!-- adr-0087: not-required (runtime-interface-only packages/core/src/security/resolve-authz-context.ts#ResolvedAuthzContext) A published runtime TypeScript interface lost an optional member. No Zod schema, no `packages/spec` declaration, no object definition and no stored representation is touched — `ResolvedAuthzContext` is a plain interface in `packages/core`, projected from no schema and referenced by no metadata surface — so `objectstack migrate meta` has nothing to rewrite and there is no tombstone to mint. The channel that reaches an affected consumer is the compiler at the read site (`TS2339`), which is more precise than a ledger line. The in-repo census (zero runtime readers; the only readers were test assertions, relocated onto the `warnApiKeyRefusal` line) and the workspace typecheck are recorded on the PR. -->
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`DatasetMeasureSchema.format` now documents what a DATE-valued measure can and cannot say, and the numeral-pattern examples no longer stand as the whole story.
6+
7+
The field was silent about date measures while advertising `e.g. "$0,0.00", "0.0%"` — the pattern grammar a date measure is precisely unable to read. An author with a `min` / `max` over a date field read that line, wrote `format: 'YYYY-MM-DD'`, parsed clean, and got the locale default.
8+
9+
The statement is carried by a `.describe()` where there was none, so it reaches the published surfaces an author actually reads: the generated JSON Schema (`json-schema/ui/Dataset.json`, `DatasetMeasure.json`) and the reference table in `content/docs/references/ui/dataset.mdx`, whose Description cell for `format` had been rendering the silence as a blank. The docblock above it carries the longer measured record.
10+
11+
What it now says, measured rather than assumed against the objectui pin this repo builds against: a numeral pattern applies to a numeric measure; a date-valued measure never reads a date PATTERN — a date-only value reads `format` as a display STYLE (`short`, `relative`), and a datetime value ignores `format` altogether.
12+
13+
Nothing accepts or rejects differently: `format` remains `z.string().optional()` and no measure is refused. Documentation over a published schema (objectui#7178 ruled A).
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`GetMetaItemsRequestSchema.organizationId` no longer documents itself as always consulted.
6+
7+
The published `describe()` opened with "Selects the org partition in the ADR-0005 overlay read order" and closed with "Absent = environment-wide read: only env-level overlays apply and no org partition is consulted." Stating only the absent case invites the converse, and an integrator reading it completes it as *present ⇒ consulted* — so a caller who supplies an organization believes it has scoped a read that can in fact be environment-wide. A supplied organization is not consulted on every `getMetaItems` read.
8+
9+
The corrected text qualifies the promise instead of implying its converse: the parameter selects the org partition **when an org partition applies**, and supplying a value "does not by itself guarantee an org partition is consulted; where none applies, and whenever it is absent, the read is environment-wide and only env-level overlays apply."
10+
11+
Prose only. No key is added, removed or renamed, no export moves, no accept set changes and no runtime behaviour changes — the schema's shape and validation are byte-for-byte what they were. What ships is the JSON-Schema `description` for the existing `organizationId` key and the matching row in the generated API reference, which is why this is user-visible enough to owe an entry and narrow enough to be a patch.
12+
13+
The three sibling `organizationId` describes on `GetMetaItemRequestSchema`, `GetMetaItemLayeredRequestSchema` and `GetMetaItemCachedRequestSchema` are deliberately left alone.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`KnowledgeRefreshPolicy.cron` no longer tells authors that the `cron` dialect engine judges their syntax "when the expression is evaluated". Both halves of that sentence were false: nothing evaluates `refresh.cron``service-knowledge` reads `refresh.onRecordChange` and never `refresh.cron` — and `@objectstack/formula`'s registered `cron` engine has no caller outside that package, so it was never going to issue that verdict either. The claim shipped to authors through the generated reference page (`content/docs/references/ai/knowledge-source.mdx`), naming both an engine that never sees the value and an event that never happens.
6+
7+
The docblock, the `.describe()` and the slot's two pin-test comments now say what is true today, matching the wording of the already-corrected Expression Protocol dialect table: cron syntax is not checked at parse time and no engine evaluates this slot — `croner` judges a cron pattern only where a schedule is wired (`CronSchedule.expression`, a different slot) — so the verdict belongs to whatever external scheduler the author hands the value to. Documentation only: no exported symbol, no authorable key and no accept-set movement; the parse behaviour is byte-for-byte unchanged, and the pin that proves `'not a cron'` still normalizes is untouched.

.changeset/membership-ended-session-revoke.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ an evaluation cannot.
1818
cause. There is no Zod enum behind the column — it is free `text` — so the field's own
1919
description is the published vocabulary, and that is where the value is declared. The
2020
string deliberately matches the one the API-key arm of the same ruling family already
21-
mints for this event (`authRefusal.reason` in `resolve-authz-context.ts`), so one grep
21+
mints for this event (`ApiKeyRefusalReason` in `resolve-authz-context.ts`), so one grep
2222
finds every place the platform acts on a membership ending.
2323
- **The trigger acts on the ORGANIZATION'S CLAIM, never on the user** (maintainer ruling,
2424
decision batch #49 item 4, option B). A user who still holds another membership is
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`navigationContributions[].group` now documents the mis-aimed case, not only the omitted one (#14925)
6+
7+
The `describe()` on that key said what happens when `group` is **omitted** and nothing about what happens when it is **present and names no group the target app declares** — which is the case that actually bites. A contributing package cannot see the target app's group ids at authoring time (the target app belongs to another package), so a wrong id is undetectable by reading the contributor's own source; and the platform **relocates** the items to the app's top level rather than refusing them, so the menu renders, a smoke test passes, and the information architecture has silently changed.
8+
9+
The description now names that third case: it is **not refused**, the items are appended at the app top level anyway, and a `nav_contribution_group_missing` diagnostic is emitted — by the runtime at `warn`, and by **both** `os build` and `os validate` at compile time, in each command's `--json` payload under the existing `warnings` key.
10+
11+
Prose only. `group` remains `SnakeCaseIdentifierSchema.optional()`, the accept set is unchanged and nothing is refused that was not refused before; the recorded authorable key surface (`authorable-surface.json`) and the schema manifest (`json-schema.manifest.json`) are byte-identical. What moves is the string an author reads: the generated reference rows in `content/docs/references/ui/app.mdx` and `content/docs/references/kernel/manifest.mdx`, and the `description` on the published JSON Schemas that embed `NavigationContribution` (its own schema, the bundled `objectstack.json`, and 22 `json-schema/api/*` and `json-schema/kernel/*` package envelopes).

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -470,11 +470,9 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
470470
- ② 认领评论(英文),固定形状见 模板与表 节;首行以字面 `Claim:` 开头是机器判据。
471471
- 巡查谓词只认 `Claim:` 这一个拼写且保持严格,修法是全舰队向它收敛,⛔ 不放宽谓词。
472472
- session ID 不可省;`mode:subagent` 的 dev 与 PM 同会话同 ID,甄别身份是分支。
473-
- `Clause-②: yes | no` 是条款②内容肢的强制申报,恒英文机器判据,恰这两种拼写。
474-
- Clause-② 判据:本卡改变契约接受/拒绝行为或扩大公开面吗;`yes` 绑定入队闸门。
473+
- `Clause-②: yes | no` 恰这两种拼写,恒英文机器判据;判据:本卡放宽接受集或扩大公开面吗。
475474
- Container & model 行的档位引当次 `node scripts/pm/dispatch-gates.mjs --tier <paths>` 输出,⛔ 不凭记忆。
476475
- 末行 Serial constraints cleared 是落在评论里的读数,同包在飞单不点名等于没查。
477-
- 末行点名同包/同文件的前驱 PR 与在飞认领,及本卡 pin 断言的兄弟卡;无则 none。
478476
- 本卡 pin 断言兄弟卡在改的行为 ⇒ 派发令注明,并在用例内预登记翻转触发词,⛔ 不修绿。
479477
- 文件面申报到区域级,每单必填;分支名必须带 issue 号。
480478
- 家族派发的折叠认领:共享分支按链首卡命名;每张成员卡各留认领评论并点名该分支。
@@ -507,10 +505,14 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
507505
-`references/**` 面、或治理面上一行级机械文本改动的卡,降为默认判断档施工。
508506
- 纯机械一行 PM 酌定可至下限档;降档施工的补偿控制 = 复核席跑契约复审档。
509507
- 降档施工只经契约复审档复核到达维护者,⛔ 不新增标签不新增链。
510-
- 强制条款②:凡改变契约接受/拒绝行为或扩大公开面的卡一律契约复审档
508+
- 强制条款②:凡放宽接受集或扩大公开面的卡一律契约复审档;拉回已声明契约按常规档
511509
- 条款②判据即代裁的机械边界测试与 `references/lanes/spec.md` 席内分派判据,⛔ 不另抄。
512510
- 负边界:运行时权限/安全行为变更不是条款②,归人工地板安全/权限边界类。
513511
- 条款②只指已发布契约面。
512+
- 删已发布契约文本本就否定的误拒本身是 `no`;该 `no` 须引那段文本,缺引即读作缺申报。
513+
- 引文不符 ⇒ 降为 `yes`;裁决守卫、安全拒绝、失败关闭是刻意拒绝非误拒,删之 `yes`
514+
- 错误码已发布面 = `ERROR_CODE_LEDGER`/`StandardErrorCode`:新码恒 `yes`,无门亦然,漏登即缺口。
515+
- 机械放宽判别门禁落地前,入队前的席内契约复核是方向自述的补偿控制。
514516
- 卡面对条款②的复述仍是条款②;额度耗尽豁免及其 `needs:contract-review` 补偿一并及于它。
515517
- 豁免够不到的地板只有维护者裁决能设。
516518
- 席位档策略:skills 车道外的执行席与分诊席默认判断档会话。
@@ -637,8 +639,6 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
637639
- `--tier` 嫌疑行是提示非裁定;双肢命中任一且派发档位低于契约复审档 ⇒ ⛔ 禁止入队。
638640
- 路径肢 = diff 触及契约面 `packages/spec/src/**`,含 error-code-ledger 与 `*.zod.ts` 契约 schema。
639641
- 声明肢 = 认领评论声明 `Clause-②: yes`,与路径无关;错误的 `no` 是可审计的假申报。
640-
- ⛔ 永不把路径触发读作条款②的完整覆盖:它只盖路径肢,内容肢由声明行承载。
641-
- 档位以 dispatch-gates 常量 `CONTRACT_REVIEW_TIER` 为准,模型升级只改一行一个文件。
642642
- 交付后复核由派发席在席内完成:达档 PM 自审,或派契约复审档复核子任务。
643643
- PASS ⇒ 同席剥标、ready、auto-merge;FAIL ⇒ 补丁轮;⛔ 免复核不放行。
644644
- 真正设计分叉照旧进决策箱,席内复核 ⛔ 不替代维护者裁定。

.claude/skills/pm-dispatch/references/contract-review.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- ⛔ 判据不是提到契约:纯算子清单、拼写、格式不触发。
99
- 受管面人工合并 ⛔ 不替代复核,两道保障叠加。
1010
- `Clause-②: yes | no` 按设计临时:只定派发档位的保守方向,⛔ 非终审。
11+
- 删误拒的 `Clause-②: no``Contract-text:` 引已发布契约文本,双载体同载;缺引即缺申报。
1112
- 真闸门在 PR 或报告时点的档位复核;声明被档位推翻 ⛔ 不作席位过失。
1213
- 机械地板 claim 时可查树:新导出符号或已发布载荷上的新键恒 `yes`,直接锁契约复审档。
1314
- conformance 类 ⛔ 不机械化:填充已声明字段、在两个已发布码之间重选输入类都需判断。
@@ -31,7 +32,6 @@
3132
- ③ 边界旗处置:dev 挂旗与 `open_questions` 逐旗答复或升级。
3233
- 独立性件:契约判断真分叉、或 dev 挂旗待裁 ⇒ 起上下文隔离的复核子代理出第二意见。
3334
- 只喂卡片、既有裁决与 PR 本体,⛔ 不喂派发令与派发席自己的结论;简报写成对抗性。
34-
- 未达档席 ⛔ 不凭自述复核,恒走本路径;子代理裁决按转录核验采信。
3535
- 裁决载独立性对(机读):产出 diff 的身份写 `Implemented-by:`,出裁决的席位写 `Reviewed-by:`
3636
- `mode:subagent` dev 记其分支 `claude/issue-…`(子代理无自有 session);`mode:remote` dev 记 session id。
3737
- 两者同 session ⇒ 报 SELF-REVIEW,⛔ 不作独立复核;两行皆无的历史裁决恒静默。

.claude/skills/pm-dispatch/references/lanes/engine.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
## 常设承诺
1818

1919
- driver conformance 台账只降不升:driver 卡的派发令要求 dev 前后各读一次 covered 与 DEBT。
20-
- 改变契约接受或拒绝行为的卡按条款②处理,判据与档位在 SKILL.md,⛔ 不另抄。
20+
- 放宽接受集或扩大公开面的卡按条款②处理,判据与档位在 SKILL.md,⛔ 不另抄。
2121

2222
## 席内判断
2323

.claude/skills/pm-dispatch/references/lanes/spec.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
- 语义与文本按合法元数据集合变没变分:改动前过校验的输入改动后同判 ⇒ 文本面。
1818
- 文本面的 changeset 恒 patch,默认 sweep-first;否则语义面。
19-
- 任何改变接受或拒绝行为的卡,不论多小,按语义面处理,即条款②。
19+
- 放宽接受集或扩大公开面的卡,不论多小,即条款②;收窄仍是语义面,不触条款②。
2020
- 机器面改围契约的机器,与文本面不交,⛔ 不碰 `packages/spec/src/**/*.zod.ts` 与 strictness 台账。
2121
- 产物随源走:describe 与 JSDoc 改动重生成的 references 产物归触发它的源 PR。
2222
- 生成物门禁重生成提交,⛔ 手改。

0 commit comments

Comments
 (0)