pi-mega-compact 0.20.45 → 0.20.47

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 (92) hide show
  1. package/dist/config/vector-cortex-enc0d.js +39 -0
  2. package/dist/config/vector-cortex-enc0e.js +40 -0
  3. package/dist/config/vector-cortex-vc3to8.js +239 -0
  4. package/dist/config/vector-cortex.js +5 -227
  5. package/dist/config.js +1 -1
  6. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +2 -0
  7. package/dist/extensions/dashboard-server/routes-setup-cortex.js +30 -1
  8. package/dist/extensions/dashboard-server/setup-cortex-blockers.js +2 -1
  9. package/dist/src/config/vector-cortex-enc0d.js +39 -0
  10. package/dist/src/config/vector-cortex-enc0e.js +40 -0
  11. package/dist/src/config/vector-cortex-vc3to8.js +239 -0
  12. package/dist/src/config/vector-cortex.js +5 -227
  13. package/dist/src/config.js +1 -1
  14. package/dist/src/vector-cortex/encoder/decision.js +17 -0
  15. package/dist/src/vector-cortex/encoder/promotion-emit.js +40 -0
  16. package/dist/src/vector-cortex/encoder/promotion.js +64 -0
  17. package/dist/src/vector-cortex/encoder/runtime-emit.js +8 -2
  18. package/dist/src/vector-cortex/encoder/runtime-select.js +35 -1
  19. package/dist/vector-cortex/encoder/decision.js +17 -0
  20. package/dist/vector-cortex/encoder/promotion-emit.js +40 -0
  21. package/dist/vector-cortex/encoder/promotion.js +64 -0
  22. package/dist/vector-cortex/encoder/runtime-emit.js +8 -2
  23. package/dist/vector-cortex/encoder/runtime-select.js +35 -1
  24. package/extensions/dashboard-client/dist/assets/{AreaChart-Cn6X7epL.js → AreaChart-COnj8FHi.js} +2 -2
  25. package/extensions/dashboard-client/dist/assets/{AreaChart-Cn6X7epL.js.map → AreaChart-COnj8FHi.js.map} +1 -1
  26. package/extensions/dashboard-client/dist/assets/{BarChart-APy1Wc7z.js → BarChart-BYqt8c5-.js} +2 -2
  27. package/extensions/dashboard-client/dist/assets/{BarChart-APy1Wc7z.js.map → BarChart-BYqt8c5-.js.map} +1 -1
  28. package/extensions/dashboard-client/dist/assets/{CacheTab-CEv_Ss9O.js → CacheTab-PLRjMrZV.js} +2 -2
  29. package/extensions/dashboard-client/dist/assets/{CacheTab-CEv_Ss9O.js.map → CacheTab-PLRjMrZV.js.map} +1 -1
  30. package/extensions/dashboard-client/dist/assets/{EventsTab-8jV1Pr0C.js → EventsTab-DXKSft6w.js} +2 -2
  31. package/extensions/dashboard-client/dist/assets/{EventsTab-8jV1Pr0C.js.map → EventsTab-DXKSft6w.js.map} +1 -1
  32. package/extensions/dashboard-client/dist/assets/{HealthTab-B2VgEz1j.js → HealthTab-BgVIsrde.js} +2 -2
  33. package/extensions/dashboard-client/dist/assets/{HealthTab-B2VgEz1j.js.map → HealthTab-BgVIsrde.js.map} +1 -1
  34. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-m37AlTbg.js → MaintenanceTab-DNgFC8KZ.js} +2 -2
  35. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-m37AlTbg.js.map → MaintenanceTab-DNgFC8KZ.js.map} +1 -1
  36. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-Co2jbqdT.js → MemoryMapTab-B6yY47sz.js} +2 -2
  37. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-Co2jbqdT.js.map → MemoryMapTab-B6yY47sz.js.map} +1 -1
  38. package/extensions/dashboard-client/dist/assets/{MetricsTab-D0yV9X6D.js → MetricsTab-BrhJ6rkx.js} +2 -2
  39. package/extensions/dashboard-client/dist/assets/{MetricsTab-D0yV9X6D.js.map → MetricsTab-BrhJ6rkx.js.map} +1 -1
  40. package/extensions/dashboard-client/dist/assets/{OverviewTab-J5TjXB14.js → OverviewTab-RFHU9L4x.js} +2 -2
  41. package/extensions/dashboard-client/dist/assets/{OverviewTab-J5TjXB14.js.map → OverviewTab-RFHU9L4x.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/{ReposTab-BadNIkWu.js → ReposTab-UFIWKkJM.js} +2 -2
  43. package/extensions/dashboard-client/dist/assets/{ReposTab-BadNIkWu.js.map → ReposTab-UFIWKkJM.js.map} +1 -1
  44. package/extensions/dashboard-client/dist/assets/{SessionsTab-CdKShJuR.js → SessionsTab-BHFYsmK8.js} +2 -2
  45. package/extensions/dashboard-client/dist/assets/{SessionsTab-CdKShJuR.js.map → SessionsTab-BHFYsmK8.js.map} +1 -1
  46. package/extensions/dashboard-client/dist/assets/{SetupTab-CPemizDx.js → SetupTab-C3dnKa8y.js} +3 -3
  47. package/extensions/dashboard-client/dist/assets/{SetupTab-CPemizDx.js.map → SetupTab-C3dnKa8y.js.map} +1 -1
  48. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-TvULibwW.js → TimeSavedCard-aPG5Knqn.js} +2 -2
  49. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-TvULibwW.js.map → TimeSavedCard-aPG5Knqn.js.map} +1 -1
  50. package/extensions/dashboard-client/dist/assets/{TurnsTab-CglFIcYy.js → TurnsTab-fGm9n-Tk.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{TurnsTab-CglFIcYy.js.map → TurnsTab-fGm9n-Tk.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-H2bBNhrR.js → VcStatusBadge-BtmSNH8X.js} +2 -2
  53. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-H2bBNhrR.js.map → VcStatusBadge-BtmSNH8X.js.map} +1 -1
  54. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-CXPv3sYQ.js → VectorCortexTab-meALMMOZ.js} +2 -2
  55. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-CXPv3sYQ.js.map → VectorCortexTab-meALMMOZ.js.map} +1 -1
  56. package/extensions/dashboard-client/dist/assets/{WikiTab-BNfN175_.js → WikiTab-0TK6q6g1.js} +2 -2
  57. package/extensions/dashboard-client/dist/assets/{WikiTab-BNfN175_.js.map → WikiTab-0TK6q6g1.js.map} +1 -1
  58. package/extensions/dashboard-client/dist/assets/{button-DjN7aqD2.js → button-CKEnMZ98.js} +2 -2
  59. package/extensions/dashboard-client/dist/assets/{button-DjN7aqD2.js.map → button-CKEnMZ98.js.map} +1 -1
  60. package/extensions/dashboard-client/dist/assets/{card-DLHA5XP8.js → card-BVVhm3sL.js} +2 -2
  61. package/extensions/dashboard-client/dist/assets/{card-DLHA5XP8.js.map → card-BVVhm3sL.js.map} +1 -1
  62. package/extensions/dashboard-client/dist/assets/{client-extra-ZT6mIQZS.js → client-extra-BvGxKZpm.js} +2 -2
  63. package/extensions/dashboard-client/dist/assets/{client-extra-ZT6mIQZS.js.map → client-extra-BvGxKZpm.js.map} +1 -1
  64. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DAl4dRVG.js → generateCategoricalChart-B1PeCct1.js} +2 -2
  65. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DAl4dRVG.js.map → generateCategoricalChart-B1PeCct1.js.map} +1 -1
  66. package/extensions/dashboard-client/dist/assets/{index-H-sfROoW.js → index-DuThOA-d.js} +3 -3
  67. package/extensions/dashboard-client/dist/assets/{index-H-sfROoW.js.map → index-DuThOA-d.js.map} +1 -1
  68. package/extensions/dashboard-client/dist/assets/{switch-BCxvJYon.js → switch-7qLS7ajc.js} +2 -2
  69. package/extensions/dashboard-client/dist/assets/{switch-BCxvJYon.js.map → switch-7qLS7ajc.js.map} +1 -1
  70. package/extensions/dashboard-client/dist/assets/{toggle-DviUMYeg.js → toggle-D7mgOVxz.js} +2 -2
  71. package/extensions/dashboard-client/dist/assets/{toggle-DviUMYeg.js.map → toggle-D7mgOVxz.js.map} +1 -1
  72. package/extensions/dashboard-client/dist/assets/{useSSE-DP2O8vHY.js → useSSE-ChpyZVeX.js} +2 -2
  73. package/extensions/dashboard-client/dist/assets/{useSSE-DP2O8vHY.js.map → useSSE-ChpyZVeX.js.map} +1 -1
  74. package/extensions/dashboard-client/dist/index.html +1 -1
  75. package/extensions/dashboard-client/src/tabs/SetupTab/CortexBlockersCard.tsx +17 -1
  76. package/extensions/dashboard-client/src/tabs/SetupTab/CortexSetup.tsx +1 -0
  77. package/extensions/dashboard-client/src/types/setup-cortex.ts +10 -0
  78. package/extensions/dashboard-server/api-contracts/setup-cortex.ts +11 -0
  79. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +12 -0
  80. package/extensions/dashboard-server/routes-setup-cortex.ts +35 -1
  81. package/extensions/dashboard-server/setup-cortex-blockers.ts +2 -1
  82. package/package.json +1 -1
  83. package/src/config/vector-cortex-enc0d.ts +41 -0
  84. package/src/config/vector-cortex-enc0e.ts +42 -0
  85. package/src/config/vector-cortex-vc3to8.ts +256 -0
  86. package/src/config/vector-cortex.ts +23 -245
  87. package/src/config.ts +2 -0
  88. package/src/vector-cortex/encoder/decision.ts +28 -1
  89. package/src/vector-cortex/encoder/promotion-emit.ts +63 -0
  90. package/src/vector-cortex/encoder/promotion.ts +95 -0
  91. package/src/vector-cortex/encoder/runtime-emit.ts +21 -4
  92. package/src/vector-cortex/encoder/runtime-select.ts +46 -1
