fix(automation,spec): the cold-boot flow bind must survive the read path's own annotations - #4424
Merged
Merged
Conversation
…ath's own annotations
`getMetaItems({ type: 'flow' })` decorates every served item with
`_diagnostics` (and `_draft` on a preview read). The cold-boot bind fed that
served document straight into `engine.registerFlow` → `FlowSchema.parse`, and
since #4001 closed the metadata schemas an unrecognized key THROWS instead of
being dropped. So every flow failed to register on every boot:
WARN [Automation] cold-boot flow bind: failed to register task_hours_pause_project:
{ "code": "unrecognized_keys", "keys": ["_diagnostics"], … }
Not AI-specific — cloud's boot-smoke saw it on the sample package's
`overdue_escalation` / `task_completion` / `quick_add_task` too. The stored
metadata was always clean; we broke our own parse with our own annotation.
Not fatal today only by luck: the record-change plugin binds record flows by a
second path, so automations kept firing behind the WARN. A flow whose only
binding path is this one would have gone silently dead.
Fixed at the read seam (`readFlowDefsFromProtocol`), not by loosening
`FlowSchema`: the payload is malformed because WE decorated it, so the producer's
annotation is the producer's to remove — widening the schema would make our own
read shape a second, permanent contract.
The canonical decoration list moves from `metadata-protocol` (module-private)
into `@objectstack/spec/kernel`, because the producer and the consumers sit in
different layers and must not drift; `metadata-protocol` imports it and
re-exports `stripReadDecorations` unchanged. The strip removes ONLY the read
decorations — never the ADR-0010 envelope (`_lock`, `_packageId`, …), which
`FLOW_KEYS` allowlists and a rebind must preserve.
Regression coverage, all three verified to fail without the fix:
• flow-cold-boot-bind.test.ts — a decorated payload binds, in both the bare
and `{ item: … }` envelope shapes, with `_packageId` preserved.
• protocol.read-decorations.test.ts — against the REAL `getMetaItems`: the
raw served flow must still be REJECTED (the strip is load-bearing), the
stripped one must parse, and every key the read ADDS must be either a known
decoration or allowlisted by the closed schema. That last one is the drift
guard: stamping a new `_foo` on the read path fails it by name with the fix
to apply.
Refs cloud#971, #4001, #4326.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
… snapshot METADATA_READ_DECORATIONS / MetadataReadDecoration / stripReadDecorations — additive only (0 breaking), moved in from metadata-protocol so the read-path producer and its cross-layer consumers share one definition.
59 tasks
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 the framework half of objectstack-ai/cloud#971.
The bug
getMetaItems({ type: 'flow' })decorates every served item with_diagnostics(and_drafton a preview read). The cold-boot bind fed that served document straight intoengine.registerFlow→FlowSchema.parse— and since #4001 closed the metadata schemas, an unrecognized key throws instead of being dropped. So every flow failed to register on every boot:Not AI-specific — cloud's boot-smoke saw the same on the sample package's
overdue_escalation/task_completion/quick_add_task. The stored metadata was always clean (_diagnosticsis never persisted — #4326 sees to that on the write path). We broke our own parse with our own annotation.Not fatal today only by luck:
RecordChangeTriggerPluginbinds record flows by a second path, so automations kept firing behind the WARN. A flow whose only binding path is this one would simply have gone silently dead — and every boot log carried one WARN per flow, which is exactly the noise that hides a real failure.resyncFlowsFromProtocolreads the same seam, so publish-time rebinds were failing identically (flow re-sync: failed to register …).The fix
Strip the read decorations at the read seam (
readFlowDefsFromProtocol) — not by looseningFlowSchema. The payload is malformed because we decorated it, so the producer's annotation is the producer's to remove; widening the schema would make our own read shape a second, permanent contract.The canonical list moves from
metadata-protocol(where it was module-private) into@objectstack/spec/kernel, because the producer (metadata-protocol) and the consumers (service-automation's bind, and whoever is next) sit in different layers and must not drift.metadata-protocolnow imports it and re-exportsstripReadDecorationsunchanged — no public-surface change.The strip removes only the read decorations, never the ADR-0010 envelope (
_lock,_packageId, …):FLOW_KEYSallowlists those precisely so a served flow keeps its provenance on re-parse, and a blanket_-purge would erase a packaged flow's binding on every rebind.Regression coverage
All three verified to fail without the fix (and the two pre-existing cold-boot tests pass either way — they served naked bodies, which is why they never caught this):
flow-cold-boot-bind.test.ts— a decorated payload binds, in both the bare and{ item: … }envelope shapes, with_packageIdpreserved and_diagnostics/_draftgone.protocol.read-decorations.test.ts— driven through the realgetMetaItems:_servedAton the read path fails it by name:Sweep for other re-feeds
This is the only live one. Everything else that touches a served document is already safe: the write-back paths funnel through
saveMetaItem(strips first, #4326);permission-set-projection,assemblePackageManifestand cloud'sservice-ai-studioeach run their own strip;service-analytics/import-prepare/admin-import-usersread named fields only; andAgentSchema/SkillSchemaare deliberately not.strict()._packageIdis not the same disease — every closed schema allowlists it viaMetadataProtectionFields.Test
@objectstack/spec7225 ✓ ·@objectstack/metadata-protocol139 ✓ ·@objectstack/service-automation566 ✓🤖 Generated with Claude Code