Repository navigation
Commit fd9664f
fix(app-showcase): ship the approver-bindings specimen obsolete, not draft (#24008)
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:
- `docs/qa/platform-checklist/areas/automation.json:985`
("showcase_approver_bindings is draft on purpose");
- `docs/qa/platform-checklist/areas/approvals.json:1224`
("approver-bindings.flow.ts is status:'draft' with EMPTY values,
deliberately");
- `docs/qa/platform-checklist/areas/approvals.json:1290` ("the #3508
DESIGNER specimen — draft, empty values").
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](https://claude.ai/code/session_018VqJPGA1NDNSCZsnecsChP)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 19955fb commit fd9664f
3 files changed
Lines changed: 133 additions & 9 deletions
File tree
- examples/app-showcase
- src/automation/flows
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
26 | | - | |
| 26 | + | |
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
37 | 48 | | |
38 | 49 | | |
39 | 50 | | |
| |||
44 | 55 | | |
45 | 56 | | |
46 | 57 | | |
47 | | - | |
| 58 | + | |
| 59 | + | |
48 | 60 | | |
49 | | - | |
| 61 | + | |
50 | 62 | | |
51 | 63 | | |
52 | 64 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2010 | 2010 | | |
2011 | 2011 | | |
2012 | 2012 | | |
2013 | | - | |
2014 | | - | |
| 2013 | + | |
| 2014 | + | |
2015 | 2015 | | |
2016 | 2016 | | |
2017 | 2017 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
0 commit comments