@@ -25,13 +25,40 @@ import type { EncoderPlatform } from "./types.js";
25
25
  /** Backend demotion disposition for a platform row. */
26
26
  export type BackendDemotion = "none" | "wasm" | "modeB";
27
27
 
28
- /** One platform row: the concrete runtime + install size + demotion disposition. */
28
+ /**
29
+ * One platform row: the concrete runtime + install size + demotion disposition.
30
+ *
31
+ * `demotionReason` is the ENC-0e canonical reason string for a demoted platform
32
+ * (single source of truth — no literals scattered in routes or the event writer).
33
+ */
29
34
  export interface EncoderPlatformRow {
30
35
  readonly runtime: string;
31
36
  readonly installMiB: number;
32
37
  readonly demotion: BackendDemotion;
38
+ readonly demotionReason?: string;
33
39
  }
34
40
 
41
+ /**
42
+ * The ENC-0e canonical darwin-x64 demotion reason (HG-4 / ENC-0e).
43
+ *
44
+ * macOS Intel has no native onnxruntime-node binary upstream (arm64-only; a
45
+ * darwin-x64 transform-class package ships only WASM), so the runtime demotes
46
+ * to mode-B WASM. This is the SINGLE canonical string — consumed by
47
+ * runtime-select.ts (the runtime-selection event) and the Setup Cortex blockers
48
+ * card. No string literal for this reason survives anywhere else.
49
+ */
50
+ export const DARWIN_X64_DEMOTION_REASON =
51
+ "darwin-x64: no native binary upstream (arm64-only); mode-B WASM per HG-4";
52
+
53
+ /**
54
+ * Deterministic sentinel used when a darwin-x64 platform-matrix row is missing
55
+ * the `demotionReason` string (unique-failure injection per the ENC-0e spec):
56
+ * the selection must still choose mode-B WASM, never throw, and never fabricate
57
+ * a native claim — the reason just falls back to this sentinel.
58
+ */
59
+ export const DARWIN_X64_DEMOTION_REASON_SENTINEL =
60
+ "darwin-x64: demoted to WASM (reason unavailable in platform matrix)";
61
+
35
62
  /**
36
63
  * EncoderBackendDecisionV1 — the locked learned-encoder runtime-backend decision.
37
64
  *
@@ -0,0 +1,63 @@
1
+ /**
2
+ * vector-cortex/encoder/promotion-emit.ts — ENC-0d promotion event writers.
3
+ *
4
+ * Writes the three real-asset promotion events to the monitoring `events.log`
5
+ * as structured append-only JSON lines via the existing `logBenchEvent` seam
6
+ * (`src/monitoring.ts`):
7
+ *
8
+ * - `vector_cortex_asset_promoted` (green digest-verified swap)
9
+ * - `vector_cortex_asset_demoted` (red qualification / digest failure)
10
+ * - `vector_cortex_asset_rollback_back` (restore of a prior stack digest)
11
+ *
12
+ * Non-fatal and log-and-swallow: a write failure never breaks the agent loop
13
+ * (best-effort store/write contract). Each line is `{ts, event, ...fields}`
14
+ * shaped so the dashboard live-stream tail and evidence tooling parse it
15
+ * identically to every other monitoring event. Events carry digests, colors
16
+ * and verdicts ONLY — never message content (EVAL-REDACT-002).
17
+ *
18
+ * The path defaults to the standard state-dir events.log (mirrors
19
+ * defaultEventsPath). Zero network, no `any` (PREVENT-PI-004 / PREVENT-011).
20
+ */
21
+
22
+ import { defaultEventsPath, logBenchEvent } from "../../monitoring.js";
23
+ import { getStateDir } from "../../store.js";
24
+
25
+ /** Default events.log beside the state dir (mirrors bench.ts). */
26
+ export function promotionEventsPath(stateDir: string = getStateDir()): string {
27
+ return defaultEventsPath(stateDir);
28
+ }
29
+
30
+ /** The promotion event kinds this emitter can write. */
31
+ export type PromotionEventKind =
32
+ | "promoted"
33
+ | "demoted"
34
+ | "rollback_back";
35
+
36
+ const EVENT_NAME: Record<PromotionEventKind, string> = {
37
+ promoted: "vector_cortex_asset_promoted",
38
+ demoted: "vector_cortex_asset_demoted",
39
+ rollback_back: "vector_cortex_asset_rollback_back",
40
+ };
41
+
42
+ /** Fields carried by a promotion event: digests, colors and verdicts only.
43
+ * Never any payload or message content (EVAL-REDACT-002). */
44
+ export interface PromotionEventFields {
45
+ readonly color: "green" | "red";
46
+ readonly assetDigest: string | null;
47
+ readonly priorAssetDigest: string | null;
48
+ readonly verdict: string;
49
+ }
50
+
51
+ /**
52
+ * Append one promotion event to events.log (best-effort, non-fatal). The line
53
+ * is `{ts, event, ...fields}` shaped, written append-only via the monitoring
54
+ * logBenchEvent seam. On any write failure the event is swallowed — the agent
55
+ * loop is never broken.
56
+ */
57
+ export function appendPromotionEvent(
58
+ path: string,
59
+ kind: PromotionEventKind,
60
+ fields: PromotionEventFields,
61
+ ): void {
62
+ logBenchEvent(path, EVENT_NAME[kind], { ...fields });
63
+ }
@@ -14,6 +14,15 @@
14
14
  * an atomic digest swap (flip the committed pointer to the prior entry's
15
15
  * digest in one step) — no partial state is possible because the swap is a
