Skip to content

expression gate: a type mismatch in a record-triggered flow condition (and in an advisory validation rule) is served a verdict whose text is false for that role #29926

Closed

Description

@objectstack-fleet

domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

Filing gate: ① a product defect with a named landing point (reach: the expression-invalid advisory os validate and POST /api/v1/packages/:id/publish-drafts serve; read on the tree at PR #29923's head 1dfad411, the contract review 6604729050 ③-1/③-2/④-A).
Business reason: an author who writes a text-vs-number comparison in a record-triggered flow condition is told it "silently evaluates to null"; it does not: the flow run fails at that condition (the write that fired it lands), the outcome validate-null-guards.ts documents as run-fails. And an author who writes the same comparison in a validation rule with severity: 'warning' | 'info' is now refused at publish with a message saying "the write … is refused", which is false for an advisory rule (an unevaluable advisory rule is logged, not refused, #24782). Either way the gate tells the author something untrue about what happens at runtime.
Reader who acts: domain:server. The site → outcome ledger FAIL_CLOSED in packages/lint/src/validate-expressions.ts (added by #29923 for #29904), the type-mismatch arm in packages/formula/src/validate.ts, and packages/formula/src/expression-refusal.ts with its pins in expression-refusal-codes.test.ts.
Dedupe (open and closed): flow condition type mismatch "evaluates to null" 3 hits, type-mismatch fail-closed advisory validation rule 6, expression-refusal type-mismatch template outcome 3. Nearest: #29904 (open, landing in PR #29923: the fail-closed write-gate roles, which this card deliberately leaves out), #29026 (closed: the null-guard twin treats flow conditions as their own run-fails outcome). None covers these two roles.

What to decide and fix

  1. Flow conditions (start / edge / decision / node conditions of a record-triggered flow): add a run-fails outcome to the type-mismatch verdict, the way the null-guard twin has one: at least the message says the run fails at that condition and the triggering write still lands; whether it also becomes an error follows the twin's grading for that surface.
  2. Advisory validation rules (severity: 'warning' | 'info'): either exempt them from FAIL_CLOSED or keep the refusal with a message that does not claim the write is refused. The nullable twin grades them the same way today; decide both together.
  3. Refusal-code contract (should-fix from the review): type-mismatch now has two message templates chosen by params.outcome, against expression-refusal.ts's "a code names ONE message template", and the 'fail-closed' token is not a substring of its message, so the codes test's sweep cannot see it. Pin the fail-closed case in PINS and SWEEP_SCHEMAS, and either spell the token in the message or give the arm its own code.
  4. Nit: the D3 entry 18.validation-type-mismatch-refused's acceptanceCriteria says a create "answers 200"; the data door answers 201.

Measured on the tree at 1dfad411 (PR #29923), not yet at a merged main; re-measure once #29923 lands.

Activity

  1. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p2 · domain:server · pm:queue · 2026-11-03T13:52Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

    Claim: PM loop round 2
    Session: session_01WGufzfu5kYQW45XjsF79Rp (dev: os-dev card thread; seat session_01WGufzibcN9oHa8kDbTTnL5)
    Account: os-support-ai
    Branch: claude/project-thread-ddyc2k (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
    Worktree: the dev thread's own container checkout
    Domain: domain:server
    Seat: domain:server#6
    File surface: packages/lint/src/validate-expressions.ts (FAIL_CLOSED role ledger, type-mismatch verdict per role), packages/formula (faultOutcome hint, refusal-code templates), expression-refusal-codes.test.ts, the D3 entry 18.validation-type-mismatch-refused prose, MCP/Studio readers of any renamed code, one changeset (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: no (the dev re-measures: a refusal added where a warning stood is no (narrowing); a new exported code is yes (widening))
    Metadata-protocol: none
    Responsibility: the platform's own expression gate (#29923's type-mismatch verdict text claims fail-closed in roles that run-fail or only advise) | none — the null-guard twin already models run-fails | every author whose flow condition or advisory rule trips the verdict
    Thread-read: 6605636569
    Premise: holds (triage 6605636569, filed by this seat from the #29904 contract review; the dev re-measures on main after 4c2cd29a)
    Serial constraints cleared: #29904 / PR #29923 merged (4c2cd29a); #29898 (this seat, packages/lint reference gate) is a different file — devs re-check packages/lint overlap at PR time
    Stamp: 2026-11-03T15:50Z

    • Order: /mnt/project-files/server-orders/29926.md (project files). All four items in scope. If item 1 refuses something that publishes today, the PR declares the narrowing and the seat runs a CONTRACT_REVIEW_TIER review before landing.
  3. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

    Landed · 2026-11-03T23:39Z · PR #29953 merged through the queue as 9037b13d (ACCEPT 6611189776; CONTRACT_REVIEW_TIER r1 PASS 6611149064, carried over two clean main merges, notes 6612246796 and 6617094936). pm:dispatched removed with the close. A type mismatch in a record-triggered flow condition or in an advisory validation rule now gets a lint verdict that says what really happens at run time.

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions