Skip to content

Commit 317b646

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-1bmr1f
2 parents 5e49fa7 + 5374967 commit 317b646

81 files changed

Lines changed: 2638 additions & 199 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: 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.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/service-datasource': patch
3+
'@objectstack/cli': patch
4+
'@objectstack/spec': patch
5+
---
6+
7+
feat(service-datasource): a datasource defined in code reads its credential at connect from `OS_DATASOURCE_<NAME>_PASSWORD`
8+
9+
Clause-②: yes (widening) — `@objectstack/service-datasource` exports `datasourceEnvCredentialVar`, `resolveDatasourceEnvCredentialsSwitch`, `DATASOURCE_ENV_CREDENTIALS_SWITCH` and the `DatasourceEnvCredentialSource` type, and `DatasourceConnectionServiceConfig` gains the optional `envCredentials`; `@objectstack/spec/data` exports `CODE_DEFINED_CREDENTIAL_ROUTE`. Nothing an author writes is added or refused.
10+
Metadata-protocol: none
11+
12+
A datasource declared in code (`defineDatasource`, or a package's `*.datasource.ts`) had no way to get its password: the driver contracts refuse `config.password` and a `user:password@` URL, the Setup → Datasources form cannot edit a code datasource, and nothing mints a `sys_secret:` handle for one. Now the credential stays out of the declaration and is read at connect from a variable derived from the datasource name: `warehouse` reads `OS_DATASOURCE_WAREHOUSE_PASSWORD` (upper-cased; every character outside `A-Z0-9` becomes `_`). The value is the connection's secret: the password for postgres, mysql and mongodb, the `authToken` for turso.
13+
14+
- **A host switch.** `OS_DATASOURCE_ENV_CREDENTIALS` takes `on` or `off`; any other value refuses the boot with a sentence. `os serve` (and so `os start` and `os dev`) turns it on unless the operator set it. Every other composition leaves it off, so a host that loads several tenants' artifacts into one process never reads the variable.
15+
- **Code datasources only, by provenance.** The variable is read on the boot's declared connect, for a name in the host's code-datasource set: the booted artifact's datasources and those of a package installed locally. A datasource created through Setup or the API under the same name never reads it, and neither does the `default` datasource, which keeps `OS_DATABASE_URL`. A record's own `origin` is never consulted.
16+
- **A declared credential wins.** With `external.credentialsRef` or `external.connector` declared the variable is not read, and a reference that fails to resolve stays fail-closed.
17+
- **Unset.** The connect goes ahead without a secret as before; when the database then refuses, the reason names the variable to set.
18+
- **Two names deriving one variable** are refused at their connect.
19+
- **Who can read it.** Any code datasource the host registers under a name reads that name's variable, including one in an installed package. Install only packages you trust with the credentials in the process environment.
20+
21+
The credential refusals (`config.password`, a URL userinfo password, a credential query parameter, the mongo `options` passthrough) and the two `password` guidance entries on `DatasourceSchema` / `ExternalDatasourceSettingsSchema` end with one shared sentence naming this route, after the Setup and `external.credentialsRef` routes they already name. `planCredentialMigration`'s remedy for a code-defined row names the row's variable.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
feat(platform-objects): the platform identity actions an agent may reach declare it — additive acts exposed, destructive lifecycle acts exposed behind confirmation, credential and ownership acts human-only
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The `sys_*` identity objects' actions now carry the ADR-0011 `ai` block by risk, as ADR-0011's 2026-10-30 addendum records (58 actions assessed, 38 declared):
11+
12+
- **Exposed (21).** Additive or self-scoped acts declare `ai: { exposed: true, description }`: the four `invite_user` mirrors, resending and accepting an invitation, adding a member to an organization or a team, creating, editing and renaming a team or an organization, switching the active organization, re-enabling an OAuth application, enabling two-factor authentication, updating your own profile, resending the verification email, and requesting SSO domain verification.
13+
- **Exposed, confirmation required (17).** Destructive or privilege-changing lifecycle acts also declare `requiresConfirmation: true`, so MCP `run_action` refuses them `428 ACTION_CONFIRMATION_REQUIRED`, with nothing run, until the call carries `confirm: true`: banning, unbanning and unlocking a user, setting a user's manager, revoking an API key or a session, signing out other devices, cancelling or declining an invitation, removing a member or a team member, deleting a team, an OAuth application or an SSO provider, disabling an OAuth application, leaving an organization, and unlinking a sign-in provider.
14+
- **Human-only (20).** Acts that mint a credential or a session, change ownership, a role or a sign-in factor, or destroy an account or an organization (impersonating a user, setting a password, creating a user or an API key, transferring ownership, changing a member's role, deleting an organization, and the rest the addendum names) declare no `ai` block and are not listed over MCP. `sys_invitation.copy_invitation_link` declares none either; it was not part of the assessment.
15+
16+
Over MCP these actions are listed and run behind every existing gate: the capability gate, the installation's action switch, the param contract, the confirmation gate and the identity route's own authorization. They are `type: 'api'` actions forwarded to identity routes that accept a signed-in session only, so an agent connected over OAuth sees them listed and is refused at the route when it runs one.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec): a calendar view can declare the date it opens on, `calendar.initialDate`
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
10+
A calendar view used to open on the current month, always, so a calendar whose records all fall months ahead (a renewals calendar) opened on an empty grid and the author had no key to say otherwise. `CalendarConfigSchema` (the list view's `calendar` block, and the `object-calendar` node's `calendar` prop, which is the same block by reference) gains one optional key:
11+
12+
- **`initialDate`, one of two forms.** A record anchor: `'first_record'` opens on the period containing the earliest start date (the `startDateField` value) among the records the view selects, and `'next_record'` on the period containing the earliest start date on or after today among those records; when no record qualifies, the calendar opens on today. Or a wrapped date-macro token, the same formula vocabulary filter values and a dashboard date filter's `defaultValue` already accept (`DATE_MACRO_TOKENS` and the parameterised `{N_unit_ago}` / `{N_unit_from_now}` grammar): `'{today}'`, `'{next_month_start}'`, `'{current_quarter_start}'`, `'{3_months_from_now}'`, resolved each time the calendar opens. The period is the month, week or day the active view mode shows.
13+
- **Absent means today.** The key has no spec-side default, so a calendar stored before it existed parses to exactly the block it stored and opens where it always did. There is no bare `'today'`: `'{today}'` is its one spelling.
14+
- **What is refused.** A fixed calendar date (`'2026-11-01'`) or timestamp, an unwrapped token (`'today'`), an unknown token (`'{next_moon}'`) and any other spelling are refused at `calendar.initialDate`. The message names both forms and points to `DATE_MACRO_TOKENS`, and a bare known token is told the wrapped spelling to write. A fixed date goes stale as time passes; a macro does not.
15+
16+
The Console renderer reads the key in a follow-up change; until then a calendar that declares it still opens on today.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): the field-types guide names the backfill for an amount switched to record mode; the dashboards guide says a record-mode amount aggregate is served only grouped by its code field
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **`objectstack-data/rules/field-types.md`**: an amount field with stored rows that is switched to `currencyMode: 'record'` is backfilled with `os migrate record-currency-codes`. The derived-measure sentence moves out of this guide; it already sits in the dashboards guide, which the line now points to.
11+
- **`objectstack-ui/rules/dashboards.md`**: a record-mode amount refuses `sum` / `avg` / `min` / `max` unless grouped by its code field. The old "ungrouped" read as if any grouping would do.

