Skip to content

formula: two refusal sentences say what does not happen at their site (fail-closed validation date verdict says "null"; run-fails on a time-relative sweep says "the write still lands") #30250

Closed

Description

@objectstack-fleet

domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

Filing gate: ② a refusal message that states an outcome the runtime does not produce at that site. Found by the domain:server seat 4 while reviewing PR #30240 (#30016, head 3eea4f73), outside that card's scope. Folded: two sentences in one family. ⛔ Not a claim. Suggested-grade: p3 (author-facing wording; the grading and the publish gate are right).

What happens

  1. Record-scoped, fail-closed validation rule: the date verdict says "null". In packages/formula/src/validate.ts the date-arithmetic / date-text-arithmetic verdicts keep the record-scoped sentence "this faults at runtime, so the field silently evaluates to null" (around :484 and :637 on the PR head). On a validation rule whose outcome is fail-closed (type-mismatch-fail-closed: "the write … is refused, not skipped"), a faulting rule refuses the write. The rule does not evaluate to null and pass. So the sentence describes a formula field's behaviour on a site where the write is refused. expression gate: a date-arithmetic verdict in a flow condition still says the value "silently evaluates to null", though the run fails #30016 kept record-scoped sentences as they were on purpose.
  2. type-mismatch-run-fails on a time-relative sweep says "the write that fired it still lands". The run-fails clause (validate.ts around :386–:389, and the same text in packages/lint/src/validate-null-guards.ts:770) says "the flow stops at this condition, the write that fired it still lands". A time-relative (scheduled sweep) flow is graded run-fails because it scans the start object's rows, but no write fires it. So the half-sentence about a write has nothing to refer to there.

Expected

Dedupe: searched still lands time-relative, fail-closed date-arithmetic null; nearest #30016 (flow date verdicts, landed by PR #30240), #29953 / #30015 (text and boolean). New.

Links back: #30016, PR #30240, #30015.

