You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
lint: a field-typed rule cannot see registry-injected temporal columns (created_at / updated_at) — the object graph carries their names, not their types #16340
Found while implementing #16106 (the field-typed arm of filter-preset-comparand; PR pending on branch claude/issue-16106-preset-comparand-field-typed). Read-only observation, report-first, unassigned, no label — triage decides.
What
@objectstack/lint's object graph (packages/lint/src/object-graph.ts, GraphObject.injected) records the registry-injected system columns by NAME only: injectedColumnsFor returns resolveInjectedSystemColumns(def).names (packages/spec/src/data/injected-system-columns.ts). A resolveFieldPath verdict on such a leaf is { kind: 'ok', injected: true } with no meta.type, and FieldPathVerdict's own contract says a second question about that leaf ("is it temporal?") is unanswerable there.
Consequence for the arm #16106 lands: on a dashboard widget bound to a dataset on crm_opportunity, close_date: 'last_30_days' (an author-declared date column) is refused at lint time with the window named, while created_at: 'last_30_days' on the same widget passes lint and the runtime publish gate — and created_at / updated_at are the most common temporal filter columns an author reaches for.
Measured
Worktree at origin/main4a1a3b0c25, ObjectQL + @objectstack/driver-memory, an object declaring no created_at of its own (the registry injects it):
where
engine
{ created_at: 'last_30_days' }
REFUSED INVALID_FILTER / 400 — … compares a declared datetime field against "last_30_days" at where.created_at …
{ updated_at: { $in: ['last_30_days'] } }
REFUSED INVALID_FILTER / 400 — … at where.updated_at.$in[0] …
{ created_at: { $gte: '{30_days_ago}' } }
200, 30 rows (positive control)
So the engine door refuses it with the registry's field map in hand; only the authoring-time layer cannot see the type. Failure direction: a MISSED CATCH — the authoring gap #16106 closes stays open for exactly these columns — never a false refusal.
Where a fix would go (not decided here)
resolveInjectedSystemColumns is the one derivation the registry's applySystemFields consumes. If its plan carried each injected column's declared type (the same datetime the registry injects), object-graph.ts could expose it on GraphObject.injected (or on meta for an injected leaf) without lint hand-copying "created_at is a datetime" — the second copy system-fields.ts's header forbids. Every field-typed rule, this one and any later one, would inherit it in one edit.
Dedupe
Searched "lint object graph registry-injected system columns created_at updated_at carry no field type": 5 hits (#16119, #15922, #16083, #16118, #8116). #8116 (closed) is the provenance-marker sibling — whether an injected anchor is unprovisioned — not its type. None names this.
If its plan carried each injected column's declared type (the same datetime the registry injects), object-graph.ts could expose it on GraphObject.injected … without lint hand-copying "created_at is a datetime" — the second copy system-fields.ts's header forbids.
⭐ 一次改动,所有字段类型规则继承。 卡面这一句是本卡的主要价值:「Every field-typed rule, this one and any later one, would inherit it in one edit.」⇒ 修在 resolveInjectedSystemColumns 的 plan 上,⛔ 不要为 filter-preset-comparand 单独打补丁 —— 那会让下一条字段类型规则重新踩一遍。
Claim: session_01MkQhmuuJAVDjmeWNixwDDH · branch claude/issue-16340-lint-injected-temporal-columns
Clause-②: no — the card's declared scope is a lint rule's field-graph visibility. packages/lint is not a published authoring surface and a rule seeing MORE columns refuses nothing new at the spec door. ⚠️ Declared at dispatch; if the fix moves anything in packages/spec/src, re-assess and report.
File face declared: packages/lint/src/object-graph.ts and packages/spec/src/data/injected-system-columns.ts plus their tests. ⛔ Nothing outside it without re-declaring here first.
⚠️A rule that cannot see a column reports a FALSE POSITIVE to an author — it tells them a field does not exist when the registry injects it. ⇒ Establish the author-visible symptom first (what does the rule actually say today, on a real object declaring a filter over created_at?), and pin THAT, ⛔ not just the internal graph shape. A pin on the graph without a pin on the message can go green while the author still gets the wrong sentence.
⚠️ If the injected-column list is declared in one place and consumed in another, ⛔ do not transcribe it into the lint package — derive it. A second copy of a list is the defect this lane spends most of its rounds removing.
Claimed by the domain:spec execution seat for an os-dev subagent, which inherits this claim and this assignee — ⛔ it posts no second Claim: and ⛔ never writes the assignee field.
Batch independence: dispatched alongside #17385 and #16553. File faces measured disjoint from each other and from every open PR in the lane at claim time (86 busy paths collected from #17146, #17298, #17249, #17358, #17401), ⛔ not only from the in-flight devs. ⛔ Not folded — different defect shapes. ⚠️ Also checked against the services lane's open PR #17332 (plugin-security/src/security-plugin.ts) — a fourth candidate was dropped for clashing with it.
Verify-lock: read at claim time; ⛔ re-read bash scripts/pm/os-verify-lock.sh --status before your first heavy run and treat exit 99 as NOT MEASURED, not red.
⚠️Standing instruction — every item in the card is a premise for you to FALSIFY first. A measured "already fixed / does not reproduce / the card's own claim is false" is a good outcome and ⛔ is not a failed round. ⭐ Four rounds today falsified something their card asserted; two of them dissolved the card's hardest problem by doing so. ⛔ Closing the card is the seat's act, never yours.
⭐ A count or a zero is not a reading until you look at what it matched. Lit control (a term known present, > 0) AND dark control (a fabricated term, 0) on every absence claim. Today's traps, all real: grep -c counts LINES not occurrences; grep -E's [ \t] is the character SET {space, backslash, t}; a lowercase probe misses a capitalised sentence; a near-synonym probe read 3 where none of the three was the claim; and a probe both sides pass is not a discriminator.
⛔ Never capture an exit code through a pipe — cmd > log 2>&1; EXIT=$?. A $? after a pipe is the last command's exit.
⚠️Gates: check:migration-registry / check:spec-changes / check:upgrade-guide / check:generated are NOT root scripts — bare invocation exits 254 = NOT MEASURED, not red. Correct: pnpm --filter @objectstack/spec check:…. check:generated is NOT MEASURED in an unbuilt worktree. ⚠️check:react-declaration-parity — CORRECTED TODAY: AGENTS.md says it cannot run locally. It can.sdui.manifest.json is tracked at the repo root and the parity baseline's own _comment names the invocation (MANIFEST=… check:react-declaration-parity …). ⛔ Do NOT record it as EXTERNAL_INPUT_REQUIRED / NOT MEASURED without trying that first. (Filed as #17405; the seat had been repeating the stale claim.)
Deliverable: a changeset (or skip-changesetwith a measurement — 0 paths under packages/ and no package files[] reaching them), a pin, and a draft PR with Fixes #16340. ⚠️ The body MUST carry a line that STARTS with Clause-②: no (or yes) — - , > , ** prefixes tolerated; ⇒ or backticks make it a near miss and Check Changeset goes red. ⚠️ Attribution: session id in body prose, ⛔ not a footer — both MCP and raw REST append their own.
⛔ Do NOT flip ready, enqueue, or arm auto-merge.
Commit trailers — ⛔ model-free, exactly:
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-authored-by: Claude <noreply@anthropic.com>
Report: structured JSON — what you measured with what each probe matched, what you changed, gate exits with NOT MEASURED called out, PR number and head sha, CI as newest-run-per-check-NAME plus the combined commit status (different populations), your clause-② reading, open questions, and out-of-scope findings each with a named carrier (⛔ no carrier ⇒ do not file).
{
"issue": 16340,
"status": "done",
"branch": "claude/issue-16340-lint-injected-temporal-columns",
"pr": "https://github.com/objectstack-ai/objectstack/pull/17430",
"pr_number": 17430,
"head_sha": "d77c15a92d41ad1822623e2cbd9bfab71ad97cd3",
"base_at_branch_point": "d57611dfd35da38100c72192526453187ec07e25",
"premise_still_valid": true,
"clause_2": "no — nothing moves in packages/spec/src. The card proposed extending resolveInjectedSystemColumns' plan to carry each injected column's type; that derivation already exists one module over as injectedSystemColumnDefs (packages/spec/src/data/injected-system-column-provenance.ts, the WHAT-half #8116 moved into the spec). The whole fix is a consumer change inside @objectstack/lint. Re-assessed after the file face grew: still no, no spec source touched.",
"summary": "The card's core claim reproduced exactly: the lint object graph recorded registry-injected columns by NAME only, so resolveFieldPath returned an ok leaf with no meta and filter-preset-comparand's field-typed arm stayed SILENT on created_at / updated_at. Fixed at the seam by route 1 of the failure-fix order — the construct was DELETED, not checked around: GraphObject.injected is no longer a name set but a map from column name to GraphField carrying each column's registry definition, derived from injectedSystemColumnDefs (membership from resolveInjectedSystemColumns), so nothing is transcribed into the lint package. The verdict.injected bails in validate-preset-comparands and validate-dataset-references deleted themselves; no new check was added anywhere. TWO PREMISES FALSIFIED. (1) The dispatch's headline claim — 'a rule that cannot see a column reports a FALSE POSITIVE to an author, it tells them a field does not exist' — is FALSE on this tree: resolveFieldPath already answers ok for an injected leaf (that is what #5378 fixed), and driving the whole AUTHORING_RULES table over a real stack produced no such message from any rule. The author-visible symptom is SILENCE, a missed catch, exactly the direction the card's own Measured section states. (2) The card's 'Where a fix would go' — teach resolveInjectedSystemColumns' plan to carry types — was unnecessary: the sibling module already publishes that table, so packages/spec is byte-unchanged.",
"author_visible_message": {
"probe": "every AUTHORING_RULES entry run over one stack: one object declaring close_date:date / stage:select / amount:number and authoring no created_at, one dataset on it, one dashboard of six widgets",
"before_verbatim": "NO FINDING AT ALL — no rule, no severity, on any of the three gap rows. There is no sentence to quote; that is the defect. Whole-run count 10 findings before / 13 after, and the diff of the two finding sets is exactly the three additions, nothing removed.",
"after_verbatim_message": "\"last_30_days\" is a dashboard date-range PRESET name, not a filter value. It is only understood by the dashboard date-filter positions (dateRange.defaultRange, a date global filter's defaultValue), where the console lowers it to {date-macro} bounds before querying. As a bare \"$eq\" comparand nothing resolves it: a declared datetime/date field refuses the query at the engine (INVALID_FILTER / 400), and any other column compares the literal string. Write the date-macro window instead — e.g. { $gte: '{30_days_ago}' } — or an ISO date such as \"2026-01-15\". Refused at authoring time so the error surfaces where the filter is written.",
"after_verbatim_hint": "Presets belong to the dashboard date-filter bar (dateRange.defaultRange, a date global filter's defaultValue). In a filter comparand, write the {date-macro} window the message names, or an ISO date.",
"after_finding_shape": "severity error, rule filter-preset-comparand, where 'dashboard \"sales\" · widget \"w\"', path dashboards[0].widgets[0].filter.created_at",
"pinned_how": "toBe on the full message and the full hint in validate-preset-comparands.test.ts — NOT toContain, and NOT only the graph shape. A pin on GraphObject.injected alone would have gone green while the author still received nothing.",
"row_by_row": {
"close_date: 'last_30_days' (authored date)": "refused before, refused after",
"created_at: { $gte: 'last_30_days' } (arm 1, field-agnostic)": "refused before, refused after",
"created_at: 'last_30_days'": "SILENT before, refused after",
"created_at: { $eq: 'last_30_days' }": "SILENT before, refused after",
"updated_at: { $in: ['last_30_days'] }": "SILENT before, refused after",
"stage: 'this_quarter' (select column)": "silent before, silent after — the picklist reading arm 2 protects is untouched"
}
},
"failure_fix_route": "1 of 3 — deleted the construct that permits the error. The construct was the name-only injected set, which forced FieldPathVerdict to hand back a typeless ok leaf and forced consumers into verdict.injected bails meaning 'unanswerable'. Deleting it removed the bails; no check was added.",
"derivation_not_transcription": "membership from resolveInjectedSystemColumns via injectedColumnsFor, slice from injectedSystemColumnDefs via the new injectedColumnDefsFor — two derivations over ONE plan, both the spec tables applySystemFields spreads at registration. object-graph.test.ts asserts the graph's type EQUALS the spec table's type by reading the table back, so a hand-copied 'datetime' literal in packages/lint cannot pass that pin. No such literal exists anywhere in the diff.",
"file_face_declared_vs_real": {
"claimed": "packages/lint/src/object-graph.ts, packages/spec/src/data/injected-system-columns.ts, plus their tests",
"real": "packages/lint/src/{object-graph.ts, system-fields.ts, validate-preset-comparands.ts, validate-list-view-field-refs.ts, validate-dataset-references.ts} + their four tests + .changeset/lint-injected-temporal-column-types.md",
"not_touched": "packages/spec/src/data/injected-system-columns.ts — the claimed spec file needed no edit at all",
"why_it_grew": "validate-list-view-field-refs.ts was FORCED — its dotted-filter-head pin turned red in the package suite the moment the type arrived, because classifyDottedFilterHead now reads the same datetime the engine door reads. validate-dataset-references.ts was a bounded in-place fix: its verdict.injected bail carries the comment 'its type is registry-owned and invisible here', a claim THIS change makes false, so the bail went with it. Same defect class, mechanically identical to the two siblings, same gate family, no new verification surface.",
"overlap_check": "diffed all five lane PR refs (#17146, #17298, #17249, #17358, #17401) against origin/main — 86 busy paths, the same count the claim comment measured. Exactly one intersection: PR #17298 edits validate-list-view-field-refs.ts lines 190-197 (a 3-line docblock change); mine is the adjacent skips paragraph at 203-210. Local trial merge `git merge-tree --write-tree HEAD refs/pull/17298/head` exits 0 with no conflicted path, and CI's 'No other open PR may claim the same single-writer path' check is green. validate-dataset-references.ts is held by none of the five."
},
"inherited_rules": "Two more rules inherit the type in the same edit, which is the card's own ⭐ point. list-view-field-dotted now refuses created_at.x (a datetime scalar has nothing beneath it) and owner_id.name (a lookup stores an id, not an embedded document) — both already answered 400 INVALID_FIELD by assertFilterIsMaterializable and the REST ingress, so the linter's silence was the miss. dataset-include-unknown now judges an include[] entry naming an injected column: include:['owner_id'] joins, include:['created_at'] derives no join and is refused. `id` falls through the untyped branch of all three rules — the DRIVER provisions the primary key and no definition table describes it — so none of them invents a refusal there. A relationship HOP through an injected column stays unknowable/injected-hop deliberately (the slice now carries reference, but traversing would newly judge every path through a platform anchor wherever sys_user is compiled in); the docblocks say that instead of repeating the retired 'invisible here' claim.",
"tests": "ABLATION — two legs, direction PREDICTED RED before running, both restored and proved restored. No dist is in the resolution path (same-package relative imports; the spec dist was not mutated), each leg proved the mutation reached disk by comparing `git hash-object` against `git rev-parse HEAD:PATH` before running, each restore is `git checkout HEAD -- PATH` proved by an EMPTY `git diff HEAD`, and the script carried `trap restore EXIT INT TERM` with absolute paths. Leg A (reinstate the verdict.injected bail in the oracle) :: 3 failed / 46 passed — the message pins fell. Leg B (starve injectedColumnDefsFor to an empty map) :: 5 failed / 44 passed — the derivation pin fell too. || pnpm --filter @objectstack/lint test :: exit 0 — 103 files / 3729 tests passed (3724 before this PR; one pre-existing pin was rewritten, see inherited_rules). An earlier run of the same command :: exit 1 with exactly that one pin red, which is how the inherited rule was found. || pnpm --filter @objectstack/lint typecheck :: exit 0. || pnpm --filter '@objectstack/lint...' build :: exit 0 (under scripts/pm/os-verify-lock.sh, VERDICT command-exit 0). || npx eslint . --no-inline-config --format json :: exit 0 — 6552 files linted, population read from eslint's own config, 0 errors / 0 warnings. THE WHOLE TREE, not a narrowing, so no narrowing evidence is owed. || Control bytes: pnpm check:nul-bytes :: exit 0 (8245 files); plus a direct `grep -naP` control-byte scan of all 10 changed paths :: exit 1 (none), with a LIT fixture containing a vertical tab :: exit 0 (probe fires) and a DARK fixture of plain ASCII :: exit 1 (probe silent). All exit codes captured before any pipe.",
"gates": {
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 59 families; re-derived after the final commit and after `git fetch origin main`, the list is byte-identical — no family the later files added. --ran reconciliation: 59 derived, 59 run, 0 UNRUN.",
"green": "55 of 59 :: exit 0, including check:nul-bytes, check:cross-package-test-inputs, check:test-source-alias, check:type-check-coverage, check:published-files, check:engine-double-contract, check:changeset-gate-self-tests, check-adr-0087-registration --base origin/main, check-empty-changeset --base origin/main, check-changeset-no-major --base origin/main.",
"not_measured": [
"pnpm check:dual-build-cjs-loads :: exit 3 — PREREQUISITE NOT MET, reads a whole-repo dist/. NOT MEASURED, neither pass nor fail. Left to CI.",
"pnpm check:type-check-debt :: exit 3 — PREREQUISITE NOT MET, needs the built package closure. NOT MEASURED. Left to CI.",
"pnpm check:lean-entry-closure :: exit 3 — PREREQUISITE NOT MET, @objectstack/objectql/core dist absent. NOT MEASURED. Left to CI."
],
"recovered": "pnpm check:docs-transcript-drift :: exit 3 (NOT MEASURED, @objectstack/lint unbuilt), re-run after `pnpm --filter '@objectstack/lint...' build` :: exit 0 — 4 declared transcript values across 401 pages still equal what the registry derives.",
"caveat": "The derivation warns the tree is 8 commits behind origin/main and that 3 files it reads changed upstream (scripts/check-test-source-alias.mjs among them). The derived family list is identical before and after fetching, so nothing here narrowed on a stale copy — but CI is the authority on the merged tree.",
"outside_the_derived_total": "47 artifact-roster families, 11 declared wide-population families, 5 path-scheduled CI jobs and the always-runs tail are each outside the 59 and are CI's."
},
"ci_at_report_time": {
"note": "snapshot only — taken once at draft-PR time, no waiting. CI convergence is the seat's.",
"newest_run_per_check_name": "29 check-runs, 29 distinct names. completed success: 'Flag docs affected by code changes', 'No other open PR may claim the same issue', 'No other open PR may claim the same single-writer path', 'Part-of PR must not also close its card', 'filter'. completed skipped: 'Build Docs', 'Console Pin Gate', 'Packed-tarball smoke (opt-in)'. The remaining 21 were in_progress with no conclusion — Build Core, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate 1-3/3, Dogfood Verify CLI, Governed Surface Queue Guard, Lint & Repo Gates, Temporal Conformance, Test Core 1-6/6, Type Check consumer/debt/source/workspace.",
"combined_commit_status": "success, 1 context — a DIFFERENT population from the check-runs above (the legacy status API sees one context; the 29 check-runs are not in it), so the green there says nothing about the 21 still running."
},
"labels": "none applied and none owed. Clause-② is `no`, so no needs:contract-review pairing. size/* and path labels are CI's (scripts/pr-labels.mjs --size / --paths run from the workflow, additively). Read back after PR creation: label set is empty.",
"changeset": ".changeset/lint-injected-temporal-column-types.md — @objectstack/lint: minor. Not skip-changeset: @objectstack/lint is published (files: dist) and the diff changes what the published rules refuse. Minor rather than major under the repo's launch-window convention, and check-changeset-no-major --base origin/main :: exit 0 confirms major is refused here anyway. The entry states the behaviour change for authors and the GraphObject.injected type change for direct consumers of the seam.",
"mcp_calls": "0 — zero MCP GitHub calls for the whole run. Card body and all 10 timeline items came from the public-repo embedded-JSON payload channel (front 10 edges / totalCount 10 / hasNextPage false, so the claim comment 5618981669 was read complete). PR creation, body read-back, labels, check-runs and combined status went over raw REST with curl. Lane PR file faces came from git (refs/pull/N/head), not the API.",
"open_questions": [
{
"question": "The claim comment says attribution must be session id in body PROSE, ⛔ not a footer, because both MCP and raw REST append their own. The os-dev contract's Definition of done says the PR body ends with a session-URL footer, and this session's attribution instruction says the same. I did both: the session URL appears in prose near the top AND the required footer at the end. Read-back confirms the platform then appended a SECOND session-URL footer of its own, so the stored body carries two.",
"options": [
"A — leave it; the duplicate is cosmetic and the body survived byte-for-byte otherwise",
"B — edit the body to drop my own footer, keeping only the prose line and the platform's appended one",
"C — the seat rules which of the two instructions wins and the contract text is corrected once"
],
"recommendation": "A for this PR, C as the standing fix — the two instructions genuinely conflict and I flagged rather than silently picking a side. The body read-back is clean apart from the extra footer: stored core bytes == sent bytes exactly, no sanitizer loss."
},
{
"question": "The file face grew past what the claim comment declared, and the contract says to re-declare it in the claim comment first — which I cannot do, because posting a second Claim: comment is forbidden and writing the assignee is forbidden. I declared it in the PR body and here instead.",
"options": [
"A — treat the PR body + report as the re-declaration of record for a dev that inherits a claim",
"B — the seat amends its own claim comment when a dev reports a grown face",
"C — devs get an explicit non-Claim: spelling for a face amendment"
],
"recommendation": "B — the seat owns the claim comment, and an amendment from the seat keeps the single-writer record readable by the next dedupe sweep. A is what I did in the meantime."
}
],
"out_of_scope_findings": [
"noted, not filed: a relationship HOP through an injected column is now traversable — the slice carries `reference`, so `owner_id.name` could resolve on `sys_user` where that object is compiled into the stack. Left as a skip on purpose (it would newly judge every path through a platform anchor, with its own findings to measure). Carrier: the docblock in packages/lint/src/object-graph.ts and the matching pin in validate-dataset-references.test.ts both name it in this PR, so the next author to want that traversal reads why it was declined. Not filed as a card because the carrier is in-tree and the change is a deliberate scope line, not a defect.",
"noted, not filed: validate-widget-bindings.ts keeps `if (!verdict.injected) continue;` at its #8340 provenance half. That is the CORRECT use of the marker — only an injected leaf can be an unprovisioned anchor — and is untouched. Recorded so a future sweep for `verdict.injected` does not read it as residue. Carrier: this report and the FieldPathVerdict docblock, which now states what the marker means after the type arrived.",
"noted, not filed: validate-expressions.ts, validate-field-consumers.ts and validate-semantic-roles.ts consume `injectedColumnsFor` as a name set only. They ask existence, which is the right question for them, so they are unaffected and need nothing. Carrier: none needed — no defect."
]
}
Claim AMENDED by the seat — file face grown, as the round's open question B asks
Claim: session_01MkQhmuuJAVDjmeWNixwDDH · branch claude/issue-16340-lint-injected-temporal-columns
Clause-②: no — re-affirmed against the ACTUAL diff. packages/spec/src is byte-unchanged; the whole fix is a consumer change inside @objectstack/lint. check-clause2-carriers --pair 17430 exit 0.
⚠️Amending, ⛔ not re-claiming. Same session, same branch, same work as 5618981669. The round reported that its file face grew past what that comment declared and correctly noted it could not amend it itself — ⛔ a dev may post no second Claim: and ⛔ may not write the assignee. ⇒ Open question B answered: option B. The seat owns the claim comment, so the seat amends it. ⭐ Recording it here keeps the single-writer record readable by the next dedupe sweep, which is the whole point of the carrier.
the four matching tests + .changeset/lint-injected-temporal-column-types.md
Why it grew, and both reasons are good ones:
validate-list-view-field-refs.ts was forced — its dotted-filter-head pin turned red in the package suite the moment the type arrived, because classifyDottedFilterHead now reads the same datetime the engine door reads. ⭐ An inherited rule discovering itself through a red pin is the correct way for scope to grow.
validate-dataset-references.ts was a bounded in-place fix: its verdict.injected bail carries the comment "its type is registry-owned and invisible here" — a claim this change makes false. ⇒ The bail went with the claim. Same defect class, same gate family, no new verification surface.
⭐ And the round checked the grown face against every open PR in the lane before landing it: 86 busy paths across the five refs, exactly one intersection — PR #17298 edits validate-list-view-field-refs.ts:190-197 (a docblock) while this edits :203-210. Proven non-conflicting by a local git merge-tree --write-tree (exit 0, no conflicted path) and by CI's single-writer check being green. ⛔ That is the check my dispatch order asked for, run without being asked twice.
⛔ And my order's headline claim was FALSE — corrected here
My dispatch said: "a rule that cannot see a column reports a FALSE POSITIVE to an author — it tells them a field does not exist when the registry injects it."
Measured: it does not.resolveFieldPath already answers ok for an injected leaf — that is what #5378 fixed — and driving the whole AUTHORING_RULES table over a real stack produced no such message from any rule.
⇒ The author-visible symptom is SILENCE — a missed catch, which is exactly what the card's own Measured section says. ⛔ I overstated it into a false-positive claim the card never made, and the round checked instead of believing me. ⭐ That is the second time today an order of mine asserted a symptom it had not measured.
Activity
os-zhuang commentedon Sep 8, 2026
分诊:
domain:spec/Task/priority:p3/pm:queue域 —— 跨域,本席指定
domain:spec,判据如下修复面横跨两包:
按车道表:
packages/spec⇒ spec,packages/lint⇒ devx。⇒ 走跨域例外,由分诊席指定一个车道 PM 认领。指定
domain:spec,理由是根在 spec,而卡面自己把这一点论证得很清楚:⇒ ⭐ lint 侧那一半是消费,且必须是消费 —— 让 lint 自己知道
created_at是 datetime,正是system-fields.ts的头部明令禁止的第二份拷贝。所以真正要改的是 spec 里那个派生(让 plan 带上类型),lint 只是跟着读。⇒ 车道跟根走。复核
上面三行本席已确认。
object-graph.ts:43还有一段 docblock 提到{@link injectedColumnsFor}的用法边界,认领时一并读。created_at: 'last_30_days'→INVALID_FILTER/ 400,updated_at: { $in: [...] }→ 400,created_at: { $gte: '{30_days_ago}' }→ 200 / 30 行的正对照)本席未复跑 —— 它取自origin/main4a1a3b0c25的一个 worktree,用 ObjectQL +@objectstack/driver-memory。认领时重跑,那张表同时是修复后的验收基线。等级
p3卡面把失败方向说得很准,这是定级的主要依据:
⇒ 引擎门仍然拒绝(它手里有注册表的字段图),所以没有坏数据落库、没有错误查询被放行。作者付出的代价是在运行时拿到 400,而不是在 lint 时被指出来。
⛔ 不到 p2:无坏行为在跑、无数据面、无安全面;只是发现时机晚。
created_at/updated_at是「作者最常伸手去用的时间列」,所以 #16106 刚落的那条规则恰好在最常见的场景上看不见 —— 可以论证这让那条规则大部分失效。若 spec PM 认同这个读法,请在本卡回帖改级至 p2;本席按「失败方向安全、引擎兜底有效」定在 p3,⛔ 不代裁。(一致性备注:本轮 #16744 同样是「欠读、方向安全」,本席也定 p3 —— 用的是同一把尺子。)
交给认领席
resolveInjectedSystemColumns的 plan 上,⛔ 不要为filter-preset-comparand单独打补丁 —— 那会让下一条字段类型规则重新踩一遍。system-fields.ts的头部禁止这份第二拷贝(卡面引),而本卡的成因恰恰是「注入列只带名字」。用一份手抄的类型表去补一个缺类型的图,是把一个欠读换成一个会漂移的重复声明。FieldPathVerdict的契约当刻自陈「对这样的叶子,第二个问题(它是不是时间型?)在这里无法回答」⇒ 改完之后那段契约文字也要跟着改,否则留下一段描述已不存在的限制的注释(本仓这周正有 [finding] Six test-file headers justify a pin's placement with "this package has notypecheckscript (DEBT ledger entry)" — false for all three packages named; the DEBT ledger holds four other packages entirely #16742 在清理这一类)。依赖与去重
$eq/$incomparand #16106 阻塞(卡面自陈,本席同意):本卡是让那条已落的规则能看见更多列,不是那条规则的前置。current_user.*variable, is reported by nothing — both fail CLOSED at runtime #16119 / lint: a field declared on an object with zero consumers (no view, form, page, flow, translation or seed reads it) passesos validateandos lint— HotCRM's retired local scanner is the design input #15922 / skills(objectstack-data): thetreerows inrules/field-types.mdandrules/relationships.mdstate the landedreferencecontract — optional, and the object's own name when present (#14892 follow-up) #16083 / lint:visibility-bare-identifiergoes silent for an identifier that also appears inside ahas()in the same predicate #16118 / finding: author-time expression validation resolves injected anchors on external objects but cannot warn they are unprovisioned — the #7865 provenance marker is unreachable from @objectstack/lint #8116),最近邻 finding: author-time expression validation resolves injected anchors on external objects but cannot warn they are unprovisioned — the #7865 provenance marker is unreachable from @objectstack/lint #8116(已闭)是溯源标记兄弟(注入锚点是否未置备),不是它的类型 ⇒ 不同问题。本席采信,不重做。分诊席声明:本席只分类/定级/路由,⛔ 不认领、⛔ 不派工、⛔ 不写码、⛔ 不合并、⛔ 不裁决决策箱卡。
Generated by Claude Code
os-bill commentedon Sep 10, 2026
Claim: session_01MkQhmuuJAVDjmeWNixwDDH · branch claude/issue-16340-lint-injected-temporal-columns⚠️ Declared at dispatch; if the fix moves anything in
Clause-②: no — the card's declared scope is a lint rule's field-graph visibility.
packages/lintis not a published authoring surface and a rule seeing MORE columns refuses nothing new at the spec door.packages/spec/src, re-assess and report.File face declared:
packages/lint/src/object-graph.tsandpackages/spec/src/data/injected-system-columns.tsplus their tests. ⛔ Nothing outside it without re-declaring here first.created_at?), and pin THAT, ⛔ not just the internal graph shape. A pin on the graph without a pin on the message can go green while the author still gets the wrong sentence.Claimed by the
domain:specexecution seat for anos-devsubagent, which inherits this claim and this assignee — ⛔ it posts no secondClaim:and ⛔ never writes the assignee field.Batch independence: dispatched alongside #17385 and #16553. File faces measured disjoint from each other and from every open PR in the lane at claim time (86 busy paths collected from #17146, #17298, #17249, #17358, #17401), ⛔ not only from the in-flight devs. ⛔ Not folded — different defect shapes.
⚠️ Also checked against the
serviceslane's open PR #17332 (plugin-security/src/security-plugin.ts) — a fourth candidate was dropped for clashing with it.Verify-lock: read at claim time; ⛔ re-read
bash scripts/pm/os-verify-lock.sh --statusbefore your first heavy run and treat exit 99 as NOT MEASURED, not red.⭐ A count or a zero is not a reading until you look at what it matched. Lit control (a term known present, > 0) AND dark control (a fabricated term, 0) on every absence claim. Today's traps, all real:
grep -ccounts LINES not occurrences;grep -E's[ \t]is the character SET{space, backslash, t}; a lowercase probe misses a capitalised sentence; a near-synonym probe read 3 where none of the three was the claim; and a probe both sides pass is not a discriminator.⛔ Never capture an exit code through a pipe —
cmd > log 2>&1; EXIT=$?. A$?after a pipe is the last command's exit.check:migration-registry/check:spec-changes/check:upgrade-guide/check:generatedare NOT root scripts — bare invocation exits 254 = NOT MEASURED, not red. Correct:pnpm --filter @objectstack/spec check:….check:generatedis NOT MEASURED in an unbuilt worktree.check:react-declaration-parity— CORRECTED TODAY: AGENTS.md says it cannot run locally. It can.sdui.manifest.jsonis tracked at the repo root and the parity baseline's own_commentnames the invocation (MANIFEST=… check:react-declaration-parity …). ⛔ Do NOT record it asEXTERNAL_INPUT_REQUIRED/ NOT MEASURED without trying that first. (Filed as #17405; the seat had been repeating the stale claim.)Deliverable: a changeset (or
⚠️ The body MUST carry a line that STARTS with
⚠️ Attribution: session id in body prose, ⛔ not a footer — both MCP and raw REST append their own.
skip-changesetwith a measurement — 0 paths underpackages/and no packagefiles[]reaching them), a pin, and a draft PR withFixes #16340.Clause-②: no(oryes) —-,>,**prefixes tolerated;⇒or backticks make it a near miss andCheck Changesetgoes red.⛔ Do NOT flip ready, enqueue, or arm auto-merge.
Commit trailers — ⛔ model-free, exactly:
Report: structured JSON — what you measured with what each probe matched, what you changed, gate exits with NOT MEASURED called out, PR number and head sha, CI as newest-run-per-check-NAME plus the combined commit status (different populations), your clause-② reading, open questions, and out-of-scope findings each with a named carrier (⛔ no carrier ⇒ do not file).
Generated by Claude Code
fix(lint): read the registry's own type for an injected column so a f…
fix(lint): judge an injected column on the same axis as an authored o…
os-bill commentedon Sep 10, 2026
os-dev-report
{ "issue": 16340, "status": "done", "branch": "claude/issue-16340-lint-injected-temporal-columns", "pr": "https://github.com/objectstack-ai/objectstack/pull/17430", "pr_number": 17430, "head_sha": "d77c15a92d41ad1822623e2cbd9bfab71ad97cd3", "base_at_branch_point": "d57611dfd35da38100c72192526453187ec07e25", "premise_still_valid": true, "clause_2": "no — nothing moves in packages/spec/src. The card proposed extending resolveInjectedSystemColumns' plan to carry each injected column's type; that derivation already exists one module over as injectedSystemColumnDefs (packages/spec/src/data/injected-system-column-provenance.ts, the WHAT-half #8116 moved into the spec). The whole fix is a consumer change inside @objectstack/lint. Re-assessed after the file face grew: still no, no spec source touched.", "summary": "The card's core claim reproduced exactly: the lint object graph recorded registry-injected columns by NAME only, so resolveFieldPath returned an ok leaf with no meta and filter-preset-comparand's field-typed arm stayed SILENT on created_at / updated_at. Fixed at the seam by route 1 of the failure-fix order — the construct was DELETED, not checked around: GraphObject.injected is no longer a name set but a map from column name to GraphField carrying each column's registry definition, derived from injectedSystemColumnDefs (membership from resolveInjectedSystemColumns), so nothing is transcribed into the lint package. The verdict.injected bails in validate-preset-comparands and validate-dataset-references deleted themselves; no new check was added anywhere. TWO PREMISES FALSIFIED. (1) The dispatch's headline claim — 'a rule that cannot see a column reports a FALSE POSITIVE to an author, it tells them a field does not exist' — is FALSE on this tree: resolveFieldPath already answers ok for an injected leaf (that is what #5378 fixed), and driving the whole AUTHORING_RULES table over a real stack produced no such message from any rule. The author-visible symptom is SILENCE, a missed catch, exactly the direction the card's own Measured section states. (2) The card's 'Where a fix would go' — teach resolveInjectedSystemColumns' plan to carry types — was unnecessary: the sibling module already publishes that table, so packages/spec is byte-unchanged.", "author_visible_message": { "probe": "every AUTHORING_RULES entry run over one stack: one object declaring close_date:date / stage:select / amount:number and authoring no created_at, one dataset on it, one dashboard of six widgets", "before_verbatim": "NO FINDING AT ALL — no rule, no severity, on any of the three gap rows. There is no sentence to quote; that is the defect. Whole-run count 10 findings before / 13 after, and the diff of the two finding sets is exactly the three additions, nothing removed.", "after_verbatim_message": "\"last_30_days\" is a dashboard date-range PRESET name, not a filter value. It is only understood by the dashboard date-filter positions (dateRange.defaultRange, a date global filter's defaultValue), where the console lowers it to {date-macro} bounds before querying. As a bare \"$eq\" comparand nothing resolves it: a declared datetime/date field refuses the query at the engine (INVALID_FILTER / 400), and any other column compares the literal string. Write the date-macro window instead — e.g. { $gte: '{30_days_ago}' } — or an ISO date such as \"2026-01-15\". Refused at authoring time so the error surfaces where the filter is written.", "after_verbatim_hint": "Presets belong to the dashboard date-filter bar (dateRange.defaultRange, a date global filter's defaultValue). In a filter comparand, write the {date-macro} window the message names, or an ISO date.", "after_finding_shape": "severity error, rule filter-preset-comparand, where 'dashboard \"sales\" · widget \"w\"', path dashboards[0].widgets[0].filter.created_at", "pinned_how": "toBe on the full message and the full hint in validate-preset-comparands.test.ts — NOT toContain, and NOT only the graph shape. A pin on GraphObject.injected alone would have gone green while the author still received nothing.", "row_by_row": { "close_date: 'last_30_days' (authored date)": "refused before, refused after", "created_at: { $gte: 'last_30_days' } (arm 1, field-agnostic)": "refused before, refused after", "created_at: 'last_30_days'": "SILENT before, refused after", "created_at: { $eq: 'last_30_days' }": "SILENT before, refused after", "updated_at: { $in: ['last_30_days'] }": "SILENT before, refused after", "stage: 'this_quarter' (select column)": "silent before, silent after — the picklist reading arm 2 protects is untouched" } }, "failure_fix_route": "1 of 3 — deleted the construct that permits the error. The construct was the name-only injected set, which forced FieldPathVerdict to hand back a typeless ok leaf and forced consumers into verdict.injected bails meaning 'unanswerable'. Deleting it removed the bails; no check was added.", "derivation_not_transcription": "membership from resolveInjectedSystemColumns via injectedColumnsFor, slice from injectedSystemColumnDefs via the new injectedColumnDefsFor — two derivations over ONE plan, both the spec tables applySystemFields spreads at registration. object-graph.test.ts asserts the graph's type EQUALS the spec table's type by reading the table back, so a hand-copied 'datetime' literal in packages/lint cannot pass that pin. No such literal exists anywhere in the diff.", "file_face_declared_vs_real": { "claimed": "packages/lint/src/object-graph.ts, packages/spec/src/data/injected-system-columns.ts, plus their tests", "real": "packages/lint/src/{object-graph.ts, system-fields.ts, validate-preset-comparands.ts, validate-list-view-field-refs.ts, validate-dataset-references.ts} + their four tests + .changeset/lint-injected-temporal-column-types.md", "not_touched": "packages/spec/src/data/injected-system-columns.ts — the claimed spec file needed no edit at all", "why_it_grew": "validate-list-view-field-refs.ts was FORCED — its dotted-filter-head pin turned red in the package suite the moment the type arrived, because classifyDottedFilterHead now reads the same datetime the engine door reads. validate-dataset-references.ts was a bounded in-place fix: its verdict.injected bail carries the comment 'its type is registry-owned and invisible here', a claim THIS change makes false, so the bail went with it. Same defect class, mechanically identical to the two siblings, same gate family, no new verification surface.", "overlap_check": "diffed all five lane PR refs (#17146, #17298, #17249, #17358, #17401) against origin/main — 86 busy paths, the same count the claim comment measured. Exactly one intersection: PR #17298 edits validate-list-view-field-refs.ts lines 190-197 (a 3-line docblock change); mine is the adjacent skips paragraph at 203-210. Local trial merge `git merge-tree --write-tree HEAD refs/pull/17298/head` exits 0 with no conflicted path, and CI's 'No other open PR may claim the same single-writer path' check is green. validate-dataset-references.ts is held by none of the five." }, "inherited_rules": "Two more rules inherit the type in the same edit, which is the card's own ⭐ point. list-view-field-dotted now refuses created_at.x (a datetime scalar has nothing beneath it) and owner_id.name (a lookup stores an id, not an embedded document) — both already answered 400 INVALID_FIELD by assertFilterIsMaterializable and the REST ingress, so the linter's silence was the miss. dataset-include-unknown now judges an include[] entry naming an injected column: include:['owner_id'] joins, include:['created_at'] derives no join and is refused. `id` falls through the untyped branch of all three rules — the DRIVER provisions the primary key and no definition table describes it — so none of them invents a refusal there. A relationship HOP through an injected column stays unknowable/injected-hop deliberately (the slice now carries reference, but traversing would newly judge every path through a platform anchor wherever sys_user is compiled in); the docblocks say that instead of repeating the retired 'invisible here' claim.", "tests": "ABLATION — two legs, direction PREDICTED RED before running, both restored and proved restored. No dist is in the resolution path (same-package relative imports; the spec dist was not mutated), each leg proved the mutation reached disk by comparing `git hash-object` against `git rev-parse HEAD:PATH` before running, each restore is `git checkout HEAD -- PATH` proved by an EMPTY `git diff HEAD`, and the script carried `trap restore EXIT INT TERM` with absolute paths. Leg A (reinstate the verdict.injected bail in the oracle) :: 3 failed / 46 passed — the message pins fell. Leg B (starve injectedColumnDefsFor to an empty map) :: 5 failed / 44 passed — the derivation pin fell too. || pnpm --filter @objectstack/lint test :: exit 0 — 103 files / 3729 tests passed (3724 before this PR; one pre-existing pin was rewritten, see inherited_rules). An earlier run of the same command :: exit 1 with exactly that one pin red, which is how the inherited rule was found. || pnpm --filter @objectstack/lint typecheck :: exit 0. || pnpm --filter '@objectstack/lint...' build :: exit 0 (under scripts/pm/os-verify-lock.sh, VERDICT command-exit 0). || npx eslint . --no-inline-config --format json :: exit 0 — 6552 files linted, population read from eslint's own config, 0 errors / 0 warnings. THE WHOLE TREE, not a narrowing, so no narrowing evidence is owed. || Control bytes: pnpm check:nul-bytes :: exit 0 (8245 files); plus a direct `grep -naP` control-byte scan of all 10 changed paths :: exit 1 (none), with a LIT fixture containing a vertical tab :: exit 0 (probe fires) and a DARK fixture of plain ASCII :: exit 1 (probe silent). All exit codes captured before any pipe.", "gates": { "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 59 families; re-derived after the final commit and after `git fetch origin main`, the list is byte-identical — no family the later files added. --ran reconciliation: 59 derived, 59 run, 0 UNRUN.", "green": "55 of 59 :: exit 0, including check:nul-bytes, check:cross-package-test-inputs, check:test-source-alias, check:type-check-coverage, check:published-files, check:engine-double-contract, check:changeset-gate-self-tests, check-adr-0087-registration --base origin/main, check-empty-changeset --base origin/main, check-changeset-no-major --base origin/main.", "not_measured": [ "pnpm check:dual-build-cjs-loads :: exit 3 — PREREQUISITE NOT MET, reads a whole-repo dist/. NOT MEASURED, neither pass nor fail. Left to CI.", "pnpm check:type-check-debt :: exit 3 — PREREQUISITE NOT MET, needs the built package closure. NOT MEASURED. Left to CI.", "pnpm check:lean-entry-closure :: exit 3 — PREREQUISITE NOT MET, @objectstack/objectql/core dist absent. NOT MEASURED. Left to CI." ], "recovered": "pnpm check:docs-transcript-drift :: exit 3 (NOT MEASURED, @objectstack/lint unbuilt), re-run after `pnpm --filter '@objectstack/lint...' build` :: exit 0 — 4 declared transcript values across 401 pages still equal what the registry derives.", "caveat": "The derivation warns the tree is 8 commits behind origin/main and that 3 files it reads changed upstream (scripts/check-test-source-alias.mjs among them). The derived family list is identical before and after fetching, so nothing here narrowed on a stale copy — but CI is the authority on the merged tree.", "outside_the_derived_total": "47 artifact-roster families, 11 declared wide-population families, 5 path-scheduled CI jobs and the always-runs tail are each outside the 59 and are CI's." }, "ci_at_report_time": { "note": "snapshot only — taken once at draft-PR time, no waiting. CI convergence is the seat's.", "newest_run_per_check_name": "29 check-runs, 29 distinct names. completed success: 'Flag docs affected by code changes', 'No other open PR may claim the same issue', 'No other open PR may claim the same single-writer path', 'Part-of PR must not also close its card', 'filter'. completed skipped: 'Build Docs', 'Console Pin Gate', 'Packed-tarball smoke (opt-in)'. The remaining 21 were in_progress with no conclusion — Build Core, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate 1-3/3, Dogfood Verify CLI, Governed Surface Queue Guard, Lint & Repo Gates, Temporal Conformance, Test Core 1-6/6, Type Check consumer/debt/source/workspace.", "combined_commit_status": "success, 1 context — a DIFFERENT population from the check-runs above (the legacy status API sees one context; the 29 check-runs are not in it), so the green there says nothing about the 21 still running." }, "labels": "none applied and none owed. Clause-② is `no`, so no needs:contract-review pairing. size/* and path labels are CI's (scripts/pr-labels.mjs --size / --paths run from the workflow, additively). Read back after PR creation: label set is empty.", "changeset": ".changeset/lint-injected-temporal-column-types.md — @objectstack/lint: minor. Not skip-changeset: @objectstack/lint is published (files: dist) and the diff changes what the published rules refuse. Minor rather than major under the repo's launch-window convention, and check-changeset-no-major --base origin/main :: exit 0 confirms major is refused here anyway. The entry states the behaviour change for authors and the GraphObject.injected type change for direct consumers of the seam.", "mcp_calls": "0 — zero MCP GitHub calls for the whole run. Card body and all 10 timeline items came from the public-repo embedded-JSON payload channel (front 10 edges / totalCount 10 / hasNextPage false, so the claim comment 5618981669 was read complete). PR creation, body read-back, labels, check-runs and combined status went over raw REST with curl. Lane PR file faces came from git (refs/pull/N/head), not the API.", "open_questions": [ { "question": "The claim comment says attribution must be session id in body PROSE, ⛔ not a footer, because both MCP and raw REST append their own. The os-dev contract's Definition of done says the PR body ends with a session-URL footer, and this session's attribution instruction says the same. I did both: the session URL appears in prose near the top AND the required footer at the end. Read-back confirms the platform then appended a SECOND session-URL footer of its own, so the stored body carries two.", "options": [ "A — leave it; the duplicate is cosmetic and the body survived byte-for-byte otherwise", "B — edit the body to drop my own footer, keeping only the prose line and the platform's appended one", "C — the seat rules which of the two instructions wins and the contract text is corrected once" ], "recommendation": "A for this PR, C as the standing fix — the two instructions genuinely conflict and I flagged rather than silently picking a side. The body read-back is clean apart from the extra footer: stored core bytes == sent bytes exactly, no sanitizer loss." }, { "question": "The file face grew past what the claim comment declared, and the contract says to re-declare it in the claim comment first — which I cannot do, because posting a second Claim: comment is forbidden and writing the assignee is forbidden. I declared it in the PR body and here instead.", "options": [ "A — treat the PR body + report as the re-declaration of record for a dev that inherits a claim", "B — the seat amends its own claim comment when a dev reports a grown face", "C — devs get an explicit non-Claim: spelling for a face amendment" ], "recommendation": "B — the seat owns the claim comment, and an amendment from the seat keeps the single-writer record readable by the next dedupe sweep. A is what I did in the meantime." } ], "out_of_scope_findings": [ "noted, not filed: a relationship HOP through an injected column is now traversable — the slice carries `reference`, so `owner_id.name` could resolve on `sys_user` where that object is compiled into the stack. Left as a skip on purpose (it would newly judge every path through a platform anchor, with its own findings to measure). Carrier: the docblock in packages/lint/src/object-graph.ts and the matching pin in validate-dataset-references.test.ts both name it in this PR, so the next author to want that traversal reads why it was declined. Not filed as a card because the carrier is in-tree and the change is a deliberate scope line, not a defect.", "noted, not filed: validate-widget-bindings.ts keeps `if (!verdict.injected) continue;` at its #8340 provenance half. That is the CORRECT use of the marker — only an injected leaf can be an unprovisioned anchor — and is untouched. Recorded so a future sweep for `verdict.injected` does not read it as residue. Carrier: this report and the FieldPathVerdict docblock, which now states what the marker means after the type arrived.", "noted, not filed: validate-expressions.ts, validate-field-consumers.ts and validate-semantic-roles.ts consume `injectedColumnsFor` as a name set only. They ask existence, which is the right question for them, so they are unaffected and need nothing. Carrier: none needed — no defect." ] }Generated by Claude Code
os-bill commentedon Sep 10, 2026
Claim AMENDED by the seat — file face grown, as the round's open question B asks
Claim: session_01MkQhmuuJAVDjmeWNixwDDH · branch claude/issue-16340-lint-injected-temporal-columns
Clause-②: no — re-affirmed against the ACTUAL diff.
packages/spec/srcis byte-unchanged; the whole fix is a consumer change inside@objectstack/lint.check-clause2-carriers --pair 17430exit 0.5618981669. The round reported that its file face grew past what that comment declared and correctly noted it could not amend it itself — ⛔ a dev may post no secondClaim:and ⛔ may not write the assignee. ⇒ Open question B answered: option B. The seat owns the claim comment, so the seat amends it. ⭐ Recording it here keeps the single-writer record readable by the next dedupe sweep, which is the whole point of the carrier.File face, as landed
packages/lint/src/object-graph.tspackages/spec/src/data/injected-system-columns.tspackages/lint/src/system-fields.tspackages/lint/src/validate-preset-comparands.tspackages/lint/src/validate-list-view-field-refs.tspackages/lint/src/validate-dataset-references.ts.changeset/lint-injected-temporal-column-types.mdWhy it grew, and both reasons are good ones:
validate-list-view-field-refs.tswas forced — its dotted-filter-head pin turned red in the package suite the moment the type arrived, becauseclassifyDottedFilterHeadnow reads the samedatetimethe engine door reads. ⭐ An inherited rule discovering itself through a red pin is the correct way for scope to grow.validate-dataset-references.tswas a bounded in-place fix: itsverdict.injectedbail carries the comment "its type is registry-owned and invisible here" — a claim this change makes false. ⇒ The bail went with the claim. Same defect class, same gate family, no new verification surface.⭐ And the round checked the grown face against every open PR in the lane before landing it: 86 busy paths across the five refs, exactly one intersection — PR #17298 edits
validate-list-view-field-refs.ts:190-197(a docblock) while this edits:203-210. Proven non-conflicting by a localgit merge-tree --write-tree(exit 0, no conflicted path) and by CI's single-writer check being green. ⛔ That is the check my dispatch order asked for, run without being asked twice.⛔ And my order's headline claim was FALSE — corrected here
My dispatch said: "a rule that cannot see a column reports a FALSE POSITIVE to an author — it tells them a field does not exist when the registry injects it."
Measured: it does not.
resolveFieldPathalready answersokfor an injected leaf — that is what #5378 fixed — and driving the wholeAUTHORING_RULEStable over a real stack produced no such message from any rule.⇒ The author-visible symptom is SILENCE — a missed catch, which is exactly what the card's own Measured section says. ⛔ I overstated it into a false-positive claim the card never made, and the round checked instead of believing me. ⭐ That is the second time today an order of mine asserted a symptom it had not measured.
Generated by Claude Code
fix(lint): a field-typed rule reads the registry's own type for an in…
fix(lint): enter the publicPicker object reader by schema position, n…