Skip to content

Commit fa44dd2

Browse files
feat(spec): an api flow's start node may name its hook secret (config.secretRef), resolved per post (#25703)
Fixes #25675 Clause-②: yes (widening) Metadata-protocol: added Ruling-ref: 6386630545 An `api` flow's start node may now name its inbound hook secret instead of carrying it: `config.secretRef: 'NAME'`. The deployment fills that name, `os validate` and the engine accept the reference, and the engine resolves it at every inbound post. An app package can ship an inbound `api` flow without publishing a credential, and the repository's one literal hook secret (the showcase webhook flow) is gone. Ruling 6386630545 (batch #12 item 3, letter A), verbatim: > 「`api` 型流程的起始节点可以声明**密钥引用**(流程凭据名 / 环境变量名)而不是字面值;部署方填值,`os validate` 接受引用,引擎每次入站请求按引用解析,复用 #20790 已有的写入式流程凭据存储与 `resolveSecret`。公开仓库里不再出现任何凭据。」 ## What changes **`@objectstack/spec`: the key.** `packages/spec/src/automation/api-trigger-secret-ref.zod.ts` (new) declares `ApiTriggerSecretRefSchema`: a name of the shape `[A-Za-z_][A-Za-z0-9_]*`, never in the platform's `OS_` namespace. Both rules are patterns, so the published JSON Schema carries them too. `FlowSchema`'s superRefine runs `apiTriggerSecretRefRefusals` on every start node and refuses, at `nodes[i].config.secretRef`: - a reference that is not a name; - one beside a literal `secret`. Two sources for one credential, and the literal would publish it; - one on a flow that binds no inbound `api` trigger (`resolveFlowTriggerKind`). Nothing would ever read it. `apiTriggerSecretRefOf(config)` is the one reader of the name. The lint rule and the engine both call it, so neither restates the grammar. **`@objectstack/lint`: `flow-api-trigger-secret-missing` accepts a usable reference.** It accepts one at all three commands and at the runtime publish gate, with no restored-credential position needed. A malformed reference is still "no secret". The rule's one-line remedy and its `os explain` entry now name `config.secretRef`. **`@objectstack/service-automation`: resolution per post.** - `hookSecretResolver` gives a start node with a usable `secretRef` a reader. At every call the reader asks the credential resolver for that name. It is never cached and never asks the channel. A name that resolves to nothing, blank or whitespace rejects with `FlowCredentialUnresolvableError` (`SERVICE_UNAVAILABLE` / 503). The error names the flow and the reference, never a value. `trigger-api` already answers that rejection `503` and logs it, so an unfilled reference refuses every post and never admits one. - The resolver is the plugin's existing `credentialResolver`, the one a declarative connector's `auth.credentialRef` goes through (ADR-0097 §3). It moved to `credential-resolver.ts` (re-exported from `plugin.ts` unchanged) and is handed to the engine through the new `AutomationEngineOptions.credentialResolver`. The default is the open tier's environment variable. - The registration check passes on that reader. Its refusal text now names `config.secretRef` first. - The write-only flow credential channel gives the start-node position a fifth form, `referenced`. Saving a flow that names its secret removes any hook-secret row the channel held for that node, the way a cleared position does, so no stale credential stays at rest. `heldPaths` does not report it. **`@objectstack/trigger-api`**: the arm-time refusal and the docblock say a `secretRef` is resolved by the automation engine. The trigger never reads the name itself, so a host that binds without that engine still refuses the flow. **Showcase**: `showcase_inbound_task_webhook` now carries `secretRef: 'SHOWCASE_WEBHOOK_SECRET'`, and the literal `'showcase-webhook-secret'` and its "Demo secret" comment are removed. `git grep showcase-webhook-secret` (CHANGELOGs aside) finds 0 hits. The two platform-checklist items that signed with the old literal now export `SHOWCASE_WEBHOOK_SECRET` at boot and sign with it. **Docs**: `automation/flows.mdx` (the credential callout said a packaged flow keeps its literal in source), `automation/connectors.mdx` (the resolver now serves `secretRef` too), and the regenerated reference page. The `objectstack-automation` skill's start-node table (it said a non-blank `secret` is required) is corrected in #25708, a governed `skills/**` PR that merges after this one. Only `skills/objectstack-automation/references/_index.md` rides here: it is `gen:skill-refs` output that `check:skill-refs` requires beside the spec change, and the generated-surface exception (#11705) lifts it. ## Decisions taken (recorded for the reviewer) 1. **A name, resolved through the credential resolver, rather than a flow-credential-store name.** The ruling allows "流程凭据名 / 环境变量名". Measured on `origin/main`: the flow credential store is keyed by position (flow, node, key), not by a name. Its only write door is the metadata save door, and a packaged flow cannot be overlaid (`flow` has `allowEnvironmentOverlay: false`). So a deployment has no door to fill a store entry for a packaged flow, and a store-name form would need a new write surface. The connector `credentialRef` seam already resolves a name per deployment: an environment variable in the open tier, or a host-wired secrets service. Reusing that seam serves both arms of the ruling with one dialect. The binding's `resolveSecret` (#20790) is the per-post reader, as ruled. 2. **A distinct key, not a tagged `secret` value.** `secretRef` cannot be mistaken for the secret, and a literal beside it is refused. 3. **Not withheld on read.** The reference is a name, not a credential. Withholding it would break the GET → edit → PUT round trip for no protection. Only `config.secret` stays in the redaction table. 4. **`OS_` names are refused.** Whoever signs the posts holds the hook secret, so a hook secret that is a platform secret (`OS_AUTH_SECRET`) would hand it out. 5. **An unfilled reference does not block registration.** The deployment may fill it after deploy. Each post is refused `503` until then, with a located `warn` naming the flow and the reference. ## Acceptance notes - A stored flow row written before the #20790 migration still carries a literal `secret`. If an author edits it to `secretRef` through `/meta`, the carry-forward would restore that literal beside the reference, and `FlowSchema` then refuses the save. That is loud, not silent, and the boot migration moves such literals out first. Not changed here. ## Verification All readings are at head `8a23b3ad`, where `origin/main` `b77bb37e` is merged in. Test suites were last run at `1abed922` or earlier. The commits after that change only `llms.txt` counts (and the test-alias pin and docblock, which were re-run). - **Package suites** (full `test`, each through the verify lock): `@objectstack/spec` 0, `@objectstack/lint` 0, `@objectstack/trigger-api` 0, `@objectstack/service-automation` 0, `@objectstack/metadata-protocol` 0, `@objectstack/runtime` 0. - **`test:repo`**: `@objectstack/spec` 0 (79/79 files, 1269/1269 tests, after the docblock fix; the first run counted 32 demoted descriptions against the pinned 31), `@objectstack/runtime` 0, `@objectstack/rest` 0. - **Typecheck**: `spec`, `lint`, `service-automation` and `trigger-api` 0. `example-showcase` NOT MEASURED: ten TS2307 errors from workspace packages outside the build closure (`service-print`, `plugin-approvals`, `connector-rest`), none in a changed file. The showcase flow's new start node parses with `FlowSchema` against the rebuilt spec (`true`). - **Spec generated artifacts**: `pnpm --filter @objectstack/spec check:generated`, all 14 up to date after a build at the merged head. - **Derived gates** (`dispatch-gates --commands`, 128 families): 124 exit 0. Four exit 3, a refused prerequisite, NOT MEASURED: `check-plugin-teardown-shape --self-test` (shallow clone, needs `--unshallow`), `spec check:skill-examples` (`client-react` not built), `check:dual-build-cjs-loads` and `check:i18n-coverage` (the full workspace build). `--ran` reconciliation: 128 accounted, 124 run, 4 NOT-MEASURED, 0 UNRUN. - `check-platform-checklist --changed-from origin/main`: OK. `check:nul-bytes`: 0. - **At head `088e4423`** (the split, which only restores `SKILL.md` to `origin/main`): `dispatch-gates --commands` 125 derived, 121 exit 0, the same four NOT MEASURED, 0 UNRUN. Ablations on the two fail-closed branches (the unfilled-name `503` in `service-automation`, the beside-a-literal refusal in `spec`) each turned their pins red and were restored blob-identical (report addendum `6388889634` on #25675). - **Dogfood**: n/a. The title has no `!` and Clause-② has no (narrowing) arm. _Body edited by the `domain:spec` seat 2 after the governed split, as the dev proposed in `6388889634`: the skill row and its readings moved to #25708, which carries its own 维护者速读; this PR's draft 维护者速读 is dropped, since its diff no longer needs a human merge._ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01NZJKktPWaz2EZJ95a9XZa8 --- _Generated by [Claude Code](https://claude.ai/code/session_01NZJKktPWaz2EZJ95a9XZa8)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4bcc4df commit fa44dd2

