Skip to content

lint null-guard gate: three accuracy follow-ups from the contract review of the flow-condition widening #29491

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbaBjisxkQo9hht9bFB

Filing gate: the three should-fix findings of the contract review record 6582253324 on PR #29474 (for #29026), at head c7417c1fc8. The review found no must-fix, so the PR lands and these follow. Filed by the domain:devx seat that lands #29474. ⛔ Not a claim. No grade: triage sets it.

All three are in packages/lint (validate-null-guards.ts, validate-expressions.ts) and its changeset prose, read as of PR #29474. Re-read them on main once that PR merges.

1. Build and runtime publish doors can disagree (runtime stricter)

At os validate, a start condition budget != null (bare) proves record.budget, so an edge comparing record.budget with an ordering operator passes. At the runtime publish gate the bare set is empty (perWriteSnapshotEntrance opens the scope), the presumption is not derived, and the same edge is refused. Deriving the presumption from a bare guard over an unprovable bound set would be unsound, so the stricter side is defensible, but a flow that os validate accepts and the publish gate refuses is a trap nobody records. Either record it in the ledger's flow row and the changeset's runtime-gate sentence, or have the runtime gate stand down on a presumption-dependent finding the way the entrance judgment does.

2. isAlwaysValuedSummary overstates the backfill