‎content/docs/ai/connect-mcp.mdx‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,20 @@ can reach is the **default exposure policy (v1)**:
261261
The stored-metadata tables (`sys_metadata`, `sys_metadata_history`) stay
262262
refused even when flagged. Each action `list_actions` returns carries
263263
`aiExposed: true`, the server's attestation of that opt-in.
264+
- **The platform's own identity actions declare it by risk.** Additive acts
265+
(inviting someone, adding a member to a team, creating or renaming a team or
266+
an organization, re-enabling an OAuth application) are exposed. Destructive
267+
lifecycle acts (banning a user, revoking a session or an API key, removing a
268+
member, deleting a team, leaving an organization) are exposed with
269+
`ai.requiresConfirmation: true`, so `run_action` refuses them until the call
270+
carries `confirm: true`. Acts that mint a credential or a session, change
271+
ownership or a sign-in factor, or destroy an account or an organization
272+
(impersonating a user, setting a password, creating an API key, transferring
273+
ownership, deleting an organization) declare nothing and are not listed. The
274+
full list is ADR-0011's 2026-10-30 addendum. These actions forward to
275+
identity routes that accept a signed-in session only, so an agent connected
276+
over OAuth sees them listed, but a call that reaches the identity route is
277+
refused there (`401`) and changes nothing.
264278

