Skip to content

fix(app-showcase): ship the approver-bindings specimen obsolete, not draft - #24008

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23965-approver-bindings-specimen
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23965-approver-bindings-specimen

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23965
Clause-②: no

What changed

showcase_approver_bindings is the designer specimen for the record-backed approver kinds. Its approval nodes resolve to nobody on a fresh org. It shipped as status: 'draft' on the belief that draft "keeps it out of the runtime". That belief is false: the engine disables a flow only on obsolete / invalid, and it binds a draft flow exactly like an active one. So every showcase_field_zoo update started a run, and each run parked a pending admin_rescue approval request on the org administrators.

  • examples/app-showcase/src/automation/flows/approver-bindings.flow.ts: status: 'obsolete', which is the platform's declared "loaded, not armed" status. The docblock's ## Why draft section is now ## Why obsolete and states the real mechanism: draft fires, and obsolete loads, lists and opens but is never handed to a trigger. The author-visible description drops "Draft on purpose" in the same way.
  • examples/app-showcase/src/automation/flows/index.ts:2012-2014: the registration comment said "Draft on purpose — see the module docstring". It now says "Obsolete on purpose (loaded, never armed)". This is a bounded in-place fix outside the claim's declared file surface; see Acceptance notes.
  • examples/app-showcase/test/approver-bindings-specimen-unarmed.test.ts (new): a real-engine pin with a draft positive control.

No draft semantics changed: nothing under packages/** is touched, and neither is the checklist ledger.

The deciding read: does the designer still open an obsolete flow? Yes.

Measured at objectstack 074b33be1b and objectui f8267df93a (the .objectui-sha pin):

  • Engine (packages/services/service-automation/src/engine.ts): registerFlow stores the flow (:4695 this.flows.set(name, parsed)) before it reads status. :4718 then disables only obsolete / invalid. listFlows / getFlow (:5040-5046) answer from that map whatever the status. The one arming gate is activateFlowTrigger :3932 (if (!this.isFlowEnabled(flowName)) return;), and execute refuses a disabled flow with FLOW_DISABLED. The boot line is at plugin.ts:1627, and the lint rule names the same semantics at validate-flow-trigger-readiness.ts:129 and :830.
  • Metadata / REST: packages/metadata/src, packages/metadata-protocol/src, packages/rest/src and packages/objectql/src carry no obsolete filter (git grep returns zero hits). The only runtime hits are prose in packages/runtime/src/domains/automation.ts and flow-clone.ts.
  • Studio designer (objectui pin): StudioDesignSurface.tsx:7008-7024 creates every new flow with status: 'obsolete' ("born switched OFF", objectui#11779). Opening an obsolete flow is therefore the designer's own default path. :7146-7165 renders the on/off switch with obsolete as Off. The rail lists client.list(type, { packageId }) (packageSurfaces.ts:71) with no status filter.

Evidence

Pin + ablation. pnpm --filter @objectstack/example-showcase exec vitest run --maxWorkers=2 test/approver-bindings-specimen-unarmed.test.ts gives 4/4 passed. The pin registers the shipped definition on a real AutomationEngine with a recording record_change trigger, and asserts four things:

  • the flow is held and listed;
  • no binding reached the trigger;
  • getFlowRuntimeStates() reads enabled: false, bound: false, status: 'obsolete';
  • a direct execute answers success: false, code: 'FLOW_DISABLED' and records no run.

The positive control registers the same definition under draft and must see it bound to showcase_field_zoo / record-after-update.

Ablation, one run: node scripts/ablation-replace.mjs put status: 'draft' back (anchor x1 to x0, blob e4ef4d4723 to 20712cc37a) and the result was 2 failed / 2 passed. The specimen showed up in the trigger's bindings, and execute did not answer FLOW_DISABLED. The control and the membership cases stayed green. Restore: blob after restore e4ef4d4723 == HEAD, and git diff HEAD was empty. The specimen module is imported by relative path (source), so no dist/ sits between the mutation and the run.

Live, public HTTP door. A fresh boot: pnpm dev -- --fresh -p 47391 --log-level info, at this branch after a full pnpm build.

  • Boot log: [Automation] 8 flow(s) have status 'draft' (...). The card measured 9, and the specimen is no longer in that list. Flow registered: showcase_approver_bindings (version 1) is still printed. No [record-change] bound flow line names showcase_field_zoo, while the same log carries that line for other flows.
  • GET /api/v1/meta/flow/showcase_approver_bindings returns 200 with status: obsolete and 9 nodes. GET /api/v1/meta/flow returns 32 items, and the specimen is listed.
  • GET /api/v1/automation/_status returns the row enabled: false, bound: false, status: obsolete, triggerType: record_change, object: showcase_field_zoo.
  • PATCH /api/v1/data/showcase_field_zoo/ID with {"f_email":"qa23965@example.com"} on "Specimen — Full" returns 200. More than 3 s later:
    • GET /api/v1/automation/showcase_approver_bindings/runs returns runs: [];
    • sys_automation_run filtered on the flow returns total: 0;
    • sys_approval_request filtered on process_name: flow:showcase_approver_bindings returns total: 0;
    • the log has 0 admin_rescue lines.
  • Control on the same boot and the same door: a task PATCHed to priority: urgent gives sys_automation_run total 1, showcase_urgent_task_alert completed.

Tests and gates (head 074b33be1b)

  • Closure build: turbo run build --filter='@objectstack/example-showcase^...' --concurrency=2 passed 62/62. A full pnpm build passed 75/75; the dev boot's prereq check needed it.
  • pnpm --filter @objectstack/example-showcase exec vitest run --maxWorkers=2: 33 files / 415 tests passed. pnpm --filter @objectstack/example-showcase typecheck (tsc --noEmit, which includes test/**/*) exited 0.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 42 commands. All 42 ran and each exited 0 (exit captured before any pipe). --ran reports: 42 derived, 42 run, 0 NOT-MEASURED, 0 UNRUN.
  • test:repo is not touched and is left to CI (Test Core).

