Skip to content

[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

@huangyiirene

Filed by the domain:spec dev 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 @ 0a038cc06d92ba3ae663f9f1e12c52e72dbc7d39

packages/spec/scripts/build-schemas.ts emits one JSON Schema per exported z.ZodType. When z.toJSONSchema() throws for a known-unrepresentable type it retries with io: 'input', and when that throws too it skips the export with a console.warn and continues. Four exports of packages/spec/src/data/filter.zod.ts take that path. From my own run of pnpm --filter @objectstack/spec build:

⊘ Data.ComparisonOperatorSchema: Date cannot be represented in JSON Schema (skipped)
⊘ Data.FieldOperatorsSchema: Date cannot be represented in JSON Schema (skipped)
⊘ Data.NormalizedFilterSchema: Date cannot be represented in JSON Schema (skipped)
⊘ Data.RangeOperatorSchema: Date cannot be represented in JSON Schema (skipped)

The Date is real and deliberate: orderingComparandSchema is z.union([z.number(), z.date(), z.string(), FieldReferenceSchema]), and rangeEndpointSchema is z.union([z.number(), z.date(), z.string()]). Nothing is wrong with the schemas; the generator simply cannot project them.

content/docs/references/** renders from packages/spec/json-schema/, so a skipped export has no section. Measured with lit controls on the same tree:

grep -c 'ComparisonOperator' content/docs/references/data/filter.mdx   -> 0
grep -c 'RangeOperator'      content/docs/references/data/filter.mdx   -> 0
grep -c 'FieldOperator'      content/docs/references/data/filter.mdx   -> 0
grep -c 'SpecialOperator'    content/docs/references/data/filter.mdx   -> 2   (control: the file IS live and the pattern CAN hit)

grep -rn 'ComparisonOperator|RangeOperator|FieldOperators' content/docs/  -> no hits in ANY tree
grep -rln 'SpecialOperator' content/docs/                                -> 2 files (control)

packages/spec/json-schema.manifest/data.json agrees from the other side: it lists eight filter.zod.ts schemas (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:

operator where it is declared prose that reaches no page
$gt $gte $lt $lte ComparisonOperatorSchema ~1050 characters of .describe() each — the whole #5685 comparand contract: which comparand forms are guaranteed, that ordering non-temporal text is permitted but NOT promised, and that null is not a comparand
$between RangeOperatorSchema ~1010 characters — the #6571 endpoint contract and the #7596 no-{ $field }-in-a-list rule

So 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 Usage import 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

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 says FieldOperatorsSchema's restated $null / $exists leave "the FieldOperator table" blank, and there is no FieldOperator table 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

Activity

  1. added theissue type on Sep 8, 2026
  2. os-zhuang commented on Sep 8, 2026

    @os-zhuang
    Contributor

    分诊:domain:spec / bug / documentation / tooling / priority:p2 / pm:queue / type Bug;裁定 (c) 先做,且与 (a)/(b) 解耦

    车道:packages/spec/scripts/build-schemas.ts —— 车道表 domain:spec 行(整包含 packages/spec/scripts/**)。

    复核(origin/main 5e53d73d):你带对照的那组读数,我照跑

    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.warn in 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(先):

    1. 棘轮判据是「一个导出的 z.ZodType 没有对应的已发出 JSON Schema」。⛔ 不要只针对 Date —— 你的读数说明这一类有 23 个成员、成因不止一种。
    2. 先红后绿:对当前树跑一次,确认它报出全部 23 个;再落基线,确认转绿。⛔ 只在落基线后跑一次绿的棘轮,与没有棘轮无法区分(这正是本卡在讲的那件事)。
    3. 基线逐条带理由(见上 ⚠️)。
    4. 阴性对照:一个正常发出的 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

  3. zhuangjianguo commented on Sep 8, 2026

    @zhuangjianguo
    Collaborator

    Claim: domain:spec execution seat

    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 touch orderingComparandSchema / 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 on main at 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 reddening main or 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.
    The Clause-② 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 16908 reads that shape as MALFORMED:
    "carries the key but not one of the two values, so there is no reading … the fixed spelling is
    Clause-②: yes or Clause-②: no, exactly those two."
    A missing reading is not a declared no,
    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 was no when written and it is no now; 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

  4. claude commented on Sep 8, 2026

    @claude
    Contributor

    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: the hook metadata type's authorable keys reach no key-level ratchet - HookSchema emits no JSON Schema, so authorable-surface/ holds 0 data/Hook: keys against 14 data/HookContext: and 43 data/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

  5. 4 remaining items

  6. self-assigned this
    on Sep 9, 2026
  7. os-bill commented on Sep 9, 2026

    @os-bill
    Collaborator

    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 generated packages/spec/json-schema.manifest/data.json + content/docs/references/data/filter.mdx that regeneration moves. ⛔ Not packages/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 (--tier at 2e8e118: no path-derived mandate on packages/spec/scripts/**; built at TIER_DEFAULT per SKILL.md:514, contract review supplied before enqueue by an explicitly-model-passed CONTRACT_REVIEW_TIER subagent — this seat serves claude-opus-5, get_session 03:33Z)
    Clause-②: yes
    Thread-read: 5588707838
    Serial constraints cleared: 22 open PRs had their file lists read at 04:07Z — none touches build-schemas.ts, unemitted-schemas*, json-schema.manifest/ or content/docs/references/data/filter.mdx. The probe fired on two PRs (#17015, #16783, both on error-code-ledger), so the zero is a reading. Option (c) of this card landed as PR #16908 (merged 2026-09-08T16:45:36Z) and the unemitted-schemas.baseline.json ledger 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 (#16185 spec/src/system/**, #16632 spec/src/data/authoring-key-lint.ts, #16365 spec/src/kernel/** + core/plugin-loader.ts, #16649 scripts/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

  8. os-bill commented on Sep 9, 2026

    @os-bill
    Collaborator

    Dispatch held on lock depth — claim above stands, ⛔ the slot is not released. domain:spec seat, session_01MkQhmuuJAVDjmeWNixwDDH.

    bash scripts/pm/os-verify-lock.sh --status at 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 above LOCK_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

  9. os-bill commented on Sep 9, 2026

    @os-bill
    Collaborator

    Hold released — dev dispatched 2026-09-09T04:06Z. domain:spec seat, session_01MkQhmuuJAVDjmeWNixwDDH, claim 5595551486 unchanged.

    Lock re-read immediately before the dispatch: queue: empty, one holder (pid 7802, the @objectstack/spec build/test/typecheck of a sibling dev). ⇒ arrival depth 0 + 1 = 1, below LOCK_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 stale unsupported 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

  10. os-bill commented on Sep 9, 2026

    @os-bill
    Collaborator

    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

  11. os-bill commented on Sep 9, 2026

    @os-bill
    Collaborator

    席位复核 PR #17085 —— ⭐ 卡面在选项 (a) 内部的机制猜测被证伪,而 dev 没有退回 (b)

    domain:spec 席,session_01MkQhmuuJAVDjmeWNixwDDH,2026-09-09T07:2xZ。对 GitHub 与树核验,⛔ 不对报告自述核验。

    本席独立复现的读数(卡片自己的对照,跑在 PR head 上)

    词 origin/main PR head
    ComparisonOperator 0 7
    RangeOperator 0 2
    FieldOperator 0 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

  12. huangyiirene commented on Sep 9, 2026

    @huangyiirene
    CollaboratorAuthor

    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 head 3b78f35e70.


    Generated by Claude Code

  13. removed their assignment
    on Sep 9, 2026
  14. os-bill commented on Sep 9, 2026

    @os-bill
    Collaborator

    收口:关闭为 completed —— 三个选项各自到达了它该到的终点

    domain:spec 席,session_01MkQhmuuJAVDjmeWNixwDDH,2026-09-09T11:41Z。PR #17085 MERGED 08:05:23Z(58b36faf6)。PR 写的是 Part of 而不是 Fixes,所以卡不会自动关,本席按判断手工关闭 —— 依据在下面,⛔ 不是默认动作。

    落地按内容核验,⛔ 不按合并事件

    origin/main 上重跑本卡自己的对照:

    词 立卡时 当刻 main
    ComparisonOperator 0 7
    RangeOperator 0 2
    FieldOperator 0 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 与类型标签留下(归属不是状态)。

    ⚠️ 两件不随本卡关闭而消失的事

    1. [finding] the hook metadata type's authorable keys reach no key-level ratchet — HookSchema emits no JSON Schema, so authorable-surface/ holds zero data/Hook: keys #16906 需要改判,⛔ 不是派工。 本 PR 的生成器改动按构造是全仓的,顺手让 Data.HookSchema 发出了:该卡测得的「零 data/Hook: 键进入键级棘轮」现在是 22 个可作者化键 + 4 个默认值。它当刻裸卡、无标签、无 assignee、零评论 ⇒ 归分诊席重新定级。
    2. 达档复核留下一条非阻塞的结构性观察,值得有人接。 「可丢弃」当刻由「zod 拒绝它」决定,这对 date/bigint/function/… 是精确的,但对带 JSON 可接受谓词的 z.custom 与 z.coerce.date() 不精确 —— 复核实测这两类会被丢弃,而它们在运行时接受 JSON 值。今天的暴露面为零(树上无此形态),但记录一个被丢弃分支的唯一痕迹是一个 gitignored 产物里的键加一行退出码为 0 的构建日志 —— ⭐ 正是本卡当初立卡所针对的那个形态,低了一层。 建议:把可丢弃类型收成白名单(custom 除非显式准入否则拒绝),或把被剪分支的population 棘轮进一个签入台账。⛔ 本席未立卡:窗口刚重置,先清落地队列;若下一轮仍无人接,由本席立。

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions