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
Commit 52eb6b1
Browse filesBrowse the repository at this point in the historyBrowse files
feat(runtime)!: the flow clone door is "new similar" — the copy lands switched off in a writable package, by default the source's paired `<base>.custom`, never package-less (#28149)
9
+
10
+
Clause-②: yes (narrowing) — `POST /api/v1/automation/:name/clone` accepts a new optional `package` member and answers a new `packageId` member (the copy's package, beside `flow` and `notice`), and a request it accepted before is now refused: a clone of a flow no read-only package ships, sent with no `package`, is refused `400` where it was written package-less.
11
+
Metadata-protocol: added — `CloneFlowRequestSchema` and `CloneFlowResponseSchema` are new authorable API schemas in `@objectstack/spec/api` (the `authorable-surface/api.json` shard gains them), and the request's `package` member is a new key a caller can write. No existing key is removed, refused or changed in meaning.
12
+
13
+
<!-- adr-0087: not-required (no-migration-prescription) No key of any existing schema is removed, renamed or re-shaped and no stored metadata changes: the door's request body gains one optional member and its answer one, and a copy written before this change stays where it is. The remedy for the newly refused request is the request's own (name the writable package with `package`), so there is nothing for a migration to rewrite and no tombstone. -->
14
+
15
+
**BREAKING** request narrowing at the flow clone door (`@objectstack/runtime`'s `/automation` domain), per ADR-0146 D3/D4.
16
+
17
+
-**Before.** The clone door wrote the copy as a package-less environment row with `status: 'draft'`, which the engine arms like `active`, so a copy of a record-triggered flow ran beside its source as soon as it was saved.
18
+
-**Now.** The copy lands in a writable package and is written switched off (`status: 'obsolete'`, the value Studio's Enabled switch writes for Off). With no `package`, the target is the source package's paired customization package `<base>.custom`, ensured through the protocol's ensure door (the one creation path of the pair). With `package`, the target must be installed and writable (`422 WRITABLE_PACKAGE_REQUIRED` otherwise) and depend on the source's package (`409 RESOURCE_CONFLICT` otherwise). Every refusal is answered before anything is written. The save is a first write, so a name the target package already holds is refused `409 RESOURCE_CONFLICT` instead of overwriting it. The answer is `{ flow, packageId, notice }`, and the notice says the copy is switched off.
19
+
-**Unchanged.** The whole-definition copy, the mandatory new name and label, the credential refusal, the walled-environment refusal and the absence of clone lineage.
20
+
-**New surface.**`CloneFlowRequestSchema` and `CloneFlowResponseSchema` (`@objectstack/spec/api`), the SDK's `client.automation.clone(name, { name, label, package? })`, and the route ledger row now `sdk` with both schemas named. Setup's "Open the clone in Studio" link opens the copy in the package the answer names.
21
+
-**Who sees it.** A client that clones a flow authored in the deployment (or in a writable package) without naming a package; and anything that read the copy's `status` as `draft`.
22
+
23
+
**The remedy.** Name the writable package the copy lands in with `package`. To have the copy run, switch it on (`POST /api/v1/automation/:name/toggle`, body `{enabled: true}`).
Copy file name to clipboardExpand all lines: content/docs/concepts/metadata-lifecycle.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -178,7 +178,7 @@ See [ADR-0005](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/
178
178
179
179
| Type | Sanctioned path the refusal names |
180
180
| :--- | :--- |
181
-
|`flow`| Clone it under a new name (`POST /api/v1/automation/:name/clone`, body `{name, label}`), or switch it off (`POST /api/v1/automation/:name/toggle`, body `{enabled: false}`). A flow that holds a credential (an inbound hook's `secret`, an `http` node's `signingSecret`) is not cloned in one step: the clone door refuses it with `409`, because a copy would share the secret, and the copy is authored as a new flow with its own. |
181
+
|`flow`| Clone it under a new name (`POST /api/v1/automation/:name/clone`, body `{name, label}`), or switch it off (`POST /api/v1/automation/:name/toggle`, body `{enabled: false}`). A flow that holds a credential (an inbound hook's `secret`, an `http` node's `signingSecret`) is not cloned in one step: the clone door refuses it with `409`, because a copy would share the secret, and the copy is authored as a new flow with its own. The copy is written switched off, into the source package's paired customization package `<base>.custom` (created on first use), or into the writable package the body's optional `package` names; a flow no read-only package ships has no pair, so its clone names `package`. The answer's `packageId` says where the copy landed ([ADR-0146](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md) D3, D4). |
182
182
|`action`| Switch it off (`POST /api/v1/actions/_activation/:object/:action`, body `{enabled: false}`; `:object` is `global` for an object-less action). No clone is offered. |
183
183
|`permission`| Clone it under a new name: the "Clone" action on the permission set, or the clone door `POST /api/v1/security/permission-sets/:name/clone` (body `{name, label, package?}`) with a new name. The clone door copies the whole definition from the security catalog; a copy of a packaged set lands in the package's paired `<X>.custom` (ADR-0146 D3/D6). |
@@ -99,6 +100,64 @@ const result = AutomationApiErrorCode.parse(data);
99
100
|**runId**|`string`| ✅ | Execution run ID |
100
101
101
102
103
+
---
104
+
105
+
## CloneFlowRequest
106
+
107
+
### Properties
108
+
109
+
| Property | Type | Required | Description |
110
+
| :--- | :--- | :--- | :--- |
111
+
|**name**|`string`| ✅ | The copy's machine name — mandatory, and a name no flow uses (ADR-0126 §7.1): the automation engine keys flows by bare name, so a taken name is refused 409 `RESOURCE_CONFLICT`. |
112
+
|**label**|`string`| ✅ | The copy's display name — mandatory, so the copy and its source are told apart in every list. |
113
+
|**package**|`string`| optional | Id of the package the copy lands in. Given, it must be an installed, writable package whose dependency closure holds the source's package: an unknown or read-only one is refused 422 `WRITABLE_PACKAGE_REQUIRED`, one that declares no dependency path to the source's package 409 `RESOURCE_CONFLICT`, and nothing is written. Omitted, the copy lands in the source's paired customization package `<base>.custom`, created on first use; a source no read-only package ships has no pair, and the request is refused 400 `VALIDATION_FAILED` located on this member. |
114
+
115
+
116
+
---
117
+
118
+
## CloneFlowResponse
119
+
120
+
### Properties
121
+
122
+
| Property | Type | Required | Description |
123
+
| :--- | :--- | :--- | :--- |
124
+
|**success**|`boolean`| ✅ | Operation success status |
|**code**|`Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>`| ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) |
134
+
|**declaredCode**|`string`| optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) |
|**userMessage**|`string`| optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. |
137
+
|**refusal**|`true`| optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. |
|**requestId**|`string`| optional | Request ID for tracking |
142
+
143
+
### Nested Shape: `CloneFlowResponse.meta`
144
+
145
+
| Property | Type | Required | Description |
146
+
| :--- | :--- | :--- | :--- |
147
+
|**timestamp**|`string`| ✅ ||
148
+
|**duration**|`integer`| optional | Server-side processing duration in milliseconds |
149
+
|**requestId**|`string`| optional ||
150
+
|**traceId**|`string`| optional ||
151
+
152
+
### Nested Shape: `CloneFlowResponse.data`
153
+
154
+
| Property | Type | Required | Description |
155
+
| :--- | :--- | :--- | :--- |
156
+
|**flow**|`{ name: string; label: string; description?: string; successMessage?: string; … }`| ✅ | The copy as stored: switched off, in `packageId`, with no lineage. |
157
+
|**packageId**|`string`| ✅ | The package the copy landed in: the one the request named, else the source's paired customization package `<base>.custom`. |
158
+
|**notice**|`string`| ✅ | One line for the administrator, in the server's words: the copy is switched off until someone switches it on, and its references are not re-pointed. |
|[Automation Protocol](/docs/references/automation)| 15 | 77 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
26
26
|[Data Protocol](/docs/references/data)| 31 | 174 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
0 commit comments