@skrr-ai/cli 0.1.42 → 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 (74) 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 +3 -2
  8. package/dist/commands/harnesses/install.d.ts +11 -0
  9. package/dist/commands/harnesses/install.js +33 -14
  10. package/dist/commands/harnesses/installers.d.ts +10 -0
  11. package/dist/commands/harnesses/installers.js +31 -0
  12. package/dist/commands/harnesses/list.js +3 -2
  13. package/dist/commands/tasks/attachments/set-role.d.ts +24 -0
  14. package/dist/commands/tasks/attachments/set-role.js +60 -0
  15. package/dist/commands/tasks/attachments/upload.d.ts +1 -0
  16. package/dist/commands/tasks/attachments/upload.js +18 -52
  17. package/dist/commands/tasks/comments/add.d.ts +1 -0
  18. package/dist/commands/tasks/comments/add.js +67 -2
  19. package/dist/commands/tasks/complete.d.ts +1 -0
  20. package/dist/commands/tasks/complete.js +56 -1
  21. package/dist/commands/tasks/result/submit.d.ts +3 -0
  22. package/dist/commands/tasks/result/submit.js +76 -8
  23. package/dist/commands/tasks/update.js +4 -2
  24. package/dist/commands/tasks/updates/add.d.ts +1 -0
  25. package/dist/commands/tasks/updates/add.js +63 -0
  26. package/dist/lib/api-fetch.js +7 -2
  27. package/dist/lib/cli-installers.d.ts +23 -4
  28. package/dist/lib/cli-installers.js +47 -7
  29. package/dist/lib/dedicated-machines.js +4 -2
  30. package/dist/lib/file-mime.js +1 -1
  31. package/dist/lib/first-party-harness-agent.js +6 -5
  32. package/dist/lib/first-party-harness-doctor.js +15 -32
  33. package/dist/lib/first-party-harness-managed.d.ts +9 -4
  34. package/dist/lib/first-party-harness-managed.js +13 -10
  35. package/dist/lib/first-party-harness.d.ts +25 -21
  36. package/dist/lib/first-party-harness.js +40 -27
  37. package/dist/lib/harness-provider-input.d.ts +18 -12
  38. package/dist/lib/harness-provider-input.js +18 -12
  39. package/dist/lib/harness-tiers.d.ts +4 -3
  40. package/dist/lib/harness-tiers.js +4 -3
  41. package/dist/lib/html-text.js +25 -0
  42. package/dist/lib/session-task-endpoints.d.ts +1 -1
  43. package/dist/lib/session-task-endpoints.js +3 -0
  44. package/dist/lib/task-asset-upload.d.ts +84 -0
  45. package/dist/lib/task-asset-upload.js +374 -0
  46. package/dist/lib/task-closure.d.ts +15 -0
  47. package/dist/lib/task-closure.js +20 -0
  48. package/dist/lib/tasks.d.ts +8 -0
  49. package/dist/lib/tasks.js +16 -0
  50. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +113 -39
  51. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +148 -71
  52. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.d.ts +21 -33
  53. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.js +24 -50
  54. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.d.ts +19 -7
  55. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.js +26 -10
  56. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.d.ts +9 -5
  57. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.js +9 -5
  58. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  59. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +1 -13
  60. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +113 -39
  61. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +146 -70
  62. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.d.ts +21 -33
  63. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.js +24 -49
  64. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.d.ts +19 -7
  65. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.js +27 -11
  66. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.d.ts +9 -5
  67. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.js +9 -5
  68. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  69. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -4
  70. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  71. package/dist/node_modules/@skrr-ai/data-provider/index.js +3855 -3779
  72. package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
  73. package/oclif.manifest.json +26935 -26810
  74. 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 ────────────────────────────────
32
- *
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.
43
- *
44
- * The old code treated those three as one string, which is why no single
45
- * change could rename it safely. See
34
+ * ── Four fields, kept apart on purpose ────────────────────────────────────
35
+ *
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.
58
+ *
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 ──────────────────────────────────────────────
@@ -61,24 +76,28 @@ export interface FirstPartyHarnessIdentity {
61
76
  readonly provider: string;
62
77
  /** Older spellings still ACCEPTED on every ingress, newest first. */
63
78
  readonly aliases: readonly string[];
79
+ /**
80
+ * Retired spellings, newest first: accepted on NO ingress, recognised only in
81
+ * artifacts that predate their retirement (see the file header).
82
+ */
83
+ readonly formerSpellings: readonly string[];
64
84
  /** The spelling daemons and the server exchange on the wire. */
65
85
  readonly wireProvider: string;
66
86
  }
67
87
  /**
68
88
  * THE identity — the only literal product name in the codebase.
69
89
  *
70
- * Phase: WIRE MOVED. `skrr-code` is canonical — stored, returned, rendered — and
71
- * it is now also what the server and a daemon say to each other. `sky-code` is
72
- * still accepted on every ingress, so a daemon released before this change
73
- * (which advertises and answers on `sky-code`) keeps working against a server
74
- * that speaks `skrr-code`, and the reverse. Contract (OSK-8663) empties
75
- * `aliases` once no connected daemon still advertises the old capability.
90
+ * Phase: CONTRACTED (OSK-8663). `skrr-code` is the only spelling any ingress
91
+ * accepts and the only one on the wire. `sky-code` is retired: nothing admits
92
+ * it, and it is still recognised where a binary, a checkout, a published
93
+ * release or a stored value written before the rename is looked for.
76
94
  */
77
95
  declare const IDENTITY: {
78
96
  readonly displayName: "skrr Code";
79
97
  readonly command: "skrr code";
80
98
  readonly provider: "skrr-code";
81
- readonly aliases: readonly ["sky-code"];
99
+ readonly aliases: readonly [];
100
+ readonly formerSpellings: readonly ["sky-code"];
82
101
  readonly wireProvider: "skrr-code";
83
102
  };
84
103
  /** The canonical slug, as a literal type. */
