@timurproko/a1 0.2.7-dev.1245 → 0.2.7-dev.1252
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/README.md +11 -36
- package/bin/cli.js +2 -17
- package/bin/ui.js +7 -48
- package/dist/app/session-shell/exit-notice.d.ts +2 -2
- package/dist/app/session-shell/exit-notice.js +10 -10
- package/dist/app/session-shell/prompt-submission.d.ts +2 -4
- package/dist/app/session-shell/prompt-submission.js +3 -23
- package/dist/app/session-shell/response-copy-transport.js +1 -2
- package/dist/app/session-shell/session-lifecycle.d.ts +1 -2
- package/dist/app/session-shell/session-lifecycle.js +2 -4
- package/dist/app/session-shell/session-shell-root.d.ts +0 -31
- package/dist/app/session-shell/session-shell-root.js +3 -4
- package/dist/app/session-shell/session-shell.js +6 -47
- package/dist/app/session-shell/session-viewport-controller.js +3 -6
- package/dist/app/session-shell/shell-host.js +2 -18
- package/dist/app/session-shell/transcript-document.d.ts +1 -2
- package/dist/app/session-shell/transcript-document.js +3 -28
- package/dist/app/session-shell/workflow-output.js +1 -1
- package/dist/app/session-shell/workflow-router.d.ts +1 -3
- package/dist/app/session-shell/workflow-router.js +1 -4
- package/dist/cli/dispatch.d.ts +1 -11
- package/dist/cli/dispatch.js +5 -92
- package/dist/cli/session-context.js +5 -7
- package/dist/cli/uninstall.d.ts +1 -5
- package/dist/cli/uninstall.js +6 -35
- package/dist/cli/version-stats.js +4 -7
- package/dist/composition/owned-settings.d.ts +0 -1
- package/dist/composition/owned-settings.js +0 -1
- package/dist/composition/owned-ui.d.ts +2 -24
- package/dist/composition/owned-ui.js +11 -87
- package/dist/composition/release-update-observation.d.ts +4 -4
- package/dist/composition/release-update-observation.js +17 -47
- package/dist/composition/session-backend.d.ts +0 -1
- package/dist/composition/session-runtime-registry.d.ts +3 -3
- package/dist/composition/settings-route-host.d.ts +0 -7
- package/dist/composition/settings-route-host.js +4 -10
- package/dist/composition/startup-route.d.ts +0 -1
- package/dist/composition/startup-route.js +0 -1
- package/dist/contracts/owned-ui/index.d.ts +1 -1
- package/dist/contracts/owned-ui/model.d.ts +0 -3
- package/dist/contracts/owned-ui/session-workflows.d.ts +1 -4
- package/dist/features/launch/development-launch.js +0 -1
- package/dist/features/launch/index.d.ts +1 -1
- package/dist/features/launch/intent.d.ts +1 -3
- package/dist/features/launch/intent.js +2 -4
- package/dist/features/launch/session-context.d.ts +2 -2
- package/dist/features/owned-ui/reference-screen-app.d.ts +0 -2
- package/dist/features/owned-ui/reference-screen-app.js +2 -9
- package/dist/features/owned-ui/settings-app.d.ts +0 -6
- package/dist/features/owned-ui/settings-app.js +5 -36
- package/dist/foundation/launch-context/index.d.ts +0 -12
- package/dist/foundation/launch-context/index.js +0 -26
- package/dist/foundation/lifecycle/claim-gate.d.ts +8 -0
- package/dist/foundation/lifecycle/claim-gate.js +37 -0
- package/dist/foundation/lifecycle/index.d.ts +5 -9
- package/dist/foundation/lifecycle/index.js +3 -4
- package/dist/foundation/lifecycle/model.d.ts +2 -59
- package/dist/foundation/lifecycle/model.js +0 -35
- package/dist/foundation/lifecycle/paths.d.ts +0 -24
- package/dist/foundation/lifecycle/paths.js +0 -40
- package/dist/foundation/lifecycle/process-liveness.d.ts +8 -0
- package/dist/foundation/lifecycle/process-liveness.js +18 -0
- package/dist/foundation/lifecycle/session-claim-lock.d.ts +1 -1
- package/dist/foundation/lifecycle/session-claim-lock.js +2 -2
- package/dist/foundation/lifecycle/session-repository-context.d.ts +6 -6
- package/dist/foundation/lifecycle/session-repository-context.js +2 -3
- package/dist/foundation/release/automatic-update.d.ts +1 -3
- package/dist/foundation/release/automatic-update.js +9 -30
- package/dist/foundation/release/bootstrap.d.ts +2 -25
- package/dist/foundation/release/bootstrap.js +33 -344
- package/dist/foundation/release/cohort-state.d.ts +21 -46
- package/dist/foundation/release/cohort-state.js +30 -62
- package/dist/foundation/release/dependency-certification.d.ts +1 -1
- package/dist/foundation/release/index.d.ts +10 -17
- package/dist/foundation/release/index.js +7 -11
- package/dist/foundation/release/latest-release.d.ts +1 -2
- package/dist/foundation/release/latest-release.js +1 -2
- package/dist/foundation/release/process-liveness.d.ts +2 -0
- package/dist/foundation/release/process-liveness.js +12 -0
- package/dist/foundation/release/release-gc.d.ts +12 -9
- package/dist/foundation/release/release-gc.js +34 -98
- package/dist/foundation/release/release-store.d.ts +2 -2
- package/dist/foundation/release/release-store.js +2 -2
- package/dist/foundation/release/restart-certification.js +1 -1
- package/dist/foundation/release/update-activation.d.ts +2 -9
- package/dist/foundation/release/update-activation.js +28 -43
- package/dist/foundation/release/update-execution-lease.js +1 -1
- package/dist/foundation/release/update-launch.d.ts +0 -2
- package/dist/foundation/release/update-launch.js +0 -7
- package/dist/foundation/release/update-recovery.js +1 -1
- package/dist/foundation/release/update-transaction.d.ts +1 -1
- package/dist/foundation/release/update-transaction.js +5 -2
- package/dist/foundation/release/update.d.ts +2 -10
- package/dist/foundation/release/update.js +23 -200
- package/dist/foundation/session-writer-lock/index.d.ts +14 -6
- package/dist/foundation/session-writer-lock/index.js +48 -103
- package/dist/foundation/startup/startup-runtime.d.ts +1 -1
- package/dist/foundation/terminal-cleanup/exit-notice.d.ts +6 -0
- package/dist/foundation/terminal-cleanup/exit-notice.js +36 -0
- package/dist/foundation/terminal-cleanup/index.d.ts +2 -1
- package/dist/foundation/terminal-cleanup/index.js +2 -1
- package/dist/foundation/terminal-cleanup/terminal-reset.d.ts +0 -5
- package/dist/foundation/terminal-cleanup/terminal-reset.js +0 -5
- package/dist/integrations/pi/components/index.d.ts +1 -1
- package/dist/integrations/pi/components/index.js +1 -1
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +1 -4
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +2 -2
- package/dist/integrations/pi/components/shell-hotkey-sections.js +1 -1
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +2 -5
- package/dist/integrations/pi/components/shell-presenters-info.js +4 -18
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +1 -1
- package/dist/integrations/pi/components/shell-presenters-transcript.js +1 -1
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +0 -2
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +2 -5
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +2 -16
- package/dist/integrations/pi/components/upstream/components/session-footer.d.ts +1 -2
- package/dist/integrations/pi/components/upstream/components/session-footer.js +6 -8
- package/dist/integrations/pi/engine/adapter.d.ts +0 -1
- package/dist/integrations/pi/engine/adapter.js +1 -16
- package/dist/integrations/pi/engine/compaction-progress.js +13 -5
- package/dist/integrations/pi/engine/diagnostics-state.d.ts +0 -1
- package/dist/integrations/pi/engine/diagnostics-state.js +0 -14
- package/dist/integrations/pi/engine/prompt-suggestions.d.ts +0 -1
- package/dist/integrations/pi/engine/prompt-suggestions.js +0 -40
- package/dist/integrations/pi/engine/session-runtime.js +0 -2
- package/dist/integrations/pi/engine/workflow-runner.js +1 -1
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -2
- package/dist/integrations/pi/tui-runtime/adapter.js +2 -2
- package/dist/product-identity.d.ts +4 -6
- package/dist/product-identity.js +6 -11
- package/dist/product-identity.json +1 -40
- package/dist/runtime-payload-inventory.json +0 -3
- package/dist/ui/components/index.d.ts +2 -2
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/submitted-prompt.d.ts +3 -13
- package/dist/ui/components/submitted-prompt.js +8 -41
- package/dist/ui/components/transcript-viewport.d.ts +1 -3
- package/dist/ui/components/transcript-viewport.js +1 -4
- package/dist/ui/settings/declarations.d.ts +3 -97
- package/dist/ui/settings/declarations.js +3 -78
- package/dist/ui/settings/migrations.js +18 -2
- package/docs/architecture/boundaries.md +22 -39
- package/docs/architecture/code-documentation.md +2 -2
- package/docs/architecture/internal-naming.md +9 -17
- package/docs/architecture/project-structure.md +30 -29
- package/docs/architecture/resource-and-data-policy.md +16 -31
- package/docs/architecture/toolchain.md +14 -19
- package/docs/architecture/ui-reference-provenance.md +2 -2
- package/docs/ci-release-runbook.md +6 -4
- package/docs/features/launch-profiles.md +11 -17
- package/docs/features/prompt-history.md +10 -11
- package/docs/local-worktree-cleanup.md +32 -1
- package/docs/manual-owned-ui-checkpoint.md +0 -1
- package/docs/openspec-archive-automation.md +11 -3
- package/docs/validation.md +5 -9
- package/docs/worktree-link-recovery.md +6 -6
- package/package.json +7 -19
- package/bin/guardian.js +0 -42
- package/bin/supervisor.js +0 -7
- package/bin/tab-launch-context.d.ts +0 -27
- package/bin/tab-launch-context.js +0 -94
- package/dist/app/session-shell/resident-tab-coordinator.d.ts +0 -38
- package/dist/app/session-shell/resident-tab-coordinator.js +0 -77
- package/dist/cli/tabs-request.d.ts +0 -7
- package/dist/cli/tabs-request.js +0 -1
- package/dist/cli/tabs.d.ts +0 -14
- package/dist/cli/tabs.js +0 -151
- package/dist/composition/resident-tab-bridge.d.ts +0 -41
- package/dist/composition/resident-tab-bridge.js +0 -209
- package/dist/composition/utility-tab.d.ts +0 -28
- package/dist/composition/utility-tab.js +0 -244
- package/dist/features/owned-ui/plus-menu-dialog.d.ts +0 -28
- package/dist/features/owned-ui/plus-menu-dialog.js +0 -89
- package/dist/features/resident-tab/auto-name.d.ts +0 -11
- package/dist/features/resident-tab/auto-name.js +0 -44
- package/dist/features/resident-tab/index.d.ts +0 -5
- package/dist/features/resident-tab/index.js +0 -7
- package/dist/features/resident-tab/shortcut-bindings-file.d.ts +0 -8
- package/dist/features/resident-tab/shortcut-bindings-file.js +0 -40
- package/dist/features/resident-tab/shortcut-declarations.d.ts +0 -13
- package/dist/features/resident-tab/shortcut-declarations.js +0 -26
- package/dist/features/resident-tab/shortcut-projection.d.ts +0 -29
- package/dist/features/resident-tab/shortcut-projection.js +0 -64
- package/dist/features/resident-tab/status-deriver.d.ts +0 -10
- package/dist/features/resident-tab/status-deriver.js +0 -12
- package/dist/foundation/launch-guardian/exit-watcher.d.ts +0 -14
- package/dist/foundation/launch-guardian/exit-watcher.js +0 -103
- package/dist/foundation/launch-guardian/index.d.ts +0 -2
- package/dist/foundation/launch-guardian/index.js +0 -1
- package/dist/foundation/launch-guardian/main.d.ts +0 -39
- package/dist/foundation/launch-guardian/main.js +0 -232
- package/dist/foundation/lifecycle/commands.d.ts +0 -40
- package/dist/foundation/lifecycle/commands.js +0 -1
- package/dist/foundation/lifecycle/launch-instance.d.ts +0 -46
- package/dist/foundation/lifecycle/launch-instance.js +0 -134
- package/dist/foundation/lifecycle/native-claim-gate.d.ts +0 -5
- package/dist/foundation/lifecycle/native-claim-gate.js +0 -41
- package/dist/foundation/lifecycle/supervisor-startup.d.ts +0 -30
- package/dist/foundation/lifecycle/supervisor-startup.js +0 -106
- package/dist/foundation/process-containment/addon-containment.d.ts +0 -20
- package/dist/foundation/process-containment/addon-containment.js +0 -139
- package/dist/foundation/process-containment/addon-process-inspector.d.ts +0 -11
- package/dist/foundation/process-containment/addon-process-inspector.js +0 -36
- package/dist/foundation/process-containment/addon.d.ts +0 -47
- package/dist/foundation/process-containment/addon.js +0 -57
- package/dist/foundation/process-containment/artifact.d.ts +0 -1
- package/dist/foundation/process-containment/artifact.js +0 -35
- package/dist/foundation/process-containment/contracts.d.ts +0 -33
- package/dist/foundation/process-containment/contracts.js +0 -35
- package/dist/foundation/process-containment/index.d.ts +0 -9
- package/dist/foundation/process-containment/index.js +0 -7
- package/dist/foundation/process-containment/linux-process-inspector.d.ts +0 -7
- package/dist/foundation/process-containment/linux-process-inspector.js +0 -27
- package/dist/foundation/process-containment/native-process-inspector.d.ts +0 -4
- package/dist/foundation/process-containment/native-process-inspector.js +0 -21
- package/dist/foundation/protocol/client.d.ts +0 -18
- package/dist/foundation/protocol/client.js +0 -130
- package/dist/foundation/protocol/index.d.ts +0 -3
- package/dist/foundation/protocol/index.js +0 -2
- package/dist/foundation/protocol/messages.d.ts +0 -90
- package/dist/foundation/protocol/messages.js +0 -100
- package/dist/foundation/release/cohort-selection.d.ts +0 -33
- package/dist/foundation/release/cohort-selection.js +0 -67
- package/dist/foundation/release/endpoint-lock.d.ts +0 -2
- package/dist/foundation/release/endpoint-lock.js +0 -37
- package/dist/foundation/release/endpoint-recovery.d.ts +0 -10
- package/dist/foundation/release/endpoint-recovery.js +0 -45
- package/dist/foundation/release/endpoints.d.ts +0 -32
- package/dist/foundation/release/endpoints.js +0 -152
- package/dist/foundation/release/prepared-update-activation.d.ts +0 -3
- package/dist/foundation/release/prepared-update-activation.js +0 -27
- package/dist/foundation/release/process-cleanup.d.ts +0 -14
- package/dist/foundation/release/process-cleanup.js +0 -76
- package/dist/foundation/release/supervisor-identity.d.ts +0 -15
- package/dist/foundation/release/supervisor-identity.js +0 -63
- package/dist/foundation/resident-tabs/boot-identity.d.ts +0 -6
- package/dist/foundation/resident-tabs/boot-identity.js +0 -16
- package/dist/foundation/resident-tabs/bounded-host-command.d.ts +0 -20
- package/dist/foundation/resident-tabs/bounded-host-command.js +0 -62
- package/dist/foundation/resident-tabs/bridge-client.d.ts +0 -73
- package/dist/foundation/resident-tabs/bridge-client.js +0 -327
- package/dist/foundation/resident-tabs/continuity.d.ts +0 -12
- package/dist/foundation/resident-tabs/continuity.js +0 -63
- package/dist/foundation/resident-tabs/doctor.d.ts +0 -28
- package/dist/foundation/resident-tabs/doctor.js +0 -166
- package/dist/foundation/resident-tabs/draft-checkpoint.d.ts +0 -27
- package/dist/foundation/resident-tabs/draft-checkpoint.js +0 -145
- package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +0 -36
- package/dist/foundation/resident-tabs/ensure-terminal-host.js +0 -151
- package/dist/foundation/resident-tabs/fault-injection.d.ts +0 -8
- package/dist/foundation/resident-tabs/fault-injection.js +0 -26
- package/dist/foundation/resident-tabs/fixture-control.d.ts +0 -7
- package/dist/foundation/resident-tabs/fixture-control.js +0 -35
- package/dist/foundation/resident-tabs/host-entry.d.ts +0 -1
- package/dist/foundation/resident-tabs/host-entry.js +0 -17
- package/dist/foundation/resident-tabs/index.d.ts +0 -15
- package/dist/foundation/resident-tabs/index.js +0 -10
- package/dist/foundation/resident-tabs/maintenance.d.ts +0 -39
- package/dist/foundation/resident-tabs/maintenance.js +0 -75
- package/dist/foundation/resident-tabs/progress-observation-schema.d.ts +0 -34
- package/dist/foundation/resident-tabs/progress-observation-schema.js +0 -1
- package/dist/foundation/resident-tabs/progress-observation.d.ts +0 -4
- package/dist/foundation/resident-tabs/progress-observation.js +0 -150
- package/dist/foundation/resident-tabs/prompt-journal.d.ts +0 -63
- package/dist/foundation/resident-tabs/prompt-journal.js +0 -213
- package/dist/foundation/resident-tabs/protocol.d.ts +0 -12
- package/dist/foundation/resident-tabs/protocol.js +0 -195
- package/dist/foundation/resident-tabs/recovery.d.ts +0 -26
- package/dist/foundation/resident-tabs/recovery.js +0 -205
- package/dist/foundation/resident-tabs/release-holds.d.ts +0 -28
- package/dist/foundation/resident-tabs/release-holds.js +0 -78
- package/dist/foundation/resident-tabs/routes.d.ts +0 -5
- package/dist/foundation/resident-tabs/routes.js +0 -5
- package/dist/foundation/resident-tabs/routing.d.ts +0 -11
- package/dist/foundation/resident-tabs/routing.js +0 -10
- package/dist/foundation/resident-tabs/runtime-control.d.ts +0 -38
- package/dist/foundation/resident-tabs/runtime-control.js +0 -74
- package/dist/foundation/resident-tabs/session-sync.d.ts +0 -14
- package/dist/foundation/resident-tabs/session-sync.js +0 -37
- package/dist/foundation/resident-tabs/settings.d.ts +0 -16
- package/dist/foundation/resident-tabs/settings.js +0 -60
- package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +0 -14
- package/dist/foundation/resident-tabs/terminal-host-artifact.js +0 -78
- package/dist/foundation/resident-tabs/zip-archive.d.ts +0 -9
- package/dist/foundation/resident-tabs/zip-archive.js +0 -56
- package/dist/foundation/storage/control-store.d.ts +0 -20
- package/dist/foundation/storage/control-store.js +0 -331
- package/dist/foundation/storage/index.d.ts +0 -1
- package/dist/foundation/storage/index.js +0 -1
- package/dist/foundation/supervision/index.d.ts +0 -3
- package/dist/foundation/supervision/index.js +0 -3
- package/dist/foundation/supervision/main.d.ts +0 -1
- package/dist/foundation/supervision/main.js +0 -86
- package/dist/foundation/supervision/paths.d.ts +0 -1
- package/dist/foundation/supervision/paths.js +0 -1
- package/dist/foundation/supervision/server.d.ts +0 -46
- package/dist/foundation/supervision/server.js +0 -606
- package/dist/integrations/pi/engine/offline-settings-port.d.ts +0 -23
- package/dist/integrations/pi/engine/offline-settings-port.js +0 -54
- package/dist/native/darwin-arm64/manifest.json +0 -23
- package/dist/native/darwin-arm64/process-guardian.node +0 -0
- package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +0 -10500
- package/dist/native/darwin-arm64/terminal-host.manifest.json +0 -46
- package/dist/native/darwin-arm64/terminal-host.node +0 -0
- package/dist/native/darwin-x64/manifest.json +0 -23
- package/dist/native/darwin-x64/process-guardian.node +0 -0
- package/dist/native/darwin-x64/terminal-host-THIRD-PARTY-NOTICES.txt +0 -10737
- package/dist/native/darwin-x64/terminal-host.manifest.json +0 -46
- package/dist/native/darwin-x64/terminal-host.node +0 -0
- package/dist/native/linux-x64/manifest.json +0 -23
- package/dist/native/linux-x64/process-guardian.node +0 -0
- package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +0 -11226
- package/dist/native/linux-x64/terminal-host.manifest.json +0 -46
- package/dist/native/linux-x64/terminal-host.node +0 -0
- package/dist/native/win32-x64/manifest.json +0 -23
- package/dist/native/win32-x64/process-guardian.node +0 -0
- package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +0 -10757
- package/dist/native/win32-x64/terminal-host.manifest.json +0 -46
- package/dist/native/win32-x64/terminal-host.node +0 -0
- package/docs/architecture/evidence/resident-tabs/linux-x64.md +0 -37
- package/docs/architecture/evidence/resident-tabs/macos-arm64.md +0 -36
- package/docs/architecture/evidence/resident-tabs/macos-x64.md +0 -35
- package/docs/architecture/evidence/resident-tabs/windows-x64.md +0 -36
- package/docs/architecture/evidence/terminal-host-spike/manual-one-pane-terminal-host.md +0 -70
- package/docs/architecture/evidence/terminal-host-spike/manual-two-by-two-basic-terminal-host.md +0 -32
- package/docs/architecture/evidence/terminal-host-spike/milestone-baseline.json +0 -115
- package/docs/architecture/evidence/terminal-host-spike/owned-ui-foundation-prerequisite.json +0 -48
- package/docs/architecture/evidence/terminal-host-spike/terminal-host-provenance.json +0 -125
- package/docs/architecture/evidence/terminal-host-spike/terminal-host-task-5-1.json +0 -56
- package/docs/architecture/evidence/terminal-host-spike/terminal-host-task-5-3.json +0 -174
- package/docs/architecture/evidence/terminal-host-spike/terminal-spike-acceptance-record.json +0 -18
- package/docs/architecture/native-fixture-isolation.md +0 -23
- package/docs/architecture/process-guardian-provenance.md +0 -36
- package/docs/architecture/resident-tabs-requirements.md +0 -18
- package/docs/architecture/resident-tabs.md +0 -668
- package/docs/architecture/terminal-host-proof-gate.md +0 -34
- package/docs/architecture/terminal-host-provenance.md +0 -81
- package/docs/architecture/terminal-host-spike-evidence.md +0 -39
- package/docs/launch-recovery.md +0 -23
- package/docs/manual-launch-instance-acceptance.md +0 -43
- package/docs/manual-resident-tabs-acceptance.md +0 -51
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { access, lstat, readFile, realpath, rename } from "node:fs/promises";
|
|
3
|
-
import {
|
|
4
|
-
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
5
4
|
import crossSpawn from "cross-spawn";
|
|
6
5
|
import { valid as validSemver } from "semver";
|
|
7
6
|
import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
|
|
8
7
|
import { earlyAccentPalette, earlyTerminalAppearance, earlyTerminalColorMode, resolveEarlyAccentColor } from "./early-accent.js";
|
|
9
8
|
import { SHARED_PROGRESS_ACCENT_ANSI } from "./progress-palette.generated.js";
|
|
10
9
|
import { createRegistryFetcher, invalidateReleaseCheckCache, isNewerRelease, queryPublishedVersions, releaseChannelOf, } from "./latest-release.js";
|
|
11
|
-
import { certifyMaterializedRelease
|
|
12
|
-
import {
|
|
13
|
-
import { resolveCohortEndpoint, resolveProductPaths } from "../lifecycle/index.js";
|
|
14
|
-
import { encodeFrame, LineFrameDecoder } from "../protocol/index.js";
|
|
10
|
+
import { certifyMaterializedRelease } from "./bootstrap.js";
|
|
11
|
+
import { resolveProductPaths } from "../lifecycle/index.js";
|
|
15
12
|
import { CohortStateStore } from "./cohort-state.js";
|
|
16
|
-
import { cleanupVerifiedOwner, processIsAlive } from "./process-cleanup.js";
|
|
17
13
|
import { materializeRelease, readMaterializedRelease } from "./release-store.js";
|
|
18
14
|
import { scheduleReleaseCleanup } from "./release-gc.js";
|
|
19
|
-
import { UPDATE_ACTIVATION_CONTRACT,
|
|
15
|
+
import { UPDATE_ACTIVATION_CONTRACT, activateInstalledRelease, delegateActivation, readActivationContracts, } from "./update-activation.js";
|
|
20
16
|
import { UpdateTransactionStore } from "./update-transaction.js";
|
|
21
17
|
import { acquireUpdateExecutionLease, reclaimDeadLease } from "./update-execution-lease.js";
|
|
22
18
|
import { npmPrefixForGlobalRoot, removeUpdateRecoveryCapsule, resolveNpmCli, runProtectedPackageReplacement, updateLauncherPaths, updateNpmInstallArguments, } from "./update-recovery.js";
|
|
@@ -37,7 +33,6 @@ const defaultFileSystem = {
|
|
|
37
33
|
*/
|
|
38
34
|
export const PACKAGE_UNLOCK_PATIENCE_MS = 15_000;
|
|
39
35
|
export const UPDATE_ALREADY_RUNNING_EXIT_CODE = 75;
|
|
40
|
-
export const UPDATE_ACTIVATION_BLOCKED_EXIT_CODE = 76;
|
|
41
36
|
/** A named preview reads the registry's whole package document, which is larger than a dist-tag lookup. */
|
|
42
37
|
const PREVIEW_LOOKUP_TIMEOUT_MS = 30_000;
|
|
43
38
|
const PACKAGE_UNLOCK_INITIAL_DELAY_MS = 100;
|
|
@@ -53,7 +48,7 @@ export function isPackageLockError(error) {
|
|
|
53
48
|
/** What the user reads when the package stayed held for the whole patience window. */
|
|
54
49
|
export function lockedPackageDiagnostic(packageRoot, waitedMs, attempts, error) {
|
|
55
50
|
const seconds = Math.max(1, Math.round(waitedMs / 1_000));
|
|
56
|
-
return PRODUCT_TEXT.diagnostic(`package remained locked for ${seconds}s
|
|
51
|
+
return PRODUCT_TEXT.diagnostic(`package remained locked for ${seconds}s (${attempts} checks): ${errorMessage(error)}. `
|
|
57
52
|
+ `Another program is holding a file under ${packageRoot}: an antivirus scan, a file browser or terminal open inside it, `
|
|
58
53
|
+ `or a session still shutting down. Close it and run the update again; nothing was changed.`);
|
|
59
54
|
}
|
|
@@ -87,103 +82,14 @@ export function createNpmProcessRunner(platform = process.platform) {
|
|
|
87
82
|
} });
|
|
88
83
|
});
|
|
89
84
|
}
|
|
90
|
-
|
|
91
|
-
* What an update does about the owner it found. A cohort running from retained immutable
|
|
92
|
-
* content keeps its work: the installation replaces files it does not read, and it listens on
|
|
93
|
-
* its own endpoint. A cohort running from the mutable installation is the one exception,
|
|
94
|
-
* because that installation is exactly what is about to be replaced under it.
|
|
95
|
-
*/
|
|
96
|
-
async function verifyAutomaticUpdateSafety(paths, stateStore, environment) {
|
|
97
|
-
const state = await stateStore.read();
|
|
98
|
-
const owner = await readActiveEndpoint(paths, state.references.active, environment);
|
|
99
|
-
if (!owner)
|
|
100
|
-
return;
|
|
101
|
-
const ownership = await probeOwnership(owner.metadata);
|
|
102
|
-
if (ownership === "dead")
|
|
103
|
-
return;
|
|
104
|
-
if (ownership !== "live-verified") {
|
|
105
|
-
throw new UpdateAutomaticDeferred(PRODUCT_TEXT.diagnostic(`automatic update deferred because supervisor ownership is ${ownership}`));
|
|
106
|
-
}
|
|
107
|
-
if (!await canonicalImmutableRoot(paths.dataDir, owner.metadata.releaseRoot)) {
|
|
108
|
-
throw new UpdateAutomaticDeferred(PRODUCT_TEXT.diagnostic("automatic update deferred because the running session is not using a retained immutable release; run the update command manually"));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
export function planUpdateOwnership(ownership, runsFromRetainedRelease) {
|
|
112
|
-
if (ownership === "dead")
|
|
113
|
-
return "clean-dead-record";
|
|
114
|
-
return runsFromRetainedRelease ? "leave-running" : "end-session";
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* The endpoint of the active cohort, together with where it is recorded, so a caller that
|
|
118
|
-
* finds it can also clean it up. A release that predates cohort-scoped endpoints published
|
|
119
|
-
* one endpoint for the runtime directory, and that one is recognized too.
|
|
120
|
-
*/
|
|
121
|
-
async function readActiveEndpoint(paths, activeReleaseId, environment) {
|
|
122
|
-
if (activeReleaseId !== null) {
|
|
123
|
-
const cohort = resolveCohortEndpoint(paths, activeReleaseId, environment);
|
|
124
|
-
const metadata = await readEndpointMetadata(cohort.endpointMetadataPath);
|
|
125
|
-
if (metadata)
|
|
126
|
-
return { metadata, paths: cohort };
|
|
127
|
-
}
|
|
128
|
-
const legacyPaths = { endpoint: paths.endpoint, endpointMetadataPath: paths.endpointMetadataPath };
|
|
129
|
-
const legacy = await readEndpointMetadata(legacyPaths.endpointMetadataPath);
|
|
130
|
-
return legacy ? { metadata: legacy, paths: legacyPaths } : null;
|
|
131
|
-
}
|
|
132
|
-
export function createUpdateLifecycleCoordinator(environment = process.env, fileSystem = defaultFileSystem, output = defaultOutput, patience = {}) {
|
|
85
|
+
export function createUpdateLifecycleCoordinator(environment = process.env, fileSystem = defaultFileSystem, _output = defaultOutput, patience = {}) {
|
|
133
86
|
const paths = resolveProductPaths(environment);
|
|
134
87
|
const stateStore = new CohortStateStore(paths.dataDir);
|
|
135
88
|
return {
|
|
136
89
|
async targetIsActive(targetVersion) {
|
|
137
90
|
const state = await stateStore.read(), activeId = state.references.active;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const endpoint = await readActiveEndpoint(paths, activeId, environment);
|
|
141
|
-
return endpoint?.metadata.releaseId === activeId && await probeOwnership(endpoint.metadata) === "live-verified";
|
|
142
|
-
},
|
|
143
|
-
async verifyAutomaticSafety() { await verifyAutomaticUpdateSafety(paths, stateStore, environment); },
|
|
144
|
-
async shutdownVerifiedOwners(targetVersion) {
|
|
145
|
-
const state = await stateStore.read();
|
|
146
|
-
const activeId = state.references.active;
|
|
147
|
-
const priorActiveVersion = activeId ? state.releases[activeId]?.packageVersion ?? null : null;
|
|
148
|
-
const owner = await readActiveEndpoint(paths, activeId, environment);
|
|
149
|
-
if (!owner)
|
|
150
|
-
return { priorActiveVersion };
|
|
151
|
-
const endpoint = owner.metadata;
|
|
152
|
-
const ownership = await probeOwnership(endpoint);
|
|
153
|
-
if (ownership !== "live-verified" && ownership !== "dead") {
|
|
154
|
-
throw new Error(PRODUCT_TEXT.diagnostic(`refused update shutdown because supervisor ownership is ${ownership}`));
|
|
155
|
-
}
|
|
156
|
-
const plan = planUpdateOwnership(ownership, await canonicalImmutableRoot(paths.dataDir, endpoint.releaseRoot));
|
|
157
|
-
if (plan === "clean-dead-record") {
|
|
158
|
-
await removeEndpointArtifacts(owner.paths.endpointMetadataPath, owner.paths.endpoint, endpoint);
|
|
159
|
-
return { priorActiveVersion };
|
|
160
|
-
}
|
|
161
|
-
// Rationale: leaving sessions alone is the expected outcome, and saying so mid-update tears
|
|
162
|
-
// the progress bar; only ending a session (below) is worth interrupting it for.
|
|
163
|
-
if (plan === "leave-running")
|
|
164
|
-
return { priorActiveVersion };
|
|
165
|
-
// Rationale: say whose work is ending before it ends.
|
|
166
|
-
const live = endpoint.ownership.liveInstanceIds.length;
|
|
167
|
-
if (live > 0) {
|
|
168
|
-
output.stderr(`${PRODUCT_TEXT.diagnostic(`ending ${live === 1 ? "one session" : `${live} sessions`} that run from the installation being replaced; a session started by an installed release would have been left alone.`)}\n`);
|
|
169
|
-
}
|
|
170
|
-
const identity = await requestUpdateShutdown(endpoint, targetVersion, 2_000);
|
|
171
|
-
if (!identity.accepted && processIsAlive(endpoint.pid)) {
|
|
172
|
-
// Security: explicit update consent permits bounded cleanup of an authenticated
|
|
173
|
-
// older owner that predates the update-shutdown message.
|
|
174
|
-
const cleanup = await cleanupVerifiedOwner(endpoint, { allowLiveInstances: true, reason: "legacy-mutable-install" });
|
|
175
|
-
if (!cleanup.terminated)
|
|
176
|
-
throw new Error(`verified ${PRODUCT_TEXT.displayName} owner ${endpoint.pid} rejected shutdown and could not be terminated: ${identity.reason}`);
|
|
177
|
-
}
|
|
178
|
-
else if (identity.accepted) {
|
|
179
|
-
await waitForProcessExit(endpoint.pid, 3_000).catch(async () => {
|
|
180
|
-
const cleanup = await cleanupVerifiedOwner(endpoint, { allowLiveInstances: true, reason: "legacy-mutable-install" });
|
|
181
|
-
if (!cleanup.terminated)
|
|
182
|
-
throw new Error(`verified ${PRODUCT_TEXT.displayName} owner ${endpoint.pid} did not terminate`);
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
await removeEndpointArtifacts(owner.paths.endpointMetadataPath, owner.paths.endpoint, endpoint);
|
|
186
|
-
return { priorActiveVersion };
|
|
91
|
+
const active = activeId === null ? undefined : state.releases[activeId];
|
|
92
|
+
return active?.approval === "approved" && active.packageVersion === targetVersion;
|
|
187
93
|
},
|
|
188
94
|
async verifyPackageUnlocked(packageRoot) {
|
|
189
95
|
if (fileSystem.access)
|
|
@@ -196,7 +102,7 @@ export function createUpdateLifecycleCoordinator(environment = process.env, file
|
|
|
196
102
|
const startedAt = now();
|
|
197
103
|
let delay = PACKAGE_UNLOCK_INITIAL_DELAY_MS;
|
|
198
104
|
let attempts = 0;
|
|
199
|
-
// Rationale: a holder found
|
|
105
|
+
// Rationale: a holder found before the replacement is almost always transient,
|
|
200
106
|
// so one failed rename is a reason to look again, not to abandon an update that has not
|
|
201
107
|
// yet changed anything. The wait is bounded so a genuine lock still fails clearly.
|
|
202
108
|
const withPatience = async (from, to, describeFailure) => {
|
|
@@ -236,13 +142,8 @@ export function createUpdateLifecycleCoordinator(environment = process.env, file
|
|
|
236
142
|
else
|
|
237
143
|
await activateInstalledRelease(request, callbacks);
|
|
238
144
|
},
|
|
239
|
-
async activatePrepared(targetVersion, phase) { await retryPreparedActivation(paths.dataDir, environment, targetVersion, phase); },
|
|
240
145
|
};
|
|
241
146
|
}
|
|
242
|
-
async function retryPreparedActivation(dataDir, environment, targetVersion, phase) {
|
|
243
|
-
await (await import("./prepared-update-activation.js"))
|
|
244
|
-
.activatePreparedRelease({ dataDir, targetVersion, environment }, { phase });
|
|
245
|
-
}
|
|
246
147
|
const PROGRESS_BAR_WIDTH = 38;
|
|
247
148
|
const PROGRESS_TICK_MS = 200;
|
|
248
149
|
/**
|
|
@@ -513,10 +414,10 @@ async function withUpdateExecutionLease(input, options, output, operation) {
|
|
|
513
414
|
let lease = await acquireUpdateExecutionLease(input);
|
|
514
415
|
if (lease.status === "interrupted") {
|
|
515
416
|
// Rationale: the owner that targeted another version is gone. Its durable transaction is rolled
|
|
516
|
-
// back to the
|
|
417
|
+
// back to the release that ran before it, so this request proceeds as an ordinary update of its
|
|
517
418
|
// own target instead of reporting a dead attempt as in progress.
|
|
518
419
|
const interrupted = lease.owner.targetVersion;
|
|
519
|
-
const rollback = await
|
|
420
|
+
const rollback = await rollbackPriorRelease(input.dataDir, lease.transaction.priorActiveReleaseId)
|
|
520
421
|
.catch(error => `rollback failed: ${errorMessage(error)}`);
|
|
521
422
|
const store = new UpdateTransactionStore(input.dataDir);
|
|
522
423
|
await store.finish("rolled-back", `interrupted ${interrupted} update settled by ${input.targetVersion}; ${rollback}`);
|
|
@@ -610,8 +511,6 @@ export async function runSelfUpdate(options) {
|
|
|
610
511
|
let transaction = await transactionStore.read();
|
|
611
512
|
let npmCli;
|
|
612
513
|
try {
|
|
613
|
-
if (mode === "automatic")
|
|
614
|
-
await lifecycle.verifyAutomaticSafety?.();
|
|
615
514
|
if ((!transaction || transaction.status === "completed") && await lifecycle.targetIsActive(targetVersion)) {
|
|
616
515
|
await maintenance();
|
|
617
516
|
await transactionStore.clearCompleted();
|
|
@@ -650,10 +549,7 @@ export async function runSelfUpdate(options) {
|
|
|
650
549
|
priorActiveReleaseId,
|
|
651
550
|
});
|
|
652
551
|
if (phaseBefore(transaction.phase, "ownership-released")) {
|
|
653
|
-
await measure("ownership-release", async () => {
|
|
654
|
-
await lifecycle.shutdownVerifiedOwners(targetVersion);
|
|
655
|
-
await lifecycle.verifyPackageUnlocked(packageRoot);
|
|
656
|
-
});
|
|
552
|
+
await measure("ownership-release", async () => { await lifecycle.verifyPackageUnlocked(packageRoot); });
|
|
657
553
|
transaction = await transactionStore.advance("ownership-released");
|
|
658
554
|
}
|
|
659
555
|
progress.set(15, 70);
|
|
@@ -711,12 +607,7 @@ export async function runSelfUpdate(options) {
|
|
|
711
607
|
transaction = await transactionStore.advance("package-installed");
|
|
712
608
|
}
|
|
713
609
|
}
|
|
714
|
-
progress.set(70,
|
|
715
|
-
// Concurrency: ownership can be reacquired after an interrupted installation (for
|
|
716
|
-
// example, if bare A1 is launched before the update is resumed). Recheck
|
|
717
|
-
// immediately before activation so recovery cannot start a second cohort.
|
|
718
|
-
await measure("ownership-release", async () => { await lifecycle.shutdownVerifiedOwners(targetVersion); });
|
|
719
|
-
progress.set(75, MATERIALIZE_PROGRESS.from);
|
|
610
|
+
progress.set(70, MATERIALIZE_PROGRESS.from);
|
|
720
611
|
let activationPhaseStartedAt = now();
|
|
721
612
|
const recordActivationPhase = async (phase) => {
|
|
722
613
|
options.onPhaseTiming?.({ phase, durationMs: Math.max(0, now() - activationPhaseStartedAt) });
|
|
@@ -724,7 +615,7 @@ export async function runSelfUpdate(options) {
|
|
|
724
615
|
progress.set(ACTIVATION_PROGRESS[phase].at, ACTIVATION_PROGRESS[phase].creepTo);
|
|
725
616
|
activationPhaseStartedAt = now();
|
|
726
617
|
};
|
|
727
|
-
await
|
|
618
|
+
await lifecycle.activateInstalled(packageRoot, targetVersion, recordActivationPhase, ({ completed, total }) => progress.set(MATERIALIZE_PROGRESS.from
|
|
728
619
|
+ (MATERIALIZE_PROGRESS.to - MATERIALIZE_PROGRESS.from) * (total > 0 ? Math.min(1, completed / total) : 0)), state => {
|
|
729
620
|
if (state === "started") {
|
|
730
621
|
activationPhaseStartedAt = now();
|
|
@@ -736,9 +627,7 @@ export async function runSelfUpdate(options) {
|
|
|
736
627
|
progress.set(99);
|
|
737
628
|
}
|
|
738
629
|
});
|
|
739
|
-
options.onPhaseTiming?.({ phase: "supervisor-verified", durationMs: Math.max(0, now() - activationPhaseStartedAt) });
|
|
740
630
|
const transactionStartedAt = now();
|
|
741
|
-
await transactionStore.advance("supervisor-verified");
|
|
742
631
|
if (transaction.recovery?.capsulePath)
|
|
743
632
|
await removeUpdateRecoveryCapsule(paths.dataDir, transaction.transactionId);
|
|
744
633
|
// Invariant: successful output follows the durable cleanup disposition. Slow recursive
|
|
@@ -755,18 +644,13 @@ export async function runSelfUpdate(options) {
|
|
|
755
644
|
catch (error) {
|
|
756
645
|
progress.clear();
|
|
757
646
|
const message = errorMessage(error);
|
|
758
|
-
if (error instanceof UpdateAutomaticDeferred)
|
|
759
|
-
return 1;
|
|
760
|
-
const blockedExit = await preparedActivationExit(error, transaction, mode, output, transactionStore, targetVersion, paths.configDir);
|
|
761
|
-
if (blockedExit !== null)
|
|
762
|
-
return blockedExit;
|
|
763
647
|
if (error instanceof UpdatePreflightFailure) {
|
|
764
648
|
output.stderr(`${PRODUCT_TEXT.diagnostic(`update could not start safely: ${message}. Nothing was changed. Retry with npm x -y -- ${PRODUCT_PACKAGE}-install`)}\n`);
|
|
765
649
|
return 1;
|
|
766
650
|
}
|
|
767
651
|
const rollback = options.lifecycle
|
|
768
652
|
? "previous test lifecycle retained"
|
|
769
|
-
: await
|
|
653
|
+
: await rollbackPriorRelease(paths.dataDir, transaction?.priorActiveReleaseId ?? null).catch(rollbackError => `rollback failed: ${errorMessage(rollbackError)}`);
|
|
770
654
|
if (transaction)
|
|
771
655
|
await transactionStore.finish(rollback === "rolled back" ? "rolled-back" : "failed", `${message}; ${rollback}`);
|
|
772
656
|
output.stderr(updateFailureLine(message, rollback, transactionStore.path));
|
|
@@ -790,35 +674,6 @@ export function assertUpdatePerformanceBudget(evidence, maximumPostNpmDurationMs
|
|
|
790
674
|
if (failures.length > 0)
|
|
791
675
|
throw new Error(`update performance budget failed: ${failures.join("; ")}`);
|
|
792
676
|
}
|
|
793
|
-
async function requestUpdateShutdown(metadata, targetVersion, timeoutMs) {
|
|
794
|
-
if (!processIsAlive(metadata.pid))
|
|
795
|
-
return { accepted: false, reason: "recorded owner is dead" };
|
|
796
|
-
return await new Promise(resolvePromise => {
|
|
797
|
-
const socket = connect(metadata.endpoint);
|
|
798
|
-
const decoder = new LineFrameDecoder();
|
|
799
|
-
let settled = false;
|
|
800
|
-
const finish = (value) => {
|
|
801
|
-
if (settled)
|
|
802
|
-
return;
|
|
803
|
-
settled = true;
|
|
804
|
-
socket.destroy();
|
|
805
|
-
resolvePromise(value);
|
|
806
|
-
};
|
|
807
|
-
socket.once("error", () => finish({ accepted: false, reason: "verified endpoint did not respond" }));
|
|
808
|
-
socket.once("connect", () => socket.write(encodeFrame({ type: "release-update-ownership", bootNonce: metadata.bootNonce, targetVersion })));
|
|
809
|
-
socket.on("data", chunk => {
|
|
810
|
-
try {
|
|
811
|
-
const message = decoder.push(chunk)[0];
|
|
812
|
-
if (message?.type === "release-update-result")
|
|
813
|
-
finish({ accepted: message.accepted === true, reason: String(message.reason ?? "shutdown rejected") });
|
|
814
|
-
}
|
|
815
|
-
catch {
|
|
816
|
-
finish({ accepted: false, reason: "invalid shutdown response" });
|
|
817
|
-
}
|
|
818
|
-
});
|
|
819
|
-
setTimeout(() => finish({ accepted: false, reason: "verified endpoint shutdown timed out" }), timeoutMs).unref();
|
|
820
|
-
});
|
|
821
|
-
}
|
|
822
677
|
async function runNpm(runner, arguments_, captureStdout, output, action, reportNonzero = true) {
|
|
823
678
|
let result;
|
|
824
679
|
try {
|
|
@@ -843,51 +698,31 @@ function reportChildDiagnostics(output, result) {
|
|
|
843
698
|
output.stderr(`${text}\n`);
|
|
844
699
|
return true;
|
|
845
700
|
}
|
|
846
|
-
async function
|
|
701
|
+
async function rollbackPriorRelease(dataDir, priorReleaseId) {
|
|
847
702
|
if (!priorReleaseId)
|
|
848
|
-
return "no prior
|
|
703
|
+
return "no prior release was available for rollback";
|
|
849
704
|
const stateStore = new CohortStateStore(dataDir);
|
|
850
705
|
const state = await stateStore.read();
|
|
851
706
|
const prior = state.releases[priorReleaseId];
|
|
852
707
|
if (!prior || prior.approval !== "approved")
|
|
853
|
-
return "prior
|
|
708
|
+
return "prior release is not a verified rollback candidate";
|
|
854
709
|
if (state.references.active !== priorReleaseId) {
|
|
855
710
|
if (state.references.rollback === priorReleaseId)
|
|
856
|
-
await stateStore.rollback(
|
|
711
|
+
await stateStore.rollback();
|
|
857
712
|
else
|
|
858
|
-
throw new Error(`prior release ${priorReleaseId} is not the recorded rollback
|
|
713
|
+
throw new Error(`prior release ${priorReleaseId} is not the recorded rollback release`);
|
|
859
714
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
await ensureSupervisor(release, environment);
|
|
715
|
+
// Invariant: rollback selects only a prior release whose retained content still verifies.
|
|
716
|
+
await readMaterializedRelease(prior.releaseRoot);
|
|
863
717
|
return "rolled back";
|
|
864
718
|
}
|
|
865
|
-
async function activateUpdateTransaction(lifecycle, transaction, packageRoot, targetVersion, phase, materializing, warmup) {
|
|
866
|
-
if (!phaseBefore(transaction.phase, "certified") && phaseBefore(transaction.phase, "active-reference-committed")
|
|
867
|
-
&& lifecycle.activatePrepared !== undefined)
|
|
868
|
-
await lifecycle.activatePrepared(targetVersion, phase);
|
|
869
|
-
else
|
|
870
|
-
await lifecycle.activateInstalled(packageRoot, targetVersion, phase, materializing, warmup);
|
|
871
|
-
}
|
|
872
|
-
async function preparedActivationExit(error, transaction, mode, output, journal, targetVersion, configDir) {
|
|
873
|
-
if (!(error instanceof UpdateActivationBlockedError) || transaction === null || phaseBefore(transaction.phase, "certified"))
|
|
874
|
-
return null;
|
|
875
|
-
await journal.recordActiveError?.(error.message);
|
|
876
|
-
if (mode === "automatic")
|
|
877
|
-
return UPDATE_ACTIVATION_BLOCKED_EXIT_CODE;
|
|
878
|
-
// Invariant: a certified candidate waiting only for resident continuity is an installed update;
|
|
879
|
-
// the next startup check retries activation without another installation.
|
|
880
|
-
await invalidateReleaseCheckCache(configDir).catch(() => undefined);
|
|
881
|
-
output.stdout(`${PRODUCT_TEXT.commandName} updated to ${targetVersion}; it takes over automatically once running Agents can switch\n`);
|
|
882
|
-
return 0;
|
|
883
|
-
}
|
|
884
719
|
/** One reason and one next step; routine rollback detail stays in the journal. */
|
|
885
720
|
function updateFailureLine(message, rollback, journalPath) {
|
|
886
721
|
const note = rollback.startsWith("rollback failed") ? ` (${rollback})` : "";
|
|
887
722
|
return `${PRODUCT_TEXT.diagnostic(`update failed: ${message}${note}. Run ${PRODUCT_TEXT.commandName} update to retry. Details: ${journalPath}`)}\n`;
|
|
888
723
|
}
|
|
889
724
|
function phaseBefore(current, target) {
|
|
890
|
-
const phases = ["shutdown-intent", "ownership-released", "package-installed", "materialized", "certified", "active-reference-committed"
|
|
725
|
+
const phases = ["shutdown-intent", "ownership-released", "package-installed", "materialized", "certified", "active-reference-committed"];
|
|
891
726
|
return phases.indexOf(current) < phases.indexOf(target);
|
|
892
727
|
}
|
|
893
728
|
class UpdateFailure extends Error {
|
|
@@ -896,18 +731,6 @@ class UpdateFailure extends Error {
|
|
|
896
731
|
}
|
|
897
732
|
class UpdatePreflightFailure extends Error {
|
|
898
733
|
}
|
|
899
|
-
class UpdateAutomaticDeferred extends Error {
|
|
900
|
-
}
|
|
901
|
-
async function canonicalImmutableRoot(dataDir, releaseRoot) {
|
|
902
|
-
try {
|
|
903
|
-
const [store, selected] = await Promise.all([realpath(resolve(dataDir, "releases")), realpath(releaseRoot)]);
|
|
904
|
-
const fromStore = relative(store, selected);
|
|
905
|
-
return fromStore.length > 0 && fromStore !== ".." && !fromStore.startsWith(`..${sep}`) && !isAbsolute(fromStore);
|
|
906
|
-
}
|
|
907
|
-
catch {
|
|
908
|
-
return false;
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
734
|
function samePath(left, right) {
|
|
912
735
|
return process.platform === "win32" ? resolve(left).toLowerCase() === resolve(right).toLowerCase() : resolve(left) === resolve(right);
|
|
913
736
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
export interface
|
|
1
|
+
/** One held authority; `bindCreated` moves a reservation onto the file it created. */
|
|
2
|
+
export interface WriterLock {
|
|
3
3
|
readonly bound: boolean;
|
|
4
4
|
bindCreated(path: string): void;
|
|
5
5
|
release(): void;
|
|
6
6
|
}
|
|
7
7
|
export interface LockBackend {
|
|
8
|
-
acquire(path: string):
|
|
8
|
+
acquire(path: string): WriterLock;
|
|
9
9
|
}
|
|
10
10
|
/** Reports that a canonical session identity already has a live writer. */
|
|
11
11
|
export declare class SessionWriterConflict extends Error {
|
|
@@ -19,12 +19,20 @@ export interface SessionWriterHandle {
|
|
|
19
19
|
bind(createdPath?: string): void;
|
|
20
20
|
release(): void;
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* A crashed writer is not released by the kernel; its lock becomes stale once its holder stops
|
|
24
|
+
* refreshing it. The bound keeps resume after a crash a matter of seconds.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SESSION_WRITER_STALE_MS = 10000;
|
|
27
|
+
/**
|
|
28
|
+
* Locks live in one per-user directory keyed by canonical identity, not beside the session, so two
|
|
29
|
+
* spellings of one file (case, short names, links, hard links) contend for the same lock.
|
|
30
|
+
*/
|
|
31
|
+
export declare function lockfileBackend(root?: string, staleMs?: number): LockBackend;
|
|
24
32
|
export declare function acquireExisting(path: string): SessionWriterHandle;
|
|
25
33
|
export declare function reserveNew(parent: string, name: string): SessionWriterHandle;
|
|
26
34
|
export declare function acquireSessionWriter(path: string): SessionWriterHandle;
|
|
27
35
|
export declare function bind(handle: SessionWriterHandle, createdPath?: string): void;
|
|
28
36
|
export declare function release(handle: SessionWriterHandle): void;
|
|
29
|
-
/** Test-only backend seam; passing undefined restores
|
|
37
|
+
/** Test-only backend seam; passing undefined restores the default backend. */
|
|
30
38
|
export declare function setSessionWriterLockBackendForTest(value: LockBackend | undefined): void;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createRequire } from "node:module";
|
|
1
|
+
import { existsSync, mkdirSync, realpathSync, statSync } from "node:fs";
|
|
3
2
|
import { tmpdir } from "node:os";
|
|
4
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
4
|
import { createHash } from "node:crypto";
|
|
7
|
-
import
|
|
5
|
+
import lockfile from "proper-lockfile";
|
|
8
6
|
/** Reports that a canonical session identity already has a live writer. */
|
|
9
7
|
export class SessionWriterConflict extends Error {
|
|
10
8
|
path;
|
|
@@ -16,107 +14,60 @@ export class SessionWriterConflict extends Error {
|
|
|
16
14
|
this.holderKind = holderKind;
|
|
17
15
|
}
|
|
18
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* A crashed writer is not released by the kernel; its lock becomes stale once its holder stops
|
|
19
|
+
* refreshing it. The bound keeps resume after a crash a matter of seconds.
|
|
20
|
+
*/
|
|
21
|
+
export const SESSION_WRITER_STALE_MS = 10_000;
|
|
19
22
|
let backendOverride;
|
|
20
23
|
let cachedBackend;
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const override = env[PRODUCT_IDENTITY.environment.developmentInstanceId]
|
|
27
|
-
? env[PRODUCT_IDENTITY.environment.terminalHostPath]?.trim() || undefined
|
|
28
|
-
: undefined;
|
|
29
|
-
const local = exists(own)
|
|
30
|
-
? [own]
|
|
31
|
-
: [resolve(cwd, "dist", "native", `${process.platform}-${process.arch}`, addon), resolve(cwd, ".artifacts", "terminal-host", addon)];
|
|
32
|
-
return override ? [override, ...local] : local;
|
|
33
|
-
}
|
|
34
|
-
function loadNativeBackend() {
|
|
35
|
-
const require = createRequire(import.meta.url);
|
|
36
|
-
for (const candidate of nativeCandidates()) {
|
|
37
|
-
if (!existsSync(candidate))
|
|
38
|
-
continue;
|
|
39
|
-
try {
|
|
40
|
-
const loaded = require(candidate);
|
|
41
|
-
if (loaded.SessionLock?.acquire)
|
|
42
|
-
return loaded.SessionLock;
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// Compatibility: an addon for another Node ABI is unusable; try the next location.
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return undefined;
|
|
49
|
-
}
|
|
50
|
-
function processIsLive(pid) {
|
|
51
|
-
try {
|
|
52
|
-
process.kill(pid, 0);
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/** Development fallback. Installed releases carry the verified Node-API lock resolved above. */
|
|
60
|
-
function portableBackend() {
|
|
61
|
-
const root = join(tmpdir(), "a1-session-writer-locks");
|
|
62
|
-
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
24
|
+
/**
|
|
25
|
+
* Locks live in one per-user directory keyed by canonical identity, not beside the session, so two
|
|
26
|
+
* spellings of one file (case, short names, links, hard links) contend for the same lock.
|
|
27
|
+
*/
|
|
28
|
+
export function lockfileBackend(root = join(tmpdir(), "a1-session-writer-locks"), staleMs = SESSION_WRITER_STALE_MS) {
|
|
63
29
|
const authorityPath = (kind, key) => join(root, `${createHash("sha256").update(`${kind}\0${key}`).digest("hex")}.lock`);
|
|
30
|
+
// Platform: Windows file names are case-insensitive, and realpath canonicalizes only existing parents.
|
|
64
31
|
const locationKey = (path) => `${realpathSync.native(dirname(path))}\0${process.platform === "win32" ? basename(path).toLowerCase() : basename(path)}`;
|
|
65
32
|
const identityKey = (path) => {
|
|
66
|
-
const
|
|
67
|
-
const metadata = statSync(physical, { bigint: true });
|
|
33
|
+
const metadata = statSync(realpathSync.native(path), { bigint: true });
|
|
68
34
|
return `${metadata.dev}:${metadata.ino}`;
|
|
69
35
|
};
|
|
70
36
|
const acquireAuthority = (path) => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const record = JSON.parse(readFileSync(path, "utf8"));
|
|
82
|
-
if (typeof record.pid === "number" && !processIsLive(record.pid)) {
|
|
83
|
-
rmSync(path, { force: true });
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
// Security: malformed or racing lock evidence remains authoritative and fails closed.
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
37
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
38
|
+
// Rationale: the lock directory is the authority; no target file exists or is needed.
|
|
39
|
+
return lockfile.lockSync(path, {
|
|
40
|
+
realpath: false,
|
|
41
|
+
lockfilePath: path,
|
|
42
|
+
stale: staleMs,
|
|
43
|
+
// Rationale: a missed refresh means this process stalled; the next writer takes over, and the
|
|
44
|
+
// stalled one must not crash for it.
|
|
45
|
+
onCompromised: () => { },
|
|
46
|
+
});
|
|
94
47
|
};
|
|
95
48
|
return {
|
|
96
49
|
acquire(requested) {
|
|
97
50
|
const absolute = resolve(requested);
|
|
98
|
-
|
|
99
|
-
acquireAuthority(location);
|
|
100
|
-
let identity;
|
|
101
|
-
let identityAcquired = false;
|
|
51
|
+
let releaseCurrent = acquireAuthority(authorityPath("location", locationKey(absolute)));
|
|
102
52
|
let bound = false;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (identityKey(absolute) !== key) {
|
|
110
|
-
throw new Error("session identity changed while its writer lock was acquired");
|
|
111
|
-
}
|
|
112
|
-
rmSync(location, { force: true });
|
|
113
|
-
bound = true;
|
|
53
|
+
const bindTo = () => {
|
|
54
|
+
const key = identityKey(absolute);
|
|
55
|
+
const releaseIdentity = acquireAuthority(authorityPath("identity", key));
|
|
56
|
+
if (identityKey(absolute) !== key) {
|
|
57
|
+
releaseIdentity();
|
|
58
|
+
throw new Error("session identity changed while its writer lock was acquired");
|
|
114
59
|
}
|
|
60
|
+
// Invariant: the identity lock is held before the location lock goes, so there is no gap.
|
|
61
|
+
releaseCurrent();
|
|
62
|
+
releaseCurrent = releaseIdentity;
|
|
63
|
+
bound = true;
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
if (existsSync(absolute))
|
|
67
|
+
bindTo();
|
|
115
68
|
}
|
|
116
69
|
catch (error) {
|
|
117
|
-
|
|
118
|
-
if (identityAcquired && identity !== undefined)
|
|
119
|
-
rmSync(identity, { force: true });
|
|
70
|
+
releaseCurrent();
|
|
120
71
|
throw error;
|
|
121
72
|
}
|
|
122
73
|
let released = false;
|
|
@@ -126,29 +77,23 @@ function portableBackend() {
|
|
|
126
77
|
if (bound || resolve(created) !== absolute || !existsSync(absolute)) {
|
|
127
78
|
throw new Error("created session does not match its reservation");
|
|
128
79
|
}
|
|
129
|
-
|
|
130
|
-
identity = authorityPath("identity", key);
|
|
131
|
-
acquireAuthority(identity);
|
|
132
|
-
if (identityKey(absolute) !== key) {
|
|
133
|
-
rmSync(identity, { force: true });
|
|
134
|
-
identity = undefined;
|
|
135
|
-
throw new Error("created session identity changed during bind");
|
|
136
|
-
}
|
|
137
|
-
rmSync(location, { force: true });
|
|
138
|
-
bound = true;
|
|
80
|
+
bindTo();
|
|
139
81
|
},
|
|
140
82
|
release() {
|
|
141
83
|
if (released)
|
|
142
84
|
throw new Error("session lock was already released");
|
|
143
85
|
released = true;
|
|
144
|
-
|
|
86
|
+
try {
|
|
87
|
+
releaseCurrent();
|
|
88
|
+
}
|
|
89
|
+
catch { /* Compatibility: a reclaimed stale lock is already gone. */ }
|
|
145
90
|
},
|
|
146
91
|
};
|
|
147
92
|
},
|
|
148
93
|
};
|
|
149
94
|
}
|
|
150
95
|
function backend() {
|
|
151
|
-
return backendOverride ?? (cachedBackend ??=
|
|
96
|
+
return backendOverride ?? (cachedBackend ??= lockfileBackend());
|
|
152
97
|
}
|
|
153
98
|
function acquire(path) {
|
|
154
99
|
const absolute = resolve(path);
|
|
@@ -157,7 +102,7 @@ function acquire(path) {
|
|
|
157
102
|
lock = backend().acquire(absolute);
|
|
158
103
|
}
|
|
159
104
|
catch {
|
|
160
|
-
throw new SessionWriterConflict(absolute
|
|
105
|
+
throw new SessionWriterConflict(absolute);
|
|
161
106
|
}
|
|
162
107
|
let released = false;
|
|
163
108
|
return {
|
|
@@ -196,7 +141,7 @@ export function bind(handle, createdPath) {
|
|
|
196
141
|
export function release(handle) {
|
|
197
142
|
handle.release();
|
|
198
143
|
}
|
|
199
|
-
/** Test-only backend seam; passing undefined restores
|
|
144
|
+
/** Test-only backend seam; passing undefined restores the default backend. */
|
|
200
145
|
export function setSessionWriterLockBackendForTest(value) {
|
|
201
146
|
backendOverride = value;
|
|
202
147
|
cachedBackend = undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
2
|
-
export type StartupPhase = "command-invoked" | "bootstrap-start" | "bootstrap-selected" | "durable-validation-start" | "durable-validation-complete" | "
|
|
2
|
+
export type StartupPhase = "command-invoked" | "bootstrap-start" | "bootstrap-selected" | "durable-validation-start" | "durable-validation-complete" | "ui-entry" | "ui-modules-loaded" | "pi-services" | "resource-discovery" | "session-created" | "settings-loaded" | "first-input-ready-render";
|
|
3
3
|
export interface StartupTraceEvent {
|
|
4
4
|
readonly schema: typeof PRODUCT_IDENTITY.evidence.startupTraceSchema;
|
|
5
5
|
readonly traceId: string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The launcher's half of the unclean-exit protocol. The UI arms a notice file while it runs and
|
|
3
|
+
* removes it on a clean exit, so a notice that is still present when the UI ends means the UI was
|
|
4
|
+
* stopped unexpectedly: restore the terminal, print the notice as plain text, and consume it.
|
|
5
|
+
*/
|
|
6
|
+
export declare function deliverExitNotice(path: string, write: (text: string) => void, restore?: () => void): boolean;
|