Skip to content

Commit e2b7d13

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-18897-echo-satisfied-controls-sweep
2 parents d0c45f1 + b1d3945 commit e2b7d13

52 files changed

Lines changed: 1038 additions & 239207 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.changeset/16045-spec-declaration-text-snapshots.md‎

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
**BREAKING** for authored metadata — `ObjectSchema.fields` refuses a key named `__proto__`, `constructor` or `prototype`, and `AssignmentConfigSchema.assignments` (the `assignment` flow node's variable map) refuses a key named `__proto__` — both refused with a named, located error at parse time, rather than silently accepted and then silently mishandled (objectstack#17852, objectstack#18847).
6+
7+
## Why
8+
9+
zod's `z.record()` skips a `__proto__` own key entirely, above its own key schema — the record parser's `if (key === "__proto__") continue;` runs before `def.keyType._zod.run`, so no key grammar (a regex, `.refine()`, `.superRefine()`, even a key schema that rejects every string) can ever see that key. A document whose `fields` (or `assignments`) carried a `__proto__` own key — which `JSON.parse` produces routinely — used to parse as SUCCESS with that key silently missing from the output: the validator accepted a document and handed back a *different* document. `os build` writes the release artifact from that returned document, so the failure shape is success, silent, and irreversible into the shipped artifact.
10+
11+
Two independent mechanisms close this, one per name class, because they are not reachable the same way:
12+
13+
- `__proto__` is refused by a **pre-parse guard** that reads the raw input's own keys before the record ever parses, at both `ObjectSchema.fields` and `AssignmentConfigSchema.assignments`.
14+
- `constructor` and `prototype` — which, unlike `__proto__`, DO reach the key schema unskipped — are refused by `ObjectSchema.fields`' own key grammar (they were ordinary lowercase words its regex already admitted). They are **not** refused at `AssignmentConfigSchema.assignments`: that slot's key type carries no grammar at all (`z.string().min(1)`), both names are legal flow-VARIABLE names measured to survive parse intact today, and no ruling narrows that slot's accept set for them — only its `__proto__` half moves.
15+
16+
Measured: zero authored use of any of the three names as a `fields` key or an `assignments` variable name, across this repo, `examples/` and `objectui`.
17+
18+
## Known gap, left open on purpose
19+
20+
The guard runs at parse time only. It does not project into the published JSON Schema (`packages/spec/json-schema/**`) — the general gap that closes is tracked separately (objectstack#18670) and stays open after this change.
21+
22+
Clause-②: yes (narrowing)
23+
24+
<!-- adr-0087: not-required (no-migration-prescription) zero authored use of `__proto__`, `constructor` or `prototype` as a `fields` key or an `assignments` variable name across this repo, examples/ and objectui — nobody has anything to rewrite, so there is no prescription to give. -->
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
"@objectstack/metadata-protocol": patch
3+
---
4+
5+
The in-process install primitive honours `enableOnInstall` instead of ignoring it (#19277).
6+
7+
`InstallPackageRequestSchema.enableOnInstall` (`kernel/package-registry.zod.ts`) is the request contract of `ObjectStackProtocol.installPackage` / `MetadataProtocol.installPackage`. The implementation read `request.manifest` and `request.settings` and nothing else, so a caller that asked for `enableOnInstall: false` got an ENABLED install — no refusal, no warning, no effect. That is a declared option the runtime did not deliver, which ADR-0049 (enforce-or-remove) and Prime Directive #10 refuse outright. Ruling batch #153 item 5 letter 1 (#18605) kept this declaration as a COPY of the HTTP request key with the same meaning, so the disposition is enforce, not retire.
8+
9+
The primitive now applies the same rule the HTTP door applies (maintainer ruling batch #157 item 5 letter C, 「缺省 = 保持,有旗 = 设置」), through the same registry verbs `PATCH /packages/:id/enable` and `PATCH /packages/:id/disable` use:
10+
11+
```text
12+
enableOnInstall: true ⇒ enablePackage — clears a disable, including a boot-seeded one
13+
enableOnInstall: false ⇒ disablePackage — the row and its `status` both move
14+
enableOnInstall absent ⇒ no lifecycle call at all; the row the registry returned stands
15+
```
16+
17+
Absent is a third state, not a synonym for `true`: on a FRESH id the registry still lands the package enabled (the declared default), and on an EXISTING row it preserves whatever that row says (#18877). A non-boolean value is read as absent rather than coerced.
18+
19+
⚠️ **What this seam does not write, stated rather than implied.** The runtime's durable disabled-package file is keyed by environment (`setPackageDisabled(environmentId, id, disabled)`, `@objectstack/runtime`), and an `InstallPackageRequest` carries no environment, so that record cannot be written from here — the HTTP door owns that half and writes it from the row it returned. `enableOnInstall` through the in-process primitive therefore moves the registry row, which is what every in-process reader serves from, for the life of the process; a caller that needs the choice replayed after a restart goes through the door that owns the durable record.
20+
21+
No behaviour changes for any caller on the tree: measured across `packages/**`, `examples/**` and `apps/**`, no existing call site sets the key — the HTTP door deliberately calls `installPackage({ manifest, settings })` and performs the flip itself, and `duplicatePackage` passes `{ manifest }` alone. The change is observable only to a caller that sets the key, which until now got silence.
22+
23+
Clause-②: no

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
211211
- transfer 不可用时重建:出处头 + 裸 `#N` 改全名 + 关源单为 moved。
212212
- 缝卡收窄为真协调卡:留 objectstack 带 `repo:*`,正文点名读者(哪个座位、哪一步)。
213213
- 决策收件箱按仓:平台在 objectstack / objectui,元数据项目在本仓;在飞卡 ⛔ 不中途转仓。
214-
- 规则 2:跨仓 feature 永不是一次派发:父单 + 每仓一 sub-issue,spec/后端先行。
214+
- 规则 2:跨仓 feature 或 objectui 消费的 `Seam:` 卡恒由分诊立父单 + 每仓一子单,spec/后端先行。
215215
- 下游带 `Blocked-by: <owner/repo>#<n>`;`Blocked-by` 未关闭/未合并的不派发,对 GitHub 现验。
216216
- 被链接或同父的两单永不同批。
217217
- pin 滞后是盲区:本仓 pin 是否已覆盖该 commit 是第二读数,派发前用 REST `compare` 核祖先。
@@ -231,16 +231,16 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
231231
- 认领即跟到 MERGED:派发后发现的跨车道面(含 spec)不移卡,认领席借契约复审档隔离复核。
232232
- 复核记录带 `Implemented-by:`/`Reviewed-by:`,自审机读可见;`pm:retriage` 改路由只对未派发卡。
233233
- 转移落对方队列:目标仓立单带 `pm:queue`、出处行与一行可执行判据,依赖用 `Blocked-by:`。
234-
- 新 `packages/spec` 工作恒由 `domain:spec` 席收口,不论谁需要它;已派发卡 ⛔ 不因触 spec 转席。
235234
- 任何跨座位请求都是工作(要读数、要开卡、要授权):一律立卡进目标车道队列。
236235
- ⛔ 座位贴敲门或裁决评论永不作跨座位请求的唯一载体;评论是加速器不是记录。
237236
- 等待方同一笔把自卡翻 `pm:blocked` + `Blocked-by:` 指向请求卡;⛔ 不设新标签新 sweep。
238237
- 目标仓不可达是读数缺口,不是落点:由可达席在目标仓立卡,此前请求记座位贴或协调卡。
239238

240239
## 域车道
241240

242-
- 锚定规则:每个包恰好属于一个域;issue 的 `domain:*` = 修复落地的那个包所属的域。
241+
- 锚定规则:每个包恰属一个域;`domain:*` = 修复落地包的域;`Seam:` 卡归 spec 席,默认纵向派发。
243242
- 唯一例外 `packages/lint` 等与 spec 相交的 devx 面:围着 spec 契约转的归 `domain:spec`,余留 devx。
243+
- `Seam:` 卡认领申报两端;验收 = 消费端读该键(活性账本行离开 `planned`)或键随账本行退役。
244244
- 域由分诊读代码判定,⛔ 绝不从 issue 标题的词汇猜域;说不出修复碰哪个文件就还不可标。
245245

246246
| 标签 | 包家族 |
@@ -438,7 +438,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
438438
- 第 N 单派发前读 `scripts/pm/os-verify-lock.sh --status`:到达深度 ≥ `LOCK_DEPTH_HOLD`(= 2)即等。
439439
- 到达深度 = `queue N:` 行数 + 1(待派 dev 的运行算作到达);`state:` holder 与 `parked` 行不计。
440440
- 有效上限是锁宽的函数,⛔ 不是第二个 `batch`;`priority:p0` 可超 `batch`,⛔ 不越过深度等待。
441-
- 同文件单跨轮硬串行;延后不是搁置,被延后那一刻就把已知的坑记到该 issue 上。
441+
- 同区域单跨轮硬串行;延后不是搁置,被延后那一刻就把已知的坑记到该 issue 上。
442442
- 家族派发是范围澄清不是豁免:一个 dev 有意覆盖 N 张同区域已裁卡,可折叠为一次派发。
443443
- 折叠准入五门全过才可折:① 同缺陷形态同修法(⛔ 不是同关键词/同子系统)。
444444
- ② 同包/区域(一 worktree、一 changeset、一队列位)。

‎.claude/skills/pm-dispatch/references/core-rules.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@
5555
- 多车道仓的域标签、type、定级与查重只由中央分诊生产,单车道仓自理三务不产域标签。
5656
- 跨仓查重与 shadow 检查恒归中央 ⛔ 不下放;决策卡入本仓收件箱,在飞卡 ⛔ 不中途转仓。
5757
- issue 住在修复落地的仓,抽掉 objectstack 仍成立的当场转仓;缝卡须带仓标签并点名读者。
58-
- 跨仓 feature 拆成父单加每仓一张子单,下游带 `Blocked-by:`,上游未关闭 ⛔ 不派发。
58+
- 跨仓 feature 或跨仓 `Seam:` 卡拆成父单加每仓子单,下游带 `Blocked-by:`,上游未关 ⛔ 不派发。
5959
- pin 消费的仓派发前核 pin 是否已覆盖那个提交,发布包消费的仓判据是消费方可安装。
6060
- 上游未发版时转 hold ⛔ 不回队列;已验收产物流向另一仓由接受席立即立后续单。
6161
- 跨座位请求即立卡进目标车道,自卡翻 `pm:blocked`;阻塞项无主由被挡席认领,在飞即等。
6262
- 认领席跟到 MERGED,派发后的跨车道面不移卡、借隔离复核;新 spec 工作由 spec 席收口。
6363
## 域车道
64-
- 每个包恰好属于一个域(唯一例外见 SKILL.md 锚定规则),域标签等于修复落地的包所属的域。
64+
- 每个包恰好属于一个域,域标签 = 修复落地包的域;例外与 `Seam:` 卡见 SKILL.md 锚定规则。
6565
- 域由分诊读代码判定,⛔ 绝不从 issue 标题的词汇猜域;说不出修复碰哪个文件就还不可标。
6666
- 域到包家族的对照表住 SKILL.md,增删一个域标签必须同批改那张表。
6767
- 域标签只由分诊席产出,例外仅在飞卡衍生 sub-issue 承父卡域;无标签的卡 ⛔ 不得认领。
@@ -102,7 +102,7 @@
102102
- 每轮巡检先读半状态巡查锚上点名本车道的行并逐行处置,未处置 ⛔ 不开新派发。
103103
- 候选整车道一次读全再本地求交,每张读全文与全部评论并把裁决逐字引入派发词。
104104
- 派发前做前提过时检查,动作面、卡引用面与工作项面三面都对树核验。
105-
- 同批独立性按文件面不相交判,⛔ 不按包;同文件硬串行,冲突交合并队列仲裁 ⛔ 不手排。
105+
- 同批独立性按文件面不相交判,⛔ 不按包;同区域硬串行,冲突交合并队列仲裁 ⛔ 不手排。
106106
- 家族派发须过五门:同缺陷同修法、同包区域、成员皆已裁、逐成员可核、点名排除清单。
107107
- 两张以上排队卡共享热文件时,必须以五门为判据显式回答折叠还是串行。
108108
- 取卡全序:插队卡、有下游依赖者的卡、板上项、p1、p2、p3、无级;同级缺陷卡先再卡龄。

‎.gitattributes‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,10 @@ packages/spec/authorable-surface.base.json merge=os-regen
144144
packages/spec/authorable-defaults/** merge=os-regen
145145
packages/spec/json-schema.manifest/** merge=os-regen
146146
packages/spec/api-surface/** merge=os-regen
147-
packages/spec/api-surface-declarations/** merge=os-regen
148147
packages/spec/src/meta-spelling/meta-url-data.generated.ts merge=os-regen
149148
packages/spec/export-origins/** merge=os-regen
150149
packages/spec/declaration-map/** merge=os-regen
150+
packages/spec/api-surface-signatures.json merge=os-regen
151151
docs/protocol-upgrade-guide.md merge=os-regen
152152
docs/audits/2026-07-unknown-key-strictness-ledger.counts.md merge=os-regen
153153
content/docs/references/** merge=os-regen

‎.github/workflows/lint.yml‎

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5173,16 +5173,11 @@ jobs:
51735173

51745174
# The authorable KEY surface — what a metadata author may write, which for
51755175
# this platform is the third-party API. `api-surface/` records exported
5176-
# names only, so it does not see a key added to or removed from a schema;
5177-
# the shape sibling that used to sit beside it (`api-surface-signatures.json`,
5178-
# retired at #16045) hashed factory types as TypeScript PRINTS them — a
5179-
# reference, never structurally expanded — so it did not see one either.
5180-
# #3883 removed three authorable keys with every witness green; #3733 did it
5181-
# by accident. ADR-0059 §5 deferred this gate until a narrowing actually
5182-
# slipped both — it has. `check:api-surface-declarations` (consumer-gates
5183-
# lane) now records the declaration TEXT of every export, which DOES move on
5184-
# such a key; this gate stays the authority on the AUTHORABLE key set, which
5185-
# is a different question from the declared TypeScript shape.
5176+
# names and `api-surface-signatures.json` hashes factory types as TypeScript
5177+
# PRINTS them (a reference, never structurally expanded), so neither sees a
5178+
# key added to or removed from a schema. #3883 removed three authorable keys
5179+
# with every witness green; #3733 did it by accident. ADR-0059 §5 deferred
5180+
# this gate until a narrowing actually slipped both — it has.
51865181
#
51875182
# ⚠ ORDER: this step must stay ABOVE the `check:docs` step below. Its
51885183
# `--check` run of scripts/build-schemas.ts writes the gitignored
@@ -6104,29 +6099,6 @@ jobs:
61046099
- name: Check @objectstack/spec public API surface
61056100
run: pnpm --filter @objectstack/spec run check:api-surface
61066101

6107-
# [#16045] The SHAPE half of the same surface, and the step the card above
6108-
# exists for: `api-surface/` pins 5336 `name (kind)` rows and a signature
6109-
# change, a renamed interface field and a dropped union member move NONE of
6110-
# them, so 99.5% of the pinned surface could not go red on a breaking shape
6111-
# change to a ratified public type. The declaration-text snapshot records
6112-
# what the packed `.d.ts` actually declares for every export, per entry
6113-
# point. Ruled at #16045 (director batch #60, maintainer 「同意」): text and
6114-
# ⛔ NOT a hash, because a red hash gets accepted rather than investigated
6115-
# and a readable diff is what makes contract review a guard.
6116-
#
6117-
# WHY THIS LANE. It resolves each entry point through the `exports` map to
6118-
# the BUILT `.d.ts` — the declarations a consumer installs — so it is
6119-
# build-dependent and sits after the two build steps above with its family
6120-
# (`check:api-surface`, `check:published-readme-exports`). A missing or
6121-
# stale dist is a HARD REFUSAL in both of the script's modes, never a skip:
6122-
# a build-dependent gate that silently reads nothing reports "not measured"
6123-
# as if it were "measured and clean" (#4690).
6124-
#
6125-
# It adds no required context: a step in an existing lane, so no open PR
6126-
# waits on a check whose name no head has ever reported (#9325).
6127-
- name: Check @objectstack/spec declaration text (the shape half)
6128-
run: pnpm --filter @objectstack/spec run check:api-surface-declarations
6129-
61306102
# [#11350] Consumer-shaped declaration-emit pin against the BUILT root
61316103
# entry (an un-annotated `export default defineStack(...)` must compile
61326104
# with `declaration: true` — the TS2883 class). The pin is environment-

0 commit comments

Comments
 (0)