Skip to content

fix(runtime): New similar on a flow with a live override writes the copy instead of answering 403 NOT_OVERRIDABLE - #29689

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-yzu3d4
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-yzu3d4

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread

Fixes #29650

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:流程「New similar」(克隆)复制定义时,连同保护信封一起丢掉 overrides 声明,副本成为配对包自己的条目。
  • 为什么改:管理员在 Studio 定制并发布了打包流程后再点 New similar,副本带着原定制的 overrides 换了新名字,写入闸门答 403 NOT_OVERRIDABLE,什么都没写成。
  • 风险与回滚:只多删一个键(从 spec 的 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 spreads Object.keys(OverrideDeclarationFields) from @objectstack/spec/kernel, derived the same way the protection envelope already is, so a clone never carries overrides. 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).
  • What the copy is copied from, as the code serves it. The door reads its source with 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, which structuredClone does not copy, so nothing else from the pair's row reaches the copy.
  • QA case 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, status obsolete and no overrides key, 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. automated names the new pin.
  • Changeset: @objectstack/runtime patch.

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

  • New describe [#29650, ADR-0146 D2/D4] New similar while the source's override is live in automation-flow-clone-custom-package.test.ts. It uses the existing real-stack rig (better-sqlite3, the real ObjectQL, a real ObjectStackProtocolImplementation). It ensures the pair, saves the override into it declaring overrides: { package, basedOn }, arms it in the engine map, and then drives POST /automation/:name/clone through HttpDispatcher. It has two cases: the copy answers 200 into the pair with no overrides on 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.
  • Control: the no-override cases already in the same file (ensures the pair through the ensure door…) stay at 200.

Acceptance notes

  • The order's Clause-② (no) and Metadata-protocol (none) hold on the diff: no packages/spec/**, no packages/metadata-protocol/**, no wire shape changed. OverrideDeclarationFields was already exported from @objectstack/spec/kernel.
  • Section order follows the os-dev order, with the repository template's Why heading 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)
× the copy is the pair's own item: 200, no `overrides` block, stored in the pair
× copies what the source serves: the override's definition, and the override stays as it was
AssertionError: {"success":false,"error":{"code":"NOT_OVERRIDABLE","message":"'flow/acme_lead_followup' declares an override of 'com.acme.crm', which ships no flow named 'acme_lead_followup'. Remove `overrides` to keep it as 'com.acme.crm.custom''s own item, or correct the name (ADR-0146 D2).","httpStatus":403}}: expected 403 to be 200
Tests  2 failed | 17 passed (19)
Green after the fix (both clone suites)
Test Files  2 passed (2)
     Tests  38 passed (38)
Negative pin ablation: the override keys put back on the copy

scripts/ablation-replace.mjs --delete removed the ...Object.keys(OverrideDeclarationFields), line from FLOW_CLONE_DROPPED_KEYS (anchor 1 to 0, blob 9864c4f16874 to 60975c62ef63). The suite imports flow-clone.ts from source, so no dist leg applies.

× the copy is the pair's own item: 200, no `overrides` block, stored in the pair
× copies what the source serves: the override's definition, and the override stays as it was
Tests  2 failed | 17 passed (19)
ok restored: blob == HEAD (9864c4f16874) and `git diff HEAD` is empty
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-test first exited 3 on the shallow clone (its pinned fixture was out of reach), then exited 0 after git fetch --unshallow origin main.
  • NOT MEASURED: 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

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
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling labels Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 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): node scripts/docs-audit/affected-docs.mjs --json ece5faacbe5ebf3cad45114aff5f0b2f440f5cb1 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 2c4dae7b17020061ff1cbbd0031ae929c7ff8916 — the merge of head 14f1ec09dd277c4d8b4b64955aeeadc1c0f740c9 into base ece5faacbe5ebf3cad45114aff5f0b2f440f5cb1, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-warren · session_01JQwxbZUR3xvHyDPCjNL5P4

席位核对

ACCEPT · domain:server#5 · head 14f1ec09d · 2026-11-03T01:55Z · seat review of record at TIER_DEFAULT (not governed: check-governed-merges --pr 29689 NOT governed, 99 lines; Clause-②: no; no packages/spec path)

  1. Shape: draft, base main, body Fixes #29650. 4 files, +95/-4: one @objectstack/runtime patch changeset (declaration lines present), FLOW_CLONE_DROPPED_KEYS in packages/runtime/src/flow-clone.ts (one spread of Object.keys(OverrideDeclarationFields) plus docblock), a new describe in automation-flow-clone-custom-package.test.ts, QA case automation.package-flow-override revision 3 (step 10, acceptance[6], history row). Seat read the diff.
  2. Premise measured: holds on origin/main: FLOW_CLONE_DROPPED_KEYS named only the read decorations, the protection fields and protection, so a clone of a live override kept overrides: { package, basedOn } under its new name. OverrideDeclarationFields is the spec's own spread (packages/spec/src/kernel/override-declaration.zod.ts), so the key set is derived, not retyped. Dev red first: 2 failed / 17 passed at db4ce28f with the card's 403 sentence; green 38/38; ablation (spread removed) red 2/19, restored.
  3. Gates on this head: check-runs pinned to 14f1ec09: 49 total, 36 success, 13 skipped, 0 failed (Test Core 1–8/8, Type Check · workspace and consumer gates, Build Core green). Dev local: dispatch-gates --ran 63 derived / 62 run / 1 NOT MEASURED (check:dual-build-cjs-loads, covered by CI build), pnpm lint 0, runtime 8514 passed.
  4. Accepted deviations: none. The copy carries what getFlow serves (the override's body with a live override), as the card's Wanted asks; the PR states it.
  5. Out of scope: none found. Skills-docs: n/a (Metadata-protocol: none).
  6. Landing: not governed, under 3000 lines ⇒ the seat lands it (pr_ready + automerge_enable). At MERGED: landing record on automation: New similar on a flow with a live override answers 403 NOT_OVERRIDABLE, because the copy keeps the source's overrides block #29650 (closed by Fixes), pm:dispatched removed.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 3, 2026 01:56
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 3, 2026 01:56
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit c9636f1 Nov 3, 2026
50 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-yzu3d4 branch November 3, 2026 02:13
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/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

automation: New similar on a flow with a live override answers 403 NOT_OVERRIDABLE, because the copy keeps the source's overrides block

1 participant