@skrr-ai/cli 0.1.41 → 0.1.43

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 (79) hide show
  1. package/dist/commands/balance/overage.js +4 -3
  2. package/dist/commands/balance/plan.js +10 -6
  3. package/dist/commands/balance/usage/events.js +3 -2
  4. package/dist/commands/balance/usage.js +2 -1
  5. package/dist/commands/browser/install.js +3 -2
  6. package/dist/commands/browser/uninstall.js +3 -2
  7. package/dist/commands/code/install.js +6 -6
  8. package/dist/commands/followups/remind.js +4 -0
  9. package/dist/commands/harnesses/install.d.ts +29 -0
  10. package/dist/commands/harnesses/install.js +153 -25
  11. package/dist/commands/harnesses/installers.d.ts +10 -0
  12. package/dist/commands/harnesses/installers.js +31 -0
  13. package/dist/commands/harnesses/list.js +3 -2
  14. package/dist/commands/skills/import-as-actions.js +6 -15
  15. package/dist/commands/tasks/attachments/set-role.d.ts +24 -0
  16. package/dist/commands/tasks/attachments/set-role.js +60 -0
  17. package/dist/commands/tasks/attachments/upload.d.ts +1 -0
  18. package/dist/commands/tasks/attachments/upload.js +18 -52
  19. package/dist/commands/tasks/comments/add.d.ts +1 -0
  20. package/dist/commands/tasks/comments/add.js +67 -2
  21. package/dist/commands/tasks/complete.d.ts +1 -0
  22. package/dist/commands/tasks/complete.js +69 -1
  23. package/dist/commands/tasks/result/submit.d.ts +3 -0
  24. package/dist/commands/tasks/result/submit.js +78 -5
  25. package/dist/commands/tasks/update.js +4 -2
  26. package/dist/commands/tasks/updates/add.d.ts +1 -0
  27. package/dist/commands/tasks/updates/add.js +63 -0
  28. package/dist/lib/api-fetch.js +7 -2
  29. package/dist/lib/cli-installers.d.ts +63 -4
  30. package/dist/lib/cli-installers.js +98 -8
  31. package/dist/lib/dedicated-machines.js +4 -2
  32. package/dist/lib/file-mime.js +1 -1
  33. package/dist/lib/first-party-harness-agent.js +6 -5
  34. package/dist/lib/first-party-harness-doctor.js +15 -32
  35. package/dist/lib/first-party-harness-managed.d.ts +9 -4
  36. package/dist/lib/first-party-harness-managed.js +13 -10
  37. package/dist/lib/first-party-harness.d.ts +25 -21
  38. package/dist/lib/first-party-harness.js +40 -27
  39. package/dist/lib/followups.d.ts +2 -0
  40. package/dist/lib/harness-provider-input.d.ts +18 -12
  41. package/dist/lib/harness-provider-input.js +18 -12
  42. package/dist/lib/harness-tiers.d.ts +4 -3
  43. package/dist/lib/harness-tiers.js +4 -3
  44. package/dist/lib/html-text.js +25 -0
  45. package/dist/lib/local-skills.d.ts +27 -0
  46. package/dist/lib/local-skills.js +38 -0
  47. package/dist/lib/session-task-endpoints.d.ts +1 -1
  48. package/dist/lib/session-task-endpoints.js +3 -0
  49. package/dist/lib/task-asset-upload.d.ts +84 -0
  50. package/dist/lib/task-asset-upload.js +374 -0
  51. package/dist/lib/task-closure.d.ts +28 -0
  52. package/dist/lib/task-closure.js +37 -0
  53. package/dist/lib/tasks.d.ts +8 -0
  54. package/dist/lib/tasks.js +16 -0
  55. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +113 -39
  56. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +148 -71
  57. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.d.ts +21 -33
  58. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.js +24 -50
  59. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.d.ts +19 -7
  60. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.js +26 -10
  61. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.d.ts +9 -5
  62. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.js +9 -5
  63. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  64. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +1 -13
  65. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +113 -39
  66. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +146 -70
  67. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.d.ts +21 -33
  68. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.js +24 -49
  69. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.d.ts +19 -7
  70. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.js +27 -11
  71. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.d.ts +9 -5
  72. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.js +9 -5
  73. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  74. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -4
  75. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  76. package/dist/node_modules/@skrr-ai/data-provider/index.js +4025 -3938
  77. package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
  78. package/oclif.manifest.json +12853 -12721
  79. package/package.json +2 -2
@@ -24,25 +24,40 @@
24
24
  * phase, once every peer accepts the new one;
25
25
  * 4. regenerate `first-party-harness.json`
26
26
  * (`node --import tsx scripts/emit-first-party-harness-json.mjs`);
27
- * 5. ship the data migration, which is generic over `aliases`.
27
+ * 5. ship the data migration, which is generic over `aliases` and
28
+ * `formerSpellings`;
29
+ * 6. at that rename's contract, move the spelling from `aliases` to the front
30
+ * of `formerSpellings`.
28
31
  *
29
32
  * No identifier, file, env var name or workflow input moves.
30
33
  *
31
- * ── Three spellings, kept apart on purpose ────────────────────────────────
34
+ * ── Four fields, kept apart on purpose ────────────────────────────────────
32
35
  *
