fix(metadata-protocol): saveMetaItem canonicalizes flow bodies on write (#4542) - #4548
Merged
Merged
Conversation
…te (#4542) A Studio edit healed every legacy row except a flow's: reads serve stored flows verbatim (the ADR-0078 conflict guard needs the live executor registry), and FlowNodeSchema.config is an open z.record, so the served legacy dialect sailed back through the schema gate and re-persisted verbatim — the row stayed `pending` in `os migrate meta --stored` no matter how many times an author edited it. saveMetaItem now runs resolveFlowCanonicalizer (#4498) on flow bodies before the schema gate and persists `storable` (never the parsed shape — schema defaults stay excluded, ADR-0087). A refused node-type rename fails the save with 409 FLOW_CONVERSION_CONFLICT naming the token; a body the stricter canonicalizer cannot parse (cycles, regions) falls back to the raw save in draft and publish mode alike, so WIP drafts stay saveable; with no automation service reachable the save behaves exactly as before. Copy-on-write keeps migrateStoredMetadata / duplicatePackage re-entry free. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NDmJ5ASMSzcw1q17vh32MG
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4542.
What
A Studio edit healed every legacy metadata row except a flow's. Reads serve stored flows verbatim (the ADR-0078 open-namespace conflict guard needs the engine's live executor registry, so
convertStoredItemskips them), andFlowNodeSchema.configis an openz.record— so the legacy dialect an author was served (config.filters, pre-17 node aliases) sailed back throughsaveMetaItem's schema gate and re-persisted verbatim. The row stayedpendinginos migrate meta --storedno matter how many times an author edited it, contradicting the boot warning's own remediation text ("re-save it (Studio edit → save …) to persist the canonical shape").saveMetaItemnow runsresolveFlowCanonicalizer()(#4498) on flow bodies before the schema gate — the same seam and postures asduplicatePackage:storable(conversions + derived condition envelopes, never the schema's defaults — ADR-0087). Copy-on-write, somigrateStoredMetadata/duplicatePackagere-enteringsaveMetaItemwith already-canonical bodies pay nothing.409 FLOW_CONVERSION_CONFLICTnaming the token and path, never a silent legacy persist. 409 rather than 422 because the body may be perfectly valid — the refusal comes from environment state, so resubmitting the same body cannot help.registerFlowstill refuses to arm a malformed flow.os migrate meta --storedreports what it could not canonicalize.Reads are unchanged ("reads diagnose, never drop") — the heal happens on the way back in.
Decision flagged in the issue
The issue left open whether the throw-fallback applies in
publishmode or drafts only. This PR applies it in both modes: default mode ispublish, so refusing on canonicalizer throw would silently tighten validation for every existing caller and make save semantics diverge between control-plane hosts (no canonicalizer → no strict parse) and automation hosts. The alternative (publish-mode 422 for unparseable flows) is defensible — it just cannot register anyway — but is a behavior change for bodies that save fine today, better made as its own deliberate change. Reviewers can overrule cheaply; the branch is one condition.Tests
packages/metadata-protocol/src/protocol.save-flow-canonicalization.test.ts(11 tests): runs the real repository write path with a stub engine (the existing flow-canonicalizer harness mockssaveMetaItemitself, which a fix inside it cannot use). Pins: heal + checksum pairs with the canonical body; conflict → 409 with token, nothing persisted; throw → raw save; throw does not rescue a gate-invalid body (422 still wins); absent service → verbatim save; copy-on-write identity; both behaviors in draft mode; lazy resolution; called with name; non-flow types never consult the canonicalizer.packages/cli/.../meta.stored-flow-resolution.integration.test.ts, realbootSchemaStack+ SQLite): the exact probe from [P2] A Studio edit heals every legacy row except a flow's —saveMetaItemre-persists the pre-protocol dialect an author was served #4542 — seed a pre-17 flow,getMetaItem(served legacy), edit only the label, save; assert the disk row is canonical, carries no schema defaults, and the--storedpreview reportspending: 0.metadata-protocolsuite (201 tests), objectqlsaveMetaItemrepo-path suites (98), automation canonicalizer/inert-mode suites — all green. Typecheck + eslint clean.Riders
Changeset (
.changeset/save-meta-item-flow-canonicalization.md) and an ADR-0087 addendum ("the save seam itself") — the 2026-08-01c addendum ended with "saveMetaItemis the seam they would adopt"; this completes it. Nocontent/docs/releases/changes.🤖 Generated with Claude Code
https://claude.ai/code/session_01NDmJ5ASMSzcw1q17vh32MG
Generated by Claude Code