16
16
  * single value assignment.
17
+ *
18
+ * ENC-0d extends the ledger to the real-asset promotion path: `PromotionV1`
19
+ * carries `{color, assetDigestStack}` — a promotion color from gate
20
+ * qualification ("green" atomically swaps to the trained asset, "red" keeps
21
+ * the prior asset live) and a LIFO of prior shipped digest for O(1)-by-sha256
22
+ * rollback. The on-disk manifest byte-swap itself (write-temp-then-rename,
23
+ * never in-place partial) is the promotion gate script's job; these helpers
24
+ * stay pure and return the restorable digest + the updated manifest/stack so
25
+ * the script can perform the swap in one atomic step.
17
26
  */
18
27
 
19
28
  // ---------------------------------------------------------------------------
@@ -54,6 +63,13 @@ export interface PromotionV1 {
54
63
  readonly verdict: "promoted" | "demoted" | "noop";
55
64
  /** Present only when verdict is "demoted": the demotion event name. */
56
65
  readonly demotedEvent: "demoted_new_asset" | null;
66
+ /** ENC-0d promotion color from gate qualification. "green" atomically swaps
67
+ * the shipped manifest to the trained asset; "red" keeps the prior asset
68
+ * live and emits a demotion. */
69
+ readonly color: "green" | "red";
70
+ /** ENC-0d LIFO of prior shipped asset digests for O(1)-by-sha256 rollback.
71
+ * The most recent prior digest (top of the stack) is the rollback target. */
72
+ readonly assetDigestStack: readonly string[];
57
73
  }