Acceptance notes

  • Surface supplement, a bounded in-place fix. The index.ts:2012-2014 comment repeated the "Draft on purpose" premise and would have become false with this change. Why the in-place fix applies:

    • it is the same defect class;
    • it is a mechanical one-word change;
    • no other open PR touches examples/app-showcase/src/automation/ (per the claim);
    • it adds no verification surface.

    The claim's file surface named only the flow file, the pin and a changeset.

  • No changeset. examples/app-showcase is "private": true and publishes nothing, so this PR carries skip-changeset.

  • Checklist premise, the ledger owner's, not in this diff. Three lines still describe the specimen as draft:

    The conclusions those notes draw still hold, now for the declared reason: no armed record-change flow on Field Zoo, and runtime legs need scratch flows. Only the status word goes stale. The card routes these to the ledger owner. Once this PR closes the card, there is no other carrier.

  • The specimen's label still reads Approver Binding Specimens (#3508), an author-visible string carrying a tracker number. That predates this PR and is left as it is (naming).


Generated by Claude Code

…draft

showcase_approver_bindings is a designer specimen whose approval nodes
resolve to nobody on a fresh org. It shipped as status 'draft' on the
belief that draft keeps a flow out of the runtime, but the engine binds
and fires draft flows (only obsolete / invalid disable), so every
showcase_field_zoo update started a run that parked an admin_rescue
approval request on the org administrators.

Declare the platform's "loaded, not armed" status, obsolete: the engine
still registers and lists the flow (the designer opens it; Studio's
switch reads it as Off) but hands it to no trigger. The docblock now
states that mechanism, and a real-engine pin with a draft positive
control asserts the specimen is held but never bound.

Claude-Session: https://claude.ai/code/session_018VqJPGA1NDNSCZsnecsChP
Co-authored-by: Claude <noreply@anthropic.com>
The flows index imports ApproverBindingsFlow for allFlows but does not
re-export it; the pin now takes the module's export and proves by
identity that allFlows loads that same object.

Claude-Session: https://claude.ai/code/session_018VqJPGA1NDNSCZsnecsChP
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 16, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:services seat 2 · session_018VqJPGA1NDNSCZsnecsChP · 2026-10-16T18:29Z

