Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .changeset/28149-flow-clone-lands-in-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'@objectstack/spec': minor
'@objectstack/runtime': patch
'@objectstack/client': patch
'@object-ui/app-shell': patch
---

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)

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

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

**BREAKING** request narrowing at the flow clone door (`@objectstack/runtime`'s `/automation` domain), per ADR-0146 D3/D4.

- **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.
- **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.
- **Unchanged.** The whole-definition copy, the mandatory new name and label, the credential refusal, the walled-environment refusal and the absence of clone lineage.
- **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.
- **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`.

**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}`).
2 changes: 1 addition & 1 deletion content/docs/concepts/metadata-lifecycle.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ See [ADR-0005](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/

| Type | Sanctioned path the refusal names |
| :--- | :--- |
| `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. |
| `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). |
| `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. |
| `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). |

Expand Down
63 changes: 61 additions & 2 deletions content/docs/references/api/automation-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ PUT /api/v1/automation/:name — Update flow
DELETE /api/v1/automation/:name — Delete flow
POST /api/v1/automation/:name/trigger — Trigger flow execution
POST /api/v1/automation/:name/toggle — Enable/disable a packaged flow
POST /api/v1/automation/:name/clone — Copy a flow, switched off, into a package
GET /api/v1/automation/:name/runs — List execution runs
GET /api/v1/automation/:name/runs/:runId — Get single execution run
```
Expand All @@ -52,8 +53,8 @@ GET /api/v1/automation/:name/runs/:runId — Get single execution run
## TypeScript Usage

```typescript
import { AutomationApiErrorCode, AutomationFlowPathParamsSchema, AutomationRunPathParamsSchema, CreateFlowRequestSchema, CreateFlowResponseSchema, DeleteFlowRequestSchema, DeleteFlowResponseSchema, FlowFailureDetailsSchema, FlowInnerRefusalSchema, GetFlowRequestSchema, GetFlowResponseSchema, GetRunRequestSchema, GetRunResponseSchema, ListRunsRequestSchema, ListRunsResponseSchema, ResumeFailureDetailsSchema, ToggleFlowRequestSchema, ToggleFlowResponseSchema, TriggerFlowRequestSchema, TriggerFlowResponseSchema, UpdateFlowRequestSchema, UpdateFlowResponseSchema } from '@objectstack/spec/api';
import type { AutomationApiErrorCode, AutomationFlowPathParams, AutomationRunPathParams, CreateFlowRequest, CreateFlowResponse, DeleteFlowRequest, DeleteFlowResponse, FlowFailureDetails, FlowInnerRefusal, GetFlowRequest, GetFlowResponse, GetRunRequest, GetRunResponse, ListRunsRequest, ListRunsResponse, ResumeFailureDetails, ToggleFlowRequest, ToggleFlowResponse, TriggerFlowRequest, TriggerFlowResponse, UpdateFlowRequest, UpdateFlowResponse } from '@objectstack/spec/api';
import { AutomationApiErrorCode, AutomationFlowPathParamsSchema, AutomationRunPathParamsSchema, CloneFlowRequestSchema, CloneFlowResponseSchema, CreateFlowRequestSchema, CreateFlowResponseSchema, DeleteFlowRequestSchema, DeleteFlowResponseSchema, FlowFailureDetailsSchema, FlowInnerRefusalSchema, GetFlowRequestSchema, GetFlowResponseSchema, GetRunRequestSchema, GetRunResponseSchema, ListRunsRequestSchema, ListRunsResponseSchema, ResumeFailureDetailsSchema, ToggleFlowRequestSchema, ToggleFlowResponseSchema, TriggerFlowRequestSchema, TriggerFlowResponseSchema, UpdateFlowRequestSchema, UpdateFlowResponseSchema } from '@objectstack/spec/api';
import type { AutomationApiErrorCode, AutomationFlowPathParams, AutomationRunPathParams, CloneFlowRequest, CloneFlowResponse, CreateFlowRequest, CreateFlowResponse, DeleteFlowRequest, DeleteFlowResponse, FlowFailureDetails, FlowInnerRefusal, GetFlowRequest, GetFlowResponse, GetRunRequest, GetRunResponse, ListRunsRequest, ListRunsResponse, ResumeFailureDetails, ToggleFlowRequest, ToggleFlowResponse, TriggerFlowRequest, TriggerFlowResponse, UpdateFlowRequest, UpdateFlowResponse } from '@objectstack/spec/api';

