You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Declared endpoints (type: 'flow'): converge onto the #9378 status table — the ruled follow-up to #9446's staged execution #9462
Filed by the triage seat as the door-3 half of the #9446 ruling (maintainer, 2026-08-18, verbatim 「同意」: the #9378 status table is a property of the flow-dispatch CONTRACT, executed in stages — door 2 in #9446 itself, door 3 as this card).
Scope
packages/runtime/src/endpoint-executor.ts's flow branch currently ends with an unconditional successAnswer(await automation.execute(...)) — HTTP 200 wrapping {"success":false,...} on every failure, the double envelope #3962 ruled out for /actions. Converge it onto the four-row table the trigger routes answer after #9413/#9415 (404 / 409 FLOW_DISABLED / 422 FLOW_NO_START_NODE / 400 FLOW_FAILED), consuming the producer's result.code.
Constraints (from the ruling)
The envelope flip is BREAKING for any consumer of the current double envelope on declared endpoints. The PR must say so plainly, and landing is sequenced against consumers — enumerate them (objectui, examples, docs) with real greps before flipping, and coordinate the objectui half if one exists.
Door 3 carries the endpoint policy chain and outputMapping, so this is not a copy of door 2's mapper — the mapping must sit where those still apply.
Claim: PM loop round 18
Session: session_012WKSnqAaoqtW3QX7SSf1Vk
Branch: claude/issue-9462-declared-endpoints-status-table
Worktree: objectstack-issue-9462
Domain: domain:cli
File surface: packages/runtime/src/endpoint-executor.ts + its test siblings, and content/docs/protocol/kernel/http-protocol.mdx (the boundary wording this card's own body says flips back) — stop on breach; explain in the report
Container & model: M–L, mode:subagent, model: opus — a DOWNGRADE from the Clause-② mandate, under the maintainer's one-time authorization
Clause-②: yes, verbatim from this card's own body — accept/reject behaviour changes on a public surface.
Serial constraints cleared: packages/runtime freed at 12:38:35Z — #9446 landed (PR #9584). Verified on origin/main by probe, ⛔ not taken from the merge event: flow-dispatch-status is referenced by exactly 2 files, action-execution.ts and domains/automation.ts — the two live doors — against a control of 243 files in packages/runtime/src. ⚠️#9416 and #9457 are deliberately NOT dispatched alongside this card (same package; the maintainer re-confirmed 「不豁免,维持串行」 on 2026-08-18). This card goes first because the staging ruling sequenced door 3 after door 2, and door 2 has just landed.
Model-tier downgrade, recorded as the rule requires
Maintainer authorization, 2026-08-18, verbatim: 「授权一次性降档到 opus」, given in answer to a direct question naming five cards — #9446, #9416, #9462, #9488, #9487 — while the Fable 5 budget was exhausted (five measurements, ~01:2x through ~09:3x, the last three by throwaway probe touching no card). ⛔ The authorization does not generalise beyond those five.
The review tier is reduced; the verification bar is not. The reverse-verification requirement below is the compensating control, and I will verify the breaking-change analysis myself rather than on report.
Premise re-verified on origin/main this round
Door 3 still ends its flow branch with an unconditional return successAnswer(await automation.execute(plan.flow, automationContext)); — endpoint-executor.ts:483 (control: the file is 508 lines, so it resolves).
⚠️ What makes this card heavier than #9446, and what the dispatch will demand
The envelope flip is breaking for any consumer of the current HTTP 200 {"success":true,"data":{"success":false,...}} double envelope — the shape #3962 already ruled out for /actions. The card's own constraints require the consumers be enumerated with real greps before flipping, and the objectui half coordinated if one exists. My starting measurement for the dev to extend rather than trust: packages/client/src/index.ts plus two of its test files match the double-envelope shape.
⛔ If implementation surfaces a reason door 3 cannot converge, that is a fork report back to the decision inbox — ⛔ never a silent scope change. And door 3 carries the endpoint policy chain and outputMapping, so this is not a copy of door 2's mapper: the mapping has to sit where those still apply.
The claim's declared file surface named packages/runtime/src/endpoint-executor.ts + its
test siblings and content/docs/protocol/kernel/http-protocol.mdx. The consumer
enumeration this card requires turned up a second page carrying the same boundary
sentence, so the surface is amended here rather than left to be discovered in the diff:
added:content/docs/automation/flows.mdx
It reads, today on origin/main (line 1317):
The same table answers at the action door. … Declared endpoints (type: 'flow') are
the remaining exception: they still answer 200 for every outcome, so read data.success there rather than the status.
That is the identical defect class as the http-protocol.mdx sentence this card's own
constraint 5 says flips back — boundary wording added by the #9413/#9415 series to
describe a divergence this PR removes — and the correct form is already pinned by the
sibling page plus the constraint. Leaving it would ship a documented falsehood created by
this very diff. Scope is unchanged otherwise: still one defect class, no new verification
surface (both pages ride the same check:docs-* / docs-drift family, all green locally).
Evidence query behind the find, with its control:
grep -rn "\.data\.success|data\?\.success" --include=*.ts --include=*.tsx --include=*.mdx \
--exclude-dir=node_modules --exclude-dir=dist . # 40 hits, 2 of them in content/docs
The fix is named explicitly in the PR body, per the bounded in-place-fix rule.
{
"issue": 9462,
"status": "done",
"branch": "claude/issue-9462-declared-endpoints-status-table",
"pr": "https://github.com/objectstack-ai/objectstack/pull/9676",
"premise_still_valid": true,
"summary": "Door 3 converged. `executeFlow` in packages/runtime/src/endpoint-executor.ts now CALLS the shared definition (`flow-dispatch-status.ts`) rather than open-coding a third table: `flowIsUnknown` answers row 1 (404) via the same optional getFlow probe doors 1 and 2 use, and `classifyFlowRefusal` answers rows 2-4 (409 FLOW_DISABLED / 422 FLOW_NO_START_NODE / 400 FLOW_FAILED), with the 400 arm carrying the run's `errorMessage`/`summary` in `error.details` byte-identically to the trigger door. The unconditional `successAnswer` is gone from every refusal path; a successful run is untouched. Both doc pages that asserted the divergence are flipped, and a minor changeset frames the break. NOTE the one judgement call, raised in open_questions and pinned as a test: the UNCLASSIFIED `success:false` residual keeps today's 200 at this door, matching door 1 (the route #5040 section 4 makes this endpoint an alias of), not door 2.",
"tests": "All at final commit `45c2720d2` (git rev-parse --short HEAD from the same runs; tree byte-identical after the ablation was restored — `git diff HEAD` empty). Full workspace `pnpm build` EXIT=0. `@objectstack/runtime` test: `Test Files 171 passed (171) / Tests 2543 passed (2543)`, EXIT=0. `@objectstack/runtime` typecheck (`tsc --noEmit`) EXIT=0. DOWNSTREAM consumers (prefix filter `...@objectstack/runtime` = dependents; 13 packages + runtime): 12 of 13 run, all green — client 310, cli 1452, client-react 34, hono 73, http-conformance 72, verify 32, plugin-dev 54, cloud-connection 191, app-showcase 334, app-crm 42, app-todo 106; plus the dogfood consumer of THIS seam over a real socket (showcase-declarative-endpoints + declarative-endpoint-policy) `Test Files 2 passed (2) / Tests 25 passed (25)`. NOT RUN: `@objectstack/driver-turso` (13th consumer) — its lock turn never came inside the ~20min queue budget and I tore my waiter down rather than stall; it is a database driver with no path to the HTTP flow envelope, and CI covers it. Gates, all EXIT=0 at the same sha: check:type-check-coverage, check:type-check-debt, check:nul-bytes, check:route-envelope, check:error-status-conformance, check:error-code-casing, check:dispatcher-error-vocabulary, check:cross-package-test-inputs, check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:docs-audit-scope, check:docs-redirects, check:role-word, scripts/docs-audit/check-affected-docs.mjs, check-adr-0087-registration, check-changeset-no-major, check-empty-changeset — the last four beyond the derivation the dispatch carried, re-derived from my ACTUAL changed paths with scripts/pm/dispatch-gates.mjs. CI job status at report time: in_progress (reported at draft-PR time per the 2026-08-10 ruling; no CI waiting). ⚠️ ONE MEASUREMENT RETRACTED: a first consumer run scoped `--filter '...@objectstack/runtime'` alone reported 4 red suites — every one `Failed to resolve entry for package @objectstack/service-settings`/`-automation`/`-storage`/`-realtime`, packages OUTSIDE that filter closure and therefore unbuilt. NOT MEASURED, not red; the numbers above are the re-run on a fully built tree. ABLATION (restore the unconditional successAnswer): predictions written to a file BEFORE running, direction predicted RED — predicted 11 fail / 4 pass, ACTUAL 11 fail / 4 pass, and the same four survivors. The 4 green ones are deliberate controls (success path 200, unclassified residual 200, and their two cross-door twins) pinning behaviour this change must NOT alter; `endpoint-executor.test.ts` (49 cases, the pre-change suite) also stayed fully green under the ablation. CROSS-DOOR PARITY: the same AutomationResult driven through all three doors from ONE service object — disabled 409 / no-start-node 422 / ran-and-failed 400 / unknown-flow 404 (and none of the three dispatches it) / success 200 — all equal at all three doors.",
"open_questions": [
{
"question": "The UNCLASSIFIED refusal residual (`success:false` with no `code` and no `status`) is the one outcome on which the three doors do not all agree, and the shared table deliberately does not own it. I chose door 1's reading (keep 200) for door 3 and pinned it. Confirm or overturn.",
"options": [
"A — mirror the TRIGGER door: keep 200. #5040 section 4, the rule endpoint-executor.ts opens with, makes a declared `flow` endpoint a stable URL plus a policy layer over POST /automation/:name/trigger — same context builder, same execute call, so the same answer, or the alias has become the second execution dialect that module exists to prevent. Also never PROMOTES an exit the producer did not classify, which the shared module's note calls the thing a door must not do. Cost: the double envelope survives for this one residual on a public app surface, and outputMapping can still project that 200-wrapped failure as data.",
"B — mirror /actions: refuse it 400 FLOW_FAILED. Removes the double envelope from this seam completely and closes the outputMapping hole for the residual too. Cost: breaks alias-equivalence with door 1 for that case (the exact drift #5040 section 4 forbids), and imports a ruling made about a different route.",
"C — settle it once in the SHARED table for all three doors, as a follow-up card: make the residual a fifth row, or have the producer classify it so it stops being a residual at all."
],
"recommendation": "A now (it is what landed), C as the durable answer. A is the conservative, already-ruled choice and B is an unruled behaviour widening decided at one call site — precisely the shape that produced three divergent doors in the first place. But A leaves a real asymmetry on a public surface, so if the maintainer wants the double envelope gone everywhere, the correct act is C: one edit to flow-dispatch-status.ts that moves all three doors together, not a fourth private reading at this one. I did not file C as an issue — it is a decision, not a defect, and it belongs in this thread rather than in the backlog until it is ruled."
}
],
"out_of_scope_findings": []
}
Zone-2 assumptions, checked
assumption
verdict
packages/client/src/index.ts + 2 test files are consumers
FALSIFIED. Those hits are /actions (normalizeActionResult, already converged in #9446) and the generic {success,data,meta} unwrap. The SDK has 0 references to api/v1/apps — control: 4 hits for v1/actions. No SDK method calls a declared endpoint at all.
the shared module's rows apply unchanged at door 3
HELD, including the getFlow seam. Door 3 has it: deps.automationService is the same IAutomationService occupant, so flowIsUnknown(service, plan.flow) is the identical probe, asked after the existing isServiceServeable 501 gate and before execute. A service omitting getFlow dispatches as before, pinned. No dead row.
outputMapping applies only on the success path
HELD, and the mapping site needed no change. It lives in api-endpoint-step.ts keyed on answer.status < 400, upstream-independent of this function, so the refusal rows fall outside it by the rule already written. Flipping the envelope therefore also closes a real hole: the projection used to be applied to the 200-wrapped FAILURE body and could present a refused dispatch as data. cacheTtl's Cache-Control rides the same test; the rateLimit/authRequired chain runs upstream of execution and is untouched.
only http-protocol.mdx carries the divergence wording
FALSIFIED.content/docs/automation/flows.mdx carried the same sentence ("Declared endpoints … are the remaining exception"). Fixed under the bounded in-place-fix rule; claim file surface amended in the comment above, named in the PR body.
Activity
docs(actions): the action door answers the flow-dispatch table, and a…
os-zhuang commentedon Aug 18, 2026
Claim: PM loop round 18⚠️ #9416 and #9457 are deliberately NOT dispatched alongside this card (same package; the maintainer re-confirmed 「不豁免,维持串行」 on 2026-08-18). This card goes first because the staging ruling sequenced door 3 after door 2, and door 2 has just landed.
Session:
session_012WKSnqAaoqtW3QX7SSf1VkBranch:
claude/issue-9462-declared-endpoints-status-tableWorktree:
objectstack-issue-9462Domain:
domain:cliFile surface:
packages/runtime/src/endpoint-executor.ts+ its test siblings, andcontent/docs/protocol/kernel/http-protocol.mdx(the boundary wording this card's own body says flips back) — stop on breach; explain in the reportContainer & model:
M–L,mode:subagent,model: opus— a DOWNGRADE from the Clause-② mandate, under the maintainer's one-time authorizationClause-②: yes, verbatim from this card's own body — accept/reject behaviour changes on a public surface.
Serial constraints cleared:
packages/runtimefreed at 12:38:35Z — #9446 landed (PR #9584). Verified onorigin/mainby probe, ⛔ not taken from the merge event:flow-dispatch-statusis referenced by exactly 2 files,action-execution.tsanddomains/automation.ts— the two live doors — against a control of 243 files inpackages/runtime/src.Model-tier downgrade, recorded as the rule requires
Maintainer authorization, 2026-08-18, verbatim: 「授权一次性降档到 opus」, given in answer to a direct question naming five cards — #9446, #9416, #9462, #9488, #9487 — while the Fable 5 budget was exhausted (five measurements, ~01:2x through ~09:3x, the last three by throwaway probe touching no card). ⛔ The authorization does not generalise beyond those five.
The review tier is reduced; the verification bar is not. The reverse-verification requirement below is the compensating control, and I will verify the breaking-change analysis myself rather than on report.
Premise re-verified on
origin/mainthis roundreturn successAnswer(await automation.execute(plan.flow, automationContext));—endpoint-executor.ts:483(control: the file is 508 lines, so it resolves).endpoint-executor.tshas zero references toflow-dispatch-status— the shared module The other two flow-dispatch doors never got the #9378 status table —/actionsmislabels never-dispatched refusals, declared endpoints still answer 200 #9446 just created exists and this door is not yet wired to it. That wiring is the card, and it is why the ruling staged this after door 2: there is now one definition to call rather than a third table to write.The envelope flip is breaking for any consumer of the current
HTTP 200 {"success":true,"data":{"success":false,...}}double envelope — the shape #3962 already ruled out for/actions. The card's own constraints require the consumers be enumerated with real greps before flipping, and the objectui half coordinated if one exists. My starting measurement for the dev to extend rather than trust:packages/client/src/index.tsplus two of its test files match the double-envelope shape.⛔ If implementation surfaces a reason door 3 cannot converge, that is a fork report back to the decision inbox — ⛔ never a silent scope change. And door 3 carries the endpoint policy chain and
outputMapping, so this is not a copy of door 2's mapper: the mapping has to sit where those still apply.Generated by Claude Code
os-zhuang commentedon Aug 18, 2026
File-surface amendment (dev seat, same round)
The claim's declared file surface named
packages/runtime/src/endpoint-executor.ts+ itstest siblings and
content/docs/protocol/kernel/http-protocol.mdx. The consumerenumeration this card requires turned up a second page carrying the same boundary
sentence, so the surface is amended here rather than left to be discovered in the diff:
content/docs/automation/flows.mdxIt reads, today on
origin/main(line 1317):That is the identical defect class as the
http-protocol.mdxsentence this card's ownconstraint 5 says flips back — boundary wording added by the #9413/#9415 series to
describe a divergence this PR removes — and the correct form is already pinned by the
sibling page plus the constraint. Leaving it would ship a documented falsehood created by
this very diff. Scope is unchanged otherwise: still one defect class, no new verification
surface (both pages ride the same
check:docs-*/ docs-drift family, all green locally).Evidence query behind the find, with its control:
The fix is named explicitly in the PR body, per the bounded in-place-fix rule.
Generated by Claude Code
Generated by Claude Code
fix(runtime): declared `type: 'flow'` endpoints answer the #9378 flow…
os-zhuang commentedon Aug 18, 2026
{ "issue": 9462, "status": "done", "branch": "claude/issue-9462-declared-endpoints-status-table", "pr": "https://github.com/objectstack-ai/objectstack/pull/9676", "premise_still_valid": true, "summary": "Door 3 converged. `executeFlow` in packages/runtime/src/endpoint-executor.ts now CALLS the shared definition (`flow-dispatch-status.ts`) rather than open-coding a third table: `flowIsUnknown` answers row 1 (404) via the same optional getFlow probe doors 1 and 2 use, and `classifyFlowRefusal` answers rows 2-4 (409 FLOW_DISABLED / 422 FLOW_NO_START_NODE / 400 FLOW_FAILED), with the 400 arm carrying the run's `errorMessage`/`summary` in `error.details` byte-identically to the trigger door. The unconditional `successAnswer` is gone from every refusal path; a successful run is untouched. Both doc pages that asserted the divergence are flipped, and a minor changeset frames the break. NOTE the one judgement call, raised in open_questions and pinned as a test: the UNCLASSIFIED `success:false` residual keeps today's 200 at this door, matching door 1 (the route #5040 section 4 makes this endpoint an alias of), not door 2.", "tests": "All at final commit `45c2720d2` (git rev-parse --short HEAD from the same runs; tree byte-identical after the ablation was restored — `git diff HEAD` empty). Full workspace `pnpm build` EXIT=0. `@objectstack/runtime` test: `Test Files 171 passed (171) / Tests 2543 passed (2543)`, EXIT=0. `@objectstack/runtime` typecheck (`tsc --noEmit`) EXIT=0. DOWNSTREAM consumers (prefix filter `...@objectstack/runtime` = dependents; 13 packages + runtime): 12 of 13 run, all green — client 310, cli 1452, client-react 34, hono 73, http-conformance 72, verify 32, plugin-dev 54, cloud-connection 191, app-showcase 334, app-crm 42, app-todo 106; plus the dogfood consumer of THIS seam over a real socket (showcase-declarative-endpoints + declarative-endpoint-policy) `Test Files 2 passed (2) / Tests 25 passed (25)`. NOT RUN: `@objectstack/driver-turso` (13th consumer) — its lock turn never came inside the ~20min queue budget and I tore my waiter down rather than stall; it is a database driver with no path to the HTTP flow envelope, and CI covers it. Gates, all EXIT=0 at the same sha: check:type-check-coverage, check:type-check-debt, check:nul-bytes, check:route-envelope, check:error-status-conformance, check:error-code-casing, check:dispatcher-error-vocabulary, check:cross-package-test-inputs, check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:docs-audit-scope, check:docs-redirects, check:role-word, scripts/docs-audit/check-affected-docs.mjs, check-adr-0087-registration, check-changeset-no-major, check-empty-changeset — the last four beyond the derivation the dispatch carried, re-derived from my ACTUAL changed paths with scripts/pm/dispatch-gates.mjs. CI job status at report time: in_progress (reported at draft-PR time per the 2026-08-10 ruling; no CI waiting). ⚠️ ONE MEASUREMENT RETRACTED: a first consumer run scoped `--filter '...@objectstack/runtime'` alone reported 4 red suites — every one `Failed to resolve entry for package @objectstack/service-settings`/`-automation`/`-storage`/`-realtime`, packages OUTSIDE that filter closure and therefore unbuilt. NOT MEASURED, not red; the numbers above are the re-run on a fully built tree. ABLATION (restore the unconditional successAnswer): predictions written to a file BEFORE running, direction predicted RED — predicted 11 fail / 4 pass, ACTUAL 11 fail / 4 pass, and the same four survivors. The 4 green ones are deliberate controls (success path 200, unclassified residual 200, and their two cross-door twins) pinning behaviour this change must NOT alter; `endpoint-executor.test.ts` (49 cases, the pre-change suite) also stayed fully green under the ablation. CROSS-DOOR PARITY: the same AutomationResult driven through all three doors from ONE service object — disabled 409 / no-start-node 422 / ran-and-failed 400 / unknown-flow 404 (and none of the three dispatches it) / success 200 — all equal at all three doors.", "open_questions": [ { "question": "The UNCLASSIFIED refusal residual (`success:false` with no `code` and no `status`) is the one outcome on which the three doors do not all agree, and the shared table deliberately does not own it. I chose door 1's reading (keep 200) for door 3 and pinned it. Confirm or overturn.", "options": [ "A — mirror the TRIGGER door: keep 200. #5040 section 4, the rule endpoint-executor.ts opens with, makes a declared `flow` endpoint a stable URL plus a policy layer over POST /automation/:name/trigger — same context builder, same execute call, so the same answer, or the alias has become the second execution dialect that module exists to prevent. Also never PROMOTES an exit the producer did not classify, which the shared module's note calls the thing a door must not do. Cost: the double envelope survives for this one residual on a public app surface, and outputMapping can still project that 200-wrapped failure as data.", "B — mirror /actions: refuse it 400 FLOW_FAILED. Removes the double envelope from this seam completely and closes the outputMapping hole for the residual too. Cost: breaks alias-equivalence with door 1 for that case (the exact drift #5040 section 4 forbids), and imports a ruling made about a different route.", "C — settle it once in the SHARED table for all three doors, as a follow-up card: make the residual a fifth row, or have the producer classify it so it stops being a residual at all." ], "recommendation": "A now (it is what landed), C as the durable answer. A is the conservative, already-ruled choice and B is an unruled behaviour widening decided at one call site — precisely the shape that produced three divergent doors in the first place. But A leaves a real asymmetry on a public surface, so if the maintainer wants the double envelope gone everywhere, the correct act is C: one edit to flow-dispatch-status.ts that moves all three doors together, not a fourth private reading at this one. I did not file C as an issue — it is a decision, not a defect, and it belongs in this thread rather than in the backlog until it is ruled." } ], "out_of_scope_findings": [] }Zone-2 assumptions, checked
packages/client/src/index.ts+ 2 test files are consumers/actions(normalizeActionResult, already converged in #9446) and the generic{success,data,meta}unwrap. The SDK has 0 references toapi/v1/apps— control: 4 hits forv1/actions. No SDK method calls a declared endpoint at all.getFlowseam. Door 3 has it:deps.automationServiceis the sameIAutomationServiceoccupant, soflowIsUnknown(service, plan.flow)is the identical probe, asked after the existingisServiceServeable501 gate and beforeexecute. A service omittinggetFlowdispatches as before, pinned. No dead row.outputMappingapplies only on the success pathapi-endpoint-step.tskeyed onanswer.status < 400, upstream-independent of this function, so the refusal rows fall outside it by the rule already written. Flipping the envelope therefore also closes a real hole: the projection used to be applied to the200-wrapped FAILURE body and could present a refused dispatch as data.cacheTtl'sCache-Controlrides the same test; therateLimit/authRequiredchain runs upstream of execution and is untouched.http-protocol.mdxcarries the divergence wordingcontent/docs/automation/flows.mdxcarried the same sentence ("Declared endpoints … are the remaining exception"). Fixed under the bounded in-place-fix rule; claim file surface amended in the comment above, named in the PR body.Generated by Claude Code
Generated by Claude Code
fix(runtime): /actions answers the objectstack-ai#9378 flow-dispatch …
fix(runtime): declared `type: 'flow'` endpoints answer the objectstac…