58
74
 
59
75
  // ---------------------------------------------------------------------------
@@ -104,6 +120,85 @@ export function rollbackTo(
104
120
  };
105
121
  }
106
122
 
123
+ // ---------------------------------------------------------------------------
124
+ // ENC-0d LIFO rollback stack + atomic swap. Pure pointer arithmetic; the
125
+ // on-disk write-temp-then-rename of the swapped manifest bytes is the gate
126
+ // script's job so no partial state can ever be observed (PREVENT-PI-004).
127
+ // ---------------------------------------------------------------------------
128
+
129
+ /** Push a freshly-shipped digest onto the LIFO rollback stack (top = latest
130
+ * prior shipped digest). Non-mutating: returns a new array. */
131
+ export function pushAssetDigest(
132
+ stack: readonly string[],
133
+ digest: string,
134
+ ): readonly string[] {
135
+ return [...stack, digest];
136
+ }
137
+
138
+ /** Pop the top (most recent prior) digest off the LIFO rollback stack without
139
+ * mutating it. Returns the prior digest (null when the stack is empty) plus
140
+ * the remaining stack for the caller to commit (atomic card-swap fallback:
141
+ * a green swap pushes the incumbent digest; a rollback pops it). */
142
+ export function popAssetDigest(
143
+ stack: readonly string[],
144
+ ): { prior: string | null; rest: readonly string[] } {
145
+ if (stack.length === 0) return { prior: null, rest: stack };
146
+ const rest = stack.slice(0, -1);
147
+ return { prior: stack[stack.length - 1]!, rest };
148
+ }
149
+
150
+ /** Outcome of an atomic promotion swap (pure): the (possibly restored)
151
+ * manifest, the updated rollback stack, and whether a digest swap occurred. */
152
+ export interface AtomicSwapOutcome {
153
+ readonly manifest: AssetManifest;
154
+ readonly stack: readonly string[];
155
+ /** True when the committed pointer changed (green swap or rollback). */
156
+ readonly swapped: boolean;
157
+ }
158
+
159
+ /** Atomically swap the committed manifest to a real trained asset's digest.
160
+ * Green: append the new entry (append-only — the incumbent stays in the
161
+ * ledger, never overwritten), commit to it, and record the INCUMBENT digest
162
+ * onto the LIFO stack for O(1) rollback (rollback restores the incumbent, so
163
+ * the stack must hold the pre-swap committed digest — pushing the swapped-in
164
+ * digest would point rollback back at the very asset being rolled back). Red
165
+ * (or no candidate digest): return the manifest and stack untouched — no
166
+ * swap, prior asset stays live. */
167
+ export function atomicSwap(
168
+ manifest: AssetManifest,
169
+ entry: AssetManifestEntry,
170
+ color: "green" | "red",
171
+ stack: readonly string[],
172
+ ): AtomicSwapOutcome {
173
+ if (color !== "green" || !entry.assetDigest) {
174
+ return { manifest, stack, swapped: false };
175
+ }
176
+ const incumbent = manifest.committed;
177
+ const next = appendAsset(manifest, entry);
178
+ return {
179
+ manifest: next,
180
+ stack: incumbent === null ? stack : pushAssetDigest(stack, incumbent),
181
+ swapped: true,
182
+ };
183
+ }
184
+
185
+ /** Roll back to the previous shipped asset by SHA-256 (O(1) via the LIFO
186
+ * stack): restores the most recent prior digest in the manifest and pops that
187
+ * entry off the stack. Returns null when no prior digest exists or the prior
188
+ * digest is no longer in the append-only manifest (defensive: append-only
189
+ * means it should always be findable). Never leaves a partial state — the
190
+ * committed pointer flip and the stack pop happen together or not at all. */
191
+ export function assetRollback(
192
+ manifest: AssetManifest,
193
+ stack: readonly string[],
194
+ ): AtomicSwapOutcome | null {
195
+ const { prior, rest } = popAssetDigest(stack);
196
+ if (prior === null) return null;
197
+ const restored = rollbackTo(manifest, prior);
198
+ if (!restored) return null;
199
+ return { manifest: restored, stack: rest, swapped: true };
200
+ }
201
+
107
202
  // ---------------------------------------------------------------------------
