Skip to content

Commit ddcf7c1

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-15783-typescript-import-thunks
2 parents 023a8d1 + 9e9f03a commit ddcf7c1

20 files changed

Lines changed: 2394 additions & 215 deletions
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
"@objectstack/plugin-auth": patch
3+
---
4+
5+
A self-registration grant is refused, not silently redirected, when a permission-set row is malformed — and the fourteen dead `{ records }` / `{ data }` normalizer limbs behind that code are gone.
6+
7+
`plugin-auth` carried fourteen array-or-envelope normalizer blocks of the shape `Array.isArray(x) ? x : x.records ?? []` (thirteen on a `records` limb, one on a `data` limb, four of them written as a guard clause rather than a ternary). All fourteen read the same concrete engine — the `ObjectQL` instance the kernel registers as the `objectql` / `data` service — which answers a bare array on every path, populated or empty. The envelope limb was unreachable code that read as a contract, so the next author writing a defensive normalizer here believed an envelope was possible. The limbs are removed, and the three local engine ports that declared `Promise<unknown>` (`BootProbeEngine`, `DevAdminSeedProbeEngine`, `PhoneSmsTemplateEngine`) now declare the array they always returned.
8+
9+
The user-visible change is in `settleSelfRegistrationGrant`, which carried the opposite defect. Its candidate filter dropped any permission-set row whose `id` was missing or blank, silently, before choosing which row to grant:
10+
11+
- When the malformed row was the only one, the operator was told `no active sys_permission_set row named 'X' resolves` — false, since an active row named exactly that was present. That report is the only signal this path emits, and nothing retries it.
12+
- When the malformed row was the **organization-scoped** one and a global row also carried the declared name, dropping it let the `organization_id == null` arm match instead, and the self-registrant was granted the **global** permission set their organization never declared — with a success log and no other trace.
13+
14+
`active !== false` remains a selection predicate: a deactivated set still reports the ordinary "does not resolve". A malformed row is no longer a selection at all — the grant is refused and the report names the malformed row, so the ambiguity is surfaced instead of resolved by accident. A well-formed family grants exactly as before.
15+
16+
**Upgrade note — one family now gets a refusal where it previously got a grant.** If a deployment's `sys_permission_set` already contains a row that is active and carries the declared name but whose `id` is missing or blank, self-registration grants against that name now stop and report, including the case where the malformed row is one nobody was relying on: a malformed **global** row sitting alongside a well-formed **organization-scoped** row used to be dropped silently, letting the org row be granted, and is now refused. This is deliberate — the old behaviour could not tell that family apart from the one where the silent drop granted the *wrong* set — and it is fully reversible without a code change: repair or delete the malformed row and the grant proceeds exactly as before. The refusal is loud and names the row, so it is visible rather than something to discover later; nothing is written while it stands.

