Skip to content

spec: the FlowSchema @example in flow.zod.ts compares a nullable record.amount unguarded on two edges (sibling of #31334) #31367

Closed

Description

@objectstack-fleet

domain:devx#3 · os-project-manager · session_01FEkQ5njkh3xbcDN6EtSfw7

Filing gate class ② (a site found outside a landed card's claim). Found by the #31334 card thread (os-dev report 6717074863, out-of-scope finding 1), and confirmed by that PR's contract review on #31358.

What is wrong

packages/spec/src/automation/flow.zod.ts, in the FlowSchema TSDoc @example (around L1070–1071 on main), has two edges that compare amount without a guard:

  • e2: condition: "record.amount < 500"
  • e3: condition: "record.amount >= 500"

The publish-time null-guard rule (packages/lint/src/validate-null-guards.ts) refuses an unguarded comparison on a nullable field in a gated position. This example passes today only because its start node names no objectName. Once an author binds it to an object with a nullable amount, as anyone copying it would, the rule refuses it.

Ask

Guard it the way #31334 / PR #31358 did:

  • either guard the start condition (record.amount != null && …, per the formula skill),
  • or have the example declare the field required: true, if that is the honest fix.

Give the choice and its reason, and prove it against the real rule: refused before, accepted after. If the example feeds a generated reference page, regenerate the page. Spec example text only: an @objectstack/spec patch, Clause-②: no, Metadata-protocol: none.

Related: #31334, PR #31358, #31206, PR #31322.

Reader: the domain:devx lane seat, from pm:queue after triage grades it.


Generated by Claude Code

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    documentationImprovements or additions to documentation
    domain:specDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Nov 8, 2026
  2. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p3 · domain:spec · 2026-11-08T04:53Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#4 · marchtian · session_017ihL2eAG5J8aqAyPZif4BJ

    Claim: PM loop round 3 · 2026-11-08T05:49Z
    Session: session_017ihL2ejruuhep9NDrZsCT7 (card thread; dispatched by seat session session_017ihL2eAG5J8aqAyPZif4BJ)
    Account: marchtian
    Branch: claude/project-thread-ao69dp
    Worktree: the card thread's own container checkout
    Domain: domain:spec
    Seat: domain:spec#4
    File surface: packages/spec/src/automation/flow.zod.ts (the FlowSchema TSDoc @example only) + one .changeset/*.md + any regenerated content/docs/references/** page embedding the example
    Container & model: XS docs card, mode:thread, model: default tier, effort: default; Fable review of the spec diff before ACCEPT (the maintainer's standing spec rule)
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — example text; the sibling of #31334 / PR #31358
    Thread-read: 6717448070
    Premise: holds (triage 6717448070 read on main); the dev re-measures against validate-null-guards.ts
    Serial constraints cleared: none (disjoint from #31344's restriction-profile surface)

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 8, 2026
  5. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#4 · marchtian · session_017ihL2ejruuhep9NDrZsCT7

    os-dev-report

    {
     "issue": 31367,
     "status": "done",
     "branch": "claude/project-thread-ao69dp",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/31416",
     "session": "session_017ihL2ejruuhep9NDrZsCT7",
     "premise_still_valid": true,
     "summary": "The FlowSchema TSDoc example now binds its start node to `order` (record-after-update), so the null-guard rule judges it as written. Edge e2 is guarded with `record.amount != null && record.amount < 500`, and edge e3 becomes the default edge (`isDefault: true`), so a missing amount goes to review. I guarded the condition rather than declaring `required: true` because the example defines no object. The default edge replaces a hand-written negation, as the Fable review suggested and the schema's own isDefault doc prescribes. Doc-comment text plus a spec patch changeset.",
     "tests": "Scratch vitest file in packages/lint/src (not committed). It evals the @example literal verbatim and runs validateStackExpressions with order.amount {type:'currency'}. Before (origin/main), verbatim: 0 findings, which is the premise: the start was unbound. Before, bound to order: 2 errors (e2 '<', e3 '>=' on record.amount, declared nullable). After, verbatim: 0 null-guard findings, 0 expression issues total, and FlowSchema.safeParse success=true. Re-run on f6f11f22 after merging main, same readings. No permanent test: the guard semantics are pinned in validate-null-guards.test.ts and validate-expressions.flow-null-guards.test.ts. No ablation: this is doc text only.",
     "gates": "At f6f11f22: dispatch-gates --commands derived 79; 79 run, all exit 0; --ran: '79 run, 0 NOT-MEASURED (a DERIVED zero)'. Prerequisites: a ./packages/** turbo build (81 tasks) and a fetched teardown positive-control commit. spec gen:schema + gen:docs produced no diff. check:generated: the first run's check:test-typecheck hit a heap-limited tsc ('failed without diagnostics'); rerun alone with a 6144 MB heap gave exit 0 (52 files / 240 errors / 131 pinned held). eslint on flow.zod.ts: exit 0. check-nul-bytes: exit 0. PR check-runs at report time: 5 success, 8 skipped, 20 in_progress.",
     "line_budget": "XS: 2 files, flow.zod.ts +8/-3 and a changeset of +12",
     "files_changed": [
      "packages/spec/src/automation/flow.zod.ts",
      ".changeset/31367-flow-example-amount-null-guard.md"
     ],
     "deviations": [
      "The order's file surface named the two edges. The fix also adds a start-node config to the same @example, because round-1 Fable should-fix: without it the example is never record-triggered and the rule never judges it. It stays inside the @example.",
      "Commit trailer is model-free (Co-Authored-By: Claude) per the standing rule; the harness's model-named variant was not used."
     ],
     "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
     "fable_review": "PR comment 6718445025: round 1 PASS with nits (2 should-fix, fixed in 8c1cc372); incremental re-review PASS",
     "mcp_calls": "0 GitHub MCP calls",
     "api_writes": "2 relay writes (fleet-write dispatch, objectstack-fleet[bot]): pr_create #31416 with assignee marchtian; post-stamped comment 6718445025 on #31416. This report is the 3rd. git push x3 (not REST).",
     "open_questions": [],
     "security_findings": "none",
     "out_of_scope_findings": [
      "class: c · reach: named producer (published skill an agent copies) · skills/objectstack-automation/references/state-machines-and-approvals.md compares record.amount > 500000 / <= 500000 unguarded in a flow condition · dedupe words: state-machines-and-approvals amount null guard, skill flow example nullable",
      "carrier: none · packages/services/service-automation/README.md record.amount > 500 unguarded (README, noted, not filed)"
     ]
    }
  6. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#4 · marchtian · session_017ihL2eAG5J8aqAyPZif4BJ

    ACCEPT → PR #31416 comment 6718517378 · head 0ec8e99a1 · 2026-11-08T07:02Z

    • Contract review at CONTRACT_REVIEW_TIER: round 1 PASS at f6f11f22 (6718493750) with one should-fix (the example's record-after-update start had no start condition, so its own auto-approve write re-launched it); fixed at 0ec8e99a with a start condition per content/docs/getting-started/common-patterns.mdx:337-339; delta PASS 6718517378, 0 open findings.
    • The fix: e2 guards record.amount != null && record.amount < 500; e3 is the isDefault edge (≥ 500 or null → approval, the conservative branch); the start is bound to order so the null-guard rule judges the example as written. Dev's proof 6718452460: 2 refusals before, 0 after; the review confirms by reading the rule.
    • Declared Clause-②: no, Metadata-protocol: none, @objectstack/spec patch: confirmed. Path face: packages/spec/src/** (docblock) + one changeset, not governed, 23 lines ⇒ Tier S; lands on green CI through the merge queue.
    • Accepted deviation: the file surface grew by the start-node config inside the same @example (needed by the review's should-fix).
    • Out of scope: the same unguarded idiom in skills/objectstack-automation/references/state-machines-and-approvals.md (e1, e4, e5) is filed as skills(objectstack-automation): the approval-flow example in state-machines-and-approvals.md still compares a nullable record.amount unguarded on e1, e4 and e5 (residual of #31206) #31417; packages/services/service-automation/README.md:192 is named there.
  7. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#4 · marchtian · session_017ihL2eAG5J8aqAyPZif4BJ

    Landed · domain:spec#4 · 2026-11-08T08:00Z

    reading value
    PR #31416 merged 2026-11-08T07:58:58Z through the queue, merge commit 18ed2db1b770578bb54f6c02aa86d17cd0b229f8, reviewed head 0ec8e99a (contract review PASS 6718493750 + delta PASS 6718517378 + ACCEPT on that head)
    card closed completed by Fixes

    State: pm:dispatched removed. Residual: the unguarded record.amount comparisons in skills/objectstack-automation/references/state-machines-and-approvals.md are carried by #31417.

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

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationdomain:specDomain lane — seat card indexed by label:pm:seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions