Skip to content

Commit 606046e

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-20301-list-tabs-retired
# Conflicts: # packages/spec/src/conversions/registry.ts # packages/spec/src/migrations/registry.ts
2 parents c9f81fa + df3ba16 commit 606046e

84 files changed

Lines changed: 3982 additions & 380 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.

‎.changeset/19920-exported-types-not-unknown.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ Four published type aliases were derived from a schema whose own static type era
1818

1919
The types are the members' declared shapes, not the schemas' verdicts. Each schema still accepts some bodies its type refuses (the preprocess folds and strips) and still refuses some bodies its type admits (refinements are not types), so the schema remains the only judge.
2020

21-
`JoinedReportBlock` is not changed by this change, and still resolves to `unknown`.
21+
`JoinedReportBlock` is not changed by this change. It stops resolving to `unknown` in its own entry (#19920).
2222

2323
<!-- adr-0087: not-required (no-migration-prescription) Nothing an author writes moves — no spec key, no export and no stored row changes and every runtime accept set is unchanged, so `objectstack migrate meta` has nothing to reach — and only TypeScript annotations narrow, whose channel is the consumer's compiler. -->
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
fix(spec): `JoinedReportBlock`, a ViewItem's `config`, a flattened overlay's `viewKind` and a flattened list overlay's `type` / `columns` carry the shapes their doors accept (#19920)
6+
7+
Clause-②: yes (narrowing)
8+
9+
**BREAKING for TypeScript code that annotates with `JoinedReportBlock`, `Report`, `ReportParsed`, `ViewItem`, `ViewItemWire`, `ViewMetadata`, `ViewMetadataParsed`, `AssembledViewArtifact` or `AssembledViewArtifactParsed`, or that passes an unchecked value to `defineReport` / `defineViewItem`**: a narrowing of published TYPES, landing in the launch window as `minor` (the lockstep convention: the bump level is not the carrier, this banner and the disposition below are). The runtime accept set does not move at all: no schema's parse, no value and no existing export changes. Three parsed-state type names are added (below); nothing is removed or renamed.
10+
11+
Four places in the published types were wider than the doors that judge the same bodies, so values those doors refuse type-checked:
12+
13+
- `JoinedReportBlock`: FROM `unknown` TO the input shape of `JoinedReportBlockSchema`. The schema was annotated `z.ZodTypeAny`, which erased its shape; it now carries its inferred type. The same erasure made every `blocks[]` element of `Report` / `ReportParsed` (and so of `defineReport`'s parameter) `unknown`; each is now a block.
14+
- A ViewItem's `config`: FROM `unknown` TO the arm's own config type, a `ListView` config on the `list` arm and a `FormView` config on the `form` arm. This holds on `ViewItem`, `ViewItemWire`, `defineViewItem`'s parameter and return, and the `viewItem` member of `ViewMetadata`, `ViewMetadataParsed`, `AssembledViewArtifact` and `AssembledViewArtifactParsed`. The arm builder took `config` as `z.ZodTypeAny`; it is now a generic parameter.
15+
- A flattened overlay member's `viewKind`: FROM `'list' | 'form'` on both members TO `'list'` on the list overlay and `'form'` on the form overlay, the one value each member accepts. A list-shaped body naming `viewKind: 'form'` used to type-check, through the list overlay member, as `ViewMetadata`, `ViewMetadataParsed`, `AssembledViewArtifact` and `AssembledViewArtifactParsed`.
16+
- A flattened list overlay's `type` and `columns`: FROM `unknown` TO the list view's own types, both optional: `type` one of the list view types, `columns` a field list. This holds on the list overlay member of `ViewMetadata`, `ViewMetadataParsed`, `AssembledViewArtifact` and `AssembledViewArtifactParsed`. The member read both keys off the list view shape through a cast that erased them, so `{ object, viewKind: 'list', columns: 42 }` type-checked as all four while that member refuses it.
17+
18+
**If your code stops compiling.** A value you annotated with one of these names, or passed to `defineReport` / `defineViewItem`, is not the shape the door accepts: correct it, or type a value that is still unvalidated as `unknown` and let the schema's `safeParse` decide. A ViewItem's `config` must match its `viewKind`: a `ListView` config under `viewKind: 'list'`, a `FormView` config under `viewKind: 'form'`. A flattened list overlay's `columns` is a field list and its `type` one of the list view types.
19+
20+
The declared types of `JoinedReportBlockSchema`, `ViewItemSchema` and `ViewItemWireSchema` narrow with them, so `z.input` / `z.infer` of each is typed where it was `unknown` (or carried an `unknown` `config`). Typed, each schema's input and output now differ by its defaults, so three ADR-0122 parsed-state aliases are added beside the bare names: `JoinedReportBlockParsed`, `ViewItemParsed` and `ViewItemWireParsed`. Nothing is removed or renamed.
21+
22+
One default is applied by the parse and is absent from `ViewMetadataParsed` / `AssembledViewArtifactParsed`, and their TSDoc now says so: the flattened list overlay member re-applies `type: 'grid'` in an `.overwrite()`, so every body it parses carries `type`, while its output type leaves `type` optional.
23+
24+
The types are the members' declared shapes, not the schemas' verdicts: refinements are not types, so each schema remains the only judge.
25+
26+
<!-- adr-0087: not-required (no-migration-prescription) Nothing an author writes moves — no spec key, no existing export and no stored row changes (three parsed-state type names are added, none removed or renamed) and every runtime accept set is unchanged, so `objectstack migrate meta` has nothing to reach — and only TypeScript annotations narrow, whose channel is the consumer's compiler. -->
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): `os migrate meta` guidance for the `driver-*`, `kernel-*` and `system-*` migration entries states each lesson in words instead of citing tracker numbers
6+
7+
Clause-②: no
8+
9+
The ADR-0087 semantic entries of the `driver-*` family (the driver query-argument
10+
narrowings, the inert capability bits, the SQL driver's unresolvable-column and
11+
cross-row upsert refusals, and the retired Turso config keys), the `kernel-*` family
12+
(preview mode, and the kernel duration keys that now carry their unit in the key name)
13+
and the `system-*` family (the system duration keys renamed under the same rule) are
14+
printed by `os migrate meta` as the header, `why:` and `verify:` lines of a manual
15+
change. Their text sent the reader to issue-tracker and decision-batch numbers — some
16+
of which no longer resolve — for what a ruling, measurement or fix had decided; it now
17+
says what was decided, in the sentence being read. ADR ids are kept.
18+
19+
Text only: no entry id, `surface`, `from` / `to`, conversion or matching logic changes,
20+
and the chain rewrites exactly what it rewrote before. The generated migration registry,
21+
`spec-changes.json` and the protocol upgrade guide carry the same text.

‎.changeset/20263-having-temporal-comparand-door.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ What is judged:
2828

2929
- The same walk and the same predicate, `isUninterpretableTemporalComparand` in `@objectstack/core`, that the door runs on `where` and on each per-aggregation `filter`. A change to that rule reaches `having` with it.
3030
- The kind is the aggregated column's class, the one the `addDays` rule already reads: `min` / `max` of a `date`, `datetime` or `time` field keeps that kind, a groupBy projection of such a field takes its kind, and a `day` bucket is a `date`. `count`, `count_distinct`, `sum` and `avg`, a `week` / `month` / `quarter` / `year` bucket, and every other column are not temporal, so they are not judged.
31-
- Every comparison and set operator's comparand, each `$in` / `$nin` member and `$between` endpoint, and the implicit-equality slot, under `$and`, `$or` and `$not`. As on `where`, a `{placeholder}` string, the empty string, `null` and a `{ $field }` reference are not judged. `having` does not resolve placeholders, and did not before, so the refusal's remedy names none.
31+
- Every comparison and set operator's comparand, each `$in` / `$nin` member and `$between` endpoint, and the implicit-equality slot, under `$and`, `$or` and `$not`. As on `where`, a `{placeholder}` string, the empty string, `null` and a `{ $field }` reference are not judged. `having` resolves placeholders from the same release (#20334), after this door, so the refusal's remedy on a `date` or `datetime` column is the `where` refusal's and names them, e.g. `{30_days_ago}` / `{current_month_start}`.
3232
- The text operators (`$contains`, `$notContains`, `$startsWith`, `$endsWith`, `$icontains`) are not judged. On `where` the text-operator declared-type door answers them first, and that door does not front `having`.
3333
- The door runs after every other `having` door, so a clause one of them refuses (an unknown operator, a key naming no column, a comparand of no comparable type, an `addDays` pair, an array in the equality slot) keeps that refusal and its words.
3434

3535
The refusal follows the `where` door's words. It names the `having` path, the column, what the column aggregates and its kind, and the comparand, for example: `` `having` on 'last_placed' (max(placed_on), a date column) compares against "not-a-date" at having.last_placed.$lt ``. Like the `where` refusal, it names the column's kind, and otherwise only what the query carries.
3636

3737
**Who is affected.** `having` is a request-only key (`QuerySchema.having`, `EngineAggregateOptions.having`), and no metadata type stores it. Every `having` in this repository's docs and published skills compares a numeric aggregation alias, which is not judged. Callers of `engine.aggregate` and of the REST aggregate query in a deployment were NOT measured.
3838

39-
**Fix.** Compare a `date` column with a `YYYY-MM-DD` day, a `datetime` column with an ISO-8601 instant, a bare day or epoch milliseconds, and a `time` column with an `HH:MM` or `HH:MM:SS` wall clock.
39+
**Fix.** Compare a `date` column with a `YYYY-MM-DD` day, a `datetime` column with an ISO-8601 instant, a bare day or epoch milliseconds, either one with a relative-date placeholder the resolver knows (`{30_days_ago}`, `{current_month_start}`; `having` resolves them from the same release, #20334), and a `time` column with an `HH:MM` or `HH:MM:SS` wall clock.
4040

4141
**Unchanged**, measured identical before and after on the three drivers, both paths and both doors: every `where` and per-aggregation `filter` answer; every `having` on a temporal column whose comparand the rule reads (a `YYYY-MM-DD` day, an ISO instant, an epoch-millisecond number or string, an in-range `Date`, a zone-naive instant, a wall clock, an extended-year instant on a `datetime` column, which that rule reads); `{today}`-style placeholders, known or not; the empty and the whitespace-only string; `null`, `$exists`, `$in` / `$nin`, `$between`, `$not` / `$or` / `$and` and `{ $field }` references; `$contains` and `$startsWith`; every `count` / `sum` / `avg` column, a string comparand included; a `month` bucket; and every existing `having` refusal, in its words.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
"@objectstack/objectql": minor
3+
---
4+
5+
fix(objectql)!: a number, currency, percent, rating, slider or progress field refuses an array, a boolean or an object with `invalid_number` (#20309)
6+
7+
Clause-②: no (narrowing)
8+
9+
**BREAKING**: shipped as `minor` under the launch-window convention
10+
(`check-changeset-no-major` refuses `major` until GA; breaking-ness is carried by
11+
this banner and the ADR-0087 disposition below, never by the level). The
12+
narrowing: an array, a boolean or an object whose `Number()` is finite, such as
13+
`[500]`, `[]`, `true` or `false`, written to one of those fields is now refused
14+
with `400 VALIDATION_FAILED` / `invalid_number`. It used to be accepted and
15+
stored as sent.
16+
17+
## What was wrong
18+
19+
The record validator judged `Number(value)` on a number-typed field, but the
20+
write carried `value` itself. Every value that JavaScript coerces to a finite
21+
number therefore passed the check and reached the driver unchanged:
22+
23+
- **SQLite** stored `[500]` as the TEXT `'[500]'`, which a read returned as the
24+
string `"[500]"`; `[]` as the TEXT `'[]'`; and `true` / `false` as `1` / `0`.
25+
- **memory** stored the array or the boolean itself.
26+
27+
`[5, 7]` and `{}` were already refused, because `Number()` of each is `NaN`.
28+
29+
## What changes
30+
31+
- On `number`, `currency`, `percent`, `rating`, `slider` and `progress`, a value
32+
that is neither a number nor a string is refused with `invalid_number`: an
33+
array, a boolean, a plain object, a `Date`. This holds on every engine, REST,
34+
batch and import write door, because they all write through the same
35+
validator.
36+
- A number is judged and stored exactly as before, and so are the `min`, `max`
37+
and `scale` checks and their messages.
38+
- A string is also unchanged. It is still judged by `Number()` and stored as
39+
sent. Which strings a number field accepts is a separate change.
40+
- `summary` is still not judged by this check (it is in the spec's
41+
`COMPUTED_VALUE_TYPES`). A blank still becomes `null` before the check runs.
42+
43+
## Rows already stored
44+
45+
This refuses new writes only; a stored value is never re-read by the check.
46+
Rows written earlier on SQLite may hold such a value as TEXT in a numeric
47+
column. To find them, run this once per number-typed column:
48+
49+
```sql
50+
SELECT id, "FIELD" FROM "OBJECT" WHERE typeof("FIELD") = 'text';
51+
```
52+
53+
OBJECT is the object name and FIELD is the field name. A match is a cell that
54+
SQLite could not store as a number: an array written as TEXT, or a string such
55+
as `'0x10'`. Decide its number by hand; nothing here rewrites it.
56+
57+
<!-- adr-0087: not-required (no-migration-prescription) Nothing authored moves: `packages/spec` is untouched and no metadata key is added, removed or reshaped, so `objectstack migrate meta` has nothing to rewrite and the ledger has no row to gain. What is refused is a caller-written VALUE (an array, a boolean or an object on a number-typed field) at the write door; stored rows are never re-read by the check, and a caller that sends a number, a string or a blank is unaffected. The other categories are closed on facts: the package publishes (not `unpublished`); no ADR-0087 id covers a write-door value check (not `registered` / `already-registered`); and the change is runtime behaviour, not a TypeScript declaration (not `runtime-interface-only` / `type-surface-only`). -->
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: retire `rowLevelSecurity[].tags` — no mainstream platform tags a row-level policy, and nothing here ever read one (#20321)
6+
7+
Clause-②: no (narrowing)
8+
9+
**BREAKING** — shipped as `minor` under the launch-window convention
10+
(`check-changeset-no-major` refuses `major` until GA; breaking-ness is carried by
11+
this banner, the `(narrowing)` arm above and the ADR-0087 disposition below).
12+
13+
`tags` is removed from the row-level security policy (`RowLevelSecurityPolicySchema`,
14+
the entries of a permission set's `rowLevelSecurity`). ADR-0049
15+
enforce-or-remove, graded RETIRE by the maintainer's criterion for
16+
declared-but-unenforced families — does a mainstream platform have the
17+
capability? None does: Salesforce sharing rules, Dataverse security roles and
18+
PostgreSQL RLS policies carry no tag attribute, and compliance reporting there
19+
keys on the rule itself.
20+
21+
The key promised "categorization and reporting" for governance and compliance.
22+
Nothing ever read it. Measured before removal, each against a lit control: the
23+
RLS compiler reads a policy's `name`, `object`, `operation`, `positions`,
24+
`enabled` and predicates, never `tags`; objectui's permission preview renders
25+
the policy COUNT and its policy editor neither seeds nor reads the key; cloud
26+
has no reader. No example, default permission set or cloud source wrote it.
27+
28+
### FROM → TO
29+
30+
| removed | what to write instead |
31+
| --- | --- |
32+
| `rowLevelSecurity[].tags` | delete the key. To limit whom a policy applies to, list the positions in `positions` — a tag never did that. To say why a policy exists, use `description`. |
33+
34+
**The one-line fix: delete `tags:` from every row-level security policy.**
35+
`os migrate meta --from 17` lists the mechanical edits for existing sources;
36+
apply them by hand.
37+
38+
⚠️ Runtime behaviour is deliberately **unchanged**. No access decision ever
39+
depended on a tag, so removing the key removes no behaviour. What changes is the
40+
answer an author gets: a policy carrying `tags` is now refused at parse, with the
41+
prescription, instead of being stored with no effect. An author who wrote a tag
42+
such as `managers_only` believing it scoped the policy now learns that only
43+
`positions` does.
44+
45+
### The retirement kit
46+
47+
- **A `retiredKey()` tombstone** on `RowLevelSecurityPolicySchema` (the
48+
`priority` posture one key over): `tsc` types the key `never`, and every parse
49+
raises the prescription rather than a bare unknown-key verdict. The shape's
50+
did-you-mean never offers it: a near-miss `tag` is refused as unknown.
51+
- **D2 conversion `permission-rls-tags-removed`** (step 18, retired from the load
52+
path): a lossless delete over `permissions[].rowLevelSecurity[]`, so a stored
53+
permission row that still carries the key replays clean through the
54+
rehydration seam, while a live author is refused rather than rewritten.
55+
- **`RETIRED_KEYS_BY_MAJOR[18]`**: `security/RowLevelSecurityPolicy:tags`, and
56+
the family's D3 entry `permission-rls-tags-retired`, which states what the
57+
strip cannot decide — any report, audit filter or review process built on the
58+
belief that policy tags were read needs another path.
59+
- **The liveness row stays**, `dead`, under its tombstone (the key is still in
60+
the walked shape); `authorable-surface/security.json` carries it as
61+
`security/RowLevelSecurityPolicy:tags [RETIRED]`, and the generated reference
62+
pages print the prescription in place of the old describe.
63+
- **No deprecation window**, per the project's startup-stage posture.
64+
65+
⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec`
66+
is published, so this is breaking for consumers no telemetry was consulted for.
67+
68+
<!-- adr-0087: registered permission-rls-tags-removed, permission-rls-tags-retired -->

0 commit comments

Comments
 (0)