Skip to content

Commit e2e0121

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-20637-cube-refreshkey-retired
2 parents d068d3b + a8acee2 commit e2e0121

46 files changed

Lines changed: 1326 additions & 296 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: 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: 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: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
fix(cli): `os migrate meta --from N` prints the schema verdict and the refusals first, then the applied mechanical edits, then the manual changes
6+
7+
Clause-②: no
8+
9+
`os migrate meta --from N` prints every semantic entry of every protocol major the
10+
chain crosses, whatever the stack uses: a `--from 17` run prints 242 manual changes.
11+
Those used to come before the schema verdict, which was the last line of the report
12+
and said "resolve the manual changes above". The refusals that keep the migrated
13+
stack from parsing were not listed at all. The only refusal list was the one printed
14+
while the config loaded, and that list names the stack as authored, including the
15+
keys the chain goes on to convert.
16+
17+
The human-readable report now prints three groups, each opened by one header line
18+
that counts it:
19+
20+
1. the verdict: `Migrated stack is schema-valid`, or
21+
`Migrated stack does not yet pass schema validation — N refusals left after the chain`
22+
followed by one `✗ path: message` line per refusal of the migrated stack;
23+
2. `Applied N mechanical change(s):`;
24+
3. `N manual change(s) require your judgment:`.
25+
26+
No manual change is dropped, merged or reworded. Every applied edit and every
27+
manual change prints byte for byte as before, in the same order within its group.
28+
The data-migration advice is still the last thing printed. A range that holds no
29+
migration step also leads with the verdict, which now lists the source's refusals,
30+
and the note naming the range to use follows it.
31+
32+
`--json` is unchanged: same keys, same values, same array order. The exit code is
33+
unchanged too: a run whose migrated stack does not parse still exits 0.
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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
Provenance comments in `conversions/registry.ts` and `integration/connector.zod.ts` were re-anchored
6+
7+
Clause-②: no
8+
9+
Thirteen comment and docblock sites in `src/conversions/registry.ts` and
10+
`src/integration/connector.zod.ts` cited tracker numbers that no longer resolve on
11+
GitHub. They now cite the record that decided the matter: ADR-0087's 2026-09-13
12+
addendum for the data-at-rest seams `retiredFromLoadPath` does not hold back, and
13+
otherwise the commit in this repository's history. Comments only: no type, schema,
14+
export, `describe()` text, conversion `summary` or runtime behaviour changes.

‎content/docs/references/api/analytics.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ const result = AnalyticsEndpoint.parse(data);
122122
| **success** | `boolean` | ✅ | Operation success status |
123123
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false |
124124
| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata |
125-
| **data** | `{ rows: Record<string, any>[]; fields: object[]; sql?: string; totals?: object[] }` | ✅ | |
125+
| **data** | `{ rows: Record<string, any>[]; fields: object[]; sql?: string; totals?: object[]; … }` | ✅ | |
126126

127127
### Nested Shape: `AnalyticsResultResponse.error`
128128

@@ -155,6 +155,7 @@ const result = AnalyticsEndpoint.parse(data);
155155
| **fields** | `{ name: string; type: string; label?: string; format?: string; … }[]` | ✅ | Column metadata |
156156
| **sql** | `string` | optional | Executed SQL (if debug enabled) |
157157
| **totals** | `{ dimensions: string[]; rows: Record<string, any>[] }[]` | optional | Marginal aggregates - one entry per requested totals grouping, in request order, each computed with the measure's true aggregate over the underlying data (never re-derived from bucketed values). The grand-total grouping yields a single dimensionless row. |
158+
| **object** | `string` | optional | The base object of the dataset the answer was computed from: the dataset's `object`, by machine name. Every dataset answer (`POST /analytics/dataset/query`) must carry it, whatever dimensions are selected and whether or not rows came back, so a consumer can refresh on that object's record changes and drill into its records. Absent on a cube query answer, which has no dataset behind it. |
158159

159160

160161
---

0 commit comments

Comments
 (0)