Skip to content

Commit b7745ef

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-t38h8d
2 parents f5e41e5 + 4bc077d commit b7745ef

27 files changed

Lines changed: 760 additions & 55 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/skills': patch
4+
---
5+
6+
docs(spec,skills): an `object-metric` tile's `polarity` is declared against the aggregate before `invert`, and `pages.md` points at it
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
- **Before.** The `polarity` description said the badge arrow shows which way the number moved. Under `invert: true` the tile shows `1 - value`, so there are two numbers, and the text did not say which one the arrow and `polarity` read. The `objectstack-ui` skill's `rules/pages.md` taught the inline `object-metric` without naming `compareTo` or `polarity`.
12+
- **Now.** The `polarity` description (and the reference page generated from it) says the badge and `polarity` read the aggregated value before `invert`, so `polarity` is declared against the raw value: an error rate shown as uptime is `lower`. The `object-metric` row in `rules/pages.md` names `polarity` and links to the `compareTo` section of `rules/dashboards.md`. The pointer is paid for by deleting three restatements in the same file; the file stays at its token count and no ceiling moves.
13+
- **Unchanged.** No schema, accepted value, default or renderer behaviour moves.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@object-ui/fields': patch
3+
'@object-ui/components': patch
4+
'@object-ui/types': patch
5+
'@object-ui/plugin-form': patch
6+
'@objectstack/spec': patch
7+
---
8+
9+
feat(fields): a lookup pick fills the fields its `lookupAutofill` declares, in the record form and in the line-item grid
10+
11+
Clause-②: yes (widening)
12+
Metadata-protocol: none
13+
14+
`FieldSchema.lookupAutofill` (`[{ target, source, overwrite? }]` on a single-value `lookup` / `master_detail`) now has its reader. Picking a record writes each declared `target` of the record being edited from the picked record's `source` — a quote line's `unit_price` from the picked product's `list_price` — before save, where a server hook used to be the only filler.
15+
16+
- **The overwrite rule is the spec's.** Without `overwrite: true` a pick fills a target only while it is empty or still holds the value an earlier pick in the same form (or grid row) filled in; a value the user typed, or one the record already stored, stays. A pair declaring `overwrite: true` replaces the target on every pick. A `source` the picked record does not carry (a field the reader may not read is not served) writes nothing.
17+
- **Record form.** `LookupField` applies the pairs on every single-value pick path: the dropdown, the keyboard, quick-create and the browse-all dialog. The form renderer hands it a new widget prop, `onAutofill(patch)`, which writes the targets as edits of the form (`setValue`, dirty), so the filled values are saved with the record.
18+
- **Line-item grid.** `GridField` takes the child object's pairs through a new host prop, `lookupAutofill` (lookup field name → pairs, built with the new `lookupAutofillByField`), which `MasterDetailForm` and `LineItemsPanel` pass from the child schema they load. The column key `autofill` is unchanged and still governs only the same-named copy; a declared pair applies whatever it says and takes its target out of that copy, so one target has one writer per pick. A pick from the grid's browse-all dialog now reaches the grid once, with its record, so both copies run on that path too.
19+
- **Types.** `LookupFieldMetadata.lookupAutofill` is declared by reference to the spec. New exports from `@object-ui/fields`: `declaredLookupAutofillPatch`, `lookupAutofillByField`, `LookupAutofillPair`, `LookupAutofillByField`; `lookupAutofillPatch` takes an optional fourth argument carrying the declared pairs.
20+
- **Liveness.** The `lookupAutofill` row of `@objectstack/spec`'s field ledger moves from `planned` to `live`, citing these readers.
21+
22+
A master-detail collection whose relationship and every column are authored loads no child schema, so its grid fills only the same-named copy.

‎content/docs/automation/connectors.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ shape on a provider-bound instance —
415415
// with `auth: { type, credentialRef }` instead (ADR-0097 §3)."
416416
authentication: { type: 'bearer', token: 'sk_live_9f…' },
417417

