gentle-pi 4.0.0 → 4.2.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/README.md +157 -18
- package/assets/agents/gentle-ai-explore.md +11 -4
- package/assets/agents/gentle-ai-verify.md +23 -4
- package/assets/agents/gentle-ai-worker.md +6 -6
- package/assets/install-wizard/index.html +47 -0
- package/assets/install-wizard/wizard.css +1279 -0
- package/assets/install-wizard/wizard.js +951 -0
- package/assets/orchestrator-delegation.md +70 -163
- package/assets/orchestrator-memory.md +11 -4
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-tracking.md +53 -0
- package/assets/orchestrator-verification.md +28 -0
- package/assets/orchestrator-writer.md +38 -0
- package/assets/orchestrator.md +29 -25
- package/assets/sounds/LICENSE.md +52 -0
- package/assets/sounds/attention.wav +0 -0
- package/assets/sounds/error.wav +0 -0
- package/assets/sounds/success.wav +0 -0
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +708 -22
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/auto-permission-answers.md +25 -0
- package/docs/auto-permission-evidence.md +39 -0
- package/docs/bundled-install.md +399 -0
- package/docs/delegated-verification.md +3 -5
- package/docs/gentle-agents-activity.md +513 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +90 -33
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +1 -1
- package/docs/permission-modes.md +127 -0
- package/docs/prompt-history.md +1 -1
- package/docs/readme-reference.md +310 -47
- package/docs/render-cache-performance.md +28 -0
- package/docs/session-profile-format.md +39 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/yolo-mode.md +11 -10
- package/extensions/ask-user-choice.ts +4 -0
- package/extensions/ask-user-question.ts +6 -1
- package/extensions/child-context.ts +11 -2
- package/extensions/child-safety.ts +4 -2
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +718 -87
- package/extensions/gentle-ai.ts +991 -263
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +271 -80
- package/extensions/gentle-todo.ts +123 -18
- package/extensions/history/index.ts +11 -6
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +91 -21
- package/extensions/skill-registry.ts +175 -46
- package/extensions/startup-banner.ts +11 -2
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +40 -1
- package/lib/agents-runner.ts +90 -15
- package/lib/auto-permission-answers.ts +133 -0
- package/lib/auto-permission-classifier.ts +459 -0
- package/lib/auto-permission-evidence.ts +88 -0
- package/lib/auto-permission-memory.ts +69 -0
- package/lib/auto-permission-runtime.ts +484 -0
- package/lib/auto-permission-tool-context.ts +61 -0
- package/lib/background-jobs-tools.ts +202 -0
- package/lib/background-jobs.ts +253 -0
- package/lib/background-monitor.ts +154 -0
- package/lib/bounded-writer-admission.ts +83 -10
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +14 -4
- package/lib/command-palette-catalog.ts +3 -2
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +104 -28
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-shell-bundled.ts +535 -0
- package/lib/gentle-shell-launcher.ts +75 -24
- package/lib/gentle-shell-startup-flow.ts +54 -0
- package/lib/gentle-shell-startup-update.ts +92 -0
- package/lib/gentle-shell-uninstall.ts +840 -0
- package/lib/gentle-shell-update-panel.ts +231 -0
- package/lib/herdr-activity.ts +183 -0
- package/lib/inprocess-reviewer.ts +3 -0
- package/lib/jobs-sidebar-state.ts +20 -0
- package/lib/jobs-view.ts +161 -0
- package/lib/model-price-ratio.ts +83 -0
- package/lib/model-routing-authority.ts +6 -1
- package/lib/nan-provider.ts +33 -11
- package/lib/native-review-cli.ts +194 -73
- package/lib/notification-audio-native.ts +162 -0
- package/lib/notification-audio-windows.ts +399 -0
- package/lib/notification-audio.ts +332 -0
- package/lib/notification-customize.ts +241 -0
- package/lib/notification-events.ts +113 -0
- package/lib/notification-policy.ts +148 -0
- package/lib/notification-pulse-client.ts +367 -0
- package/lib/notification-pulse-protocol.ts +305 -0
- package/lib/notification-pulse-stream.ts +213 -0
- package/lib/notification-pulse-worker.ts +66 -0
- package/lib/notification-scheduler.ts +173 -0
- package/lib/notification-service.ts +202 -0
- package/lib/notification-ui.ts +87 -0
- package/lib/odd-phase.ts +1 -1
- package/lib/orchestrator-alias.ts +20 -0
- package/lib/orchestrator-catalog.ts +79 -0
- package/lib/orchestrator-consultation.ts +61 -0
- package/lib/orchestrator-discovery.ts +49 -0
- package/lib/orchestrator-helper-consent.ts +122 -0
- package/lib/orchestrator-helper.ts +167 -0
- package/lib/orchestrator-message-card.ts +105 -0
- package/lib/orchestrator-presence.ts +115 -14
- package/lib/orchestrator-scope.ts +80 -0
- package/lib/orchestrator-state.ts +107 -0
- package/lib/orchestrator-tool-card.ts +280 -0
- package/lib/orchestrator-work-search.ts +218 -0
- package/lib/orchestrator-work.ts +79 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/review-candidate-view-owner.ts +8 -6
- package/lib/review-candidate-view.ts +3 -2
- package/lib/review-compact-contract.ts +2 -2
- package/lib/review-host-relay.ts +7 -2
- package/lib/review-integration-v2.ts +100 -22
- package/lib/review-publication-gate.ts +1 -1
- package/lib/review-risk-assessment.ts +77 -51
- package/lib/review-sidebar-state.ts +2 -1
- package/lib/review-transaction.ts +72 -3
- package/lib/runtime-metrics-children.ts +1 -1
- package/lib/runtime-metrics-native.ts +3 -2
- package/lib/selection-engine.ts +22 -7
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +261 -0
- package/lib/session-profile-persistence.ts +187 -0
- package/lib/session-worktree-registry.ts +74 -8
- package/lib/shell-bar.ts +41 -11
- package/lib/shell-changes-view.ts +40 -13
- package/lib/shell-changes.ts +88 -71
- package/lib/shell-prompt.ts +6 -4
- package/lib/shell-sidebar-layout.ts +5 -1
- package/lib/shell-todo.ts +81 -26
- package/lib/shell-usage.ts +4 -3
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/vim-editor-adapter.ts +27 -5
- package/lib/visual-customization-policy.ts +20 -9
- package/lib/visual-customize-view.ts +171 -20
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +198 -121
- package/package.json +5 -5
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +536 -0
- package/runtime/gentle-shell-launcher.mjs +74 -23
- package/runtime/gentle-shell-startup-flow.mjs +55 -0
- package/runtime/gentle-shell-startup-update.mjs +93 -0
- package/runtime/gentle-shell-uninstall.mjs +841 -0
- package/runtime/gentle-shell-update-panel.mjs +232 -0
- package/runtime/native-review-cli.mjs +191 -70
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +77 -51
- package/runtime/status-timing-diagnostics.mjs +202 -0
- package/runtime/telemetry-trigger.mjs +3 -1
- package/scripts/bootstrap.cmd +255 -0
- package/scripts/bootstrap.sh +168 -0
- package/scripts/build-distribution.mjs +157 -0
- package/scripts/build-installer-bundles.mjs +136 -0
- package/scripts/build-runtime-modules.mjs +7 -0
- package/scripts/bundled-install.mjs +1308 -0
- package/scripts/bundled-wizard.mjs +399 -0
- package/scripts/check-types.mjs +4 -6
- package/scripts/gentle-ai-installer.mjs +79 -32
- package/scripts/install-gentle-ai.mjs +48 -1
- package/scripts/install-tui-mode-setting.mjs +10 -1
- package/scripts/install-wizard-preview.mjs +154 -0
- package/scripts/installer-downloads.mjs +648 -0
- package/scripts/installer-preflight.mjs +387 -0
- package/scripts/installer-probes.mjs +564 -0
- package/scripts/installer-runner.mjs +1102 -0
- package/scripts/installer-server.mjs +881 -0
- package/scripts/installer-windows-artifacts.json +15 -0
- package/scripts/installer-windows.mjs +752 -0
- package/scripts/main-channel.mjs +452 -0
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/pi-update.mjs +223 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +22 -8
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +23 -5
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +82 -18
- package/skills/gentle-ai/SKILL.md +12 -11
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +12 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-protocol.test.ts +57 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +89 -4
- package/tests/append-system-prompt-route.test.ts +18 -1
- package/tests/auto-mode-human-answers.test.ts +68 -0
- package/tests/auto-mode-invariants.test.ts +276 -0
- package/tests/auto-mode-sdk.test.ts +84 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-answers.test.ts +170 -0
- package/tests/auto-permission-classifier.test.ts +634 -0
- package/tests/auto-permission-compound.test.ts +24 -0
- package/tests/auto-permission-evidence.test.ts +88 -0
- package/tests/auto-permission-memory.test.ts +46 -0
- package/tests/auto-permission-public-regressions.test.ts +111 -0
- package/tests/auto-permission-runtime.test.ts +454 -0
- package/tests/autonomous-guard.test.ts +86 -16
- package/tests/background-cache-warming.test.ts +2 -1
- package/tests/background-jobs.test.ts +227 -0
- package/tests/background-monitor.test.ts +195 -0
- package/tests/background-subagents-default-mode.test.ts +28 -5
- package/tests/bounded-stop-contract.test.ts +85 -0
- package/tests/bounded-writer-admission.test.ts +52 -1
- package/tests/bridge-wake-lifecycle.test.ts +129 -0
- package/tests/bridge-wake-rendering.test.ts +56 -0
- package/tests/build-distribution.test.ts +269 -0
- package/tests/builtin-codemode-optout.test.ts +242 -0
- package/tests/bundled-install.test.ts +1319 -0
- package/tests/bundled-wizard.test.ts +505 -0
- package/tests/card-content-policy.test.ts +68 -0
- package/tests/card-style-policy.test.ts +34 -2
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-package-entrypoints.test.ts +65 -0
- package/tests/child-package-injection.test.ts +74 -0
- package/tests/child-safety.test.ts +12 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +107 -2
- package/tests/command-palette.test.ts +5 -5
- package/tests/context-threshold.test.ts +31 -0
- package/tests/delegate-for-reason-contract.test.ts +327 -0
- package/tests/delegated-key-learnings-contract.test.ts +3 -2
- package/tests/devbinary/native-review-parity.devtest.ts +1 -1
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +316 -0
- package/tests/generic-agent-tools.test.ts +16 -1
- package/tests/gentle-agents.test.ts +1895 -76
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +19 -2
- package/tests/gentle-ai-child-guards.test.ts +202 -0
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
- package/tests/gentle-ai-installer.test.ts +548 -58
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +6 -6
- package/tests/gentle-ai.test.ts +999 -31
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +263 -19
- package/tests/gentle-shell-bundled.test.ts +1036 -0
- package/tests/gentle-shell-launcher.test.ts +251 -25
- package/tests/gentle-shell-startup-flow.test.ts +75 -0
- package/tests/gentle-shell-startup-update.test.ts +85 -0
- package/tests/gentle-shell-uninstall.test.ts +978 -0
- package/tests/gentle-shell-update-panel.test.ts +158 -0
- package/tests/gentle-shell.test.ts +611 -100
- package/tests/gentle-todo.test.ts +317 -14
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/inprocess-reviewer.test.ts +20 -0
- package/tests/install-gentle-ai-failure.test.ts +69 -0
- package/tests/install-tui-mode-setting.test.ts +15 -0
- package/tests/install-wizard.test.ts +1223 -0
- package/tests/installer-bundles.test.ts +122 -0
- package/tests/installer-posix-bootstrap.test.ts +1201 -0
- package/tests/installer-preflight.test.ts +750 -0
- package/tests/installer-probes.test.ts +1106 -0
- package/tests/installer-runner.test.ts +2159 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +395 -0
- package/tests/main-channel.test.ts +344 -0
- package/tests/maintainer/provider-relay.maintest.ts +13 -12
- package/tests/model-price-ratio.test.ts +116 -0
- package/tests/model-profiles-preservation.test.ts +153 -0
- package/tests/model-routing-package.test.ts +10 -0
- package/tests/nan-provider-runtime.test.ts +156 -0
- package/tests/nan-provider.test.ts +28 -18
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-cli.test.ts +236 -1
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +1 -1
- package/tests/native-review-parity.test.ts +12 -48
- package/tests/native-risk-evidence-parity.test.ts +21 -0
- package/tests/no-self-pause-contract.test.ts +87 -0
- package/tests/notification-audio-native.test.ts +281 -0
- package/tests/notification-audio-windows.test.ts +581 -0
- package/tests/notification-audio.test.ts +548 -0
- package/tests/notification-customize.test.ts +655 -0
- package/tests/notification-events.test.ts +115 -0
- package/tests/notification-policy.test.ts +201 -0
- package/tests/notification-pulse-client.test.ts +356 -0
- package/tests/notification-pulse-protocol.test.ts +213 -0
- package/tests/notification-pulse-stream.test.ts +320 -0
- package/tests/notification-scheduler.test.ts +258 -0
- package/tests/notification-ui.test.ts +157 -0
- package/tests/npm-pack-result.test.ts +41 -0
- package/tests/odd-integration.test.ts +14 -5
- package/tests/odd-routing-canonical-ratchet.test.ts +53 -12
- package/tests/odd-routing-contract.test.ts +80 -23
- package/tests/odd-runtime-delegation-gate.test.ts +4 -1
- package/tests/orchestrator-budget.test.ts +29 -6
- package/tests/orchestrator-catalog.test.ts +146 -0
- package/tests/orchestrator-consultation-sdk.test.ts +614 -0
- package/tests/orchestrator-consultation.test.ts +111 -0
- package/tests/orchestrator-discovery.test.ts +241 -0
- package/tests/orchestrator-helper-consent.test.ts +171 -0
- package/tests/orchestrator-helper.test.ts +297 -0
- package/tests/orchestrator-message-card.test.ts +190 -0
- package/tests/orchestrator-presence.test.ts +23 -0
- package/tests/orchestrator-rdd-ownership.test.ts +6 -5
- package/tests/orchestrator-scope.test.ts +107 -0
- package/tests/orchestrator-state.test.ts +114 -0
- package/tests/orchestrator-status-publication-contract.test.ts +34 -0
- package/tests/orchestrator-tool-card.test.ts +398 -0
- package/tests/orchestrator-work-search.test.ts +318 -0
- package/tests/orchestrator-work.test.ts +147 -0
- package/tests/package-manifest.test.ts +105 -21
- package/tests/packed-runner-owned-path.test.ts +35 -1
- package/tests/parallel-reason-contract.test.ts +21 -0
- package/tests/parallel-writer-admission.test.ts +210 -0
- package/tests/permission-mode-policy.test.ts +194 -0
- package/tests/persona-single-channel.test.ts +4 -12
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/pi-update-launcher.test.ts +87 -0
- package/tests/pi-update.test.ts +186 -0
- package/tests/provider-defect-handoff.test.ts +4 -3
- package/tests/quiet-tool-rendering.test.ts +160 -3
- package/tests/rdd-aware-verification-contract.test.ts +29 -41
- package/tests/rdd-status-line.test.ts +52 -0
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/review-agent-end-preflight.test.ts +62 -10
- package/tests/review-candidate-view.test.ts +101 -12
- package/tests/review-contract-prompt.test.ts +33 -4
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +255 -1
- package/tests/review-controller-workspace-root.test.ts +43 -2
- package/tests/review-controller.test.ts +2 -2
- package/tests/review-correction-lifecycle.test.ts +2 -1
- package/tests/review-gate.test.ts +7 -4
- package/tests/review-graph-schema.test.ts +2 -2
- package/tests/review-host-relay-routing.test.ts +5 -3
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +21 -1
- package/tests/review-json-arguments.test.ts +167 -0
- package/tests/review-last-event-closure.test.ts +157 -8
- package/tests/review-ledger-contract.test.ts +5 -4
- package/tests/review-policy-judgment-day.test.ts +1 -1
- package/tests/review-policy-ordinary.test.ts +2 -2
- package/tests/review-relay-transport-agent.test.ts +66 -1
- package/tests/review-risk-assessment.test.ts +109 -33
- package/tests/review-session-standing-permission-controller.test.ts +16 -17
- package/tests/review-session-standing-permission-ipc.test.ts +6 -5
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +83 -5
- package/tests/runtime-harness.mjs +42 -15
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +65 -5
- package/tests/session-change-capture.test.ts +10 -10
- package/tests/session-changes-shell.test.ts +242 -48
- package/tests/session-changes.test.ts +17 -24
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +394 -0
- package/tests/session-profile-persistence.test.ts +334 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +57 -2
- package/tests/shell-bar.test.ts +40 -0
- package/tests/shell-changes-view.test.ts +85 -34
- package/tests/shell-changes.test.ts +79 -62
- package/tests/shell-render-cache-runtime.test.ts +69 -0
- package/tests/shell-render-cache.test.ts +129 -0
- package/tests/shell-sidebar-layout.test.ts +27 -2
- package/tests/shell-todo.test.ts +132 -11
- package/tests/shell-usage-view.test.ts +1 -1
- package/tests/shell-usage.test.ts +35 -0
- package/tests/short-check-routing-contract.test.ts +49 -0
- package/tests/skill-registry-watcher.test.ts +119 -0
- package/tests/skill-registry.test.ts +311 -5
- package/tests/startup-banner.test.ts +117 -3
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/status-timing-diagnostics.test.ts +283 -0
- package/tests/support/orchestrator-modules.ts +19 -0
- package/tests/task-size-routing-contract.test.ts +241 -0
- package/tests/telemetry-trigger.test.ts +27 -1
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/verify-agent-contract.test.ts +90 -0
- package/tests/verify-first-launch-contract.test.ts +139 -0
- package/tests/verify-package-files.test.ts +56 -0
- package/tests/vim-editor-adapter.test.ts +47 -21
- package/tests/visual-customization-policy.test.ts +32 -3
- package/tests/visual-customize-view.test.ts +288 -10
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +18 -3
- package/tests/yolo-mode-runtime.test.ts +37 -10
- package/tests/yolo-mode.test.ts +38 -5
- package/tests/yolo-session-policy.test.ts +1 -1
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -0,0 +1,2040 @@
|
|
|
1
|
+
# Installation wizard groundwork
|
|
2
|
+
|
|
3
|
+
The preflight planner, real host probes, POSIX bootstrap, Windows prerequisite
|
|
4
|
+
foundation, the standard installation runner module (including runtime
|
|
5
|
+
persistence), the secure local wizard host with its packaged entry and the
|
|
6
|
+
interactive browser wizard UI are implemented. Windows native validation
|
|
7
|
+
remains unavailable locally.
|
|
8
|
+
**Preview:** download the unsigned double-click installer for your system
|
|
9
|
+
from a release, or run it from a checkout with `sh scripts/bootstrap.sh`
|
|
10
|
+
(macOS, Linux) or `scripts\bootstrap.cmd` (Windows), as the
|
|
11
|
+
[README](../README.md#easiest-download-and-double-click-no-terminal-needed)
|
|
12
|
+
shows. Native macOS/Windows clean-machine runs are still unverified (T7); one
|
|
13
|
+
Linux clean-container run passed (see
|
|
14
|
+
[Clean-machine acceptance](#clean-machine-acceptance-t7)).
|
|
15
|
+
|
|
16
|
+
### Double-click downloads
|
|
17
|
+
|
|
18
|
+
[`scripts/build-installer-bundles.mjs`](../scripts/build-installer-bundles.mjs)
|
|
19
|
+
builds three archives with stable names, attached to every release by the
|
|
20
|
+
`installers` job of `publish.yml` after the verified npm publication (same
|
|
21
|
+
commit, `contents: write` only in that job), with
|
|
22
|
+
`gentle-shell-installers-SHA256SUMS.txt`:
|
|
23
|
+
|
|
24
|
+
| Asset | Launcher |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `gentle-shell-installer-macos.zip` | `Gentle Shell Installer/Install Gentle Shell.command` |
|
|
27
|
+
| `gentle-shell-installer-windows.zip` | `Gentle Shell Installer/Install Gentle Shell.cmd` (CRLF, ends with `pause`) |
|
|
28
|
+
| `gentle-shell-installer-linux.tar.gz` | `gentle-shell-installer/install-gentle-shell.sh` |
|
|
29
|
+
|
|
30
|
+
Each launcher runs `installer/scripts/bootstrap.sh` (or `bootstrap.cmd`) next to
|
|
31
|
+
it; `installer/` holds `package.json`, every `installerPaths` file and every
|
|
32
|
+
module they import (only `node:` built-ins otherwise), as real files because the
|
|
33
|
+
bootstrap refuses symlinked bundle files. The downloads are **unsigned**:
|
|
34
|
+
macOS Gatekeeper and Windows SmartScreen warn on first run, and the README
|
|
35
|
+
explains how to continue. Signing and notarization are not done yet.
|
|
36
|
+
|
|
37
|
+
The `installers` job uploads to the release with the tag the `publish` job
|
|
38
|
+
verified (`needs.publish.outputs.tag`). It runs after npm publication, so its
|
|
39
|
+
failure never affects npm, but it marks the run failed: recover with
|
|
40
|
+
`gh run rerun <run id> --failed`, not a new `publish.yml` dispatch (that version
|
|
41
|
+
is already on npm). Each launcher reports whether the bootstrap succeeded before
|
|
42
|
+
it lets the window close.
|
|
43
|
+
|
|
44
|
+
## What is available
|
|
45
|
+
|
|
46
|
+
`scripts/installer-preflight.mjs` exports four small integration surfaces:
|
|
47
|
+
|
|
48
|
+
| API | Contract |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `requirements` | Repository-derived Node/Pi minima, pnpm acquisition pin, package version, native installer pin and Windows Go minimum. |
|
|
51
|
+
| `collectInventory({ platform, arch, probes })` | Calls injected named read-only probes serially. Missing/failed probes become unknown; error text is not retained. |
|
|
52
|
+
| `planPreflight(inventory)` | Pure classification and ordered action intents; no commands, downloads, installation or setup execution. |
|
|
53
|
+
| `pnpmGlobalBin({ platform, env })` | Pure pnpm 11 global-bin resolution: `{ pnpmHome, path, onPath }` or `null` when unknowable. |
|
|
54
|
+
| `persistencePins` | Fixed runtime persisted under PNPM_HOME: Node 24.21.0, its bundled npm 11.19.0 and the pnpm acquisition pin. |
|
|
55
|
+
|
|
56
|
+
`scripts/installer-probes.mjs` supplies the real read-only probes for
|
|
57
|
+
`collectInventory`; see [Host probes](#host-probes).
|
|
58
|
+
|
|
59
|
+
Current requirements come from `package.json` and
|
|
60
|
+
[`gentle-ai-installer.mjs`](../scripts/gentle-ai-installer.mjs): Node ≥22.19.0,
|
|
61
|
+
Pi ≥1.2.0, pnpm acquisition 11.1.1, Gentle Shell package 4.0.0 and package-local
|
|
62
|
+
Gentle AI 4.1.1. Windows native source builds require Go ≥1.25.10 when the
|
|
63
|
+
required usable native binary is missing. A reusable binary does not require Go.
|
|
64
|
+
When a build needs Go and the user's Go is missing, older or unchecked, the installer uses
|
|
65
|
+
its own [pinned Go](#pinned-go) instead of blocking.
|
|
66
|
+
Pins change with the repository; the API reads the metadata rather than copying
|
|
67
|
+
these values into a second installer.
|
|
68
|
+
|
|
69
|
+
## Probe adapter contract
|
|
70
|
+
|
|
71
|
+
Supply `platform` as `linux`, `darwin` or `win32`, and `arch` as `x64` or `arm64`.
|
|
72
|
+
These are planning targets, **not evidence of native OS/libc support**. Other
|
|
73
|
+
pairs produce an unsupported-target blocker and no actions. Native lanes,
|
|
74
|
+
minimum OS versions, libc compatibility, artifact trust and elevation handling
|
|
75
|
+
remain to be verified in later units.
|
|
76
|
+
|
|
77
|
+
Probes are named `node`, `pnpm`, `pi`, `shell`, `gentleAi`, `go`, `globalBin`, and
|
|
78
|
+
`setup`, plus the optional advisory `git` (kept as `inventory.git` only when the
|
|
79
|
+
probe answers) and Windows `folders`. `collectInventory` has no default probes; the real implementations live
|
|
80
|
+
in [`installer-probes.mjs`](#host-probes). Probes must use bounded read-only
|
|
81
|
+
checks, never launcher setup, postinstall, paid model requests, credential
|
|
82
|
+
inspection or home writes. The collector cannot enforce purity of
|
|
83
|
+
caller-provided functions; adapter implementations require review.
|
|
84
|
+
|
|
85
|
+
- Tool observations use `{ available: true, version, usable: true }`; absence is
|
|
86
|
+
`{ available: false }`. Unknown availability, usability or unparseable versions
|
|
87
|
+
block planning. Versions are exact stable `major.minor.patch` strings (optional
|
|
88
|
+
`v` prefix); prereleases and raw command output are unknown, not silently reused.
|
|
89
|
+
- Node may add `persistent` (resolvable from the user's PATH without bootstrap
|
|
90
|
+
tool directories) and `npm` (a usable npm resolves there) booleans; pnpm
|
|
91
|
+
may add `persistent` (resolvable from the user's PATH). A `false` value
|
|
92
|
+
yields the runtime persistence intents below; absent or `null` values add
|
|
93
|
+
nothing, and the runner's own npm gate still applies.
|
|
94
|
+
- Shell additionally needs `global: true`, proving normal global ownership.
|
|
95
|
+
Compatible newer Node, Pi and Shell versions are reused, never downgraded.
|
|
96
|
+
- pnpm additionally needs `compatible: true`, based on its actual Node engine and
|
|
97
|
+
required global-install capabilities. The acquisition pin is not a minimum:
|
|
98
|
+
newer compatible pnpm is reusable. Do not infer compatibility from its version
|
|
99
|
+
alone; the later acquisition adapter must validate the pinned engine too.
|
|
100
|
+
- Gentle AI additionally needs `compatible: true`: evidence from the normal
|
|
101
|
+
package-local binary resolution/integrity contract, for the selected Shell
|
|
102
|
+
installation. Its exact installer pin is required; another version blocks
|
|
103
|
+
rather than being overwritten. Do not confuse an unrelated PATH binary with
|
|
104
|
+
the package's reusable native binary. For a newer reusable Shell, the adapter
|
|
105
|
+
must establish native-pin compatibility before presenting this evidence.
|
|
106
|
+
- Global-bin observations use `{ available: true, path, writable: true, onPath }`.
|
|
107
|
+
A known missing PATH entry yields an explicit setup intent. Unknown or
|
|
108
|
+
non-writable directories block; no directory is created or permission probed
|
|
109
|
+
by writing. pnpm 11 places global executables in **`$PNPM_HOME/bin`**, not
|
|
110
|
+
`$PNPM_HOME`, and `pnpm root -g`/`bin -g` fail when that directory is not on
|
|
111
|
+
PATH. Probes must report `pnpmGlobalBin().path`, whose `onPath` compares PATH
|
|
112
|
+
entries with that bin directory. PNPM_HOME is the user's existing absolute
|
|
113
|
+
value; otherwise pnpm's documented default (`$XDG_DATA_HOME/pnpm` or
|
|
114
|
+
`~/.local/share/pnpm` on Linux, `~/Library/pnpm` on macOS,
|
|
115
|
+
`%LOCALAPPDATA%\pnpm` on Windows). A relative PNPM_HOME or missing home is
|
|
116
|
+
unknown, not guessed. On Windows the wizard first checks that folder's
|
|
117
|
+
permissions and may use `%USERPROFILE%\.pnpm` instead or block
|
|
118
|
+
([Windows PNPM_HOME](#windows-pnpm_home)).
|
|
119
|
+
- `setup` is a boolean evidence of normal Shell setup readiness. Unknown setup
|
|
120
|
+
on an existing stack blocks; a missing Shell/native binary needs normal setup.
|
|
121
|
+
|
|
122
|
+
## Reading a plan
|
|
123
|
+
|
|
124
|
+
Tool statuses distinguish `unavailable`, `unknown`, `incompatible`, `reusable`,
|
|
125
|
+
`needs-setup`, `needs-update`, `needs-install` and `not-required`. A known version below the minimum is `incompatible` even when the tool cannot
|
|
126
|
+
run (for example an older Shell whose global command is missing); Gentle AI and
|
|
127
|
+
setup add no separate `unknown` blocker then, because they are only checked for
|
|
128
|
+
the pinned Shell. When Node.js, Pi, Gentle Shell or Go is simply older than its
|
|
129
|
+
minimum, the wizard's guidance names the found and required versions (for the
|
|
130
|
+
Shell, also the pnpm commands that update or remove it); other blockers keep
|
|
131
|
+
their fixed guidance. A missing, older or unchecked Go is not a blocker: `tools.go` is
|
|
132
|
+
`needs-acquire` with `required`, the pinned `version` and the older `found`
|
|
133
|
+
version (or `unchecked: true` when `go version` reported no version it can
|
|
134
|
+
check), and the plan acquires the [pinned Go](#pinned-go). An
|
|
135
|
+
older Node.js or an incompatible pnpm on the user's PATH is not a blocker when
|
|
136
|
+
the bootstrap's pinned copy runs the wizard: the probe reports that copy
|
|
137
|
+
(bootstrap-only) with the user's stable version as `found`, and `tools.node` or
|
|
138
|
+
`tools.pnpm` records `found` and the persisted pin as `version` (see
|
|
139
|
+
[Existing installations](#existing-installations)). That copy is persisted
|
|
140
|
+
only for a new installation of Gentle Shell: with an existing Shell (updated or
|
|
141
|
+
current) the replaced runtime is an `incompatible-tool` blocker before consent,
|
|
142
|
+
except for a setup recovery, which persists nothing. A user's pnpm in
|
|
143
|
+
`$PNPM_HOME/bin`, where persisting pnpm writes, is never reported as `found`:
|
|
144
|
+
the probe reports that pnpm itself with `inGlobalBin: true`, so an older or
|
|
145
|
+
newer-major one blocks and the guidance names both versions and, for an older
|
|
146
|
+
one, `pnpm self-update <pin>`. One whose shim is not recognized, or whose
|
|
147
|
+
`--version` does not report a stable version, is never run again: the probe
|
|
148
|
+
reports the bootstrap's pnpm in its place with `inGlobalBin: true`,
|
|
149
|
+
`unchecked: true` and `persistent: true` (on every platform), so it does not
|
|
150
|
+
block and nothing is persisted over it; `tools.pnpm` records `unchecked: true`
|
|
151
|
+
and the plan says before consent that it was left as it is and that the
|
|
152
|
+
installer uses its own verified pnpm. An older Pi is not a blocker: one that pnpm or npm owns is
|
|
153
|
+
`needs-update`, and one neither owns is `needs-install` (see
|
|
154
|
+
[Existing installations](#existing-installations)). A Pi or Shell command on PATH that pnpm does not manage
|
|
155
|
+
stays `unknown` (never replaced), and its probe adds `outsidePnpm: true` so the
|
|
156
|
+
guidance can say so; Gentle AI and setup add no separate blocker while the Shell
|
|
157
|
+
blocks. Any blocker suppresses all actions: repairing an
|
|
158
|
+
existing incompatible or uncertain component requires a later explicit decision,
|
|
159
|
+
not automatic replacement. `ready` means no acquisition/setup is indicated by
|
|
160
|
+
this inventory, **not that verification has executed**.
|
|
161
|
+
|
|
162
|
+
A clean target receives these intents in dependency order:
|
|
163
|
+
|
|
164
|
+
1. Acquire and verify Node, then pnpm; prepare the usable global-bin environment.
|
|
165
|
+
2. When a build needs Go (the main channel, or a Windows Gentle Shell install or
|
|
166
|
+
update whose postinstall may build Gentle AI) and Go is missing, older or unchecked,
|
|
167
|
+
`acquire-go` and `verify-go` come next, before every persistence and install
|
|
168
|
+
step, so a failed Go download leaves the computer as it was.
|
|
169
|
+
3. Persist only what is missing. A bootstrap-only reusable Node gets the full
|
|
170
|
+
group, always together: `persist-node` (`persist-runtime`, version
|
|
171
|
+
24.21.0), `persist-package-managers` (`install-global`) and
|
|
172
|
+
`configure-npm-prefix` (`configure`). When pnpm is already persistent
|
|
173
|
+
(`inventory.pnpm.persistent !== false`, such as a newer pnpm 11 in
|
|
174
|
+
`$PNPM_HOME/bin`), `persist-npm` replaces `persist-package-managers`, so
|
|
175
|
+
that pnpm is never replaced or downgraded. A persistent Node is never replaced:
|
|
176
|
+
it gets at most one intent, `persist-npm` (npm 11.19.0) when no usable
|
|
177
|
+
npm resolves (on POSIX a working npm from any version manager is usable), `persist-pnpm` (pnpm 11.1.1) when pnpm is bootstrap-only, or
|
|
178
|
+
`persist-package-managers` when both are missing.
|
|
179
|
+
4. Install Pi globally, then `gentle-pi` globally (its existing postinstall owns
|
|
180
|
+
native installation). For an existing Shell with missing native binary, call
|
|
181
|
+
the existing installer instead.
|
|
182
|
+
5. Run normal Shell setup and verify stack readiness. Verification is always
|
|
183
|
+
included, even when all components can be reused.
|
|
184
|
+
|
|
185
|
+
A recoverable setup (the pinned stack is installed, Gentle AI is verified and
|
|
186
|
+
only setup did not finish) plans exactly `setup-global-bin` (only when the
|
|
187
|
+
global bin is off PATH), `setup-shell` and `verify-readiness`: never an
|
|
188
|
+
acquisition, persistence or installation intent. See
|
|
189
|
+
[Setup recovery](#setup-recovery).
|
|
190
|
+
|
|
191
|
+
Actions are structured `{ id, kind, target, version? }` descriptors, **not shell
|
|
192
|
+
commands**. Later runners must re-inventory after changes, verify prerequisite
|
|
193
|
+
versions and global-bin readiness before dependent steps, obtain explicit consent
|
|
194
|
+
and stop on mandatory failures. They must preserve existing versions/paths and
|
|
195
|
+
use normal global package ownership and default `~/.gentle-shell/agent` semantics.
|
|
196
|
+
No alternate product root, Engram database/server, credential migration or custom
|
|
197
|
+
companion installer belongs in this plan.
|
|
198
|
+
|
|
199
|
+
## Remaining boundaries
|
|
200
|
+
|
|
201
|
+
`npm:` registry sources are package references, not npm executable calls. Pi's
|
|
202
|
+
`npmCommand` supports pnpm, but independent upstream `npm exec` control remains
|
|
203
|
+
unverified. **This preflight does not establish an npm-free installation chain.**
|
|
204
|
+
|
|
205
|
+
The consent UI is the local wizard host described below. A published
|
|
206
|
+
distribution bundle and native acceptance evidence remain future work in the
|
|
207
|
+
[feature plan](../odd/tasks/browser-install-wizard.md). Deterministic injected
|
|
208
|
+
tests do not prove clean-machine installation on Windows, macOS or Linux.
|
|
209
|
+
|
|
210
|
+
Focused verification:
|
|
211
|
+
|
|
212
|
+
```sh
|
|
213
|
+
node --experimental-strip-types --test tests/installer-server.test.ts tests/verify-package-files.test.ts tests/installer-probes.test.ts tests/installer-runner.test.ts tests/installer-preflight.test.ts tests/installer-posix-bootstrap.test.ts tests/installer-windows-bootstrap.test.ts
|
|
214
|
+
sh -n scripts/bootstrap.sh
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Host probes
|
|
218
|
+
|
|
219
|
+
`scripts/installer-probes.mjs` exports `createProbes({ platform, env, run, fs,
|
|
220
|
+
home?, verifyGentleAi? })`, returning the eight named probes for
|
|
221
|
+
`collectInventory`. `env` is the wizard's own environment, with bootstrap tools
|
|
222
|
+
first on PATH. Every probe returns the documented shape or the unknown shape
|
|
223
|
+
`{ available: null }`; adapter errors are caught and never retained.
|
|
224
|
+
|
|
225
|
+
| Export | Contract |
|
|
226
|
+
| --- | --- |
|
|
227
|
+
| `bootstrapRoots({ platform, env })` | Bootstrap tool roots: an absolute `GENTLE_BOOTSTRAP_TOOLS` plus every PATH entry segment named `.gentle-shell-bootstrap-tools.*` (case-insensitive on Windows). |
|
|
228
|
+
| `userEnvironment({ platform, env })` | A copy of `env` whose PATH omits entries inside those roots: the user's real PATH. |
|
|
229
|
+
| `hostAdapters({ maxOutputBytes?, maxTextBytes? })` | Real `run` and read-only `fs` adapters, described below. |
|
|
230
|
+
|
|
231
|
+
Adapters:
|
|
232
|
+
|
|
233
|
+
- `run(command, argv, { env, deadlineMs })` is the runner's process contract:
|
|
234
|
+
argv arrays with `shell:false`, SIGKILL at the deadline and stdout bounded
|
|
235
|
+
to `maxOutputBytes` (default 1 MiB, reported as `truncated: true`). stderr
|
|
236
|
+
is discarded because it can hold private paths, unless the caller passes
|
|
237
|
+
`stderrTail: <bytes>` (a positive integer, clamped to 4 KiB): then stderr is
|
|
238
|
+
piped and only its last bytes are kept and returned as `stderrTail`, also on a
|
|
239
|
+
deadline. Only the runner's `shell-setup`, `persist-path` and `pnpm add -g` steps
|
|
240
|
+
ask for it, and the main channel's `go install` and source extraction. A spawn failure returns
|
|
241
|
+
`code: null`. The deadline signals the direct child only, not descendants;
|
|
242
|
+
it then destroys the child's pipes and settles as timed out (`code: null`,
|
|
243
|
+
`signal: "SIGKILL"`) without waiting for `close`, which a descendant holding
|
|
244
|
+
stdout could otherwise delay indefinitely.
|
|
245
|
+
- `fs` offers `isFile`, `isDirectory`, `exists` (only ENOENT/ENOTDIR are
|
|
246
|
+
absent), `realpath`, `readText` (regular files up to `maxTextBytes`) and
|
|
247
|
+
`writable` (an access check, never a write). No probe creates, deletes or
|
|
248
|
+
writes anything.
|
|
249
|
+
|
|
250
|
+
Probes run only fixed argv: `node --version`, `pnpm --version`,
|
|
251
|
+
`pnpm list -g --depth 0 --json` (once, shared), `go version`, and
|
|
252
|
+
`npm --version` and `npm config get prefix` (POSIX), or npm's
|
|
253
|
+
`npm-cli.js --version` for a pinned or Windows npm. Deadlines are 10 seconds for versions and 30 seconds for
|
|
254
|
+
the listing; truncated, nonzero, signalled or timed-out output is unknown.
|
|
255
|
+
|
|
256
|
+
| Probe | Evidence |
|
|
257
|
+
| --- | --- |
|
|
258
|
+
| `node` | The first `node` on the user's real PATH (Go `exec.LookPath` order, PATHEXT on Windows), else the bootstrap one. `persistent` says which. `npm` is the runner's usable-npm proof in the user's real PATH with `$PNPM_HOME/bin` first, so a bootstrap npm never counts. A Windows `.cmd`/`.bat` cannot run with `shell:false`: a Node that resolves to one is unknown, and npm is run through what its shim runs ([Windows command shims](#windows-command-shims)). |
|
|
259
|
+
| `pnpm` | The runner's invocation (bootstrap handoff, or a POSIX `pnpm` on PATH). `compatible` requires a successful run (pnpm checks its Node engine at startup) at the pin or newer, of any major: the runner's commands were run with pnpm 12 against a pnpm 11 global home and back (see [pnpm 12](#pnpm-12)). `persistent` is whether any `pnpm` resolves on the real PATH. A Windows user pnpm in `$PNPM_HOME\bin` whose shim or target fails the storage walk is never run: the bootstrap's pnpm is reported in its place with `inGlobalBin: true`, `untrusted: true` and `persistent: true`, so it does not block and nothing is persisted over it. A user pnpm in `$PNPM_HOME/bin` whose shim is not recognized, or whose `--version` does not report a stable version, is handled the same way with `unchecked: true` instead, on every platform. |
|
|
260
|
+
| `pi`, `shell` | pnpm-global entries from the listing. A package that is not pnpm-global but whose command (`pi`, `gentle-shell`) resolves on the real PATH is unknown, never absent, so another installation is not duplicated. On Windows a `pi.cmd` or `gentle-shell.cmd` is run, and its package found, through what the shim runs; an unknown shim is never run. Shell is `usable` only when `$PNPM_HOME/bin/gentle-shell` (`.cmd` on Windows) exists. |
|
|
261
|
+
| `gentleAi` | Absent without gentle-pi or without its package-native binary. Otherwise compatible only for this package version, a listed path that resolves inside PNPM_HOME and `verifyGentleAi` (default `packageNativeGentleAi`) success; anything else is unknown. |
|
|
262
|
+
| `go` | `go version` from the real PATH; `go1.22` normalizes to `1.22.0`; devel and release-candidate builds are unknown. |
|
|
263
|
+
| `folders` | Windows only, after the other probes: the commands `node`, `npm`, `go`, `pi` and `gentle-shell` resolve to on the real PATH, and what each one runs, walked in one Windows PowerShell launch (`verifyWindowsStorageMany`). Returns `{ node?, npm?, go?, pi?, shell? }` with the first failing path's finding per tool, or `null`. A walk that cannot finish is `null`, never a blocker ([reused tool folders](#reused-tool-folders)). |
|
|
264
|
+
| `git` | Advisory only, for the standard and the bundled plan alike: `{ available }` from `git` on the real PATH, never run. On macOS `/usr/bin/git` is a shim that opens an install dialog without the Command Line Tools, so `xcode-select -p` (which never opens one) decides it; another Git first on PATH counts as it is. A missing Git adds a **Git not found** notice before consent (`gitNotice`) with the install command for the system (`sudo apt-get install -y git` on Debian or Ubuntu, `xcode-select --install`, `winget install --id Git.Git -e`). Installing does not need Git, and it is never a blocker; code review, worktrees and repository features do need it. |
|
|
265
|
+
| `globalBin` | `pnpmGlobalBin` over the real PATH; `writable` is write access on the nearest existing ancestor of `$PNPM_HOME/bin` (itself included). A non-directory ancestor is not writable. |
|
|
266
|
+
| `setup` | `false` when gentle-pi is absent. `{ available: true, recoverable: true }` for the pinned stack this pnpm installed (see [Setup recovery](#setup-recovery)), using the runner's `recoverableStackRoot`. Unknown otherwise, because an existing Shell's setup readiness has no read-only evidence. |
|
|
267
|
+
|
|
268
|
+
A failed, empty or unparseable listing makes Pi, Shell, Gentle AI and setup
|
|
269
|
+
unknown, which blocks planning. A package listed in two projects is ambiguous
|
|
270
|
+
and unknown.
|
|
271
|
+
|
|
272
|
+
## Bundled installation
|
|
273
|
+
|
|
274
|
+
A new installation can install Gentle Shell as one self-contained product
|
|
275
|
+
instead: its own pinned Node.js (with the npm in the same archive), pnpm, Pi,
|
|
276
|
+
and Go on Windows. The user's Node.js, npm, pnpm, Go and Pi are not used or
|
|
277
|
+
changed. The building blocks are in [bundled install](bundled-install.md);
|
|
278
|
+
`scripts/bundled-wizard.mjs` wires them into the wizard.
|
|
279
|
+
|
|
280
|
+
### When the wizard uses it
|
|
281
|
+
|
|
282
|
+
`bundledGate` decides, as a pure function, after the usual preflight. The
|
|
283
|
+
bundled plan is used only when all of these hold:
|
|
284
|
+
|
|
285
|
+
- The channel is `release`.
|
|
286
|
+
- The target is macOS, Linux or Windows on x64 or arm64.
|
|
287
|
+
- Preflight found no Gentle Shell (`shell.available === false`). An existing
|
|
288
|
+
one that a global pnpm or npm owns migrates instead (`migrationGate`, see
|
|
289
|
+
[Migration from a global install](bundled-install.md#migration-from-a-global-install)).
|
|
290
|
+
Any other existing or unknown one keeps the current path.
|
|
291
|
+
- This package's own release, `v<package version>`, publishes both
|
|
292
|
+
[distribution assets](bundled-install.md#release-distribution-assets), and
|
|
293
|
+
`readDistribution` accepts them.
|
|
294
|
+
|
|
295
|
+
The assets are fetched from
|
|
296
|
+
`https://github.com/Gentleman-Programming/gentle-shell/releases/download/v<version>/`:
|
|
297
|
+
|
|
298
|
+
- The download asks for the identity encoding.
|
|
299
|
+
- It is bounded to 64 KiB for the manifest and 8 MiB for the lockfile.
|
|
300
|
+
- It follows a redirect only to an https GitHub asset host.
|
|
301
|
+
|
|
302
|
+
| Fetch result | Plan |
|
|
303
|
+
|---|---|
|
|
304
|
+
| Both assets read | Bundled |
|
|
305
|
+
| 404 for either asset (v4.0.0 has none) | Standard, unchanged |
|
|
306
|
+
| Network failure, or assets that do not read | Standard, unchanged |
|
|
307
|
+
|
|
308
|
+
The main channel, and existing installations that neither pnpm nor npm owns,
|
|
309
|
+
keep the standard runner.
|
|
310
|
+
|
|
311
|
+
### Plan and steps
|
|
312
|
+
|
|
313
|
+
The plan has the standard plan's shape, with no tools or blockers, plus
|
|
314
|
+
`bundled`. That field holds:
|
|
315
|
+
|
|
316
|
+
- the exact versions and the lockfile's sha256;
|
|
317
|
+
- the prefix and its `bin` folder;
|
|
318
|
+
- the pinned runtimes;
|
|
319
|
+
- the single PATH change, from `pathEntryPlan`.
|
|
320
|
+
|
|
321
|
+
The review screen says, before consent, that the user's own tools are not used
|
|
322
|
+
or changed and that their npm registry, authentication and proxy settings are
|
|
323
|
+
respected.
|
|
324
|
+
|
|
325
|
+
`runBundledInstall` checks that the consented plan matches the fetched assets.
|
|
326
|
+
It then runs and logs these steps, in this order:
|
|
327
|
+
|
|
328
|
+
| Step | What it does |
|
|
329
|
+
|---|---|
|
|
330
|
+
| `claim-prefix` | `claimPrefix`: `~/.gentle-shell`, or the Windows private folder. |
|
|
331
|
+
| `copy-npm-settings` | Writes the filtered copy of the user's npmrc (S12) into the prefix. |
|
|
332
|
+
| `install-runtime` | `ensureRuntime`: our Node with its npm, and pnpm. Go too on Windows. |
|
|
333
|
+
| `install-version` | `installVersion` from the release lockfile, `--frozen-lockfile`. |
|
|
334
|
+
| `activate-version` | `activateVersion`. |
|
|
335
|
+
| `write-launcher` | `ensureLauncher`. |
|
|
336
|
+
| `bundled-path` | `applyPathEntry`, only for a link, a profile line or the HKCU user Path. The change is planned again first; one that differs from the consented plan is not applied. |
|
|
337
|
+
| `shell-setup` | `gentle-shell setup` in `setupEnvironment`: the launcher on POSIX, and our Node with the same entry on Windows. |
|
|
338
|
+
|
|
339
|
+
A migration plan adds `verify-launcher` (the launcher's `--version` must print
|
|
340
|
+
both exact versions) before `bundled-path`, and ends with `remove-global-shell`.
|
|
341
|
+
The wizard's progress list (`expectedSteps`) follows the same order.
|
|
342
|
+
|
|
343
|
+
| PATH change | Outcome |
|
|
344
|
+
|---|---|
|
|
345
|
+
| None, or a link into a folder already on PATH | `ready` |
|
|
346
|
+
| A profile line or the user Path | `terminal-action-required` with `open-new-terminal` |
|
|
347
|
+
| A profile the installer may not edit | `terminal-action-required` with `add-path-line`; the outcome screen shows the exact line to copy and the file to add it to |
|
|
348
|
+
|
|
349
|
+
A failed `install-version` or `shell-setup` reports its last error line.
|
|
350
|
+
|
|
351
|
+
On Windows the user Path is edited by `windowsPathRegistry`:
|
|
352
|
+
|
|
353
|
+
- Windows PowerShell reads `HKCU\Environment\Path` without expanding its
|
|
354
|
+
`%VARIABLES%`, and keeps its value kind.
|
|
355
|
+
- The entry is added once, compared without case or a trailing backslash.
|
|
356
|
+
- A `WM_SETTINGCHANGE` broadcast lets new terminals see the change.
|
|
357
|
+
|
|
358
|
+
### What this path does not change yet
|
|
359
|
+
|
|
360
|
+
- Preflight still probes the user's tools, as it does today. T5 removes those probes.
|
|
361
|
+
- On POSIX, gentle-pi 4.0.0's postinstall and `gentle-shell setup` run `go version`
|
|
362
|
+
from the user's PATH, which follows ours. Observed in a local run; Go is never
|
|
363
|
+
used for the POSIX release path.
|
|
364
|
+
- Gentle AI's Engram step in `gentle-shell setup` runs on its own terms. On macOS
|
|
365
|
+
it ran `brew install engram`. T4 (S9) owns that.
|
|
366
|
+
|
|
367
|
+
## Standard installation runner
|
|
368
|
+
|
|
369
|
+
`scripts/installer-runner.mjs` exports `runStandardInstall({ plan, consent },
|
|
370
|
+
adapters)`, the fixed installation step the local wizard host
|
|
371
|
+
(`bin/gentle-shell-install.mjs`, below) calls after bootstrap and a fresh
|
|
372
|
+
preflight. It is a pure module: every process, filesystem,
|
|
373
|
+
environment, integrity and log effect goes through adapters supplied by trusted
|
|
374
|
+
local code, never through the browser.
|
|
375
|
+
|
|
376
|
+
### Request contract
|
|
377
|
+
|
|
378
|
+
The request is exactly `{ plan, consent }`. `plan` must be an unmodified
|
|
379
|
+
`planPreflight` result: every action must equal a descriptor preflight can emit
|
|
380
|
+
(same id, kind, target and repository-derived version, no extra keys). Extra
|
|
381
|
+
request keys, commands, URLs, roots, environment or altered versions are
|
|
382
|
+
rejected as `invalid-request` before any process runs. `consent` must be the
|
|
383
|
+
boolean `true`; anything else returns `consent-required` with no commands. The
|
|
384
|
+
consent covers the whole displayed plan, including the PATH profile change and
|
|
385
|
+
the runtime persistence and npm prefix actions below, so the wizard must show
|
|
386
|
+
those changes before asking.
|
|
387
|
+
|
|
388
|
+
### Adapters
|
|
389
|
+
|
|
390
|
+
| Adapter | Contract |
|
|
391
|
+
| --- | --- |
|
|
392
|
+
| `platform` | `linux`, `darwin` or `win32`. |
|
|
393
|
+
| `nodePath` | Absolute Node executable of the trusted host, used for npm proof and `gentle-shell setup`. |
|
|
394
|
+
| `env` | The user's environment. Never mutated; child environments are copies. |
|
|
395
|
+
| `home` | Optional; defaults to `HOME` (`USERPROFILE` on Windows). |
|
|
396
|
+
| `run(command, argv, { env, deadlineMs })` | Argv arrays with `shell:false` semantics and a hard deadline; returns `{ code, signal, timedOut, stdout }` with bounded output. |
|
|
397
|
+
| `fs` | Read-only `isFile`, `realpath`, `readText` (also used to read pnpm's npm shim). The runner has no delete or write operation. |
|
|
398
|
+
| `verifyGentleAi({ packageRoot, platform, env, home })` | Returns `{ ok: true }` only for package-native integrity. `packageNativeGentleAi` is the default implementation over `runtime/gentle-ai-binary.mjs`. |
|
|
399
|
+
| `log({ step, status, reason? })` | Receives step identifiers and statuses only, never raw command output or error text. |
|
|
400
|
+
|
|
401
|
+
### Fixed sequence
|
|
402
|
+
|
|
403
|
+
No-process gates, all returning `blocked`:
|
|
404
|
+
|
|
405
|
+
1. Valid request and explicit consent.
|
|
406
|
+
2. Preflight blockers are absent. Two plans are supported. The clean-stack
|
|
407
|
+
plan must contain `install-pi`, `install-shell`, `setup-shell` and
|
|
408
|
+
`verify-readiness`, plus optional `setup-global-bin` and at most one exact
|
|
409
|
+
persistence variant: `persist-node` + `persist-package-managers` +
|
|
410
|
+
`configure-npm-prefix`, `persist-node` + `persist-npm` +
|
|
411
|
+
`configure-npm-prefix`, or `persist-package-managers` alone, or
|
|
412
|
+
`persist-npm` alone, or `persist-pnpm` alone. The
|
|
413
|
+
[setup recovery](#setup-recovery) plan is exactly `setup-shell` and
|
|
414
|
+
`verify-readiness`, plus optional `setup-global-bin`, and nothing else.
|
|
415
|
+
Prerequisite acquisition intents for Node and pnpm, `provision-native`, any
|
|
416
|
+
other persistence combination, other partial existing stacks and fully
|
|
417
|
+
reused stacks are `unsupported-plan`. `update-pi` (an
|
|
418
|
+
[older Pi](#existing-installations)) needs `tools.pi` to be `needs-update`
|
|
419
|
+
and never comes with `install-pi`; it may precede `install-shell` or an
|
|
420
|
+
update of Gentle Shell, or form a plan of its own with only `verify-readiness`.
|
|
421
|
+
With a reusable Gentle Shell, `install-pi` and `verify-readiness` alone are
|
|
422
|
+
supported too (`tools.pi` `unavailable` or `needs-install`).
|
|
423
|
+
3. Go: `acquire-go` and `verify-go` come together, only when `tools.go` is
|
|
424
|
+
`needs-acquire` and the plan builds with Go (the main overlay,
|
|
425
|
+
`update-shell-main`, or any Windows plan other than a recovery or a Pi-only
|
|
426
|
+
plan); otherwise `unsupported-plan`. On Windows, Go must be `reusable` or
|
|
427
|
+
acquired in such a plan because gentle-pi's postinstall may build Gentle AI
|
|
428
|
+
from source (`go-required`). A recovery or a Pi-only update runs no
|
|
429
|
+
postinstall, so it needs no Go.
|
|
430
|
+
4. `pnpmGlobalBin` resolves PNPM_HOME (`pnpm-home-unknown` otherwise) and
|
|
431
|
+
`nodePath` is absolute. A Windows plan with a private PNPM_HOME must name
|
|
432
|
+
that same folder (`pnpm-home-changed` otherwise).
|
|
433
|
+
5. pnpm comes from the bootstrap handoff `GENTLE_INSTALL_PNPM_NODE` +
|
|
434
|
+
`GENTLE_INSTALL_PNPM_ENTRY` (both absolute), or on POSIX from a `pnpm`
|
|
435
|
+
executable on PATH. Windows requires the handoff because a `.cmd` shim cannot
|
|
436
|
+
run with `shell:false` (`pnpm-unavailable`); a native pnpm the bootstrap
|
|
437
|
+
reused arrives alone as the absolute `.exe` in `GENTLE_INSTALL_PNPM_COMMAND`.
|
|
438
|
+
|
|
439
|
+
Every child process receives the user's environment plus `PNPM_HOME` and
|
|
440
|
+
`$PNPM_HOME/bin` first on PATH. With a private Windows PNPM_HOME, the children
|
|
441
|
+
also get `XDG_CONFIG_HOME`, `XDG_CACHE_HOME` and `XDG_STATE_HOME` under it
|
|
442
|
+
(unless the user set them) and `TEMP`/`TMP` set to its `tmp` folder, and the
|
|
443
|
+
first step, `prepare-pnpm-home`, claims that folder before any command
|
|
444
|
+
([Windows PNPM_HOME](#windows-pnpm_home)).
|
|
445
|
+
|
|
446
|
+
Pre-install checks, returning `blocked` on a false result or adapter error:
|
|
447
|
+
|
|
448
|
+
1. `check-npm`: Gentle AI's normal Pi install always runs an Engram init step
|
|
449
|
+
that can use `npm exec` and stops on failure. The runner resolves `npm` the
|
|
450
|
+
way Go's `exec.LookPath` (used by Gentle AI) does: absolute child PATH
|
|
451
|
+
directories in order and, on Windows, every child PATHEXT extension in
|
|
452
|
+
PATHEXT order (case-insensitive keys; default `.COM;.EXE;.BAT;.CMD`). On
|
|
453
|
+
POSIX that first candidate is accepted when it works the way Gentle AI
|
|
454
|
+
will run it, whatever installed it (Node.js, Homebrew, nvm, fnm, or a
|
|
455
|
+
mise, asdf or Volta shim): run directly from `/` in the child environment
|
|
456
|
+
(30-second deadline each), `npm --version` must print a stable version and
|
|
457
|
+
`npm config get prefix` a single absolute path. Windows applies the same
|
|
458
|
+
behavior from the drive root (`C:\`) to an `npm.exe` (a Volta or mise
|
|
459
|
+
shim) run as it is, or to what a known `npm.cmd` runs: Node.js's own
|
|
460
|
+
`npm.cmd` (also behind nvm-windows or fnm) or an npm cmd-shim, never
|
|
461
|
+
through cmd.exe ([Windows command shims](#windows-command-shims)). Any
|
|
462
|
+
other `npm.cmd`, such as mise's file shim, is not usable, so the plan
|
|
463
|
+
persists the installer's npm.
|
|
464
|
+
A candidate in `$PNPM_HOME/bin` must instead be pnpm's global npm: a POSIX
|
|
465
|
+
symlink, or a shim whose single quoted `npm-cli.js` target (`$basedir/...`
|
|
466
|
+
on POSIX, `%~dp0\...` or `%dp0%\...` on Windows, or absolute), must
|
|
467
|
+
resolve (realpath) to `node_modules/npm/bin/npm-cli.js` inside PNPM_HOME,
|
|
468
|
+
and the package must be exactly `npm@11.19.0`. Two different targets, a
|
|
469
|
+
target outside PNPM_HOME or another version are rejected. When the plan
|
|
470
|
+
adds npm, this check runs after the add and fails as a mutating step. On
|
|
471
|
+
Windows an `npm.com`, `npm.bat` or other non-`.cmd`/`.exe` npm that
|
|
472
|
+
resolves first, in the same or an earlier directory, blocks as
|
|
473
|
+
`npm-shadowed`; any other mismatch blocks as `npm-unavailable`. No npm
|
|
474
|
+
wrapper is created.
|
|
475
|
+
2. `check-global-bin`: `pnpm bin -g` must succeed and equal `$PNPM_HOME/bin`
|
|
476
|
+
(`global-bin-mismatch`; case-insensitive on Windows).
|
|
477
|
+
3. `check-existing-stack`: `pnpm list -g --depth 0 --json` runs before any
|
|
478
|
+
mutation. If `@earendil-works/pi-coding-agent` or `gentle-pi` appears in the
|
|
479
|
+
dependencies, devDependencies or optionalDependencies of any listed project,
|
|
480
|
+
the runner blocks as `existing-stack` and never overwrites it, whatever the
|
|
481
|
+
caller's plan says. A failed, timed-out or unparseable listing, or an
|
|
482
|
+
unexpected JSON shape, blocks as `global-list-unavailable`. A recovery runs
|
|
483
|
+
`check-recoverable-stack` instead (see [Setup recovery](#setup-recovery)).
|
|
484
|
+
|
|
485
|
+
Mutating and verification steps, returning `failed` with `failedStep` and the
|
|
486
|
+
`completed` step list. When the plan acquires the pinned Go, `acquire-go` and
|
|
487
|
+
`verify-go` run first, after `check-global-bin` and `check-existing-stack`
|
|
488
|
+
([Pinned Go](#pinned-go)). When it contains a persistence variant, its
|
|
489
|
+
[runtime persistence](#runtime-persistence) steps run next:
|
|
490
|
+
|
|
491
|
+
1. `install-global`: exactly one
|
|
492
|
+
`pnpm add -g @earendil-works/pi-coding-agent@1.2.0 gentle-pi@<package version> --allow-build=gentle-pi`.
|
|
493
|
+
pnpm 11 installs each argument as its own global group, so gentle-pi gets
|
|
494
|
+
its optional peer Pi beside it, possibly newer than 1.2.0
|
|
495
|
+
([The Pi that Gentle Shell runs](#the-pi-that-gentle-shell-runs)). pnpm 11
|
|
496
|
+
silently skips global postinstall scripts by default; the package-scoped
|
|
497
|
+
approval runs only gentle-pi's postinstall, which provisions Gentle AI. No
|
|
498
|
+
blanket build approval is ever passed.
|
|
499
|
+
2. `verify-global-list`: `pnpm list -g --depth 0 --json` must report exactly the
|
|
500
|
+
two pinned versions in the single listed project that owns gentle-pi
|
|
501
|
+
(other projects, such as the persisted npm and pnpm, are ignored), and
|
|
502
|
+
gentle-pi's absolute `path` must resolve inside PNPM_HOME. The Pi the
|
|
503
|
+
launcher runs from that real path, as Node resolves it (gentle-pi's own
|
|
504
|
+
`node_modules` first, then beside gentle-pi), must be a
|
|
505
|
+
`@earendil-works/pi-coding-agent` package at a stable version at least the
|
|
506
|
+
minimum (1.2.0); a missing, unreadable, prerelease or older one fails. When
|
|
507
|
+
it is not 1.2.0, the outcome reports it as `piVersion` and the wizard says
|
|
508
|
+
which Pi Gentle Shell runs.
|
|
509
|
+
3. `verify-shell-bin`: `$PNPM_HOME/bin/gentle-shell` (`gentle-shell.cmd` on
|
|
510
|
+
Windows) exists.
|
|
511
|
+
4. `verify-gentle-ai`: package-native integrity of the installed package. A
|
|
512
|
+
declared development override (`GENTLE_PI_GENTLE_AI_DEV_BINARY` or its
|
|
513
|
+
registration file) is never package-native proof and fails.
|
|
514
|
+
5. `shell-setup`: the installed public CLI, `node <gentle-pi>/bin/gentle-shell.mjs
|
|
515
|
+
setup`, with the child environment minus every `GENTLE_BOOTSTRAP_*` and
|
|
516
|
+
`GENTLE_INSTALL_*` key (case-insensitive on Windows); pnpm commands keep the
|
|
517
|
+
handoff. Nonzero, signal or deadline fails. The runner neither duplicates
|
|
518
|
+
setup logic nor writes the automatic provisioning marker. It requests the
|
|
519
|
+
last 4 KiB of setup's stderr, where `gentle-shell setup` reports errors, and
|
|
520
|
+
a failure carries `detail`: the last error line (containing `Error:` or a
|
|
521
|
+
pnpm `ERR_` code), else the last non-empty line, without terminal escapes,
|
|
522
|
+
control or bidi characters, with the user's home replaced by `~` and at most
|
|
523
|
+
300 characters (`setupErrorDetail`). The raw output is not kept; only this
|
|
524
|
+
step, `persist-path`, `acquire-go` (the Go folder in its way), `install-global`,
|
|
525
|
+
the main steps `build-gentle-ai-main` and `install-shell-main`, and the update's
|
|
526
|
+
`update-shell` have a detail. A failed `pnpm add -g` (`install-global`, or the
|
|
527
|
+
main package's) takes it from the same 4-KiB stderr tail or, when that has no
|
|
528
|
+
line, from stdout, where pnpm reports a failed postinstall; the postinstall
|
|
529
|
+
prints its error's whole `cause` chain and ends with the root cause's last
|
|
530
|
+
output line as `root cause Error: …`, so that line is the one selected. A main
|
|
531
|
+
step or the update takes it only from a `MainChannelError`: its message, then
|
|
532
|
+
the failed `go install` or extraction's stderr tail kept as its cause (for
|
|
533
|
+
example `main-commit-unavailable: … (HTTP 403)` when GitHub refuses the latest
|
|
534
|
+
commit). Any other error's text is never kept.
|
|
535
|
+
6. `persist-path`, only when `$PNPM_HOME/bin` was not on the user's own PATH:
|
|
536
|
+
`pnpm setup`, then the outcome is `terminal-action-required` with
|
|
537
|
+
`action: "open-new-terminal"`. A child environment never proves that a fresh
|
|
538
|
+
terminal resolves `gentle-shell`. On POSIX, `pnpm setup` picks the profile
|
|
539
|
+
to edit from the inherited `SHELL` variable (bash, zsh, fish, ksh, dash, sh
|
|
540
|
+
or nushell); without it pnpm 11.1.1 fails with `ERR_PNPM_UNKNOWN_SHELL`,
|
|
541
|
+
with another shell `ERR_PNPM_UNSUPPORTED_SHELL`. A terminal session always
|
|
542
|
+
exports `SHELL`; a launcher that does not (for example `docker exec`, some
|
|
543
|
+
service managers or IDE tasks) makes this step fail. pnpm prints these
|
|
544
|
+
errors on stdout, so a failure takes `detail` from the same 4-KiB stderr
|
|
545
|
+
tail or, when that has no line, from stdout. Before checking the shell, the
|
|
546
|
+
observed pnpm 11.1.1 also installs the latest `@pnpm/exe` globally ("Installing
|
|
547
|
+
pnpm CLI globally"), which is upstream behavior the runner does not control.
|
|
548
|
+
|
|
549
|
+
`ready` requires every step above to complete and the global bin to already be
|
|
550
|
+
on the user's PATH. Skipped, unverified or failed mandatory steps never yield
|
|
551
|
+
`ready`, and no existing installation, home or data is deleted on any outcome.
|
|
552
|
+
Deadlines are 30 seconds for probes and npm config commands, and 20 minutes
|
|
553
|
+
each for the install, persistence and setup steps, including `pnpm setup`,
|
|
554
|
+
which installs `@pnpm/exe` from the registry.
|
|
555
|
+
|
|
556
|
+
### Setup recovery
|
|
557
|
+
|
|
558
|
+
`gentle-shell setup` runs after `add -g`, so a setup that stops (for example on
|
|
559
|
+
the [GitHub API limit](#github-api-limit-during-setup)) or a failed `pnpm setup`
|
|
560
|
+
leaves Pi and gentle-pi installed. Rerunning the installer then completes that
|
|
561
|
+
installation instead of blocking as an existing stack.
|
|
562
|
+
|
|
563
|
+
Detection is the setup probe, with the same rule the runner applies
|
|
564
|
+
(`recoverableStackRoot`): the global listing names Pi and gentle-pi exactly
|
|
565
|
+
once each, both in the single project that owns gentle-pi, Pi at exactly
|
|
566
|
+
1.2.0 and gentle-pi at exactly this package's version, gentle-pi's path
|
|
567
|
+
resolves (realpath) inside PNPM_HOME, and the Pi beside gentle-pi is at least
|
|
568
|
+
the minimum. Preflight also requires Pi, Shell and
|
|
569
|
+
Gentle AI to be `reusable`. Anything else (another version, a second listing,
|
|
570
|
+
a path outside PNPM_HOME, Pi missing) keeps the setup probe unknown, so a
|
|
571
|
+
foreign or other-version stack stays blocked exactly as before.
|
|
572
|
+
|
|
573
|
+
The recovery plan is all or nothing: `setup-shell`, `verify-readiness` and
|
|
574
|
+
`setup-global-bin` only when the global bin is off PATH. The runner then runs
|
|
575
|
+
`check-npm`, `check-global-bin` and `check-recoverable-stack`, which lists the
|
|
576
|
+
global packages again and blocks as `existing-stack-unverified` when the stack
|
|
577
|
+
no longer matches (or as `global-list-unavailable` when pnpm cannot list).
|
|
578
|
+
It skips `install-global` (never `add -g`) and continues with the unchanged
|
|
579
|
+
`verify-global-list`, `verify-shell-bin`, `verify-gentle-ai`, `shell-setup`
|
|
580
|
+
and, when planned, `persist-path`, with the same outcomes. A recovery never
|
|
581
|
+
persists the runtime: the earlier run persisted it before installing the
|
|
582
|
+
stack, and `check-npm` still proves a usable npm in the child environment.
|
|
583
|
+
|
|
584
|
+
Rerunning `gentle-shell setup` is acceptable because it is the public,
|
|
585
|
+
rerunnable command the clean installation already runs; the runner adds no
|
|
586
|
+
setup logic of its own. The review screen says "Gentle Shell is already
|
|
587
|
+
installed; this completes setup" and offers **Complete setup**.
|
|
588
|
+
|
|
589
|
+
### Runtime persistence
|
|
590
|
+
|
|
591
|
+
A bootstrap-acquired Node lives in a temporary tools directory, so a later
|
|
592
|
+
terminal would not find `node` or the working npm that Gentle AI's Engram step
|
|
593
|
+
needs. Every persistence step uses the same pnpm argv prefix and child
|
|
594
|
+
environment as the stack install, and the runner persists only what is missing.
|
|
595
|
+
|
|
596
|
+
For a bootstrap-only Node, the full group runs these fixed steps:
|
|
597
|
+
|
|
598
|
+
1. `persist-node`: `pnpm runtime set node 24.21.0 -g`, which installs Node under
|
|
599
|
+
PNPM_HOME and links `node` into `$PNPM_HOME/bin`.
|
|
600
|
+
2. `persist-package-managers`: `pnpm add -g npm@11.19.0 pnpm@11.1.1`, which
|
|
601
|
+
adds the `npm`, `npx` and `pnpm` shims to `$PNPM_HOME/bin`. No build
|
|
602
|
+
approval of any kind is passed. When pnpm is already persistent (a newer
|
|
603
|
+
pnpm 11 in `$PNPM_HOME/bin`, for example), the plan has `persist-npm`
|
|
604
|
+
instead: `pnpm add -g npm@11.19.0` only, so that pnpm is never downgraded.
|
|
605
|
+
3. `verify-persistent-runtime`: in the child environment both `node` and `npm`
|
|
606
|
+
must resolve (`exec.LookPath` order) from `$PNPM_HOME/bin`, the node must be
|
|
607
|
+
spawnable (`.exe`/`.com` on Windows) and print `v24.21.0`.
|
|
608
|
+
4. `check-npm`: the npm gate above. `$PNPM_HOME/bin` is first in the child
|
|
609
|
+
environment, so it resolves the pinned shim, never a user npm.
|
|
610
|
+
5. `configure-npm-prefix`: with a pnpm-managed node, npm's default prefix is
|
|
611
|
+
derived from a path inside pnpm's store, so `npm install -g` would write
|
|
612
|
+
there. The runner reads `npm config get prefix` with the persisted node.
|
|
613
|
+
Unless an `npm_config_prefix` (any case) or `PREFIX` variable is set, it
|
|
614
|
+
asks pnpm for its store with `pnpm store path` (30-second deadline); a
|
|
615
|
+
failed, non-absolute or unresolvable answer fails this step rather than
|
|
616
|
+
assuming `$PNPM_HOME/store`. Only when the effective prefix resolves inside
|
|
617
|
+
that store does it run
|
|
618
|
+
`npm config set prefix <PNPM_HOME> --location=user`, then require the
|
|
619
|
+
effective prefix to equal PNPM_HOME. An explicit user prefix elsewhere
|
|
620
|
+
makes npm report that prefix, so it is never overwritten. A failed read,
|
|
621
|
+
write or verification fails this step with the completed list.
|
|
622
|
+
|
|
623
|
+
The outcome then also reports `npmPrefix: "configured"` or `"unchanged"`.
|
|
624
|
+
|
|
625
|
+
A persistent Node is never replaced or shadowed: there is no `runtime set` and
|
|
626
|
+
no prefix change. An older Node on the user's PATH is not a persistent Node
|
|
627
|
+
here: the bootstrap's pinned Node runs the wizard, so the full group above
|
|
628
|
+
persists it next to the older one, which is left unchanged. One fixed add runs, then only the matching checks:
|
|
629
|
+
|
|
630
|
+
| Intent | Command | Then |
|
|
631
|
+
| --- | --- | --- |
|
|
632
|
+
| `persist-npm` | `pnpm add -g npm@11.19.0` | `check-npm` |
|
|
633
|
+
| `persist-pnpm` | `pnpm add -g pnpm@11.1.1` | `verify-persistent-pnpm` |
|
|
634
|
+
| `persist-package-managers` | `pnpm add -g npm@11.19.0 pnpm@11.1.1` | `check-npm`, `verify-persistent-pnpm` |
|
|
635
|
+
|
|
636
|
+
`check-npm` is the npm gate above. `verify-persistent-pnpm` requires
|
|
637
|
+
the first `pnpm` in the child environment to be in `$PNPM_HOME/bin` (`.cmd`
|
|
638
|
+
on Windows), resolving like the npm shim to `node_modules/pnpm/bin/pnpm.mjs`
|
|
639
|
+
inside PNPM_HOME, with `package.json` `pnpm@11.1.1`, and `node pnpm.mjs
|
|
640
|
+
--version` printing `11.1.1`. When pnpm is not added, `check-npm` stays a
|
|
641
|
+
pre-install gate.
|
|
642
|
+
`npm config get` ignores `--location`, and `.npmrc` files can hold credentials,
|
|
643
|
+
so the runner never reads them: a user-level prefix that itself points into
|
|
644
|
+
the store is indistinguishable from the default and would be replaced.
|
|
645
|
+
|
|
646
|
+
### Existing installations
|
|
647
|
+
|
|
648
|
+
The wizard installs what is missing and updates what exists; it updates an older
|
|
649
|
+
Pi but never reinstalls or downgrades it, and nothing changes before consent.
|
|
650
|
+
|
|
651
|
+
| Found | Plan |
|
|
652
|
+
| --- | --- |
|
|
653
|
+
| A compatible Pi (pnpm-global, or any `pi` on PATH whose `pi --version` reports a stable version ≥ the minimum) and no Gentle Shell | Install only Gentle Shell: `pnpm add -g gentle-pi@<version> --allow-build=gentle-pi`, after checking that pnpm lists no gentle-pi (`check-existing-shell`). Pi is left as it is, also when it is newer than 1.2.0. |
|
|
654
|
+
| A Gentle Shell that pnpm or npm owns | `update-shell-release` when it is older, unusable or a main build; `update-shell-main` on the main channel (needs Go). Then `setup-shell`. A missing Pi is installed first (`install-pi`). |
|
|
655
|
+
| A current Gentle Shell that npm owns, on release | Nothing to do. |
|
|
656
|
+
| A Pi older than the minimum that pnpm or npm owns | `update-pi` to `PI_INSTALL_VERSION` with that package manager (`pnpm add -g @earendil-works/pi-coding-agent@<version>` or `npm install -g …`), before any Gentle Shell step: ahead of `install-shell` or `update-shell-*`, or alone when Gentle Shell is current. The plan names the found and target versions and the manager. |
|
|
657
|
+
| A Pi older than the minimum that neither pnpm nor npm owns (mise, Homebrew, a standalone binary) | Left unchanged. `install-pi` adds the installer's Pi with pnpm exactly as when Pi is absent (in the same `pnpm add -g` as gentle-pi, before an update of Gentle Shell, or alone when Gentle Shell is current), and the plan says so before consent. |
|
|
658
|
+
| A Gentle Shell neither pnpm nor npm owns (an `npm link` of a source checkout, for example) | Blocked with an explanation; never reinstalled. |
|
|
659
|
+
| A Node.js older than the minimum with a stable version (mise, nvm, Homebrew, an old installer) | Left unchanged. The POSIX bootstrap acquires its verified Node 24.21.0 as when Node is absent, and the plan persists it (`persist-node`, `persist-package-managers`, `configure-npm-prefix`). The plan says so before consent. An unknown or prerelease version still blocks. Windows: `bootstrap.cmd` does the same, and also acquires its Node for a user Node whose storage it cannot trust (nvm-windows' symlink, fnm's junction), which it never runs. |
|
|
660
|
+
| A pnpm older than 11.1.1, or whose engine rejects the bootstrap's Node (or, for the bootstraps only, another major) | Left unchanged. Both bootstraps acquire the verified pnpm 11.1.1 as when pnpm is absent, and the plan persists it (`persist-pnpm`, or `persist-package-managers` with npm). The plan says so before consent. Unknown evidence still blocks. |
|
|
661
|
+
| That pnpm in `$PNPM_HOME/bin` itself (where persisting pnpm writes) | Never replaced or downgraded. At 11.1.1 or newer, of any major (pnpm 12 included), it is used as it is and nothing is persisted over it. An older one blocks before consent; the guidance names the found and required versions and updates it with `pnpm self-update 11.1.1`. |
|
|
662
|
+
| A pnpm in `$PNPM_HOME/bin` whose shim is not recognized or whose `pnpm --version` reports no stable version | Left unchanged and never run again. The bootstrap's verified pnpm runs the installer and nothing is persisted over it (no `persist-pnpm`). The plan says so before consent. |
|
|
663
|
+
| An older Node.js or incompatible pnpm next to an existing Gentle Shell (updated or current) | Blocked before consent: runtimes are persisted only while installing Gentle Shell. A setup recovery is not blocked and persists nothing. |
|
|
664
|
+
| A Go missing or older than the minimum (main channel, or a Windows build) | Left unchanged. The installer downloads its [pinned Go](#pinned-go) only for the build, and the plan says so before consent with the found version (or missing). A Go whose version cannot be checked gets the pinned Go the same way, and is never run for the build. A release install on macOS or Linux never downloads Go. |
|
|
665
|
+
|
|
666
|
+
Ownership comes from real paths ([`installOwner`](../scripts/main-channel.mjs)):
|
|
667
|
+
pnpm when the package lives under PNPM_HOME, npm only when it is
|
|
668
|
+
`<npm root -g>/gentle-pi` itself. On Windows the probe follows
|
|
669
|
+
`gentle-shell.cmd` to the package it runs, and npm and pnpm never run as a
|
|
670
|
+
`.cmd`: `runUpgrade` takes an `invocation(name)` adapter
|
|
671
|
+
(`upgradeInvocation`) that runs npm through what `npm.cmd` runs
|
|
672
|
+
(`npmInvocation`) and pnpm from the bootstrap handoff in the wizard, or through
|
|
673
|
+
`windowsInvocation` of the first `pnpm` on PATH in `gentle-shell upgrade`. A
|
|
674
|
+
manager that resolves only to a `.cmd` or `.bat` counts as missing. On macOS
|
|
675
|
+
and Linux nothing changes: the command on PATH runs as it is. The update runs
|
|
676
|
+
the same code as `gentle-shell upgrade --channel <channel>`
|
|
677
|
+
(`check-installed-shell`, `update-shell`), then `verify-updated-shell` requires
|
|
678
|
+
the same owner and a stable version not older than before (release) or a
|
|
679
|
+
`-main.<sha12>` version (main), and for pnpm a Pi beside the updated
|
|
680
|
+
gentle-pi at least the minimum (its version is reported when it is not 1.2.0);
|
|
681
|
+
release also re-runs `verify-gentle-ai`. An
|
|
682
|
+
update never runs `pnpm setup`: the existing installation already has its PATH.
|
|
683
|
+
|
|
684
|
+
An older Pi uses the same ownership rule with its own package name
|
|
685
|
+
(`<npm root -g>/@earendil-works/pi-coding-agent` for npm, on every platform:
|
|
686
|
+
Windows runs `npm root -g` and `npm install -g` through what `npm.cmd` runs and
|
|
687
|
+
compares roots case-insensitively). Its probe reports
|
|
688
|
+
`owner` only when Pi is older than the minimum, and the plan records the found
|
|
689
|
+
version and owner in `tools.pi`. Before any change, `check-installed-pi` finds
|
|
690
|
+
Pi again and requires that same version and owner, a stable version below the
|
|
691
|
+
minimum (so the update never downgrades), and for npm an `npm root -g` that
|
|
692
|
+
holds it. `update-pi` then runs `pnpm add -g` through the runner's pnpm, or the
|
|
693
|
+
user's `npm install -g` with the user's environment. `verify-updated-pi`
|
|
694
|
+
requires one Pi from the same owner (pnpm listing it twice makes it ambiguous),
|
|
695
|
+
at `PI_INSTALL_VERSION` or newer, and for npm at the same root. A failure stops
|
|
696
|
+
before any Gentle Shell step. With pnpm 11.1.1, `pnpm add -g` of a newer Pi
|
|
697
|
+
replaces the existing global package in place (its isolated global directory is
|
|
698
|
+
swapped), whether Pi was added alone or together with gentle-pi, so the next run
|
|
699
|
+
sees a single Pi; `npm install -g` replaces it in npm's global root.
|
|
700
|
+
|
|
701
|
+
An older Node.js or incompatible pnpm is never changed or removed, and once the
|
|
702
|
+
bootstrap has its pinned copy the installer never runs it for installation
|
|
703
|
+
steps: the wizard and runner run under the bootstrap's Node (`process.execPath`),
|
|
704
|
+
`persist-node` puts the pinned Node first in the runner's child `PATH`, and the
|
|
705
|
+
runner invokes the bootstrap's pnpm by absolute path (`runnerEnvironment` puts
|
|
706
|
+
its directory first on POSIX; Windows uses the `GENTLE_INSTALL_PNPM_*`
|
|
707
|
+
handoff). The wizard reads the user's pnpm only for its `--version` (POSIX,
|
|
708
|
+
from `/`). The plan's runtime persistence text names the found and pinned
|
|
709
|
+
versions and says the user's copy is left unchanged. It promises no PATH
|
|
710
|
+
precedence: a version manager such as `mise activate` puts its own paths first
|
|
711
|
+
again on every prompt, so new terminals may still run the older Node or pnpm.
|
|
712
|
+
An update of an existing Gentle Shell persists nothing, so an older Node or
|
|
713
|
+
incompatible pnpm still blocks it (`incompatible-tool`).
|
|
714
|
+
|
|
715
|
+
An older Pi that neither pnpm nor npm owns is never run, changed or removed. The
|
|
716
|
+
plan's `install-pi` description names its version and the version the installer
|
|
717
|
+
adds. Alone, `install-pi` is checked like a shell-only installation:
|
|
718
|
+
`check-existing-pi` requires pnpm to list no Pi, `pnpm add -g` adds it, and
|
|
719
|
+
`verify-installed-pi` requires one pnpm-global Pi at `PI_INSTALL_VERSION` or
|
|
720
|
+
newer. The `pi` probe reads pnpm's global list before PATH, so the next run
|
|
721
|
+
reuses the pnpm-global Pi and no longer looks at the other one. Gentle Shell
|
|
722
|
+
prefers the Pi beside gentle-pi over any `pi` on PATH
|
|
723
|
+
([The Pi that Gentle Shell runs](#the-pi-that-gentle-shell-runs)); a
|
|
724
|
+
terminal's `pi` command may still run the older one when it comes first on PATH.
|
|
725
|
+
|
|
726
|
+
### The Pi that Gentle Shell runs
|
|
727
|
+
|
|
728
|
+
The launcher runs the Pi that Node resolves from gentle-pi (gentle-pi's own
|
|
729
|
+
`node_modules`, then beside it), and only without one the first `pi` on PATH.
|
|
730
|
+
pnpm 11 installs every `pnpm add -g` argument in its own isolated global
|
|
731
|
+
directory (group) and auto-installs gentle-pi's optional peer Pi in gentle-pi's
|
|
732
|
+
group at the latest version: verified with pnpm 11.1.1 in an isolated
|
|
733
|
+
PNPM_HOME, `pnpm add -g @earendil-works/pi-coding-agent@1.0.0 gentle-pi@4.0.0`
|
|
734
|
+
lists Pi 1.0.0 while the Pi beside gentle-pi is 1.1.0. So Gentle Shell may run a
|
|
735
|
+
Pi newer than `PI_INSTALL_VERSION`, always at least the minimum: the runner
|
|
736
|
+
verifies that Pi (`verify-global-list`, the setup recovery, a pnpm
|
|
737
|
+
`verify-updated-shell`) and, when it is not `PI_INSTALL_VERSION` (1.2.0 now; the 1.0.0/1.1.0 figures above are that older verification), the outcome says which Pi
|
|
738
|
+
Gentle Shell runs. `--config.auto-install-peers=false` would leave gentle-pi
|
|
739
|
+
with no Pi beside it, so the launcher would run any `pi` on PATH, and
|
|
740
|
+
`--config.overrides` is ignored for global installs.
|
|
741
|
+
|
|
742
|
+
Pi and gentle-pi stay separate pnpm groups on every path (the clean install,
|
|
743
|
+
the main package and `gentle-shell upgrade`). A single comma-separated group
|
|
744
|
+
(`pnpm add -g <pi>,gentle-pi@<version>`) would pin the Pi beside gentle-pi, but
|
|
745
|
+
pnpm replaces every existing group that shares a package with a new one: Pi's
|
|
746
|
+
own `pi update` (`pnpm install -g @earendil-works/pi-coding-agent@<latest>`)
|
|
747
|
+
then deleted the whole group, gentle-pi and its `gentle-shell` command
|
|
748
|
+
included, and the group also replaced unrelated packages sharing the user's Pi
|
|
749
|
+
group. User decision (2026-10-10): "Quitar el agrupamiento (recomendado)".
|
|
750
|
+
Pinning the Pi gentle-pi runs at the package level (an exact gentle-pi
|
|
751
|
+
dependency instead of the host peer) needs its own design.
|
|
752
|
+
|
|
753
|
+
### Documented decisions
|
|
754
|
+
|
|
755
|
+
- `check-npm` failing after `persist-npm` (a reported case) no longer occurs in
|
|
756
|
+
that setup: on POSIX a working user npm (mise, Volta, asdf and others) is
|
|
757
|
+
accepted, so `persist-npm` is not planned for it. The failure itself cannot
|
|
758
|
+
be root-caused without the reporter's `PNPM_HOME`, `pnpm store path` and
|
|
759
|
+
`readlink -f $PNPM_HOME/bin/npm`, so nothing else changes until that evidence
|
|
760
|
+
exists.
|
|
761
|
+
- <a id="pnpm-12"></a>A pnpm 12 in `$PNPM_HOME/bin` no longer blocks. Measured on
|
|
762
|
+
macOS with pnpm 12.11.2 (`pnpm self-update` into an isolated `PNPM_HOME`): the
|
|
763
|
+
bootstrap's pnpm 11.1.1 ran `bin -g`, `store path`, `add -g` of Pi and of
|
|
764
|
+
`gentle-pi --allow-build=gentle-pi`, and `list -g --depth 0 --json` there; then
|
|
765
|
+
pnpm 12 listed, re-added (the `gentle-shell upgrade` path) and removed the same
|
|
766
|
+
packages, and pnpm 11 still read the global home. Both majors share
|
|
767
|
+
`store/v11` and `global/v11`. With pnpm 12 alone, `runtime set node … -g`,
|
|
768
|
+
`add -g npm@…`, Pi and `gentle-pi` also succeeded. pnpm 12 refuses
|
|
769
|
+
`add -g pnpm` (`ERR_PNPM_GLOBAL_PNPM_INSTALL`); the installer never persists
|
|
770
|
+
pnpm over a compatible one, so that command is not run.
|
|
771
|
+
|
|
772
|
+
### Main channel
|
|
773
|
+
|
|
774
|
+
The Review screen offers **Latest release** (default) or **Latest main**. Choosing
|
|
775
|
+
main reloads the plan with `GET /api/plan?channel=main`; the install request is
|
|
776
|
+
unchanged because the consented plan already holds its steps. Neither repository
|
|
777
|
+
publishes main builds, so main is built on this computer
|
|
778
|
+
([`scripts/main-channel.mjs`](../scripts/main-channel.mjs)):
|
|
779
|
+
|
|
780
|
+
- Preflight reuses a Go ≥ the Windows minimum on every platform; a missing or
|
|
781
|
+
older Go, or one whose version cannot be checked, gets the
|
|
782
|
+
[pinned Go](#pinned-go), first on the build's PATH. Main steps are added only when the
|
|
783
|
+
plan installs or completes setup; a stack that is already set up switches with
|
|
784
|
+
`gentle-shell upgrade --channel main`, or the wizard's update plan below.
|
|
785
|
+
- After the release stack is installed and `verify-gentle-ai` passed, the runner:
|
|
786
|
+
1. `build-gentle-ai-main`: resolves the latest `main` commit of
|
|
787
|
+
Gentleman-Programming/gentle-ai (GitHub API, raw SHA), runs
|
|
788
|
+
`go install github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai@<sha>`
|
|
789
|
+
in a sealed Go environment (`GOPROXY=https://proxy.golang.org`,
|
|
790
|
+
`GOSUMDB=sum.golang.org`, `GOTOOLCHAIN=local`, `GOFLAGS=-modcacherw`), requires
|
|
791
|
+
the binary's version to end in that commit's first 12 characters, keeps it
|
|
792
|
+
under `<config home>/main/gentle-ai/<sha>/` and registers it as the
|
|
793
|
+
`gentle-pi.dev-binary/v1` override, which never falls back to the pinned
|
|
794
|
+
binary silently.
|
|
795
|
+
2. `install-shell-main`: downloads the latest `main` commit's source tarball,
|
|
796
|
+
extracts it (on Windows with `%SystemRoot%\System32\tar.exe` by absolute
|
|
797
|
+
path, never a `tar` from PATH: Git for Windows' MSYS tar in `usr\bin` reads
|
|
798
|
+
a `D:\…` archive path as a remote host and fails with `Cannot connect to D:
|
|
799
|
+
resolve failed`), sets its version to `<version>-main.<sha12>`, removes `prepack` and `prepare`
|
|
800
|
+
(which would run the full test suite), packs it with `pnpm pack` into
|
|
801
|
+
`<config home>/main/packages/`, runs `pnpm add -g <tgz> --allow-build=gentle-pi`
|
|
802
|
+
and requires `pnpm list -g` to report that exact version under PNPM_HOME,
|
|
803
|
+
with a Pi at least the minimum beside it.
|
|
804
|
+
Installing `github:` or codeload URLs directly is not used: pnpm runs
|
|
805
|
+
`prepack` for both.
|
|
806
|
+
3. `record-channel`: writes `{"schema":"gentle-shell.channel/v1","channel":"main",
|
|
807
|
+
"shellCommit":…,"gentleAiCommit":…}` to `<config home>/channel.json`.
|
|
808
|
+
- `shell-setup` then runs from the main package. The config home is
|
|
809
|
+
`GENTLE_PI_CONFIG_HOME` or `~/.pi/gentle-ai`, as for the dev-binary override.
|
|
810
|
+
- `gentle-shell upgrade --channel main` builds with the user's Go only when it
|
|
811
|
+
reports a stable version that meets the minimum. Otherwise (missing, older, or
|
|
812
|
+
a version it cannot check) it builds with the pinned Go under
|
|
813
|
+
`<config home>/tools/go`, downloading and verifying it there with the same
|
|
814
|
+
`acquireGo` as the wizard when no earlier run published it, and only when
|
|
815
|
+
Gentle AI is rebuilt. A failed download stops with `main-requires-tools`
|
|
816
|
+
before anything is built; the user's Go is never run for the build.
|
|
817
|
+
- Trust: Gentle AI source is verified by Go's checksum database; the Gentle Shell
|
|
818
|
+
source is fetched over TLS from GitHub by exact commit SHA, with no pinned
|
|
819
|
+
digest. Main builds are development builds.
|
|
820
|
+
|
|
821
|
+
### Pinned Go
|
|
822
|
+
|
|
823
|
+
Go is needed only after the wizard knows the channel, so it is not a bootstrap
|
|
824
|
+
prerequisite: Node and pnpm are acquired by the bootstraps because the wizard
|
|
825
|
+
cannot run without them, while Go is acquired by the runner after consent, as the
|
|
826
|
+
first mutating steps of a plan that builds with it, before anything is persisted
|
|
827
|
+
or installed. Nothing is downloaded before consent, and a failed download leaves
|
|
828
|
+
the computer as it was.
|
|
829
|
+
|
|
830
|
+
- `acquire-go`: [`acquireGo`](../scripts/installer-downloads.mjs) downloads the
|
|
831
|
+
official go.dev archive for the platform (darwin, linux and windows; amd64 and
|
|
832
|
+
arm64), pinned by exact version (Go 1.25.14), size and SHA-256 from go.dev's
|
|
833
|
+
published checksums (`https://dl.google.com/go/<file>`, where go.dev/dl
|
|
834
|
+
redirects). The bytes are verified before they are read. The `.tar.gz` (POSIX
|
|
835
|
+
ustar, PAX `path` only) or Windows `.zip` (stored or deflated, CRC-checked) is
|
|
836
|
+
extracted in process into a private staging directory: only regular files and
|
|
837
|
+
directories under `go/`, with no links, traversal, absolute paths or duplicate
|
|
838
|
+
names, and `go/VERSION` must name the pin. The tree is published without
|
|
839
|
+
replacing anything as `<config home>/tools/go/<version>/go` (directory `0700`):
|
|
840
|
+
the marker naming the archive is written into the staging directory, which then
|
|
841
|
+
becomes `<version>` in one rename, so that folder never exists unmarked. A
|
|
842
|
+
later run reuses a marked copy without downloading. Any mismatch fails the step
|
|
843
|
+
and publishes nothing. An existing `<version>` folder without the marker (left
|
|
844
|
+
by an interrupted run of an earlier installer, or not the installer's) is never
|
|
845
|
+
replaced: the step fails before downloading, and the wizard shows that folder
|
|
846
|
+
(home as `~`) with guidance to remove it.
|
|
847
|
+
- `verify-go`: the published `go` (by absolute path, `GOTOOLCHAIN=local`) must
|
|
848
|
+
print `go version go<pin> …`.
|
|
849
|
+
- The pinned Go is never put on the user's PATH or profile, and the user's Go
|
|
850
|
+
(Homebrew, mise, an official package, anything) is only asked its version
|
|
851
|
+
(`go version`, as the preflight probe always did), never changed or removed. The runner passes it by path to `build-gentle-ai-main` and puts its
|
|
852
|
+
`bin` directory first on PATH only for the children that may build Gentle AI:
|
|
853
|
+
`pnpm add -g` (gentle-pi's Windows postinstall finds `go.exe` there) and the
|
|
854
|
+
wizard's Gentle Shell update.
|
|
855
|
+
- The plan copy before consent names the pinned version, the Go found (or
|
|
856
|
+
missing), and that the user's Go, PATH and shell profile stay unchanged.
|
|
857
|
+
|
|
858
|
+
### Remaining T7 real-machine checks
|
|
859
|
+
|
|
860
|
+
The runner is verified only with deterministic fake adapters. Observed pnpm
|
|
861
|
+
11.1.1 facts come from one Linux laboratory run. Before claiming support, T7
|
|
862
|
+
must establish on real Windows, macOS and Linux machines:
|
|
863
|
+
|
|
864
|
+
- `pnpm list -g --json` reports a dependency `path` that resolves inside
|
|
865
|
+
PNPM_HOME for the installed layout, and prints a parseable JSON array (for
|
|
866
|
+
example `[]`) when no global package exists yet; empty output would block
|
|
867
|
+
a clean installation as `global-list-unavailable`;
|
|
868
|
+
- Windows npm resolution matches Gentle AI's actual lookup, including its
|
|
869
|
+
current-directory handling;
|
|
870
|
+
- the scoped `--allow-build=gentle-pi` postinstall provisions package-native
|
|
871
|
+
Gentle AI, including Windows source builds with the user's Go;
|
|
872
|
+
- `gentle-shell setup` completes the Engram init step with the accepted npm, and
|
|
873
|
+
whether Gentle AI selects `pnpm dlx` or `npm exec`;
|
|
874
|
+
- `pnpm setup` makes `gentle-shell` resolvable in a fresh terminal of each
|
|
875
|
+
supported shell (the Linux lab showed it does not make `pnpm` itself
|
|
876
|
+
resolvable);
|
|
877
|
+
- the chosen deadlines suffice on slow networks, and pnpm's registry and
|
|
878
|
+
update-notice traffic is acceptable;
|
|
879
|
+
- interrupted installs are recoverable by rerunning the wizard without data loss;
|
|
880
|
+
- `pnpm runtime set node -g` on Windows links a spawnable `node.exe` (a `.cmd`
|
|
881
|
+
shim fails `verify-persistent-runtime`), and pnpm's real `npm.cmd`/POSIX
|
|
882
|
+
shim text matches the accepted target forms;
|
|
883
|
+
- npm's Windows global layout: it places executables in the prefix itself, so
|
|
884
|
+
a PNPM_HOME prefix may not put `npm install -g` executables on PATH; also
|
|
885
|
+
whether Windows npm's default prefix lands in the store at all;
|
|
886
|
+
- `pnpm store path` reports a directory (for example `$PNPM_HOME/store/v11`)
|
|
887
|
+
that actually contains the effective npm prefix of a pnpm-managed node;
|
|
888
|
+
- pnpm's real global `pnpm` shim targets `node_modules/pnpm/bin/pnpm.mjs`, and
|
|
889
|
+
a persistent Node missing npm has a prefix outside the store (the variant
|
|
890
|
+
without `runtime set` configures no prefix);
|
|
891
|
+
- download integrity of the Node runtime and the npm and pnpm tarballs fetched
|
|
892
|
+
during persistence (the Linux lab saw a lockfile hash matching the Node pin,
|
|
893
|
+
not download-time verification);
|
|
894
|
+
- pnpm's update notifier contacting registry.npmjs.org during these commands;
|
|
895
|
+
- `pnpm list -g --json` listing persisted npm/pnpm and the stack as separate
|
|
896
|
+
projects with a usable gentle-pi `path`;
|
|
897
|
+
- the probes' `access(W_OK)` writability check, which ignores Windows ACLs, and
|
|
898
|
+
process deadlines that kill only the direct child.
|
|
899
|
+
|
|
900
|
+
## Local wizard host and entry
|
|
901
|
+
|
|
902
|
+
`bin/gentle-shell-install.mjs` is the entry the bootstrap's `launchWizard`
|
|
903
|
+
starts with no argv and inherited stdio. It is not a `package.json` `bin`
|
|
904
|
+
command; it ships inside the package's `bin/` directory and is listed in
|
|
905
|
+
`scripts/verify-package-files.mjs` with the other installer files
|
|
906
|
+
(`installerPaths`).
|
|
907
|
+
|
|
908
|
+
### Entry wiring
|
|
909
|
+
|
|
910
|
+
- Probes: `createProbes` over the wizard's own environment with
|
|
911
|
+
`hostAdapters()` (argv spawn with `shell:false`, deadlines, bounded output,
|
|
912
|
+
read-only fs). Each plan request creates fresh probes, then
|
|
913
|
+
`collectInventory` and `planPreflight` run server-side.
|
|
914
|
+
- Runner: `runStandardInstall` with `nodePath: process.execPath`, the same
|
|
915
|
+
adapters, `packageNativeGentleAi` and `runnerEnvironment`: the user's real
|
|
916
|
+
PATH from `userEnvironment`, so a temporary bootstrap npm or node never counts
|
|
917
|
+
as persistent and `onPath` stays truthful. A POSIX bootstrap pnpm, which the
|
|
918
|
+
bootstrap passes only through PATH, stays reachable as the **last** PATH
|
|
919
|
+
entry (its directory holds only the `pnpm` wrapper); Windows uses the
|
|
920
|
+
`GENTLE_INSTALL_PNPM_*` handoff, which `userEnvironment` keeps.
|
|
921
|
+
- The entry prints the session URL, then opens it through a private redirect
|
|
922
|
+
file so the one-time code never appears in the opener's argv (visible to
|
|
923
|
+
local process listings). `writeRedirect` creates a fresh `mkdtemp` directory
|
|
924
|
+
`gentle-shell-install-*` under the OS temp dir (mode 0700) holding
|
|
925
|
+
`open.html` (mode 0600, exclusive create): a static page with a meta refresh
|
|
926
|
+
and a link to the session URL, no script. `openBrowser` passes only that file
|
|
927
|
+
path to a fixed opener spawned with `shell:false` and detached:
|
|
928
|
+
`/usr/bin/open` on macOS, `xdg-open` on Linux, `%SystemRoot%\System32\rundll32.exe
|
|
929
|
+
url.dll,FileProtocolHandler` on Windows (absolute, so the current directory is
|
|
930
|
+
never searched). Without an opener nothing is written; a failed write opens
|
|
931
|
+
nothing; an opener that fails to start removes the file. The file and its
|
|
932
|
+
directory are removed (file, then directory, best effort) once the code is
|
|
933
|
+
redeemed (`onRedeemed`) or when the host closes. File modes are POSIX only;
|
|
934
|
+
on Windows the per-user `%TEMP%` ACLs apply. The printed URL stays the
|
|
935
|
+
fallback.
|
|
936
|
+
|
|
937
|
+
| Exit code | When |
|
|
938
|
+
| --- | --- |
|
|
939
|
+
| 0 | The last installation outcome was `ready` or `terminal-action-required`. Node, npm and pnpm now persist under `$PNPM_HOME` or were already the user's, so the bootstrap removes the tools directory it created. |
|
|
940
|
+
| 1 | `blocked`, `failed`, a shutdown or idle close without a completed installation, a signal, or a host start failure. The bootstrap removes its tools. |
|
|
941
|
+
|
|
942
|
+
SIGINT and SIGTERM close the server and exit with the code above. An
|
|
943
|
+
installation that is still running is not cancelled cleanly: Ctrl+C reaches the
|
|
944
|
+
runner's children through the terminal's process group, but SIGTERM does not.
|
|
945
|
+
|
|
946
|
+
Known debt, not fixed yet: on SIGINT or SIGTERM, `close` does not wait for a
|
|
947
|
+
running installation to settle, and runner deadlines kill only the direct child
|
|
948
|
+
process, not the processes it started (for example, the ones `pnpm` or
|
|
949
|
+
`gentle-shell setup` spawn).
|
|
950
|
+
|
|
951
|
+
### Security model
|
|
952
|
+
|
|
953
|
+
`scripts/installer-server.mjs` exports `createInstallerServer({ collectPlan,
|
|
954
|
+
runInstall, assetsDir, now?, random?, limits?, onRedeemed? })`, a
|
|
955
|
+
dependency-free `node:http` host. Every effect is injected; tests use fake
|
|
956
|
+
plans and runners. `onRedeemed()` runs once when the one-time code is used; an
|
|
957
|
+
exception from it is ignored.
|
|
958
|
+
|
|
959
|
+
- Binds `127.0.0.1` on port 0 only and asserts the bound IPv4 address; never
|
|
960
|
+
`localhost`, `::` or `0.0.0.0`.
|
|
961
|
+
- Every request needs `Host` exactly `127.0.0.1:<port>`, otherwise 421 before
|
|
962
|
+
any authorization (a missing Host included).
|
|
963
|
+
- Every response carries `Content-Security-Policy: default-src 'self';
|
|
964
|
+
frame-ancestors 'none'; base-uri 'none'; form-action 'none'`,
|
|
965
|
+
`X-Content-Type-Options: nosniff`, `Cache-Control: no-store`,
|
|
966
|
+
`Referrer-Policy: no-referrer`, `X-Frame-Options: DENY`,
|
|
967
|
+
`Cross-Origin-Resource-Policy: same-origin` and
|
|
968
|
+
`Cross-Origin-Opener-Policy: same-origin`. No CORS header is ever sent; error
|
|
969
|
+
bodies are generic codes such as `{ "error": "forbidden" }`.
|
|
970
|
+
- Session: a 32-byte random secret becomes the cookie
|
|
971
|
+
`gentle_install_session` (`HttpOnly; SameSite=Strict; Path=/`). The printed
|
|
972
|
+
URL `http://127.0.0.1:<port>/session?code=<code>` carries a separate random
|
|
973
|
+
one-time code that expires after 2 minutes; a valid `GET /session` consumes it
|
|
974
|
+
and answers 200 with a minimal HTML page that sets the cookie and refreshes
|
|
975
|
+
to `/` (`<meta http-equiv="refresh" content="0; url=/">`, no script or
|
|
976
|
+
style, same strict headers). It is not a 303: reached from the `file:`
|
|
977
|
+
redirect page, a redirect chain counts as cross-site and the
|
|
978
|
+
`SameSite=Strict` cookie could be withheld on `/`, while a navigation started
|
|
979
|
+
by this same-origin page carries it. Code and cookie comparisons use
|
|
980
|
+
`timingSafeEqual`. Every asset and `/api/*` request needs the cookie (401).
|
|
981
|
+
- A request with an `Origin` other than `http://127.0.0.1:<port>` is rejected
|
|
982
|
+
(403), including GET. Every `/api/*` request, GET included, needs the header
|
|
983
|
+
`X-Gentle-Install: 1` (403 otherwise, before any probe or plan rotation), so
|
|
984
|
+
a cross-origin fetch always needs a CORS preflight, which never succeeds.
|
|
985
|
+
POST also requires that exact Origin and `Content-Type: application/json`
|
|
986
|
+
(415 otherwise).
|
|
987
|
+
- Bodies are limited to 1 KiB (413, by declared length and while streaming).
|
|
988
|
+
Server `headersTimeout` is 10 s and `requestTimeout` 30 s.
|
|
989
|
+
- The host closes after 30 minutes without activity while no installation
|
|
990
|
+
runs (`limits.idleMs`, checked every 15 s with the injected clock). Only a
|
|
991
|
+
request with the right Host and a valid session cookie, a successful code
|
|
992
|
+
redemption or a finished installation counts as activity; wrong-Host,
|
|
993
|
+
unauthenticated or bad-code traffic never extends the timeout.
|
|
994
|
+
|
|
995
|
+
### Endpoints
|
|
996
|
+
|
|
997
|
+
Fixed allowlist, matched on the raw request path: anything else is 404, a
|
|
998
|
+
known path with another method is 405. Only `/session`, `/api/progress` and
|
|
999
|
+
`/api/plan` (exactly `channel=release` or `channel=main`) take a query string. Assets map to fixed file names in `assetsDir`; no request
|
|
1000
|
+
path reaches the filesystem, so traversal forms (`/../`, `%2e%2e`, `%2f`) are
|
|
1001
|
+
simply unknown paths.
|
|
1002
|
+
|
|
1003
|
+
| Route | Contract |
|
|
1004
|
+
| --- | --- |
|
|
1005
|
+
| `GET /session?code=` | Consumes the one-time code, sets the cookie and returns 200 `text/html` that refreshes to `/`; 401 for a missing, wrong, used or expired code. |
|
|
1006
|
+
| `GET /`, `/wizard.js`, `/wizard.css` | `index.html`, `wizard.js`, `wizard.css` from `assetsDir`; 404 when absent. |
|
|
1007
|
+
| `GET /api/plan` | Runs `collectPlan(channel)` server-side for `?channel=release` (also the default without a query) or `?channel=main`, stores the plan under a new opaque `planId` and returns a view model: `actions` (`id` plus a fixed English description), `blockers` (`code`, `tool` — a preflight tool key such as `node` or `gentleAi`, otherwise `unknown` — and guidance), `profileChange` (whether `pnpm setup` edits the shell profile or Windows user PATH, and the bin directory), `persistence` (which of node, npm and pnpm go under `$PNPM_HOME`), `sharedFolders` (Windows: `{ tools, description }` for reused tools whose folders another account can change, or `null`), `ready` and `channel`. 409 while installing. Any other query is 400. |
|
|
1008
|
+
| `POST /api/install` | Body exactly `{ "planId": string, "consent": true }` (400 otherwise, including extra keys or a plan). 409 `install-running` while an installation runs; 409 `already-completed` once an installation has a final outcome (one installation per wizard run; the outcome is never replaced); 409 `plan-changed` for a stale `planId` or when a fresh re-inventory differs from the stored plan. Otherwise 202, and the runner receives the server-stored plan with `consent: true`. |
|
|
1009
|
+
| `GET /api/progress?after=<seq>` | Entries `{ seq, step, status, reason }` after `seq` from a ring buffer of the last 200; values outside `[a-z][a-z0-9-]*` become `unknown`, and no other runner field is kept. Also `running` and the final `outcome` with fixed guidance. |
|
|
1010
|
+
| `POST /api/shutdown` | Body empty or `{}`. Closes the host (409 while installing). |
|
|
1011
|
+
|
|
1012
|
+
`guidance` (exported) holds fixed English text for every runner blocked
|
|
1013
|
+
reason, every failed step, every preflight blocker code and both successful
|
|
1014
|
+
outcomes, plus a generic fallback; a runner exception becomes a `failed`
|
|
1015
|
+
outcome with the fallback. A failed `shell-setup`, `persist-path`, `acquire-go`, `install-global`,
|
|
1016
|
+
`install-shell-main`, `build-gentle-ai-main` or `update-shell` outcome also passes the
|
|
1017
|
+
runner's `detail` through, bounded again (string only, control and bidi
|
|
1018
|
+
characters removed, at most 300 characters); the host drops it for any other
|
|
1019
|
+
step. When that detail names GitHub's rate limit (`rate limit`, or `GitHub API`
|
|
1020
|
+
together with `403`), the guidance becomes `guidance.setupRateLimit`: GitHub's
|
|
1021
|
+
anonymous API limit was reached on this network, wait up to an hour and run the
|
|
1022
|
+
installer again. For `install-global`, `install-shell-main`, `build-gentle-ai-main`
|
|
1023
|
+
and `update-shell`, the same match, or `main-commit-unavailable` together with
|
|
1024
|
+
`HTTP 403`, selects `guidance.githubRateLimit`, the same advice without naming
|
|
1025
|
+
`gentle-shell setup`. No token or credential is ever requested. For `persist-path`,
|
|
1026
|
+
a detail with `ERR_PNPM_UNKNOWN_SHELL` or `ERR_PNPM_UNSUPPORTED_SHELL` selects
|
|
1027
|
+
`guidance.persistPathShell`: open a regular terminal and run the installer
|
|
1028
|
+
again, or add `$PNPM_HOME/bin` to PATH manually. For `acquire-go`, a detail
|
|
1029
|
+
starting with `Conflicting Go destination: ` selects
|
|
1030
|
+
`guidance.goDestinationConflict`: a folder from an earlier, interrupted run is in
|
|
1031
|
+
the way and is never replaced; remove that folder and run the installer again.
|
|
1032
|
+
`scripts/installer-runner.mjs` exports the frozen
|
|
1033
|
+
arrays `blockedReasons` and `failedSteps`; a test requires guidance for exactly
|
|
1034
|
+
those entries, and the runner tests check that every reason and step their
|
|
1035
|
+
scenarios observe is listed.
|
|
1036
|
+
|
|
1037
|
+
### Wizard UI
|
|
1038
|
+
|
|
1039
|
+
`assets/install-wizard/index.html`, `wizard.js` (an ES module) and
|
|
1040
|
+
`wizard.css` are the whole browser side. They load only from the host itself
|
|
1041
|
+
and follow its CSP: no inline script or style, no external fonts, images or
|
|
1042
|
+
requests, and server strings are inserted as text nodes only (never parsed as
|
|
1043
|
+
HTML).
|
|
1044
|
+
|
|
1045
|
+
| Screen | What the user sees |
|
|
1046
|
+
| --- | --- |
|
|
1047
|
+
| Check | Shown while `GET /api/progress?after=0` and `GET /api/plan` run. A running installation resumes the Install screen; a finished one shows its outcome instead of a new plan. |
|
|
1048
|
+
| Review | Each fixed action with its description, the `profileChange` and `persistence` disclosures (state as text: "Will change", "No change"), on Windows a **Tools in folders other accounts can change** notice when the plan records one ([reused tool folders](#reused-tool-folders)), a **Git not found** notice when Git is missing (never a blocker), one consent checkbox and **Install Gentle Shell** (for a setup recovery, "Finish setting up Gentle Shell" and **Complete setup**). Without the checkbox no request is sent; the error is announced and focus moves to the checkbox. Preflight blockers replace the plan with their guidance, **Check again** and **Close installer**. |
|
|
1049
|
+
| Install | Expected runner steps (`expectedSteps`) with a text status (Done, In progress, Pending, Failed, Blocked, Not run), a `<progress>` bar and a terminal-style log of `{ seq, step, status, reason }` entries. |
|
|
1050
|
+
| Done | `ready`: run `gentle-shell`. `terminal-action-required`: open a new terminal, then run `gentle-shell`. `blocked`/`failed`: the host's guidance, the reason or failed step, the steps that finished and the log. A failed step with a detail also shows it under **Last error from** its label: `gentle-shell setup`, `pnpm setup`, `the Go download`, `pnpm add -g` (`install-global`), `the Gentle Shell main install`, `the Gentle AI main build` or `the Gentle Shell update`, as one plain text node (backticks stay literal). Every outcome offers **Close installer** (`POST /api/shutdown`). |
|
|
1051
|
+
|
|
1052
|
+
Behavior worth knowing:
|
|
1053
|
+
|
|
1054
|
+
- `409 plan-changed` reloads the plan, clears consent and explains why;
|
|
1055
|
+
`409 install-running` and `already-completed` resume the current state; 401
|
|
1056
|
+
or 403 ends the session with instructions to run the installer again. When
|
|
1057
|
+
the install request itself fails on the network, the wizard checks
|
|
1058
|
+
`/api/progress` once: a running or finished installation is followed
|
|
1059
|
+
instead of an error, and the install request is never re-sent
|
|
1060
|
+
automatically.
|
|
1061
|
+
- Progress polling starts at 500 ms, backs off by 1.5× to 4 s while nothing
|
|
1062
|
+
changes, resets when entries arrive and stops on the final outcome. Five
|
|
1063
|
+
consecutive errors, or three answers that are neither running nor finished,
|
|
1064
|
+
stop polling and offer **Try again**. The client keeps the last 200 log
|
|
1065
|
+
entries, like the host.
|
|
1066
|
+
- Accessibility: landmarks, one `h1` per screen that receives focus on each
|
|
1067
|
+
step change (it is not interactive, so it shows no focus ring), a polite
|
|
1068
|
+
`role="status"` region for progress and the closed state, an assertive
|
|
1069
|
+
`role="alert"` region for errors, labelled controls, native buttons and
|
|
1070
|
+
checkbox (keyboard operable), visible `:focus-visible` rings on every
|
|
1071
|
+
control, text labels for every color state, `prefers-reduced-motion` and
|
|
1072
|
+
`forced-colors` support.
|
|
1073
|
+
- Visual language: the gentlemanprogramming.com dark palette (accent
|
|
1074
|
+
`#f095c8` on `#1a1218`) and its static hero glow behind the panel, with
|
|
1075
|
+
local font stacks only and a text wordmark; no logos, brand images or
|
|
1076
|
+
favicon are copied.
|
|
1077
|
+
|
|
1078
|
+
`wizard.js` exports its pure models (`planModel`, `progressModel`,
|
|
1079
|
+
`outcomeModel`, `expectedSteps`, `installBody`, `interpretInstall`,
|
|
1080
|
+
`nextPoll`), its renderers and `createWizard({ document, fetch, setTimeout,
|
|
1081
|
+
clearTimeout, clipboard? })`; it starts itself only inside a page.
|
|
1082
|
+
`tests/install-wizard.test.ts` drives the controller with a fake DOM (where
|
|
1083
|
+
`innerHTML` throws), fake fetch and fake timers, and serves the real assets
|
|
1084
|
+
through the real host.
|
|
1085
|
+
|
|
1086
|
+
### Development preview
|
|
1087
|
+
|
|
1088
|
+
`scripts/install-wizard-preview.mjs` starts the **real** host with the real
|
|
1089
|
+
assets but a fake `collectPlan` and a fake runner, so it never probes the
|
|
1090
|
+
machine, runs a package manager, downloads or installs anything:
|
|
1091
|
+
|
|
1092
|
+
```sh
|
|
1093
|
+
node scripts/install-wizard-preview.mjs --scenario=terminal --step-ms=600
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
Scenarios: `ready`, `terminal` (`terminal-action-required` with runtime
|
|
1097
|
+
persistence), `blocked` (`existing-stack`), `failed` (`install-global`),
|
|
1098
|
+
`plan-changed` (the first plan goes stale, so the host answers 409),
|
|
1099
|
+
`recovery` (an installed stack whose setup did not finish; ends in
|
|
1100
|
+
`terminal-action-required`) and `preflight` (blockers, no plan). It prints the one-time session URL; Ctrl+C
|
|
1101
|
+
or **Close installer** stops it. The preview is a development tool: it is not
|
|
1102
|
+
listed in `installerPaths` (the entry never imports it), although it ships
|
|
1103
|
+
with the rest of `scripts/` in the package.
|
|
1104
|
+
|
|
1105
|
+
### Remaining T6/T7 checks
|
|
1106
|
+
|
|
1107
|
+
- T6 (done): `wizard.js` and `wizard.css` are in `installerPaths`; the preview
|
|
1108
|
+
scenarios ran end to end in headless Chromium 1148 at 1440 and 390 px wide,
|
|
1109
|
+
including a keyboard-only consent and install, with no CSP violations.
|
|
1110
|
+
Screen readers and other browsers were not tested.
|
|
1111
|
+
- T7: the opener on each desktop (including a missing `xdg-open`); that the
|
|
1112
|
+
redirect file opens in the browser rather than another `.html` handler, and
|
|
1113
|
+
that sandboxed browsers with a private `/tmp` (for example snap or flatpak
|
|
1114
|
+
packages on Linux) can read it; that a meta refresh from a `file:` page
|
|
1115
|
+
reaches the session and the session page's same-origin refresh to `/`
|
|
1116
|
+
carries the `SameSite=Strict` cookie in each browser; browsers honouring
|
|
1117
|
+
`SameSite=Strict` and the cookie on `127.0.0.1`; DNS-rebinding and
|
|
1118
|
+
cross-site request rejection in real browsers; the bootstrap's handling of
|
|
1119
|
+
exit 1 end to end, and exit 0 outside Linux; SIGTERM during a running
|
|
1120
|
+
installation; Windows `rundll32` behavior; real probe and runner execution
|
|
1121
|
+
through the host outside Linux.
|
|
1122
|
+
|
|
1123
|
+
## POSIX bootstrap: bundle-local tooling only
|
|
1124
|
+
|
|
1125
|
+
Run `sh scripts/bootstrap.sh` from a trusted extracted installation bundle or
|
|
1126
|
+
checkout. There is **no published bundle URL or remote-pipe installer contract**.
|
|
1127
|
+
The bundle must include `package.json`, both bootstrap modules and the wizard
|
|
1128
|
+
entry `bin/gentle-shell-install.mjs`. When that entry is missing, the script
|
|
1129
|
+
reports it before downloads or home writes. T7 owns packaging and distribution
|
|
1130
|
+
proof.
|
|
1131
|
+
|
|
1132
|
+
With that entry available, the fixed sequence is:
|
|
1133
|
+
|
|
1134
|
+
1. Probe existing Node against the bundle's repository requirement. Unknown or
|
|
1135
|
+
prerelease versions block. A stable older version is never replaced: it is
|
|
1136
|
+
left as it is and the verified Node is acquired as in step 2.
|
|
1137
|
+
2. If missing (or older), select a fixed native Node archive, download with TLS and bounded
|
|
1138
|
+
size/time, verify its hardcoded SHA256 using stock shell utilities, extract
|
|
1139
|
+
only its regular `bin/node`, and check the exact executable version before
|
|
1140
|
+
publishing it. Neither npm nor Corepack is acquired or invoked.
|
|
1141
|
+
3. Reuse pnpm only after stable version, package engine and read-only global
|
|
1142
|
+
`add`/`bin` help-capability evidence. Engines support only simple `>=x.y` or
|
|
1143
|
+
`>=x.y.z` lower bounds; comparison fills an omitted patch with zero. Actual
|
|
1144
|
+
Node versions must remain exact stable versions; other ranges block rather
|
|
1145
|
+
than guess. The package is found from the resolved `pnpm` command; when that
|
|
1146
|
+
command is a regular cmd-shim file (as pnpm writes in `$PNPM_HOME` or
|
|
1147
|
+
`$PNPM_HOME/bin` when it installs or updates itself), the search starts from
|
|
1148
|
+
its `# cmd-shim-target=` path or, without one, the single
|
|
1149
|
+
`"$basedir/<target>" "$@"` it runs. A shim is a script (`#!`) read whole,
|
|
1150
|
+
up to 64 KiB, so a target named after the first 4 KiB is still found; a
|
|
1151
|
+
larger script fails closed. That path only locates the evidence;
|
|
1152
|
+
`pnpm --version` must still match it. A standalone pnpm (a Mach-O or ELF
|
|
1153
|
+
executable, directly or as that shim target, as mise, asdf and pnpm's own
|
|
1154
|
+
installer with `@pnpm/exe` provide) embeds its Node runtime: it skips the
|
|
1155
|
+
package engine check but must still report an exact stable `--version`, equal
|
|
1156
|
+
to the version of the `pnpm` or `@pnpm/*` `package.json` beside it when there
|
|
1157
|
+
is one, and pass the same global `add`/`bin` capability checks. A script or
|
|
1158
|
+
shim without a `pnpm` package still blocks. Prerequisite
|
|
1159
|
+
checks run from `/`, so a pnpm that switches to a project's `packageManager`
|
|
1160
|
+
pin reports its own version.
|
|
1161
|
+
A pnpm whose stable version evidence (`package.json`, or a standalone
|
|
1162
|
+
pnpm's `--version`) is of another major or older than 11.1.1, or whose
|
|
1163
|
+
simple engine bound rejects this Node, is left as it is, and pnpm is acquired
|
|
1164
|
+
as when missing; the private tools directory is claimed only then.
|
|
1165
|
+
Missing pnpm is acquired from a fixed registry tarball, SHA512-SRI verified,
|
|
1166
|
+
checked for unsafe paths/links, extracted and probed before publication.
|
|
1167
|
+
4. Start the fixed bundle entry with the refreshed child environment. A mandatory
|
|
1168
|
+
acquisition/probe/child failure is an error, never installation success.
|
|
1169
|
+
|
|
1170
|
+
### Ownership and failure boundaries
|
|
1171
|
+
|
|
1172
|
+
Acquisition uses a mode-0700, uniquely created
|
|
1173
|
+
`$HOME/.gentle-shell-bootstrap-tools.<random>` directory, with an explicit
|
|
1174
|
+
`.bootstrap-owned` marker. This is **prerequisite tooling, not a product home**.
|
|
1175
|
+
HOME must be absolute, owned by the current user, not group/world writable and
|
|
1176
|
+
free of symlink ancestors. Staging and destinations reject conflicts and
|
|
1177
|
+
symlinks. Unrelated similarly named directories are not scanned, reused or
|
|
1178
|
+
removed. Failed attempts clean only their own private directories; successful
|
|
1179
|
+
acquisition removes its temporary archives/staging and retains verified tools
|
|
1180
|
+
while the wizard runs. After the wizard exits 0, the script removes the tools
|
|
1181
|
+
directory it claimed, and the Node helper removes the one it created when only
|
|
1182
|
+
pnpm was missing. Both removals require the exact
|
|
1183
|
+
`$HOME/.gentle-shell-bootstrap-tools.*` name, a real directory (not a symlink)
|
|
1184
|
+
and the `.bootstrap-owned` marker with its exact content; `rm -rf` and
|
|
1185
|
+
`rmSync` remove links inside without following them. When removal is refused or
|
|
1186
|
+
fails, the bootstrap still exits 0 and prints
|
|
1187
|
+
`Bootstrap: installation finished, but temporary tools could not be removed:
|
|
1188
|
+
<path>` on stderr.
|
|
1189
|
+
A new attempt reuses tools only if already visible and proven on its PATH; it
|
|
1190
|
+
does not discover or garbage-collect previous private attempts.
|
|
1191
|
+
|
|
1192
|
+
No profile, global PATH, existing executable installation, product agent home,
|
|
1193
|
+
Engram state or companion installation is changed. Added paths affect only the
|
|
1194
|
+
bootstrap and its child. T4 must implement standard global installation and the
|
|
1195
|
+
persistent **ordinary terminal** handoff; these private wrappers are not that
|
|
1196
|
+
handoff. Existing unverifiable pnpm wrappers/binaries block instead of being
|
|
1197
|
+
silently replaced. No sudo or security exclusions are requested.
|
|
1198
|
+
|
|
1199
|
+
Stock utilities are prerequisites, not silently installed: POSIX sh, awk,
|
|
1200
|
+
dirname, uname, mkdir, mktemp, chmod, mv, rm, sleep, wc, id and ls; missing Node
|
|
1201
|
+
also needs curl, tar and sha256sum or shasum. Linux acquisition additionally
|
|
1202
|
+
requires getconf evidence of glibc >=2.28. Missing pnpm requires tar. Missing
|
|
1203
|
+
utilities are reported. Shell executable-version probes have a 10-second
|
|
1204
|
+
watchdog, hash/archive probes 30 seconds; curl has a 10-second connection and
|
|
1205
|
+
120-second total limit with a 100-MiB artifact cap. A subprocess-only `ulimit -f`
|
|
1206
|
+
adds a hard disk ceiling (at most 200 MiB depending on shell block units) for
|
|
1207
|
+
older curl implementations; failure to establish it blocks acquisition.
|
|
1208
|
+
The JavaScript transport rejects
|
|
1209
|
+
redirects, caps pnpm at 32 MiB and aborts after 60 seconds; process checks have
|
|
1210
|
+
15-second/1-MiB bounds. At a prerequisite-process deadline, the shell watchdog
|
|
1211
|
+
and Node process adapter send SIGKILL to their directly spawned child, rather
|
|
1212
|
+
than catchable SIGTERM. A killed probe is a failure even if it printed valid
|
|
1213
|
+
output before hanging. Shell watchdog cancellation also kills and waits for its
|
|
1214
|
+
owned sleeper. The interactive wizard child intentionally has no total runtime
|
|
1215
|
+
timeout. Raw downloader/process error text is not logged.
|
|
1216
|
+
|
|
1217
|
+
### Artifact trust and shared helper API
|
|
1218
|
+
|
|
1219
|
+
| Artifact | Acquisition pin and provenance |
|
|
1220
|
+
| --- | --- |
|
|
1221
|
+
| Node native darwin/linux x64/arm64 | 24.21.0; parent-verified SHA256 entries from [official SHASUMS256](https://nodejs.org/dist/latest-v24.x/SHASUMS256.txt), applied to fixed [v24.21.0 archive URLs](https://nodejs.org/dist/v24.21.0/). |
|
|
1222
|
+
| pnpm JavaScript CLI | 11.1.1; parent-verified raw engine `>=22.13` (comparison minimum `22.13.0`), tarball and SHA512 SRI from [registry version metadata](https://registry.npmjs.org/pnpm/11.1.1); [fixed tarball](https://registry.npmjs.org/pnpm/-/pnpm-11.1.1.tgz). |
|
|
1223
|
+
|
|
1224
|
+
POSIX/pnpm integrity values live in `installer-downloads.mjs`; Windows Node
|
|
1225
|
+
values live in `installer-windows-artifacts.json`. The shell copies
|
|
1226
|
+
only Node's acquisition pin/hashes because first acquisition cannot depend on
|
|
1227
|
+
Node. Tests cross-check shell selection against the shared descriptors. Changing
|
|
1228
|
+
these pins requires renewed primary-source integrity evidence and updating both
|
|
1229
|
+
Node representations together. There is no live latest resolution. The pnpm pin retains the exact raw upstream
|
|
1230
|
+
engine string for metadata identity checks; only version comparisons normalize
|
|
1231
|
+
its supported partial minimum. Product versions/minima remain repository-derived;
|
|
1232
|
+
the pnpm pin is not a compatibility
|
|
1233
|
+
minimum. Registry-hosted artifacts do not imply using the npm executable.
|
|
1234
|
+
|
|
1235
|
+
`scripts/installer-downloads.mjs` exports `artifactFor(name, platform, arch)`
|
|
1236
|
+
(frozen allowlisted descriptors), `verifiedDownload(name, adapters, platform,
|
|
1237
|
+
arch)` (verified bytes), `compatibleEngine(range, version)`,
|
|
1238
|
+
`ensurePnpm({ tools, env, nodeVersion, adapters })` and
|
|
1239
|
+
`launchWizard({ bundle, env })`. Only known artifact names are accepted, not
|
|
1240
|
+
caller URLs/checksums/commands. Trusted local test adapters inject byte download,
|
|
1241
|
+
digest and process checks; they are not exposed through any browser interface.
|
|
1242
|
+
The caller owns a private `tools` directory. `ensurePnpm` returns `{ env,
|
|
1243
|
+
acquired }`, leaving the supplied environment unchanged. The Windows adapter reuses descriptors/integrity verification without changing
|
|
1244
|
+
POSIX process semantics, defaults or pins.
|
|
1245
|
+
|
|
1246
|
+
### Evidence, not platform certification
|
|
1247
|
+
|
|
1248
|
+
Native shell execution was exercised on this Linux host with disposable Unicode
|
|
1249
|
+
and whitespace paths, fake OS/download/hash/archive utilities and injected
|
|
1250
|
+
JavaScript acquisition/process adapters. No real network or host prerequisite
|
|
1251
|
+
installation was performed. Darwin selections are simulated, **not native
|
|
1252
|
+
macOS execution**. Native macOS, Linux clean-machine/loader and minimum macOS
|
|
1253
|
+
version acceptance remain T7 work. Linux acquisition is native glibc-only:
|
|
1254
|
+
musl/Alpine and emulation/Rosetta compatibility are not claimed. Node's
|
|
1255
|
+
[upstream build/platform requirements](https://github.com/nodejs/node/blob/v24.21.0/BUILDING.md)
|
|
1256
|
+
remain the platform reference; successful descriptor planning alone proves no
|
|
1257
|
+
OS/kernel/libc support.
|
|
1258
|
+
|
|
1259
|
+
### Subprocess deadline evidence
|
|
1260
|
+
|
|
1261
|
+
The regression fixtures exercise production process paths without shortened
|
|
1262
|
+
production timeouts or a mocked process adapter:
|
|
1263
|
+
|
|
1264
|
+
| Path | Observed Linux fixture behavior |
|
|
1265
|
+
| --- | --- |
|
|
1266
|
+
| Shell acquired-Node version probe | A single Node process prints the expected version, ignores TERM and waits without busy-looping. The real 10-second watchdog kills/reaps it, rejects acquisition and removes owned tooling while preserving an unrelated HOME file. |
|
|
1267
|
+
| Node helper tar probe | Fake verified bytes feed `ensurePnpm`, but its default process adapter runs a real single-process TERM-ignoring tar stand-in. The real 15-second deadline kills/reaps it, rejects acquisition and removes owned staging while preserving an unrelated file. Ordinary nonzero tar exit also rejects and cleans staging. |
|
|
1268
|
+
|
|
1269
|
+
Before correction, both paths exceeded their deadlines and needed the test
|
|
1270
|
+
harness's independent outer SIGKILL guard (13 seconds for shell, 18 for Node).
|
|
1271
|
+
The guard kills only each freshly created detached fixture group and also
|
|
1272
|
+
cleans residual fixture processes on exit. Production does **not** kill groups,
|
|
1273
|
+
match process names or use an external timeout utility. After correction, both
|
|
1274
|
+
probe PIDs are reaped before their parents return; neither outer guard fires.
|
|
1275
|
+
Ordinary success remains covered by the existing reuse/acquisition tests.
|
|
1276
|
+
|
|
1277
|
+
These are **direct-child, non-forking probe** guarantees, not process-tree
|
|
1278
|
+
cancellation evidence. Unknown programs that fork descendants retaining stdio
|
|
1279
|
+
may keep pipes open; this unit does not claim bounded return or descendant
|
|
1280
|
+
cleanup for those programs. The fixtures prove neither native macOS behavior
|
|
1281
|
+
nor live artifact acquisition. Independent verification and native review remain
|
|
1282
|
+
separate parent-owned gates.
|
|
1283
|
+
|
|
1284
|
+
## Windows foundation: fixed commands, no policy repair
|
|
1285
|
+
|
|
1286
|
+
Run `scripts\bootstrap.cmd` from a trusted extracted bundle or checkout. Like
|
|
1287
|
+
POSIX, it stops before acquisition or home writes when the wizard entry
|
|
1288
|
+
`bin/gentle-shell-install.mjs` or any Windows-dependent helper/metadata file is
|
|
1289
|
+
missing. There is no published bundle URL or remote-pipe contract, and the
|
|
1290
|
+
Windows bootstrap end to end still lacks native acceptance evidence.
|
|
1291
|
+
|
|
1292
|
+
| Step | Windows contract |
|
|
1293
|
+
| --- | --- |
|
|
1294
|
+
| Entry | Small CMD entry invokes fixed stock Windows PowerShell commands with no profile. Paths are environment data, not interpolated PowerShell source. Delayed CMD expansion is disabled. |
|
|
1295
|
+
| Storage | Claim a new random-named prerequisite directory directly below `%LOCALAPPDATA%`, or below `%USERPROFILE%` when `%LOCALAPPDATA%` fails `acl-mask` or `home-owner` (see [claim candidates](#claim-candidates-and-owners)); never reuse an existing destination. Verify each path component's reparse attributes, owner and role-specific ACL rights. Set the claimed directory's owner to the invoking SID, protect its DACL for the invoking SID, SYSTEM and Administrators, and read both back. |
|
|
1296
|
+
| Node | Reuse a proven stable existing Node ≥24.3.0 and the repository minimum. Otherwise (missing, an older stable version, or a Node whose storage fails the reparse/owner/ACL walk, which therefore never runs) leave it as it is and acquire only the fixed official Node 24.21.0 Windows x64/arm64 ZIP, with no redirects and bounded transport, verify SHA256 before opening the archive, validate the whole namespace and extract only regular `node.exe`. |
|
|
1297
|
+
| pnpm | A pnpm whose wrapper, Node, entry, metadata or exe fails the reparse/owner/ACL walk never runs: it is left as it is and pnpm is acquired as when missing. Reuse only a [known shim](#windows-command-shims) of pnpm's own JS entry, with package identity, bin target, stable CLI version, compatible engine and global add/bin help evidence, preserving its sibling-Node preference or proving its inherited cwd/PATH/PATHEXT Node selection; or a native `pnpm.exe` (`pnpm setup`, `pnpm self-update`, @pnpm/exe, pnpm 12), directly or as a known shim's target (an extensionless target resolves through PATHEXT in order, and only an `.exe` first match counts), whose `--version` is stable and equals the `package.json` naming pnpm or `@pnpm/*` beside it, with the same global help evidence. A `pnpm.exe` without that `package.json` (pnpm's standalone installer, a Volta or mise shim), Corepack's `pnpm.cmd` and mise's file shim are not proven pnpm: they are never run and pnpm is acquired as when missing. Never execute a shim via cmd.exe. Other unknown wrappers block without replacement. One whose stable version is of another major or older than 11.1.1 is left as it is, and pnpm is acquired as when missing. |
|
|
1298
|
+
| Missing pnpm | Shared pnpm 11.1.1 URL/SRI and raw `>=22.13` engine identity are unchanged. Parse bounded gzip/USTAR bytes, reject unsupported extensions, links and unsafe Windows namespaces before no-clobber publication. Return a direct Node + JS-entry invocation; do not fabricate a wrapper. |
|
|
1299
|
+
| Handoff | Existing Node helper starts the fixed wizard entry `bin/gentle-shell-install.mjs`. Only child PATH is refreshed. No persistent PATH, global installation, product root or companion installation is created here. |
|
|
1300
|
+
|
|
1301
|
+
Windows Node SHA256 provenance is the parent's fresh primary-source read of
|
|
1302
|
+
[24.21.0 SHASUMS256](https://nodejs.org/dist/v24.21.0/SHASUMS256.txt). A narrow
|
|
1303
|
+
JSON metadata file lets pre-Node PowerShell and the shared Node helper consume
|
|
1304
|
+
the same Windows pins without mixing the POSIX archive format or copying pnpm
|
|
1305
|
+
integrity into another runtime. Pin changes still require fresh primary evidence.
|
|
1306
|
+
|
|
1307
|
+
### T4/T5 integration contract
|
|
1308
|
+
|
|
1309
|
+
`scripts/installer-windows.mjs` exports:
|
|
1310
|
+
|
|
1311
|
+
- `ensureWindowsPnpm({ tools, env, node?, adapters? })` → `{ acquired, env,
|
|
1312
|
+
command, prefix }`. Invoke `[command, ...prefix, ...args]` with `shell:false`;
|
|
1313
|
+
keep the returned environment. `tools` must be a private attempt-owned root.
|
|
1314
|
+
Test adapters are trusted local code only, never a browser API.
|
|
1315
|
+
- `bootstrapWindows({ bundle, tools, env })` validates fixed bundle files and
|
|
1316
|
+
requirements, acquires/reuses pnpm, then delegates to shared `launchWizard`.
|
|
1317
|
+
CLI entry is `installer-downloads.mjs --bootstrap-windows BUNDLE TOOLS`.
|
|
1318
|
+
- T5 receives `GENTLE_INSTALL_PNPM_NODE` and `GENTLE_INSTALL_PNPM_ENTRY`, or
|
|
1319
|
+
`GENTLE_INSTALL_PNPM_COMMAND` alone for a native `pnpm.exe`, as data for that
|
|
1320
|
+
proven direct invocation. T4 must re-inventory before installation;
|
|
1321
|
+
these values are not an arbitrary command API or persistent terminal repair.
|
|
1322
|
+
- Storage, namespace, wrapper, tar and bounded process functions are exported
|
|
1323
|
+
for focused verification. Production storage verification requires native
|
|
1324
|
+
Windows and fixed stock PowerShell ACL commands, never POSIX mode/UID evidence.
|
|
1325
|
+
|
|
1326
|
+
Go preparation is not part of the Windows bootstrap. The standard installation
|
|
1327
|
+
runner blocks on Windows unless preflight reports a reusable Go ≥1.25.10 or the
|
|
1328
|
+
plan acquires the [pinned Go](#pinned-go) after consent, because gentle-pi's
|
|
1329
|
+
postinstall may build Gentle AI from source; it never treats an explicit
|
|
1330
|
+
override as native-package evidence.
|
|
1331
|
+
|
|
1332
|
+
That Go SumDB source build runs Go in a fresh private directory created with
|
|
1333
|
+
`mkdtemp` under the system temp directory (`%TEMP%\gai-XXXXXX`: GOBIN, GOPATH,
|
|
1334
|
+
GOMODCACHE, GOCACHE, TEMP/TMP and the commands' working directory), never inside
|
|
1335
|
+
the installed package, and always removes it. The binary is then copied into the
|
|
1336
|
+
package's staging directory, checked there (build metadata, exact version,
|
|
1337
|
+
SHA-256 manifest) and published as before. Run 38063142924 (LongPathsEnabled=0)
|
|
1338
|
+
reproduced why: from the 179-character pnpm 11 store path of a main package, the
|
|
1339
|
+
build inside the package failed with `asm.exe: fork/exec …: The directory name is
|
|
1340
|
+
invalid.`, because the working directories Go gives `asm.exe` inside GOMODCACHE
|
|
1341
|
+
exceed MAX_PATH, which CreateProcess rejects; the same build succeeded from a
|
|
1342
|
+
4-character and a 161-character root. When the build fails, the postinstall prints
|
|
1343
|
+
the whole `cause` chain with each code and the last 4000 characters of the failed
|
|
1344
|
+
command's stderr and stdout.
|
|
1345
|
+
|
|
1346
|
+
### Policy, bounds and evidence limits
|
|
1347
|
+
|
|
1348
|
+
No unsigned `.ps1` file, script-file evaluation, execution-policy relaxation,
|
|
1349
|
+
file unblocking, certificate/TLS bypass, elevation, security exclusion or profile
|
|
1350
|
+
change is used. Restricted permits individual commands; it is not every Windows
|
|
1351
|
+
client's default. [Execution policies](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies)
|
|
1352
|
+
do not override [AppLocker CMD/BAT rules](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/applocker/script-rules-in-applocker)
|
|
1353
|
+
or [managed App Control constraints](https://learn.microsoft.com/en-us/powershell/scripting/security/app-control/how-app-control-works).
|
|
1354
|
+
ConstrainedLanguage and any download/execution denial stop with an error. There
|
|
1355
|
+
is no fallback intended to evade those controls; if CMD itself is denied, the
|
|
1356
|
+
OS owns the denial diagnostic before our entry can run.
|
|
1357
|
+
|
|
1358
|
+
Node transport is capped at 100 MiB with a 60-second request/total elapsed check
|
|
1359
|
+
and 10-second blocking-read timeout. A blocking read can consume up to that last
|
|
1360
|
+
read timeout beyond the elapsed check. ZIP metadata caps expanded size at 512 MiB
|
|
1361
|
+
and the selected executable at 150 MiB. Namespace checks reject traversal,
|
|
1362
|
+
backslashes, links/reparse attributes, duplicate/case-aliased names, conflicting
|
|
1363
|
+
file/directory parents, devices, ADS and trailing dots/spaces. Shared pnpm
|
|
1364
|
+
transport remains 32 MiB/60 seconds; tar expansion is capped at 128 MiB.
|
|
1365
|
+
Unsupported archive extensions fail closed. **Actual pnpm 11.1.1 TGZ acceptance
|
|
1366
|
+
is unproved:** it requires an integrity-backed offline copy of the pinned
|
|
1367
|
+
artifact, not a synthetic USTAR fixture or an injected digest. No such evidence
|
|
1368
|
+
was supplied and no acquisition is authorized in this correction.
|
|
1369
|
+
|
|
1370
|
+
The pre-Node probe drains both pipes with a combined 1-MiB cap and kills only
|
|
1371
|
+
its direct child at 10 seconds, with a bounded one-second final wait. Node helper
|
|
1372
|
+
probes use the shared 15-second/1-MiB-style bounds and an unignorable direct-child
|
|
1373
|
+
kill. Valid output followed by a hang is a failure. Production never kills
|
|
1374
|
+
process groups or unrelated processes. Forked descendants retaining stdio are
|
|
1375
|
+
outside the guarantee; the interactive wizard has no total deadline.
|
|
1376
|
+
|
|
1377
|
+
### Claim candidates and owners
|
|
1378
|
+
|
|
1379
|
+
The claim tries at most two bases, in this order:
|
|
1380
|
+
|
|
1381
|
+
1. `%LOCALAPPDATA%`, with the CMD's random `.gentle-shell-bootstrap-tools.*` name.
|
|
1382
|
+
2. `%USERPROFILE%`, with its own random name. It is tried only when the first
|
|
1383
|
+
candidate fails `acl-mask` or `home-owner`. Any other rejection stops at once.
|
|
1384
|
+
|
|
1385
|
+
A beta tester's profile showed why. Another principal had Modify rights
|
|
1386
|
+
(`0x1301bf`) on `%LOCALAPPDATA%` and on `AppData`, so the claim failed
|
|
1387
|
+
`acl-mask`. That rejection is right: a folder another principal can write is
|
|
1388
|
+
never used to run binaries, and it is never relaxed. `%USERPROFILE%` does not
|
|
1389
|
+
sit below `AppData`, so it can still be a clean base.
|
|
1390
|
+
|
|
1391
|
+
Each candidate gets every check: the exact-parent rule (`path-mismatch`), the
|
|
1392
|
+
base owner (`home-owner`) and the whole ancestor walk up to the drive root
|
|
1393
|
+
(`ancestor-reparse`, `ancestor-owner`, `acl-mask`). Nothing is created until one
|
|
1394
|
+
candidate passes them all. The `acl-mask` masks, the reparse checks, the
|
|
1395
|
+
collision check, the random name and the no-UNC rule are the same for both.
|
|
1396
|
+
When the fallback is claimed, the stage exits 2 and CMD switches
|
|
1397
|
+
`GENTLE_BOOTSTRAP_TOOLS` to it before it records ownership. Every later stage,
|
|
1398
|
+
the failure cleanup and the success cleanup use that one path.
|
|
1399
|
+
|
|
1400
|
+
The base may be owned by the invoking SID, SYSTEM (`S-1-5-18`) or
|
|
1401
|
+
BUILTIN\Administrators (`S-1-5-32-544`). The tester's profile was owned by
|
|
1402
|
+
SYSTEM. Both principals are already trusted for every ancestor and keep
|
|
1403
|
+
FullControl in the private DACL, so neither gains anything new.
|
|
1404
|
+
TrustedInstaller, which ancestors may still have as owner, and any other SID
|
|
1405
|
+
fail `home-owner`.
|
|
1406
|
+
|
|
1407
|
+
An Administrators member's new folders may default to BUILTIN\Administrators as
|
|
1408
|
+
owner. The claim never relies on that default. It writes the invoking SID as
|
|
1409
|
+
owner together with the protected DACL, then reads back the protection, the
|
|
1410
|
+
owner (`private-owner`) and every ACE.
|
|
1411
|
+
|
|
1412
|
+
When no candidate passes, the first line is unchanged and still ends with
|
|
1413
|
+
`Reason: <code>`, the code of the check that stopped the claim. One more line
|
|
1414
|
+
follows for each candidate tried, with its folder and code. For `acl-mask` the
|
|
1415
|
+
line also names the folder holding the ACE, the principal (SID, and the account
|
|
1416
|
+
name when it resolves) and the rights it was allowed. For `home-owner` it names
|
|
1417
|
+
the owner. For example:
|
|
1418
|
+
|
|
1419
|
+
```text
|
|
1420
|
+
Bootstrap: private storage ACL/reparse/ownership claim failed or policy denied it. Reason: acl-mask
|
|
1421
|
+
Bootstrap: storage candidate C:\Users\me\AppData\Local: acl-mask at C:\Users\me\AppData\Local: S-1-5-21-…-1002 (PC\other) allowed 0x001301BF
|
|
1422
|
+
Bootstrap: storage candidate C:\Users\me: acl-mask at C:\Users: S-1-5-21-…-1002 (PC\other) allowed 0x00100002
|
|
1423
|
+
```
|
|
1424
|
+
|
|
1425
|
+
A failure after the walk (`collision`, `protected-dacl`, `private-owner`,
|
|
1426
|
+
`private-ace`, or an unexpected step) adds a line naming the chosen directory.
|
|
1427
|
+
|
|
1428
|
+
The Node helper accepts the same location. Its storage check walks the tools
|
|
1429
|
+
directory under `%USERPROFILE%` with the same masks and owners, so the
|
|
1430
|
+
tester's layout passes and a writable ancestor still rejects it.
|
|
1431
|
+
|
|
1432
|
+
The helper also stops failing on the user's own pnpm in such a profile. A pnpm
|
|
1433
|
+
whose wrapper, Node, entry, metadata or `pnpm.exe` fails the reparse, owner or
|
|
1434
|
+
ACL walk is never run: like an untrusted user Node in `bootstrap.cmd`, it is
|
|
1435
|
+
left as it is, and the pinned pnpm is acquired into the private directory. So
|
|
1436
|
+
is a `pnpm.exe` without pnpm's `package.json` beside it, Corepack's `pnpm.cmd`
|
|
1437
|
+
and mise's file shim: nothing proves they are pnpm, so they count as no pnpm
|
|
1438
|
+
at all. A `policy` denial, unknown evidence and every other check still stop.
|
|
1439
|
+
In the wizard, the user's pnpm in `$PNPM_HOME\bin` that fails the walk does not
|
|
1440
|
+
block either: it is not run, not replaced and not downgraded, and the
|
|
1441
|
+
bootstrap's verified pnpm runs every pnpm step.
|
|
1442
|
+
|
|
1443
|
+
### Windows PNPM_HOME
|
|
1444
|
+
|
|
1445
|
+
The bootstrap's private directory only holds the temporary tools. The wizard
|
|
1446
|
+
then installs Node.js, npm, pnpm, Pi and Gentle Shell under PNPM_HOME and runs
|
|
1447
|
+
them from `$PNPM_HOME\bin`. On the tester's profile that folder,
|
|
1448
|
+
`%LOCALAPPDATA%\pnpm`, sat below the same `%LOCALAPPDATA%` another account
|
|
1449
|
+
could modify. So before any probe the wizard walks PNPM_HOME with the same
|
|
1450
|
+
checks as the tools directory: reparse points, trusted owners and the
|
|
1451
|
+
`acl-mask` masks, up to the drive root. It walks the folder and its `bin` when
|
|
1452
|
+
they exist, or the nearest existing ancestor when the folder does not exist
|
|
1453
|
+
yet (held to the strict depth-0 mask).
|
|
1454
|
+
|
|
1455
|
+
| PNPM_HOME | Walk | Result |
|
|
1456
|
+
|-----------|------|--------|
|
|
1457
|
+
| Set by the user | Passes | Used as before. |
|
|
1458
|
+
| Set by the user | Fails | Blocked: `untrusted-pnpm-home`. |
|
|
1459
|
+
| Not set: `%LOCALAPPDATA%\pnpm` | Passes | Used as before; the plan is unchanged. |
|
|
1460
|
+
| Not set: `%LOCALAPPDATA%\pnpm` | Fails, and nothing is installed there | `%USERPROFILE%\.pnpm` is used, if it passes the walk too. |
|
|
1461
|
+
| Not set: `%LOCALAPPDATA%\pnpm` | Fails, and something is installed there | Blocked: `untrusted-pnpm-home`. |
|
|
1462
|
+
| Any | The walk cannot finish (policy, PowerShell) | Blocked: `unknown-tool` for `pnpmHome`. |
|
|
1463
|
+
|
|
1464
|
+
A blocked decision (`untrusted-pnpm-home`, or `unknown-tool` for `pnpmHome`)
|
|
1465
|
+
runs no probe at all: the node probe would otherwise run the user's npm with
|
|
1466
|
+
that PNPM_HOME's `bin` first on PATH. The plan then holds only that blocker.
|
|
1467
|
+
|
|
1468
|
+
"Nothing is installed" has one exact meaning: `%LOCALAPPDATA%\pnpm` does not
|
|
1469
|
+
exist, or it is a real directory (not a link) with no entries at all. Any
|
|
1470
|
+
entry, even an empty `store` folder or a pnpm config file, counts as an
|
|
1471
|
+
installation. The installer never moves, deletes or adopts it.
|
|
1472
|
+
|
|
1473
|
+
`%USERPROFILE%\.pnpm` is accepted only when it passes the walk and is absent,
|
|
1474
|
+
empty, or marked as created by this flow: it holds a regular
|
|
1475
|
+
`.gentle-shell-pnpm-home` file with the exact text
|
|
1476
|
+
`gentle-pi private pnpm home`. A non-empty folder without that marker is
|
|
1477
|
+
foreign and blocks, and so does one that fails the walk.
|
|
1478
|
+
|
|
1479
|
+
The plan says all of this before consent: the default folder, the account
|
|
1480
|
+
(SID, and its name when it resolves) and the rights that failed, the private
|
|
1481
|
+
folder, and that `pnpm setup` will save `PNPM_HOME=%USERPROFILE%\.pnpm` and
|
|
1482
|
+
add `%USERPROFILE%\.pnpm\bin` to the user PATH for new terminals. A blocker
|
|
1483
|
+
names the same folder, account and rights, plus the remedy: remove that
|
|
1484
|
+
account's write access, or set PNPM_HOME to a private folder such as
|
|
1485
|
+
`%USERPROFILE%\.pnpm` and reinstall the global pnpm packages there.
|
|
1486
|
+
|
|
1487
|
+
After consent the runner's first step, `prepare-pnpm-home`, claims the private
|
|
1488
|
+
folder before any pnpm command could create it with inherited permissions. It
|
|
1489
|
+
uses the bootstrap claim's exact statements: owner set to the user, a
|
|
1490
|
+
protected DACL with FullControl only for the user, SYSTEM and Administrators,
|
|
1491
|
+
then a readback of the protection, the owner and every ACE. An empty existing
|
|
1492
|
+
folder gets the same DACL; a marked one is kept as it is. Then the marker is
|
|
1493
|
+
written, a `tmp` folder is created inside it (a real directory, never a link),
|
|
1494
|
+
and the folder and `tmp` are walked again. Any failure stops the installation
|
|
1495
|
+
before any command (`prepare-pnpm-home`).
|
|
1496
|
+
|
|
1497
|
+
The installer's children, and an update's `gentle-shell upgrade` children, get
|
|
1498
|
+
`TEMP` and `TMP` set to that `tmp` folder. It inherits the protected DACL, so
|
|
1499
|
+
a postinstall's `os.tmpdir()` stays private: the Windows Gentle AI source
|
|
1500
|
+
build creates its `gai-` directory there and runs `gentle-ai.exe` from it
|
|
1501
|
+
before publishing it. A default `%TEMP%` lives under `%LOCALAPPDATA%`, which
|
|
1502
|
+
another account may write. These values are never persisted, and nothing
|
|
1503
|
+
changes outside private mode or on POSIX.
|
|
1504
|
+
|
|
1505
|
+
`pnpm setup` persists the folder. pnpm 11.1.1's `setup` reads its home from
|
|
1506
|
+
the `PNPM_HOME` environment variable (`getDataDir`) and, on Windows, writes it
|
|
1507
|
+
to `HKCU\Environment` as `PNPM_HOME`, then puts `%PNPM_HOME%\bin` first in the
|
|
1508
|
+
user Path (`addDirToWindowsEnvPath`). The runner gives `pnpm setup` the
|
|
1509
|
+
private PNPM_HOME, and runs it even for an update, which otherwise keeps the
|
|
1510
|
+
PATH its installation already uses. A registry `PNPM_HOME` that already holds
|
|
1511
|
+
another value makes `pnpm setup` fail with `ERR_PNPM_BAD_ENV_FOUND` instead of
|
|
1512
|
+
overwriting it. The native Windows tests check the decision, the claim and the
|
|
1513
|
+
probe. No test runs `pnpm setup` against a real user registry: the registry
|
|
1514
|
+
write is pnpm's own behavior, read from the pnpm 11.1.1 package source.
|
|
1515
|
+
|
|
1516
|
+
pnpm also keeps its global config (`%LOCALAPPDATA%\pnpm\config`), cache
|
|
1517
|
+
(`%LOCALAPPDATA%\pnpm-cache`) and state (`%LOCALAPPDATA%\pnpm-state`) under
|
|
1518
|
+
`%LOCALAPPDATA%`. For exact versions, pnpm 11.1.1 resolves from cached registry
|
|
1519
|
+
metadata without asking the registry. With a private PNPM_HOME, the installer's
|
|
1520
|
+
own children therefore get `XDG_CONFIG_HOME`, `XDG_CACHE_HOME` and
|
|
1521
|
+
`XDG_STATE_HOME` under it, unless the user set them. They are never persisted:
|
|
1522
|
+
`pnpm setup` still saves only PNPM_HOME and the user Path.
|
|
1523
|
+
|
|
1524
|
+
Residual risk (accepted, and stated in the plan before consent): after the
|
|
1525
|
+
installation, the user's own pnpm commands, and Gentle Shell's later
|
|
1526
|
+
`gentle-shell upgrade`, still use pnpm's default config, cache and state
|
|
1527
|
+
folders under `%LOCALAPPDATA%` unless the user configures them. pnpm 11.1.1
|
|
1528
|
+
resolves its config folder only from `XDG_CONFIG_HOME` or
|
|
1529
|
+
`%LOCALAPPDATA%\pnpm\config`, so there is no safe persistent pnpm-native
|
|
1530
|
+
setting the installer could save instead. The installer protects its own run
|
|
1531
|
+
and the binaries it persists; it does not make the rest of a writable
|
|
1532
|
+
`%LOCALAPPDATA%` safe.
|
|
1533
|
+
|
|
1534
|
+
Accepted risk (user decision): the installer never creates or runs its own
|
|
1535
|
+
binaries in a folder another account can change. The user's own tools are
|
|
1536
|
+
used the way the user already uses them: a Node.js, npm, Go, Pi or npm-owned
|
|
1537
|
+
Gentle Shell that the plan reuses or updates may live in such a folder (an
|
|
1538
|
+
npm global prefix under `%APPDATA%`, for example), and the installer runs it
|
|
1539
|
+
there, as any terminal would. Only pnpm, the installer's own runner, and the
|
|
1540
|
+
Node.js that `bootstrap.cmd` runs are held to the walk as a blocker. The plan
|
|
1541
|
+
discloses the rest as a notice (below).
|
|
1542
|
+
|
|
1543
|
+
#### Reused tool folders
|
|
1544
|
+
|
|
1545
|
+
Before consent the wizard walks every reused tool's command and what it runs:
|
|
1546
|
+
`node`, `npm`, `go`, `pi` and `gentle-shell` as the user's PATH resolves them,
|
|
1547
|
+
plus the Node and JS entry of a known shim. Each file is walked where it really
|
|
1548
|
+
is (its realpath), and for every link on its path (a symbolic link or a
|
|
1549
|
+
junction, as `lstat` reports it) the real folder that holds the link is walked
|
|
1550
|
+
too, since whoever can change that folder can retarget the link. A component
|
|
1551
|
+
whose realpath is merely spelled differently, such as an 8.3 short name
|
|
1552
|
+
(`C:\Users\RUNNER~1`), is not a link: walking `C:\Users` as a link holder would
|
|
1553
|
+
hold `C:\` to the strict parent mask, which Windows' default
|
|
1554
|
+
CreateDirectories (`0x4`) for Authenticated Users fails. A real location that
|
|
1555
|
+
is not on a local drive (a mapped network drive, or a link to a UNC share) is
|
|
1556
|
+
never sent to PowerShell: it is "could not be checked" for that tool only, and
|
|
1557
|
+
the rest is walked as usual. pnpm's own links, the global `global\v11\<hash>\node_modules\<pkg>`
|
|
1558
|
+
junction into the store and a `pnpm runtime` `node.exe`, are therefore never a
|
|
1559
|
+
finding, while a weak ACL on the folders they lead to, or on the folder holding
|
|
1560
|
+
them, still is. A file whose real location cannot be read is reported as
|
|
1561
|
+
"could not be checked". All of them go to one Windows
|
|
1562
|
+
PowerShell launch (`verifyWindowsStorageMany`): the paths travel as one
|
|
1563
|
+
environment value joined by `|`, which no Windows path holds, and the script
|
|
1564
|
+
prints one line per path, in order: `safe`, the same `unsafe:<role>-<check>|<detail>`
|
|
1565
|
+
as the single-path walk, or `unknown` when that path's walk fails for another
|
|
1566
|
+
reason (an owner that denies READ_CONTROL, for example). `unknown` becomes
|
|
1567
|
+
`{ check: "unchecked", at }`: the notice says that path could not be checked.
|
|
1568
|
+
A count that does not match, any other line, or `unsafe:policy` rejects
|
|
1569
|
+
the whole result. A test keeps that walk identical to the single-path one.
|
|
1570
|
+
|
|
1571
|
+
The plan records only the tools it reuses as they are: the user's Node.js and
|
|
1572
|
+
its npm, a Go that a build reuses, a Pi kept as it is, and a Gentle Shell
|
|
1573
|
+
that npm owns (kept or updated with npm). `tools.folders` is then
|
|
1574
|
+
`{ status: "notice", reused: [{ tool, check, at, sid?, account?, rights? }] }`
|
|
1575
|
+
(per tool the first walk rejection, else the first `unchecked` path), and the
|
|
1576
|
+
review screen names each tool, folder, account and rights. It never
|
|
1577
|
+
blocks and changes no action: the user decided to accept this risk. A
|
|
1578
|
+
policy denial, a PowerShell failure or a walk that cannot finish means no
|
|
1579
|
+
notice, never a blocker. The notice is left out of the plan fingerprint the
|
|
1580
|
+
install request re-checks, so a re-inventory whose walk timed out, or found a
|
|
1581
|
+
different notice, still installs the consented plan instead of answering
|
|
1582
|
+
`plan-changed` again and again.
|
|
1583
|
+
|
|
1584
|
+
A pnpm whose storage fails the walk is never run anywhere. That includes the
|
|
1585
|
+
wizard probe's `--version` of the user's own pnpm next to the bootstrap's:
|
|
1586
|
+
its shim and what the shim runs are walked first, and any failure counts as
|
|
1587
|
+
not usable.
|
|
1588
|
+
|
|
1589
|
+
### Target-versus-ancestor ACL boundary
|
|
1590
|
+
|
|
1591
|
+
For untrusted SIDs, the fixed predicates allow ReadAndExecute plus Synchronize
|
|
1592
|
+
(`0x1200a9`), with one narrow directory-specific distinction:
|
|
1593
|
+
|
|
1594
|
+
| Role | Mutation boundary |
|
|
1595
|
+
| --- | --- |
|
|
1596
|
+
| Actual file/executable or owned tooling root (depth 0) | No write, deletion, child-deletion, ACL or ownership rights. |
|
|
1597
|
+
| Immediate parent (depth 1) | Same strict protection, including no CreateDirectories/AppendData. A new tooling claim starts checking its parent at this depth. |
|
|
1598
|
+
| Distant **existing directory** (depth ≥2) | May additionally allow CreateDirectories/AppendData (`4`) for sibling-only creation. WriteData/CreateFiles, WriteExtendedAttributes, WriteAttributes, Delete, DeleteSubdirectoriesAndFiles, WriteDACL, TakeOwnership, generic/unknown rights still reject. |
|
|
1599
|
+
|
|
1600
|
+
Directory CreateDirectories and file AppendData share an enum bit; a distant
|
|
1601
|
+
ancestor here must actually be an existing directory. This is not permission to
|
|
1602
|
+
append to an executable. Reparse checks, trusted ownership and every actual path
|
|
1603
|
+
component remain mandatory. An inheritable ACE is checked again where it becomes
|
|
1604
|
+
effective on the descendant; InheritOnly does not grant rights on its current
|
|
1605
|
+
object. Effective unsafe allow ACEs block even when deny ACEs might otherwise
|
|
1606
|
+
limit them: this is a conservative mutation boundary, not a general Windows
|
|
1607
|
+
ACL/access-check framework. Trusted SIDs and protected-DACL readback are unchanged.
|
|
1608
|
+
The portable fixture model checks parity with all three fixed production
|
|
1609
|
+
predicates; it does not execute Windows ACL APIs or claim prevalence on Windows.
|
|
1610
|
+
|
|
1611
|
+
Failure cleanup targets only the directory claimed by the actual attempt;
|
|
1612
|
+
collisions and unrelated storage are not removed. Prerequisite tools remain
|
|
1613
|
+
available to children while the wizard runs. After the helper exits 0, a final
|
|
1614
|
+
stage removes the claimed root only when its full path is a direct child of
|
|
1615
|
+
`%LOCALAPPDATA%` or `%USERPROFILE%` named `.gentle-shell-bootstrap-tools.*`, neither it nor its
|
|
1616
|
+
`.bootstrap-owned` marker is a reparse point, and the marker holds the exact
|
|
1617
|
+
text. It uses `[IO.Directory]::Delete(path, true)`, which does not recurse
|
|
1618
|
+
through reparse points, instead of Windows PowerShell 5.1 `Remove-Item`. A
|
|
1619
|
+
refusal or failure prints the same notice with the path and still exits 0.
|
|
1620
|
+
Redirected cleanup notices use UTF-8 so captured logs preserve Unicode paths.
|
|
1621
|
+
Interactive stderr keeps its existing encoding; bootstrap does not change the
|
|
1622
|
+
console code page. PowerShell helper lookup honors Windows `SystemRoot` casing
|
|
1623
|
+
in copied environments. ACL/ancestor checks are conservative and may
|
|
1624
|
+
reject managed/nonstandard layouts rather than relax security. They do not
|
|
1625
|
+
claim protection from a malicious process running as the same principal or an
|
|
1626
|
+
administrator, nor eliminate same-principal time-of-check/time-of-use races.
|
|
1627
|
+
|
|
1628
|
+
### Claim reason codes and 8.3 short paths
|
|
1629
|
+
|
|
1630
|
+
A rejected storage claim keeps its user-facing message and appends one fixed
|
|
1631
|
+
code naming the failed check, for example `... policy denied it. Reason: home-owner`.
|
|
1632
|
+
The code never contains a path, SID or exception text. The lines after it name
|
|
1633
|
+
each candidate folder and, for `acl-mask`, the principal and its rights (see
|
|
1634
|
+
[claim candidates](#claim-candidates-and-owners)). Intentional rejections
|
|
1635
|
+
report their own code:
|
|
1636
|
+
|
|
1637
|
+
| Code | Rejected check |
|
|
1638
|
+
|------|----------------|
|
|
1639
|
+
| `policy` | PowerShell is not in FullLanguage mode. |
|
|
1640
|
+
| `path-mismatch` | The candidate base (`%LOCALAPPDATA%` or `%USERPROFILE%`) is not rooted, the target is UNC, or the target's parent is not exactly that base. |
|
|
1641
|
+
| `home-owner` | The candidate base is not owned by the invoking SID, SYSTEM or Administrators. On `%LOCALAPPDATA%` it leads to the `%USERPROFILE%` candidate. |
|
|
1642
|
+
| `ancestor-reparse` | An ancestor is not a directory or is a reparse point. |
|
|
1643
|
+
| `ancestor-owner` | An ancestor owner is not the invoking SID, SYSTEM, Administrators or TrustedInstaller. |
|
|
1644
|
+
| `acl-mask` | An untrusted effective allow ACE exceeds the depth's allowed rights mask. On `%LOCALAPPDATA%` it leads to the `%USERPROFILE%` candidate. |
|
|
1645
|
+
| `collision` | The random destination already exists; it is never reused or changed. |
|
|
1646
|
+
| `protected-dacl` | The readback DACL is not protected from inheritance. |
|
|
1647
|
+
| `private-owner` | The readback owner is not the invoking SID. |
|
|
1648
|
+
| `private-ace` | The readback holds a deny ACE or a SID other than the invoking SID, SYSTEM or Administrators. |
|
|
1649
|
+
|
|
1650
|
+
Any other exception, such as an unreadable ACL, a denied write or a failed
|
|
1651
|
+
cmdlet, reports `unexpected-<step>` for the step that was running, so it can
|
|
1652
|
+
never pose as an intentional rejection:
|
|
1653
|
+
|
|
1654
|
+
| Step | Work in progress |
|
|
1655
|
+
|------|------------------|
|
|
1656
|
+
| `unexpected-policy` | Language mode and identity lookup. |
|
|
1657
|
+
| `unexpected-path-mismatch` | Path normalization. |
|
|
1658
|
+
| `unexpected-home-owner` | Reading the candidate base's owner. |
|
|
1659
|
+
| `unexpected-ancestor-walk` | Reading an ancestor's attributes or ACL. |
|
|
1660
|
+
| `unexpected-create` | Creating the new directory, including a destination that appeared after the collision check. |
|
|
1661
|
+
| `unexpected-private-acl` | Writing or reading back the private DACL. |
|
|
1662
|
+
| `unexpected-marker` | Writing the ownership marker. |
|
|
1663
|
+
|
|
1664
|
+
The Node probe stage follows the same contract. Its message is unchanged and
|
|
1665
|
+
ends with `Reason: <code>`. Walk codes are prefixed with the role of the path
|
|
1666
|
+
component that failed: `target` (`node.exe` itself, depth 0), `parent` (its
|
|
1667
|
+
directory, depth 1) or `ancestor` (depth 2 and above). The role is fixed text,
|
|
1668
|
+
never the path.
|
|
1669
|
+
|
|
1670
|
+
| Code | Rejected check |
|
|
1671
|
+
|------|----------------|
|
|
1672
|
+
| `policy` | PowerShell is not in FullLanguage mode. |
|
|
1673
|
+
| `missing-target` | The `.node-target` record is absent, not a file, or empty. |
|
|
1674
|
+
| `unsafe-target` | The recorded Node target is a directory or a reparse point. |
|
|
1675
|
+
| `unsafe-path` | The recorded Node target is not rooted, or is UNC. |
|
|
1676
|
+
| `<role>-reparse` | A walked component is a reparse point. |
|
|
1677
|
+
| `<role>-owner` | A walked component's owner is not the invoking SID, SYSTEM, Administrators or TrustedInstaller. |
|
|
1678
|
+
| `<role>-acl-mask` | An untrusted effective allow ACE on a walked component exceeds its depth's allowed rights mask. |
|
|
1679
|
+
| `no-start` | The process did not start. |
|
|
1680
|
+
| `deadline` | Node did not exit and close both pipes within 10 seconds. |
|
|
1681
|
+
| `output-limit` | Combined stdout and stderr exceeded 1 MiB. |
|
|
1682
|
+
| `exit-code` | Node exited with a nonzero code. |
|
|
1683
|
+
| `version-format` | `node --version` did not print exactly `vMAJOR.MINOR.PATCH`. |
|
|
1684
|
+
| `engine` | The acquired Node is below 24.3.0 or the repository `engines.node` floor. |
|
|
1685
|
+
| `acquired-version` | A Node acquired by this bootstrap is not exactly v24.21.0. |
|
|
1686
|
+
|
|
1687
|
+
The user's Node (no `.node-stem`) is never refused for `engine` or for the
|
|
1688
|
+
storage codes `unsafe-target`, `unsafe-path` and `<role>-<reparse|owner|acl-mask>`:
|
|
1689
|
+
the stage removes its `.node-target` record, and the entry jumps back to the
|
|
1690
|
+
download, ZIP and probe stages to acquire the pinned Node exactly as when Node
|
|
1691
|
+
is missing. A Node that fails the walk is never started. The acquired Node then
|
|
1692
|
+
has a `.node-stem`, so it can only pass or refuse; every other code still
|
|
1693
|
+
refuses, including `version-format` for an unknown or prerelease version.
|
|
1694
|
+
|
|
1695
|
+
Any other exception reports `unexpected-<step>`: `unexpected-policy`,
|
|
1696
|
+
`unexpected-target` (reading the recorded target), `unexpected-acl-walk`
|
|
1697
|
+
(reading a component's attributes or ACL), `unexpected-start` (for example, an
|
|
1698
|
+
application-control denial while starting the process), `unexpected-drain`
|
|
1699
|
+
(reading the pipes) or `unexpected-version` (reading the repository metadata).
|
|
1700
|
+
A child that cannot be confirmed terminated keeps its separate
|
|
1701
|
+
`direct-child termination could not be confirmed` message.
|
|
1702
|
+
|
|
1703
|
+
The `.node-target` record holds a full path, which may contain non-ASCII text,
|
|
1704
|
+
for example a user profile such as `C:\Users\José` or the Unicode native
|
|
1705
|
+
fixture root. Windows PowerShell 5.1 writes `New-Item -Value` content as
|
|
1706
|
+
BOM-less UTF-8 but reads `Get-Content` without a BOM as the ANSI code page, so
|
|
1707
|
+
such a path came back corrupted and the probe failed with `unexpected-target`.
|
|
1708
|
+
The record is now created no-clobber with `[IO.File]::Open(..., CreateNew)` and
|
|
1709
|
+
explicit BOM-less UTF-8 bytes, and the probe and launch stages read it with
|
|
1710
|
+
`[IO.File]::ReadAllText(path, [Text.Encoding]::UTF8)`. The other bootstrap
|
|
1711
|
+
records (`.bootstrap-owned`, `.node-stem`) hold fixed ASCII text and keep their
|
|
1712
|
+
cmdlets.
|
|
1713
|
+
|
|
1714
|
+
The Node helper (`installer-downloads.mjs --bootstrap-windows`) follows the same
|
|
1715
|
+
contract. Its failure line is unchanged and ends with `Reason: <code> (<step>)`
|
|
1716
|
+
for an intentional rejection, or `Reason: unexpected-<step>` for any other
|
|
1717
|
+
error, such as a file-system, JSON or spawn error. A value without that fixed
|
|
1718
|
+
shape, including a failed module import, prints `Reason: unexpected-helper`.
|
|
1719
|
+
|
|
1720
|
+
| Step | Phase |
|
|
1721
|
+
|------|-------|
|
|
1722
|
+
| `bundle` | Native platform, bundle files and repository Node/pnpm metadata. |
|
|
1723
|
+
| `tools-storage` | ACL walk of the claimed tools directory. |
|
|
1724
|
+
| `pnpm-discovery` | CMD-equivalent search of the current directory, `Path` and `PATHEXT` for `pnpm`. |
|
|
1725
|
+
| `wrapper-storage`, `wrapper` | ACL walk of the found `pnpm.cmd`/`pnpm.exe` and its [exact shim](#windows-command-shims) proof. |
|
|
1726
|
+
| `node-discovery` | The wrapper's sibling `node.exe`, or `node` on `Path`. |
|
|
1727
|
+
| `node-storage`, `entry-storage`, `metadata-storage` | ACL walk of the selected Node, the pnpm entry and its `package.json`. |
|
|
1728
|
+
| `package` | The pnpm `package.json` binary target. |
|
|
1729
|
+
| `cli-proof` | `node --version`, `pnpm --version` and the `add`/`bin` global capability. |
|
|
1730
|
+
| `exe-storage`, `exe-proof` | ACL walk of a native `pnpm.exe` and the `package.json` beside it, then its `--version` and global capability. |
|
|
1731
|
+
| `tools-check`, `download`, `archive`, `publish` | Pinned pnpm acquisition when no `pnpm` was found. |
|
|
1732
|
+
| `launch` | The bundle wizard entry and its exit code. |
|
|
1733
|
+
|
|
1734
|
+
Codes: `native-unavailable`, `bundle-missing`, `prerequisite`, `unsafe-path`,
|
|
1735
|
+
`acl-evidence`, the storage walk codes `policy` and
|
|
1736
|
+
`<target|parent|ancestor>-<reparse|owner|acl-mask>`, `cwd-search`,
|
|
1737
|
+
`path-missing`, `pathext`, `path-entry`, `extensionless`, `wrapper-unknown`,
|
|
1738
|
+
`wrapper-unproven`, `wrapper-node`, `wrapper-target`, `package-target`,
|
|
1739
|
+
`pnpm-engine`, `pnpm-version`, `pnpm-capability`, `process-failed`,
|
|
1740
|
+
`interpreter`, `unsafe-tools`, `pnpm-conflict`, `archive`, `pnpm-pin`,
|
|
1741
|
+
`pnpm-entry`, `acquisition`, `wizard-missing`, `wizard-start` and
|
|
1742
|
+
`wizard-exit`. For example, `parent-owner (tools-check)` means the
|
|
1743
|
+
directory holding the claimed tools directory has an untrusted owner. A walk
|
|
1744
|
+
code on the user's own pnpm (`wrapper-storage`, `node-storage`,
|
|
1745
|
+
`entry-storage`, `metadata-storage`, `exe-storage`) is not a failure: that
|
|
1746
|
+
pnpm is never run and the pinned one is acquired. The helper's storage check
|
|
1747
|
+
prints `unsafe:<code>` for a walk rejection and still accepts only exact `safe`;
|
|
1748
|
+
any other PowerShell exception exits nonzero and reports `unexpected-<step>`.
|
|
1749
|
+
|
|
1750
|
+
`.CPL` deserves a note. Windows PowerShell 5.1 appends `.CPL` to `PATHEXT` in
|
|
1751
|
+
its own environment, so its children see
|
|
1752
|
+
`.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL` even when CMD did
|
|
1753
|
+
not. The bootstrap always starts the helper from Windows PowerShell, and native
|
|
1754
|
+
CI showed the old discovery allowlist rejecting every Windows run with
|
|
1755
|
+
`pathext (pnpm-discovery)`. `.cpl` is now a known extension, but not an
|
|
1756
|
+
accepted candidate. Discovery keeps the CMD order (current directory, then each
|
|
1757
|
+
`Path` entry, then `PATHEXT` order within a directory). A `pnpm.cpl` found
|
|
1758
|
+
before the real `pnpm.cmd` stops with `wrapper-unknown (wrapper)`, and a
|
|
1759
|
+
`node.cpl` found before `node.exe` stops with `wrapper-node (node-discovery)`;
|
|
1760
|
+
neither is skipped or executed. Any other unknown, duplicate or empty
|
|
1761
|
+
`PATHEXT` extension still fails with `pathext`.
|
|
1762
|
+
|
|
1763
|
+
The wizard inherits the same environment. The runner's `lookPath` (Go
|
|
1764
|
+
`exec.LookPath` order) and the host probes accept any `PATHEXT` extension and
|
|
1765
|
+
already refuse a `.cpl` result: npm reports `npm-shadowed`, and Node or Go
|
|
1766
|
+
resolving to a non-`.exe`/`.com` file reports unknown without running it.
|
|
1767
|
+
|
|
1768
|
+
### Windows command shims
|
|
1769
|
+
|
|
1770
|
+
Node.js refuses to spawn a `.cmd` with `shell:false`, and the installer never
|
|
1771
|
+
starts cmd.exe: a `.cmd` runs a shell, and its paths and arguments would become
|
|
1772
|
+
shell text. Instead, `windowsShim` (`scripts/installer-windows.mjs`) reads a
|
|
1773
|
+
shim and accepts it only when it is, byte for byte apart from CRLF, one its
|
|
1774
|
+
generator writes, with a single quoted target that holds no `"` or `%`. The
|
|
1775
|
+
installer then runs that target itself with `shell:false`
|
|
1776
|
+
(`windowsInvocation` in the runner, `ensureWindowsPnpm` in the bootstrap):
|
|
1777
|
+
|
|
1778
|
+
| Shim | Writer | What runs |
|
|
1779
|
+
| --- | --- | --- |
|
|
1780
|
+
| npm cmd-shim 4.1-8 and 9, JS target | `npm install -g` (pnpm, Pi, npm itself in `%APPDATA%\npm`) | The shim's sibling `node.exe`, else the first `node` on PATH (an `.exe`), with the JS entry. |
|
|
1781
|
+
| npm cmd-shim, native target | `npm install -g pnpm@12` or `@pnpm/exe` | That `.exe` (an extensionless @pnpm/exe hard link runs as its `.exe` twin). |
|
|
1782
|
+
| @zkochan/cmd-shim 9, native or JS target | pnpm 10-12 global bins: `pnpm setup`, `pnpm self-update`, `pnpm add -g` | The `.exe`, or the JS entry with the sibling/PATH Node or the `node.exe` the shim names. pnpm's optional NODE_PATH block only extends module lookup and is not reproduced. |
|
|
1783
|
+
| Node.js's `npm.cmd` (npm 6-9, and 10-11 with `npm-prefix.js`) | Node.js, also behind nvm-windows (`mklink /D`) or fnm (a junction) | Node as above with `node_modules\npm\bin\npm-cli.js`, after npm's own redirect: the fixed prefix query (`npm-prefix.js`, or `npm-cli.js prefix -g`) from the drive root, then the `npm-cli.js` under that prefix when it exists. |
|
|
1784
|
+
| Any `.exe` | Volta (`C:\Program Files\Volta\npm.exe`), mise's default `exe` shims, pnpm's installer | Itself. |
|
|
1785
|
+
|
|
1786
|
+
Anything else (mise's `file` shims, Volta's package shims, Corepack's pnpm, a
|
|
1787
|
+
hand-written wrapper) is never run: an npm is then not usable and the plan
|
|
1788
|
+
persists the installer's npm, a Pi is unknown, and the bootstrap refuses an
|
|
1789
|
+
unknown pnpm wrapper as before, except Corepack's `pnpm.cmd` and mise's file
|
|
1790
|
+
shim, which it treats as no pnpm and acquires its own. A `pnpm.cmd` runs only
|
|
1791
|
+
pnpm's own `node_modules\pnpm\bin\pnpm.cjs` or `pnpm.mjs` entry, so Corepack's
|
|
1792
|
+
`pnpm.js` never runs.
|
|
1793
|
+
|
|
1794
|
+
An extensionless native target (an @pnpm/exe hard link) resolves the way CMD
|
|
1795
|
+
resolves it: through PATHEXT in order, and only when that first match is an
|
|
1796
|
+
`.exe`; a `.com`, `.bat` or `.cmd` found first, or none, is not run.
|
|
1797
|
+
|
|
1798
|
+
Only local drive paths (`C:\...`) count: the command, the Node a shim selects
|
|
1799
|
+
from PATH and npm's global prefix. `lookPath` skips any other PATH entry (UNC,
|
|
1800
|
+
`\\?\`, `\\.\` or a drive-less rooted path) without touching it, since even a
|
|
1801
|
+
lookup can reach a remote share, and `windowsInvocation` rejects such a command
|
|
1802
|
+
or Node and keeps the bundled npm for such a prefix. Sources: the npm/cmd-shim tap snapshots
|
|
1803
|
+
(v4.1.0-v9.0.2), @zkochan/cmd-shim 9.0.8 and npm 6.14.18-11.19.0 `bin/npm.cmd`
|
|
1804
|
+
from the npm registry, volta-cli/volta `wix/main.wxs`, jdx/mise `src/shims.rs`,
|
|
1805
|
+
Schniz/fnm `src/fs.rs` and coreybutler/nvm-windows `src/nvm.go`.
|
|
1806
|
+
|
|
1807
|
+
These writers also put an extensionless Git Bash script beside each `.cmd`.
|
|
1808
|
+
CMD runs the `PATHEXT` match in that directory, so the bootstrap's discovery
|
|
1809
|
+
does too; an extensionless file alone in a directory still stops it with
|
|
1810
|
+
`extensionless`. The native CI lanes compare what CMD runs with what the
|
|
1811
|
+
installer runs for the runner's real `npm.cmd` and an npm cmd-shim `pnpm.cmd`.
|
|
1812
|
+
|
|
1813
|
+
ACLs are read and written with .NET Framework APIs
|
|
1814
|
+
(`[IO.Directory]::GetAccessControl`, `[IO.File]::GetAccessControl`,
|
|
1815
|
+
`[IO.Directory]::SetAccessControl`) instead of `Get-Acl`/`Set-Acl`. Those
|
|
1816
|
+
cmdlets live in Microsoft.PowerShell.Security, which Windows PowerShell 5.1
|
|
1817
|
+
autoloads from `PSModulePath`. Started from a PowerShell 7 terminal (or the
|
|
1818
|
+
GitHub Actions `pwsh` step shell), the inherited `PSModulePath` lists
|
|
1819
|
+
PowerShell 7's Core-only copy first, and 5.1 fails with
|
|
1820
|
+
`CouldNotAutoloadMatchingModule`. The checks themselves are unchanged: the
|
|
1821
|
+
same owner, mask, protected-DACL, readback and reparse predicates in the same
|
|
1822
|
+
order. The bootstrap still uses cmdlets from Microsoft.PowerShell.Management
|
|
1823
|
+
(`Get-Item`, `Get-Content`, `New-Item`, `Remove-Item`, `Test-Path`,
|
|
1824
|
+
`Join-Path`) and Microsoft.PowerShell.Utility (`New-Object`, `ConvertFrom-Json`,
|
|
1825
|
+
`Get-FileHash`, `Add-Type`, `Select-Object`, `Start-Sleep`). The native fixtures
|
|
1826
|
+
observed both modules loading under the same `pwsh` parent, and a module loads
|
|
1827
|
+
as a unit. `Get-Command` is built into the engine.
|
|
1828
|
+
|
|
1829
|
+
The path comparison is insensitive to 8.3 short names by construction: both
|
|
1830
|
+
sides derive from the same `%LOCALAPPDATA%` string and pass through
|
|
1831
|
+
`[IO.Path]::GetFullPath`, so a short `%LOCALAPPDATA%` or `%TEMP%` is never
|
|
1832
|
+
compared with a long canonical form. The host probe adapter's `realpath` is the
|
|
1833
|
+
native call and returns the long form, for example `C:\Users\runneradmin\...`
|
|
1834
|
+
for a `%TEMP%` exposed as `C:\Users\RUNNER~1\...`; tests compare it with
|
|
1835
|
+
`realpathSync.native`, not the JavaScript `realpathSync`, which keeps short
|
|
1836
|
+
names.
|
|
1837
|
+
|
|
1838
|
+
An earlier CI run reported `home-owner` for every native claim. The actual
|
|
1839
|
+
cause was this module-load failure, reported as the step that was running;
|
|
1840
|
+
`unexpected-<step>` now separates the two. The `home-owner` check first
|
|
1841
|
+
required the base to be owned by the invoking SID itself. A real
|
|
1842
|
+
`%LOCALAPPDATA%` created by the User Profile Service is owned by the user, but
|
|
1843
|
+
a beta tester's profile was owned by SYSTEM, and elevated members of
|
|
1844
|
+
Administrators may create new directories owned by BUILTIN\Administrators,
|
|
1845
|
+
depending on the default-owner policy. The check now also accepts SYSTEM and
|
|
1846
|
+
Administrators as the base owner (see
|
|
1847
|
+
[claim candidates](#claim-candidates-and-owners)); any other owner, including
|
|
1848
|
+
TrustedInstaller, still fails closed with `home-owner`. The native fixtures
|
|
1849
|
+
still set the invoking SID as owner of the fixture root, read it back and fail
|
|
1850
|
+
loudly before any production stage runs. The candidate tests also hand one
|
|
1851
|
+
fixture base to Administrators on purpose.
|
|
1852
|
+
|
|
1853
|
+
### Implemented fixtures versus missing execution evidence
|
|
1854
|
+
|
|
1855
|
+
The native gates now contain runnable assertions, not empty or always-skipped
|
|
1856
|
+
callbacks. They run on Windows without a pending-fixture override:
|
|
1857
|
+
|
|
1858
|
+
- Actual production claim/check predicates: protected-root creation, collision
|
|
1859
|
+
preservation, harmless distant sibling creation, dangerous-right refusal,
|
|
1860
|
+
strict target/immediate-parent checks and junction rejection. Junction creation
|
|
1861
|
+
alone can be capability-skipped after an actual permission/unsupported error.
|
|
1862
|
+
DACL changes are limited to new disposable fixture-owned objects.
|
|
1863
|
+
- Actual fixed PowerShell ZIP namespace/publication stage: local ZIPs with the
|
|
1864
|
+
fixed archive/member identity; valid publication and traversal, aliases,
|
|
1865
|
+
symlink/reparse attributes, ADS, devices, trailing names and parent conflicts.
|
|
1866
|
+
The valid member contains the available fixture Node's unchanged bytes and is
|
|
1867
|
+
never executed. Collision tests preserve unrelated published storage.
|
|
1868
|
+
- Actual pre-Node process primitive: approved Node executes local fixture JS;
|
|
1869
|
+
nonzero exit, both output limits, quiet/stdout/stderr/both-pipe hangs and valid
|
|
1870
|
+
output followed by a hang are rejected. Production drain/deadline/validation/
|
|
1871
|
+
cleanup lines are unchanged. An independent hard guard checks recorded process
|
|
1872
|
+
creation ticks and only kills fresh fixture-owned handles. A guard firing or
|
|
1873
|
+
finding a residual recorded child is a test failure, not successful deadline
|
|
1874
|
+
evidence; fixture cleanup cannot mask a missing production reap.
|
|
1875
|
+
- Complete local sentinel composition exercises CMD continuation/quoting,
|
|
1876
|
+
bundle checks, claim, existing-Node resolution/probe, real helper pnpm handoff
|
|
1877
|
+
and late cleanup with spaces, Unicode and CMD metacharacters. Only the approved
|
|
1878
|
+
available Node's unchanged bytes are cloned into the disposable fixture PATH,
|
|
1879
|
+
excluding operator npm/Corepack/pnpm shims. A local JS pnpm fixture provides
|
|
1880
|
+
read-only identity/help responses. The trusted fixture helper copy has a fixed,
|
|
1881
|
+
fail-only fetch guard: accidental acquisition throws before any network call;
|
|
1882
|
+
it never supplies bytes/digests or adds a production integrity-bypass switch.
|
|
1883
|
+
No network/install occurs.
|
|
1884
|
+
|
|
1885
|
+
The fixtures preserve the exact production command lines and insert only
|
|
1886
|
+
fixture-owned process observations. They compose primitives in a disposable
|
|
1887
|
+
`.cmd`, not a loaded/evaluated PowerShell script file. Native AppLocker and
|
|
1888
|
+
ConstrainedLanguage denials still apply, with no policy relaxation. This is
|
|
1889
|
+
**stage/composition evidence, not uninstrumented whole-entry certification**:
|
|
1890
|
+
transport/integrity gates and the real pinned artifacts require separate proof.
|
|
1891
|
+
The original missing-entry test also exercises the unchanged whole entry.
|
|
1892
|
+
|
|
1893
|
+
All these native fixtures are **implemented but unrun on this Linux host**:
|
|
1894
|
+
Windows PowerShell and a Windows runner are unavailable. Portable descriptor,
|
|
1895
|
+
ACL-predicate model, tar, wrapper and process fixtures are not native Windows
|
|
1896
|
+
proof. Native OS/ACL/CMD/loader acceptance remains T7 evidence work; the actual
|
|
1897
|
+
pinned pnpm TGZ offline acceptance remains an explicitly unfinished evidence
|
|
1898
|
+
item. No live artifact was downloaded/executed, operator-home installation
|
|
1899
|
+
performed, operator/global ACL or policy changed, or Windows OS minimum/support
|
|
1900
|
+
claimed. No same-principal-adversary or forked-descendant guarantee is made.
|
|
1901
|
+
T7 must establish those facts before advertising clean-machine support.
|
|
1902
|
+
|
|
1903
|
+
## Clean-machine acceptance (T7)
|
|
1904
|
+
|
|
1905
|
+
### Linux clean container
|
|
1906
|
+
|
|
1907
|
+
A disposable `debian:bookworm-slim` container without Node, npm, pnpm, Pi or
|
|
1908
|
+
Gentle Shell, driven as a non-root user, passed end to end:
|
|
1909
|
+
`sh scripts/bootstrap.sh` exited 0 and the wizard, driven over HTTP exactly as
|
|
1910
|
+
the page does, reached `terminal-action-required` with all 13 runner steps
|
|
1911
|
+
`done` in about 27 seconds. A fresh `bash -ic` resolved `node` v24.21.0, `npm`
|
|
1912
|
+
11.19.0 and `pnpm` 11.1.1 from `$PNPM_HOME/bin`, and `gentle-shell --version`
|
|
1913
|
+
reported 4.0.0 with Pi 1.0.0 and the isolated home `~/.gentle-shell/agent`.
|
|
1914
|
+
That run predates the removal of bootstrap tools after success.
|
|
1915
|
+
|
|
1916
|
+
The first run of `scripts/test-installer-acceptance.sh --worktree` (with the
|
|
1917
|
+
tools removal, not yet committed) did **not** pass: the bootstrap acquired its
|
|
1918
|
+
tools and printed the session URL after 113 seconds, the plan had no blockers,
|
|
1919
|
+
and 11 runner steps finished `done` before `shell-setup` (`gentle-shell setup`)
|
|
1920
|
+
failed; the outcome was `failed`, the bootstrap exited 1 and removed its tools
|
|
1921
|
+
through the failure path, after 146 seconds in total. A separate diagnostic
|
|
1922
|
+
container proved the cause: `gentle-shell setup` failed with `Error: execute
|
|
1923
|
+
install pipeline: download engram binary: fetch latest engram version: GitHub
|
|
1924
|
+
API returned HTTP 403` because the anonymous GitHub API quota of this network
|
|
1925
|
+
was exhausted. After the quota reset, setup in the same container exited 0
|
|
1926
|
+
without installing any extra package. It was not caused by the tools removal,
|
|
1927
|
+
which only runs after the wizard exits. See
|
|
1928
|
+
[GitHub API limit during setup](#github-api-limit-during-setup).
|
|
1929
|
+
|
|
1930
|
+
The second run (with the quota precheck: 43 anonymous requests left) got past
|
|
1931
|
+
that: `shell-setup` finished `done`, then the last step, `persist-path`
|
|
1932
|
+
(`pnpm setup`), failed. The installation part took about 34 seconds, so it was
|
|
1933
|
+
not its 30-second deadline alone. The outcome was `failed`, the bootstrap exited
|
|
1934
|
+
1 and removed its tools through the failure path; 147 seconds in total. Cause:
|
|
1935
|
+
the script started the bootstrap with `docker exec ... sh -c`, which exports no
|
|
1936
|
+
`SHELL`, while the passing lab run had `SHELL=/bin/bash`; `pnpm setup` then
|
|
1937
|
+
cannot pick a profile (see `persist-path` above). A real terminal exports
|
|
1938
|
+
`SHELL`, so the script now passes `SHELL=/bin/bash` to the bootstrap, and a
|
|
1939
|
+
failed `persist-path` now reports pnpm's error line and SHELL guidance.
|
|
1940
|
+
|
|
1941
|
+
The third run, with `SHELL=/bin/bash`, **passed**: 23 anonymous GitHub
|
|
1942
|
+
requests left before it, session URL after 113 seconds, all 13 runner steps
|
|
1943
|
+
`done` including `shell-setup` and `persist-path`, outcome
|
|
1944
|
+
`terminal-action-required`, bootstrap exit 0; 142 seconds for bootstrap and
|
|
1945
|
+
wizard, 147 in total. A fresh `bash -ic` resolved `node` v24.21.0, `npm`
|
|
1946
|
+
11.19.0, `pnpm` 11.1.1 and `gentle-shell` from `~/.local/share/pnpm/bin`;
|
|
1947
|
+
`gentle-shell --version` reported 4.0.0, Pi 1.0.0 and the isolated home
|
|
1948
|
+
`~/.gentle-shell/agent`; no `~/.gentle-shell-bootstrap-tools.*` directory
|
|
1949
|
+
remained, so the success-path tools removal is proven in a clean container.
|
|
1950
|
+
`bash -lc` did not resolve `gentle-shell`, as documented below.
|
|
1951
|
+
|
|
1952
|
+
To reproduce it, run `sh scripts/test-installer-acceptance.sh` from the
|
|
1953
|
+
repository. It refuses to start without a working Docker daemon and never runs
|
|
1954
|
+
the installer on the host or touches the host HOME. It starts a container
|
|
1955
|
+
(`--pull missing`), installs only `ca-certificates` and `curl`, creates the
|
|
1956
|
+
user `tester`, then queries `https://api.github.com/rate_limit` from inside the
|
|
1957
|
+
container (this endpoint does not consume quota) and stops before the
|
|
1958
|
+
bootstrap, printing the reset time, when fewer than 5 anonymous core requests
|
|
1959
|
+
remain. It then copies the bundle to `/opt/bundle` (`git archive HEAD`, or the
|
|
1960
|
+
tracked working-tree files including uncommitted changes with `--worktree`) and
|
|
1961
|
+
runs the bootstrap. A headless driver inside the container redeems
|
|
1962
|
+
`/session?code=` with a cookie jar, sends `X-Gentle-Install: 1` on every
|
|
1963
|
+
`/api/*` request and the same Origin plus `Content-Type: application/json` on
|
|
1964
|
+
every POST, then requests the plan, consents, polls progress and shuts the host
|
|
1965
|
+
down. It then checks, in a fresh `bash -ic`, that `node`, `npm`, `pnpm` and
|
|
1966
|
+
`gentle-shell --version` resolve and that no `~/.gentle-shell-bootstrap-tools.*`
|
|
1967
|
+
directory remains, and reports whether `bash -lc` resolves `gentle-shell`. The
|
|
1968
|
+
container is removed on every exit unless `GENTLE_ACCEPTANCE_KEEP=1` keeps a
|
|
1969
|
+
failed one for inspection; the pulled image stays cached. Network
|
|
1970
|
+
access happens only inside the container. The installation is bounded by
|
|
1971
|
+
`GENTLE_ACCEPTANCE_INSTALL_SECONDS` (default 1500). CI does not run it.
|
|
1972
|
+
|
|
1973
|
+
### GitHub API limit during setup
|
|
1974
|
+
|
|
1975
|
+
`gentle-shell setup` runs upstream Gentle AI's install pipeline, which looks up
|
|
1976
|
+
the latest Engram version through the GitHub REST API without authentication:
|
|
1977
|
+
2 anonymous requests per setup. GitHub allows 60 anonymous requests per hour
|
|
1978
|
+
per public IP address, so machines sharing one address (offices, NAT, VPNs, CI
|
|
1979
|
+
runners, repeated test runs) can exhaust it; setup then fails with
|
|
1980
|
+
`GitHub API returned HTTP 403` and the wizard shows the rate-limit guidance and
|
|
1981
|
+
that error line. Waiting for the hourly reset is enough; nothing has to be
|
|
1982
|
+
uninstalled first: rerunning the installer finds the installed stack and runs
|
|
1983
|
+
only its [setup recovery](#setup-recovery).
|
|
1984
|
+
|
|
1985
|
+
A missing `git` only produces a warning during setup; it was not the cause.
|
|
1986
|
+
|
|
1987
|
+
Upstream advisory for Gentle AI (not changed here): pin the Engram version or
|
|
1988
|
+
fall back to it when the lookup fails, accept an optional `GITHUB_TOKEN` for
|
|
1989
|
+
the lookup, and replace the misleading "installed via go install" message that
|
|
1990
|
+
accompanies this failure.
|
|
1991
|
+
|
|
1992
|
+
### Shell profiles after `pnpm setup`
|
|
1993
|
+
|
|
1994
|
+
`pnpm setup` writes `PNPM_HOME` and its PATH entry into the interactive shell's
|
|
1995
|
+
profile only: `~/.bashrc` for bash on Debian. Debian's `~/.bashrc` returns
|
|
1996
|
+
early for non-interactive shells, and login shells read `~/.profile`, so these
|
|
1997
|
+
do not find `gentle-shell`, `node`, `npm` or `pnpm`:
|
|
1998
|
+
|
|
1999
|
+
- non-interactive login shells such as `bash -lc '...'`, and tools or IDE tasks
|
|
2000
|
+
that start one;
|
|
2001
|
+
- `ssh host command`: bash reads `~/.bashrc` but stops at its non-interactive
|
|
2002
|
+
guard, so the lines must sit above that guard (or the command must use an
|
|
2003
|
+
absolute path);
|
|
2004
|
+
- cron jobs, which read neither file: set PATH in the crontab or use absolute
|
|
2005
|
+
paths.
|
|
2006
|
+
|
|
2007
|
+
For login shells, add the same lines to `~/.profile` manually (the wizard does
|
|
2008
|
+
not edit it), using the `PNPM_HOME` that `pnpm setup` wrote, for example:
|
|
2009
|
+
|
|
2010
|
+
```sh
|
|
2011
|
+
export PNPM_HOME="$HOME/.local/share/pnpm"
|
|
2012
|
+
case ":$PATH:" in *":$PNPM_HOME/bin:"*) ;; *) export PATH="$PNPM_HOME/bin:$PATH" ;; esac
|
|
2013
|
+
```
|
|
2014
|
+
|
|
2015
|
+
### Upstream `~/.pi/gentle-ai`
|
|
2016
|
+
|
|
2017
|
+
Gentle AI's setup creates an empty `~/.pi/gentle-ai` directory outside the
|
|
2018
|
+
isolated Gentle Shell home `~/.gentle-shell/agent`. This is upstream behavior;
|
|
2019
|
+
the installer neither relies on nor removes it.
|
|
2020
|
+
|
|
2021
|
+
### CI matrix
|
|
2022
|
+
|
|
2023
|
+
The `installer` job in `.github/workflows/ci.yml` runs the installer suites with
|
|
2024
|
+
Node 24 on `ubuntu-latest`, `macos-latest` and `windows-latest`. The native
|
|
2025
|
+
Windows tests in `tests/installer-windows-bootstrap.test.ts` are gated on
|
|
2026
|
+
`process.platform === "win32"`, so they run on the Windows runner and skip
|
|
2027
|
+
elsewhere; the POSIX shell fixtures in `tests/installer-posix-bootstrap.test.ts`
|
|
2028
|
+
skip on Windows. CI only runs after a push, which remains a user decision.
|
|
2029
|
+
|
|
2030
|
+
### Remaining T7 checks
|
|
2031
|
+
|
|
2032
|
+
- real browsers and screen readers (only headless Chromium was used);
|
|
2033
|
+
- native macOS and Windows clean machines, including the Windows bootstrap
|
|
2034
|
+
end to end and its tools removal;
|
|
2035
|
+
- `pnpm setup` and fresh-terminal resolution in zsh and fish;
|
|
2036
|
+
- SIGTERM during a running installation;
|
|
2037
|
+
- reinstalling over an existing stack;
|
|
2038
|
+
- download-time integrity of the Node runtime and the npm and pnpm packages
|
|
2039
|
+
fetched during persistence;
|
|
2040
|
+
- the earlier T4/T5/T6 items listed above that the Linux run does not cover.
|