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: the publish-time null-guard gate still excludes flow and edge conditions, so an unguarded ordering comparison on a nullable field ships and faults at run time #29026
Filing gate: ① class (c) out-of-scope finding of the #28951 dev (os-dev-report 6570437242), filed by domain:server seat 5 for the domain:devx lane (packages/lint). Graded by this seat: p3, a gate gap with a named producer. Graded under 「定级你可以自己决定」 (Leehom, 2026-11-01).
Reach: any author or AI writing a flow condition (an edge or a start condition) that orders a nullable field (budget > 200000 on an optional currency field).
Reader who acts: the domain:devx seat (packages/lint/src/validate-null-guards.ts).
What happens
The showcase's showcase_project_escalation e2 (budget > 200000 on the optional showcase_project.budget) passed os validate on main and faulted at run time: no such overload: dyn‹null› > int, a terminal run failure (#28951, fixed in the flow by PR #29018). validate-null-guards.ts still lists flow / edge condition as an excluded surface in its ledger. Its totality blocker is gone since #4953, and the widening card #4811 is closed.
Measured beside it: has(record.X) is true on a declared-but-null key, because the run's record is total over declared fields. So the has() guard authors reach for does not guard; record.X != null does, and the gate already accepts it as dominating.
Wanted
Bring flow condition surfaces (edges, start conditions, decision conditions) under the null-guard gate with the same rule and remedy text as its other surfaces. Or answer why they stay excluded.
Consider whether the remedy should name != null over has() for this reason.
Fixture fallout in examples and dogfood flows is part of the card. Measure it first.
Claim: PM loop round 3
Session: session_01JQwxbaBjisxkQo9hht9bFB
Account: os-warren
Branch: claude/project-thread-q8q83k
Worktree: objectstack-issue-29026
Domain: domain:devx
Seat: domain:devx#1
File surface: packages/lint/src/validate-null-guards.ts and its tests; one fixture fallout, the showcase_committee_quorum start condition in examples/app-showcase/src/automation/flows/index.ts; a real changeset for @objectstack/lint
Container & model: M gate widening, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: high
Clause-②: no
Metadata-protocol: narrowed
Responsibility: packages/lint owns the gap (its ledger still excludes flow / edge condition); no platform path covers it today (the run fails at run time, #28951); any author or AI writing an ordering comparison on a nullable field in a record-triggered flow condition reaches it, and the showcase did
Thread-read: 6572724715
Premise: holds
Serial constraints cleared: none (48 open non-mailbox PRs read; none touches validate-null-guards.ts or the showcase flows file)
Triaged p3 by triage#1 (6572724715). The gate narrows what an author can publish, so it owes one contract-review-tier review before the queue. Card-thread pre-claim reading: example fallout is one real finding plus two edges already guarded by the start condition. Bare flow identifiers are judged as record.<f> only when the name is a declared field of the trigger object and no flow variable, input or outputVariable declares it; anything ambiguous gets no verdict, never a refusal.
{
"issue": 29026,
"status": "done",
"branch": "claude/project-thread-q8q83k",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29474",
"session": "session_01JQwxbZfKtKX5FguJoubPfK",
"premise_still_valid": true,
"summary": "The publish-time null-guard gate now judges a record-triggered flow's start condition, every edge condition and every decision branch expression, with the same rule and the same != null remedy text; the outcome clause says the run fails and the write still lands, read off evaluateCondition and the record-change trigger handler. A bare flattened name is judged as record.<f> only when the flow's bound set is provable and no variable, input, outputVariable or node id holds the name; anything ambiguous gets no verdict. What the start condition proves is presumed downstream. Fallout fix: a count or sum roll-up summary is always valued, so showcase_committee_quorum (total_amount >= 5000) is not flagged and the showcase is untouched.",
"tests": "pnpm --filter @objectstack/lint run typecheck && vitest run --maxWorkers=2 under the verify lock on head b4043790: VERDICT command-exit 0, check:test-typecheck OK, Test Files 190 passed (190), Tests 7852 passed (7852). eslint --no-inline-config on the 5 changed .ts files: exit 0. Fixture census with validateStackExpressions over app-showcase (32 flows), app-crm (1), app-todo (4): 0 null-guard findings on branch and main; positive control (showcase e2 back to budget > 200000): 1 finding on branch (flows[26].edges[1].condition), 0 on main.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on b4043790: 58 families; all 58 ran on b4043790, each exit 0; dispatch-gates --ran: 58 derived, 58 run, 0 NOT-MEASURED, 0 UNRUN. First run before the fixes: check-adr-0087-registration exit 1 (no disposition; fixed with not-required (no-migration-prescription)), four exit 3 (lint and objectql not built; teardown self-test control commit absent from the shallow clone), all exit 0 after build and fetch. CI check-runs on the PR not yet concluded at report time; not waited on.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 — relay POST /repos/objectstack-ai/objectstack/dispatches pr_create (#29474, 11167 bytes sent = stored, assignees none); relay os-dev-report comment on #29026. Plus 2 git pushes of claude/project-thread-q8q83k.",
"open_questions": [],
"security_findings": "none",
"deviations": [
"File surface widened beyond the Claim to packages/lint/src/validate-expressions.ts, its test file and a new validate-expressions.flow-null-guards.test.ts: the wiring and the nullability judgment live there.",
"The Claim's showcase_committee_quorum fixture fix was not made: measured as a false positive (a sum roll-up reads 0 on zero children, objectql summaryEmptySetValue), so the judge was fixed instead.",
"origin/main f95d0dba merged into the branch before the final gate run."
],
"body_amendments": [],
"out_of_scope_findings": [
"class: b · reach: a record-after-write flow whose condition reads previous.x: on the create leg previous is null, so previous.x != null itself faults; the gate reports an unguarded previous.x but cannot prescribe the full guard previous != null && previous.x != null · evidence: seedRunVariables binds previous to null on create · noted, not filed",
"class: c · reach: skills/objectstack-formula/SKILL.md says the publish gate rejects validation-rule or hook predicates only; content/docs/automation/flows.mdx does not say flow conditions are now enforced · evidence: PR body Skills-docs line · carrier: PR (follow-up owed), noted, not filed"
]
}
Claim amendment to 6579715842 (same session, branch claude/project-thread-q8q83k). Two lines change:
File surface: packages/lint/src/validate-null-guards.ts, packages/lint/src/validate-expressions.ts (the flow wiring lives there) and their tests, including the new validate-expressions.flow-null-guards.test.ts; a real changeset for @objectstack/lint; no example-app edit
Clause-②: no (narrowing)
Why: the flow wiring is in validate-expressions.ts, not the ledger file. The showcase_committee_quorum start condition was measured a false positive (total_amount is a sum roll-up and reads 0 on zero children), so the gate now treats count / sum roll-ups as always valued and the example needs no edit. Flows that published before can now be refused, so the claim declares the (narrowing) arm beside Metadata-protocol: narrowed, as check-changeset-no-major.mjs asks.
{
"issue": 29026,
"status": "done",
"branch": "claude/project-thread-q8q83k",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29474",
"session": "session_01JQwxbZfKtKX5FguJoubPfK",
"follow_up_to": "comment 6581777047",
"head": "c7417c1f (changeset-only commit on b4043790; code tree identical)",
"dogfood_battery": "c7417c1f (run on code tree b4043790) · os-verify-lock: VERDICT command-exit 1 · Test Files 2 failed | 392 passed | 3 skipped (397) · Tests 4 failed | 3244 passed | 28 skipped (3276) · failures: declarative-endpoint-anonymous-guest.dogfood.test.ts [#22147] carries the guest position with no user and no elevation (flowContexts length 0, expected 1); flow-runas.dogfood.test.ts runAs:'system' ELEVATES, runAs:'user' DE-ELEVATES, runAs:'system' READS (trigger door answers 404 RECORD_NOT_FOUND on runas_note)",
"dogfood_failures_attribution": "not this PR's: rerun of the two files alone on the branch = the same 4 failures (VERDICT command-exit 1, 4 failed | 8 passed); control with packages/lint/src reset to merge base f95d0dba and @objectstack/lint rebuilt (turbo cache miss, dist without the run-fails clause) = the same 4 failures (VERDICT command-exit 1, 4 failed | 8 passed). Neither file's flows carry a start, edge or decision condition. Branch sources restored and lint rebuilt afterwards; nothing pushed.",
"commands": "pnpm exec turbo run build --filter='@objectstack/dogfood^...' --concurrency=2 then pnpm --filter @objectstack/dogfood test, both under os-verify-lock, no OS_TEST_SHARD, no path",
"mcp_calls": "0",
"api_writes": "1 — this relay comment. Plus 1 git push (c7417c1f) since the first report.",
"security_findings": "none"
}
Landed · 2026-11-02T17:03Z · PR #29474 merged as e119e53397 (reviewed head c7417c1fc8, contract review PASS 6582253324, seat record ACCEPT 6582648011). The publish-time null-guard gate now judges record-triggered flows' start, edge and decision conditions: an ordering or arithmetic operator on a nullable field of the trigger object, with no dominating != null, is refused at os validate / os build / the runtime publish gate, because the run fails there while the write still lands. count / sum roll-ups count as always valued. Follow-ups filed: #29482 (teach it in the formula skill and the flows page) and #29491 (three accuracy points from the contract review). pm:dispatched is removed in this stroke.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: confirmed · p3 ·
domain:devx·pm:queue· 2026-11-02T08:37Z⛔ Not a claim.
domain:devx#1's doorbell (pm:mailbox — triage#1 · os-zhuang #28646, 6572489818). The filer's routing and grade stand. The publish-time null-guard gate (packages/lint/src/validate-null-guards.ts) still skips flow and edge conditions, so an unguarded ordering comparison on a nullable field passes, and then fails the run at runtime (showcase: showcase_project_escalation fails terminally (CEL no such overload on null budget) whenever a project with no budget turns red, so the escalation notifies nobody #28951's showcase flow).packages/lintisdomain:devx(lanes/server.md). p3 holds: the run fails loudly, so nothing is silently lost.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:devx#1 · os-warren · session_01JQwxbaBjisxkQo9hht9bFB
Claim: PM loop round 3
Session:
session_01JQwxbaBjisxkQo9hht9bFBAccount:
os-warrenBranch:
claude/project-thread-q8q83kWorktree:
objectstack-issue-29026Domain:
domain:devxSeat:
domain:devx#1File surface:
packages/lint/src/validate-null-guards.tsand its tests; one fixture fallout, theshowcase_committee_quorumstart condition inexamples/app-showcase/src/automation/flows/index.ts; a real changeset for@objectstack/lintContainer & model:
M gate widening,mode:thread,model: default tier (dispatch-gates --tier: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: narrowed
Responsibility:
packages/lintowns the gap (its ledger still excludes flow / edgecondition); no platform path covers it today (the run fails at run time, #28951); any author or AI writing an ordering comparison on a nullable field in a record-triggered flow condition reaches it, and the showcase didThread-read: 6572724715
Premise: holds
Serial constraints cleared: none (48 open non-mailbox PRs read; none touches
validate-null-guards.tsor the showcase flows file)Triaged p3 by triage#1 (6572724715). The gate narrows what an author can publish, so it owes one contract-review-tier review before the queue. Card-thread pre-claim reading: example fallout is one real finding plus two edges already guarded by the start condition. Bare flow identifiers are judged as
record.<f>only when the name is a declared field of the trigger object and no flow variable, input oroutputVariabledeclares it; anything ambiguous gets no verdict, never a refusal.chore(changeset): answer the ADR-0087 disposition for #29026
objectstack-fleet commentedon Nov 2, 2026
domain:devx#1 · os-warren · session_01JQwxbZfKtKX5FguJoubPfK
os-dev-report
{ "issue": 29026, "status": "done", "branch": "claude/project-thread-q8q83k", "pr": "https://github.com/objectstack-ai/objectstack/pull/29474", "session": "session_01JQwxbZfKtKX5FguJoubPfK", "premise_still_valid": true, "summary": "The publish-time null-guard gate now judges a record-triggered flow's start condition, every edge condition and every decision branch expression, with the same rule and the same != null remedy text; the outcome clause says the run fails and the write still lands, read off evaluateCondition and the record-change trigger handler. A bare flattened name is judged as record.<f> only when the flow's bound set is provable and no variable, input, outputVariable or node id holds the name; anything ambiguous gets no verdict. What the start condition proves is presumed downstream. Fallout fix: a count or sum roll-up summary is always valued, so showcase_committee_quorum (total_amount >= 5000) is not flagged and the showcase is untouched.", "tests": "pnpm --filter @objectstack/lint run typecheck && vitest run --maxWorkers=2 under the verify lock on head b4043790: VERDICT command-exit 0, check:test-typecheck OK, Test Files 190 passed (190), Tests 7852 passed (7852). eslint --no-inline-config on the 5 changed .ts files: exit 0. Fixture census with validateStackExpressions over app-showcase (32 flows), app-crm (1), app-todo (4): 0 null-guard findings on branch and main; positive control (showcase e2 back to budget > 200000): 1 finding on branch (flows[26].edges[1].condition), 0 on main.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on b4043790: 58 families; all 58 ran on b4043790, each exit 0; dispatch-gates --ran: 58 derived, 58 run, 0 NOT-MEASURED, 0 UNRUN. First run before the fixes: check-adr-0087-registration exit 1 (no disposition; fixed with not-required (no-migration-prescription)), four exit 3 (lint and objectql not built; teardown self-test control commit absent from the shallow clone), all exit 0 after build and fetch. CI check-runs on the PR not yet concluded at report time; not waited on.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 — relay POST /repos/objectstack-ai/objectstack/dispatches pr_create (#29474, 11167 bytes sent = stored, assignees none); relay os-dev-report comment on #29026. Plus 2 git pushes of claude/project-thread-q8q83k.", "open_questions": [], "security_findings": "none", "deviations": [ "File surface widened beyond the Claim to packages/lint/src/validate-expressions.ts, its test file and a new validate-expressions.flow-null-guards.test.ts: the wiring and the nullability judgment live there.", "The Claim's showcase_committee_quorum fixture fix was not made: measured as a false positive (a sum roll-up reads 0 on zero children, objectql summaryEmptySetValue), so the judge was fixed instead.", "origin/main f95d0dba merged into the branch before the final gate run." ], "body_amendments": [], "out_of_scope_findings": [ "class: b · reach: a record-after-write flow whose condition reads previous.x: on the create leg previous is null, so previous.x != null itself faults; the gate reports an unguarded previous.x but cannot prescribe the full guard previous != null && previous.x != null · evidence: seedRunVariables binds previous to null on create · noted, not filed", "class: c · reach: skills/objectstack-formula/SKILL.md says the publish gate rejects validation-rule or hook predicates only; content/docs/automation/flows.mdx does not say flow conditions are now enforced · evidence: PR body Skills-docs line · carrier: PR (follow-up owed), noted, not filed" ] }Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:devx#1 · os-warren · session_01JQwxbaBjisxkQo9hht9bFB
Claim amendment to 6579715842 (same session, branch
claude/project-thread-q8q83k). Two lines change:File surface:
packages/lint/src/validate-null-guards.ts,packages/lint/src/validate-expressions.ts(the flow wiring lives there) and their tests, including the newvalidate-expressions.flow-null-guards.test.ts; a real changeset for@objectstack/lint; no example-app editClause-②: no (narrowing)
Why: the flow wiring is in
validate-expressions.ts, not the ledger file. Theshowcase_committee_quorumstart condition was measured a false positive (total_amountis asumroll-up and reads 0 on zero children), so the gate now treatscount/sumroll-ups as always valued and the example needs no edit. Flows that published before can now be refused, so the claim declares the(narrowing)arm besideMetadata-protocol: narrowed, ascheck-changeset-no-major.mjsasks.objectstack-fleet commentedon Nov 2, 2026
domain:devx#1 · os-warren · session_01JQwxbZfKtKX5FguJoubPfK
os-dev-report
{ "issue": 29026, "status": "done", "branch": "claude/project-thread-q8q83k", "pr": "https://github.com/objectstack-ai/objectstack/pull/29474", "session": "session_01JQwxbZfKtKX5FguJoubPfK", "follow_up_to": "comment 6581777047", "head": "c7417c1f (changeset-only commit on b4043790; code tree identical)", "dogfood_battery": "c7417c1f (run on code tree b4043790) · os-verify-lock: VERDICT command-exit 1 · Test Files 2 failed | 392 passed | 3 skipped (397) · Tests 4 failed | 3244 passed | 28 skipped (3276) · failures: declarative-endpoint-anonymous-guest.dogfood.test.ts [#22147] carries the guest position with no user and no elevation (flowContexts length 0, expected 1); flow-runas.dogfood.test.ts runAs:'system' ELEVATES, runAs:'user' DE-ELEVATES, runAs:'system' READS (trigger door answers 404 RECORD_NOT_FOUND on runas_note)", "dogfood_failures_attribution": "not this PR's: rerun of the two files alone on the branch = the same 4 failures (VERDICT command-exit 1, 4 failed | 8 passed); control with packages/lint/src reset to merge base f95d0dba and @objectstack/lint rebuilt (turbo cache miss, dist without the run-fails clause) = the same 4 failures (VERDICT command-exit 1, 4 failed | 8 passed). Neither file's flows carry a start, edge or decision condition. Branch sources restored and lint rebuilt afterwards; nothing pushed.", "commands": "pnpm exec turbo run build --filter='@objectstack/dogfood^...' --concurrency=2 then pnpm --filter @objectstack/dogfood test, both under os-verify-lock, no OS_TEST_SHARD, no path", "mcp_calls": "0", "api_writes": "1 — this relay comment. Plus 1 git push (c7417c1f) since the first report.", "security_findings": "none" }Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:devx#1 · os-warren · session_01JQwxbaBjisxkQo9hht9bFB
Landed · 2026-11-02T17:03Z · PR #29474 merged as
e119e53397(reviewed headc7417c1fc8, contract review PASS 6582253324, seat record ACCEPT 6582648011). The publish-time null-guard gate now judges record-triggered flows' start, edge and decision conditions: an ordering or arithmetic operator on a nullable field of the trigger object, with no dominating!= null, is refused atos validate/os build/ the runtime publish gate, because the run fails there while the write still lands.count/sumroll-ups count as always valued. Follow-ups filed: #29482 (teach it in the formula skill and the flows page) and #29491 (three accuracy points from the contract review).pm:dispatchedis removed in this stroke.