@skrr-ai/cli 0.1.93 → 0.1.95

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.
Files changed (174) hide show
  1. package/README.md +153 -165
  2. package/bin/run.js +32 -0
  3. package/bin/update-npm-runner.cjs +33 -0
  4. package/bin/update-session.cjs +194 -0
  5. package/bin/update-worker.cjs +208 -0
  6. package/dist/base-command.js +0 -15
  7. package/dist/commands/agents/operating-plan.d.ts +11 -0
  8. package/dist/commands/agents/operating-plan.js +54 -0
  9. package/dist/commands/agents/roles/example.d.ts +11 -0
  10. package/dist/commands/agents/roles/example.js +30 -0
  11. package/dist/commands/{commitments/mode.d.ts → agents/roles/export.d.ts} +5 -5
  12. package/dist/commands/agents/roles/export.js +81 -0
  13. package/dist/commands/agents/roles/install.d.ts +12 -0
  14. package/dist/commands/agents/roles/install.js +48 -0
  15. package/dist/commands/agents/roles/list.d.ts +11 -0
  16. package/dist/commands/agents/roles/list.js +36 -0
  17. package/dist/commands/agents/roles/preview.d.ts +14 -0
  18. package/dist/commands/agents/roles/preview.js +49 -0
  19. package/dist/commands/agents/roles/withdraw.d.ts +13 -0
  20. package/dist/commands/agents/roles/withdraw.js +43 -0
  21. package/dist/commands/code/doctor.d.ts +1 -0
  22. package/dist/commands/code/doctor.js +6 -0
  23. package/dist/commands/code/index.js +4 -0
  24. package/dist/commands/code/install.d.ts +5 -62
  25. package/dist/commands/code/install.js +86 -279
  26. package/dist/commands/code/rollback.d.ts +8 -0
  27. package/dist/commands/code/rollback.js +20 -0
  28. package/dist/commands/code/update.d.ts +8 -37
  29. package/dist/commands/code/update.js +134 -213
  30. package/dist/commands/commitments/{autonomy-requests → cadence-requests}/decide.d.ts +5 -7
  31. package/dist/commands/commitments/{autonomy-requests → cadence-requests}/decide.js +13 -15
  32. package/dist/commands/commitments/cost.d.ts +1 -1
  33. package/dist/commands/commitments/cost.js +1 -1
  34. package/dist/commands/commitments/create.d.ts +0 -7
  35. package/dist/commands/commitments/create.js +9 -98
  36. package/dist/commands/commitments/delivery.js +3 -21
  37. package/dist/commands/commitments/doctor.js +28 -27
  38. package/dist/commands/commitments/effective-policy.js +5 -36
  39. package/dist/commands/commitments/evaluations.d.ts +15 -0
  40. package/dist/commands/commitments/evaluations.js +54 -0
  41. package/dist/commands/commitments/pack/activate.d.ts +11 -0
  42. package/dist/commands/commitments/pack/activate.js +22 -0
  43. package/dist/commands/commitments/pack/bind.js +1 -1
  44. package/dist/commands/commitments/remediate.js +2 -5
  45. package/dist/commands/commitments/show.js +1 -1
  46. package/dist/commands/commitments/update.js +9 -9
  47. package/dist/commands/commitments/wake-capabilities.d.ts +1 -3
  48. package/dist/commands/commitments/wake-capabilities.js +5 -21
  49. package/dist/commands/daemon/install.js +4 -0
  50. package/dist/commands/generate-recovery-code.d.ts +13 -1
  51. package/dist/commands/generate-recovery-code.js +146 -13
  52. package/dist/commands/instructions/guide.d.ts +20 -0
  53. package/dist/commands/instructions/guide.js +84 -0
  54. package/dist/commands/instructions/install.js +57 -33
  55. package/dist/commands/instructions/setup.d.ts +14 -0
  56. package/dist/commands/instructions/setup.js +72 -0
  57. package/dist/commands/integrations/execute.js +1 -1
  58. package/dist/commands/login.d.ts +5 -6
  59. package/dist/commands/login.js +27 -11
  60. package/dist/commands/logout.js +7 -7
  61. package/dist/commands/security/overview.d.ts +11 -0
  62. package/dist/commands/security/overview.js +158 -0
  63. package/dist/commands/spaces/claim.d.ts +12 -0
  64. package/dist/commands/spaces/claim.js +42 -0
  65. package/dist/commands/spaces/ownership.d.ts +12 -0
  66. package/dist/commands/spaces/ownership.js +46 -0
  67. package/dist/commands/tasks/cancel-cascade.js +1 -1
  68. package/dist/commands/tasks/create-tree.d.ts +5 -1
  69. package/dist/commands/tasks/create-tree.js +35 -7
  70. package/dist/commands/tasks/interrupt.js +2 -2
  71. package/dist/commands/tasks/pause-schedule.js +1 -1
  72. package/dist/commands/token/list.d.ts +3 -5
  73. package/dist/commands/token/list.js +99 -57
  74. package/dist/commands/update.d.ts +14 -0
  75. package/dist/commands/update.js +61 -0
  76. package/dist/generated/managed-instruction-guides.json +9 -0
  77. package/dist/hooks/init.d.ts +5 -0
  78. package/dist/hooks/init.js +27 -0
  79. package/dist/lib/commitment-product.d.ts +0 -6
  80. package/dist/lib/commitment-product.js +1 -30
  81. package/dist/lib/commitments.d.ts +9 -53
  82. package/dist/lib/commitments.js +23 -146
  83. package/dist/lib/daemon-installer.d.ts +2 -0
  84. package/dist/lib/daemon-installer.js +100 -24
  85. package/dist/lib/first-party-harness-doctor.js +31 -1
  86. package/dist/lib/first-party-harness-remote-install.d.ts +71 -0
  87. package/dist/lib/first-party-harness-remote-install.js +310 -0
  88. package/dist/lib/first-party-harness-remote-update.d.ts +46 -0
  89. package/dist/lib/first-party-harness-remote-update.js +242 -0
  90. package/dist/lib/first-party-harness-startup-update.d.ts +26 -0
  91. package/dist/lib/first-party-harness-startup-update.js +166 -0
  92. package/dist/lib/first-party-harness-update.d.ts +46 -0
  93. package/dist/lib/first-party-harness-update.js +289 -0
  94. package/dist/lib/first-party-harness.js +7 -4
  95. package/dist/lib/managed-instruction-local.d.ts +12 -0
  96. package/dist/lib/managed-instruction-local.js +58 -0
  97. package/dist/lib/managed-instruction-setup.d.ts +35 -0
  98. package/dist/lib/managed-instruction-setup.js +306 -0
  99. package/dist/lib/memory-sync.d.ts +1 -0
  100. package/dist/lib/memory-sync.js +279 -68
  101. package/dist/lib/northstars.js +1 -1
  102. package/dist/lib/platform-update-local.d.ts +2 -0
  103. package/dist/lib/platform-update-local.js +113 -0
  104. package/dist/lib/platform-update.d.ts +39 -0
  105. package/dist/lib/platform-update.js +84 -0
  106. package/dist/lib/profile-machine-identity-guard.d.ts +2 -0
  107. package/dist/lib/profile-machine-identity-guard.js +13 -0
  108. package/dist/lib/prompt.d.ts +1 -0
  109. package/dist/lib/prompt.js +4 -3
  110. package/dist/lib/roles.d.ts +98 -0
  111. package/dist/lib/roles.js +45 -0
  112. package/dist/lib/task-batch.d.ts +9 -0
  113. package/dist/lib/task-batch.js +94 -17
  114. package/dist/lib/task-instruction-offer.d.ts +1 -4
  115. package/dist/lib/task-instruction-offer.js +3 -129
  116. package/dist/lib/update-check.d.ts +61 -53
  117. package/dist/lib/update-check.js +453 -314
  118. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/commitmentExecutionContract.d.ts +45 -0
  119. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/commitmentExecutionContract.js +550 -0
  120. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.d.ts +3 -4
  121. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.js +3 -4
  122. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessReleaseKeys.d.ts +82 -0
  123. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessReleaseKeys.js +105 -0
  124. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessUpdater.d.ts +110 -0
  125. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessUpdater.js +945 -0
  126. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.d.ts +32 -0
  127. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.js +62 -0
  128. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -0
  129. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -1
  130. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +35 -5
  131. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releasePolicy.d.ts +63 -0
  132. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releasePolicy.js +169 -0
  133. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseVersions.d.ts +2 -0
  134. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseVersions.js +44 -0
  135. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.d.ts +7 -44
  136. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.js +8 -47
  137. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/commitmentExecutionContract.d.ts +45 -0
  138. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/commitmentExecutionContract.js +545 -0
  139. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.d.ts +3 -4
  140. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.js +3 -4
  141. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessReleaseKeys.d.ts +82 -0
  142. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessReleaseKeys.js +102 -0
  143. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessUpdater.d.ts +110 -0
  144. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessUpdater.js +923 -0
  145. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.d.ts +32 -0
  146. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.js +59 -0
  147. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -0
  148. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -0
  149. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +35 -5
  150. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releasePolicy.d.ts +63 -0
  151. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releasePolicy.js +163 -0
  152. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseVersions.d.ts +2 -0
  153. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseVersions.js +41 -0
  154. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.d.ts +7 -44
  155. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.js +8 -47
  156. package/dist/node_modules/@skrr-ai/auth-core/package.json +41 -1
  157. package/dist/node_modules/@skrr-ai/data-provider/index.js +7362 -7232
  158. package/dist/node_modules/@skrr-ai/data-provider/package.json +2 -2
  159. package/docs/v1/README.md +28 -0
  160. package/docs/v1/api.md +55 -0
  161. package/docs/v1/changelog.md +16 -0
  162. package/docs/v1/check-examples.mjs +178 -0
  163. package/docs/v1/examples/api-readonly.mjs +144 -0
  164. package/docs/v1/operations.md +87 -0
  165. package/docs/v1/quickstart.md +71 -0
  166. package/oclif.manifest.json +46381 -45478
  167. package/package.json +16 -11
  168. package/dist/commands/commitments/autonomy.d.ts +0 -30
  169. package/dist/commands/commitments/autonomy.js +0 -67
  170. package/dist/commands/commitments/mode.js +0 -58
  171. package/dist/commands/commitments/pack/promote.d.ts +0 -19
  172. package/dist/commands/commitments/pack/promote.js +0 -46
  173. package/dist/commands/commitments/pack/shadow.d.ts +0 -20
  174. package/dist/commands/commitments/pack/shadow.js +0 -42
@@ -0,0 +1,945 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MAX_ARTIFACT_BYTES = exports.PROVENANCE_SUFFIX = exports.PREVIOUS_SUFFIX = exports.firstPartyHarnessManifestFile = exports.firstPartyHarnessFeedCandidates = exports.firstPartyHarnessFeedBase = exports.FIRST_PARTY_HARNESS_RELEASE_PUBLIC_KEYS = void 0;
7
+ exports.firstPartyHarnessInstallDir = firstPartyHarnessInstallDir;
8
+ exports.firstPartyHarnessBinaryPath = firstPartyHarnessBinaryPath;
9
+ exports.firstPartyHarnessPlatformKey = firstPartyHarnessPlatformKey;
10
+ exports.fetchVerifiedManifest = fetchVerifiedManifest;
11
+ exports.isManagedFirstPartyHarnessReleasePath = isManagedFirstPartyHarnessReleasePath;
12
+ exports.getVerifiedInstalledFirstPartyHarness = getVerifiedInstalledFirstPartyHarness;
13
+ exports.readFirstPartyHarnessUpdateState = readFirstPartyHarnessUpdateState;
14
+ exports.activateVerifiedFirstPartyHarnessArtifact = activateVerifiedFirstPartyHarnessArtifact;
15
+ exports.checkFirstPartyHarnessUpdate = checkFirstPartyHarnessUpdate;
16
+ exports.installFirstPartyHarness = installFirstPartyHarness;
17
+ exports.rollbackFirstPartyHarness = rollbackFirstPartyHarness;
18
+ exports.installedFirstPartyHarnessSize = installedFirstPartyHarnessSize;
19
+ /**
20
+ * One signed engine installation transaction for CLI, Daemon and Desktop.
21
+ * Releases contain both executable and provenance; replacing `current` is the
22
+ * only activation operation. Existing sessions keep their immutable executable.
23
+ * The signing identity is independent of the daemon release identity.
24
+ */
25
+ const node_crypto_1 = require("node:crypto");
26
+ const node_fs_1 = require("node:fs");
27
+ const node_path_1 = __importDefault(require("node:path"));
28
+ const proper_lockfile_1 = __importDefault(require("proper-lockfile"));
29
+ const firstPartyHarness_js_1 = require("./firstPartyHarness.js");
30
+ const firstPartyHarnessHome_js_1 = require("./firstPartyHarnessHome.js");
31
+ const firstPartyHarnessChannels_js_1 = require("./firstPartyHarnessChannels.js");
32
+ const releaseManifest_js_1 = require("./releaseManifest.js");
33
+ const firstPartyHarnessReleaseKeys_js_1 = require("./firstPartyHarnessReleaseKeys.js");
34
+ const releaseVersions_js_1 = require("./releaseVersions.js");
35
+ const releasePolicy_js_1 = require("./releasePolicy.js");
36
+ var firstPartyHarnessReleaseKeys_js_2 = require("./firstPartyHarnessReleaseKeys.js");
37
+ Object.defineProperty(exports, "FIRST_PARTY_HARNESS_RELEASE_PUBLIC_KEYS", { enumerable: true, get: function () { return firstPartyHarnessReleaseKeys_js_2.FIRST_PARTY_HARNESS_RELEASE_PUBLIC_KEYS; } });
38
+ var firstPartyHarnessChannels_js_2 = require("./firstPartyHarnessChannels.js");
39
+ Object.defineProperty(exports, "firstPartyHarnessFeedBase", { enumerable: true, get: function () { return firstPartyHarnessChannels_js_2.firstPartyHarnessFeedBase; } });
40
+ Object.defineProperty(exports, "firstPartyHarnessFeedCandidates", { enumerable: true, get: function () { return firstPartyHarnessChannels_js_2.firstPartyHarnessFeedCandidates; } });
41
+ var firstPartyHarness_js_2 = require("./firstPartyHarness.js");
42
+ Object.defineProperty(exports, "firstPartyHarnessManifestFile", { enumerable: true, get: function () { return firstPartyHarness_js_2.firstPartyHarnessManifestFile; } });
43
+ exports.PREVIOUS_SUFFIX = firstPartyHarnessHome_js_1.FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX;
44
+ exports.PROVENANCE_SUFFIX = firstPartyHarnessHome_js_1.FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX;
45
+ exports.MAX_ARTIFACT_BYTES = 400 * 1024 * 1024;
46
+ const MANIFEST_FETCH_TIMEOUT_MS = 30_000;
47
+ const ARTIFACT_FETCH_TIMEOUT_MS = 10 * 60_000;
48
+ const LOCK_STALE_MS = 120_000;
49
+ function fail(code, error) {
50
+ return { ok: false, code, error };
51
+ }
52
+ function fetchSignal(options, timeout) {
53
+ return options.signal
54
+ ? AbortSignal.any([options.signal, AbortSignal.timeout(timeout)])
55
+ : AbortSignal.timeout(timeout);
56
+ }
57
+ function firstPartyHarnessInstallDir(env = process.env) {
58
+ return (0, firstPartyHarnessHome_js_1.firstPartyHarnessBinDir)(env);
59
+ }
60
+ function firstPartyHarnessBinaryPath(env = process.env) {
61
+ return (0, firstPartyHarnessHome_js_1.firstPartyHarnessInstalledBinaryPath)(env);
62
+ }
63
+ function firstPartyHarnessPlatformKey(platform = process.platform, arch = process.arch) {
64
+ const cpu = arch === 'arm64' || arch === 'aarch64'
65
+ ? 'arm64'
66
+ : arch === 'x64' || arch === 'x86_64'
67
+ ? 'x64'
68
+ : null;
69
+ if (!cpu ||
70
+ !['darwin', 'linux', 'win32'].includes(platform) ||
71
+ (platform === 'win32' && cpu !== 'x64'))
72
+ return null;
73
+ return `${platform}-${cpu}`;
74
+ }
75
+ async function fetchVerifiedManifest(options = {}) {
76
+ const env = options.env ?? process.env;
77
+ const keys = options.keys ?? firstPartyHarnessReleaseKeys_js_1.FIRST_PARTY_HARNESS_RELEASE_PUBLIC_KEYS;
78
+ const rawFetch = options.fetchImpl ?? globalThis.fetch;
79
+ if (options.version && (0, releaseVersions_js_1.compareReleaseVersions)(options.version, options.version) === null)
80
+ return fail('version-invalid', 'requested version is not a semantic release version');
81
+ if (!keys || keys.length === 0) {
82
+ // See the header: unlike the daemon's self-update, an unverifiable harness
83
+ // is refused rather than installed. Provisioning the keypair must never be
84
+ // the thing that REDUCES what the daemon accepts.
85
+ return fail('no-pinned-keys', `no pinned ${firstPartyHarness_js_1.FIRST_PARTY_HARNESS.displayName} release keys — refusing to install`);
86
+ }
87
+ if (typeof rawFetch !== 'function') {
88
+ return fail('feed-unreachable', 'no fetch implementation available');
89
+ }
90
+ // A host the egress policy blocks must say so, not read as a certificate
91
+ // error (OSK-11947; see fetch-failure.ts).
92
+ const doFetch = rawFetch;
93
+ // WL-6.6 — the channel decides WHICH signed manifest, and nothing else.
94
+ //
95
+ // `latest/` was the only path this installer could read, which left the
96
+ // channel prefixes provisioned in terraform unreachable by any client. The
97
+ // channel is resolved here and nowhere else: artifact urls come from INSIDE
98
+ // the manifest and are never rewritten, because a channel in an artifact url
99
+ // would enter the signed message and turn every promotion into a re-signing
100
+ // ceremony.
101
+ //
102
+ // An unrecognised channel resolves to `stable` and reports rather than
103
+ // throwing. A typo must not leave a machine unable to install anything —
104
+ // that turns a harmless mistake into the outage this installer exists to
105
+ // avoid.
106
+ const { channel, invalid, source } = (0, firstPartyHarnessChannels_js_1.resolveFirstPartyHarnessChannel)(env);
107
+ if (invalid) {
108
+ options.onProgress?.(`unknown update channel ${JSON.stringify(invalid)} in ${source ?? firstPartyHarness_js_1.FIRST_PARTY_HARNESS_ENV.channel.name}; ` +
109
+ `using ${channel}. Valid: ${firstPartyHarnessChannels_js_1.FIRST_PARTY_HARNESS_CHANNELS.join(', ')}.`);
110
+ }
111
+ // Every place the manifest may be published, canonical spelling first.
112
+ //
113
+ // Which spellings depends on what is read. A CHANNEL pointer is current state,
114
+ // so only the accepted spellings are tried: a former spelling's pointer stopped
115
+ // moving when publishing stopped writing it, and falling back to one would
116
+ // install whatever it named on the day of contract. A PINNED release is an
117
+ // immutable artifact, so former spellings are tried too: a release published
118
+ // before the canonical prefix existed lives only under the old one, with the
119
+ // same signed bytes it would have anywhere.
120
+ //
121
+ // Move to the next candidate ONLY when this one is not there. A candidate that
122
+ // answers with a manifest ends the search, whatever that manifest turns out to
123
+ // be: falling through on a malformed or badly signed canonical manifest would
124
+ // let anyone who can make the canonical object fail verification steer the
125
+ // install onto an older spelling's feed — a downgrade by denial.
126
+ //
127
+ // "Not there" is 403 as well as 404. A prefix with no manifest answers 403 (the
128
+ // bucket denies ListBucket, so a missing key cannot be reported as 404).
129
+ const notFound = [];
130
+ let body;
131
+ for (const candidate of (0, firstPartyHarnessChannels_js_1.firstPartyHarnessFeedCandidates)(env, {
132
+ pinnedRelease: Boolean(options.version),
133
+ })) {
134
+ const url = (0, firstPartyHarnessChannels_js_1.resolveChannelManifestUrl)({
135
+ base: candidate.base,
136
+ manifestFilename: candidate.manifestFilename,
137
+ channel,
138
+ pinnedVersion: options.version ?? null,
139
+ });
140
+ try {
141
+ const res = await doFetch(url, {
142
+ signal: fetchSignal(options, options.manifestTimeoutMs ?? MANIFEST_FETCH_TIMEOUT_MS),
143
+ redirect: 'follow',
144
+ });
145
+ if (res.status === 403 || res.status === 404) {
146
+ notFound.push(`HTTP ${res.status} for ${url}`);
147
+ continue;
148
+ }
149
+ if (!res.ok) {
150
+ return fail('feed-unreachable', `manifest fetch failed: HTTP ${res.status} for ${url}`);
151
+ }
152
+ body = await res.text();
153
+ break;
154
+ }
155
+ catch (err) {
156
+ return fail('feed-unreachable', `manifest fetch failed: ${err.message}`);
157
+ }
158
+ }
159
+ if (body === undefined) {
160
+ // Name the CHANNEL when the object simply is not there anywhere.
161
+ //
162
+ // A 403 reads as "you are not allowed" — the one thing it does not mean,
163
+ // since this feed is public. A user on the default channel with nothing
164
+ // published yet would otherwise see a permissions error for a
165
+ // release-management state, and go looking at their credentials. A PINNED
166
+ // version keeps the raw failure: `releases/<v>/` missing means that exact
167
+ // release does not exist, and a channel setting has nothing to do with it.
168
+ const tried = notFound.join('; ');
169
+ if (!options.version) {
170
+ return fail('feed-unreachable', `no ${firstPartyHarness_js_1.FIRST_PARTY_HARNESS.displayName} release published on the '${channel}' channel (${tried}). ` +
171
+ `Set ${firstPartyHarness_js_1.FIRST_PARTY_HARNESS_ENV.channel.name} to a channel that has one, or pin a version.`);
172
+ }
173
+ return fail('feed-unreachable', `manifest fetch failed: ${tried}`);
174
+ }
175
+ let manifest;
176
+ try {
177
+ manifest = JSON.parse(body);
178
+ }
179
+ catch (err) {
180
+ return fail('manifest-malformed', `manifest is not JSON: ${err.message}`);
181
+ }
182
+ if (!manifest ||
183
+ typeof manifest.version !== 'string' ||
184
+ !manifest.platforms ||
185
+ typeof manifest.platforms !== 'object' ||
186
+ Array.isArray(manifest.platforms) ||
187
+ (0, releaseVersions_js_1.compareReleaseVersions)(manifest.version, manifest.version) === null) {
188
+ return fail('manifest-malformed', 'manifest is missing version or platforms');
189
+ }
190
+ const verdict = (0, releaseManifest_js_1.verifyManifest)(manifest, keys);
191
+ if (!('ok' in verdict) || verdict.ok !== true) {
192
+ const reason = 'reason' in verdict ? verdict.reason : 'verification disabled';
193
+ return fail('manifest-unverified', `manifest signature rejected: ${reason}`);
194
+ }
195
+ if (options.version && manifest.version !== options.version) {
196
+ return fail('manifest-malformed', 'pinned manifest version does not match requested version');
197
+ }
198
+ if ((0, releaseVersions_js_1.compareReleaseVersions)(manifest.minimum, manifest.version) === null ||
199
+ (0, releaseVersions_js_1.compareReleaseVersions)(manifest.minimum, manifest.version) > 0) {
200
+ return fail('manifest-malformed', 'manifest minimum is invalid or newer than its release');
201
+ }
202
+ try {
203
+ const policy = validateEnginePolicy(manifest, options);
204
+ if (policy)
205
+ return policy;
206
+ }
207
+ catch (error) {
208
+ return fail('policy-rejected', `engine release policy is malformed: ${describe(error)}`);
209
+ }
210
+ return { ok: true, manifest };
211
+ }
212
+ /** A present policy must explicitly describe this component, never the daemon. */
213
+ function validateEnginePolicy(manifest, options) {
214
+ if (!manifest.policy)
215
+ return null;
216
+ const keys = options.keys ?? firstPartyHarnessReleaseKeys_js_1.FIRST_PARTY_HARNESS_RELEASE_PUBLIC_KEYS;
217
+ const policy = manifest.policy;
218
+ const verdict = (0, releasePolicy_js_1.verifyReleasePolicy)(policy, keys);
219
+ if (!('ok' in verdict) || !verdict.ok)
220
+ return fail('policy-rejected', 'engine release policy signature/shape rejected');
221
+ const component = [firstPartyHarness_js_1.FIRST_PARTY_HARNESS.provider, 'firstPartyHarness'].find((name) => policy.components[name]);
222
+ if (!component)
223
+ return fail('policy-rejected', 'release policy has no first-party engine component');
224
+ const assessment = (0, releasePolicy_js_1.assessReleasePolicy)({
225
+ policy,
226
+ targetDaemonVersion: manifest.version,
227
+ targetComponent: component,
228
+ channel: (0, firstPartyHarnessChannels_js_1.resolveFirstPartyHarnessChannel)(options.env).channel,
229
+ contracts: options.contracts ?? {},
230
+ cohortSubject: options.cohortSubject ?? '',
231
+ autonomous: options.autonomous === true,
232
+ now: options.now,
233
+ });
234
+ if (!assessment.valid || !assessment.eligible)
235
+ return fail('policy-rejected', assessment.reason ?? 'engine release is not eligible for this policy');
236
+ const minimum = policy.components[component].minimumVersion;
237
+ if (minimum &&
238
+ ((0, releaseVersions_js_1.compareReleaseVersions)(manifest.version, minimum) === null ||
239
+ (0, releaseVersions_js_1.compareReleaseVersions)(manifest.version, minimum) < 0))
240
+ return fail('minimum-version', 'engine release is below its signed policy minimum');
241
+ return null;
242
+ }
243
+ function describe(error) {
244
+ return error instanceof Error ? error.message : String(error);
245
+ }
246
+ function paths(env) {
247
+ const home = (0, firstPartyHarnessHome_js_1.firstPartyHarnessHomeDir)(env);
248
+ return {
249
+ home,
250
+ releases: node_path_1.default.join(home, 'releases'),
251
+ current: node_path_1.default.join(home, 'current'),
252
+ previous: node_path_1.default.join(home, 'previous'),
253
+ state: node_path_1.default.join(home, 'update-state.json'),
254
+ floor: node_path_1.default.join(home, 'minimum-manifest.json'),
255
+ lock: node_path_1.default.join(node_path_1.default.dirname(home), `.${firstPartyHarness_js_1.FIRST_PARTY_HARNESS.provider}-update`),
256
+ };
257
+ }
258
+ function prepareLock(env) {
259
+ const target = paths(env).lock;
260
+ (0, node_fs_1.mkdirSync)(node_path_1.default.dirname(target), { recursive: true, mode: 0o700 });
261
+ (0, node_fs_1.writeFileSync)(target, '', { flag: 'a', mode: 0o600 });
262
+ return target;
263
+ }
264
+ function atomicJson(file, data) {
265
+ (0, node_fs_1.mkdirSync)(node_path_1.default.dirname(file), { recursive: true, mode: 0o700 });
266
+ const staging = `${file}.tmp-${(0, node_crypto_1.randomUUID)()}`;
267
+ try {
268
+ (0, node_fs_1.writeFileSync)(staging, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600 });
269
+ (0, node_fs_1.renameSync)(staging, file);
270
+ }
271
+ finally {
272
+ (0, node_fs_1.rmSync)(staging, { force: true });
273
+ }
274
+ }
275
+ function atomicLink(target, link) {
276
+ const staging = `${link}.tmp-${(0, node_crypto_1.randomUUID)()}`;
277
+ try {
278
+ (0, node_fs_1.symlinkSync)(target, staging);
279
+ (0, node_fs_1.renameSync)(staging, link);
280
+ }
281
+ finally {
282
+ (0, node_fs_1.rmSync)(staging, { force: true });
283
+ }
284
+ }
285
+ function readLink(file) {
286
+ try {
287
+ return (0, node_fs_1.readlinkSync)(file);
288
+ }
289
+ catch {
290
+ return null;
291
+ }
292
+ }
293
+ function hasEntry(file) {
294
+ try {
295
+ (0, node_fs_1.lstatSync)(file);
296
+ return true;
297
+ }
298
+ catch {
299
+ return false;
300
+ }
301
+ }
302
+ function isManagedFirstPartyHarnessReleasePath(binaryPath, env = process.env) {
303
+ const canonical = firstPartyHarnessBinaryPath(env);
304
+ return (node_path_1.default.resolve(binaryPath) === node_path_1.default.resolve(canonical) &&
305
+ readLink(canonical) === `../current/${node_path_1.default.basename(canonical)}`);
306
+ }
307
+ /** Read a single immutable binary/sidecar pair, never two different `current` targets. */
308
+ function getVerifiedInstalledFirstPartyHarness(options = {}) {
309
+ const canonical = firstPartyHarnessBinaryPath(options.env);
310
+ return inspectBinary(canonical, options.keys ?? firstPartyHarnessReleaseKeys_js_1.FIRST_PARTY_HARNESS_RELEASE_PUBLIC_KEYS, firstPartyHarnessPlatformKey(options.platform, options.arch));
311
+ }
312
+ function inspectBinary(binaryPath, keys, expectedPlatform = firstPartyHarnessPlatformKey()) {
313
+ const result = {
314
+ binaryPath,
315
+ version: null,
316
+ verified: false,
317
+ exists: (0, node_fs_1.existsSync)(binaryPath),
318
+ provenance: null,
319
+ };
320
+ if (!result.exists || !keys.length)
321
+ return result;
322
+ try {
323
+ const resolved = (0, node_fs_1.realpathSync)(binaryPath);
324
+ const size = (0, node_fs_1.statSync)(resolved).size;
325
+ if (size > exports.MAX_ARTIFACT_BYTES)
326
+ return result;
327
+ const record = JSON.parse((0, node_fs_1.readFileSync)(resolved + exports.PROVENANCE_SUFFIX, 'utf8'));
328
+ if (!record ||
329
+ (0, releaseVersions_js_1.compareReleaseVersions)(record.version, record.version) === null ||
330
+ typeof record.platform !== 'string' ||
331
+ typeof record.sha256 !== 'string' ||
332
+ typeof record.url !== 'string' ||
333
+ typeof record.sig !== 'string')
334
+ return result;
335
+ record.sha256 = record.sha256.toLowerCase();
336
+ result.provenance = record;
337
+ if (record.platform !== expectedPlatform)
338
+ return result;
339
+ const verdict = (0, releaseManifest_js_1.verifyArtifact)(record.platform, record.version, { ...record, file: node_path_1.default.basename(binaryPath), size }, keys);
340
+ if (!('ok' in verdict) ||
341
+ !verdict.ok ||
342
+ (0, node_crypto_1.createHash)('sha256').update((0, node_fs_1.readFileSync)(resolved)).digest('hex') !==
343
+ record.sha256.toLowerCase())
344
+ return result;
345
+ result.version = record.version;
346
+ result.verified = true;
347
+ }
348
+ catch {
349
+ /* absent/malformed/tampered provenance is not a version authority */
350
+ }
351
+ return result;
352
+ }
353
+ function readFirstPartyHarnessUpdateState(env = process.env) {
354
+ try {
355
+ const value = JSON.parse((0, node_fs_1.readFileSync)(paths(env).state, 'utf8'));
356
+ if (!value ||
357
+ !['install', 'rollback'].includes(value.operation) ||
358
+ !['running', 'succeeded', 'failed'].includes(value.status) ||
359
+ !Number.isFinite(value.attemptedAt))
360
+ return null;
361
+ // A killed updater cannot finalize its durable running record. Diagnose it
362
+ // without writing during a status/check command; live lock heartbeats keep
363
+ // long downloads distinguishable from interrupted transactions.
364
+ if (value.status === 'running' &&
365
+ !proper_lockfile_1.default.checkSync(paths(env).lock, { realpath: false, stale: LOCK_STALE_MS })) {
366
+ return {
367
+ ...value,
368
+ status: 'failed',
369
+ code: 'install-failed',
370
+ error: 'The previous update was interrupted. Run `skrr code update` to retry.',
371
+ };
372
+ }
373
+ return value;
374
+ }
375
+ catch {
376
+ return null;
377
+ }
378
+ }
379
+ function finishState(env, operation, attemptedAt, result, targetVersion, keys = firstPartyHarnessReleaseKeys_js_1.FIRST_PARTY_HARNESS_RELEASE_PUBLIC_KEYS) {
380
+ atomicJson(paths(env).state, {
381
+ operation,
382
+ status: result.ok ? 'succeeded' : 'failed',
383
+ attemptedAt,
384
+ completedAt: Date.now(),
385
+ targetVersion,
386
+ installedVersion: result.version ?? getVerifiedInstalledFirstPartyHarness({ env, keys }).version ?? undefined,
387
+ code: result.code,
388
+ error: result.error,
389
+ changed: result.changed,
390
+ });
391
+ }
392
+ function retainedManifest(env, keys) {
393
+ try {
394
+ const manifest = JSON.parse((0, node_fs_1.readFileSync)(paths(env).floor, 'utf8'));
395
+ const verdict = (0, releaseManifest_js_1.verifyManifest)(manifest, keys);
396
+ if (!('ok' in verdict) ||
397
+ !verdict.ok ||
398
+ (0, releaseVersions_js_1.compareReleaseVersions)(manifest.minimum, manifest.version) === null ||
399
+ (0, releaseVersions_js_1.compareReleaseVersions)(manifest.minimum, manifest.version) > 0)
400
+ throw new Error('retained minimum manifest is invalid; refusing to discard the signed floor');
401
+ return manifest;
402
+ }
403
+ catch (error) {
404
+ if (hasEntry(paths(env).floor))
405
+ throw error;
406
+ return null;
407
+ }
408
+ }
409
+ function effectiveMinimum(manifest, keys) {
410
+ let minimum = manifest.minimum;
411
+ if (manifest.policy) {
412
+ const policy = manifest.policy;
413
+ const verdict = (0, releasePolicy_js_1.verifyReleasePolicy)(policy, keys);
414
+ if (!('ok' in verdict) || !verdict.ok)
415
+ throw new Error('retained signed policy is invalid');
416
+ const component = [firstPartyHarness_js_1.FIRST_PARTY_HARNESS.provider, 'firstPartyHarness'].find((name) => policy.components[name]);
417
+ const floor = component ? policy.components[component].minimumVersion : undefined;
418
+ if (floor && (0, releaseVersions_js_1.compareReleaseVersions)(floor, minimum) === null)
419
+ throw new Error('signed policy minimum is invalid');
420
+ if (floor && (0, releaseVersions_js_1.compareReleaseVersions)(floor, minimum) === 1)
421
+ minimum = floor;
422
+ }
423
+ return minimum;
424
+ }
425
+ function retainedMinimum(env, keys) {
426
+ const manifest = retainedManifest(env, keys);
427
+ return manifest ? effectiveMinimum(manifest, keys) : null;
428
+ }
429
+ function preserveMinimum(env, keys, manifest) {
430
+ if (!manifest)
431
+ return;
432
+ const previous = retainedManifest(env, keys);
433
+ if (!previous ||
434
+ (0, releaseVersions_js_1.compareReleaseVersions)(effectiveMinimum(manifest, keys), effectiveMinimum(previous, keys)) === 1)
435
+ atomicJson(paths(env).floor, manifest);
436
+ }
437
+ function createAliases(env) {
438
+ const canonical = firstPartyHarnessBinaryPath(env);
439
+ for (const spelling of (0, firstPartyHarness_js_1.firstPartyHarnessArtifactSpellings)()) {
440
+ const alias = node_path_1.default.join(node_path_1.default.dirname(canonical), (0, firstPartyHarness_js_1.firstPartyHarnessBinaryName)(spelling));
441
+ if (alias === canonical)
442
+ continue;
443
+ for (const suffix of [exports.PROVENANCE_SUFFIX, '']) {
444
+ if (!hasEntry(alias + suffix)) {
445
+ try {
446
+ (0, node_fs_1.symlinkSync)(node_path_1.default.basename(canonical) + suffix, alias + suffix);
447
+ }
448
+ catch {
449
+ /* compatibility aliases must not invalidate an activated release */
450
+ }
451
+ }
452
+ }
453
+ }
454
+ }
455
+ function stableLinks(env) {
456
+ const binary = firstPartyHarnessBinaryPath(env);
457
+ (0, node_fs_1.mkdirSync)(node_path_1.default.dirname(binary), { recursive: true, mode: 0o700 });
458
+ for (const suffix of [exports.PROVENANCE_SUFFIX, '']) {
459
+ const target = `../current/${node_path_1.default.basename(binary)}${suffix}`;
460
+ if (readLink(binary + suffix) !== target)
461
+ atomicLink(target, binary + suffix);
462
+ }
463
+ if (hasEntry(paths(env).previous)) {
464
+ for (const suffix of [exports.PROVENANCE_SUFFIX, '']) {
465
+ const target = `../previous/${node_path_1.default.basename(binary)}${suffix}`;
466
+ if (readLink(binary + exports.PREVIOUS_SUFFIX + suffix) !== target)
467
+ atomicLink(target, binary + exports.PREVIOUS_SUFFIX + suffix);
468
+ }
469
+ }
470
+ createAliases(env);
471
+ }
472
+ function migrateFlatLayout(env, _progress) {
473
+ const p = paths(env);
474
+ const binary = firstPartyHarnessBinaryPath(env);
475
+ (0, node_fs_1.mkdirSync)(p.releases, { recursive: true, mode: 0o700 });
476
+ if (isManagedFirstPartyHarnessReleasePath(binary, env) || !(0, node_fs_1.existsSync)(binary))
477
+ return;
478
+ // Adopt flat bytes without pretending an unsigned legacy binary is trusted.
479
+ // Copies leave the original runnable throughout the one-time layout change.
480
+ const dir = node_path_1.default.join(p.releases, `legacy-${(0, node_crypto_1.randomUUID)()}`);
481
+ (0, node_fs_1.mkdirSync)(dir, { mode: 0o700 });
482
+ const adopted = node_path_1.default.join(dir, node_path_1.default.basename(binary));
483
+ (0, node_fs_1.copyFileSync)(binary, adopted);
484
+ (0, node_fs_1.chmodSync)(adopted, 0o555);
485
+ if ((0, node_fs_1.existsSync)(binary + exports.PROVENANCE_SUFFIX)) {
486
+ (0, node_fs_1.copyFileSync)(binary + exports.PROVENANCE_SUFFIX, adopted + exports.PROVENANCE_SUFFIX);
487
+ (0, node_fs_1.chmodSync)(adopted + exports.PROVENANCE_SUFFIX, 0o444);
488
+ }
489
+ (0, node_fs_1.chmodSync)(dir, 0o700);
490
+ const outgoing = readLink(p.current);
491
+ if (outgoing)
492
+ atomicLink(outgoing, p.previous);
493
+ atomicLink(node_path_1.default.relative(p.home, dir), p.current);
494
+ stableLinks(env);
495
+ }
496
+ function validateArtifact(options) {
497
+ const keys = options.keys ?? firstPartyHarnessReleaseKeys_js_1.FIRST_PARTY_HARNESS_RELEASE_PUBLIC_KEYS;
498
+ const record = options.provenance;
499
+ if (!record ||
500
+ typeof record !== 'object' ||
501
+ typeof record.version !== 'string' ||
502
+ typeof record.platform !== 'string' ||
503
+ typeof record.sha256 !== 'string' ||
504
+ !/^[a-fA-F0-9]{64}$/.test(record.sha256) ||
505
+ typeof record.url !== 'string' ||
506
+ typeof record.sig !== 'string' ||
507
+ !Buffer.isBuffer(options.binary))
508
+ return fail('artifact-unverified', 'artifact provenance is malformed');
509
+ if (!keys.length)
510
+ return fail('no-pinned-keys', 'no pinned engine release keys; refusing activation');
511
+ if ((0, releaseVersions_js_1.compareReleaseVersions)(record.version, record.version) === null)
512
+ return fail('version-invalid', 'artifact version is not a semantic release version');
513
+ if (record.platform !== firstPartyHarnessPlatformKey(options.platform, options.arch))
514
+ return fail('unsupported-platform', `artifact ${record.platform} does not match this host`);
515
+ if ((options.platform ?? process.platform) === 'win32')
516
+ return fail('unsupported-platform', 'managed engine release activation currently requires macOS or Linux');
517
+ const verdict = (0, releaseManifest_js_1.verifyArtifact)(record.platform, record.version, {
518
+ ...record,
519
+ file: (0, firstPartyHarness_js_1.firstPartyHarnessBinaryName)(),
520
+ size: options.binary.length,
521
+ }, keys);
522
+ if (!('ok' in verdict) || !verdict.ok)
523
+ return fail('artifact-unverified', 'artifact provenance signature rejected');
524
+ if (options.binary.length > exports.MAX_ARTIFACT_BYTES)
525
+ return fail('too-large', 'artifact exceeds the download ceiling');
526
+ if ((0, node_crypto_1.createHash)('sha256').update(options.binary).digest('hex') !== record.sha256.toLowerCase())
527
+ return fail('hash-mismatch', 'artifact bytes do not match signed provenance');
528
+ if (options.manifest) {
529
+ const policyFailure = validateEnginePolicy(options.manifest, options);
530
+ if (policyFailure)
531
+ return policyFailure;
532
+ const manifestVerdict = (0, releaseManifest_js_1.verifyManifest)(options.manifest, keys);
533
+ const artifact = options.manifest.platforms?.[record.platform];
534
+ if (!('ok' in manifestVerdict) ||
535
+ !manifestVerdict.ok ||
536
+ options.manifest.version !== record.version ||
537
+ artifact?.sha256?.toLowerCase() !== record.sha256.toLowerCase() ||
538
+ (0, releaseVersions_js_1.compareReleaseVersions)(options.manifest.minimum, record.version) === null)
539
+ return fail('manifest-unverified', 'activation manifest does not authenticate this artifact');
540
+ }
541
+ return null;
542
+ }
543
+ function activateLocked(options) {
544
+ const env = options.env ?? process.env;
545
+ const keys = options.keys ?? firstPartyHarnessReleaseKeys_js_1.FIRST_PARTY_HARNESS_RELEASE_PUBLIC_KEYS;
546
+ const progress = options.onProgress ?? (() => { });
547
+ const p = paths(env);
548
+ const binary = firstPartyHarnessBinaryPath(env);
549
+ const canonicalLink = readLink(binary);
550
+ // Older spelling links are migration inputs; arbitrary symlinks belong to
551
+ // an external install and are never followed for writes.
552
+ const legacyLinks = (0, firstPartyHarness_js_1.firstPartyHarnessArtifactSpellings)().map((spelling) => (0, firstPartyHarness_js_1.firstPartyHarnessBinaryName)(spelling));
553
+ if (canonicalLink &&
554
+ !isManagedFirstPartyHarnessReleasePath(binary, env) &&
555
+ !legacyLinks.includes(canonicalLink)) {
556
+ return options.preserveExternalLink
557
+ ? {
558
+ ok: true,
559
+ changed: false,
560
+ binaryPath: binary,
561
+ reason: 'canonical engine is an externally owned symlink',
562
+ }
563
+ : fail('install-failed', 'managed engine path is an external symlink; update its owning installation');
564
+ }
565
+ if (options.preserveExternalLink && !hasEntry(binary)) {
566
+ for (const spelling of (0, firstPartyHarness_js_1.firstPartyHarnessArtifactSpellings)()) {
567
+ const alias = node_path_1.default.join(node_path_1.default.dirname(binary), (0, firstPartyHarness_js_1.firstPartyHarnessBinaryName)(spelling));
568
+ const aliasLink = readLink(alias);
569
+ if (aliasLink && aliasLink !== node_path_1.default.basename(binary))
570
+ return {
571
+ ok: true,
572
+ changed: false,
573
+ binaryPath: binary,
574
+ reason: `legacy engine at ${alias} is an externally owned symlink`,
575
+ };
576
+ }
577
+ }
578
+ const migration = (0, firstPartyHarnessHome_js_1.migrateFirstPartyHarnessHome)({ env });
579
+ for (const change of migration.moved)
580
+ progress(`engine home migration: ${change}`);
581
+ for (const error of migration.errors)
582
+ progress(`engine home migration: ${error}`);
583
+ const installed = getVerifiedInstalledFirstPartyHarness({
584
+ env,
585
+ keys,
586
+ platform: options.platform,
587
+ arch: options.arch,
588
+ });
589
+ const record = { ...options.provenance, sha256: options.provenance.sha256.toLowerCase() };
590
+ const floor = retainedMinimum(env, keys);
591
+ if (floor && (0, releaseVersions_js_1.compareReleaseVersions)(record.version, floor) < 0)
592
+ return fail('minimum-version', `release ${record.version} is below the retained signed minimum ${floor}`);
593
+ if (options.manifest && (0, releaseVersions_js_1.compareReleaseVersions)(record.version, options.manifest.minimum) < 0)
594
+ return fail('minimum-version', 'release is below its signed minimum');
595
+ if (installed.exists && !installed.verified && options.preserveUnknownInstalled)
596
+ return {
597
+ ok: true,
598
+ changed: false,
599
+ binaryPath: binary,
600
+ reason: 'installed engine has no verified provenance',
601
+ };
602
+ if (installed.version) {
603
+ const comparison = (0, releaseVersions_js_1.compareReleaseVersions)(record.version, installed.version);
604
+ if (comparison === 0 && installed.provenance?.sha256 !== record.sha256)
605
+ return fail('manifest-malformed', 'release version is immutable but signed artifact digest changed');
606
+ if (comparison === 0 || (comparison < 0 && !options.allowDowngrade)) {
607
+ preserveMinimum(env, keys, options.manifest);
608
+ return {
609
+ ok: true,
610
+ changed: false,
611
+ replaced: true,
612
+ version: installed.version,
613
+ platform: installed.provenance?.platform,
614
+ binaryPath: binary,
615
+ };
616
+ }
617
+ }
618
+ migrateFlatLayout(env, progress);
619
+ const slot = node_path_1.default.join(p.releases, `${record.version}-${record.sha256.toLowerCase()}`);
620
+ const slotBinary = node_path_1.default.join(slot, node_path_1.default.basename(binary));
621
+ if (hasEntry(slot)) {
622
+ if ((0, node_fs_1.lstatSync)(slot).isSymbolicLink())
623
+ return fail('install-failed', 'release directory must be locally owned, not a symlink');
624
+ const existing = inspectBinary(slotBinary, keys, firstPartyHarnessPlatformKey(options.platform, options.arch));
625
+ if (!existing.verified || existing.provenance?.sha256 !== record.sha256)
626
+ return fail('install-failed', 'immutable release directory already exists with invalid bytes');
627
+ }
628
+ else {
629
+ const staging = node_path_1.default.join(p.releases, `.staging-${(0, node_crypto_1.randomUUID)()}`);
630
+ (0, node_fs_1.mkdirSync)(staging, { mode: 0o700 });
631
+ try {
632
+ (0, node_fs_1.writeFileSync)(node_path_1.default.join(staging, node_path_1.default.basename(binary)), options.binary, { mode: 0o755 });
633
+ (0, node_fs_1.writeFileSync)(node_path_1.default.join(staging, node_path_1.default.basename(binary) + exports.PROVENANCE_SUFFIX), `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600 });
634
+ (0, node_fs_1.chmodSync)(node_path_1.default.join(staging, node_path_1.default.basename(binary)), 0o555);
635
+ (0, node_fs_1.chmodSync)(node_path_1.default.join(staging, node_path_1.default.basename(binary) + exports.PROVENANCE_SUFFIX), 0o444);
636
+ (0, node_fs_1.chmodSync)(staging, 0o700);
637
+ (0, node_fs_1.renameSync)(staging, slot);
638
+ }
639
+ finally {
640
+ if (hasEntry(staging)) {
641
+ (0, node_fs_1.chmodSync)(staging, 0o700);
642
+ (0, node_fs_1.rmSync)(staging, { recursive: true, force: true });
643
+ }
644
+ }
645
+ }
646
+ const previous = readLink(p.current);
647
+ const target = node_path_1.default.relative(p.home, slot);
648
+ if (previous && previous !== target)
649
+ atomicLink(previous, p.previous);
650
+ // Prepare the stable entry points while the original release still runs.
651
+ // Subsequent installations only change this ONE pointer.
652
+ stableLinks(env);
653
+ preserveMinimum(env, keys, options.manifest);
654
+ atomicLink(target, p.current);
655
+ progress(`${firstPartyHarness_js_1.FIRST_PARTY_HARNESS.displayName} ${record.version} installed; existing sessions keep their current release.`);
656
+ return {
657
+ ok: true,
658
+ changed: true,
659
+ replaced: installed.exists,
660
+ version: record.version,
661
+ platform: record.platform,
662
+ binaryPath: binary,
663
+ };
664
+ }
665
+ /** Desktop's already-downloaded signed artifact uses the exact same transaction. */
666
+ function activateVerifiedFirstPartyHarnessArtifact(options) {
667
+ const invalid = validateArtifact(options);
668
+ const env = options.env ?? process.env;
669
+ const attemptedAt = Date.now();
670
+ let release;
671
+ try {
672
+ release = proper_lockfile_1.default.lockSync(prepareLock(env), { realpath: false, stale: LOCK_STALE_MS });
673
+ const result = invalid ?? activateLocked(options);
674
+ finishState(env, 'install', attemptedAt, result, options.provenance?.version, options.keys);
675
+ return result;
676
+ }
677
+ catch (error) {
678
+ const result = fail(error.code === 'ELOCKED' ? 'lock-busy' : 'install-failed', describe(error));
679
+ if (release) {
680
+ try {
681
+ finishState(env, 'install', attemptedAt, result, options.provenance?.version, options.keys);
682
+ }
683
+ catch {
684
+ // Preserve the activation failure if its diagnostic journal is also unwritable.
685
+ }
686
+ }
687
+ return result;
688
+ }
689
+ finally {
690
+ release?.();
691
+ }
692
+ }
693
+ /** Signed-manifest check reads only; no lock, migration, state writes or login. */
694
+ async function checkFirstPartyHarnessUpdate(options = {}) {
695
+ const fetched = await fetchVerifiedManifest(options);
696
+ if (!fetched.ok)
697
+ return fetched;
698
+ const env = options.env ?? process.env;
699
+ const keys = options.keys ?? firstPartyHarnessReleaseKeys_js_1.FIRST_PARTY_HARNESS_RELEASE_PUBLIC_KEYS;
700
+ const platform = firstPartyHarnessPlatformKey(options.platform, options.arch);
701
+ if (!platform || (options.platform ?? process.platform) === 'win32')
702
+ return fail('unsupported-platform', 'this host has no managed engine artifact');
703
+ const artifact = fetched.manifest.platforms[platform];
704
+ if (!artifact)
705
+ return fail('artifact-missing', `release has no ${platform} artifact`);
706
+ const verdict = (0, releaseManifest_js_1.verifyArtifact)(platform, fetched.manifest.version, artifact, keys);
707
+ if (!('ok' in verdict) || !verdict.ok)
708
+ return fail('artifact-unverified', 'offered artifact signature rejected');
709
+ let minimum = effectiveMinimum(fetched.manifest, keys);
710
+ try {
711
+ const floor = retainedMinimum(env, keys);
712
+ if (floor && (0, releaseVersions_js_1.compareReleaseVersions)(fetched.manifest.version, floor) < 0)
713
+ return fail('minimum-version', 'offered release is below the retained signed minimum');
714
+ if (floor && (0, releaseVersions_js_1.compareReleaseVersions)(floor, minimum) === 1)
715
+ minimum = floor;
716
+ }
717
+ catch (error) {
718
+ return fail('minimum-version', describe(error));
719
+ }
720
+ const installed = getVerifiedInstalledFirstPartyHarness(options);
721
+ if (installed.version === fetched.manifest.version &&
722
+ installed.provenance?.sha256 !== artifact.sha256.toLowerCase())
723
+ return fail('manifest-malformed', 'same release version offers a different signed artifact digest');
724
+ const override = (0, firstPartyHarness_js_1.readFirstPartyHarnessEnv)(env, 'path').value;
725
+ return {
726
+ ok: true,
727
+ installedVersion: installed.version,
728
+ latestVersion: fetched.manifest.version,
729
+ channel: (0, firstPartyHarnessChannels_js_1.resolveFirstPartyHarnessChannel)(env).channel,
730
+ updateAvailable: !installed.version ||
731
+ (0, releaseVersions_js_1.compareReleaseVersions)(installed.version, fetched.manifest.version) === -1,
732
+ binaryPath: installed.binaryPath,
733
+ owner: override && node_path_1.default.resolve(override) !== node_path_1.default.resolve(installed.binaryPath)
734
+ ? 'external'
735
+ : 'managed',
736
+ installedVerified: installed.verified,
737
+ minimumVersion: minimum,
738
+ lastUpdate: readFirstPartyHarnessUpdateState(env),
739
+ };
740
+ }
741
+ async function installFirstPartyHarness(options = {}) {
742
+ const env = options.env ?? process.env;
743
+ const keys = options.keys ?? firstPartyHarnessReleaseKeys_js_1.FIRST_PARTY_HARNESS_RELEASE_PUBLIC_KEYS;
744
+ const progress = options.onProgress ?? (() => { });
745
+ const platform = firstPartyHarnessPlatformKey(options.platform, options.arch);
746
+ if (!platform || (options.platform ?? process.platform) === 'win32')
747
+ return fail('unsupported-platform', 'no managed engine activation for this platform');
748
+ if (options.version && (0, releaseVersions_js_1.compareReleaseVersions)(options.version, options.version) === null)
749
+ return fail('version-invalid', 'requested version is not a semantic release version');
750
+ if (options.allowDowngrade && !options.version)
751
+ return fail('downgrade-refused', 'a downgrade requires an explicit pinned version');
752
+ const attemptedAt = Date.now();
753
+ let release;
754
+ let result;
755
+ try {
756
+ release = await proper_lockfile_1.default.lock(prepareLock(env), {
757
+ realpath: false,
758
+ stale: LOCK_STALE_MS,
759
+ update: 10_000,
760
+ retries: { retries: 20, minTimeout: 100, maxTimeout: 500, factor: 1.2 },
761
+ });
762
+ atomicJson(paths(env).state, {
763
+ operation: 'install',
764
+ status: 'running',
765
+ attemptedAt,
766
+ targetVersion: options.version,
767
+ });
768
+ result = await downloadAndActivate();
769
+ finishState(env, 'install', attemptedAt, result, result.version ?? options.version, keys);
770
+ return result;
771
+ }
772
+ catch (error) {
773
+ result = fail(error.code === 'ELOCKED' ? 'lock-busy' : 'install-failed', describe(error));
774
+ if (release) {
775
+ try {
776
+ finishState(env, 'install', attemptedAt, result, options.version, keys);
777
+ }
778
+ catch {
779
+ /* preserve original error */
780
+ }
781
+ }
782
+ return result;
783
+ }
784
+ finally {
785
+ await release?.();
786
+ }
787
+ async function downloadAndActivate() {
788
+ progress('Fetching signed release manifest…');
789
+ const fetched = await fetchVerifiedManifest(options);
790
+ if (!fetched.ok)
791
+ return fetched;
792
+ const { manifest } = fetched;
793
+ const artifact = manifest.platforms[platform];
794
+ if (!artifact)
795
+ return fail('artifact-missing', `release ${manifest.version} has no ${platform} artifact`);
796
+ const verdict = (0, releaseManifest_js_1.verifyArtifact)(platform, manifest.version, artifact, keys);
797
+ if (!('ok' in verdict) || !verdict.ok)
798
+ return fail('artifact-unverified', 'artifact signature rejected');
799
+ if (typeof artifact.size === 'number' && artifact.size > exports.MAX_ARTIFACT_BYTES)
800
+ return fail('too-large', 'artifact declares bytes above the ceiling');
801
+ const installed = getVerifiedInstalledFirstPartyHarness({
802
+ env,
803
+ keys,
804
+ platform: options.platform,
805
+ arch: options.arch,
806
+ });
807
+ const floor = retainedMinimum(env, keys);
808
+ if (floor && (0, releaseVersions_js_1.compareReleaseVersions)(manifest.version, floor) < 0)
809
+ return fail('minimum-version', `offered release is below the retained signed minimum ${floor}`);
810
+ if (installed.version &&
811
+ (0, releaseVersions_js_1.compareReleaseVersions)(manifest.version, installed.version) < 0 &&
812
+ !options.allowDowngrade) {
813
+ return fail('downgrade-refused', `offered ${manifest.version} is older than installed ${installed.version}; pin a version and use --allow-downgrade for recovery`);
814
+ }
815
+ if (installed.version === manifest.version) {
816
+ if (installed.provenance?.sha256 !== artifact.sha256.toLowerCase())
817
+ return fail('manifest-malformed', 'same release version offers a different signed artifact digest');
818
+ preserveMinimum(env, keys, manifest);
819
+ return {
820
+ ok: true,
821
+ changed: false,
822
+ version: installed.version,
823
+ binaryPath: installed.binaryPath,
824
+ platform: installed.provenance?.platform,
825
+ replaced: true,
826
+ };
827
+ }
828
+ progress(`Downloading ${firstPartyHarness_js_1.FIRST_PARTY_HARNESS.displayName} ${manifest.version} (${platform})…`);
829
+ let binary;
830
+ try {
831
+ const response = await (options.fetchImpl ?? globalThis.fetch)(artifact.url, {
832
+ signal: fetchSignal(options, ARTIFACT_FETCH_TIMEOUT_MS),
833
+ redirect: 'follow',
834
+ });
835
+ if (!response.ok)
836
+ return fail('download-failed', `HTTP ${response.status} for ${artifact.url}`);
837
+ const contentLength = Number(response.headers.get('content-length'));
838
+ if (Number.isFinite(contentLength) && contentLength > exports.MAX_ARTIFACT_BYTES)
839
+ return fail('too-large', 'response declares bytes above the ceiling');
840
+ const chunks = [];
841
+ let size = 0;
842
+ if (!response.body)
843
+ return fail('download-failed', 'artifact response has no body');
844
+ const reader = response.body.getReader();
845
+ try {
846
+ while (true) {
847
+ const part = await reader.read();
848
+ if (part.done)
849
+ break;
850
+ size += part.value.byteLength;
851
+ if (size > exports.MAX_ARTIFACT_BYTES) {
852
+ await reader.cancel();
853
+ return fail('too-large', 'download exceeds the ceiling');
854
+ }
855
+ chunks.push(part.value);
856
+ }
857
+ }
858
+ finally {
859
+ reader.releaseLock();
860
+ }
861
+ binary = Buffer.concat(chunks, size);
862
+ }
863
+ catch (error) {
864
+ return fail('download-failed', `download failed: ${describe(error)}`);
865
+ }
866
+ const provenance = {
867
+ version: manifest.version,
868
+ platform: platform,
869
+ sha256: artifact.sha256.toLowerCase(),
870
+ url: artifact.url,
871
+ sig: artifact.sig,
872
+ ...(artifact.sigs ? { sigs: artifact.sigs } : {}),
873
+ ...(manifest.keyId ? { keyId: manifest.keyId } : {}),
874
+ };
875
+ const activation = { ...options, binary, provenance, manifest };
876
+ const invalid = validateArtifact(activation);
877
+ return invalid ?? activateLocked(activation);
878
+ }
879
+ }
880
+ /** Rollback validates the retained immutable release and preserves its signature. */
881
+ function rollbackFirstPartyHarness(env = process.env, options = {}) {
882
+ const keys = options.keys ?? firstPartyHarnessReleaseKeys_js_1.FIRST_PARTY_HARNESS_RELEASE_PUBLIC_KEYS;
883
+ const p = paths(env);
884
+ const attemptedAt = Date.now();
885
+ let release;
886
+ try {
887
+ release = proper_lockfile_1.default.lockSync(prepareLock(env), { realpath: false, stale: LOCK_STALE_MS });
888
+ const result = performRollback();
889
+ finishState(env, 'rollback', attemptedAt, result, result.version, keys);
890
+ return result;
891
+ }
892
+ catch (error) {
893
+ const result = fail(error.code === 'ELOCKED' ? 'lock-busy' : 'install-failed', `rollback failed: ${describe(error)}`);
894
+ if (release) {
895
+ try {
896
+ finishState(env, 'rollback', attemptedAt, result, undefined, keys);
897
+ }
898
+ catch {
899
+ // Preserve the rollback failure if its diagnostic journal is also unwritable.
900
+ }
901
+ }
902
+ return result;
903
+ }
904
+ finally {
905
+ release?.();
906
+ }
907
+ function performRollback() {
908
+ if (!isManagedFirstPartyHarnessReleasePath(firstPartyHarnessBinaryPath(env), env))
909
+ return fail('install-failed', 'active engine is outside the managed release store; install an explicitly pinned signed release before rolling back');
910
+ const previous = readLink(p.previous);
911
+ const current = readLink(p.current);
912
+ if (!previous ||
913
+ !current ||
914
+ !previous.startsWith('releases/') ||
915
+ node_path_1.default.resolve(p.home, previous).startsWith(p.releases + node_path_1.default.sep) === false)
916
+ return fail('install-failed', 'no managed previous release to roll back to');
917
+ const restored = inspectBinary(node_path_1.default.join(p.home, previous, node_path_1.default.basename(firstPartyHarnessBinaryPath(env))), keys);
918
+ if (!restored.verified || !restored.version)
919
+ return fail('artifact-unverified', 'previous release provenance is missing or invalid; install an explicitly pinned signed release for recovery');
920
+ const floor = retainedMinimum(env, keys);
921
+ if (floor && (0, releaseVersions_js_1.compareReleaseVersions)(restored.version, floor) < 0)
922
+ return fail('minimum-version', `previous release is below the retained signed minimum ${floor}`);
923
+ // Preserve the outgoing target only after activation. A crash between these
924
+ // operations still leaves `current` on a complete verified release.
925
+ atomicLink(previous, p.current);
926
+ atomicLink(current, p.previous);
927
+ stableLinks(env);
928
+ return {
929
+ ok: true,
930
+ changed: true,
931
+ replaced: true,
932
+ binaryPath: firstPartyHarnessBinaryPath(env),
933
+ version: restored.version,
934
+ platform: restored.provenance?.platform,
935
+ };
936
+ }
937
+ }
938
+ function installedFirstPartyHarnessSize(env = process.env) {
939
+ try {
940
+ return (0, node_fs_1.statSync)(firstPartyHarnessBinaryPath(env)).size;
941
+ }
942
+ catch {
943
+ return null;
944
+ }
945
+ }