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
{{ message }}
Repository navigation
package enable: after a module package is disabled and enabled again, every module's navigationContributions entries appear twice in the console sidebar until the server restarts #30581
Measured by HotCRM on platform 18.3.0 during hotcrm#2315 phase 1a (PR hotcrm#2418). The implementer's report is hotcrm#2315 comment 6663533795, in its out-of-scope findings. The screenshot 09 is in the HotCRM project files under issue-2315/phase1a/.
What happens
The HotCRM artifact carries five packages. Four of them are modules that put entries into the app's navigation groups through navigationContributions.
The console sidebar now lists the contributed entries of every module twice: Service Overview, Cases and Knowledge (service); Quotes, Products and Price Books (revenue); the three Order to Cash entries.
It stays that way until the server restarts. After a restart, each entry is listed once.
So one module's enable duplicates the entries of all modules, not just its own. That suggests re-enable re-folds the contributions into the app without first dropping the folded copy.
Ask
Package enable (and disable) re-derives the app's navigation from the enabled packages' contributions, so each entry appears exactly once after any number of disable and enable cycles. A test pins two cycles.
Consumer
HotCRM documents module disable as the per-tenant switch for Service, Revenue, Marketing and Order to Cash (Admin Manual, #2383). An administrator who turns a module off and back on sees a doubled menu.
domain:engine is a retired alias of domain:server. p2 kept (HotCRM measured, 18.3.0): after a module package is disabled and enabled again, its navigationContributions appear twice until restart.
Claim: PM loop round 18
Session: session_01Utp6Cbvcmv8YWFhFJwzjKL (seat) · dev thread session_01Utp6CZUUc37kySmxP9uGue
Account: os-bill
Branch: claude/project-thread-nxu7nt (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-30581
Domain: domain:server
Seat: domain:server#2
File surface: where measuring puts the navigation fold (expected packages/objectql/src/engine.ts and/or registry.ts, possibly packages/metadata-protocol/src/protocol-package-install.ts or packages/runtime/src/domains/packages.ts) and their tests; one changeset .changeset/30581-nav-contributions-reenable.md. Exact files named before editing. ⛔ packages/spec/**, ui/**, apps/**, skills/**, docs/adr/**, content/docs/releases/**, .claude/** (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default tier (opus) — dispatch-gates --tier: no path-derived mandate, effort: high
Clause-②: no expected — after any enable/disable the served navigation equals what a fresh boot serves
Metadata-protocol: none
Responsibility: re-enabling a module folds every module's navigation contributions into the app again without dropping the folded copy | the app's own navigation and each package's declared contributions (unchanged) | administrators who switch modules off and on (HotCRM documents this as the per-tenant module switch)
Thread-read: 6664843769
Premise: read, to be measured first — at 6701c08a: the enable path re-folds contributions; a restart clears the duplicates
Serial constraints cleared: seat 4's #30377 (PR #30587, merged) changed the re-enable path in registry.ts / engine.ts / runtime packages.ts — a base, not a wait. This seat's #29029 touches objectql reference checks, seat 3's #30542engine-actions.ts / plugin.ts; whichever lands second merges main.
Order: project file server-seat2/dispatch-30581.md, delivered as the card thread's opening message.
{
"issue": 30581,
"status": "done",
"branch": "claude/project-thread-nxu7nt",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30656",
"session": "session_01Utp6CZUUc37kySmxP9uGue",
"premise_still_valid": true,
"summary": "Symptom reproduced, mechanism corrected. On a real kernel (MetadataPlugin + ObjectQLPlugin + AppPlugin, PATCH /packages/:id/disable|enable through HttpDispatcher, origin/main 6701c08a; the 18.3.0 enable door is the same code), an app plus two contributing modules: two disable/enable cycles of one module leave each entry once (2 contributions). What doubles EVERY module's entries is a re-registration of the package bodies: one metadata:reloaded carrying the artifact (ObjectQLPlugin.refreshReloadedPackages re-registers every package; fired by the os dev artifact watcher and the HMR reload POST) gives two copies of each module's entries (4 contributions), the manifest door once more gives three (6). Duplicating line: ObjectQL.registerApp's manifest step called SchemaRegistry.registerAppNavContribution, append-only. Fix (derive, not dedupe): new SchemaRegistry.setPackageNavContributions(packageId, list) replaces the package's whole set in every app, in place, so fold ties keep fresh-boot order; registerApp calls it for every body with an id (empty list clears); uninstallPackage clears the package's contributions (seat ask: an uninstalled package's entries stayed folded, no row left to say disabled). registerAppNavContribution, the fold, enablePackage/disablePackage and the onPackageEnabled announcement (#30377) are unchanged.",
"tests": "Final head 0be4e9a0b (merged origin/main 14c67d446), dogfood closure rebuilt first, all under the verify lock: pnpm --filter @objectstack/objectql test: Test Files 555 passed (555), Tests 11057 passed (11057), typecheck exit 0; pnpm --filter @objectstack/runtime test: Test Files 544 passed (544), Tests 8702 passed | 19 skipped (8721), typecheck exit 0. New pins: registry-nav-contributions.test.ts describe [#30581] x7 through ObjectQL.registerApp, each against a fresh-registration snapshot in the same test (two full reloads; one module re-registered in place; two disable/enable cycles each with a reload, onPackageEnabled heard once per enable; no-contribution module toggled + re-registered = unchanged; changed body replaces; body with none clears; uninstall drops); packages-disable-app-module.test.ts describe [#30581] x1 through the real doors (two cycles of disable, metadata:reloaded with the artifact, enable; then the manifest door again; served GET /meta/app group equals the fresh boot after each). Ablations via scripts/ablation-replace.mjs, objectql rebuilt each leg, ablation-dist-preflight marker present before the run, restored to HEAD blob: (1) registerApp back to the additive call -> objectql 4 red + runtime pin red ('enabled again #1' got nav_orders twice); (2) replace at the end, not in place -> 2 red; (3) uninstall clear removed -> 1 red. The first leg-1 attempt used a comment marker tsup strips (preflight said ABSENT); rerun with a code marker, preflight present. Restore leg: rebuilt, preflight --absent green, tree clean.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on 0be4e9a0b: 63 families, each run with its exit code written before any pipe, all exit 0; --ran: 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN. An earlier run on d9f60a87 had check-plugin-teardown-shape --self-test exit 3 (shallow; cleared after fetching its fixture commit) and check:dual-build-cjs-loads exit 3 (closure not built); both exit 0 on the final head. eslint --no-inline-config over the 5 changed .ts files exit 0; repo-wide pnpm lint left to CI Lint & Repo Gates. check-nul-bytes exit 0. CI check-runs on the PR: in_progress at report time.",
"dogfood_battery": "0be4e9a0b · os-verify-lock: VERDICT command-exit 0 · Test Files 420 passed | 3 skipped (423) · Tests 3504 passed | 36 skipped (3540) · no failures (run because the order asked; the title has no ! and Clause-② has no narrowing arm)",
"mcp_calls": "3 reads, no write tool: issue_read get x1, issue_read get_comments x1, pull_request_read get_files x1 (PR 30587)",
"api_writes": "2 — pr_create via fleet-write relay (run 42109611765, PR 30656, read-back 8060 bytes identical, no assignees, no labels); this os-dev-report comment via post-stamped",
"line_budget": "6 files, +260 / -3 vs merge base 14c67d446; under the 3000-line human-merge threshold",
"files_changed": [
".changeset/30581-nav-contributions-reenable.md",
"packages/objectql/src/engine.ts",
"packages/objectql/src/registry.ts",
"packages/objectql/src/registry-module-mock.ts",
"packages/objectql/src/registry-nav-contributions.test.ts",
"packages/runtime/src/domains/packages-disable-app-module.test.ts"
],
"deviations": [
"Mechanism premise of the card ('re-enable re-folds without dropping the folded copy') measured false: the doors only flip the row; the re-fold is the reload's re-registration. Route changed per ruling 2's intent (derive per package), announced to the seat before editing and accepted.",
"Uninstall clear added at the seat's ask (same defect class, same verb); registry-module-mock.ts added to the file surface because engine.test.ts's registry double had no setPackageNavContributions (4 red until added).",
"PR body's signature line carries the seat's session (as the order spelled it) rather than this thread's; the seat may correct it, the dev does not PATCH the body.",
"Session-context asks for an MCP assignee + reviewer request after pr_create; skipped per the order's no-MCP-write rule and os-dev rule 2 (left to the seat at hand-over).",
"Repo-wide pnpm lint not run locally (CI-owned per os-dev); eslint over the changed files instead."
],
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: none · noted, not filed — registerAppNavContribution stays additive; no production caller today (only tests), so a plugin calling it twice from code would still get two copies."
]
}
Activity
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit ·
domain:engine→domain:server· 2026-11-05T17:35Z⛔ Not a claim.
domain:engineis a retired alias ofdomain:server. p2 kept (HotCRM measured, 18.3.0): after a module package is disabled and enabled again, itsnavigationContributionsappear twice until restart.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
Claim: PM loop round 18
Session:
session_01Utp6Cbvcmv8YWFhFJwzjKL(seat) · dev threadsession_01Utp6CZUUc37kySmxP9uGueAccount:
os-billBranch:
claude/project-thread-nxu7nt(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-30581Domain:
domain:serverSeat:
domain:server#2File surface: where measuring puts the navigation fold (expected
packages/objectql/src/engine.tsand/orregistry.ts, possiblypackages/metadata-protocol/src/protocol-package-install.tsorpackages/runtime/src/domains/packages.ts) and their tests; one changeset.changeset/30581-nav-contributions-reenable.md. Exact files named before editing. ⛔packages/spec/**,ui/**,apps/**,skills/**,docs/adr/**,content/docs/releases/**,.claude/**(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default tier (opus) — dispatch-gates --tier: no path-derived mandate,effort: highClause-②: no expected — after any enable/disable the served navigation equals what a fresh boot serves
Metadata-protocol: none
Responsibility: re-enabling a module folds every module's navigation contributions into the app again without dropping the folded copy | the app's own navigation and each package's declared contributions (unchanged) | administrators who switch modules off and on (HotCRM documents this as the per-tenant module switch)
Thread-read: 6664843769
Premise: read, to be measured first — at
6701c08a: the enable path re-folds contributions; a restart clears the duplicatesSerial constraints cleared: seat 4's #30377 (PR #30587, merged) changed the re-enable path in
registry.ts/engine.ts/ runtimepackages.ts— a base, not a wait. This seat's #29029 touches objectql reference checks, seat 3's #30542engine-actions.ts/plugin.ts; whichever lands second mergesmain.Order: project file
server-seat2/dispatch-30581.md, delivered as the card thread's opening message.objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6CZUUc37kySmxP9uGue
os-dev-report
{ "issue": 30581, "status": "done", "branch": "claude/project-thread-nxu7nt", "pr": "https://github.com/objectstack-ai/objectstack/pull/30656", "session": "session_01Utp6CZUUc37kySmxP9uGue", "premise_still_valid": true, "summary": "Symptom reproduced, mechanism corrected. On a real kernel (MetadataPlugin + ObjectQLPlugin + AppPlugin, PATCH /packages/:id/disable|enable through HttpDispatcher, origin/main 6701c08a; the 18.3.0 enable door is the same code), an app plus two contributing modules: two disable/enable cycles of one module leave each entry once (2 contributions). What doubles EVERY module's entries is a re-registration of the package bodies: one metadata:reloaded carrying the artifact (ObjectQLPlugin.refreshReloadedPackages re-registers every package; fired by the os dev artifact watcher and the HMR reload POST) gives two copies of each module's entries (4 contributions), the manifest door once more gives three (6). Duplicating line: ObjectQL.registerApp's manifest step called SchemaRegistry.registerAppNavContribution, append-only. Fix (derive, not dedupe): new SchemaRegistry.setPackageNavContributions(packageId, list) replaces the package's whole set in every app, in place, so fold ties keep fresh-boot order; registerApp calls it for every body with an id (empty list clears); uninstallPackage clears the package's contributions (seat ask: an uninstalled package's entries stayed folded, no row left to say disabled). registerAppNavContribution, the fold, enablePackage/disablePackage and the onPackageEnabled announcement (#30377) are unchanged.", "tests": "Final head 0be4e9a0b (merged origin/main 14c67d446), dogfood closure rebuilt first, all under the verify lock: pnpm --filter @objectstack/objectql test: Test Files 555 passed (555), Tests 11057 passed (11057), typecheck exit 0; pnpm --filter @objectstack/runtime test: Test Files 544 passed (544), Tests 8702 passed | 19 skipped (8721), typecheck exit 0. New pins: registry-nav-contributions.test.ts describe [#30581] x7 through ObjectQL.registerApp, each against a fresh-registration snapshot in the same test (two full reloads; one module re-registered in place; two disable/enable cycles each with a reload, onPackageEnabled heard once per enable; no-contribution module toggled + re-registered = unchanged; changed body replaces; body with none clears; uninstall drops); packages-disable-app-module.test.ts describe [#30581] x1 through the real doors (two cycles of disable, metadata:reloaded with the artifact, enable; then the manifest door again; served GET /meta/app group equals the fresh boot after each). Ablations via scripts/ablation-replace.mjs, objectql rebuilt each leg, ablation-dist-preflight marker present before the run, restored to HEAD blob: (1) registerApp back to the additive call -> objectql 4 red + runtime pin red ('enabled again #1' got nav_orders twice); (2) replace at the end, not in place -> 2 red; (3) uninstall clear removed -> 1 red. The first leg-1 attempt used a comment marker tsup strips (preflight said ABSENT); rerun with a code marker, preflight present. Restore leg: rebuilt, preflight --absent green, tree clean.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on 0be4e9a0b: 63 families, each run with its exit code written before any pipe, all exit 0; --ran: 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN. An earlier run on d9f60a87 had check-plugin-teardown-shape --self-test exit 3 (shallow; cleared after fetching its fixture commit) and check:dual-build-cjs-loads exit 3 (closure not built); both exit 0 on the final head. eslint --no-inline-config over the 5 changed .ts files exit 0; repo-wide pnpm lint left to CI Lint & Repo Gates. check-nul-bytes exit 0. CI check-runs on the PR: in_progress at report time.", "dogfood_battery": "0be4e9a0b · os-verify-lock: VERDICT command-exit 0 · Test Files 420 passed | 3 skipped (423) · Tests 3504 passed | 36 skipped (3540) · no failures (run because the order asked; the title has no ! and Clause-② has no narrowing arm)", "mcp_calls": "3 reads, no write tool: issue_read get x1, issue_read get_comments x1, pull_request_read get_files x1 (PR 30587)", "api_writes": "2 — pr_create via fleet-write relay (run 42109611765, PR 30656, read-back 8060 bytes identical, no assignees, no labels); this os-dev-report comment via post-stamped", "line_budget": "6 files, +260 / -3 vs merge base 14c67d446; under the 3000-line human-merge threshold", "files_changed": [ ".changeset/30581-nav-contributions-reenable.md", "packages/objectql/src/engine.ts", "packages/objectql/src/registry.ts", "packages/objectql/src/registry-module-mock.ts", "packages/objectql/src/registry-nav-contributions.test.ts", "packages/runtime/src/domains/packages-disable-app-module.test.ts" ], "deviations": [ "Mechanism premise of the card ('re-enable re-folds without dropping the folded copy') measured false: the doors only flip the row; the re-fold is the reload's re-registration. Route changed per ruling 2's intent (derive per package), announced to the seat before editing and accepted.", "Uninstall clear added at the seat's ask (same defect class, same verb); registry-module-mock.ts added to the file surface because engine.test.ts's registry double had no setPackageNavContributions (4 red until added).", "PR body's signature line carries the seat's session (as the order spelled it) rather than this thread's; the seat may correct it, the dev does not PATCH the body.", "Session-context asks for an MCP assignee + reviewer request after pr_create; skipped per the order's no-MCP-write rule and os-dev rule 2 (left to the seat at hand-over).", "Repo-wide pnpm lint not run locally (CI-owned per os-dev); eslint over the changed files instead." ], "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "carrier: none · noted, not filed — registerAppNavContribution stays additive; no production caller today (only tests), so a plugin calling it twice from code would still get two copies." ] }Report taken 2026-11-05T21:07Z.
objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
Landed · 2026-11-05T22:25Z
a2437b56, reviewed head0be4e9a0(seat ACCEPT 6669627248)Fixes #30581. A package registered again lists its navigation contributions once, not once more per registration.State change:
pm:dispatchedremoved, assignee cleared.