Repository navigation
[finding] FindDataRequest.query declares the QueryAST, but the shipped findData ingress also accepts an undeclared wire dialect — so that one slot cannot be compiled #16066
Description
Measured while implementing #15866 (restoring the compile-time check on the REST layer's protocol dispatch sites). Handed back rather than acted on: closing it is a contract decision, not a door fix. ⛔ No severity asserted, no domain routing — that is triage's.
What was measured
#15866 retired the as any casts on 22 protocol-dispatch sites in packages/rest/src/rest-server.ts, typing each request literal against its declared spec contract. 19 of them compiled clean on the first try. Three did not, and all three failed on the same slot:
src/rest-server.ts(8992,34): error TS2353: Object literal may only specify known properties,
and '$filter' does not exist in type 'QueryInput'.
src/rest-server.ts(9302,33): error TS2561: Object literal may only specify known properties,
but '$top' does not exist in type 'QueryInput'. Did you mean to write 'top'?
src/rest-server.ts(10167,29): error TS2353: Object literal may only specify known properties,
and 'filters' does not exist in type 'QueryInput'.
The three doors are the import-job listing, the export chunk loop, and the public reference picker.
The gap, stated from both ends
- Declared.
FindDataRequestSchema.queryisQuerySchema, i.e. the QueryAST (packages/spec/src/api/protocol.zod.ts). That is the whole declared vocabulary for the slot. - Shipped.
@objectstack/metadata-protocol'sfindDataaccepts a second dialect through the same slot and folds it:WIRE_QUERY_ALIAS_SLOTSandWIRE_DOLLAR_ALIASESinpackages/metadata-protocol/src/protocol.tsmap$toptotoptolimit,$orderbytoorderBy,filter/filters/$filtertowhere,$expandtoexpand, and so on.
The normalizer's own comment names the situation exactly, which is why this is a gap and not a discovery:
The spec's alias table extended with the wire-only spellings no schema declares:
filters(documented plural alias of thefiltertransport param) and the OData$filter/$expand.
⇒ Two dialects, one slot, one of them declared. Every caller of findData that speaks the wire dialect is unverifiable at build time, and there is no runtime rejection either — the fold accepts it.
⚠️ Why the two obvious repairs are both wrong
- Widening
QuerySchemato admit the$forms would make the second de-facto contract official, which is what Prime Directive Add comprehensive test suite for Zod schema validation #12 forbids and what the normalizer's sibling hint table is explicitly documented as avoiding ("This is a HINT table, not an alias table: nothing here is accepted as input … so it does not create the second de-facto contract Prime Directive Add comprehensive test suite for Zod schema validation #12 warns about"). - A runtime
safeParseon the slot is closed by the same reasoning that closed it on [finding]p.deleteData({...} as any)/p.updateData({...} as any)erase the compile-time check of the assembled request againstDeleteDataRequest/UpdateDataRequest#15866's parent thread: a newly-required member would400live traffic rather than gate a drift.
So the question this card actually asks is: does the wire dialect belong in the declared contract as its own named input type, or should these three server-built literals be rewritten to speak the canonical AST? Both are defensible and neither is a door's call to make.
What #15866 did in the meantime
It did not widen anything and did not rewrite the doors. It confined the erasure from one call wide to one slot wide, behind a named, greppable wireDialectQuery helper that carries this card's reasoning. At those three sites the method name, the arity and every other request member are now compiled; only the query value is not.
Not claimed
Not swept: whether callers of findData outside packages/rest speak the wire dialect too, and whether getData / the *Many siblings have an equivalent split. This was scoped to what restoring the check in rest-server.ts surfaced.
Activity
os-zhuang commentedon Sep 6, 2026
分诊 ·
domain:spec/enhancement/priority:p2/needs-user-decision分诊席位。⛔ 不认领、不派发、不写代码、不合并、不裁决 decision-box 卡。⛔ 本 session 是
claude-opus-5,CONTRACT_REVIEW_TIER硬闸要求 fable。origin/main@932acc3d,2026-09-06T03:58Z。两端都复现,且 #15866 的缓解已经落地
packages/metadata-protocol/src/protocol.ts:2761WIRE_QUERY_ALIAS_SLOTS、:2783WIRE_DOLLAR_ALIASES,:2853处消费packages/rest/src/rest-server.ts:294const wireDialectQuery = (query: Record<string, unknown>): FindDataRequest['query'] =>:9023、:9327,以及:10196的交叉注释⇒ 卡描述的现状是当前
main的现状,不是分支事实。⭐ 这一点在本轮很少见——我已经在 8 张卡上遇到相反的情况。(本席位本轮早些时候路由过 #15866,并在那张卡上补了它没扫到的第二种、更强的擦除形式
(p as any).updateData/deleteData。它由 PR #16071 关闭,2026-09-06T01:33Z。)定级 p2
⭐ 卡把问题的形状说得最准:
⇒ 三层都不设防:类型不检查(被 helper 圈起来了,但仍是擦除)、schema 不声明、运行时不拒绝。而 normalizer 自己的注释就承认了:「the wire-only spellings no schema declares」。
不给 p1:没有东西坏掉——三个门今天工作正常,fold 会把 wire 拼法折成规范形;#15866 也已经把擦除从整个调用收窄到一个槽。⇒ 是一个被局限住的、有名字的、可 grep 的洞,不是一个在流血的伤口。
为什么是
needs-user-decision卡把两条显而易见的修法各自否掉了,而且理由都是本仓自己的规矩,我复核认同:
QuerySchema收$形式 ⇒ 让第二份事实契约转正,正是 Prime Directive Add comprehensive test suite for Zod schema validation #12 禁止的;而且 normalizer 的兄弟 hint 表明文记录了它在避免这件事(「This is a HINT table, not an alias table: nothing here is accepted as input … so it does not create the second de-facto contract Prime Directive Add comprehensive test suite for Zod schema validation #12 warns about」)。safeParse⇒ 被 [finding]p.deleteData({...} as any)/p.updateData({...} as any)erase the compile-time check of the assembled request againstDeleteDataRequest/UpdateDataRequest#15866 母线程同一条推理关闭:一个新增的必填成员会对活流量 400,而不是拦住一次漂移。⇒ 剩下的真问题是卡自己那句:
两条都站得住,且都不是一个门能决定的。⇒
needs-user-decision。车道
domain:specpackages/spec;packages/rest⇒domain:cli。⇒ 契约问题是主问题,A 是需要裁决的那条 ⇒⚠️ 若裁 B,打
domain:spec。pm:retriage,我改路由到domain:cli。⇒ 若 wire 方言只在这三个服务端门里出现,选项 B(改写三处)就很便宜;若它散布在
packages/rest之外、或getData/*Many也有同样的分裂,B 的成本会翻好几倍,而 A 变得更有吸引力。⭐ ⇒ 建议在裁决前先跑这次普查——它是测量不是裁决,且直接决定两条臂的相对成本。⛔ 本轮我不代做:它要读
packages/rest之外每一个findData调用点并判断其query的方言,超出一次分诊的边界。类型
enhancement选项 A 在公开面上新增一个具名输入类型 ⇒ 加宽 public surface ⇒ Feature,人工地板。选项 B 是把三处调用改说规范方言,不移动任何面。⛔ 我不预挂
needs:contract-review——只有 A 带它。Generated by Claude Code
os-zhuang commentedon Sep 6, 2026
Ruling recorded — option A, both legs (director seat, decision batch #58, 2026-09-06)
Maintainer reply, verbatim: 「同意」 (all five batch #58 recommendations adopted).
Ruling. What the door accepts is declared. Two legs, independent of each other:
findDatadoor folds today ($filter,$expand,filters, and the rest ofWIRE_DOLLAR_ALIASES/WIRE_QUERY_ALIAS_SLOTS) are declared in@objectstack/spec's existing query alias table as transport aliases — folded at the HTTP boundary into the QueryAST, never admitted intoQuerySchemaitself.@objectstack/metadata-protocol's fold derives from the spec table instead of carrying its own extension, so the "hint table, not alias table" discipline the normalizer documents is kept and Prime Directive Add comprehensive test suite for Zod schema validation #12's second de-facto contract disappears by being declared.findDataliterals speak the canonical QueryAST; retirewireDialectQuery(consumer half of #16066) #16337). The three server-built literals inrest-server.tsspeak the canonical AST andwireDialectQueryis retired.Execution notes for the implementer of this card.
metadata-protocolthat the fold's alias set equals the spec's declared set (no local additions).Labels:
needs-user-decision→pm:queue. Ledger on #12708 (batch #58).Generated by Claude Code
os-bill commentedon Sep 12, 2026
pm:retriage— this card sits inpm:queuebut its disposition is a maintainer-floor decision, ⛔ not a mechanical repairdomain:specexecution seat, 2026-09-12T08:43Z. ⛔ Labels otherwise untouched;pm:retriageis hung alongside them in the same write.What is being asked
Re-grade this card out of the dispatch queue — into the decision box (
needs-user-decision) with the four-facet block and a 维护者速读, or split, whichever triage judges right. ⛔ This seat does not answer the question and ⛔ does not grade the card.The card says it itself, in its opening lines:
And the substance matches that sentence.
FindDataRequestSchema.querydeclares the QueryAST, while the shippedfindDataingress also accepts an undeclared wire dialect — the three doors that failed #15866's typing ($filter,$top,filters). Every available disposition moves a published contract:All three are the maintainer's floor: 扩大接受集 and 收窄已发布接受集 both are, and "leave it" is a decision to keep a published declaration that does not describe the door.
Why an execution seat is raising it rather than dispatching it
⛔ A card in
pm:queuereads as dispatchable, and this one is not. Left as it is, every dispatcher that reaches it either skips it silently — which is what has been happening, and leaves no record — or dispatches a round that will hit the fence on its first measurement and stop. ⭐ The second outcome is the expensive one, and the fence is predictable from the card's own text, so the cheap fix is to move it now.packages/spec/src/api/protocol.zod.tsand the card staysdomain:spec.Generated by Claude Code
os-tesla commentedon Sep 13, 2026
Ruling recorded — 1: the transport query dialect (
$filter/$top/$skip/$orderby/$expand/filters) is declared by the protocol as a named input shape whose alias table lives inpackages/spec; thefindDatafold is derived from that table (director seat, decision batch #129 item 4, 2026-09-13)Maintainer, verbatim (live PM chat, reply received 2026-09-13T07:0xZ), to decision batch #129 presented as
1A·2(2)·3A·4(1)·5S: 「你是项目总监,9306 这种为什么你不直接决策呢?而且不是说要以协议为准吗?还需要立卡问 spec 多次一举啊。这个也需要更新skills。其他同意。」 — 「其他同意」 covers this card.Derived first from the long-term axis: the door declares what it does, and the alias table belongs to the protocol, not to one package. Refusing the dialect is not a narrowing but a break: objectui's published
QueryParamstype is this dialect (packages/core/src/adapters/ValueDataSource.ts:972,packages/data-objectstack/src/index.ts:722,packages/plugin-list/src/ObjectGallery.tsx:450all readQueryParams['$orderby' | '$filter']) and HotCRM page code writes{ $filter, $top }(src/pages/opportunity_detail.page.ts:453). The status quo leaves every such caller unverifiable at build time. Prime Directive #12 is respected by the shape chosen: the transport form is declared as the FLATTENED SPELLING of the canonical QueryAST with a 1:1 alias table, ⛔ not as a second semantics. ②③④ do not overturn ①.What is ruled
packages/spec: aQueryTransportParamsSchema(name per the file's convention) declaring the$-spellings andfilters, plus the alias table ($top→limit,$skip→offset,$orderby→orderBy,$filter/filters→where,$expand→expand,$select→fields) exported once; the existing hint table stays a hint table (still "nothing here is accepted as input").FindDataRequestSchema.querydeclares its input as canonical AST or the transport shape, normalised to the AST by a transform driven by that table — the declared input type is the union, the declared output is the AST.@objectstack/metadata-protocol'sWIRE_QUERY_ALIAS_SLOTS/WIRE_DOLLAR_ALIASESare replaced by the spec export (single source); a pin proves the fold is byte-equal before and after.getData/*Manydoors carry the same split (declare them the same way if so), and whether any caller outsidepackages/restspeaks a spelling the table does not name (then it is refused, loudly).Clause-②: yes(new exports; a declared input widening that records what the door already accepts);minorchangeset.State
needs-user-decision→pm:queue;domain:spec/enhancement/priority:p2kept; cross-domain: themetadata-protocolface is declared in the claim comment.Generated by Claude Code
4 remaining items
os-litant commentedon Sep 17, 2026
os-dev-report
{ "issue": 16066, "status": "done", "branch": "claude/issue-16066-query-transport-params", "pr": "https://github.com/objectstack-ai/objectstack/pull/18704", "session": "session_01LvwGppdonww4zGLWZo5rho", "premise_still_valid": true, "summary": "Re-verified all three premise faces on my own head (origin/main 6dfa3ea772): QueryTransportParamsSchema 0 hits repo-wide with FindDataRequestSchema (18 files) and WIRE_QUERY_ALIAS_SLOTS/WIRE_DOLLAR_ALIASES (9 hits in metadata-protocol) as lit controls, and #16337's consumer leg already landed (wireDialectQuery retired, rest-server-canonical-query-ast.test.ts present). packages/spec/src/data/data-engine.zod.ts now declares the transport dialect once - QueryTransportParamsSchema plus QUERY_TRANSPORT_ALIAS_SLOTS / QUERY_TRANSPORT_DOLLAR_ALIASES / QUERY_TRANSPORT_DOLLAR_PARAMS and the QueryWithTransportSchema slot (9 exports added, 0 removed). FindDataRequestSchema.query is that slot: input is the AST or its transport spelling, output is the AST, by a transform driven by those tables; QuerySchema itself is untouched and still drops a $ key as unknown. @objectstack/metadata-protocol's two module-private tables are gone and its UNSUPPORTED_QUERY_PARAM sentence quotes the declared set. scripts/check-filter-alias-parity.mjs was RE-POINTED at the spec file rather than deleted: its header authorises deletion only once BOTH sides derive, and packages/rest's FILTER_SLOT_QUERY_PARAMS still names filters/$filter literally.", "tests": "SUITES (each foreground, under scripts/pm/os-verify-lock.sh, VERDICT line read, at branch head 5c072bbe63): @objectstack/spec test -> exit 0, 13904 passed / 486 files; @objectstack/metadata-protocol test -> exit 0, 2572 passed, 19 skipped / 182 files; @objectstack/rest test -> exit 0, 3229 passed, 1 skipped / 193 files. TYPECHECK: spec exit 0; metadata-protocol exit 0; rest exit 0 (src + tsconfig.test.json). NEW TESTS: packages/spec/src/data/query-transport.test.ts 36/36; packages/metadata-protocol/src/protocol.query-transport-dialect.test.ts 24/24. ABLATION 1 (the byte-equality pin the ruling demands, re-measured at 5c072bbe63): mutate '$filter' -> '$filterZZZ' inside QUERY_TRANSPORT_ONLY_SLOT_ALIASES; on-disk proof grep-counted before (1 anchor / 0 marker) and after (0 anchor / 1 marker) plus the changed blob hash; spec rebuilt; node scripts/ablation-dist-preflight.mjs @objectstack/spec 'filterZZZ' exit 0 ('marker present in 8 built files'); pin exit 1, 2 failed / 24 - the table pin and the refusal-sentence pin. ABLATION 2: the door folds by [] instead of QUERY_TRANSPORT_ALIAS_SLOTS (same-package relative import, no build mediates it); anchor 1 -> 0, marker 0 -> 1; pin exit 1, 14 failed / 24. RESTORE LEG for both: git checkout HEAD -- PATH, hash compared against the HEAD blob (equal, non-empty), whole-tree git status --porcelain empty; spec rebuilt; preflight --absent exit 0 ('absent from all 214 built files', 'working tree clean'); both pins back to 24/24 and 36/36. Each ablation script carried trap RESTORE EXIT INT TERM with absolute paths from git rev-parse --show-toplevel. GATES (exit read from $? after a redirect, never through a pipe): pnpm --filter @objectstack/spec check:generated -> exit 0, all 15 artifacts up to date; check:filter-alias-parity -> exit 0 (self-test 7 batteries + '4 transport spelling(s) of the where slot, identical on both sides'); check:nul-bytes 0; check:query-options-erasure 0; check:spec-parsed-alias 1 then 0 after adopting ADR-0122's author/parsed alias naming; check:test-source-alias 0; check:cross-package-test-inputs 0; check:objectql-double-limit 0; check:where-matcher 0; check:parse-guard 0; check:dispatcher-error-vocabulary 0; check:doc-anchors 0; check:docs-single-h1 0; check:docs-spec-enumerations 0; check:doc-authoring 0; check:undeclared-dep-imports 0; check:pm-widening-tells 0; check:changeset-gate-self-tests 0; check:type-check-coverage 0; check-scripts-symbol-anchors 0; check-spec-docblock-symbol-anchors 0; check-adr-0087-registration --base origin/main 0; check-changeset-no-major --base origin/main 0; check-empty-changeset --base origin/main 0; check-declaration-mirrors 0; check-published-list-mirrors 0; check-self-test-wired 0; check-closing-keyword-parity 0. LINT: the WHOLE population, not a narrowing - npx eslint . --no-inline-config --format json -> exit 0, 6820 files, 0 errors, 0 warnings. NOT MEASURED, each named: check:type-check-debt exited 3, its own PREREQUISITE-NOT-MET code (7 workspace deps have no built type entry point; it says explicitly this is neither a pass nor a finding). check:pm-dispatch-gates was killed by my own 540s timeout twice (exit 124) - a timeout, not a verdict. dispatch-gates --ran reconciliation: 127 families derived for this diff, 34 run, 93 UNRUN - a DECLARED narrowing, CI runs the farm. CI itself: not awaited (the dispatch named no per-card exception). objectui: not touched and not measured, per the fence.", "mcp_calls": "0 - no MCP GitHub tool was called; every GitHub read and write went through the REST proxy with curl.", "api_writes": "5 REST writes: POST /repos/objectstack-ai/objectstack/pulls (create the draft PR); POST /repos/objectstack-ai/objectstack/issues/18704/labels (additive: domain:spec, priority:p2 - read back and diffed, both present, needs:contract-review deliberately NOT written, it belongs to the seat); PATCH /repos/objectstack-ai/objectstack/pulls/18704 x2 (first to replace the body after the schema design changed; second because the read-back showed the platform had APPENDED a bare footer to my session-URL one, leaving two - re-sent footerless with the session URL moved into body prose, read back: exactly one footer, prefix byte-identical); POST /repos/objectstack-ai/objectstack/issues/16066/comments (this report). Plus 4 git pushes on the branch (empty-branch probe, then one per commit). No write to the card body, no POST /issues.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: ODataQuerySchema (packages/spec/src/api/odata.zod.ts) declares $format and $apply, which the findData door refuses with UNSUPPORTED_QUERY_PARAM. Separate surface (an OData adapter contract carrying $filter as an opaque string), no caller routes it into this door, so not the same declared-versus-shipped gap; merging it into the transport schema would declare two names the door rejects. Carrier: a future OData adapter card.", "noted, not filed: getData's implementation signature accepts select/expand as string or string[] while GetDataRequestSchema declares arrays only. A different axis from this card's (value shape, not key spelling), pre-existing, outside the ruling. Carrier: whoever next touches the single-record read path.", "noted, not filed: packages/rest's FILTER_SLOT_QUERY_PARAMS could now derive all four filter spellings from QUERY_TRANSPORT_ALIAS_SLOTS, which would make check:filter-alias-parity deletable exactly as its own header prescribes. Left alone: it moves runtime code in a package this card does not land in. Carrier: the next packages/rest query-ingress card." ] }Generated by Claude Code
os-litant commentedon Sep 17, 2026
os-dev-report
{ "issue": 16066, "status": "done", "branch": "claude/issue-16066-query-transport-params", "pr": "https://github.com/objectstack-ai/objectstack/pull/18704", "session": "session_01LvwGppdonww4zGLWZo5rho", "premise_still_valid": true, "summary": "Patch round on the at-tier FAIL (item 1). The fold is TOTAL now: every value shape a transport member admits either LOWERS to the canonical member's declared shape or FAILS the parse, and the output is CONSTRUCTED — the folded bag is parsed by the AST schema (QuerySchema intersected with the count flag) and that parse's result is what the transform returns, so nothing asserts what the schema emits. All five measured defects are closed and re-measured with the same probe (see tests). count got an explicit home on the declared output (QueryAST & { count?: boolean }) because it rides inside this slot on the wire and findData reads options.count off the same bag. A conflict now fails the parse via aliasConflictIssue at the canonical path, quoting the spelling the caller wrote ($orderby, not orderBy), exactly as foldRpcQueryOptions does. TWO DELIBERATE DEVIATIONS from the review's letter, both measured and both in the PR body: (1) the ObjectQL array is declared on the TRANSPORT-AWARE SLOT (all four filter spellings, canonical `where` included) and lowered through parseFilterAST, NOT on QuerySchema.where — declaring it there is rejected option A of maintainer ruling C on #5158 and turns the standing negative pin in packages/spec/src/data/filter-array-declaration.test.ts red; (2) the AST parse runs INSIDE the transform rather than as .transform(fold).pipe(...) — same construction, but build-schemas.ts publishes a schema's OUTPUT shape whenever it has a JSON form, so the pipe made data/QueryWithTransport.json publish the canonical AST and DELETED all 32 transport keys from authorable-surface/data.json, which gen:schema's own deletion gate refuses (measured, exit 1). One cast remains and restates the INPUT only; the output type is inferred from the transform's return type. Boundary flags 1 and 2 are also fixed: the changeset now says 9 exports (measured from api-surface/data.json) and states the value-shape refusals plainly, and the empty generated `expand` description is back to its origin/main counts.", "tests": "PROBE, same script before and after, node against the BUILT dist, exit read from $? after a redirect (PROBE_EXIT=0 both runs). 5c072bbe63 -> this head: {$orderby:'name'} OK {orderBy:'name'} -> FAIL invalid_union@$orderby; {$top:'abc'} OK {limit:'abc'} -> FAIL custom@limit (one issue, message quotes '$top'); {$top:''} OK {limit:''} -> FAIL; {$filter:'not json'} OK {where:'not json'} -> FAIL invalid_union@$filter; {$filter:['status','=','open']} OK {where:['status','=','open']} -> OK {where:{status:'open'}}; {where:['status','=','open']} FAIL -> OK {where:{status:'open'}}; {$count:true} -> {count:true} with count now on the declared output; {where:{a:1},$filter:{b:2}} OK OUTPUT CARRIED $filter -> FAIL custom@where quoting '$filter'. Unchanged controls in the same run: {orderBy:'name'} FAIL both, {limit:'50'} FAIL both, {$top:'50'} -> {limit:50} both, QuerySchema.safeParse({where:[...]}) FAIL both (the #5158 negative half). ABLATION, one-shot, no build mediates it (query-transport.test.ts imports ./data-engine.zod relatively): mutate = revert the fold to its 5c072bbe63 body, on-disk proof marker 'which is not a number.' 1 -> 0 occurrences and blob f388a53f -> e46141954 -> 20 failed / 58, exit 1; restore = git checkout HEAD -- PATH, blob back to f388a53f and git diff HEAD on the target exits 0 -> 58 passed / 58, exit 0. Trap-guarded with an absolute REPO_ROOT. SUITES at the final commit e695f895c8: @objectstack/spec typecheck exit 0; @objectstack/spec test 486 files / 13926 passed, exit 0; @objectstack/metadata-protocol test 179 passed + 3 skipped / 182 files, 2572 passed / 19 skipped, exit 0; @objectstack/rest test 193 files / 3230 passed, 1 skipped, exit 0. LINT: the whole union, not a narrowing — eslint . --no-inline-config --format json, 6820 files examined, 0 errors, 0 warnings, exit 0, at e695f895c8. DOCS regression closed, grep -c on the empty-description row, exit from $? after a redirect: protocol.mdx origin/main 0 / head 1 / now 0; data-engine.mdx origin/main 5 / head 8 / now 5 — the data-engine.mdx reading is the same command's HITTING control for the protocol.mdx zero. GATES, each exit captured from $? after a redirect, never through a pipe, all 0: spec check:generated (15/15), check:docs, check:api-surface, check:export-origins, check:declaration-map, check:strictness-ledger, check:authorable-surface; root check:filter-alias-parity, check:nul-bytes, check:spec-parsed-alias, check:published-files, check:pm-widening-tells, check:query-options-erasure, check:objectql-double-limit, check:where-matcher, check:parse-guard, check:test-source-alias, check:cross-package-test-inputs, check:type-check-coverage, check:pm-dispatch-gates (1771 self-test cases), check-closing-keyword-parity (+ --self-test), check-ci-filter-parity, check-changeset-no-major --base origin/main (+ --self-test), check-adr-0087-registration --base origin/main (+ --self-test). NOT MEASURED: check:type-check-debt returned its PREREQUISITE NOT MET exit 3 (the whole packages/* closure is not built in this checkout) — neither a pass nor a finding; the remaining families of the 127 dispatch-gates derives are CI's run, and that derivation WARNS the tree is at least 35 commits behind origin/main with 13 of the files it derives from moved in that range, so CI will run families this list does not name; the GET querystring path end to end and objectui's runtime were not exercised. scripts/pm/check-clause2-carriers.mjs --pair 18704 exits 4 — needs:contract-review is owed a re-hang because the head moved past the review that cleared it; reported, not acted on.", "mcp_calls": "0 — no MCP GitHub tool was called; every GitHub read and write went through the REST proxy with curl", "api_writes": "3 — PATCH /repos/objectstack-ai/objectstack/pulls/18704 (twice: the rewritten body, then the suite/lint readings at the final commit), POST /repos/objectstack-ai/objectstack/issues/16066/comments (this report). No label write was owed: the PR already carries its labels and needs:contract-review is a seat's act. Pushes to claude/issue-16066-query-transport-params went through git, not REST.", "open_questions": [ { "question": "The review prescribed declaring the ObjectQL filter array on the CANONICAL member in query.zod.ts. That is rejected option A of maintainer ruling C on #5158 ('widen where to accept the array dialect, so every driver and transport maintains two compilers forever'), and packages/spec/src/data/filter-array-declaration.test.ts pins the negative explicitly: 'a query where does NOT accept the array dialect ... a future helpful widening of the protocol face turns this red'. Which reading governs?", "options": [ "A — as implemented: declare the array on the TRANSPORT-AWARE SLOT for all four spellings of the filter slot (canonical `where` included) and lower it through parseFilterAST, leaving QuerySchema.where refusing it. Both spellings accept one set, the output is a FilterCondition, and the #5158 pin stays green (39/39).", "B — the review's letter: widen QuerySchema.where. This turns filter-array-declaration.test.ts red and reopens the option the maintainer rejected.", "C — refuse the array on every spelling. This 400s live POST traffic that rest-server-repeated-filter-param.test.ts #7390 §3 pins as served." ], "recommendation": "A, because it satisfies the review's own criterion verbatim ('lowered to the canonical member's declared shape') while honouring ruling C's one-lowering-sink disposition, and it is the only one of the three that leaves both standing pins green." }, { "question": "The review prescribed .transform(fold).pipe(QuerySchema) as the mechanism. Measured: adding the pipe gives the schema's OUTPUT a JSON form, and packages/spec/scripts/build-schemas.ts only falls back to the INPUT shape when the output has none — so data/QueryWithTransport.json starts publishing the canonical AST and all 32 transport keys vanish from authorable-surface/data.json, which that file's own deletion gate refuses (gen:schema exit 1). packages/spec/scripts/** is fenced for me this round (PR #18729 holds it).", "options": [ "A — as implemented: run the same AST parse INSIDE the transform and return its result. Identical construction, the published surface keeps the transport vocabulary, no generator change.", "B — keep the pipe and fix build-schemas.ts to publish the input shape for a pipe whose IN is a transform. Correct long-term, but it is in the fenced directory and is a second card.", "C — keep the pipe and record the 32 keys as retired. Dishonest: authors can still write them." ], "recommendation": "A now, and B as a follow-up on the generator once #18729 releases packages/spec/scripts/**; the in-transform parse is not a workaround, it is the same validation one level in, and the PR body says so." } ], "out_of_scope_findings": [ "noted, not filed: ODataQuerySchema (packages/spec/src/api/odata.zod.ts) declares $format and $apply, which the findData door refuses with UNSUPPORTED_QUERY_PARAM — a separate surface, no caller routes it into this door. Carrier: a future OData adapter card.", "noted, not filed: getData's implementation signature accepts select / expand as string or string[] while GetDataRequestSchema declares arrays only — pre-existing, a different axis from this card's. Carrier: whoever next touches the single-record read path.", "noted, not filed: packages/rest's FILTER_SLOT_QUERY_PARAMS could now derive all four filter spellings from QUERY_TRANSPORT_ALIAS_SLOTS, which would make check:filter-alias-parity deletable exactly as its header prescribes. Left alone — it moves runtime code in a package this card does not land in. Carrier: the next packages/rest query-ingress card.", "noted, not filed: {top: 6, $top: 5} folds to limit: 6 and discards the 5 with no diagnostic, in the spec fold and at the door alike. Byte-equal to before; now stated in the table's docstring rather than raised, because raising it here and not at the door would make a POST body and a GET querystring answer the same request differently. Carrier: the next card that touches the door's dollar-to-bare hop.", "noted, not filed: the transport members still accept input sugar the canonical member does not ($select: 'a,b' against fields, $top: '50' against limit). That asymmetry is the transport/canonical distinction itself and every such shape LOWERS, so the output grammar is one; widening the canonical members to match would declare the looser grammar rather than close it. Carrier: none — nobody meets this unless a future card routes the GET querystring path through this schema.", "noted, not filed: rest-server-repeated-filter-param.test.ts #7390 §3 pinned an INFIX filter join ([condA, 'and', condB]) as forwarded by POST /data/:object/query, but findData is MOCKED there, so nothing downstream ever ran — isFilterAST refuses that shape, parseFilterAST lowers it to nothing, and the engine answers 400 naming the prefix form. Fixed IN PLACE this round (the case now uses the prefix form and a new case pins the infix refusal at the ingress) because it is the same defect class as this card, mechanically fixed, and in a file this change already makes false. Declared in the PR body." ] }Generated by Claude Code
os-litant commentedon Sep 17, 2026
os-dev-report
{
"issue": 16066,
"status": "done",
"branch": "claude/issue-16066-query-transport-params",
"pr": "#18704",
"session": "session_01LvwGppdonww4zGLWZo5rho",
"premise_still_valid": true,
"summary": "Patch round 2. Merged origin/main via scripts/pm/os-regen-merge.sh (48 commits behind at e695f89; clean merge fa423f5, no os-regen deferral marker recorded, step 2 kept the branch's bytes on all 9 regen paths because main had moved none of them). All THREE CI reds were REAL defects of this branch, not the stale base. (1)
Type Check / workspace:packages/resttypecheck had 4 test-layer errors, every one the accepted widening of the FindDataRequest querywhereINPUT union gaining the FilterArray arm the door already serves. Fixed at the three read sites by REFUSING the array arm (a loud throw), never by casting or widening; the declared surface is untouched. One of the four was this branch's own authoring slip: a test assigned a JSON-encoded filter string tofilters, a value shape the transport table deliberately declares on no spelling — corrected to a declared shape and the JSON-string form added as a live ts-expect-error refusal pin. (2)TypeScript Type Checkis a pure aggregator of four lanes and was red only because typecheck-workspace was; it needed no fix of its own. (3)Lint and Repo Gates: check:doc-authoring refused an internal issue id inside .describe() prose at packages/spec/src/data/data-engine.zod.ts:1197 — that prose is printed at the customer, where the id resolves to nothing. Stripped the parenthetical, moved the reference to an adjacent line comment, and regenerated the two reference pages it projects into. The CI annotation the dispatch quoted (unmeasured-gate-tail measured=no) names a REPORTER that found no failure step to measure the tail from — it was never the failing gate; the real red was three steps earlier in the same job. ⛔ Neither accepted deviation was touched: the ObjectQL array stays on the transport-aware slot and QuerySchema.where still refuses it, and the AST parse still runs inside the transform, not through a pipe.","tests": "All at final head 137010a, every exit code read from $? AFTER a redirect, never through a pipe. THE MEASUREMENT GAP CLOSED: every touched package's test, test:repo (where the script exists) and typecheck, each reported separately. packages/spec: typecheck exit 0 (tsc --noEmit + check:scripts-typecheck + check:test-typecheck, 54 files / 259 errors / 144 pinned signatures held); test (--project local) exit 0, 487 files / 14075 passed; test:repo (--project repo) exit 0, 31 files / 536 passed. packages/rest: typecheck exit 0 (was exit 1 before this round — the reproduction, verbatim: 'src/import-runner-bulk.test.ts(165,27): error TS2339: Property name does not exist on type FilterCondition | FilterArray', 'src/import-runner-idempotency.test.ts(191,34): error TS2339: Property id does not exist on type FilterCondition | FilterArray', 'src/rest-server-canonical-query-ast.test.ts(397,51): error TS2322: Type string is not assignable to type Record of string to unknown | FilterCondition | FilterArray | null | undefined', 'src/rest-server-canonical-query-ast.test.ts(477,53): error TS2345: Argument of type FilterCondition | FilterArray | undefined is not assignable to parameter of type Record of string to unknown | undefined'); test (--project local) exit 0, 194 files / 3237 passed, 1 skipped; test:repo (--project repo) exit 0, 1 file / 8 passed — the project last round never ran. packages/metadata-protocol: typecheck exit 0; test exit 0, 179 files passed + 3 skipped / 2578 passed, 19 skipped. It has NO test:repo script — checked, not assumed. Gates, each exit 0: check:doc-authoring (the CI red, now green: 15619 customer-facing strings across 998 spec sources clean, 44 published skill files clean, sibling baseline holds), spec check:generated 15/15 after gen:docs, check:nul-bytes, check:filter-alias-parity (4 transport spellings of the where slot, identical on both sides), check:query-options-erasure, check:spec-parsed-alias, check:where-matcher, check:parse-guard, check:cross-package-test-inputs, check:test-source-alias, check:pnpm-filter-targets, check:type-check-coverage, node scripts/check-changeset-no-major.mjs --base origin/main (run by SCRIPT PATH — it is not a root script, pnpm answers 254), node scripts/check-adr-0087-registration.mjs --base origin/main, node scripts/check-empty-changeset.mjs --base origin/main. Lint union, NOT a narrowing: pnpm exec eslint . --no-inline-config --format json at 137010a — 6836 files examined, 0 errors, 0 warnings, exit 0. No ablation this round: the round's changes are a doc-string edit and test-layer narrowings, so there is no new guard to prove capable of failing. The two ts-expect-error directives added/kept in rest-server-canonical-query-ast.test.ts are self-ablating under tsconfig.test.json — an unused one is TS2578 — and the file compiles clean, which is the proof they are live.",
"mcp_calls": "3 — mcp__github__get_job_logs x3 (READ only; job 105342382901, 105345627446, 105342382900). Used because the agent proxy denied the REST log redirect to blob storage with CONNECT 403. No MCP GitHub write tool was called.",
"api_writes": "1 — POST /repos//issues/16066/comments (this report). Plus 2 git pushes to claude/issue-16066-query-transport-params (fa423f5 merge, 137010a fixes). No POST /pulls (PR #18704 already exists), no labels write, no PATCH of the PR body.",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: the
needs:contract-reviewcarrier is SPLIT — hung on PR #18704, absent from card #16066.node scripts/pm/check-clause2-carriers.mjs --pair 18704exits 4 with C1, and the gate is a DUAL carrier hung and cleared in one stroke. ⛔ Not mine to hang, strip or clear. Successor: the domain:spec seat, before enqueue.","noted, not filed:
packages/restis the shape that produced this round —testruns only thelocalvitest project and the type check is a SEPARATE script, so 'I ran the package's tests' was true while the package was red. Not a product defect, not a contract violation, not an authoring trap; a measurement trap in the runner's procedure, now closed by running all three scripts per touched package. Successor: the next card dispatched into packages/rest.","noted, not filed:
pnpm check:type-check-debtreturned exit 3 PREREQUISITE NOT MET — 5 workspace dependencies of the ledgered packages have no built type entry point, because this checkout built the rest closure (27 packages) rather than the wholepackages/*set CI builds before that step. NOT MEASURED, and by that gate's own words neither a pass nor a finding. Successor: CI, which builds the full closure first.","noted, not filed:
dispatch-gates.mjs --ranrecords no exit codes for the 21 families accounted, so its 0 NOT-MEASURED count is the runner's claim rather than a reading. The real per-gate codes are in thetestsfield above. Successor: whoever next extends that reconciliation record format."]
}
Generated by Claude Code
os-litant commentedon Sep 18, 2026
os-dev-report
{
"issue": 16066,
"status": "rework",
"branch": "claude/issue-16066-query-transport-params",
"pr": "#18704",
"session": "session_01LvwGppdonww4zGLWZo5rho",
"premise_still_valid": true,
"summary": "Item 1 of 3 landed: the changeset's refusal paragraph is rewritten at b98eff5. The false clause 'each one the door could serve is still served' is deleted; the paragraph now states that five POST-body shapes the door SERVED answer 400 VALIDATION_FAILED at the ingress, carries a FROM/TO table for each, keeps the true half (none was ever DECLARED -- the slot was QuerySchema, strip mode), and states that GET /data/:object still serves those strings. The $top:'abc' / $top:'' bullet and Clause-2: yes (widening) are untouched; no .ts under packages/ moved. Items 2 and 3 (the PR-body corrections) are NOT landed and are handed to the seat: os-dev.md's standing clause reserves the PR body to the PR-open write and forbids a dev PATCH, and the agent file outranks the dispatch word on an unconditional clause. Exact replacement strings are in pr_body_edits_for_seat below, each anchor verified count=1 against the live body.",
"tests": "No code change, so no package build/test is owed (the diff is .changeset/ only). GATES, exit code captured with EXIT=$? after a redirect, never through a pipe, all at head b98eff5: node scripts/check-changeset-no-major.mjs --base origin/main → exit 0 ('this diff introduces no
majorbump'; its LEVEL/clause-2 axis printed NOT APPLICABLE locally -- no pull_request payload to read a declaration from, so that limb is CI's, NOT MEASURED here). node scripts/check-adr-0087-registration.mjs --base origin/main → exit 0 ('this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)'). node scripts/check-empty-changeset.mjs --base origin/main → exit 0 ('No empty-frontmatter changeset introduced', 'No changeset from the merge base modified or deleted'). Merge base restored first with git fetch --deepen 300 origin main (exit 0); merge-base = 2085be2. node scripts/check-nul-bytes.mjs → exit 0 (8843 text files, no raw ASCII control bytes). node scripts/check-changeset-fixed.mjs → exit 0 -- run because dispatch-gates marks its roster as living under .changeset, where one of my paths is, so its silence would not have been evidence. pnpm check:changeset-gate-self-tests → exit 0 (159 + 384 + 299 assertions). pnpm check:pm-changeset-deadline-census → exit 0 and pnpm check:objectui-changeset → exit 0, both CHECKER-HEALTH only (they ran --self-test): they grade the checker's fixtures, not my diff. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack → exit 0; it derives from the WHOLE branch change set (20 paths vs merge base), i.e. the previous round's obligation, not this round's -- and it warns STALE TREE, at least 33 commits behind origin/main with 9 derivation files changed in that range. VERIFICATION OF THE REVIEW'S MEASUREMENT (I did not take it on trust): (a) at head, QueryWithTransportSchema.safeParse REFUSES all six -- $orderby:'name desc', sort:'-created_at', $orderby:['name'], and the JSON string on $filter / filters / filter -- each with one issue at its own key; (b) the prescribed replacements all PARSE and lower to the canonical AST: $orderby:{name:'desc'} and orderBy:[{field,order}] and sort:{created_at:'desc'} and sort:[{field,order}] and $orderby:[{field,order}] and $filter/filters/filter:{status:'open'} -- so the FROM/TO I shipped is measured, not asserted; (c) packages/spec/src/data/query.zod.ts is BYTE-UNCHANGED between merge base and head (git diff --stat empty), and QuerySchema.safeParse at head STRIPS all six keys and succeeds -- that is the merge-base ingress leg, so the bodies passed validation there; (d) the door leg by source read: normalizeSortNodes' fromShorthand handles 'name desc' / '-created_at' and fromElement maps each string element of ['name'], and protocol.ts:10697 JSON.parses a string filter -- and the whole fold is unchanged in this diff except its table source, which is pinned byte-equal; (e) FindDataRequestSchema is parsed at EXACTLY ONE non-test call site in packages/rest + packages/metadata-protocol (rest-server.ts:9025, the POST /data/:object/query handler, which forwards the ORIGINAL body and answers 400 VALIDATION_FAILED), which is what makes the GET-querystring sentence true. THE 39/39 FIGURE: filter-array-declaration.test.ts holds 13 it()s and no .each, at the merge base, at origin/main and at head alike; run under the shared lock at head -- pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/data/filter-array-declaration.test.ts → 'Test Files 1 passed (1) / Tests 13 passed (13)', VERDICT command-exit 0 (waited 518s behind issue-18058's spec test:repo, slot OS_VERIFY_LOCK_SLOT=issue-16066-patch-round). 13/13 confirmed; 39 is wrong at every ref. No ablation is owed -- this round changes no executable behaviour.","mcp_calls": "0 -- no MCP GitHub tool was called, read or write.",
"api_writes": "1 REST proxy write: POST /repos//issues/16066/comments (this os-dev-report). No PATCH of the PR body (see open_questions), no POST /pulls (the PR already exists), no label write (the dispatch reserves needs:contract-review to the seat). Reads, for the record: 1 GET /repos/objectstack-ai/objectstack/pulls/18704 by me, plus 7 GETs issued by scripts/pm/check-clause2-carriers.mjs --pair 18704. 1 git push (b98eff5).",
"open_questions": [
{
"question": "The dispatch orders me to edit the PR body (items 2 and 3). os-dev.md's standing clause says the dev writes the PR body exactly once, at the PR-open write, never a PATCH, and that post-hoc changes are named in the report for the seat to write. The same file says that when the dispatch word and the agent file conflict, the agent file governs and the conflict is reported rather than silently resolved. So: does the seat apply the two PR-body edits, or is the clause relaxed for a review-remediation round?",
"options": [
"A. Seat applies pr_body_edits_for_seat verbatim (what I did: changeset landed by me, body left to the seat). Costs one seat action; keeps the clause intact; the dispatch's own trap note -- that a careless body edit destroys the Clause-2 line -- is exactly the hazard the clause exists for.",
"B. Relax the clause for remediation rounds and let the dev PATCH the body. Removes the handoff, but the dev would be re-sending a body whose footer and Clause-2 line it did not author, against a platform that mutates body bytes -- and a clause that bends for one dispatch word is not an unconditional clause.",
"C. Amend os-dev.md to carve out an explicit exception for a PR-body correction ordered by a review round, so the next dev does not have to adjudicate this mid-task. .claude/** is governed -- maintainer's call."
],
"recommendation": "A now, and file C as a rules-layer card. A is what the agent file compels and it costs one paste; the false sentence is remediated either way, only the hand differs. C removes the adjudication from the next dev's critical path instead of re-litigating it per round."
}
],
"pr_body_edits_for_seat": {
"note": "Three find/replace edits on PR #18704's body. Each old string was verified count=1 against the body as fetched at 2026-09-18T00:3x. Do NOT touch line 3, 'Clause-2: yes (widening)', which must survive verbatim; editing the body re-triggers Check Changeset, and the body should be read back after the write.",
"edit_1_the_one_refusal_claim": {
"old": "This is the one refusal that takes a body from
200to400: the door parses those strings and the spec must not own a second parser for them,","new": "This is NOT the only refusal that takes a body from
200to400. Five shapes were read off the forwarded ORIGINAL body and SERVED CORRECTLY before this change, and now answer400 VALIDATION_FAILEDat the ingress:{$orderby: 'name desc'},{sort: '-created_at'},{$orderby: ['name']},{$filter: '{\"status\":\"open\"}'}, and that same JSON string onfilters/filter. Two more went200to400for the opposite reason --$top: 'abc'reached the engine as an unbounded read and$top: ''aslimit: 0, a wrong answer under a200. The changeset carries each of the five with its FROM to TO. The door parses those strings and the spec must not own a second parser for them,"},
"edit_2_thirty_nine_first": {
"old": "(that file is green, 39 cases)",
"new": "(that file is green, 13 cases)"
},
"edit_3_thirty_nine_second": {
"old": "-
packages/spec/src/data/filter-array-declaration.test.ts— 39/39, the #5158 negative half included.","new": "-
packages/spec/src/data/filter-array-declaration.test.ts— 13/13, the #5158 negative half included."},
"optional_edit_4_consistency": {
"old": "None of these narrows a DECLARED surface: no such value shape was ever declared. They narrow how far an unservable body travels before it is refused.",
"new": "None of these narrows a DECLARED surface: no such value shape was ever declared. What five of them DO narrow is what the door SERVES -- see the changeset, which carries the FROM to TO for each. The rest only narrow how far an unservable body travels before it is refused.",
"note": "NOT ordered by the dispatch. Flagged because it is the same 'travels' framing as the clause that was ruled false, one section further down; the seat decides whether it reads as incomplete."
}
},
"out_of_scope_findings": [
"noted, not filed: the dispatch's parenthetical that the three filter-array files hold '34 + 12 + 13' does not reproduce for the first figure -- packages/objectql/src/engine-filter-array-lowering.test.ts declares 40 it()s at head, and it uses .each (6 sites), so its RUN count and its declaration count differ and 34 is plausibly a run count I did not take (I did not spend a second lock round on it). 12 and 13 both reproduce. Not load-bearing: the PR body names only filter-array-declaration.test.ts. Who would meet it: the seat correcting its own comment 5720453068.",
"noted, not filed: check-clause2-carriers.mjs --pair 18704 exits 4 at the new head -- needs:contract-review is owed a re-hang because my patch commit (2026-09-18T00:36:12Z) moved the head past the removal at 2026-09-18T00:28:35Z. Reported, not acted on: the dispatch reserves that label to the seat and the script itself refuses to write it. Who would meet it: the seat, on its next pass.",
"noted, not filed: dispatch-gates warns this tree is at least 33 commits behind origin/main with 9 of its own derivation files changed in that range, so the family list it printed is a well-formed answer about a tree nobody is on. Unchanged from the previous round and harmless for a .changeset-only diff, but it means the 127-command list should not be read as this round's obligation. Who would meet it: the next patch round on this branch, or PM reading CI."
]
}
Generated by Claude Code
os-litant commentedon Sep 18, 2026
os-dev-report
{ "issue": 16066, "status": "done", "branch": "claude/issue-16066-query-transport-params", "pr": "https://github.com/objectstack-ai/objectstack/pull/18704", "session": "session_01LvwGppdonww4zGLWZo5rho", "premise_still_valid": true, "summary": "Patch round. Merged origin/main through scripts/pm/os-regen-merge.sh (merge commit 0f221a71bc0; step 2 kept the branch's bytes of 9 os-regen paths main had not moved, step 3 had nothing extra to commit), then declared the three dropped-refinement ledger lines the gate named: data/QueryTransportParams (4 sites) and data/QueryWithTransport (4 sites) added, api/FindDataRequest re-sited from the single query.where.lazy to the four query.in.* paths the new query slot produces. Every site string and both header totals are copied from the gate's own output at my head, not retyped from the dispatch and not computed from the file. No .refine() or .superRefine() was touched, added or weakened: the ledger is a visibility ratchet and the published JSON Schema stays exactly as wide as it was. Ledger header moved 243/737 to 245/748 from the gate's printed line; refinementSitesThatDidProject 0 and refinementSitesWithNoJsonFormToCompare 3 both unchanged, also read from the print. Head 53591e48be2.", "tests": "All heavy runs through scripts/pm/os-verify-lock.sh, slot issue-16066, verdicts read from its own VERDICT command-exit line. FOUR SPEC SCRIPTS, all at HEAD 53591e48be2 with a clean worktree: (1) pnpm --filter @objectstack/spec build :: VERDICT command-exit 0, held 163s - the failing job's root cause is cleared; the ratchet now prints '748 refinement site(s) across 245 published schema(s) reach the RUNTIME and not the published JSON Schema - all declared' and 'Also measured this run: 0 refinement site(s) DID reach the file, 3 had no JSON form on either side to compare'. (2) pnpm --filter @objectstack/spec test (--project local) :: VERDICT command-exit 0, 488 test files / 14113 tests passed. (3) pnpm --filter @objectstack/spec test:repo (--project repo) :: VERDICT command-exit 0, 31 files / 537 tests passed. (4) pnpm --filter @objectstack/spec typecheck :: VERDICT command-exit 0. LEDGER PIN, run on its own so the assertion is named rather than counted: vitest run --project local scripts/dropped-refinements.test.ts :: exit 0, 27 tests, including 'names at least one site per entry, and its header totals match its body' - the sibling failure on PR 18752 is the one this pins, and it is green. CROSS-CHECK on the header: the gate's printed 748/245 and the arithmetic over the file's own entries (748 site strings across 245 entries) agree; the numbers written are the printed ones. GATES, exit codes captured before any pipe: pnpm --filter @objectstack/spec run check:authorable-surface 0, check:generated 0 ('All 15 generated artifacts are up to date'), pnpm check:nul-bytes 0 (8872 text files, no raw control bytes; plus a direct grep -naP control-byte sweep of the edited file, 0 hits), pnpm check:published-files 0, pnpm check:ratchet-remedy-authority 0, pnpm check:filter-alias-parity 0, node scripts/check-changeset-no-major.mjs --base origin/main 0 and --self-test 0 (run by script path - it is not a root script), node scripts/check-empty-changeset.mjs --base origin/main 0. DECLARED NARROWING: scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran reconciles 127 derived / 9 run with exit codes / 0 NOT-MEASURED / 118 UNRUN. The 127 is derived for the PR's whole 21-path change set; this round's own diff is one file, packages/spec/dropped-refinements.baseline.json. The 118 are left to CI. No ablation in this round: nothing new is asserted - the ledger's failure direction was demonstrated by CI itself, which is red on it at b98eff5b015 and whose remedy text this round pasted.", "mcp_calls": "3 - mcp__github__get_job_logs three times, read-only. No MCP GitHub write tool was called.", "api_writes": "1 - POST /repos/objectstack-ai/objectstack/issues/16066/comments (this report). Reads only otherwise: GET commits/SHA/check-runs x2, GET actions/jobs/ID x2, GET actions/runs/ID x2, GET pulls/18704, GET issues/16066 and its comments. One git push over the git protocol, not REST. No POST /pulls (18704 is already open), no label write (needs:contract-review is the seat's carrier per the dispatch fence), no PATCH of the PR body.", "open_questions": [], "out_of_scope_findings": [ "MEASURED, hypothesis FALSIFIED - the green at 137010a79c was NOT a turbo cache hit, and it is not the 'a green that never ran' class. Three readings. (1) Build Core step durations from the Actions API: at 137010a79c 'Build packages (excluding docs)' ran 339s and exited 0; at b98eff5b0156 it ran 9s and exited 1. A turbo REPLAY is the near-instant case - the failing job's own summary is 'Tasks: 2 successful, 3 total / Cached: 2 cached, 3 total / Time: 8.397s / Failed: @objectstack/spec#build', i.e. two replays plus one real 8s run. A cold pnpm --filter @objectstack/spec build measured 163s on this box, so 339s is one real spec build plus replays, not a replay. (2) The decisive one, local and reproducible: the ratchet is ABSENT FROM THE BRANCH ENTIRELY. git cat-file -e on packages/spec/dropped-refinements.baseline.json and on packages/spec/scripts/lib/dropped-refinements.ts returns ABSENT at all three of fa423f55b0e, 137010a79cf and b98eff5b015, and build-schemas.ts at each of them has 0 occurrences of DROPPED_REFINEMENTS_BASELINE_FILE. The branch last merged main at fa423f55b0e, 2026-09-17T20:31:14Z - twenty minutes BEFORE PR 18729 landed the gate on main at 20:51:30Z. So no build of the branch head's tree could ever have failed this gate, cached or not; there was nothing there to run. (3) CI is therefore not building the branch head. Both runs are pull_request events; ci.yml's Build Core uses a bare actions/checkout@v7 with no ref:, whose pull_request default is the merge ref; refs/pull/18704/merge exists on the remote; and the failing run PROVES the tree it built was a merge ref, because it printed '- query.where.lazy' for api/FindDataRequest - a ledger site that exists only in main's copy of a file the branch head does not contain at all. CONCLUSION: what flipped green to red across a markdown-only commit is the BASE moving, not the changeset. The +15/-1 .changeset file is not causal and did not bust a cache; the merge ref the 21:14 run built was computed against a main without 18729, the 00:36 run's against a main with it. NOT MEASURED, and the reason: I could not read the 'HEAD is now at SHA Merge HEADSHA into BASESHA' line that would name each run's merge sha outright, nor the passing run's own turbo summary line. Both sit near the TOP of the Actions log; the raw log download host productionresultssa5.blob.core.windows.net answers 403 through this session's egress proxy (its README says report the blocked host, do not route around it), and the MCP log reader is tail-only - I reached to within about eight lines of the turbo summary and no further. So 'the merge ref that run was handed was computed against a pre-18729 base' is the reading the three measurements above force, not a printed sha I read.", "noted, not filed: the above is GitHub platform behaviour, not a repo defect, and this repo already closes it - AGENTS.md Multi-agent discipline 7 lands through the merge queue, which rebuilds each entry as merged onto CURRENT main and re-runs the required set there, so a required check that went green on a merge ref computed against a stale base cannot land on that green. I also checked the one repo-side way this COULD have been a caching defect and it is sound: turbo.json's build task declares no inputs, so the default hash covers every file in packages/spec - the ledger and scripts/lib/dropped-refinements.ts included - and neither can be cached away by editing the other. Who would meet this file next: whoever reads a green required check on a PR whose base has moved since. Mitigation is already institutional, so there is nothing to file.", "noted, not filed: the PR body is the seat's (os-dev.md clause). Two things in it are now stale and I am naming them rather than writing them: it carries no mention of the dropped-refinement ledger declaration at all, and its test evidence predates this round. Suggested wording for the seat, one paragraph under 'What landed': the two new published schemas data/QueryTransportParams and data/QueryWithTransport each drop four refinement sites, and api/FindDataRequest's single site moves to four under the new query slot; all three are declared in packages/spec/dropped-refinements.baseline.json with its header totals moved 243/737 to 245/748 from gen:schema's own print, and no refinement is touched. Evidence line: build 0, test 488 files / 14113 tests, test:repo 31 files / 537 tests, typecheck 0, all at 53591e48be2." ] }Generated by Claude Code
github-actions commentedon Sep 18, 2026
os-closed-card-sweep — machine-findable marker for this generated comment.
Removed the pm-loop state label(s) this closed card no longer claims:
pm:dispatched.0b788da895, merged intomain.enhancement,priority:p2,domain:spec— ownership, priority and outcome are not state claims.A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.
posted by half-state-patrol run 35320894629 · trigger
scheduleGenerated by Claude Code
feat(spec): declare the query transport dialect as the flattened spel…
test(spec): the last data/ file group's test titles state each cited …