Skip to content

Commit 7534fd7

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-19961-decision-branch-expression-absent
2 parents e702ebd + 6a6a17b commit 7534fd7

68 files changed

Lines changed: 2872 additions & 844 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: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
---
2+
'@objectstack/cli': minor
3+
---
4+
5+
fix(cli): **BREAKING** — `os generate schema` is retired, and it now says why and points at `os validate` and the per-type JSON Schemas `@objectstack/spec` publishes (#19098)
6+
7+
Clause-②: no (narrowing)
8+
9+
<!-- adr-0087: not-required (no-migration-prescription) A CLI COMMAND NAME is an invocation surface, not authorable metadata. There is no authorable key, no `sys_metadata` row and no schema to tombstone here, so there is nothing for `objectstack migrate meta` to rewrite, nothing for `spec-changes.json` to project and no FROM -> TO spelling for the upgrade guide to carry: the ruling generates no replacement file, so there is no call to rename, only one to delete. Nor is the ledger the notification channel: the command itself now refuses, exits 1 and names `os validate` and the published per-type schemas at the exact moment and place of use. Same reasoning shape as the `os g agent` retirement, one command over. -->
10+
11+
**⛔ If a script, a Makefile or a CI step in your project runs `os generate schema`
12+
(or `os g schema`), it will now exit 1 and write nothing.** That is the intended
13+
outcome: the command is gone by maintainer ruling, and the failure is how you find
14+
out. Nothing in this repository reads the file it wrote.
15+
16+
`minor`, not `major`: during the launch window this stack ships breaking changes as
17+
`minor` (pre-1.0 semantics under lockstep versioning — see
18+
`scripts/check-changeset-no-major.mjs`).
19+
20+
**What the command actually did.** `os generate schema` wrote
21+
`objectstack.schema.json`, a JSON Schema of the whole stack definition for an editor
22+
to check `objectstack.config.ts` against. It projected `ObjectStackDefinitionSchema`
23+
through a bare `z.toJSONSchema`, so the refinements the platform enforces beyond the
24+
shape — a non-blank string, a required one-of, a banned key — were missing from the
25+
file. Measured against the published projection on the tree the ruling was made on,
26+
the file lacked 874 keywords, every one of them an absence. An editor pointed at it
27+
reported a config as valid, and the platform then refused that config.
28+
29+
**Why it is retired rather than repaired.** A TypeScript configuration is typed by its
30+
own `define*` helper, and `objectstack.config.ts` is typed end to end by
31+
`defineStack`, so no config format this CLI loads is one an editor validates against
32+
a JSON Schema. JSON metadata already has the per-type schemas `@objectstack/spec`
33+
publishes, which carry the published projection. Repairing the command would have
34+
added a permanent public export to `@objectstack/spec` for a file with no reader. The
35+
ruling generates no replacement file. What you see now:
36+
37+
```
38+
✗ `os g schema` was retired — its JSON Schema passed configs the platform refuses (maintainer ruling).
39+
40+
The file it wrote described only the shape of a stack. Every rule the
41+
platform enforces beyond that shape — a non-blank string, a required
42+
one-of, a banned key — was missing from it, so an editor showed a config
43+
as valid and the platform then refused it. By maintainer ruling it is
44+
retired, not repaired, and no replacement file is generated.
45+
46+
Check a project against the rules that actually run:
47+
48+
os validate
49+
50+
For JSON metadata, point your editor at the per-type schemas that
51+
@objectstack/spec publishes. They state the rules a JSON Schema can
52+
express, and name the ones it cannot under `x-dropped-refinements`:
53+
54+
node_modules/@objectstack/spec/json-schema/<category>/<Type>.json
55+
56+
`objectstack.config.ts` needs neither: `defineStack` types it in your
57+
editor. Delete the `os generate schema` call, and any editor setting
58+
that maps `objectstack.schema.json` — nothing writes that file now.
59+
60+
Docs: https://objectstack.ai/docs/deployment/cli
61+
```
62+
63+
**What to do.** The refusal's pointer is the whole of it. Delete the
64+
`os generate schema` call, and any editor setting that maps `objectstack.schema.json`
65+
(a `json.schemas` or `yaml.schemas` entry, for example). Run `os validate` to check a
66+
project against the rules that actually run. For JSON metadata, point the editor at
67+
`node_modules/@objectstack/spec/json-schema/`, one file per metadata type. There is no
68+
call to rename: nothing replaces the command.
69+
70+
**Reach outside this repository is NOT MEASURED.** There is no telemetry, so whether
71+
any project runs the command or reads its file is unknown. Inside this repository
72+
nothing does: no reader and no editor mapping of `objectstack.schema.json` exists.
73+
If these release notes also record that `os generate schema` can now write its file,
74+
this retirement supersedes that repair.
75+
76+
**Two neighbouring answers change with it.** The retirement ledger is now read before
77+
the command routes its sub-commands and before it asks for a `<name>`, which is what
78+
lets `os generate schema` (no name) reach the refusal at all. So `os g agent` with no
79+
name now prints the agent retirement instead of `Missing required argument: <name>`.
80+
The ledger lookup also reads its own keys only: `os g constructor <name>` used to be
81+
taken for a retired type and crashed with a `TypeError`, and it now falls through to
82+
the ordinary type checks.
83+
84+
The docs row that advertised the command — "Autocomplete and validation for
85+
`objectstack.config.ts` (via `os generate schema`)" in
86+
`content/docs/api/data-flow.mdx` — is gone, with the diagram's JSON Schema node above
87+
it.

‎.changeset/20156-alternate-door-read-gates.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ What each door answers now:
1313

1414
- `/published` answers exactly what the plain read answers the same caller, for every type: the same refusal, or the same pruned or masked document. That includes the dashboard widget gate: a widget bound to an optional service this deployment does not register is left out of `/published`, as it is from the plain read.
1515
- `/layers`, `?layers=true` and `/diff` refuse a `doc`, `book` or `app` the plain read refuses whole, with the same status and code. For an app, that means one whose `requiredPermissions` the caller lacks (`403`) or an unpublished app to a non-builder (`404`). They mask object fields as the plain read does. `/diff` of a `doc`, `book` or `app` with nothing behind the name answers `404 RESOURCE_NOT_FOUND`, as the plain read does. They do not apply the dashboard widget gate or any other per-deployment gate: they show the stored version, which is what an author edits.
16-
- **One case is unchanged in this release: an app the plain read would serve a caller only in part.** That is an app the caller may open, but with navigation entries withheld from them by `requiredPermissions` or the documentation audience. On `/layers`, `?layers=true` and `/diff`, such a caller still receives the stored app with those entries included, as before. No other caller does: an app the plain read refuses whole is refused on these doors too. What these doors should answer a partial caller is pending a decision, because both ways of matching the plain read restrict authors. Pruning the stored app would delete the withheld entries when Studio's designer saves back what it loaded. Refusing it would lock out any author who lacks one entry's permission, platform admins included.
16+
- For an app the caller may open, `/layers`, `?layers=true` and `/diff` answer by who is asking. A caller who may save the app (the one `PUT /meta/app/:name` admits) receives the full stored version, including the navigation entries that `requiredPermissions` or the documentation audience withhold from them: Studio's designer saves back what it loads, so a pruned load would delete those entries. Every other caller receives the app without the entries withheld from them, left out as the plain read leaves them out, on every layer and on both sides of a diff. The plain read and `/published` prune for every caller, authors included. An app the plain read refuses whole is refused on these doors to an author too.
1717
- `/history` and `/audit` answer the plain read's refusal when the plain read refuses the doc, book or app whole. Otherwise they serve the events, which carry no document body.
1818

19-
Types no per-caller gate judges, such as `view` and `flow`, are unchanged on every door. `dashboard` is unchanged on every door except `/published`. A caller the plain read serves in full gets the same answers as before. A client reading these doors as a caller the plain read restricts, including an integration reading `?layers=true` anonymously, now receives the plain read's answer, except the `app` row of `/layers`, `?layers=true` and `/diff` for a caller the plain read serves only part of the app. To read a gated doc or book through any of these doors, hold the permission set its book names.
19+
Types no per-caller gate judges, such as `view` and `flow`, are unchanged on every door. `dashboard` is unchanged on every door except `/published`. A caller the plain read serves in full gets the same answers as before. A client reading these doors as a caller the plain read restricts, including an integration reading `?layers=true` anonymously, now receives the plain read's answer, except that a caller who may save an app reads it whole on `/layers`, `?layers=true` and `/diff`. To read a gated doc or book through any of these doors, hold the permission set its book names.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/rest": patch
3+
---
4+
5+
**The stored-version doors of an app answer by who is asking: whoever may save the app reads it whole, everyone else reads it pruned.** `GET /api/v1/meta/app/:name/layers`, the deprecated `?layers=true` and `…/diff` serve the versions Studio's designer loads and saves back. Until now they served an app the caller may open as stored to every such caller, including the navigation entries that `requiredPermissions` or the documentation audience withhold from them, which exposed those entries' names and targets to members the plain read hides them from.
6+
7+
- A caller who may save the app receives the full stored version on these three doors, so a designer that saves back what it loaded keeps every entry. "May save" is exactly what `PUT /meta/app/:name` admits that caller: a system context or `manage_metadata`. `manage_org_presentation` does not save apps, so it does not qualify.
8+
- Every other caller who may open the app receives it without the entries `requiredPermissions` or the documentation audience withhold from them, left out as the plain read leaves them out: on each layer, and in the values on both sides of a diff. A diff keeps all of its entries; only their values are pruned.
9+
- Unchanged: the plain read and `/published` still prune for every caller, authors included. An app the plain read refuses whole (an app-level `requiredPermissions` the caller lacks, or an unpublished app to a caller without Studio or Setup access) is still refused on every door, to an author too. `/history`, `/audit`, and the doc, book and dashboard answers do not change.
10+
11+
A client that reads these doors as a non-author now receives fewer navigation entries. To read an app's full stored version there, read it as a caller the app's save door admits.
12+
13+
For code that runs the shared read gate: `MetaReadGatePolicy.app` is `'gate'` or `'author-exempt'`, and a door that passes `'author-exempt'` supplies the caller's save verdict as `MetaReadGateCaller.mayWriteItem`.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/lint': patch
4+
'@objectstack/platform-objects': patch
5+
---
6+
7+
fix(spec): a `joined` report draws no chart — `blocks[].chart` is removed and a container `chart` on a joined report is refused (#20161)
8+
9+
Clause-②: no (narrowing)
10+
11+
**BREAKING** — shipped as `minor` under the launch-window convention
12+
(`check-changeset-no-major` refuses `major` until GA; breaking-ness is carried by
13+
this banner, the `(narrowing)` arm above and the ADR-0087 disposition below,
14+
never by the level).
15+
16+
A `joined` report draws each of its blocks as a table. The renderer's joined
17+
branch returns before its one read of the report's `chart`, and nothing ever
18+
read a block's `chart` at all. So a chart on a joined report, on the container
19+
or on any block, parsed green, passed the `validate-chart-bindings` lint, and
20+
plotted nothing. Both coordinates now answer at parse:
21+
22+
```
23+
FROM ReportSchema.safeParse({ name: 'overview', label: 'Overview', type: 'joined',
24+
chart: { type: 'bar', xAxis: 'status', yAxis: 'task_count' },
25+
blocks: [{ name: 'open_block', dataset: 'tasks', rows: ['status'], values: ['task_count'],
26+
chart: { type: 'pie', xAxis: 'status', yAxis: 'task_count' } }] })
27+
-> { success: true } // both charts silently never drawn
28+
29+
TO -> { success: false, issues: [
30+
{ code: 'unrecognized_keys', path: ['blocks', 0],
31+
message: '… `report.blocks[].chart` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — … Delete the key. …' },
32+
{ code: 'custom', path: ['chart'],
33+
message: 'a `joined` report draws no chart — it draws each block as a table and never reads `chart`, on the container or on a block. Delete `chart`; …' } ] }
34+
```
35+
36+
**Fix.** Delete the `chart`. The report renders exactly as before, because
37+
neither value was ever drawn. To plot one of the slices a block shows, give it a
38+
non-joined report of its own with that `chart`.
39+
`os migrate meta --from 17` lists the mechanical edits for existing sources.
40+
41+
**What does not change.** `chart` on a `tabular` / `summary` / `matrix` report is
42+
untouched: it is that report's live embedded chart. A joined report with no
43+
`chart` parses byte-identically to before, and a block keeps every other key.
44+
45+
### The retirement kit
46+
47+
- **Schema.** `JoinedReportBlockSchema` is closed (`strictObject`), so `chart` is
48+
removed from its shape and answered by its `guidance` table with the
49+
prescription (build-schemas check (c) proof 4). `ReportSchema.chart` stays
50+
declared; the joined arm of its refinement refuses it, beside the
51+
`dataset` / `rows` / `columns` / `values` / `order` refusals already there.
52+
- **ADR-0087.** `RETIRED_KEYS_BY_MAJOR[18]` gains `ui/JoinedReportBlock:chart`, and
53+
the D2 conversion `report-joined-chart-removed` (protocol 18, retired from the
54+
load path) strips a block's `chart` and a joined container's `chart` from old
55+
sources and stored `sys_metadata` rows as a lossless delete. Stored rows can
56+
carry them: the Studio report form offered a block `chart` input until this
57+
change. The family's D3 semantic entry, `ui-report-joined-chart-retired`, states
58+
what the strip cannot decide: whether the chart was wanted. If it was, it moves
59+
to a non-joined report of its own, because a joined report has no chart channel.
60+
- **Form.** `reportForm` drops the block `chart` input and shows the container
61+
`chart` only when `type` is not `joined`; the `platform-objects` metadata-form
62+
translation bundles drop the `blocks.chart` label in all four locales.
63+
- **Lint.** `validate-chart-bindings` no longer resolves the axes of a block chart
64+
or of a joined container's chart against a dataset: it would be vouching for a
65+
chart that is refused at parse and never drawn. A block's own `dataset` /
66+
`rows` / `columns` / `values` are still checked.
67+
- **Ledger and docs.** `liveness/report.json` names a reader for `chart` only on
68+
non-joined reports and drops `chart` from the `blocks` row;
69+
`content/docs/ui/reports.mdx` lists what a joined container refuses.
70+
71+
<!-- adr-0087: registered report-joined-chart-removed, ui-report-joined-chart-retired -->
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`DecisionConfigSchema` refuses `mode` on a `decision` that also declares a non-empty `conditions` list (#20168). `mode` belongs to the edge-branched decision alone: a `conditions` list is first-match on its own, so a `mode` beside one would be accepted and never read, and `mode: 'inclusive'` there would promise every matching branch while the run takes one.
6+
7+
Clause-②: no
8+
9+
This corrects a key that has not been released yet, so it narrows no published accept set. Timing, measured when this landed: the npm registry's `latest` `@objectstack/spec` is `17.4.0`, and its `json-schema/automation/DecisionConfig.json` declares `conditions` only, with `additionalProperties: false`. No `mode` was published. The Version Packages PR (`chore: version packages`) is open and unmerged. `mode` reaches its first release together with this refusal, so no ADR-0087 entry is owed.
10+
11+
- **The refusal**: one issue at `mode`, for either member. It reads "`mode: 'inclusive'` is not valid on a decision that declares a `conditions` list — `mode` belongs to the edge-branched decision alone." and names the two ways out:
12+
- delete `mode` and keep the list;
13+
- or move the branches onto the out-edges (a `condition` on each branch edge, `isDefault: true` on the fallback), delete `conditions`, and keep `mode`.
14+
- **Left alone**: `mode` on an empty `conditions` list, `mode` with `conditions` absent, and a `conditions` list with no `mode` all parse as before. A `mode` outside `'exclusive' | 'inclusive'` still gets its own value refusal first.
15+
- **Where it binds**: the doors that parse `DecisionConfigSchema`. Today that is a direct parse, including the `SCHEMALESS_NODE_CONFIG_SCHEMAS.decision` handle. `decision` config is still export-only, so a flow's registration and `os validate` do not run it yet. The published JSON Schema cannot state the rule, because no arm of the closed refinement projection fits it. `automation/DecisionConfig` therefore joins `dropped-refinements.baseline.json` and carries the site as `x-dropped-refinements`.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
`ERROR_CODE_LEDGER['@objectstack/lint']` now lists `INVALID_ARTIFACT_PACKAGES`, the code `packages/lint`'s `packagesOf` reader stamps for a malformed `stack.packages` (#20206) — required by `check:error-code-provenance`, which refuses a registered code stamped by a package whose own owner key does not list it.
6+
7+
Clause-②: yes
8+
9+
Provenance, not identity: the code was already registered under `@objectstack/core` (`resolveArtifactPackageOrder`, the producer `packagesOf` deliberately mirrors rather than mints a new code for), so the `ErrorCode` union, the wire, and every other package's rows are unchanged. What widens is the per-package face a consumer reads from `ERROR_CODE_LEDGER['@objectstack/lint']`, newly present where it was absent before. Nothing to migrate.

0 commit comments

Comments
 (0)