@tutti-os/desktop-update-admission 0.0.274 → 0.0.275

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,8 +5,9 @@ shared by Tutti Desktop and TSH Desktop.
5
5
 
6
6
  The package owns the client contract, response validation, startup and
7
7
  foreground admission lifecycle, mandatory updater lease, Electron upgrade
8
- window binding, preload API factory, shared React presentation, and default
9
- i18n resources.
8
+ window binding, feature-availability validation and persistent cache, trusted
9
+ main IPC registration, preload API factories, shared React presentation, and
10
+ default i18n resources.
10
11
 
11
12
  Consumers still own:
12
13
 
@@ -50,6 +51,7 @@ standalone loopback server, never by both:
50
51
  | `DESKTOP_UPDATE_ADMISSION_POLICY` | Selects one policy outcome. |
51
52
  | `DESKTOP_UPDATE_ADMISSION_POLICY_SEQUENCE` | Selects a comma-separated per-client outcome sequence such as `upgradeRequired@1.1.0,disabled`. |
52
53
  | `DESKTOP_UPDATE_ADMISSION_SCENARIO` | Selects one named policy scenario instead of individual policy fields. |
54
+ | `DESKTOP_UPDATE_ADMISSION_FEATURE_KEYS` | Supplies a comma-separated feature key list returned by the policy mock. |
53
55
  | `DESKTOP_UPDATE_ADMISSION_MOCK_SERVER_PORT` | Selects the loopback CLI port; omit it for an ephemeral port. |
54
56
 
55
57
  The shortest startup-blocking scenario is:
@@ -110,3 +112,34 @@ accidentally gain a second policy source.
110
112
  The server binds only to `127.0.0.1`. Simulated installation never invokes a
111
113
  real installer or application restart and is rendered as a distinct
112
114
  development-only completion state.
115
+
116
+ ## Feature availability
117
+
118
+ The optional `featureAvailability.keys` response envelope is independent from
119
+ the minimum-version decision. A valid envelope replaces the current in-memory
120
+ snapshot and the exact-identity cache. A missing or invalid envelope retains
121
+ the previous snapshot and cannot change admission behavior.
122
+
123
+ Desktop hosts create one runtime with the same product, platform,
124
+ architecture, and current version used by the admission request. The cache is
125
+ stored at `<userData>/desktop-feature-availability-v1.json`, uses atomic
126
+ replacement, has no time expiry, and is accepted only when all identity fields
127
+ match. It stores no minimum version or admission decision. With no matching
128
+ cache every feature query returns `false`.
129
+
130
+ Main and renderer consumers use the shared runtime and preload API:
131
+
132
+ ```ts
133
+ const snapshot = await desktopApi.featureAvailability.getSnapshot();
134
+ const enabled = await desktopApi.featureAvailability.isSupported(
135
+ "workspace.exampleFeature"
136
+ );
137
+ const unsubscribe = desktopApi.featureAvailability.onChanged((next) => {
138
+ console.log(next.keys);
139
+ });
140
+ ```
141
+
142
+ Hosts must register the shared IPC handlers with a sender check that admits
143
+ only their trusted business-window preload. Product code should consume keys
144
+ explicitly; the runtime does not merge remote keys into user preferences or
145
+ existing feature-flag systems.
@@ -0,0 +1,59 @@
1
+ // src/feature-availability/core.ts
2
+ var featureKeyPattern = /^[A-Za-z][A-Za-z0-9]*(?:[._-][A-Za-z0-9]+)*$/u;
3
+ var maximumFeatureKeyLength = 128;
4
+ var maximumFeatureKeys = 256;
5
+ function isValidDesktopFeatureKey(value) {
6
+ return value.length <= maximumFeatureKeyLength && featureKeyPattern.test(value);
7
+ }
8
+ function normalizeDesktopFeatureKeys(value) {
9
+ if (!Array.isArray(value) || value.length > maximumFeatureKeys) {
10
+ throw new Error(
11
+ "feature availability keys must be an array of at most 256 keys"
12
+ );
13
+ }
14
+ const keys = value.map((entry) => {
15
+ if (typeof entry !== "string" || !isValidDesktopFeatureKey(entry)) {
16
+ throw new Error("feature availability contains an invalid key");
17
+ }
18
+ return entry;
19
+ });
20
+ const sorted = [...new Set(keys)].sort();
21
+ if (sorted.length !== keys.length) {
22
+ throw new Error("feature availability contains duplicate keys");
23
+ }
24
+ return Object.freeze(sorted);
25
+ }
26
+ function parseDesktopFeatureAvailability(response) {
27
+ if (!response || typeof response !== "object") {
28
+ throw new Error("desktop version response must be an object");
29
+ }
30
+ const value = response.featureAvailability;
31
+ if (value === void 0) {
32
+ return null;
33
+ }
34
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
35
+ throw new Error("feature availability must be an object");
36
+ }
37
+ const record = value;
38
+ if (Object.keys(record).some((key) => key !== "keys") || !Object.prototype.hasOwnProperty.call(record, "keys")) {
39
+ throw new Error("feature availability has invalid fields");
40
+ }
41
+ return Object.freeze({
42
+ keys: normalizeDesktopFeatureKeys(record.keys)
43
+ });
44
+ }
45
+ function desktopFeatureAvailabilityIdentityMatches(snapshot, identity) {
46
+ return snapshot.product === identity.product && snapshot.platform === identity.platform && snapshot.architecture === identity.architecture && snapshot.currentVersion === identity.currentVersion;
47
+ }
48
+ function isDesktopFeatureSupported(snapshot, key) {
49
+ return isValidDesktopFeatureKey(key) && snapshot.keys.includes(key);
50
+ }
51
+
52
+ export {
53
+ isValidDesktopFeatureKey,
54
+ normalizeDesktopFeatureKeys,
55
+ parseDesktopFeatureAvailability,
56
+ desktopFeatureAvailabilityIdentityMatches,
57
+ isDesktopFeatureSupported
58
+ };
59
+ //# sourceMappingURL=chunk-2GH725V2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/feature-availability/core.ts"],"sourcesContent":["import type {\n DesktopFeatureAvailability,\n DesktopFeatureAvailabilitySnapshot,\n DesktopProduct,\n MinimumVersionCheckRequest\n} from \"../contracts/index.ts\";\n\nconst featureKeyPattern = /^[A-Za-z][A-Za-z0-9]*(?:[._-][A-Za-z0-9]+)*$/u;\nconst maximumFeatureKeyLength = 128;\nconst maximumFeatureKeys = 256;\n\nexport function isValidDesktopFeatureKey(value: string): boolean {\n return (\n value.length <= maximumFeatureKeyLength && featureKeyPattern.test(value)\n );\n}\n\nexport function normalizeDesktopFeatureKeys(value: unknown): readonly string[] {\n if (!Array.isArray(value) || value.length > maximumFeatureKeys) {\n throw new Error(\n \"feature availability keys must be an array of at most 256 keys\"\n );\n }\n const keys = value.map((entry) => {\n if (typeof entry !== \"string\" || !isValidDesktopFeatureKey(entry)) {\n throw new Error(\"feature availability contains an invalid key\");\n }\n return entry;\n });\n const sorted = [...new Set(keys)].sort();\n if (sorted.length !== keys.length) {\n throw new Error(\"feature availability contains duplicate keys\");\n }\n return Object.freeze(sorted);\n}\n\nexport function parseDesktopFeatureAvailability(\n response: unknown\n): DesktopFeatureAvailability | null {\n if (!response || typeof response !== \"object\") {\n throw new Error(\"desktop version response must be an object\");\n }\n const value = (response as Record<string, unknown>).featureAvailability;\n if (value === undefined) {\n return null;\n }\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(\"feature availability must be an object\");\n }\n const record = value as Record<string, unknown>;\n if (\n Object.keys(record).some((key) => key !== \"keys\") ||\n !Object.prototype.hasOwnProperty.call(record, \"keys\")\n ) {\n throw new Error(\"feature availability has invalid fields\");\n }\n return Object.freeze({\n keys: normalizeDesktopFeatureKeys(record.keys)\n });\n}\n\nexport function desktopFeatureAvailabilityIdentityMatches<\n TProduct extends DesktopProduct\n>(\n snapshot: DesktopFeatureAvailabilitySnapshot,\n identity: MinimumVersionCheckRequest<TProduct>\n): snapshot is DesktopFeatureAvailabilitySnapshot<TProduct> {\n return (\n snapshot.product === identity.product &&\n snapshot.platform === identity.platform &&\n snapshot.architecture === identity.architecture &&\n snapshot.currentVersion === identity.currentVersion\n );\n}\n\nexport function isDesktopFeatureSupported(\n snapshot: Pick<DesktopFeatureAvailabilitySnapshot, \"keys\">,\n key: string\n): boolean {\n return isValidDesktopFeatureKey(key) && snapshot.keys.includes(key);\n}\n"],"mappings":";AAOA,IAAM,oBAAoB;AAC1B,IAAM,0BAA0B;AAChC,IAAM,qBAAqB;AAEpB,SAAS,yBAAyB,OAAwB;AAC/D,SACE,MAAM,UAAU,2BAA2B,kBAAkB,KAAK,KAAK;AAE3E;AAEO,SAAS,4BAA4B,OAAmC;AAC7E,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,oBAAoB;AAC9D,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,OAAO,MAAM,IAAI,CAAC,UAAU;AAChC,QAAI,OAAO,UAAU,YAAY,CAAC,yBAAyB,KAAK,GAAG;AACjE,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AACA,WAAO;AAAA,EACT,CAAC;AACD,QAAM,SAAS,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,EAAE,KAAK;AACvC,MAAI,OAAO,WAAW,KAAK,QAAQ;AACjC,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,SAAO,OAAO,OAAO,MAAM;AAC7B;AAEO,SAAS,gCACd,UACmC;AACnC,MAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACA,QAAM,QAAS,SAAqC;AACpD,MAAI,UAAU,QAAW;AACvB,WAAO;AAAA,EACT;AACA,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AAC/D,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AACA,QAAM,SAAS;AACf,MACE,OAAO,KAAK,MAAM,EAAE,KAAK,CAAC,QAAQ,QAAQ,MAAM,KAChD,CAAC,OAAO,UAAU,eAAe,KAAK,QAAQ,MAAM,GACpD;AACA,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AACA,SAAO,OAAO,OAAO;AAAA,IACnB,MAAM,4BAA4B,OAAO,IAAI;AAAA,EAC/C,CAAC;AACH;AAEO,SAAS,0CAGd,UACA,UAC0D;AAC1D,SACE,SAAS,YAAY,SAAS,WAC9B,SAAS,aAAa,SAAS,YAC/B,SAAS,iBAAiB,SAAS,gBACnC,SAAS,mBAAmB,SAAS;AAEzC;AAEO,SAAS,0BACd,UACA,KACS;AACT,SAAO,yBAAyB,GAAG,KAAK,SAAS,KAAK,SAAS,GAAG;AACpE;","names":[]}
@@ -22,12 +22,18 @@ var desktopUpdateAdmissionIpcChannels = {
22
22
  start: "desktop-update-admission:start",
23
23
  state: "desktop-update-admission:state"
24
24
  };