108
203
  // Pure decision rules.
109
204
  // ---------------------------------------------------------------------------
@@ -22,16 +22,29 @@ import type { RuntimeSelectionResult } from "./runtime-select.js";
22
22
 
23
23
  /**
24
24
  * Emit the ML5-C `vector_cortex_runtime_selected` seller event (best-effort).
25
- * The event carries ONLY the four aggregate fields the sprint spec pins
26
- * ({backend, p95Ms, budgetOk, platform}) never message content.
25
+ * The event carries ONLY the aggregate fields the sprint specs pin
26
+ * ({backend, p95Ms, budgetOk, platform}) plus the ENC-0e additively-carried
27
+ * `demotionReason` (non-null only on a demoted platform under flag ON) —
28
+ * never message content. The event writer never throws.
27
29
  */
28
30
  export function emitRuntimeSelected(
29
31
  stateDir: string,
30
- result: Pick<RuntimeSelectionResult, "backend" | "p95Ms" | "budgetOk" | "platform">,
32
+ result: Pick<
33
+ RuntimeSelectionResult,
34
+ "backend" | "p95Ms" | "budgetOk" | "platform" | "demotionReason"
35
+ >,
31
36
  ): void {
32
37
  try {
33
38
  const path = defaultEventsPath(stateDir);
34
- const payload = {
39
+ const payload: {
40
+ ts: number;
41
+ event: string;
42
+ backend: string;
43
+ p95Ms: number | null;
44
+ budgetOk: boolean;
45
+ platform: string;
46
+ demotionReason?: string;
47
+ } = {
35
48
  ts: Date.now(),
36
49
  event: "vector_cortex_runtime_selected",
37
50
  backend: result.backend,
@@ -39,6 +52,10 @@ export function emitRuntimeSelected(
39
52
  budgetOk: result.budgetOk,
40
53
  platform: result.platform,
41
54
  };
55
+ // ENC-0e: carry the demotion reason additively, only when present.
56
+ if (result.demotionReason !== null && result.demotionReason !== undefined) {
57
+ payload.demotionReason = result.demotionReason;
58
+ }
42
59
  mkdirSync(dirname(path), { recursive: true });
43
60
  appendFileSync(path, JSON.stringify(payload) + "\n", "utf8");
44
61
  } catch {
@@ -24,7 +24,12 @@ import {
24
24
  type EncoderPlatform,
25
25
  } from "./types.js";
26
26
  import type { BenchResultV1 } from "./bench-export.js";
27
- import { ML5C_ENABLED } from "../../config/vector-cortex.js";
27
+ import { ML5C_ENABLED, ENC_0E_ENABLED } from "../../config/vector-cortex.js";
28
+ import type { EncoderPlatformRow } from "./decision.js";
29
+ import {
30
+ DARWIN_X64_DEMOTION_REASON,
31
+ DARWIN_X64_DEMOTION_REASON_SENTINEL,
32
+ } from "./decision.js";
28
33
 
29
34
  /** The chosen runtime backend name as it appears on the seller event. */
30
35
  export type RuntimeBackendChoice = "wasm" | "native" | "modeB";
@@ -56,6 +61,12 @@ export interface RuntimeSelectionResult {
56
61
  readonly platform: string;
57
62
  /** Short human-readable rationale for the choice (record on the event). */
58
63
  readonly rationale: string;
64
+ /**
65
+ * ENC-0e explicit demotion reason (non-null ONLY on a demoted platform under
66
+ * `MEGACOMPACT_ENC_0E`). Null on every other platform and on flag-off, so the
67
+ * flag-off selection is byte-identical to the ENC-0d predecessor.
68
+ */
69
+ readonly demotionReason: string | null;
59
70
  }
60
71
 
61
72
  /** The 80 MiB install budget in bytes (the HG-3 ceiling, unamended). */
@@ -99,6 +110,7 @@ export function selectRuntimeBackend(input: RuntimeSelectionInput): RuntimeSelec
99
110
  p95Ms: null,
100
111
  platform: input.platform,
101
112
  rationale: "flag-off: byte-identical mode-B trigram (no selection)",
113
+ demotionReason: null,
102
114
  };
103
115
  }
104
116
 
@@ -110,6 +122,12 @@ export function selectRuntimeBackend(input: RuntimeSelectionInput): RuntimeSelec
110
122
  p95Ms: null,
111
123
  platform: input.platform,
112
124
  rationale: "darwin-x64 demoted to WASM per HG-4 (never native on this platform)",
125
+ // ENC-0e: the reason is the single canonical string from the ENC-0a
126
+ // platform matrix (decision.ts). Under flag-off the reason is stripped
127
+ // (null), keeping the flag-off selection byte-identical to the ENC-0d
128
+ // predecessor. If the matrix row ever lacks the reason, fall back to a
129
+ // deterministic sentinel — never throw, never fabricate a native claim.
130
+ demotionReason: ENC_0E_ENABLED() ? darwinX64DemotionReason(undefined) : null,
113
131
  };
114
132
  }
