pi-mega-compact 0.20.48 → 0.20.50

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 (90) hide show
  1. package/dist/config/vector-cortex-enc0g.js +37 -0
  2. package/dist/config/vector-cortex-enc1a.js +39 -0
  3. package/dist/config/vector-cortex.js +2 -0
  4. package/dist/config.js +1 -1
  5. package/dist/extensions/dashboard-server/qualification-record.js +71 -0
  6. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +2 -0
  7. package/dist/extensions/dashboard-server/routes-setup-cortex-actions.js +49 -3
  8. package/dist/extensions/dashboard-server/routes-setup-cortex.js +65 -16
  9. package/dist/extensions/dashboard-server/routes-setup-enc1a.js +157 -0
  10. package/dist/extensions/dashboard-server/routes-setup.js +21 -0
  11. package/dist/extensions/dashboard-server/setup-cortex-actions.js +21 -4
  12. package/dist/extensions/dashboard-server/setup-cortex-blockers.js +3 -69
  13. package/dist/src/config/vector-cortex-enc0g.js +37 -0
  14. package/dist/src/config/vector-cortex-enc1a.js +39 -0
  15. package/dist/src/config/vector-cortex.js +2 -0
  16. package/dist/src/config.js +1 -1
  17. package/dist/src/vector-cortex/setup-cortex-blockers-compute.js +160 -0
  18. package/dist/vector-cortex/setup-cortex-blockers-compute.js +160 -0
  19. package/extensions/dashboard-client/dist/assets/{AreaChart-COnj8FHi.js → AreaChart-CeVnaV-0.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{AreaChart-COnj8FHi.js.map → AreaChart-CeVnaV-0.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/{BarChart-BYqt8c5-.js → BarChart-DoNd35sw.js} +2 -2
  22. package/extensions/dashboard-client/dist/assets/{BarChart-BYqt8c5-.js.map → BarChart-DoNd35sw.js.map} +1 -1
  23. package/extensions/dashboard-client/dist/assets/{CacheTab-PLRjMrZV.js → CacheTab-BROshdFf.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{CacheTab-PLRjMrZV.js.map → CacheTab-BROshdFf.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{EventsTab-DXKSft6w.js → EventsTab-DAD6NeBR.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{EventsTab-DXKSft6w.js.map → EventsTab-DAD6NeBR.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{HealthTab-BgVIsrde.js → HealthTab-BBVMM54F.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{HealthTab-BgVIsrde.js.map → HealthTab-BBVMM54F.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DNgFC8KZ.js → MaintenanceTab-f9VhlFco.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DNgFC8KZ.js.map → MaintenanceTab-f9VhlFco.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-B6yY47sz.js → MemoryMapTab-BaxfyzN2.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-B6yY47sz.js.map → MemoryMapTab-BaxfyzN2.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/{MetricsTab-BrhJ6rkx.js → MetricsTab-DkUvoeS_.js} +2 -2
  34. package/extensions/dashboard-client/dist/assets/{MetricsTab-BrhJ6rkx.js.map → MetricsTab-DkUvoeS_.js.map} +1 -1
  35. package/extensions/dashboard-client/dist/assets/{OverviewTab-RFHU9L4x.js → OverviewTab-BCpK1vgt.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{OverviewTab-RFHU9L4x.js.map → OverviewTab-BCpK1vgt.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{ReposTab-UFIWKkJM.js → ReposTab-cJ5neYIG.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{ReposTab-UFIWKkJM.js.map → ReposTab-cJ5neYIG.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{SessionsTab-BHFYsmK8.js → SessionsTab-8DyQv1Ry.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{SessionsTab-BHFYsmK8.js.map → SessionsTab-8DyQv1Ry.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/SetupTab-BQGPf9CI.js +3 -0
  42. package/extensions/dashboard-client/dist/assets/SetupTab-BQGPf9CI.js.map +1 -0
  43. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-aPG5Knqn.js → TimeSavedCard-8N7Vvlch.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-aPG5Knqn.js.map → TimeSavedCard-8N7Vvlch.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{TurnsTab-fGm9n-Tk.js → TurnsTab-CCijkQoH.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{TurnsTab-fGm9n-Tk.js.map → TurnsTab-CCijkQoH.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-BtmSNH8X.js → VcStatusBadge-D9a7By8x.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-BtmSNH8X.js.map → VcStatusBadge-D9a7By8x.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-meALMMOZ.js → VectorCortexTab-pviD6CCy.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-meALMMOZ.js.map → VectorCortexTab-pviD6CCy.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/{WikiTab-0TK6q6g1.js → WikiTab-DLyicguz.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{WikiTab-0TK6q6g1.js.map → WikiTab-DLyicguz.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{button-CKEnMZ98.js → button-D8nKit8Y.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{button-CKEnMZ98.js.map → button-D8nKit8Y.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{card-BVVhm3sL.js → card-Xf5wHHBL.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{card-BVVhm3sL.js.map → card-Xf5wHHBL.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/{client-extra-BvGxKZpm.js → client-extra-CvxDLpQr.js} +2 -2
  58. package/extensions/dashboard-client/dist/assets/{client-extra-BvGxKZpm.js.map → client-extra-CvxDLpQr.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-B1PeCct1.js → generateCategoricalChart-DhQquRa8.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-B1PeCct1.js.map → generateCategoricalChart-DhQquRa8.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{index-DuThOA-d.js → index-BUP3MK5S.js} +3 -3
  62. package/extensions/dashboard-client/dist/assets/{index-DuThOA-d.js.map → index-BUP3MK5S.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{switch-7qLS7ajc.js → switch-B13rb8Z2.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{switch-7qLS7ajc.js.map → switch-B13rb8Z2.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/{toggle-D7mgOVxz.js → toggle-4h6DUEB5.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{toggle-D7mgOVxz.js.map → toggle-4h6DUEB5.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/assets/{useSSE-ChpyZVeX.js → useSSE-Djom4z1K.js} +2 -2
  68. package/extensions/dashboard-client/dist/assets/{useSSE-ChpyZVeX.js.map → useSSE-Djom4z1K.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/index.html +1 -1
  70. package/extensions/dashboard-client/src/tabs/SetupTab/CustomEndpointSection.tsx +57 -2
  71. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +9 -39
  72. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetupHelpers.tsx +50 -0
  73. package/extensions/dashboard-server/api-contracts/setup-cortex.ts +6 -2
  74. package/extensions/dashboard-server/api-contracts/setup.ts +12 -0
  75. package/extensions/dashboard-server/qualification-record.ts +70 -0
  76. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +12 -0
  77. package/extensions/dashboard-server/routes-setup-cortex-actions.ts +50 -3
  78. package/extensions/dashboard-server/routes-setup-cortex.ts +92 -16
  79. package/extensions/dashboard-server/routes-setup-enc1a.ts +184 -0
  80. package/extensions/dashboard-server/routes-setup.ts +21 -0
  81. package/extensions/dashboard-server/setup-cortex-actions.ts +23 -4
  82. package/extensions/dashboard-server/setup-cortex-blockers.ts +3 -97
  83. package/package.json +1 -1
  84. package/src/config/vector-cortex-enc0g.ts +39 -0
  85. package/src/config/vector-cortex-enc1a.ts +41 -0
  86. package/src/config/vector-cortex.ts +2 -0
  87. package/src/config.ts +2 -0
  88. package/src/vector-cortex/setup-cortex-blockers-compute.ts +216 -0
  89. package/extensions/dashboard-client/dist/assets/SetupTab-C3dnKa8y.js +0 -3
  90. package/extensions/dashboard-client/dist/assets/SetupTab-C3dnKa8y.js.map +0 -1
@@ -0,0 +1,37 @@
1
+ /**
2
+ * config/vector-cortex-enc0g.ts — ENC-0g Setup Cortex status route honest state.
3
+ *
4
+ * Extracted from vector-cortex.ts so that file stays under the 300-line soft
5
+ * limit (soft-as-hard gate), exactly as vector-cortex-enc0a.ts .. enc0f.ts and
6
+ * the VC8C/VC9A-D/ML5A-E/DEDUP_ATTR siblings were. vector-cortex.ts re-exports
7
+ * the flag below and root src/config.ts re-exports it, so no consumer import
8
+ * path changes.
9
+ *
10
+ * ENC-0g makes the Setup Cortex status route honest: the status route reads the
11
+ * latest ENC-0f `QualificationV1` record (when ENC_0F is ON and a record
12
+ * exists) and lets its verdict override the structural verifyEncoderAsset
13
+ * result for the `qualification` field; the blocker list becomes a pure
14
+ * computed function over (platform, qualification record, manifest head-count)
15
+ * with corrected HG statuses/wording; and VC9B action gating is re-derived from
16
+ * the live computed blockers instead of the stale static manifest.
17
+ *
18
+ * `MEGACOMPACT_ENC_0G=0` disables: the status route derives its `qualification`
19
+ * verdict from `verifyEncoderAsset(...).ok` alone, the blocker list is the
20
+ * static `SETUP_CORTEX_BLOCKERS` array exactly as ENC-0f-era, and action gating
21
+ * reads the static table as today — byte-identical to the predecessor. The flag
22
+ * MUST also be a dashboard SETTINGS toggle (visible in config UI, never in
23
+ * EXCLUDED_SETTINGS).
24
+ *
25
+ * Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
26
+ */
27
+ import { sprintFlag } from "./vector-cortex-flag.js";
28
+ /**
29
+ * ENC-0g — Setup Cortex status route honest state (verdict override + live
30
+ * blockers + re-derived gating). Default ON. `MEGACOMPACT_ENC_0G=0` disables
31
+ * and is byte-identical to the predecessor (ENC-0f): verdict from
32
+ * `verifyEncoderAsset` alone, static `SETUP_CORTEX_BLOCKERS`, static
33
+ * `setupCortexActionBlockers`. This flag MUST also be a dashboard SETTINGS
34
+ * toggle (visible in config UI, never in EXCLUDED_SETTINGS), mirroring
35
+ * ENC_0A/ENC_0B/ENC_0C/ENC_0D/ENC_0E/ENC_0F.
36
+ */
37
+ export const ENC_0G_ENABLED = () => sprintFlag("MEGACOMPACT_ENC_0G");
@@ -0,0 +1,39 @@
1
+ /**
2
+ * config/vector-cortex-enc1a.ts — ENC-1a external embedder API key + endpoint
3
+ * Settings fields.
4
+ *
5
+ * Extracted from vector-cortex.ts so that file stays under the 300-line soft
6
+ * limit (soft-as-hard gate), exactly as the ENC-0a..ENC-0g siblings were.
7
+ * vector-cortex.ts re-exports the flag below and root src/config.ts re-exports
8
+ * it, so no consumer import path changes.
9
+ *
10
+ * ENC-1a adds a Settings-visible field pair for the external embedder endpoint
11
+ * URL + optional Bearer API key. The runtime already reads
12
+ * `MEGACOMPACT_EMBEDDING_URL` and `MEGACOMPACT_EMBEDDING_KEY` from the process
13
+ * env (src/httpEmbedder.ts:embeddingConfigFromEnv) and the Setup tab's
14
+ * CustomEndpointSection already accepts a custom URL — but there is no
15
+ * dashboard-visible API-key field and no server route persists the pair to the
16
+ * per-repo `.mega-compact.env` so the session survives a restart. ENC-1a adds
17
+ * two additive contract fields on GET /api/setup-status (`embeddingEndpointUrl`
18
+ * echoed + `embeddingApiKeySet` boolean only — the raw key is NEVER returned),
19
+ * an additive writer branch on POST /api/setup-configure accepting
20
+ * `embeddingEndpointUrl` / `embeddingApiKey`, and two additive Setup text rows.
21
+ *
22
+ * `MEGACOMPACT_ENC_1A=0` disables: the Settings panel renders no ENC-1a text
23
+ * fields (only the existing boolDirect toggles remain), the GET body omits the
24
+ * two new fields, and the POST handler does not recognize the new keys —
25
+ * byte-identical to the ENC-0g predecessor. The flag MUST also be a dashboard
26
+ * SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS).
27
+ *
28
+ * Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
29
+ */
30
+ import { sprintFlag } from "./vector-cortex-flag.js";
31
+ /**
32
+ * ENC-1a — external embedder API key + endpoint Settings fields. Default ON.
33
+ * `MEGACOMPACT_ENC_1A=0` disables and is byte-identical to the predecessor
34
+ * (ENC-0g): no new GET fields, no writer branch, no new Setup text rows (only
35
+ * the existing boolDirect toggles remain). This flag MUST also be a dashboard
36
+ * SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS), mirroring
37
+ * ENC_0A..ENC_0G.
38
+ */
39
+ export const ENC_1A_ENABLED = () => sprintFlag("MEGACOMPACT_ENC_1A");
@@ -31,5 +31,7 @@ export { ENC_0C_ENABLED } from "./vector-cortex-enc0c.js";
31
31
  export { ENC_0D_ENABLED } from "./vector-cortex-enc0d.js";
32
32
  export { ENC_0E_ENABLED } from "./vector-cortex-enc0e.js";
33
33
  export { ENC_0F_ENABLED } from "./vector-cortex-enc0f.js";
34
+ export { ENC_0G_ENABLED } from "./vector-cortex-enc0g.js";
35
+ export { ENC_1A_ENABLED } from "./vector-cortex-enc1a.js";
34
36
  // Breaker constants (TRIAD_RESILIENCE.md §breaker) extracted to vector-cortex-breakers.ts.
35
37
  export { BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./vector-cortex-breakers.js";
package/dist/config.js CHANGED
@@ -114,4 +114,4 @@ export const NEW_UI = () => ragEnabled("MEGACOMPACT_NEW_UI");
114
114
  // default ON, `=0`/`_DISABLED` off. Re-exported from src/config/vector-cortex.ts
115
115
  // so root consumers share one source of truth.
116
116
  // ---------------------------------------------------------------------------
117
- export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, VC8B_ENABLED, VC8C_ENABLED, VC9A_ENABLED, VC9B_ENABLED, VC9C_ENABLED, VC9D_ENABLED, PCC_ENABLED, ML5A_ENABLED, ML5B_ENABLED, ML5C_ENABLED, ML5D_ENABLED, ML5E_ENABLED, DEDUP_ATTR_ENABLED, ENC_0A_ENABLED, ENC_0B_ENABLED, ENC_0C_ENABLED, ENC_0D_ENABLED, ENC_0E_ENABLED, ENC_0F_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
117
+ export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, VC6C_ENABLED, VC7A_ENABLED, VC7B_ENABLED, VC7C_ENABLED, VC8A_ENABLED, VC8B_ENABLED, VC8C_ENABLED, VC9A_ENABLED, VC9B_ENABLED, VC9C_ENABLED, VC9D_ENABLED, PCC_ENABLED, ML5A_ENABLED, ML5B_ENABLED, ML5C_ENABLED, ML5D_ENABLED, ML5E_ENABLED, DEDUP_ATTR_ENABLED, ENC_0A_ENABLED, ENC_0B_ENABLED, ENC_0C_ENABLED, ENC_0D_ENABLED, ENC_0E_ENABLED, ENC_0F_ENABLED, ENC_0G_ENABLED, ENC_1A_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
@@ -0,0 +1,71 @@
1
+ /**
2
+ * dashboard-server/qualification-record.ts — ENC-0g QualificationV1 reader.
3
+ *
4
+ * Read-only reader for the ENC-0f qualification record at
5
+ * <stateDir>/encoder-qualification.json. The computed-blocker + action-gating
6
+ * functions live in setup-cortex-blockers.ts (Worker A's canonical module).
7
+ *
8
+ * Reader-only: local filesystem read only (PREVENT-PI-004), no `any`
9
+ * (PREVENT-011), never payloads/prompts/ledger (EVAL-REDACT-002).
10
+ */
11
+ import { homedir } from "node:os";
12
+ import { readFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { QUALIFICATION_RECORD_UNAVAILABLE } from "../../src/vector-cortex/setup-cortex-blockers-compute.js";
15
+ /** Sentinel appended to thresholdFailures when no valid record is readable (canonical def in setup-cortex-blockers-compute). */
16
+ export { QUALIFICATION_RECORD_UNAVAILABLE };
17
+ /**
18
+ * Resolve the encoder state dir (the record's home) — same precedence as
19
+ * scripts/encoder/gate-qualify.mjs: MEGACOMPACT_STATE_DIR first, else
20
+ * ~/.pi/mega-compact-encoder.
21
+ */
22
+ export function encoderStateDir() {
23
+ const env = process.env.MEGACOMPACT_STATE_DIR;
24
+ return env !== undefined && env.length > 0
25
+ ? env
26
+ : join(homedir(), ".pi", "mega-compact-encoder");
27
+ }
28
+ /**
29
+ * Read + minimally validate the QualificationV1 record. Returns null on a
30
+ * missing/unreadable file, a JSON parse failure, or a shape mismatch (the
31
+ * route degrades to verify-only — never crashes). Read-only (PREVENT-PI-004).
32
+ */
33
+ export function readQualificationRecord(stateDir) {
34
+ let raw;
35
+ try {
36
+ // guardrails-allow PREVENT-PI-004: local qualification-record filesystem read (loopback)
37
+ raw = readFileSync(join(stateDir, "encoder-qualification.json"), "utf8");
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ let parsed;
43
+ try {
44
+ parsed = JSON.parse(raw);
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ const rec = parsed;
50
+ if (rec === null || typeof rec !== "object")
51
+ return null;
52
+ if (rec["schema"] !== "qualification-v1")
53
+ return null;
54
+ if (typeof rec["verdict"] !== "string")
55
+ return null;
56
+ if (!Array.isArray(rec["reasons"]))
57
+ return null;
58
+ if (typeof rec["platform"] !== "string")
59
+ return null;
60
+ const reasons = rec["reasons"].map((r) => String(r));
61
+ return {
62
+ schema: "qualification-v1",
63
+ verdict: rec["verdict"] === "qualified" ? "qualified" : "failed",
64
+ reasons,
65
+ platform: rec["platform"],
66
+ p95Ms: typeof rec["p95Ms"] === "number" ? rec["p95Ms"] : 0,
67
+ rssMib: typeof rec["rssMib"] === "number" ? rec["rssMib"] : 0,
68
+ opset: typeof rec["opset"] === "number" ? rec["opset"] : 0,
69
+ digest: typeof rec["digest"] === "string" ? rec["digest"] : "",
70
+ };
71
+ }
@@ -64,5 +64,7 @@ export const VECTOR_CORTEX_SETTINGS = {
64
64
  boolDirect("MEGACOMPACT_ENC_0D", "ENC-0d Real-Asset Promotion Gate", "ENC-0d promotion gate over real trained assets: accepts a {color} real candidate manifest (the ENC-0c trained head weights + the ENC-0b trunk), digest-verifies every staged byte, and atomically swaps the shipped manifest to a green candidate with rollback-to-previous on qualification failure. OFF = accepts no candidate, swaps nothing, emits nothing, shipped manifest stays at the ENC-0c survivor.", true),
65
65
  boolDirect("MEGACOMPACT_ENC_0E", "ENC-0e darwin-x64 Demotion Reason", "ENC-0e darwin-x64 explicit demotion: on an Intel Mac the runtime demotes to WASM (no native binary upstream); this surfaces a deterministic reason on the runtime-selection event and the Setup Cortex blockers card so an operator sees why mode A is unreachable. OFF = no demotion reason on the event and no Setup card row (the ML5-C WASM demotion itself remains the default, byte-identical predecessor).", true),
66
66
  boolDirect("MEGACOMPACT_ENC_0F", "ENC-0f p95 + marginal-RSS Qualification Gate", "ENC-0f p95 + marginal-RSS qualification gate (real trained asset → mode A): runs the ENC-0d-promoted trained asset through the bench under --expose-gc, asserts p95 ≤ 40 ms @ 512/4 threads, marginal RSS ≤ 150 MiB (baseline-subtracted), determinism, and opset-21; on pass emits QualificationV1 + flips runtime to qualified mode A, on failure asset stays demoted to mode B. OFF = no qualification gate runs, no QualificationV1 written, runtime keeps serving the ENC-0d survivor (byte-identical predecessor).", true),
67
+ boolDirect("MEGACOMPACT_ENC_0G", "ENC-0g Setup Cortex honest state (verdict override + live blockers)", "ENC-0g makes the Setup Cortex status route honest: reads the ENC-0f QualificationV1 record (when ENC_0F is ON and a record exists) and lets its verdict override the structural verify for the qualification field; computes the blocker list from (platform, record, manifest head-count) with corrected HG statuses/wording; re-derives VC9B action gating from live blockers. OFF = verdict from verifyEncoderAsset alone, static SETUP_CORTEX_BLOCKERS, static action gating (byte-identical ENC-0f predecessor).", true),
68
+ boolDirect("MEGACOMPACT_ENC_1A", "ENC-1a external embedder API key + endpoint Settings fields", "ENC-1a adds a Settings-visible field pair for the external embedder endpoint URL + optional Bearer API key, persisted by the setup-configure writer branch to the per-repo .mega-compact.env (MEGACOMPACT_EMBEDDING_URL / MEGACOMPACT_EMBEDDING_KEY). The GET status route echoes the endpoint URL and reports embeddingApiKeySet as a boolean ONLY — the raw key is never returned. OFF = no new GET fields, no writer branch, no new Settings text rows (byte-identical ENC-0g predecessor).", true),
67
69
  ],
68
70
  };
@@ -17,9 +17,14 @@
17
17
  * readJsonBody), no string literals for scripts/blockers (delegated to
18
18
  * setup-cortex-actions.ts + setup-cortex-blockers.ts).
19
19
  */
20
+ import { existsSync } from "node:fs";
21
+ import { join, dirname } from "node:path";
22
+ import { fileURLToPath } from "node:url";
20
23
  import { sendJson, readJsonBody } from "./routes-vector-cortex-shared.js";
21
- import { VC9B_ENABLED } from "../../src/config.js";
22
- import { setupCortexActionBlockers } from "./setup-cortex-blockers.js";
24
+ import { VC9B_ENABLED, ENC_0G_ENABLED, ENC_0F_ENABLED } from "../../src/config.js";
25
+ import { setupCortexActionBlockers, computeSetupCortexBlockers } from "./setup-cortex-blockers.js";
26
+ import { readEncoderManifest, detectPlatform } from "../../src/vector-cortex/encoder/asset.js";
27
+ import { readQualificationRecord, encoderStateDir, } from "./qualification-record.js";
23
28
  import { runSetupCortexAction, readActionLogTail, } from "./setup-cortex-actions.js";
24
29
  const ACTION_KINDS = new Set([
25
30
  "fetch-model",
@@ -29,6 +34,44 @@ const ACTION_KINDS = new Set([
29
34
  function isActionKind(v) {
30
35
  return typeof v === "string" && ACTION_KINDS.has(v);
31
36
  }
37
+ /** The encoder asset dir used by the action gate (mirrors routes-setup-cortex.ts). */
38
+ function encoderAssetDir() {
39
+ let dir = dirname(fileURLToPath(import.meta.url));
40
+ const rel = join("assets", "vector-cortex", "encoder-v1");
41
+ for (let i = 0; i < 8; i++) {
42
+ const candidate = join(dir, rel);
43
+ if (existsSync(join(candidate, "manifest.json")))
44
+ return candidate;
45
+ const next = dirname(dir);
46
+ if (next === dir)
47
+ break;
48
+ dir = next;
49
+ }
50
+ return null;
51
+ }
52
+ /**
53
+ * ENC-0g: the live computed blockers for the action gate. When both gates are
54
+ * ON, read the QualificationV1 record + verified manifest head-count and derive
55
+ * the live list (so a closed HG-1 no longer gates fetch-model/bench). Otherwise
56
+ * return null and let setupCortexActionBlockers fall back to the static base.
57
+ */
58
+ function liveActionBlockers() {
59
+ if (!ENC_0G_ENABLED() || !ENC_0F_ENABLED())
60
+ return null;
61
+ const record = readQualificationRecord(encoderStateDir());
62
+ let headCount = null;
63
+ const dir = encoderAssetDir();
64
+ if (dir !== null) {
65
+ const manifest = readEncoderManifest(dir);
66
+ if (manifest !== null)
67
+ headCount = Object.keys(manifest.heads).length;
68
+ }
69
+ return computeSetupCortexBlockers({
70
+ platform: detectPlatform(),
71
+ qualification: record,
72
+ headCount,
73
+ });
74
+ }
32
75
  /** Flag-off response, byte-identical regardless of request (VC9B absent). */
33
76
  function sendDisabled(res) {
34
77
  sendJson(res, 404, { error: "disabled" });
@@ -65,7 +108,10 @@ export function handleSetupCortexAction(req, res, ctx) {
65
108
  }
66
109
  // Hard-gate check: when an OPEN hard-gate item gates this action, do NOT
67
110
  // spawn — surface the blocker ids so the client highlights the matching rows.
68
- const blockers = setupCortexActionBlockers(action);
111
+ // ENC-0g: re-derive against the LIVE computed blockers (a closed HG-1 no
112
+ // longer blocks), falling back to the static base when the gates are off.
113
+ const live = liveActionBlockers();
114
+ const blockers = live !== null ? setupCortexActionBlockers(action, live) : setupCortexActionBlockers(action);
69
115
  if (blockers.length > 0) {
70
116
  sendJson(res, 423, {
71
117
  error: "action_blocked_by_open_item",
@@ -22,12 +22,13 @@ import { createHash } from "node:crypto";
22
22
  import { readFileSync, statSync } from "node:fs";
23
23
  import { join, dirname } from "node:path";
24
24
  import { fileURLToPath } from "node:url";
25
- import { VC9A_ENABLED, ENC_0E_ENABLED } from "../../src/config.js";
25
+ import { VC9A_ENABLED, ENC_0E_ENABLED, ENC_0F_ENABLED, ENC_0G_ENABLED } from "../../src/config.js";
26
26
  import { readEncoderManifest, verifyEncoderAsset, detectPlatform } from "../../src/vector-cortex/encoder/asset.js";
27
27
  import { selectRuntimeBackend } from "../../src/vector-cortex/encoder/runtime-select.js";
28
28
  import { sendJson } from "./routes-vector-cortex-shared.js";
29
29
  import { deriveVcStatus } from "./vc-status.js";
30
- import { SETUP_CORTEX_BLOCKERS } from "./setup-cortex-blockers.js";
30
+ import { SETUP_CORTEX_BLOCKERS, computeSetupCortexBlockers } from "./setup-cortex-blockers.js";
31
+ import { readQualificationRecord, encoderStateDir, QUALIFICATION_RECORD_UNAVAILABLE, } from "./qualification-record.js";
31
32
  /** Resolve the committed encoder-v1 asset dir by walking up to the repo root. */
32
33
  function encoderAssetDir() {
33
34
  let dir = dirname(fileURLToPath(import.meta.url));
@@ -51,7 +52,7 @@ function encoderAssetDir() {
51
52
  }
52
53
  let factCache = null;
53
54
  /** Stable cache key: manifest bytes + (size,mtime) of the asset files + platform. */
54
- function factCacheKey(dir, manifestPath) {
55
+ function factCacheKey(dir, manifestPath, stateDir, record, recordGate) {
55
56
  const platform = detectPlatform();
56
57
  if (!platform)
57
58
  return null;
@@ -67,15 +68,47 @@ function factCacheKey(dir, manifestPath) {
67
68
  catch {
68
69
  return null;
69
70
  }
71
+ // ENC-0g: fold the qualification-record gate + (mtime, content sha) into the
72
+ // key so a re-run gate invalidates memoized facts (no stale verdict served).
73
+ try {
74
+ const s = statSync(join(stateDir, "encoder-qualification.json"));
75
+ parts.push(`g:${recordGate ? "on" : "off"}`, `q:${s.size}:${s.mtimeMs}`);
76
+ parts.push(createHash("sha256").update(JSON.stringify(record)).digest("hex"));
77
+ }
78
+ catch {
79
+ parts.push(`g:${recordGate ? "on" : "off"}`, "q:absent");
80
+ }
70
81
  return parts.join("|");
71
82
  }
72
- function computeSetupCortexFacts(dir) {
83
+ function applyQualificationOverride(base, record, recordGate) {
84
+ // Flag-off (recordGate false): byte-identical verify-derived facts.
85
+ if (!recordGate)
86
+ return base;
87
+ if (record !== null) {
88
+ // The ENC-0f QualificationV1 failure maps onto the contract "demoted": a
89
+ // structurally-valid asset whose real-asset gate measured failure is honest
90
+ // as demoted, NOT qualified. mode stays verify-derived (A).
91
+ return {
92
+ ...base,
93
+ verdict: record.verdict === "qualified" ? "qualified" : "demoted",
94
+ thresholdFailures: [...record.reasons],
95
+ };
96
+ }
97
+ // No record + gate on: keep the verify verdict but surface the missing-record
98
+ // sentinel (never a fabricated pass, never a bare silent fallback).
99
+ return {
100
+ ...base,
101
+ thresholdFailures: [...base.thresholdFailures, QUALIFICATION_RECORD_UNAVAILABLE],
102
+ };
103
+ }
104
+ function computeSetupCortexFacts(dir, stateDir, record, recordGate) {
73
105
  const manifest = readEncoderManifest(dir);
74
106
  if (manifest === null) {
75
- return { mode: "C", assetDigestPrefix: null, verdict: "unavailable", thresholdFailures: [] };
107
+ return { mode: "C", assetDigestPrefix: null, verdict: "unavailable", thresholdFailures: [], headCount: null };
76
108
  }
109
+ const headCount = Object.keys(manifest.heads).length;
77
110
  const manifestPath = join(dir, "manifest.json");
78
- const key = factCacheKey(dir, manifestPath);
111
+ const key = factCacheKey(dir, manifestPath, stateDir, record, recordGate);
79
112
  if (key === null)
80
113
  return null;
81
114
  if (factCache !== null && factCache.key === key)
@@ -86,25 +119,27 @@ function computeSetupCortexFacts(dir) {
86
119
  digest = createHash("sha256").update(readFileSync(manifestPath)).digest("hex");
87
120
  }
88
121
  catch {
89
- const facts = { mode: "B", assetDigestPrefix: null, verdict: "demoted", thresholdFailures: ["ENC_ASSET_UNREADABLE"] };
122
+ const base = { mode: "B", assetDigestPrefix: null, verdict: "demoted", thresholdFailures: ["ENC_ASSET_UNREADABLE"], headCount };
123
+ const facts = applyQualificationOverride(base, record, recordGate);
90
124
  factCache = { key, facts };
91
125
  return facts;
92
126
  }
93
127
  const verify = verifyEncoderAsset(dir, manifest, detectPlatform());
94
128
  const prefix = digest.slice(0, 12);
95
- const facts = verify.ok
96
- ? { mode: "A", assetDigestPrefix: prefix, verdict: "qualified", thresholdFailures: [] }
97
- : { mode: "B", assetDigestPrefix: prefix, verdict: "demoted", thresholdFailures: [verify.code] };
129
+ const base = verify.ok
130
+ ? { mode: "A", assetDigestPrefix: prefix, verdict: "qualified", thresholdFailures: [], headCount }
131
+ : { mode: "B", assetDigestPrefix: prefix, verdict: "demoted", thresholdFailures: [verify.code], headCount };
132
+ const facts = applyQualificationOverride(base, record, recordGate);
98
133
  factCache = { key, facts };
99
134
  return facts;
100
135
  }
101
- function setupCortexFacts() {
136
+ function setupCortexFacts(record, recordGate) {
102
137
  const dir = encoderAssetDir();
103
138
  if (dir === null) {
104
- return { mode: "C", assetDigestPrefix: null, verdict: "unavailable", thresholdFailures: [] };
139
+ return { mode: "C", assetDigestPrefix: null, verdict: "unavailable", thresholdFailures: [], headCount: null };
105
140
  }
106
- const facts = computeSetupCortexFacts(dir);
107
- return facts ?? { mode: "C", assetDigestPrefix: null, verdict: "unavailable", thresholdFailures: [] };
141
+ const facts = computeSetupCortexFacts(dir, encoderStateDir(), record, recordGate);
142
+ return facts ?? { mode: "C", assetDigestPrefix: null, verdict: "unavailable", thresholdFailures: [], headCount: null };
108
143
  }
109
144
  /**
110
145
  * Reader-only GET /api/setup-cortex-status (VC9A).
@@ -122,8 +157,22 @@ export function handleSetupCortexStatus(req, res, _ctx) {
122
157
  return true;
123
158
  }
124
159
  const enabled = VC9A_ENABLED();
125
- const facts = enabled ? setupCortexFacts() : null;
126
- const blocks = enabled ? [...SETUP_CORTEX_BLOCKERS] : [];
160
+ // ENC-0g: when both gates are ON, read the QualificationV1 record and let its
161
+ // verdict override the structural verify for the `qualification` field. When
162
+ // ENC_0G is OFF, nothing below reads the record (byte-identical to ENC-0f-era).
163
+ const enc0g = enabled && ENC_0G_ENABLED();
164
+ const recordGate = enc0g && ENC_0F_ENABLED();
165
+ const record = recordGate ? readQualificationRecord(encoderStateDir()) : null;
166
+ const facts = enabled ? setupCortexFacts(record, recordGate) : null;
167
+ const blocks = enabled
168
+ ? enc0g
169
+ ? [...computeSetupCortexBlockers({
170
+ platform: detectPlatform(),
171
+ qualification: record,
172
+ headCount: facts ? facts.headCount : null,
173
+ })]
174
+ : [...SETUP_CORTEX_BLOCKERS]
175
+ : [];
127
176
  // ENC-0e: surface the darwin-x64 demotion reason additively (reader-only GET,
128
177
  // no new route). The platform is read locally; the selection is pure. On a
129
178
  // non-darwin-x64 host or flag-off this is null and the field is omitted —
@@ -0,0 +1,157 @@
1
+ /**
2
+ * dashboard-server/routes-setup-enc1a.ts — ENC-1a external-embedder Settings
3
+ * read/write + the additive writer branch.
4
+ *
5
+ * Extracted out of routes-setup.ts (which hovers at the 300-line source soft
6
+ * cap) so the ENC-1a additive read/write of the per-repo `.mega-compact.env`
7
+ * keys AND the pure external-embedder configure branch live in a sibling impl
8
+ * file. The two keys are `MEGACOMPACT_EMBEDDING_URL` and
9
+ * `MEGACOMPACT_EMBEDDING_KEY` — the exact names `embeddingConfigFromEnv`
10
+ * (src/httpEmbedder.ts) reads at runtime.
11
+ *
12
+ * The write is create-or-append: it upserts the two keys into the existing
13
+ * per-repo `.mega-compact.env`, preserving every unrelated line and never
14
+ * deleting other keys. The read reports the endpoint URL (echoed) and an
15
+ * `apiKeySet` boolean ONLY — the raw API key is never returned, logged, or
16
+ * emitted (redaction invariant).
17
+ *
18
+ * Guardrails: PREVENT-PI-004 (local filesystem writes only, zero network),
19
+ * PREVENT-001 (null-safe JSON), PREVENT-011 (no `any`).
20
+ */
21
+ import { writeFileSync, mkdirSync, readFileSync, existsSync } from "node:fs";
22
+ import { join } from "node:path";
23
+ import { ENC_1A_ENABLED } from "../../src/config.js";
24
+ const URL_KEY = "MEGACOMPACT_EMBEDDING_URL";
25
+ const API_KEY = "MEGACOMPACT_EMBEDDING_KEY";
26
+ function envPath(stateDir) {
27
+ return join(stateDir, ".mega-compact.env");
28
+ }
29
+ /** Extract the value of `export MEGACOMPACT_X="..."` (or unquoted) from a line. */
30
+ function lineValue(line, key) {
31
+ const m = line.match(new RegExp(`^export\\s+${key}=(.*)$`));
32
+ if (!m)
33
+ return null;
34
+ const rest = m[1].trim();
35
+ const q = rest.match(/^"([^"]*)"$/);
36
+ return q ? q[1] : rest;
37
+ }
38
+ /** True when the payload carries the ENC-1a keys (flag-gated). */
39
+ export function wantsEnc1a(body) {
40
+ return (ENC_1A_ENABLED() &&
41
+ (typeof body.embeddingEndpointUrl === "string" ||
42
+ typeof body.embeddingApiKey === "string"));
43
+ }
44
+ /** Additive GET status fields for ENC-1a (both omitted when flag-off): the
45
+ * endpoint URL (echoed) + an apiKeySet boolean (true/false) ONLY — never the
46
+ * raw key. The boolean is always present when the flag is on so an absent key
47
+ * is reported as `embeddingApiKeySet:false` (non-fatal, triad B). */
48
+ export function enc1aStatusFields(stateDir) {
49
+ if (!ENC_1A_ENABLED())
50
+ return {};
51
+ const enc1a = readEnc1aEnv(stateDir);
52
+ return {
53
+ ...(enc1a.endpointUrl !== null ? { embeddingEndpointUrl: enc1a.endpointUrl } : {}),
54
+ embeddingApiKeySet: enc1a.apiKeySet,
55
+ };
56
+ }
57
+ /** Read the ENC-1a keys from the per-repo `.mega-compact.env` (never the key
58
+ * itself to callers — only its presence). */
59
+ export function readEnc1aEnv(stateDir) {
60
+ try {
61
+ const p = envPath(stateDir);
62
+ if (!existsSync(p))
63
+ return { endpointUrl: null, apiKeySet: false };
64
+ const content = readFileSync(p, "utf8");
65
+ const lines = content.split(/\r?\n/);
66
+ let endpointUrl = null;
67
+ let apiKeySet = false;
68
+ for (const line of lines) {
69
+ const url = lineValue(line, URL_KEY);
70
+ if (url !== null)
71
+ endpointUrl = url;
72
+ const key = lineValue(line, API_KEY);
73
+ if (key !== null && key.length > 0)
74
+ apiKeySet = true;
75
+ }
76
+ return { endpointUrl, apiKeySet };
77
+ }
78
+ catch {
79
+ return { endpointUrl: null, apiKeySet: false };
80
+ }
81
+ }
82
+ /** Upsert the ENC-1a keys into the per-repo `.mega-compact.env`. Creates the
83
+ * file if absent; preserves every unrelated line; never deletes other keys.
84
+ * Passing `null` for a key leaves that line untouched. */
85
+ export function writeEnc1aEnv(stateDir, entries) {
86
+ const p = envPath(stateDir);
87
+ const existingLines = existsSync(p)
88
+ ? readFileSync(p, "utf8").split(/\r?\n/)
89
+ : [];
90
+ const out = [];
91
+ let urlWritten = false;
92
+ let keyWritten = false;
93
+ for (const line of existingLines) {
94
+ if (entries.endpointUrl !== null && lineValue(line, URL_KEY) !== null) {
95
+ out.push(`export ${URL_KEY}="${entries.endpointUrl ?? ""}"`);
96
+ urlWritten = true;
97
+ continue;
98
+ }
99
+ if (entries.apiKey !== null && lineValue(line, API_KEY) !== null) {
100
+ out.push(`export ${API_KEY}="${entries.apiKey ?? ""}"`);
101
+ keyWritten = true;
102
+ continue;
103
+ }
104
+ out.push(line);
105
+ }
106
+ if (entries.endpointUrl !== null && !urlWritten) {
107
+ out.push(`export ${URL_KEY}="${entries.endpointUrl}"`);
108
+ }
109
+ if (entries.apiKey !== null && !keyWritten) {
110
+ out.push(`export ${API_KEY}="${entries.apiKey ?? ""}"`);
111
+ }
112
+ mkdirSync(stateDir, { recursive: true });
113
+ writeFileSync(p, out.join("\n"), "utf-8");
114
+ return p;
115
+ }
116
+ /** If the payload is a pure ENC-1a configure (new keys, no valid embedder
117
+ * selection), write them additively to the per-repo env and reply. Returns
118
+ * true when it fully handled the request. */
119
+ export function tryEnc1aConfigure(body, res, ctx) {
120
+ if (!wantsEnc1a(body))
121
+ return false;
122
+ const embedder = body.embedder;
123
+ const embedderValid = !!embedder &&
124
+ (embedder === "ollama" || embedder === "llama" || embedder === "trigram" ||
125
+ embedder === "custom" || embedder === "onnx");
126
+ if (embedderValid)
127
+ return false; // combined payload — integrated after the embedder write
128
+ try {
129
+ const envPath = writeEnc1aEnv(ctx.stateDir, {
130
+ endpointUrl: typeof body.embeddingEndpointUrl === "string" && body.embeddingEndpointUrl
131
+ ? body.embeddingEndpointUrl
132
+ : null,
133
+ apiKey: typeof body.embeddingApiKey === "string" ? body.embeddingApiKey : null,
134
+ });
135
+ const currentUrl = process.env["MEGACOMPACT_EMBEDDING_URL"];
136
+ const alreadyActive = typeof body.embeddingEndpointUrl === "string" &&
137
+ currentUrl === body.embeddingEndpointUrl;
138
+ const resp = {
139
+ embedder: "custom",
140
+ url: typeof body.embeddingEndpointUrl === "string"
141
+ ? body.embeddingEndpointUrl
142
+ : null,
143
+ envPath,
144
+ restartRequired: !alreadyActive,
145
+ alreadyActive,
146
+ };
147
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
148
+ res.writeHead(200, { "Content-Type": "application/json" });
149
+ res.end(JSON.stringify(resp));
150
+ return true;
151
+ }
152
+ catch (e) {
153
+ res.writeHead(500, { "Content-Type": "application/json" });
154
+ res.end(JSON.stringify({ error: `write_failed: ${e instanceof Error ? e.message : String(e)}` }));
155
+ return true;
156
+ }
157
+ }
@@ -11,6 +11,7 @@
11
11
  import { writeFileSync, mkdirSync, readFileSync, existsSync } from "node:fs";
12
12
  import { join } from "node:path";
13
13
  import { VC9D_ENABLED } from "../../src/config.js";
14
+ import { enc1aStatusFields, writeEnc1aEnv, tryEnc1aConfigure, wantsEnc1a } from "./routes-setup-enc1a.js";
14
15
  import { detectOllama, detectLlamaCpp, detectOnnx, memoizedDetectOllama, memoizedDetectLlamaCpp, memoizedDetectOnnx, } from "./routes-setup-detect-cache.js";
15
16
  // ---------------------------------------------------------------------------
16
17
  // handleSetupStatus — "/api/setup-status"
@@ -56,6 +57,9 @@ export function handleSetupStatus(req, res, ctx) {
56
57
  }
57
58
  const configured = detectConfiguredEmbedder(ctx.stateDir);
58
59
  const active = detectCurrentEmbedder();
60
+ // ENC-1a (flag-gated, additive): echo the persisted endpoint URL and an
61
+ // embeddingApiKeySet boolean ONLY — the raw API key is never returned. When
62
+ // the flag is off (or neither is set) both fields are simply omitted.
59
63
  const body = {
60
64
  currentEmbedder: active,
61
65
  configuredEmbedder: configured.embedder,
@@ -64,6 +68,7 @@ export function handleSetupStatus(req, res, ctx) {
64
68
  embeddingUrl: process.env["MEGACOMPACT_EMBEDDING_URL"] ?? configured.url,
65
69
  embedCache: process.env["MEGACOMPACT_EMBED_CACHE"] ?? null,
66
70
  minilm: ["1", "true", "yes"].includes((process.env["MEGACOMPACT_MINILM"] ?? "").trim().toLowerCase()),
71
+ ...enc1aStatusFields(ctx.stateDir),
67
72
  };
68
73
  // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
69
74
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -165,6 +170,12 @@ export function handleSetupConfigure(req, res, ctx) {
165
170
  return;
166
171
  }
167
172
  const body = parsed.value;
173
+ // ENC-1a (flag-gated, additive): a pure external-embedder configure
174
+ // (new keys, no embedder selection) is handled by the sibling writer.
175
+ // Flag-off = the keys are simply not recognized, falling through to the
176
+ // pre-ENC-1a embedder path below (byte-identical predecessor).
177
+ if (tryEnc1aConfigure(body, res, ctx))
178
+ return;
168
179
  const embedder = body.embedder;
169
180
  if (embedder !== "ollama" && embedder !== "llama" && embedder !== "trigram" && embedder !== "custom" && embedder !== "onnx") {
170
181
  res.writeHead(400, { "Content-Type": "application/json" });
@@ -225,6 +236,16 @@ export function handleSetupConfigure(req, res, ctx) {
225
236
  res.end(JSON.stringify({ error: `write_failed: ${e instanceof Error ? e.message : String(e)}` }));
226
237
  return;
227
238
  }
239
+ // ENC-1a: a combined payload (valid embedder + new keys) also upserts
240
+ // the endpoint/key lines onto the freshly-written env file.
241
+ if (wantsEnc1a(body)) {
242
+ writeEnc1aEnv(stateDir, {
243
+ endpointUrl: typeof body.embeddingEndpointUrl === "string" && body.embeddingEndpointUrl
244
+ ? body.embeddingEndpointUrl
245
+ : null,
246
+ apiKey: typeof body.embeddingApiKey === "string" ? body.embeddingApiKey : null,
247
+ });
248
+ }
228
249
  // Detect if the new config matches what's already active (no restart needed).
229
250
  const currentUrl = process.env["MEGACOMPACT_EMBEDDING_URL"];
230
251
  const alreadyActive = (resolvedUrl === null && !currentUrl) || (resolvedUrl !== null && currentUrl === resolvedUrl);