@ashlr/hub 3.12.0 → 3.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +157 -0
- package/README.md +29 -16
- package/dist/authority-surface.json +274 -148
- package/dist/build-identity.json +2 -2
- package/dist/cli/authority.d.ts +145 -33
- package/dist/cli/authority.js +1292 -315
- package/dist/cli/authority.js.map +1 -1
- package/dist/cli/cloud.js +12 -3
- package/dist/cli/cloud.js.map +1 -1
- package/dist/cli/comms.d.ts +33 -19
- package/dist/cli/comms.js +128 -151
- package/dist/cli/comms.js.map +1 -1
- package/dist/cli/completions.js +1 -1
- package/dist/cli/completions.js.map +1 -1
- package/dist/cli/daemon.js +29 -0
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/director.d.ts +8 -12
- package/dist/cli/director.js +17 -52
- package/dist/cli/director.js.map +1 -1
- package/dist/cli/fleet.js +4 -1
- package/dist/cli/fleet.js.map +1 -1
- package/dist/cli/help.js +1 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/leader.d.ts +27 -3
- package/dist/cli/leader.js +253 -4
- package/dist/cli/leader.js.map +1 -1
- package/dist/core/authority/protected-paths.js +11 -0
- package/dist/core/authority/protected-paths.js.map +1 -1
- package/dist/core/authority/resident.d.ts +101 -0
- package/dist/core/authority/resident.js +246 -0
- package/dist/core/authority/resident.js.map +1 -0
- package/dist/core/authority/server-enforcement.d.ts +110 -0
- package/dist/core/authority/server-enforcement.js +260 -0
- package/dist/core/authority/server-enforcement.js.map +1 -0
- package/dist/core/authority/standing-grant.d.ts +24 -4
- package/dist/core/authority/standing-grant.js +19 -3
- package/dist/core/authority/standing-grant.js.map +1 -1
- package/dist/core/authority/types.d.ts +43 -0
- package/dist/core/authority/types.js +6 -0
- package/dist/core/authority/types.js.map +1 -1
- package/dist/core/cloud/budget.js +11 -1
- package/dist/core/cloud/budget.js.map +1 -1
- package/dist/core/cloud/cloud-api.d.ts +25 -2
- package/dist/core/cloud/cloud-api.js +139 -5
- package/dist/core/cloud/cloud-api.js.map +1 -1
- package/dist/core/cloud/pr-actions.d.ts +93 -0
- package/dist/core/cloud/pr-actions.js +436 -0
- package/dist/core/cloud/pr-actions.js.map +1 -0
- package/dist/core/cloud/pr-preview.d.ts +128 -0
- package/dist/core/cloud/pr-preview.js +228 -0
- package/dist/core/cloud/pr-preview.js.map +1 -0
- package/dist/core/cloud/store.d.ts +2 -0
- package/dist/core/cloud/store.js +28 -0
- package/dist/core/cloud/store.js.map +1 -1
- package/dist/core/cloud/timeline-api.d.ts +24 -0
- package/dist/core/cloud/timeline-api.js +46 -0
- package/dist/core/cloud/timeline-api.js.map +1 -0
- package/dist/core/cloud/timeline-types.d.ts +62 -0
- package/dist/core/cloud/timeline-types.js +36 -0
- package/dist/core/cloud/timeline-types.js.map +1 -0
- package/dist/core/cloud/timeline.d.ts +102 -0
- package/dist/core/cloud/timeline.js +925 -0
- package/dist/core/cloud/timeline.js.map +1 -0
- package/dist/core/cloud/tracker.js +67 -0
- package/dist/core/cloud/tracker.js.map +1 -1
- package/dist/core/cloud/types.d.ts +38 -0
- package/dist/core/cloud/types.js +1 -1
- package/dist/core/cloud/types.js.map +1 -1
- package/dist/core/comms/change-digest.d.ts +130 -0
- package/dist/core/comms/change-digest.js +451 -0
- package/dist/core/comms/change-digest.js.map +1 -0
- package/dist/core/comms/director-prompt.d.ts +4 -5
- package/dist/core/comms/director-prompt.js +4 -71
- package/dist/core/comms/director-prompt.js.map +1 -1
- package/dist/core/comms/director.d.ts +24 -61
- package/dist/core/comms/director.js +29 -382
- package/dist/core/comms/director.js.map +1 -1
- package/dist/core/comms/dispatch.d.ts +37 -12
- package/dist/core/comms/dispatch.js +244 -177
- package/dist/core/comms/dispatch.js.map +1 -1
- package/dist/core/comms/elon-dialogue.d.ts +23 -15
- package/dist/core/comms/elon-dialogue.js +36 -307
- package/dist/core/comms/elon-dialogue.js.map +1 -1
- package/dist/core/comms/fleet-pulse.js +4 -7
- package/dist/core/comms/fleet-pulse.js.map +1 -1
- package/dist/core/comms/handlers.d.ts +10 -6
- package/dist/core/comms/handlers.js +14 -48
- package/dist/core/comms/handlers.js.map +1 -1
- package/dist/core/comms/migrations.d.ts +47 -0
- package/dist/core/comms/migrations.js +125 -0
- package/dist/core/comms/migrations.js.map +1 -0
- package/dist/core/comms/requests.d.ts +32 -5
- package/dist/core/comms/requests.js +101 -5
- package/dist/core/comms/requests.js.map +1 -1
- package/dist/core/comms/telegram-channel.d.ts +104 -0
- package/dist/core/comms/telegram-channel.js +622 -0
- package/dist/core/comms/telegram-channel.js.map +1 -0
- package/dist/core/comms/telegram-thread-map.d.ts +56 -0
- package/dist/core/comms/telegram-thread-map.js +144 -0
- package/dist/core/comms/telegram-thread-map.js.map +1 -0
- package/dist/core/daemon/capacity-publisher.d.ts +8 -1
- package/dist/core/daemon/capacity-publisher.js +40 -2
- package/dist/core/daemon/capacity-publisher.js.map +1 -1
- package/dist/core/daemon/loop.js +18 -4
- package/dist/core/daemon/loop.js.map +1 -1
- package/dist/core/daemon/service-install-authority.d.ts +10 -2
- package/dist/core/daemon/service-install-authority.js +14 -5
- package/dist/core/daemon/service-install-authority.js.map +1 -1
- package/dist/core/daemon/service.d.ts +15 -0
- package/dist/core/daemon/service.js +26 -0
- package/dist/core/daemon/service.js.map +1 -1
- package/dist/core/daemon/tick-deadline.d.ts +44 -0
- package/dist/core/daemon/tick-deadline.js +83 -0
- package/dist/core/daemon/tick-deadline.js.map +1 -0
- package/dist/core/daemon/tick-hooks.d.ts +7 -0
- package/dist/core/daemon/tick-hooks.js.map +1 -1
- package/dist/core/daemon/tick-progress.d.ts +39 -0
- package/dist/core/daemon/tick-progress.js +194 -0
- package/dist/core/daemon/tick-progress.js.map +1 -0
- package/dist/core/dashboard.js +3 -1
- package/dist/core/dashboard.js.map +1 -1
- package/dist/core/fleet/cloud-intake.d.ts +95 -0
- package/dist/core/fleet/cloud-intake.js +557 -0
- package/dist/core/fleet/cloud-intake.js.map +1 -0
- package/dist/core/fleet/dispatch-router.d.ts +13 -0
- package/dist/core/fleet/dispatch-router.js +32 -8
- package/dist/core/fleet/dispatch-router.js.map +1 -1
- package/dist/core/fleet/fleet-merge-state.d.ts +16 -0
- package/dist/core/fleet/fleet-merge-state.js.map +1 -1
- package/dist/core/fleet/host-merge.d.ts +14 -0
- package/dist/core/fleet/host-merge.js +24 -2
- package/dist/core/fleet/host-merge.js.map +1 -1
- package/dist/core/fleet/merge-gates.d.ts +21 -0
- package/dist/core/fleet/merge-gates.js +39 -5
- package/dist/core/fleet/merge-gates.js.map +1 -1
- package/dist/core/fleet/mirrors.d.ts +162 -6
- package/dist/core/fleet/mirrors.js +1051 -49
- package/dist/core/fleet/mirrors.js.map +1 -1
- package/dist/core/fleet/standing-merge-pass.d.ts +8 -0
- package/dist/core/fleet/standing-merge-pass.js +102 -1
- package/dist/core/fleet/standing-merge-pass.js.map +1 -1
- package/dist/core/fleet/status.d.ts +17 -0
- package/dist/core/fleet/status.js +22 -0
- package/dist/core/fleet/status.js.map +1 -1
- package/dist/core/fleet/tick-hooks-live.d.ts +10 -1
- package/dist/core/fleet/tick-hooks-live.js +57 -8
- package/dist/core/fleet/tick-hooks-live.js.map +1 -1
- package/dist/core/fleet/verify-check-run.d.ts +90 -0
- package/dist/core/fleet/verify-check-run.js +274 -0
- package/dist/core/fleet/verify-check-run.js.map +1 -0
- package/dist/core/git.d.ts +17 -0
- package/dist/core/git.js +124 -25
- package/dist/core/git.js.map +1 -1
- package/dist/core/goals/open-goals.d.ts +47 -0
- package/dist/core/goals/open-goals.js +33 -0
- package/dist/core/goals/open-goals.js.map +1 -0
- package/dist/core/inbox/merge.d.ts +5 -0
- package/dist/core/inbox/merge.js +58 -2
- package/dist/core/inbox/merge.js.map +1 -1
- package/dist/core/integrations/locus.d.ts +28 -0
- package/dist/core/integrations/locus.js +155 -30
- package/dist/core/integrations/locus.js.map +1 -1
- package/dist/core/integrations/telegram-format.d.ts +40 -0
- package/dist/core/integrations/telegram-format.js +123 -0
- package/dist/core/integrations/telegram-format.js.map +1 -0
- package/dist/core/integrations/telegram.d.ts +60 -4
- package/dist/core/integrations/telegram.js +161 -28
- package/dist/core/integrations/telegram.js.map +1 -1
- package/dist/core/mcp-registry.d.ts +12 -0
- package/dist/core/mcp-registry.js +70 -0
- package/dist/core/mcp-registry.js.map +1 -1
- package/dist/core/observability/rollup.d.ts +12 -10
- package/dist/core/observability/rollup.js +44 -17
- package/dist/core/observability/rollup.js.map +1 -1
- package/dist/core/resources/connection-monitor.d.ts +9 -0
- package/dist/core/resources/connection-monitor.js +19 -9
- package/dist/core/resources/connection-monitor.js.map +1 -1
- package/dist/core/resources/quota-refresh-lease.d.ts +17 -0
- package/dist/core/resources/quota-refresh-lease.js +34 -1
- package/dist/core/resources/quota-refresh-lease.js.map +1 -1
- package/dist/core/routing/budget-api.d.ts +18 -1
- package/dist/core/routing/budget-api.js +120 -10
- package/dist/core/routing/budget-api.js.map +1 -1
- package/dist/core/routing/budget-store.d.ts +22 -1
- package/dist/core/routing/budget-store.js +22 -8
- package/dist/core/routing/budget-store.js.map +1 -1
- package/dist/core/routing/readiness-types.d.ts +82 -0
- package/dist/core/routing/readiness-types.js +23 -0
- package/dist/core/routing/readiness-types.js.map +1 -0
- package/dist/core/routing/readiness.d.ts +103 -0
- package/dist/core/routing/readiness.js +480 -0
- package/dist/core/routing/readiness.js.map +1 -0
- package/dist/core/sandbox/confine.js +22 -1
- package/dist/core/sandbox/confine.js.map +1 -1
- package/dist/core/sandbox/policy.d.ts +23 -0
- package/dist/core/sandbox/policy.js +100 -0
- package/dist/core/sandbox/policy.js.map +1 -1
- package/dist/core/types.d.ts +13 -0
- package/dist/core/types.js.map +1 -1
- package/dist/core/universe/builtins/preparation/manifest.json +1 -1
- package/dist/core/universe/builtins/preparation/preparation-bridge.mjs +1204 -939
- package/dist/core/universe/builtins/preparation/preparation-verification-fixtures.mjs +1319 -1054
- package/dist/core/usage/frontier-usage.d.ts +20 -2
- package/dist/core/usage/frontier-usage.js +75 -17
- package/dist/core/usage/frontier-usage.js.map +1 -1
- package/dist/core/verse/account-health.js +9 -3
- package/dist/core/verse/account-health.js.map +1 -1
- package/dist/core/verse/accounts.d.ts +45 -0
- package/dist/core/verse/accounts.js +246 -17
- package/dist/core/verse/accounts.js.map +1 -1
- package/dist/core/verse/activity-api.d.ts +5 -1
- package/dist/core/verse/activity-api.js +62 -14
- package/dist/core/verse/activity-api.js.map +1 -1
- package/dist/core/verse/apps-api.js +23 -11
- package/dist/core/verse/apps-api.js.map +1 -1
- package/dist/core/verse/apps.d.ts +11 -9
- package/dist/core/verse/apps.js +2 -2
- package/dist/core/verse/apps.js.map +1 -1
- package/dist/core/verse/authority-api.d.ts +17 -3
- package/dist/core/verse/authority-api.js +122 -7
- package/dist/core/verse/authority-api.js.map +1 -1
- package/dist/core/verse/autonomy-ladder.d.ts +172 -0
- package/dist/core/verse/autonomy-ladder.js +251 -0
- package/dist/core/verse/autonomy-ladder.js.map +1 -0
- package/dist/core/verse/context-fit.js +7 -5
- package/dist/core/verse/context-fit.js.map +1 -1
- package/dist/core/verse/control-api.d.ts +23 -1
- package/dist/core/verse/control-api.js +117 -10
- package/dist/core/verse/control-api.js.map +1 -1
- package/dist/core/verse/fleet-live-api.js +13 -5
- package/dist/core/verse/fleet-live-api.js.map +1 -1
- package/dist/core/verse/folder-io.d.ts +50 -0
- package/dist/core/verse/folder-io.js +142 -0
- package/dist/core/verse/folder-io.js.map +1 -0
- package/dist/core/verse/git-api.js +5 -4
- package/dist/core/verse/git-api.js.map +1 -1
- package/dist/core/verse/git-ops.d.ts +10 -1
- package/dist/core/verse/git-ops.js +18 -2
- package/dist/core/verse/git-ops.js.map +1 -1
- package/dist/core/verse/github-api.d.ts +1 -1
- package/dist/core/verse/github-api.js +5 -4
- package/dist/core/verse/github-api.js.map +1 -1
- package/dist/core/verse/github-repo.js +118 -14
- package/dist/core/verse/github-repo.js.map +1 -1
- package/dist/core/verse/leader-api.d.ts +5 -4
- package/dist/core/verse/leader-api.js +237 -10
- package/dist/core/verse/leader-api.js.map +1 -1
- package/dist/core/verse/local-models.d.ts +10 -0
- package/dist/core/verse/local-models.js +10 -1
- package/dist/core/verse/local-models.js.map +1 -1
- package/dist/core/verse/login-path.d.ts +73 -0
- package/dist/core/verse/login-path.js +123 -14
- package/dist/core/verse/login-path.js.map +1 -1
- package/dist/core/verse/mcp-control-api.js +4 -4
- package/dist/core/verse/mcp-control-api.js.map +1 -1
- package/dist/core/verse/mcp-scope.d.ts +22 -0
- package/dist/core/verse/mcp-scope.js +41 -1
- package/dist/core/verse/mcp-scope.js.map +1 -1
- package/dist/core/verse/mcp-seat-view.d.ts +10 -0
- package/dist/core/verse/mcp-seat-view.js +30 -4
- package/dist/core/verse/mcp-seat-view.js.map +1 -1
- package/dist/core/verse/overnight-api.js +20 -2
- package/dist/core/verse/overnight-api.js.map +1 -1
- package/dist/core/verse/path-guard.d.ts +24 -0
- package/dist/core/verse/path-guard.js +91 -22
- package/dist/core/verse/path-guard.js.map +1 -1
- package/dist/core/verse/preview-api.js +22 -0
- package/dist/core/verse/preview-api.js.map +1 -1
- package/dist/core/verse/projects.d.ts +21 -0
- package/dist/core/verse/projects.js +82 -0
- package/dist/core/verse/projects.js.map +1 -1
- package/dist/core/verse/seat-readiness.d.ts +25 -0
- package/dist/core/verse/seat-readiness.js +43 -6
- package/dist/core/verse/seat-readiness.js.map +1 -1
- package/dist/core/verse/session-controls-api.js +5 -3
- package/dist/core/verse/session-controls-api.js.map +1 -1
- package/dist/core/verse/session-handoff.d.ts +26 -0
- package/dist/core/verse/session-handoff.js +90 -12
- package/dist/core/verse/session-handoff.js.map +1 -1
- package/dist/core/verse/terminal-api.js +7 -4
- package/dist/core/verse/terminal-api.js.map +1 -1
- package/dist/core/verse/terminal.js +9 -5
- package/dist/core/verse/terminal.js.map +1 -1
- package/dist/core/verse/verse-api.d.ts +3 -1
- package/dist/core/verse/verse-api.js +87 -27
- package/dist/core/verse/verse-api.js.map +1 -1
- package/dist/core/verse/workspaces.d.ts +51 -0
- package/dist/core/verse/workspaces.js +177 -47
- package/dist/core/verse/workspaces.js.map +1 -1
- package/dist/core/vision/leader-apply.d.ts +40 -0
- package/dist/core/vision/leader-apply.js +101 -30
- package/dist/core/vision/leader-apply.js.map +1 -1
- package/dist/core/vision/leader-cadence.d.ts +114 -0
- package/dist/core/vision/leader-cadence.js +127 -0
- package/dist/core/vision/leader-cadence.js.map +1 -0
- package/dist/core/vision/leader-goal-evidence.d.ts +25 -0
- package/dist/core/vision/leader-goal-evidence.js +36 -0
- package/dist/core/vision/leader-goal-evidence.js.map +1 -0
- package/dist/core/vision/leader-health.d.ts +53 -0
- package/dist/core/vision/leader-health.js +92 -0
- package/dist/core/vision/leader-health.js.map +1 -0
- package/dist/core/vision/leader-operator.d.ts +176 -0
- package/dist/core/vision/leader-operator.js +355 -0
- package/dist/core/vision/leader-operator.js.map +1 -0
- package/dist/core/vision/leader-run-chain.d.ts +41 -0
- package/dist/core/vision/leader-run-chain.js +54 -0
- package/dist/core/vision/leader-run-chain.js.map +1 -0
- package/dist/core/vision/leader-seat-plan.d.ts +68 -0
- package/dist/core/vision/leader-seat-plan.js +93 -0
- package/dist/core/vision/leader-seat-plan.js.map +1 -0
- package/dist/core/vision/leader-seat.d.ts +71 -5
- package/dist/core/vision/leader-seat.js +235 -33
- package/dist/core/vision/leader-seat.js.map +1 -1
- package/dist/core/vision/leader-thread-types.d.ts +104 -0
- package/dist/core/vision/leader-thread-types.js +9 -0
- package/dist/core/vision/leader-thread-types.js.map +1 -0
- package/dist/core/vision/leader-thread.d.ts +138 -0
- package/dist/core/vision/leader-thread.js +926 -0
- package/dist/core/vision/leader-thread.js.map +1 -0
- package/dist/core/vision/leader-types.d.ts +75 -1
- package/dist/core/vision/leader.d.ts +88 -18
- package/dist/core/vision/leader.js +269 -69
- package/dist/core/vision/leader.js.map +1 -1
- package/dist/core/web/public/next/assets/{ActionMenu-B4-39f2S.js → ActionMenu-CIiX2X8w.js} +2 -2
- package/dist/core/web/public/next/assets/{ActionMenu-B4-39f2S.js.map → ActionMenu-CIiX2X8w.js.map} +1 -1
- package/dist/core/web/public/next/assets/Advanced-DHsysoIL.js +2 -0
- package/dist/core/web/public/next/assets/{Advanced-B-LqYXvd.js.map → Advanced-DHsysoIL.js.map} +1 -1
- package/dist/core/web/public/next/assets/App-CMeAocmW.js +7 -0
- package/dist/core/web/public/next/assets/{App-E2zCDRxm.js.map → App-CMeAocmW.js.map} +1 -1
- package/dist/core/web/public/next/assets/AppsSection-DISYnMnc.js +2 -0
- package/dist/core/web/public/next/assets/{AppsSection-8wXPPhZb.js.map → AppsSection-DISYnMnc.js.map} +1 -1
- package/dist/core/web/public/next/assets/AreaTrend-DK2Y2TbZ.js +2 -0
- package/dist/core/web/public/next/assets/AreaTrend-DK2Y2TbZ.js.map +1 -0
- package/dist/core/web/public/next/assets/AutonomyOffState-CEPSZToP.js +3 -0
- package/dist/core/web/public/next/assets/AutonomyOffState-CEPSZToP.js.map +1 -0
- package/dist/core/web/public/next/assets/AutonomyOffState-CmSSTHVe.css +1 -0
- package/dist/core/web/public/next/assets/AutonomySection-DWc3ciAD.js +2 -0
- package/dist/core/web/public/next/assets/{AutonomySection-B46igJKR.js.map → AutonomySection-DWc3ciAD.js.map} +1 -1
- package/dist/core/web/public/next/assets/AutonomyStatus-CYaHjJ9U.js +2 -0
- package/dist/core/web/public/next/assets/AutonomyStatus-CYaHjJ9U.js.map +1 -0
- package/dist/core/web/public/next/assets/{BranchBar-BRkrEQGs.js → BranchBar-BxhdiIpx.js} +2 -2
- package/dist/core/web/public/next/assets/{BranchBar-BRkrEQGs.js.map → BranchBar-BxhdiIpx.js.map} +1 -1
- package/dist/core/web/public/next/assets/BudgetControl-C-0KhjRS.js +2 -0
- package/dist/core/web/public/next/assets/BudgetControl-C-0KhjRS.js.map +1 -0
- package/dist/core/web/public/next/assets/{Button-BhO-WKPp.js → Button-D90dzI_o.js} +2 -2
- package/dist/core/web/public/next/assets/{Button-BhO-WKPp.js.map → Button-D90dzI_o.js.map} +1 -1
- package/dist/core/web/public/next/assets/{CapacityStrip-CkFz5vq2.js → CapacityStrip-qKDBC65C.js} +2 -2
- package/dist/core/web/public/next/assets/{CapacityStrip-CkFz5vq2.js.map → CapacityStrip-qKDBC65C.js.map} +1 -1
- package/dist/core/web/public/next/assets/ChartFrame-DY-GVUb3.js +2 -0
- package/dist/core/web/public/next/assets/ChartFrame-DY-GVUb3.js.map +1 -0
- package/dist/core/web/public/next/assets/ChartFrame-DyjmyV8N.css +1 -0
- package/dist/core/web/public/next/assets/ChatSection-DlT4QU6a.js +3 -0
- package/dist/core/web/public/next/assets/ChatSection-DlT4QU6a.js.map +1 -0
- package/dist/core/web/public/next/assets/{CloudBudgetForm-Bd91iStN.js → CloudBudgetForm-BrNun_Yu.js} +2 -2
- package/dist/core/web/public/next/assets/{CloudBudgetForm-Bd91iStN.js.map → CloudBudgetForm-BrNun_Yu.js.map} +1 -1
- package/dist/core/web/public/next/assets/CommandPalette-C5lDDKN0.js +2 -0
- package/dist/core/web/public/next/assets/CommandPalette-C5lDDKN0.js.map +1 -0
- package/dist/core/web/public/next/assets/CommandSection-B0xuU-yZ.css +1 -0
- package/dist/core/web/public/next/assets/CommandSection-DXFvPgKp.js +3 -0
- package/dist/core/web/public/next/assets/CommandSection-DXFvPgKp.js.map +1 -0
- package/dist/core/web/public/next/assets/{ConfirmDialog-Bv4hdK2T.js → ConfirmDialog-BR4nZQ2D.js} +2 -2
- package/dist/core/web/public/next/assets/{ConfirmDialog-Bv4hdK2T.js.map → ConfirmDialog-BR4nZQ2D.js.map} +1 -1
- package/dist/core/web/public/next/assets/{DeleteChatDialog-CH1Ix8Eh.js → DeleteChatDialog-BCfP12GH.js} +2 -2
- package/dist/core/web/public/next/assets/{DeleteChatDialog-CH1Ix8Eh.js.map → DeleteChatDialog-BCfP12GH.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Dialog-BSsIFNqD.js → Dialog-D3msQlCj.js} +2 -2
- package/dist/core/web/public/next/assets/{Dialog-BSsIFNqD.js.map → Dialog-D3msQlCj.js.map} +1 -1
- package/dist/core/web/public/next/assets/{DiffPane-BpNuJmth.js → DiffPane-Ix6hvXlY.js} +2 -2
- package/dist/core/web/public/next/assets/{DiffPane-BpNuJmth.js.map → DiffPane-Ix6hvXlY.js.map} +1 -1
- package/dist/core/web/public/next/assets/{DiffViewer-CJarUvs0.js → DiffViewer-C4WKsCIe.js} +2 -2
- package/dist/core/web/public/next/assets/{DiffViewer-CJarUvs0.js.map → DiffViewer-C4WKsCIe.js.map} +1 -1
- package/dist/core/web/public/next/assets/DockHost-aWP7liri.js +2 -0
- package/dist/core/web/public/next/assets/{DockHost-DW9q0NdR.js.map → DockHost-aWP7liri.js.map} +1 -1
- package/dist/core/web/public/next/assets/{EmptyState-DPrd2aak.js → EmptyState-D2IRy3Cg.js} +2 -2
- package/dist/core/web/public/next/assets/{EmptyState-DPrd2aak.js.map → EmptyState-D2IRy3Cg.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Epistemic-D7MxSmED.js → Epistemic-DMr18muX.js} +2 -2
- package/dist/core/web/public/next/assets/{Epistemic-D7MxSmED.js.map → Epistemic-DMr18muX.js.map} +1 -1
- package/dist/core/web/public/next/assets/EvidenceTimeline-DXmmz6Fi.js +2 -0
- package/dist/core/web/public/next/assets/EvidenceTimeline-DXmmz6Fi.js.map +1 -0
- package/dist/core/web/public/next/assets/EvidenceTimeline-PBDAiSuG.css +1 -0
- package/dist/core/web/public/next/assets/FleetSection-BfplcwFU.js +3 -0
- package/dist/core/web/public/next/assets/FleetSection-BfplcwFU.js.map +1 -0
- package/dist/core/web/public/next/assets/Funnel-Dh2yVHws.js +2 -0
- package/dist/core/web/public/next/assets/Funnel-Dh2yVHws.js.map +1 -0
- package/dist/core/web/public/next/assets/{GearTray-CIPtzeIx.js → GearTray-Dt-ee32-.js} +2 -2
- package/dist/core/web/public/next/assets/{GearTray-CIPtzeIx.js.map → GearTray-Dt-ee32-.js.map} +1 -1
- package/dist/core/web/public/next/assets/GrowthSection-P9EUfGeA.js +2 -0
- package/dist/core/web/public/next/assets/{GrowthSection-iiQg6wvK.js.map → GrowthSection-P9EUfGeA.js.map} +1 -1
- package/dist/core/web/public/next/assets/{HandoffDialog-DrQ6ofuy.js → HandoffDialog-CLJQLe7M.js} +2 -2
- package/dist/core/web/public/next/assets/{HandoffDialog-DrQ6ofuy.js.map → HandoffDialog-CLJQLe7M.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Input-C0MQxEM6.js → Input-D_KRJA9V.js} +2 -2
- package/dist/core/web/public/next/assets/{Input-C0MQxEM6.js.map → Input-D_KRJA9V.js.map} +1 -1
- package/dist/core/web/public/next/assets/LeaderCard-D-7DlJuR.js +2 -0
- package/dist/core/web/public/next/assets/LeaderCard-D-7DlJuR.js.map +1 -0
- package/dist/core/web/public/next/assets/LeaderCard-DNsbLwY3.css +1 -0
- package/dist/core/web/public/next/assets/LeaderConversation-DGZjWI-E.js +2 -0
- package/dist/core/web/public/next/assets/LeaderConversation-DGZjWI-E.js.map +1 -0
- package/dist/core/web/public/next/assets/LeaderConversation-DPHUTq6W.css +1 -0
- package/dist/core/web/public/next/assets/MatrixHeatmap-C7BCG2d7.js +2 -0
- package/dist/core/web/public/next/assets/MatrixHeatmap-C7BCG2d7.js.map +1 -0
- package/dist/core/web/public/next/assets/McpSection-DGbRcyv8.js +2 -0
- package/dist/core/web/public/next/assets/{McpSection-BRuCcTjT.js.map → McpSection-DGbRcyv8.js.map} +1 -1
- package/dist/core/web/public/next/assets/{MessageMarkdown-bRvD5nSc.js → MessageMarkdown-DFvk06qG.js} +3 -3
- package/dist/core/web/public/next/assets/{MessageMarkdown-bRvD5nSc.js.map → MessageMarkdown-DFvk06qG.js.map} +1 -1
- package/dist/core/web/public/next/assets/{MessageMarkdownRenderer-FjjkPKA2.js → MessageMarkdownRenderer-BAv4oEUf.js} +2 -2
- package/dist/core/web/public/next/assets/{MessageMarkdownRenderer-FjjkPKA2.js.map → MessageMarkdownRenderer-BAv4oEUf.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Meter-CFNLpwA-.js → Meter-CMj-VafN.js} +2 -2
- package/dist/core/web/public/next/assets/{Meter-CFNLpwA-.js.map → Meter-CMj-VafN.js.map} +1 -1
- package/dist/core/web/public/next/assets/MindSection-C6kLPIjt.js +3 -0
- package/dist/core/web/public/next/assets/MindSection-C6kLPIjt.js.map +1 -0
- package/dist/core/web/public/next/assets/{MutationTokenDialog-T7gv3a9r.js → MutationTokenDialog-DbnO8aYi.js} +2 -2
- package/dist/core/web/public/next/assets/{MutationTokenDialog-T7gv3a9r.js.map → MutationTokenDialog-DbnO8aYi.js.map} +1 -1
- package/dist/core/web/public/next/assets/NeedsYouDrawer-crsiX-Nc.js +3 -0
- package/dist/core/web/public/next/assets/NeedsYouDrawer-crsiX-Nc.js.map +1 -0
- package/dist/core/web/public/next/assets/NeedsYouList-CCsijdWt.js +2 -0
- package/dist/core/web/public/next/assets/{NeedsYouList-BgH8ezqf.js.map → NeedsYouList-CCsijdWt.js.map} +1 -1
- package/dist/core/web/public/next/assets/NeedsYouList-CH6Mq-E_.css +1 -0
- package/dist/core/web/public/next/assets/NewChatDialog-BlCHtOIr.js +3 -0
- package/dist/core/web/public/next/assets/{NewChatDialog-Db5Xjrk8.js.map → NewChatDialog-BlCHtOIr.js.map} +1 -1
- package/dist/core/web/public/next/assets/OnboardingFlow-Cy9BA9eR.js +3 -0
- package/dist/core/web/public/next/assets/OnboardingFlow-Cy9BA9eR.js.map +1 -0
- package/dist/core/web/public/next/assets/OnboardingGate-BXpNFoLt.js +3 -0
- package/dist/core/web/public/next/assets/{OnboardingGate-C4VXn7BJ.js.map → OnboardingGate-BXpNFoLt.js.map} +1 -1
- package/dist/core/web/public/next/assets/PreviewPane-COid5A0f.js +2 -0
- package/dist/core/web/public/next/assets/{PreviewPane-BPBbcROb.js.map → PreviewPane-COid5A0f.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ProviderLogo-CT2cSfkg.js → ProviderLogo-Cq-CYIwT.js} +2 -2
- package/dist/core/web/public/next/assets/{ProviderLogo-CT2cSfkg.js.map → ProviderLogo-Cq-CYIwT.js.map} +1 -1
- package/dist/core/web/public/next/assets/{RailStatus-Vgl0GYtd.js → RailStatus-Co5UB6We.js} +2 -2
- package/dist/core/web/public/next/assets/{RailStatus-Vgl0GYtd.js.map → RailStatus-Co5UB6We.js.map} +1 -1
- package/dist/core/web/public/next/assets/{RefreshIndicator-CSyqpY_q.js → RefreshIndicator-DO9ibnSq.js} +2 -2
- package/dist/core/web/public/next/assets/{RefreshIndicator-CSyqpY_q.js.map → RefreshIndicator-DO9ibnSq.js.map} +1 -1
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-Dr2WRgkq.js +7 -0
- package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-BX2nkdmZ.js.map → ResourcePoolConsoleApp-Dr2WRgkq.js.map} +1 -1
- package/dist/core/web/public/next/assets/ResourcesChrome-CwMnbqI1.js +3 -0
- package/dist/core/web/public/next/assets/ResourcesChrome-CwMnbqI1.js.map +1 -0
- package/dist/core/web/public/next/assets/ResourcesDrawer-B_Xp_JNc.js +2 -0
- package/dist/core/web/public/next/assets/ResourcesDrawer-B_Xp_JNc.js.map +1 -0
- package/dist/core/web/public/next/assets/ResourcesDrawer-DrGoO8KG.css +1 -0
- package/dist/core/web/public/next/assets/{RouteErrorBoundary-BaC7YVzR.js → RouteErrorBoundary-SkNhhUbt.js} +2 -2
- package/dist/core/web/public/next/assets/{RouteErrorBoundary-BaC7YVzR.js.map → RouteErrorBoundary-SkNhhUbt.js.map} +1 -1
- package/dist/core/web/public/next/assets/{RunInCloudAction-D5PDHJPE.js → RunInCloudAction-BXPlZwJQ.js} +2 -2
- package/dist/core/web/public/next/assets/{RunInCloudAction-D5PDHJPE.js.map → RunInCloudAction-BXPlZwJQ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{SeatSelector-Cg75LInq.js → SeatSelector-J5R3zDkB.js} +3 -3
- package/dist/core/web/public/next/assets/{SeatSelector-Cg75LInq.js.map → SeatSelector-J5R3zDkB.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Segmented-CametjLt.js → Segmented-CGh_-tBb.js} +2 -2
- package/dist/core/web/public/next/assets/{Segmented-CametjLt.js.map → Segmented-CGh_-tBb.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Select-De52fghU.js → Select-CWnmUzs3.js} +2 -2
- package/dist/core/web/public/next/assets/{Select-De52fghU.js.map → Select-CWnmUzs3.js.map} +1 -1
- package/dist/core/web/public/next/assets/{SessionGate-CzlcNq3O.js → SessionGate-sp4KsnUo.js} +2 -2
- package/dist/core/web/public/next/assets/{SessionGate-CzlcNq3O.js.map → SessionGate-sp4KsnUo.js.map} +1 -1
- package/dist/core/web/public/next/assets/{SessionInsightChip-Bk_Owa7u.js → SessionInsightChip-DLFAzdbi.js} +2 -2
- package/dist/core/web/public/next/assets/{SessionInsightChip-Bk_Owa7u.js.map → SessionInsightChip-DLFAzdbi.js.map} +1 -1
- package/dist/core/web/public/next/assets/{SessionSearch-C6C8DHN8.js → SessionSearch-DbFjwR1h.js} +2 -2
- package/dist/core/web/public/next/assets/{SessionSearch-C6C8DHN8.js.map → SessionSearch-DbFjwR1h.js.map} +1 -1
- package/dist/core/web/public/next/assets/{SettingsSection-mI9wQYZg.js → SettingsSection-Ch23msJ5.js} +2 -2
- package/dist/core/web/public/next/assets/SettingsSection-Ch23msJ5.js.map +1 -0
- package/dist/core/web/public/next/assets/SetupChecklist-Bd76MBAR.js +2 -0
- package/dist/core/web/public/next/assets/SetupChecklist-Bd76MBAR.js.map +1 -0
- package/dist/core/web/public/next/assets/SetupChecklist-BhZpFLsj.css +1 -0
- package/dist/core/web/public/next/assets/ShadowDecisions-C5RAY91f.js +3 -0
- package/dist/core/web/public/next/assets/ShadowDecisions-C5RAY91f.js.map +1 -0
- package/dist/core/web/public/next/assets/{Sheet-CJhK9Dqv.js → Sheet-BZ2AG4Eo.js} +2 -2
- package/dist/core/web/public/next/assets/{Sheet-CJhK9Dqv.js.map → Sheet-BZ2AG4Eo.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ShortcutsOverlay-DDW-i9Fc.js → ShortcutsOverlay-C8n8y3zf.js} +2 -2
- package/dist/core/web/public/next/assets/{ShortcutsOverlay-DDW-i9Fc.js.map → ShortcutsOverlay-C8n8y3zf.js.map} +1 -1
- package/dist/core/web/public/next/assets/Sidebar-DN--cPYN.js +3 -0
- package/dist/core/web/public/next/assets/Sidebar-DN--cPYN.js.map +1 -0
- package/dist/core/web/public/next/assets/{Slider-Bwd9HfXl.js → Slider-KVoJLDD6.js} +2 -2
- package/dist/core/web/public/next/assets/{Slider-Bwd9HfXl.js.map → Slider-KVoJLDD6.js.map} +1 -1
- package/dist/core/web/public/next/assets/StatTile-C4KgjltA.js +2 -0
- package/dist/core/web/public/next/assets/StatTile-C4KgjltA.js.map +1 -0
- package/dist/core/web/public/next/assets/StatTile-CNCqDoPO.css +1 -0
- package/dist/core/web/public/next/assets/{StatusBadge-DHKRtttH.js → StatusBadge-CXsYqmdU.js} +2 -2
- package/dist/core/web/public/next/assets/{StatusBadge-DHKRtttH.js.map → StatusBadge-CXsYqmdU.js.map} +1 -1
- package/dist/core/web/public/next/assets/StepBand-BMtVOFpK.js +2 -0
- package/dist/core/web/public/next/assets/StepBand-BMtVOFpK.js.map +1 -0
- package/dist/core/web/public/next/assets/{Surface-D--LXgQm.js → Surface-BfDmQcKe.js} +2 -2
- package/dist/core/web/public/next/assets/{Surface-D--LXgQm.js.map → Surface-BfDmQcKe.js.map} +1 -1
- package/dist/core/web/public/next/assets/Swimlane-B9Exxekk.js +2 -0
- package/dist/core/web/public/next/assets/Swimlane-B9Exxekk.js.map +1 -0
- package/dist/core/web/public/next/assets/Swimlane-Bf4Rjoy9.css +1 -0
- package/dist/core/web/public/next/assets/{Switch-PMixU6b0.js → Switch-9NtO_6Lj.js} +2 -2
- package/dist/core/web/public/next/assets/{Switch-PMixU6b0.js.map → Switch-9NtO_6Lj.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Tag-fordBsrJ.js → Tag-DscCRFcN.js} +2 -2
- package/dist/core/web/public/next/assets/{Tag-fordBsrJ.js.map → Tag-DscCRFcN.js.map} +1 -1
- package/dist/core/web/public/next/assets/{TerminalPane-BSnGdUuF.js → TerminalPane-B668-DJW.js} +5 -5
- package/dist/core/web/public/next/assets/{TerminalPane-BSnGdUuF.js.map → TerminalPane-B668-DJW.js.map} +1 -1
- package/dist/core/web/public/next/assets/UniverseConsoleApp-BUV_brY7.js +2 -0
- package/dist/core/web/public/next/assets/{UniverseConsoleApp-DtXFhAhw.js.map → UniverseConsoleApp-BUV_brY7.js.map} +1 -1
- package/dist/core/web/public/next/assets/{UniverseView-sWm3pnTW.js → UniverseView-DG70zH0W.js} +4 -4
- package/dist/core/web/public/next/assets/{UniverseView-sWm3pnTW.js.map → UniverseView-DG70zH0W.js.map} +1 -1
- package/dist/core/web/public/next/assets/UsageSection-DY7uOTm1.js +3 -0
- package/dist/core/web/public/next/assets/UsageSection-DY7uOTm1.js.map +1 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-CWU6R3By.js +3 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-CWU6R3By.js.map +1 -0
- package/dist/core/web/public/next/assets/Workspace-D1nP9BhD.js +10 -0
- package/dist/core/web/public/next/assets/{Workspace-BQSZFSeO.js.map → Workspace-D1nP9BhD.js.map} +1 -1
- package/dist/core/web/public/next/assets/actions-CY-PjpGc.js +2 -0
- package/dist/core/web/public/next/assets/{actions-Ci6I4C0l.js.map → actions-CY-PjpGc.js.map} +1 -1
- package/dist/core/web/public/next/assets/actions-DkWhhWIg.css +1 -0
- package/dist/core/web/public/next/assets/{apps-model-DlwnfqwJ.js → apps-model-Du02KhAf.js} +2 -2
- package/dist/core/web/public/next/assets/{apps-model-DlwnfqwJ.js.map → apps-model-Du02KhAf.js.map} +1 -1
- package/dist/core/web/public/next/assets/{authority-model-BFuPezSo.js → authority-model-79SeuKlJ.js} +2 -2
- package/dist/core/web/public/next/assets/{authority-model-BFuPezSo.js.map → authority-model-79SeuKlJ.js.map} +1 -1
- package/dist/core/web/public/next/assets/autonomy-ladder-BGWmZzP7.css +1 -0
- package/dist/core/web/public/next/assets/autonomy-ladder.module-ArDv4bkr.js +2 -0
- package/dist/core/web/public/next/assets/autonomy-ladder.module-ArDv4bkr.js.map +1 -0
- package/dist/core/web/public/next/assets/budget-queries-D8QV5XtH.js +2 -0
- package/dist/core/web/public/next/assets/{budget-queries-DsAjH8ac.js.map → budget-queries-D8QV5XtH.js.map} +1 -1
- package/dist/core/web/public/next/assets/{capacity-strip-model-Dn1cNOhz.js → capacity-strip-model-BwZ1ZbkU.js} +2 -2
- package/dist/core/web/public/next/assets/{capacity-strip-model-Dn1cNOhz.js.map → capacity-strip-model-BwZ1ZbkU.js.map} +1 -1
- package/dist/core/web/public/next/assets/charts-BZ50mZD_.js +2 -0
- package/dist/core/web/public/next/assets/charts-BZ50mZD_.js.map +1 -0
- package/dist/core/web/public/next/assets/charts-ClCB18la.css +1 -0
- package/dist/core/web/public/next/assets/cloud-model-BDivRMqv.js +2 -0
- package/dist/core/web/public/next/assets/{cloud-model-CX407gPY.js.map → cloud-model-BDivRMqv.js.map} +1 -1
- package/dist/core/web/public/next/assets/{cloud-queries-QAhpExlz.js → cloud-queries-BPQQ5_pJ.js} +2 -2
- package/dist/core/web/public/next/assets/{cloud-queries-QAhpExlz.js.map → cloud-queries-BPQQ5_pJ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{colors-BZ3a9wFf.js → colors-DvK0wMHH.js} +2 -2
- package/dist/core/web/public/next/assets/{colors-BZ3a9wFf.js.map → colors-DvK0wMHH.js.map} +1 -1
- package/dist/core/web/public/next/assets/{command-catalog-CF0cQQac.js → command-catalog-BIntSa9l.js} +2 -2
- package/dist/core/web/public/next/assets/command-catalog-BIntSa9l.js.map +1 -0
- package/dist/core/web/public/next/assets/{context-model-Co4wh3SM.js → context-model-Br8t9jgG.js} +2 -2
- package/dist/core/web/public/next/assets/{context-model-Co4wh3SM.js.map → context-model-Br8t9jgG.js.map} +1 -1
- package/dist/core/web/public/next/assets/{context-model-DV1zCfAm.js → context-model-CokrgEUq.js} +3 -3
- package/dist/core/web/public/next/assets/{context-model-DV1zCfAm.js.map → context-model-CokrgEUq.js.map} +1 -1
- package/dist/core/web/public/next/assets/context-queries-eL3caKVG.js +2 -0
- package/dist/core/web/public/next/assets/{context-queries-CktFQqSS.js.map → context-queries-eL3caKVG.js.map} +1 -1
- package/dist/core/web/public/next/assets/{control-queries-DAvlh_YM.js → control-queries-B9K-wmcB.js} +2 -2
- package/dist/core/web/public/next/assets/{control-queries-DAvlh_YM.js.map → control-queries-B9K-wmcB.js.map} +1 -1
- package/dist/core/web/public/next/assets/copy-setup-XMgCC5f8.js +2 -0
- package/dist/core/web/public/next/assets/copy-setup-XMgCC5f8.js.map +1 -0
- package/dist/core/web/public/next/assets/{dock-icons-BFuBbqJl.js → dock-icons-BJ6ZqB2t.js} +2 -2
- package/dist/core/web/public/next/assets/{dock-icons-BFuBbqJl.js.map → dock-icons-BJ6ZqB2t.js.map} +1 -1
- package/dist/core/web/public/next/assets/{fleet-model-Dfd5JyLR.js → fleet-model-BvDR3gjp.js} +2 -2
- package/dist/core/web/public/next/assets/{fleet-model-Dfd5JyLR.js.map → fleet-model-BvDR3gjp.js.map} +1 -1
- package/dist/core/web/public/next/assets/{fleet-queries-RtMTd5rR.js → fleet-queries-W2UFVMZJ.js} +2 -2
- package/dist/core/web/public/next/assets/{fleet-queries-RtMTd5rR.js.map → fleet-queries-W2UFVMZJ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{focus-trap-BdrbWfC4.js → focus-trap-CtI1SlMX.js} +2 -2
- package/dist/core/web/public/next/assets/{focus-trap-BdrbWfC4.js.map → focus-trap-CtI1SlMX.js.map} +1 -1
- package/dist/core/web/public/next/assets/format-CH4g_sl1.js +2 -0
- package/dist/core/web/public/next/assets/format-CH4g_sl1.js.map +1 -0
- package/dist/core/web/public/next/assets/{format-DKrNjdwD.js → format-D7nBg2bn.js} +2 -2
- package/dist/core/web/public/next/assets/{format-DKrNjdwD.js.map → format-D7nBg2bn.js.map} +1 -1
- package/dist/core/web/public/next/assets/{git-model-_BnI_Ogh.js → git-model-Dnkd1TnK.js} +2 -2
- package/dist/core/web/public/next/assets/{git-model-_BnI_Ogh.js.map → git-model-Dnkd1TnK.js.map} +1 -1
- package/dist/core/web/public/next/assets/global-CMNW_dqn.js +2 -0
- package/dist/core/web/public/next/assets/{global-CNNGMril.js.map → global-CMNW_dqn.js.map} +1 -1
- package/dist/core/web/public/next/assets/guarded-runners-YeAc9wLQ.js +2 -0
- package/dist/core/web/public/next/assets/{guarded-runners-DPh2d32e.js.map → guarded-runners-YeAc9wLQ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{icons-0haWy6nh.js → icons-D1sYbCTQ.js} +2 -2
- package/dist/core/web/public/next/assets/{icons-0haWy6nh.js.map → icons-D1sYbCTQ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{index-QesfXBsb.js → index-DZ-UQ7v8.js} +3 -3
- package/dist/core/web/public/next/assets/{index-QesfXBsb.js.map → index-DZ-UQ7v8.js.map} +1 -1
- package/dist/core/web/public/next/assets/leader-focus-BjeJv7CW.js +2 -0
- package/dist/core/web/public/next/assets/leader-focus-BjeJv7CW.js.map +1 -0
- package/dist/core/web/public/next/assets/leader-thread-types-CDb-VPPO.js +2 -0
- package/dist/core/web/public/next/assets/leader-thread-types-CDb-VPPO.js.map +1 -0
- package/dist/core/web/public/next/assets/{live-model-De-q0ZFK.js → live-model-D_32RAOR.js} +2 -2
- package/dist/core/web/public/next/assets/live-model-D_32RAOR.js.map +1 -0
- package/dist/core/web/public/next/assets/{mind.module-BrK2nFOL.js → mind.module-7hA92hRg.js} +2 -2
- package/dist/core/web/public/next/assets/{mind.module-BrK2nFOL.js.map → mind.module-7hA92hRg.js.map} +1 -1
- package/dist/core/web/public/next/assets/mutations-DQPbFdRJ.js +2 -0
- package/dist/core/web/public/next/assets/{mutations-DTCtKCn_.js.map → mutations-DQPbFdRJ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{nav-KmGX0CVz.js → nav-0dOYe2rJ.js} +2 -2
- package/dist/core/web/public/next/assets/{nav-KmGX0CVz.js.map → nav-0dOYe2rJ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{overnight-queries-mlO0xzsr.js → overnight-queries-C4wOnFtE.js} +2 -2
- package/dist/core/web/public/next/assets/{overnight-queries-mlO0xzsr.js.map → overnight-queries-C4wOnFtE.js.map} +1 -1
- package/dist/core/web/public/next/assets/primitives-2A5Eq8FQ.js +1 -0
- package/dist/core/web/public/next/assets/{queries-CyBE8nsW.js → queries-BTXWdZzU.js} +2 -2
- package/dist/core/web/public/next/assets/{queries-CyBE8nsW.js.map → queries-BTXWdZzU.js.map} +1 -1
- package/dist/core/web/public/next/assets/{reasoning-pref-CaoA4_Vl.js → reasoning-pref-Ca6lN6x_.js} +2 -2
- package/dist/core/web/public/next/assets/{reasoning-pref-CaoA4_Vl.js.map → reasoning-pref-Ca6lN6x_.js.map} +1 -1
- package/dist/core/web/public/next/assets/{reveal-anchor-BP1VqnPU.js → reveal-anchor-DfNLgK_I.js} +2 -2
- package/dist/core/web/public/next/assets/{reveal-anchor-BP1VqnPU.js.map → reveal-anchor-DfNLgK_I.js.map} +1 -1
- package/dist/core/web/public/next/assets/seat-subscription-C5uPHBsN.js +2 -0
- package/dist/core/web/public/next/assets/{seat-subscription-C5Tvr0WQ.js.map → seat-subscription-C5uPHBsN.js.map} +1 -1
- package/dist/core/web/public/next/assets/{sidebar-model-B5Qk95k_.js → sidebar-model-CLCdqwkR.js} +2 -2
- package/dist/core/web/public/next/assets/{sidebar-model-B5Qk95k_.js.map → sidebar-model-CLCdqwkR.js.map} +1 -1
- package/dist/core/web/public/next/assets/{slots-DHKQDoE6.js → slots-C-qBeu-8.js} +3 -3
- package/dist/core/web/public/next/assets/{slots-DHKQDoE6.js.map → slots-C-qBeu-8.js.map} +1 -1
- package/dist/core/web/public/next/assets/{surface-data-CchClvHo.js → surface-data-Dj71tdD-.js} +2 -2
- package/dist/core/web/public/next/assets/{surface-data-CchClvHo.js.map → surface-data-Dj71tdD-.js.map} +1 -1
- package/dist/core/web/public/next/assets/surface-prefetch-B0WdJOJG.js +2 -0
- package/dist/core/web/public/next/assets/surface-prefetch-B0WdJOJG.js.map +1 -0
- package/dist/core/web/public/next/assets/{tasks-model-D6WFVULI.js → tasks-model-O8yR5YXp.js} +2 -2
- package/dist/core/web/public/next/assets/{tasks-model-D6WFVULI.js.map → tasks-model-O8yR5YXp.js.map} +1 -1
- package/dist/core/web/public/next/assets/{terminal-icons-BvCvuXk2.js → terminal-icons-CoRZq9iO.js} +2 -2
- package/dist/core/web/public/next/assets/{terminal-icons-BvCvuXk2.js.map → terminal-icons-CoRZq9iO.js.map} +1 -1
- package/dist/core/web/public/next/assets/thread-data-DQB_g66d.js +2 -0
- package/dist/core/web/public/next/assets/thread-data-DQB_g66d.js.map +1 -0
- package/dist/core/web/public/next/assets/types-IYKrYLku.js +2 -0
- package/dist/core/web/public/next/assets/types-IYKrYLku.js.map +1 -0
- package/dist/core/web/public/next/assets/{usage-queries-D1SU1t7r.js → usage-queries-AdjEyEtn.js} +2 -2
- package/dist/core/web/public/next/assets/{usage-queries-D1SU1t7r.js.map → usage-queries-AdjEyEtn.js.map} +1 -1
- package/dist/core/web/public/next/assets/{use-guarded-action-BZmBv7CJ.js → use-guarded-action-CuuMHeDX.js} +2 -2
- package/dist/core/web/public/next/assets/{use-guarded-action-BZmBv7CJ.js.map → use-guarded-action-CuuMHeDX.js.map} +1 -1
- package/dist/core/web/public/next/assets/{use-ticker-CkJ4-0wU.js → use-ticker-BBcp02SZ.js} +2 -2
- package/dist/core/web/public/next/assets/{use-ticker-CkJ4-0wU.js.map → use-ticker-BBcp02SZ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{use-token-gate-CYcfk_GS.js → use-token-gate-CbXVYQYI.js} +2 -2
- package/dist/core/web/public/next/assets/{use-token-gate-CYcfk_GS.js.map → use-token-gate-CbXVYQYI.js.map} +1 -1
- package/dist/core/web/public/next/assets/{useOnboarding-CujRS6PK.js → useOnboarding-C_Fpe_sH.js} +2 -2
- package/dist/core/web/public/next/assets/{useOnboarding-CujRS6PK.js.map → useOnboarding-C_Fpe_sH.js.map} +1 -1
- package/dist/core/web/public/next/assets/{useSeatsRefresh-BEaQdkZs.js → useSeatsRefresh-Co1CnxLc.js} +2 -2
- package/dist/core/web/public/next/assets/{useSeatsRefresh-BEaQdkZs.js.map → useSeatsRefresh-Co1CnxLc.js.map} +1 -1
- package/dist/core/web/public/next/assets/{useVerseTranscript-BQ0J6Xhx.js → useVerseTranscript-DgbigUlV.js} +2 -2
- package/dist/core/web/public/next/assets/{useVerseTranscript-BQ0J6Xhx.js.map → useVerseTranscript-DgbigUlV.js.map} +1 -1
- package/dist/core/web/public/next/assets/{verse-icons-DndgEgKS.js → verse-icons-S56hIiXS.js} +2 -2
- package/dist/core/web/public/next/assets/{verse-icons-DndgEgKS.js.map → verse-icons-S56hIiXS.js.map} +1 -1
- package/dist/core/web/public/next/assets/verse-model-DAVevkNe.js +2 -0
- package/dist/core/web/public/next/assets/{verse-model-MMySehjj.js.map → verse-model-DAVevkNe.js.map} +1 -1
- package/dist/core/web/public/next/assets/{warmup-CNEH_-be.js → warmup-B_RAtNZ_.js} +3 -3
- package/dist/core/web/public/next/assets/{warmup-CNEH_-be.js.map → warmup-B_RAtNZ_.js.map} +1 -1
- package/dist/core/web/public/next/assets/why-seat-model-M_r0wX3b.js +2 -0
- package/dist/core/web/public/next/assets/why-seat-model-M_r0wX3b.js.map +1 -0
- package/dist/core/web/public/next/assets/{workspace-model-Dukb2wwv.js → workspace-model-dji5-Al3.js} +2 -2
- package/dist/core/web/public/next/assets/{workspace-model-Dukb2wwv.js.map → workspace-model-dji5-Al3.js.map} +1 -1
- package/dist/core/web/public/next/index.html +1 -1
- package/dist/core/web/read-projection-worker.js +2 -1
- package/dist/core/web/read-projection-worker.js.map +1 -1
- package/dist/core/web/read-projections.d.ts +3 -0
- package/dist/core/web/read-projections.js +2 -2
- package/dist/core/web/read-projections.js.map +1 -1
- package/dist/core/web/visibility.js +3 -4
- package/dist/core/web/visibility.js.map +1 -1
- package/dist/release-dependency-inventory.json +1 -1
- package/docs/AUTONOMY-GAP.md +1 -1
- package/package.json +3 -2
- package/dist/core/web/public/next/assets/Advanced-B-LqYXvd.js +0 -2
- package/dist/core/web/public/next/assets/App-E2zCDRxm.js +0 -7
- package/dist/core/web/public/next/assets/AppsSection-8wXPPhZb.js +0 -2
- package/dist/core/web/public/next/assets/AreaTrend-BFimExYn.js +0 -2
- package/dist/core/web/public/next/assets/AreaTrend-BFimExYn.js.map +0 -1
- package/dist/core/web/public/next/assets/AutonomyOffState-B5eEkmiz.js +0 -2
- package/dist/core/web/public/next/assets/AutonomyOffState-B5eEkmiz.js.map +0 -1
- package/dist/core/web/public/next/assets/AutonomyOffState-DmbE71vI.css +0 -1
- package/dist/core/web/public/next/assets/AutonomySection-B46igJKR.js +0 -2
- package/dist/core/web/public/next/assets/BudgetControl-T_RjPrw9.js +0 -2
- package/dist/core/web/public/next/assets/BudgetControl-T_RjPrw9.js.map +0 -1
- package/dist/core/web/public/next/assets/ChartFrame-BVuBe6RP.css +0 -1
- package/dist/core/web/public/next/assets/ChartFrame-SygZ7B3x.js +0 -2
- package/dist/core/web/public/next/assets/ChartFrame-SygZ7B3x.js.map +0 -1
- package/dist/core/web/public/next/assets/ChatSection-D6WybBpB.js +0 -3
- package/dist/core/web/public/next/assets/ChatSection-D6WybBpB.js.map +0 -1
- package/dist/core/web/public/next/assets/CommandPalette-B6SgDJH-.js +0 -2
- package/dist/core/web/public/next/assets/CommandPalette-B6SgDJH-.js.map +0 -1
- package/dist/core/web/public/next/assets/CommandSection-CvAVd1w4.css +0 -1
- package/dist/core/web/public/next/assets/CommandSection-DgMGoepd.js +0 -2
- package/dist/core/web/public/next/assets/CommandSection-DgMGoepd.js.map +0 -1
- package/dist/core/web/public/next/assets/DockHost-DW9q0NdR.js +0 -2
- package/dist/core/web/public/next/assets/FleetSection-BdZN6rGS.js +0 -3
- package/dist/core/web/public/next/assets/FleetSection-BdZN6rGS.js.map +0 -1
- package/dist/core/web/public/next/assets/Funnel-BeNQZWv2.js +0 -2
- package/dist/core/web/public/next/assets/Funnel-BeNQZWv2.js.map +0 -1
- package/dist/core/web/public/next/assets/GrowthSection-iiQg6wvK.js +0 -2
- package/dist/core/web/public/next/assets/LeaderCard-BRr3_yE-.js +0 -2
- package/dist/core/web/public/next/assets/LeaderCard-BRr3_yE-.js.map +0 -1
- package/dist/core/web/public/next/assets/MatrixHeatmap-DZvZeWXL.js +0 -2
- package/dist/core/web/public/next/assets/MatrixHeatmap-DZvZeWXL.js.map +0 -1
- package/dist/core/web/public/next/assets/McpSection-BRuCcTjT.js +0 -2
- package/dist/core/web/public/next/assets/MindSection-BTIiQS8P.js +0 -2
- package/dist/core/web/public/next/assets/MindSection-BTIiQS8P.js.map +0 -1
- package/dist/core/web/public/next/assets/NeedsYouDrawer-lYN4G_aM.js +0 -2
- package/dist/core/web/public/next/assets/NeedsYouDrawer-lYN4G_aM.js.map +0 -1
- package/dist/core/web/public/next/assets/NeedsYouList-BgH8ezqf.js +0 -2
- package/dist/core/web/public/next/assets/NeedsYouList-CruOt4JT.css +0 -1
- package/dist/core/web/public/next/assets/NewChatDialog-Db5Xjrk8.js +0 -3
- package/dist/core/web/public/next/assets/OnboardingFlow-LoY-zuSe.js +0 -2
- package/dist/core/web/public/next/assets/OnboardingFlow-LoY-zuSe.js.map +0 -1
- package/dist/core/web/public/next/assets/OnboardingGate-C4VXn7BJ.js +0 -3
- package/dist/core/web/public/next/assets/PreviewPane-BPBbcROb.js +0 -2
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-BX2nkdmZ.js +0 -7
- package/dist/core/web/public/next/assets/ResourcesChrome-B2U2lO1H.js +0 -3
- package/dist/core/web/public/next/assets/ResourcesChrome-B2U2lO1H.js.map +0 -1
- package/dist/core/web/public/next/assets/ResourcesDrawer-BCFJ2Uq8.css +0 -1
- package/dist/core/web/public/next/assets/ResourcesDrawer-DlUKOjl9.js +0 -2
- package/dist/core/web/public/next/assets/ResourcesDrawer-DlUKOjl9.js.map +0 -1
- package/dist/core/web/public/next/assets/SettingsSection-mI9wQYZg.js.map +0 -1
- package/dist/core/web/public/next/assets/Sidebar-DT7ykaqS.js +0 -3
- package/dist/core/web/public/next/assets/Sidebar-DT7ykaqS.js.map +0 -1
- package/dist/core/web/public/next/assets/StatTile-BuLMsJMx.css +0 -1
- package/dist/core/web/public/next/assets/StatTile-DPZ5yyaU.js +0 -2
- package/dist/core/web/public/next/assets/StatTile-DPZ5yyaU.js.map +0 -1
- package/dist/core/web/public/next/assets/StepBand-DQykLkk_.js +0 -2
- package/dist/core/web/public/next/assets/StepBand-DQykLkk_.js.map +0 -1
- package/dist/core/web/public/next/assets/Swimlane-BzH6OCee.js +0 -2
- package/dist/core/web/public/next/assets/Swimlane-BzH6OCee.js.map +0 -1
- package/dist/core/web/public/next/assets/Swimlane-o6_Zsrnr.css +0 -1
- package/dist/core/web/public/next/assets/UniverseConsoleApp-DtXFhAhw.js +0 -2
- package/dist/core/web/public/next/assets/UsageSection-CiD1DbuF.js +0 -3
- package/dist/core/web/public/next/assets/UsageSection-CiD1DbuF.js.map +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-BCY5uSok.js +0 -3
- package/dist/core/web/public/next/assets/VerseConsoleApp-BCY5uSok.js.map +0 -1
- package/dist/core/web/public/next/assets/Workspace-BQSZFSeO.js +0 -10
- package/dist/core/web/public/next/assets/actions-BmIf9avl.css +0 -1
- package/dist/core/web/public/next/assets/actions-Ci6I4C0l.js +0 -2
- package/dist/core/web/public/next/assets/budget-queries-DsAjH8ac.js +0 -2
- package/dist/core/web/public/next/assets/charts-C_xJbJiw.js +0 -2
- package/dist/core/web/public/next/assets/charts-C_xJbJiw.js.map +0 -1
- package/dist/core/web/public/next/assets/charts-DhKTrQLw.css +0 -1
- package/dist/core/web/public/next/assets/cloud-model-CX407gPY.js +0 -2
- package/dist/core/web/public/next/assets/command-catalog-CF0cQQac.js.map +0 -1
- package/dist/core/web/public/next/assets/context-queries-CktFQqSS.js +0 -2
- package/dist/core/web/public/next/assets/copy-setup-CqRSfELB.js +0 -2
- package/dist/core/web/public/next/assets/copy-setup-CqRSfELB.js.map +0 -1
- package/dist/core/web/public/next/assets/format-Dk4Fmxax.js +0 -2
- package/dist/core/web/public/next/assets/format-Dk4Fmxax.js.map +0 -1
- package/dist/core/web/public/next/assets/global-CNNGMril.js +0 -2
- package/dist/core/web/public/next/assets/guarded-runners-DPh2d32e.js +0 -2
- package/dist/core/web/public/next/assets/live-model-De-q0ZFK.js.map +0 -1
- package/dist/core/web/public/next/assets/mutations-DTCtKCn_.js +0 -2
- package/dist/core/web/public/next/assets/primitives-D0Fb3TtS.js +0 -1
- package/dist/core/web/public/next/assets/seat-subscription-C5Tvr0WQ.js +0 -2
- package/dist/core/web/public/next/assets/surface-prefetch-BWr0LBQn.js +0 -2
- package/dist/core/web/public/next/assets/surface-prefetch-BWr0LBQn.js.map +0 -1
- package/dist/core/web/public/next/assets/types-D5XOdyw8.js +0 -2
- package/dist/core/web/public/next/assets/types-D5XOdyw8.js.map +0 -1
- package/dist/core/web/public/next/assets/verse-model-MMySehjj.js +0 -2
- package/dist/core/web/public/next/assets/why-seat-model-jlDTwYSc.js +0 -2
- package/dist/core/web/public/next/assets/why-seat-model-jlDTwYSc.js.map +0 -1
|
@@ -92,18 +92,96 @@
|
|
|
92
92
|
* with no lockfile, or with ambiguous lockfiles — is NOT current: its sync
|
|
93
93
|
* fails with the reason, which pauses the repo (fail closed, never a guess).
|
|
94
94
|
*
|
|
95
|
+
* THE INSTALL IS THE DESIGNED NETWORK EXCEPTION. Agents and verification run
|
|
96
|
+
* confined (sandbox-exec, network denied); this install does not, and must
|
|
97
|
+
* not: it has to reach the registry, and it is not agent code. What makes it
|
|
98
|
+
* safe is what it runs, not where: only the lockfile-pinned tree the repo
|
|
99
|
+
* committed (frozen flags, and the lockfile is re-hashed afterwards), with
|
|
100
|
+
* EVERY lifecycle script off (flags + npm_config_ignore_scripts /
|
|
101
|
+
* YARN_ENABLE_SCRIPTS), from a manager binary resolved outside ~/.ashlr, in
|
|
102
|
+
* an env built from nothing (no git / npm / model tokens; proxy and CA
|
|
103
|
+
* settings pass through). Confining it would not add safety — nothing in it
|
|
104
|
+
* executes repo code — it would only make every install fail.
|
|
105
|
+
*
|
|
106
|
+
* BOUNDED (3.14, after the first live tick on 2026-09-26 spent 15 minutes
|
|
107
|
+
* here: `bun install` hung on `file:../<sibling>` packages that never exist
|
|
108
|
+
* next to a mirror, `pnpm install` stalled silently on a machine at load
|
|
109
|
+
* ~300, execFile's timeout killed only the direct children, and a synchronous
|
|
110
|
+
* rmSync of the half-written node_modules then froze the daemon's event loop
|
|
111
|
+
* for minutes):
|
|
112
|
+
* - out-of-repo local dependencies are refused at plan time (except a
|
|
113
|
+
* `file:../<dir>` sibling that resolves to an enrolled mirror — below);
|
|
114
|
+
* - each install is its own process group with a hard timeout that kills
|
|
115
|
+
* the whole group (SIGTERM, then SIGKILL), stdin at /dev/null, and a
|
|
116
|
+
* settle-anyway backstop (`runDependencyInstall`);
|
|
117
|
+
* - the partial tree is removed asynchronously;
|
|
118
|
+
* - the tick's whole mirror phase has a deadline: a hung repo fails with
|
|
119
|
+
* "prep failed: <reason>" and the others go ahead
|
|
120
|
+
* (`prepareMirrorsForTick`).
|
|
121
|
+
*
|
|
122
|
+
* SIBLING DEPENDENCIES (3.14). ashlrai/ashlrcode installs four `@ashlr/*`
|
|
123
|
+
* packages from `file:../<sibling>` — paths relative to Mason's checkout
|
|
124
|
+
* layout (…/dev-tools/ashlrcode next to …/dev-tools/ashlr-auth). A mirror
|
|
125
|
+
* sits at ~/.ashlr/fleet/mirrors/<owner>__<name>, so `../<dir>` is
|
|
126
|
+
* ~/.ashlr/fleet/mirrors/<dir>, which never existed, and 3.14's first cut
|
|
127
|
+
* refused such repos outright. The design, chosen as the safest that works:
|
|
128
|
+
* - RESOLUTION stays inside the fleet. `../<dir>` of `<owner>/<repo>` means
|
|
129
|
+
* the GitHub repo `<owner>/<dir>` (the checkout convention: directory
|
|
130
|
+
* name = repo name, same owner). It is used only when that repo is an
|
|
131
|
+
* ENROLLED fleet mirror (in the grant), its last sync succeeded, its HEAD
|
|
132
|
+
* and tracked tree match that sync, and its package.json `name` is the
|
|
133
|
+
* package being installed. Nothing reads Mason's checkouts — not even
|
|
134
|
+
* their git remotes — and an enrolled checkout never counts. Anything
|
|
135
|
+
* else keeps refusing, with the reason ("depends on ../X which is not
|
|
136
|
+
* enrolled — enroll it or vendor it"); so does every other out-of-repo
|
|
137
|
+
* shape (absolute, `~/`, `../../x`, `../x/sub`).
|
|
138
|
+
* - PINNED. Under the SIBLING's repo lease its tracked files at HEAD are
|
|
139
|
+
* copied once per commit to ~/.ashlr/fleet/sibling-pins/<slug>/<sha>
|
|
140
|
+
* (staged, then renamed: immutable once visible; no `.git`, no
|
|
141
|
+
* node_modules, no symlink that leaves it). A dependent never installs
|
|
142
|
+
* from a live mirror an agent's worktree or the next reset can change.
|
|
143
|
+
* - LAID OUT, NOT REWRITTEN. `~/.ashlr/fleet/mirrors/<dir>` becomes a
|
|
144
|
+
* relative symlink to the pin, so the committed `file:../<dir>` resolves
|
|
145
|
+
* unchanged for npm, pnpm, yarn and bun: package.json and the lockfile
|
|
146
|
+
* are never edited (the frozen-lockfile check still proves the mirror is
|
|
147
|
+
* origin's tree, and no diff is ever dirtied). The alternative — moving
|
|
148
|
+
* every mirror to <root>/<group>/<name> — would change every mirror path,
|
|
149
|
+
* and mirror paths are enrollment keys, lease keys and fleet-merge state.
|
|
150
|
+
* The link name can never read as a mirror (`__` refused), and a link
|
|
151
|
+
* already naming another ENROLLED repo is never taken over.
|
|
152
|
+
* - KEYED. The pins are part of the install key (only for repos that have
|
|
153
|
+
* siblings — every other install keeps its key), so a sibling that moves
|
|
154
|
+
* means a full clean and reinstall, and `MirrorDepsState.siblings`
|
|
155
|
+
* records exactly what node_modules was installed against. bun, pnpm and
|
|
156
|
+
* yarn copy the pin into node_modules; npm links through `../<dir>`, so
|
|
157
|
+
* the link must still name the recorded pin — `mirrorSiblingPins`
|
|
158
|
+
* checks that and G3 refuses to verify (verify-infra) while it does not.
|
|
159
|
+
* - BOUND INTO THE PROOF. `siblingPinsDigest` is recorded on the
|
|
160
|
+
* verification (inbox/merge.ts verifyProposal → ProposalVerifyResult
|
|
161
|
+
* .siblingPins), is part of G3's hashed inputs (merge-gates.ts), is
|
|
162
|
+
* compared by `hasCurrentVerificationBinding`, and the standing pass
|
|
163
|
+
* re-verifies before publishing when it changed — a sibling change
|
|
164
|
+
* invalidates the proof exactly like a base change.
|
|
165
|
+
* - ORDERED. `prepareMirrorsForTick` syncs siblings before their
|
|
166
|
+
* dependents (a repo waits only for siblings ordered before it, so a
|
|
167
|
+
* cycle cannot deadlock); the sibling lease wait is bounded.
|
|
168
|
+
* - Snapshots nothing records and no link names are pruned after an
|
|
169
|
+
* install. Install timeouts and the process-group kill are unchanged.
|
|
170
|
+
*
|
|
95
171
|
* NOTHING HERE RUNS BY ITSELF. The daemon calls `prepareMirrorsForTick` from
|
|
96
172
|
* its beforeTick hook (U5) only under a live standing policy; `ashlr mirror`
|
|
97
173
|
* (src/cli/mirror.ts) is Mason's manual surface.
|
|
98
174
|
*/
|
|
99
|
-
import { execFile } from 'node:child_process';
|
|
175
|
+
import { execFile, spawn } from 'node:child_process';
|
|
100
176
|
import { createHash, randomBytes } from 'node:crypto';
|
|
101
|
-
import { accessSync, chmodSync, constants as fsConstants, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, } from 'node:fs';
|
|
177
|
+
import { accessSync, chmodSync, constants as fsConstants, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, } from 'node:fs';
|
|
178
|
+
import { chmod, copyFile, mkdir, rm, symlink } from 'node:fs/promises';
|
|
102
179
|
import { homedir } from 'node:os';
|
|
103
|
-
import { delimiter, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
180
|
+
import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
104
181
|
import { withRepoLease, countLiveExecutionLeases } from '../sandbox/execution-leases.js';
|
|
105
182
|
import { resolveGitExecutable, runSafeGit, SafeGitError } from '../sandbox/safe-git.js';
|
|
106
183
|
import { canonicalEnrollmentPath, enroll, killSwitchOn, listEnrolled, narrowEnrollmentScope, runWithEnrollmentLens, unenrollAndDrain, } from '../sandbox/policy.js';
|
|
184
|
+
import { noteTickPhase } from '../daemon/tick-progress.js';
|
|
107
185
|
import { writePrivateFileAtomically } from '../util/private-file-write.js';
|
|
108
186
|
import { scrubSecrets } from '../util/scrub.js';
|
|
109
187
|
// ---------------------------------------------------------------------------
|
|
@@ -117,8 +195,28 @@ export const MIRROR_LOCAL_TIMEOUT_MS = 2 * 60_000;
|
|
|
117
195
|
export const MIRROR_LEASE_WAIT_MS = 5 * 60_000;
|
|
118
196
|
/** Mirrors synced in parallel by `prepareMirrorsForTick` (network-bound, but gentle on the machine). */
|
|
119
197
|
export const MIRROR_TICK_CONCURRENCY = 2;
|
|
120
|
-
/**
|
|
198
|
+
/**
|
|
199
|
+
* A lockfile install (first install of a large monorepo is the slow case).
|
|
200
|
+
* HARD: at the deadline the install's whole process group gets SIGTERM, then
|
|
201
|
+
* SIGKILL after MIRROR_DEPS_KILL_GRACE_MS (see `runDependencyInstall`).
|
|
202
|
+
*/
|
|
121
203
|
export const MIRROR_DEPS_INSTALL_TIMEOUT_MS = 10 * 60_000;
|
|
204
|
+
/** SIGTERM → SIGKILL grace for an install's process group. */
|
|
205
|
+
export const MIRROR_DEPS_KILL_GRACE_MS = 5_000;
|
|
206
|
+
/**
|
|
207
|
+
* The whole mirror phase of ONE tick (every repo's fetch/reset + install).
|
|
208
|
+
* When it passes, in-flight syncs are cancelled (their installs' process
|
|
209
|
+
* groups killed) and repos not yet reached are reported "prep failed" — the
|
|
210
|
+
* tick goes on with the mirrors that ARE current instead of waiting.
|
|
211
|
+
* (2026-09-26: the first live tick spent 15 minutes here.)
|
|
212
|
+
*/
|
|
213
|
+
export const MIRROR_TICK_PREP_DEADLINE_MS = 15 * 60_000;
|
|
214
|
+
/**
|
|
215
|
+
* After the prep deadline aborts in-flight syncs, how long prep waits for them
|
|
216
|
+
* to settle before it stops waiting and reports them failed anyway (their
|
|
217
|
+
* repo leases keep any late finisher from racing the next tick).
|
|
218
|
+
*/
|
|
219
|
+
export const MIRROR_TICK_ABORT_GRACE_MS = 30_000;
|
|
122
220
|
/**
|
|
123
221
|
* After a failed install for a given lockfile, how long later syncs report the
|
|
124
222
|
* recorded failure instead of re-running the same doomed install every tick.
|
|
@@ -489,6 +587,12 @@ function parseDepsState(raw) {
|
|
|
489
587
|
? r['manager']
|
|
490
588
|
: null;
|
|
491
589
|
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
|
|
590
|
+
const siblings = parseSiblingPins(r['siblings']);
|
|
591
|
+
if (siblings === 'invalid') {
|
|
592
|
+
// A record we cannot read the pins of cannot vouch for its install: treat
|
|
593
|
+
// it as never installed, so the next sync reinstalls (fail closed).
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
492
596
|
return {
|
|
493
597
|
status: r['status'],
|
|
494
598
|
manager,
|
|
@@ -497,8 +601,31 @@ function parseDepsState(raw) {
|
|
|
497
601
|
nodeModulesIno: num(r['nodeModulesIno']),
|
|
498
602
|
nodeModulesMtimeMs: num(r['nodeModulesMtimeMs']),
|
|
499
603
|
error: typeof r['error'] === 'string' ? r['error'].slice(0, 600) : null,
|
|
604
|
+
...(siblings.length > 0 ? { siblings } : {}),
|
|
500
605
|
};
|
|
501
606
|
}
|
|
607
|
+
function parseSiblingPins(raw) {
|
|
608
|
+
if (raw === undefined || raw === null)
|
|
609
|
+
return [];
|
|
610
|
+
if (!Array.isArray(raw) || raw.length > MAX_SIBLINGS)
|
|
611
|
+
return 'invalid';
|
|
612
|
+
const pins = [];
|
|
613
|
+
for (const row of raw) {
|
|
614
|
+
if (typeof row !== 'object' || row === null || Array.isArray(row))
|
|
615
|
+
return 'invalid';
|
|
616
|
+
const r = row;
|
|
617
|
+
const identity = parseNameWithOwner(r['nameWithOwner']);
|
|
618
|
+
const dependencies = Array.isArray(r['dependencies']) && r['dependencies'].every((d) => typeof d === 'string' && d.length > 0 && d.length <= 214)
|
|
619
|
+
? [...r['dependencies']]
|
|
620
|
+
: null;
|
|
621
|
+
if (typeof r['dir'] !== 'string' || !isLayableSiblingDir(r['dir']) || !identity || identity.name.toLowerCase() !== r['dir'].toLowerCase()
|
|
622
|
+
|| typeof r['headSha'] !== 'string' || !SHA_RE.test(r['headSha']) || !dependencies) {
|
|
623
|
+
return 'invalid';
|
|
624
|
+
}
|
|
625
|
+
pins.push({ dir: r['dir'], nameWithOwner: identity.nameWithOwner, headSha: r['headSha'], dependencies });
|
|
626
|
+
}
|
|
627
|
+
return sortPins(pins);
|
|
628
|
+
}
|
|
502
629
|
function writeMirrorState(state) {
|
|
503
630
|
const root = mirrorStateRoot();
|
|
504
631
|
ensurePrivateDir(join(canonicalHome(), '.ashlr', 'fleet'));
|
|
@@ -717,6 +844,77 @@ function declaresDependencies(pkg) {
|
|
|
717
844
|
const workspaces = pkg['workspaces'];
|
|
718
845
|
return Array.isArray(workspaces) ? workspaces.length > 0 : !!(workspaces && typeof workspaces === 'object');
|
|
719
846
|
}
|
|
847
|
+
const LOCAL_SPEC_RE = /^(?:file|link|portal):(.*)$/;
|
|
848
|
+
/**
|
|
849
|
+
* Root dependencies installed from a local path OUTSIDE the repository.
|
|
850
|
+
* A fleet mirror holds exactly one repo, so on its own that path never exists
|
|
851
|
+
* next to it — and the install does not always fail fast: on 2026-09-26 `bun
|
|
852
|
+
* install` printed "ENOENT: failed opening cache/package/version dir" for four
|
|
853
|
+
* `file:../<sibling>` packages and then sat at 0% CPU until the 10-minute
|
|
854
|
+
* timeout killed it. So the plan refuses every such path up front, EXCEPT the
|
|
855
|
+
* one shape the fleet can lay out deterministically: a direct sibling
|
|
856
|
+
* `../<dir>` (one segment), which `resolveSiblingPins` maps to an enrolled
|
|
857
|
+
* sibling's fleet mirror. Paths inside the repo (workspace-style
|
|
858
|
+
* `file:./packages/x`) are fine. Peer dependencies are not installed from the
|
|
859
|
+
* spec, so they are not checked.
|
|
860
|
+
*/
|
|
861
|
+
function scanLocalDependencies(root, pkg) {
|
|
862
|
+
const scan = { siblings: [], outside: null };
|
|
863
|
+
const parent = resolve(root, '..');
|
|
864
|
+
for (const field of ['dependencies', 'devDependencies', 'optionalDependencies']) {
|
|
865
|
+
const value = pkg[field];
|
|
866
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
867
|
+
continue;
|
|
868
|
+
for (const [name, raw] of Object.entries(value)) {
|
|
869
|
+
if (typeof raw !== 'string')
|
|
870
|
+
continue;
|
|
871
|
+
const spec = raw.trim();
|
|
872
|
+
const local = LOCAL_SPEC_RE.exec(spec);
|
|
873
|
+
const target = local ? local[1] : /^(?:\.{1,2}\/|\/|~\/)/.test(spec) ? spec : null;
|
|
874
|
+
if (target === null)
|
|
875
|
+
continue;
|
|
876
|
+
if (target.startsWith('~/') || isAbsolute(target)) {
|
|
877
|
+
scan.outside ??= { name, spec };
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
const absolute = resolve(root, target);
|
|
881
|
+
const rel = relative(root, absolute);
|
|
882
|
+
if (!(rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)))
|
|
883
|
+
continue; // inside the repo
|
|
884
|
+
// A direct sibling: written as `../<dir>` (optionally `./../<dir>` or a
|
|
885
|
+
// trailing slash) and resolving to exactly one segment under the parent.
|
|
886
|
+
const sibling = relative(parent, absolute);
|
|
887
|
+
if (/^(?:\.\/)?\.\.\/[^/\\]+\/?$/.test(target) && sibling.length > 0 && !sibling.includes(sep) && sibling !== '..') {
|
|
888
|
+
scan.siblings.push({ dependency: name, spec, dir: sibling });
|
|
889
|
+
}
|
|
890
|
+
else {
|
|
891
|
+
scan.outside ??= { name, spec };
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
scan.siblings.sort((a, b) => a.dir.localeCompare(b.dir) || a.dependency.localeCompare(b.dependency));
|
|
896
|
+
return scan;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* The `file:../<dir>` (and `link:` / `portal:` / bare `../<dir>`) root
|
|
900
|
+
* dependencies of the repo at `root` — what `resolveSiblingPins` must pin
|
|
901
|
+
* before the plan can install. Empty when package.json is absent or unreadable
|
|
902
|
+
* (the plan reports that on its own). Pure over the tree.
|
|
903
|
+
*/
|
|
904
|
+
export function siblingDependenciesOf(root) {
|
|
905
|
+
const raw = readRootFile(root, 'package.json', 4 * 1024 * 1024);
|
|
906
|
+
if (!raw)
|
|
907
|
+
return [];
|
|
908
|
+
try {
|
|
909
|
+
const parsed = JSON.parse(raw.toString('utf8'));
|
|
910
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
911
|
+
return [];
|
|
912
|
+
return scanLocalDependencies(root, parsed).siblings;
|
|
913
|
+
}
|
|
914
|
+
catch {
|
|
915
|
+
return [];
|
|
916
|
+
}
|
|
917
|
+
}
|
|
720
918
|
/**
|
|
721
919
|
* Which install (if any) the mirror at `root` needs. Pure over the tree:
|
|
722
920
|
* - no root package.json → nothing to install;
|
|
@@ -725,11 +923,16 @@ function declaresDependencies(pkg) {
|
|
|
725
923
|
* - dependencies with no lockfile → refuse: the fleet installs only
|
|
726
924
|
* lockfile-pinned versions (an unpinned install is a different tree than
|
|
727
925
|
* the one CI and Mason test).
|
|
926
|
+
* - a `file:../<dir>` sibling dependency installs only against `pins`
|
|
927
|
+
* (`resolveSiblingPins`: an enrolled sibling's mirror at a pinned commit);
|
|
928
|
+
* the pins are part of the key, so a sibling that moves means a reinstall.
|
|
929
|
+
* Without a pin for every sibling it refuses; any other out-of-repo local
|
|
930
|
+
* path always refuses.
|
|
728
931
|
* Every install is frozen (the lockfile is never rewritten) and runs with
|
|
729
932
|
* lifecycle scripts OFF: the daemon runs unconfined, and a dependency's
|
|
730
933
|
* postinstall is exactly the supply-chain code it must not execute.
|
|
731
934
|
*/
|
|
732
|
-
export function planMirrorDependencies(root) {
|
|
935
|
+
export function planMirrorDependencies(root, pins = []) {
|
|
733
936
|
const pkgRaw = readRootFile(root, 'package.json', 4 * 1024 * 1024);
|
|
734
937
|
if (!pkgRaw)
|
|
735
938
|
return { kind: 'none', reason: 'no package.json at the repo root' };
|
|
@@ -774,6 +977,25 @@ export function planMirrorDependencies(root) {
|
|
|
774
977
|
return { kind: 'none', reason: 'package.json declares no dependencies' };
|
|
775
978
|
return { kind: 'refuse', reason: 'package.json declares dependencies but no lockfile is committed; the fleet installs only lockfile-pinned dependencies' };
|
|
776
979
|
}
|
|
980
|
+
const local = scanLocalDependencies(root, pkg);
|
|
981
|
+
if (local.outside) {
|
|
982
|
+
return {
|
|
983
|
+
kind: 'refuse',
|
|
984
|
+
reason: `package.json installs ${local.outside.name} from ${JSON.stringify(local.outside.spec.slice(0, 120))}, a path outside the repository; a fleet mirror holds only this repo, so that install cannot succeed`,
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
const usedPins = [];
|
|
988
|
+
for (const sibling of local.siblings) {
|
|
989
|
+
const pin = pins.find((row) => row.dir === sibling.dir);
|
|
990
|
+
if (!pin) {
|
|
991
|
+
return {
|
|
992
|
+
kind: 'refuse',
|
|
993
|
+
reason: `package.json installs ${sibling.dependency} from ${JSON.stringify(sibling.spec.slice(0, 120))}, a sibling repository; it installs only against an enrolled sibling's fleet mirror, and ../${sibling.dir} was not resolved to one`,
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
if (!usedPins.includes(pin))
|
|
997
|
+
usedPins.push(pin);
|
|
998
|
+
}
|
|
777
999
|
const lockBytes = readRootFile(root, chosen.lockfile);
|
|
778
1000
|
if (!lockBytes)
|
|
779
1001
|
return { kind: 'refuse', reason: `${chosen.lockfile} is not a regular file the fleet can read` };
|
|
@@ -793,13 +1015,474 @@ export function planMirrorDependencies(root) {
|
|
|
793
1015
|
return ['bun', 'install', '--frozen-lockfile', '--ignore-scripts'];
|
|
794
1016
|
}
|
|
795
1017
|
})();
|
|
796
|
-
const
|
|
1018
|
+
const hash = createHash('sha256')
|
|
797
1019
|
.update(`ashlr:mirror-deps:v1\0${chosen.manager}\0${argv.join(' ')}\0${chosen.lockfile}\0`)
|
|
798
1020
|
.update(lockBytes)
|
|
799
1021
|
.update('\0')
|
|
800
|
-
.update(pkgRaw)
|
|
801
|
-
|
|
802
|
-
|
|
1022
|
+
.update(pkgRaw);
|
|
1023
|
+
// Only repos WITH siblings hash pins, so every existing install keeps its
|
|
1024
|
+
// key (no fleet-wide reinstall on upgrade).
|
|
1025
|
+
if (usedPins.length > 0)
|
|
1026
|
+
hash.update(`\0ashlr:sibling-pins\0${canonicalPins(usedPins)}`);
|
|
1027
|
+
const key = hash.digest('hex');
|
|
1028
|
+
return {
|
|
1029
|
+
kind: 'install',
|
|
1030
|
+
manager: chosen.manager,
|
|
1031
|
+
lockfile: chosen.lockfile,
|
|
1032
|
+
argv,
|
|
1033
|
+
key,
|
|
1034
|
+
...(usedPins.length > 0 ? { siblings: sortPins(usedPins) } : {}),
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
// ---------------------------------------------------------------------------
|
|
1038
|
+
// Sibling dependencies (3.14) — see "SIBLING DEPENDENCIES" in the header
|
|
1039
|
+
// ---------------------------------------------------------------------------
|
|
1040
|
+
/** How long a dependent's sync waits for a sibling mirror's lease (the sibling may be mid-sync). */
|
|
1041
|
+
export const MIRROR_SIBLING_LEASE_WAIT_MS = 2 * 60_000;
|
|
1042
|
+
const MAX_SIBLINGS = 16;
|
|
1043
|
+
/** Bounds on one sibling snapshot (a pathological repo must not fill the disk or stall the tick). */
|
|
1044
|
+
const SIBLING_PIN_MAX_FILES = 50_000;
|
|
1045
|
+
const SIBLING_PIN_MAX_BYTES = 512 * 1024 * 1024;
|
|
1046
|
+
/** A crashed snapshot's staging dir older than this is removed by the next prune. */
|
|
1047
|
+
const SIBLING_STAGING_STALE_MS = 60 * 60_000;
|
|
1048
|
+
const SIBLING_LINK_TARGET_RE = /^\.\.\/sibling-pins\/([^/]+)\/([0-9a-f]{40}(?:[0-9a-f]{24})?)$/;
|
|
1049
|
+
/** ~/.ashlr/fleet/sibling-pins — immutable per-commit snapshots of sibling mirrors. */
|
|
1050
|
+
export function siblingPinsRoot() {
|
|
1051
|
+
return join(canonicalHome(), '.ashlr', 'fleet', 'sibling-pins');
|
|
1052
|
+
}
|
|
1053
|
+
/** `~/.ashlr/fleet/sibling-pins/<owner>__<name>/<sha>` */
|
|
1054
|
+
export function siblingPinPath(pin) {
|
|
1055
|
+
return join(siblingPinsRoot(), mirrorSlug(pin.nameWithOwner), pin.headSha);
|
|
1056
|
+
}
|
|
1057
|
+
/** Where `../<dir>` lands for a mirror: `~/.ashlr/fleet/mirrors/<dir>`. */
|
|
1058
|
+
export function siblingLinkPath(dir) {
|
|
1059
|
+
return join(fleetMirrorsRoot(), dir);
|
|
1060
|
+
}
|
|
1061
|
+
/** The link's text — relative, so it can only ever name something under ~/.ashlr/fleet. */
|
|
1062
|
+
function siblingLinkTarget(pin) {
|
|
1063
|
+
return `../sibling-pins/${mirrorSlug(pin.nameWithOwner)}/${pin.headSha}`;
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* A `<dir>` the fleet may create next to its mirrors: a plain GitHub repo
|
|
1067
|
+
* name that can never read as a mirror slug (`owner__name`), a dot entry
|
|
1068
|
+
* (`.trash`, staging) or an option.
|
|
1069
|
+
*/
|
|
1070
|
+
function isLayableSiblingDir(dir) {
|
|
1071
|
+
return NAME_RE.test(dir) && !dir.startsWith('.') && !dir.startsWith('-') && !dir.includes(SLUG_SEPARATOR);
|
|
1072
|
+
}
|
|
1073
|
+
function sortPins(pins) {
|
|
1074
|
+
return pins
|
|
1075
|
+
.map((pin) => ({ ...pin, dependencies: [...new Set(pin.dependencies)].sort() }))
|
|
1076
|
+
.sort((a, b) => a.dir.localeCompare(b.dir));
|
|
1077
|
+
}
|
|
1078
|
+
function canonicalPins(pins) {
|
|
1079
|
+
return JSON.stringify(sortPins(pins).map((pin) => [pin.dir, pin.nameWithOwner.toLowerCase(), pin.headSha, pin.dependencies]));
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* The digest G3 binds verification to (inbox/merge.ts, merge-gates.ts
|
|
1083
|
+
* evaluateG3): null when the repo has no sibling dependencies, so every
|
|
1084
|
+
* existing verification binding and gate digest is unchanged.
|
|
1085
|
+
*/
|
|
1086
|
+
export function siblingPinsDigest(pins) {
|
|
1087
|
+
if (pins.length === 0)
|
|
1088
|
+
return null;
|
|
1089
|
+
return createHash('sha256').update(`ashlr:sibling-pins:v1\0${canonicalPins(pins)}`, 'utf8').digest('hex');
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* The sibling pins a mirror's installed dependencies rest on RIGHT NOW, for
|
|
1093
|
+
* G3 (verify binding + digest) and the confined suites' read grants. Not a
|
|
1094
|
+
* mirror, or no siblings ⇒ ok with nothing. Not ok when a `../<dir>` link no
|
|
1095
|
+
* longer resolves to the recorded pin (another dependent's sync moved it to
|
|
1096
|
+
* a newer sibling commit): npm links `file:` siblings through that path, so
|
|
1097
|
+
* the install is no longer the pinned tree until this mirror's next sync
|
|
1098
|
+
* reinstalls. Reads only ~/.ashlr/fleet; never throws.
|
|
1099
|
+
*/
|
|
1100
|
+
export function mirrorSiblingPins(repoPath) {
|
|
1101
|
+
const none = { ok: true, pins: [], digest: null, readPaths: [] };
|
|
1102
|
+
let name;
|
|
1103
|
+
try {
|
|
1104
|
+
name = mirrorNameForPath(repoPath);
|
|
1105
|
+
}
|
|
1106
|
+
catch {
|
|
1107
|
+
name = null;
|
|
1108
|
+
}
|
|
1109
|
+
if (!name)
|
|
1110
|
+
return none;
|
|
1111
|
+
const deps = readMirrorState(name)?.deps;
|
|
1112
|
+
const pins = deps?.status === 'installed' ? deps.siblings ?? [] : [];
|
|
1113
|
+
if (pins.length === 0)
|
|
1114
|
+
return none;
|
|
1115
|
+
const readPaths = [];
|
|
1116
|
+
try {
|
|
1117
|
+
for (const pin of pins) {
|
|
1118
|
+
const link = siblingLinkPath(pin.dir);
|
|
1119
|
+
let current = null;
|
|
1120
|
+
try {
|
|
1121
|
+
current = readlinkSync(link);
|
|
1122
|
+
}
|
|
1123
|
+
catch {
|
|
1124
|
+
current = null;
|
|
1125
|
+
}
|
|
1126
|
+
if (current !== siblingLinkTarget(pin)) {
|
|
1127
|
+
return {
|
|
1128
|
+
ok: false,
|
|
1129
|
+
reason: `../${pin.dir} no longer resolves to ${pin.nameWithOwner}@${pin.headSha.slice(0, 12)}, the pin ${name}'s dependencies were installed against; its next mirror sync reinstalls`,
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
const dir = siblingPinPath(pin);
|
|
1133
|
+
const stat = lstatSync(dir);
|
|
1134
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
1135
|
+
throw new Error(`${dir} is not a directory`);
|
|
1136
|
+
readPaths.push(dir, link);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
catch (error) {
|
|
1140
|
+
return { ok: false, reason: `the sibling pins of ${name} cannot be read: ${error.message}` };
|
|
1141
|
+
}
|
|
1142
|
+
return { ok: true, pins, digest: siblingPinsDigest(pins), readPaths };
|
|
1143
|
+
}
|
|
1144
|
+
function readPackageName(root) {
|
|
1145
|
+
const raw = readRootFile(root, 'package.json', 4 * 1024 * 1024);
|
|
1146
|
+
if (!raw)
|
|
1147
|
+
return null;
|
|
1148
|
+
try {
|
|
1149
|
+
const parsed = JSON.parse(raw.toString('utf8'));
|
|
1150
|
+
return typeof parsed?.name === 'string' ? parsed.name : null;
|
|
1151
|
+
}
|
|
1152
|
+
catch {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
function isInsideDir(child, parent) {
|
|
1157
|
+
const rel = relative(parent, child);
|
|
1158
|
+
return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Snapshot the sibling mirror's tracked files at its current HEAD into
|
|
1162
|
+
* `siblingPinPath(pin)` — once per commit; an existing snapshot is reused.
|
|
1163
|
+
* Runs under the SIBLING's lease with its HEAD and clean tree just verified,
|
|
1164
|
+
* so the working tree is exactly `pin.headSha`. Copies only what git tracks
|
|
1165
|
+
* (never `.git`, never its node_modules); a tracked symlink is kept only when
|
|
1166
|
+
* it stays inside the snapshot; submodules are left out. Written to a staging
|
|
1167
|
+
* dir and renamed into place, so a half-copied pin is never visible. Returns
|
|
1168
|
+
* null, or why it could not.
|
|
1169
|
+
*/
|
|
1170
|
+
async function ensureSiblingSnapshot(siblingMirror, pin, git) {
|
|
1171
|
+
const dest = siblingPinPath(pin);
|
|
1172
|
+
try {
|
|
1173
|
+
const stat = lstatSync(dest);
|
|
1174
|
+
return !stat.isSymbolicLink() && stat.isDirectory() ? null : `${dest} exists and is not a pin directory`;
|
|
1175
|
+
}
|
|
1176
|
+
catch { /* absent — take it */ }
|
|
1177
|
+
const listed = await git(['ls-files', '-z', '-s']);
|
|
1178
|
+
if (!listed.ok)
|
|
1179
|
+
return `git ls-files failed: ${listed.stderr || 'unknown error'}`;
|
|
1180
|
+
let staging = null;
|
|
1181
|
+
try {
|
|
1182
|
+
ensurePrivateDir(join(canonicalHome(), '.ashlr', 'fleet'));
|
|
1183
|
+
ensurePrivateDir(siblingPinsRoot());
|
|
1184
|
+
ensurePrivateDir(join(siblingPinsRoot(), mirrorSlug(pin.nameWithOwner)));
|
|
1185
|
+
staging = join(siblingPinsRoot(), `.staging-${mirrorSlug(pin.nameWithOwner)}-${randomBytes(6).toString('hex')}`);
|
|
1186
|
+
await mkdir(staging, { mode: 0o700 });
|
|
1187
|
+
const source = realpathSync(siblingMirror);
|
|
1188
|
+
let files = 0;
|
|
1189
|
+
let bytes = 0;
|
|
1190
|
+
for (const entry of listed.stdout.split('\0')) {
|
|
1191
|
+
if (entry.length === 0)
|
|
1192
|
+
continue;
|
|
1193
|
+
const tab = entry.indexOf('\t');
|
|
1194
|
+
const [mode, , stage] = entry.slice(0, Math.max(0, tab)).split(' ');
|
|
1195
|
+
const rel = entry.slice(tab + 1);
|
|
1196
|
+
if (tab < 0 || stage !== '0')
|
|
1197
|
+
throw new Error(`unexpected index entry ${JSON.stringify(entry.slice(0, 120))}`);
|
|
1198
|
+
if (isAbsolute(rel) || rel.split('/').some((seg) => seg === '' || seg === '.' || seg === '..' || seg === '.git')) {
|
|
1199
|
+
throw new Error(`refusing tracked path ${JSON.stringify(rel.slice(0, 120))}`);
|
|
1200
|
+
}
|
|
1201
|
+
if (mode === '160000')
|
|
1202
|
+
continue; // a submodule is not part of the package
|
|
1203
|
+
if (++files > SIBLING_PIN_MAX_FILES)
|
|
1204
|
+
throw new Error(`more than ${SIBLING_PIN_MAX_FILES} tracked files`);
|
|
1205
|
+
const from = join(source, rel);
|
|
1206
|
+
const to = join(staging, rel);
|
|
1207
|
+
await mkdir(dirname(to), { recursive: true, mode: 0o700 });
|
|
1208
|
+
const stat = lstatSync(from);
|
|
1209
|
+
if (mode === '120000') {
|
|
1210
|
+
if (!stat.isSymbolicLink())
|
|
1211
|
+
throw new Error(`${rel} is tracked as a symlink but is not one`);
|
|
1212
|
+
const target = readlinkSync(from);
|
|
1213
|
+
if (isAbsolute(target) || !isInsideDir(resolve(dirname(to), target), staging)) {
|
|
1214
|
+
throw new Error(`${rel} is a symlink that leaves the repository (${JSON.stringify(target.slice(0, 120))})`);
|
|
1215
|
+
}
|
|
1216
|
+
await symlink(target, to);
|
|
1217
|
+
}
|
|
1218
|
+
else if (mode === '100644' || mode === '100755') {
|
|
1219
|
+
if (!stat.isFile())
|
|
1220
|
+
throw new Error(`${rel} is tracked as a file but is not one`);
|
|
1221
|
+
bytes += stat.size;
|
|
1222
|
+
if (bytes > SIBLING_PIN_MAX_BYTES)
|
|
1223
|
+
throw new Error(`more than ${SIBLING_PIN_MAX_BYTES} bytes of tracked files`);
|
|
1224
|
+
await copyFile(from, to, fsConstants.COPYFILE_EXCL);
|
|
1225
|
+
await chmod(to, mode === '100755' ? 0o755 : 0o644);
|
|
1226
|
+
}
|
|
1227
|
+
else {
|
|
1228
|
+
throw new Error(`${rel} has unsupported mode ${String(mode)}`);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
renameSync(staging, dest);
|
|
1232
|
+
staging = null;
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
catch (error) {
|
|
1236
|
+
// Lost a race to an identical snapshot: theirs is as good as ours.
|
|
1237
|
+
try {
|
|
1238
|
+
if (lstatSync(dest).isDirectory())
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
1241
|
+
catch { /* still absent */ }
|
|
1242
|
+
return error.message;
|
|
1243
|
+
}
|
|
1244
|
+
finally {
|
|
1245
|
+
if (staging)
|
|
1246
|
+
await rm(staging, { recursive: true, force: true }).catch(() => undefined);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Point `<mirrors root>/<dir>` at the pin (atomic rename of a fresh link).
|
|
1251
|
+
* An entry the fleet did not make there is never replaced; a link that
|
|
1252
|
+
* already resolves `../<dir>` to a DIFFERENT repository is taken over only
|
|
1253
|
+
* when that repository is no longer an enrolled mirror (two siblings cannot
|
|
1254
|
+
* share one name next to the mirrors).
|
|
1255
|
+
*/
|
|
1256
|
+
function layOutSiblingLink(pin, enrolledMirrors) {
|
|
1257
|
+
const link = siblingLinkPath(pin.dir);
|
|
1258
|
+
const target = siblingLinkTarget(pin);
|
|
1259
|
+
let current = null;
|
|
1260
|
+
try {
|
|
1261
|
+
const stat = lstatSync(link);
|
|
1262
|
+
if (!stat.isSymbolicLink())
|
|
1263
|
+
return `${link} exists and is not a link the fleet made; move it aside`;
|
|
1264
|
+
current = readlinkSync(link);
|
|
1265
|
+
}
|
|
1266
|
+
catch (error) {
|
|
1267
|
+
if (error.code !== 'ENOENT')
|
|
1268
|
+
return `${link} cannot be read: ${error.message}`;
|
|
1269
|
+
}
|
|
1270
|
+
if (current === target)
|
|
1271
|
+
return null;
|
|
1272
|
+
if (current !== null) {
|
|
1273
|
+
const match = SIBLING_LINK_TARGET_RE.exec(current);
|
|
1274
|
+
const holder = match ? slugToIdentity(match[1]) : null;
|
|
1275
|
+
if (!holder)
|
|
1276
|
+
return `${link} points somewhere the fleet did not put it (${JSON.stringify(current.slice(0, 120))}); move it aside`;
|
|
1277
|
+
if (holder.nameWithOwner.toLowerCase() !== pin.nameWithOwner.toLowerCase() && enrolledMirrors.has(holder.nameWithOwner.toLowerCase())) {
|
|
1278
|
+
return `../${pin.dir} already resolves to ${holder.nameWithOwner}, another enrolled mirror; two sibling repositories cannot share the name ${pin.dir}`;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
const tmp = join(fleetMirrorsRoot(), `.${pin.dir}.link-${randomBytes(6).toString('hex')}`);
|
|
1282
|
+
try {
|
|
1283
|
+
symlinkSync(target, tmp, 'dir');
|
|
1284
|
+
renameSync(tmp, link);
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
catch (error) {
|
|
1288
|
+
rmSync(tmp, { force: true });
|
|
1289
|
+
return `${link} could not be linked: ${error.message}`;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Resolve every `file:../<dir>` dependency of the mirror in `ctx` to a pinned
|
|
1294
|
+
* sibling (see "SIBLING DEPENDENCIES"): the fleet mirror of `<owner>/<dir>`
|
|
1295
|
+
* (same owner — the checkout layout convention of `../<dir>`), which must be
|
|
1296
|
+
* an ENROLLED mirror, current, and whose package.json names the package.
|
|
1297
|
+
* Each is snapshotted at its HEAD under its own lease and `../<dir>` is
|
|
1298
|
+
* linked to the snapshot. Never reads outside ~/.ashlr/fleet.
|
|
1299
|
+
*/
|
|
1300
|
+
async function resolveSiblingPins(ctx, needed) {
|
|
1301
|
+
const byDir = new Map();
|
|
1302
|
+
for (const sibling of needed)
|
|
1303
|
+
byDir.set(sibling.dir, [...(byDir.get(sibling.dir) ?? []), sibling]);
|
|
1304
|
+
if (byDir.size > MAX_SIBLINGS) {
|
|
1305
|
+
return { ok: false, error: `package.json installs from ${byDir.size} sibling repositories; the fleet pins at most ${MAX_SIBLINGS}` };
|
|
1306
|
+
}
|
|
1307
|
+
// Enrolled FLEET MIRRORS only, by canonical identity: a checkout of Mason's
|
|
1308
|
+
// (enrolled or not) never counts, and is never read.
|
|
1309
|
+
const enrolledMirrors = new Map();
|
|
1310
|
+
try {
|
|
1311
|
+
for (const path of listEnrolled()) {
|
|
1312
|
+
const name = mirrorNameForPath(path);
|
|
1313
|
+
if (name)
|
|
1314
|
+
enrolledMirrors.set(name.toLowerCase(), name);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
catch (error) {
|
|
1318
|
+
return { ok: false, error: `the enrollment registry could not be read: ${error.message}` };
|
|
1319
|
+
}
|
|
1320
|
+
const pins = [];
|
|
1321
|
+
for (const [dir, rows] of [...byDir].sort(([a], [b]) => a.localeCompare(b))) {
|
|
1322
|
+
const first = rows[0];
|
|
1323
|
+
const what = `package.json installs ${first.dependency} from ${JSON.stringify(first.spec.slice(0, 120))}`;
|
|
1324
|
+
if (!isLayableSiblingDir(dir)) {
|
|
1325
|
+
return { ok: false, error: `${what}: ../${dir} is not a plain repository name the fleet can lay out next to its mirrors — vendor it` };
|
|
1326
|
+
}
|
|
1327
|
+
const wanted = parseNameWithOwner(`${ctx.identity.owner}/${dir}`);
|
|
1328
|
+
if (!wanted || wanted.nameWithOwner.toLowerCase() === ctx.identity.nameWithOwner.toLowerCase()) {
|
|
1329
|
+
return { ok: false, error: `${what}: ../${dir} does not name a sibling repository` };
|
|
1330
|
+
}
|
|
1331
|
+
const enrolledName = enrolledMirrors.get(wanted.nameWithOwner.toLowerCase());
|
|
1332
|
+
if (!enrolledName) {
|
|
1333
|
+
return {
|
|
1334
|
+
ok: false,
|
|
1335
|
+
error: `${what}: depends on ../${dir} which is not enrolled — enroll it or vendor it (the fleet resolves ../${dir} to ${wanted.nameWithOwner}'s fleet mirror, so ${wanted.nameWithOwner} must be in the grant)`,
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
const sibling = requireIdentity(enrolledName);
|
|
1339
|
+
const siblingPath = mirrorPathFor(sibling.nameWithOwner);
|
|
1340
|
+
const git = (args) => runMirrorRepoGit(siblingPath, args, {
|
|
1341
|
+
timeoutMs: MIRROR_LOCAL_TIMEOUT_MS,
|
|
1342
|
+
allowLocalOrigin: ctx.deps.allowLocalOrigin === true,
|
|
1343
|
+
...(ctx.deps.signal ? { signal: ctx.deps.signal } : {}),
|
|
1344
|
+
});
|
|
1345
|
+
const leased = await withRepoLease(mirrorLeaseKey(siblingPath), async () => {
|
|
1346
|
+
if (inspectMirrorLayout(siblingPath).state !== 'mirror') {
|
|
1347
|
+
return { ok: false, error: `${what}: ../${dir} resolves to ${sibling.nameWithOwner}, which is enrolled but has no fleet mirror yet (it is created on its first sync)` };
|
|
1348
|
+
}
|
|
1349
|
+
const state = readMirrorState(sibling.nameWithOwner);
|
|
1350
|
+
if (!state || state.lastSyncOk !== true || !state.headSha) {
|
|
1351
|
+
return { ok: false, error: `${what}: ${sibling.nameWithOwner}'s fleet mirror is not current${state?.lastError ? ` (${state.lastError.slice(0, 200)})` : ''}` };
|
|
1352
|
+
}
|
|
1353
|
+
const head = await git(['rev-parse', '--verify', 'HEAD^{commit}']);
|
|
1354
|
+
if (!head.ok || head.stdout.trim() !== state.headSha) {
|
|
1355
|
+
return { ok: false, error: `${what}: ${sibling.nameWithOwner}'s fleet mirror is not at its last synced commit ${state.headSha.slice(0, 12)}` };
|
|
1356
|
+
}
|
|
1357
|
+
const status = await git(['status', '--porcelain', '--untracked-files=no']);
|
|
1358
|
+
if (!status.ok || status.stdout.trim().length > 0) {
|
|
1359
|
+
return { ok: false, error: `${what}: ${sibling.nameWithOwner}'s fleet mirror does not match its commit ${state.headSha.slice(0, 12)}` };
|
|
1360
|
+
}
|
|
1361
|
+
// Proof it is the intended package, not merely a same-named repo.
|
|
1362
|
+
const packageName = readPackageName(siblingPath);
|
|
1363
|
+
const wrong = rows.find((row) => row.dependency !== packageName);
|
|
1364
|
+
if (wrong) {
|
|
1365
|
+
return { ok: false, error: `${what}: ${sibling.nameWithOwner}'s package.json is named ${JSON.stringify(packageName)}, not ${JSON.stringify(wrong.dependency)}` };
|
|
1366
|
+
}
|
|
1367
|
+
const pin = { dir, nameWithOwner: sibling.nameWithOwner, headSha: state.headSha, dependencies: rows.map((row) => row.dependency) };
|
|
1368
|
+
noteTickPhase('mirror prep', `${ctx.identity.nameWithOwner}: pinning ../${dir} to ${sibling.nameWithOwner}@${state.headSha.slice(0, 12)}`);
|
|
1369
|
+
const snapshot = await ensureSiblingSnapshot(siblingPath, pin, git);
|
|
1370
|
+
if (snapshot)
|
|
1371
|
+
return { ok: false, error: `${what}: could not pin ${sibling.nameWithOwner}@${state.headSha.slice(0, 12)}: ${snapshot}` };
|
|
1372
|
+
return { ok: true, pin };
|
|
1373
|
+
}, {
|
|
1374
|
+
waitMs: ctx.deps.siblingLeaseWaitMs ?? MIRROR_SIBLING_LEASE_WAIT_MS,
|
|
1375
|
+
...(ctx.deps.signal ? { signal: ctx.deps.signal } : {}),
|
|
1376
|
+
});
|
|
1377
|
+
if (!leased.ok)
|
|
1378
|
+
return { ok: false, error: `${what}: ${sibling.nameWithOwner}'s fleet mirror stayed busy (${leased.reason})` };
|
|
1379
|
+
if (!leased.value.ok)
|
|
1380
|
+
return leased.value;
|
|
1381
|
+
pins.push(leased.value.pin);
|
|
1382
|
+
}
|
|
1383
|
+
const enrolledSet = new Set(enrolledMirrors.keys());
|
|
1384
|
+
for (const pin of pins) {
|
|
1385
|
+
const laid = layOutSiblingLink(pin, enrolledSet);
|
|
1386
|
+
if (laid)
|
|
1387
|
+
return { ok: false, error: `could not lay out ../${pin.dir}: ${laid}` };
|
|
1388
|
+
}
|
|
1389
|
+
return { ok: true, pins: sortPins(pins) };
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Remove snapshots of the siblings in `installed` that nothing uses any more:
|
|
1393
|
+
* not a current `../<dir>` link target and not recorded by any mirror's
|
|
1394
|
+
* installed deps (a dependent not yet reinstalled still needs its old pin).
|
|
1395
|
+
* `self` just installed against `installed` — its state record, not yet
|
|
1396
|
+
* rewritten, is read as those pins. Also clears crashed staging dirs. Best
|
|
1397
|
+
* effort, async; never throws.
|
|
1398
|
+
*/
|
|
1399
|
+
async function pruneSiblingPins(self, installed) {
|
|
1400
|
+
try {
|
|
1401
|
+
const keep = new Set(installed.map((pin) => `${mirrorSlug(pin.nameWithOwner)}/${pin.headSha}`));
|
|
1402
|
+
for (const mirror of listMirrors()) {
|
|
1403
|
+
if (mirror.nameWithOwner.toLowerCase() === self.toLowerCase())
|
|
1404
|
+
continue;
|
|
1405
|
+
for (const pin of mirror.state?.deps?.siblings ?? [])
|
|
1406
|
+
keep.add(`${mirrorSlug(pin.nameWithOwner)}/${pin.headSha}`);
|
|
1407
|
+
}
|
|
1408
|
+
for (const entry of readdirSync(fleetMirrorsRoot())) {
|
|
1409
|
+
if (entry.startsWith('.'))
|
|
1410
|
+
continue;
|
|
1411
|
+
try {
|
|
1412
|
+
const match = SIBLING_LINK_TARGET_RE.exec(readlinkSync(join(fleetMirrorsRoot(), entry)));
|
|
1413
|
+
if (match)
|
|
1414
|
+
keep.add(`${match[1]}/${match[2]}`);
|
|
1415
|
+
}
|
|
1416
|
+
catch { /* not a link */ }
|
|
1417
|
+
}
|
|
1418
|
+
const root = siblingPinsRoot();
|
|
1419
|
+
for (const entry of readdirSync(root)) {
|
|
1420
|
+
if (entry.startsWith('.staging-')) {
|
|
1421
|
+
const stat = lstatSync(join(root, entry));
|
|
1422
|
+
if (Date.now() - stat.mtimeMs > SIBLING_STAGING_STALE_MS)
|
|
1423
|
+
await rm(join(root, entry), { recursive: true, force: true });
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
for (const nameWithOwner of new Set(installed.map((pin) => pin.nameWithOwner))) {
|
|
1427
|
+
const slug = mirrorSlug(nameWithOwner);
|
|
1428
|
+
let shas = [];
|
|
1429
|
+
try {
|
|
1430
|
+
shas = readdirSync(join(root, slug));
|
|
1431
|
+
}
|
|
1432
|
+
catch {
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
for (const sha of shas) {
|
|
1436
|
+
if (!SHA_RE.test(sha) || keep.has(`${slug}/${sha}`))
|
|
1437
|
+
continue;
|
|
1438
|
+
await rm(join(root, slug, sha), { recursive: true, force: true, maxRetries: 2 });
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
catch { /* best effort: an unpruned pin is only disk */ }
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Tick order with siblings first: returns `repos` topologically ordered so a
|
|
1446
|
+
* sibling syncs before the repos that install from it, and for each repo the
|
|
1447
|
+
* siblings (in this tick) it must wait for. Reads only the mirrors' current
|
|
1448
|
+
* package.json; a cycle keeps the original order and never waits backwards
|
|
1449
|
+
* (a repo only ever waits for repos ordered BEFORE it — no deadlock).
|
|
1450
|
+
*/
|
|
1451
|
+
function orderForSiblings(repos) {
|
|
1452
|
+
const byLower = new Map(repos.map((repo) => [repo.toLowerCase(), repo]));
|
|
1453
|
+
const edges = new Map();
|
|
1454
|
+
for (const repo of repos) {
|
|
1455
|
+
const identity = parseNameWithOwner(repo);
|
|
1456
|
+
let needs = [];
|
|
1457
|
+
try {
|
|
1458
|
+
if (identity) {
|
|
1459
|
+
needs = siblingDependenciesOf(mirrorPathFor(identity.nameWithOwner))
|
|
1460
|
+
.map((row) => byLower.get(`${identity.owner}/${row.dir}`.toLowerCase()))
|
|
1461
|
+
.filter((name) => name !== undefined && name !== repo);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
catch { /* unknown ⇒ no ordering constraint */ }
|
|
1465
|
+
edges.set(repo, [...new Set(needs)]);
|
|
1466
|
+
}
|
|
1467
|
+
const order = [];
|
|
1468
|
+
const state = new Map();
|
|
1469
|
+
const visit = (repo) => {
|
|
1470
|
+
if (state.has(repo))
|
|
1471
|
+
return;
|
|
1472
|
+
state.set(repo, 'visiting');
|
|
1473
|
+
for (const next of edges.get(repo) ?? [])
|
|
1474
|
+
visit(next);
|
|
1475
|
+
state.set(repo, 'done');
|
|
1476
|
+
order.push(repo);
|
|
1477
|
+
};
|
|
1478
|
+
for (const repo of repos)
|
|
1479
|
+
visit(repo);
|
|
1480
|
+
const position = new Map(order.map((repo, index) => [repo, index]));
|
|
1481
|
+
const waitsFor = new Map();
|
|
1482
|
+
for (const repo of order) {
|
|
1483
|
+
waitsFor.set(repo, (edges.get(repo) ?? []).filter((dep) => (position.get(dep) ?? Infinity) < position.get(repo)));
|
|
1484
|
+
}
|
|
1485
|
+
return { order, waitsFor };
|
|
803
1486
|
}
|
|
804
1487
|
/**
|
|
805
1488
|
* The package manager binary on the daemon's PATH. Only absolute PATH entries
|
|
@@ -856,38 +1539,212 @@ function installEnv() {
|
|
|
856
1539
|
});
|
|
857
1540
|
return env;
|
|
858
1541
|
}
|
|
859
|
-
|
|
860
|
-
|
|
1542
|
+
const INSTALL_OUTPUT_TAIL_BYTES = 16 * 1024;
|
|
1543
|
+
/** Process groups of installs still running in this process — killed if the daemon exits under them. */
|
|
1544
|
+
const liveInstallGroups = new Set();
|
|
1545
|
+
let exitReaperInstalled = false;
|
|
1546
|
+
function installExitReaper() {
|
|
1547
|
+
if (exitReaperInstalled)
|
|
1548
|
+
return;
|
|
1549
|
+
exitReaperInstalled = true;
|
|
1550
|
+
// Installs run in their OWN process group (so a timeout can kill the whole
|
|
1551
|
+
// tree), which also means launchd's job teardown no longer reaches them. A
|
|
1552
|
+
// daemon that exits mid-install takes its installs with it.
|
|
1553
|
+
process.once('exit', () => {
|
|
1554
|
+
for (const pgid of liveInstallGroups) {
|
|
1555
|
+
try {
|
|
1556
|
+
process.kill(-pgid, 'SIGKILL');
|
|
1557
|
+
}
|
|
1558
|
+
catch { /* already gone */ }
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Run one dependency install as its own process group with a HARD deadline.
|
|
1564
|
+
* Never throws; always settles.
|
|
1565
|
+
*
|
|
1566
|
+
* - stdin is /dev/null: nothing can wait on a prompt (CI=1 and the
|
|
1567
|
+
* managers' non-interactive flags already say so; this makes it true —
|
|
1568
|
+
* `execFile` left stdin an open pipe nobody ever wrote to or closed).
|
|
1569
|
+
* - `detached` puts the manager in a new process group; at the deadline (or
|
|
1570
|
+
* on abort) the WHOLE group gets SIGTERM, then SIGKILL after `killGraceMs`
|
|
1571
|
+
* — a manager's worker or a stray child cannot outlive it. `execFile`'s
|
|
1572
|
+
* own `timeout` signals only the direct child.
|
|
1573
|
+
* - when the leader exits by itself, any straggler left in its group is
|
|
1574
|
+
* SIGKILLed too (a finished install leaves nothing running).
|
|
1575
|
+
* - if even SIGKILL does not produce an exit within the grace (a process in
|
|
1576
|
+
* uninterruptible I/O on an overloaded machine), the promise settles
|
|
1577
|
+
* anyway with `abandoned` — the tick never waits on the kernel forever.
|
|
1578
|
+
* - output is kept as a bounded tail, never buffered whole.
|
|
1579
|
+
*/
|
|
1580
|
+
export function runDependencyInstall(bin, args, opts) {
|
|
1581
|
+
const started = Date.now();
|
|
1582
|
+
const grace = Math.max(50, opts.killGraceMs ?? MIRROR_DEPS_KILL_GRACE_MS);
|
|
1583
|
+
const base = () => ({
|
|
1584
|
+
ok: false, code: null, signal: null, timedOut: false, cancelled: false, abandoned: false,
|
|
1585
|
+
spawnError: null, outputTail: '', durationMs: Date.now() - started,
|
|
1586
|
+
});
|
|
1587
|
+
if (opts.signal?.aborted)
|
|
1588
|
+
return Promise.resolve({ ...base(), cancelled: true });
|
|
861
1589
|
return new Promise((resolveRun) => {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1590
|
+
const useGroup = process.platform !== 'win32';
|
|
1591
|
+
let child;
|
|
1592
|
+
try {
|
|
1593
|
+
child = spawn(bin, [...args], {
|
|
1594
|
+
cwd: opts.cwd,
|
|
1595
|
+
env: opts.env,
|
|
1596
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1597
|
+
detached: useGroup,
|
|
1598
|
+
windowsHide: true,
|
|
1599
|
+
});
|
|
865
1600
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
if (!bin) {
|
|
869
|
-
resolveRun({ ok: false, reason: `${name ?? 'the package manager'} is not installed on the daemon's PATH, so ${plan.lockfile} cannot be installed` });
|
|
1601
|
+
catch (error) {
|
|
1602
|
+
resolveRun({ ...base(), spawnError: error.message });
|
|
870
1603
|
return;
|
|
871
1604
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1605
|
+
const pid = child.pid;
|
|
1606
|
+
if (useGroup && typeof pid === 'number') {
|
|
1607
|
+
installExitReaper();
|
|
1608
|
+
liveInstallGroups.add(pid);
|
|
1609
|
+
}
|
|
1610
|
+
let stdoutTail = '';
|
|
1611
|
+
let stderrTail = '';
|
|
1612
|
+
const keep = (tail, chunk) => {
|
|
1613
|
+
const next = tail + String(chunk);
|
|
1614
|
+
return next.length > INSTALL_OUTPUT_TAIL_BYTES ? next.slice(-INSTALL_OUTPUT_TAIL_BYTES) : next;
|
|
1615
|
+
};
|
|
1616
|
+
child.stdout?.on('data', (chunk) => { stdoutTail = keep(stdoutTail, chunk); });
|
|
1617
|
+
child.stderr?.on('data', (chunk) => { stderrTail = keep(stderrTail, chunk); });
|
|
1618
|
+
let timedOut = false;
|
|
1619
|
+
let cancelled = false;
|
|
1620
|
+
let settled = false;
|
|
1621
|
+
let exit = null;
|
|
1622
|
+
const timers = [];
|
|
1623
|
+
const later = (ms, fn) => {
|
|
1624
|
+
timers.push(setTimeout(fn, ms));
|
|
1625
|
+
};
|
|
1626
|
+
const signalGroup = (sig) => {
|
|
1627
|
+
if (typeof pid !== 'number')
|
|
884
1628
|
return;
|
|
1629
|
+
try {
|
|
1630
|
+
if (useGroup)
|
|
1631
|
+
process.kill(-pid, sig);
|
|
1632
|
+
else
|
|
1633
|
+
child.kill(sig);
|
|
885
1634
|
}
|
|
886
|
-
|
|
887
|
-
|
|
1635
|
+
catch { /* the group is already gone */ }
|
|
1636
|
+
};
|
|
1637
|
+
const tail = () => {
|
|
1638
|
+
const lines = (stderrTail.trim() || stdoutTail.trim()).split('\n').map((line) => line.trim()).filter(Boolean);
|
|
1639
|
+
return lines.slice(-4).join(' ');
|
|
1640
|
+
};
|
|
1641
|
+
const onAbort = () => {
|
|
1642
|
+
if (exit || cancelled || timedOut)
|
|
1643
|
+
return;
|
|
1644
|
+
cancelled = true;
|
|
1645
|
+
stop();
|
|
1646
|
+
};
|
|
1647
|
+
const finish = (extra) => {
|
|
1648
|
+
if (settled)
|
|
1649
|
+
return;
|
|
1650
|
+
settled = true;
|
|
1651
|
+
for (const timer of timers)
|
|
1652
|
+
clearTimeout(timer);
|
|
1653
|
+
opts.signal?.removeEventListener('abort', onAbort);
|
|
1654
|
+
if (typeof pid === 'number')
|
|
1655
|
+
liveInstallGroups.delete(pid);
|
|
1656
|
+
resolveRun({
|
|
1657
|
+
...base(),
|
|
1658
|
+
...(exit ? { code: exit.code, signal: exit.signal } : {}),
|
|
1659
|
+
timedOut,
|
|
1660
|
+
cancelled,
|
|
1661
|
+
outputTail: tail(),
|
|
1662
|
+
...extra,
|
|
1663
|
+
});
|
|
1664
|
+
};
|
|
1665
|
+
function stop() {
|
|
1666
|
+
signalGroup('SIGTERM');
|
|
1667
|
+
later(grace, () => {
|
|
1668
|
+
signalGroup('SIGKILL');
|
|
1669
|
+
// Last resort: still no exit a full grace after SIGKILL — stop waiting.
|
|
1670
|
+
later(grace, () => {
|
|
1671
|
+
child.stdout?.destroy();
|
|
1672
|
+
child.stderr?.destroy();
|
|
1673
|
+
child.unref();
|
|
1674
|
+
finish({ abandoned: true });
|
|
1675
|
+
});
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
opts.signal?.addEventListener('abort', onAbort, { once: true });
|
|
1679
|
+
later(Math.max(1, opts.timeoutMs), () => {
|
|
1680
|
+
if (exit || cancelled)
|
|
1681
|
+
return;
|
|
1682
|
+
timedOut = true;
|
|
1683
|
+
stop();
|
|
1684
|
+
});
|
|
1685
|
+
child.once('error', (error) => {
|
|
1686
|
+
if (exit)
|
|
1687
|
+
return;
|
|
1688
|
+
finish({ spawnError: error.message });
|
|
1689
|
+
});
|
|
1690
|
+
child.once('exit', (code, sig) => {
|
|
1691
|
+
exit = { code, signal: sig };
|
|
1692
|
+
// Reap stragglers left in the group; their closing pipes then let
|
|
1693
|
+
// 'close' fire. If a straggler escaped the group and holds a pipe,
|
|
1694
|
+
// settle shortly anyway.
|
|
1695
|
+
signalGroup('SIGKILL');
|
|
1696
|
+
later(grace, () => finish({ ok: !timedOut && !cancelled && code === 0 }));
|
|
1697
|
+
});
|
|
1698
|
+
child.once('close', () => {
|
|
1699
|
+
if (!exit)
|
|
1700
|
+
return;
|
|
1701
|
+
finish({ ok: !timedOut && !cancelled && exit.code === 0 });
|
|
888
1702
|
});
|
|
889
1703
|
});
|
|
890
1704
|
}
|
|
1705
|
+
/** Production installer: spawn the planned package manager in the mirror. Never throws. */
|
|
1706
|
+
export async function installMirrorDependencies(plan, mirrorPath, signal, limits = {}) {
|
|
1707
|
+
if (signal?.aborted)
|
|
1708
|
+
return { ok: false, reason: 'cancelled' };
|
|
1709
|
+
const [name, ...args] = plan.argv;
|
|
1710
|
+
const bin = name ? resolveManagerBinary(name) : null;
|
|
1711
|
+
if (!bin) {
|
|
1712
|
+
return { ok: false, reason: `${name ?? 'the package manager'} is not installed on the daemon's PATH, so ${plan.lockfile} cannot be installed` };
|
|
1713
|
+
}
|
|
1714
|
+
const label = plan.argv.slice(0, 2).join(' ');
|
|
1715
|
+
const timeoutMs = limits.timeoutMs ?? MIRROR_DEPS_INSTALL_TIMEOUT_MS;
|
|
1716
|
+
const run = await runDependencyInstall(bin, args, {
|
|
1717
|
+
cwd: mirrorPath,
|
|
1718
|
+
env: installEnv(),
|
|
1719
|
+
timeoutMs,
|
|
1720
|
+
...(limits.killGraceMs !== undefined ? { killGraceMs: limits.killGraceMs } : {}),
|
|
1721
|
+
...(signal ? { signal } : {}),
|
|
1722
|
+
});
|
|
1723
|
+
if (run.ok)
|
|
1724
|
+
return { ok: true, reason: `${label} installed ${plan.lockfile}` };
|
|
1725
|
+
return { ok: false, reason: boundedScrub(`${label} ${describeInstallFailure(run, timeoutMs)}`) };
|
|
1726
|
+
}
|
|
1727
|
+
/** "10 min", "30 s", "300 ms" — for failure sentences. */
|
|
1728
|
+
function describeLimit(ms) {
|
|
1729
|
+
if (ms >= 60_000)
|
|
1730
|
+
return `${Math.round(ms / 60_000)} min`;
|
|
1731
|
+
if (ms >= 1_000)
|
|
1732
|
+
return `${Math.round(ms / 1_000)} s`;
|
|
1733
|
+
return `${Math.max(0, Math.round(ms))} ms`;
|
|
1734
|
+
}
|
|
1735
|
+
/** "timed out after 10 min and its process group was killed; it printed nothing" … */
|
|
1736
|
+
export function describeInstallFailure(run, timeoutMs) {
|
|
1737
|
+
const output = run.outputTail ? `; last output: ${run.outputTail}` : '; it printed nothing';
|
|
1738
|
+
const limit = describeLimit(timeoutMs);
|
|
1739
|
+
if (run.spawnError)
|
|
1740
|
+
return `could not start: ${run.spawnError}`;
|
|
1741
|
+
if (run.timedOut) {
|
|
1742
|
+
return `timed out after ${limit} and its process group was killed${run.abandoned ? ' (it had not exited even after SIGKILL)' : ''}${output}`;
|
|
1743
|
+
}
|
|
1744
|
+
if (run.cancelled)
|
|
1745
|
+
return `was cancelled and its process group was killed${output}`;
|
|
1746
|
+
return `failed: exited ${run.code !== null ? `with code ${run.code}` : `on ${run.signal ?? 'an unknown signal'}`}${output}`;
|
|
1747
|
+
}
|
|
891
1748
|
function nodeModulesIdentity(mirrorPath) {
|
|
892
1749
|
try {
|
|
893
1750
|
const stat = lstatSync(join(mirrorPath, 'node_modules'));
|
|
@@ -906,7 +1763,19 @@ function nodeModulesIdentity(mirrorPath) {
|
|
|
906
1763
|
* clean (nothing stale or planted survives) and a fresh frozen install.
|
|
907
1764
|
*/
|
|
908
1765
|
async function prepareMirrorDependencies(ctx, previous, nowIso) {
|
|
909
|
-
const
|
|
1766
|
+
const repo = ctx.identity.nameWithOwner;
|
|
1767
|
+
// 3.14: `file:../<dir>` siblings are pinned (and `../<dir>` laid out)
|
|
1768
|
+
// BEFORE planning, so the plan's key carries the pins.
|
|
1769
|
+
const needed = siblingDependenciesOf(ctx.path);
|
|
1770
|
+
let plan;
|
|
1771
|
+
if (needed.length > 0) {
|
|
1772
|
+
noteTickPhase('mirror prep', `${repo}: resolving ${needed.length} sibling dependenc${needed.length === 1 ? 'y' : 'ies'}`);
|
|
1773
|
+
const resolved = await resolveSiblingPins(ctx, needed);
|
|
1774
|
+
plan = resolved.ok ? planMirrorDependencies(ctx.path, resolved.pins) : { kind: 'refuse', reason: resolved.error };
|
|
1775
|
+
}
|
|
1776
|
+
else {
|
|
1777
|
+
plan = planMirrorDependencies(ctx.path);
|
|
1778
|
+
}
|
|
910
1779
|
const cleanAll = async () => {
|
|
911
1780
|
const clean = await git(ctx, ['clean', '-ffdxq']);
|
|
912
1781
|
return clean.ok ? null : `clean before the dependency install failed: ${clean.stderr || 'git clean failed'}`;
|
|
@@ -946,20 +1815,35 @@ async function prepareMirrorDependencies(ctx, previous, nowIso) {
|
|
|
946
1815
|
}
|
|
947
1816
|
const failed = (error) => {
|
|
948
1817
|
const scrubbed = boundedScrub(error);
|
|
1818
|
+
// The retry window runs from when the install FAILED, not from when the
|
|
1819
|
+
// sync began: a 10-minute timeout must not eat a third of the backoff.
|
|
1820
|
+
const failedAtIso = (ctx.deps.now ? ctx.deps.now() : new Date()).toISOString();
|
|
949
1821
|
return {
|
|
950
1822
|
ok: false,
|
|
951
1823
|
error: scrubbed,
|
|
952
|
-
deps: { status: 'failed', manager: plan.manager, key: plan.key, at:
|
|
1824
|
+
deps: { status: 'failed', manager: plan.manager, key: plan.key, at: failedAtIso, nodeModulesIno: null, nodeModulesMtimeMs: null, error: scrubbed },
|
|
953
1825
|
};
|
|
954
1826
|
};
|
|
1827
|
+
noteTickPhase('mirror prep', `${repo}: cleaning before the ${plan.manager} install`);
|
|
955
1828
|
const cleaned = await cleanAll();
|
|
956
1829
|
if (cleaned)
|
|
957
1830
|
return failed(cleaned);
|
|
1831
|
+
noteTickPhase('mirror prep', `${repo}: installing dependencies with ${plan.manager} (${plan.lockfile})`);
|
|
958
1832
|
const run = await (ctx.deps.installDependencies ?? installMirrorDependencies)(plan, ctx.path, ctx.deps.signal);
|
|
959
1833
|
if (!run.ok) {
|
|
960
|
-
// A half-written tree is worse than none: verification would run against
|
|
961
|
-
|
|
962
|
-
|
|
1834
|
+
// A half-written tree is worse than none: verification would run against
|
|
1835
|
+
// it. ASYNC on purpose: on 2026-09-26 a synchronous rmSync of a killed
|
|
1836
|
+
// pnpm install's node_modules held the daemon's event loop for minutes on
|
|
1837
|
+
// an overloaded machine (no heartbeat, the killed child left unreaped).
|
|
1838
|
+
noteTickPhase('mirror prep', `${repo}: removing the partial node_modules after a failed ${plan.manager} install`);
|
|
1839
|
+
let leftover = '';
|
|
1840
|
+
try {
|
|
1841
|
+
await rm(join(ctx.path, 'node_modules'), { recursive: true, force: true, maxRetries: 2 });
|
|
1842
|
+
}
|
|
1843
|
+
catch (error) {
|
|
1844
|
+
leftover = `; the partial node_modules could not be removed (${error.message})`;
|
|
1845
|
+
}
|
|
1846
|
+
return failed(`dependencies could not be installed from ${plan.lockfile}: ${run.reason}${leftover}`);
|
|
963
1847
|
}
|
|
964
1848
|
const installed = nodeModulesIdentity(ctx.path);
|
|
965
1849
|
if (installed === 'not-a-directory') {
|
|
@@ -969,10 +1853,30 @@ async function prepareMirrorDependencies(ctx, previous, nowIso) {
|
|
|
969
1853
|
// (npm ci creates no node_modules for it) — recorded as ino null.
|
|
970
1854
|
// The install must not have rewritten the committed lockfile (frozen flags
|
|
971
1855
|
// say it cannot; this proves it) — verification must see origin's tree.
|
|
972
|
-
const
|
|
1856
|
+
const siblings = plan.siblings ?? [];
|
|
1857
|
+
const after = planMirrorDependencies(ctx.path, siblings);
|
|
973
1858
|
if (after.kind !== 'install' || after.key !== plan.key) {
|
|
974
1859
|
return failed(`the ${plan.manager} install changed ${plan.lockfile} or package.json; the mirror is not origin's tree`);
|
|
975
1860
|
}
|
|
1861
|
+
// A `../<dir>` link moved during the install (another dependent pinned a
|
|
1862
|
+
// newer sibling commit): what was installed may not be the pin. Not a
|
|
1863
|
+
// failure of this lockfile — retried on the very next sync (no key, so no
|
|
1864
|
+
// backoff).
|
|
1865
|
+
const moved = siblings.find((pin) => {
|
|
1866
|
+
try {
|
|
1867
|
+
return readlinkSync(siblingLinkPath(pin.dir)) !== siblingLinkTarget(pin);
|
|
1868
|
+
}
|
|
1869
|
+
catch {
|
|
1870
|
+
return true;
|
|
1871
|
+
}
|
|
1872
|
+
});
|
|
1873
|
+
if (moved) {
|
|
1874
|
+
await rm(join(ctx.path, 'node_modules'), { recursive: true, force: true, maxRetries: 2 }).catch(() => undefined);
|
|
1875
|
+
const error = `../${moved.dir} moved off ${moved.nameWithOwner}@${moved.headSha.slice(0, 12)} during the ${plan.manager} install; reinstalling on the next sync`;
|
|
1876
|
+
return { ok: false, error, deps: { status: 'failed', manager: plan.manager, key: null, at: nowIso, nodeModulesIno: null, nodeModulesMtimeMs: null, error } };
|
|
1877
|
+
}
|
|
1878
|
+
if (siblings.length > 0)
|
|
1879
|
+
await pruneSiblingPins(repo, siblings);
|
|
976
1880
|
return {
|
|
977
1881
|
ok: true,
|
|
978
1882
|
deps: {
|
|
@@ -983,6 +1887,7 @@ async function prepareMirrorDependencies(ctx, previous, nowIso) {
|
|
|
983
1887
|
nodeModulesIno: installed === 'absent' ? null : installed.ino,
|
|
984
1888
|
nodeModulesMtimeMs: installed === 'absent' ? null : installed.mtimeMs,
|
|
985
1889
|
error: null,
|
|
1890
|
+
...(siblings.length > 0 ? { siblings: sortPins(siblings) } : {}),
|
|
986
1891
|
},
|
|
987
1892
|
};
|
|
988
1893
|
}
|
|
@@ -1048,6 +1953,7 @@ export async function ensureMirror(spec, deps = {}) {
|
|
|
1048
1953
|
let outcome;
|
|
1049
1954
|
let created = false;
|
|
1050
1955
|
if (layout.state === 'mirror') {
|
|
1956
|
+
noteTickPhase('mirror prep', `${identity.nameWithOwner}: fetching and resetting to origin/${base}`);
|
|
1051
1957
|
outcome = await fetchAndReset(ctx, base);
|
|
1052
1958
|
}
|
|
1053
1959
|
else {
|
|
@@ -1059,6 +1965,7 @@ export async function ensureMirror(spec, deps = {}) {
|
|
|
1059
1965
|
return fail(`mirror path holds something that is not a mirror (${layout.reason}) and could not be moved aside: ${error.message}`, { path, base, auth });
|
|
1060
1966
|
}
|
|
1061
1967
|
}
|
|
1968
|
+
noteTickPhase('mirror prep', `${identity.nameWithOwner}: cloning ${base}`);
|
|
1062
1969
|
outcome = await cloneInto(ctx, base);
|
|
1063
1970
|
created = !('error' in outcome);
|
|
1064
1971
|
}
|
|
@@ -1229,8 +2136,16 @@ export async function removeMirror(nameWithOwner, opts = {}) {
|
|
|
1229
2136
|
* Create and reset every mirror the standing policy's current stage names
|
|
1230
2137
|
* (SPEC-310B §2: "reset to origin/<base> every tick"), at most
|
|
1231
2138
|
* MIRROR_TICK_CONCURRENCY at a time. A repo whose mirror is not current is
|
|
1232
|
-
* reported for pausing rather than dispatched on
|
|
1233
|
-
* does nothing and pauses
|
|
2139
|
+
* reported for pausing ("prep failed: <reason>") rather than dispatched on
|
|
2140
|
+
* stale code; the others go ahead. Under KILL it does nothing and pauses
|
|
2141
|
+
* everything.
|
|
2142
|
+
*
|
|
2143
|
+
* BOUNDED: the whole phase has a deadline (MIRROR_TICK_PREP_DEADLINE_MS).
|
|
2144
|
+
* At the deadline — or when `deps.signal` aborts (daemon shutdown, the tick's
|
|
2145
|
+
* own deadline) — in-flight syncs are cancelled (an install's process group
|
|
2146
|
+
* is killed), repos not yet reached fail at once, and after a short grace
|
|
2147
|
+
* prep returns with whatever is current even if a cancelled sync has not
|
|
2148
|
+
* settled. One stuck repo can no longer hold the tick.
|
|
1234
2149
|
*/
|
|
1235
2150
|
export async function prepareMirrorsForTick(policy, deps = {}) {
|
|
1236
2151
|
const repos = [...new Set(policy.repos.map((repo) => repo.nameWithOwner))];
|
|
@@ -1248,25 +2163,112 @@ export async function prepareMirrorsForTick(policy, deps = {}) {
|
|
|
1248
2163
|
result.failed.push({ nameWithOwner, path: pathOf(nameWithOwner), reason: 'kill switch armed; mirrors not synced' });
|
|
1249
2164
|
}
|
|
1250
2165
|
}
|
|
1251
|
-
else {
|
|
1252
|
-
const
|
|
2166
|
+
else if (repos.length > 0) {
|
|
2167
|
+
const deadlineMs = Math.max(1, deps.deadlineMs ?? MIRROR_TICK_PREP_DEADLINE_MS);
|
|
2168
|
+
const graceMs = Math.max(0, deps.abortGraceMs ?? MIRROR_TICK_ABORT_GRACE_MS);
|
|
2169
|
+
const limit = describeLimit(deadlineMs);
|
|
2170
|
+
const controller = new AbortController();
|
|
2171
|
+
let deadlineHit = false;
|
|
2172
|
+
let stopWaiting = false;
|
|
2173
|
+
const onParentAbort = () => controller.abort();
|
|
2174
|
+
if (deps.signal?.aborted)
|
|
2175
|
+
controller.abort();
|
|
2176
|
+
else
|
|
2177
|
+
deps.signal?.addEventListener('abort', onParentAbort, { once: true });
|
|
2178
|
+
const deadlineTimer = setTimeout(() => {
|
|
2179
|
+
deadlineHit = true;
|
|
2180
|
+
controller.abort();
|
|
2181
|
+
}, deadlineMs);
|
|
2182
|
+
let graceTimer;
|
|
2183
|
+
const why = () => (deadlineHit
|
|
2184
|
+
? `the tick's mirror-prep deadline (${limit}) passed`
|
|
2185
|
+
: 'mirror prep was cancelled (the daemon is stopping or the tick ran out of time)');
|
|
2186
|
+
const fail = (nameWithOwner, path, reason) => {
|
|
2187
|
+
if (stopWaiting)
|
|
2188
|
+
return;
|
|
2189
|
+
result.failed.push({ nameWithOwner, path, reason: boundedScrub(reason) });
|
|
2190
|
+
};
|
|
2191
|
+
const inner = { ...deps, signal: controller.signal };
|
|
2192
|
+
// 3.14: siblings sync before the repos that install from them, and a
|
|
2193
|
+
// dependent starts only once its siblings' syncs this tick have settled,
|
|
2194
|
+
// so it pins their fresh HEAD instead of waiting on their lease.
|
|
2195
|
+
const { order, waitsFor } = orderForSiblings(repos);
|
|
2196
|
+
const settled = new Map();
|
|
2197
|
+
for (const repo of order) {
|
|
2198
|
+
let resolveSettled;
|
|
2199
|
+
const promise = new Promise((done) => { resolveSettled = done; });
|
|
2200
|
+
settled.set(repo, { promise, resolve: resolveSettled });
|
|
2201
|
+
}
|
|
2202
|
+
const aborted = new Promise((done) => {
|
|
2203
|
+
if (controller.signal.aborted)
|
|
2204
|
+
done();
|
|
2205
|
+
else
|
|
2206
|
+
controller.signal.addEventListener('abort', () => done(), { once: true });
|
|
2207
|
+
});
|
|
2208
|
+
const queue = [...order];
|
|
2209
|
+
const inFlight = new Set();
|
|
1253
2210
|
const workers = Array.from({ length: Math.min(queue.length, Math.max(1, Math.floor(deps.concurrency ?? MIRROR_TICK_CONCURRENCY))) }, async () => {
|
|
1254
2211
|
for (let next = queue.shift(); next !== undefined; next = queue.shift()) {
|
|
1255
|
-
|
|
2212
|
+
if (controller.signal.aborted) {
|
|
2213
|
+
fail(next, pathOf(next), `prep failed: ${why()} before ${next} was synced`);
|
|
2214
|
+
continue;
|
|
2215
|
+
}
|
|
2216
|
+
inFlight.add(next);
|
|
2217
|
+
let synced;
|
|
2218
|
+
try {
|
|
2219
|
+
const before = (waitsFor.get(next) ?? []).map((sibling) => settled.get(sibling).promise);
|
|
2220
|
+
if (before.length > 0)
|
|
2221
|
+
await Promise.race([Promise.all(before), aborted]);
|
|
2222
|
+
synced = await ensureMirror({ nameWithOwner: next }, inner);
|
|
2223
|
+
}
|
|
2224
|
+
catch (error) {
|
|
2225
|
+
// ensureMirror never throws by contract; a bug must not sink the others.
|
|
2226
|
+
synced = { ok: false, nameWithOwner: next, path: pathOf(next), base: null, headSha: null, created: false, changed: false, quarantinedTo: null, auth: null, reason: error.message, durationMs: 0 };
|
|
2227
|
+
}
|
|
2228
|
+
finally {
|
|
2229
|
+
inFlight.delete(next);
|
|
2230
|
+
settled.get(next)?.resolve();
|
|
2231
|
+
}
|
|
2232
|
+
if (stopWaiting)
|
|
2233
|
+
return;
|
|
1256
2234
|
if (synced.ok && synced.base && synced.headSha) {
|
|
1257
2235
|
result.ready.push({ nameWithOwner: synced.nameWithOwner, path: synced.path, base: synced.base, headSha: synced.headSha });
|
|
1258
2236
|
}
|
|
1259
2237
|
else {
|
|
1260
|
-
|
|
2238
|
+
fail(next, synced.path || pathOf(next), controller.signal.aborted
|
|
2239
|
+
? `prep failed: ${why()}: ${synced.reason}`
|
|
2240
|
+
: `prep failed: ${synced.reason}`);
|
|
1261
2241
|
}
|
|
1262
2242
|
}
|
|
1263
2243
|
});
|
|
1264
|
-
|
|
2244
|
+
const stopped = new Promise((resolveStopped) => {
|
|
2245
|
+
const arm = () => { graceTimer = setTimeout(() => resolveStopped('stopped'), graceMs); };
|
|
2246
|
+
if (controller.signal.aborted)
|
|
2247
|
+
arm();
|
|
2248
|
+
else
|
|
2249
|
+
controller.signal.addEventListener('abort', arm, { once: true });
|
|
2250
|
+
});
|
|
2251
|
+
const outcome = await Promise.race([Promise.all(workers).then(() => 'done'), stopped]);
|
|
2252
|
+
clearTimeout(deadlineTimer);
|
|
2253
|
+
if (graceTimer)
|
|
2254
|
+
clearTimeout(graceTimer);
|
|
2255
|
+
deps.signal?.removeEventListener('abort', onParentAbort);
|
|
2256
|
+
if (outcome === 'stopped') {
|
|
2257
|
+
// Stop waiting: a cancelled sync that has not settled (its repo lease
|
|
2258
|
+
// keeps a late finisher from racing the next tick) and any repo not
|
|
2259
|
+
// reached are reported now.
|
|
2260
|
+
for (const nameWithOwner of inFlight) {
|
|
2261
|
+
fail(nameWithOwner, pathOf(nameWithOwner), `prep failed: ${why()} and its sync had not stopped ${describeLimit(graceMs)} after being cancelled`);
|
|
2262
|
+
}
|
|
2263
|
+
for (let next = queue.shift(); next !== undefined; next = queue.shift()) {
|
|
2264
|
+
fail(next, pathOf(next), `prep failed: ${why()} before ${next} was synced`);
|
|
2265
|
+
}
|
|
2266
|
+
stopWaiting = true;
|
|
2267
|
+
}
|
|
1265
2268
|
}
|
|
1266
|
-
result.
|
|
1267
|
-
result.
|
|
1268
|
-
|
|
1269
|
-
return result;
|
|
2269
|
+
const failed = [...result.failed].sort((a, b) => a.nameWithOwner.localeCompare(b.nameWithOwner));
|
|
2270
|
+
const ready = [...result.ready].sort((a, b) => a.nameWithOwner.localeCompare(b.nameWithOwner));
|
|
2271
|
+
return { ready, failed, pausedRepoPaths: failed.map((row) => row.path).filter((path) => path.length > 0) };
|
|
1270
2272
|
}
|
|
1271
2273
|
// ---------------------------------------------------------------------------
|
|
1272
2274
|
// The autonomous lane (3.10 R3f) — see "WHY ENROLLMENT IS A LANE" above
|