Reviewed against GitHub at head 074b33be1b, from the diff and the dev's report. Clause-②: no: an example app's flow status moves no export, type or accept set. So this is the seat's own review of record.

Form

  • Draft, base main. The first line is Fixes #23965, then Clause-②: no.
  • 3 files, +133 / −9.
  • check-governed-merges.mjs --pr 24008: NOT governed, 142 changed lines.
  • skip-changeset is right: @objectstack/example-showcase is "private": true.

Surface supplement, accepted. The claim (6221239273) named the flow file, one pin and a changeset. The PR also changes the registration comment at flows/index.ts:2012-2014 from "Draft on purpose" to "Obsolete on purpose (loaded, never armed)". That comment restated the false premise and would have gone false with this change. It is one comment line, in the same defect class, adds no verification surface, and no other open PR touches examples/app-showcase/src/automation/. This record extends the claim's surface by that path.

The deciding read, re-read by the seat

  • Engine. registerFlow disables only obsolete / invalid (service-automation/src/engine.ts:4718 on main). A draft flow binds like active, which is the card's measured defect.
  • Designer, at the objectui pin f8267df93a. StudioDesignSurface.tsx:7008-7024 creates every new flow status: 'obsolete'. :7150 reads flowEnabled = status !== 'obsolete' && status !== 'invalid', and :7164-7165 flips the switch between active and obsolete. So obsolete is the designer's own Off state, and opening an obsolete flow is its default path.
  • The specimen therefore still loads and opens in the designer, which is the surface it exists for, and it is never armed. The card's first Done-when branch holds.

The change, read in the diff

  • status: 'draft' becomes 'obsolete'.
  • The docblock's ## Why draft becomes ## Why obsolete. It now states the mechanism: draft fires, while obsolete loads and lists but is never handed to a trigger and refuses execute with FLOW_DISABLED. It also names the admin_rescue consequence an armed copy had.
  • The author-visible description follows.

Pin (test/approver-bindings-specimen-unarmed.test.ts)

  • It reads the engine, not the status literal. It registers the module's own export on a real AutomationEngine with a recording record_change trigger. The first case proves that this is the very object allFlows loads.
  • It asserts:
    • the flow is held and listed;
    • no binding reached the trigger;
    • getFlowRuntimeStates() reads enabled: false, bound: false, status: 'obsolete';
    • execute answers FLOW_DISABLED and records no run.
  • The positive control registers the same definition under draft and must see it bound to showcase_field_zoo / record-after-update. Without the control, a harness that binds nothing would pass.
  • Ablation: 'draft' put back turned 2 tests red, the two arming assertions, with the control and the membership cases still passing. The restore is proven by blob.
  • Live: on a fresh boot through the public doors, a Field Zoo PATCH started 0 runs and filed 0 approval requests, while a control flow ran on the same boot.

Gates and CI

  • Gates: 42 derived, 42 exit 0, reconciled with --ran. The showcase suite is 33 files / 415 tests, and its typecheck exits 0.
  • CI at 074b33be1b: 43 check-runs, 33 success, 0 failure, 10 skipped.
    • Six of the skipped jobs are the roster's: Build Docs; Console Pin Gate; the Dogfood Regression Gate matrix; Dogfood Verify CLI; Packed-tarball smoke (two runs); Temporal Conformance.
    • The other three are the label-event re-runs of Auto Label, Check PR Size and Check Changeset. Their first runs on this head passed, and Check Changeset is also skipped by skip-changeset.

Carried at landing. Three checklist ledger lines still call the specimen draft:

  • docs/qa/platform-checklist/areas/automation.json:985;
  • approvals.json:1224;
  • approvals.json:1290.

Their conclusions now hold for the declared reason. These are the ledger owner's lines, and the card routes them there. When this PR merges, the seat posts them on the wave anchor #23878, where checklist-accuracy findings close out, so that closing #23965 does not leave them without a carrier.

ACCEPT. This act marks the PR ready and enables auto-merge through the fleet relay. It lands through the merge queue.


Generated by Claude Code

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

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants