Commit ab52182
fix(cloud-connection,plugin-security): a hot install fires the package record-change flows and projects its permission sets without a restart (#21488)
Part of #21322. This PR covers the flows and permission-set half. The
jobs half is left open for a decision (see "Jobs" below), so merging
this must not close the card.
Clause-②: no
After `os package install ./dist/objectstack.json` into a running `os
start`, the installed package's record-change flow now fires, and its
permission set has its `sys_permission_set` row right away. Before this,
both needed a restart. The restart path and the `--artifact` boot path
are unchanged, and each reads the same as before.
## What was measured first (the card's premise holds on `main` 4c8363f,
after PR #21401)
This was measured at the public door: a new CLI integration suite spawns
`os start`, runs `os package install` against it, and probes the result
over REST. The runtime boots a host artifact that declares `requires:
['automation', 'triggers']`. An empty `os start` composes neither
capability, so on an empty kernel no flow fires at all, whether
hot-installed or restarted. The package reaches the runtime only through
the install.
| phase | `sys_permission_set?name=tasks_app_task_user` | flow note
after `PATCH status=done` | `sys_job?name=tasks_app_tick` |
|---|---|---|---|
| hot install, before | **0 rows** | **0 rows** | 0 rows |
| restart on the same home, before | 1 row (`managed_by: package`) | 1
row | 0 rows |
| `os start --artifact` control, before | 1 row | 1 row | 1 row (active)
|
| hot install, **after** | **1 row** (`managed_by: package`,
`package_id: com.example.tasksapp`) | **1 row** | 0 rows |
| restart, after | 1 row | 1 row | 0 rows |
| control, after | 1 row | 1 row | 1 row |
## Where the boot does this work (measured from the symbols, not the
card's line numbers)
- **Flows.** Binding is done by `service-automation`'s
`AutomationServicePlugin`: `syncFlowsFromProtocol` on `kernel:ready`,
and `resyncFlowsFromProtocol` on `metadata:reloaded`. `AppPlugin.start`
has no flow step.
- **Permission-set projection.** This is done by `plugin-security`'s
`SecurityPlugin.runBootstrap` on `kernel:ready`, through
`seedCatalogPermissions` and then `bootstrapDeclaredPermissions(ql,
metadata, …)` (ADR-0086 D5). That pass reads
`ql.registry.listItems('permission')`. Nothing re-ran it after the boot.
- **Why a restart worked.** The install-local rehydrate runs inside
`kernel:ready` and is registered before both sweeps, so they read the
rehydrated package. A hot install registers the package after both
sweeps have already run.
## What changed
- **`@objectstack/cloud-connection`, the install route.** As its last
step, after register, schema sync, the #21321 handler binder, the ledger
write and the seed, the route announces `metadata:reloaded` with
`changed: ['app/MANIFEST_ID']`. This is the platform's one post-boot
re-sync signal. A Studio package publish (`publish-drafts`), a per-item
publish and an artifact reload already announce it. It runs after the
seed because that is where the boot runs these sweeps: a record-change
flow bound before the seed would fire on every seeded row. A subscriber
failure is logged at `warn` with the restart that repairs it, and never
fails the install. The rehydrate does not announce, so the restart path
is unchanged.
- **`@objectstack/plugin-security`.** A `metadata:reloaded` subscriber
re-runs the same declared-permission seeding the boot runs. It uses the
same function, the same organization passes (`catalogSeedPasses`) and
the same provenance rules. It runs only once the boot's own pass has
finished (`bootstrapRanOnce`), so the platform defaults keep their
insert-once shape. It never throws, because `trigger` dispatch
propagates. The seeder is idempotent and writes nothing when no set
changed. As a side effect, the artifact-reload door gets the same
projection.
- Nothing changed in `packages/runtime` (`app-artifact-handlers.ts` and
`app-plugin.ts` are untouched), in `packages/spec`, `service-automation`
or `objectql`. The install response and the CLI output keep their fields
and text.
**The landing point differs from the claim's file surface, and why.**
The claim expected `packages/runtime/src/app-artifact-handlers.ts`, and
triage said flows and permission-set projection would "extend that one
binder". The measurement shows that at boot, neither flows nor
permission-set projection is an `AppPlugin.start` step that the binder
could share. Both are `kernel:ready` sweeps owned by the consumer
plugins. `bindAppArtifactHandlers` is a synchronous `ql`-only function,
and `AppPlugin.start` calls it before `kernel:ready`. Putting flow
binding or projection into the binder would have been exactly the second
path the ruling forbids. So the hot install re-runs the consumers' own
sweeps, and the one edit outside this lane is the producer side in
`packages/plugins/plugin-security`. That edit is a cross-lane path,
named here for the seat to declare.
## Jobs: measured, not folded in (needs a decision)
An installed package's `defineStack({ jobs })` are never scheduled by
install-local, on a hot install or after a restart (table above). The
control schedules them. That is not a missing registration step. A job's
`handler` names a `functions` entry, a compiled artifact carries only
the lowered string ref, and the callable rides in the sibling
`objectstack-runtime.HASH.mjs` that only `os start --artifact` imports
(`mergeRuntimeModule`). An inline install sends the JSON alone, so no
step can resolve a handler. The ruling's exception arm (the install
response and the CLI name what did not bind) would widen the public
response and CLI surface. The hazard note says to stop before writing
that, so it is not in this PR. The options are in the report on the
card.
## Tests
-
`packages/cli/test/package-install-local-boot-steps.integration.test.ts`
(integration tier, new). It has three phases: hot install, restart on
the same home, and the `--artifact` control. Each phase pins the
`sys_permission_set` row and the flow's note. Result at `ed91d99506`: 7
of 7 green. The #21321 sibling
`package-install-local-handlers.integration.test.ts` ran in the same
run, 17 of 17 green. The new announce does not double-bind the installed
package's hooks or actions.
-
`packages/cloud-connection/src/marketplace-install-local-hot-resync.test.ts`
(new, 5 tests). The install announces once, naming the app, after
register and persist. A reinstall announces again. The rehydrate
announces nothing. A throwing subscriber leaves the install at 200 with
one `warn` that names the restart. A context without `trigger` says so.
-
`packages/plugins/plugin-security/src/declared-permission-reload-projection.test.ts`
(new, 3 tests). The tests drive the real `SecurityPlugin` hooks. A set
registered after `kernel:ready` gets its row, with package provenance,
on the reload. A second reload adds no row. A reload before the boot
pass writes nothing. Its engine double is recorded in
`scripts/engine-double-contract.pinned.json`, as the gate asks.
- Full suites: `@objectstack/cloud-connection` 32 files, 406 tests
green. `@objectstack/plugin-security` 163 files, 3522 tests green (45
skipped). `@objectstack/cli --project unit` 248 files green. Two
published-subpath pins first stopped on PREREQUISITE NOT MET (no CLI
`dist`) and were green after `pnpm --filter @objectstack/cli build`.
Typecheck is green for all three packages.
## Ablations (one-shot; each leg mutated through
`scripts/ablation-replace.mjs`, proven in `dist/` with
`ablation-dist-preflight.mjs`, restored and rebuilt)
- **Leg A: the install-route announce replaced by a marker.** The pin
read: install phase, permission-set row red and flow red; restart and
control green (2 failed, 5 passed). The leg's DTS step failed on TS6133
for the now-unused private method, but the JS bundles carried the
marker, and preflight proved it in `dist/`. The unit file read 4 red,
with the rehydrate case green.
- **Leg B: the security subscriber renamed to a non-event.** The pin
read: only the install-phase permission-set row red; the install-phase
flow stayed green (1 failed, 6 passed). The two halves are independent.
The unit file read 2 red, with the before-boot control green.
- Both restore legs: rebuilt, `--absent` preflight green, tree clean
against HEAD.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` was re-derived with no paths after the last
code commit. `--ran` reconciliation: **76 derived, 76 run, 0
NOT-MEASURED**, each with a recorded exit 0.
`check:dual-build-cjs-loads` first exited 3 (PREREQUISITE NOT MET) and
was green after building the 9 unbuilt packages.
`check:engine-double-contract` first exited 1 until the new test's
double was recorded. The last commit (`f1fefdf6e9`) only adds an
ADR-0086 D5 anchor to one comment. The comment-reading gates and
`check:adr-anchors` were re-run on it, all green.
`pnpm lint` (CI-owned) as a proven narrowing at `ed91d99506`. ① ESLint's
own `isPathIgnored` reports all 5 changed TS files as linted. The
changeset and the JSON ledger are not in any config object. ② `eslint
--no-inline-config --format json` over them gives 5 files, 0 errors and
0 warnings, and 1 file, 0 and 0 on `f1fefdf6e9`. ③ `eslint.config.mjs`
enables no type-aware linting: no `parserOptions.project` and no typed
rules, as its own header states. It has no cross-file import rules
either, so this diff cannot move a verdict on any untouched file.
## Acceptance notes
- **Uninstall symmetry**, measured because this change makes it
reachable without a restart. After a hot install, `DELETE
/api/v1/marketplace/install-local/com.example.tasksapp` answers 200. The
flow still fires and the set's row stays, which matches the route's
documented "remains loaded until the next restart". After a restart the
package's object answers 404, but the `sys_permission_set` row stays.
The pre-existing path (install, restart, DELETE, restart) leaves the
same row. Install-local's DELETE runs no `registerUninstallCleanup`
(`security.package-permissions`). That is reported as a finding on the
card, not fixed here. `DELETE /api/v1/packages/com.example.tasksapp`
answers 422 `WRITABLE_PACKAGE_REQUIRED`, which is a different door.
- **Same family, not measured.** A hot-installed package's declared
`positions` and `capabilities`, and the ADR-0090 audience-binding
suggestion for an `isDefault` set, are also seeded only by the
`kernel:ready` bootstrap. This PR re-runs only the permission-set
seeding the card names.
- **Composition.** `os package install` cannot add capabilities to a
running runtime. A package whose flows need `automation` and `triggers`
installs green into a runtime booted without them, and its flows never
fire, before or after a restart.
- **Docs drift.** The `metadata:reloaded` description in
`packages/spec/src/contracts/plugin-lifecycle-events.ts` still names
only the artifact watcher as its emitter, but it has four now. Carrier:
none (spec-seat file).
- `main` moved 3 commits past the base (4c8363f) during the run. None
touches `packages/cloud-connection`, `plugin-security`, `runtime`,
`service-automation`, `objectql` or `packages/cli`, so the branch was
not merged forward.
---
_Generated by [Claude
Code](https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 100c394 commit ab52182
7 files changed
Lines changed: 827 additions & 0 deletions
File tree
- .changeset
- packages
- cli/test
- cloud-connection/src
- plugins/plugin-security/src
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
Lines changed: 368 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 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
0 commit comments