Repository navigation
feat(runtime)!: the flow clone door lands the copy switched off in a writable package, by default the paired <base>.custom, never package-less - #28621
Conversation
…tom, switched off (WIP) Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pRVPsxnFxPBAQLUMh
…s land in a package Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pRVPsxnFxPBAQLUMh
…tup link, docs, changeset Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pRVPsxnFxPBAQLUMh
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pRVPsxnFxPBAQLUMh
…declare the target helper to route-envelope Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pRVPsxnFxPBAQLUMh
…jectstack/lint/runtime to source in runtime tests Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pRVPsxnFxPBAQLUMh
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pRVPsxnFxPBAQLUMh
…rged tree Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pRVPsxnFxPBAQLUMh
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pRVPsxnFxPBAQLUMh
📓 Docs Drift CheckThis PR changes 3 package(s): 24 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 177 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 e3988a17151edfb49ba4cd97a1263b0464629647 && git checkout e3988a17151edfb49ba4cd97a1263b0464629647
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6e11a929e453e1f9a78a9eb7b311c46b2fa8d094 387e88ca7346efc317c9e5993b3ebe0b9170f375 && git checkout -B drift-repro 6e11a929e453e1f9a78a9eb7b311c46b2fa8d094 && git merge --no-ff 387e88ca7346efc317c9e5993b3ebe0b9170f375
node scripts/docs-audit/affected-docs.mjs --json 6e11a929e453e1f9a78a9eb7b311c46b2fa8d094
|
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pRVPsxnFxPBAQLUMh
|
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo Contract reviewServed-tier: Faces owed and served in this one record: the schema face ( ① Derived judgmentsThe accept set and public face the diff moves, each judged:
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS Generated by Claude Code |
…opped-refinements header counts its new entry Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pRVPsxnFxPBAQLUMh
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pRVPsxnFxPBAQLUMh # Conflicts: # packages/runtime/src/domains/new-similar-target.ts
|
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo Contract reviewServed-tier: Faces owed and served in this one record: the schema face ( What moved since the previous record. This head is the merge of ① Derived judgmentsThe accept set and public face the diff moves, each judged:
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo 席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo
Requested by Leehom · project thread
Fixes #28149
Clause-②: yes (narrowing) —
POST /api/v1/automation/:name/cloneaccepts a new optionalpackagemember and answers a newpackageIdmember, and a request it accepted before is now refused: a clone of a flow no read-only package ships, sent with nopackage, is refused 400 where it was written package-less.Metadata-protocol: added —
CloneFlowRequestSchemaandCloneFlowResponseSchemaare new authorable API schemas in@objectstack/spec/api(theauthorable-surface/api.jsonshard gains them), and the request'spackagemember is a new key a caller can write. No existing key is removed, refused or changed in meaning.Skills-docs: none — no page under
skills/**describes the clone door. The one docs sentence that does (content/docs/concepts/metadata-lifecycle.mdx, the ADR-0126 sanctioned-path table) is updated in this diff: it says where the copy lands, that it is switched off, and whenpackageis needed.BREAKING request narrowing at the flow clone door (
@objectstack/runtime's/automationdomain), per ADR-0146 D3/D4. The claim declaredyes (widening)and asked for the narrowing arm if measured so: a request that answered 200 before is refused now, so the one arm is(narrowing).维护者速读
POST /automation/:name/clone)改为"新建相似":副本写进可写包,默认是源包的配对定制包<base>.custom(只经 N-13 的 ensure 门创建),也可用可选的package指定;副本写成关闭状态(status: 'obsolete'),响应用packageId说明落在哪个包;Setup 的"在 Studio 打开克隆"链接按这个包打开。draft在引擎里和active一样会运行,副本与原流程同时触发。package克隆现在返回 400;读取副本status为draft的调用方会看到obsolete。纯 revert 即可回滚,没有存量数据迁移。packages/runtime/src/domains/automation.ts的resolveCloneTarget,以及共享目标规则packages/runtime/src/domains/new-similar-target.ts(权限集克隆 runtime: the permission-set clone door lands "new similar" in a writable target package, by default the source package's <base>.custom, never package-less #28563 会复用同一文件)。pnpm --filter @objectstack/runtime exec vitest run --project local src/domains/automation-flow-clone-custom-package.test.ts(真实协议、注册表与 sqlite 存储,13 例)。What changes
CloneFlowRequestSchema({ name, label, package? }, closed) andCloneFlowResponseSchema(data: { flow, packageId, notice }) in@objectstack/spec/api, withCloneFlowRequest/CloneFlowRequestParsed/CloneFlowResponse/CloneFlowResponseParsed, added toAutomationApiContracts. Generated surfaces regenerated (api-surface, export-origins, declaration-map, authorable-surface, json-schema manifest, strictness-ledger counts, reference docs); OpenAPI regenerates from the same schemas at build.packagegiven → that package;protocol.packagedArtifactOwner({type:'flow', name})(the loader's fact, never the engine's_packageIdstamp) names the base, andprotocol.ensureCustomizationPackage({ basePackageId })returns the pair. That is the body ofPOST /packages/:id/ensure-customization, called in process, not re-implemented;VALIDATION_FAILEDlocated onpackage(code: 'required').domains/new-similar-target.ts, shared with the permission-set clone door): not installed → 422WRITABLE_PACKAGE_REQUIRED; read-only (isWritablePackage) → 422WRITABLE_PACKAGE_REQUIRED; writable but its dependency closure (resolvePackageClosure, the save door's own walk) does not hold the source's package → 409RESOURCE_CONFLICT. Each answer carriesdetails.packageIdanddetails.docs(ADR-0146), and nothing is written.packageId: <target>andparentVersion: null(a first write), so a name the target already holds answers the clone's taken-name 409 instead of overwriting the row.FLOW_CLONE_STATUSis'obsolete', and the notice says the copy is switched off.client.automation.clone(name, body); the route-ledger row moves fromserver-onlytosdkwith both schemas named.PackagedAutomationPage.tsxreadsdata.packageIdand links toartifactStudioPath(packageId, { type: 'flow', name }); no link when the answer names no package.metadata-lifecycle.mdx's flow row states the landing rules.Two judgement calls, for the review
status: 'obsolete', notdraft. The card says "draft", but the engine disables a flow only onobsolete/invalid, anddraftarms exactly likeactive(the pre-change notice already said "status: 'draft'is not an off-switch").obsoleteis the value Studio's Enabled switch writes for Off and the one Studio's new-flow skeleton uses. The red-first run read'draft'on the old door; the ablation that puts'draft'back reds 4 cases.DUPLICATE_SOURCE_NOT_A_BASEnames a bad SOURCE, and every refusal here is about the TARGET, so it is not reused. Unknown and read-only targets take the save door's ownWRITABLE_PACKAGE_REQUIRED; out-of-closure takes 409RESOURCE_CONFLICT, as the ensure door answers a base whose declared state rules its pair out.Also: if the ensure call succeeds and the save is then refused, the pair stays. That is the ensure door's idempotent result, and the next clone reuses it.
Tests
automation-flow-clone-custom-package.test.ts: real ObjectQL + sqlitesys_metadata+ the real protocol and its ensure door, packages base (system scope), dependent writable, unrelated writable, sealed. It covers: default lands in the pair (one row, bound to the pair, answer parsed byCloneFlowResponseSchema);obsoletestored, registered and answered; no base envelope; the original untouched; a second clone reuses the pair; an explicit dependent target; refusals (sealed 422, unknown 422, unrelated 409, the base itself 422) with nothing written; blankpackage400; a local source withoutpackage400required; a local source into a named package; and the route-ledger row (disposition, client, schemas, key set, an undeclared key refused). Red first on the base sources: 11 red, 2 green.automation-flow-clone,-credential,-door-package-address,automation-packaged-base-lock,automation-tenant-authored-write,automation-flow-save-actor; dogfoodautomation-flow-clone-doorexpectsobsoleteandcom.example.crm.custom; the Setup page test pins the link to the answer's package, plus a no-link control.target = owner→ 5 red;FLOW_CLONE_STATUS = 'draft'→ 4 red.Verification
At head
c3369fd2f5(main merged atd94dd3a65):@objectstack/runtimelocal + repo projects: 505/505 files, 9108 passed / 19 skipped (os-verify-lockVERDICT command-exit 0).@objectstack/client:tsc -p tsconfig.test.jsonclean, 53/53 files, 690 tests.@object-ui/app-shelltype-check could not resolve its sibling@object-ui/*packages in this container (no ui build), so it is left to CI's Type Check UI; no error names the two touched files.dispatch-gates --commands(121 gates): the first pass flagged stale generated spec artifacts, a missing...Parsedalias, an un-aliased@objectstack/lint/runtimeimport in runtime tests, an issue id in a route-ledger note, and the new domain file missing fromcheck-route-envelope. All fixed in this head. Build-dependent gates and the full dogfood battery are re-run at this head, and their readings are in the os-dev-report on the card.set-state-in-effectwarning inPackagedAutomationPage.tsx, not on a changed line).🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5pRVPsxnFxPBAQLUMh