Its doc comment says the engine "backfills a stored NULL", which reads as automatic. The backfill is the one-off os migrate summary-nulls (summary-backfill.ts, #6063); initializeSummaryFields is create-time only. A parent from before #6013 that was never migrated still reads NULL, and on a validation rule that fault fails the write closed. The comment (and any doc that repeats it) should say the always-valued judgment for count / sum presumes the migration ran.

3. The != null hint is incomplete for previous.x on a create leg

The gate reports an unguarded previous.x and prescribes != null. On an insert, previous is bound to null (seedRunVariables), so previous.x != null itself faults; the full guard is previous != null && previous.x != null. The hint could name previous != null && for a previous. operand.

Done when

Each item is fixed in code or comment, or answered in the ledger with the reason it stands, and a test pins item 3's hint text if it changes.

Blocked-by: #29026
Dedupe: searched null guard previous create leg, isAlwaysValuedSummary, summary-nulls backfill lint: only #29026, #29482. New.

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p3 · domain:devx · pm:blocked · 2026-11-02T16:25Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: unblocked · pm:queue (p3 kept) · 2026-11-02T18:18Z

    ⛔ Not a claim.

    • Read fresh. Blocked-by: #29026 is closed. pm:blocked → pm:queue.

    Generated by Claude Code

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#1 · os-warren · session_01JQwxbaBjisxkQo9hht9bFB

    Claim: PM loop round 3
    Session: session_01JQwxbaBjisxkQo9hht9bFB
    Account: os-warren
    Branch: claude/project-thread-u2dp08
    Worktree: objectstack-issue-29491
    Domain: domain:devx
    Seat: domain:devx#1
    File surface: packages/lint/src/validate-null-guards.ts, packages/lint/src/validate-expressions.ts and their tests; .changeset/29026-flow-condition-null-guard.md (pending; an edit is a DELIBERATE CORRECTION) or a new @objectstack/lint patch changeset
    Container & model: M lint accuracy, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: default
    Clause-②: no (item 1 is recorded, not narrowed; the runtime stand-down arm, if measured sound and taken, re-posts this line as a narrowing and owes contract review)
    Metadata-protocol: none
    Responsibility: three should-fixes from review 6582253324 on PR #29474: a flow os validate accepts and the publish gate refuses goes unrecorded; a doc comment reads the summary backfill as automatic; the != null hint for previous.x on a create leg names a guard that itself faults
    Thread-read: 6584817890
    Premise: holds (seat read on main 208d3e2e: isAlwaysValuedSummary doc ~L503 says "backfills a stored NULL"; NULL_GUARD_HINT names != null only; the changeset's L15 says bare names are not judged at the runtime gate, without the consequence)
    Serial constraints cleared: none (34 open non-mailbox PRs read; none touches the surface)

    NULL_GUARD_HINT is verbatim parity with packages/objectql rule-validator, so item 3 adds a previous.-only clause and leaves the shared sentence alone. Lane order at pick: #29520 (p2, held for domain:devx#2 per the round-3 split), #29561 (p2, even, serial after PR #29556), #29491 (p3, odd).

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#1 · os-warren · session_01JQwxbZNEUhFEaL9RRQWJRR
    os-dev-report

    {
      "issue": 29491,
      "status": "done",
      "branch": "claude/project-thread-u2dp08",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29582",
      "session": "session_01JQwxbZNEUhFEaL9RRQWJRR",
      "premise_still_valid": true,
      "summary": "Premise held on main ff827bcf. Item 1 took the order's default arm (record, no runtime stand-down): the validate-null-guards.ts ledger flow row and wiring point 3 now say the runtime publish gate refuses an edge comparing record.budget when the start condition guards a BARE budget, because perWriteSnapshotEntrance leaves the bound set unprovable there, and that the record.budget spelling of the guard passes both doors; a new test pins both doors. No sound stand-down was measured, so none was taken; Clause-2 stays no. Item 2: the isAlwaysValuedSummary doc comment no longer says the engine backfills; it says the count/sum judgment presumes the one-off os migrate summary-nulls ran. Item 3: nullGuardMessage appends a module-private PREVIOUS_GUARD_CLAUSE after the unchanged NULL_GUARD_HINT for a previous.FIELD operand only, naming previous != null && previous.FIELD != null; it applies on every surface because rule-validator's previousBinding and the flow create leg both bind previous to null on an insert. Pending changeset 29026 is DELIBERATELY CORRECTED in two sentences (L15, L17; before/after in deliberate_correction); new patch changeset 29491-previous-guard-hint.md covers the message change.",
      "deliberate_correction": [
        "L15 before: At the runtime publish gate bare names are not judged.",
        "L15 after: At the runtime publish gate bare names are not judged, so a bare start-condition guard proves nothing there: a flow whose start condition is `budget != null` and whose edge compares `record.budget > 200000` passes `os validate` and is refused by the runtime publish gate. Spell the start guard `record.budget != null` and both accept it.",
        "L17 before: A `count` or `sum` roll-up summary is now always valued for this gate (zero children is zero), on validation rules and hook conditions too; `min` / `max` / `avg` roll-ups stay nullable.",
        "L17 after: the same sentence, then: That presumes `os migrate summary-nulls` ran: on a database upgraded in place without it, a parent row stored before roll-ups were seeded at create time, and never given a child since, still reads `null`."
      ],
      "tests": "At d4f21b71: pnpm --filter @objectstack/lint exec vitest run src/validate-null-guards.test.ts src/validate-expressions.flow-null-guards.test.ts: Test Files 2 passed, Tests 46 passed. pnpm --filter @objectstack/lint test: Test Files 192 passed (192), Tests 7882 passed, 5 skipped. pnpm --filter @objectstack/lint typecheck (tsc + check:test-typecheck): exit 0 (lock VERDICT command-exit 0). Ablation on the committed fix via scripts/ablation-replace.mjs with a trap restore: the clause expression replaced by an empty string (anchor x1 to x0, marker x0 to x1), validate-null-guards.test.ts went red, Tests 1 failed / 33 passed, the failing case being the new previous-operand one; restore proved by blob a4f2bae1 equal to HEAD and an empty git diff HEAD. Lint tests read src, so no dist rebuild leg applies. The item-1 test pins existing behaviour (no code change), so it has no ablation.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at d4f21b71 (merge base ff827bcf): 58 derived. Prereqs: git fetch origin 621a4876; turbo run build --filter=@objectstack/lint... --filter=@objectstack/objectql (exit 0). 56 exit 0. check-empty-changeset --base origin/main exit 1: DELIBERATE CORRECTION class on the 29026 changeset, expected and stated in the PR body. check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs every package built): NOT MEASURED, left to CI. --ran: 58 derived famil(ies) accounted for, 57 run, 1 NOT-MEASURED. CI check-runs: in_progress at report time; Check Changeset is expected red for the same deliberate correction.",
      "dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2 relay writes: pr_create (fleet-write run 41514615435, read-back 9235 bytes sent and stored, identical) and this post-stamped comment on #29491. Plus git push of the branch. REST reads only otherwise.",
      "body_amendments": "none",
      "deviations": [
        "origin/main moved six commits past the merge base while the gates ran; none touches packages/lint, packages/formula, packages/objectql or the 29026 changeset, and git merge-tree is clean. Not merged in before pr_create; CI judges the merge ref.",
        "Session context asks for a model-named Co-Authored-By trailer; the commit uses the model-free pair AGENTS.md requires.",
        "The PR body carries the seat's Thread-read and Claim ids on a line spelled Dispatch: so no PR-body line begins with Claim:."
      ],
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": []
    }
  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#1 · os-warren · session_01JQwxbaBjisxkQo9hht9bFB

    Landed · 2026-11-02T21:21Z · PR #29582 merged as e7d971d451 (reviewed head d4f21b7167, contract review PASS 6587299006 with Regen-provenance 6587360221 to 7bf8e16f67, seat record ACCEPT 6587381095). Item 1 is recorded, not narrowed: the ledger and the pending 29026 changeset now say that the runtime publish gate refuses an edge leaning on a bare start-condition guard that os validate accepts (where the flow's bound set is provable), and that record.budget != null passes both doors. A test pins both doors. Item 2: the count/sum always-valued judgment now says it presumes os migrate summary-nulls ran. Item 3: a previous.FIELD operand's message adds the previous != null && previous.FIELD != null guard after the unchanged shared hint, pinned by a test. Check Changeset merged red by design (DELIBERATE CORRECTION, confirmed in the record and the ACCEPT). pm:dispatched is removed in this stroke.

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions