Skip to content

Commit 5e33224

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-sek37l
2 parents bc2269f + 0c64014 commit 5e33224

70 files changed

Lines changed: 3078 additions & 304 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: 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): the customizations read's `contributions` declares a `nav` kind, with an entry shape per kind
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
10+
`GET /packages/:id/customizations` names the package behind each item another installed package contributes to a base (ADR-0029 D7 as amended by ADR-0146 D3). `GetPackageCustomizationsResponseSchema.contributions` now declares a third kind: the navigation items another package's `navigationContributions` add to an app the base owns.
11+
12+
- **The entry shape is per kind.** A `field` or `flow` entry is `{ kind, object, name, packageId }` and accepts exactly what it accepted before. A `nav` entry is `{ kind: 'nav', app, group?, name, packageId }`: the base-owned app, the group the contribution targets when it targets one, the nav item's id, and the contributing package. Each shape is strict, so a `nav` entry carrying `object`, or a `field` entry carrying `app`, is refused.
13+
- **Order.** The `field` and `flow` entries come first, in the order they had before. The `nav` entries follow, by app, group (an entry with no group first), name and package. `counts.contributions` still counts items.
14+
- **Not served yet.** The runtime lists no `nav` entry yet. The kind is declared ahead of its producer, and its describe opens `[PLANNED — not yet served]`. No nav item gains a key: the served app's `navigation` stays as declared.
15+
16+
TypeScript code that reads `object` from a `contributions` entry now narrows on `kind` first (`entry.kind !== 'nav'`), because a `nav` entry has no `object`.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/service-automation': patch
4+
'@objectstack/runtime': patch
5+
---
6+
7+
feat(automation): an `oauth2` connector instance with `clientSource: 'installer'` connects with the OAuth client its administrator entered, and uses it for every exchange and refresh
8+
9+
Clause-②: yes (narrowing)
10+
Metadata-protocol: none
11+
Ruling-ref: 6566728470
12+
13+
<!-- adr-0087: not-required (no-migration-prescription) No metadata key, schema, accepted value, stored row, export or error code is removed or renamed: the begin leg of the connector connection door now refuses, with the existing 400 VALIDATION_ERROR, a request body it used to drop unread, so nothing stored changes and there is nothing for objectstack migrate meta to rewrite, and the two contract types only gain OPTIONAL members no existing caller has to send. -->
14+
15+
**BREAKING** for three call shapes only, each of which used to be answered as if its extra input were absent and now answers `400 VALIDATION_ERROR` with nothing connected, read or revoked: a `POST /api/v1/automation/connectors/NAME/connection` whose JSON body carries a key other than `clientId` and `clientSecret`; the same begin carrying a client for an instance that does not declare `clientSource: 'installer'`; and an in-process `getConnectorConnection` or `revokeConnectorConnection` call whose request carries `client`. What to do: nothing, unless a caller sends a body to that begin leg. Send none (or `{}`) for an instance that declares its client or leaves it to MCP dynamic registration, and send only `clientId` and `clientSecret` for one that takes the installer's.
16+
17+
An `authorization_code` instance that declares `clientSource: 'installer'` names no client in its definition. Its administrator registers a client with the vendor against this deployment's redirect URI and hands it to the connect door:
18+
19+
```http
20+
POST /api/v1/automation/connectors/esign/connection
21+
{ "clientId": "CLIENT_ID", "clientSecret": "CLIENT_SECRET" }
22+
```
23+
24+
- **What it does.** The authorization request, the code exchange and every refresh of that connection go out as the administrator's client. Each organization's connection keeps its own client, and a reconnect replaces it. On `provider: 'mcp'` over http the MCP authorization flow still discovers the endpoints, and registers nothing.
25+
- **Where the client is kept.** In the connection record's encrypted client column, the one that keeps a dynamically registered client. The id alone is also kept in plain text, so the connection's read shows `client: { source: 'installer', clientId, hasSecret }`. The secret is never answered back, logged or written to metadata.
26+
- **What it refuses (400 `VALIDATION_ERROR`).** A connect of such an instance with no client: the message names the instance and the vendor registration step, with the redirect URI to register. A client offered to an instance that declares its own client or leaves it to dynamic registration, which used to be ignored. A body key other than `clientId` and `clientSecret` on the begin leg, which used to be ignored too. A client on the read or revoke leg. No refusal repeats a value the caller sent.
27+
- **Contracts.** `ConnectorConnectionRequest` gains an optional `client`, and `ConnectorConnectionState` an optional `client`.
28+
- **Unchanged.** An instance that declares its `clientId`, or leaves its client to MCP dynamic registration, connects, exchanges and refreshes exactly as before, and a begin with no body is still a begin with no client.
29+
30+
The `clientSource` key no longer carries `[PLANNED — not enforced]`, and the liveness ledger's `connector/auth` row no longer calls it planned.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/metadata-protocol': minor
3+
'@objectstack/metadata-core': minor
4+
'@objectstack/service-automation': patch
5+
---
6+
7+
**BREAKING** fix(metadata)!: a flow whose loop is not closed by a `type: 'back'` edge is refused at publish, and its draft read says so, instead of publishing and then never registering
8+
9+
Clause-②: no (narrowing)
10+
Metadata-protocol: narrowed
11+
Skills-docs: none — `skills/objectstack-automation/references/state-machines-and-approvals.md` teaches that the resubmit edge MUST be typed `'back'` and that `registerFlow` rejects an unmarked cycle; both stay true, and the publish door now refuses the same flow earlier, in the same words.
12+
13+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped. The publish doors now refuse a flow body the automation engine already refused to register, with the engine's own sentence, so a published body that could never run has nothing a migration could rewrite it to: the author's fix is the one the refusal names. -->
14+
15+
**BREAKING** accept-set narrowing at the runtime authoring gate (`@objectstack/metadata-protocol`), which the metadata draft read and every publish door run (`PUT /api/v1/meta/:type/:name` live, `POST …/publish`, `POST /api/v1/packages/:id/publish-drafts`).
16+
17+
- **Before.** A flow whose top-level graph had a cycle with no edge typed `back` (ADR-0044) saved as a draft, its draft read answered `_diagnostics: { valid: true }`, and it published (`outcome: 'published'`). The automation engine then refused to register it ("Flow contains a cycle: push → check → revise → push. …"), so the flow never ran, and only the by-name read's `registration` and a boot-log warning said why.
18+
- **Now.** The draft still saves (drafts are never gated). Its read answers `valid: false` with an error coded `flow-undeclared-cycle` on the cycle-closing edge (`edges.N`), and every publish door refuses it `422 INVALID_METADATA` with that finding; a package publish refuses the batch (`outcome: 'refused'`). The message is the engine's registration refusal, word for word.
19+
- **One judge.** The cycle walk moved from `@objectstack/service-automation` to `@objectstack/metadata-core` as `findUndeclaredFlowCycle` (with `undeclaredFlowCycleMessage`). The engine's registration and the publish gate both call it, so they refuse exactly the same graphs; the engine's behaviour and message are unchanged. `@objectstack/metadata-core/testing` exports `FLOW_CYCLE_CORPUS`, the case table both faces are tested against.
20+
- **Unchanged.** A cycle with any of its edges typed `back` publishes and registers, as before. Cycles inside a `loop` / `parallel` / `try_catch` region are judged where they were. `os validate` and `os build` do not run this check yet.
21+
22+
**The remedy.** None for a flow that registers today: it publishes exactly as before. A flow this now refuses was already refused by the engine at registration and never ran; the refusal names the closing edge, and ADR-0044's `type: 'back'` on that edge is how an intended loop is declared. A stored flow published before this change is re-registered exactly as before (still refused by the engine).
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/driver-sql': patch
4+
'@objectstack/metadata-core': patch
5+
---
6+
7+
feat(spec): a lookup can declare that its target's name is visible to every reader of the record (`referenceNameVisible`)
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: added
11+
12+
`FieldSchema` declares one optional boolean, `referenceNameVisible`, on `lookup` and `user` fields. `true` declares that a principal who can read the record carrying the field may see the referenced record's display name (the target object's `nameField` value) and its id, even when that principal cannot read the referenced record. Nothing else of the target is exposed: `$expand` on the field returns the same name and id only, the field's `displayField` does not widen it, and the name renders as text with no link to the target. The same key covers `sys_user` targets. It is declared on the referencing field only, so each application decides whose names its own pages reveal; there is no target-object switch.
13+
14+
Absent or `false` keeps the current posture: a lookup whose target the reader cannot read shows no name. The key is refused at parse on any other field type, `master_detail` included, and an authored `false` off-type is refused too.
15+
16+
This is the contract half. No runtime serves the name yet, so the describe carries the `[PLANNED — not yet served]` marker and setting the key changes no read today. The server read and `$expand` branch and the console's text-only rendering follow in later changes. Nothing to do for existing metadata.
17+
18+
`@objectstack/driver-sql` and `@objectstack/metadata-core` classify the new key in their closed `FieldSchema` key tables (a presentation key that names no field), so the built-in column collision warning and the field-level-security projection keep covering every key.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/plugin-form': patch
3+
---
4+
5+
fix(plugin-form): an edit `ObjectForm` in its default layout whose record does not exist shows the record not-found state instead of an empty editable form
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`DataSource.findOne` answers `null` for a record that does not exist (the ObjectStack adapter maps the server's 404 to it). The default `object-form` layout seeded its form from that answer, so editing a deleted or unknown record opened a blank form with an enabled Update button, and the 404 surfaced only after submit. It now draws the record not-found state ("Record not found", the same copy the record page and the modal, drawer, tabbed and wizard forms show) with no fields and no submit, both on the first read and when a background re-read finds the record deleted. Create, and an edit of a record that exists, are unchanged.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): inside a package's second app, the sidebar and the `nav:menu` block link into that app
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`/apps/SEGMENT` opens a package's first app when SEGMENT is the package id, and a package can ship more than one app. The sidebar built every entry link, and the route its area election reads, on the package id, so inside a package's second app a click on any entry opened the first app. The `nav:menu` page block built its links the same way. Both now build on the app's one address among the metadata app list (`appAddressSegment`, the address the app page resolves its own links with): the package id when it reaches the app, else the app's name. A package's first or only app keeps its package-id links unchanged.

‎content/docs/api/metadata-api.mdx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ never overridden; it is copied under a new name. What the reads then serve is in
109109
[Metadata lifecycle](/docs/concepts/metadata-lifecycle#customizing-an-installed-package-the-paired-custom-override-adr-0146).
110110
`POST /packages/hotcrm/ensure-customization` creates the pair when absent (Studio calls
111111
it on the first change), and `GET /packages/hotcrm/customizations` lists its overrides,
112-
extensions and switched-off items in one read.
112+
extensions and switched-off items in one read, beside `contributions`: the fields and flows
113+
other installed packages contribute to `hotcrm`, each entry naming its package. Its `nav` kind
114+
(a navigation item another package contributes to a `hotcrm` app) is declared and not yet served.
113115

114116
**Body**: `{ type: "object", name: "account", item: { ... } }`
115117
**Response**: `{ success: true }`

‎content/docs/automation/connectors.mdx‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,11 @@ What the publish door checks, each refusal located at its key under `auth`:
558558
`authorization_code`. The one exception is `provider: 'mcp'` over an `http`
559559
transport, which may omit the endpoints and the client id: the MCP
560560
authorization flow discovers the server's metadata and can register the
561-
client. `client_credentials` has no authorization step, so
561+
client. An `authorization_code` instance may instead declare
562+
`clientSource: 'installer'` and name no `clientId` and no `credentialRef`:
563+
the administrator of each deployment registers their own client with the
564+
vendor and enters it when they connect (below); a `clientId` beside it is
565+
refused. `client_credentials` has no authorization step, so
562566
`authorizationUrl` is refused there, and it requires `credentialRef`.
563567
A `credentialRef` without its `clientId` is refused. An inline
564568
`clientSecret`, `refreshToken` or `accessToken` is refused by name, pointing
@@ -640,6 +644,17 @@ a rotating refresh token is never spent twice.
640644
DELETE /api/v1/automation/connectors/:name/connection → revoke
641645
```
642646

647+
An instance declaring `clientSource: 'installer'` takes its client in the
648+
connect's body, `{ "clientId": "…", "clientSecret": "…" }` (omit
649+
`clientSecret` for a public client). Register that client with the vendor
650+
first, with the redirect URI `<origin>/api/v1/automation/connectors/oauth/callback`.
651+
The authorization request, the code exchange and every refresh of that
652+
organization's connection then go out as that client, and a reconnect may
653+
bring a new one. The connection keeps it encrypted; the status read shows
654+
`client: { source: 'installer', clientId, hasSecret }`, never the secret. A
655+
connect of such an instance without a client, a client offered to any other
656+
instance, and any other body key are refused `400 VALIDATION_ERROR`.
657+
643658
**In the Console**, **Setup → Integrations → Connector Connections** drives
644659
these calls; the entry is shown to holders of `manage_connector_connections`.
645660
It lists each `authorization_code` instance with the organization's status,

0 commit comments

Comments
 (0)