Skip to content

Commit 048467a

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-ulif5z
2 parents 5ceec11 + dbf7e04 commit 048467a

282 files changed

Lines changed: 6115 additions & 9094 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: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/lint': patch
4+
'@objectstack/metadata-core': patch
5+
---
6+
7+
feat(spec)!: retirement kits whose conversion retired at or below protocol 16.x leave the tree; the strict unknown-key refusal names the protocol upgrade guide
8+
9+
Clause-②: no (narrowing)
10+
Metadata-protocol: narrowed
11+
12+
**BREAKING** for a source or a stored `sys_metadata` row still carrying a key whose ≤16.x kit aged out.
13+
14+
- **The rule (ADR-0087, amended 2026-10-26, #25767 ruling D2 = A as scoped by ruling 6434038284).** A retirement kit — the `retiredKey()` tombstone and its D2 conversion with fixtures — lives through the protocol major after the one it retired in and is removed when the next major is cut. At 18 that removes the kits whose conversion retired at or below 16.x; 19 repeats the move one major on. The kit ages out; the migration chain's step stays.
15+
- **Before.** Such a key met a `retiredKey()` tombstone: the parse refused it with the key's own prescription in the error, `os migrate meta --from 16` listed and applied the mechanical edit, and a stored row in that shape was rewritten at read by the ≤16.x conversions.
16+
- **Now.** Where the tombstone sat in a strict container, the key is gone from the shape, so a `strictObject` refuses it as an unknown key. The refusal carries one more sentence, after its fix channels and ahead of its history sentence (`RETIRED_KEY_UPGRADE_GUIDE_SENTENCE`, exported with `PROTOCOL_UPGRADE_GUIDE_URL` from `@objectstack/spec`): it says that keys retired before the previous major are no longer prescribed in the error and names the protocol upgrade guide, `https://objectstack.ai/docs/protocol-upgrade`, where the prescription lives; the CHANGELOG of the release that retired the key carries it too. A tombstone that stays (its container strips unknown keys) ends its error with a sentence that says the edit left `os migrate meta` with the 16.x conversions, to be applied by hand or on a release that still carries the conversion, and links the guide.
17+
- **The chain stays.** `MIGRATION_SUPPORT_FLOOR` stays 16; step 17 and its entry files stay, and `os migrate meta --from 16` still replays 16 → 18, listing every surviving conversion and semantic to-do. Step 17's graduated list drops the ids of the 45 conversions that aged out, so their edits are no longer applied for you.
18+
- **Stored rows.** A `sys_metadata` row still carrying such a spelling is no longer rewritten at read; canonicalize it once with `os migrate meta --stored --apply` on 18.2 or earlier, before moving to 18.3. `app-hidden-to-unpublished` is among the removed conversions, so a pre-split `hidden: true` app row is no longer rewritten to `_unpublished` by any seam, and the artifact door's refusal of it by id is removed as moot (`@objectstack/metadata-core`).
19+
- **What left.** 50 D2 conversions with `retiredAfter` at or below 16.x (5 at 15.0.0, 45 at 16.1.0) and their fixtures; 55 tombstones already `[RETIRED]` at `@objectstack/spec@17.0.0` whose container is strict; 61 `[RETIRED]` lines of the authorable surface and their liveness-ledger rows. 58 same-age tombstones whose container strips unknown keys stay, because deleting one would turn a refusal into a silent strip; the four inline-credential refusals are not retirements and are untouched.
20+
- **The deletion gate.** `check:authorable-surface` proof 1 ages a tombstone one major (was two). A deleted `[RETIRED]` line is dated by an exact `RETIRED_KEYS_BY_MAJOR` row or, new, by the previous major's release tag: a line already `[RETIRED]` on the surface at `@objectstack/spec@<current major − 1>.0.0` was retired before that cut. The tag is immutable, so neither a row a PR writes nor one it deletes moves the clock; where the tag cannot be read the gate dates by the registry alone and says so. No gate was added.
21+
- **Who moves.** No `examples/**`, `ui/` or `hotcrm` source in this repository writes a removed key (the `ui/` hits for the removed names are the console's own same-named props and type mirrors, not readers of the retired spec keys, per the liveness ledger's measurements). A consumer source still carrying one meets the strict refusal and the guide link at `os validate`.
22+
23+
<!-- adr-0087: not-required (no-migration-prescription) the diff registers no retirement: it removes the ≤16.x kits that aged out under the 2026-10-26 ruling (ADR-0087 D2 = A), and nothing here prescribes a rewrite — each removed key's prescription lives in the protocol upgrade guide and in the CHANGELOG of the release that retired it -->
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/components': patch
3+
---
4+
5+
fix(components): the `filter-builder` renderer calls `onChange` with the `FilterGroup` that `FilterBuilderSchema.onChange` declares
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`FilterBuilderSchema.onChange` declares `(filter: FilterGroup) => void`, but the `filter-builder` renderer typed its own `onChange` as `(event: any) => void` and called it with a form-style `{ target: { name, value } }` event. A handler written from the declaration read `.logic` and `.conditions` off that event and got `undefined`, and the `any` kept the compiler from reporting the mismatch. The renderer now takes the schema's own `onChange` type and passes the group it holds, so the declaration is the contract on both sides. No in-repo host bound the event shape. A host that read `event.target.value` reads the argument itself instead.
11+
12+
The builder's own rows also admit the opt-in `exists` / `notExists` operators, which a `FilterGroup` cannot carry. The renderer no longer forwards a host's `extraOperators` to the builder, since `FilterBuilderSchema` declares no such key, so the node never offers them. The `FilterGroup` zod mirror refuses them in an authored `value`. If a stored value that skipped that validation still carries one, the renderer does not call `onChange` and logs a console error naming the operator, instead of handing on a group the declared type does not describe.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
fix(spec)!: every filter door refuses a `{ $now, addDays }` anchor that carries any other key, as `NowAnchorSchema` already declares, instead of running it on the `addDays` reading alone
6+
7+
`NowAnchorSchema` is closed: `{ $now: true, addDays: -30, subDays: 7 }` fails to parse on its own. But the comparand-type walk every filter door runs (`normalizeFilterComparandTypes`, through `FilterConditionSchema` and `parseFilterAST`) judged an anchor by its `addDays` alone, so the extra key was kept, every executor ignored it, and the query answered the `addDays` reading with no word. The walk now refuses an anchor with any key other than `$now` and `addDays`, with the existing `INVALID_FILTER` / 400 envelope, and the refusal names the key: `it carries an undeclared key "subDays" — the anchor takes $now and addDays only (there is no subDays: a negative addDays subtracts)`.
8+
9+
Clause-②: no (narrowing) — every filter door refuses a `$now` anchor carrying a key beyond `$now` / `addDays`, which it ran before. Nothing is widened.
10+
11+
Metadata-protocol: narrowed (a filter saved through `FilterConditionSchema` may no longer carry a key beside `$now` and `addDays` on a now-anchored comparand)
12+
13+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed or renamed: NowAnchorSchema declared exactly $now and addDays since it shipped, and this aligns the walk with that declaration. An extra key never had a meaning at any executor, so there is no rewrite that keeps what the author meant; the author decides, and the refusal names the key. -->
14+
15+
**BREAKING** accept-set narrowing at the filter comparand-type walk (`@objectstack/spec`), served at every filter door.
16+
17+
- **Before.** `engine.find()` / `count()` / `update()` in object or array form, `aggregate()`'s `where`, `aggregations[i].filter`, `having`, and a filter saved through `FilterConditionSchema` all kept `{ $now: true, addDays: N, subDays: M }` or `{ …, note: 'x' }`. The query ran on `addDays` alone.
18+
- **Now.** Each of them answers `INVALID_FILTER` / 400 (a save fails its parse), naming the undeclared key.
19+
- **Unchanged.** A well-formed anchor, `{ $now: true, addDays: <integer | { $field }> }`, and its resolved form (`$now` holding the pinned ISO instant) run as before at every door. The refusals for a missing or malformed `addDays` keep their wording.
20+
- **Who sees it.** An author who wrote an extra key on an anchor. That key was ignored before, so the rows answered were the `addDays` rows.
21+
22+
**The remedy.** Drop the extra key. To move the instant back, give `addDays` a negative value; to read the offset off a column, use `addDays: { $field: 'numeric_column' }`.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/example-showcase': patch
3+
---
4+
5+
Clause-②: no
6+
Metadata-protocol: none
7+
8+
The showcase's record actions now say which record they changed. Mark Done, Recalculate Estimate,
9+
Recalculate Selection, Archive and Submit for Sign-off name the task's title (or the invoice number)
10+
in their success toast, through a template hole over the dispatched record, so "Task marked done."
11+
becomes "PII access review marked done.". When the dispatch carries no title (a list row whose view
12+
does not show the column), each message falls back to the object wording it used before. The zh-CN
13+
bundle entries for the four task actions carry the same hole, so a zh-CN viewer is told the record too.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/service-automation': patch
3+
'@objectstack/runtime': patch
4+
---
5+
6+
fix(service-automation): switching a packaged flow off now records which administrator did it
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
The flow toggle (`POST /api/v1/automation/:name/toggle`) writes a row in the activation ledger (`sys_metadata_activation`). Until now, that row and its audit entry named nobody: `created_by` / `updated_by` on the ledger row and `user_id` / `actor` on the `sys_audit_log` row were null, even when a signed-in administrator made the request. The action and security catalog doors were fixed in the previous release; the flow door reaches the ledger through `IAutomationService.toggleFlow`, which could not carry the caller until its `caller` parameter landed.
12+
13+
- `@objectstack/runtime`: the flow toggle door passes `toggleFlow(name, enabled, { userId })`, the signed-in user's id and nothing else.
14+
- `@objectstack/service-automation`: `AutomationEngine.toggleFlow(name, enabled, caller?)` implements the contract's `caller`. The ledger write runs as that user, elevated and with no positions or permissions, so the engine and the audit plugin record the user's id on both rows. Only `userId` is read from `caller`. With no caller, or a caller without an id, the write stays unattributed, as before. `FlowActivationStore.setActive(row, options?)` takes the same optional `{ context }` as the shared activation store in `@objectstack/core`; a store that ignores it keeps working.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/service-settings': patch
3+
---
4+
5+
fix(service-settings): a secret overwrite's audit row records the previous value's keyed digest as `old_hash` (#26534)
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
Ruling-ref: #26534 triage `6436617436`; the server seat 2 ruling, arm (a)
10+
11+
A secret-valued setting's overwrite used to record `old_hash` null on `sys_setting_audit` / `sys_platform_setting_audit`, though the column declares null only when the key was previously unset. It now records the same keyed digest (`hmac-sha256:…`) that `new_hash` records, taken over the previous value, so an overwrite's `old_hash` equals the previous row's `new_hash` and a rewrite of the same secret reads `old_hash == new_hash`. It stays null when the key was unset or reset, when no keyed digest is available, or when the previous value can no longer be read, and it is never an unkeyed hash of the secret. Only a secret overwrite pays for it: one `sys_secret` read and one decrypt of the previous value, before the rotated-away secret is reaped.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/metadata-core': patch
3+
---
4+
5+
fix(metadata-core): an exclusive protocol comparator (`'>18'`, `'<18'`) names the major it admits in the `OS_PROTOCOL_INCOMPATIBLE` refusal
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The refusal for a package whose `engines.protocol` excludes this runtime names a target major (`targetMajor` in the diagnostic and in `error.details`) and, for a package behind the runtime, `objectstack migrate meta --from N`. It used to find the target by peeling every leading operator off the range as if it were `^`, so on a protocol-18 runtime both `'>18'` (19 and up) and `'<18'` (17 and below) answered `migrate meta --from 18`, a migration from the runtime's own major.
11+
12+
Now the target is read from the majors the range admits, with the same grammar that decides whether it admits the runtime:
13+
14+
- **`'>18'` on an 18 runtime** targets 19. The package is ahead, so the answer is the upgrade remedy from the previous release: "Upgrade the runtime to protocol 19", and no `migrateCommand`. On a 17 runtime it now names protocol 19 too, where it used to name 18, which `'>18'` does not admit.
15+
- **`'<18'` and `'<18.0.0'`** target 17: `migrate meta --from 17`.
16+
- **A compound range** targets its floor in either order: `'<18 >=15'` targets 15 (it named 18), and `'>15 <18'` targets 16 (it named 15).
17+
- **A `||` union** still targets its lowest alternative, now read the same way: `'<17 || >19'` targets 16.
18+
- **A range that admits no major** (`'>=19 <15'`, `'<0'`) has `targetMajor: null` and the bare `objectstack migrate meta`.
19+
20+
`^`, `~`, `>=`, `<=`, wildcards, exact versions, hyphen ranges and partial bounds (`'>18.1'`, `'<18.5'`) answer as before. No range changes between admitted and refused: only the refusal's target and prescription change.
21+
22+
Two CLI readers of `targetMajor` follow: `objectstack migrate meta` now rewrites a `'<18'` range to `'^18'` when it migrates a stack to protocol 18 (it used to leave it for the load to refuse), and the protocol-version advisory now speaks for such a range.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
---
4+
5+
fix(plugin-security): the seven built-in positions are served in Chinese, Japanese and Spanish
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Setup → Positions lists the platform's built-in positions (Platform Admin, Organization Owner, Organization Admin, Organization Member, Organization External Member, Everyone, Guest) beside the app's own. Under zh-CN the app's positions read in Chinese while these seven stayed English, because this plugin's translation bundle carried no `positions` group. The bundle now carries `positions.<name>.label` and `.description` for each built-in position in zh-CN, ja-JP and es-ES, the locales its permission-set translations cover, so `GET /api/v1/meta/position` serves them in the requested locale. English is unchanged, and nothing else of a position changes.

‎.claude/skills/checklist-test/SKILL.md‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ Tier 1 批量清掉,剩下的预算才对得起 Tier 2。
102102
`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --tier <paths>` 的输出、
103103
⛔ 不凭记忆(floor sonnet · default opus · ceiling fable);每个给:该项 JSON、RUNNER.md、dogfood 技
104104
能、自己的端口/DB、结果不进仓规则(§4)。
105+
- **QA 运行不是车道卡。** runner(子代理或云卡)由发起本轮的会话直接派,⛔ 不走车道认领
106+
与座位贴;pm-dispatch 只管写入门(§4)与安全修复派卡(Guardrails)。
105107
- **没有子代理工具时,顺序跑 —— 并在运行记录里声明这一轮是顺序执行的。** 规则与它
106108
的论证住在 `checklist-author` 技能(维护者所定,一处成文);⛔ 不留第二份拷贝。
107109
- 忠实执行每项的 `steps`,按各自声明的 `oracle` 判定每条 `acceptance` 与 `negative`
@@ -124,7 +126,10 @@ expected-fail 探针 —— 永不在真实缺陷上把条款打成绿。
124126
**issue 是纯文本 —— 永不放图。** 截图只为让你和子代理**现场**得出判定;它们是判断辅
125127
助,随运行环境一起丢弃。持久报告需要的是**复现规则,不是图片**。
126128

127-
经 REST 代理 `POST .../issues` 立单(`Content-Type: application/json`;⛔ 永不 MCP `issue_write`,锁 1 已拒):
129+
GitHub 写入只走 pm-dispatch 写入门:仓根直调 `scripts/pm/issue-create.mjs`、`post-stamped.mjs`、
130+
`close-cards.mjs`,批量先 `write-pace.mjs --announce-batch N`,云容器内自走 fleet relay;⛔ 永不 MCP
131+
`issue_write`(锁 1 已拒)或裸 `curl`。写入被会话权限拦下 ⛔ 不绕行:待发文本落文件、终报给
132+
出路径与正文,放行后照序补发 —— 写入未落地即未收口。记录这样写:
128133

129134
- **标题** —— `QA run · <selector> (<已判定>/<总数>) · <sha8> · <date> · <counts>`:唯一
130135
形状、严格解析、分数**强制**;语法与退役写法见 RUNNER.md「The record title」。
@@ -229,10 +234,13 @@ QA-source: #运行记录号 · area.item · 条款
229234
- **不伪造覆盖。** 缺 fixture → `blocked(fixture)` 带理由;console 没构建 → 构建它
230235
或记 `blocked(environment)`;证到一半的项是 `partial`,不是 `pass`。带证据的
231236
blocked 判定是一次成功运行;伪造的 pass 不是。
237+
- **每个产品问题同一轮经 §4 立 issue。** `fail` 条款与条款外探测同样,⛔ 不只留在运行记
238+
录或聊天;认证 / 授权类按下一条。
232239
- **认证 / 授权漏洞的复现,永不发布到 GitHub 的任何地方** —— 不进运行 issue,不进跟踪
233-
卡,不进评论。记下项 id、条款与 `detail withheld pending maintainer`,复现留在会话里,本轮以该报
234-
告终局,⛔ 不等维护者。⚠ 这与 RUNNER 规则 2 是**同一条规则写在两处**,⛔ 两边都不是对
235-
另一边的优先级主张;完整论证与它的维护者裁定在 RUNNER。
240+
卡,不进评论。卡只载项 id、条款与 `detail withheld pending maintainer`,复现留在会话里;修复直
241+
接经 pm-dispatch 派卡:席内 `mode:subagent` 派 `os-dev`,复现只进派发令,⛔ 不等维护者;runner 无
242+
派发通道时由派它的会话接手。⚠ 这与 RUNNER 规则 2 是**同一条规则写在两处**,⛔ 两边
243+
都不是对另一边的优先级主张;完整论证与它的维护者裁定在 RUNNER。
236244
- **不把 blocked 项当可运行的跑** —— 解析器隐藏它们正为此。
237245
- **一个选择器、一次运行、一张 issue;验证重测不立 issue,证据进 PR 正文(§5)。** release
238246
sweep 把 `since:vN` 与 `priority:P0` 作为分开的两轮跑 → 两张 issue,不要糊在一起。

0 commit comments

Comments
 (0)