35 files changed

Lines changed: 728 additions & 52 deletions
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/lint': minor
4+
'@objectstack/service-automation': minor
5+
'@objectstack/trigger-api': minor
6+
---
7+
8+
feat(spec): an `api` flow's start node may name its inbound hook secret (`config.secretRef`) instead of carrying it
9+
10+
An app package can now ship an inbound `api` flow without publishing its credential. The start node names the secret, the deployment fills that name, and the engine reads it at every post:
11+
12+
```ts
13+
{
14+
id: 'start',
15+
type: 'start',
16+
config: { triggerType: 'api', hookId: 'heartbeat', secretRef: 'SRM_HEARTBEAT_SECRET' },
17+
}
18+
```
19+
20+
- The name resolves through the automation plugin's `credentialResolver`, the one a declarative connector's `auth.credentialRef` already uses: in the open tier it is the environment variable of that name (`process.env.SRM_HEARTBEAT_SECRET`); a host that wires a secrets service serves it from there.
21+
- It is read per post, never cached, so a rotation applies to the next post. A post that arrives while the name has no value is refused `503 SERVICE_UNAVAILABLE` and is never verified; the server log names the flow and the reference, never a value.
22+
- `FlowSchema` refuses a reference that is not a name (a letter or `_`, then letters, digits and `_`), one in the platform's `OS_` namespace (whoever signs the posts holds the hook secret, so a platform secret named there would be handed out with it), one beside a literal `secret`, and one on a flow that binds no inbound `api` trigger.
23+
- `os validate` (`flow-api-trigger-secret-missing`) and the engine's registration check accept a usable reference in place of a literal, and the remedy they print now names `config.secretRef`.
24+
- The reference is a name, not a credential: a flow read serves it as written. Saving a flow through the metadata API with a reference removes any hook secret the write-only flow credential store held for that start node.
25+
- New exports from `@objectstack/spec/automation`: `ApiTriggerSecretRefSchema`, `apiTriggerSecretRefOf`, `apiTriggerSecretRefRefusals`, `API_TRIGGER_SECRET_REF_KEY`, `API_TRIGGER_SECRET_REF_RESERVED_PREFIX`. `AutomationEngineOptions` gains `credentialResolver`.
26+
27+
A literal `config.secret` still works. To move a flow off one, replace `secret: '<value>'` with `secretRef: 'YOUR_NAME'` and set `YOUR_NAME` in the deployment's environment.
28+
29+
Clause-②: yes (widening)
30+
31+
Metadata-protocol: added
32+
33+
Ruling-ref: 6386630545

