Skip to content

Commit 6471f55

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-19986-explain-read-scope
2 parents 568d5cd + bfa23a8 commit 6471f55

50 files changed

Lines changed: 782 additions & 3517 deletions

Some content is hidden

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

‎.changeset/19846-automation-caller-param-keys.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ A screen whose fields the run's caller already supplied continues without pausin
1212

1313
**What the key says.** The keys of the caller's own `params`, recorded before the door seeds anything. An empty array means the caller supplied nothing; an absent key means the producer does not say.
1414

15-
**Who fills it.** The action door (`dispatchFlowAction`: `POST /api/v1/actions/...` and MCP `run_action`) and the trigger door (`buildAutomationContext`: `POST /api/v1/automation/:name/trigger`, the legacy `POST /api/v1/automation/trigger/:name`, and a declarative `type: 'flow'` endpoint). Record-change, schedule, time-relative and webhook triggers, and code calling `execute` directly, leave it absent. `subflow` and `map` nodes drop the parent's list from the child run's context, because it describes the parent's bag.
15+
**Who fills it.** The action door (`dispatchFlowAction`: `POST /api/v1/actions/...` and MCP `run_action`) and the trigger door (`buildAutomationContext`: `POST /api/v1/automation/:name/trigger`, the legacy `POST /api/v1/automation/trigger/:name`, and a declarative `type: 'flow'` endpoint). Record-change, time-relative and webhook triggers, and code calling `execute` directly, leave it absent; the schedule trigger, whose run has no caller, states an empty list (#19900). `subflow` and `map` nodes drop the parent's list from the child run's context, because it describes the parent's bag.
1616

1717
**What the screen does with it.** When the key is present, a field is caller-supplied when its name is in the list and `params` holds a value for it; the inference is not consulted. A present value that is not an array names nothing, so the screen pauses. When the key is absent, the inference from #15787 applies unchanged.
1818

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/console': patch
3+
---
4+
5+
The prebuilt Console dist now ships `dist/sdui.manifest.json`: the ADR-0080 public-tier
6+
component manifest of the objectui registry at the pinned commit.
7+
8+
It is the same file the framework repository tracks at its root and gates on every pull
9+
request. `scripts/build-console.sh` copies it in, and one producer writes it:
10+
`scripts/gen-sdui-manifest-node.mjs`, which reads objectui's built tree at the pin. No
11+
earlier published `@objectstack/console` carried this file. The RC cut used to write a
12+
browser-dumped copy into `dist/`, but the release build replaced `dist/` before packing, so
13+
none reached a tarball (17.0.0, 17.3.0 and 17.4.0 each list 0 matches). That browser dump is
14+
retired. It was byte-identical to the tracked file over the same built tree.
15+
16+
For now the file is only present in the tarball. This package's `exports` map exposes
17+
`./package.json` and nothing else, so resolving `@objectstack/console/dist/sdui.manifest.json`
18+
through `exports` fails with `ERR_PACKAGE_PATH_NOT_EXPORTED`. Anything that resolves through
19+
`exports` cannot read the file yet. That includes the CLI's JSX-page manifest fallback, which
20+
catches the error and keeps parse-level validation, as before.

‎.claude/hooks/guard-process-kill.selftest.sh‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ echo "== the teardown AGENTS.md itself prescribes: the port is one YOU picked ==
124124
expect allow 'kill $(lsof -ti tcp:38421)'
125125
expect allow 'kill $(lsof -ti tcp:3000) 2>/dev/null'
126126

127-
echo "== pgrep -P / -s select by a handle the caller owns — live in two tracked scripts =="
128-
# scripts/publish-smoke.sh kill_tree() and scripts/gen-sdui-manifest.sh sdui_live_pids().
127+
echo "== pgrep -P / -s select by a handle the caller owns — legitimate teardown shapes =="
128+
# scripts/publish-smoke.sh kill_tree() uses -P; -s was the retired gen-sdui-manifest.sh's.
129129
# A rule that reddened these would be routed around within the hour.
130130
expect allow 'for child in $(pgrep -P "$pid"); do kill "$child"; done'
131131
expect allow 'pgrep -s "$leader" | xargs kill'

‎.claude/hooks/guard-process-kill.sh‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@
5353
# you picked, not a name
5454
# pgrep -P "$pid" / pgrep -s "$leader" — even piped into a kill: the selector is a parent
5555
# or session handle the caller owns, and both are
56-
# live in scripts/publish-smoke.sh and
57-
# scripts/gen-sdui-manifest.sh
56+
# legitimate teardown shapes (scripts/publish-smoke.sh
57+
# uses the first)
5858
# pkill -P "$pid" / pkill -s "$sid" — the same selectors on pkill, with NO pattern
5959
# operand. Add a pattern and it is blocked again.
6060
# pgrep -f foo / ps aux | grep node — a READ. Nothing dies; look all you like. But a
@@ -243,7 +243,7 @@ check_killall() {
243243
# --- does this segment select processes by NAME? (pgrep with a bare pattern operand) -----
244244
# Returns 0 = yes, this is a name-pattern selector. `pgrep -s "$leader"` and
245245
# `pgrep -P "$pid"` consume their argument and leave no operand behind, so they answer 1 —
246-
# which is what keeps scripts/publish-smoke.sh and scripts/gen-sdui-manifest.sh legal.
246+
# which is what keeps scripts/publish-smoke.sh's `pgrep -P` teardown legal.
247247
is_name_selector() {
248248
[ "$HEAD" = "pgrep" ] || return 1
249249
local n=${#W[@]} i=$((HEAD_I + 1))

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
9191
- 状态只经 GitHub 标签、assignee、正文行与 `pm:seat` 座位贴读写;循环须能从全新会话恢复。
9292
- 用户账号仅三用:assignee、授权批准、维护者亲手;批准账号永不跑席位或作其关联用户。
9393
- 内容写只走 REST 代理,⛔ 无 MCP 写;`user.login` 记令牌不记席位,归属 = 文本里的 session ID。
94-
- 写侧恒走 `claude[bot]` ⛔ 不走用户令牌;节奏 ≥3 秒/笔、每账号每小时 ≤40 笔,429 即停写。
94+
- 写侧恒为 `objectstack-fleet[bot]`(AGENTS.md);节奏 ≥3 秒/笔、每账号每小时 ≤40 笔,429 即停写。
9595
- GitHub 上一切新内容用英文;中文只留四通道(维护者速读、轮报、裁决引文、四维分析)。
9696
- 裁决引文照抄不译;四维中文只管新记录,存量英文块 ⛔ 不迁移;存量中文 ⛔ 不追溯改写。
9797
- 一 PM 恰管一车道,一车道由一组座位管,一席一贴;PM Y 管什么恰一个答案。

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
- 写节奏 ⛔ 非可选:REST 写(评论/标签/关单/立卡)间隔 ≥3 秒,每账号每小时 ≤40 笔。
2929
- 收到 429 或次级限流 ⇒ 该席本 fire 停写,并把停写记在座位贴上。
3030
- 关单 ⛔ 永不由脚本循环连发:须维护者确认的分组,且逐笔之间留间隔。
31-
- 立卡、开 PR 与评论恒走 App 安装身份(`claude[bot]`),⛔ 不走席位用户令牌。
31+
- 立卡、开 PR 与评论恒经 `scripts/pm/` 为 `objectstack-fleet[bot]`(AGENTS.md),⛔ 不走席位用户令牌。
3232
- 用户类写绑作者:封号则其卡与评论按作者 404,故一个 fire 的首写回读 `user.type`。
3333
- 读到 `User` ⇒ 记座位贴、本 fire ⛔ 不立新卡;认领仍靠 assignee,署名仍是文本的 session ID。
3434

‎.github/workflows/cut-rc.yml‎

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -405,42 +405,11 @@ jobs:
405405
- name: Verify the Console dist stamp matches the pin
406406
run: pnpm check:console-sha
407407

408-
# ────────────────────────────────────────────────────────────────────────
409-
# ADR-0082 D4 declaration-parity ratchet — the mandatory SECOND HALF of
410-
# every pin move (#5960), run here against the pin as COMMITTED.
411-
#
412-
# ⚠️ Since #10134 this lane no longer moves the pin, so this is no longer
413-
# "the second half of a move this run performed" — the move, and the
414-
# ratchet that belongs to it, happen on the manual bump PR. Kept anyway,
415-
# and deliberately: the ratchet is an on-demand gate by decision (ADR-0082
416-
# addendum), which means "unrun" is its live failure mode, and a bump PR
417-
# that skipped it leaves a divergence that reaches a release unnoticed.
418-
# This is the last place before publish that can still say so. It writes to
419-
# a gitignored path and asserts against the committed baseline, so it adds
420-
# no file to the version commit; on a pin that already ratcheted it simply
421-
# passes.
422-
#
423-
# Installing a Playwright browser is fine HERE. The #5960 ruling that keeps
424-
# the ratchet off CI is about not putting an objectui build plus a browser
425-
# download on every matching PR; this workflow runs a handful of times a
426-
# month, on purpose, and has already built the console two steps above.
427-
# ────────────────────────────────────────────────────────────────────────
428-
- name: Install a Playwright browser for the manifest dump
429-
run: |
430-
# The dump runs INSIDE objectui's build tree (gen-sdui-manifest.sh does
431-
# `pushd $BUILD_ROOT` and drives objectui's own playwright), so the
432-
# browser must be installed against THAT workspace. `pnpm exec
433-
# playwright` from the framework root resolves nothing — playwright is
434-
# not a framework dependency.
435-
BUILD_ROOT=".cache/objectui-${OBJECTUI_SHA:0:12}"
436-
if [ ! -d "$BUILD_ROOT" ]; then
437-
echo "::error::expected objectui build tree at ${BUILD_ROOT} (created by 'pnpm objectui:build'). Cannot install the browser the ratchet needs."
438-
exit 1
439-
fi
440-
pnpm --dir "$BUILD_ROOT" exec playwright install chromium-headless-shell
441-
442-
- name: 'Declaration-parity ratchet at the committed pin (ADR-0082 D4)'
443-
run: pnpm sdui:manifest
408+
# No SDUI manifest step follows. The console dist built above already
409+
# carries the TRACKED repo-root sdui.manifest.json (build-console.sh copies
410+
# it), whose freshness against the pin the required lint job holds on every
411+
# PR; the ADR-0082 D4 declaration-parity ratchet that used to run here, and
412+
# the Playwright browser it needed, are retired.
444413

445414
# ────────────────────────────────────────────────────────────────────────
446415
# PRE-VERSION GATES. Every one of these reads `.changeset/*`, so all of them

‎.github/workflows/lint.yml‎

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,8 @@ jobs:
902902
# is the thing that notices. Offline record checks only (existence, shape,
903903
# sha256 vs the provenance record, record pin vs `.objectui-sha`): the
904904
# regenerate-and-compare half runs where a regeneration can (the Node
905-
# generator needs the published @object-ui set — see
905+
# generator reads objectui's BUILT tree at the pin, which only
906+
# `pnpm objectui:build` materialises — see
906907
# scripts/gen-sdui-manifest-node.mjs), and a pin bump goes red HERE until
907908
# it has been re-run — the same moment the lockstep step above forces the
908909
# parser parity re-verification, with an objectui checkout guaranteed in
@@ -6712,18 +6713,6 @@ jobs:
67126713
- name: Check docs YAML examples parse against live spec schemas
67136714
run: pnpm --filter @objectstack/spec run check:yaml-examples
67146715

6715-
# ADR-0082 D4's spec↔registry react-block declaration-parity ratchet, fed
6716-
# by the checked-in repo-root manifest (#12924 execution point 1: the same
6717-
# artefact resolves this gate's EXTERNAL_INPUT_REQUIRED standing — the
6718-
# input it always lacked now lives in the tree, held fresh by the
6719-
# check-sdui-manifest step in the lint job). Baseline-ratcheted: only a
6720-
# divergence NEW since packages/spec/react-declaration-parity.baseline.json
6721-
# reds, so wiring it per-PR promotes the cut-rc-only run without
6722-
# manufacturing a standing red. Reads spec src/ through tsx like its
6723-
# neighbours — no dist. ~5 s.
6724-
- name: Ratchet spec-vs-registry react declaration parity against the checked-in manifest
6725-
run: MANIFEST="$PWD/sdui.manifest.json" pnpm --filter @objectstack/spec check:react-declaration-parity --baseline react-declaration-parity.baseline.json --strict
6726-
67276716
# Same anti-drift class as the gates above, for the generated translation
67286717
# bundles in packages/platform-objects/src/apps/translations/. Nothing
67296718
# regenerated them either, so they rotted three ways at once (#3670):

‎AGENTS.md‎

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,11 @@ pull its build into `packages/console/`.
155155
Other scripts: `objectui:bump` (pull only), `objectui:build`, `objectui:clean`. ⛔ Never hand-edit
156156
`packages/console/dist/` or `.cache/objectui-*/` — regenerated.
157157

158-
**Moving the pin has a second half: regenerate the committed manifest** — `node scripts/gen-sdui-manifest-node.mjs
159-
--objectui-version {the @object-ui version the new pin ships}`; `scripts/check-sdui-manifest.mjs` reds until you do.
160-
ADR-0082 D4's spec↔registry declaration-parity ratchet reads that tracked artefact, so it gates **every PR**, not a
161-
pin bump alone. `pnpm sdui:manifest` is the separate browser dump of objectui's own registry (needs Playwright
162-
chromium); `objectui:bump` and `objectui:refresh` print it. Full procedure: `docs/releases-maintenance.md`.
158+
**Moving the pin has a second half: regenerate the committed manifest** — `pnpm objectui:build`, then
159+
`node scripts/gen-sdui-manifest-node.mjs`, which reads objectui's BUILT tree at the pin (never an npm install, whose
160+
version string names an older commit) and takes no arguments; `scripts/check-sdui-manifest.mjs` reds until you do.
161+
`objectui:bump` and `objectui:refresh` print the step, and the console build ships that same tracked file in
162+
`packages/console/dist/`. Full procedure: `docs/releases-maintenance.md`.
163163

164164
**Fast iteration on `../objectui` src (no commit/refresh loop):** run objectui's own console dev server —
165165
`cd ../objectui && pnpm --filter @object-ui/console dev` (Vite on **:5180**, HMR). Its `/api` proxy targets
@@ -755,18 +755,6 @@ Principles the wrapper encodes (its own output is the authority on detail):
755755
a name, with accepted cases in the shrink-only, hand-edited
756756
`dual-source-exports.baseline.json`.
757757

758-
**`check:react-declaration-parity` compares two DECLARATIONS, not a declaration against an
759-
implementation** — the props the spec zod schema declares vs the inputs the objectui
760-
registry config declares. Its `spec-only` / `registry-only` / `missing` signals are real;
761-
just don't read it as proof anything renders. Its right-hand side is the **tracked
762-
repo-root `sdui.manifest.json`** and its record `scripts/sdui-manifest.record.json`, which
763-
the required lint job's `scripts/check-sdui-manifest.mjs` checks OFFLINE only — existence,
764-
shape, sha256 vs the record, record pin vs `.objectui-sha`; its version-vs-pin leg runs
765-
only where an objectui checkout is in hand, so lint prints `NOT CHECKED` by design.
766-
`lint.yml` runs THIS gate `--strict` against it on every PR; it still **exits 1** with no
767-
usable manifest and `check:generated` files it `EXTERNAL_INPUT_REQUIRED` because that
768-
aggregate hands it none. ⛔ Do not "fix" a red by re-adding a skip.
769-
770758
Two generators have **no** gate at all — `gen:openapi` and `gen:sbom`. Nothing verifies
771759
their output is current; the wrapper reports that each run rather than staying silent.
772760

@@ -1100,7 +1088,7 @@ Both non-handshake shapes, and how to classify and probe your own:
11001088
so a removal or rename the pinned sibling still imports turns `main` red for every PR in the repo the moment it
11011089
merges — "retire the surface" and "leave the sibling untouched" cannot both hold. A ruling that authorizes such a
11021090
removal therefore implicitly authorizes the objectui-side fix and the pin bump as part of the same landing (the
1103-
bump's `sdui:manifest` second half included — see the Frontend section). Pre-merge check for any removal or rename
1091+
bump's manifest regeneration included — see the Frontend section). Pre-merge check for any removal or rename
11041092
of an exported surface: does the pinned sibling import what you are removing? `git grep` it in `../objectui` at the
11051093
pinned SHA before merging.
11061094
5. **Touched `packages/spec`? Regenerate and commit its artifacts before pushing** — § *Touched `packages/spec`*

‎content/docs/api/error-catalog.mdx‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ entry's cross-reference sentence so the in-process one stays findable.
6969
"httpStatus": 400,
7070
"retryable": false,
7171
"fields": [
72-
{ "field": "email", "message": "Invalid email format", "code": "invalid_format" }
72+
{ "field": "email", "message": "Invalid email format", "code": "invalid_email" }
7373
]
7474
}
7575
```
@@ -197,8 +197,21 @@ rather than deriving it from the table at the end of this page.
197197
</Callout>
198198

199199
### `INVALID_FORMAT`
200-
**Cause:** Field value does not match the expected format (e.g., invalid email, wrong date format).
201-
**Fix:** Ensure the value matches the field's `format` constraint or built-in type validation.
200+
**Cause:** Reserved for a value that does not match an expected format. **No route emits it
201+
today.** A malformed value is refused as a *field-level* failure inside `VALIDATION_FAILED`
202+
instead, and its `fields[].code` names what checked it:
203+
204+
- **the field's `type`** — an `email`, `url` or `phone` field runs a built-in shape check keyed
205+
on `type` itself and answers `invalid_email` / `invalid_url` / `invalid_phone`; a value a
206+
`date`, `datetime` or `time` field cannot parse answers `invalid_date` / `invalid_time`;
207+
- **a [`format` validation rule](/docs/data-modeling/validation#format-validation)** — its
208+
`regex`, or its named `format` (`email` | `url` | `phone` | `json`) — answers `invalid_format`.
209+
210+
A value that misses a declared `pattern` outside record metadata (a settings value, a request
211+
body a route parses with Zod) also answers field-level `invalid_format`.
212+
**Fix:** Do not branch on this code; branch on `VALIDATION_FAILED` + `fields[].code`. To change
213+
what a field accepts, change its `type` or the `format` validation rule — a field-level `format`
214+
key runs no write-time check on any field type, so editing it changes nothing.
202215
**Retry:** `no_retry`
203216

204217
### `VALUE_TOO_LONG`

0 commit comments

Comments
 (0)