265279
<Callout type="info">
266280
This is the default policy, not a ceiling. A finer, **metadata-declared** exposure

‎content/docs/automation/connectors.mdx‎

Lines changed: 21 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'`.
@@ -579,6 +597,7 @@ retry):
579597
| 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 |
580598
| 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 |
581599
| Wrong spec URL (other 4xx) or unparseable document | Configuration fault — **hard boot error** |
600+
| 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 |
582601
| 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 |
583602

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

‎content/docs/data-modeling/drivers.mdx‎

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -944,11 +944,13 @@ export default defineStack({
944944
```
945945

946946
Each `url` must be credential-free when the stack is published: a
947-
`user:password@` URL is refused, like an inline `config.password`. Bind the
948-
password through `external.credentialsRef` instead, keeping the user in the URL
949-
(`mongodb://svc@host/db`): the bound secret is injected beside it at connect. The
950-
[read-only example](#read-only-grant-it-at-the-database-not-in-metadata) below
951-
shows the binding.
947+
`user:password@` URL is refused, like an inline `config.password`. Keep the user
948+
in the URL (`mongodb://svc@host/db`) and supply the password at runtime: a
949+
datasource declared in code reads it at connect from `OS_DATASOURCE_<NAME>_PASSWORD`
950+
(`OS_DATASOURCE_ANALYTICS_PASSWORD` here), which `os serve` / `os start` honour by
951+
default, and the value is injected beside the user. A datasource created through
952+
Setup binds it through `external.credentialsRef` instead. See
953+
[Credentials](/docs/data-modeling/external-datasources#4-credentials).
952954

953955
Then in your object definition:
954956

@@ -990,19 +992,21 @@ export const Analytics = defineDatasource({
990992
driver: 'postgres',
991993
// The connection itself cannot write. Nothing in the app can talk past it.
992994
config: { host: 'db.internal', database: 'analytics', username: 'analytics_ro' },
993-
// The account's password, bound through the secret store (see below).
994-
external: { credentialsRef: 'sys_secret:9f2c…' },
995+
// The account's password is read at connect from OS_DATASOURCE_ANALYTICS_PASSWORD.
995996
active: true,
996997
});
997998
```
998999

9991000
The read-only account's password never goes in `config`: the driver contract
1000-
refuses `config.password`, and a `user:password@` URL, at publish. Bind it
1001-
instead. The Setup → Datasources connection form's secret field encrypts it into
1002-
`sys_secret` and stores the opaque handle at `external.credentialsRef`, or you
1003-
write a handle the secret store minted there yourself. `credentialsRef` is the
1004-
one `external` key a `managed` datasource may carry; every federation key is
1005-
still refused there. See
1001+
refuses `config.password`, and a `user:password@` URL, at publish. Because this
1002+
datasource is defined in code, the runtime reads the password at connect from
1003+
`OS_DATASOURCE_ANALYTICS_PASSWORD`, derived from its name. The CLI host
1004+
(`os serve` / `os start`) reads it by default; elsewhere the host turns it on
1005+
with `OS_DATASOURCE_ENV_CREDENTIALS=on`. The same datasource created through
1006+
Setup → Datasources binds the password instead: the connection form's secret
1007+
field encrypts it into `sys_secret` and stores the opaque handle at
1008+
`external.credentialsRef`. `credentialsRef` is the one `external` key a `managed`
1009+
datasource may carry; every federation key is still refused there. See
10061010
[Credentials](/docs/data-modeling/external-datasources#4-credentials).
10071011

10081012
Note that a read-only account also refuses **DDL**, so such a datasource cannot

0 commit comments

Comments
 (0)