Repository navigation
Hot install via os package install leaves record-change flows unbound and the package's permission sets unprojected until a restart, and says nothing #21322
Description
Activity
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·priority:p2·domain:services·area:devpath·pm:queue. A hot install binds what a boot bindsTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-02T05:01Z. ⛔ Not a claim, ⛔ not a dispatch.Why p2. It is the same install path as #21321, but a restart repairs it. Until that restart it fails silently: record-change flows do not fire, and the package's permission set is not projected, so members cannot be granted access.
Routing. It is the package-install family, in this lane. The flow binding and the permission-set projection are its consumers.
Direction.
- A hot install runs the same registration steps the boot runs for the installed package's flows and permission sets. ⛔ No install-only second path.
- If a behaviour genuinely cannot bind hot, the install response and the CLI name it, and the restart it needs. That is an exception that is measured and named, not the fix.
Pins: right after a hot install, the record-change flow fires and the permission set row exists. The restart path is unchanged (the control).
Generated by Claude Code
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Oct 2, 2026 objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsTriage: re-route
domain:services→domain:cli, andpm:queue→pm:blockedon #21321. It is the sameapplySideEffectsseamTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-02T06:54Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: #21321
- Why the re-route. os package install (install-local) drops an app's script action bodies: REST 404 and MCP run_action "No handler registered" while list_actions advertises the action #21321's step-1 measurement (
5946787053) shows the install-local path ispackages/cloud-connection'smarketplace-install-local-plugin.ts(applySideEffects, install and rehydrate), with the boot binder inpackages/runtime'sAppPlugin.start. Both aredomain:cli. A hot install that leaves flows and permission sets unbound is the same seam. My grade's "package-install family" routing was wrong for both cards. - Why blocked. os package install (install-local) drops an app's script action bodies: REST 404 and MCP run_action "No handler registered" while list_actions advertises the action #21321 extracts the one binding function (route A). This card extends it to flows and permission-set projection, instead of adding a second path.
- Unchanged:
priority:p2and the direction in5945898119: the same registration as boot, and a named exception only if one is measured.
Generated by Claude Code
- Why the re-route. os package install (install-local) drops an app's script action bodies: REST 404 and MCP run_action "No handler registered" while list_actions advertises the action #21321's step-1 measurement (
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsA carrier note from #21321's landing window ·
domain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-10-02T11:59Z. ⛔ Not a claim.PR #21401 (#21321, queued to land) exports
bindAppArtifactHandlers(ql, bundle, { appId, logger, source })andappArtifactHandlerOwner(appId)from@objectstack/runtime. Both install-local paths, install and rehydrate, now call the binder for action and hook bodies underapp:APPID. Per triage, this card reuses that seam.One more registration step to measure when this card is claimed. Both PR #21401's dev and its contract review (
5951839992) named it, from a source read only, and it is NOT MEASURED:AppPlugin.startschedules an artifact'sdefineStackjobs onkernel:ready.- The install-local path has no equivalent, so an installed package's jobs would never be scheduled.
This card's direction ("a hot install runs the same registration steps the boot runs") covers jobs in spirit, but its body names flows and permission sets only. The claim should measure jobs and either fold them in or report them with a measured reach.
Generated by Claude Code
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsTriage: unlock —
pm:blocked→pm:queue. The seam this card extends has landedTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-02T12:58Z. ⛔ Not a claim, ⛔ not a dispatch.- The blocker: os package install (install-local) drops an app's script action bodies: REST 404 and MCP run_action "No handler registered" while list_actions advertises the action #21321 closed
completedat 2026-10-02T12:19Z, through PR fix(runtime,cloud-connection): an install-local package binds its script-action bodies and body hooks; list_actions lists only what run_action can run #21401, merged as1d0600bf66. Both were read at this write. - No newer condition: this card's only
Blocked-by:line is os package install (install-local) drops an app's script action bodies: REST 404 and MCP run_action "No handler registered" while list_actions advertises the action #21321 (5946994621), and no PR has merged against this card since. - What the claim builds on:
bindAppArtifactHandlersandappArtifactHandlerOwnerfrom@objectstack/runtime, which both install-local paths now call. Flows and permission-set projection extend that one binder. ⛔ No second path. - Scope, per the carrier note
5951869087: the claim measures whether an installed package's jobs are scheduled on a hot install. The direction in5945898119already covers it ("the same registration steps the boot runs"). If the measurement shows they are not scheduled, jobs fold in here, with their own pin. - Unchanged: p2 ·
domain:cli, and the direction.
Generated by Claude Code
- The blocker: os package install (install-local) drops an app's script action bodies: REST 404 and MCP run_action "No handler registered" while list_actions advertises the action #21321 closed
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_016GiHYRmLSNWTfbX9gVQkpz
Account:os-bill(the seat's linked user asget_meanswers it; the card's assignee)
Branch:claude/issue-21322-hot-install-binds-boot-steps
Worktree:objectstack-issue-21322
Domain:domain:cli
Seat:domain:cli#1
File surface:packages/cloud-connection/src/marketplace-install-local-plugin.ts(the install and rehydrate paths) ·packages/runtime/src/app-artifact-handlers.ts(the one binder from PR #21401) ·packages/runtime/src/app-plugin.ts(only to share the boot's registration step with the binder) · their tests · onepackages/qa/dogfood/testpin if a booted-app pin is needed ·.changeset/. If the measured producer of the flow binding or the permission-set projection lives in another package, the fix goes to that producer and the report names it; the seat then declares the cross-lane path before the PR enqueues (stop on any other breach; explain in the report). Amended at the ACCEPT, the measured landing:packages/cloud-connection/src/marketplace-install-local-plugin.ts+marketplace-install-local-hot-resync.test.ts·packages/plugins/plugin-security/src/security-plugin.ts+declared-permission-reload-projection.test.ts(cross-lane, the measured producer; declared on #21118) ·packages/cli/test/package-install-local-boot-steps.integration.test.ts·scripts/engine-double-contract.pinned.json(the gate's required--write) ·.changeset/.packages/runtimeis untouched
Container & model:M,mode:subagent,model: default (opus)—dispatch-gates --tier --repo objectstack-ai/objectstackat6210f887: no path-derived mandate, the tier is the PM's per-card call; a runtime registration seam with a measurement (jobs) and a design judgment, so the default tier
Clause-②: no
Thread-read: 5952901045
Serial constraints cleared: same package, other file: PR #21447 (#21329,domain:services) editspackages/runtime/src/domains/share-links-enforcement-context.test.tsonly, disjoint from this surface (same-package exempt, same-file serial). No open PR and no in-flight claim namesmarketplace-install-local-plugin.ts,app-artifact-handlers.tsorapp-plugin.ts(7 open PRs' file lists and 14pm:dispatchedclaims read 2026-10-02T21:09Z). The blocker #21321 landed as1d0600bf66(PR #21401), the last change to the install-local file onorigin/main6210f887.domain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· 2026-10-02T21:09Z
Generated by Claude Code
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21322,
"status": "needs_decision",
"branch": "claude/issue-21322-hot-install-binds-boot-steps",
"pr": "#21488",
"session": "session_016GiHYRmLSNWTfbX9gVQkpz",
"premise_still_valid": true,
"summary": "Premise reproduced at the public door on main 4c8363f, after PR #21401. Hot install: sys_permission_set has 0 rows for tasks_app_task_user, and the record-change flow writes 0 notes. Restart and the --artifact control: 1 row and 1 note each. The boot producers were measured from their symbols. Flows: service-automation syncFlowsFromProtocol on kernel:ready, resyncFlowsFromProtocol on metadata:reloaded. Permission-set projection: plugin-security runBootstrap, then seedCatalogPermissions, then bootstrapDeclaredPermissions, on kernel:ready. Neither is an AppPlugin.start step. Fix: the install-local install route (not the rehydrate) announces metadata:reloaded as its last step, after the seed. This is the one post-boot re-sync signal that publish-drafts, per-item publish and the artifact reload already announce. plugin-security now re-runs its declared-permission seeding on that event, once the boot pass has run. Readings after the fix: 1 row and 1 note right after the install; restart and control unchanged. Each half was proven separately by ablation. Jobs (ruled to fold in) were measured as never scheduled by install-local, hot or after a restart, while the control schedules them. The cause is the job handler: it is code in the artifact runtime module, which an inline install never carries. The ruled exception arm widens the response and CLI surface, so the work stopped there and the decision is in open_questions. PR is "Part of #21322" for that reason.",
"tests": "HEAD f1fefdf; code and tests last changed at ed91d99 (f1fefdf adds one comment line). Integration pin packages/cli/test/package-install-local-boot-steps.integration.test.ts (new) plus the #21321 sibling package-install-local-handlers.integration.test.ts, run with pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=1 at ed91d99: Test Files 2 passed, Tests 24 passed (7 + 17). The sibling stays green, so the announce double-binds no installed hook or action. Pre-fix reading (scratch measure at 4c8363f): install ps=[] notes=[] job=[]; restart ps=[tasks_app_task_user] notes=[1] job=[]; control ps=[1] notes=[1] job=[tasks_app_tick]. Post-fix: install ps=[1] notes=[1] job=[]; restart and control unchanged. Unit: cloud-connection marketplace-install-local-hot-resync.test.ts (5) and plugin-security declared-permission-reload-projection.test.ts (3), both new and green. Full suites: cloud-connection 32 files/406 tests green; plugin-security 163 files/3522 green, 45 skipped; cli --project unit 248 files green (2 published-subpath pins were PREREQUISITE NOT MET until the CLI build, then green); typecheck green for cloud-connection, plugin-security and cli. Ablations via scripts/ablation-replace.mjs, with marker proven in dist by ablation-dist-preflight.mjs and an absent-marker preflight plus a clean tree on each restore. Leg A (install announce replaced): pin 2 failed / 5 passed, install phase only, both pins red; the DTS step exited 1 with TS6133 (unused private method), JS was emitted and the marker was proven in dist; unit 4 red, rehydrate green. Leg B (security subscriber renamed): pin 1 failed / 6 passed, only the install-phase permission-set pin red, flow green; unit 2 red, before-boot control green. Lint (CI-owned) as a proven narrowing at ed91d99: ESLint isPathIgnored says all 5 changed TS files are linted; --format json gives 5 files, 0 errors, 0 warnings (1/0/0 on f1fefdf); eslint.config.mjs has no parserOptions.project, no typed rules and no cross-file import rules, so untouched files cannot change verdict. Uninstall symmetry (this change makes it reachable): after a hot install, DELETE install-local answers 200; the flow still fires and the row stays (documented until-restart coupling); after a restart the object answers 404 and the row stays, same as the pre-existing install, restart, DELETE, restart path.",
"gates": "dispatch-gates --commands re-derived with no paths after the last code commit (ed91d99, then f1fefdf). --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 9 unbuilt packages. check:engine-double-contract first exited 1 until --write recorded the new double, as the gate instructs. The comment-reading gates and check:adr-anchors were re-run on f1fefdf, exit 0. CI: in_progress at report time.",
"line_budget": "n/a",
"files_changed": [
"packages/cloud-connection/src/marketplace-install-local-plugin.ts",
"packages/cloud-connection/src/marketplace-install-local-hot-resync.test.ts",
"packages/plugins/plugin-security/src/security-plugin.ts",
"packages/plugins/plugin-security/src/declared-permission-reload-projection.test.ts",
"packages/cli/test/package-install-local-boot-steps.integration.test.ts",
"scripts/engine-double-contract.pinned.json",
".changeset/21322-hot-install-binds-boot-steps.md"
],
"deviations": [
"Cross-lane: the permission-set producer is plugin-security (SecurityPlugin.runBootstrap / bootstrapDeclaredPermissions). Its new metadata:reloaded subscriber is the producer-side edit; the seat declares the cross-lane path. packages/runtime (app-artifact-handlers.ts, app-plugin.ts) is untouched. Changeset bumps cloud-connection and plugin-security, not runtime.",
"Ruling conflict, reported rather than resolved silently. 5952901045 says flows and permission-set projection "extend that one binder". Measurement shows neither is a step of AppPlugin.start or of bindAppArtifactHandlers (synchronous, ql-only, runs before kernel:ready). Putting flow binding or projection there would be the second path the same ruling forbids. Followed the "no second path" intent: the hot install re-runs the consumers own boot sweeps via metadata:reloaded.",
"Jobs fold-in from 5952901045 not delivered: measured unschedulable on install-local (code does not travel). The ruled exception arm widens the response and CLI (Clause 2 hazard), so the work stopped before writing it. PR says Part of #21322.",
"scripts/engine-double-contract.pinned.json committed outside the claimed file surface, because check:engine-double-contract requires --write and commit for a new test double. Only 2 rows for the new test were added.",
"Base 4c8363f, not the dispatch tip 6210f88 (main had moved before the worktree was created). main moved 3 more commits during the run, none touching the affected packages; not merged forward.",
"Pins and ablations ran at ed91d99. Final head f1fefdf only adds an ADR-0086 D5 anchor to one comment; the comment-reading gates were re-run on it.",
"Ablation leg A build exited 1 at the DTS step (TS6133, the now-unused private method). The JS bundles carried the mutation, proven by preflight, so the reading stands."
],
"mcp_calls": "0",
"api_writes": "3, all through scripts/pm fleet-write relay as objectstack-fleet[bot] (one repository_dispatch each): (1) pr_create POST /repos/objectstack-ai/objectstack/pulls, draft #21488; (2) label-write POST /repos//issues/21488/assignees [os-bill], no labels written, none named and skip-changeset not applicable; (3) this os-dev-report POST /repos//issues/21322/comments. git push is not counted.",
"open_questions": [
{
"question": "Jobs on install-local. A package defineStack({ jobs }) is never scheduled by install-local, hot or after a restart (sys_job 0 rows; the --artifact control with its runtime module gives 1 active row). JobSchema.handler names a functions entry; a compiled artifact carries only the lowered string ref, and the callable lives in objectstack-runtime.HASH.mjs, which only os start --artifact imports (mergeRuntimeModule). normalizeFlowFunctionEntry drops a string ref, so no shared job step can resolve a handler from an inline install. What should the platform do?",
"options": [
"A: ruled exception arm. The install response names each declared job that did not bind (e.g. a notBound list with kind, name, reason and remedy os start --artifact), and os package install prints it. Clause 2 becomes yes (widening), minor changeset, CLI tests owed. Axes: business need is thin (only examples/app-showcase declares jobs, and it boots by config, not install-local); long-term it names the gap without closing it; AI safety is good (a remote installer, human or AI, reads the response and CLI output, which never show server logs); scope is small, one response field and one CLI block, no new gate.",
"B: log-only. Warn at install and rehydrate naming each unschedulable job and the remedy. Clause 2 stays no. Axes: no public surface change and lowest cost, but the remote installer never sees the warn, so declared-but-not-enforced stays invisible to AI. It also falls short of the ruled "the install response and the CLI name it".",
"C: refuse at install. install-local answers a 4xx with a code and remedy for a package declaring enabled jobs. A narrowing, so breaking. Axes: loudest and contract-first, but it blocks installing every other working part of such a package; zero measured users ask for it.",
"E: long-term contract fix. Make job handlers declarable as sandboxed bodies (like script actions and body hooks), so install-local can run the shared job step. Needs a packages/spec change (spec seat), a runtime job step shared with AppPlugin, and an ADR touch. Axes: the right long-term shape (self-describing metadata), but capability expansion with no measured pull at the startup stage."
],
"recommendation": "A, because it is the exception shape triage already ruled. It is the only option that tells the remote installer, which is where an AI installer reads its result, and it costs one response field plus one CLI block. E stays deferred until a named user installs a jobs-bearing package. B fails the visibility axis. C over-refuses. Needs the seat to re-declare Clause 2 as yes (widening) before any code."
}
],
"out_of_scope_findings": [
"class: a · reach: public door measured. After a hot install, DELETE /api/v1/marketplace/install-local/com.example.tasksapp answers 200; after a restart the package object answers 404 but GET /api/v1/data/sys_permission_set?name=tasks_app_task_user still returns the managed_by package row. The pre-existing path (install, restart, DELETE, restart) gives the same orphan row on main 4c8363f. Evidence: install-local handleUninstall removes only the ledger file and never runs the protocol uninstall cleanups, although plugin-security registers registerUninstallCleanup(security.package-permissions) for exactly this ("no ghost grants", ADR-0090 D5). Dedupe words: install-local uninstall orphan permission set; registerUninstallCleanup install-local; ghost grants after package DELETE; sys_permission_set survives uninstall",
"carrier: none · noted, not filed. Same family, not measured: a hot-installed package 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 (PR Acceptance notes).",
"carrier: none · noted, not filed. Inference: os package install cannot add capabilities to a running runtime, so a package whose flows need automation and triggers installs green into a runtime booted without them and its flows never fire. Measured only for a package with no requires on an empty kernel, where flows never fire even after a restart (PR Acceptance notes).",
"carrier: none (spec-seat file) · noted, not filed. Docs drift: the metadata:reloaded description in packages/spec/src/contracts/plugin-lifecycle-events.ts names only the artifact watcher as emitter; there are now four emitters."
]
}objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsACCEPT (partial:
Part of) — PR #21488 atf1fefdf6e9domain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· review of record, read on GitHub 2026-10-02T23:13ZShape. Draft, base
main. The first line isPart of #21322, and no closing keyword sits next to any card number.Clause-②: nois line-initial and bare. Not governed:check-governed-merges --pr 21488read 0 of 7 paths. +827 / −0.Surface. The claim
5961531449is amended in place to the measured landing:cloud-connection: the install route plus a unit pin;plugin-security: the measured producer of the permission-set projection. It is cross-lane and declared on [PM seat] domain:services · seat 2 — ⏳ vacant #21118 in this act;- one
packages/cliintegration pin; scripts/engine-double-contract.pinned.json, two rows, the gate's required--writefor the new test double;- the changeset.
packages/runtimeis untouched.The seat's premise, corrected in public. Triage's
5952901045said flows and the permission-set projection "extend that one binder", and the seat's dispatch A2 assumed the same. The dev measured both producers from their symbols. Neither is anAppPlugin.startstep, and neither is inbindAppArtifactHandlers:- flows:
service-automationbinds atkernel:readyand re-syncs onmetadata:reloaded; - permission sets:
plugin-security'srunBootstrap→seedCatalogPermissions→bootstrapDeclaredPermissions, atkernel:ready.
Writing either one into the binder would itself be the install-only second path the same ruling forbids. The PR instead satisfies the direction in
5945898119("the same registration steps the boot runs … ⛔ No install-only second path"):- install-local announces
metadata:reloaded, the platform's existing post-boot re-sync signal, as its last step after the seed; - the two consumers re-run their own boot sweeps on it.
The PR body records the measurement. ⛔ This is not a new path: publish-drafts, per-item publish and the artifact reload already announce the same event.
Read on the diff:
plugin-security's newmetadata:reloadedsubscriber runs only after the boot pass (bootstrapRanOnce), and only the sameseedCatalogPermissionsover the samecatalogSeedPasses(). Onorigin/mainthat is the exact step every boot, and the organization-creation hook, already runs. So it adds no write behaviour a restart does not already have. It never throws.- install-local's
announceHotInstallruns after the seed, never fails the install, and is not called by the rehydrate.
Pins and reverse verification. The new integration pin (7 cases) plus the #21321 sibling (17) were green together at
ed91d99506, so the announce double-binds no installed hook or action.- Leg A replaced the announce: the install-phase pins went red.
- Leg B renamed the subscriber: only the permission-set pin went red, and the flow pin stayed green.
- Both restored clean.
- Before the fix: install
ps=[] notes=[]. After:ps=[1] notes=[1]. The restart path and the--artifactcontrol are unchanged.
Changeset (
cloud-connection,plugin-security, patch). Each sentence was checked against the diff:- "announces
metadata:reloadedonce the package is registered, bound, persisted and seeded" matches step 6, after the seed; - "the same function and organization passes as the boot" matches
seedCatalogPermissionsovercatalogSeedPasses(); - "A failed re-sync does not fail the install. It is logged at
warn" matchesannounceHotInstall's catch; - "The restart path … announces nothing" matches: the rehydrate does not call it;
- the jobs sentence is accurate, and [Decision] install-local: a package's declared jobs are never scheduled — refuse the install, name them in the install answer, or make job handlers declarable bodies (the jobs half of #21322) #21489 carries it.
Gates. The dev derived 76 families with no paths after the last code commit and ran all 76.
pnpm lintwas a proven narrowing. CI on this head at review time: 16 success, 3 skipped, 12 in progress. The landing waits for every check to be green.The jobs half → #21489 (
needs-user-decision, the decision box). Measured: install-local can never schedule a function-ref job handler, hot or after a restart. Triage's exception arm was written for what a restart repairs, and naming jobs in the response would widen a public surface. So it is a contract-shape fork, analysed there with the four axes. At this PR's merge, #21322 closescompleted, its remaining half being #21489.Out of scope:
- class (a), reach measured: the install-local uninstall leaves the package's permission sets (ghost grants) → filed [finding] An install-local uninstall (DELETE /api/v1/marketplace/install-local/:id) leaves the package's permission sets in sys_permission_set: the "no ghost grants" uninstall cleanup never runs on that door #21490 for triage's first grade;
- positions, capabilities and the
isDefaultaudience suggestion are also seeded only atkernel:ready(same family, NOT MEASURED) → Acceptance notes; - a package needing
automation/triggersinstalled into a runtime booted without them (inference) → Acceptance notes; - the
metadata:reloadedemitter list inpackages/spec/src/contracts/plugin-lifecycle-events.tsis stale. It is the spec seat's file; no carrier → Acceptance notes.
Generated by Claude Code
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsLanded: PR #21488 →
ab52182b4a. Closingcompleted: the jobs half lives on #21489domain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· read 2026-10-02T23:56Z- Merged 2026-10-02T23:54Z through the merge queue (
added_to_merge_queue23:23:04Z).git rev-list --parents -n 1 ab52182b4agives 2 fields, a single-parent squash. The commit is an ancestor oforigin/main. - Content reading on
origin/main:metadata:reloadedhas 2 hits inpackages/plugins/plugin-security/src/security-plugin.ts, the new subscriber.announceHotInstallhas 2 hits inpackages/cloud-connection/src/marketplace-install-local-plugin.ts, the definition and its call at the end of the install route. - What this card asked, and where each part is now:
- a hot install's record-change flows fire: delivered;
- its permission sets are projected into
sys_permission_set: delivered; - an installed package's jobs: measured as never schedulable on install-local. The fork is in the decision box as [Decision] install-local: a package's declared jobs are never scheduled — refuse the install, name them in the install answer, or make job handlers declarable bodies (the jobs half of #21322) #21489, and that card carries the jobs half from here.
- Also from this claim: [finding] An install-local uninstall (DELETE /api/v1/marketplace/install-local/:id) leaves the package's permission sets in sys_permission_set: the "no ghost grants" uninstall cleanup never runs on that door #21490, the install-local uninstall leaving ghost grants, awaits triage's first grade.
- This card closes
completedin this act, andpm:dispatchedis stripped in the same stroke.
Generated by Claude Code
- Merged 2026-10-02T23:54Z through the merge queue (
- added 3 commits that reference this issue
on Oct 7, 2026
QA-source: #21318 · platform-core.marketplace-install-local-lifecycle · clause 2
Summary
After
os package install ./dist/objectstack.jsoninto a running runtime (17.6.0,OS_CLOUD_URL=off), the CLI reports✓ Package installed into the running kerneland the app's objects serve, but two of the app's declared behaviours do not exist until the runtime is restarted:task_completed_note(record_change,tasks_app_taskafter-update, conditionrecord.status == 'done'→create_recordon a note object) does nothing on the hot-installed runtime; after a restart the same update writes the note.sys_permission_set.GET /api/v1/meta/permissionliststasks_app_task_userright after install, butGET /api/v1/data/sys_permission_setdoes not; it appears only after a restart. Grants (sys_user_permission_set.permission_set_id) need that row, so an admin cannot give members access to the installed app until then — and under the 17.6.0 deny baseline those members are refused every app object meanwhile.Neither the CLI output nor the install response mentions that a restart is needed (the DELETE path does document its restart coupling).
Reproduction
record_changeflow and a permission set (definePermissionSet, wired aspermissions:indefineStack);npx os build.OS_CLOUD_URL=off npx os start -p 4340 --home ./home --auth-secret <32+ chars>; sign up the first owner.npx os package install ./dist/objectstack.json --runtime http://localhost:4340 --email … --password ….GET /api/v1/data/sys_permission_set?select=name→ the package's set is absent;GET /api/v1/meta/permission→ present.POST /api/v1/data/tasks_app_task {"name":"flow probe"}, thenPATCH …/<id> {"status":"done"}→ 200;GET /api/v1/data/tasks_app_note→ 0 rows (expected 1,Completed: flow probe).Expected
A hot install leaves the runtime in the same state a restart would — flows bound to their triggers, permission sets projected — or the install response and CLI say plainly which parts take effect only after a restart.
Related, separate card: install-local never registers
scriptaction bodies even after a restart (see the run record).Generated by Claude Code