115
133
 
@@ -127,6 +145,7 @@ export function selectRuntimeBackend(input: RuntimeSelectionInput): RuntimeSelec
127
145
  p95Ms: input.benchRecord?.p95Ms ?? null,
128
146
  platform: input.platform,
129
147
  rationale: `native opt-in (MEGACOMPACT_ENCODER_NATIVE=1); shipped ${shippedMib} MiB across 5 platforms → budget amended to ${shippedMib} MiB`,
148
+ demotionReason: null,
130
149
  };
131
150
  }
132
151
 
@@ -142,6 +161,7 @@ export function selectRuntimeBackend(input: RuntimeSelectionInput): RuntimeSelec
142
161
  p95Ms: input.benchRecord?.p95Ms ?? null,
143
162
  platform: input.platform,
144
163
  rationale: `no qualifying bench record — native fallback with budget amendment (${shippedMib} MiB shipped, HG-3 amendment recorded)`,
164
+ demotionReason: null,
145
165
  };
146
166
  }
147
167
 
@@ -154,6 +174,7 @@ export function selectRuntimeBackend(input: RuntimeSelectionInput): RuntimeSelec
154
174
  p95Ms: input.benchRecord.p95Ms,
155
175
  platform: input.platform,
156
176
  rationale: `WASM qualifies: p95 ${input.benchRecord.p95Ms}ms <= ${ENCODER_LATENCY_P95_MS}ms`,
