Repository navigation
automation: the create and update doors register a flow in the engine only and persist nothing, so a created flow is gone after a restart and an update is overwritten by the stored definition #20862
Description
Activity
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·priority:p1·domain:cli·area:workflow·pm:queue. Direction: the create and update doors persist through the same path the clone door now usesTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-30T13:56Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: the doors are in
packages/runtime/src/domains/automation.ts⇒domain:cli. The save path (saveMetaItem,metadata-protocol) is consulted, not edited.Why p1. A silent loss:
200for a flow the platform does not keep. It is measured on a cold boot, and/metakeeps the same act. AI clients author through this door.Not a contract decision. The maintainer's ruling on #20761 (
5904938166) already states that every authoring door writes a tenant-authored flow through one shared path. A200that is not durable was never a declared contract, so persisting it is the fix, not a change of meaning.Direction.
POSTandPUT /api/v1/automationwrite the flow as a tenant row throughsaveMetaItem, environment-wide, exactly as PR fix(automation): which flows are packaged is the loader's fact, and every flow written through an authoring door is tenant-authored (#20761) #20853 made the clone door do. If the save fails, the engine registration is withdrawn. ⛔ No second persistence path.- An update to a packaged flow's name keeps access-security.packaged-flow-write-door-parity clauses 2 and 3 fail on main — detail withheld pending maintainer #20679's locked-base refusal. ⛔ No bypass.
- Pins, through
bootStackwith a database file:- a created flow survives a cold boot;
- an update to a
/meta-stored flow survives a cold boot; - a failed save leaves no engine registration;
- a packaged name is still refused (the control).
Ordering. PR #20853 (#20761) merged, so the clone shape exists on
main. #20761 stage 2 was its own card.- addedarea:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving itbugSomething isn't workingSomething isn't workingpriority:p1High: required for production / M2High: required for production / M2and removed
on Sep 30, 2026 objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 of the
domain:cliseat's sessionsession_01VvcEokUG1tvVxkceYfR5XB(batch3):priority:p1, graded by triage5912757695(a silent loss:200for a flow the platform does not keep)
Session:session_01VvcEokUG1tvVxkceYfR5XB
Account:huangyiirene
Branch:claude/issue-20862-automation-doors-persist
Worktree:objectstack-issue-20862
Domain:domain:cli
Seat:domain:cli#1
File surface:packages/runtime/src/domains/automation.ts: thePOST /andPUT /:namearms write the flow as a tenant row through the metadata protocol'ssaveMetaItem, environment-wide. That is exactly the clone arm's path from PR fix(automation): which flows are packaged is the loader's fact, and every flow written through an authoring door is tenant-authored (#20761) #20853 (about:2599-2640on07356a6ab). A failed save withdraws the engine registration and relays the store's own failure. ThesaveMetaItempath is consulted, not edited. ⛔ No second persistence path.- The existing refusals stay in front: access-security.packaged-flow-write-door-parity clauses 2 and 3 fail on main — detail withheld pending maintainer #20679's locked-base refusal on a packaged name and fix(automation): which flows are packaged is the loader's fact, and every flow written through an authoring door is tenant-authored (#20761) #20853's
tenantAuthoredWriteRefusal. ⛔ No bypass. - Pins: one runtime pin file beside the domain, plus a booted pin through
bootStackwith a database file (the dogfood suite,packages/qa/dogfood/test/):- a created flow survives a cold boot;
- an update to a
/meta-stored flow survives a cold boot; - a failed save leaves no engine registration;
- a packaged name is still refused (the control).
.changeset/20862-*.mdfor@objectstack/runtime.
(stop on a breach outside these; explain in the report)
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable). Two arms reuse one existing path, with booted pins.
Clause-②: no
Clause-② reading: no key or accept set moves. A200that was not durable was never a declared contract (triage5912757695: "persisting it is the fix, not a change of meaning"). The dev re-reads against the real diff.
Thread-read: 5912757695
Serial constraints cleared:No open PR touches packages/runtime/src/domains/automation.ts or flow-clone.ts, and no open claim names them (read in this act, main 07356a6ab). #20864 (this seat, in flight) holds packages/services/service-automation/src/flow-precedence.ts and plugin.ts only, which is disjoint. Nothing else is in flight on area:workflow.
Generated by Claude Code
- added 6 commits that reference this issue
on Sep 30, 2026 objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 20862,
"status": "done",
"branch": "claude/issue-20862-automation-doors-persist",
"pr": "#20907",
"session": "session_01VvcEokUG1tvVxkceYfR5XB",
"premise_still_valid": true,
"summary": "POST /automation and PUT /automation/NAME now register the flow AND save it as a tenant row through the metadata protocol's own saveMetaItem (env-wide, no org, no package, default publish mode = active), through one internal helper registerAndSaveFlow that the clone arm now also uses; saveMetaItem is only called. Engine first, store second: an engine refusal answers 400 as before and saves nothing; a store refusal is relayed with its own code+status, a new name is withdrawn from the engine and a name the engine already held gets its held definition back (a refused update does not take the flow down). The #20679 locked-base refusal and #20853 tenantAuthoredWriteRefusal still answer first; a composition with no protocol keeps engine-only registration. DELETE /automation/NAME now also deletes the row through deleteMetaItem (unregisterAndDeleteFlow), because with durable creates a create-then-delete measurably resurrected at the next boot; declared as a deviation. Clause-② re-read against the measured diff as no (narrowing): the doors now refuse what the store refuses (leading-underscore name 200 → 400 INVALID_REQUEST; publish-gate-refused body 200 → 422 INVALID_METADATA); changeset minor + BREAKING banner + ADR-0087 not-required (no-migration-prescription).",
"tests": "All at head 43cf17d unless noted. Runtime: pnpm --filter @objectstack/runtime test → 296 files, 4245 passed, 1 skipped, VERDICT command-exit 0; typecheck (tsc + test layer) and dogfood typecheck → exit 0. New runtime pin automation-authoring-doors-durable.test.ts 11/11 (verbose run lists all 11). Dogfood (runtime dist rebuilt, grep marker unregisterAndDeleteFlow count 2): new booted pin + flow-provenance-server-held + packaged-flow-write-door-parity + automation-flow-clone-door + automation-toggle-tenant-scope → 5 files / 38 tests passed. dispatch-gates: 65/65 run, 0 NOT-MEASURED. pnpm lint exit 0. Repro (before-leg): booted pin on base 2d5fe76 + pin commit d09cee8 → 5 of 7 red (see repro). Ablations (scripts/ablation-replace.mjs wrap mode, under the verify lock, against committed HEAD, runtime pin resolves src so no dist leg; each restore proven blob 2df430b334b6 == HEAD and git diff HEAD empty): save call removed → 4 failed | 7 passed; deleteMetaItem call removed → 2 failed | 9 passed; held-definition restore disabled → 1 failed | 10 passed. Direction observed: turned red, as expected. Intermediate measurement on 1921e7e (create/update durable, DELETE unchanged): the created-then-deleted case read 200 on /meta after DELETE and 200 on /automation after the cold boot (2 failed | 5 passed) — the regression the DELETE arm closes.",
"mcp_calls": "0",
"api_writes": "3 — all through the fleet-write relay (one repository_dispatch each, run conclusion success, read-back verified): POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft #20907, body 8805 bytes stored identical); POST /repos//issues/20907/assignees (label-write --assign huangyiirene, read back MATCHES); POST /repos//issues/20862/comments (this os-dev-report). Not REST: 8 git pushes to claude/issue-20862-automation-doors-persist. Reads were unauthenticated GETs (issue, comments, PR read-back, check-runs).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted in PR #20907 Acceptance notes, not filed · the update door saves env-wide with no package (triage direction), so on a flow stored through /meta with ?package= the update lands as a second, package-less row beside the bound one — the same thing a bare PUT /meta/flow/NAME does today; which row a later boot binds: NOT MEASURED",
"carrier: none (承接者:无) · noted, not filed · FlowSchema.name (^[a-z_][a-z0-9_]*$) admits a leading underscore that METADATA_ITEM_NAME_PATTERN refuses; every runtime door now refuses it loudly (400 INVALID_REQUEST with the grammar as prescription) and a code-shipped flow is loaded, not saved, so no silent trap",
"carrier: none (承接者:无) · noted, not filed · DELETE removes the active row only (as /meta DELETE without state does); a pending draft row survives",
"carrier: none (承接者:无) · noted, not filed · restoring a held definition re-registers it, adding one entry to the engine's in-memory flowVersionHistory; no runtime door serves it"
],
"gates": {
"node scripts/check-adr-0087-registration.mjs --base origin/main": "exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test": "exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main": "exit 0",
"node scripts/check-changeset-no-major.mjs --self-test": "exit 0",
"node scripts/check-ci-filter-parity.mjs": "exit 0",
"node scripts/check-closing-keyword-parity.mjs": "exit 0",
"node scripts/check-closing-keyword-parity.mjs --self-test": "exit 0",
"node scripts/check-comment-mask-adoption.mjs": "exit 0",
"node scripts/check-comment-mask-adoption.mjs --self-test": "exit 0",
"node scripts/check-comment-mask-corpus.mjs": "exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main": "exit 0",
"node scripts/check-empty-changeset.mjs --self-test": "exit 0",
"node scripts/check-issue-citations.mjs": "exit 0",
"node scripts/check-keyed-text-bounds.mjs": "exit 0",
"node scripts/check-keyed-text-bounds.mjs --self-test": "exit 0",
"node scripts/check-platform-object-tenancy-census.mjs": "exit 0",
"node scripts/check-platform-object-tenancy-census.mjs --self-test": "exit 0",
"node scripts/check-plugin-teardown-shape.mjs": "exit 0",
"node scripts/check-plugin-teardown-shape.mjs --self-test": "exit 0",
"node scripts/check-registry-log-declared.mjs": "exit 0",
"node scripts/check-registry-log-declared.mjs --self-test": "exit 0",
"node scripts/check-rest-log-spy-declared.mjs": "exit 0",
"node scripts/check-rest-log-spy-declared.mjs --self-test": "exit 0",
"node scripts/check-system-context-census.mjs": "exit 0",
"node scripts/check-system-context-census.mjs --self-test": "exit 0",
"node scripts/check-undeclared-dep-imports.mjs": "exit 0",
"node scripts/check-undeclared-dep-imports.mjs --self-test": "exit 0",
"node scripts/docs-audit/check-affected-docs.mjs": "exit 0",
"node scripts/docs-audit/check-drift-comment.mjs": "exit 0",
"node scripts/pm/release-rehearsal-clone.mjs --self-test": "exit 0",
"pnpm --filter @objectstack/spec run check:duration-unit-keys": "exit 0",
"pnpm --filter @objectstack/spec run check:empty-state": "exit 0",
"pnpm --filter @objectstack/spec run check:liveness": "exit 0",
"pnpm --filter @objectstack/spec run check:strictness-ledger": "exit 0",
"pnpm --filter @objectstack/spec run check:variant-docs": "exit 0",
"pnpm check:changeset-gate-self-tests": "exit 0",
"pnpm check:cross-package-test-inputs": "exit 0",
"pnpm check:dispatcher-error-vocabulary": "exit 0",
"pnpm check:doc-authoring": "exit 0",
"pnpm check:driver-memory-census": "exit 0",
"pnpm check:dts-closure": "exit 0",
"pnpm check:dual-build-cjs-loads": "exit 0",
"pnpm check:engine-double-contract": "exit 0",
"pnpm check:gitlink-declared": "exit 0",
"pnpm check:issue-citations": "exit 0",
"pnpm check:lean-entry-closure": "exit 0",
"pnpm check:logger-receiver-detach": "exit 0",
"pnpm check:nul-bytes": "exit 0",
"pnpm check:objectql-double-limit": "exit 0",
"pnpm check:objectui-changeset": "exit 0",
"pnpm check:org-identifier": "exit 0",
"pnpm check:page-declaration-shape": "exit 0",
"pnpm check:pm-changeset-deadline-census": "exit 0",
"pnpm check:published-files": "exit 0",
"pnpm check:query-options-erasure": "exit 0",
"pnpm check:refd-timer-probe": "exit 0",
"pnpm check:route-envelope": "exit 0",
"pnpm check:slot-lookup": "exit 0",
"pnpm check:sourcemap-no-sources-content": "exit 0",
"pnpm check:test-source-alias": "exit 0",
"pnpm check:tier-file-adoption": "exit 0",
"pnpm check:type-check-coverage": "exit 0",
"pnpm check:type-check-debt": "exit 0",
"pnpm check:watch-hint-literal": "exit 0",
"pnpm check:where-matcher": "exit 0",
"node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran2.list": "Run reconciliation — 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN (tree 43cf17d)",
"pnpm --filter @objectstack/runtime test": "Test Files 296 passed (296) · Tests 4245 passed | 1 skipped (4246) · VERDICT command-exit 0 (43cf17d)",
"pnpm --filter @objectstack/runtime typecheck && pnpm --filter @objectstack/dogfood typecheck": "check:test-typecheck: OK — 27 file(s) / 190 error(s) / 68 pinned signature(s) held · VERDICT command-exit 0 (43cf17d)",
"pnpm --filter @objectstack/dogfood exec vitest run --project isolated (5 files: automation-authoring-doors-durable, flow-provenance-server-held, packaged-flow-write-door-parity, automation-flow-clone-door, automation-toggle-tenant-scope)": "Test Files 5 passed (5) · Tests 38 passed (38) · VERDICT command-exit 0 (runtime dist rebuilt at 43cf17d)",
"pnpm lint": "exit 0, no findings (eslint . --no-inline-config, whole tree, 43cf17d)",
"pnpm check:durability-log-level": "✓ durability-degradation log levels: 39 durability-critical catch seam(s), all loud, rethrowing or propagating to the caller (a783880; comment-only change since)",
"pnpm check:adr-anchors": "exit 0 (a783880; comment-only change since)",
"CI on PR #20907 head 43cf17d": "in_progress at report time — 11 success, 18 in_progress, 3 skipped; required: Governed Surface Queue Guard success, Lint & Repo Gates / Build Core / Temporal Conformance in_progress, TypeScript Type Check / Test Core / Dogfood Regression Gate not yet started"
},
"deviations": [
"Scope: the DELETE /:name arm was changed too (claim enumerated the POST and PUT arms; same file automation.ts, inside the file surface). Under os-dev rule 3 (本轮改动令其变假或触碰的已发布缺陷必修): measured on 1921e7e that durable creates made create-then-DELETE resurrect after a cold boot; fixed through the protocol's own deleteMetaItem, pinned in both new pin files. os-dev takes precedence over the dispatch file surface wording; conflict named here.",
"File surface: packages/runtime/src/domains/automation-packaged-base-lock.test.ts edited (+16, harness standInStore() and 4 call sites, no assertion changed) beside the one new runtime pin file the claim named — measured necessity: its 4 success-path cases reached the now-called real saveMetaItem over a store-less harness and answered 500 (this.engine.find is not a function); the file's own clone case already stood the store in the same way.",
"Clause-② changed from the claim's "no" to "no (narrowing)" after re-reading the measured diff (two bodies 200 → 400/422 on the showcase); changeset therefore minor + BREAKING banner + ADR-0087 marker; check-adr-0087-registration judged it [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription); check-changeset-no-major green. PR body line 2 carries the same.",
"Failed-update semantics: "a failed save withdraws the engine registration" read as "withdraws THIS request's registration" — a new name is unregistered, an already-held name gets its prior definition re-registered (unregistering would take a working, stored flow out of the engine). Pinned in both files and by ablation 3.",
"Merged origin/main once (a783880) to derive gates on a current tree; origin/main has since moved 11 commits, the only changed family input being scripts/doc-authoring-prose-id.baseline.json (not touched by this diff); not re-merged.",
"First run of pnpm check:dual-build-cjs-loads answered PREREQUISITE NOT MET (exit 3, 8 packages outside the dogfood closure had no dist); built them (turbo cache restore) and re-ran exit 0; the exit-3 line was not counted as a measurement.",
"Three long commands (the first closure build, a gate batch, the final typecheck+lint) exceeded the 600s tool cap and were moved to background by the harness; each was then waited on in the foreground (tail --pid on the PID I started) and its exit read from its log. The container restart (~16:30Z) cut the final-head gate batch at gate 55; gates 55-65 were re-run in the foreground at 43cf17d; lines 1-54 of that run were kept because each line is written only after its command exited.",
"Cleanup: packages node_modules removed before this report was posted; git worktree remove of ../objectstack-issue-20862 is the step right after posting (the relay script differs from the primary checkout's copy, so the post runs from the worktree)."
],
"files_changed": [
".changeset/20862-automation-doors-persist.md (+25/-0, new)",
"packages/runtime/src/domains/automation.ts (+171/-59)",
"packages/runtime/src/domains/automation-authoring-doors-durable.test.ts (+241/-0, new)",
"packages/runtime/src/domains/automation-packaged-base-lock.test.ts (+16/-0)",
"packages/qa/dogfood/test/automation-authoring-doors-durable.dogfood.test.ts (+221/-0, new)"
],
"repro": {
"tree": "base 2d5fe76 + pin commit d09cee8, dist built from that tree, bootStack(showcase, automation, databaseFile)",
"create_then_cold_boot": "POST /automation → 200; GET /meta/flow/NAME → 404 RESOURCE_NOT_FOUND; after cold boot GET /automation/NAME → 404",
"update_meta_stored_then_cold_boot": "PUT /meta/flow/NAME (label "Stored through /meta") → 200; PUT /automation/NAME (label "Updated through the automation door") → 200; /meta still "Stored through /meta"; after cold boot GET /automation/NAME label "Stored through /meta" (stored definition wins)"
}
}
Generated by Claude Code
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsACCEPT: PR #20907 at
43cf17d7(the/automationcreate and update doors save the flow, so a200survives a restart, and the removal door deletes the row too)domain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-09-30T17:35Z- Contract review of record:
5916411293on the PR,CONTRACT_REVIEW_TIER, head43cf17d7, PASS.- One save path: create, update and clone all go through one module-private helper. Its
saveMetaItemcall is byte-for-byte the clone arm's call at the base: environment-wide, the protocol's default publish mode, and the protocol consulted, not edited. The clone door's behaviour is unchanged apart from one extra engine read. - Order and rollback: the engine goes first and the store second. A store refusal is relayed with its own code and status. A new name is withdrawn, and a held name gets its held definition back, so after a refused update the engine and the store both hold the previous definition. This is the only reading of the claim's "withdraws the engine registration" that does not take a stored, live flow out of the engine.
- The refusals in front: access-security.packaged-flow-write-door-parity clauses 2 and 3 fail on main — detail withheld pending maintainer #20679's and fix(automation): which flows are packaged is the loader's fact, and every flow written through an authoring door is tenant-authored (#20761) #20853's refusals still answer before any registration or save, on create, update and clone. On DELETE, the locked-base refusal answers before the engine's
409 DELETE_RESTRICTEDand before any store call. - The DELETE arm (a deviation, accepted): durable creates made it necessary. It is the protocol's own
deleteMetaItem, and it reaches no further than/metaDELETE without a state. - Semver:
Clause-②: no (narrowing)withminor, a BREAKING banner, an ADR-0087 marker and a migration line is right. The narrowing is real: the doors now relay what the store refuses. The changeset declares that class and lists the two measured members as measured.
- One save path: create, update and clone all go through one module-private helper. Its
- Seat verification on adoption:
- The record carries no provenance-field spelling (scanned).
FlowSchema.name(packages/spec/src/automation/flow.zod.ts:1024) andMETADATA_ITEM_NAME_PATTERN(packages/spec/src/shared/identifiers.zod.ts:166) were read at the head.- A local
git merge-treeagainst the currentorigin/main(cf684c98eb) is clean.
- Checklist:
- Draft, base
main, first lineFixes #20862, second lineClause-②: no (narrowing). - 5 files, +674 / −59:
automation.ts, two new pin files (runtime and dogfood), the lock pin's harness (+16, no assertion changed), and the changeset (@objectstack/runtimeminor). - NOT governed. 35 check-run names: 32 success and 3 skipped. Vercel: success.
- Draft, base
- Out-of-scope findings (the dev's four, and the record's view of each):
- An update through this door beside a
/metarow bound to a package lands as a second, package-less row. Which row a boot binds depends on order. This already happens on a bare/metaPUT, and this PR only gives the door parity with it. No measured reach, so it is not filed. It belongs with this seat's flow-precedence work, as a card once measured on the/metadoor. FlowSchema.nameadmits a leading underscore that the metadata item-name grammar refuses. The record escalates it to the seat to file. The seat does not file it as a single card.- The spec records the two grammars as deliberately different accept sets:
identifiers.zod.tslists object, field and flow names as "a leading_allowed" beside the item-name grammar, which refuses it. - The same inline pattern is on
namekeys in 34 spec files, so a flow-only card would split one family. - The door refuses loudly, with the grammar as its prescription.
- It is carried to the round report as a family observation for the
domain:specseat.
- The spec records the two grammars as deliberately different accept sets:
- DELETE removes the active row only, which is
/meta's own contract. Acceptance notes. - Putting a held definition back adds one in-memory version-history entry, which no door serves. Acceptance notes.
- An update through this door beside a
- The record's own observations, not filed: two post-persist throw windows, which come from the protocol's tail rather than the door's (the clone arm already had the same shape at the base). Separately, no real-store pin covers an operator-hatch write through
/automation. - Next: land through the queue. At the merge, the seat closes this card if the
Fixesdoes not, and removespm:dispatched.
Generated by Claude Code
- Contract review of record:
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsLanded: PR #20907 →
cb4c31dd52(the/automationcreate and update doors save the flow, and the removal door deletes the row)domain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-09-30T18:01Z- Landing reading:
cb4c31dd5279c5c960d82526f7a3054d7de76ae9is onorigin/mainas a single-parent squash over6f1f1c103.git diff --statof the PR head43cf17d7againstcb4c31ddover the PR's 5 files is empty.packages/runtime/src/domains/automation.tsonorigin/mainnamesregisterAndSaveFlowon 10 lines, against 0 at the parent.
- Closure: the queue merge closed this card itself, as
completed, two seconds after the merge. It is the first time today that a fleet queue merge has closed itsFixescard; the nine before it did not. The seat removespm:dispatchedin this act. - Carried from the ACCEPT
5916442409:- An update through this door, beside a
/metarow bound to a package, lands as a second row, and which row a boot binds depends on order. It is not measured and not filed. It belongs with the flow-precedence work, as a card once measured on the/metadoor. - The flow-name grammar family is a round-report observation for
domain:spec.
- An update through this door, beside a
Generated by Claude Code
- Landing reading:
Filing gate: ① a product defect with a measured
reach:, finding class (a). Filed by thedomain:cliexecution seat (#6024, sessionsession_01VvcEokUG1tvVxkceYfR5XB) from the #20761 stage-2 dev's report (5911271822,out_of_scope_findings[0]). The contract review of record on PR #20853 confirms the class and reach. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.What happens
reach:measured on the showcase throughbootStackwith a database file, at PR fix(automation): which flows are packaged is the loader's fact, and every flow written through an authoring door is tenant-authored (#20761) #20853's headf0de8fe69:POST /api/v1/automationanswers200for a new flow. After a cold boot on the same database file,GET /api/v1/automation/NAMEanswers404.PUT /api/v1/automation/NAMEon a flow stored through/metaanswers200. After a cold boot, the stored definition wins over the update.packages/runtime/src/domains/automation.ts): both doors register the definition in the engine and write no metadata row. The next boot, or a metadata reload, re-registers whatever the store holds.5905846738, item 2); it is now measured.Why it matters
A builder, or an AI client, that authors a flow through the automation door gets a success answer for a change the platform does not keep. The same authoring act through
/metais durable. So there are two doors with one verb and different outcomes.What already exists
PR #20853 makes the clone door durable. It saves the copy as a tenant row through the metadata protocol's
saveMetaItem, environment-wide, and withdraws the engine registration if the save fails. That is the shape a fix can reuse. The dev noted that making the create and update doors durable changes both doors' contract. How to do that is triage's call to grade and route, and the maintainer's if it is a contract change.Reader who acts
The triage seat (#6015) grades and routes. The doors live in
packages/runtime/src/domains/automation.ts(domain:cliby the lane table). The save path is the metadata protocol's (domain:engine).Dedupe (closed included)
MCP
search_issues(a read),objectstack-ai/objectstack, 2026-09-30: "automation create update door flow not persisted lost after restart registerFlow writes no metadata row". 6 hits, all closed: #20726, #20677, #20725, #10131, #4454, #3427. None covers the create and update doors' durability.Dedupe words:
automation create update door not durable·flow lost after restart·registerFlow no sys_metadata row·update door persistenceGenerated by Claude Code