You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
automation clone: a clone refused 400 for an invalid name has already installed the source package's empty .custom pair #29016
Filing gate: ① a product defect, found beside the clauses (automation.packaged-flow-clone-contract), reach: POST /api/v1/automation/:name/clone, measured twice.
Business reason: a refused write should change nothing. Today an admin who tries to copy a packaged flow under an illegal name gets 400 and, as a side effect, a newly installed empty BASE.custom package that shows up in the package list and Studio. Low impact (the pair is the one Studio's New would ensure anyway, and it is empty), but it breaks the "a refusal leaves no state" rule the item and the domain both state.
Reader who acts: the triage seat, then the domain:server lane, packages/runtime/src/domains/automation.ts. QA suggestion only: area:automation, p3.
Duplicate search: resolveCloneTarget → 0 hits; clone custom package invalid name → 112 hits, all QA run records or #28410 (closed, the override write gate), none about this.
Batch-of: release-18.3.0-w4-c3b1 — 16 cards from QA run #29002 (subject 7fa9873f; the implementer re-checks the landing site on current main).
Reproduction
Fresh boot with an empty file DB, sign in admin. GET /api/v1/packages lists no com.example.showcase.custom.
POST /api/v1/automation/showcase_urgent_task_alert/clone {"name":"Bad-Name!","label":"Illegal"} → 400 VALIDATION_FAILED "Invalid flow definition: name: … must match pattern /^[a-z_][a-z0-9_]*$/".
GET /api/v1/packages → com.example.showcase.custom is now installed.
Expected: nothing installed. Actual: an installed, empty pair package. Reproduced 2 times (decisive on a fresh DB, before and after).
Mechanism (suspected)
The clone arm in packages/runtime/src/domains/automation.ts calls resolveCloneTarget (which ensures BASE.custom) before registerAndSaveFlow runs the FlowSchema name check. Validating the clone body before ensuring the target would close it.
QA-source: #29002 · automation.packaged-flow-clone-contract · observed beside the clauses
Read fresh. The clone arm in packages/runtime/src/domains/automation.ts calls resolveCloneTarget, which installs BASE.custom, before registerAndSaveFlow validates the name, so domain:server. A refused write that leaves an installed package behind is a user-visible defect, p2 (QA suggested p3 for low impact). The item's run FAIL was a stale-spec clause, and this card was seen beside it, so the release rule does not apply.
Claim: PM loop round 1 · card thread · 2026-11-02T08:20Z
Session: session_01ADHkmQKwKFCp1m6Pb6Q8c1 (the seat); the card thread is named in a follow-up claim once it reports its branch
Account: os-steve (the seat's linked user; the card's assignee)
Branch: pending — the card thread's harness-pinned branch, written into a follow-up claim before the dev starts
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#5
Ruling-ref: no maintainer ruling on this card; triage grade p2 (6571514688).
File surface: packages/runtime/src/domains/automation.ts, the clone arm only (resolveCloneTarget and its caller; validation of the clone body before the target package is ensured), its tests, changeset. ⛔ No packages/spec/**; ⛔ not the trigger routes or the connector-connection door of the same file.
Container & model: S, mode:thread, model: the thread's default judgement tier, effort: medium
Clause-②: no
Metadata-protocol: none
Responsibility: the code is the runtime flow clone door · a clone refused for an invalid name has already installed the empty paired .custom package · reached by any admin cloning a packaged flow under an illegal name (Studio, REST, MCP)
Thread-read: 6571514688
Premise: holds (read at origin/maina4015ab47b: the clone arm calls resolveCloneTarget before registerAndSaveFlow runs the FlowSchema name check)
Serial constraints cleared: same file as this seat's #29050 (trigger routes / admission, other region) and seat 2's open PR #29097 (connector-connection door, other region); region-disjoint; the dev merges origin/main before opening the PR and the second to land resolves.
Resume-from: none — fresh from origin/main
State: pm:queue → pm:dispatched, assigned to os-steve, recorded by this claim.
Claim: PM loop round 1 · card thread · branch named · 2026-11-02T08:20Z (supersedes 6572425707, which had the branch pending)
Session: session_01ADHkmQKwKFCp1m6Pb6Q8c1 (the seat); the card thread is session_01ADHkmPX8EnvNFVzRVU5NHx
Account: os-steve (the seat's linked user; the card's assignee)
Branch: claude/project-thread-tc6dy0 (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#5
Ruling-ref: no maintainer ruling on this card; triage grade p2 (6571514688).
File surface: packages/runtime/src/domains/automation.ts, the clone arm only (resolveCloneTarget and its caller; validation of the clone body before the target package is ensured), its tests, changeset. ⛔ No packages/spec/**; ⛔ not the trigger routes or the connector-connection door of the same file.
Container & model: S, mode:thread, model: the thread's default judgement tier, effort: medium
Clause-②: no
Metadata-protocol: none
Responsibility: the code is the runtime flow clone door · a clone refused for an invalid name has already installed the empty paired .custom package · reached by any admin cloning a packaged flow under an illegal name (Studio, REST, MCP)
Thread-read: 6572425707
Premise: holds (read at origin/maina4015ab47b: the clone arm calls resolveCloneTarget before registerAndSaveFlow runs the FlowSchema name check)
Serial constraints cleared: same file as this seat's #29050 (trigger routes / admission, other region) and seat 2's open PR #29097 (connector-connection door, other region); region-disjoint; the dev merges origin/main before opening the PR and the second to land resolves.
Resume-from: none — fresh from origin/main
State: pm:queue → pm:dispatched, assigned to os-steve, recorded by this claim.
{
"issue": 29016,
"status": "done",
"branch": "claude/project-thread-tc6dy0",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29193",
"session": "session_01ADHkmPX8EnvNFVzRVU5NHx",
"premise_still_valid": true,
"summary": "The clone arm ensured the default BASE.custom pair in resolveCloneTarget before registerFlow ran its strict FlowSchema parse, so a 400 for an illegal name left an installed empty pair. resolveCloneTarget now judges everything that needs no pair up front and returns a land step; registerAndSaveFlow calls land after the engine accepts the copy and before the save, undoing the registration if landing refuses or throws. The engine stays the single validation of the definition; the authoring-rule refusal also now precedes the ensure.",
"tests": "head 3de5e31ba: build runtime^... then vitest run src/domains/automation under os-verify-lock: Test Files 42 passed, Tests 766 passed, VERDICT command-exit 0. New describe [#29016] (control + pin) in automation-flow-clone-custom-package.test.ts; no existing test edited. Ablation via scripts/ablation-replace.mjs (eager ensure re-inserted, anchor 1->0, marker grep 1): pin red 'expected [com.acme.crm, ...(4)] to deeply equal [Array(4)] + com.acme.crm.custom', Tests 1 failed | 16 passed (17); restored, blob == HEAD, git diff HEAD empty. Runtime tests import src relatively, so no dist leg.",
"gates": "dispatch-gates --commands: 61 derived; 60 run exit 0 (check-plugin-teardown-shape --self-test first exit 3 on shallow fixture, green after fetching 621a4876). check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs full workspace build): NOT MEASURED locally, CI is the reading. --ran reconcile: 61 derived, 60 run, 1 UNRUN (that one). pnpm --filter @objectstack/runtime typecheck VERDICT 0. eslint --no-inline-config on the two touched runtime files exit 0. CI check-runs: in_progress at report time.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "3 — issue_read get, issue_read get_comments, pull_request_read get (reads only)",
"api_writes": "2 — pr_create via fleet-write relay (#29193, 5596 bytes sent = stored), this os-dev-report comment via post-stamped.mjs",
"files_changed": [
"packages/runtime/src/domains/automation.ts",
"packages/runtime/src/domains/automation-flow-clone-custom-package.test.ts",
".changeset/29016-clone-refusal-no-pair.md"
],
"line_budget": "+131/-37 across 3 files, not governed",
"deviations": [
"check:dual-build-cjs-loads not run locally (prerequisite: full build), declared",
"lint run as eslint --no-inline-config on the touched files rather than pnpm lint repo-wide"
],
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: POST /api/v1/automation/:name/clone with package omitted, when the store refuses the save after the pair was ensured (new name already stored in the pair but unbound, or a store fault) · evidence: registerAndSaveFlow save catch restores only the engine registration; ensureCustomizationPackage has no undo · noted in PR Acceptance notes, not filed (needs a store probe before ensure or a pair rollback) · dedupe words: clone save refused custom pair left installed",
"carrier: none · the ensured pair judged not writable right after its own ensure is reachable only if the ensure door answers an unwritable pair; noted, not filed"
]
}
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: automation.packaged-flow-clone-contract
Triage:
domain:server· p2 ·pm:queue· 2026-11-02T07:23Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1
Claim: PM loop round 1 · card thread · 2026-11-02T08:20Z
Session:
session_01ADHkmQKwKFCp1m6Pb6Q8c1(the seat); the card thread is named in a follow-up claim once it reports its branchAccount:
os-steve(the seat's linked user; the card's assignee)Branch: pending — the card thread's harness-pinned branch, written into a follow-up claim before the dev starts
Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#5Ruling-ref: no maintainer ruling on this card; triage grade p2 (6571514688).
File surface:
packages/runtime/src/domains/automation.ts, the clone arm only (resolveCloneTargetand its caller; validation of the clone body before the target package is ensured), its tests, changeset. ⛔ Nopackages/spec/**; ⛔ not the trigger routes or the connector-connection door of the same file.Container & model:
S,mode:thread, model: the thread's default judgement tier,effort: mediumClause-②: no
Metadata-protocol: none
Responsibility: the code is the runtime flow clone door · a clone refused for an invalid name has already installed the empty paired
.custompackage · reached by any admin cloning a packaged flow under an illegal name (Studio, REST, MCP)Thread-read: 6571514688
Premise: holds (read at
origin/maina4015ab47b: the clone arm callsresolveCloneTargetbeforeregisterAndSaveFlowruns the FlowSchema name check)Serial constraints cleared: same file as this seat's #29050 (trigger routes / admission, other region) and seat 2's open PR #29097 (connector-connection door, other region); region-disjoint; the dev merges
origin/mainbefore opening the PR and the second to land resolves.Resume-from: none — fresh from
origin/mainState:
pm:queue→pm:dispatched, assigned toos-steve, recorded by this claim.objectstack-fleet commentedon Nov 2, 2026
domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1
Claim: PM loop round 1 · card thread · branch named · 2026-11-02T08:20Z (supersedes 6572425707, which had the branch pending)
Session:
session_01ADHkmQKwKFCp1m6Pb6Q8c1(the seat); the card thread issession_01ADHkmPX8EnvNFVzRVU5NHxAccount:
os-steve(the seat's linked user; the card's assignee)Branch:
claude/project-thread-tc6dy0(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#5Ruling-ref: no maintainer ruling on this card; triage grade p2 (6571514688).
File surface:
packages/runtime/src/domains/automation.ts, the clone arm only (resolveCloneTargetand its caller; validation of the clone body before the target package is ensured), its tests, changeset. ⛔ Nopackages/spec/**; ⛔ not the trigger routes or the connector-connection door of the same file.Container & model:
S,mode:thread, model: the thread's default judgement tier,effort: mediumClause-②: no
Metadata-protocol: none
Responsibility: the code is the runtime flow clone door · a clone refused for an invalid name has already installed the empty paired
.custompackage · reached by any admin cloning a packaged flow under an illegal name (Studio, REST, MCP)Thread-read: 6572425707
Premise: holds (read at
origin/maina4015ab47b: the clone arm callsresolveCloneTargetbeforeregisterAndSaveFlowruns the FlowSchema name check)Serial constraints cleared: same file as this seat's #29050 (trigger routes / admission, other region) and seat 2's open PR #29097 (connector-connection door, other region); region-disjoint; the dev merges
origin/mainbefore opening the PR and the second to land resolves.Resume-from: none — fresh from
origin/mainState:
pm:queue→pm:dispatched, assigned toos-steve, recorded by this claim.objectstack-fleet commentedon Nov 2, 2026
domain:server#5 · os-steve · session_01ADHkmPX8EnvNFVzRVU5NHx
os-dev-report
{ "issue": 29016, "status": "done", "branch": "claude/project-thread-tc6dy0", "pr": "https://github.com/objectstack-ai/objectstack/pull/29193", "session": "session_01ADHkmPX8EnvNFVzRVU5NHx", "premise_still_valid": true, "summary": "The clone arm ensured the default BASE.custom pair in resolveCloneTarget before registerFlow ran its strict FlowSchema parse, so a 400 for an illegal name left an installed empty pair. resolveCloneTarget now judges everything that needs no pair up front and returns a land step; registerAndSaveFlow calls land after the engine accepts the copy and before the save, undoing the registration if landing refuses or throws. The engine stays the single validation of the definition; the authoring-rule refusal also now precedes the ensure.", "tests": "head 3de5e31ba: build runtime^... then vitest run src/domains/automation under os-verify-lock: Test Files 42 passed, Tests 766 passed, VERDICT command-exit 0. New describe [#29016] (control + pin) in automation-flow-clone-custom-package.test.ts; no existing test edited. Ablation via scripts/ablation-replace.mjs (eager ensure re-inserted, anchor 1->0, marker grep 1): pin red 'expected [com.acme.crm, ...(4)] to deeply equal [Array(4)] + com.acme.crm.custom', Tests 1 failed | 16 passed (17); restored, blob == HEAD, git diff HEAD empty. Runtime tests import src relatively, so no dist leg.", "gates": "dispatch-gates --commands: 61 derived; 60 run exit 0 (check-plugin-teardown-shape --self-test first exit 3 on shallow fixture, green after fetching 621a4876). check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs full workspace build): NOT MEASURED locally, CI is the reading. --ran reconcile: 61 derived, 60 run, 1 UNRUN (that one). pnpm --filter @objectstack/runtime typecheck VERDICT 0. eslint --no-inline-config on the two touched runtime files exit 0. CI check-runs: in_progress at report time.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "3 — issue_read get, issue_read get_comments, pull_request_read get (reads only)", "api_writes": "2 — pr_create via fleet-write relay (#29193, 5596 bytes sent = stored), this os-dev-report comment via post-stamped.mjs", "files_changed": [ "packages/runtime/src/domains/automation.ts", "packages/runtime/src/domains/automation-flow-clone-custom-package.test.ts", ".changeset/29016-clone-refusal-no-pair.md" ], "line_budget": "+131/-37 across 3 files, not governed", "deviations": [ "check:dual-build-cjs-loads not run locally (prerequisite: full build), declared", "lint run as eslint --no-inline-config on the touched files rather than pnpm lint repo-wide" ], "open_questions": [], "out_of_scope_findings": [ "class: a · reach: POST /api/v1/automation/:name/clone with package omitted, when the store refuses the save after the pair was ensured (new name already stored in the pair but unbound, or a store fault) · evidence: registerAndSaveFlow save catch restores only the engine registration; ensureCustomizationPackage has no undo · noted in PR Acceptance notes, not filed (needs a store probe before ensure or a pair rollback) · dedupe words: clone save refused custom pair left installed", "carrier: none · the ensured pair judged not writable right after its own ensure is reachable only if the ensure door answers an unwritable pair; noted, not filed" ] }Stamped 2026-11-02T09:08Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1
Landed · #29016 · 2026-11-02T10:30Z
f6a04e3af7(head3de5e31b, ACCEPT 6573306622)completedby the PR'sFixeslineState:
pm:dispatchedremoved;domain:server,area:automationand the priority label stay. Nothing remains on this card.