25
+ var desktopFeatureAvailabilityIpcChannels = {
26
+ changed: "desktop-feature-availability:changed",
27
+ getSnapshot: "desktop-feature-availability:get-snapshot",
28
+ isSupported: "desktop-feature-availability:is-supported"
29
+ };
25
30
 
26
31
  export {
27
32
  desktopUpdatePolicies,
28
33
  desktopUpdateChannels,
29
34
  desktopUpdateStatuses,
30
35
  desktopProducts,
31
- desktopUpdateAdmissionIpcChannels
36
+ desktopUpdateAdmissionIpcChannels,
37
+ desktopFeatureAvailabilityIpcChannels
32
38
  };
33
- //# sourceMappingURL=chunk-TRALK3GT.js.map
39
+ //# sourceMappingURL=chunk-BESTTWBK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/contracts/index.ts"],"sourcesContent":["export const desktopUpdatePolicies = [\"off\", \"prompt\", \"auto\"] as const;\nexport type DesktopUpdatePolicy = (typeof desktopUpdatePolicies)[number];\n\nexport const desktopUpdateChannels = [\"stable\", \"rc\"] as const;\nexport type DesktopUpdateChannel = (typeof desktopUpdateChannels)[number];\n\nexport const desktopUpdateStatuses = [\n \"disabled\",\n \"unsupported\",\n \"idle\",\n \"checking\",\n \"available\",\n \"downloading\",\n \"downloaded\",\n \"up_to_date\",\n \"error\"\n] as const;\nexport type DesktopUpdateStatus = (typeof desktopUpdateStatuses)[number];\n\nexport interface ConfigureDesktopUpdatesInput {\n channel: DesktopUpdateChannel;\n policy: DesktopUpdatePolicy;\n}\n\nexport interface DesktopUpdateState {\n channel: DesktopUpdateChannel;\n checkedAt: string | null;\n currentVersion: string;\n downloadedBytes: number | null;\n downloadPercent: number | null;\n latestVersion: string | null;\n message: string | null;\n policy: DesktopUpdatePolicy;\n releaseDate: string | null;\n releaseName: string | null;\n releaseNotesUrl: string | null;\n status: DesktopUpdateStatus;\n totalBytes: number | null;\n}\n\nexport const desktopProducts = [\"tsh-desktop\", \"tutti-desktop\"] as const;\nexport type DesktopProduct = (typeof desktopProducts)[number];\nexport type DesktopPlatform = \"macos\" | \"windows\" | \"linux\";\nexport type DesktopArchitecture = \"arm64\" | \"x64\";\n\nexport interface MinimumVersionCheckRequest<\n TProduct extends DesktopProduct = DesktopProduct\n> {\n product: TProduct;\n platform: DesktopPlatform;\n architecture: DesktopArchitecture;\n currentVersion: string;\n}\n\nexport type MinimumVersionDecision =\n | \"allowed\"\n | \"upgradeRequired\"\n | \"notApplicable\";\n\nexport interface MinimumVersionCheckResponse<\n TProduct extends DesktopProduct = DesktopProduct\n> extends MinimumVersionCheckRequest<TProduct> {\n channel: DesktopUpdateChannel | \"unmanaged\";\n minimumVersion: string;\n decision: MinimumVersionDecision;\n reason:\n | \"unmanagedPrerelease\"\n | \"productDisabled\"\n | \"unsupportedRelease\"\n | \"belowMinimum\"\n | \"meetsMinimum\";\n policySource: \"\" | \"defaultMinimum\" | \"platformOverride\";\n policyRevision: string;\n featureAvailability?: DesktopFeatureAvailability;\n}\n\nexport interface DesktopFeatureAvailability {\n keys: readonly string[];\n}\n\nexport type DesktopFeatureAvailabilitySource = \"remote\" | \"cache\" | \"empty\";\n\nexport interface DesktopFeatureAvailabilitySnapshot<\n TProduct extends DesktopProduct = DesktopProduct\n> extends MinimumVersionCheckRequest<TProduct> {\n policyRevision: string | null;\n fetchedAt: string | null;\n source: DesktopFeatureAvailabilitySource;\n keys: readonly string[];\n}\n\nexport interface DesktopFeatureAvailabilityRuntime<\n TProduct extends DesktopProduct = DesktopProduct\n> {\n getSnapshot(): DesktopFeatureAvailabilitySnapshot<TProduct>;\n isSupported(key: string): boolean;\n subscribe(\n listener: (snapshot: DesktopFeatureAvailabilitySnapshot<TProduct>) => void\n ): () => void;\n}\n\nexport interface DesktopFeatureAvailabilityApi {\n getSnapshot(): Promise<DesktopFeatureAvailabilitySnapshot>;\n isSupported(key: string): Promise<boolean>;\n onChanged(\n listener: (snapshot: DesktopFeatureAvailabilitySnapshot) => void\n ): () => void;\n}\n\nexport type MinimumVersionUpgradePhase =\n | \"blocked\"\n | \"checking\"\n | \"ready\"\n | \"downloading\"\n | \"downloaded\"\n | \"simulationComplete\"\n | \"error\"\n | \"released\";\n\nexport type MinimumVersionUpgradeError =\n | \"releaseBelowMinimum\"\n | \"updateUnavailable\"\n | \"policyCheckFailed\"\n | \"installFailed\"\n | \"updateFailed\";\n\nexport interface MinimumVersionUpgradeState<\n TProduct extends DesktopProduct = DesktopProduct\n> {\n phase: MinimumVersionUpgradePhase;\n check: MinimumVersionCheckResponse<TProduct>;\n update: DesktopUpdateState;\n message: MinimumVersionUpgradeError | null;\n}\n\nexport interface MandatoryDesktopUpdateTarget {\n channel: DesktopUpdateChannel;\n minimumVersion: string;\n policyRevision: string;\n}\n\nexport interface MandatoryDesktopUpdateSession {\n retarget(input: MandatoryDesktopUpdateTarget): void;\n prepare(): Promise<DesktopUpdateState>;\n downloadUpdate(): Promise<DesktopUpdateState>;\n installUpdate(): Promise<void>;\n release(options?: { restoreNormal?: boolean }): Promise<void>;\n}\n\nexport interface MinimumVersionAppUpdateService {\n getState(): DesktopUpdateState;\n acquireMandatorySession(\n input: MandatoryDesktopUpdateTarget\n ): Promise<MandatoryDesktopUpdateSession>;\n subscribe(listener: (state: DesktopUpdateState) => void): () => void;\n}\n\nexport interface DesktopUpdateAdmissionRuntime {\n checksEnabled: boolean;\n currentVersion: string;\n development: boolean;\n foregroundCheckIntervalMs: number;\n}\n\nexport const desktopUpdateAdmissionIpcChannels = {\n exit: \"desktop-update-admission:exit\",\n getState: \"desktop-update-admission:get-state\",\n later: \"desktop-update-admission:later\",\n manualDownload: \"desktop-update-admission:manual-download\",\n retry: \"desktop-update-admission:retry\",\n start: \"desktop-update-admission:start\",\n state: \"desktop-update-admission:state\"\n} as const;\n\nexport const desktopFeatureAvailabilityIpcChannels = {\n changed: \"desktop-feature-availability:changed\",\n getSnapshot: \"desktop-feature-availability:get-snapshot\",\n isSupported: \"desktop-feature-availability:is-supported\"\n} as const;\n\nexport interface DesktopMinimumVersionApi {\n getState(): Promise<MinimumVersionUpgradeState | null>;\n start(): Promise<MinimumVersionUpgradeState | null>;\n retry(): Promise<MinimumVersionUpgradeState | null>;\n later(): Promise<void>;\n openManualDownload(): Promise<void>;\n exit(): Promise<void>;\n onState(listener: (state: MinimumVersionUpgradeState) => void): () => void;\n}\n"],"mappings":";AAAO,IAAM,wBAAwB,CAAC,OAAO,UAAU,MAAM;AAGtD,IAAM,wBAAwB,CAAC,UAAU,IAAI;AAG7C,IAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAwBO,IAAM,kBAAkB,CAAC,eAAe,eAAe;AA4HvD,IAAM,oCAAoC;AAAA,EAC/C,MAAM;AAAA,EACN,UAAU;AAAA,EACV,OAAO;AAAA,EACP,gBAAgB;AAAA,EAChB,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACT;AAEO,IAAM,wCAAwC;AAAA,EACnD,SAAS;AAAA,EACT,aAAa;AAAA,EACb,aAAa;AACf;","names":[]}
@@ -0,0 +1,185 @@
1
+ import {
2
+ desktopFeatureAvailabilityIdentityMatches,
3
+ isDesktopFeatureSupported,
4
+ normalizeDesktopFeatureKeys,
5
+ parseDesktopFeatureAvailability
6
+ } from "./chunk-2GH725V2.js";
7
+
8
+ // src/feature-availability/runtime.ts
9
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
10
+ import { dirname } from "node:path";
11
+ var cacheSchemaVersion = "tutti.desktop-feature-availability-cache.v1";
12
+ function log(logger, level, details) {
13
+ logger[level](`[desktop-feature-availability] ${JSON.stringify(details)}`);
14
+ }
15
+ function emptySnapshot(identity) {
16
+ return Object.freeze({
17
+ ...identity,
18
+ fetchedAt: null,
19
+ keys: Object.freeze([]),
20
+ policyRevision: null,
21
+ source: "empty"
22
+ });
23
+ }
24
+ function parseCacheDocument(raw, identity) {
25
+ const value = JSON.parse(raw);
26
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
27
+ throw new Error("feature availability cache must be an object");
28
+ }
29
+ const record = value;
30
+ if (record.schemaVersion !== cacheSchemaVersion || typeof record.product !== "string" || typeof record.platform !== "string" || typeof record.architecture !== "string" || typeof record.currentVersion !== "string" || typeof record.policyRevision !== "string" || !record.policyRevision.trim() || typeof record.fetchedAt !== "string" || !record.fetchedAt.trim()) {
31
+ throw new Error("feature availability cache has invalid metadata");
32
+ }
33
+ const snapshot = {
34
+ architecture: record.architecture,
35
+ currentVersion: record.currentVersion,
36
+ fetchedAt: record.fetchedAt,
37
+ keys: normalizeDesktopFeatureKeys(record.keys),
38
+ platform: record.platform,
39
+ policyRevision: record.policyRevision,
40
+ product: record.product,
41
+ source: "cache"
42
+ };
43
+ if (!desktopFeatureAvailabilityIdentityMatches(snapshot, identity)) {
44
+ return null;
45
+ }
46
+ return Object.freeze(snapshot);
47
+ }
48
+ async function loadCache(cacheFilePath, identity, logger) {
49
+ try {
50
+ const raw = await readFile(cacheFilePath, "utf8");
51
+ const cached = parseCacheDocument(raw, identity);
52
+ if (!cached) {
53
+ log(logger, "info", {
54
+ result: "ignored",
55
+ stage: "cache-read",
56
+ reason: "identityMismatch"
57
+ });
58
+ return emptySnapshot(identity);
59
+ }
60
+ log(logger, "info", {
61
+ count: cached.keys.length,
62
+ policyRevision: cached.policyRevision,
63
+ result: "success",
64
+ stage: "cache-read"
65
+ });
66
+ return cached;
67
+ } catch (error) {
68
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
69
+ if (code !== "ENOENT") {
70
+ log(logger, "error", {
71
+ error: error instanceof Error ? error.message : String(error),
72
+ result: "failure",
73
+ stage: "cache-read"
74
+ });
75
+ }
76
+ return emptySnapshot(identity);
77
+ }
78
+ }
79
+ async function writeCache(cacheFilePath, snapshot) {
80
+ await mkdir(dirname(cacheFilePath), { recursive: true });
81
+ const temporaryPath = `${cacheFilePath}.${process.pid}.${Date.now()}.tmp`;
82
+ const document = {
83
+ architecture: snapshot.architecture,
84
+ currentVersion: snapshot.currentVersion,
85
+ fetchedAt: snapshot.fetchedAt,
86
+ keys: snapshot.keys,
87
+ platform: snapshot.platform,
88
+ policyRevision: snapshot.policyRevision,
89
+ product: snapshot.product,
90
+ schemaVersion: cacheSchemaVersion
91
+ };
92
+ try {
93
+ await writeFile(temporaryPath, `${JSON.stringify(document)}
94
+ `, {
95
+ encoding: "utf8",
96
+ mode: 384
97
+ });
98
+ await rename(temporaryPath, cacheFilePath);
99
+ } catch (error) {
100
+ await rm(temporaryPath, { force: true }).catch(() => void 0);
101
+ throw error;
102
+ }
103
+ }
104
+ async function createDesktopFeatureAvailabilityRuntime(input) {
105
+ let snapshot = await loadCache(
106
+ input.cacheFilePath,
107
+ input.identity,
108
+ input.logger
109
+ );
110
+ const listeners = /* @__PURE__ */ new Set();
111
+ let pendingWrite = Promise.resolve();
112
+ let disposed = false;
113
+ return {
114
+ acceptRemoteResponse(response, policyRevision) {
115
+ if (disposed) {
116
+ return;
117
+ }
118
+ const availability = parseDesktopFeatureAvailability(response);
119
+ if (!availability) {
120
+ log(input.logger, "info", {
121
+ result: "retained",
122
+ stage: "remote-response",
123
+ reason: "featureAvailabilityMissing"
124
+ });
125
+ return;
126
+ }
127
+ const next = Object.freeze({
128
+ ...input.identity,
129
+ fetchedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
130
+ keys: availability.keys,
131
+ policyRevision,
132
+ source: "remote"
133
+ });
134
+ snapshot = next;
135
+ for (const listener of listeners) {
136
+ try {
137
+ listener(next);
138
+ } catch (error) {
139
+ log(input.logger, "error", {
140
+ error: error instanceof Error ? error.message : String(error),
141
+ result: "failure",
142
+ stage: "subscriber-notify"
143
+ });
144
+ }
145
+ }
146
+ pendingWrite = pendingWrite.then(() => writeCache(input.cacheFilePath, next)).then(() => {
147
+ log(input.logger, "info", {
148
+ count: next.keys.length,
149
+ policyRevision,
150
+ result: "success",
151
+ stage: "cache-write"
152
+ });
153
+ }).catch((error) => {
154
+ log(input.logger, "error", {
155
+ error: error instanceof Error ? error.message : String(error),
156
+ result: "failure",
157
+ stage: "cache-write"
158
+ });
159
+ });
160
+ },
161
+ async dispose() {
162
+ disposed = true;
163
+ listeners.clear();
164
+ await pendingWrite;
165
+ },
166
+ getSnapshot() {
167
+ return snapshot;
168
+ },
169
+ isSupported(key) {
170
+ return isDesktopFeatureSupported(snapshot, key);
171
+ },
172
+ subscribe(listener) {
173
+ if (disposed) {
174
+ return () => void 0;
175
+ }
176
+ listeners.add(listener);
177
+ return () => listeners.delete(listener);
178
+ }
179
+ };
180
+ }
181
+
182
+ export {
183
+ createDesktopFeatureAvailabilityRuntime
184
+ };
185
+ //# sourceMappingURL=chunk-DBINBWFF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/feature-availability/runtime.ts"],"sourcesContent":["import { mkdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type {\n DesktopFeatureAvailabilityRuntime,\n DesktopFeatureAvailabilitySnapshot,\n DesktopProduct,\n MinimumVersionCheckRequest\n} from \"../contracts/index.ts\";\nimport {\n desktopFeatureAvailabilityIdentityMatches,\n isDesktopFeatureSupported,\n normalizeDesktopFeatureKeys,\n parseDesktopFeatureAvailability\n} from \"./core.ts\";\n\nconst cacheSchemaVersion = \"tutti.desktop-feature-availability-cache.v1\";\n\nexport interface DesktopFeatureAvailabilityLogger {\n info(message: string): void;\n error(message: string): void;\n}\n\nexport interface MutableDesktopFeatureAvailabilityRuntime<\n TProduct extends DesktopProduct = DesktopProduct\n> extends DesktopFeatureAvailabilityRuntime<TProduct> {\n acceptRemoteResponse(response: unknown, policyRevision: string): void;\n dispose(): Promise<void>;\n}\n\ninterface CacheDocument {\n schemaVersion: typeof cacheSchemaVersion;\n product: DesktopProduct;\n platform: MinimumVersionCheckRequest[\"platform\"];\n architecture: MinimumVersionCheckRequest[\"architecture\"];\n currentVersion: string;\n policyRevision: string;\n fetchedAt: string;\n keys: readonly string[];\n}\n\nfunction log(\n logger: DesktopFeatureAvailabilityLogger,\n level: \"info\" | \"error\",\n details: Record<string, unknown>\n): void {\n logger[level](`[desktop-feature-availability] ${JSON.stringify(details)}`);\n}\n\nfunction emptySnapshot<TProduct extends DesktopProduct>(\n identity: MinimumVersionCheckRequest<TProduct>\n): DesktopFeatureAvailabilitySnapshot<TProduct> {\n return Object.freeze({\n ...identity,\n fetchedAt: null,\n keys: Object.freeze([]),\n policyRevision: null,\n source: \"empty\"\n });\n}\n\nfunction parseCacheDocument<TProduct extends DesktopProduct>(\n raw: string,\n identity: MinimumVersionCheckRequest<TProduct>\n): DesktopFeatureAvailabilitySnapshot<TProduct> | null {\n const value: unknown = JSON.parse(raw);\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(\"feature availability cache must be an object\");\n }\n const record = value as Record<string, unknown>;\n if (\n record.schemaVersion !== cacheSchemaVersion ||\n typeof record.product !== \"string\" ||\n typeof record.platform !== \"string\" ||\n typeof record.architecture !== \"string\" ||\n typeof record.currentVersion !== \"string\" ||\n typeof record.policyRevision !== \"string\" ||\n !record.policyRevision.trim() ||\n typeof record.fetchedAt !== \"string\" ||\n !record.fetchedAt.trim()\n ) {\n throw new Error(\"feature availability cache has invalid metadata\");\n }\n const snapshot: DesktopFeatureAvailabilitySnapshot = {\n architecture:\n record.architecture as MinimumVersionCheckRequest[\"architecture\"],\n currentVersion: record.currentVersion,\n fetchedAt: record.fetchedAt,\n keys: normalizeDesktopFeatureKeys(record.keys),\n platform: record.platform as MinimumVersionCheckRequest[\"platform\"],\n policyRevision: record.policyRevision,\n product: record.product as DesktopProduct,\n source: \"cache\"\n };\n if (!desktopFeatureAvailabilityIdentityMatches(snapshot, identity)) {\n return null;\n }\n return Object.freeze(snapshot);\n}\n\nasync function loadCache<TProduct extends DesktopProduct>(\n cacheFilePath: string,\n identity: MinimumVersionCheckRequest<TProduct>,\n logger: DesktopFeatureAvailabilityLogger\n): Promise<DesktopFeatureAvailabilitySnapshot<TProduct>> {\n try {\n const raw = await readFile(cacheFilePath, \"utf8\");\n const cached = parseCacheDocument(raw, identity);\n if (!cached) {\n log(logger, \"info\", {\n result: \"ignored\",\n stage: \"cache-read\",\n reason: \"identityMismatch\"\n });\n return emptySnapshot(identity);\n }\n log(logger, \"info\", {\n count: cached.keys.length,\n policyRevision: cached.policyRevision,\n result: \"success\",\n stage: \"cache-read\"\n });\n return cached;\n } catch (error) {\n const code =\n error && typeof error === \"object\" && \"code\" in error\n ? String(error.code)\n : \"\";\n if (code !== \"ENOENT\") {\n log(logger, \"error\", {\n error: error instanceof Error ? error.message : String(error),\n result: \"failure\",\n stage: \"cache-read\"\n });\n }\n return emptySnapshot(identity);\n }\n}\n\nasync function writeCache(\n cacheFilePath: string,\n snapshot: DesktopFeatureAvailabilitySnapshot\n): Promise<void> {\n await mkdir(dirname(cacheFilePath), { recursive: true });\n const temporaryPath = `${cacheFilePath}.${process.pid}.${Date.now()}.tmp`;\n const document: CacheDocument = {\n architecture: snapshot.architecture,\n currentVersion: snapshot.currentVersion,\n fetchedAt: snapshot.fetchedAt!,\n keys: snapshot.keys,\n platform: snapshot.platform,\n policyRevision: snapshot.policyRevision!,\n product: snapshot.product,\n schemaVersion: cacheSchemaVersion\n };\n try {\n await writeFile(temporaryPath, `${JSON.stringify(document)}\\n`, {\n encoding: \"utf8\",\n mode: 0o600\n });\n await rename(temporaryPath, cacheFilePath);\n } catch (error) {\n await rm(temporaryPath, { force: true }).catch(() => undefined);\n throw error;\n }\n}\n\nexport async function createDesktopFeatureAvailabilityRuntime<\n TProduct extends DesktopProduct\n>(input: {\n cacheFilePath: string;\n identity: MinimumVersionCheckRequest<TProduct>;\n logger: DesktopFeatureAvailabilityLogger;\n now?: () => Date;\n}): Promise<MutableDesktopFeatureAvailabilityRuntime<TProduct>> {\n let snapshot = await loadCache(\n input.cacheFilePath,\n input.identity,\n input.logger\n );\n const listeners = new Set<\n (value: DesktopFeatureAvailabilitySnapshot<TProduct>) => void\n >();\n let pendingWrite: Promise<void> = Promise.resolve();\n let disposed = false;\n\n return {\n acceptRemoteResponse(response, policyRevision) {\n if (disposed) {\n return;\n }\n const availability = parseDesktopFeatureAvailability(response);\n if (!availability) {\n log(input.logger, \"info\", {\n result: \"retained\",\n stage: \"remote-response\",\n reason: \"featureAvailabilityMissing\"\n });\n return;\n }\n const next: DesktopFeatureAvailabilitySnapshot<TProduct> = Object.freeze({\n ...input.identity,\n fetchedAt: (input.now?.() ?? new Date()).toISOString(),\n keys: availability.keys,\n policyRevision,\n source: \"remote\"\n });\n snapshot = next;\n for (const listener of listeners) {\n try {\n listener(next);\n } catch (error) {\n log(input.logger, \"error\", {\n error: error instanceof Error ? error.message : String(error),\n result: \"failure\",\n stage: \"subscriber-notify\"\n });\n }\n }\n pendingWrite = pendingWrite\n .then(() => writeCache(input.cacheFilePath, next))\n .then(() => {\n log(input.logger, \"info\", {\n count: next.keys.length,\n policyRevision,\n result: \"success\",\n stage: \"cache-write\"\n });\n })\n .catch((error) => {\n log(input.logger, \"error\", {\n error: error instanceof Error ? error.message : String(error),\n result: \"failure\",\n stage: \"cache-write\"\n });\n });\n },\n async dispose() {\n disposed = true;\n listeners.clear();\n await pendingWrite;\n },\n getSnapshot() {\n return snapshot;\n },\n isSupported(key) {\n return isDesktopFeatureSupported(snapshot, key);\n },\n subscribe(listener) {\n if (disposed) {\n return () => undefined;\n }\n listeners.add(listener);\n return () => listeners.delete(listener);\n }\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  validateMinimumVersionResponse
3
3
  } from "./chunk-LV67FBCG.js";
