Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(core tests): object-validation-engine.test.ts:162 authors { dialect, ast } with no source, which today's spec makes required — the other of the gate's 2 diagnostics #9979

Closed

Description

@os-tesla

Dedup keywords: object-validation-engine.test · dialect cel ast missing source · ValidationRule source required · spec main shape gate TS2322 dialect · core validation engine fixture

Reported by the Spec Main Shape Gate on PR objectui#9877, check run 105804001660, on head 2b3836f07 at 2026-09-19T00:19:51Z. Filed by the domain:ui#2 execution seat. ⛔ Ungraded and ⛔ unrouted.

The diagnostic, quoted from the gate's own table

objectui file line code
packages/core/src/validation/__tests__/object-validation-engine.test.ts 162 TS2322

Type { dialect: "cel"; ast: { op: string; args: never[]; }; } is not assignable to type string \| { dialect: "cel" \| "cron" \| "template"; source: string; ast?: unknown; meta?: { rationale?: string \| undefined; generatedBy?: string \| undefined; } \| undefined; }.

Compiled against objectstack-ai/objectstack@87f776bd7210.

What it says

The expression object the fixture authors carries dialect and ast but no source, and on today's objectstack main the declared shape makes source: string required — ast is the optional one. ⇒ an authored expression that names its dialect and hands over a parsed AST, without the text it was parsed from, is no longer a thing the contract accepts.

⭐ This is a different break from the page retirement that the rest of this family is about: different member, different union, no relation to ViewType. It happens to surface in the same gate run.

⚠️ ⛔ Not measured by this seat: whether any non-test objectui code authors the same { dialect, ast }-without-source shape. If it does, the fixture is the smaller half of the problem and the card's scope is wrong — that census is the first thing whoever takes this card should run, with its control lit.

Why it matters now

It is one of only 2 diagnostics the gate currently reports (down from 20), and it blocks objectui#9860 (priority:p1), the card holding PR objectui#9877. ⚠️ That 2 is a lower bound: Tasks: 18 successful, 24 total, failing at @object-ui/core#type-check, so 6 tasks never ran — objectui#9944's property.

Refs: objectui#9860 (the gate card, p1) · objectui#9944 (the lower-bound property) · PR objectui#9877.


Filed by the domain:ui#2 execution seat (session_018HrVaotisyhgmot9o2MLRq) at 2026-09-19T00:24Z from check run 105804001660's job log. ⛔ No line was measured in a checkout by this seat; it is the gate's reading, relayed with its provenance.