‎content/docs/automation/connectors.mdx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ The enterprise tier swaps in a vault/KMS-backed resolver
235235
(`AutomationServicePluginOptions.credentialResolver`) without changing what
236236
you author: the entry still just names a ref.
237237

238+
The same resolver serves an `api` flow's start-node `config.secretRef`, the
239+
name of its inbound hook secret. That one is read at every post rather than at
240+
boot, so a name with no value refuses each post with `503` instead of failing
241+
the boot ([Flows](/docs/automation/flows)).
242+
238243
Inlining a secret is rejected when the stack is validated. Writing the runtime
239244
shape on a provider-bound instance —
240245

‎content/docs/automation/flows.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ the call, so a `durable` callout enqueues exactly the request the inline call
515515
would send.
516516

517517
<Callout type="warn" title="Do not put a secret in an http node's url or headers">
518-
A flow definition, including an `http` node's `url` and `headers`, is served to every member who can read flows. A token, API key or signed webhook url written there is readable by all of them. Route an outbound credential by where it sits, and call the connector with a `connector_action` node instead — see [Connectors](/docs/automation/connectors#authentication). A credential in a header goes to a declarative connector with `bearer`, `basic` or `api-key` auth, whose `auth.credentialRef` names the secret. A key in the query string goes to `api-key` auth with `paramName`. No `credentialRef` variant carries a secret in the url path, so an incoming-webhook url (whose path is the secret) cannot be routed that way: call the service through a token-authenticated connector instead, such as the `slack` connector, whose bot token is supplied to the plugin by host code rather than written in the flow (it is registered by that plugin, not declared as a `connectors:` instance). Otherwise such a url is served with the definition. Only `signingSecret` and a start node's `secret` are kept out of a flow saved through the metadata API: the metadata save door stores each in a write-only, encrypted flow credential store, a read never returns it, and the engine reads it only to verify an inbound post or sign an outbound request. A packaged flow keeps its literal in its package source. The literal is withheld when the definition is served, and a credential store row for that flow, where one exists, wins when the engine verifies or signs. `url` and `headers` are stored and served as written.
518+
A flow definition, including an `http` node's `url` and `headers`, is served to every member who can read flows. A token, API key or signed webhook url written there is readable by all of them. Route an outbound credential by where it sits, and call the connector with a `connector_action` node instead — see [Connectors](/docs/automation/connectors#authentication). A credential in a header goes to a declarative connector with `bearer`, `basic` or `api-key` auth, whose `auth.credentialRef` names the secret. A key in the query string goes to `api-key` auth with `paramName`. No `credentialRef` variant carries a secret in the url path, so an incoming-webhook url (whose path is the secret) cannot be routed that way: call the service through a token-authenticated connector instead, such as the `slack` connector, whose bot token is supplied to the plugin by host code rather than written in the flow (it is registered by that plugin, not declared as a `connectors:` instance). Otherwise such a url is served with the definition. Only `signingSecret` and a start node's `secret` are kept out of a flow saved through the metadata API: the metadata save door stores each in a write-only, encrypted flow credential store, a read never returns it, and the engine reads it only to verify an inbound post or sign an outbound request. A flow that ships in a package names its inbound hook secret instead of carrying it: the start node's `config.secretRef` holds the name the deployment fills (an environment variable of that name in the open tier, resolved through the same credential resolver as a connector's `auth.credentialRef`), and the engine reads it at every post, so no credential is published with the package. A post that arrives while the name has no value is answered `503`, never verified. The reference is a name, not a credential: it is served as written, it may not sit beside a literal `secret`, and it may not name a variable in the platform's `OS_` namespace. A packaged flow that carries a literal keeps it in its package source; the literal is withheld when the definition is served, and a credential store row for that flow, where one exists, wins when the engine verifies or signs. `url` and `headers` are stored and served as written.
519519
</Callout>
520520