4
+ import {
5
+ normalizeDesktopFeatureKeys
6
+ } from "./chunk-2GH725V2.js";
4
7
 
5
8
  // src/development/environment.ts
6
9
  var desktopUpdateAdmissionDevelopmentEnvironment = {
@@ -8,6 +11,7 @@ var desktopUpdateAdmissionDevelopmentEnvironment = {
8
11
  development: "DESKTOP_UPDATE_ADMISSION_DEV",
9
12
  download: "DESKTOP_UPDATE_ADMISSION_DOWNLOAD",
10
13
  foregroundIntervalMs: "DESKTOP_UPDATE_ADMISSION_FOREGROUND_INTERVAL_MS",
14
+ featureKeys: "DESKTOP_UPDATE_ADMISSION_FEATURE_KEYS",
11
15
  install: "DESKTOP_UPDATE_ADMISSION_INSTALL",
12
16
  latestVersion: "DESKTOP_UPDATE_ADMISSION_LATEST_VERSION",
13
17
  minimumVersion: "DESKTOP_UPDATE_ADMISSION_MINIMUM_VERSION",
@@ -108,6 +112,15 @@ function compareDevelopmentManagedVersions(left, right) {
108
112
  }
109
113
 
110
114
  // src/development/policyScenario.ts
115
+ function resolveFeatureKeys(env) {
116
+ const raw = env[desktopUpdateAdmissionDevelopmentEnvironment.featureKeys]?.trim();
117
+ if (!raw) {
118
+ return Object.freeze([]);
119
+ }
120
+ return normalizeDesktopFeatureKeys(
121
+ raw.split(",").map((value) => value.trim())
122
+ );
123
+ }
111
124
  function parseOutcome(value) {
112
125
  switch (value) {
113
126
  case "allowed":
@@ -300,9 +313,11 @@ function resolveDesktopUpdateDevelopmentPolicyScenario(input) {
300
313
  return null;
301
314
  }
302
315
  const scenario = {
316
+ featureKeys: resolveFeatureKeys(input.env),
303
317
  policySteps: resolvePolicySteps(input.env)
304
318
  };
305
319
  return Object.freeze({
320
+ featureKeys: scenario.featureKeys,
306
321
  policySteps: Object.freeze(
307
322
  scenario.policySteps.map(
308
323
  (step) => Object.freeze(
@@ -345,11 +360,14 @@ function waitForAbort(signal) {
345
360
  });
346
361
  });
347
362
  }
348
- function responseForStep(request, step, revision) {
363
+ function responseForStep(request, step, revision, featureKeys) {
349
364
  const channel = developmentChannel(request.currentVersion);
350
365
  const base = {
351
366
  ...request,
352
367
  channel,
368
+ featureAvailability: {
369
+ keys: featureKeys
370
+ },
353
371
  minimumVersion: "",
354
372
  policyRevision: `development-policy-${revision}`,
355
373
  policySource: ""
@@ -415,6 +433,7 @@ function responseForStep(request, step, revision) {
415
433
  }
416
434
  function createDevelopmentMinimumVersionChecker(policy, options = {}) {
417
435
  const checkIndexes = /* @__PURE__ */ new Map();
436
+ const policyFeatureKeys = policy.featureKeys;
418
437
  return async (request, signal) => {
419
438
  if (options.expectedCurrentVersion && request.currentVersion !== options.expectedCurrentVersion) {
420
439
  throw new Error(
@@ -437,10 +456,10 @@ function createDevelopmentMinimumVersionChecker(policy, options = {}) {
437
456
  throw new Error(step.message);
438
457
  }
439
458
  validateDevelopmentPolicyScenarioForCurrentVersion(
440
- { policySteps: [step] },
459
+ { featureKeys: policyFeatureKeys, policySteps: [step] },
441
460
  request.currentVersion
442
461
  );
443
- return responseForStep(request, step, stepIndex + 1);
462
+ return responseForStep(request, step, stepIndex + 1, policyFeatureKeys);
444
463
  };
445
464
  }
446
465
 
@@ -600,4 +619,4 @@ export {
600
619
  createDevelopmentMinimumVersionChecker,
601
620
  startDesktopUpdateDevelopmentMockServer
602
621
  };
603
- //# sourceMappingURL=chunk-ZQTIYDGZ.js.map
622
+ //# sourceMappingURL=chunk-K55GAWZZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/development/environment.ts","../src/development/version.ts","../src/development/policyScenario.ts","../src/development/policyChecker.ts","../src/development/mockServer.ts"],"sourcesContent":["export const desktopUpdateAdmissionDevelopmentEnvironment = {\n currentVersion: \"DESKTOP_UPDATE_ADMISSION_CURRENT_VERSION\",\n development: \"DESKTOP_UPDATE_ADMISSION_DEV\",\n download: \"DESKTOP_UPDATE_ADMISSION_DOWNLOAD\",\n foregroundIntervalMs: \"DESKTOP_UPDATE_ADMISSION_FOREGROUND_INTERVAL_MS\",\n featureKeys: \"DESKTOP_UPDATE_ADMISSION_FEATURE_KEYS\",\n install: \"DESKTOP_UPDATE_ADMISSION_INSTALL\",\n latestVersion: \"DESKTOP_UPDATE_ADMISSION_LATEST_VERSION\",\n minimumVersion: \"DESKTOP_UPDATE_ADMISSION_MINIMUM_VERSION\",\n mockServerUrl: \"DESKTOP_UPDATE_ADMISSION_MOCK_SERVER_URL\",\n policy: \"DESKTOP_UPDATE_ADMISSION_POLICY\",\n policySequence: \"DESKTOP_UPDATE_ADMISSION_POLICY_SEQUENCE\",\n scenario: \"DESKTOP_UPDATE_ADMISSION_SCENARIO\",\n transport: \"DESKTOP_UPDATE_ADMISSION_TRANSPORT\",\n updater: \"DESKTOP_UPDATE_ADMISSION_UPDATER\"\n} as const;\n\nexport function invalidDevelopmentScenario(message: string): never {\n throw new Error(`invalid desktop update development scenario: ${message}`);\n}\n\nexport function readRequiredDevelopmentEnvironment(\n env: Readonly<Record<string, string | undefined>>,\n name: string\n): string {\n const value = env[name]?.trim();\n if (!value) {\n return invalidDevelopmentScenario(`${name} is required`);\n }\n return value;\n}\n\nexport function readDevelopmentEnabled(\n env: Readonly<Record<string, string | undefined>>\n): boolean {\n const name = desktopUpdateAdmissionDevelopmentEnvironment.development;\n const value = env[name]?.trim().toLowerCase();\n if (!value || [\"0\", \"false\", \"no\", \"off\"].includes(value)) {\n return false;\n }\n if ([\"1\", \"true\", \"yes\", \"on\"].includes(value)) {\n return true;\n }\n return invalidDevelopmentScenario(`${name} must be a boolean flag`);\n}\n","import type { DesktopUpdateChannel } from \"../contracts/index.ts\";\nimport { invalidDevelopmentScenario } from \"./environment.ts\";\n\nconst strictSemVerPattern =\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$/u;\nconst managedStablePattern =\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$/u;\nconst managedRcPattern =\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)-rc\\.(0|[1-9]\\d*)$/u;\n\nexport type DevelopmentManagedVersion = {\n channel: DesktopUpdateChannel;\n core: [string, string, string];\n rc: string | null;\n};\n\nexport function validateStrictDevelopmentSemVer(\n value: string,\n name: string\n): string {\n const normalized = value.trim();\n const match = strictSemVerPattern.exec(normalized);\n if (!match) {\n return invalidDevelopmentScenario(`${name} must be valid SemVer`);\n }\n const prerelease = match[4];\n if (prerelease) {\n for (const identifier of prerelease.split(\".\")) {\n if (\n /^\\d+$/u.test(identifier) &&\n identifier.length > 1 &&\n identifier[0] === \"0\"\n ) {\n return invalidDevelopmentScenario(`${name} must be valid SemVer`);\n }\n }\n }\n return normalized;\n}\n\nexport function parseDevelopmentManagedVersion(\n value: string\n): DevelopmentManagedVersion | null {\n const stable = managedStablePattern.exec(value);\n if (stable) {\n return {\n channel: \"stable\",\n core: [stable[1]!, stable[2]!, stable[3]!],\n rc: null\n };\n }\n const rc = managedRcPattern.exec(value);\n if (!rc) {\n return null;\n }\n return {\n channel: \"rc\",\n core: [rc[1]!, rc[2]!, rc[3]!],\n rc: rc[4]!\n };\n}\n\nfunction compareNumericIdentifier(left: string, right: string): number {\n if (left.length !== right.length) {\n return left.length < right.length ? -1 : 1;\n }\n return left === right ? 0 : left < right ? -1 : 1;\n}\n\nexport function compareDevelopmentManagedVersions(\n left: DevelopmentManagedVersion,\n right: DevelopmentManagedVersion\n): number {\n for (const index of [0, 1, 2] as const) {\n const compared = compareNumericIdentifier(\n left.core[index],\n right.core[index]\n );\n if (compared !== 0) {\n return compared;\n }\n }\n if (left.rc === right.rc) {\n return 0;\n }\n if (left.rc === null) {\n return 1;\n }\n if (right.rc === null) {\n return -1;\n }\n return compareNumericIdentifier(left.rc, right.rc);\n}\n","import {\n desktopUpdateAdmissionDevelopmentEnvironment,\n invalidDevelopmentScenario,\n readDevelopmentEnabled\n} from \"./environment.ts\";\nimport {\n compareDevelopmentManagedVersions,\n parseDevelopmentManagedVersion,\n validateStrictDevelopmentSemVer\n} from \"./version.ts\";\nimport { normalizeDesktopFeatureKeys } from \"../feature-availability/core.ts\";\n\nexport type DesktopUpdateDevelopmentPolicyOutcome =\n | \"allowed\"\n | \"upgradeRequired\"\n | \"disabled\"\n | \"unsupported\"\n | \"unmanagedPrerelease\"\n | \"error\"\n | \"timeout\";\n\nexport type DesktopUpdateDevelopmentPolicyMinimum =\n | {\n kind: \"configured\";\n version: string;\n }\n | {\n kind: \"requestCurrentVersion\";\n };\n\nexport type DesktopUpdateDevelopmentPolicyStep =\n | {\n outcome: \"allowed\" | \"upgradeRequired\";\n minimum: DesktopUpdateDevelopmentPolicyMinimum;\n policySource: \"defaultMinimum\" | \"platformOverride\";\n }\n | {\n outcome: \"disabled\" | \"unsupported\" | \"unmanagedPrerelease\";\n }\n | {\n outcome: \"error\";\n message: string;\n }\n | {\n outcome: \"timeout\";\n };\n\nexport interface DesktopUpdateDevelopmentPolicyScenario {\n featureKeys: readonly string[];\n policySteps: readonly DesktopUpdateDevelopmentPolicyStep[];\n}\n\nfunction resolveFeatureKeys(\n env: Readonly<Record<string, string | undefined>>\n): readonly string[] {\n const raw =\n env[desktopUpdateAdmissionDevelopmentEnvironment.featureKeys]?.trim();\n if (!raw) {\n return Object.freeze([]);\n }\n return normalizeDesktopFeatureKeys(\n raw.split(\",\").map((value) => value.trim())\n );\n}\n\nfunction parseOutcome(value: string): DesktopUpdateDevelopmentPolicyOutcome {\n switch (value) {\n case \"allowed\":\n case \"upgradeRequired\":\n case \"disabled\":\n case \"unsupported\":\n case \"unmanagedPrerelease\":\n case \"error\":\n case \"timeout\":\n return value;\n default:\n return invalidDevelopmentScenario(\n `unknown policy outcome ${JSON.stringify(value)}`\n );\n }\n}\n\nfunction configuredMinimum(\n value: string,\n name: string\n): DesktopUpdateDevelopmentPolicyMinimum {\n const version = validateStrictDevelopmentSemVer(value, name);\n if (!parseDevelopmentManagedVersion(version)) {\n return invalidDevelopmentScenario(\n `${name} must be a managed stable or RC version`\n );\n }\n return { kind: \"configured\", version };\n}\n\nfunction createPolicyStep(\n token: string,\n fallbackMinimumVersion: string | undefined\n): DesktopUpdateDevelopmentPolicyStep {\n const [rawOutcome, rawMinimumVersion, ...extra] = token.split(\"@\");\n if (!rawOutcome || extra.length > 0) {\n return invalidDevelopmentScenario(\n `invalid policy step ${JSON.stringify(token)}`\n );\n }\n const outcome = parseOutcome(rawOutcome.trim());\n if (outcome === \"allowed\" || outcome === \"upgradeRequired\") {\n return {\n minimum: configuredMinimum(\n rawMinimumVersion?.trim() || fallbackMinimumVersion || \"\",\n desktopUpdateAdmissionDevelopmentEnvironment.minimumVersion\n ),\n outcome,\n policySource: \"defaultMinimum\"\n };\n }\n if (rawMinimumVersion !== undefined) {\n return invalidDevelopmentScenario(\n `policy outcome ${outcome} must not include a minimum version`\n );\n }\n if (outcome === \"error\") {\n return {\n message: \"Development minimum-version policy check failed\",\n outcome\n };\n }\n return { outcome };\n}\n\nfunction createPresetPolicySteps(\n name: string,\n minimumVersion: string | undefined\n): readonly DesktopUpdateDevelopmentPolicyStep[] {\n const requiredMinimum = (): DesktopUpdateDevelopmentPolicyMinimum =>\n configuredMinimum(\n minimumVersion || \"\",\n desktopUpdateAdmissionDevelopmentEnvironment.minimumVersion\n );\n switch (name) {\n case \"startup-force-success\":\n case \"startup-updater-unavailable\":\n case \"startup-target-below-minimum\":\n case \"startup-download-error\":\n return [\n {\n minimum: requiredMinimum(),\n outcome: \"upgradeRequired\",\n policySource: \"defaultMinimum\"\n }\n ];\n case \"startup-policy-timeout\":\n return [{ outcome: \"timeout\" }];\n case \"retry-policy-released\":\n return [\n {\n minimum: requiredMinimum(),\n outcome: \"upgradeRequired\",\n policySource: \"defaultMinimum\"\n },\n { outcome: \"disabled\" }\n ];\n case \"foreground-upgrade-required\":\n return [\n {\n minimum: { kind: \"requestCurrentVersion\" },\n outcome: \"allowed\",\n policySource: \"defaultMinimum\"\n },\n {\n minimum: requiredMinimum(),\n outcome: \"upgradeRequired\",\n policySource: \"defaultMinimum\"\n }\n ];\n default:\n return invalidDevelopmentScenario(\n `unknown named scenario ${JSON.stringify(name)}`\n );\n }\n}\n\nfunction resolvePolicySteps(\n env: Readonly<Record<string, string | undefined>>\n): readonly DesktopUpdateDevelopmentPolicyStep[] {\n const names = desktopUpdateAdmissionDevelopmentEnvironment;\n const fallbackMinimumVersion = env[names.minimumVersion]?.trim();\n const preset = env[names.scenario]?.trim();\n if (preset) {\n for (const conflictingName of [names.policy, names.policySequence]) {\n if (env[conflictingName]?.trim()) {\n return invalidDevelopmentScenario(\n `${names.scenario} and ${conflictingName} are mutually exclusive`\n );\n }\n }\n return createPresetPolicySteps(preset, fallbackMinimumVersion);\n }\n const sequence = env[names.policySequence]?.trim();\n const single = env[names.policy]?.trim();\n if (sequence && single) {\n return invalidDevelopmentScenario(\n `${names.policy} and ${names.policySequence} are mutually exclusive`\n );\n }\n const rawSteps = sequence\n ? sequence.split(\",\").map((value) => value.trim())\n : single\n ? [single]\n : invalidDevelopmentScenario(\n `${names.policy} or ${names.policySequence} is required`\n );\n if (rawSteps.some((value) => value.length === 0)) {\n return invalidDevelopmentScenario(\n `${names.policySequence} contains an empty step`\n );\n }\n return rawSteps.map((token) =>\n createPolicyStep(token, fallbackMinimumVersion)\n );\n}\n\nfunction configuredVersion(\n minimum: DesktopUpdateDevelopmentPolicyMinimum,\n currentVersion: string\n): string {\n return minimum.kind === \"configured\" ? minimum.version : currentVersion;\n}\n\nexport function validateDevelopmentPolicyScenarioForCurrentVersion(\n scenario: DesktopUpdateDevelopmentPolicyScenario,\n currentVersion: string\n): void {\n const normalizedCurrent = validateStrictDevelopmentSemVer(\n currentVersion,\n desktopUpdateAdmissionDevelopmentEnvironment.currentVersion\n );\n const current = parseDevelopmentManagedVersion(normalizedCurrent);\n for (const step of scenario.policySteps) {\n if (step.outcome === \"unmanagedPrerelease\") {\n if (current) {\n invalidDevelopmentScenario(\n \"unmanagedPrerelease requires an unmanaged currentVersion\"\n );\n }\n continue;\n }\n if (step.outcome !== \"allowed\" && step.outcome !== \"upgradeRequired\") {\n if (\n (step.outcome === \"disabled\" || step.outcome === \"unsupported\") &&\n !current\n ) {\n invalidDevelopmentScenario(\n `${step.outcome} requires a managed currentVersion`\n );\n }\n continue;\n }\n if (!current) {\n invalidDevelopmentScenario(\n `${step.outcome} requires a managed currentVersion`\n );\n }\n const minimumVersion = configuredVersion(step.minimum, normalizedCurrent);\n const minimum = parseDevelopmentManagedVersion(minimumVersion);\n if (!minimum || minimum.channel !== current.channel) {\n invalidDevelopmentScenario(\n `minimumVersion must use the ${current.channel} channel`\n );\n }\n const compared = compareDevelopmentManagedVersions(current, minimum);\n if (step.outcome === \"allowed\" && compared < 0) {\n invalidDevelopmentScenario(\n \"allowed requires currentVersion to meet minimumVersion\"\n );\n }\n if (step.outcome === \"upgradeRequired\" && compared >= 0) {\n invalidDevelopmentScenario(\n \"upgradeRequired requires currentVersion below minimumVersion\"\n );\n }\n }\n}\n\nexport function resolveDesktopUpdateDevelopmentPolicyScenario(input: {\n env: Readonly<Record<string, string | undefined>>;\n}): DesktopUpdateDevelopmentPolicyScenario | null {\n if (!readDevelopmentEnabled(input.env)) {\n return null;\n }\n const scenario: DesktopUpdateDevelopmentPolicyScenario = {\n featureKeys: resolveFeatureKeys(input.env),\n policySteps: resolvePolicySteps(input.env)\n };\n return Object.freeze({\n featureKeys: scenario.featureKeys,\n policySteps: Object.freeze(\n scenario.policySteps.map((step) =>\n Object.freeze(\n \"minimum\" in step\n ? {\n ...step,\n minimum: Object.freeze({ ...step.minimum })\n }\n : { ...step }\n )\n )\n )\n });\n}\n","import type {\n DesktopProduct,\n MinimumVersionCheckRequest,\n MinimumVersionCheckResponse\n} from \"../contracts/index.ts\";\nimport { validateMinimumVersionResponse } from \"../core/index.ts\";\nimport type {\n DesktopUpdateDevelopmentPolicyMinimum,\n DesktopUpdateDevelopmentPolicyScenario,\n DesktopUpdateDevelopmentPolicyStep\n} from \"./policyScenario.ts\";\nimport { validateDevelopmentPolicyScenarioForCurrentVersion } from \"./policyScenario.ts\";\n\nfunction developmentChannel(\n currentVersion: string\n): \"stable\" | \"rc\" | \"unmanaged\" {\n if (\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)-rc\\.(0|[1-9]\\d*)$/u.test(\n currentVersion\n )\n ) {\n return \"rc\";\n }\n if (\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$/u.test(\n currentVersion\n )\n ) {\n return \"stable\";\n }\n return \"unmanaged\";\n}\n\nfunction abortError(): Error {\n const error = new Error(\"development minimum-version policy check aborted\");\n error.name = \"AbortError\";\n return error;\n}\n\nfunction waitForAbort(signal: AbortSignal): Promise<never> {\n if (signal.aborted) {\n return Promise.reject(abortError());\n }\n return new Promise<never>((_resolve, reject) => {\n signal.addEventListener(\"abort\", () => reject(abortError()), {\n once: true\n });\n });\n}\n\nfunction responseForStep<TProduct extends DesktopProduct>(\n request: MinimumVersionCheckRequest<TProduct>,\n step: DesktopUpdateDevelopmentPolicyStep,\n revision: number,\n featureKeys: readonly string[]\n): MinimumVersionCheckResponse<TProduct> {\n const channel = developmentChannel(request.currentVersion);\n const base = {\n ...request,\n channel,\n featureAvailability: {\n keys: featureKeys\n },\n minimumVersion: \"\",\n policyRevision: `development-policy-${revision}`,\n policySource: \"\"\n } as const;\n const minimumVersion = (\n minimum: DesktopUpdateDevelopmentPolicyMinimum\n ): string =>\n minimum.kind === \"configured\" ? minimum.version : request.currentVersion;\n switch (step.outcome) {\n case \"allowed\":\n return validateMinimumVersionResponse(\n {\n ...base,\n channel,\n decision: \"allowed\",\n minimumVersion: minimumVersion(step.minimum),\n policySource: step.policySource,\n reason: \"meetsMinimum\"\n },\n request\n );\n case \"upgradeRequired\":\n return validateMinimumVersionResponse(\n {\n ...base,\n channel,\n decision: \"upgradeRequired\",\n minimumVersion: minimumVersion(step.minimum),\n policySource: step.policySource,\n reason: \"belowMinimum\"\n },\n request\n );\n case \"disabled\":\n return validateMinimumVersionResponse(\n {\n ...base,\n decision: \"notApplicable\",\n reason: \"productDisabled\"\n },\n request\n );\n case \"unsupported\":\n return validateMinimumVersionResponse(\n {\n ...base,\n decision: \"notApplicable\",\n reason: \"unsupportedRelease\"\n },\n request\n );\n case \"unmanagedPrerelease\":\n return validateMinimumVersionResponse(\n {\n ...base,\n channel: \"unmanaged\",\n decision: \"notApplicable\",\n reason: \"unmanagedPrerelease\"\n },\n request\n );\n case \"error\":\n case \"timeout\":\n throw new Error(`cannot synthesize ${step.outcome} as a policy response`);\n }\n}\n\nexport function createDevelopmentMinimumVersionChecker(\n policy: DesktopUpdateDevelopmentPolicyScenario,\n options: {\n expectedCurrentVersion?: string;\n } = {}\n): <TProduct extends DesktopProduct>(\n request: MinimumVersionCheckRequest<TProduct>,\n signal: AbortSignal\n) => Promise<MinimumVersionCheckResponse<TProduct>> {\n const checkIndexes = new Map<string, number>();\n const policyFeatureKeys = policy.featureKeys;\n return async <TProduct extends DesktopProduct>(\n request: MinimumVersionCheckRequest<TProduct>,\n signal: AbortSignal\n ): Promise<MinimumVersionCheckResponse<TProduct>> => {\n if (\n options.expectedCurrentVersion &&\n request.currentVersion !== options.expectedCurrentVersion\n ) {\n throw new Error(\n `development policy expected currentVersion ${options.expectedCurrentVersion}, received ${request.currentVersion}`\n );\n }\n const requestIdentity = [\n request.product,\n request.platform,\n request.architecture\n ].join(\":\");\n const checkIndex = checkIndexes.get(requestIdentity) ?? 0;\n const stepIndex = Math.min(checkIndex, policy.policySteps.length - 1);\n const step = policy.policySteps[stepIndex]!;\n checkIndexes.set(requestIdentity, checkIndex + 1);\n if (step.outcome === \"timeout\") {\n return await waitForAbort(signal);\n }\n if (step.outcome === \"error\") {\n throw new Error(step.message);\n }\n validateDevelopmentPolicyScenarioForCurrentVersion(\n { featureKeys: policyFeatureKeys, policySteps: [step] },\n request.currentVersion\n );\n return responseForStep(request, step, stepIndex + 1, policyFeatureKeys);\n };\n}\n","import {\n createServer,\n type IncomingMessage,\n type ServerResponse\n} from \"node:http\";\nimport type {\n DesktopProduct,\n MinimumVersionCheckRequest\n} from \"../contracts/index.ts\";\nimport { createDevelopmentMinimumVersionChecker } from \"./policyChecker.ts\";\nimport type { DesktopUpdateDevelopmentPolicyScenario } from \"./policyScenario.ts\";\n\nconst maximumRequestBodyBytes = 64 * 1_024;\nconst minimumVersionPath = \"/api/desktop/v1/public/desktop-version/check\";\n\nexport interface DesktopUpdateDevelopmentMockServer {\n readonly baseUrl: string;\n close(): Promise<void>;\n}\n\nclass InvalidDevelopmentMockRequestError extends Error {\n public constructor(message: string) {\n super(message);\n this.name = \"InvalidDevelopmentMockRequestError\";\n }\n}\n\nfunction invalidRequest(message: string): never {\n throw new InvalidDevelopmentMockRequestError(message);\n}\n\nfunction writeJson(\n response: ServerResponse,\n statusCode: number,\n value: unknown\n): void {\n const body = JSON.stringify(value);\n response.writeHead(statusCode, {\n \"content-length\": Buffer.byteLength(body),\n \"content-type\": \"application/json; charset=utf-8\"\n });\n response.end(body);\n}\n\nasync function readJson(request: IncomingMessage): Promise<unknown> {\n let size = 0;\n const chunks: Buffer[] = [];\n for await (const value of request) {\n const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);\n size += chunk.byteLength;\n if (size > maximumRequestBodyBytes) {\n return invalidRequest(\"request body exceeds 64 KiB\");\n }\n chunks.push(chunk);\n }\n try {\n return JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n } catch {\n return invalidRequest(\"request body must contain valid JSON\");\n }\n}\n\nfunction normalizeRequest(\n value: unknown\n): MinimumVersionCheckRequest<DesktopProduct> {\n if (!value || typeof value !== \"object\") {\n return invalidRequest(\"request must be an object\");\n }\n const candidate = value as Record<string, unknown>;\n if (\n candidate.product !== \"tsh-desktop\" &&\n candidate.product !== \"tutti-desktop\"\n ) {\n return invalidRequest(\"request has an invalid product\");\n }\n if (\n candidate.platform !== \"macos\" &&\n candidate.platform !== \"windows\" &&\n candidate.platform !== \"linux\"\n ) {\n return invalidRequest(\"request has an invalid platform\");\n }\n if (candidate.architecture !== \"arm64\" && candidate.architecture !== \"x64\") {\n return invalidRequest(\"request has an invalid architecture\");\n }\n if (\n typeof candidate.currentVersion !== \"string\" ||\n candidate.currentVersion.trim() === \"\"\n ) {\n return invalidRequest(\"request has an invalid currentVersion\");\n }\n return {\n architecture: candidate.architecture,\n currentVersion: candidate.currentVersion,\n platform: candidate.platform,\n product: candidate.product\n };\n}\n\nexport async function startDesktopUpdateDevelopmentMockServer(input: {\n port?: number;\n policy: DesktopUpdateDevelopmentPolicyScenario;\n}): Promise<DesktopUpdateDevelopmentMockServer> {\n const checker = createDevelopmentMinimumVersionChecker(input.policy);\n const server = createServer(async (request, response) => {\n if (request.url === \"/healthz\") {\n if (request.method !== \"GET\") {\n response.writeHead(405, { allow: \"GET\" });\n response.end();\n return;\n }\n writeJson(response, 200, { status: \"ok\" });\n return;\n }\n if (request.url !== minimumVersionPath) {\n writeJson(response, 404, { error: \"not found\" });\n return;\n }\n if (request.method !== \"POST\") {\n response.writeHead(405, { allow: \"POST\" });\n response.end();\n return;\n }\n if (\n !String(request.headers[\"content-type\"] ?? \"\")\n .toLowerCase()\n .startsWith(\"application/json\")\n ) {\n writeJson(response, 415, {\n error: \"content-type must be application/json\"\n });\n return;\n }\n const abortController = new AbortController();\n request.once(\"aborted\", () => abortController.abort());\n response.once(\"close\", () => {\n if (!response.writableEnded) {\n abortController.abort();\n }\n });\n try {\n const policyRequest = normalizeRequest(await readJson(request));\n const result = await checker(policyRequest, abortController.signal);\n if (!response.destroyed) {\n writeJson(response, 200, result);\n }\n } catch (error) {\n if (abortController.signal.aborted || response.destroyed) {\n return;\n }\n const message = error instanceof Error ? error.message : String(error);\n const statusCode =\n error instanceof InvalidDevelopmentMockRequestError ? 400 : 500;\n writeJson(response, statusCode, { error: message });\n }\n });\n const port = input.port ?? 0;\n if (!Number.isSafeInteger(port) || port < 0 || port > 65_535) {\n throw new Error(\"development mock server port must be between 0 and 65535\");\n }\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, \"127.0.0.1\", () => {\n server.removeListener(\"error\", reject);\n resolve();\n });\n });\n const address = server.address();\n if (!address || typeof address === \"string\") {\n await new Promise<void>((resolve) => server.close(() => resolve()));\n throw new Error(\"development mock server did not expose a TCP address\");\n }\n return {\n baseUrl: `http://127.0.0.1:${address.port}`,\n close: () =>\n new Promise<void>((resolve, reject) => {\n server.close((error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n })\n };\n}\n"],"mappings":"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","names":[]}
@@ -41,6 +41,27 @@ interface MinimumVersionCheckResponse<TProduct extends DesktopProduct = DesktopP
41
41
  reason: "unmanagedPrerelease" | "productDisabled" | "unsupportedRelease" | "belowMinimum" | "meetsMinimum";
42
42
  policySource: "" | "defaultMinimum" | "platformOverride";
43
43
  policyRevision: string;
44
+ featureAvailability?: DesktopFeatureAvailability;
45
+ }
46
+ interface DesktopFeatureAvailability {
47
+ keys: readonly string[];
48
+ }
49
+ type DesktopFeatureAvailabilitySource = "remote" | "cache" | "empty";
50
+ interface DesktopFeatureAvailabilitySnapshot<TProduct extends DesktopProduct = DesktopProduct> extends MinimumVersionCheckRequest<TProduct> {
51
+ policyRevision: string | null;
52
+ fetchedAt: string | null;
53
+ source: DesktopFeatureAvailabilitySource;
54
+ keys: readonly string[];
55
+ }
56
+ interface DesktopFeatureAvailabilityRuntime<TProduct extends DesktopProduct = DesktopProduct> {
57
+ getSnapshot(): DesktopFeatureAvailabilitySnapshot<TProduct>;
58
+ isSupported(key: string): boolean;
59
+ subscribe(listener: (snapshot: DesktopFeatureAvailabilitySnapshot<TProduct>) => void): () => void;
60
+ }
61
+ interface DesktopFeatureAvailabilityApi {
62
+ getSnapshot(): Promise<DesktopFeatureAvailabilitySnapshot>;
63
+ isSupported(key: string): Promise<boolean>;
64
+ onChanged(listener: (snapshot: DesktopFeatureAvailabilitySnapshot) => void): () => void;
44
65
  }
45
66
  type MinimumVersionUpgradePhase = "blocked" | "checking" | "ready" | "downloading" | "downloaded" | "simulationComplete" | "error" | "released";
46
67
  type MinimumVersionUpgradeError = "releaseBelowMinimum" | "updateUnavailable" | "policyCheckFailed" | "installFailed" | "updateFailed";
@@ -84,6 +105,11 @@ declare const desktopUpdateAdmissionIpcChannels: {
84
105
  readonly start: "desktop-update-admission:start";
85
106
  readonly state: "desktop-update-admission:state";
86
107
  };
108
+ declare const desktopFeatureAvailabilityIpcChannels: {
109
+ readonly changed: "desktop-feature-availability:changed";
110
+ readonly getSnapshot: "desktop-feature-availability:get-snapshot";
111
+ readonly isSupported: "desktop-feature-availability:is-supported";
112
+ };
87
113
  interface DesktopMinimumVersionApi {
88
114
  getState(): Promise<MinimumVersionUpgradeState | null>;
89
115
  start(): Promise<MinimumVersionUpgradeState | null>;
@@ -94,4 +120,4 @@ interface DesktopMinimumVersionApi {
94
120
  onState(listener: (state: MinimumVersionUpgradeState) => void): () => void;
95
121
  }
96
122
 
97
- export { type ConfigureDesktopUpdatesInput, type DesktopArchitecture, type DesktopMinimumVersionApi, type DesktopPlatform, type DesktopProduct, type DesktopUpdateAdmissionRuntime, type DesktopUpdateChannel, type DesktopUpdatePolicy, type DesktopUpdateState, type DesktopUpdateStatus, type MandatoryDesktopUpdateSession, type MandatoryDesktopUpdateTarget, type MinimumVersionAppUpdateService, type MinimumVersionCheckRequest, type MinimumVersionCheckResponse, type MinimumVersionDecision, type MinimumVersionUpgradeError, type MinimumVersionUpgradePhase, type MinimumVersionUpgradeState, desktopProducts, desktopUpdateAdmissionIpcChannels, desktopUpdateChannels, desktopUpdatePolicies, desktopUpdateStatuses };
123
+ export { type ConfigureDesktopUpdatesInput, type DesktopArchitecture, type DesktopFeatureAvailability, type DesktopFeatureAvailabilityApi, type DesktopFeatureAvailabilityRuntime, type DesktopFeatureAvailabilitySnapshot, type DesktopFeatureAvailabilitySource, type DesktopMinimumVersionApi, type DesktopPlatform, type DesktopProduct, type DesktopUpdateAdmissionRuntime, type DesktopUpdateChannel, type DesktopUpdatePolicy, type DesktopUpdateState, type DesktopUpdateStatus, type MandatoryDesktopUpdateSession, type MandatoryDesktopUpdateTarget, type MinimumVersionAppUpdateService, type MinimumVersionCheckRequest, type MinimumVersionCheckResponse, type MinimumVersionDecision, type MinimumVersionUpgradeError, type MinimumVersionUpgradePhase, type MinimumVersionUpgradeState, desktopFeatureAvailabilityIpcChannels, desktopProducts, desktopUpdateAdmissionIpcChannels, desktopUpdateChannels, desktopUpdatePolicies, desktopUpdateStatuses };
@@ -1,11 +1,13 @@
1
1
  import {
2
+ desktopFeatureAvailabilityIpcChannels,
2
3
  desktopProducts,
3
4
  desktopUpdateAdmissionIpcChannels,
4
5
  desktopUpdateChannels,
5
6
  desktopUpdatePolicies,
6
7
  desktopUpdateStatuses
7
- } from "../chunk-TRALK3GT.js";
8
+ } from "../chunk-BESTTWBK.js";
8
9
  export {
10
+ desktopFeatureAvailabilityIpcChannels,
9
11
  desktopProducts,
10
12
  desktopUpdateAdmissionIpcChannels,
11
13
  desktopUpdateChannels,
@@ -20,6 +20,7 @@ type DesktopUpdateDevelopmentPolicyStep = {
20
20
  outcome: "timeout";
21
21
  };
22
22
  interface DesktopUpdateDevelopmentPolicyScenario {
23
+ featureKeys: readonly string[];
23
24
  policySteps: readonly DesktopUpdateDevelopmentPolicyStep[];
24
25
  }
25
26
  declare function resolveDesktopUpdateDevelopmentPolicyScenario(input: {
@@ -119,6 +120,7 @@ declare const desktopUpdateAdmissionDevelopmentEnvironment: {
119
120
  readonly development: "DESKTOP_UPDATE_ADMISSION_DEV";
120
121
  readonly download: "DESKTOP_UPDATE_ADMISSION_DOWNLOAD";
121
122
  readonly foregroundIntervalMs: "DESKTOP_UPDATE_ADMISSION_FOREGROUND_INTERVAL_MS";
123
+ readonly featureKeys: "DESKTOP_UPDATE_ADMISSION_FEATURE_KEYS";
122
124
  readonly install: "DESKTOP_UPDATE_ADMISSION_INSTALL";
123
125
  readonly latestVersion: "DESKTOP_UPDATE_ADMISSION_LATEST_VERSION";
124
126
  readonly minimumVersion: "DESKTOP_UPDATE_ADMISSION_MINIMUM_VERSION";