Repository navigation
Commit 901e7cf
fix(cloud-connection): an install-local uninstall withdraws the package from the running kernel (#21581)
Fixes #21576
Clause-②: no
## What this changes
- The install-local `DELETE
/api/v1/marketplace/install-local/:manifestId` now withdraws the package
from the running kernel. It does this right after the ledger removal,
through `SchemaRegistry.uninstallPackage`, the one verb `deletePackage`
uses. The uninstall cleanups from #21490 then run as before.
- Every later reader of the registered packages is now right without a
special case. That includes plugin-security's declared-permission
seeding on `metadata:reloaded`, which is what re-projected the
uninstalled package's set.
- This follows triage's ruling `5968468119`: the first seam, the root.
There is no "skip uninstalled" check in plugin-security.
- Nothing changes in `packages/metadata-protocol`,
`packages/plugins/plugin-security` or `packages/spec`. The install and
rehydrate path of `marketplace-install-local-plugin.ts` is untouched.
Files:
- `packages/cloud-connection/src/marketplace-install-local-plugin.ts`:
the uninstall path only. That is `handleUninstall`, the new
`withdrawFromRunningKernel`, the corrected header block and the
corrected log line.
-
`packages/cloud-connection/src/marketplace-install-local-uninstall-withdrawal.test.ts`:
a new unit pin, 6 cases.
-
`packages/cli/test/package-install-local-uninstall-cleanups.integration.test.ts`:
order 3's two `it.fails` promoted to plain `it`, a hot-object case in
orders 1 and 2, and a new order 4.
- `.changeset/21576-install-local-uninstall-withdraws-registration.md`:
`@objectstack/cloud-connection` patch, `Clause-②: no`.
## A1: reproduction on `74281a8e4a`
- I ran order 3 with its two `it.fails` changed to plain `it`, on
packages built in this worktree. It read `Tests 2 failed | 11 passed
(13)`. The 13 includes one temporary measurement case, used for A4
below. The two red cases are exactly the two set readings:
- the other package's hot install re-projects the set;
- the set survives the restart.
- Then I restored the file: its blob equals HEAD's (`72bd1cc16e`), and
`git status --porcelain` was empty.
- After the fix, the same two cases are plain greens, and the
grant-stays-revoked case stays green.
## A2: how the door reaches the registry
- `deletePackage` reaches the registry as
`this.engine.registry.uninstallPackage(packageId)`. `this.engine` is the
ObjectQL engine the protocol is assembled over
(`assembleMetadataProtocol(ctx, this.ql, …)` in
`packages/objectql/src/plugin.ts`).
- The same plugin registers that engine as the `objectql` service. The
`manifest` service, which this door installs through, calls
`ql.registerApp` on it.
- So the door reads `ctx.getService('objectql').registry` and calls
`uninstallPackage(manifestId)`. The call is typed by the spec contract
`IObjectQLEngine.registry` (`EngineSchemaRegistryView.getPackage` /
`uninstallPackage`), with no `as any`.
- It uses one verb, and there is no second unregistration mechanism.
- The id is the manifest id, which is the key `installPackage` files the
record under. The cleanups get the same id.
## A3: order and failure
The order is: ledger removal, then the withdrawal, then the cleanups.
The withdrawal is synchronous, and nothing is awaited between it and the
ledger removal. It goes first for three reasons:
1. `deletePackage` uses the same order: the registry withdrawal, then
`runUninstallCleanups`.
2. It closes a window. The cleanups await the store row by row. If the
package were still registered while they ran, a concurrent hot install's
`metadata:reloaded` could re-project the sets they had just removed.
3. The one cleanup registered today, `security.package-permissions`,
loses nothing. It selects by package id in the store and never reads the
registry. Measured: orders 1 and 2 still report it as `success: true`,
and the set and the grant are revoked.
Nothing is withdrawn when the uninstall did not happen. The unit pin
covers each case:
- a refused caller (401);
- an id this door never installed (404), even one the running registry
holds;
- a failed ledger write (500).
When the withdrawal fails:
- If the withdrawal throws (ADR-0029: another package extends an object
this one owns), or the registry cannot be asked, the response carries
one failed outcome named `registry.uninstallPackage` in `cleanups`. That
is the way a failed cleanup is reported.
- The cleanups still run, and the request still succeeds, because the
ledger entry is already gone.
- The operator log names the cause and the remedy. The thrown text stays
in the log and never reaches the wire.
When the registry does not hold the package (for example, a cloud
install whose hot-register failed), there is nothing to withdraw: no
call and no outcome.
## A4: what `uninstallPackage` withdraws, and what the object doors
answer
From `SchemaRegistry.uninstallPackage`
(`packages/objectql/src/registry.ts`), the verb withdraws:
- the package's object contributions, including the overlay layer over
an owned object;
- its namespace;
- every metadata item keyed to the package (`unregisterItemsByPackage`);
- its boot disable seed;
- the package record.
It does not touch tables or rows.
**The object doors' answer moves, as intended.** These are the package
object's answers on the data route, read hot in the same process right
after the DELETE:
| | order 1 | order 2 | order 3 | order 4 |
|---|---|---|---|---|
| before the fix (`74281a8e4a`, measured) | 200, empty list | 200, empty
list | 200, empty list | (the order is new) |
| after the fix (pinned) | 404 | 404 | read, not asserted | 404 |
After the fix, the hot answer carries the same error code the object
answers after a restart. This is the consequence of "withdraws the
package from the running registry". Orders 1, 2 and 4 pin it.
**The reinstall control.** I added order 4: hot install, then DELETE,
then a hot reinstall of the same package in the same process. The object
answers 200 again, and the set is projected again, exactly once, as the
package's own. So the install path puts back everything the withdrawal
removes.
**One more value moves with the registry state.** A same-process
reinstall after an uninstall is now classified as a fresh install, so
the install answer's `upgradedFrom` is `null` instead of
`previous-marketplace-version`. A reinstall after a restart already
answered `null`. No key moves, no value leaves the existing set, and
nothing in this repository reads the field.
## A5: the note
- Before: "… The app remains loaded in the running kernel until the next
restart (the kernel API does not support unregistering apps in-place)."
- After: "Cached manifest removed, the package withdrawn from the
running kernel, and the uninstall cleanups this runtime's plugins
registered ran — each one's outcome is in `cleanups`."
- When the withdrawal fails, the note says the package stays loaded
until the next restart, and points at the `registry.uninstallPackage`
entry.
- The file header and the info log line are corrected the same way.
Nothing parses the note.
## A6: reverse verification
**The mutation.** `scripts/ablation-replace.mjs` replaced the anchor
`registry.uninstallPackage(manifestId);` with a marker statement that
logs `ABLATED_21576_withdrawal`. The anchor went x1 to x0 and the
replacement x0 to x1. The blob changed from `7667b86205` to
`83d263078b`.
**The build.** I rebuilt `@objectstack/cloud-connection`.
`ablation-dist-preflight` found the marker in `dist/index.js` and
`dist/index.cjs`.
**The readings:**
- Unit (the withdrawal file and the #21490 cleanups file): `2 failed |
12 passed`. The two red cases are the ones that read the withdrawal.
- Integration: `5 failed | 11 passed`. The red cases are:
- order 3's two promoted set readings;
- both hot-object cases;
- order 4's precondition (404 right after the DELETE).
- The controls stayed green:
- orders 1 and 2: the preconditions, the DELETE reporting the security
cleanup, and the set and grant revoked right after the DELETE and after
a restart;
- order 3: the grant stays revoked;
- order 4: the reinstall.
**The restore.** `ablation-replace` restored the file: its blob equals
HEAD's `7667b86205`, and `git diff HEAD` is empty. After a rebuild,
`ablation-dist-preflight --absent` found the marker absent from all 6
built files, with the whole tree clean.
## Tests and gates, at `9f3e65608d`
- **Integration file**, on packages built in this worktree with no dist
overlay (turbo build of `@objectstack/cli^...`, then
`@objectstack/cloud-connection` rebuilt): `Test Files 1 passed (1) /
Tests 16 passed (16)`.
- **`@objectstack/cloud-connection` full suite:** `34 passed (34)`
files, `420 passed (420)` tests.
- **`@objectstack/cloud-connection` typecheck:** both programs are
green, and `--listFiles` shows the new test file in both.
- **`@objectstack/cli`:** typecheck is green, including
`check:test-typecheck`, and the tier partition pin passes 22/22. The
only `packages/cli` change is the integration-tier file above, which ran
in full. The rest of the unit tier is declared to CI.
- **`dispatch-gates --commands`:** it derives 64 commands, and all 64
exit 0.
- `check:dual-build-cjs-loads` first answered PREREQUISITE NOT MET,
because a whole-repo `dist/` was missing. After `pnpm build` it exits 0.
- The `--ran` reconciliation with an exit code per line reads "64 run, 0
NOT-MEASURED (a DERIVED zero)".
- Two path-matched families take a value from the workflow and cannot
run locally: `check-issue-citations --census` and
`check-shard-attestation`. NOT MEASURED, left to CI.
- **`pnpm lint`** (`eslint . --no-inline-config`), the full run with no
narrowing: exit 0.
## Acceptance notes
- **A collision edge, not handled.** Suppose a ledger entry's id is also
a config-defined app's id. The install door refuses to create that
(`MANIFEST_CONFLICT`), but an older ledger can still overlay the app at
rehydrate. The DELETE of that entry now withdraws the id from the
running kernel until a restart registers the config app again. The
#21490 cleanups already removed that id's package-managed sets. Carrier:
none.
- **State outside the registry is untouched by the withdrawal**, as it
was before: the package's tables and rows, the handlers
`bindArtifactHandlers` bound, and the i18n bundles and seed datasets
stashed at install. Once the objects are withdrawn, the bound handlers
have no object route to fire through. This PR does not change any of it,
and I did not measure it further.
- **An existing unit fake.** The `objectql.registry` fake in
`marketplace-install-local-id-gate.test.ts` carries only
`getAllPackages`. So its DELETE case now reports a failed
`registry.uninstallPackage` outcome. That case asserts the status,
`success` and the ledger removal, and all three still hold. I left it as
is.
- **A failed withdrawal cannot be retried through this door,** because
the ledger entry is already gone. The remedy is the restart, and the log
line names it.
---
_Generated by [Claude
Code](https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent a7ab047 commit 901e7cf
4 files changed
Lines changed: 461 additions & 36 deletions
File tree
- .changeset
- packages
- cli/test
- cloud-connection/src
Lines changed: 12 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 | + | |
Lines changed: 92 additions & 23 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
32 | 32 | | |
33 | 33 | | |
34 | | - | |
35 | | - | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
36 | 42 | | |
37 | 43 | | |
38 | 44 | | |
| |||
261 | 267 | | |
262 | 268 | | |
263 | 269 | | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
264 | 274 | | |
265 | 275 | | |
266 | 276 | | |
| |||
269 | 279 | | |
270 | 280 | | |
271 | 281 | | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
272 | 286 | | |
273 | 287 | | |
274 | 288 | | |
| |||
288 | 302 | | |
289 | 303 | | |
290 | 304 | | |
| 305 | + | |
291 | 306 | | |
292 | 307 | | |
| 308 | + | |
293 | 309 | | |
294 | 310 | | |
295 | 311 | | |
| |||
298 | 314 | | |
299 | 315 | | |
300 | 316 | | |
301 | | - | |
| 317 | + | |
302 | 318 | | |
303 | 319 | | |
304 | 320 | | |
| |||
342 | 358 | | |
343 | 359 | | |
344 | 360 | | |
345 | | - | |
346 | | - | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
347 | 364 | | |
348 | 365 | | |
349 | 366 | | |
| |||
357 | 374 | | |
358 | 375 | | |
359 | 376 | | |
360 | | - | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
361 | 394 | | |
362 | 395 | | |
363 | 396 | | |
| |||
392 | 425 | | |
393 | 426 | | |
394 | 427 | | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
395 | 438 | | |
396 | 439 | | |
397 | 440 | | |
| |||
403 | 446 | | |
404 | 447 | | |
405 | 448 | | |
406 | | - | |
| 449 | + | |
407 | 450 | | |
408 | | - | |
409 | | - | |
410 | | - | |
411 | | - | |
412 | | - | |
413 | | - | |
414 | | - | |
415 | | - | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
416 | 459 | | |
417 | | - | |
418 | | - | |
| 460 | + | |
| 461 | + | |
419 | 462 | | |
420 | 463 | | |
421 | 464 | | |
| |||
439 | 482 | | |
440 | 483 | | |
441 | 484 | | |
442 | | - | |
| 485 | + | |
443 | 486 | | |
444 | 487 | | |
445 | 488 | | |
446 | 489 | | |
447 | | - | |
| 490 | + | |
448 | 491 | | |
449 | 492 | | |
450 | 493 | | |
451 | 494 | | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
452 | 521 | | |
0 commit comments