Skip to content

Commit 5374967

Browse files
feat(spec): tencentcloud-tc3-hmac-sha256 joins the signed connector auth schemes (ADR-0143 D2.3, slice 2 of #26710) (#28279)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D6hhjvTokP3gaKQjZx1GtR5)_ domain:server#3 · os-elon-musk · session_016WV4cxRP1AqTeP2PWAhwGK Refs #26710 Clause-②: yes (widening: one member added to the closed signing-scheme list and one shape to the `signed` union; one schema export and its type added. `ConnectorRequestSigner.scheme` is now typed by the scheme list, but that interface is in no release yet: it ships with #28273's pending changeset, so no published surface narrows) Metadata-protocol: added ## 维护者速读 - 改了什么:`signed` 认证新增第二个签名方案,腾讯云 API 3.0 签名 v3(`tencentcloud-tc3-hmac-sha256`)。元数据仍只写方案名、公开的 SecretId(`keyId`)和密钥引用 `credentialRef`;`rest` / `openapi` 每次请求(含重试)用新的时间戳重新签名。 - 为什么改:#26710 第二片(ADR-0143 Q4 A 的第二个成员)。腾讯官方文档示例把 SecretKey 打了码,无法复现,所以按 D2.3 改用腾讯官方 Node SDK 的测试向量,测试里写明了 SDK 和版本。 - 拒绝什么:与第一片相同。内联密钥按名拒绝、手写 `signature` 拒绝、`mcp` 上拒绝;`secretId` 提示改写为 `keyId`;未知 scheme 的提示现在列出两个方案。 - 没变的:阿里云方案、`bearer` / `api-key` / `basic` 不变;签名接缝只把 `scheme` 的类型从任意字符串收紧到方案列表(该接口尚未发布)。 - 先看:`packages/services/service-automation/src/request-signing/tencentcloud-tc3-hmac-sha256.ts` 和它的测试。 - 两分钟验证:`pnpm --filter @objectstack/service-automation exec vitest run src/request-signing`,看到腾讯 SDK 的 POST 与 GET 两个签名逐字节复现,错误密钥得到不同签名。 - 需要你做的:无。契约复审(CONTRACT_REVIEW_TIER)待做;Tier S,席位验收后落地。 ## What changes **`@objectstack/spec`** - `ConnectorSigningSchemeSchema` gains `tencentcloud-tc3-hmac-sha256`. The `signed` union gains `ConnectorInstanceSignedTencentCloudTc3AuthSchema`: `{ type: 'signed', scheme: 'tencentcloud-tc3-hmac-sha256', keyId, credentialRef }`, a `strictObject`. `keyId` is the SecretId. The SecretKey is only ever a `credentialRef`. - The publish door refuses on the new shape what it refuses on the first one: `secret`, `privateKey`, `secretKey` and `accessKeySecret` by name with the `credentialRef` prescription, an authored `signature`, and `signed` on `provider: 'mcp'`. `secretId` is answered with `keyId`. The unknown-scheme refusal names both shipped schemes. - The review nit from #28273 is fixed: `ConnectorRequestSigner.scheme` is typed `ConnectorSigningScheme` rather than `string`. **`@objectstack/service-automation`** - `request-signing/tencentcloud-tc3-hmac-sha256.ts`: - The canonical request is the method, the path, the query exactly as sent, `content-type:` and `host:`, `content-type;host`, and the hex SHA-256 of the body. - The string to sign is `TC3-HMAC-SHA256`, the timestamp, the scope `<date>/<service>/tc3_request` and the hashed canonical request. - The key is derived `HMAC("TC3"+SecretKey, date)` → service → `tc3_request`. - Every attempt gets a fresh `X-TC-Timestamp`. - The service is the first label of the endpoint host (regional endpoints included). - `X-TC-Action` / `-Version` / `-Region` stay unsigned, as in the vendor's SDK. - A request that names no content type is signed and sent with `application/x-www-form-urlencoded` (what the SDK sends on a GET), so the signed header is the sent header. - The registry gains the case. Its exhaustive check now tests `auth` itself against `never`, which still names an unhandled scheme at compile time (ablation below). **Docs:** `connectors.mdx` "Request signing" gains the Tencent example and the scheme's header rules. The `variant-docs.json` scheme key lists both members. The references are regenerated with `check:generated --fix`. The surface is inside the amended claim 6535175098. ## Why ADR-0143 D2.3 / Q4 A: the closed list grows one scheme at a time, each with its signer and vectors. D2.3's vector rule takes the vendor's worked example when it is complete, and otherwise the vendor's official SDK test suite named with its version. Tencent's own example masks the SecretKey, so this slice takes the second branch. ## Verification - **Vectors:** `tencentcloud-tc3-hmac-sha256.test.ts` reproduces, byte for byte, the two `sign3` cases of the official `tencentcloud-sdk-nodejs` release 4.1.336, `test/common/sign.test.ts`: POST `application/json` and GET `application/x-www-form-urlencoded`. It uses that suite's own placeholder key pair, timestamp and service, and the file header names the SDK, release and file. The suite's multipart case is not carried, because a signed body is text here. A wrong key gives a different signature. The bound signer at the vector's instant gives the vector. - **Beyond the vectors:** - The unsigned API headers do not move the signature. - A request with no content type gets the form type, and its signature equals the GET vector. CONTROL: a typed request is not given another type. - A regional host scopes to `cvm`. - The query is signed as sent. - The timestamp is fresh per request. - The registry binds the scheme, and the key is absent from the output. - 23 signing tests pass. - **Publish door:** `connector-auth-signed.test.ts` adds: - a CONTROL accept on `rest` and `openapi` - the inline-key refusals over both schemes - the `secretId` alias - both names in the unknown-scheme message - union membership - **End to end:** `connector-rest-plugin.test.ts` runs a real host with the rest provider and a `tencentcloud-tc3-hmac-sha256` instance. The call carries a TC3 `Authorization` scoped to `cvm` and an `X-TC-Timestamp`, and the key is absent from calls and descriptors. - **Suites:** - spec signed, fetch-policy, grant-kinds, connector-auth and pins: 74 passed. - service-automation signing, materialization and credential source: 105 passed. - connector-rest: 33 passed. - connector-openapi: 75 passed. - `pnpm --filter <pkg> typecheck` is green for spec, service-automation and connector-rest. - **Ablations:** - With the `tencentcloud-tc3-hmac-sha256` registry case removed, `service-automation` typecheck fails, naming the unhandled shape (TS2322 at the `never` check). - With the `"TC3"` prefix dropped from the key derivation, 6 vector pins go red. - With the code as pushed, everything is green. - Every family `dispatch-gates --commands` derives (105) ran and exited 0, reconciled with `--ran`: 105 run, 0 NOT-MEASURED. `pnpm lint` exits 0. Skills-docs: `grep -rlnE "credentialRef|app-credential|api-key" skills` hits `skills/objectstack-automation/{SKILL.md,references/integration-recipes.md,evals/integrations-declared.json}`. The `signed` text rides #28226, as for #28273, because a `skills/**` edit would make this slice Tier H. `content/docs/automation/connectors.mdx` carries it here. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (The signer is in `service-automation`.) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request (slice 2 of 3 of #26710). - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. - [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 lane's maintainer summary). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016WV4cxRP1AqTeP2PWAhwGK --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8f36f6b commit 5374967

21 files changed

Lines changed: 537 additions & 35 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/service-automation': minor
4+
---
5+
6+
feat(spec): `tencentcloud-tc3-hmac-sha256` joins the `signed` connector-instance auth schemes (ADR-0143 D2.3, #26710)
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: added
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) One member is added to the closed signing-scheme list and one shape to the `signed` union; the signer seam's `scheme` member is typed as that list instead of any string; that interface is in no release yet (it ships with the pending `26710-signed-connector-auth-aliyun` changeset), so no published surface narrows. Nothing an author wrote before is refused or re-shaped, so there is nothing for a migration to rewrite. -->
12+
13+
A declarative connector instance can now declare Tencent Cloud's API 3.0 signature (v3) as its signing scheme. It joins Alibaba Cloud's V3 signature, which shipped with the `signed` kind.
14+
15+
- **`@objectstack/spec`**
16+
- `ConnectorSigningSchemeSchema` gains `tencentcloud-tc3-hmac-sha256`.
17+
- The `signed` union gains its closed shape, `{ type: 'signed', scheme: 'tencentcloud-tc3-hmac-sha256', keyId, credentialRef }`. `keyId` is the SecretId. The SecretKey is only ever a `credentialRef`.
18+
- The same publish refusals apply as for the first scheme: an inline key spelling by name, an authored `signature`, and `signed` on `provider: 'mcp'`. `secretId` is answered with `keyId`. An unknown scheme's refusal now lists both shipped schemes.
19+
- `ConnectorRequestSigner.scheme` is typed as `ConnectorSigningScheme` rather than `string`. The interface ships for the first time in this release, together with the first scheme.
20+
- **`@objectstack/service-automation`**
21+
- The signer registry binds a `tencentcloud-tc3-hmac-sha256` instance's key to its TC3-HMAC-SHA256 signer.
22+
- Every attempt is signed with a fresh `X-TC-Timestamp`, over `content-type` and `host`. The scope's service is the first label of the endpoint host.
23+
- A request without a content type is signed and sent as `application/x-www-form-urlencoded`.
24+
- The key never leaves the host.

‎content/docs/automation/connectors.mdx‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,12 +483,21 @@ auth: {
483483
keyId: 'YourAccessKeyId', // the AccessKey ID: public, stays in metadata
484484
credentialRef: 'ALIYUN_ACCESS_KEY_SECRET',
485485
}
486+
487+
// Tencent Cloud API 3.0, signature v3 (TC3-HMAC-SHA256):
488+
auth: {
489+
type: 'signed',
490+
scheme: 'tencentcloud-tc3-hmac-sha256',
491+
keyId: 'YourSecretId', // the SecretId: public, stays in metadata
492+
credentialRef: 'TENCENT_SECRET_KEY',
493+
}
486494
```
487495

488496
What the platform does with it:
489497

490498
- **Every request is signed, each attempt fresh.** A retry is signed again with
491-
a new nonce and date, so a scheme that refuses a replayed nonce accepts it.
499+
a new nonce and date, so a scheme that refuses a replayed nonce or a stale
500+
timestamp accepts it.
492501
- **The key never reaches the provider.** The automation service binds the
493502
resolved key to the scheme's signer at materialization and hands the
494503
provider the signer only. A datasource cannot borrow a `signed` instance
@@ -501,6 +510,14 @@ What the platform does with it:
501510
`x-acs-content-sha256` and `Authorization`. The API's own action and version
502511
are yours to send, as `x-acs-action` / `x-acs-version` in `defaultHeaders`
503512
(rest) or in each call's headers.
513+
- **`tencentcloud-tc3-hmac-sha256`** signs `content-type` and `host`, and sets
514+
`X-TC-Timestamp` and `Authorization`. The service in the signature's scope is
515+
the first label of the endpoint host (`cvm.tencentcloudapi.com`, or a
516+
regional `cvm.ap-guangzhou.tencentcloudapi.com`, is `cvm`), so point
517+
`baseUrl` at the product's own endpoint. `X-TC-Action`, `X-TC-Version` and
518+
`X-TC-Region` are yours to send and are not signed. A request without a body
519+
is sent with `Content-Type: application/x-www-form-urlencoded`, the type it
520+
is signed with.
504521

505522
What the publish door refuses:
506523

@@ -509,7 +526,8 @@ What the publish door refuses:
509526
signer and its test vectors, so a scheme the server cannot run is never
510527
accepted.
511528
- **An inline key**: `secret`, `privateKey`, `secretKey` or `accessKeySecret`
512-
is refused by name, pointing at `credentialRef`. An authored `signature` is
529+
is refused by name, pointing at `credentialRef`. The vendors' words for the
530+
key id (`accessKeyId`, `secretId`) are answered with `keyId`. An authored `signature` is
513531
refused too: it is computed for every request.
514532
- **`provider: 'mcp'`**, at `auth.type`: an MCP server is not signed per
515533
request. Declare `provider: 'rest'` or `provider: 'openapi'`.

‎content/docs/references/index.mdx‎

Lines changed: 5 additions & 5 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 — 1569 schemas across 14 protocol modules
4+
description: Every schema published by @objectstack/spec — 1570 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/. */}
@@ -25,7 +25,7 @@ counts are sums of the rows they head. Regenerate with
2525
| [Automation Protocol](/docs/references/automation) | 15 | 77 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
2626
| [Data Protocol](/docs/references/data) | 31 | 174 | 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. |
28-
| [Integration Protocol](/docs/references/integration) | 1 | 21 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. |
28+
| [Integration Protocol](/docs/references/integration) | 1 | 22 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. |
2929
| [Kernel Protocol](/docs/references/kernel) | 29 | 151 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. |
3030
| [Marketplace Protocol](/docs/references/marketplace) | 4 | 30 | The package & marketplace format — package identity and versions, listing, publish, review, search, install, template manifests. |
3131
| [QA Protocol](/docs/references/qa) | 1 | 8 | Declarative test suites — scenarios, steps, actions and assertions. |
@@ -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) | 37 | 287 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
3636
| [UI Protocol](/docs/references/ui) | 16 | 172 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
37-
| **Total** | **203** | **1569** | 14 protocol modules |
37+
| **Total** | **203** | **1570** | 14 protocol modules |
3838

3939
---
4040

@@ -191,13 +191,13 @@ Users and accounts, organizations, positions, SCIM provisioning.
191191

192192
## Integration Protocol
193193

194-
**Source:** `packages/spec/src/integration/` · **Import:** `@objectstack/spec/integration` · **1 page, 21 schemas**
194+
**Source:** `packages/spec/src/integration/` · **Import:** `@objectstack/spec/integration` · **1 page, 22 schemas**
195195

196196
The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances.
197197

198198
| File | Schemas |
199199
| :--- | :--- |
200-
| [`connector.zod.ts`](/docs/references/integration/connector) | `Connector`, `ConnectorAction`, `ConnectorActionEffect`, `ConnectorAppCredentialVendor`, `ConnectorInstanceAPIKeyAuth`, `ConnectorInstanceAppCredentialAuth`, `ConnectorInstanceAuth`, `ConnectorInstanceBasicAuth`, `ConnectorInstanceBearerAuth`, `ConnectorInstanceNoAuth`, `ConnectorInstanceOAuth2Auth`, `ConnectorInstanceOAuth2AuthorizationCodeAuth`, `ConnectorInstanceOAuth2ClientCredentialsAuth`, `ConnectorInstanceSignedAliyunAcs3Auth`, `ConnectorInstanceSignedAuth`, `ConnectorOAuth2Grant`, `ConnectorRetryStrategy`, `ConnectorSigningScheme`, `ConnectorType`, `DeclarativeConnectorEntry`, `RetryConfig` |
200+
| [`connector.zod.ts`](/docs/references/integration/connector) | `Connector`, `ConnectorAction`, `ConnectorActionEffect`, `ConnectorAppCredentialVendor`, `ConnectorInstanceAPIKeyAuth`, `ConnectorInstanceAppCredentialAuth`, `ConnectorInstanceAuth`, `ConnectorInstanceBasicAuth`, `ConnectorInstanceBearerAuth`, `ConnectorInstanceNoAuth`, `ConnectorInstanceOAuth2Auth`, `ConnectorInstanceOAuth2AuthorizationCodeAuth`, `ConnectorInstanceOAuth2ClientCredentialsAuth`, `ConnectorInstanceSignedAliyunAcs3Auth`, `ConnectorInstanceSignedAuth`, `ConnectorInstanceSignedTencentCloudTc3Auth`, `ConnectorOAuth2Grant`, `ConnectorRetryStrategy`, `ConnectorSigningScheme`, `ConnectorType`, `DeclarativeConnectorEntry`, `RetryConfig` |
201201

202202
---
203203

‎content/docs/references/integration/connector.mdx‎

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@ an exception for itself.)
165165
## TypeScript Usage
166166

167167
```typescript
168-
import { ConnectorSchema, ConnectorActionSchema, ConnectorActionEffectSchema, ConnectorAppCredentialVendorSchema, ConnectorInstanceAPIKeyAuthSchema, ConnectorInstanceAppCredentialAuthSchema, ConnectorInstanceAuthSchema, ConnectorInstanceBasicAuthSchema, ConnectorInstanceBearerAuthSchema, ConnectorInstanceNoAuthSchema, ConnectorInstanceOAuth2AuthSchema, ConnectorInstanceOAuth2AuthorizationCodeAuthSchema, ConnectorInstanceOAuth2ClientCredentialsAuthSchema, ConnectorInstanceSignedAliyunAcs3AuthSchema, ConnectorInstanceSignedAuthSchema, ConnectorOAuth2GrantSchema, ConnectorRetryStrategySchema, ConnectorSigningSchemeSchema, ConnectorTypeSchema, DeclarativeConnectorEntrySchema, RetryConfigSchema } from '@objectstack/spec/integration';
169-
import type { Connector, ConnectorAction, ConnectorActionEffect, ConnectorAppCredentialVendor, ConnectorInstanceAPIKeyAuth, ConnectorInstanceAppCredentialAuth, ConnectorInstanceAuth, ConnectorInstanceBasicAuth, ConnectorInstanceBearerAuth, ConnectorInstanceNoAuth, ConnectorInstanceOAuth2Auth, ConnectorInstanceOAuth2AuthorizationCodeAuth, ConnectorInstanceOAuth2ClientCredentialsAuth, ConnectorInstanceSignedAliyunAcs3Auth, ConnectorInstanceSignedAuth, ConnectorOAuth2Grant, ConnectorRetryStrategy, ConnectorSigningScheme, ConnectorType, DeclarativeConnectorEntry, RetryConfig } from '@objectstack/spec/integration';
168+
import { ConnectorSchema, ConnectorActionSchema, ConnectorActionEffectSchema, ConnectorAppCredentialVendorSchema, ConnectorInstanceAPIKeyAuthSchema, ConnectorInstanceAppCredentialAuthSchema, ConnectorInstanceAuthSchema, ConnectorInstanceBasicAuthSchema, ConnectorInstanceBearerAuthSchema, ConnectorInstanceNoAuthSchema, ConnectorInstanceOAuth2AuthSchema, ConnectorInstanceOAuth2AuthorizationCodeAuthSchema, ConnectorInstanceOAuth2ClientCredentialsAuthSchema, ConnectorInstanceSignedAliyunAcs3AuthSchema, ConnectorInstanceSignedAuthSchema, ConnectorInstanceSignedTencentCloudTc3AuthSchema, ConnectorOAuth2GrantSchema, ConnectorRetryStrategySchema, ConnectorSigningSchemeSchema, ConnectorTypeSchema, DeclarativeConnectorEntrySchema, RetryConfigSchema } from '@objectstack/spec/integration';
169+
import type { Connector, ConnectorAction, ConnectorActionEffect, ConnectorAppCredentialVendor, ConnectorInstanceAPIKeyAuth, ConnectorInstanceAppCredentialAuth, ConnectorInstanceAuth, ConnectorInstanceBasicAuth, ConnectorInstanceBearerAuth, ConnectorInstanceNoAuth, ConnectorInstanceOAuth2Auth, ConnectorInstanceOAuth2AuthorizationCodeAuth, ConnectorInstanceOAuth2ClientCredentialsAuth, ConnectorInstanceSignedAliyunAcs3Auth, ConnectorInstanceSignedAuth, ConnectorInstanceSignedTencentCloudTc3Auth, ConnectorOAuth2Grant, ConnectorRetryStrategy, ConnectorSigningScheme, ConnectorType, DeclarativeConnectorEntry, RetryConfig } from '@objectstack/spec/integration';
170170

171171
// Validate data
172172
const result = ConnectorSchema.parse(data);
@@ -293,7 +293,7 @@ const result = ConnectorSchema.parse(data);
293293
| **appId** | `string` | ✅ | The vendor's app identifier (not a secret): Feishu `app_id`, DingTalk `appKey`, WeCom `corpid`. |
294294
| **credentialRef** | `string` | ✅ | Secrets-layer reference resolved to the app secret (WeCom: the application `corpsecret`). Never an inline secret. |
295295

296-
### Nested Shape: `Connector.auth[option 7]`
296+
### Nested Shape: `Connector.auth[option 7][scheme='aliyun-acs3-hmac-sha256']`
297297

298298
| Property | Type | Required | Description |
299299
| :--- | :--- | :--- | :--- |
@@ -302,6 +302,15 @@ const result = ConnectorSchema.parse(data);
302302
| **keyId** | `string` | ✅ | The AccessKey ID (not a secret); sent in the `Credential` of every signature. |
303303
| **credentialRef** | `string` | ✅ | Secrets-layer reference resolved to the AccessKey secret, the HMAC key every request is signed with. Never an inline secret. |
304304

305+
### Nested Shape: `Connector.auth[option 7][scheme='tencentcloud-tc3-hmac-sha256']`
306+
307+
| Property | Type | Required | Description |
308+
| :--- | :--- | :--- | :--- |
309+
| **type** | `'signed'` | ✅ | |
310+
| **scheme** | `'tencentcloud-tc3-hmac-sha256'` | ✅ | Tencent Cloud API 3.0 signature v3 (TC3-HMAC-SHA256). |
311+
| **keyId** | `string` | ✅ | The SecretId (not a secret); sent in the `Credential` of every signature. |
312+
| **credentialRef** | `string` | ✅ | Secrets-layer reference resolved to the SecretKey every request's signing key is derived from. Never an inline secret. |
313+
305314
### Nested Shape: `Connector.actions[number]`
306315

307316
| Property | Type | Required | Description |
@@ -481,7 +490,7 @@ Type: `{ type: 'oauth2'; tokenUrl?: string; scopes?: string[]; resource?: string
481490

482491
#### Option 7
483492

484-
Type: `{ type: 'signed'; scheme: 'aliyun-acs3-hmac-sha256'; keyId: string; credentialRef: string }`
493+
Type: `{ type: 'signed'; scheme: 'aliyun-acs3-hmac-sha256'; keyId: string; credentialRef: string } | { type: 'signed'; scheme: 'tencentcloud-tc3-hmac-sha256'; keyId: string; credentialRef: string }`
485494

486495
---
487496

@@ -640,6 +649,35 @@ This schema accepts one of the following structures:
640649

641650
---
642651

652+
#### Option 2
653+
654+
**Type:** `signed`
655+
656+
### Properties
657+
658+
| Property | Type | Required | Description |
659+
| :--- | :--- | :--- | :--- |
660+
| **type** | `'signed'` | ✅ | |
661+
| **scheme** | `'tencentcloud-tc3-hmac-sha256'` | ✅ | Tencent Cloud API 3.0 signature v3 (TC3-HMAC-SHA256). |
662+
| **keyId** | `string` | ✅ | The SecretId (not a secret); sent in the `Credential` of every signature. |
663+
| **credentialRef** | `string` | ✅ | Secrets-layer reference resolved to the SecretKey every request's signing key is derived from. Never an inline secret. |
664+
665+
---
666+
667+
668+
---
669+
670+
## ConnectorInstanceSignedTencentCloudTc3Auth
671+
672+
### Properties
673+
674+
| Property | Type | Required | Description |
675+
| :--- | :--- | :--- | :--- |
676+
| **type** | `'signed'` | ✅ | |
677+
| **scheme** | `'tencentcloud-tc3-hmac-sha256'` | ✅ | Tencent Cloud API 3.0 signature v3 (TC3-HMAC-SHA256). |
678+
| **keyId** | `string` | ✅ | The SecretId (not a secret); sent in the `Credential` of every signature. |
679+
| **credentialRef** | `string` | ✅ | Secrets-layer reference resolved to the SecretKey every request's signing key is derived from. Never an inline secret. |
680+
643681

644682
---
645683

@@ -671,11 +709,12 @@ Retry strategy
671709

672710
## ConnectorSigningScheme
673711

674-
Request-signing scheme: `aliyun-acs3-hmac-sha256` (Alibaba Cloud OpenAPI V3 signature).
712+
Request-signing scheme: `aliyun-acs3-hmac-sha256` (Alibaba Cloud OpenAPI V3 signature) or `tencentcloud-tc3-hmac-sha256` (Tencent Cloud API 3.0 signature v3).
675713

676714
### Allowed Values
677715

678716
* `aliyun-acs3-hmac-sha256`
717+
* `tencentcloud-tc3-hmac-sha256`
679718

680719

681720
---
@@ -815,7 +854,7 @@ Connector type
815854
| **appId** | `string` | ✅ | The vendor's app identifier (not a secret): Feishu `app_id`, DingTalk `appKey`, WeCom `corpid`. |
816855
| **credentialRef** | `string` | ✅ | Secrets-layer reference resolved to the app secret (WeCom: the application `corpsecret`). Never an inline secret. |
817856

818-
### Nested Shape: `DeclarativeConnectorEntry.auth[option 7]`
857+
### Nested Shape: `DeclarativeConnectorEntry.auth[option 7][scheme='aliyun-acs3-hmac-sha256']`
819858

820859
| Property | Type | Required | Description |
821860
| :--- | :--- | :--- | :--- |
@@ -824,6 +863,15 @@ Connector type
824863
| **keyId** | `string` | ✅ | The AccessKey ID (not a secret); sent in the `Credential` of every signature. |
825864
| **credentialRef** | `string` | ✅ | Secrets-layer reference resolved to the AccessKey secret, the HMAC key every request is signed with. Never an inline secret. |
826865

866+
### Nested Shape: `DeclarativeConnectorEntry.auth[option 7][scheme='tencentcloud-tc3-hmac-sha256']`
867+
868+
| Property | Type | Required | Description |
869+
| :--- | :--- | :--- | :--- |
870+
| **type** | `'signed'` | ✅ | |
871+
| **scheme** | `'tencentcloud-tc3-hmac-sha256'` | ✅ | Tencent Cloud API 3.0 signature v3 (TC3-HMAC-SHA256). |
872+
| **keyId** | `string` | ✅ | The SecretId (not a secret); sent in the `Credential` of every signature. |
873+
| **credentialRef** | `string` | ✅ | Secrets-layer reference resolved to the SecretKey every request's signing key is derived from. Never an inline secret. |
874+
827875
### Nested Shape: `DeclarativeConnectorEntry.actions[number]`
828876

829877
| Property | Type | Required | Description |

‎docs/audits/2026-07-unknown-key-strictness-ledger.counts/shared.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ hand-patch a number here** — fix the code or the verdict and regenerate.
1919

2020
| Dir | Sites |
2121
|---|---|
22-
| `shared/` | 24 |
22+
| `shared/` | 25 |

‎packages/connectors/connector-rest/src/connector-rest-plugin.test.ts‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,4 +270,55 @@ describe('ConnectorRestPlugin — a declarative `signed` instance (ADR-0143 D2.3
270270

271271
await kernel.shutdown();
272272
});
273+
274+
it('signs a tencentcloud-tc3-hmac-sha256 instance the same way, with the service named by its host', async () => {
275+
const KEY = 'dummy-tencent-secret-key-26710';
276+
const calls: Array<{ url: string; headers: Record<string, string> }> = [];
277+
const impl = (async (url: string, init: RequestInit) => {
278+
calls.push({ url, headers: init.headers as Record<string, string> });
279+
return new Response('{"Response":{"RequestId":"r-1"}}', { status: 200, headers: { 'content-type': 'application/json' } });
280+
}) as unknown as typeof fetch;
281+
const declared = [{
282+
name: 'tencent_cvm',
283+
label: 'Tencent CVM',
284+
type: 'api',
285+
provider: 'rest',
286+
providerConfig: { baseUrl: 'https://cvm.tencentcloudapi.com', defaultHeaders: { 'X-TC-Version': '2017-03-12', 'X-TC-Region': 'ap-guangzhou' } },
287+
auth: { type: 'signed', scheme: 'tencentcloud-tc3-hmac-sha256', keyId: 'test-secret-id', credentialRef: 'TENCENT_SECRET_26710' },
288+
}];
289+
const kernel = new LiteKernel();
290+
kernel.use(new AutomationServicePlugin({ credentialResolver: (r) => (r === 'TENCENT_SECRET_26710' ? KEY : undefined) }));
291+
kernel.use(new ConnectorRestPlugin({ fetchImpl: impl }));
292+
kernel.use({
293+
name: 'test.metadata',
294+
type: 'standard',
295+
version: '1.0.0',
296+
dependencies: [],
297+
async init(ctx: any) {
298+
ctx.registerService('objectql', { registry: { listItems: (t: string) => (t === 'connector' ? declared : []) } });
299+
},
300+
async start() {},
301+
} as never);
302+
await kernel.bootstrap();
303+
const engine = kernel.getService<AutomationEngine>('automation');
304+
expect(engine.getConnectorDegradedReason('tencent_cvm')).toBeUndefined();
305+
306+
const request = engine.resolveConnectorAction('tencent_cvm', 'request')!;
307+
const answered = await request({ method: 'POST', path: '/', body: { Limit: 1 }, headers: { 'X-TC-Action': 'DescribeInstances' } }, {} as never);
308+
expect(answered).toEqual({ status: 200, ok: true, body: { Response: { RequestId: 'r-1' } } });
309+
310+
expect(calls).toHaveLength(1);
311+
const headers = calls[0]!.headers;
312+
expect(headers.authorization).toMatch(
313+
/^TC3-HMAC-SHA256 Credential=test-secret-id\/\d{4}-\d{2}-\d{2}\/cvm\/tc3_request, SignedHeaders=content-type;host, Signature=[0-9a-f]{64}$/,
314+
);
315+
expect(headers['x-tc-timestamp']).toMatch(/^\d{10}$/);
316+
expect(headers['content-type']).toBe('application/json');
317+
expect(headers['x-tc-action']).toBe('DescribeInstances');
318+
319+
expect(JSON.stringify(calls)).not.toContain(KEY);
320+
expect(JSON.stringify(engine.getConnectorDescriptors())).not.toContain(KEY);
321+
322+
await kernel.shutdown();
323+
});
273324
});

0 commit comments

Comments
 (0)