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
10 changes: 10 additions & 0 deletions .changeset/29255-installer-client-source-skill.md
Original file line number Diff line number Diff line change
@@ -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.
23 changes: 22 additions & 1 deletion scripts/check-skills-token-ratchet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading