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
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.
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).)
30
30
31
31
### Add more metadata
32
32
@@ -83,11 +83,35 @@ Scaffolds a new ObjectStack project with configuration, TypeScript setup, and in
83
83
> **Which scaffolder?** For a new app, prefer **`npm create objectstack@latest`** — it
84
84
> also derives your namespace, pins the framework packages to the current release, and
85
85
> 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 |
| 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
+
<Callouttype="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>
87
111
88
112
```bash
89
113
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
91
115
os init blank -t empty # Minimal config only
92
116
os init my-app --no-install # Skip dependency installation
0 commit comments