521521
**Script:**

‎content/docs/getting-started/quick-reference.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ REST endpoints, real-time subscriptions, and discovery.
150150
| **[Metadata](/docs/references/api/metadata)** | `metadata.zod.ts` | Metadata | API metadata endpoints |
151151
| **[Storage](/docs/references/api/storage)** | `storage.zod.ts` | Storage | API storage operations |
152152

153-
## Automation Protocol (3 of 13 schemas)
153+
## Automation Protocol (3 of 14 schemas)
154154

155155
Flows, approvals, and integrations. A record's lifecycle transitions are a `state_machine` validation rule on the object (see [State Machine](/docs/protocol/objectql/state-machine)).
156156

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
title: Api Trigger Secret Ref — Automation Protocol
3+
navTitle: Api Trigger Secret Ref
4+
description: "An inbound api hook's secret, by reference. An api flow's start node arms an inbound hook that verifies every post against a per-flow HMAC secret."
5+
---
6+
7+
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
8+
9+
An inbound `api` hook's secret, by reference.
10+
11+
An `api` flow's start node arms an inbound hook that verifies every post
12+
against a per-flow HMAC secret (ADR-0041). Until this key the only way to
13+
declare that secret in a definition was the literal `config.secret`, so an
14+
app package could not ship an `api` flow without publishing its credential:
15+
a packaged flow cannot be overlaid, so the write-only flow credential store
16+
the metadata save door fills is out of a package's reach.
17+
18+
`config.secretRef` names the secret instead of carrying it. The deployment
19+
supplies the value under that name, and the automation engine reads it at
20+
every inbound post through the same credential resolver a declarative
21+
connector's `auth.credentialRef` goes through (ADR-0097 §3): an environment
22+
variable of that name in the open tier, a secrets service where the host
23+
wires one. A post that arrives while the name resolves to no value is
24+
refused as unavailable, never verified against nothing.
25+
26+
The reference is NOT a credential: it is served with the definition like any
27+
other key, and only the literal `config.secret` is withheld on read.
28+
29+
Three refusals, each anchored on the start node's `config`:
30+
31+
- a reference that is not a name — `ApiTriggerSecretRefSchema`;
32+
- a start node that declares both `secret` and `secretRef` — two sources for
33+
one credential, and no reader could say which one an author meant;
34+
- a reference on a flow that binds no inbound `api` trigger — nothing would
35+
ever read it, so a deployment would fill a name for nothing.
36+
37+
<Callout type="info">
38+
**Source:** `packages/spec/src/automation/api-trigger-secret-ref.zod.ts`
39+
</Callout>
40+
41+
## TypeScript Usage
42+
43+
```typescript
44+
import { ApiTriggerSecretRefSchema } from '@objectstack/spec/automation';
45+
import type { ApiTriggerSecretRef } from '@objectstack/spec/automation';
46+
47+
// Validate data
48+
const result = ApiTriggerSecretRefSchema.parse(data);
49+
```
50+
51+
---
52+
53+
## ApiTriggerSecretRef
54+
55+
The name of the inbound hook's HMAC secret, filled by the deployment and read at every post through the automation credential resolver (an environment variable of that name in the open tier). In place of a literal `secret`, never beside it; not in the `OS_` namespace.
56+
57+
58+
---
59+

