Skip to content

Commit b93069e

Browse files
committed
Merge origin/main into claude/project-thread-s2vcdk (#30892)
Brings main's attach fix (members attach files again). The tenant-audit census conflicted again: main's artefacts taken, regenerated over the merged tree (267 write call sites), and the page's hand-stated figures moved to match. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzuTqmB5hqFYmMATiY
2 parents c2a3229 + 5210acf commit b93069e

158 files changed

Lines changed: 12114 additions & 3352 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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@object-ui/types': patch
3+
---
4+
5+
refactor(types)!: delete `ActionExecutor` and the Batch Operations pair nothing reads
6+
7+
Clause-②: no (narrowing)
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (runtime-export-retired @object-ui/types#ActionExecutor, @object-ui/types#BatchOperationConfig, @object-ui/types#BatchOperationSummary) the retired operands are TypeScript-only shapes declared in `ui-action.ts`: a function type for an action executor and a batch-operation config and summary pair that no renderer, adapter or host reads; nothing in the workspace or in hotcrm imports any of them from the package entry, they have no zod twin, they are not arms of `AnySchema`, and no packages/spec schema, registered node type, conversion surface or stored shape names them, so there is nothing for `objectstack migrate meta` to rewrite -->
11+
12+
**BREAKING** for a host that imports any of these three types from `@object-ui/types`. None had an importer anywhere in the workspace or in hotcrm, so the Console and every `@object-ui/*` package are unaffected.
13+
14+
They are **gone from the package, not only from the entry**: `src/ui-action.ts` no longer declares them. They were types only, so the built JavaScript is unchanged. The "Transaction Support" block beside them stays (`TransactionIsolationLevel`, `TransactionConfig`, `TransactionResult`; `@object-ui/core`'s `TransactionManager` reads them).
15+
16+
What leaves, and what to write instead:
17+
18+
- FROM `ActionExecutor` TO no replacement in this package. Actions run through `@object-ui/core`'s `ActionRunner`, which registers `ActionRunnerHandler` functions; a host that typed its own executor declares that signature locally.
19+
- FROM `BatchOperationConfig` TO no replacement. No renderer or runner read a batch-operation config; a host that kept one declares it locally.
20+
- FROM `BatchOperationSummary` TO no replacement. Nothing produced this summary; a host that kept one declares it locally.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/console': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(console): a settings field an environment variable pins offers to clear the value saved beneath the pin
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
- **`@object-ui/console`:** on a Setup settings page, a field an `OS_*` environment variable pins still shows the lock badge and stays read-only. When the namespace read lists a value stored beneath the pin at the scope the page writes (a secret's as the read mask), the row also says that a value is saved there and that the environment value stays in force, and offers "Clear the saved value". The control asks first, then writes `null` for that one key, the one write the server lets through a pin; the page's unsaved edits are left as they are. After the clear the row still reads the environment value, and the saved value no longer comes back when the variable is removed. A pinned field with nothing stored at that scope, a field no variable pins, and the first-run checklist show no control.
12+
- **`@object-ui/i18n`:** four new `console.settingsView` keys in all eleven packs: `savedBeneathPin`, `clearSaved`, `clearSavedConfirm` and `savedCleared`.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(console): Connect an Agent lists the AI assistants you authorized and your API keys
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
- **`@object-ui/app-shell`**: under the setup steps on Connect an Agent (which sits in Account's AI Assistant group), a "What can act as you" card lists the assistants the signed-in person authorized on the OAuth consent page, read from `oauth.authorizedClients.list()`: the client's name (its client id beside a fallback when it declares none), each granted scope in the consent page's own words, when it was authorized, when it was last used (the server records the hour, so it reads "in the hour from …") or that no use is recorded, and the organization only when it is not the active one. Assistant rows carry no Revoke yet; the card says so. Below them, the person's own API keys (`sys_api_key` rows filtered to the signed-in user, so a platform administrator sees their own keys here too): name, prefix, created, last used or never used, and the revoked or expired state. An active key's Revoke asks first, naming the key, then writes `revoked: true`, which ends the key's access at once; the list re-reads in place, and a key minted on the same page appears without a reload.
12+
- **`@object-ui/i18n`**: the new `connectAgent.access.*` strings in all eleven packs. Scope wording reuses the existing `oauth.consent.scope.*` keys.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(app-shell): Connect an Agent marks claude.ai (web) and Doubao cloud tasks as not available on an address that is not public HTTPS
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
claude.ai on the web and Doubao Work's cloud tasks connect from their vendor's servers, which reach only a public HTTPS address. When the address Connect an Agent shows is an `http://` address, `localhost`, a single-label or `.local` host, or a private, loopback or link-local IP, those two ways in now carry a "not available for this deployment" note with the reason in one sentence (naming the address) and what makes them available (serving the system at a public HTTPS address). Claude Desktop, Claude Code, Codex, Cursor and the other clients stay as they are, and on a public HTTPS address nothing changes. The five new strings ship in every console language pack, Traditional Chinese generated from Simplified.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(app-shell): Connect an AI Assistant opens with what a connected assistant can do, in plain words
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
The page now opens with two plain sentences: once connected, your AI assistant can look up, create, change and delete the records you are allowed to, and run this system's actions, always as you. Three example requests follow, each with a copy button, plus a line on which one to try first to see that the connection works. The page cannot yet tell whether you have connected, so that line is shown to everyone. The address card and the Claude Code step no longer speak of MCP or OAuth. Protocol words stay only where a client's own menu or form uses them. The steps that fall back to an API key now point to the "Advanced: scripts and CI" section where the key is created. Seven new strings and the reworded ones ship in every console language pack, with Traditional Chinese generated from Simplified.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/client': patch
3+
---
4+
5+
feat(client): `client.oauth.authorizedClients.list()` and `.listForUser(userId)` read the AI assistants a person has authorized and when each was last used
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: none
9+
10+
`list()` calls `GET /api/v1/auth/authorized-clients` with no parameter: the caller's own grants. `listForUser(userId)` calls `GET /api/v1/auth/admin/authorized-clients?userId=...` and is answered for a platform administrator only. Both unwrap the `{ success, data }` envelope to the new exported `AuthorizedClientsList` type, `{ clients: AuthorizedClient[]; truncated: boolean }`. `AuthorizedClient.lastUsedAt` is an hour start, `null` when no use has been recorded. A refusal throws, as every other method does. Nothing existing changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
feat(platform-objects): `sys_oauth_consent` gains `last_used_at`, the start of the most recent hour in which the client made a verified MCP call under the consent
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: none
9+
10+
A nullable, read-only datetime on the better-auth-managed consent table, owned by ObjectStack. The OAuth provider neither reads nor writes it. Its one writer is plugin-auth, server-side, after it verifies an MCP access token minted under the consent. The object stays off the data API (`apiEnabled: false`), so no client reaches the column. A withdrawn or revoked consent takes the stamp with it. Schema sync adds the column, and existing rows read `null`.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/plugin-auth': patch
3+
---
4+
5+
feat(plugin-auth): a person can read the AI assistants they have authorized and when each was last used, and a platform administrator can read another person's
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: none
9+
10+
Two new reads, both ObjectStack mounts ahead of the better-auth catch-all and both in `AUTH_ROUTE_LEDGER`:
11+
12+
- `GET /api/v1/auth/authorized-clients` answers the signed-in person's own OAuth consents (Claude Desktop, an IDE agent, any MCP client). It is keyed by the session and nothing else: it takes no query parameter, and one that is sent (`?userId=` included) is refused `400 VALIDATION_ERROR` rather than ignored.
13+
- `GET /api/v1/auth/admin/authorized-clients?userId=ID` answers another person's, for a platform administrator only. It is gated exactly as the admin session doors are (ADR-0068 D2, a held session refused per ADR-0069 D3). The gate runs before the query is read, so anyone else (an organization owner or admin included) gets one `403 PERMISSION_DENIED` whether or not the person exists. It lists every organization the person granted in. A missing, empty or repeated `userId` is `400`, and an id naming no user is `404 RESOURCE_NOT_FOUND`. It is read only.
14+
15+
Each row carries exactly `id` (the consent id, the key `POST /oauth2/delete-consent` takes), `clientId`, `name`, `scopes`, `organizationId`, `authorizedAt` and `lastUsedAt`. Nothing in it identifies or reconstructs a token. The answer is newest first, at most 200 rows, with `truncated: true` when the person holds more, and it is sent with `Cache-Control: no-store`.
16+
17+
`lastUsedAt` is the start of the most recent clock hour in which the client made a verified MCP call under that consent, or `null` when none has been recorded. The server writes it after `verifyMcpAccessToken` admits a token, and only on the consent that admitted it: the consent for the token's client, person and organization, created no later than the token was issued, the rule the verifier's own consent read applies. So a revoked consent, or a token of an earlier grant, stamps nothing. The write is off the request path, at most once per consent per hour in each process (several processes may each write the same hour start), from the verified token and the server clock alone. A store failure never fails the MCP call, and each distinct failure is logged once per process at `error`; a consent withdrawn between the stamp's read and its write is not a failure. It lands in the new `sys_oauth_consent.last_used_at` column, so a deployment picks it up at its next schema sync. Until then the reads answer `500` and the stamp logs that error once per process.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/plugin-audit': patch
3+
'@objectstack/rest': patch
4+
---
5+
6+
feat(audit): every data export writes one run-level `sys_audit_log` row, and the `export` action comes back with its writer
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
`GET /data/:object/export` used to leave the compliance ledger nothing to say: a user could download up to 50,000 rows of any object they may export, `sys_audit_log` included, and no row recorded who, what or how much. Each export the door streams now writes one row with `action: "export"`, the object, `record_id: null` and the exporter (user, actor, tenant, and the agent or impersonator when there is one). Its `metadata` carries the format, the row count, the effective and requested limit and whether the 50,000 cap clipped it, whether the file was truncated at the limit, a bounded summary of the filter (field names and operators, at most 32 terms, never a comparand), whether a search term narrowed it (never the term) and the outcome. An export whose read fails after the checks records `outcome: "failed"` with the status and machine code it answered.
12+
13+
A request refused before anything is read (no export permission, a bad parameter, an unparseable filter) writes no row; neither does the import template. The row is written after the file is finished, so the audit write never changes or delays the download; a ledger write that fails is logged at `error` once per cause and the export is unaffected.
14+
15+
`export` is declared again on `sys_audit_log.action` (it was retired under #8147 because nothing wrote it) and is shown by the `config_changes` list view beside `config_change` and `import`. The writer lives on the `audit` service slot as `recordDataExport`, beside `recordAuthEvent` and `recordActionInvocation`; a kernel without the audit plugin exports exactly as before.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
feat(spec): the published JSON Schema states the marketplace taxonomy facets' no-duplicates rule as `uniqueItems: true`
6+
7+
Clause-②: no (narrowing) — the published `json-schema/**` declarations of the two taxonomy facets, and of the six schemas that carry them, refuse a list that repeats a value, which they admitted before. The parse already refused it; nothing is widened and no key is added, removed or renamed.
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 type or stored shape moves: the set of documents the parse accepts is byte-for-byte what it was, and the lists the published files stop admitting are lists the parse already refused. An author lists each facet value once; there is nothing for `objectstack migrate meta` to rewrite and no tombstone. -->
11+
12+
**BREAKING (published artifact narrows)** — a validator reading `packages/spec/json-schema/**` (an editor, codegen, an external check) now refuses a duplicated `businessFunctions` or `industries` value at 14 sites in 8 published schemas.
13+
14+
`businessFunctions` and `industries` refuse a repeated value at parse time (`['sales', 'sales']` is refused with "`businessFunctions` lists a value more than once; list each value once."), but `packages/spec/json-schema/**` dropped that rule, so an editor or an external validator reading the published files accepted the duplicate. The files now say `uniqueItems: true` on both facets, wherever they appear: `marketplace/MarketplaceBusinessFunctions`, `marketplace/MarketplaceIndustries`, and the `businessFunctions` / `industries` properties of `marketplace/Package`, `marketplace/CreatePackageRequest`, `marketplace/UpdatePackageRequest`, `marketplace/MarketplaceListing`, `marketplace/TemplateManifest` and `marketplace/MarketplaceSearchResponse` (under `items`).
15+
16+
**Not a behaviour change.** The runtime accepts and refuses exactly what it did before; only the published file catches up. A document the published file now refuses was already refused by the parse.
17+
18+
**How.** The closed refinement projection (`src/shared/refinement-projection.ts`) gains one arm, `unique-items`, and a declared predicate for it, `UNIQUE_SCALAR_ITEMS`, which the taxonomy's list helper now uses in place of an inline copy of the same expression. The arm is exact only on lists of JSON scalars: a `Set` compares strings, numbers, booleans and `null` the way `uniqueItems` does (`0` and `-0` are one value to both, `"1"` and `1` are two), but compares objects by identity, so the predicate's parameter type is `readonly JsonScalar[]` and composing it on a list of objects does not compile.
19+
20+
**The ledger shrinks.** `packages/spec/dropped-refinements.baseline.json` loses the eight entries above (14 sites), from 1037 dropped sites in 260 published schemas to 1023 in 252.

0 commit comments

Comments
 (0)