33
- * `provider` CANONICAL. What the platform stores, returns and renders.
34
- * `aliases` ACCEPTED on every ingress until contract, and normalised to
35
- * canonical at the edge. Nothing new is ever written with one.
36
- * `wireProvider` What the server and a daemon say to EACH OTHER — the
37
- * capability a daemon advertises, `daemon:<p>:request|response`,
38
- * the `provider` of a session RPC. It moves after the flip and
39
- * BEFORE contract, because a daemon on a user's machine may be
40
- * any version and the server cannot tell which one sent a
41
- * request: only a released daemon that already advertises the
42
- * canonical capability lets the old one age out of the fleet.
36
+ * `provider` CANONICAL. What the platform stores, returns and renders.
37
+ * `aliases` ACCEPTED on every ingress during a rename's window, and
38
+ * normalised to canonical at the edge. Nothing new is ever
39
+ * written with one. Empty between renames.
40
+ * `formerSpellings` RETIRED. Accepted on NO ingress: no HTTP body, enum, Mongo
41
+ * filter, capability, wire event, CLI flag or trust table
42
+ * admits one. Recognised only where an ARTIFACT that predates
43
+ * the rename is read — a binary already installed, a checkout
44
+ * directory already on disk, a release already published
45
+ * under its prefix, a row or config key already written.
46
+ * Those exist whatever the platform accepts, and dropping a
47
+ * spelling from this list does not make them go away. Read it
48
+ * through `firstPartyHarnessArtifactSpellings`, never as an
49
+ * ingress check.
50
+ * `wireProvider` What the server and a daemon say to EACH OTHER — the
51
+ * capability a daemon advertises,
52
+ * `daemon:<p>:request|response`, the `provider` of a session
53
+ * RPC. It moves after the flip and BEFORE contract, because a
54
+ * daemon on a user's machine may be any version and the
55
+ * server cannot tell which one sent a request: only a
56
+ * released daemon that already advertises the canonical
57
+ * capability lets the old one age out of the fleet.
43
58
  *
44
- * The old code treated those three as one string, which is why no single
45
- * change could rename it safely. See
59
+ * The old code treated these as one string, which is why no single change could
60
+ * rename it safely. See
46
61
  * `docs/architecture/skrr-code-identifier-rename-2026-09-13.md`.
47
62
  *
48
63
  * ── Constraints on this file ──────────────────────────────────────────────
@@ -56,38 +71,43 @@
56
71
  const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
