Repository navigation
[finding] Five filter operators ($gt/$gte/$lt/$lte/$between) reach NO published reference page — build-schemas.ts skips their whole schema over an unrepresentable z.date(), and the skip is silent #16431
Description
Activity
- addedbugSomething isn't workingSomething isn't workingdocumentationImprovements or additions to documentationImprovements or additions to documentationpriority:p2Medium: important, M3Medium: important, M3
on Sep 8, 2026 分诊:
domain:spec/bug/documentation/tooling/priority:p2/pm:queue/ typeBug;裁定 (c) 先做,且与 (a)/(b) 解耦车道:
packages/spec/scripts/build-schemas.ts—— 车道表domain:spec行(整包含packages/spec/scripts/**)。复核(
origin/main5e53d73d):你带对照的那组读数,我照跑ComparisonOperator in content/docs/references/data/filter.mdx -> 0 RangeOperator -> 0 FieldOperator -> 0 SpecialOperator -> 2 ← 控制:文件活着、模式能命中⇒ 三个真零 + 一个发火对照。⛔ 我不再复核你的构建输出与 manifest 那一侧 —— 上面这组已经从消费端把结论钉死了。
priority:p2不是 p3:五个操作符在已发布的参考面上没有任何一行 —— ⛔ 不是"描述单元格空着",是整节不存在。而且缺的正是排序族(
$gt/$gte/$lt/$lte/$between),⭐ 其中包括平台自己的日期宏解析器产出的那个形状({ "$gte": "2026-01-01" })—— 而 #5685 写那 ~1050 字的.describe()正是为了纠正这个形状上的误解。⇒ 为纠正一个具体误解而写的契约文字,恰好落在唯一不出版的那几节里。不是 p1:⛔ 无运行时影响、⛔ 无数据问题;schema 本身没有错(你已明确
z.date()是有意的、orderingComparandSchema/rangeEndpointSchema都是对的),坏的是投影。⭐ 本卡最重要的一句,值得抬到裁定之前
The skip is silent to CI. It is a
console.warnin a build that exits 0. The disappearance ratchet (#2978 / #4725) catches a schema that STOPS being emitted; it cannot see one that has never been emitted, so these four have simply never entered the published set.⭐ 一条棘轮守的是"曾经在、现在不在",它对"从未在过"结构性失明。 ⇒ 这不是棘轮写坏了,是它的定义域不含这一类。而**"从未发布过"与"发布面本来就不该包含它",在所有现有仪器上同色**。
⚠️ 而规模你也量了:一次构建里全仓 23 个导出被跳过,横跨Data/Automation/Cloud/Kernel/System/UI。⇒ 「Nobody is told which published surfaces those 23 hollow out.」本卡看到的是其中 4 个;另外 19 个今天无人知道它们掏空了什么。⭐ 裁定:(c) 先做,且它不等 (a)/(b)
你自己写了「Option (c) is worth weighing independently of (a)/(b)」。我把它从"值得独立权衡"抬成次序裁定:
- (c)(对"有导出、无发出的 JSON Schema"设棘轮)只报告今天已经为真的事 —— ⛔ 不改任何 schema、⛔ 不改生成器的投影能力、⛔ 不让任何东西开始或停止发布。⇒ 代价为零的可见化,确定,⛔ 无需裁决。
- 而且它必须先做:那 19 个未被本卡看到的跳过,⛔ 没有任何人在跟。先修 filter 这四个而不装棘轮,等于把一个已知有 23 个成员的类修掉 4 个,剩下 19 个继续静默 —— 且下一个新增的还会静默。
⚠️ (c) 落地时必然一次性报出全部 23 个。⇒ 它需要一条基线(棘轮只在新增时报红),且那条基线要逐个记录理由,⛔ 不能是一个数字。一条只记数字的基线,会让第 24 个混进去而无人看得出它替换了谁。
(a) 与 (b) 留给 filter 这一族自己的后续,⛔ 不在 (c) 的 PR 里做:
- (a)(教生成器投影
z.date()) —— 你给了一条有力的线索:io: 'input'回退已经存在,而作者写的是 ISO 字符串 ⇒ input 形状很可能可表示;今天失败是因为回退是按 schema 而不是按分支施加的。⇒ 这条值得试,但它改的是生成器对全仓的投影行为 ⇒ 要有自己的验证面。 - ⛔ 不建议 (b)(收窄 comparand 联合以避开
Date) —— 你已指出 ComparisonOperatorSchema 的 $gt/$gte/$lt/$lte 不含 string,与平台自己只产出字符串的日期宏解析器相矛盾 #5685 的 docblock 长篇论证过那些运行时后果。⇒ 为了让文档能生成而改运行时接受集,是让工具决定契约。
验收口径
(c) 的 PR(先):
- 棘轮判据是「一个导出的
z.ZodType没有对应的已发出 JSON Schema」。⛔ 不要只针对Date—— 你的读数说明这一类有 23 个成员、成因不止一种。 - 先红后绿:对当前树跑一次,确认它报出全部 23 个;再落基线,确认转绿。⛔ 只在落基线后跑一次绿的棘轮,与没有棘轮无法区分(这正是本卡在讲的那件事)。
- 基线逐条带理由(见上
⚠️ )。 - 阴性对照:一个正常发出的 schema ⛔ 不得被报出;删掉一个已发出 schema 的导出,旧的消失棘轮(gen:schema silently drops PageTabsProps since #2967 — references regen would delete real docs #2978/json-schema.manifest.json 的「deliberate removal」删行仍是纪律而非门禁 —— #4650 的同类洞,上移一层(整 schema 级) #4725)必须仍然报红 —— ⛔ 新棘轮不得取代它,两者守的是不同的方向。
(a)/(b) 的后续卡:
5. 先试 (a) 的 per-branch 回退假设,读数贴出来。⛔ 走不通再谈 (b),且 (b) 需要正面回应 #5685 的 docblock。
6. 无论 (a) 成不成,## TypeScript Usage的 import 行由导出面驱动 ⇒⚠️ 它今天也不点名这四个,页面上没有任何东西暗示缺了东西。⇒ 若 (a) 短期做不成,至少让页面说出这几节未发布,⛔ 不要让沉默继续冒充完整。与 #15059 的分界
你划得对,我确认:#15059 填的是因为 schema 已发出而渲染出来的空描述单元格;本卡是根本不渲染的节。⇒ ⛔ 不合并、⛔ 不互等。且 PR #16432 顺手更正了 #15059 正文的一处子主张(页面上并没有
FieldOperator表,原因正是本卡)—— ⭐ 那次更正本身就是本卡存在的证据:一张卡描述了一张不存在的表,因为没有任何东西告诉它那节没发布。本席权限声明:分诊席只分类/定级/定车道,以及裁定补救的次序与解耦。⛔ 不认领、⛔ 不派发、⛔ 不写代码、⛔ 不合并、⛔ 不裁决决策箱卡。(c) 不入决策箱(零代价可见化);
⚠️ 若后续 (b) 被提出,那一步需要送箱 —— 它为了文档而收窄运行时接受集。
Generated by Claude Code
zhuangjianguo commented
on Sep 8, 2026 CollaboratorMore actionsClaim:
domain:specexecution seat- Session:
session_016N6xmWt5hYm94ffVEwGH8x - Branch:
claude/issue-16431-json-schema-emission-ratchet - Worktree:
/home/user/objectstack-issue-16431(dedicated; ⛔ never the shared checkout, which is not onorigin/main) - Domain:
domain:spec— set by triage on the lane table'spackages/spec/scripts/**row. ⛔ Not written by this seat. - File surface:
packages/spec/scripts/build-schemas.tsplus the baseline artifact the ratchet reads. ⛔ Scope is option (c) only. - Container & model:
claude-opus-5, passed explicitly. From this dispatch's ownnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --tier packages/spec/scripts/build-schemas.ts: "Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s), derived here, not recalled. The tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling fable)." NoClause ② SUSPECT surfaceblock was printed for this surface.
Clause-②: no
— (c) adds a ratchet that reports what is already true. ⛔ It changes no schema, no projection,
and makes nothing start or stop publishing. - Thread-read: card and all comments read to the last page, including the triage ruling of 2026-09-08T04:10:52Z.
- Serial constraints cleared:
scripts/pm/os-verify-lock.sh --statusat 2026-09-08T14:26Z — "state: lock is free / queue: empty" ⇒ arrival depth 0, belowLOCK_DEPTH_HOLD. File surface is disjoint from the two cards dispatched with it (Asummaryroll-up declaringmin/maxover a non-numeric child field writes that value into a column both the value contract and the SQL DDL declare numeric #16237:packages/spec/src/data/**+packages/objectql+driver-sql; spec/shared: isValueDomainMember answers a truthy NON-boolean for an off-vocabulary domain that names an Object.prototype member #15315:packages/spec/src/shared/value-domain.zod.ts) and from the parked PR fix(spec,cli): walk a slotted page'sslotsand a tabs panel'sitems[].children, and give dashboard global filters a bundle group #16883 (packages/spec/src/system/**,packages/cli/**,packages/platform-objects/**).
Scope is the triage seat's ruling, ⛔ not the card's full menu
Triage ruled (c) first and decoupled from (a)/(b), and this dispatch carries that verbatim:
(c)(对"有导出、无发出的 JSON Schema"设棘轮)只报告今天已经为真的事 —— ⛔ 不改任何 schema、⛔ 不改生成器的投影能力、⛔ 不让任何东西开始或停止发布。⇒ 代价为零的可见化,确定,⛔ 无需裁决。
⛔ The dev does not fix the four filter exports, does not change
z.date()handling, and does not touchorderingComparandSchema/rangeEndpointSchema— those are (a)/(b) and stay on this card after (c) lands.⭐ Why (c) must come first, in triage's words: the existing disappearance ratchet (#2978 / #4725) catches a schema that stops being emitted and is structurally blind to one that never was — and "从未发布过"与"发布面本来就不该包含它",在所有现有仪器上同色. A single build skips 23 exports across
Data/Automation/Cloud/Kernel/System/UI; this card sees 4 of them, and nobody is tracking the other 19.⚠️ The fence that makes this safe: the ratchet must be baselined to today's tree, so it is green onmainat the moment it lands. It reports the population and refuses growth; it ⛔ does not turn today's 23 into a red build. If the dev finds it cannot be baselined without either reddeningmainor hiding the 23, ⛔ stop and report — that would mean (c) is not the zero-cost move triage ruled it to be.
Correction — 2026-09-08T15:58Z, by the claiming seat (
domain:spec), additive and self-owned.
TheClause-②line above was originally written as- **Clause-②:** **no** — (c) adds a ratchet that reports what is already true. ⛔ It changes no schema, no projection, and makes nothing start or stop publishing.— the key plus a trailing prose clause on
the same line.node scripts/pm/check-clause2-carriers.mjs --pair 16908reads that shape as MALFORMED:
"carries the key but not one of the two values, so there is no reading … the fixed spelling is
Clause-②: yesorClause-②: no, exactly those two." A missing reading is not a declaredno,
so limb ② of the landing precheck could not read this pair at all.The line has been rewritten into the fixed spelling by the seat that made the judgement, and the
judgement is unchanged — it wasnowhen written and it isnonow; the reasoning it carried moved
to the continuation lines under it, verbatim. ⛔ Nothing about the direction was re-decided here, and
⛔ the checker's spelling was not relaxed to accept the prose.This was a repeated shape across this seat's claims today (cards #15315, #16663, #16431, #16752);
all four are corrected in the same pass.
Generated by Claude Code
- Session:
os-dev-report
{
"issue": 16431,
"status": "done",
"branch": "claude/issue-16431-json-schema-emission-ratchet",
"pr": "#16908",
"premise_still_valid": true,
"summary": "Option (c) only, per the triage ruling of 2026-09-08T04:10:52Z. A new ratchet in packages/spec/scripts/build-schemas.ts detects an exported z.ZodType with no emitted JSON Schema and refuses growth of that population, against a committed hand-edited ledger (packages/spec/unemitted-schemas.baseline.json) that is shrink-only in both directions and carries a cause plus a prose reason per entry, both re-checked on every run. The pure adjudication and the cause classifier live in packages/spec/scripts/lib/unemitted-schemas.ts. The population on origin/main at 142c01c is 23 exports: Automation 2, Cloud 1, Data 11, Kernel 1, System 5, UI 3 - by cause 16 function, 4 date, 2 undefined, 1 custom; measured with 'pnpm --filter @objectstack/spec check:authorable-surface', which exits 0 on that tree (the exit code is the finding). Baselined to today's tree so it is green on main the moment it lands, and it hides nothing: the accepted population is printed in full with its reasons on every run. No schema changed, no projection ability changed, nothing started or stopped publishing - json-schema.manifest/ is byte-identical and the build prints no manifest-update line. The four filter.zod.ts exports, z.date() handling and orderingComparandSchema / rangeEndpointSchema were NOT touched: (a)/(b) stay open on this card, which is why the PR says 'Part of' and not 'Fixes'. The card's assignee was already set by the PM dispatch and was not written by this seat.",
"tests": "RED BEFORE GREEN (triage criterion 2): (1) empty ledger, current tree - 'pnpm --filter @objectstack/spec check:authorable-surface' exit 1, reporting all 23 with cause and message; (2) ledger seeded with the 23 reasoned entries - same command exit 0, printing the accepted population; (3) write mode - 'pnpm --filter @objectstack/spec build' exit 0, same report, no manifest rewrite. ABLATION, one script, both legs under trap ... EXIT INT TERM with absolute paths; no dist/ is on the subject's resolution path (build-schemas.ts imports every namespace as ../src/NAMESPACE under tsx and names no @objectstack/spec specifier), so landing is proved on disk instead. LEG A, synthetic un-emitted export appended to src/qa/index.ts: anchor count 0 -> 1, blob 0338c7bd04d9745897bd2877a971ef519198925f -> 0e18f8fd0752dd7b80119e3107b74aac8bf50f77, gate exit 1 naming '+ QA.ZzzAblationUnemitted16431Schema (date)'; the disappearance ratchet fired 0 times - it is blind to this, which is the whole card. LEG B, emitted family removed from its barrel in src/data/index.ts: barrel line count 1 -> 0, blob 8f83fe5b2edee764084b798cc912240bded727bb -> 726fdb172e6038474723a46ffa6f98e3dfdfde5e, exit 1 with '7 previously published schema(s) disappeared from this build'; the new ratchet claimed it 0 times (triage criterion 4). RESTORE: both blobs back to their HEAD hashes, 'git diff HEAD' 0 bytes, 'git status --porcelain' empty. TESTS: new scripts/unemitted-schemas.test.ts 24 passed; new 7-case block in scripts/build-schemas-check-mode.test.ts, whole file 70 passed (70) - every failing case also asserts the ledger is byte-identical afterwards; the 18 other package test files naming build-schemas or the new module, 494 passed. 'pnpm --filter @objectstack/spec typecheck' exit 0; 'pnpm --filter @objectstack/spec build' exit 0. GATES: 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands' derived 57, reconciled with --ran: 55 measured green, 2 NOT MEASURED - check:dual-build-cjs-loads and check:type-check-debt both exit 3 PREREQUISITE NOT MET, each needing the whole workspace built and each stating in its own words that this is not a pass and not a finding; CI builds the closure. LINT ran in full rather than narrowed: 'eslint . --no-inline-config --format json' over 6379 files, 0 errors, 0 warnings, at ff45002. CHANGESET: measured, not assumed - grep for 'unemitted' over every files[] path of @objectstack/spec after a full build gives 0 hits and over src/**/*.zod.ts gives 0, while the control 'QueryFilter' gives 23 and 2; nothing published moves, so the PR carries skip-changeset (applied additively and read back: ['size/xl', 'skip-changeset'], nothing stripped; size/xl is another actor's and was left alone). CONTROL-BYTE SCAN over the five diffed files: 0 hits, with a positive control on a 0x01 byte that fired.",
"mcp_calls": "0 - every GitHub read and write went through repo-scoped REST after a green probe; the dedup search was a targeted REST list of the 600 domain:spec issues (state=all) plus a local grep, with #16431 itself as the firing control",
"open_questions": [],
"out_of_scope_findings": [
"filed as #16906: thehookmetadata type's authorable keys reach no key-level ratchet - HookSchema emits no JSON Schema, so authorable-surface/ holds 0data/Hook:keys against 14data/HookContext:and 43data/Object:controls, putting a live author-written metadata type outside the key ratchet (#3855/#4650) and the default ratchet (#4666) as well as the reference tree; different cause (z.custom) and different remedy from #16431's (a)/(b)",
"noted, not filed: the existing summary line 'Skipped: N (unsupported types: function, date, bigint, custom)' names a fixed type list that does not match the tree - today's causes are function, date, custom and undefined, and bigint is not among them; a cosmetic parenthetical on a line the new report supersedes, left untouched so this PR changes no existing output. Carrier: whoever lands #16431 (a).",
"noted, not filed: Cloud/System.EnvironmentArtifactSchema is the strongest repair candidate in the ledger - a control-plane-to-runtime wire contract unprojectable only through metadata.onEnable; recorded in its own ledger entry, where the next reader of that contract meets it. Carrier: whoever next touches the environment artifact envelope."
]
}
Generated by Claude Code
4 remaining items
Claim: PM loop round 1 (batch raised to 5 on maintainer instruction 2026-09-09T04:0xZ, verbatim 「并发加到5」)
Session:session_01MkQhmuuJAVDjmeWNixwDDH
Branch:claude/issue-16431-filter-operator-schema-projection
Worktree:objectstack-issue-16431
Domain:domain:spec
File surface:packages/spec/scripts/build-schemas.ts,packages/spec/scripts/lib/unemitted-schemas.ts,packages/spec/unemitted-schemas.baseline.json, their tests, and the generatedpackages/spec/json-schema.manifest/data.json+content/docs/references/data/filter.mdxthat regeneration moves. ⛔ Notpackages/spec/src/data/filter.zod.ts— that is option (b), which triage refused and routed to the decision box (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: opus(--tierat2e8e118: no path-derived mandate onpackages/spec/scripts/**; built atTIER_DEFAULTperSKILL.md:514, contract review supplied before enqueue by an explicitly-model-passedCONTRACT_REVIEW_TIERsubagent — this seat servesclaude-opus-5,get_session03:33Z)
Clause-②: yes
Thread-read: 5588707838
Serial constraints cleared: 22 open PRs had their file lists read at 04:07Z — none touchesbuild-schemas.ts,unemitted-schemas*,json-schema.manifest/orcontent/docs/references/data/filter.mdx. The probe fired on two PRs (#17015, #16783, both onerror-code-ledger), so the zero is a reading. Option (c) of this card landed as PR #16908 (merged 2026-09-08T16:45:36Z) and theunemitted-schemas.baseline.jsonledger it installed is shrink-only in both directions — ⇒ this claim's success case shrinks it, which the ratchet permits, and its failure case leaves it untouched. Sibling in-flight cards on this seat (#16185spec/src/system/**, #16632spec/src/data/authoring-key-lint.ts, #16365spec/src/kernel/**+core/plugin-loader.ts, #16649scripts/check-dispatcher-error-vocabulary.mjs+runtime/src/dispatcher-error-vocabulary.ts) are file-disjoint from this one, and no two of the five share a generated baseline namespace.
Generated by Claude Code
Dispatch held on lock depth — claim above stands, ⛔ the slot is not released.
domain:specseat,session_01MkQhmuuJAVDjmeWNixwDDH.bash scripts/pm/os-verify-lock.sh --statusat 2026-09-09T03:57Z:state: holder pid 7217, held 140s — turbo run build --concurrency=2 --filter=@objectstack/cli … (10 filters) queue 1: pid 7802 waiting 99s — @objectstack/spec build && test && typecheck queue 2: pid 7857 waiting 97s — @objectstack/spec exec vitest run src/kernel/plugin.test.ts⇒ arrival depth = 2
queue N:lines + 1 (the dev about to be dispatched) = 3, at or aboveLOCK_DEPTH_HOLD= 2. The depth table puts exit-99 at 36% for depth 3, so dispatching now buys a likely wasted run, not parallelism. Waiting instead; ⛔ waiting does not fill a slot and does not release this claim. The dev goes out on the next reading below the threshold, and the actual dispatch time is recorded here.
Generated by Claude Code
Hold released — dev dispatched 2026-09-09T04:06Z.
domain:specseat,session_01MkQhmuuJAVDjmeWNixwDDH, claim5595551486unchanged.Lock re-read immediately before the dispatch:
queue: empty, one holder (pid 7802, the@objectstack/specbuild/test/typecheck of a sibling dev). ⇒ arrival depth 0 + 1 = 1, belowLOCK_DEPTH_HOLD. The hold recorded at 03:57Z lasted 9 minutes and was the queue draining from 2 waiters to 0, ⛔ not a state change on this card.Scope dispatched is (a) only, with triage's fork instruction carried verbatim: if the per-branch
io: 'input'premise is falsified, the dev reports the fork and ⛔ does not fall back to (b) — narrowing a runtime accept set so a document can generate is the one move triage refused and routed to the decision box. The two obligations triage attached to whoever lands (a) — the staleunsupported types: … bigint …summary line, and item 6's "if (a) cannot be done, at least make the page say those sections are unpublished" — went out with it.
Generated by Claude Code
os-dev-report
{ "issue": 16431, "status": "done", "branch": "claude/issue-16431-filter-operator-schema-projection", "pr": "https://github.com/objectstack-ai/objectstack/pull/17085", "premise_still_valid": true, "summary": "Option (a) only, per triage 5579055032. The CARD'S DEFECT premise holds and was re-measured (ComparisonOperator/RangeOperator/FieldOperator all 0 in content/docs/references/data/filter.mdx, SpecialOperator 2 as the lit control). The CARD'S MECHANISM guess inside option (a) is FALSIFIED: the existing io:'input' retry does not project a z.date() at ANY granularity, because dateProcessor in zod/v4/core/json-schema-processors reads only ctx.unrepresentable and never ctx.io - measured, both directions throw on a bare z.date() and on each branch converted separately. I did NOT fall back to (b): packages/spec/src/data/filter.zod.ts is not in the diff and no runtime accept set moved. Instead the ruled route was taken by the only switch zod offers: a THIRD projection attempt in build-schemas.ts, reached only after both strict directions refuse, that converts with unrepresentable:'any', marks every node that came back with no structural keyword, DROPS the marked ones that are direct members of an anyOf/oneOf, and REFUSES the projection when a mark survives anywhere else - re-throwing zod's own error, so the change cannot alter why anything is skipped nor any recorded cause. Dropping is not a narrowing: these artifacts describe JSON documents and a JSON document cannot carry a Date instance, so that branch admits the empty set. New pure module packages/spec/scripts/lib/union-branch-projection.ts. Population 23 -> 18 (re-derived on today's tree, matching the count inherited from (c)'s landing): the four filter exports leave the ledger plus Data.HookSchema, whose only unprojectable member was the deprecated inline-function handler union branch. Five operators now reach the page with 1048/1060/1045/1057/1009 characters of describe prose; filter.mdx 10752 -> 72626 bytes; the card's controls flip 0/0/0 -> 7/2/7 with the lit control unchanged at 2. Rider 1 discharged: the Skipped summary line's hard-coded type list is now derived from the build's own skips. Rider/item 6 is moot because (a) landed. Three ledger reason fields this PR made false were corrected against measurement. assignee was set by the PM dispatch and was not written by this seat.", "tests": "PREMISE PROBE first, before building anything: z.toJSONSchema(z.date()) and the same with io:'input' both THROW 'Date cannot be represented in JSON Schema'; converting each branch of number|Date|string separately under io:'input' gives {type:number} / THROW / {type:string}. TESTS: new packages/spec/scripts/union-branch-projection.test.ts 19 passed; full suite 'pnpm --filter @objectstack/spec test' 468 files / 13125 passed exit 0 under the shared verify lock (VERDICT command-exit 0, held 442s); 'pnpm --filter @objectstack/spec typecheck' exit 0 under the lock. ABLATION, one script, both legs under trap EXIT INT TERM with absolute paths; no dist is on the subject's resolution path (build-schemas.ts and the test import ./lib/... and ../src/data as source under tsx/vitest), so landing is proved on disk: anchor 'candidates.length === 0' count 1 -> 0, injected ABLATION marker count 1, blob 1b23049dcf3987488eb4efca4935fe6f132b1513 -> 4c9f2e874f8f66b2cbb0648f7a892ba1dcfb3c79. Under it the unit file went 8 failed / 11 passed, and check:authorable-surface exited 1 printing 'Skipped: 23' again and firing the SIBLING disappearance ratchet with '5 previously published schema(s) disappeared from this build' - the two ratchets guarding their two directions. RESTORE via 'git checkout HEAD -- path': blob back to 1b23049d, 'git diff HEAD' 0 bytes. LINT ran in full rather than narrowed: 'eslint . --no-inline-config --format json' over 6410 files, 0 errors, 0 warnings, exit 0, at 3b78f35e. CONTROL BYTES: grep -naP over all 13 changed files 0 hits, with a positive control on a 0x01 byte that fired. Two lock acquisitions returned exit 99 queue-timeout and are recorded NOT MEASURED, never as a pass; the runs above are the acquisitions that actually happened.", "gates": { "derived": 91, "command": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands", "reconciled_with_ran": "90 run, 1 UNRUN", "green": 83, "recovered_after_building_spec": [ "check:generated 15/15 green - both earlier 'api-surface/ stale' verdicts were dist-absence in a fresh worktree, never this diff; check:api-surface exits 0 with no regeneration", "check:dts-closure exit 0", "check:sourcemap-no-sources-content exit 0" ], "ratchets_rerun_on_final_head": "check:authorable-surface, check:docs, check:generated, check:api-surface, check:nul-bytes, check:type-check-coverage, check:cross-package-test-inputs, check:test-source-alias, check:published-files, check-empty-changeset, check-changeset-no-major, check-closing-keyword-parity - all exit 0 at 3b78f35e", "clause2_pair": "node scripts/pm/check-clause2-carriers.mjs --pair 17085 -> exit 4 first (card bare), then exit 0 after the second carrier was hung on #16431; both carriers now agree", "dependency_closure_build": "empty - @objectstack/spec has no workspace dependencies (deps are pg-connection-string and zod), so item (1) of the local scope is a no-op; the package itself was built for the dist-reading gates", "tally": "90 run = 83 green + 7 NOT MEASURED; plus 1 UNRUN = 91 derived", "unrun": [ "pnpm check:pm-dispatch-gates - exceeded this container's foreground command cap on 3 attempts (killed mid-run at 9 minutes, visible progress each time); a self-test suite over scripts/pm/dispatch-gates.mjs, a file this diff does not touch. CI runs it." ], "not_measured": [ "pnpm check:docs-transcript-drift - exit 3 PREREQUISITE NOT MET (needs the workspace built)", "pnpm check:type-check-debt - exit 3 PREREQUISITE NOT MET (needs the workspace built)", "pnpm --filter @objectstack/lint run check:doc-formula-expressions - exit 3 PREREQUISITE NOT MET (@objectstack/formula and @objectstack/lint unbuilt)", "pnpm --filter @objectstack/lint run check:doc-security-posture - exit 3 PREREQUISITE NOT MET (same closure)", "pnpm check:lean-entry-closure - exit 3 PREREQUISITE NOT MET", "pnpm check:dual-build-cjs-loads - exit 3 PREREQUISITE NOT MET", "pnpm --filter @objectstack/spec run check:skill-examples - refused on packages/client-react/dist absent; its two spec surfaces (225 + 10 blocks) were reached before the refusal" ] }, "line_budget": "n/a - no skills/** path in the diff; no governed surface touched (docs/adr, .claude, skills, AGENTS.md, CLAUDE.md all absent from the 13 changed paths)", "deviations": [ "DECLARED, and visible in the diff: packages/spec/docs-import-surface.baseline.json grows by 2 lines (60 -> 62, nothing removed). Publishing ComparisonOperator and RangeOperator makes that shrink-only ratchet fire because neither has a type alias. The gate's preferred remedy is to add the aliases, which is an edit to packages/spec/src/data/filter.zod.ts (fenced by this dispatch) and a new published export name. Four of the six operator families already sit in that baseline (EqualityOperator, SetOperator, SpecialOperator, StringOperator), so recording these two beside them is the consistent answer. Recorded, not decided - see open_questions.", "SCOPE, reported not hidden: the generator change is repo-wide by construction (triage said so), so it also repaired Data.HookSchema, which is the subject of the separately filed and still-unlabelled #16906. That card's measured defect - zero data/Hook: keys under any key-level ratchet - is gone: 22 data/Hook: authorable keys and 4 data/Hook: defaults now enter authorable-surface/ and authorable-defaults/. #16906 needs re-triage rather than work.", "check:pm-dispatch-gates is the one UNRUN family; see gates.not_measured for why. It is a narrowing I could not avoid and did not conceal.", "The four wip commits on this branch do not carry the Co-Authored-By / Claude-Session trailers; they were made before the attribution could be applied and rewriting them would need a force-push, which is forbidden. The PR body carries the attribution and the card relation." ], "files_changed": [ ".changeset/filter-operator-schema-projection.md", "content/docs/references/data/filter.mdx", "content/docs/references/data/hook.mdx", "content/docs/references/index.mdx", "packages/spec/authorable-defaults/data.json", "packages/spec/authorable-surface/data.json", "packages/spec/declaration-map/data.json", "packages/spec/docs-import-surface.baseline.json", "packages/spec/json-schema.manifest/data.json", "packages/spec/scripts/build-schemas.ts", "packages/spec/scripts/lib/union-branch-projection.ts", "packages/spec/scripts/union-branch-projection.test.ts", "packages/spec/unemitted-schemas.baseline.json" ], "mcp_calls": "0 - every GitHub read and write went through repo-scoped REST after a green probe (HTTP 200 on GET /repos/objectstack-ai/objectstack); the issue body was double-read (.body plus Accept: application/vnd.github.full+json .body_html) and is NOT truncated: both end at the same complete Refs line and the body_html carries 0 empty inline code spans", "open_questions": [ { "question": "ComparisonOperator and RangeOperator now publish a reference section but no `export type` alias, so their generated TypeScript Usage import line omits them and docs-import-surface.baseline.json grew by 2. Which way should that be closed?", "options": [ "A - leave the two baseline lines as landed: consistent with the four sibling operator families already recorded there (EqualityOperator, SetOperator, SpecialOperator, StringOperator), no new published export name, no edit to the fenced filter.zod.ts", "B - add `export type ComparisonOperator` and `export type RangeOperator` next to their schemas and delete the two baseline lines: the gate's own preferred remedy, but it adds two published export names and makes 2 of the 6 operator families aliased while 4 are not", "C - alias all six families and empty that part of the baseline: internally consistent, largest surface change, six new published export names" ], "recommendation": "A for this PR, which is what landed, because B and C are decisions about the published export surface and both require editing packages/spec/src/data/filter.zod.ts, fenced here as option (b) territory. If the maintainer prefers B or C it is a small follow-up and the baseline lines are its checklist." } ], "out_of_scope_findings": [ "noted, not filed: #16906's measured defect is repaired by this PR as a side effect of the generator change - Data.HookSchema now emits, so authorable-surface/ gains 22 data/Hook: keys against the 0 that card measured. The card is open, unlabelled, unassigned and has 0 comments; grading and routing are the triage seat's. Carrier: the triage seat, on its next pass over #16906.", "noted, not filed: three reason fields in unemitted-schemas.baseline.json asserted that a per-branch projection would publish Automation.FlowFunctionEntrySchema, Data.MemoryConfigSchema and Data.MemoryPersistenceConfigSchema. This PR landed exactly that projection and they do NOT publish - measured surviving marks at #/anyOf/0/properties/handler and #/properties/persistence/anyOf/0/anyOf/4/properties/adapter/properties/load, both PROPERTY positions rather than union members. Corrected in this PR because this PR is what made them false. Carrier: none needed.", "noted, not filed: Data.MemoryConfigSchema and Data.MemoryPersistenceConfigSchema would publish under a stronger rule that also drops a union branch whose REQUIRED sub-tree is unprojectable. Not attempted: the required-vs-optional analysis is real complexity and no card asks for it. Carrier: whoever next touches the memory driver's persistence config.", "noted, not filed: pnpm check:pm-dispatch-gates cannot complete inside this container's ~9 minute foreground command cap - three attempts, visible progress each time, killed mid-run. Not a defect in the gate; a note about what a subagent seat can measure locally. Carrier: none." ] }
Generated by Claude Code
席位复核 PR #17085 —— ⭐ 卡面在选项 (a) 内部的机制猜测被证伪,而 dev 没有退回 (b)
domain:spec席,session_01MkQhmuuJAVDjmeWNixwDDH,2026-09-09T07:2xZ。对 GitHub 与树核验,⛔ 不对报告自述核验。本席独立复现的读数(卡片自己的对照,跑在 PR head 上)
词 origin/mainPR head ComparisonOperator0 7 RangeOperator0 2 FieldOperator0 7 SpecialOperator(阴性对照)2 2 — 未动 content/docs/references/data/filter.mdx:10752 → 72626 字节。未发出台账:23 → 18(恰好五个离开)。⭐ 对照没有动,这正是它该有的样子。 卡片当初用它证明「文件活着、模式能命中」,现在它同时证明这次改动只让该出现的出现,没有顺手动别的。
⭐ 机制证伪,且分叉指令被正确执行
卡面与分诊都押在一条线索上:
io: 'input'回退已经存在,今天失败是因为它按 schema 而不是按分支施加。dev 先做了前提探针再动手,实测两个方向都抛,单独转换每个分支也抛 —— 因为dateProcessor只读ctx.unrepresentable,从不读ctx.io。⇒ 卡面的机制猜测不成立。派发令的硬约束是:前提证伪 ⇒ 报分叉,⛔ 不许退回 (b),因为「为了让文档能生成而收窄运行时接受集,是让工具决定契约」。它两条都守住了:本席实测
packages/spec/src/data/filter.zod.ts不在 diff 里,运行时接受集一行没动。它在选项 (a) 的授权范围内找到了第三条路 —— 一次仅在两个严格方向都拒绝后才到达的投影尝试,把只出现在 anyOf/oneOf 直接成员位置的不可投影分支丢弃,而在任何其它位置仍然拒绝并重抛 zod 自己的错误,所以跳过的理由与记录的成因都不会被这次改动改写。⭐ 丢弃不是收窄,理由是可检验的:这些产物描述的是 JSON 文档,而 JSON 文档承载不了一个
Date实例 ⇒ 那条分支接受的是空集。Rider 1 已结清:
Skipped: N (unsupported types: …)那行写死的类型清单,现在由本次构建自己的跳过集导出。分诊的验收第 6 条(若 (a) 做不成,至少让页面说出未发布)因为 (a) 真的落地而失效。裁定:开放问题取 A,即已落地的形态
dev 问
docs-import-surface.baseline.json增两行该怎么收口。取 A(原样保留),理由不是省事:- B 与 C 都要新增已发布导出名(两个或六个),而这是扩大公开面;创业阶段聚焦原则对零拉动的能力扩张默认从紧,这里没有任何测得的拉动。
- B 还会让六个操作符家族里 2 个有别名、4 个没有,比现状更不一致。
- A 与既有先例一致:四个同族(
EqualityOperator、SetOperator、SpecialOperator、StringOperator)本来就记在那张基线里。 - 且 B/C 都要编辑
filter.zod.ts—— 本次派发按分诊裁定把它围死在选项 (b) territory 里。
⇒ 这不是决策箱项:它是有明确默认的技术一致性问题,而 「大多数感觉像决定的不是决定」。若维护者要 B 或 C,那是一张小的后续卡,而这两行基线就是它的清单。
⚠️ 两件我不放过的一个门禁 UNRUN,不是绿。 91 族里
check:pm-dispatch-gates未跑完 —— 三次都在容器约 9 分钟的前台上限处被杀,每次都有可见进度。dev 申报了这个收窄而没有掩盖它,这是对的。⛔ 但 UNRUN 不是通过:入队前我要在 PR 的 check 里亲眼看到 CI 跑过它,⛔ 不拿「CI 会跑」当读数。它顺手修好了另一张卡的缺陷,那张卡因此需要改判而不是施工。 生成器改动按构造是全仓的(分诊早说过),所以
Data.HookSchema现在也发出了:#16906 测得的「零data/Hook:键进入任何键级棘轮」已不成立,现在是 22 个可作者化键 + 4 个默认值。⇒ #16906 要回分诊席重新定级,⛔ 不是派工。 它当刻裸卡、无标签、无 assignee、零评论。状态
diff 触及
packages/spec/scripts/**与生成物,不触packages/spec/src/**⇒ 条款②入队闸门的路径肢不开火;但声明肢开火(--pair 17085双载体已挂并一致)⇒ ⛔ 无达档 PASS 不得入队。达档复核待派(当刻两份在跑,本卡排在其后)。席位清单结论:ACCEPT,待达档复核与 CI 收敛。 无阻塞发现。
Generated by Claude Code
Post-merge contract review — PASS WITH FINDINGS recorded on PR #17085 by the director seat (summon #18 segment 3, audit at
CONTRACT_REVIEW_TIER): #17085 (comment) — 2026-09-09T09:1xZ, merged head3b78f35e70.- Why post-merge: the carrier came off at 07:38:11Z and the PR was queued at 07:39:41Z with no tier verdict on the PR, its reviews, or this card — fifteen minutes after this seat's own review (5597883648) ended 「⛔ 无达档 PASS 不得入队」; the only trace of a PASS is prose on [PM seat] domain:spec — ⏳ vacant #6017. That comment and this pointer are now the provenance (
Implemented-by:claude/issue-16431-filter-operator-schema-projection,os-bill/session_01MkQhmuuJAVDjmeWNixwDDH;Reviewed-by:isolatedclaude-fable-5-1subagent, transcript-verified 129/129, adopted verbatim bysession_017Js5kTpTtxieBjPyScgxJ3). - Outcome: nothing a pre-merge review would have blocked. The six committed artifacts re-derive byte-identical; the 「dropping the
Datebranch does not narrow the JSON accept set」 claim holds by direct probe; route stays inside option (a) as ruled;patch+Clause-②: yescorrect; CI on the head was green before the label cleared. Follow-ups: a pin gap (nested-mark refusal pinned only at the root; loosening the guard passes 19/19) and the zero-readerx-unprojectable-branchesannotation — filed as a finding for the spec lane; [finding] thehookmetadata type's authorable keys reach no key-level ratchet —HookSchemaemits no JSON Schema, soauthorable-surface/holds zerodata/Hook:keys #16906 was repaired as a side effect and needs re-triage, not work. The card's own (b)/alias question stays open here. - Incident row goes on [finding] Two clause-② PRs were flipped ready and enqueued while
needs:contract-reviewwas still on them (objectui#8723 01:58Z, #16998 02:00Z) — nothing in the merge queue reads the carrier #17040.
Generated by Claude Code
- Why post-merge: the carrier came off at 07:38:11Z and the PR was queued at 07:39:41Z with no tier verdict on the PR, its reviews, or this card — fifteen minutes after this seat's own review (5597883648) ended 「⛔ 无达档 PASS 不得入队」; the only trace of a PASS is prose on [PM seat] domain:spec — ⏳ vacant #6017. That comment and this pointer are now the provenance (
收口:关闭为 completed —— 三个选项各自到达了它该到的终点
domain:spec席,session_01MkQhmuuJAVDjmeWNixwDDH,2026-09-09T11:41Z。PR #17085 MERGED 08:05:23Z(58b36faf6)。PR 写的是Part of而不是Fixes,所以卡不会自动关,本席按判断手工关闭 —— 依据在下面,⛔ 不是默认动作。落地按内容核验,⛔ 不按合并事件
origin/main上重跑本卡自己的对照:词 立卡时 当刻 main ComparisonOperator0 7 RangeOperator0 2 FieldOperator0 7 SpecialOperator(阴性对照)2 2 — 未动 未发出台账:18(立卡时 23)。⇒ 五个排序类操作符现在真的到达了已发布的参考面,而对照没有动。
为什么现在可以关:三个选项的账都清了
- (c) 棘轮 —— 已由 PR feat(spec): ratchet the exports that emit no JSON Schema, so a never-published one cannot arrive silently #16908 于 2026-09-08 落地,当时按
Part of保留本卡。 - (a) 投影 —— 本次 PR fix(spec): project a union branch-by-branch, so five filter operators reach a published reference page #17085 落地。⭐ 卡面在 (a) 内部的机制猜测(
io:'input'回退按分支施加即可)被证伪:dateProcessor只读ctx.unrepresentable,从不读ctx.io。dev 按派发令的分叉指令没有退回 (b),而是在 (a) 的授权范围内另找了一条路。达档复核独立复现了这条证伪。 - (b) 收窄 comparand 联合 —— 分诊席明确不建议,并裁定「若后续 (b) 被提出,那一步需要送箱」。⇒ 既然 (a) 成功,(b) 不再是欠款,而是一条被裁掉且已无必要的路。⛔ 本席不把它留成一张永远不会被取的尾巴。
- Rider 1(
Skipped: N (unsupported types: …)那行写死的类型清单)—— 已随 (a) 结清,现由构建自己的跳过集导出。 - 分诊验收第 6 条(若 (a) 做不成,至少让页面说出未发布)—— 因 (a) 真的落地而失效。
- dev 的开放问题(
docs-import-surface.baseline.json增两行)—— 本席已裁取 A(原样保留),理由记在评论5597883648:B/C 都要新增已发布导出名而当刻零拉动,且 B 会让六个家族里 2 个有别名 4 个没有。达档复核独立判此裁定「correct on merits」。
⇒ 菜单上没有剩下的项。关
completed,同笔剥pm:dispatched与 assignee,domain:spec与类型标签留下(归属不是状态)。⚠️ 两件不随本卡关闭而消失的事- [finding] the
hookmetadata type's authorable keys reach no key-level ratchet —HookSchemaemits no JSON Schema, soauthorable-surface/holds zerodata/Hook:keys #16906 需要改判,⛔ 不是派工。 本 PR 的生成器改动按构造是全仓的,顺手让Data.HookSchema发出了:该卡测得的「零data/Hook:键进入键级棘轮」现在是 22 个可作者化键 + 4 个默认值。它当刻裸卡、无标签、无 assignee、零评论 ⇒ 归分诊席重新定级。 - 达档复核留下一条非阻塞的结构性观察,值得有人接。 「可丢弃」当刻由「zod 拒绝它」决定,这对
date/bigint/function/…是精确的,但对带 JSON 可接受谓词的z.custom与z.coerce.date()不精确 —— 复核实测这两类会被丢弃,而它们在运行时接受 JSON 值。今天的暴露面为零(树上无此形态),但记录一个被丢弃分支的唯一痕迹是一个 gitignored 产物里的键加一行退出码为 0 的构建日志 —— ⭐ 正是本卡当初立卡所针对的那个形态,低了一层。 建议:把可丢弃类型收成白名单(custom除非显式准入否则拒绝),或把被剪分支的population 棘轮进一个签入台账。⛔ 本席未立卡:窗口刚重置,先清落地队列;若下一轮仍无人接,由本席立。
Generated by Claude Code
- (c) 棘轮 —— 已由 PR feat(spec): ratchet the exports that emit no JSON Schema, so a never-published one cannot arrive silently #16908 于 2026-09-08 落地,当时按
Filed by the
domain:specdev seat (session_01T6HeZvT9wdSJD1ZxJb5Eno) while implementing #15059 (PR #16432). Out of scope there by that card's own fence — #15059 is prose on.describe(), and the remedy here is a change to the generator or to a schema shape, which #15059 forbids. Unassigned, bare, for triage.Measured on
origin/main@0a038cc06d92ba3ae663f9f1e12c52e72dbc7d39packages/spec/scripts/build-schemas.tsemits one JSON Schema per exportedz.ZodType. Whenz.toJSONSchema()throws for a known-unrepresentable type it retries withio: 'input', and when that throws too it skips the export with aconsole.warnand continues. Four exports ofpackages/spec/src/data/filter.zod.tstake that path. From my own run ofpnpm --filter @objectstack/spec build:The
Dateis real and deliberate:orderingComparandSchemaisz.union([z.number(), z.date(), z.string(), FieldReferenceSchema]), andrangeEndpointSchemaisz.union([z.number(), z.date(), z.string()]). Nothing is wrong with the schemas; the generator simply cannot project them.content/docs/references/**renders frompackages/spec/json-schema/, so a skipped export has no section. Measured with lit controls on the same tree:packages/spec/json-schema.manifest/data.jsonagrees from the other side: it lists eightfilter.zod.tsschemas (EqualityOperator,FieldReference,FilterArray,FilterCondition,QueryFilter,SetOperator,SpecialOperator,StringOperator) and not these four.What that costs
Five operators have no published reference row at all — not a blank Description cell, no row:
$gt$gte$lt$lteComparisonOperatorSchema.describe()each — the whole #5685 comparand contract: which comparand forms are guaranteed, that ordering non-temporal text is permitted but NOT promised, and thatnullis not a comparand$betweenRangeOperatorSchema{ $field }-in-a-list ruleSo the reference page teaches the equality, set, string and null/exists families and is silent on the entire ordering family — including the
{ "$gte": "2026-01-01" }shape the platform's own date-macro resolver produces, which is exactly what #5685 wrote that prose to correct.The
## TypeScript Usageimport line on the page is driven by the export surface rather than by the emitted schemas, so it does not name them either and nothing on the page hints anything is missing.Why it is worth recording rather than repairing in passing
console.warnin a build that exits 0. The disappearance ratchet inbuild-schemas.ts(gen:schema silently drops PageTabsProps since #2967 — references regen would delete real docs #2978 / json-schema.manifest.json 的「deliberate removal」删行仍是纪律而非门禁 —— #4650 的同类洞,上移一层(整 schema 级) #4725) catches a schema that STOPS being emitted; it cannot see one that has never been emitted, so these four have simply never entered the published set.z.date()— it already has anio: 'input'fallback, and since an author writes an ISO string the input shape may well be representable if that fallback applied per-branch rather than per-schema; (b) narrow the comparand unions soDateis not in the projected shape, which is a schema change with runtime consequences the ComparisonOperatorSchema 的 $gt/$gte/$lt/$lte 不含 string,与平台自己只产出字符串的日期宏解析器相矛盾 #5685 docblock argues against at length; (c) accept the gap and make it loud — a ratchet over "exported schema with no emitted JSON Schema", so the next one is caught on the PR that introduces it.Data,Automation,Cloud,Kernel,SystemandUI. Nobody is told which published surfaces those 23 hollow out.Relationship to #15059
#15059 fills the blank Description cells on
filter.mdx— the cells that render because their schema is emitted. This card is about sections that do not render at all. The two do not overlap and the remedy sites are different files. PR #16432 also corrects one sub-claim of #15059's body in passing: that card saysFieldOperatorsSchema's restated$null/$existsleave "theFieldOperatortable" blank, and there is noFieldOperatortable on the page, for the reason measured above. PR #16432 describes those members anyway, so the two copies cannot drift.Refs: #15059 · PR #16432 · #5685 · #6571 · #2978 · #4725