content/docs/deployment/cli.mdx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ npm create objectstack@latest my-app
2626
cd my-app
2727
```
2828

29-
This scaffolds a working project with `objectstack.config.ts`, a sample object, and all dependencies installed — plus the AI skills bundle and an `AGENTS.md` for coding agents. (`os init` is the CLI's own scaffolder for plugin skeletons and bare configs — see [below](#os-init).)
29+
This scaffolds a working project with `objectstack.config.ts`, a sample object, and all dependencies installed — plus the AI skills bundle and an `AGENTS.md` for coding agents. (`os init` is the CLI's own scaffolder for metadata-plugin skeletons and bare configs — see [below](#os-init).)
3030

3131
### Add more metadata
3232

@@ -83,11 +83,35 @@ Scaffolds a new ObjectStack project with configuration, TypeScript setup, and in
8383
> **Which scaffolder?** For a new app, prefer **`npm create objectstack@latest`** — it
8484
> also derives your namespace, pins the framework packages to the current release, and
8585
> installs the AI skills bundle + `AGENTS.md`. Reach for `os init` when you want a
86-
> **plugin** skeleton or a **bare config** in an existing directory.
86+
> **bare config** in an existing directory — or a **metadata plugin**, which is only one
87+
> of the two different artifacts the word `plugin` names in this CLI.
88+
89+
**`plugin` names two artifacts. Route by the artifact you want, not by the word.**
90+
91+
| You want | Command | What it emits | Built by | Publishable? | Read next |
92+
|:---------|:--------|:--------------|:---------|:-------------|:----------|
93+
| A **metadata plugin** — declarative objects another stack loads, no kernel code | `os init <name> -t plugin` | `objectstack.config.ts` whose manifest declares `type: 'plugin'`, plus `src/objects/*.object.ts` | `objectstack compile` (its `build` script) | **No** — the emitted `package.json` is `private: true` | [`os init`](#os-init) below, and [Object Metadata](/docs/data-modeling/objects) for the objects it holds |
94+
| A **kernel code plugin** — TypeScript implementing the kernel `Plugin` contract | `os create plugin <name>` | `src/index.ts` exporting a `Plugin` with `init` / `destroy` | `tsc` (its `build` script) | **Yes** — a publishable `@objectstack/plugin-<name>` package | [`os create`](#os-create) below, then [Plugin Anatomy](/docs/plugins/anatomy) and [Plugin Development](/docs/plugins/development) |
95+
96+
Every page under [Plugins & Packages](/docs/plugins) teaches the **kernel code** plugin, so
97+
`os create plugin` is the scaffolder those pages mean — `os init -t plugin` will not give
98+
you a `Plugin` to implement, and `os create plugin` will not give you declarative objects
99+
to compile.
100+
101+
<Callout type="info">
102+
**Why the two scaffolders are deliberately separate.** Merging the `os init` and
103+
`os create` command families was measured and ruled against in
104+
[#15531](https://github.com/objectstack-ai/objectstack/issues/15531): the two commands
105+
emit two different artifacts, so collapsing a metadata plugin and a kernel code plugin
106+
under one command word would make this collision **structural** instead of merely
107+
documented — teaching the wrong artifact to everyone, human or agent, who generates a
108+
plugin from the CLI. The collision, and the misdirection this table replaces, are recorded
109+
in [#15817](https://github.com/objectstack-ai/objectstack/issues/15817).
110+
</Callout>
87111

88112
```bash
89113
os init my-app # Create with default "app" template
90-
os init my-plugin -t plugin # Create a plugin project
114+
os init my-plugin -t plugin # Create a metadata plugin project
91115
os init blank -t empty # Minimal config only
92116
os init my-app --no-install # Skip dependency installation
93117
```
@@ -102,7 +126,7 @@ os init my-app --no-install # Skip dependency installation
102126
| Template | What it creates |
103127
|----------|-----------------|
104128
| `app` | Full application with objects, barrel imports |
105-
| `plugin` | Reusable plugin package with objects |
129+
| `plugin` | **Metadata** plugin: declarative objects, built by `objectstack compile`, `private`*not* the kernel code plugin `os create plugin` emits |
106130
| `empty` | Minimal project with just `objectstack.config.ts` |
107131

108132
#### `os dev`
@@ -1281,8 +1305,9 @@ third-party extension primitive, authored as `src/skills/<name>.skill.ts` with
12811305

12821306
#### `os create`
12831307

1284-
Scaffolds a **standalone** project — a plugin, or an example application — into
1285-
the current directory:
1308+
Scaffolds a **standalone** project — a **kernel code** plugin (the `Plugin` contract,
1309+
built by `tsc`, publishable; *not* the metadata plugin `os init -t plugin` emits — see
1310+
[Which scaffolder?](#os-init)), or an example application — into the current directory:
12861311

12871312
```bash
12881313
os create plugin analytics # Create ./plugin-analytics

docs/adr/0087-metadata-protocol-upgrade-contract.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,7 @@ gate's log and its `--list` output.
585585
<!-- adr-0087: not-required (no-migration-prescription) <why> -->
586586
<!-- adr-0087: not-required (runtime-interface-only <path>#<Symbol>[, ...]) <why> -->
587587
<!-- adr-0087: not-required (type-surface-only <path>#<Symbol>[, ...]) <why> -->
588+
<!-- adr-0087: not-required (type-surface-only <path>#<a>.<b>.<member>[, ...]) <why> -->
588589
```
589590

590591
**The vocabulary is closed, and every exemption is re-verified on every run** — an
@@ -676,6 +677,7 @@ The vocabulary above gained a sixth answer:
676677

677678
```text
678679
<!-- adr-0087: not-required (type-surface-only <path>#<Symbol>[, ...]) <why> -->
680+
<!-- adr-0087: not-required (type-surface-only <path>#<a>.<b>.<member>[, ...]) <why> -->
679681
```
680682

681683
### The dead end it closes
@@ -748,6 +750,23 @@ checks all four by name (`published`, `no-spec-diff`, `no-metadata-surface-diff`
748750
*both* revs and requires `any` / `unknown` / no annotation at base, and a
749751
concrete type at HEAD.
750752

753+
**The reference is a bare symbol OR a dotted member path.** `<path>#<Symbol>`
754+
resolves a bare name to the **first same-named definition in the file**, and on a
755+
real SDK module that is not a symbol identity: `packages/client/src/index.ts`
756+
declares `get` 14 times and `delete` 10 times, so the members PR #15445 and PR
757+
#15451 actually narrowed had **no addressable spelling at all** — the category
758+
was closed to them by the grammar rather than by any judgement about the claim,
759+
and the gate answered a true sentence about a member the diff never touched
760+
(#15627). A reference may therefore also be written `<path>#<a>.<b>.<member>`:
761+
the object-literal nesting the member sits in, walked **structurally** from the
762+
top of the file over a comment- and literal-masked projection, with the member's
763+
definition taken from inside the resolved body. Bare references keep their exact
764+
previous meaning. ⛔ A line number is never the disambiguator — this file's line
765+
numbers were measured to rot within one day. A dotted path that resolves to
766+
**zero** candidates, or to **more than one**, is reported by name and refused,
767+
never guessed at: a reference that silently landed on the wrong same-named member
768+
would be writable but wrong, which is worse than the refusal it replaces.
769+
751770
### ⭐ Predicate 4 is what makes this a narrowing rather than a hole
752771

753772
This is the **only** category exempt from the `no-migration-prescription` refusal.

0 commit comments

Comments
 (0)