@@ -92,8 +111,26 @@ export type FirstPartyHarnessCapabilityName = `${FirstPartyHarnessProvider}_sess
92
111
  export declare const FIRST_PARTY_HARNESS: FirstPartyHarnessIdentity;
93
112
  /** The canonical slug with its literal type, for `as const` tuples and keyed tables. */
94
113
  export declare const FIRST_PARTY_HARNESS_PROVIDER: FirstPartyHarnessCanonicalProvider;
95
- /** Every spelling, canonical first. The order is the resolution order. */
114
+ /**
115
+ * Every ACCEPTED spelling, canonical first — what an ingress admits. The order
116
+ * is the resolution order.
117
+ */
96
118
  export declare function firstPartyHarnessSpellings(): readonly string[];
119
+ /**
120
+ * Every spelling an ARTIFACT that already exists may carry, canonical first:
121
+ * the accepted spellings, then every former one.
122
+ *
123
+ * For code that LOOKS FOR what an earlier release left behind — an installed
124
+ * binary, a checkout directory, an immutable published release, a stored value
125
+ * or config key — and for the tooling that must keep recognising those
126
+ * (ignore lists, the identity ratchet). A machine that never ran a post-rename
127
+ * client still has its engine under the old name, and it must still be found.
128
+ *
129
+ * Never an ingress check: a former spelling is not accepted anywhere, and a
130
+ * reader that admitted one here would silently reopen what contract closed. Use
131
+ * `firstPartyHarnessSpellings` / `isFirstPartyHarnessProvider` for that.
132
+ */
133
+ export declare function firstPartyHarnessArtifactSpellings(): readonly string[];
97
134
  /** True for the canonical provider or any alias (case- and whitespace-insensitive). */
98
135
  export declare function isFirstPartyHarnessProvider(value: unknown): boolean;
99
136
  /**
@@ -102,6 +139,24 @@ export declare function isFirstPartyHarnessProvider(value: unknown): boolean;
102
139
  * caller's own normalisation still owns case and shape for other providers).
103
140
  */
104
141
  export declare function canonicalHarnessProvider<T>(value: T): T | string;
142
+ /**
143
+ * The canonical provider for a value read back from an ARTIFACT that may predate
144
+ * a rename — any artifact spelling (canonical, alias or FORMER) becomes the
145
+ * provider; every other value is returned unchanged.
146
+ *
147
+ * For values nobody migrates and nobody can refuse: a harness row's stored
148
+ * display NAME (the operator migration rewrites `provider`, never `name`), a
149
+ * daemon's config file, a harness session binding on a user's machine. Read raw
150
+ * after contract, each would name an unknown provider — a label that renders the
151
+ * retired name again, a local session silently running another backend, a live
152
+ * resume context reset as new — with no error anywhere.
153
+ *
154
+ * NEVER for anything a peer, a client or an operator SENDS, and never for a
155
+ * trust, dispatch or admission decision: a former spelling is accepted on no
156
+ * ingress, and canonicalising one here would reopen what contract closed. Use
157
+ * `canonicalHarnessProvider` for those.
158
+ */
159
+ export declare function canonicalArtifactHarnessProvider<T>(value: T): T | string;
105
160
  /**
106
161
  * Every spelling a stored row for this provider may carry, canonical first —
107
162
  * for a Mongo `$in` filter while rows written before a flip still exist. Any
@@ -138,7 +193,16 @@ export declare function isFirstPartyHarnessEnvironment(value: unknown): boolean;
138
193
  export declare function canonicalExecutionEnvironment<T>(value: T): T | string;
139
194
  /** Binary file name. `platform` is a `process.platform` value; only `win32` differs. */
140
195
  export declare function firstPartyHarnessBinaryName(provider?: string, platform?: string): string;
141
- /** Every binary file name, canonical first — the lookup order. */
196
+ /**
197
+ * Every binary file name an install may carry, canonical first — the lookup
198
+ * order, and the names an install links to the canonical binary.
199
+ *
200
+ * Built from the ARTIFACT spellings, former ones included: binaries are
201
+ * artifacts. An engine installed before a rename is still on disk under its old
202
+ * name, and a platform CLI or daemon built before it still looks for that name,
203
+ * so resolvers try it and installers leave a link at it. Neither makes the
204
+ * spelling acceptable on any ingress.
205
+ */
142
206
  export declare function firstPartyHarnessBinaryNames(platform?: string): readonly string[];
143
207
  /**
144
208
  * The engine home directory name under the skrr config root.
@@ -161,23 +225,32 @@ export declare function firstPartyHarnessManifestFile(provider?: string): string
161
225
  export declare const FIRST_PARTY_HARNESS_FEED_ORIGIN = "https://updates.oversky.ai";
162
226
  /** Object-key prefix for one spelling's feed, e.g. `skrr-code/`. */
163
227
  export declare function firstPartyHarnessFeedPrefix(provider?: string): string;
164
- /** Every feed prefix, canonical first. Publishing writes all of them. */
228
+ /**
229
+ * Every feed prefix a publish WRITES, canonical first: the accepted spellings.
230
+ *
231
+ * A former spelling's prefix still holds every release published under it, and
232
+ * those stay served — their artifact URLs are inside signed manifests — but
233
+ * nothing new is written there, so its channel pointers stop moving at contract.
234
+ */
165
235
  export declare function firstPartyHarnessFeedPrefixes(): readonly string[];
166
236
  /** Object-key prefix the fork stages unsigned builds under, e.g. `skrr-code-staging/`. */
167
237
  export declare function firstPartyHarnessStagingPrefix(provider?: string): string;
168
238
  /** Default feed base URL for one spelling. */
169
239
  export declare function firstPartyHarnessDefaultFeedBase(provider?: string): string;
170
240
  export interface FirstPartyHarnessEnvName {
171
- /** Brand-neutral name. Set this one. */
241
+ /** Brand-neutral name — the only one the platform reads. */
172
242
  readonly name: string;
173
- /** Older names still read, in precedence order, until contract. */
174
- readonly legacy: readonly string[];
175
243
  }
176
244
  /**
177
- * Every environment variable the platform reads for the first-party harness,
178
- * by purpose. The neutral name wins; a legacy name is read only when the neutral
179
- * one is unset or blank, because a variable set in an ECS task definition, a
180
- * user's shell or a LaunchAgent does not change when the code does.
245
+ * Every environment variable the PLATFORM reads for the first-party harness, by
246
+ * purpose, under one brand-neutral name each — so a product rename never renames
247
+ * one.
248
+ *
249
+ * The product-named variables these replaced were read second until contract
250
+ * (OSK-8663) and are not read at all now. The ENGINE's own variables are a
251
+ * different namespace (`FIRST_PARTY_HARNESS_ENGINE.env`): the fork reads them,
252
+ * so a platform reader that must agree with the engine consults the engine's
253
+ * name explicitly — the engine home is the one case (`home` below).
181
254
  */
182
255
  export declare const FIRST_PARTY_HARNESS_ENV: Readonly<{
183
256
  /** Absolute path to an engine binary, overriding resolution. */
@@ -192,7 +265,12 @@ export declare const FIRST_PARTY_HARNESS_ENV: Readonly<{
192
265
  devKeys: FirstPartyHarnessEnvName;
193
266
  /** Engine version the desktop build bundles. */
194
267
  bundleVersion: FirstPartyHarnessEnvName;
195
- /** Engine home override (the engine reads its own name too — see ENGINE env below). */
268
+ /**
269
+ * Engine home override. The engine reads only its own variable
270
+ * (`FIRST_PARTY_HARNESS_ENGINE.env.home`), so a reader that resolves the home
271
+ * the engine will use reads that one second, and a launcher hands the engine
272
+ * the resolved path under it.
273
+ */
196
274
  home: FirstPartyHarnessEnvName;
197
275
  /** Engine version baked into the cloud-coding image. */
198
276
  cloudCodingVersion: FirstPartyHarnessEnvName;
@@ -213,12 +291,8 @@ export interface FirstPartyHarnessEnvReading {
213
291
  readonly value: string | undefined;
214
292
  /** The variable that supplied `value`. */
215
293
  readonly name: string | undefined;
216
- /** True when the value came from a legacy name — worth telling the operator. */
217
- readonly legacy: boolean;
218
294
  }
219
- /** Every name for one purpose, neutral first — for spawn-env forwarding and sanitisers. */
220
- export declare function firstPartyHarnessEnvNames(key: FirstPartyHarnessEnvKey): readonly string[];
221
- /** Read one purpose's variable: neutral name first, legacy names after. */
295
+ /** Read one purpose's variable. A blank value counts as unset. */
222
296
  export declare function readFirstPartyHarnessEnv(env: Readonly<Record<string, string | undefined>>, key: FirstPartyHarnessEnvKey): FirstPartyHarnessEnvReading;
223
297
  /**
224
298
  * Names the ENGINE itself defines and reads. The platform sets or reads them
@@ -25,25 +25,40 @@
25
25
  * phase, once every peer accepts the new one;
26
26
  * 4. regenerate `first-party-harness.json`
27
27
  * (`node --import tsx scripts/emit-first-party-harness-json.mjs`);
28
- * 5. ship the data migration, which is generic over `aliases`.
28
+ * 5. ship the data migration, which is generic over `aliases` and
29
+ * `formerSpellings`;
30
+ * 6. at that rename's contract, move the spelling from `aliases` to the front
31
+ * of `formerSpellings`.
29
32
  *
30
33
  * No identifier, file, env var name or workflow input moves.
31
34
  *
32
- * ── Three spellings, kept apart on purpose ────────────────────────────────
35
+ * ── Four fields, kept apart on purpose ────────────────────────────────────
33
36
  *
34
- * `provider` CANONICAL. What the platform stores, returns and renders.
35
- * `aliases` ACCEPTED on every ingress until contract, and normalised to
36
- * canonical at the edge. Nothing new is ever written with one.
37
- * `wireProvider` What the server and a daemon say to EACH OTHER — the
38
- * capability a daemon advertises, `daemon:<p>:request|response`,
39
- * the `provider` of a session RPC. It moves after the flip and
40
- * BEFORE contract, because a daemon on a user's machine may be
41
- * any version and the server cannot tell which one sent a
42
- * request: only a released daemon that already advertises the
43
- * canonical capability lets the old one age out of the fleet.
37
+ * `provider` CANONICAL. What the platform stores, returns and renders.
38
+ * `aliases` ACCEPTED on every ingress during a rename's window, and
39
+ * normalised to canonical at the edge. Nothing new is ever
40
+ * written with one. Empty between renames.
41
+ * `formerSpellings` RETIRED. Accepted on NO ingress: no HTTP body, enum, Mongo
42
+ * filter, capability, wire event, CLI flag or trust table
43
+ * admits one. Recognised only where an ARTIFACT that predates
44
+ * the rename is read — a binary already installed, a checkout
45
+ * directory already on disk, a release already published
46
+ * under its prefix, a row or config key already written.
47
+ * Those exist whatever the platform accepts, and dropping a
48
+ * spelling from this list does not make them go away. Read it
49
+ * through `firstPartyHarnessArtifactSpellings`, never as an
50
+ * ingress check.
51
+ * `wireProvider` What the server and a daemon say to EACH OTHER — the
52
+ * capability a daemon advertises,
53
+ * `daemon:<p>:request|response`, the `provider` of a session
54
+ * RPC. It moves after the flip and BEFORE contract, because a
55
+ * daemon on a user's machine may be any version and the
56
+ * server cannot tell which one sent a request: only a
57
+ * released daemon that already advertises the canonical
58
+ * capability lets the old one age out of the fleet.
44
59
  *
45
- * The old code treated those three as one string, which is why no single
46
- * change could rename it safely. See
60
+ * The old code treated these as one string, which is why no single change could
61
+ * rename it safely. See
47
62
  * `docs/architecture/skrr-code-identifier-rename-2026-09-13.md`.
48
63
  *
49
64
  * ── Constraints on this file ──────────────────────────────────────────────
@@ -56,8 +71,10 @@
56
71
  Object.defineProperty(exports, "__esModule", { value: true });
57
72
  exports.FIRST_PARTY_HARNESS_ENGINE = exports.FIRST_PARTY_HARNESS_ENV = exports.FIRST_PARTY_HARNESS_FEED_ORIGIN = exports.FIRST_PARTY_HARNESS_PROVIDER = exports.FIRST_PARTY_HARNESS = void 0;
58
73
  exports.firstPartyHarnessSpellings = firstPartyHarnessSpellings;
74
+ exports.firstPartyHarnessArtifactSpellings = firstPartyHarnessArtifactSpellings;
59
75
  exports.isFirstPartyHarnessProvider = isFirstPartyHarnessProvider;
60
76
  exports.canonicalHarnessProvider = canonicalHarnessProvider;
77
+ exports.canonicalArtifactHarnessProvider = canonicalArtifactHarnessProvider;
61
78
  exports.harnessProviderSpellings = harnessProviderSpellings;
62
79
  exports.wireHarnessProvider = wireHarnessProvider;
63
80
  exports.isSameHarnessProvider = isSameHarnessProvider;
@@ -79,44 +96,48 @@ exports.firstPartyHarnessFeedPrefix = firstPartyHarnessFeedPrefix;
79
96
  exports.firstPartyHarnessFeedPrefixes = firstPartyHarnessFeedPrefixes;
80
97
  exports.firstPartyHarnessStagingPrefix = firstPartyHarnessStagingPrefix;
81
98
  exports.firstPartyHarnessDefaultFeedBase = firstPartyHarnessDefaultFeedBase;
82
- exports.firstPartyHarnessEnvNames = firstPartyHarnessEnvNames;
83
99
  exports.readFirstPartyHarnessEnv = readFirstPartyHarnessEnv;
84
100
  /** A slug usable as a directory name, a capability prefix and an event segment. */
85
101
  const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
86
102
  function defineIdentity(identity) {
87
103
  const spellings = [identity.provider, ...identity.aliases];
88
- for (const spelling of spellings) {
104
+ const every = [...spellings, ...identity.formerSpellings];
105
+ for (const spelling of every) {
89
106
  if (!SLUG.test(spelling)) {
90
107
  throw new Error(`first-party harness spelling ${JSON.stringify(spelling)} is not a slug; it becomes a ` +
91
108
  `directory name, a capability prefix and a wire event segment`);
92
109
  }
93
110
  }
94
- if (new Set(spellings).size !== spellings.length) {
95
- throw new Error('first-party harness provider and aliases must be distinct');
111
+ if (new Set(every).size !== every.length) {
112
+ throw new Error('first-party harness provider, aliases and formerSpellings must be distinct — a spelling ' +
113
+ 'is accepted or retired, never both');
96
114
  }
97
115
  if (!spellings.includes(identity.wireProvider)) {
98
116
  throw new Error(`first-party harness wireProvider ${JSON.stringify(identity.wireProvider)} must be the ` +
99
117
  'provider or one of its aliases — a peer can only accept a spelling it knows');
100
118
  }
101
- return Object.freeze({ ...identity, aliases: Object.freeze([...identity.aliases]) });
119
+ return Object.freeze({
120
+ ...identity,
121
+ aliases: Object.freeze([...identity.aliases]),
122
+ formerSpellings: Object.freeze([...identity.formerSpellings]),
123
+ });
102
124
  }
103
125
  /** The canonical slug. Written once, and read by every field that names the current spelling. */
104
126
  const PROVIDER = 'skrr-code';
105
127
  /**
106
128
  * THE identity — the only literal product name in the codebase.
107
129
  *
108
- * Phase: WIRE MOVED. `skrr-code` is canonical — stored, returned, rendered — and
109
- * it is now also what the server and a daemon say to each other. `sky-code` is
110
- * still accepted on every ingress, so a daemon released before this change
111
- * (which advertises and answers on `sky-code`) keeps working against a server
112
- * that speaks `skrr-code`, and the reverse. Contract (OSK-8663) empties
113
- * `aliases` once no connected daemon still advertises the old capability.
130
+ * Phase: CONTRACTED (OSK-8663). `skrr-code` is the only spelling any ingress
131
+ * accepts and the only one on the wire. `sky-code` is retired: nothing admits
132
+ * it, and it is still recognised where a binary, a checkout, a published
133
+ * release or a stored value written before the rename is looked for.
114
134
  */
115
135
  const IDENTITY = {
116
136
  displayName: 'skrr Code',
117
137
  command: 'skrr code',
118
138
  provider: PROVIDER,
119
- aliases: ['sky-code'],
139
+ aliases: [],
140
+ formerSpellings: ['sky-code'],
120
141
  wireProvider: PROVIDER,
121
142
  };
122
143
  exports.FIRST_PARTY_HARNESS = defineIdentity(IDENTITY);
@@ -127,6 +148,11 @@ const SPELLINGS = Object.freeze([
127
148
  ...exports.FIRST_PARTY_HARNESS.aliases,
128
149
  ]);
129
150
  const SPELLING_SET = new Set(SPELLINGS);
151
+ const ARTIFACT_SPELLINGS = Object.freeze([
152
+ ...SPELLINGS,
153
+ ...exports.FIRST_PARTY_HARNESS.formerSpellings,
154
+ ]);
155
+ const ARTIFACT_SPELLING_SET = new Set(ARTIFACT_SPELLINGS);
130
156
  const SESSION_SUFFIX = '_session';
131
157
  const ENVIRONMENT_PREFIX = 'local-';
132
158
  function asSlug(value) {
@@ -135,10 +161,30 @@ function asSlug(value) {
135
161
  const slug = value.trim().toLowerCase();
136
162
  return slug ? slug : null;
137
163
  }
138
- /** Every spelling, canonical first. The order is the resolution order. */
164
+ /**
165
+ * Every ACCEPTED spelling, canonical first — what an ingress admits. The order
166
+ * is the resolution order.
167
+ */
139
168
  function firstPartyHarnessSpellings() {
140
169
  return SPELLINGS;
141
170
  }
171
+ /**
172
+ * Every spelling an ARTIFACT that already exists may carry, canonical first:
173
+ * the accepted spellings, then every former one.
174
+ *
175
+ * For code that LOOKS FOR what an earlier release left behind — an installed
176
+ * binary, a checkout directory, an immutable published release, a stored value
177
+ * or config key — and for the tooling that must keep recognising those
178
+ * (ignore lists, the identity ratchet). A machine that never ran a post-rename
179
+ * client still has its engine under the old name, and it must still be found.
180
+ *
181
+ * Never an ingress check: a former spelling is not accepted anywhere, and a
182
+ * reader that admitted one here would silently reopen what contract closed. Use
183
+ * `firstPartyHarnessSpellings` / `isFirstPartyHarnessProvider` for that.
184
+ */
185
+ function firstPartyHarnessArtifactSpellings() {
186
+ return ARTIFACT_SPELLINGS;
187
+ }
142
188
  /** True for the canonical provider or any alias (case- and whitespace-insensitive). */
143
189
  function isFirstPartyHarnessProvider(value) {
144
190
  const slug = asSlug(value);
@@ -152,6 +198,27 @@ function isFirstPartyHarnessProvider(value) {
152
198
  function canonicalHarnessProvider(value) {
153
199
  return isFirstPartyHarnessProvider(value) ? exports.FIRST_PARTY_HARNESS.provider : value;
154
200
  }
201
+ /**
202
+ * The canonical provider for a value read back from an ARTIFACT that may predate
203
+ * a rename — any artifact spelling (canonical, alias or FORMER) becomes the
204
+ * provider; every other value is returned unchanged.
205
+ *
206
+ * For values nobody migrates and nobody can refuse: a harness row's stored
207
+ * display NAME (the operator migration rewrites `provider`, never `name`), a
208
+ * daemon's config file, a harness session binding on a user's machine. Read raw
209
+ * after contract, each would name an unknown provider — a label that renders the
210
+ * retired name again, a local session silently running another backend, a live
211
+ * resume context reset as new — with no error anywhere.
212
+ *
213
+ * NEVER for anything a peer, a client or an operator SENDS, and never for a
214
+ * trust, dispatch or admission decision: a former spelling is accepted on no
215
+ * ingress, and canonicalising one here would reopen what contract closed. Use
216
+ * `canonicalHarnessProvider` for those.
217
+ */
218
+ function canonicalArtifactHarnessProvider(value) {
219
+ const slug = asSlug(value);
220
+ return slug !== null && ARTIFACT_SPELLING_SET.has(slug) ? exports.FIRST_PARTY_HARNESS.provider : value;
221
+ }
155
222
  /**
156
223
  * Every spelling a stored row for this provider may carry, canonical first —
157
224
  * for a Mongo `$in` filter while rows written before a flip still exist. Any
@@ -231,9 +298,18 @@ function canonicalExecutionEnvironment(value) {
231
298
  function firstPartyHarnessBinaryName(provider = exports.FIRST_PARTY_HARNESS.provider, platform) {
232
299
  return platform === 'win32' ? `${provider}.exe` : provider;
233
300
  }
234
- /** Every binary file name, canonical first — the lookup order. */
301
+ /**
302
+ * Every binary file name an install may carry, canonical first — the lookup
303
+ * order, and the names an install links to the canonical binary.
304
+ *
305
+ * Built from the ARTIFACT spellings, former ones included: binaries are
306
+ * artifacts. An engine installed before a rename is still on disk under its old
307
+ * name, and a platform CLI or daemon built before it still looks for that name,
308
+ * so resolvers try it and installers leave a link at it. Neither makes the
309
+ * spelling acceptable on any ingress.
310
+ */
235
311
  function firstPartyHarnessBinaryNames(platform) {
236
- return SPELLINGS.map((spelling) => firstPartyHarnessBinaryName(spelling, platform));
312
+ return ARTIFACT_SPELLINGS.map((spelling) => firstPartyHarnessBinaryName(spelling, platform));
237
313
  }
238
314
  /**
239
315
  * The engine home directory name under the skrr config root.
@@ -262,7 +338,13 @@ exports.FIRST_PARTY_HARNESS_FEED_ORIGIN = 'https://updates.oversky.ai';
262
338
  function firstPartyHarnessFeedPrefix(provider = exports.FIRST_PARTY_HARNESS.provider) {
263
339
  return `${provider}/`;
264
340
  }
265
- /** Every feed prefix, canonical first. Publishing writes all of them. */
341
+ /**
342
+ * Every feed prefix a publish WRITES, canonical first: the accepted spellings.
343
+ *
344
+ * A former spelling's prefix still holds every release published under it, and
345
+ * those stay served — their artifact URLs are inside signed manifests — but
346
+ * nothing new is written there, so its channel pointers stop moving at contract.
347
+ */
266
348
  function firstPartyHarnessFeedPrefixes() {
267
349
  return SPELLINGS.map((spelling) => firstPartyHarnessFeedPrefix(spelling));
268
350
  }
@@ -274,66 +356,61 @@ function firstPartyHarnessStagingPrefix(provider = exports.FIRST_PARTY_HARNESS.p
274
356
  function firstPartyHarnessDefaultFeedBase(provider = exports.FIRST_PARTY_HARNESS.provider) {
275
357
  return `${exports.FIRST_PARTY_HARNESS_FEED_ORIGIN}/${provider}`;
276
358
  }
277
- function envName(name, legacy) {
278
- return Object.freeze({ name, legacy: Object.freeze([...legacy]) });
359
+ function envName(name) {
360
+ return Object.freeze({ name });
279
361
  }
280
362
  /**
281
- * Every environment variable the platform reads for the first-party harness,
282
- * by purpose. The neutral name wins; a legacy name is read only when the neutral
283
- * one is unset or blank, because a variable set in an ECS task definition, a
284
- * user's shell or a LaunchAgent does not change when the code does.
363
+ * Every environment variable the PLATFORM reads for the first-party harness, by
364
+ * purpose, under one brand-neutral name each — so a product rename never renames
365
+ * one.
366
+ *
367
+ * The product-named variables these replaced were read second until contract
368
+ * (OSK-8663) and are not read at all now. The ENGINE's own variables are a
369
+ * different namespace (`FIRST_PARTY_HARNESS_ENGINE.env`): the fork reads them,
370
+ * so a platform reader that must agree with the engine consults the engine's
371
+ * name explicitly — the engine home is the one case (`home` below).
285
372
  */
286
373
  exports.FIRST_PARTY_HARNESS_ENV = Object.freeze({
287
374
  /** Absolute path to an engine binary, overriding resolution. */
288
- path: envName('SKRR_FIRST_PARTY_HARNESS_PATH', ['OVERSKY_SKY_CODE_PATH']),
375
+ path: envName('SKRR_FIRST_PARTY_HARNESS_PATH'),
289
376
  /** Forces the CLI's managed-inference mode. */
290
- managed: envName('SKRR_FIRST_PARTY_HARNESS_MANAGED', ['OVERSKY_SKY_CODE_MANAGED']),
377
+ managed: envName('SKRR_FIRST_PARTY_HARNESS_MANAGED'),
291
378
  /** Update channel. */
292
- channel: envName('SKRR_FIRST_PARTY_HARNESS_CHANNEL', ['OVERSKY_SKY_CODE_CHANNEL']),
379
+ channel: envName('SKRR_FIRST_PARTY_HARNESS_CHANNEL'),
293
380
  /** Update-feed base URL override. */
294
- updateFeedBase: envName('SKRR_FIRST_PARTY_HARNESS_UPDATE_FEED_BASE', [
295
- 'OVERSKY_SKY_CODE_UPDATE_FEED_BASE',
296
- ]),
381
+ updateFeedBase: envName('SKRR_FIRST_PARTY_HARNESS_UPDATE_FEED_BASE'),
297
382
  /** Path to a file of development release keys. */
298
- devKeys: envName('SKRR_FIRST_PARTY_HARNESS_DEV_KEYS', ['OVERSKY_SKY_CODE_DEV_KEYS']),
383
+ devKeys: envName('SKRR_FIRST_PARTY_HARNESS_DEV_KEYS'),
299
384
  /** Engine version the desktop build bundles. */
300
- bundleVersion: envName('SKRR_FIRST_PARTY_HARNESS_BUNDLE_VERSION', ['OVERSKY_BUNDLE_SKY_CODE']),
301
- /** Engine home override (the engine reads its own name too — see ENGINE env below). */
302
- home: envName('SKRR_FIRST_PARTY_HARNESS_HOME', ['SKY_CODE_HOME']),
385
+ bundleVersion: envName('SKRR_FIRST_PARTY_HARNESS_BUNDLE_VERSION'),
386
+ /**
387
+ * Engine home override. The engine reads only its own variable
388
+ * (`FIRST_PARTY_HARNESS_ENGINE.env.home`), so a reader that resolves the home
389
+ * the engine will use reads that one second, and a launcher hands the engine
390
+ * the resolved path under it.
391
+ */
392
+ home: envName('SKRR_FIRST_PARTY_HARNESS_HOME'),
303
393
  /** Engine version baked into the cloud-coding image. */
304
- cloudCodingVersion: envName('CLOUD_CODING_FIRST_PARTY_HARNESS_VERSION', [
305
- 'CLOUD_CODING_SKY_CODE_VERSION',
306
- ]),
394
+ cloudCodingVersion: envName('CLOUD_CODING_FIRST_PARTY_HARNESS_VERSION'),
307
395
  /** Engine version baked into the Dedicated Runtime AMI. */
308
- dedicatedRuntimeVersion: envName('SKRR_DEDICATED_RUNTIME_FIRST_PARTY_HARNESS_VERSION', [
309
- 'SKRR_DEDICATED_RUNTIME_SKY_CODE_VERSION',
310
- ]),
396
+ dedicatedRuntimeVersion: envName('SKRR_DEDICATED_RUNTIME_FIRST_PARTY_HARNESS_VERSION'),
311
397
  /** Engine version the desktop release workflow bundles. */
312
- desktopBundledVersion: envName('DESKTOP_BUNDLED_FIRST_PARTY_HARNESS_VERSION', [
313
- 'DESKTOP_BUNDLED_SKY_CODE_VERSION',
314
- ]),
398
+ desktopBundledVersion: envName('DESKTOP_BUNDLED_FIRST_PARTY_HARNESS_VERSION'),
315
399
  /** Update-feed bucket used by publish and promote. */
316
- updatesBucket: envName('FIRST_PARTY_HARNESS_UPDATES_BUCKET', ['SKY_CODE_UPDATES_BUCKET']),
400
+ updatesBucket: envName('FIRST_PARTY_HARNESS_UPDATES_BUCKET'),
317
401
  /** Release signing key (CI secret). */
318
- signingKey: envName('FIRST_PARTY_HARNESS_SIGNING_KEY', ['SKY_CODE_SIGNING_KEY']),
402
+ signingKey: envName('FIRST_PARTY_HARNESS_SIGNING_KEY'),
319
403
  /** Release signing key id (CI secret). */
320
- signingKeyId: envName('FIRST_PARTY_HARNESS_SIGNING_KEY_ID', ['SKY_CODE_SIGNING_KEY_ID']),
404
+ signingKeyId: envName('FIRST_PARTY_HARNESS_SIGNING_KEY_ID'),
321
405
  });
322
- /** Every name for one purpose, neutral first — for spawn-env forwarding and sanitisers. */
323
- function firstPartyHarnessEnvNames(key) {
324
- const entry = exports.FIRST_PARTY_HARNESS_ENV[key];
325
- return [entry.name, ...entry.legacy];
326
- }
327
- /** Read one purpose's variable: neutral name first, legacy names after. */
406
+ /** Read one purpose's variable. A blank value counts as unset. */
328
407
  function readFirstPartyHarnessEnv(env, key) {
329
- const entry = exports.FIRST_PARTY_HARNESS_ENV[key];
330
- for (const name of [entry.name, ...entry.legacy]) {
331
- const raw = env[name];
332
- if (typeof raw === 'string' && raw.trim() !== '') {
333
- return { value: raw.trim(), name, legacy: name !== entry.name };
334
- }
408
+ const { name } = exports.FIRST_PARTY_HARNESS_ENV[key];
409
+ const raw = env[name];
410
+ if (typeof raw === 'string' && raw.trim() !== '') {
411
+ return { value: raw.trim(), name };
335
412
  }
336
- return { value: undefined, name: undefined, legacy: false };
413
+ return { value: undefined, name: undefined };
337
414
  }
338
415
  /* ── The ENGINE's own namespace (defined inside the fork) ───────────────── */
339
416
  /**
@@ -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[];