Generated by Claude Code

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 19, 2026
  2. os-tesla commented on Sep 19, 2026

    @os-tesla
    CollaboratorAuthor

    域与优先级按在飞卡衍生继承,⛔ 不是本席定级

    domain:ui#2 执行席,session_018HrVaotisyhgmot9o2MLRq,2026-09-19T00:28Z。本卡从 finding 转为 pm:queue + domain:ui + priority:p1,依据是章程里唯一那条绕过分诊的通道,原样引用:

    在飞卡衍生三分:不修它验收过不过;in-scope ⇒ 父卡 sub-issue,认领席自有、优先级继承。该 sub-issue 带父卡域与优先级直接派发,唯一绕过分诊。

    三分判据逐条,现读:

    判据 读数
    父卡在飞? objectui#9860 —— priority:p1、domain:ui、assignee os-tesla(本席),其交付 PR objectui#9877 处于 HELD
    「不修它,父卡验收过不过?」 过不了。 本卡是门禁当前报出的两处诊断之一;门禁红着,PR objectui#9877 就不能落地 —— 而 advisory 门禁红着进 main 是共享损伤
    in-scope? 是。本卡由该 PR 自己的门禁运行 105804001660 报出,⛔ 不是顺手发现

    ⇒ 继承 domain:ui + priority:p1,与父卡同。⛔ 本席没有做任何定级或路由判断 —— 这两个值是抄父卡的,不是本席判的;若分诊席认为该走常规首触定级,摘掉即可,本席不争。

    ⚠️ 同时落在父卡上:objectui#9860 的 Blocked-by: 已于 2026-09-19T00:24Z 改为 #9978 #9979 #9943。


    domain:ui#2 执行席 · session_018HrVaotisyhgmot9o2MLRq · 父卡的三个标签与 assignee、以及门禁运行号都在本动作 2026-09-19T00:28Z 现读。


    Generated by Claude Code

  3. self-assigned this
    on Sep 19, 2026
  4. os-tesla commented on Sep 19, 2026

    @os-tesla
    CollaboratorAuthor

    Claim: objectui#9979 — domain:ui#2 execution seat

    Seat: domain:ui#2
    Branch: claude/issue-9979-validation-dialect-source-required
    Thread-read: 5737842477

    Claimed at 2026-09-19T00:29Z by session_018HrVaotisyhgmot9o2MLRq (GitHub identity os-tesla). Labels first, this comment second, the thread re-read third — the id above is the newest comment on the card.

    取卡前置 —— 北极星第 3 条,本动作现读

    ⛔ 下表两行是板面读数,取数时刻 2026-09-19T00:29:12Z(REST GET /repos/{o}/{r}/issues?state=open&labels=priority:pN,两仓各两次)。此行为 H44 的就地补正:原文把计数写成了裸数字而没带取数时刻,按 2026-09-02 的裁定那样的读数应当被当作未取。⛔ 这是补上原读数的时刻,⛔ 不是重新测量后写成原样。

    仓(取数时刻 2026-09-19T00:29:12Z) open p0 open p1
    objectstack-ai/objectui 0 7
    objectstack-ai/objectstack 4 51

    ⇒ 门关着:p2/p3 的工具卡与契约卫生卡此刻 ⛔ 不派。本卡不落在禁面内 —— 它按「在飞卡衍生」继承了父卡 objectui#9860 的 priority:p1,而且它本身就是该 P1 的解锁项:objectui#9860 的 Blocked-by: 现读 #9978 #9979 #9943,本卡是其中之一,父卡的交付 PR objectui#9877 因门禁红而 HELD。

    前提 —— 门禁自己报的,⛔ 本席未在检出里复现

    本卡的读数来自 PR objectui#9877 自己的门禁运行 105804001660(head 2b3836f07,2026-09-19T00:19:51Z),对比基准 objectstack-ai/objectstack@87f776bd7210。⚠️ 那是当前读数:同一门禁在 12 小时前报的是 20 处,现在报 2 处,本卡是其中之一。

    ⚠️ ⛔ 而它是下界:同一运行打印 Tasks: 18 successful, 24 total,失败在 @object-ui/core#type-check ⇒ 6 个任务从未跑到。⇒ 「只剩两处」⛔ 不成立,「跑到的部分里只剩两处」才成立。

    ⛔ 本席没有在任何检出里复现这条诊断 —— 装着的 spec 仍是 17.4.0,那上面这一行编译得过。复现要把 objectstack main 构建出的 spec 注入进去,这是派发令的 step zero。


    domain:ui#2 执行席 · session_018HrVaotisyhgmot9o2MLRq · 门禁四格与父卡的 Blocked-by: 行都在本动作 2026-09-19T00:29Z 现读。


    Generated by Claude Code

  5. os-tesla commented on Sep 19, 2026

    @os-tesla
    CollaboratorAuthor

    os-dev-report
    {
    "issue": 9979,
    "status": "done",
    "branch": "claude/issue-9979-validation-dialect-source-required",
    "pr": "#9981",
    "session": "session_018HrVaotisyhgmot9o2MLRq",
    "premise_still_valid": true,
    "summary": "Reproduced the gate's TS2322 before editing anything: built @objectstack/spec from objectstack main (6819dcbd, a descendant of the card's 87f776bd) and injected it into this worktree's pnpm virtual store by remove-then-copy (hardlink count on the package's declaration file read 6 before, 1 while injected, 6 after revert) with TURBO_FORCE=true, and got BOTH of the gate's diagnostics back verbatim. The test named 'fails OPEN on an envelope with nothing evaluable, and says so' was pinning a shape the contract no longer has: objectstack's evaluated-expression-slots-source-required migration narrowed every EVALUATED slot, ScriptValidation.condition among them, from ExpressionInputSchema to EvaluatedExpressionInputSchema where source is required and non-blank. No source was invented beside the AST (it is synthetic with an empty argument list, and printCelAst answers null for an AST it cannot round-trip); instead the test now pins the same engine behaviour on both sides of the split the spec made - the still-authorable non-CEL dialect through the typed slot, and the ast-only envelope as wire data through a cast that says so, with the docblock recording that what it pins changed. No runtime code touched; the engine's reader is unchanged.",
    "census": "Run on the base with its control lit, BEFORE fixing. Population: every .ts/.tsx/.mjs/.json under packages/, apps/, examples/, scripts/ (excluding node_modules and dist), searched for the ast property beside dialect and separately for every dialect property occurrence. Control lit: the search returns the known positive (this card's own fixture) plus three more sites, so it is not a silent zero. Reading: all four sites authoring the ast-without-source shape are TEST files; the three in packages/app-shell are typed against ExpressionInput, the persistence union the same ruling deliberately left wide, so they still compile and are not a second half of this problem. ZERO non-test sites author it - every non-test constructor supplies a source (field-rule normalizer, predicate-input normalizer, CEL authoring surface, object-field inspector). Therefore the card's scope as written is CORRECT; the fixture is the whole of it. Instrument reach, stated not assumed: a shape assembled at RUNTIME from variables never appears in a source grep - the named target outside reach is the inspector path that builds an envelope by spreading an existing one and then setting source (a sibling path that spread without setting it would be invisible), and more broadly the renderer's whole input is server-sent JSON, which no source grep sees.",
    "tests": "INJECTED BUILD RED (before): pnpm --filter @object-ui/core run type-check under the main-built spec, wrapper exit 2, two diagnostics - normalize-list-view.pageResidual-8429.test.ts(146,13) TS2322 'page' not assignable to ViewType (objectui#9978's, untouched) and object-validation-engine.test.ts(162,9) TS2322 verbatim as the card quotes it. INJECTED BUILD GREEN (after): same command, the validation-engine file reports ZERO diagnostics; only the page one remains. INSTRUMENT CONTROL, both read from the SAME injected build: ScriptValidationSchema.condition declares source as a required string with ast optional (subject), while ExpressionInputSchema still declares source as an optional string (control) - subject and control differ exactly where the ruling says, so the instrument discriminates rather than answering everything the same way. PINNED SPEC: spec restored to the installed 17.4.0 (hardlink count back to 6, the injected-only declaration file absent), dependency closure rebuilt so no stale declaration answered, then pnpm --filter @object-ui/core run type-check - exit 0, no diagnostics. ABLATION (fix committed first): revert the file to its pre-fix bytes, proven on disk (new marker count 0 after, old line count 1) and proven not a no-op (mutated blob hash differs from HEAD's), which brings the TS2322 back verbatim on the injected build; restore via git checkout HEAD -- ABSOLUTE_PATH, proven by the blob hash matching the HEAD blob AND an empty git diff HEAD with clean git status (not by an exit code), which brings the injected type-check back to one diagnostic. trap on EXIT/INT/TERM, absolute paths throughout. VITEST from the REPO ROOT (never from a package dir): pnpm exec vitest run packages/core/src/validation/tests/object-validation-engine.test.ts gives Test Files 1 passed, Tests 41 passed; pnpm exec vitest run packages/core/src/validation/ gives Test Files 8 passed, Tests 144 passed. GATES: check-governed-queue-guard --test over the FINAL file list, verdict verbatim 'NOT GOVERNED - 2 path(s) checked against 5 governed surface(s); none matched. An ordinary pull request: the normal review and merge-queue route applies.' (exit 0); check-changeset-presence exit 0 ('1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)... EMPTY frontmatter... the explicit exemption and a complete answer'); check:control-bytes exit 0 (8044 tracked text files); check:new-line-citations exit 0 (0 new citations); check:test-path-roots exit 0; check:comment-mask-corpus exit 0 (within the residue it holds open). Plus a control-byte self-sweep over the two changed files: no match. LINT, narrowed to packages/core/ and declared: population read from eslint's OWN output for that run - it enumerated 261 files under eslint.config.js; count from --format json - 0 errors, 583 pre-existing warnings, exit 0, the changed test file 0/0, the changeset markdown not in eslint's population at all; invariance - eslint --print-config on the changed file resolves parserOptions with NO project and NO projectService, so type-aware linting is off and nothing in this diff can move a verdict on a file it does not touch. --no-inline-config was NOT added. Everything heavy ran behind bash /home/user/objectstack/scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=os-dev-9979; every VERDICT line read command-exit as reported above. NOT MEASURED: remote CI - the dispatch says no CI wait, the seat lands it.",
    "mcp_calls": "0 - no MCP GitHub tool was called at all; every GitHub read and write went through the REST proxy",
    "api_writes": "2 REST writes: POST /repos/objectstack-ai/objectui/pulls (draft PR 9981) and POST /repos//issues/9979/comments (this report). Reads of the card, its two comments and the PR read-back were GET. Two git pushes: the empty-branch routing probe before any edit, and the commit. NO label write - the dispatch forbids a dev-applied label, and objectui's skip-changeset label is read by no workflow or script and is forbidden here anyway since a changeset is present.",
    "open_questions": [],
    "out_of_scope_findings": [
    "noted, not filed: the three packages/app-shell test sites that author the same ast-without-source shape are typed against the unnarrowed persistence union ExpressionInput and compile fine against objectstack main today - recorded so the next reader of this family does not re-measure them. Who would meet this: whoever takes the rest of the objectui#9860 family. Handler: that family.",
    "noted, not filed: the gate run this card came from reported 'Tasks: 18 successful, 24 total' and failed at @object-ui/core#type-check, so six tasks never ran - already tracked as objectui#9944's lower-bound property. Nothing new claimed; repeated only because the app-shell reading above is exactly what those six tasks would or would not have surfaced. Handler: objectui#9944.",
    "noted, not filed: this branch's injected build shows the OTHER of the gate's two diagnostics, the 'page' TS2322 in normalize-list-view.pageResidual-8429.test.ts, still red - that is objectui#9978, already claimed and in flight in a sibling worktree, so it is untouched here. Handler: objectui#9978."
    ]
    }


    Generated by Claude Code

  6. os-tesla commented on Sep 19, 2026

    @os-tesla
    CollaboratorAuthor

    Seat review — ACCEPT

    PR objectui#9981, head 3567012cb, 2 files, reviewed against origin/main 0564d42e1. NOT GOVERNED (your run, verbatim: 2 path(s) checked against 5 governed surface(s); none matched). The seat lands it once CI converges.

    Step zero did the thing it was written for

    You reproduced both gate diagnostics verbatim before touching a line — spec built from objectstack 6819dcbd (a descendant of the card's 87f776bd), injected by remove-then-copy with the hardlink count read 6 → 1 → 6 across the whole operation, TURBO_FORCE=true. That is the injection done correctly rather than described correctly, and the restore proof is the half people skip.

    ⭐ And the instrument control is the one that makes the reading mean something: from the same injected build, ScriptValidationSchema.condition answers source required / ast optional while ExpressionInputSchema still answers source optional. Subject and control differ exactly where the ruling says they should — so the build is discriminating, not uniformly refusing.

    The census, re-run by the seat with a blunter instrument — and it took two wrong turns before agreeing with you

    ⚠️ Worth recording, because both wrong turns are the failure mode the charter's new clause names (控制通过 ≠ 问题问对):

    1. The seat first grepped dialect: 'cel' and got 8 non-test files, which looked like a contradiction of your 「zero non-test sites」. It is not a contradiction — it is a different question: mentioning the dialect is not authoring ast without source.
    2. The seat then grepped ast: across those non-test files and found exactly two hits — ActionEngine.ts and containers.tsx. Both dissolved on inspection: ActionEngine.ts:175 is the comment 「No cast:」 and containers.tsx:1019 is the Tailwind class l**ast:**border-b-0. Substring noise, not constructors.

    ⇒ your census stands, confirmed by a second instrument that had to be corrected twice to reach it. Zero non-test sites author the shape; the fixture is the whole of the card, and the card's scope as written is right.

    Your reach statement is the part the seat could not have improved: a shape assembled at runtime from variables never appears in a source grep, and the named target outside reach — the inspector path that builds an envelope by spreading an existing one and then setting source, where a sibling path that spread without setting it would be invisible — is a real specimen rather than a formula. That is what a zero owes now.

    The repair, and why refusing to invent a source is the right call

    You did not put a placeholder source beside the AST. The AST is synthetic with an empty argument list and printCelAst answers null for an AST it cannot round-trip ⇒ any source you wrote would have satisfied the type and lied about the value. Instead the test pins the same engine behaviour on both sides of the split the spec actually made: the still-authorable non-CEL dialect through the typed slot, and the ast-only envelope as wire data through a cast that says so — with the docblock recording that what it pins changed.

    ⇒ the fixture still fails loudly if the engine's fail-open behaviour regresses, which is the thing this card must not destroy. Runtime code untouched; the engine's reader unchanged.

    The legs

    Injected RED before (both diagnostics, the page one being objectui#9978's and left alone) → injected GREEN after for this file, with only objectui#9978's remaining. Pinned-spec build exit 0 after restoring 17.4.0 and rebuilding the closure, so no stale declaration answered. Ablation from the committed state with the mutation proven not a no-op by blob-hash difference, and the restore proven by hash equality and an empty git diff HEAD — ⛔ not by an exit code, which is the distinction that matters. 41 and 144 tests from the repo root. Lint narrowed with all three declarations, population read from eslint's own output.

    Findings

    All three out_of_scope_findings are correctly unfiled and each names its handler. The app-shell one is the useful one to have written down: those three sites author the same shape but are typed against ExpressionInput, the persistence union the same ruling deliberately left wide, so they compile and are ⛔ not a second half of this problem. That saves the next reader of this family a re-measurement.


    domain:ui#2 execution seat · session_018HrVaotisyhgmot9o2MLRq · the file list, the two census re-runs and the disambiguation of their two false hits were taken in this act at 2026-09-19T00:54Z against origin/main 0564d42e1 and branch head 3567012cb; the injection, its hardlink counts, the ablation blob hashes and the gate exits are the dev's, relayed and attributed.


    Generated by Claude Code

  7. os-tesla commented on Sep 19, 2026

    @os-tesla
    CollaboratorAuthor

    Landed — verified by content on origin/main

    PR objectui#9981 merged at 2026-09-19T01:33:32Z; merge commit c26d990037e2e3b59e7bc5510574a581ca86e3b5. Reading taken at 2026-09-19T01:36Z against origin/main (freshly fetched). BEFORE = 8a7e09fa158a01889607b46c855879e99fc29415.

    ⛔ Verified by CONTENT on both refs, never by sha and never by ancestry. Path both legs: packages/core/src/validation/__tests__/object-validation-engine.test.ts.

    reading BEFORE AFTER
    ⭐ ast: { envelopes 1 1 — unmoved
    dialect: 'cel' lines 3 3 — unmoved
    source: keys 2 3
    CONTROL — expect( (instrument lit on both) 70 71

    ⭐ The source: row, read properly rather than counted

    A bare count says source: went up by one, and the card's whole point was that the dev must not invent a source beside the synthetic AST. So the count alone would read as a violation. It is not — I read the diff:

    // Authorable: a dialect the CEL evaluator does not run.
    const nonCelDialect: ScriptValidation = { …, condition: { dialect: 'cron', source: '0 9 * * 1-5' } };
    
    // Wire-only: refused by the narrowed slot, still carried by persistence.
    const astOnly: ScriptValidation = {
      …,
      condition: { dialect: 'cel', ast: { op: '<', args: [] } } as unknown as ScriptValidation['condition'],
    };

    ⇒ the added source belongs to a new control fixture (cron), and the astOnly envelope still carries no source — exactly the discipline the card asked for. ⛔ The count moved; the thing the count was watching did not.

    What the fix pins

    The fixture now stands on both sides of the spec's evaluated/persistence split, and says so in the file rather than leaving a reader to infer it:

    • a non-CEL dialect stays authorable through the typed slot — condition takes the whole ExpressionDialect enum, and cron / template are not predicates;
    • an ast-only envelope is no longer authorable at this slot — objectstack's evaluated-expression-slots-source-required migration narrowed every EVALUATED slot from ExpressionInputSchema to EvaluatedExpressionInputSchema, where source is required and non-blank. The PERSISTENCE union is deliberately not narrowed by that ruling, so the shape still travels the wire and still reaches this reader — which is why it stays pinned, now through a cast that names it as wire data.

    ⭐ And the reason no source was invented is written into the file where the next reader will hit it: the AST is synthetic (args: [] carries no operands), so no text was ever parsed into it, and the migration's own recovery printCelAst(ast) answers null rather than guessing. A source here 「would satisfy the type and lie about the value」.

    Both arms now assert the engine abstains loudly: results is empty and console.warn fired for each rule by name. Delivered face from the merge's own stat: the test file plus a changeset — ⛔ the engine reader itself is untouched, which is the other thing this card required.

    Closing state: auto-closed completed by the PR's Fixes line. pm:dispatched and the assignee cleared in the same label write; pm:blocking comes off with them — objectui#9860's Blocked-by: edge is discharged.

    Implemented-by: os-dev subagent, dispatched by domain:ui#2 (session session_018HrVaotisyhgmot9o2MLRq)
    Reviewed-by: domain:ui#2 — the content verification above, including the diff read that resolved the source: count


    Generated by Claude Code

  8. removed their assignment
    on Sep 19, 2026
  9. added a commit that references this issue on Sep 28, 2026
    c26d990
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions