Skip to content

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

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbWBtYWqaV8NhvUrShB

Filed by the dispatching PM seat session for the W4-C3a runner session_01JQwxbZsvATYMNMArStUo2M, whose own write was refused by its session permissions (RUNNER rule 2: staged text, posted verbatim through the same door).

立卡门类别: ① 产品缺陷 — 具名落点 (examples/app-showcase/src/automation/flows/index.ts, ProjectEscalationFlow edges e2/e3) + 复现
reach: 公开入口实测一次错误结果 — PATCH /api/v1/data/showcase_project/ID {"health":"red"} on a project without a budget makes the flow run fail terminally (two fresh boots at 7fa9873)
业务理由: the showcase is the reference app new users read and copy. Its escalation flow fails on an ordinary record (no budget, or no owner), so the "red project alerts someone" scenario it demonstrates does not happen, and the pattern it teaches (an unguarded comparison on an optional field) is the bug. Not doing it: the reference teaches a failing pattern and its own demo escalations notify nobody.
动手的读者: the domain:devx seat (example apps / dogfood); triage first touch confirms the lane, then os-dev.
查重: showcase_project_escalation → 3 hits, 0 open (#7746, #4414, #16405 closed, all different); escalation budget null → 187 hits, 1 open (#6367, a seat post). No duplicate.

QA-source: #28942 · records-forms.validation-rule-type-matrix · observed beside the clauses

Path: examples/app-showcase/src/automation/flows/index.ts (ProjectEscalationFlow, edges e2/e3)

What happens. The flow triggers on health == "red" && previous.health != "red", then branches at the triage decision on edge conditions budget > 200000 (e2) and budget <= 200000 (e3). budget is an optional currency field (project.object.ts) and nothing defaults it. For a project with no budget the CEL comparison has no overload for null, so the run dies at the decision. The server logs ERROR Trigger-fired run of flow 'showcase_project_escalation' failed (trigger 'record_change') … "condition failed to evaluate as CEL: no such overload: dyn‹null› > int … budget > 200000"; nothing retries, and neither the owner nor the exec is alerted. The record write itself succeeds (200).

Reproduction (stock objectstack dev --seed-admin, framework 7fa9873; reproduced on two fresh boots):

  1. Admin. POST /api/v1/data/showcase_project {"name":"esc","account":"ANY_SHOWCASE_ACCOUNT_ID","status":"planned","health":"yellow","owner":"admin@objectos.ai"} → 201 (no budget).
  2. PATCH /api/v1/data/showcase_project/ID {"health":"red"} → 200.
  3. The server log shows the ERROR line above; the flow run fails.
  • Control: the same with "budget":1000 → no error; the run takes e3 → notify_normal.

Related, on the same flow (seen once): a budgeted project with no owner fails at notify_normal with "notify: at least one recipient is required, but every recipient value expression evaluated to nothing: has(record.owner) ? record.owner : null". owner is optional too.

  • Expected: the reference app's flow handles its own optional fields — guard the decision (has(record.budget) && record.budget > 200000, plus an otherwise edge), and route around the owner notify when there is no owner, as the node's own remedy text suggests.
  • Actual: a terminal flow failure on an ordinary record.

Full evidence: QA run record #28942.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: priority:p2 · domain:server · pm:queue · 2026-11-02T05:00Z

    ⛔ Not a claim.

    • Read fresh. The fix lands in ProjectEscalationFlow edges e2/e3 and the owner notify in examples/app-showcase/src/automation/flows/index.ts; the body's Path: line is a file, not a defined path item, and the card was observed beside records-forms.validation-rule-type-matrix, not on that capability. Examples route to the subsystem they exercise (automation), so the lane is domain:server, not devx. The reference flow failing terminally on an ordinary record is a user-visible defect, so it is p2 and goes to pm:queue. Under 「平台相关的功能,应该尽量先改平台」 the claimant should first say whether the flow validator can warn about a decision with no otherwise edge, and treat guarding the showcase as the minimum fix.

    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt

    Claim: PM loop round 1 · card thread · 2026-11-02T05:18Z
    Session: session_01FEkQ5oc56xChD6Wrc4cGSt (the seat); the card thread is session_01FEkQ5kZjdnPFmYVCV7PUQU
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-zxfajt (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#5
    Ruling-ref: 「定级你可以自己决定」 (Leehom, 2026-11-01T12:11Z) with the card's grade and this seat's rulings in the order; card assigned to seat 5 by seat 1's queue split after Leehom's 「队列进了很多任务,抓紧派发」 (2026-11-02T05:15Z).
    File surface: examples/app-showcase/src/automation/ and its tests (packages/qa/dogfood if the test lives there), changesets. ⛔ No packages/services/**, ⛔ no packages/spec/**.
    Container & model: S, mode:thread, model and effort as the thread was opened
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the code is the showcase escalation flow · a red-turned project with no budget (or no owner) fails the run terminally and notifies nobody · reached by every showcase user and every reader who copies the reference flow
    Thread-read: 6569465543
    Premise: holds (read at origin/main 135054eb: ProjectEscalationFlow edges e2/e3 compare budget unguarded; the owner notify has no route around a missing owner)
    Serial constraints cleared: none needed.
    Resume-from: none — fresh from origin/main

    State: pm:queue → pm:dispatched, assigned to os-project-manager, recorded by this claim. Order: pm-orders/server/order-28951.md in the project.

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

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5kZjdnPFmYVCV7PUQU

    os-dev-report
    {
    "issue": 28951,
    "status": "done",
    "branch": "claude/project-thread-zxfajt",
    "pr": "#29018",
    "head": "39cd0102",
    "session": "session_01FEkQ5kZjdnPFmYVCV7PUQU",
    "premise_still_valid": true,
    "summary": "ProjectEscalationFlow now runs to the end on a project with no budget or no owner. e2 is record.budget != null && record.budget > 200000 and e3 is the triage decision's isDefault edge, so no budget routes as the normal path (the flow's wording 'Critical budget?' reads no budget as not critical). notify_normal and converge fall back to role:owner (the exec) when the owner is blank (isBlank), and the critical branch's owner alert sits behind its own has_owner decision, since the Exec branch beside it already alerts role:owner. The notify node's required-recipient refusal is untouched; no engine, CEL, services or spec file changed.",
    "tests": "New examples/app-showcase/test/project-escalation-optional-fields.test.ts, 7 cases on a real ObjectKernel (ObjectQL over sqlite + automation plugin, the app's real showcase_project and flow; the run is handed the stored row, which each case asserts carries budget: null / owner: null; messaging is an audience recorder, fetch stubbed). Red on base (flows file at 135054e, 6 cases then): 4 failed / 2 passed: condition failed to evaluate as CEL: no such overload: dyn‹null› > int (no budget; neither), Node 'notify_normal' failed: notify: at least one recipient is required … (no owner), Node 'alert' failed: parallel 'alert': branch failed — Node 'alert_owner' failed … (critical, no owner); both controls green. Head: Tests 7 passed (7). Ablation 1 (ablation-replace, anchor x1, blob 9272a372 -> 9aa0d008, e2 guard dropped): 2 failed / 5 passed; restored, blob == HEAD, git diff HEAD empty. Ablation 2 (blob 9272a372 -> 900018be, has_owner edge -> 'true'): 1 failed / 6 passed; restored the same way. No dist on the path (test imports src). Live REST on pnpm dev -- --fresh, card steps (create as admin, PATCH health red -> 200) for no budget / no owner / control: base logs two ERROR Trigger-fired run of flow 'showcase_project_escalation' failed (trigger 'record_change') lines (no such overload: dyn‹null› > int … budget > 200000 and the notify_normal recipient refusal), runs completed/failed/failed; head logs none, runs completed x3 (start, triage, alert skipped, notify_normal, converge). On 39cd010 (merges origin/main 00d0668): pnpm --filter @objectstack/example-showcase test Test Files 43 passed (43), Tests 518 passed (518); typecheck exit 0; validate exit 0 (no row names the flow); eslint --no-inline-config on both files exit 0.",
    "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack: 40 families, each exit captured before any pipe; 39 exit 0. --ran: ✓ dispatch-gates --ran: 40 derived famil(ies) accounted for — 39 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3). NOT MEASURED: check:dual-build-cjs-loads, reason: needs every published package's dist; read 3 (seven unbuilt packages), then 1 after building them, with 82 Cannot find module '../chunk-….js' findings in packages this diff does not touch (stale chunks from overlapping local rebuilds). The diff changes no published package; CI's fresh build is the reading. PR check-runs: in_progress at report time.",
    "line_budget": "263 changed lines (+257 / -6) over 2 files, under the 3000 human-merge threshold",
    "files_changed": [
    "examples/app-showcase/src/automation/flows/index.ts",
    "examples/app-showcase/test/project-escalation-optional-fields.test.ts"
    ],
    "deviations": [
    "Ruling 1 spelling: the order named has(record.budget) && record.budget > 200000. Measured with ExpressionEngine on {budget: null} it still faults (no such overload: dyn‹null› > int): has() is true on a null key, and the flow's record is total over declared fields (the card's own log shows dyn‹null›). Used record.budget != null && record.budget > 200000, the spelling skills/objectstack-formula teaches, showcase_project's own validation rules use, and validate-null-guards accepts as dominating. The ruling's intent (guard the optional field, no budget routes normal) is kept; the no-budget edge is the decision's isDefault 'otherwise'.",
    "The test runs the flow through automation.execute with the stored row rather than the record-change trigger: @objectstack/trigger-record-change is not a dependency of the showcase app. The live REST repro covers the real trigger.",
    "Changeset: none, skip-changeset on the create; @objectstack/example-showcase is private."
    ],
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "0",
    "api_writes": "2 — relay pr_create (fw-20261102T061052Z-c65425, run 41398309972: PR #29018 + label skip-changeset, body read back identical, 10021 bytes) and this os-dev-report comment via post-stamped. git push is not a REST write.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: c · reach: named producer — this very flow's e2 budget > 200000 on the nullable showcase_project.budget passed os validate on main and faulted at runtime (live base repro above) · the publish-time null-guard gate (packages/lint/src/validate-null-guards.ts) still lists flow / edge condition as excluded in its surface ledger, though its totality blocker is gone since #4953 and the widening card #4811 is closed; so an author or AI can ship an unguarded ordering comparison on a nullable field in a flow condition · dedupe words: null-guard flow edge condition, validate-null-guards flow surface, nullable ordering flow condition, has() null flow",
    "Triage's platform-first question: no lint rule warns about a decision with no otherwise edge (flow-decision-* rules are inclusive-overlap, mode-invalid, unconditional-branch). A reading, not filed; carrier: none · noted in the PR's Acceptance notes",
    "The same has(FIELD) ? FIELD : null recipient guard is in ten other notify slots of the showcase flows file on other flows (assignee, owner, task.owner, task.watcher); it answers null on a null column and would leave that notify with no recipient. Reach not measured per slot, so not filed; carrier: none · noted in the PR's Acceptance notes",
    "showcase_committee_quorum start condition … && total_amount >= 5000 on a sum summary: whether it reads null on a report with no lines is not measured; showcase_budget_approval start condition budget > 100000 … faults on no budget but in the live run logged nothing and did not fire (benign). carrier: none · noted in the PR's Acceptance notes"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt

    ACCEPT → PR #29018 comment 6570492269 · head 39cd0102 · 2026-11-02T06:17Z. No contract review owed. Landing: pr_ready + automerge_enable through the relay; the merge queue closes this card. Follow-ups: #29026 (null-guard gate on flow conditions, devx) and #29028 (other showcase recipient guards).

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt

    Landed · 2026-11-02T06:58Z · PR #29018 merged through the merge queue as 6b3ba82cbb; this card closed as completed by its Fixes line.

    reading value
    PR state merged, 6b3ba82cbb
    card closed (completed); pm:dispatched removed and the seat unassigned in this stroke, domain:* / area:* kept

    Follow-up already dispatched: #29028 (the other has() notify slots and two start conditions), whose order waited for this merge.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions