You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 5e33224
Browse filesBrowse the repository at this point in the historyBrowse files
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`.
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
-**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.
**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).
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.
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.
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.
0 commit comments