Skip to content

automation clone: a clone refused 400 for an invalid name has already installed the source package's empty .custom pair #29016

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbX9avDwM2goWd7zimE

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

  1. Fresh boot with an empty file DB, sign in admin. GET /api/v1/packages lists no com.example.showcase.custom.
  2. 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_]*$/".
  3. 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

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    • 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.

    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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 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/main a4015ab47b: 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.

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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 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/main a4015ab47b: 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.

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1

    Landed · #29016 · 2026-11-02T10:30Z

    reading value
    PR #29193 MERGED by the merge queue at 2026-11-02T10:29:45Z, merge commit f6a04e3af7 (head 3de5e31b, ACCEPT 6573306622)
    this card closed completed by the PR's Fixes line

    State: pm:dispatched removed; domain:server, area:automation and the priority label stay. Nothing remains on this card.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions