Skip to content

feat(automation): write doors answer the canonicalized parsed flow (#12206, Option A) - #13525

Draft
os-project-manager wants to merge 3 commits into
mainfrom
claude/issue-12206-automation-write-doors-parsed
Draft

feat(automation): write doors answer the canonicalized parsed flow (#12206, Option A)#13525
os-project-manager wants to merge 3 commits into
mainfrom
claude/issue-12206-automation-write-doors-parsed

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #12206

Implements the maintainer's Option A ruling (2026-08-26, card comment 5421206583, verbatim 「12452 不处理,其他接受」) on the automation write-door response contracts, exactly as ruled — evidence base: survey 5420919914 (accepted 5420937223), re-verified unchanged at 240aad5f (5469341400). The #12452-related surface is untouched per the ruling; #12452 remains open and is not addressed here.

Ruled items

  1. Both write doors answer the canonicalized (parsed) flow. POST /automation and PUT /automation/:name now end deps.success(registered) where registered is the parsed flow registerFlow stored — the same shape GET /automation/:name serves — instead of echoing the caller's pre-parse bytes (packages/runtime/src/domains/automation.ts). The plumbing is the contract itself: IAutomationService.registerFlow returns FlowParsed (previously void) — the value was already computed and discarded on every successful write (AutomationEngine.canonicalizeStoredFlowregisterFlow), so this is zero marginal compute and no new refusal surface. The PUT answer now always carries name, which the old echo could omit.
  2. SDK binds the single true return type. client.automation.create / .update return Promise<FlowParsed>, replacing the two deliberate Promise<any> declarations; the justifying comments ("no published type describes what comes back") were falsified by the survey (CreateFlowResponseSchema / UpdateFlowResponseSchema exist and promise data: Flow) and fall out with the any. The two exported-any-returns.json ledger entries are deleted (shrink-only graduation; gate line: "no NEW exported callable of @objectstack/client resolves to any: 317 callables reached … 40 ledgered site(s) still open"), and return-type-precision.test.ts compile-pins both.
  3. Inherited item ①CreateFlowResponseSchema / UpdateFlowResponseSchema are now conformant instead of aspirational: the new end-to-end test (packages/client/src/automation-write-door-parsed-answer.test.ts, real AutomationEngine + real HttpDispatcher + real ObjectStackClient, in-process socket) parses both schemas against the REAL wire bodies — the first route responses these schemas have ever seen.
  4. Inherited item ②UpdateFlowRequestSchema.definition drops .partial(): the engine's registerFlow runs FlowSchema.parse, so a partial body was always a 400 against a real server; the declaration now matches the complete definition the engine requires. A real partial-update capability remains a future feature card — none was built.
  5. Inherited item ③ — the two SDK unit tests whose bodies were 400 against a real server (packages/client/src/client.test.ts) now use registrable bodies, each pinned registrable by a real FlowSchema.parse of the exact wire body the SDK sends.
  6. Changeset with migration note — behaviour change on a published SDK surface; the one residual risk is named verbatim from the survey: the edge.condition string → {dialect, source} type change, zero measured consumers (cloud repo unmeasured, declared as the gap).

Ablation / reverse verification (direction predicted before each run; mutation and restore both proved on disk)

  • OLD pin vs NEW schema (predicted red, observed red): automation-api.zod.test.ts "should accept a partial update" failed against the tightened UpdateFlowRequestSchema (ZodError: type/nodes/edges required) — 1 failed / 42 passed — then replaced by a reject-partial + accept-complete pair, green.
  • OLD echo pin vs NEW answer (predicted red, observed red): automation-register-error-class.test.ts "a well-formed body still registers…" failed with data: undefined (its void double + the door now relaying registerFlow's return) — the double now returns parsed (faithful to the engine) and the pin asserts the materialized version: 1 the caller never wrote.
  • Leg 1 — engine mutation (return parsedreturn undefined, marker grep 1→0/0→1 on disk): predicted red observed red on the new engine pin ("returns the canonicalized parsed flow it stored", exit 1) and both client e2e cases (exit 1); restore via git checkout HEAD --, proven by git hash-object = HEAD blob (1a3bf67f…); rerun green (exit 0 / 0).
  • Leg 2 — route mutation (deps.success(registered) ×2 reverted to the echo, marker grep 2→0/0→2 on disk): predicted red observed red on the updated runtime pin (echo carries no version, exit 1); restore hash-verified (0ff5f6bd… = HEAD); rerun green (exit 0).
  • Resolution-path note: no rebuild was owed in either leg by construction — every suite involved resolves the mutated file as SOURCE (service-automation tests import in-package src; the client e2e resolves @objectstack/service-automation through the vitest src alias; runtime domain tests are in-package src). The runtime→dist seam (client e2e over @objectstack/runtime) was deliberately not used as a leg for that reason.

Gates (all at f734a5f unless noted; judgment lines quoted from the gates' own output)

  • Derived families (dispatch-gates.mjs --repo objectstack-ai/objectstack, no hand-fed paths; provenance line: "gate list derived from the tree of 'objectstack-ai/objectstack'"): 28 families at the pre-regen head, +17 delta families after the docs regen commit — all green, including check:docs ("230 generated files in sync with packages/spec"), check:authorable-surface, check:generated, check:cross-package-test-inputs, check:test-source-alias, check:engine-double-contract, check:dual-build-cjs-loads ("102 published require entry point(s) across 66 package(s) load"), check:skill-examples ("260 prose examples type-check"), check:docs-single-h1, check:type-check-coverage, check:type-check-debt ("29 ledger entr(ies) re-measured … none above its recorded number").
  • check:exported-any-returns green on the rebuilt client dist (line quoted in item 2).
  • Typecheck: @objectstack/spec, @objectstack/client, @objectstack/runtime green (single && chain, wrapper VERDICT command-exit 0). @objectstack/service-automation has no typecheck script (pre-existing ratchet-ledger posture) — not measured, not claimed. Runtime's tsc program excludes **/*.test.ts (pre-existing), so the edited runtime test files are vitest-executed but not tsc-measured.
  • Tests, full union at f734a5fa: spec 443 files / 11812 tests, client 31 / 407 (incl. the new e2e), runtime 201 / 2973, service-automation 91 / 1092 — PKG-EXITS all 0, each suite's exit captured before any pipe.

Notes for review (clause-②: parked draft, needs:contract-review)

  • IAutomationService.registerFlow return-type change is implementer-facing: any third-party registerFlow must now return the stored parsed flow. In-repo, the only typed implementer is AutomationEngine; route/test doubles were re-measured (spy doubles untyped; the two faithful doubles now return parsed).
  • The out-of-scope route quirk kept as-is per the ruling's fence: PUT's two-dialect body unwrap (body.definition ?? body) is pre-existing and untouched.
  • create(name, definition: any) / update(name, definition: any) parameter types deliberately untouched: the ruling names the RETURN type; narrowing the request accept-set is a separate decision.
  • Deviation, flagged for the merger: the first commit's trailer carries the harness's default co-author line naming the model; the dispatch forbids model identifiers in pushed artifacts, and history is append-only without a force-push (banned). Later commits drop it. Please use a clean squash message when landing.

Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N

Generated by Claude Code


Generated by Claude Code

claude added 3 commits August 30, 2026 15:14
…12206, Option A)

POST /automation and PUT /automation/:name relay the FlowParsed that
registerFlow now returns — the same shape GET serves. SDK binds
Promise<FlowParsed> on create/update; response schemas conformant;
UpdateFlowRequestSchema requires the complete definition; SDK unit
tests get registrable bodies; changeset carries the migration note.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N
…aithful to registerFlow's return (#12206)

Predicted red observed red: the old echo pin failed against the new
answer (data undefined from a void double); the double now returns the
parsed flow and the pin asserts the materialized version default.

Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 30, 2026
@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/client, @objectstack/runtime, @objectstack/service-automation, @objectstack/spec, touching 11 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/client/exported-any-returns.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx (via registerFlow (symbol))
  • content/docs/data-modeling/formulas.mdx (via registerFlow (symbol))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx (via IAutomationService (symbol), registerFlow (symbol))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/client/exported-any-returns.json) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: /automation/:name (route, 31 pages)
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 133 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 430a2783714f74b1c13add9a6347bedd32ea070apackageMentionDocs.

Which tree this was computed on

This run read content/docs from b60626fb2f14b21ff692fa5820ac6131e3ac418c — the merge of head 8d2d0c43bb55f8a675b2a45e118966bbd903bab9 into base 430a2783714f74b1c13add9a6347bedd32ea070a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b60626fb2f14b21ff692fa5820ac6131e3ac418c && git checkout b60626fb2f14b21ff692fa5820ac6131e3ac418c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 430a2783714f74b1c13add9a6347bedd32ea070a 8d2d0c43bb55f8a675b2a45e118966bbd903bab9 && git checkout -B drift-repro 430a2783714f74b1c13add9a6347bedd32ea070a && git merge --no-ff 8d2d0c43bb55f8a675b2a45e118966bbd903bab9

node scripts/docs-audit/affected-docs.mjs --json 430a2783714f74b1c13add9a6347bedd32ea070a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 430a2783714f74b1c13add9a6347bedd32ea070a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator Author

Docs Drift Check follow-up — hand-read verdict on the two flagged pages (symbol anchor registerFlow), measured at f734a5fa:

Zero doc edits were required: callers that ignore registerFlow's return remain correct (void → FlowParsed is additive for callers), and the retired echo shape was never documented anywhere under content/docs.

Generated by Claude Code


Generated by Claude Code

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

Labels

Projects

None yet

2 participants