Skip to content

Commit 91fa06d

Browse files
docs(skills,automation): a connector_action input is judged against the action's inputSchema before dispatch, refused as VALIDATION_ERROR (#28170)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DBrWLqn9GU5duvKTbHrFr2M)_ domain:skills#2 · os-elon-musk · session_016WV4cwp1Wc6nB6ePmS4ePn Fixes #28041 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:只改文档,不改代码。自动化 skill 的 `connector_action` 一行、`integration-recipes.md` 的输入条目,现在写明:求值后的 `connectorConfig.input` 在派发前按动作声明的 `inputSchema` 校验(`required`、`type` 含 OpenAPI `nullable`、`properties`、`items`),不符即以 `VALIDATION_ERROR` 失败(可被 `fault` 边接住),请求不发出;可选参数上的 `null` 当作未传;文本里的数字 id 要在 CEL 里 `int()` 转型。`connectors.mdx` 的故障表加一行。 - 为什么改:PR #28035(#27995)合入后,旧文案仍教「输入原样发出」,契约复审 6520142501 点名此处欠文档。 - 风险与回滚:两个 skill 文件都在 token 天花板上(余量 0),新文字全靠同文件删重复付清,不动 ratchet 脚本:SKILL.md 6457→6452,recipes 2236→2236。回滚即 revert。 - 先看:`skills/objectstack-automation/references/integration-recipes.md` 的 Holds 第二条,再看 SKILL.md:88。 - 两分钟验证:`node scripts/check-skills-token-ratchet.mjs`,预期 exit 0。 - 需要你做的:欠一次契约复审(CONTRACT_REVIEW_TIER);落地档以登记册为准,本地读为 Tier H(`skills/**`),需 os-zhuang 或 hotlong 批准。 ## What changes - **`skills/objectstack-automation/SKILL.md`, the `connector_action` row:** the evaluated input is judged against the action's `inputSchema` (`required`, `type`, `properties`, `items`) before dispatch; a breach fails the step `VALIDATION_ERROR` (`fault`-routable) and nothing is sent; `os validate` does not judge it yet (#27968). - **`references/integration-recipes.md`, Holds bullet 2:** "any other value sent as written" becomes "kept as written", then the same judgment with OpenAPI `nullable`, null-on-optional read as absent, the per-path naming, and the `int()` cast for a string-held id (`path: { dialect: 'cel', source: '{"petId": int(record.vendor_id)}' }`, the shape the merged suite pins at `connector-input-schema.test.ts:208`). - **`content/docs/automation/connectors.mdx`, Failure modes at a glance:** one row for the refusal, with the code, the routing, the remedy (`int()` / `double()` / `bool()`), the listing route and the `os validate` note. The cross-domain exception is triage 6521031879. Wording measured against the merged code (`ec7e15abc`, `packages/services/service-automation/src/builtin/connector-nodes.ts:126-163` and `collectInputSchemaViolations`), not the card's paraphrase. Shipped message: `connector_action '<node>': input for '<connector>.<action>' does not match the action's inputSchema — <violations>. Nothing was sent. Fix connectorConfig.input so it satisfies the inputSchema GET /api/v1/automation/connectors lists for this action.` plus, for text where a number or boolean is declared, `A text value for an integer, number or boolean parameter is not converted: cast it in a CEL envelope with int(), double() or bool().` The result carries `code: 'VALIDATION_ERROR'` and the default `runtime` class, so a `fault` edge receives it (pinned in the same suite). #27968 is still open, so the `os validate` note stays. ## Paid for in the same file (restatement only, each with a surviving home) SKILL.md: the `approval` row's "(from `plugin-approvals`)" (the `requires` table row says it), "never an `http` node holding a credential" in § Integrations (the `http` row says it), the filter-operand "(not `fault`-routable)" (the ROUTES table lists that case as not routing), and "`notify` delivers, `connector_action` reaches a vendor, a function computes" (the node rows and the PURE-compute paragraph below say it). 6457 → 6452 tokens. integration-recipes.md: recipe 1's "trimming still keeps the package small" (recipe 1's comment says to trim), recipe 4's two comments "an inbound post carries no trigger user" (recipe 1 teaches the same `runAs` reason) and "never a literal secret" (the intro says the hook secret goes to the deployment), recipe 5's "// inline OpenAPI 3.x" (the next line spells `openapi: '3.0.3'` inline), the OAuth bullet's "Declare the connector, fields and flows, mark the integration blocked …, and say so" (the section heading says it), and "with its input schema, visible" after recipe 5 (bullet 2 now carries the schema). 2236 → 2236 tokens, headroom 0. No ceiling moved; `scripts/check-skills-token-ratchet.mjs` is untouched. ## Verification - `node scripts/check-skills-token-ratchet.mjs`: exit 0 (SKILL.md 6452/6457, integration-recipes.md 2236/2236). `pnpm check:pm-skill-ratchet` exit 0. - Gates: `dispatch-gates --commands` at the committed head `f2cd1d93` derives 60 families; all 60 run in a fresh worktree after `pnpm install --frozen-lockfile --ignore-scripts` and builds of `@objectstack/lint...` and `@objectstack/client-react...`, exit codes recorded per command. 59 exit 0 (including `check:pm-skill-id-lint`, `check:skill-docs`, `check:skill-examples`, `check:skill-refs`, `check:docs`, `check:doc-anchors`, `check:doc-formula-expressions`, `check:skill-identifier-liveness`). `check:dual-build-cjs-loads` exits 3 (needs every dist), NOT MEASURED. `--ran` reconciles 60 derived / 59 run / 1 NOT-MEASURED / 0 UNRUN; the derivation re-run at the final head is identical. - No changeset derived: no published package is touched, so `skip-changeset`. - Landing tier: `check-governed-merges.mjs --test` reads 2 of 3 paths on the register (`skills/**`), Tier H. The register's reading at PR time is the seat's. - Not measured locally: repo-wide `pnpm lint`, the type-check lanes, and the docs site build (CI's). ## Human Minimum Each item is one rule from [CONTRIBUTING.md › Human Minimum](https://github.com/objectstack-ai/objectstack/blob/main/CONTRIBUTING.md#human-minimum), which links the AGENTS.md text that states it. - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (not touched) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; the derived lint families pass. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (no published package: `skip-changeset`) - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (not touched) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English (the 维护者速读 block is the one Chinese channel). - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016WV4cwp1Wc6nB6ePmS4ePn --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6285203 commit 91fa06d

4 files changed

Lines changed: 41 additions & 39 deletions

File tree

‎content/docs/automation/connectors.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,7 @@ retry):
579579
| MCP server unreachable / `tools/list` fails; remote spec URL unreachable or transiently failing (408/429/5xx) | **Not fatal**: the instance registers **degraded** (visible on `GET /api/v1/automation/connectors` with the reason), dispatches fail with that reason, and the platform retries on a 5s→5min backoff — recovery is automatic |
580580
| A retry of a degraded instance reaches the upstream and meets a configuration fault (an `include` naming a tool or operation the upstream does not offer) | **Not fatal**: the instance stays **degraded**, now with the configuration reason, and is **not retried** — dispatches fail saying to edit its declaration; an edit of the entry (or a restart) re-attempts it |
581581
| Wrong spec URL (other 4xx) or unparseable document | Configuration fault — **hard boot error** |
582+
| A `connector_action` input that, once evaluated, breaks the action's `inputSchema` (a missing `required` parameter, a wrong `type`) | **The step fails before dispatch** with `VALIDATION_ERROR`, naming each parameter path, and nothing is sent; a `fault` edge or `try_catch` can route it. Supply the parameter or send the declared type: text is never converted, so cast it in a CEL envelope (`int()`, `double()`, `bool()`). `GET /api/v1/automation/connectors` lists the schema; `os validate` does not judge the input against the `inputSchema` yet |
582583
| A bad entry arrives via **runtime reload** (a Studio publish, a direct active save through `PUT /api/v1/meta/connector/:name`, or a delete, made on any replica — peers reconcile on [cluster lane 2](/docs/kernel/cluster#62-the-cluster-invalidation-family); or a dev reload) | Logged and **skipped**, never crashing the live server; a changed instance's old connector keeps serving until its replacement materializes |
583584

584585
## Catalog descriptors: entries without a `provider`

‎skills/objectstack-automation/SKILL.md‎

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,12 @@ A **Flow** is a directed graph of nodes — the primary automation building bloc
4646
| `autolaunched` | Runs without user interaction — triggered by events, APIs, or other flows |
4747
| `screen` | Interactive — presents UI screens to the user (wizards, forms) |
4848
| `schedule` | Runs on a cron/interval cadence declared on the **start node's `config.schedule`** — or a **per-record date sweep** via `config.timeRelative`, see *Time-relative triggers* |
49-
| `record_change` | Fires automatically on record create/update/delete (bind via the `start` node's `triggerType`). `autolaunched` + the same `record-*` binding behaves identically; `record_change` also opts into the trigger-readiness lint |
49+
| `record_change` | Fires automatically on record create/update/delete. `autolaunched` + the same `record-*` binding behaves identically; `record_change` also opts into the trigger-readiness lint |
5050
| `api` | Inbound **webhook** — needs a start-node `secretRef` (or a literal `secret`; see *Inbound webhook triggers* below); a flow only ever started explicitly is `autolaunched` |
5151

5252
### Flow Node Types
5353

54-
Flows are built from **built-in node types** (plugins register more, e.g.
55-
`approval`):
54+
Flows are built from **built-in node types** (plugins register more):
5655

5756
#### Control Flow
5857

@@ -85,15 +84,15 @@ Flows are built from **built-in node types** (plugins register more, e.g.
8584
|:-----|:--------|
8685
| `http` | Call an external HTTP API — always set `timeoutMs` (unset = no timeout). `url` and `headers` are served as written with the definition to every member who can read flows — only `signingSecret` and a start node's `secret` are withheld — so never a credential there: a credential means a declared connector called via `connector_action` |
8786
| `notify` | Send a notification through the messaging service (inbox by default) |
88-
| `connector_action` | Dispatch an action of a declared connector (§ *Integrations are declared, not coded*) or a plugin's. `connectorConfig.input` is a value-slot map like `subflow` `input`; `os validate` does not judge it yet, so a malformed envelope fails at run time |
87+
| `connector_action` | Dispatch an action of a declared connector (§ *Integrations are declared, not coded*) or a plugin's. `connectorConfig.input` is a value-slot map like `subflow` `input`: `registerFlow` and `os validate` refuse a `{…}` token at `connectorConfig.input.<key>`. Once evaluated, it is judged against the action's `inputSchema` (`required`, `type`, `properties`, `items`) before dispatch: a breach fails the step `VALIDATION_ERROR` (`fault`-routable), nothing sent; `os validate` does not judge the `inputSchema` fit yet |
8988
| `script` | Call a **registered** function named by `config.function` (see *Valid-but-silently-wrong* #3) |
9089
| `screen` | Display a UI form to the user (screen flows only). `config.fields[]`: `{ name, label, type, required, options, defaultValue }`, `type` an open widget hint; `'file'` renders the upload widget and resumes with the `sys_file` id bound to `name` — a later `create_record` / `update_record` writes it into a `file` field, which claims the file: collect first, write after the last screen. `'record_link'` (never `'record'`, an object field type) takes no input — it shows the `reference` record whose id `defaultValue` evaluates to, by display name, as a link to its record page: the result screen of a flow that created records, `{ name: 'account_link', type: 'record_link', reference: 'crm_account', defaultValue: { dialect: 'cel', source: 'account.id' } }` over a `create_record` `outputVariable: 'account'`. Both keys required; `required` / `options` / `placeholder` / `min` / `max` refused; a resume value under its name refused (`400 INVALID_SCREEN_INPUT`). A completed `end` `message` is refused and a markdown link in a screen `description` renders literally. Next / Submit and "Step N of M" are not authored: the engine reads the graph when the screen pauses and serves `progress` (`nextScreen: 'none' | 'possible' | 'certain'`; `step` / `stepCount` only when every path agrees), which alone labels the button and draws the counter. A `decision`, `fault` edge, loop back, non-screen pause (`approval`, `wait`) or `subflow` after a screen answers `possible` — "Next", no count. `progress` in a node `config` is refused; never hand-roll a button label or step counter into `fields` |
9190

9291
#### Human Decision
9392

9493
| Node | Purpose |
9594
|:-----|:--------|
96-
| `approval` | Route a record for human sign-off — **suspends** the run until a decision, then continues down the `approve` / `reject` branch (from `plugin-approvals`) |
95+
| `approval` | Route a record for human sign-off — **suspends** the run until a decision, then continues down the `approve` / `reject` branch |
9796

9897
### `notify` — the most-used node type
9998

@@ -193,13 +192,13 @@ type: 'text', isInput: true }, { name: 'approval_result', type: 'boolean', isOut
193192
A whole-string **query placeholder** (`{current_user_id}`, `{today}`,
194193
`{current_year_start}`) passes through verbatim for the query engine; a flow
195194
variable never shadows it, and only `filter` gets this hand-off.
196-
- **An operand that evaluates to `null` refuses the node** (not
197-
`fault`-routable): a dropped condition does not narrow a query, it widens
198-
it, so a routed `delete_record` would empty the object while the run reports
199-
success. Where the value may be absent, route around the node with a
200-
`decision`; to match an empty field, write the literal `null`. A relation
201-
hop (`record.owner.email`) needs the start node's `config.expand` to list
202-
the field — else the fault names the missing hop, not the remedy.
195+
- **An operand that evaluates to `null` refuses the node**: a dropped condition
196+
does not narrow a query, it widens it, so a routed `delete_record` would empty
197+
the object while the run reports success. Where the value may be absent,
198+
route around the node with a `decision`; to match an empty field, write the
199+
literal `null`. A relation hop (`record.owner.email`) needs the start node's
200+
`config.expand` to list the field — else the fault names the missing hop, not
201+
the remedy.
203202

204203
---
205204

@@ -244,8 +243,7 @@ write them right the first time:
244243

245244
3. **`script` nodes call a registered function — nothing else.** `config.function`
246245
(**required**: an empty node refuses at execute, an unregistered name fails
247-
loudly) names one registered via `defineStack({ functions: { my_fn: (ctx) => … } })`;
248-
`notify` delivers, `connector_action` reaches a vendor, a function computes.
246+
loudly) names one registered via `defineStack({ functions: { my_fn: (ctx) => … } })`.
249247

250248
**A flow `function` is a PURE compute step — it does NOT read/write the
251249
database.** It receives `ctx.input` and **returns** a value; `config.outputVariable`
@@ -329,9 +327,9 @@ node's `config`.
329327

330328
### Prerequisite — declare the capabilities your nodes need
331329

332-
Metadata registers without its `defineStack({ requires })` token; the surface
333-
just never runs. Two pairs are instead **refused** at `os validate` / `build` /
334-
`dev` / `serve` (422); neither token implies the other:
330+
Metadata registers without its `defineStack({ requires })` token. Two pairs are
331+
instead **refused** at `os validate` / `build` / `dev` / `serve` (422); neither
332+
token implies the other:
335333

336334
| `requires` token | Turns on | Absent ⇒ |
337335
|:--|:--|:--|
@@ -384,7 +382,7 @@ owns `label`, `type`, `nodes` and the `edges` `FlowSchema` requires):
384382
config: {
385383
objectName: 'support_case',
386384
triggerType: 'record-after-update',
387-
// bare CEL; gates whether the flow launches on the event
385+
// gates whether the flow launches on the event
388386
condition: "previous.status != 'escalated' && record.status == 'escalated'",
389387
},
390388
}
@@ -408,7 +406,7 @@ A vendor system reachable over HTTP (OpenAPI, remote MCP, plain REST) is a
408406
`connectors:` entry inside the app — `provider: 'openapi' | 'mcp' | 'rest'`, a
409407
packaged spec, `auth.credentialRef` (the customer's administrator supplies the
410408
value) — dispatched by `connector_action`; an inbound vendor callback is an
411-
`api` flow. Never a plugin, never an `http` node holding a credential. Recipes,
409+
`api` flow. Never a plugin. Recipes,
412410
credential hand-off, loader limits: [references/integration-recipes.md](./references/integration-recipes.md).
413411
Platform code only when one holds — and then it is the platform's, filed as a
414412
card, the app still writing none: the protocol is not HTTP; the work writes

‎skills/objectstack-automation/evals/integrations-declared.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{
55
"id": 1,
66
"prompt": "Our contract-lifecycle app (objects `contract`, `crm_account`) needs DocuSign: send a contract out for signature, read an envelope's status, and reflect status changes on the record. DocuSign publishes a 4.7 MB Swagger 2.0 document with several hundred operations, authenticates with OAuth 2.0, and its Connect webhooks sign posts with their own HMAC header. Propose what to build and where.",
7-
"expected_output": "A declarative instance inside the app, not a platform plugin and not a `connector-esign` family: `defineConnector` with `provider: 'openapi'`, whose `providerConfig.spec` is a packaged OpenAPI 3.x document converted offline from the Swagger 2.0 document and trimmed to the three operations (create envelope, get envelope, list status changes) (or `providerConfig.include` names those three operation keys, the `mcp` provider's allowlist meaning, where a non-array or unknown entry is fatal; trimming still keeps the package small). Fields on `contract` (`envelope_id`, `signature_status`). Flows dispatch `connector_action`; the callback is an `api` flow with a start-node `secretRef`. Two gaps are named plainly, each blocked on its platform card: the declarative `auth` union has no `oauth2`, so the integration is marked **blocked** on outbound OAuth (no short-lived token parked behind a `bearer` `credentialRef`); DocuSign Connect signs its own header, which the `api` door does not verify, so status is polled until vendor schemes are verified. `connectorConfig.input` is a value slot: the per-contract payload reads the record through a CEL envelope (`envelopeId: { dialect: 'cel', source: 'record.envelope_id' }`). The answer proposes no `registerConnector` code and no plugin.",
7+
"expected_output": "A declarative instance inside the app, not a platform plugin and not a `connector-esign` family: `defineConnector` with `provider: 'openapi'`, whose `providerConfig.spec` is a packaged OpenAPI 3.x document converted offline from the Swagger 2.0 document and trimmed to the three operations (create envelope, get envelope, list status changes) (or `providerConfig.include` names those three operation keys, the `mcp` provider's allowlist meaning, where a non-array or unknown entry is fatal). Fields on `contract` (`envelope_id`, `signature_status`). Flows dispatch `connector_action`; the callback is an `api` flow with a start-node `secretRef`. Two gaps are named plainly, each blocked on its platform card: the declarative `auth` union has no `oauth2`, so the integration is marked **blocked** on outbound OAuth (no short-lived token parked behind a `bearer` `credentialRef`); DocuSign Connect signs its own header, which the `api` door does not verify, so status is polled until vendor schemes are verified. `connectorConfig.input` is a value slot: the per-contract payload reads the record through a CEL envelope (`envelopeId: { dialect: 'cel', source: 'record.envelope_id' }`), judged against the action's `inputSchema` before dispatch. The answer proposes no `registerConnector` code and no plugin.",
88
"files": [],
99
"assertions": {
1010
"must_contain": ["defineConnector", "provider: 'openapi'", "credentialRef", "connector_action", "type: 'api'", "secretRef", "Swagger 2.0", "blocked"],

‎skills/objectstack-automation/references/integration-recipes.md‎

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@ deployment and to the sender. Scaffolded apps list the three executors in
1616
- Actions come from the upstream (an OpenAPI `operationId`, an MCP tool name,
1717
the `rest` provider's single `request`) — authored on the entry, they are refused.
1818
- `connectorConfig.input` is a value-slot map like `subflow` `input`: a top-level
19-
CEL envelope is evaluated, any other value sent as written (`{…}`, `{{ }}` are
20-
literal text) — `input: { customer: { dialect: 'cel', source: 'record.stripe_id' } }`.
21-
`os validate` does not judge it yet, so a malformed envelope fails at run time.
19+
CEL envelope is evaluated, any other value kept as written. `registerFlow` and
20+
`os validate` refuse a `{…}` token at `connectorConfig.input.<key>`: a value
21+
from the record is a CEL envelope. Before dispatch the result is judged
22+
against the action's `inputSchema`: `required`, `type` (with OpenAPI
23+
`nullable`), `properties`, `items`; `null` on an optional parameter is
24+
absent. A breach fails the step `VALIDATION_ERROR` (`fault`-routable) naming
25+
each path, nothing sent. Text is never converted: cast a string-held id,
26+
`path: { dialect: 'cel', source: '{"petId": int(record.vendor_id)}' }`.
27+
`os validate` does not judge the `inputSchema` fit yet.
2228
- The answer lands as `<nodeId>.<key>` (`rest` / `openapi`: `{ status, ok, body }`;
23-
`mcp`: `{ ok, content, structuredContent? }`), read from a later node's value
24-
slot: `{ dialect: 'cel', source: 'bal.body.available[0].amount' }`.
29+
`mcp`: `{ ok, content, structuredContent? }`), read from a later node's value slot.
2530
- `auth`, each variant with a `credentialRef`: `bearer`; `api-key` (`headerName`,
2631
default `X-API-Key`, or `paramName` for a query-string key); `basic` (`username`
2732
stays in metadata). A secret in a url path has no variant — use a
@@ -31,12 +36,12 @@ deployment and to the sender. Scaffolded apps list the three executors in
3136
## 1 · OpenAPI document, static key — Stripe balance, nightly
3237

3338
```ts
34-
// Trim the vendor's OpenAPI 3.x document to the operations the app calls and ship
35-
// it in the package, package-relative.
39+
// Trim the vendor's OpenAPI 3.x document to the operations the app calls; its path
40+
// is package-relative.
3641
export const StripeConnector = defineConnector({
3742
name: 'stripe', label: 'Stripe', type: 'api', provider: 'openapi',
38-
providerConfig: { spec: './src/connectors/stripe.trimmed.json' }, // holds `GetBalance` alone
39-
auth: { type: 'bearer', credentialRef: 'STRIPE_SECRET_KEY' }, // admin sets STRIPE_SECRET_KEY
43+
providerConfig: { spec: './src/connectors/stripe.trimmed.json' },
44+
auth: { type: 'bearer', credentialRef: 'STRIPE_SECRET_KEY' },
4045
});
4146
// crm_account gains `stripe_available` (currency) and `stripe_synced_at` (datetime).
4247
export const StripeBalanceSweep = defineFlow({
@@ -63,7 +68,7 @@ dereference every `$ref` offline, in-document ones included: the loader skips a
6368
`$ref` parameter, request body or response, and the derived input/output schema
6469
silently loses it. `providerConfig.include` allowlists operation keys as the
6570
`mcp` provider's does (absent = every operation; a non-array or unknown entry is
66-
fatal); trimming still keeps the package small.
71+
fatal).
6772

6873
## 2 · Remote MCP server, static header — GitHub
6974

@@ -103,10 +108,10 @@ export const WarehouseApi = defineConnector({
103108
```ts
104109
export const CarrierCallback = defineFlow({
105110
name: 'carrier_callback', label: 'Carrier status callback', type: 'api',
106-
runAs: 'system', // an inbound post carries no trigger user
111+
runAs: 'system', // no trigger user
107112
nodes: [
108113
{ id: 'start', type: 'start', label: 'POST …/hooks/carrier_callback/carrier',
109-
config: { hookId: 'carrier', secretRef: 'CARRIER_HOOK_SECRET' } }, // never a literal secret
114+
config: { hookId: 'carrier', secretRef: 'CARRIER_HOOK_SECRET' } },
110115
{ id: 'apply', type: 'update_record', label: 'Apply status', config: {
111116
objectName: 'shipment', filter: { tracking_no: { dialect: 'cel', source: 'record.tracking_no' } },
112117
fields: { status: { dialect: 'cel', source: 'record.status' } } } },
@@ -127,7 +132,7 @@ name the gap and poll its status (recipe 1's shape) until vendor schemes verify.
127132
```ts
128133
export const LegacyErp = defineConnector({
129134
name: 'legacy_erp', label: 'Legacy ERP', type: 'api', provider: 'openapi',
130-
providerConfig: { spec: { // inline OpenAPI 3.x
135+
providerConfig: { spec: {
131136
openapi: '3.0.3', info: { title: 'Legacy ERP', version: '1' },
132137
servers: [{ url: 'https://erp.example.com' }],
133138
paths: { '/v2/open-orders': { get: { operationId: 'listOpenOrders',
@@ -138,16 +143,14 @@ export const LegacyErp = defineConnector({
138143
// connector_action { connectorId: 'legacy_erp', actionId: 'listOpenOrders', input: {} }
139144
```
140145

141-
Each `operationId` is a named action with its input schema, visible in the flow
142-
palette and to agents; `provider: 'rest'` instead when the path is per call.
146+
Each `operationId` is a named action in the flow palette and to agents;
147+
`provider: 'rest'` instead when the path is per call.
143148

144149
## Not yet — declare the rest, mark the integration blocked, write no plugin
145150

146151
- **OAuth-protected vendor** (DocuSign, Salesforce, Google): the declarative `auth`
147152
union has no `oauth2`; the authorization-code / refresh
148153
lifecycle is not delivered in the open tier. Do not park a short-lived access
149-
token behind a `bearer` ref. Declare the connector, fields and flows, mark the
150-
integration **blocked** on the platform's outbound-OAuth card, and say so.
154+
token behind a `bearer` ref. Blocked on the platform's outbound-OAuth card.
151155
- **Signature-authenticated vendor** (Alipay, WeChat Pay, Aliyun — RSA2 / HMAC
152-
canonical-request signing): no auth kind signs requests today; same handling,
153-
blocked on the request-signing card.
156+
canonical-request signing): no auth kind signs requests today; blocked on the request-signing card.

0 commit comments

Comments
 (0)