You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 28ad90e
Browse filesBrowse the repository at this point in the historyBrowse files
* feat(types,cloud-connection,lint,cli): ADR-0120 17.x closeout — isolated install gate, D5c advisory, truth sweep, three-posture conformance (#5081)
ADR-0120's third and final 17.x block, on top of #5212 (driver D3+D4) and
#5208 (spec vocabulary + D5a/D5b lint).
D5e — the isolated-posture install gate. Installing an app that carries
'global' uniques on its own (non-sys) objects into an isolated environment
now stops and lists each index; the installer confirms them as genuinely
platform-wide or rewrites them to 'organization'. The confirmation is
recorded in the install manifest ADR-0104 attestation style (what, who,
when, under which posture) and is never re-asked. A stopped install
registers nothing and writes no ledger entry. NEVER a boot-time warning
(#4884): rehydrate does not evaluate the gate, and the two populations the
gate cannot reach — pre-gate installs, post-install posture changes — are
covered by the advisory form in `os doctor` / `os migrate plan`.
The pure enumerator lives in @objectstack/types so the hard stop and both
advisories read one classification. Declared-index bare `unique: true`
counts (D1 makes it the positional spelling of 'global'); field-level
`true` does not; sys_/base_ objects do not.
D5c — new advisory rule `unique/legacy-organization-composite` for the S6
hand-written organization composite, pointing at the 'organization'
respelling that closes its NULL hole (#5030). Advisory forever, never
auto-fixed: opting in is a real D4 tightening.
D6 — indexing.mdx §Two ways to say "unique" rewritten in the new
vocabulary; schema.mdx §Uniqueness and tenancy rewritten as §Uniqueness and
scope, replacing the "composite degenerates to the single-column one" claim
#5030 falsified; cli.mdx drift-op table updated; references regenerated via
gen:schema && gen:docs; skills/objectstack-data swept (and its non-existent
`tenant_id` column corrected to organization_id).
Conformance — one fixture app booted under single | group | isolated, each
S-row's enforcement asserted with real violating inserts, the materialized
unique key parts asserted byte-identical across all three, plus the one
transition smoke the ADR asks for: a posture flip emits zero drift ops.
Fixes#5081
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
* fix(cli): read the `objectql` slot through its IObjectQLEngine contract, not `any` (#5081)
The ADR-0120 D5e advisory's `SchemaStack.allObjects()` erased its
service-lookup result to `any`, which `no-restricted-syntax` rejects
(#4168/#4176/#4251): the lookup already returns the slot's contract, and
the annotation switched that checking off while looking identical to code
that has it.
The `objectql` slot HAS a contract — `IObjectQLEngine`, whose
`registry.getAllObjects()` is exactly what this reads — so this takes the
prescription's first branch (pass the contract type) rather than the
UNCONTRACTED_SLOTS registration. Concretely it buys what the rule is for: a
rename of `registry` or `getAllObjects` now breaks this at compile time
instead of silently returning zero objects and turning the advisory mute.
`allObjects()` returns `unknown[]` accordingly; its only consumer,
`collectGlobalUniques`, already accepts `unknown`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: content/docs/data-modeling/validation.mdx
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ description: Define data integrity rules — formula conditions, uniqueness, for
10
10
<Callouttype="info">
11
11
Three patterns that look like validation rules are deliberately **not** rule types, because each needs I/O or is a client-side concern. Use the layer that already does each one correctly:
12
12
13
-
-**Uniqueness** → a unique index (`ObjectSchema.indexes`, `{ fields, unique: true }`, with`partial` for a scoped constraint) or field-level `unique: true`. A SELECT-then-INSERT rule is inherently racy (TOCTOU); a DB unique constraint is not.
13
+
-**Uniqueness** → a unique index (`ObjectSchema.indexes`, `{ fields, unique: 'organization' | 'global' }` — state the scope, [ADR-0120](/docs/data-modeling/indexing);`partial` for a scoped constraint) or field-level `unique`. A SELECT-then-INSERT rule is inherently racy (TOCTOU); a DB unique constraint is not.
14
14
-**Async / remote validation** → a client-form concern, and an SSRF/latency hazard on the server write path. Keep it in the form layer, or enforce the invariant with a `unique` index / lifecycle hook.
15
15
-**Custom handler** → a `beforeInsert` / `beforeUpdate` lifecycle hook, the supported extension point for arbitrary validation code.
16
16
-**Delete-time guards** → a `beforeDelete` lifecycle hook. Validation rules run only on insert/update (a delete carries no record payload to validate), so there is no `'delete'` validation event — block or gate deletions from a `beforeDelete` hook.
@@ -127,11 +127,11 @@ There is **no** `uniqueness` validation type. A SELECT-then-INSERT rule is inher
Copy file name to clipboardExpand all lines: content/docs/deployment/cli.mdx
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -598,7 +598,7 @@ occupancy on its own.
598
598
599
599
| Category | Examples | Applied by |
600
600
|----------|----------|------------|
601
-
|`safe`| relax `NOT NULL` → nullable, widen a `varchar`, create a declared index, replace a legacy global unique with its tenant-scoped composite |`os migrate apply` (and dev auto-reconcile) |
601
+
|`safe`| relax `NOT NULL` → nullable, widen a `varchar`, create a declared index, replace a legacy installation-wide unique with its per-organization composite |`os migrate apply` (and dev auto-reconcile) |
602
602
|`needs_confirm`| non-narrowing type change, rebuild a non-unique index whose columns changed |`os migrate apply`|
603
603
|`destructive`| drop an orphaned column or index, tighten `NOT NULL`, narrow a type, rebuild an index as `UNIQUE`|`os migrate apply --allow-destructive`|
604
604
@@ -609,8 +609,8 @@ occupancy on its own.
609
609
| Op | What it means |
610
610
|----|---------------|
611
611
|`create_index`| Metadata declares an index the database does not have |
612
-
|`replace_unique_index`| A field's `unique` used to be enforced platform-wide, but metadata now scopes it per tenant — the legacy single-column index is swapped for the `(tenantField, field)` composite. A pure relaxation: it creates before it drops, and cannot fail |
613
-
|`recreate_index`| An index exists under the declared name but with different columns/uniqueness. The additive sync skips it by name, so it must be dropped and rebuilt |
612
+
|`replace_unique_index`| A field's `unique` used to be enforced installation-wide, but metadata now scopes it per organization — the legacy single-column index is swapped for the NULL-safe `(COALESCE(organization_id, '__global__'), field)` composite. A pure relaxation: it creates before it drops, and cannot fail |
613
+
|`recreate_index`| An index exists under the declared name but with different columns/uniqueness. The additive sync skips it by name, so it must be dropped and rebuilt. This is also how a per-organization unique becomes NULL-safe: a **tightening**, so it runs a duplicate pre-flight probe first — rows the old NULL-distinct index wrongly admitted **block** the op with a report instead of failing a boot, and the old index stays in place until they are resolved|
614
614
|`drop_index`| An index carrying ObjectStack's generated naming (`uniq_…` / `idx_…`) that metadata no longer declares |
615
615
616
616
Orphan detection is deliberately limited to indexes ObjectStack itself
@@ -620,7 +620,7 @@ drift, and `--allow-destructive` will not delete it.
620
620
<Callouttype="tip">
621
621
**Dev self-heal.**`os dev` runs the SQL driver with `autoMigrate: 'safe'`, so
622
622
safe changes (you just made a field optional; a `unique` field became
623
-
tenant-scoped) are applied to your existing dev database automatically on
623
+
organization-scoped) are applied to your existing dev database automatically on
624
624
restart — no `os migrate` needed, no data loss. Auto-reconcile is **dev-only and
625
625
never destructive**; it is force-disabled under `NODE_ENV=production`, where
626
626
every change is shown by `os migrate plan` before you apply it deliberately.
0 commit comments