Repository navigation
app-showcase: showcase_approver_bindings is status: 'draft' on the belief that draft "keeps it out of the runtime" — draft flows fire, so every showcase_field_zoo edit opens an admin_rescue approval request on the org admins #23965
Description
Filing gate ① — a product defect in the reference app, with a named landing and a live reproduction. The authored intent ("out of the runtime") contradicts the platform's declared semantics, and the result is user-visible: an approval request lands in the admins' inbox on every edit.
QA-source: #23926 · approvals.approver-resolution-matrix · observation (wave close-out on #23878; re-checked and filed under #23878)
What happens
showcase_approver_bindings is an authoring specimen: one approval node per record-backed approver kind, every value left empty. Its docblock says it is shipped as draft so that it stays out of the runtime: "Shipping that as a demo would re-teach the bug. draft keeps it out of the runtime while still loading in the designer". On this platform, though, draft flows fire. The boot log says so on every start: "9 flow(s) have status 'draft' (… showcase_approver_bindings …) — draft flows still fire their triggers; set status: 'active' to make intent explicit, or 'obsolete' to disable". The engine arms its record-after-update trigger on showcase_field_zoo.
So any update to a Field Zoo record does the following:
- starts a run;
- reaches
by_userwith no approver; - logs WARN "resolved to no concrete approver … opening on this organization's administrators per onEmptyApprovers: 'admin_rescue'";
- parks a pending
sys_approval_requeston the org admins.
That is exactly the "silent dead slot" demo the docblock says it avoids shipping.
Reach — measured through the public HTTP entry, 2/2 fresh boots
Environment: objectstack 7277c07f (main, 2026-10-16), examples/app-showcase, OS_PORT=P objectstack dev --seed-admin -p P -d file:…/data.db --log-level info, with a fresh DB per boot (ports 47311 and 47312). Seeded admin, bearer token.
- Boot log:
[record-change] bound flow 'showcase_approver_bindings' → afterUpdate on 'showcase_field_zoo',Flow 'showcase_approver_bindings' bound to trigger 'record_change', and the draft-fires INFO line quoted above. GET /api/v1/data/sys_automation_run?filter={"flow_name":"showcase_approver_bindings"}→ 0 rows.PATCH /api/v1/data/showcase_field_zoo/ID{"f_email":"qa14@example.com"}on the seeded "Specimen — Full" record → 200.- Within 3 s:
- the same list returns 1 run,
pausedon nodeby_user; GET /api/v1/automation/showcase_approver_bindings/runsshows triggerrecord-after-update, stepsstart→by_user;- the server logs the
admin_rescueWARN above; GET /api/v1/data/sys_approval_request/areq_…→status: pending,process_name: flow:showcase_approver_bindings,current_step: by_user,pending_approvers= the seeded admin.
- the same list returns 1 run,
- Expected: the specimen does not run, as its docblock says.
- Actual: it runs on every Field Zoo update and files an approval request on the admins each time.
Where (objectstack main 7277c07f)
examples/app-showcase/src/automation/flows/approver-bindings.flow.ts:33-36is the docblock premise,:49isstatus: 'draft', and:53-58is the liverecord-after-updatestart onshowcase_field_zoo. The flow is registered atexamples/app-showcase/src/automation/flows/index.ts:5and:2015.- The semantics it contradicts:
packages/services/service-automation/src/engine.ts:4674-4676and:4694: onlyobsoleteandinvalidare disabled;draftstays enabled.packages/services/service-automation/src/plugin.ts:1624-1628: the boot line.packages/lint/src/validate-flow-trigger-readiness.ts:129: "Draft flows DO fire".
- The same wrong premise also sits in the checklist, at
docs/qa/platform-checklist/areas/automation.json:985("showcase_approver_bindings is draft on purpose") and inapprovals.approver-resolution-matrixknownGaps. Those are ledger-accuracy notes for the ledger owner and are not part of this card's diff.
Done when
- The specimen no longer fires on a Field Zoo update and still loads in the designer (for example
status: 'obsolete', if the designer still opens it; the author confirms which), and the docblock states the real mechanism. Alternatively, it stays armed on purpose with approvers that resolve, and the docblock says it runs. - A showcase pin asserts the chosen behaviour, for example that a field-zoo update starts no
showcase_approver_bindingsrun.
Reader who acts: the app-showcase owner (domain:devx execution seat). The diff is in examples/app-showcase/src/automation/flows/approver-bindings.flow.ts.
Dedupe
MCP search_issues on objectstack-ai/objectstack, all states: "showcase approver bindings draft flow docblock fires triggers" → 16 hits, top 10 read. #7571 (packaged flows read-only, closed) and #14622 (batch_reminders containment, closed) are neighbours. None covers this flow firing.
Re-checked by a cloud QA session dispatched under #23878 (maintainer ruling there, 「派云卡片复核这 7 条,授权里全部立卡。有时间间隔就行」).
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 16, 2026
Triage: first grade,
bug·priority:p2·domain:services·area:workflow·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-16T16:05ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
draftsemantics.domain:services:examples/*belongs to the subsystem it exercises, here approvals.obsolete), or keep the specimen out of the loaded set. ⛔ Do not change whatdraftmeans.objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 11 · 2026-10-16T17:40Z
Session:
session_018VqJPGA1NDNSCZsnecsChP(the dispatching seat; the dev runsmode:subagentin this session's container and shares its session ID, so its identity is the branch)Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-23965-approver-bindings-specimenWorktree:
objectstack-issue-23965Domain:
domain:services(triage6219537092:examples/*belongs to the subsystem it exercises, here approvals)Seat:
domain:services#2(seat post #21118)File surface (at
origin/main5fb8d158b5or later; stop on a breach and explain it in the report):examples/app-showcase/src/automation/flows/approver-bindings.flow.ts(itsstatusand docblock);examples/app-showcase's changeset, only if the package publishes one.Exclusions:
draftmeans (engine.ts,plugin.ts,validate-flow-trigger-readiness.ts).docs/qa/platform-checklist/areas/automation.json. The checklist's matching premise is the ledger owner's; the card notes it.packages/**.Container & model:
S, one declaration and its docblock, with one read that decides the status (does the designer still open an obsolete flow?) ⇒ S,mode:subagent,model: opus(TIER_DEFAULT; no path-derived mandate)Clause-②: no — an example app's flow status moves no export, type or accept set.
Responsibility:
examples/app-showcase (this lane, by the subsystem it exercises): the approver-bindings specimen does not fire on a Field Zoo update, and its docblock states the real mechanism | none: the declared status semantics already exist (obsolete / invalid are disabled) | the checklist note goes to the ledger ownerThread-read:
6219537092Ruling-ref: none. Triage's first grade
6219537092is the routing.Serial constraints cleared: no open PR touches
examples/app-showcase/src/automation/or the checklist's automation area (the open PRs' file lists were read at this write). The lock is free.Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{ "issue": 23965, "status": "done", "branch": "claude/issue-23965-approver-bindings-specimen", "pr": "https://github.com/objectstack-ai/objectstack/pull/24008", "session": "session_018VqJPGA1NDNSCZsnecsChP (mode:subagent; shares the dispatching seat's id, identity is the branch)", "premise_still_valid": true, "summary": "Premise held at origin/main d132140ebc: the engine disables a flow only on obsolete/invalid (engine.ts:4718), so the draft specimen bound to showcase_field_zoo afterUpdate. The deciding read is that the designer still opens an obsolete flow. Evidence: the engine stores the flow before it reads status (engine.ts:4695) and getFlow/listFlows answer it (:5040-5046); no metadata/REST/objectql layer filters obsolete; Studio at the objectui pin f8267df93a creates every new flow as status obsolete (StudioDesignSurface.tsx:7008-7024) and renders obsolete as the switch's Off state (:7146-7165). The rail lists client.list with no status filter (packageSurfaces.ts:71). So the specimen is now status 'obsolete'. Its docblock (## Why obsolete) and description state the real mechanism, and a real-engine pin with a draft positive control asserts it loads but is never armed. A live fresh boot confirmed it through the public doors: the metadata door serves and lists it as obsolete, /automation/_status reads enabled:false bound:false, and a Field Zoo PATCH started 0 runs and filed 0 approval requests, while a control flow ran on the same door.", "tests": "Head 074b33be1b. (1) New pin, run alone: `pnpm --filter @objectstack/example-showcase exec vitest run --maxWorkers=2 test/approver-bindings-specimen-unarmed.test.ts` gave 'Tests 4 passed (4)', VERDICT command-exit 0. The first run had 4 red because flows/index.ts imports ApproverBindingsFlow without re-exporting it; the pin now imports the module's export and proves allFlows holds that same object (commit 114aca756c). (2) Ablation, one run, through scripts/ablation-replace.mjs WRAP mode under an outer absolute-path trap: anchor \" status: 'obsolete',\" x1 to x0, replacement \" status: 'draft',\" x0 to x1, blob e4ef4d4723 to 20712cc37a. Result 'Tests 2 failed | 2 passed (4)'. The failures were 'expected [ showcase_approver_bindings ] to not include showcase_approver_bindings' and execute not answering code FLOW_DISABLED. Expected direction (red) observed, with the control and membership cases green. Restore: 'blob after restore e4ef4d4723 == blob at HEAD, git diff HEAD empty', and an independent git status --porcelain was empty. No dist/ preflight was run: the mutated subject is imported by relative path from source, so no build artifact sits between mutation and run. (3) Full showcase suite after merging origin/main 61a6809564: `vitest run --maxWorkers=2` gave 'Test Files 33 passed (33) / Tests 415 passed (415)'; `pnpm --filter @objectstack/example-showcase typecheck` (tsc --noEmit, include covers test/**/*) gave VERDICT command-exit 0. (4) Builds: turbo build --filter='@objectstack/example-showcase^...' gave 62/62 successful; then a full workspace build (75/75) because check-dev-prereqs refused the dev boot on 8 missing dists. (5) Live reading on a fresh boot `pnpm dev -- --fresh -p 47391 --log-level info`:\n- the boot line lists 8 draft flows (the card measured 9) without the specimen; 'Flow registered: showcase_approver_bindings' is still printed; no record-change bound line names showcase_field_zoo;\n- GET /api/v1/meta/flow/showcase_approver_bindings gives 200 with status obsolete and 9 nodes; GET /api/v1/meta/flow gives 32 items, specimen listed;\n- GET /api/v1/automation/_status gives enabled:false, bound:false, status obsolete, triggerType record_change, object showcase_field_zoo;\n- PATCH showcase_field_zoo 'Specimen — Full' f_email gives 200; more than 3 s later /automation/showcase_approver_bindings/runs gives runs:[], sys_automation_run on the flow gives total 0, sys_approval_request on process_name flow:showcase_approver_bindings gives total 0, and the log has 0 admin_rescue lines;\n- control on the same boot: a task PATCHed to priority urgent gives sys_automation_run total 1 for showcase_urgent_task_alert, completed.\nThe server was stopped by its recorded port PID, and the port read back free.", "gates": "Local, at 074b33be1b: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 42 commands (stderr: tree objectstack-ai/objectstack at 074b33be1b, --repo holds, change set 3 paths vs merge base 61a680956, 142 changed lines). All 42 ran sequentially, each exit captured before any pipe, and every one exited 0. Examples: check:test-source-alias 'OK — 75 packages with tests scanned'; check:examples-live-imports 'inventory OK'; check:nul-bytes, check:cross-package-test-inputs, check:type-check-coverage, check:engine-double-contract, check:i18n-coverage all exit 0. `node scripts/pm/dispatch-gates.mjs --ran ran.list` exit 0: '42 derived, 42 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero). There are 5 workflow-valued families, NOT MEASURED locally (CI-only argv). test:repo is not touched and is left to CI. The local-only families (check:dev-prereqs, check:skill-frame-freshness) do not apply: no packages/spec or framework-file change. CI check-runs read once at open on head 074b33be1b: 40 runs, 8 success (Auto Label, Check Documentation Links, Check PR Size, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, The card this PR closes must claim this branch, filter), 10 skipped (incl. Check Changeset, which skip-changeset skips), 22 in_progress (Build Core, Test Core 1-6/6, Type Check x4, Lint x8, Governed Surface Queue Guard, Dogfood Regression Gate), 0 failed. Not waited on, per dispatch.", "mcp_calls": "0", "api_writes": "3 relay strokes. Each is one POST /repos/objectstack-ai/objectstack/dispatches (repository_dispatch, session token) executed by fleet-write.yml as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24008/assignees [os-elon-musk]; (2) labels_add (via label-write.mjs) = POST /repos/objectstack-ai/objectstack/issues/24008/labels [skip-changeset]; (3) this os-dev-report = POST /repos/objectstack-ai/objectstack/issues/23965/comments (via post-stamped.mjs). git push x4 to the branch (not REST). Reads were REST GETs only.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed. Three checklist ledger lines still describe the specimen as draft: docs/qa/platform-checklist/areas/automation.json:985 ('showcase_approver_bindings is draft on purpose'), approvals.json:1224 ('approver-bindings.flow.ts is status:draft with EMPTY values, deliberately'), approvals.json:1290 ('the #3508 DESIGNER specimen — draft, empty values'). Their conclusions still hold (no armed record-change flow on Field Zoo; runtime legs need scratch flows), but the status word goes stale with this PR. These are the ledger owner's lines (excluded by the claim); the card routed them to the ledger owner, and closing the card leaves no carrier. Recorded in the PR's Acceptance notes.", "carrier: none · noted, not filed. The specimen's label 'Approver Binding Specimens (#3508)' carries a tracker number in an author-visible string. Naming only, predates this PR; in Acceptance notes." ], "line_budget": "n/a — no skills/** or governed ledger touched. Diff: 3 files, +133/-9 (142 changed lines, under the 3000 human-merge threshold): approver-bindings.flow.ts +17/-9 net, index.ts 2/2, new test 112 lines.", "deviations": [ "Surface supplement (bounded in-place fix): examples/app-showcase/src/automation/flows/index.ts:2012-2014. The registration comment 'Draft on purpose — see the module docstring' now reads 'Obsolete on purpose (loaded, never armed)'. It is outside the claim's declared file surface. All four os-dev conditions hold: same defect class; mechanical; no other claim on the file (the claim read no open PR touching examples/app-showcase/src/automation/); same gate family, no new verification surface. Named with evidence in the PR body. The claim comment's file surface should gain this path; that is a seat write.", "Built the whole workspace (pnpm build equivalent, 75/75, 63 cached) beyond the (1) closure, because check-dev-prereqs refused `pnpm dev` with 8 packages' dist missing. This was needed only for the live HTTP reading.", "Commit trailers use the AGENTS.md model-free pair (Claude-Session + Co-authored-by: Claude). The harness reminder offered a model-named Co-Authored-By; AGENTS.md takes precedence and the dispatch forbids model identifiers. No card trailer is in any commit." ], "files_changed": [ "examples/app-showcase/src/automation/flows/approver-bindings.flow.ts", "examples/app-showcase/src/automation/flows/index.ts", "examples/app-showcase/test/approver-bindings-specimen-unarmed.test.ts" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
ACCEPT → PR #24008 comment 6222136817 ·
domain:servicesseat 2 ·session_018VqJPGA1NDNSCZsnecsChP· 2026-10-16T18:30Z. Landing through the merge queue.Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
Landed ·
domain:servicesseat 2 (#21118) ·os-elon-musk·session_018VqJPGA1NDNSCZsnecsChP· 2026-10-16T19:00Z.Landed: PR #24008 (
Fixes #23965) merged through the merge queue asfd9664ffac. That commit is onorigin/main(git merge-base --is-ancestor). Timeline:added_to_merge_queue2026-10-16T18:33Z →merged2026-10-16T18:46Z. ACCEPT6222136817.origin/mainexamples/app-showcase/src/automation/flows/approver-bindings.flow.ts:status:'obsolete'(:61)examples/app-showcase/test/approver-bindings-specimen-unarmed.test.ts:it(casesWhat an admin sees: editing a Field Zoo record no longer starts a
showcase_approver_bindingsrun, and noadmin_rescueapproval request reaches the org administrators' inbox. The specimen still lists and opens in the designer, where its on/off switch reads Off. The boot log's draft-flow list no longer names it.Carried: the three checklist ledger lines that still call the specimen
draftare posted on the wave anchor #23878, so closing this card leaves them a carrier.pm:dispatchedis cleared and the assignee is released in this act.Generated by Claude Code