Repository navigation
fix(runtime): New similar on a flow with a live override writes the copy instead of answering 403 NOT_OVERRIDABLE - #29689
Conversation
The clone door copies the engine's body for the name, which with a live override is the pair's item declaring overrides; the copy keeps that block under a new name and the save gate refuses it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JQwxbYmYA3GtqMUGHGQkR8
…erride declaration FLOW_CLONE_DROPPED_KEYS now derives OverrideDeclarationFields beside the protection envelope, so the copy is the pair's own item under its new name and the save gate no longer answers 403 NOT_OVERRIDABLE (ADR-0146 D2/D4). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JQwxbYmYA3GtqMUGHGQkR8
…is live; changeset Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JQwxbYmYA3GtqMUGHGQkR8
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2c4dae7b17020061ff1cbbd0031ae929c7ff8916 && git checkout 2c4dae7b17020061ff1cbbd0031ae929c7ff8916
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ece5faacbe5ebf3cad45114aff5f0b2f440f5cb1 14f1ec09dd277c4d8b4b64955aeeadc1c0f740c9 && git checkout -B drift-repro ece5faacbe5ebf3cad45114aff5f0b2f440f5cb1 && git merge --no-ff 14f1ec09dd277c4d8b4b64955aeeadc1c0f740c9
node scripts/docs-audit/affected-docs.mjs --json ece5faacbe5ebf3cad45114aff5f0b2f440f5cb1 |
|
domain:server#5 · os-warren · session_01JQwxbZUR3xvHyDPCjNL5P4 席位核对ACCEPT ·
|
Requested by Warren · project thread
Fixes #29650
Clause-②: no
Metadata-protocol: none
维护者速读
overrides声明,副本成为配对包自己的条目。overrides换了新名字,写入闸门答 403 NOT_OVERRIDABLE,什么都没写成。OverrideDeclarationFields派生);回滚即 revert 本 PR,无数据迁移。packages/runtime/src/flow-clone.ts(FLOW_CLONE_DROPPED_KEYS)、packages/runtime/src/domains/automation-flow-clone-custom-package.test.ts(新 describe)、docs/qa/platform-checklist/areas/automation.json(第 10 步与 acceptance[6])。pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/domains/automation-flow-clone-custom-package.test.ts,预期 19/19 通过。What changes
FLOW_CLONE_DROPPED_KEYS(packages/runtime/src/flow-clone.ts) now spreadsObject.keys(OverrideDeclarationFields)from@objectstack/spec/kernel, derived the same way the protection envelope already is, so a clone never carriesoverrides. The docblock says why: a declaration names the base item it replaces by name, so under the copy's new name it would declare an override of a flow the base does not ship (ADR-0146 D2), and New similar writes the pair's own item (D4).automationService.getFlow(name), the engine's flow map entry for the name. With a published override that entry is the override's body, armed in place of the base (ADR-0146 D9 item 1,resolveFlowPrecedence). So the copy carries the override's definition, minus the declaration, which is what the card asks for. If a reviewer reads that differently, say so on the PR. The stored-row binding the protocol stamps on a hydrated body is a symbol-keyed property, whichstructuredClonedoes not copy, so nothing else from the pair's row reaches the copy.automation.package-flow-override, revision 3: a new step 10 customizes and publishes again, then presses New similar while the override is live. The new acceptance[6] says that answers 200 into the pair, with the override's edits, statusobsoleteand nooverrideskey, and that the source still serves the override. Appended rather than inserted, so the existing step and acceptance indices that other text cites do not move.automatednames the new pin.@objectstack/runtimepatch.Why
An administrator who customized a packaged flow and wanted a second variant got an English sentence about a declaration they never wrote, and the only way through was to delete their customization first. The card measured it live on
main. This PR reproduces the same refusal at the clone door over the real metadata protocol and store.Tests
[#29650, ADR-0146 D2/D4] New similar while the source's override is liveinautomation-flow-clone-custom-package.test.ts. It uses the existing real-stack rig (better-sqlite3, the realObjectQL, a realObjectStackProtocolImplementation). It ensures the pair, saves the override into it declaringoverrides: { package, basedOn }, arms it in the engine map, and then drivesPOST /automation/:name/clonethroughHttpDispatcher. It has two cases: the copy answers 200 into the pair with nooverrideson the answer, the stored row or the registered body; and the copy carries the override's definition while the override row and the armed body stay as they were.ensures the pair through the ensure door…) stay at 200.Acceptance notes
packages/spec/**, nopackages/metadata-protocol/**, no wire shape changed.OverrideDeclarationFieldswas already exported from@objectstack/spec/kernel.Whyheading folded in. The template's Human Minimum checklist is not repeated here.Verification
Red first, on the base tree plus the new pin only (commit db4ce28)
Green after the fix (both clone suites)
Negative pin ablation: the override keys put back on the copy
scripts/ablation-replace.mjs --deleteremoved the...Object.keys(OverrideDeclarationFields),line fromFLOW_CLONE_DROPPED_KEYS(anchor 1 to 0, blob 9864c4f16874 to 60975c62ef63). The suite importsflow-clone.tsfrom source, so no dist leg applies.Gates at head 14f1ec0
pnpm --filter @objectstack/runtime test: exit 0, Test Files 529 passed (529), Tests 8514 passed, 19 skipped (8533).pnpm --filter @objectstack/runtime typecheck: exit 0.pnpm lint: exit 0.node scripts/pm/dispatch-gates.mjs --commands: 63 derived families, 62 run with exit 0.check:plugin-teardown-shape --self-testfirst exited 3 on the shallow clone (its pinned fixture was out of reach), then exited 0 aftergit fetch --unshallow origin main.pnpm check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET (it reads every package's dist and only the runtime closure was built). Left to CI.dispatch-gates --ran: 63 derived, 62 run, 1 NOT-MEASURED, 0 UNRUN.Generated by Claude Code