Skip to content

Commit 743ac88

Browse files
committed
Merge origin/main into claude/issue-28404-override-declaration
Regenerate the references index from the merged tree after #28446. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V
2 parents d548ee9 + 5e706e7 commit 743ac88

49 files changed

Lines changed: 2201 additions & 85 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/service-automation': minor
4+
---
5+
6+
feat(spec): `alipay-v3` joins the `signed` connector-instance auth schemes, with response verification (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 registry's third parameter changes from the key string to an object carrying the key and an optional vendor public key; that function is internal to the automation service and in no release yet (it ships with the pending `26710-signed-connector-auth-aliyun` changeset). 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 Alipay's OpenAPI v3 signature as its signing scheme. Its responses are verified too. It joins the Alibaba Cloud and Tencent Cloud schemes.
14+
15+
- **`@objectstack/spec`**
16+
- `ConnectorSigningSchemeSchema` gains `alipay-v3`.
17+
- The `signed` union gains its closed shape: `{ type: 'signed', scheme: 'alipay-v3', keyId, credentialRef, vendorPublicKeyRef }`.
18+
- `keyId` is the app id.
19+
- `credentialRef` names the application private key.
20+
- `vendorPublicKeyRef` names the Alipay public key that every response is verified with. It is required.
21+
- The same publish refusals apply as for the other schemes: inline key spellings by name, an authored `signature`, and `signed` on `provider: 'mcp'`. Also refused:
22+
- `appPrivateKey`, by name;
23+
- `alipayPublicKey`, which is answered with `vendorPublicKeyRef`.
24+
25+
`appId` is answered with `keyId`. An unknown scheme's refusal now lists all three schemes.
26+
- **`@objectstack/service-automation`**
27+
- Every request is signed `ALIPAY-SHA256withRSA` with a fresh nonce and timestamp.
28+
- Every response below status 400 must carry a valid Alipay signature, an empty body included. A signed error response is verified too. A response that fails either check fails the dispatch, and its body is never returned.
29+
- The vendor public key resolves through the same chain as `credentialRef`. An instance whose public key is missing is degraded with that reference named. The credential door lists both references against the instance.
30+
- A key that is not an RSA key fails boot, naming which reference, never the value.
31+
- The keys never leave the host.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/metadata-core': patch
3+
'@objectstack/objectql': patch
4+
'@objectstack/rest': patch
5+
---
6+
7+
fix(objectql,rest,metadata-core)!: a datasource definition's stored row is served only to datasource administrators, whichever exit reads it (#27884)
8+
9+
Clause-②: no (narrowing for readers below datasource administration)
10+
Metadata-protocol: none
11+
12+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no metadata an author writes changes meaning: a stored datasource definition is written, stored and served to its administrators exactly as before. What narrows is which readers a stored row of a capability-gated type is served to. There is nothing for a migration to rewrite and no tombstone; a reader who needs the definition is granted the capability. -->
13+
14+
**BREAKING** read-set narrowing for callers below datasource administration. The metadata read door already served a `datasource` or `external_catalog` definition only to holders of `manage_platform_settings`, the capability the datasource family's own door requires. The stored row each definition lives in now admits the same readers, so one document admits one set of callers at every exit.
15+
16+
- **One row.** The type-to-capability table moves to `@objectstack/metadata-core` as `STORED_METADATA_TYPE_READ_CAPABILITIES`, with `storedMetadataTypesWithheldFrom(caller)` and `isCapabilityGatedStoredMetadataType(type)`. `@objectstack/rest` keeps exporting `META_TYPE_READ_CAPABILITIES`, now that same frozen object, and the metadata read door answers exactly as before.
17+
- **Now.** A caller-context read of a stored metadata row of a listed type, by a caller without the type's capability, is served no row: a list or count leaves it out, and a read of its id answers as a read of a row that does not exist. Holders of the capability, platform administrators among them, are served everything as before. System reads are unchanged.
18+
- **Events.** A record-change event of such a row carries no metadata body to any subscriber; the row's other columns are unchanged.
19+
- **Who sees it.** A reader below datasource administration is no longer served stored datasource or federation-catalog rows.
20+
21+
**The remedy.** Grant `manage_platform_settings` to a caller that needs a datasource definition, or read it through the datasource admin door (`GET /api/v1/datasources/:name`) as such a caller.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/runtime': patch
3+
---
4+
5+
fix(runtime)!: the connector connection door's begin and revoke legs refuse a delegated or agent caller by name; connecting and revoking an organization's connector is a human administrator's act
6+
7+
Clause-②: no (narrowing) — `POST` and `DELETE /api/v1/automation/connectors/:name/connection` refuse a delegated or agent caller by name: one admitted by an API key or an OAuth access token, or carrying an agent envelope (`principalKind: 'agent'`, `onBehalfOf`). Nothing is widened.
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no metadata an author writes changes meaning: one HTTP door narrows which callers may take two of its steps. The refusal is the door's existing 403 PERMISSION_DENIED with a sentence naming the caller's kind, so there is nothing for a migration to rewrite and no tombstone. -->
11+
12+
**BREAKING** caller narrowing at the connector connection door (`@objectstack/runtime`, ADR-0143 D5).
13+
14+
- **The rule.** The begin leg (connect, reconnect) and the revoke leg refuse a delegated or agent caller, whatever it holds, with `403 PERMISSION_DENIED` and a message that names its kind (an agent acting on a person's behalf, an OAuth access token, an API key) and the rule. D5 binds a connection to the session that began it, so connecting and revoking is taken by a person signed in.
15+
- **Unchanged.** Who holds the capability (platform administrators, the organization-administrator sets, explicit grants), the system context's bypass, the read leg (`GET …/:name/connection`) and the OAuth callback.
16+
- **Who sees it.** Any caller on these two steps that is not a person signed in.
17+
18+
**The remedy.** Take the step from a signed-in session (the Console, or a session token), as the administrator who holds the capability.

‎content/docs/automation/connectors.mdx‎

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,10 @@ a rotating refresh token is never spent twice.
535535
organization-scoped `manage_connector_connections` capability. Platform
536536
administrators and organization administrators hold it by default, so an
537537
organization's owners and admins can connect its vendors from the start;
538-
grant it to anyone else through a permission set:
538+
grant it to anyone else through a permission set. Connecting and revoking
539+
is a human administrator's act, taken from a signed-in session: an agent
540+
acting for them (an OAuth client) or an API key is refused by name, whatever
541+
it holds.
539542

540543
```http
541544
POST /api/v1/automation/connectors/:name/connection → { authorizationUrl, expiresAt }
@@ -617,7 +620,9 @@ Some upstreams take no token at all: every request carries a signature over its
617620
own method, path, query, headers and body, computed with a key the caller never
618621
sends. The `signed` kind declares that (ADR-0143 D2.3). The metadata names the
619622
**scheme** and the public **key id**; the signing key itself is a
620-
`credentialRef`, resolved through the same chain as every other kind.
623+
`credentialRef`, resolved through the same chain as every other kind. A scheme
624+
whose vendor signs its responses also names the vendor's public key, as a
625+
second reference.
621626

622627
```ts
623628
// Alibaba Cloud OpenAPI, V3 signature (ACS3-HMAC-SHA256):
@@ -635,8 +640,22 @@ auth: {
635640
keyId: 'YourSecretId', // the SecretId: public, stays in metadata
636641
credentialRef: 'TENCENT_SECRET_KEY',
637642
}
643+
644+
// Alipay OpenAPI v3 (ALIPAY-SHA256withRSA), responses verified:
645+
auth: {
646+
type: 'signed',
647+
scheme: 'alipay-v3',
648+
keyId: '2021000000000000', // the app id: public, stays in metadata
649+
credentialRef: 'ALIPAY_APP_PRIVATE_KEY', // the application private key (RSA)
650+
vendorPublicKeyRef: 'ALIPAY_PUBLIC_KEY', // the Alipay public key responses are verified with
651+
}
638652
```
639653

654+
Both of an `alipay-v3` instance's references are listed against it by the
655+
credential door, and setting or replacing either one re-materializes it. The
656+
keys are accepted as Alipay's console issues them (the bare base64 body) or as
657+
PEM.
658+
640659
What the platform does with it:
641660

642661
- **Every request is signed, each attempt fresh.** A retry is signed again with
@@ -648,7 +667,8 @@ What the platform does with it:
648667
(`external.connector` is refused with `422`): a driver cannot sign per
649668
request.
650669
- **Schemes that sign their responses are verified** before a result is
651-
returned; a response that does not verify fails the dispatch.
670+
returned; a response that does not verify fails the dispatch, and its body
671+
never reaches the flow.
652672
- **`aliyun-acs3-hmac-sha256`** signs `host`, `content-type` and every
653673
`x-acs-*` header, and sets `x-acs-date`, `x-acs-signature-nonce`,
654674
`x-acs-content-sha256` and `Authorization`. The API's own action and version
@@ -659,9 +679,16 @@ What the platform does with it:
659679
the first label of the endpoint host (`cvm.tencentcloudapi.com`, or a
660680
regional `cvm.ap-guangzhou.tencentcloudapi.com`, is `cvm`), so point
661681
`baseUrl` at the product's own endpoint. `X-TC-Action`, `X-TC-Version` and
662-
`X-TC-Region` are yours to send and are not signed. A request without a body
663-
is sent with `Content-Type: application/x-www-form-urlencoded`, the type it
664-
is signed with.
682+
`X-TC-Region` are yours to send and are not signed. A request without a
683+
content type is sent with `Content-Type: application/x-www-form-urlencoded`,
684+
the type it is signed with.
685+
- **`alipay-v3`** signs the method, the path and query as sent and the body,
686+
with a fresh nonce and millisecond timestamp, and sets `Authorization:
687+
ALIPAY-SHA256withRSA app_id=…,nonce=…,timestamp=…,sign=…`. Every response
688+
below status 400 must carry a valid `alipay-signature` (an empty body too),
689+
checked against the key `vendorPublicKeyRef` names; an error response is
690+
verified when it is signed, and an unsigned one fails as the error it is.
691+
Public-key mode only: Alipay's certificate mode is not supported.
665692

666693
What the publish door refuses:
667694

@@ -670,9 +697,11 @@ What the publish door refuses:
670697
signer and its test vectors, so a scheme the server cannot run is never
671698
accepted.
672699
- **An inline key**: `secret`, `privateKey`, `secretKey` or `accessKeySecret`
673-
is refused by name, pointing at `credentialRef`. The vendors' words for the
674-
key id (`accessKeyId`, `secretId`) are answered with `keyId`. An authored `signature` is
675-
refused too: it is computed for every request.
700+
is refused by name, pointing at `credentialRef` (on `alipay-v3`,
701+
`appPrivateKey` too, and `alipayPublicKey` points at `vendorPublicKeyRef`).
702+
The vendors' words for the key id (`accessKeyId`, `secretId`, `appId`) are
703+
answered with `keyId`. An authored `signature` is refused too: it is computed
704+
for every request.
676705
- **`provider: 'mcp'`**, at `auth.type`: an MCP server is not signed per
677706
request. Declare `provider: 'rest'` or `provider: 'openapi'`.
678707

@@ -736,7 +765,9 @@ retry):
736765
| An `authorization_code` instance nobody connected, or whose grant the vendor refused | **Not fatal**: the instance registers **degraded**, "not connected" or "reconnect required" — connect it (`POST …/connectors/:name/connection`), and it materializes without a restart |
737766
| The token endpoint is unreachable or answers `5xx` / `429` | **Not fatal**: the dispatch fails `CONNECTOR_UPSTREAM_UNAVAILABLE` and is retried on the existing backoff; the connection stays connected |
738767
| `auth` is `signed` on a provider that does not say it applies the request signer | **Hard boot error** naming the instance, the scheme and the provider: its calls would go out unsigned — declare it through `rest` or `openapi` |
739-
| `credentialRef` resolves to nothing | **Not fatal**: the instance registers **degraded**, naming the reference — set it (`PUT …/credential-refs/:ref`), and it materializes without a restart |
768+
| `credentialRef` (or a `signed` instance's `vendorPublicKeyRef`) resolves to nothing | **Not fatal**: the instance registers **degraded**, naming the reference — set it (`PUT …/credential-refs/:ref`), and it materializes without a restart |
769+
| A `signed` reference resolves to something that is not a key of the scheme's kind (an `alipay-v3` key that is not RSA) | **Hard boot error** naming the instance and which reference cannot sign or verify, never the value |
770+
| A response to an `alipay-v3` call is unsigned (below status 400) or its signature does not verify | The dispatch **fails** naming the instance and the status; the response's body is never returned |
740771
| The upstream refuses the credential (`401` / `403`) | **Not fatal**: the instance registers **degraded**. A stored credential is marked `rejected` and waits until it is set again (or a restart); an environment or vault credential is retried on the backoff |
741772
| Instance `name` collides with a plugin-registered connector | **Hard boot error** — there is no silent precedence between the two worlds |
742773
| stdio transport without host opt-in | **Hard boot error** with the `declarativeStdio` fix inline — a security rejection is never retried |

‎content/docs/permissions/system-context.mdx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ bugs — it is the protection or the side effect that silently does not happen.
122122
| 18 | REST anonymous-deny seam satisfied | rest | Get: `enforceAuth` passes with no `userId`. Not reachable from the wire — `isSystem` is never set on an inbound request | `enforceAuth` |
123123
| 18b | Settings namespace read scope not applied | service-settings | Get: a read of `sys_setting`, `sys_setting_audit` or `sys_platform_setting` across every namespace, whatever each namespace's `readPermission` names — the settings service's own reads of its stores take this path. Lose: the per-namespace read gate the generic data API applies to every other caller, the same rule the settings routes enforce | `settingsReadDoorMiddleware` |
124124
| 18c | **Credential-column filter / sort refusal not applied** — a `where`, `orderBy`, per-aggregation `filter` or `expand` entry naming a `password` / `secret` column is evaluated | objectql | Get: the server's own match on a stored credential value, a lookup that never returns it. Every other caller, administrators included, is refused `400 INVALID_FIELD` for anything but a has-a-value check (ADR-0100). Lose: nothing a wire caller reaches — `isSystem` is never set on an inbound request | `rejectCredentialEvaluation` |
125+
| 18d | **Stored-metadata type read gate not applied** — a read of `sys_metadata` / `sys_metadata_history` is not narrowed by the capability its row's type requires | objectql | Get: every row of the family on `find` / `findOne` / `count` / `aggregate` — the metadata loader, the `/meta` doors' own store reads, boot (a read with no execution context is not narrowed either). Lose: the narrowing that serves a stored row of a capability-gated type (`datasource`, `external_catalog`) only to holders of the capability the `/meta` type gate names for it | `storedMetadataTypeReadFilter` |
125126

126127
### 2. Write pipeline and data integrity
127128

@@ -543,6 +544,7 @@ and CI refuses one that differs from what the tree produces.
543544
| `packages/objectql/src/engine.ts#insert` | 3 | 21, 22 |
544545
| `packages/objectql/src/engine.ts#isSystem` | 1 | 64 |
545546
| `packages/objectql/src/engine.ts#update` | 4 | 19, 20 |
547+
| `packages/objectql/src/stored-metadata-type-read-gate.ts#storedMetadataTypeReadFilter` | 1 | 18d |
546548
| `packages/observability/src/perf-timing.ts#isPerfDisclosurePrincipal` | 1 | 13 |
547549
| `packages/plugins/organizations/src/organizations-plugin.ts#start` | 1 | 62 |
548550
| `packages/plugins/plugin-approvals/src/approval-service.ts#comment` | 1 | 43 |

0 commit comments

Comments
 (0)