Skip to content

Commit 1e5a44a

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-19580-retire-connector-connection-timeout
Second sync: main advanced by six commits after the first merge, including the tenancy.organizationField retirement, which also appends to step18.rationale. Conflict in packages/spec/src/migrations/registry.ts, step18.rationale (hand-written, outside the generated markers): the shared closing line and the translation bundle paragraph stay once, the organizationField paragraph is kept whole, and this branch's connector.connectionTimeoutMs paragraph is appended after it. The only byte changed in main's text is the organizationField paragraph's terminator (`.',` becomes `. '`) so the concatenation continues. conversionIds merged as a set without conflict: all three step-18 additions present. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
2 parents d71d6c0 + 6eaa0f4 commit 1e5a44a

68 files changed

Lines changed: 3065 additions & 11331 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: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/metadata-core': minor
4+
'@objectstack/platform-objects': patch
5+
---
6+
7+
**BREAKING** — retire `object.tenancy.organizationField`, the stamp-only column
8+
declaration the whole protocol declared exactly once, on a table this platform ships.
9+
10+
The key answered "which column says who this platform row is ABOUT", where
11+
`tenancy.tenantField` answers "what is this object WALLED by". The spec's own docblock
12+
stated the consequence: *"For ordinary objects the two coincide and `organizationField`
13+
is never needed."* Measured on `main` before this change, the entire repository declared
14+
it **once** — `packages/platform-objects/src/identity/sys-api-key.object.ts`, the
15+
better-auth credential table — and zero business objects declared it anywhere. Its
16+
readers were three platform-row writers, scope-pinned **by name** (audit stamping, the
17+
approval-row writer, the automation-run recorder), so an application declaration was
18+
inert by construction while still being authorable on every object, which made every
19+
future piece of organization logic owe the question "what if somebody set this?".
20+
ADR-0049 enforce-or-remove; maintainer ruling 2026-09-18, verbatim and untranslated:
21+
「organizationField 撤出可授权面 同意你的建议」.
22+
23+
## FROM → TO
24+
25+
| you wrote (17.4 and earlier) | write instead |
26+
| --- | --- |
27+
| `tenancy: { enabled: false, organizationField: 'active_organization_id' }` | `tenancy: { enabled: false }` — delete the key. Nothing read it on an application object |
28+
| `tenancy: { enabled: true, organizationField: 'about_org_id' }` on an object whose tenant column really is `about_org_id` | `tenancy: { enabled: true, tenantField: 'about_org_id' }` — the surviving key both walls the object and stamps its platform rows |
29+
| you declared it to make one platform table's rows stamp differently | nothing to write. That divergence is a platform fact now, not a knob |
30+
31+
The `tenancy` block is `.strict()`, so the key is **refused** with its prescription
32+
rather than stripped, and `os migrate meta --from 17` lists the mechanical edits for
33+
existing sources.
34+
35+
## What does NOT change
36+
37+
The `sys_api_key` divergence is intact, and that is the point of the shape this takes.
38+
The credential table is `managedBy: 'better-auth'`, so `resolveInjectedSystemColumns`
39+
bails before tenancy is consulted and no `organization_id` is ever injected; the column
40+
it really carries is better-auth's `active_organization_id`. Its audit, approval and
41+
automation-run rows still stamp that column. What moved is only where the fact is
42+
written: `PLATFORM_STAMP_ORGANIZATION_COLUMNS` in `@objectstack/metadata-core`, one row,
43+
keyed by object name and read by the STAMP face alone. The WALL face
44+
(`resolveRecordWallOrganizationField`) never read the key and is untouched, so the
45+
stamp/wall divergence pin stands unchanged.
46+
47+
⛔ The column is **not** renamed to `organization_id` and must never be: in this platform
48+
"has an `organization_id` column" IS the wall, so the rename would wall the credential
49+
table on an equality that excludes NULL and every pre-existing key would vanish from its
50+
own owner's key list.
51+
52+
## For `@objectstack/metadata-core` consumers
53+
54+
`resolveRecordOrganizationField` and `createRecordOrganizationResolver` keep their
55+
signatures and their four-limb precedence. Limb 0 is now keyed by the object's
56+
registered NAME against the platform table instead of by a declaration on the definition:
57+
the engine-bound resolver passes the name it was asked about, and the two-argument
58+
function reads `objectDef.name` when the definition carries one. A caller that fed it a
59+
hand-built definition carrying `tenancy.organizationField` — only reachable by
60+
reimplementing a platform writer — now gets limbs 1 to 4.
61+
62+
The retirement kit, in the shape the playbook prescribes:
63+
64+
- the key is DELETED from `TenancyConfigSchema` (the block is a `strictObject`), and a
65+
`TENANCY_RETIRED_KEY_GUIDANCE` row carries the prescription beside the two v15.0
66+
precedents (`tenancy.strategy`, `tenancy.crossTenantAccess`)
67+
- D2 conversion `object-tenancy-organization-field-removed` (`toMajor: 18`,
68+
`retiredFromLoadPath: true`) strips the key from authored sources and stored
69+
`sys_metadata` rows; D3 wires it into the protocol-18 chain step, and
70+
`RETIRED_KEYS_BY_MAJOR[18]` declares `data/TenancyConfig:organizationField`
71+
- the `authorable-surface/data.json` row is deleted in this same commit — the strict
72+
route's tripwire — with the build computing the guidance-route proof for itself
73+
- the liveness ledger row is deleted, since the key leaves the walked shape entirely
74+
- pin tests: the authored shape is refused with its prescription, and the `sys_api_key`
75+
stamp is pinned end to end beside the closed-set control (the same shape under any
76+
other object name takes the ordinary limbs)
77+
78+
Clause-②: no
79+
80+
<!-- adr-0087: registered object-tenancy-organization-field-removed -->
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+
`packages/spec/src/migrations/registry.ts`: the shipped ADR-0087 semantic entry `filter-preset-ordering-comparand-refused` listed a page filter and a component filter among the `FilterConditionSchema` carriers, and said the schema door and the `@objectstack/lint` `filter-preset-comparand` rule refuse a bare date-range preset there at publish. Both are `ViewFilterRuleSchema` rule arrays, and a rule array carries no preset check, so an upgrader who swept stored pages with a schema parse found nothing and concluded the sweep was clean. The entry's `surface`, `reason` and `acceptanceCriteria` now put each carrier under the door that actually refuses it.
6+
7+
Clause-②: no
8+
9+
No behaviour moves. No schema, accept set or lint rule is touched, and no export is added, removed or retyped. Every line this change edits in `registry.ts` is a string literal inside that one step-18 entry, which the exported `MIGRATIONS_BY_MAJOR` carries, so what moves in `dist` is prose.
10+
11+
- **The three groups the entry now draws.** They list the carriers measured, not a closed partition; the entry's grep sentence is the catch-all. Each was measured against the built `dist` with a preset comparand (`last_30_days`, and `today` as a `between` endpoint), and an ISO-date dark control reads green in every cell.
12+
1. Slots typed `FilterConditionSchema`: `DashboardWidgetSchema.filter`, `GlobalFilterOptionsFromSchema.filter`, `DatasetSchema.filter`, `DatasetMeasureSchema.filter`, `ReportSchema.runtimeFilter`, `JoinedReportBlockSchema.runtimeFilter`, `FieldSchema.relatedListFilter` and `FieldSchema.summaryOperations.filter`. A parse of the declaring schema refuses each one at the comparand's own path, and the lint rule reports each one as well.
13+
2. Filters under a key the lint walks whose declared type carries no preset check. These are `ViewFilterRuleSchema` rule arrays (a view's `filter`, a page element's `dataSource.filter`, a page component's `filter` prop) and a Mongo-shape record typed as a loose record rather than `FilterConditionSchema` (a flow `get_record` / `update_record` / `delete_record` node's `config.filter`). These parse green, and the lint rule alone refuses them.
14+
3. A page's `interfaceConfig.filterBy` and a lookup field's `lookupFilters` parse green and also lint green, because the lint's filter walk descends neither key. Neither door refuses them at publish, so the entry now tells the upgrader to sweep both by hand. Two controls back the `filterBy` reading. A malformed `filterBy` value is refused at `interfaceConfig.filterBy.0.value`, so the slot is parsed. The same rule under `interfaceConfig.filter` is refused by the lint, so the key name is what decides. `lookupFilters` takes `gt`, `gte`, `lt` and `lte` as its only ordering spellings (`greater_than`, `after`, `between`, `>` and `$gt` are refused at `lookupFilters.0.operator`), and the by-hand clause names those four.
15+
- **Two more false sentences are narrowed.**
16+
- The `replacement` called the dashboard date-filter positions "the only place any layer ever resolved" a preset name. An analytics query's `timeDimensions[].dateRange` accepts and resolves the names too.
17+
- The `reason` said equality and membership "are NOT judged". That holds for the schema door only. The lint rule refuses a preset in an equality or membership position on a field it can resolve to a declared `date` or `datetime`, while `this_quarter` on a `select` field stays green. Where the filter binds to no object, such as a widget whose `dataset` names no dataset, that arm does not fire.
18+
- **Reach.** Counted over `dist/index.js`, `dist/index.mjs`, `dist/browser/index.js` and `dist/browser/index.mjs`:
19+
- The removed carrier list `page filter, component filter, rollup filter` and each of the four other removed claims read 4 before and 0 after.
20+
- Each of nine sentences unique to the corrected text reads 0 before and 4 after.
21+
- The unchanged dark control `compared false against every row: HTTP 200` reads 4 on both sides.
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): `defineStack(config, { strict: false })` refuses a non-array `objects` with the ADR-0112 envelope
6+
7+
**BREAKING** — `defineStack`, a public root export, now refuses under `strict: false` a class of input it used to crash on or hand on unusable: a non-array `objects`, or an `objects` array holding an entry that is not an object.
8+
9+
The non-strict door skips the parse and hands the normalized input to the action merge that ends every `defineStack` call. That merge read `objects` with no shape guard. Measured before this change:
10+
11+
| `objects` under `strict: false` | before | after |
12+
| :--- | :--- | :--- |
13+
| a number or a string (`5`, `'abc'`) | bare `TypeError: config.objects.map is not a function`, `code` and `status` both `undefined` | refused, `STACK_SCHEMA_INVALID`, `status: 422` |
14+
| `null`, `''`, `0`, `false` | returned untouched, refused one call later by `composeStacks` | refused, `STACK_SCHEMA_INVALID`, `status: 422` |
15+
| an array holding `null` (`[null, obj]`) | bare `TypeError` reading `actions` off `null` | refused, `STACK_SCHEMA_INVALID`, `status: 422`, one issue per entry at `['objects', index]` |
16+
| an array holding another non-object (`[obj, 7]`, `[obj, 'x']`) | returned with the entry in place, a success whose objects are not all objects | refused, `STACK_SCHEMA_INVALID`, `status: 422`, one issue per entry at `['objects', index]` |
17+
18+
`strict: false` skips validation — cross-references and schema detail — and never promised to accept a shape the merge cannot read. The refusal carries the code the strict parse raises for the same authored mistake, with the zod issue on `issues` — `path: ['objects']`, `expected: 'array'` for the collection, `path: ['objects', index]`, `expected: 'object'` for each non-object entry — the same line `composeStacks` draws for a non-array `objects`. Every row narrows: nothing that used to be refused is accepted now. An absent `objects` (`undefined`) is not malformed and behaves as before; the map form (`{ name: { … } }`) is still normalized to an array first and accepted.
19+
20+
Fix: author `objects` as an array of object definitions or in the map form, or drop `strict: false` to have every schema check run.
21+
22+
No code is added to the ADR-0112 ledger and no export changes: `STACK_SCHEMA_INVALID` is already registered under `@objectstack/spec`.
23+
24+
<!-- adr-0087: not-required (no-migration-prescription) Nothing authorable moves: no spec key, Zod schema, export, config field or stored metadata shape is added, removed, renamed or re-spelled. The strict defineStack parse already refused every input this refuses, so an authored stack that passed its schema builds exactly as before; what narrows is the runtime behaviour of the strict: false door on inputs that bypassed that parse, and objectstack migrate meta has no document to rewrite for it. -->
25+
26+
Clause-②: no (narrowing)

0 commit comments

Comments
 (0)