‎content/docs/references/automation/index.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
---
22
title: Automation Protocol — schema reference
33
navTitle: Automation Protocol
4-
description: "The ObjectStack Automation Protocol in 13 reference pages: every schema in @objectstack/spec with its properties, types, defaults and a TypeScript example."
4+
description: "The ObjectStack Automation Protocol in 14 reference pages: every schema in @objectstack/spec with its properties, types, defaults and a TypeScript example."
55
---
66

77
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
88

99
This section contains all protocol schemas for the automation layer of ObjectStack.
1010

1111
<Cards>
12+
<Card href="/docs/references/automation/api-trigger-secret-ref" title="Api Trigger Secret Ref" description="Source: packages/spec/src/automation/api-trigger-secret-ref.zod.ts" />
1213
<Card href="/docs/references/automation/approval" title="Approval" description="Source: packages/spec/src/automation/approval.zod.ts" />
1314
<Card href="/docs/references/automation/bpmn-interop" title="Bpmn Interop" description="Source: packages/spec/src/automation/bpmn-interop.zod.ts" />
1415
<Card href="/docs/references/automation/builtin-node-config" title="Builtin Node Config" description="Source: packages/spec/src/automation/builtin-node-config.zod.ts" />

‎content/docs/references/automation/meta.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"---Approvals & Jobs---",
1414
"approval",
1515
"---More---",
16+
"api-trigger-secret-ref",
1617
"builtin-node-config",
1718
"flow-function",
1819
"io-node-config",