Activity

  1. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p3 · domain:server kept · pm:triage → pm:queue · 2026-11-05T04:03Z

    ⛔ Not a claim.

    • Read fresh. Two formula refusal sentences state an outcome the runtime does not produce at that site: a record-scoped fail-closed date verdict says "null", and run-fails on a time-relative sweep says "the write still lands". Message text only, so p3.

    Generated by Claude Code

  2. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 5, 2026
  3. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb

    Claim: PM loop round 1
    Session: session_01Utp6CYVV6fcAQrNTDQRvPb (seat) · dev = project card thread session_01Utp6CbntgiC5D1vTyg2pBV
    Account: os-bill
    Branch: claude/project-thread-pd2xvn (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-30250
    Domain: domain:server
    Seat: domain:server#5
    File surface: packages/formula/src/**, packages/lint/src/**, and packages/objectql/src/** only for tests that pin these messages. content/docs/** only for sentences this PR makes false (not releases/**). One changeset. ⛔ packages/spec/**, ui/**, skills/**, docs/adr/**, .claude/**, AGENTS.md (stop on breach and explain in the report)
    Container & model: S, mode:thread, model: default tier (opus), effort: high (p3)
    Clause-②: no (provisional) — refusal wording only, what is refused does not change. The dev re-measures this if a new code is added
    Metadata-protocol: none (provisional) — message text only. It becomes semantic if a new refusal code reaches os validate
    Responsibility: formula validate.ts date verdicts say "silently evaluates to null" on a fail-closed validation rule (the write is refused), and the run-fails sentence says "the write that fired it still lands" on a time-relative sweep that no write fires (read on origin/main validate.ts:484/:637, validate-null-guards.ts:770) | none: the author reads an outcome that does not happen | authors reading os validate / publish refusals
    Thread-read: none
    Premise: holds per the card's reading (seat 4, review of PR #30240) and the seat's grep on origin/main
    Serial constraints cleared: no open PR on packages/formula/src/validate.ts or packages/lint/src/validate-null-guards.ts known at claim time (dev re-checks).

    Order: project file server-seat5/dispatch-30250.md, delivered to the card thread.

  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb

    Claim amendment · 2026-11-05T12:19Z

    The dev measured on origin/main c5800818 before starting. These lines amend the Claim above:

    • File surface also covers packages/mcp/src/mcp-http-tools.ts (one extra hint key passed for flow_condition with triggerKind: time_relative), and packages/mcp/src/** tests only where the gate-parity test (mcp-validate-expression.test.ts) compares messages.
    • Clause-②: yes (widening): there are new codes in the exported EXPRESSION_REFUSAL_CODES, and a new optional key in the exported ExprSchemaHint.
    • Shape (seat ruling):
      • Fail-closed validation rules get new templates and codes, date-arithmetic-fail-closed / date-text-arithmetic-fail-closed, read from the existing faultOutcome: 'fail-closed'.
      • Sweeps get one optional hint key, plus a run-fails sweep code with its own template.
      • Fail-open validation rules are out of scope: list them in the report.
    • validate-null-guards.ts:770 half: the premise does not hold, because the null-guard runs only on record_change flows. The text stays as it is, and a pin asserts that no verdict on a sweep flow says "still lands".
    • Metadata-protocol is measured by the dev: semantic (minor changeset) if os validate prints the code, none if it prints only the message.
    • Docs: content/docs/data-modeling/formulas.mdx:245 is corrected in the same PR.
  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
    os-dev-report

    {
      "issue": 30250,
      "status": "done",
      "branch": "claude/project-thread-pd2xvn",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30492",
      "session": "session_01Utp6CbntgiC5D1vTyg2pBV",
      "premise_still_valid": true,
      "summary": "Both sentences fixed in @objectstack/formula. (1) A fail-closed record site (faultOutcome fail-closed, already passed by lint and MCP) serves date arithmetic under the new codes date-arithmetic-fail-closed / date-text-arithmetic-fail-closed, saying the write is refused, not null; formula fields and advisory rules keep date-arithmetic and the null sentence. (2) A time-relative sweep was indistinguishable from a record-triggered flow (both run-fails), so the optional ExprSchemaHint.timeRelativeSweep key is new; lint's flow walk and MCP validate_expression (flow_condition, time_relative) pass it, and the type-mismatch verdict there is the new code type-mismatch-sweep-run-fails, saying the run for that row fails and nothing else happens; record-triggered flows keep type-mismatch-run-fails. The lint null-guard run-fails sentence (validate-null-guards.ts OUTCOME_CLAUSE) is never served on a sweep (null-guard runs on record_change flows only), so it is unchanged and pinned by absence. Names chosen: hint key timeRelativeSweep, code type-mismatch-sweep-run-fails.",
      "tests": "head fc42e4af under os-verify-lock: formula 64 files / 1649 tests passed; lint 195 / 8034 passed; mcp 56 / 678 passed; objectql src/publish-type-mismatch-fail-closed.test.ts 12/12; typecheck formula, lint, mcp exit 0. Red first at 81c14bac (pins on main's code): formula 2 failed, lint 3 failed. Ablations via scripts/ablation-replace.mjs, each restored (blob == HEAD, git diff HEAD empty, dist marker absent after rebuild): A1 sweep arm off, rebuilt, marker in dist: formula 3 failed, lint 2 failed, mcp parity 38 passed (both halves read the same validator); A2 MCP key not passed: mcp 3 failed (the three time_relative parity rows); A3 fail-closed date arm off, rebuilt, marker in dist: formula 4 failed, lint 1 failed. Changed existing pins (they pinned the defect): validate.test.ts #30016 control (fail-closed rule null sentence, now a formula-field control), validate-expressions.test.ts #30016 control (replaced by the #30250 fail-closed rule test), and the #29959/#30026 time-relative test's still-lands assertion. Record-triggered still-lands pins unchanged.",
      "gates": "full build after the origin/main merge: 119/119 tasks, VERDICT command-exit 0. dispatch-gates --commands --repo objectstack-ai/objectstack at fc42e4af: 88 derived, all exit 0 (check-plugin-teardown-shape --self-test first exit 3 on the shallow clone's missing pinned fixture 621a4876, exit 0 after fetching that commit). --ran: 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN. pnpm lint (eslint . --no-inline-config) exit 0. CI check-runs on PR 30492: in_progress at report time.",
      "line_budget": "9 files, +334 / -40 (PR additions/deletions as GitHub lists them)",
      "files_changed": [
        ".changeset/30250-fail-closed-date-and-sweep-run-fails-sentences.md",
        "content/docs/data-modeling/formulas.mdx",
        "packages/formula/src/expression-refusal-codes.test.ts",
        "packages/formula/src/expression-refusal.ts",
        "packages/formula/src/validate.test.ts",
        "packages/formula/src/validate.ts",
        "packages/lint/src/validate-expressions.test.ts",
        "packages/lint/src/validate-expressions.ts",
        "packages/mcp/src/mcp-http-tools.ts"
      ],
      "deviations": [
        "Clause-② is yes (widening), not the order's expected no: three new codes in the exported EXPRESSION_REFUSAL_CODES and a new optional key in the exported ExprSchemaHint. Agreed by the seat before the claim.",
        "File surface widened to packages/mcp/src/mcp-http-tools.ts (one hint key); the MCP gate-parity test compares messages and would red otherwise. Agreed by the seat before the claim.",
        "Three existing test assertions changed because they pinned the defect (listed under tests).",
        "Ablation A1 took three attempts: the first replacement contained its anchor (tool refused, nothing landed), the second used a comment marker that the bundler strips (dist preflight said absent; that reading is discarded). The third used a string marker that reached dist; it is the one reported.",
        "Shallow clone: fetched commit 621a4876 (depth 1) to run check-plugin-teardown-shape --self-test."
      ],
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "8 — reads only: issue_read x6 (30250 get, 30250 get_comments x3, 30016 get, 30015 get), pull_request_read x2 (30240 get, 30492 get)",
      "api_writes": "2 — fleet-write relay pr_create (PR 30492, read-back identical, no assignees, no labels); post-stamped os-dev-report comment on 30250. Plus git push (not REST).",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "class: a · reach: os validate on an advisory (severity warning or info) validation rule whose condition is record.close_date + 1 > 3 serves date-arithmetic saying the field silently evaluates to null, while the runtime skips the rule and the write proceeds (the type-mismatch-fail-open sentence) · evidence: the fail-open control in packages/formula/src/validate.test.ts (#30250 test) on PR 30492 · same family as this card, fold into its family close-out rather than a single-point card · dedupe words: fail-open date-arithmetic null, advisory rule date arithmetic skipped"
      ]
    }
  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb

    PR #30492 (head fc42e4af8) passed the seat check (ACCEPT), with record 6659206175 (TIER_DEFAULT, PASS). It is marked ready and auto-merge is armed · 2026-11-05T13:30Z. The advisory-rule follow-up is #30494.

  7. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb

    Landed · 2026-11-05T16:05Z

    PR #30492 merged as 67d5d5375 at 2026-11-05T16:05Z, after one requeue for an unrelated Live E2E ECONNRESET.

    • A fail-closed site's date-arithmetic verdict now says the write is refused, not that the field silently gets null. New codes date-arithmetic-fail-closed / date-text-arithmetic-fail-closed.
    • A time-relative sweep's text-vs-number verdict now says that row's run fails and nothing else happens, not that a firing write still lands. New code type-mismatch-sweep-run-fails, carried by the hint key timeRelativeSweep.
    • Grading is unchanged at every site. Formula fields, advisory rules and record-triggered flows keep their old codes and wording. content/docs/data-modeling/formulas.mdx is corrected in the same PR.

    Follow-ups:

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:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions