fix(spec): the edge-condition upgrade entry and its pending changeset name POST /api/v1/automation, not the nonexistent POST /flows - #20031
Conversation
…ition upgrade entry
The ADR-0087 D3 entry flow-edge-condition-evaluated-slot-source-required
named `POST /flows` in its `surface` and `acceptanceCriteria`, and the
pending changeset for the same change repeated it. No such route is
mounted. The door a flow definition is created through on a composed
runtime is `POST /api/v1/automation` (dispatcher-plugin mounts
`${prefix}/automation` with the default `/api/v1` prefix; route ledger
row `POST /automation`, client `automation.create`).
Entry text only; `migrations/registry.ts` regenerated with
`gen:migration-registry`. No runtime or schema change.
Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b5b97a1b17dcd1d31672501eef5134e452597e45 && git checkout b5b97a1b17dcd1d31672501eef5134e452597e45
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9d81af714f4938f368909322ac8eb2c24276598a 66417af31f21671b927f69d9feb1d5af488e24ec && git checkout -B drift-repro 9d81af714f4938f368909322ac8eb2c24276598a && git merge --no-ff 66417af31f21671b927f69d9feb1d5af488e24ec
node scripts/docs-audit/affected-docs.mjs --json 9d81af714f4938f368909322ac8eb2c24276598a
|
… ships in Two sentences of the pending flow-edge-condition-evaluated-slot note were true of their own change but are overtaken by sibling notes in the same pending release. - The whitespace-only `config.condition` ruling is untouched by this change, but the service-automation and lint notes in that release refuse the value at `registerFlow` and at `objectstack validate`. The sentence now says so and points at them. - The start node's `config.condition` producer-side gate is no longer the shape refusal alone: the structural pass at both doors follows it with a blank-source check running `EvaluatedExpressionInputSchema`. Changeset text only. Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d Co-authored-by: Claude <noreply@anthropic.com>
Maintainer confirmation — the DELIBERATE CORRECTION of
|
Contract reviewServed-tier: 94/94 Isolated at-tier reviewer subagent, run by the ① Derived judgmentsInputs read. Card #19966 body + all 4 comments (triage (a) The route — holds, from source at the PR ref.
(b) The DELIBERATE CORRECTION — exactly one note, all three sentences true.
(c) Check Changeset red and its handling — as prescribed.
(d) Scope — nothing beyond, nothing missing. (e) PR body — factual sentences hold. Every file:line in items 2–3 and the acceptance notes verified above ( ② Semver levelNo changeset added and none owed. The single corrected note keeps ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #19966
Clause-②: no
What this changes
The ADR-0087 D3 entry
flow-edge-condition-evaluated-slot-source-required(landed, unreleased, protocol step 18) and the pending changeset for the same change namedPOST /flowsas a door a flow definition is authored through. No such route is mounted. This PR replaces it with the door a flow definition is actually created through on a composed runtime,POST /api/v1/automation, measured below.packages/spec/src/migrations/entries/semantic/18.flow-edge-condition-evaluated-slot-source-required.tssurface(:21)a POST /flows bodya POST /api/v1/automation bodyacceptanceCriteria(:63)`POST /flows` bodies`POST /api/v1/automation` bodiespackages/spec/src/migrations/registry.ts:9068,:9110)pnpm --filter @objectstack/spec gen:migration-registry.changeset/flow-edge-condition-evaluated-slot.md:86`objectstack validate` / `POST /flows``objectstack validate` / `POST /api/v1/automation`:63–:67(PM patch round)config.conditionruling "is untouched.":109–:115(PM patch round)registerFlowandobjectstack validate: the shape refusal, then the blank-source check runningEvaluatedExpressionInputSchemaText only. No runtime, schema or export change.
The pending changeset: a DELIBERATE CORRECTION, not a collision
.changeset/flow-edge-condition-evaluated-slot.mdbelongs to another card's PR (#15807). This PR corrects three sentences in it, each of which reads false in the release it ships in:objectstack validate/POST /flows; a stored row has no author in front of it.", the route now readsPOST /api/v1/automation.config.conditionruling (PM patch round). The note said the service-automation:evaluateConditionanswers a silentfalsefor a non-string predicate, and a non-stringconfig.conditionregisters clean #15662 ruling "is untouched". That is true of spec/automation:FlowEdgeSchema.conditionstill accepts an envelope the engine cannot evaluate (ast-only, whitespace-onlysource) — the evaluated-slot rule of #15430 has not reached the edge condition #15807's own diff, but two sibling notes in the same pending release refuse the value: A whitespace-onlyconfig.conditionstring is a silentfalseat the node door, while #15807 made the edge door refuse the same value at parse #17322 (@objectstack/service-automation,.changeset/blank-node-condition-refused-at-registration.md) atregisterFlow, and lint:objectstack validatereports nothing for exactly the blankconfig.conditionthatregisterFlownow refuses — and a test pins that silence #17495 (@objectstack/lint,.changeset/validate-refuses-blank-structural-condition.md) atobjectstack validate. The sentence now keeps the first half and points at those two notes by card and package, without restating their rule. It names packages rather than.changeset/paths becausechangeset versiondeletes those files, so a path would dangle in the published CHANGELOG. Measured at66417af31f:service-automation/src/engine.ts:9480sends a node'sconfig.conditiontocheckStructuralCondition, whose second gate (:9460) isevaluatedSourceRefusal(:9437–:9451), and that runsEvaluatedExpressionInputSchema.safeParseon the source (:9448). The engine reaches this fromregisterFlow(:4107→validateFlowExpressions,:4134). The lint pass does the same:lint/src/validate-expressions.ts:1368→:1349→evaluatedSourceRefusal,:966–:972(safeParseat:969). All three notes are pending: none of spec/automation:FlowEdgeSchema.conditionstill accepts an envelope the engine cannot evaluate (ast-only, whitespace-onlysource) — the evaluated-slot rule of #15430 has not reached the edge condition #15807, A whitespace-onlyconfig.conditionstring is a silentfalseat the node door, while #15807 made the edge door refuse the same value at parse #17322 or lint:objectstack validatereports nothing for exactly the blankconfig.conditionthatregisterFlownow refuses — and a test pins that silence #17495 appears in the spec, service-automation or lintCHANGELOG.md.EvaluatedExpressionInputSchemaon the condition'ssource, added by A whitespace-onlyconfig.conditionstring is a silentfalseat the node door, while #15807 made the edge door refuse the same value at parse #17322 atregisterFlowand by lint:objectstack validatereports nothing for exactly the blankconfig.conditionthatregisterFlownow refuses — and a test pins that silence #17495 atobjectstack validate(the same file:line readings as item 2). "Has no Zod schema to narrow (the start node'sconfigis an open record)" is kept because it is still true.FlowNodeSchema.configisz.record(z.string(), z.unknown())(spec/src/automation/flow.zod.ts:488). The only per-type config parse on the node contract is forend(parseEndNodeConfig,:439–:449), and no start-node config schema exists inpackages/spec/src.Nothing else in that note changed. No new changeset is added, and
skip-changesetis deliberately not applied: the note being corrected is itself the release input.Check Changesetis expected red by design.check-empty-changeset --base origin/mainrefuses any edit to a changeset present on the merge base (the #17712 guard) and stays red for a deliberate correction; its own text says the remedy is to say so on the PR and get it confirmed, never to restore the base copy. This paragraph is that statement. The maintainer's word covering corrections of pending notes is quoted on the card's claim (「changeset 你看着更新就行」).How the route was measured
On a composed runtime:
bootStack(@objectstack/example-crm, { automation: true })from@objectstack/verify, the same harness the dogfood suite boots. It composescreateDispatcherPlugin({})with the default prefix, exactly likeobjectstack serve(packages/cli/src/commands/serve.ts, which passes noprefixeither;dispatcher-plugin.tsdefaults it to/api/v1). A throwaway probe (not committed) read the live Hono route table and sent real requests as the seeded platform admin. Built from this head (6820d6ef8c), 33 turbo build tasks, exit 0./flowor/flows. 17 live under/api/v1/automation, includingPOST /api/v1/automationandPUT /api/v1/automation/:namePOST /api/v1/flows, a well-formed flowENDPOINT_NOT_FOUNDPOST /api/automation, a well-formed flow (the specAutomationApiContractspath)ENDPOINT_NOT_FOUNDPOST /api/v1/automation, a well-formed flowGET /api/v1/automation/probe_goodanswers 200 with the parsed flowPOST /api/v1/automation, edgecondition: ' 'VALIDATION_FAILED,edges.0.condition, leading with theEVALUATED_EXPRESSION_SOURCE_REQUIREDsentence. Not registered (read-back 404)POST /api/v1/automation, edge{ dialect: 'cel', source: ' ' }VALIDATION_FAILED,edges.0.condition.source, the same sentencePOST /api/v1/automation,ast-only edge envelopeVALIDATION_FAILED,edges.0.condition, the same sentencePUT /api/v1/automation/probe_good, edgecondition: ' 'VALIDATION_FAILED, the same sentenceSo the entry's claim that the refused spellings are "reachable" at the REST authoring door holds, and that door is
POST /api/v1/automation. The route ledger (packages/runtime/src/route-ledger.ts:429,POST /automation, clientautomation.create; "prepend/api/v1for the wire path") agrees with the measurement.Reproduction on
origin/main44639665eebefore the edit:git grep -n 'POST /flows'found the entry:21and:63, the registry:9068and:9110(the card cited:8994/:9036at an older main), and the changeset:83.git grep -c '/flows' packages/runtime/src/route-ledger.tsfound 0 rows (exit 1). The controlPOST /automationhas one row at:429.Upgrade-guide artefacts
gen:upgrade-guideandgen:spec-changeswere re-run and produced zero byte change. The guide renders majors up toPROTOCOL_MAJOR(17 today,build-upgrade-guide.ts), and this entry sits in step 18. Sodocs/protocol-upgrade-guide.mdwill render the correctedsurfacefrom the first protocol-18 build, and not before.check:upgrade-guideandcheck:spec-changesare green at this head.Verification (head
66417af31f)66417af31f(3 paths, 23 changed lines). The set is the same 81 families as at6820d6ef8c, and all 81 were re-run at66417af31fagainst the same 33-package closure (turbo, 33 of 33 cached).--ranreconciles them: "81 derived famil(ies) accounted for — 80 run, 1 NOT-MEASURED".check-adr-0087-registration,check:migration-registry,check:upgrade-guide,check:spec-changes,check:docs,check:api-surface,check:authorable-surfaceandcheck:nul-bytes.check-empty-changeset --base origin/main: exit 1, the expected finding: random changeset filenames collide silently across parallel agents — a round overwrote a sibling PR'sminorchangeset and every gate stayed green #17712 refusal above.check:dual-build-cjs-loads, exit 3PREREQUISITE NOT MET, because the gate needs every package'sdistand this worktree built only the 33-package closure it needed. It is left to CI.6820d6ef8c, the last commit touchingpackages/spec(the patch round changes only the changeset):pnpm --filter @objectstack/spec check:generatedexit 0, "All 15 generated artifacts are up to date".packages/specvitest run --project local src/migrations/ src/conversions/conversions.test.tspassed 4 files, 354 tests.tsc --noEmitexit 0.66417af31fthe generated-artifact gates were re-run with exit 0:check:migration-registry,check:upgrade-guide,check:spec-changes,check:docsandcheck:api-surface.Acceptance notes
PUT /api/v1/automation/:nameis a second definition-write door that refuses the same spellings (measured above), and the entry does not name it.edges.0.condition: …; edges.0.condition: …). The REST mapping flattens both aborted union arms.FlowSchema.safeParseitself reports ONEinvalid_unionissue at the slot, as the changeset says (measured on the built spec).minorchangeset and every gate stayed green #17712 correction.falsefor a direct caller (engine.ts:10777,exprStr.trim() === ''). The corrected sentence names only the two authoring doors that now refuse the value.AutomationApiContracts(packages/spec/src/api/automation-api.zod.ts) declares its nine paths under/api/automation. Every other spec API contract uses/api/v1, and the composed runtime answers 404 at/api/automation. This is out of scope here and was reported to the PM seat with evidence.Generated by Claude Code