// Validate data
const result = AutomationApiErrorCode.parse(data);
Expand Down Expand Up @@ -99,6 +100,64 @@ const result = AutomationApiErrorCode.parse(data);
| **runId** | `string` | ✅ | Execution run ID |


---

## CloneFlowRequest

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **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`. |
| **label** | `string` | ✅ | The copy's display name — mandatory, so the copy and its source are told apart in every list. |
| **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. |


---

## CloneFlowResponse

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ flow: object; packageId: string; notice: string }` | ✅ | |

### Nested Shape: `CloneFlowResponse.error`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **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) |
| **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) |
| **message** | `string` | ✅ | Readable error message |
| **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`. |
| **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. |
| **category** | `string` | optional | Error category (e.g. validation, authorization) |
| **httpStatus** | `integer` | optional | HTTP status of the response carrying this error |
| **details** | `any` | optional | Additional error context (e.g. field validation errors) |
| **requestId** | `string` | optional | Request ID for tracking |

### Nested Shape: `CloneFlowResponse.meta`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **timestamp** | `string` | ✅ | |
| **duration** | `integer` | optional | Server-side processing duration in milliseconds |
| **requestId** | `string` | optional | |
| **traceId** | `string` | optional | |

### Nested Shape: `CloneFlowResponse.data`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **flow** | `{ name: string; label: string; description?: string; successMessage?: string; … }` | ✅ | The copy as stored: switched off, in `packageId`, with no lineage. |
| **packageId** | `string` | ✅ | The package the copy landed in: the one the request named, else the source's paired customization package `<base>.custom`. |
| **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. |


---

## CreateFlowRequest
Expand Down
10 changes: 5 additions & 5 deletions content/docs/references/index.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Protocol reference — every schema by module
navTitle: Protocol Reference
description: Every schema published by @objectstack/spec — 1582 schemas across 14 protocol modules
description: Every schema published by @objectstack/spec — 1584 schemas across 14 protocol modules
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand All @@ -21,7 +21,7 @@ counts are sums of the rows they head. Regenerate with
| Module | Pages | Schemas | Description |
| :--- | ---: | ---: | :--- |
| [AI Protocol](/docs/references/ai) | 12 | 68 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. |
| [API Protocol](/docs/references/api) | 34 | 458 | REST contracts, endpoints, routing, realtime, batch, discovery. |
| [API Protocol](/docs/references/api) | 34 | 460 | REST contracts, endpoints, routing, realtime, batch, discovery. |
| [Automation Protocol](/docs/references/automation) | 15 | 77 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
| [Data Protocol](/docs/references/data) | 31 | 174 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
| [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. |
Expand All @@ -34,7 +34,7 @@ counts are sums of the rows they head. Regenerate with
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
| [System Protocol](/docs/references/system) | 37 | 287 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
| [UI Protocol](/docs/references/ui) | 16 | 175 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
| **Total** | **205** | **1582** | 14 protocol modules |
| **Total** | **205** | **1584** | 14 protocol modules |

---

Expand Down Expand Up @@ -63,7 +63,7 @@ Agents, tools, skills, RAG and knowledge sources, model registry, conversations.

## API Protocol

**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **34 pages, 458 schemas**
**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **34 pages, 460 schemas**

REST contracts, endpoints, routing, realtime, batch, discovery.

Expand All @@ -73,7 +73,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery.
| [`app-bundle.zod.ts`](/docs/references/api/app-bundle) | `AppMetadataBundleResponse` |
| [`auth.zod.ts`](/docs/references/api/auth) | `AuthProvider`, `LoginRequest`, `LoginType`, `RefreshTokenRequest`, `RegisterRequest`, `Session`, `SessionResponse`, `SessionUser`, `UserProfileResponse` |
| [`auth-endpoints.zod.ts`](/docs/references/api/auth-endpoints) | `AuthEndpoint`, `AuthFeaturesConfig`, `AuthProviderInfo`, `DeviceRequestResponse`, `DeviceTokenResponse`, `EmailPasswordConfigPublic`, `GetAuthConfigResponse` |
| [`automation-api.zod.ts`](/docs/references/api/automation-api) | `AutomationApiErrorCode`, `AutomationFlowPathParams`, `AutomationRunPathParams`, `CreateFlowRequest`, `CreateFlowResponse`, `DeleteFlowRequest`, `DeleteFlowResponse`, `FlowFailureDetails`, `FlowInnerRefusal`, `GetFlowRequest`, `GetFlowResponse`, `GetRunRequest`, `GetRunResponse`, `ListRunsRequest`, `ListRunsResponse`, `ResumeFailureDetails`, `ToggleFlowRequest`, `ToggleFlowResponse`, `TriggerFlowRequest`, `TriggerFlowResponse`, `UpdateFlowRequest`, `UpdateFlowResponse` |
| [`automation-api.zod.ts`](/docs/references/api/automation-api) | `AutomationApiErrorCode`, `AutomationFlowPathParams`, `AutomationRunPathParams`, `CloneFlowRequest`, `CloneFlowResponse`, `CreateFlowRequest`, `CreateFlowResponse`, `DeleteFlowRequest`, `DeleteFlowResponse`, `FlowFailureDetails`, `FlowInnerRefusal`, `GetFlowRequest`, `GetFlowResponse`, `GetRunRequest`, `GetRunResponse`, `ListRunsRequest`, `ListRunsResponse`, `ResumeFailureDetails`, `ToggleFlowRequest`, `ToggleFlowResponse`, `TriggerFlowRequest`, `TriggerFlowResponse`, `UpdateFlowRequest`, `UpdateFlowResponse` |
| [`batch.zod.ts`](/docs/references/api/batch) | `BatchConfig`, `BatchOperationResult`, `BatchOperationType`, `BatchOptions`, `BatchRecord`, `BatchUpdateRequest`, `BatchUpdateResponse`, `CrossObjectBatchDroppedFields`, `CrossObjectBatchOperation`, `CrossObjectBatchRequest`, `CrossObjectBatchResponse`, `DeleteManyRequest`, `UpdateManyRecord`, `UpdateManyRequest` |
| [`contract.zod.ts`](/docs/references/api/contract) | `ApiError`, `BaseResponse`, `BatchLoadingStrategy`, `BulkRequest`, `BulkResponse`, `CreateRequest`, `DataLoaderConfig`, `DeleteResponse`, `ExportRequest`, `IdRequest`, `ListRecordResponse`, `ModificationResult`, `QueryOptimizationConfig`, `RecordData`, `SingleRecordResponse`, `UpdateRequest` |
| [`discovery.zod.ts`](/docs/references/api/discovery) | `ApiRoutes`, `AuthFamilies`, `CapabilityDescriptor`, `Discovery`, `DiscoveryEnvironment`, `EnvironmentType`, `RouteHealthEntry`, `RouteHealthReport`, `ServiceInfo`, `ServiceSelfInfo`, `ServiceStatus`, `WellKnownCapabilities` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ hand-patch a number here** — fix the code or the verdict and regenerate.

| Dir | Sites |
|---|---|
| `api/` | 472 |
| `api/` | 474 |
Loading
Loading