‎content/docs/references/index.mdx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Protocol reference — every schema by module
33
navTitle: Protocol Reference
4-
description: Every schema published by @objectstack/spec — 1548 schemas across 14 protocol modules
4+
description: Every schema published by @objectstack/spec — 1549 schemas across 14 protocol modules
55
---
66

77
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -22,7 +22,7 @@ counts are sums of the rows they head. Regenerate with
2222
| :--- | ---: | ---: | :--- |
2323
| [AI Protocol](/docs/references/ai) | 12 | 68 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. |
2424
| [API Protocol](/docs/references/api) | 32 | 438 | REST contracts, endpoints, routing, realtime, batch, discovery. |
25-
| [Automation Protocol](/docs/references/automation) | 13 | 72 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
25+
| [Automation Protocol](/docs/references/automation) | 14 | 73 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
2626
| [Data Protocol](/docs/references/data) | 29 | 178 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
2727
| [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. |
2828
| [Integration Protocol](/docs/references/integration) | 1 | 12 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. |
@@ -34,7 +34,7 @@ counts are sums of the rows they head. Regenerate with
3434
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
3535
| [System Protocol](/docs/references/system) | 36 | 285 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
3636
| [UI Protocol](/docs/references/ui) | 16 | 169 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
37-
| **Total** | **197** | **1548** | 14 protocol modules |
37+
| **Total** | **198** | **1549** | 14 protocol modules |
3838

3939
---
4040

@@ -106,12 +106,13 @@ REST contracts, endpoints, routing, realtime, batch, discovery.
106106

107107
## Automation Protocol
108108

109-
**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **13 pages, 72 schemas**
109+
**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **14 pages, 73 schemas**
110110

111111
Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records.
112112

113113
| File | Schemas |
114114
| :--- | :--- |
115+
| [`api-trigger-secret-ref.zod.ts`](/docs/references/automation/api-trigger-secret-ref) | `ApiTriggerSecretRef` |
115116
| [`approval.zod.ts`](/docs/references/automation/approval) | `ApprovalDecision`, `ApprovalEscalation`, `ApprovalNodeApprover`, `ApprovalNodeConfig`, `ApproverType`, `DecisionOutputDef` |
116117
| [`bpmn-interop.zod.ts`](/docs/references/automation/bpmn-interop) | `BpmnDiagnostic`, `BpmnElementMapping`, `BpmnExportOptions`, `BpmnImportOptions`, `BpmnInteropResult`, `BpmnUnmappedStrategy`, `BpmnVersion` |
117118
| [`builtin-node-config.zod.ts`](/docs/references/automation/builtin-node-config) | `AssignmentConfig`, `AssignmentExpressionValue`, `AssignmentValue`, `CreateRecordConfig`, `DeleteRecordConfig`, `EndConfig`, `FlowCollectionSlot`, `FlowValueSlot`, `GetRecordConfig`, `MapConfig`, `ScreenConfig`, `ScreenFieldConfig`, `UpdateRecordConfig` |

‎docs/qa/platform-checklist/areas/automation.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@
465465
"on-demand chain: POST /api/v1/automation/<name>/trigger for showcase_batch_reminders {params:{tasks:[…]}} (loop), showcase_inquiry_purge (get_record+decision+delete_record — seed at least one closed showcase_inquiry first), showcase_release_signoff {params:{items:[…]}} (map)",
466466
"browser chain: run the Tasks row action showcase_bulk_reassign → showcase_reassign_wizard (screen + update_record)",
467467
"approval chain: PATCH a showcase_expense_report to submitted (approval via showcase_expense_signoff); PATCH a showcase_project budget above 100000 to open showcase_budget_approval, then POST /api/v1/approvals/requests/:id/revise so the run parks at the approval_revise node",
468-
"webhook chain: POST /api/v1/automation/hooks/showcase_inbound_task_webhook/intake with a valid x-objectstack-signature HMAC (secret 'showcase-webhook-secret') to exercise create_record",
468+
"webhook chain: POST /api/v1/automation/hooks/showcase_inbound_task_webhook/intake with a valid x-objectstack-signature HMAC to exercise create_record — the flow names its secret (config.secretRef: 'SHOWCASE_WEBHOOK_SECRET'), so boot with that environment variable set to a value of your choosing and sign with it; unset, the hook answers 503",
469469
"for EACH variant: GET the owning flow's runs (GET /api/v1/automation/<name>/runs + /runs/:runId) and locate a step whose nodeType equals the variant; record run id, step status, and — for suspending types — the paused-then-resumed transition",
470470
"screenshot the Runs panel of one composite run (showcase_project_escalation or showcase_fan_out_notify) showing the container-nested steps",
471471
"unknown-type probe (the late-binding contract, ruled status quo in #7545): POST /api/v1/automation a flow whose one non-start node carries type 'bogus_node' — capture the HTTP status and the server log record the registration emits, then POST /api/v1/automation/<that flow>/trigger and read GET /api/v1/automation/<that flow>/runs/:runId"
@@ -617,10 +617,10 @@
617617
"time_relative"
618618
],
619619
"steps": [
620-
"boot the showcase on its own port and DB (dogfood §0) with OS_AUTOMATION_SCHEDULED_WORK_ENABLED=1 and OS_PORT exported (fixtures.requires — without the first, the schedule and time_relative variants can never fire); sign in as the dev admin",
620+
"boot the showcase on its own port and DB (dogfood §0) with OS_AUTOMATION_SCHEDULED_WORK_ENABLED=1, OS_PORT and SHOWCASE_WEBHOOK_SECRET (any value; the api flow's config.secretRef names it) exported (fixtures.requires — without the first, the schedule and time_relative variants can never fire; without the last, the api hook answers 503); sign in as the dev admin",
621621
"record_change (create leg): POST /api/v1/data/showcase_task with priority='urgent' — showcase_urgent_task_alert fires via record-after-write with previous == null (#3427)",
622622
"record_change (update leg): POST a normal-priority task, then PATCH priority→'urgent'; also move a task to done — create it with status 'in_review' and its required project (task_status_flow allows done only from in_review, and a create is not transition-checked), then PATCH status→'done' — and confirm a further save WITHOUT a transition fires nothing",
623-
"api: POST /api/v1/automation/hooks/showcase_inbound_task_webhook/intake with JSON {title, assignee, project} and header x-objectstack-signature: t=<unix seconds>,v1=<HMAC-SHA256 of '<t>.<raw body>', key 'showcase-webhook-secret'> — signHttpBodyAt(rawBody, 'showcase-webhook-secret', Math.floor(Date.now() / 1000)) from @objectstack/core writes it, and the door refuses a t more than 300 s from the server clock — expect a 202 ACK; then find the created showcase_task",
623+
"api: POST /api/v1/automation/hooks/showcase_inbound_task_webhook/intake with JSON {title, assignee, project} and header x-objectstack-signature: t=<unix seconds>,v1=<HMAC-SHA256 of '<t>.<raw body>', key = the value the boot exported as SHOWCASE_WEBHOOK_SECRET, which the flow's config.secretRef names> — signHttpBodyAt(rawBody, process.env.SHOWCASE_WEBHOOK_SECRET, Math.floor(Date.now() / 1000)) from @objectstack/core writes it, and the door refuses a t more than 300 s from the server clock — expect a 202 ACK; then find the created showcase_task",
624624
"schedule: wait up to ~60s for showcase_scheduled_digest (interval 60000ms) to tick; wait a second interval for a second tick",
625625
"screen: from /apps/showcase_app/showcase_task run the row action showcase_bulk_reassign, fill the dialog, submit",
626626
"autolaunched: POST /api/v1/automation/showcase_batch_reminders/trigger {params:{tasks:[…]}}",

0 commit comments

Comments
 (0)