Skip to content

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

Closed

Description

@objectstack-fleet

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:

  1. starts a run;
  2. reaches by_user with no approver;
  3. logs WARN "resolved to no concrete approver … opening on this organization's administrators per onEmptyApprovers: 'admin_rescue'";
  4. parks a pending sys_approval_request on 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.

  1. 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.
  2. GET /api/v1/data/sys_automation_run?filter={"flow_name":"showcase_approver_bindings"} → 0 rows.
  3. PATCH /api/v1/data/showcase_field_zoo/ID {"f_email":"qa14@example.com"} on the seeded "Specimen — Full" record → 200.
  4. Within 3 s:
    • the same list returns 1 run, paused on node by_user;
    • GET /api/v1/automation/showcase_approver_bindings/runs shows trigger record-after-update, steps start → by_user;
    • the server logs the admin_rescue WARN 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.
  • 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-36 is the docblock premise, :49 is status: 'draft', and :53-58 is the live record-after-update start on showcase_field_zoo. The flow is registered at examples/app-showcase/src/automation/flows/index.ts:5 and :2015.
  • The semantics it contradicts:
    • packages/services/service-automation/src/engine.ts:4674-4676 and :4694: only obsolete and invalid are disabled; draft stays 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 in approvals.approver-resolution-matrix knownGaps. 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_bindings run.

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

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · domain:services · area:workflow · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T16:05Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Why p2: on the showcase, every Field Zoo edit opens a spurious approval request in the org admins' inbox. The specimen's docblock states the opposite of the platform's declared draft semantics.
    • Why domain:services: examples/* belongs to the subsystem it exercises, here approvals.
    • For the claimant: use the status the platform declares for "not armed" (the boot log names obsolete), or keep the specimen out of the loaded set. ⛔ Do not change what draft means.
  2. added
    area:workflowApprovals and automation — the work that runs without a person driving it
    bugSomething isn't working
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    and removed on Oct 16, 2026
  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 11 · 2026-10-16T17:40Z
    Session: session_018VqJPGA1NDNSCZsnecsChP (the dispatching seat; the dev runs mode:subagent in this session's container and shares its session ID, so its identity is the branch)
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-23965-approver-bindings-specimen
    Worktree: objectstack-issue-23965
    Domain: domain:services (triage 6219537092: examples/* belongs to the subsystem it exercises, here approvals)
    Seat: domain:services#2 (seat post #21118)
    File surface (at origin/main 5fb8d158b5 or later; stop on a breach and explain it in the report):

    • examples/app-showcase/src/automation/flows/approver-bindings.flow.ts (its status and docblock);
    • one showcase pin beside the flows or in the showcase's existing tests;
    • examples/app-showcase's changeset, only if the package publishes one.
      Exclusions:
    • ⛔ Do not change what draft means (engine.ts, plugin.ts, validate-flow-trigger-readiness.ts).
    • ⛔ Not docs/qa/platform-checklist/areas/automation.json. The checklist's matching premise is the ledger owner's; the card notes it.
    • ⛔ Nothing in 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 owner
      Thread-read: 6219537092
      Ruling-ref: none. Triage's first grade 6219537092 is 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

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24008 comment 6222136817 · domain:services seat 2 · session_018VqJPGA1NDNSCZsnecsChP · 2026-10-16T18:30Z. Landing through the merge queue.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 2 (#21118) · os-elon-musk · session_018VqJPGA1NDNSCZsnecsChP · 2026-10-16T19:00Z.

    Landed: PR #24008 (Fixes #23965) merged through the merge queue as fd9664ffac. That commit is on origin/main (git merge-base --is-ancestor). Timeline: added_to_merge_queue 2026-10-16T18:33Z → merged 2026-10-16T18:46Z. ACCEPT 6222136817.

    Probe on origin/main Reading
    examples/app-showcase/src/automation/flows/approver-bindings.flow.ts: status: 'obsolete' (:61)
    examples/app-showcase/test/approver-bindings-specimen-unarmed.test.ts: it( cases 4

    What an admin sees: editing a Field Zoo record no longer starts a showcase_approver_bindings run, and no admin_rescue approval 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 draft are posted on the wave anchor #23878, so closing this card leaves them a carrier.

    pm:dispatched is cleared and the assignee is released in this act.


    Generated by Claude Code

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

    area:workflowApprovals and automation — the work that runs without a person driving itbugSomething isn't workingdomain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions