diff --git a/.changeset/29255-installer-client-source-skill.md b/.changeset/29255-installer-client-source-skill.md new file mode 100644 index 00000000000..dd942483594 --- /dev/null +++ b/.changeset/29255-installer-client-source-skill.md @@ -0,0 +1,10 @@ +--- +'@objectstack/skills': patch +--- + +docs(skills): the automation skill's OAuth recipe teaches the installer-supplied client + +Clause-②: no +Metadata-protocol: none + +Recipe 6 of `objectstack-automation/references/integration-recipes.md` now shows `clientSource: 'installer'` beside a declared `clientId` + `credentialRef`, with when to use which: a declared client for an app its publisher deploys, the installer form for one installed on deployments the publisher never sees. The administrator registers a client with the vendor against the `redirectUri` the connection's status read answers, connects with `{ clientId, clientSecret }` (no secret for a public client; a reconnect naming no client keeps the stored one), and the connection reads back `client: { source: 'installer', clientId, hasSecret }`, never the secret. diff --git a/scripts/check-skills-token-ratchet.mjs b/scripts/check-skills-token-ratchet.mjs index 5e8109225b4..2d2a9b19146 100644 --- a/scripts/check-skills-token-ratchet.mjs +++ b/scripts/check-skills-token-ratchet.mjs @@ -957,7 +957,28 @@ export const CEILINGS = new Map([ // and untranslated: 「A:先削重复,余额按实测提高」 — the standing path of // ruling record 6513306068 on #27847. // ceil($(wc -c < skills/objectstack-automation/references/integration-recipes.md) / 4) == ceil(9949 / 4) - ['skills/objectstack-automation/references/integration-recipes.md', 2488], + // 9926 -> 10377 (card #29255; the file had shrunk 23 bytes under the row + // since, headroom 6 tokens): recipe 6 teaches `clientSource: 'installer'` + // beside the declared client — when to use which, the administrator's + // `{ clientId, clientSecret }` connect body against the status read's + // `redirectUri`, and the connection's `client: { source, clientId, + // hasSecret }` read — from content/docs/automation/connectors.mdx and the + // merged connector-auth schema. Paid down first, two restatements each with + // a surviving home: the authorization_code comment's "A user grants access + // once" (the paragraph under the block says an administrator connects it, + // once per organization), and "`client_credentials` and `app-credential` + // dispatch once the ref is set." (the file's opening paragraph says an unset + // ref leaves its instance degraded until set). The contract-review round + // reworded the reconnect clause ("naming no client") and paid for it inside + // the new text: the code comment's "each deployment's own client" restated + // the when-to-use sentence below the block. Arithmetic: 9926 -> 10377 + // (+451 net: -100 cut, +551 added) -> ceil(10377 / 4) = 2595; the ceiling + // moves +107, pinned AT the measurement. Maintainer ruling 2026-10-27 on the + // seat's decision card 「skills 修复卡的教学和 eval 都撞 token 天花板,怎么付?」, + // tap A verbatim and untranslated: 「A:先削重复,余额按实测提高」 — the + // standing path of ruling record 6513306068 on #27847. + // ceil($(wc -c < skills/objectstack-automation/references/integration-recipes.md) / 4) == ceil(10377 / 4) + ['skills/objectstack-automation/references/integration-recipes.md', 2595], // 5569 -> 5587 (card #26298, audit #26184 row A2): the approval config table // gains `excludeSubmitter` (refused beside `onEmptyApprovers: 'auto_approve'`), // +105 tokens. Paid down first: Best Practices 1 and 4 restated the Branching diff --git a/skills/objectstack-automation/references/integration-recipes.md b/skills/objectstack-automation/references/integration-recipes.md index 9e48f90b02b..a95df3173b0 100644 --- a/skills/objectstack-automation/references/integration-recipes.md +++ b/skills/objectstack-automation/references/integration-recipes.md @@ -159,20 +159,25 @@ Recipe 1's shape with a grant-bearing `auth`, served on `rest`, `openapi` and token is fetched per dispatch; no metadata key holds it. ```ts -// A user grants access once (PKCE always on); the ref is the client secret, omitted for a public client. +// PKCE always on; the ref is the client secret, omitted for a public client. auth: { type: 'oauth2', grant: 'authorization_code', authorizationUrl: 'https://account.docusign.com/oauth/auth', tokenUrl: 'https://account.docusign.com/oauth/token', scopes: ['signature'], clientId: 'objectstack-prod', credentialRef: 'DOCUSIGN_CLIENT_SECRET' } +// Or clientSource: 'installer' in place of clientId + credentialRef. // Server to server: grant: 'client_credentials' with tokenUrl, clientId, credentialRef (`resource` optional). // Vendor app id + app secret (`vendor`: `feishu` / `dingtalk` / `wecom`): auth: { type: 'app-credential', vendor: 'feishu', appId: 'cli_a1b2', credentialRef: 'FEISHU_APP_SECRET' } ``` -`client_credentials` and `app-credential` dispatch once the ref is set. `authorization_code` is degraded "not connected" until an administrator holding `manage_connector_connections` connects it, once per organization, from a signed-in session (an agent or API key is refused): `POST /api/v1/automation/connectors/:name/connection` answers `{ authorizationUrl }`; the vendor redirects to `GET …/connectors/oauth/callback`. An `authorization_code` instance on `mcp` may omit the `https://` endpoints and `clientId`, discovered at connect. +Name `clientId` for an app its publisher deploys; `clientSource: 'installer'` for one installed +on deployments the publisher never sees: the administrator registers a client with the vendor +against the `redirectUri` that `GET …/:name/connection` answers, then connects with `{ clientId, clientSecret }` +(no secret for a public client; a reconnect naming no client keeps it). The connection keeps it +encrypted; a read shows `client: { source: 'installer', clientId, hasSecret }`. Still never park a short-lived token behind a `bearer` ref.