177
+ demotionReason: null,
157
178
  };
158
179
  }
159
180
 
@@ -163,5 +184,29 @@ export function selectRuntimeBackend(input: RuntimeSelectionInput): RuntimeSelec
163
184
  p95Ms: input.benchRecord.p95Ms,
164
185
  platform: input.platform,
165
186
  rationale: `native required: p95 ${input.benchRecord.p95Ms}ms > ${ENCODER_LATENCY_P95_MS}ms on WASM`,
187
+ demotionReason: null,
166
188
  };
167
189
  }
190
+
191
+ /**
192
+ * ENC-0e — resolve the canonical darwin-x64 demotion reason (pure).
193
+ *
194
+ * `provider` is the ENC-0a platform-matrix row for darwin-x64 if the caller has
195
+ * one (the resolver owns a concrete matrix; runtime-select normally passes
196
+ * none and falls back to the canonical constant). Resolution order:
197
+ * 1. the provider row's `demotionReason` (single canonical source);
198
+ * 2. the canonical DARWIN_X64_DEMOTION_REASON constant;
199
+ * 3. the deterministic SENTINEL when a row EXISTS but lacks the reason
200
+ * (unique-failure injection per ENC-0e) — never a throw, never a
201
+ * fabricated native claim.
202
+ * Kept pure so tests inject the platform seam with no real Mac.
203
+ */
204
+ export function darwinX64DemotionReason(provider: EncoderPlatformRow | undefined): string {
205
+ if (provider) {
206
+ if (provider.demotionReason && provider.demotionReason.length > 0) {
207
+ return provider.demotionReason;
208
+ }
209
+ return DARWIN_X64_DEMOTION_REASON_SENTINEL;
210
+ }
211
+ return DARWIN_X64_DEMOTION_REASON;
212
+ }