418-
// ✓ a reference, resolved at boot:
418+
// ✓ a reference, resolved at materialization (unset, it degrades the instance):
419419
auth: { type: 'bearer', credentialRef: 'BILLING_API_TOKEN' },
420420
```
421421

‎content/docs/references/ui/component.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1222,7 +1222,7 @@ Sort field and direction pair
12221222
| **trend** | `{ value: number; label?: string \| Record<string, string>; direction?: Enum<'up' \| 'down' \| 'neutral'> }` | optional | Static trend badge `{ value, label?, direction? }` — `value` painted as a percentage, `direction` up / down / neutral. A `compareTo`-derived trend replaces it |
12231223
| **drillDown** | `{ enabled?: boolean; title?: string; target?: Enum<'drawer' \| 'dialog' \| 'navigate'>; columns?: string[]; … }` | optional | Click-through drill config `{ enabled?, title?, target?, columns?, maxRows?, report? }` — opens the records behind the number, scoped by the metric's own `filter` — or draws the report `report` defines; a present block is on unless `enabled: false`. `filter` and `mode` are refused: a metric tile has no click context and no row |
12241224
| **compareTo** | `{ kind: Enum<'previousPeriod' \| 'previousYear'> }` | optional | Period-over-period comparison `{ kind }` — `previousPeriod` or `previousYear`, shifting the date macros in the tile's own `filter`. `dimension` is refused: this tile never reads a dataset time dimension |
1225-
| **polarity** | `Enum<'higher' \| 'lower' \| 'neutral'>` | optional | Which direction of change is good news: higher (rise green, fall red), lower (fall green, rise red) or neutral (no good/bad colour). Colours the trend badge, computed (`compareTo`) or static (`trend`); undeclared keeps rise green, fall red |
1225+
| **polarity** | `Enum<'higher' \| 'lower' \| 'neutral'>` | optional | Which direction of change is good news: higher (rise green, fall red), lower (fall green, rise red) or neutral (no good/bad colour). Colours the trend badge, computed (`compareTo`) or static (`trend`); undeclared keeps rise green, fall red. The badge and polarity read the aggregate before `invert`, so declare polarity against the raw value: an error rate shown as uptime is lower |
12261226

12271227
### Nested Shape: `ObjectMetricProps.aggregate`
12281228

‎docs/adr/0097-declarative-connector-instances.md‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
**Builds on**: [ADR-0015](./0015-external-datasource-federation.md) (open mechanism / enterprise lifecycle split), [ADR-0018](./0018-unified-node-action-registry.md) (`connector_action` baseline dispatch + `engine.registerConnector()`), [ADR-0022](./0022-connectors-vs-messaging-channels.md) (Connector = transport/integration mechanism), [ADR-0023](./0023-openapi-to-connector-generator.md) (OpenAPI → Connector generator), [ADR-0024](./0024-mcp-connectors.md) (MCP servers as connectors)
66
**Tracking**: framework#2977 (supersedes the interim descriptor-only contract from framework#2612)
77
**Consumers**: `@objectstack/spec` (`stack.zod.ts`, `integration/connector.zod.ts`), `@objectstack/service-automation` (boot audit → provider binding), `@objectstack/connector-openapi`, `@objectstack/connector-mcp`, `@objectstack/connector-rest`, the Studio flow palette, AI authoring (stack generation)
8-
**Amended by**: [ADR-0143](./0143-outbound-connector-auth-is-open-tier.md) (2026-10-27) — OAuth2, the vendor app-credential exchange and request signing join `ConnectorInstanceAuthSchema` in the open tier, and those kinds resolve per dispatch; see the notes under §3 and under "Open / enterprise line". PR #26798 (2026-10-27, #26709 item 3(b)) — a credential the MCP server rejects (HTTP `401` / `403`) is a fatal configuration fault, not upstream-unavailable; see the note under "Upstream availability". #27237 (2026-10-28, ruling A on #27230 and the ruling on #27237) — an unresolved `credentialRef` and a rejected credential degrade the instance instead of failing the boot (a refused stored credential holds its instance until it is set again or a restart; an environment or vault one is retried on the backoff), and the open tier reads a `sys_connector_credential` store between the environment and the host's resolver, except under a walled tenancy posture; see the notes under §6 and under "Upstream availability". #28038 (2026-10-30, after #27992 / PR #28014 and #28016 / PR #28037) — a held degraded instance (a configuration fault met on a retry, or a refused stored or declared credential) is not retried, and its dispatch refusal names the edit or the credential that unblocks it; "the platform retries automatically" stays for an instance on the backoff; see the note under "Upstream availability".
8+
**Amended by**: [ADR-0143](./0143-outbound-connector-auth-is-open-tier.md) (2026-10-27) — OAuth2, the vendor app-credential exchange and request signing join `ConnectorInstanceAuthSchema` in the open tier, and those kinds resolve per dispatch; see the notes under §3 and under "Open / enterprise line". PR #26798 (2026-10-27, #26709 item 3(b)) — a credential the MCP server rejects (HTTP `401` / `403`) is a fatal configuration fault, not upstream-unavailable; see the note under "Upstream availability". #27237 (2026-10-28, ruling A on #27230 and the ruling on #27237) — an unresolved `credentialRef` and a rejected credential degrade the instance instead of failing the boot (a refused stored credential holds its instance until it is set again or a restart; an environment or vault one is retried on the backoff), and the open tier reads a `sys_connector_credential` store between the environment and the host's resolver, except under a walled tenancy posture; see the notes under §6 and under "Upstream availability". #28038 (2026-10-30, after #27992 / PR #28014 and #28016 / PR #28037) — a held degraded instance (a configuration fault met on a retry, or a refused stored or declared credential) is not retried, and its dispatch refusal names the edit or the credential that unblocks it; "the platform retries automatically" stays for an instance on the backoff; see the note under "Upstream availability". #27429 (2026-10-31, after #27237) — on a reload too, an unresolved `credentialRef` degrades its instance instead of being skipped, and a credential that is no longer set takes the live connector down; see the note under "Runtime reconcile".
99

1010
---
1111

@@ -167,11 +167,25 @@ and skipped**, never crashing the live server; a changed instance's old connecto
167167
keeps serving until its replacement materializes successfully. Boot keeps the
168168
"fail loudly" contract.
169169

170+
> **Amendment note (2026-10-31, #27429 — after #27237).** An unresolvable `credentialRef` leaves
171+
> the reload list above, as it left the boot list (the note under §6). A reference no source
172+
> answers for degrades the instance naming it, at boot and on a reload alike, and a live
173+
> connector under that name is torn down first instead of serving on: the value it was
174+
> materialized with is gone
175+
> (`packages/services/service-automation/src/plugin.ts#degradeConnectorForCredential`). That is
176+
> how clearing a stored credential (`DELETE /api/v1/automation/connectors/credential-refs/:ref`)
177+
> takes its instances down. A stored credential this deployment cannot read right now (the store
178+
> cannot be read, or no crypto provider is registered yet) degrades the instance too, but leaves a
179+
> live connector serving, since the value still exists. Both go on the backoff under "Upstream
180+
> availability". A host resolver that throws keeps the rule above: fatal at boot, logged and
181+
> skipped on a reload.
182+
170183
### Upstream availability: degrade, don't die (follow-up, landed — #3017)
171184

172185
The fail-loud contract deliberately distinguishes **configuration** faults from
173186
**operational** faults. Unknown provider, invalid `providerConfig`, unresolvable
174-
`credentialRef`, name conflict — authoring mistakes — stay fatal at boot. But a
187+
`credentialRef`, name conflict — authoring mistakes — stay fatal at boot (an unresolvable
188+
`credentialRef` no longer does: see the notes under §6 and under "Runtime reconcile"). But a
175189
provider whose factory must contact an upstream to materialize (the `mcp`
176190
provider connects and calls `tools/list`) can fail because that upstream is
177191
*temporarily unreachable*; aborting the whole app boot for one integration's

0 commit comments

Comments
 (0)