Skip to content

Commit 6ce9c2b

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-20646-spec-registries-subpath
2 parents 360efc9 + b291fcd commit 6ce9c2b

183 files changed

Lines changed: 5691 additions & 564 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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
'@objectstack/plugin-audit': patch
4+
---
5+
6+
fix(platform-objects,plugin-audit): Setup and Studio navigation entries for the console's Audit Log and Integrations & APIs pages (#20142)
7+
8+
The console retired its System Hub card wall and its Developer Hub, which had been the only in-app links to several pages, and registered each page under a component-registry key instead. Framework navigation reaches a console page only through a `type: 'component'` item that names such a key, and no item named them, so each page was reachable only by a typed URL. Two entries now name the pages whose capability ships in the open framework:
9+
10+
| Entry | App / group | `componentRef` | Contributed by | Gate |
11+
| --- | --- | --- | --- | --- |
12+
| `nav_audit_log_browser` ("Audit Log Browser") | Setup / Diagnostics, directly under Audit Logs | `audit:log` | `@objectstack/plugin-audit` | none: it lives and dies with the plugin that owns `sys_audit_log` |
13+
| `nav_integrations` ("Integrations & APIs") | Studio / Developer, after Public Forms | `developer:integrations` | `@objectstack/platform-objects` | none beyond Studio's own `studio.access` |
14+
15+
**Two audit entries, on purpose.** The existing Audit Logs entry (the `sys_audit_log` object view) stays. It carries the named list views, search, and the actor and tenant rendered as resolved lookups. The new page adds one filterable table whose detail drawer pretty-prints a change's before and after JSON, where the record page shows `old_value` / `new_value` as raw text. Neither surface replaces the other.
16+
17+
**No entry for the console's AI Approvals page (`ai:approvals`) here.** Under ADR-0029 D7, each capability plugin contributes its own navigation entries into a Setup slot, and the Setup shell does not enumerate capability objects. The AI pending-action queue belongs to the AI capability, whose provider (`@objectstack/service-ai`) ships in Cloud/Enterprise, not in the open framework. Its entry is therefore that capability's to contribute.
18+
19+
The keys are the ones the console registers at the objectui commit this release's console is built from. Labels ship in all four locales (en, zh-CN, ja-JP, es-ES), with their source hashes recorded. Nothing is removed or renamed, and there is nothing to migrate.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/plugin-approvals': patch
3+
---
4+
5+
Provenance comments in `plugin-approvals` were re-anchored
6+
7+
Comment and docblock lines under `src/` that cited tracker numbers which no
8+
longer resolve on GitHub now cite the commit in this repository's history that
9+
decided the matter, and say in their own words what was decided. Comments
10+
only: no type, schema, export, log or refusal text, or runtime behaviour changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/service-datasource': patch
3+
---
4+
5+
Provenance comments in `service-datasource` were re-anchored
6+
7+
Comment and docblock lines under `src/` that cited tracker numbers which no
8+
longer resolve on GitHub now cite the commit in this repository's history that
9+
decided the matter, and say in their own words what was decided. Comments
10+
only: no type, schema, export, log or refusal text, or runtime behaviour changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/service-storage': patch
3+
---
4+
5+
Provenance comments in `service-storage` were re-anchored
6+
7+
Comment and docblock lines under `src/` that cited tracker numbers which no
8+
longer resolve on GitHub now cite the commit in this repository's history that
9+
decided the matter, and say in their own words what was decided. Comments
10+
only: no type, schema, export, log or refusal text, or runtime behaviour changes.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
'@objectstack/lint': minor
3+
'@objectstack/metadata-protocol': minor
4+
---
5+
6+
The runtime metadata publish gate refuses an `api` flow with no per-flow secret, and reads a secret the flow read path withheld as present (#20611).
7+
8+
Clause-②: yes (narrowing)
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) Nothing authorable changes spelling or type: `packages/spec` is untouched, and the start node `config` stays the open record it was. What changes is that the runtime metadata write door now refuses one authored shape at publish: an `api`-bound flow whose start node carries no usable `config.secret`. `objectstack migrate meta` could not rewrite that shape even in principle, because the missing value is a shared secret only the author and the sending system can supply. Rows at rest are not judged or rewritten; the automation engine has refused to register such a flow since 17.5.0, and that load path's disposition is recorded in its own published changelog entry. -->
11+
12+
**BREAKING** — an accept-set narrowing on the runtime metadata write door,
13+
shipped as `minor` under the launch-window convention (`check-changeset-no-major`
14+
refuses `major` until GA; breaking-ness is carried by this banner and the ADR-0087
15+
disposition above, not by the level). An `active` save through `/meta` of an
16+
`api`-bound flow whose start node carries no usable `config.secret` (a
17+
`PUT /api/v1/meta/flow/:name`, or the publish of such a draft) used to be stored;
18+
the automation engine then refused to register it (`400` on the `/automation`
19+
doors, a skip with a warning at boot). It is now refused at the save with
20+
`422 INVALID_METADATA`, the issue naming `flow-api-trigger-secret-missing` at the
21+
start node's `config.secret`, and nothing is stored. A draft save is still
22+
accepted; its publish is refused the same way.
23+
**One-line fix:** set a non-blank `config.secret` on the flow's start node — or,
24+
for a flow that is only ever started explicitly, declare `type: 'autolaunched'`
25+
with no `triggerType: 'api'`.
26+
27+
**What does not change: a signed flow's round trip.** Every served flow definition withholds the start node's `config.secret`, so a body saved back after a read arrives without it, and the save restores the stored secret only after every gate has run, so that no gate handles a restored credential. The gate is now told WHERE the save will restore a credential from the stored row: those positions only, never the values. `flow-api-trigger-secret-missing` reads a secret that was withheld and is stored as present, and one that is absent and not stored as missing. So a GET → edit → PUT of a signed flow, and the first save of a code-authored flow whose secret is in the app's source, keep passing and keep their secret. An explicit empty `config.secret` is the author's own value and is refused as blank.
28+
29+
`@objectstack/lint`:
30+
31+
- `validateFlowApiTriggerSecret` now runs on the runtime publish gate too (`surfaces` `['cli', 'runtime-publish']`, `runtimeTypes: ['flow']`). Its `surfaceReason` is gone.
32+
- `AuthoringRuleContext` gains an optional `restoredCredentialPaths`: a `ReadonlySet<string>` of stack-relative positions in the rules' own finding-path spelling (`flows[0].nodes[1].config.secret`). Only the runtime publish gate sets it; `runAuthoringRules` never forwards it, so `os validate`, `os build` and `os lint` judge the author's own values as before.
33+
- `runRuntimeAuthoringRules` accepts an optional `restoredCredentialPaths`: item-relative dotted positions in the `@objectstack/spec/kernel` redactor registry's `redactedKeys` spelling (`nodes.1.config.secret`). The gate re-spells them against the written item's place in its snapshot.
34+
- `validateFlowApiTriggerSecret(stack, options?)` accepts an optional `{ restoredCredentialPaths }`, and treats a listed start-node secret position as present.
35+
36+
`@objectstack/metadata-protocol`: `saveMetaItem` hands the runtime authoring gate the positions its own credential carry-forward will fill, computed from the same stored body. This costs one indexed `sys_metadata` read on an `active` save of a type with a registered redactor (`datasource`, and `flow` where the automation plugin registers one), and nothing for any other type or for a draft save. The carry-forward itself is unchanged and still runs after every gate. The draft→active promotion judges the stored draft row, which already holds what that draft's save carried forward, so it needs no such positions.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: retire an analytics cube's `refreshKey` — the refresh cadence and data-change probe nothing read (#20637)
6+
7+
**BREAKING** — `refreshKey` on an analytics cube (`CubeSchema`), with its `every` and `sql`, is now refused at parse: nothing ever read it, and no analytics result is cached, so a declared refresh cadence refreshed nothing. Delete the key. Every analytics query is computed when it is asked, as it always was. A refresh cadence is declared again when a result cache exists.
8+
9+
Clause-②: no (narrowing)
10+
11+
Measured before removal: `git grep refreshKey` over the non-test sources of `packages/services`, `packages/drivers` and `packages/rest` answered 0 lines (4 for the neighbouring `.public` in the same pathspec). `@objectstack/service-analytics` references no cache or job service; its one cache is request-scoped (dimension labels). The one in-repo author was the showcase app (`every: '1 hour'`), which no longer writes it.
12+
13+
**Removed rather than enforced** (ADR-0049 enforce-or-remove; the maintainer's ruling on the card, letter C): a result cache keyed by cube, query, read scope and tenant is a subsystem with its own design, and a key that does nothing until then is the residue ADR-0049 removes. `sql` also had no safe seam: raw SQL on a schedule, outside the read scope every other cube `sql` goes through.
14+
15+
## FROM → TO
16+
17+
| you wrote (17.5 and earlier) | write instead |
18+
| --- | --- |
19+
| `refreshKey: { every: '1 hour' }` | nothing — delete the key |
20+
| `refreshKey: { sql: 'SELECT MAX(updated_at) FROM orders' }` | nothing — delete the key |
21+
| `refreshKey: { every: '1 hour', sql: '…' }` | nothing — delete the key |
22+
23+
**The one-line fix:** delete `refreshKey` from every cube.
24+
25+
**What an author who still writes it sees.** `tsc` fails at the authoring site (`Cube` types the key `never`), and the parse — `defineCube()`, `defineStack({ analyticsCubes })`, `PUT /api/v1/meta/analytics_cube/:name` — refuses it at `refreshKey` with the prescription:
26+
27+
> `analytics_cube.refreshKey` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing read it: no analytics result is cached, so neither `every` nor `sql` ever refreshed anything. Delete the key; every analytics query is computed when it is asked. A refresh cadence is declared again when a result cache exists. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand.
28+
29+
`os migrate meta --from 17` lists the mechanical edits for existing sources; apply them by hand.
30+
31+
## The retirement kit
32+
33+
- **A `retiredKey()` tombstone** on `CubeSchema`, a `strictObject` — so the refusal carries the prescription rather than a bare unknown-key report, and `tsc` fails first. The nested `every` / `sql` shape is gone with it. `RETIRED_KEYS_BY_MAJOR[18]`: `data/Cube:refreshKey`. The key had no default, so no retired-default residue is owed.
34+
- **The D2 conversion `cube-refresh-key-removed`** (protocol 18, retired from the load path) deletes the whole block from every `analyticsCubes[]` entry, one notice per cube, as a lossless delete. A built artifact or a stored `analytics_cube` row that carries it loads through the rehydration seams, which replay it.
35+
- **The D3 entry `cube-refresh-key-retired`** asks the author whether anything they built assumed cube results were cached or refreshed on a schedule. They never were.
36+
- **Ledger:** the `refreshKey.every` / `refreshKey.sql` rows collapse into one `dead` tombstone row.
37+
- **No deprecation window**, per the project's startup-stage posture.
38+
39+
⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` is published, so this is breaking for consumers no telemetry was consulted for.
40+
41+
<!-- adr-0087: registered cube-refresh-key-removed, cube-refresh-key-retired -->
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/service-analytics': patch
3+
---
4+
5+
fix(service-analytics): every dataset answer names its base object as `object` (#20644)
6+
7+
Clause-②: no
8+
9+
**What was wrong.** `queryDataset` set `object`, the dataset's base object, only
10+
while it built drill-through metadata, which it builds only when a drillable
11+
dimension is selected and at least one row came back. A dimension-less (KPI)
12+
answer, a zero-row answer and the degraded answer for an unavailable backing
13+
object carried no `object`, and neither did a draft preview (`previewDrafts`),
14+
grouped or not. `POST /api/v1/analytics/dataset/query` relays the service answer
15+
as it is, so a consumer that refreshes on that object's record changes had
16+
nothing to subscribe to for those answers.
17+
18+
**What changed.** Every `queryDataset` answer carries `object`, the dataset's
19+
`object` by machine name, whatever dimensions are selected and whether or not
20+
rows came back, as `AnalyticsResult.object` in `@objectstack/spec` declares. A
21+
grouped answer is unchanged: `object` sits beside the same drill-through keys
22+
as before. A cube `query` answer still carries no `object`.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec): a dataset answer declares its base object as `object` (#20647)
6+
7+
Clause-②: yes (widening)
8+
9+
`AnalyticsResult` (`@objectstack/spec/contracts`) gains one optional member,
10+
`object?: string`, and `AnalyticsResultResponseSchema` (`@objectstack/spec/api`)
11+
mirrors it on `data`. It is the base object of the dataset the answer was
12+
computed from, by machine name. Nothing is removed or renamed, and no existing
13+
member changes meaning.
14+
15+
**For a consumer.** Code typed against `AnalyticsResult` can read `object` from a
16+
`queryDataset` answer without a cast, and a parse with
17+
`AnalyticsResultResponseSchema` keeps `data.object` where it used to strip it. The
18+
contract asks every dataset answer to carry it, whatever dimensions are selected
19+
and whether or not rows came back. A cube query answer has no dataset behind it
20+
and carries none.
21+
22+
**Producers.** This release declares the member. `@objectstack/service-analytics`
23+
sets it on every dataset answer once #20644 lands.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/lint': minor
3+
'@objectstack/spec': patch
4+
---
5+
6+
A credential typed as a literal into a flow position that every flow reader is served now draws one `flow-credential-literal` warning at `os validate`, `os build`, `os lint` and the runtime publish gate, and the spec describes of those positions route an outbound credential to a declarative connector's `credentialRef` (#20654).
7+
8+
Clause-②: no
9+
10+
**Why.** A flow definition is served, as authored, to every member who can read flows. The flow read path withholds the credential slots the spec declares, but it cannot withhold a value inside an open map or a url, because it cannot tell a credential there from an ordinary value. The supported home for an outbound credential is a declarative connector: its `auth: { type, credentialRef }` names a secrets-layer reference that is resolved at boot and never stored in metadata.
11+
12+
**What the warning covers.** An `http` node's `config.headers` entry, a query parameter of an `http` node's `config.url`, and a node's `connectorConfig.input` at any depth, including nodes inside `try_catch`, `loop` and `parallel` regions. A value draws when it is a non-blank string with no `{…}` template, and either its name reads as a credential (`Authorization`, `Cookie`, `x-api-key`, a name carrying `token`, `secret`, `password` and similar) or it opens with an auth scheme (`Bearer`, `Basic`, `Token`, `Digest`, `ApiKey`) followed by a value. A `{variable}` template is resolved per run and draws nothing.
13+
14+
**What it does not do.** It never refuses: every finding is a `warning`, and a save, validate, build or lint that passed before still passes (`--strict` promotes it, as it promotes every warning). It never echoes the value it names. Nothing is withheld on any read.
15+
16+
**Fix, by where the credential sits.** Declare a `connectors:` entry with a `provider` and call it from a `connector_action` node. A header credential goes to `auth: { type: 'bearer', credentialRef }`, or to `auth: { type: 'api-key', headerName, credentialRef }` for a key in a named header. A key in the url's query string goes to `auth: { type: 'api-key', paramName, credentialRef }`. On a connector node, drop the credential from `input`: the connector authenticates through its own `auth.credentialRef`.
17+
18+
`@objectstack/lint` exports the rule `lintFlowCredentialLiterals`, its id `FLOW_CREDENTIAL_LITERAL`, and the one predicate it asks, `isCredentialShapedLiteral(name, value)`. In `@objectstack/spec`, only the descriptions of `HttpConfigSchema.headers` and a flow node's `connectorConfig.input` change; no shape changes.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/driver-memory': patch
3+
---
4+
5+
fix(driver-memory): a cube `where` `$lte` on a bare day keeps the whole day on a declared `datetime` field (#20661)
6+
7+
Clause-②: no
8+
9+
`MemoryAnalyticsService` put a `$lte` comparand into the field's storage form before it applied the whole-day rule for a bare-day upper bound. On a field declared `datetime` (through `syncSchema`) the storage form of `'2026-07-28'` is the instant `'2026-07-28T00:00:00.000Z'`, and the whole-day rule does not widen an instant. So `where: { created_at: { $lte: '2026-07-28' } }` compiled an inclusive bound at that midnight and dropped every row later in the named day, while `find()` with the same filter kept them. `generateSql()` echoed the same narrowed bound.
10+
11+
Both exits now follow ADR-0053's order: the bare day is widened first, and only the resulting bound is converted to the storage form. On a declared `datetime` field the example compiles `created_at < '2026-07-29T00:00:00.000Z'` and answers the same rows as `find()`. On `9999-12-31`, the last supported day, a declared `datetime` field now asks only for a value (`IS NOT NULL` in the echo), as an undeclared field already did.
12+
13+
Unchanged: an undeclared field, a declared `date` field, a full timestamp or `Date` comparand (inclusive, as written), and a `timeDimensions[].dateRange` end, which already widened the day before building its bounds. `$between` stays refused on this face (`INVALID_FILTER`, 400). Nothing is removed or renamed, and there is nothing to migrate.

0 commit comments

Comments
 (0)