Skip to content

fix(metadata-protocol): saveMetaItem canonicalizes flow bodies on write (#4542) - #4548

Merged
os-zhuang merged 1 commit into
mainfrom
claude/flow-metadata-persistence-issue-cn4aiy
Aug 2, 2026
Merged

fix(metadata-protocol): saveMetaItem canonicalizes flow bodies on write (#4542)#4548
os-zhuang merged 1 commit into
mainfrom
claude/flow-metadata-persistence-issue-cn4aiy

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

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 convertStoredItem skips them), and FlowNodeSchema.config is an open z.record — so the legacy dialect an author was served (config.filters, pre-17 node aliases) sailed back through saveMetaItem's schema gate and re-persisted verbatim. The row stayed pending in os migrate meta --stored no 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").

saveMetaItem now runs resolveFlowCanonicalizer() (#4498) on flow bodies before the schema gate — the same seam and postures as duplicatePackage:

  • Clean canonicalization → persists storable (conversions + derived condition envelopes, never the schema's defaults — ADR-0087). Copy-on-write, so migrateStoredMetadata / duplicatePackage re-entering saveMetaItem with already-canonical bodies pay nothing.
  • Refused node-type rename (old token is a live name owned by a custom executor) → the save fails with 409 FLOW_CONVERSION_CONFLICT naming 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.
  • Canonicalizer throw (it is stricter than the gate: cycle detection, control-flow regions) → falls back to the raw save and today's gate, so a WIP draft with a temporary cycle stays saveable. registerFlow still refuses to arm a malformed flow.
  • No automation service reachable (control-plane / metadata-only host) → saves exactly as before; os migrate meta --stored reports 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 publish mode or drafts only. This PR applies it in both modes: default mode is publish, 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

  • New unit file 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 mocks saveMetaItem itself, 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.
  • Integration (packages/cli/.../meta.stored-flow-resolution.integration.test.ts, real bootSchemaStack + SQLite): the exact probe from [P2] A Studio edit heals every legacy row except a flow's — saveMetaItem re-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 --stored preview reports pending: 0.
  • Regression: full metadata-protocol suite (201 tests), objectql saveMetaItem repo-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 "saveMetaItem is the seam they would adopt"; this completes it. No content/docs/releases/ changes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NDmJ5ASMSzcw1q17vh32MG


Generated by Claude Code

…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
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 2, 2026 2:26am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol.

3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review August 2, 2026 07:41
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 2, 2026
Merged via the queue into main with commit f61edce Aug 2, 2026
20 checks passed
@os-zhuang
os-zhuang deleted the claude/flow-metadata-persistence-issue-cn4aiy branch August 2, 2026 07:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] A Studio edit heals every legacy row except a flow's — saveMetaItem re-persists the pre-protocol dialect an author was served

2 participants