Repository navigation
Commit 0ee2d15
Fixes #22257
Clause-②: no
## What was wrong
On `examples/app-showcase`, `os dev --seed-admin --fresh` logged one
`[SettingsService] Pre-bind READ of namespace 'auth'` on every boot.
Re-measured on `origin/main` `79c35d45` (after PR #22295): 1 line, in
the boot-diagnostics block.
**The reader.** A temporary stack capture in the built
`reportPreBindRead` (applied to `service-settings/dist/index.js` and
reverted; the sha256 matched before and after, and the marker count
after the revert was 0) named it:
`SettingsService.getNamespace` from `AuthPlugin.bindAuthSettings`, then
`applySettings` (`auth-plugin.ts:1535` at `79c35d45`), from
`AuthPlugin.ensureAuthSettingsBound`, from `runBackfill` (`:1235` /
`:1244`), from the kernel's `trigger`.
The handler is the `app:seeded` hook registered in `AuthPlugin.start()`
(`:1298`), which calls `runBackfill('app:seeded')`. It is the ADR-0093
D6 one-time membership backfill.
**The phase.** A debug-level boot puts the read in Phase 2 (start). It
comes after `[Seeder] Seed loading complete` (132 rows,
`plugin.app.com.example.showcase`) and before `Triggering kernel:ready
hook`. `AppPlugin.start()` emits `app:seeded` when its inline seed
lands. On `os dev`, the auth plugin started first, so its handler ran
during Phase 2. `SettingsServicePlugin` binds the engine in its
`kernel:ready` hook (`settings-service-plugin.ts:221`), which is later.
The `optionalDependencies: ['com.objectstack.service.settings']` edge on
`AuthPlugin` orders only `kernel:ready` HOOKS. An event fired during
Phase 2 is outside it.
**The consequence.** `ensureAuthSettingsBound` is memoized, so the auth
binding was computed from the manifest defaults. In the debug log,
`Auth: bound to settings namespace=auth` appears at 14:18:11.661, before
`kernel:ready` at 11.672. The persisted rows were re-applied later only
through a fire-and-forget subscription callback. The one-time pass's
first run read the default policy `auto`.
**Why `check:settings-bind-window` missed it.** The gate walks `init()`
/ `start()` bodies and `kernel:ready` handlers only (`READY_HOOK`). It
reached this same read through the `kernel:ready` registration of
`runBackfill`, and classified it `declared`. The `app:seeded`
registration fires during Phase 2, from another plugin's `start()`, and
is not in the gate's population. This is reported as a blind spot below;
this PR adds no gate.
## The fix (`packages/plugins/plugin-auth/src/auth-plugin.ts`)
The one-time pass is now armed by its own `kernel:ready` hook. Any
trigger before that does nothing: `app:seeded` during Phase 2, or
`default-org-created` from the bootstrap middleware. The `kernel:ready`
pass still runs afterwards, after the settings bind, and scans every row
such a trigger was about. Triggers after `kernel:ready` behave exactly
as before. That includes an over-budget seed that settles in the
background (the #2996 path).
- No change to which settings are read, only to when.
- No change to the reporter's level or text.
- No `packages/spec` change.
- The comment above `ensureAuthSettingsBound` in `runBackfill` was
stale: it said "registered in `init()`". It is corrected.
## Boot, before and after
| | `Pre-bind READ` lines | `Auth: bound to settings namespace=auth` |
|---|---|---|
| `79c35d45` (base) | 1 (`auth`) | Phase 2, before the `kernel:ready`
trigger |
| `9b7ac6cf` (fix; plugin-auth dist rebuilt, `backfillArmed` grep 3 in
`dist/index.mjs`) | 0 | after the `kernel:ready` trigger (16.538 vs
16.381) |
In both boots the D6 pass still records `adr-0093-membership-backfill`
once. The other boot-diagnostic lines are unchanged: `sys_migration`
UNIQUE and `[sharing-rule]`. See the acceptance notes.
## Pins
`packages/plugins/plugin-auth/src/auth-settings-seeded-boot.pin.test.ts`
composes:
- a real `ObjectKernel`
- ObjectQL on in-memory SQLite
- the REAL `SettingsServicePlugin`
- the real `AuthPlugin`, used before the settings plugin (the `os serve`
order)
- an app plugin in `AppPlugin`'s place: it writes the persisted
`auth.membership_policy = invite-only` row, then emits `app:seeded` from
its `start()`.
The dogfood harness cannot compose this. `bootStack` registers
`AppPlugin` before `AuthPlugin`, so `app:seeded` fires before auth
registers its handler.
The main case asserts four things:
1. The window is real: at `app:seeded`, the settings engine is unbound.
2. No `Pre-bind READ` is reported.
3. The auth binding's first `getNamespace('auth')` answers `{ value:
'invite-only', source: 'global' }`.
4. Every run of the one-time pass gets `policy: 'invite-only'`.
A control case forces a pre-bind read of `auth` in the same composition
and expects exactly one report.
`@objectstack/service-settings` is added as a devDependency of
plugin-auth. It is aliased to `src/` in `vitest.config.ts` (anchored,
like the three existing entries), so `KNOWN_UNALIASED_TEST_IMPORTS` is
unchanged. The lockfile gains only that importer entry.
**Control.** `settings-prebind-read-warning.test.ts` passes 9/9
unchanged. It still fires on a forced pre-bind read.
**Ablation**, via `scripts/ablation-replace.mjs` from committed
`d907051b`, with an EXIT/INT/TERM restore and the restore proven by blob
== HEAD and an empty `git diff HEAD`. `AuthPlugin` is imported
relatively, so `src/` is what runs and no dist rebuild was involved.
- Leg 1: delete `if (!backfillArmed) return backfillChain;` (anchor 1 to
0, blob `d559d607` to `83e4649d`). The main case goes RED at assertion
2: one `Pre-bind READ of namespace 'auth'`. The control stays green.
Restored to `d559d607`.
- Leg 2: the same deletion held, plus assertion 2 removed from the test.
The main case goes RED at assertion 3: the first `auth` read answered `{
value: 'auto', source: 'default' }`. Both files restored (blob == HEAD).
## Verification (the gate union was run on `b67e95f1`)
- `pnpm --filter @objectstack/plugin-auth exec vitest run
--maxWorkers=2`: 129 files, 2639 passed, 10 skipped, exit 0. This ran on
`d907051b`. The merge after it brought only `docs/adr/0096`.
- `pnpm --filter @objectstack/plugin-auth run typecheck`: exit 0.
`check:test-typecheck` is OK, and the new file is in the
`tsconfig.test.json` program (`--listFiles`: 1).
- `node scripts/pm/dispatch-gates.mjs --commands` (no paths) at
`b67e95f1` derived 79 commands, and all 79 exited 0. `--ran`: 79
derived, 79 run, 0 NOT-MEASURED, 0 UNRUN.
- `pnpm check:settings-bind-window`: `4 declared / 0 self / 1
structurally upstream / 0 ledgered (73 plugin unit(s) scanned)`.
- Selected verdict lines:
- `check-test-source-alias OK — 73 packages with tests scanned; 60
registered`
- `check:workspace-manifest-cycles OK: 80 workspace package(s), 515
workspace: edge(s)`
- `check-nul-bytes: OK`
- `check:undeclared-dep-imports` passed
- `check-adr-0087-registration: 1 non-breaking changeset(s) seen`
- eslint, narrowed to the 3 changed TS files: 0 errors and 0 warnings in
`--format json`. Each file has a non-empty rule set under
`--print-config`. `eslint.config.mjs` enables no type-aware linting, so
this diff cannot move a verdict on an untouched file. The repo-wide
`pnpm lint` is left to CI.
- NOT MEASURED: CI's Test Core, Dogfood, Build Core and the type-check
lanes. These are CI-owned.
## Acceptance notes
- **Gate blind spot (not fixed; no gate added).**
`check:settings-bind-window` cannot see two things:
- a settings read reached through a hook other than `kernel:ready` that
fires during Phase 2 (`app:seeded`, emitted from `AppPlugin.start()`);
- a read reached through a data-middleware or callback closure fired by
a Phase-2 write.
Its header says "Everything that runs before that bind hook is the
window." Its population is narrower than that window.
- **Landing order with PR #22186.** That PR makes `AppPlugin` declare
`com.objectstack.auth` as an optional dependency, and creates the
Default Organization in `AuthPlugin.start()`. Without this fix, the
Phase-2 `app:seeded` pass would then have a target and would DECIDE the
one-time backfill under the manifest-default policy. This fix arms the
pass at `kernel:ready`, so it holds under either landing order. The two
diffs touch different regions of `auth-plugin.ts`: theirs is around
`:725` and `:1168`, this one is `:1232` to `:1316`.
- **Unchanged boot line.** `WARN Insert operation failed
{"object":"sys_migration", … UNIQUE constraint failed:
sys_migration.id}` appears on the fresh showcase boot both before and
after this change. It comes right after
`adr-0093-default-org-owner-bind` is recorded. Root cause is NOT
MEASURED; it is reported to the seat.
- **Merge state.** `origin/main` was merged at `799eb000`. `main` has
since moved 4 commits. None touches plugin-auth or service-settings;
plugin-security's strict mode is the nearest, and this composition
mounts no `SecurityPlugin`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WkL6Eijt432S1Y7ekb6ovQ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent f4069c5 commit 0ee2d15
7 files changed
Lines changed: 301 additions & 13 deletions
File tree
- .changeset
- packages/plugins/plugin-auth
- src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1232 | 1232 | | |
1233 | 1233 | | |
1234 | 1234 | | |
| 1235 | + | |
| 1236 | + | |
| 1237 | + | |
| 1238 | + | |
| 1239 | + | |
| 1240 | + | |
| 1241 | + | |
| 1242 | + | |
| 1243 | + | |
| 1244 | + | |
| 1245 | + | |
| 1246 | + | |
| 1247 | + | |
1235 | 1248 | | |
| 1249 | + | |
1236 | 1250 | | |
1237 | 1251 | | |
1238 | 1252 | | |
1239 | 1253 | | |
1240 | | - | |
1241 | | - | |
1242 | | - | |
1243 | | - | |
| 1254 | + | |
| 1255 | + | |
| 1256 | + | |
| 1257 | + | |
1244 | 1258 | | |
1245 | 1259 | | |
1246 | 1260 | | |
| |||
1288 | 1302 | | |
1289 | 1303 | | |
1290 | 1304 | | |
1291 | | - | |
| 1305 | + | |
| 1306 | + | |
| 1307 | + | |
| 1308 | + | |
1292 | 1309 | | |
1293 | 1310 | | |
1294 | 1311 | | |
1295 | 1312 | | |
1296 | 1313 | | |
1297 | | - | |
| 1314 | + | |
| 1315 | + | |
1298 | 1316 | | |
1299 | 1317 | | |
1300 | 1318 | | |
| |||
Lines changed: 7 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
| |||
Lines changed: 244 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
51 | 59 | | |
52 | 60 | | |
53 | 61 | | |
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
0 commit comments