57
72
  function defineIdentity(identity) {
58
73
  const spellings = [identity.provider, ...identity.aliases];
59
- for (const spelling of spellings) {
74
+ const every = [...spellings, ...identity.formerSpellings];
75
+ for (const spelling of every) {
60
76
  if (!SLUG.test(spelling)) {
61
77
  throw new Error(`first-party harness spelling ${JSON.stringify(spelling)} is not a slug; it becomes a ` +
62
78
  `directory name, a capability prefix and a wire event segment`);
63
79
  }
64
80
  }
65
- if (new Set(spellings).size !== spellings.length) {
66
- throw new Error('first-party harness provider and aliases must be distinct');
81
+ if (new Set(every).size !== every.length) {
82
+ throw new Error('first-party harness provider, aliases and formerSpellings must be distinct — a spelling ' +
83
+ 'is accepted or retired, never both');
67
84
  }
68
85
  if (!spellings.includes(identity.wireProvider)) {
69
86
  throw new Error(`first-party harness wireProvider ${JSON.stringify(identity.wireProvider)} must be the ` +
70
87
  'provider or one of its aliases — a peer can only accept a spelling it knows');
71
88
  }
72
- return Object.freeze({ ...identity, aliases: Object.freeze([...identity.aliases]) });
89
+ return Object.freeze({
90
+ ...identity,
91
+ aliases: Object.freeze([...identity.aliases]),
92
+ formerSpellings: Object.freeze([...identity.formerSpellings]),
93
+ });
73
94
  }
74
95
  /** The canonical slug. Written once, and read by every field that names the current spelling. */
75
96
  const PROVIDER = 'skrr-code';
76
97
  /**
77
98
  * THE identity — the only literal product name in the codebase.
78
99
  *
79
- * Phase: WIRE MOVED. `skrr-code` is canonical — stored, returned, rendered — and
80
- * it is now also what the server and a daemon say to each other. `sky-code` is
81
- * still accepted on every ingress, so a daemon released before this change
82
- * (which advertises and answers on `sky-code`) keeps working against a server
83
- * that speaks `skrr-code`, and the reverse. Contract (OSK-8663) empties
84
- * `aliases` once no connected daemon still advertises the old capability.
100
+ * Phase: CONTRACTED (OSK-8663). `skrr-code` is the only spelling any ingress
101
+ * accepts and the only one on the wire. `sky-code` is retired: nothing admits
102
+ * it, and it is still recognised where a binary, a checkout, a published
103
+ * release or a stored value written before the rename is looked for.
85
104
  */
86
105
  const IDENTITY = {
87
106
  displayName: 'skrr Code',
88
107
  command: 'skrr code',
89
108
  provider: PROVIDER,
90
- aliases: ['sky-code'],
109
+ aliases: [],
110
+ formerSpellings: ['sky-code'],
91
111
  wireProvider: PROVIDER,
92
112
  };
93
113
  export const FIRST_PARTY_HARNESS = defineIdentity(IDENTITY);
@@ -98,6 +118,11 @@ const SPELLINGS = Object.freeze([
98
118
  ...FIRST_PARTY_HARNESS.aliases,
99
119
  ]);
100
120
  const SPELLING_SET = new Set(SPELLINGS);
121
+ const ARTIFACT_SPELLINGS = Object.freeze([
122
+ ...SPELLINGS,
123
+ ...FIRST_PARTY_HARNESS.formerSpellings,
124
+ ]);
125
+ const ARTIFACT_SPELLING_SET = new Set(ARTIFACT_SPELLINGS);
101
126
  const SESSION_SUFFIX = '_session';
102
127
  const ENVIRONMENT_PREFIX = 'local-';
103
128
  function asSlug(value) {
@@ -106,10 +131,30 @@ function asSlug(value) {
106
131
  const slug = value.trim().toLowerCase();
107
132
  return slug ? slug : null;
108
133
  }
109
- /** Every spelling, canonical first. The order is the resolution order. */
134
+ /**
135
+ * Every ACCEPTED spelling, canonical first — what an ingress admits. The order
136
+ * is the resolution order.
137
+ */
110
138
  export function firstPartyHarnessSpellings() {
111
139
  return SPELLINGS;
112
140
  }
141
+ /**
142
+ * Every spelling an ARTIFACT that already exists may carry, canonical first:
143
+ * the accepted spellings, then every former one.
144
+ *
145
+ * For code that LOOKS FOR what an earlier release left behind — an installed
146
+ * binary, a checkout directory, an immutable published release, a stored value
147
+ * or config key — and for the tooling that must keep recognising those
148
+ * (ignore lists, the identity ratchet). A machine that never ran a post-rename
149
+ * client still has its engine under the old name, and it must still be found.
150
+ *
151
+ * Never an ingress check: a former spelling is not accepted anywhere, and a
152
+ * reader that admitted one here would silently reopen what contract closed. Use
153
+ * `firstPartyHarnessSpellings` / `isFirstPartyHarnessProvider` for that.
154
+ */
155
+ export function firstPartyHarnessArtifactSpellings() {
156
+ return ARTIFACT_SPELLINGS;
157
+ }
113
158
  /** True for the canonical provider or any alias (case- and whitespace-insensitive). */
114
159
  export function isFirstPartyHarnessProvider(value) {
115
160
  const slug = asSlug(value);
@@ -123,6 +168,27 @@ export function isFirstPartyHarnessProvider(value) {
123
168
  export function canonicalHarnessProvider(value) {
124
169
  return isFirstPartyHarnessProvider(value) ? FIRST_PARTY_HARNESS.provider : value;
125
170
  }
171
+ /**
172
+ * The canonical provider for a value read back from an ARTIFACT that may predate
173
+ * a rename — any artifact spelling (canonical, alias or FORMER) becomes the
174
+ * provider; every other value is returned unchanged.
175
+ *
176
+ * For values nobody migrates and nobody can refuse: a harness row's stored
177
+ * display NAME (the operator migration rewrites `provider`, never `name`), a
178
+ * daemon's config file, a harness session binding on a user's machine. Read raw
179
+ * after contract, each would name an unknown provider — a label that renders the
180
+ * retired name again, a local session silently running another backend, a live
181
+ * resume context reset as new — with no error anywhere.
182
+ *
183
+ * NEVER for anything a peer, a client or an operator SENDS, and never for a
184
+ * trust, dispatch or admission decision: a former spelling is accepted on no
185
+ * ingress, and canonicalising one here would reopen what contract closed. Use
186
+ * `canonicalHarnessProvider` for those.
187
+ */
188
+ export function canonicalArtifactHarnessProvider(value) {
189
+ const slug = asSlug(value);
190
+ return slug !== null && ARTIFACT_SPELLING_SET.has(slug) ? FIRST_PARTY_HARNESS.provider : value;
191
+ }
126
192
  /**
127
193
  * Every spelling a stored row for this provider may carry, canonical first —
128
194
  * for a Mongo `$in` filter while rows written before a flip still exist. Any
@@ -202,9 +268,18 @@ export function canonicalExecutionEnvironment(value) {
202
268
  export function firstPartyHarnessBinaryName(provider = FIRST_PARTY_HARNESS.provider, platform) {
203
269
  return platform === 'win32' ? `${provider}.exe` : provider;
204
270
  }
205
- /** Every binary file name, canonical first — the lookup order. */
271
+ /**
272
+ * Every binary file name an install may carry, canonical first — the lookup
273
+ * order, and the names an install links to the canonical binary.
274
+ *
275
+ * Built from the ARTIFACT spellings, former ones included: binaries are
276
+ * artifacts. An engine installed before a rename is still on disk under its old
277
+ * name, and a platform CLI or daemon built before it still looks for that name,
278
+ * so resolvers try it and installers leave a link at it. Neither makes the
279
+ * spelling acceptable on any ingress.
280
+ */
206
281
  export function firstPartyHarnessBinaryNames(platform) {
207
- return SPELLINGS.map((spelling) => firstPartyHarnessBinaryName(spelling, platform));
282
+ return ARTIFACT_SPELLINGS.map((spelling) => firstPartyHarnessBinaryName(spelling, platform));
208
283
  }
209
284
  /**
210
285
  * The engine home directory name under the skrr config root.
@@ -233,7 +308,13 @@ export const FIRST_PARTY_HARNESS_FEED_ORIGIN = 'https://updates.oversky.ai';
233
308
  export function firstPartyHarnessFeedPrefix(provider = FIRST_PARTY_HARNESS.provider) {
234
309
  return `${provider}/`;
235
310
  }
236
- /** Every feed prefix, canonical first. Publishing writes all of them. */
311
+ /**
312
+ * Every feed prefix a publish WRITES, canonical first: the accepted spellings.
313
+ *
314
+ * A former spelling's prefix still holds every release published under it, and
315
+ * those stay served — their artifact URLs are inside signed manifests — but
316
+ * nothing new is written there, so its channel pointers stop moving at contract.
317
+ */
237
318
  export function firstPartyHarnessFeedPrefixes() {
238
319
  return SPELLINGS.map((spelling) => firstPartyHarnessFeedPrefix(spelling));
239
320
  }
@@ -245,66 +326,61 @@ export function firstPartyHarnessStagingPrefix(provider = FIRST_PARTY_HARNESS.pr
245
326
  export function firstPartyHarnessDefaultFeedBase(provider = FIRST_PARTY_HARNESS.provider) {
246
327
  return `${FIRST_PARTY_HARNESS_FEED_ORIGIN}/${provider}`;
247
328
  }
248
- function envName(name, legacy) {
249
- return Object.freeze({ name, legacy: Object.freeze([...legacy]) });
329
+ function envName(name) {
330
+ return Object.freeze({ name });
250
331
  }
251
332
  /**
252
- * Every environment variable the platform reads for the first-party harness,
253
- * by purpose. The neutral name wins; a legacy name is read only when the neutral
254
- * one is unset or blank, because a variable set in an ECS task definition, a
255
- * user's shell or a LaunchAgent does not change when the code does.
333
+ * Every environment variable the PLATFORM reads for the first-party harness, by
334
+ * purpose, under one brand-neutral name each — so a product rename never renames
335
+ * one.
336
+ *
337
+ * The product-named variables these replaced were read second until contract
338
+ * (OSK-8663) and are not read at all now. The ENGINE's own variables are a
339
+ * different namespace (`FIRST_PARTY_HARNESS_ENGINE.env`): the fork reads them,
340
+ * so a platform reader that must agree with the engine consults the engine's
341
+ * name explicitly — the engine home is the one case (`home` below).
256
342
  */
257
343
  export const FIRST_PARTY_HARNESS_ENV = Object.freeze({
258
344
  /** Absolute path to an engine binary, overriding resolution. */
259
- path: envName('SKRR_FIRST_PARTY_HARNESS_PATH', ['OVERSKY_SKY_CODE_PATH']),
345
+ path: envName('SKRR_FIRST_PARTY_HARNESS_PATH'),
260
346
  /** Forces the CLI's managed-inference mode. */
261
- managed: envName('SKRR_FIRST_PARTY_HARNESS_MANAGED', ['OVERSKY_SKY_CODE_MANAGED']),
347
+ managed: envName('SKRR_FIRST_PARTY_HARNESS_MANAGED'),
262
348
  /** Update channel. */
263
- channel: envName('SKRR_FIRST_PARTY_HARNESS_CHANNEL', ['OVERSKY_SKY_CODE_CHANNEL']),
349
+ channel: envName('SKRR_FIRST_PARTY_HARNESS_CHANNEL'),
264
350
  /** Update-feed base URL override. */
265
- updateFeedBase: envName('SKRR_FIRST_PARTY_HARNESS_UPDATE_FEED_BASE', [
266
- 'OVERSKY_SKY_CODE_UPDATE_FEED_BASE',
267
- ]),
351
+ updateFeedBase: envName('SKRR_FIRST_PARTY_HARNESS_UPDATE_FEED_BASE'),
268
352
  /** Path to a file of development release keys. */
269
- devKeys: envName('SKRR_FIRST_PARTY_HARNESS_DEV_KEYS', ['OVERSKY_SKY_CODE_DEV_KEYS']),
353
+ devKeys: envName('SKRR_FIRST_PARTY_HARNESS_DEV_KEYS'),
270
354
  /** Engine version the desktop build bundles. */
271
- bundleVersion: envName('SKRR_FIRST_PARTY_HARNESS_BUNDLE_VERSION', ['OVERSKY_BUNDLE_SKY_CODE']),
272
- /** Engine home override (the engine reads its own name too — see ENGINE env below). */
273
- home: envName('SKRR_FIRST_PARTY_HARNESS_HOME', ['SKY_CODE_HOME']),
355
+ bundleVersion: envName('SKRR_FIRST_PARTY_HARNESS_BUNDLE_VERSION'),
356
+ /**
357
+ * Engine home override. The engine reads only its own variable
358
+ * (`FIRST_PARTY_HARNESS_ENGINE.env.home`), so a reader that resolves the home
359
+ * the engine will use reads that one second, and a launcher hands the engine
360
+ * the resolved path under it.
361
+ */
362
+ home: envName('SKRR_FIRST_PARTY_HARNESS_HOME'),
274
363
  /** Engine version baked into the cloud-coding image. */
275
- cloudCodingVersion: envName('CLOUD_CODING_FIRST_PARTY_HARNESS_VERSION', [
276
- 'CLOUD_CODING_SKY_CODE_VERSION',
277
- ]),
364
+ cloudCodingVersion: envName('CLOUD_CODING_FIRST_PARTY_HARNESS_VERSION'),
278
365
  /** Engine version baked into the Dedicated Runtime AMI. */
279
- dedicatedRuntimeVersion: envName('SKRR_DEDICATED_RUNTIME_FIRST_PARTY_HARNESS_VERSION', [
280
- 'SKRR_DEDICATED_RUNTIME_SKY_CODE_VERSION',
281
- ]),
366
+ dedicatedRuntimeVersion: envName('SKRR_DEDICATED_RUNTIME_FIRST_PARTY_HARNESS_VERSION'),
282
367
  /** Engine version the desktop release workflow bundles. */
283
- desktopBundledVersion: envName('DESKTOP_BUNDLED_FIRST_PARTY_HARNESS_VERSION', [
284
- 'DESKTOP_BUNDLED_SKY_CODE_VERSION',
285
- ]),
368
+ desktopBundledVersion: envName('DESKTOP_BUNDLED_FIRST_PARTY_HARNESS_VERSION'),
286
369
  /** Update-feed bucket used by publish and promote. */
287
- updatesBucket: envName('FIRST_PARTY_HARNESS_UPDATES_BUCKET', ['SKY_CODE_UPDATES_BUCKET']),
370
+ updatesBucket: envName('FIRST_PARTY_HARNESS_UPDATES_BUCKET'),
288
371
  /** Release signing key (CI secret). */
289
- signingKey: envName('FIRST_PARTY_HARNESS_SIGNING_KEY', ['SKY_CODE_SIGNING_KEY']),
372
+ signingKey: envName('FIRST_PARTY_HARNESS_SIGNING_KEY'),
290
373
  /** Release signing key id (CI secret). */
291
- signingKeyId: envName('FIRST_PARTY_HARNESS_SIGNING_KEY_ID', ['SKY_CODE_SIGNING_KEY_ID']),
374
+ signingKeyId: envName('FIRST_PARTY_HARNESS_SIGNING_KEY_ID'),
292
375
  });
293
- /** Every name for one purpose, neutral first — for spawn-env forwarding and sanitisers. */
294
- export function firstPartyHarnessEnvNames(key) {
295
- const entry = FIRST_PARTY_HARNESS_ENV[key];
296
- return [entry.name, ...entry.legacy];
297
- }
298
- /** Read one purpose's variable: neutral name first, legacy names after. */
376
+ /** Read one purpose's variable. A blank value counts as unset. */
299
377
  export function readFirstPartyHarnessEnv(env, key) {
300
- const entry = FIRST_PARTY_HARNESS_ENV[key];
301
- for (const name of [entry.name, ...entry.legacy]) {
302
- const raw = env[name];
303
- if (typeof raw === 'string' && raw.trim() !== '') {
304
- return { value: raw.trim(), name, legacy: name !== entry.name };
305
- }
378
+ const { name } = FIRST_PARTY_HARNESS_ENV[key];
379
+ const raw = env[name];
380
+ if (typeof raw === 'string' && raw.trim() !== '') {
381
+ return { value: raw.trim(), name };
306
382
  }
307
- return { value: undefined, name: undefined, legacy: false };
383
+ return { value: undefined, name: undefined };
308
384
  }
309
385
  /* ── The ENGINE's own namespace (defined inside the fork) ───────────────── */
310
386
  /**
@@ -108,40 +108,28 @@ export interface FirstPartyHarnessFeedCandidate {
108
108
  * Every place a manifest may be published, in the order an installer should
109
109
  * try them: the canonical spelling first, then each alias.
110
110
  *
111
- * Why a list: publishing writes every spelling, but an installer can ship before
112
- * the first release that does, and a machine can point at a feed published by
113
- * an older pipeline. An installer that asked only for the canonical path would
114
- * then find NOTHING and report "no release" on a feed that has one. Trying the
115
- * aliases on not-found closes that window in both directions without anyone
116
- * having to deploy in a particular order.
111
+ * Why a list: publishing writes every accepted spelling, but an installer can
112
+ * ship before the first release that does, and a machine can point at a feed
113
+ * published by an older pipeline. An installer that asked only for the canonical
114
+ * path would then find NOTHING and report "no release" on a feed that has one.
115
+ * Trying the aliases on not-found closes that window in both directions without
116
+ * anyone having to deploy in a particular order.
117
+ *
118
+ * WHICH spellings depends on what is being read, and the difference is the
119
+ * point:
120
+ *
121
+ * - a CHANNEL pointer (`pinnedRelease` false) is current state. Only the
122
+ * accepted spellings are tried: a former spelling's pointer stopped moving
123
+ * when publishing stopped writing it, so falling back to one would install
124
+ * whatever that channel named on the day of contract — a silent downgrade.
125
+ * - a PINNED release (`pinnedRelease` true) is an immutable artifact. Former
126
+ * spellings are tried too: a release published before the canonical prefix
127
+ * existed lives only under the old one, and its signed bytes — artifact URLs
128
+ * included — are the same wherever they are found.
117
129
  *
118
130
  * An operator override keeps its base and still tries each manifest filename,
119
131
  * since a dev feed staged by an older script names the file the old way.
120
132
  */
121
- export declare function firstPartyHarnessFeedCandidates(env?: NodeJS.ProcessEnv): readonly FirstPartyHarnessFeedCandidate[];
122
- /** @deprecated Use `FIRST_PARTY_HARNESS_CHANNELS`. */
123
- export declare const SKY_CODE_CHANNELS: readonly ["internal", "canary", "beta", "stable"];
124
- /** @deprecated Use `FirstPartyHarnessChannel`. */
125
- export type SkyCodeChannel = FirstPartyHarnessChannel;
126
- /** @deprecated Use `DEFAULT_FIRST_PARTY_HARNESS_CHANNEL`. */
127
- export declare const DEFAULT_SKY_CODE_CHANNEL: "stable";
128
- /** @deprecated Read the channel with `resolveFirstPartyHarnessChannel`; the variable has a neutral name now. */
129
- export declare const SKY_CODE_CHANNEL_ENV: string;
130
- /** @deprecated Use `firstPartyHarnessManifestFile()`. Names the legacy file. */
131
- export declare const SKY_CODE_MANIFEST_FILE: string;
132
- /** @deprecated Use `isFirstPartyHarnessChannel`. */
133
- export declare const isSkyCodeChannel: typeof isFirstPartyHarnessChannel;
134
- /** @deprecated Use `resolveFirstPartyHarnessChannel`. */
135
- export declare const resolveSkyCodeChannel: typeof resolveFirstPartyHarnessChannel;
136
- /** @deprecated Use `firstPartyHarnessChannelPrefix`. */
137
- export declare const skyCodeChannelPrefix: typeof firstPartyHarnessChannelPrefix;
138
- /** @deprecated Use `firstPartyHarnessDefaultFeedBase()`. Names the legacy feed. */
139
- export declare const DEFAULT_SKY_CODE_FEED_BASE: string;
140
- /** @deprecated Read the feed with `firstPartyHarnessFeedBase`; the variable has a neutral name now. */
141
- export declare const SKY_CODE_FEED_BASE_ENV: string;
142
- /**
143
- * @deprecated Use `firstPartyHarnessFeedCandidates`. Keeps the LEGACY feed as its
144
- * default, because that is the only feed a caller compiled against this name knows
145
- * how to read.
146
- */
147
- export declare function skyCodeFeedBase(env?: NodeJS.ProcessEnv): string;
133
+ export declare function firstPartyHarnessFeedCandidates(env?: NodeJS.ProcessEnv, options?: {
134
+ pinnedRelease?: boolean;
135
+ }): readonly FirstPartyHarnessFeedCandidate[];
@@ -26,7 +26,7 @@
26
26
  * Every product-named value comes from `firstPartyHarness.ts`; nothing in this
27
27
  * file spells the product.
28
28
  */
29
- import { FIRST_PARTY_HARNESS, FIRST_PARTY_HARNESS_ENV, firstPartyHarnessDefaultFeedBase, firstPartyHarnessManifestFile, firstPartyHarnessSpellings, readFirstPartyHarnessEnv, } from './firstPartyHarness.js';
29
+ import { firstPartyHarnessArtifactSpellings, firstPartyHarnessDefaultFeedBase, firstPartyHarnessManifestFile, firstPartyHarnessSpellings, readFirstPartyHarnessEnv, } from './firstPartyHarness.js';
30
30
  /** Channels in promotion order. Index order IS the promotion order. */
31
31
  export const FIRST_PARTY_HARNESS_CHANNELS = ['internal', 'canary', 'beta', 'stable'];
32
32
  /**
@@ -116,61 +116,36 @@ export function firstPartyHarnessFeedBase(env = process.env) {
116
116
  * Every place a manifest may be published, in the order an installer should
117
117
  * try them: the canonical spelling first, then each alias.
118
118
  *
119
- * Why a list: publishing writes every spelling, but an installer can ship before
120
- * the first release that does, and a machine can point at a feed published by
121
- * an older pipeline. An installer that asked only for the canonical path would
122
- * then find NOTHING and report "no release" on a feed that has one. Trying the
123
- * aliases on not-found closes that window in both directions without anyone
124
- * having to deploy in a particular order.
119
+ * Why a list: publishing writes every accepted spelling, but an installer can
120
+ * ship before the first release that does, and a machine can point at a feed
121
+ * published by an older pipeline. An installer that asked only for the canonical
122
+ * path would then find NOTHING and report "no release" on a feed that has one.
123
+ * Trying the aliases on not-found closes that window in both directions without
124
+ * anyone having to deploy in a particular order.
125
+ *
126
+ * WHICH spellings depends on what is being read, and the difference is the
127
+ * point:
128
+ *
129
+ * - a CHANNEL pointer (`pinnedRelease` false) is current state. Only the
130
+ * accepted spellings are tried: a former spelling's pointer stopped moving
131
+ * when publishing stopped writing it, so falling back to one would install
132
+ * whatever that channel named on the day of contract — a silent downgrade.
133
+ * - a PINNED release (`pinnedRelease` true) is an immutable artifact. Former
134
+ * spellings are tried too: a release published before the canonical prefix
135
+ * existed lives only under the old one, and its signed bytes — artifact URLs
136
+ * included — are the same wherever they are found.
125
137
  *
126
138
  * An operator override keeps its base and still tries each manifest filename,
127
139
  * since a dev feed staged by an older script names the file the old way.
128
140
  */
129
- export function firstPartyHarnessFeedCandidates(env = process.env) {
141
+ export function firstPartyHarnessFeedCandidates(env = process.env, options = {}) {
130
142
  const override = readFirstPartyHarnessEnv(env, 'updateFeedBase').value?.replace(/\/+$/, '');
131
- return firstPartyHarnessSpellings().map((provider) => ({
143
+ const spellings = options.pinnedRelease
144
+ ? firstPartyHarnessArtifactSpellings()
145
+ : firstPartyHarnessSpellings();
146
+ return spellings.map((provider) => ({
132
147
  base: override || firstPartyHarnessDefaultFeedBase(provider),
133
148
  manifestFilename: firstPartyHarnessManifestFile(provider),
134
149
  provider,
135
150
  }));
136
151
  }
137
- /* ── Deprecated names ───────────────────────────────────────────────────────
138
- *
139
- * `@skrr-ai/auth-core` is PUBLISHED, and the released CLI resolves it with a
140
- * caret range. An installed CLI that picks up this version must still find the
141
- * names it was compiled against, or it crashes at import. These aliases leave in
142
- * the contract phase (OSK-8663), with a minor-version bump — never a patch.
143
- *
144
- * The ones that name a feed name the LEGACY one, the spelling a caller compiled
145
- * against them was built for — the retired alias, not `wireProvider`, which has
146
- * moved to canonical.
147
- */
148
- /** The spelling the deprecated names were written for. */
149
- const LEGACY_SPELLING = FIRST_PARTY_HARNESS.aliases[0] ?? FIRST_PARTY_HARNESS.provider;
150
- /** @deprecated Use `FIRST_PARTY_HARNESS_CHANNELS`. */
151
- export const SKY_CODE_CHANNELS = FIRST_PARTY_HARNESS_CHANNELS;
152
- /** @deprecated Use `DEFAULT_FIRST_PARTY_HARNESS_CHANNEL`. */
153
- export const DEFAULT_SKY_CODE_CHANNEL = DEFAULT_FIRST_PARTY_HARNESS_CHANNEL;
154
- /** @deprecated Read the channel with `resolveFirstPartyHarnessChannel`; the variable has a neutral name now. */
155
- export const SKY_CODE_CHANNEL_ENV = FIRST_PARTY_HARNESS_ENV.channel.legacy[0];
156
- /** @deprecated Use `firstPartyHarnessManifestFile()`. Names the legacy file. */
157
- export const SKY_CODE_MANIFEST_FILE = firstPartyHarnessManifestFile(LEGACY_SPELLING);
158
- /** @deprecated Use `isFirstPartyHarnessChannel`. */
159
- export const isSkyCodeChannel = isFirstPartyHarnessChannel;
160
- /** @deprecated Use `resolveFirstPartyHarnessChannel`. */
161
- export const resolveSkyCodeChannel = resolveFirstPartyHarnessChannel;
162
- /** @deprecated Use `firstPartyHarnessChannelPrefix`. */
163
- export const skyCodeChannelPrefix = firstPartyHarnessChannelPrefix;
164
- /** @deprecated Use `firstPartyHarnessDefaultFeedBase()`. Names the legacy feed. */
165
- export const DEFAULT_SKY_CODE_FEED_BASE = firstPartyHarnessDefaultFeedBase(LEGACY_SPELLING);
166
- /** @deprecated Read the feed with `firstPartyHarnessFeedBase`; the variable has a neutral name now. */
167
- export const SKY_CODE_FEED_BASE_ENV = FIRST_PARTY_HARNESS_ENV.updateFeedBase.legacy[0];
168
- /**
169
- * @deprecated Use `firstPartyHarnessFeedCandidates`. Keeps the LEGACY feed as its
170
- * default, because that is the only feed a caller compiled against this name knows
171
- * how to read.
172
- */
173
- export function skyCodeFeedBase(env = process.env) {
174
- const override = readFirstPartyHarnessEnv(env, 'updateFeedBase').value;
175
- return (override || firstPartyHarnessDefaultFeedBase(LEGACY_SPELLING)).replace(/\/+$/, '');
176
- }
@@ -20,9 +20,15 @@
20
20
  * leave the legacy name as a relative symlink. (No legacy app name exists
21
21
  * yet; this step is what an engine-namespace rename will use.)
22
22
  * 2. The BINARY follows the provider. Inside `<home>/bin`, if the canonical
23
- * binary does not exist and an alias binary is a real file, rename it and
24
- * each companion (rollback copy, provenance sidecar), leaving a relative
25
- * symlink at every old name.
23
+ * binary does not exist and a binary under an older spelling is a real
24
+ * file, rename it and each companion (rollback copy, provenance sidecar),
25
+ * leaving a relative symlink at every old name.
26
+ *
27
+ * "Older spelling" means every ARTIFACT spelling that is not canonical — the
28
+ * aliases AND the former spellings (`firstPartyHarnessArtifactSpellings`). An
29
+ * installed binary is an artifact: a machine that never ran a post-rename client
30
+ * still has its engine under the old name after that name stops being accepted
31
+ * anywhere, and it must still be moved rather than reinstalled beside.
26
32
  *
27
33
  * The two follow different names on purpose: the home is shared with the
28
34
  * engine, which reads its config and managed `AGENTS.md` from it, so it can only
@@ -73,7 +79,9 @@ export interface FirstPartyHarnessHomeFs {
73
79
  * Move a legacy engine home and binary onto the canonical names. See the file
74
80
  * header for the exact contract. Returns what it did; never throws.
75
81
  *
76
- * Inside `<home>/bin` it resolves the three layouts a rename can leave:
82
+ * Inside `<home>/bin` it resolves the three layouts a rename can leave. An
83
+ * "alias" below is any non-canonical artifact spelling — an accepted alias or a
84
+ * former spelling alike, since the file on disk does not know which it is now:
77
85
  *
78
86
  * - canonical ABSENT, an alias is a real file — the ordinary legacy install.
79
87
  * The alias moves to canonical and a link is left at its name.
@@ -96,13 +104,17 @@ export declare function migrateFirstPartyHarnessHome(options?: {
96
104
  fs?: FirstPartyHarnessHomeFs;
97
105
  }): FirstPartyHarnessHomeMigration;
98
106
  /**
99
- * Leave a relative symlink at every alias name — binary and provenance sidecar —
107
+ * Leave a relative symlink at every older name — binary and provenance sidecar —
100
108
  * that does not exist yet, pointing at the canonical file.
101
109
  *
102
110
  * Call after installing the canonical binary. A platform CLI or daemon installed
103
111
  * before a rename resolves the legacy name; the link keeps it on the current
104
- * engine. It never replaces anything that exists, whether a real file (an older
105
- * engine someone may still run) or a link someone else placed. Never throws.
112
+ * engine. "Older" includes FORMER spellings, not only aliases: an installed CLI
113
+ * cannot be telemetry-gated the way a connected daemon can, and it does not stop
114
+ * existing when its spelling stops being accepted, so the link outlives the
115
+ * alias window (`firstPartyHarnessBinaryNames`). It never replaces anything that
116
+ * exists, whether a real file (an older engine someone may still run) or a link
117
+ * someone else placed. Never throws.
106
118
  */
107
119
  export declare function linkFirstPartyHarnessAliases(options?: {
108
120
  env?: NodeJS.ProcessEnv;
@@ -20,9 +20,15 @@
20
20
  * leave the legacy name as a relative symlink. (No legacy app name exists
21
21
  * yet; this step is what an engine-namespace rename will use.)
22
22
  * 2. The BINARY follows the provider. Inside `<home>/bin`, if the canonical
23
- * binary does not exist and an alias binary is a real file, rename it and
24
- * each companion (rollback copy, provenance sidecar), leaving a relative
25
- * symlink at every old name.
23
+ * binary does not exist and a binary under an older spelling is a real
24
+ * file, rename it and each companion (rollback copy, provenance sidecar),
25
+ * leaving a relative symlink at every old name.
26
+ *
27
+ * "Older spelling" means every ARTIFACT spelling that is not canonical — the
28
+ * aliases AND the former spellings (`firstPartyHarnessArtifactSpellings`). An
29
+ * installed binary is an artifact: a machine that never ran a post-rename client
30
+ * still has its engine under the old name after that name stops being accepted
31
+ * anywhere, and it must still be moved rather than reinstalled beside.
26
32
  *
27
33
  * The two follow different names on purpose: the home is shared with the
28
34
  * engine, which reads its config and managed `AGENTS.md` from it, so it can only
@@ -42,7 +48,11 @@
42
48
  import { lstatSync, readlinkSync, renameSync, symlinkSync } from 'node:fs';
43
49
  import path from 'node:path';
44
50
  import { resolveConfigRoot } from './configRoot.js';
45
- import { FIRST_PARTY_HARNESS, FIRST_PARTY_HARNESS_ENGINE, firstPartyHarnessBinaryName, firstPartyHarnessHomeDirname, } from './firstPartyHarness.js';
51
+ import { FIRST_PARTY_HARNESS, FIRST_PARTY_HARNESS_ENGINE, firstPartyHarnessArtifactSpellings, firstPartyHarnessBinaryName, firstPartyHarnessHomeDirname, } from './firstPartyHarness.js';
52
+ /** Every artifact spelling except the canonical one, newest first. */
53
+ function olderSpellings() {
54
+ return firstPartyHarnessArtifactSpellings().filter((spelling) => spelling !== FIRST_PARTY_HARNESS.provider);
55
+ }
46
56
  /** Suffix of the rollback copy the installer keeps beside the binary. */
47
57
  export const FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX = '.previous';
48
58
  /**
@@ -108,7 +118,9 @@ const NEWER_THRESHOLD_MS = 1000;
108
118
  * Move a legacy engine home and binary onto the canonical names. See the file
109
119
  * header for the exact contract. Returns what it did; never throws.
110
120
  *
111
- * Inside `<home>/bin` it resolves the three layouts a rename can leave:
121
+ * Inside `<home>/bin` it resolves the three layouts a rename can leave. An
122
+ * "alias" below is any non-canonical artifact spelling — an accepted alias or a
123
+ * former spelling alike, since the file on disk does not know which it is now:
112
124
  *
113
125
  * - canonical ABSENT, an alias is a real file — the ordinary legacy install.
114
126
  * The alias moves to canonical and a link is left at its name.
@@ -171,7 +183,7 @@ export function migrateFirstPartyHarnessHome(options = {}) {
171
183
  const canonicalName = firstPartyHarnessBinaryName(canonical, platform);
172
184
  const canonicalBinary = path.join(binDir, canonicalName);
173
185
  const canonicalStat = fs.lstat(canonicalBinary);
174
- const aliasNames = FIRST_PARTY_HARNESS.aliases.map((alias) => firstPartyHarnessBinaryName(alias, platform));
186
+ const aliasNames = olderSpellings().map((spelling) => firstPartyHarnessBinaryName(spelling, platform));
175
187
  /** Move `<from><suffix>` onto `<to><suffix>` and leave a link; `replaceable` says what may sit at the target. */
176
188
  const relocate = (fromName, suffix, replaceable) => {
177
189
  const from = path.join(binDir, `${fromName}${suffix}`);
@@ -260,13 +272,17 @@ export function migrateFirstPartyHarnessHome(options = {}) {
260
272
  return { moved, errors };
261
273
  }
262
274
  /**
263
- * Leave a relative symlink at every alias name — binary and provenance sidecar —
275
+ * Leave a relative symlink at every older name — binary and provenance sidecar —
264
276
  * that does not exist yet, pointing at the canonical file.
265
277
  *
266
278
  * Call after installing the canonical binary. A platform CLI or daemon installed
267
279
  * before a rename resolves the legacy name; the link keeps it on the current
268
- * engine. It never replaces anything that exists, whether a real file (an older
269
- * engine someone may still run) or a link someone else placed. Never throws.
280
+ * engine. "Older" includes FORMER spellings, not only aliases: an installed CLI
281
+ * cannot be telemetry-gated the way a connected daemon can, and it does not stop
282
+ * existing when its spelling stops being accepted, so the link outlives the
283
+ * alias window (`firstPartyHarnessBinaryNames`). It never replaces anything that
284
+ * exists, whether a real file (an older engine someone may still run) or a link
285
+ * someone else placed. Never throws.
270
286
  */
271
287
  export function linkFirstPartyHarnessAliases(options = {}) {
272
288
  const env = options.env ?? process.env;
@@ -282,8 +298,8 @@ export function linkFirstPartyHarnessAliases(options = {}) {
282
298
  if (!isRealFile(fs.lstat(path.join(binDir, canonicalName)))) {
283
299
  return { moved, errors };
284
300
  }
285
- for (const alias of FIRST_PARTY_HARNESS.aliases) {
286
- const aliasName = firstPartyHarnessBinaryName(alias, platform);
301
+ for (const spelling of olderSpellings()) {
302
+ const aliasName = firstPartyHarnessBinaryName(spelling, platform);
287
303
  // Sidecar FIRST: a verifier that finds the binary link must find its record.
288
304
  for (const suffix of [FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX, '']) {
289
305
  const target = `${canonicalName}${suffix}`;
@@ -49,11 +49,15 @@ export type HarnessTrustTier = 1 | 2 | 3;
49
49
  * in `daemon/src/harness-trust.ts` beside the gates that read it — the argument
50
50
  * belongs where the enforcement is, not in a shared vocabulary module.
51
51
  *
52
- * It is entered under EVERY spelling (`firstPartyHarnessSpellings`), not only the
53
- * canonical one. A backend name reaches this lookup from a daemon of any version,
54
- * and an alias that fell through to the Tier 3 default would silently strip the
55
- * harness of the trust the owner granted it — a failure that looks like a
56
- * permission prompt storm, not like a rename.
52
+ * It is entered under every ACCEPTED spelling (`firstPartyHarnessSpellings`), not
53
+ * only the canonical one. During a rename a backend name reaches this lookup from
54
+ * a daemon of any version, and an alias that fell through to the Tier 3 default
55
+ * would silently strip the harness of the trust the owner granted it — a failure
56
+ * that looks like a permission prompt storm, not like a rename.
57
+ *
58
+ * A FORMER spelling is deliberately absent. Nothing dispatches under one after
59
+ * contract, and a name that is not accepted must never inherit Tier 1 by being
60
+ * remembered: it falls to the default like any unknown backend.
57
61
  */
58
62
  export declare const HARNESS_TIERS: Readonly<Record<string, HarnessTrustTier>>;
59
63
  /**