pi-mega-compact 0.20.53 → 0.20.55

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 (88) hide show
  1. package/README.md +1 -1
  2. package/dist/config/vector-cortex-cosfp.js +31 -0
  3. package/dist/config/vector-cortex.js +2 -0
  4. package/dist/config.js +1 -1
  5. package/dist/extensions/dashboard-server/api-contracts/cosine-fp.js +8 -0
  6. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry-ext.js +7 -0
  7. package/dist/extensions/dashboard-server/route-dispatch.js +3 -1
  8. package/dist/extensions/dashboard-server/routes-cosine-fp.js +261 -0
  9. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +8 -0
  10. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
  11. package/dist/extensions/dashboard-server/routes.js +1 -0
  12. package/dist/src/config/dedup.js +11 -0
  13. package/dist/src/config/vector-cortex-cosfp.js +31 -0
  14. package/dist/src/config/vector-cortex.js +2 -0
  15. package/dist/src/config.js +1 -1
  16. package/extensions/dashboard-client/dist/assets/{AreaChart-COjQ_jJm.js → AreaChart-DlndN0p2.js} +2 -2
  17. package/extensions/dashboard-client/dist/assets/{AreaChart-COjQ_jJm.js.map → AreaChart-DlndN0p2.js.map} +1 -1
  18. package/extensions/dashboard-client/dist/assets/{BarChart-eAIpwb8c.js → BarChart-DH6rZ60s.js} +2 -2
  19. package/extensions/dashboard-client/dist/assets/{BarChart-eAIpwb8c.js.map → BarChart-DH6rZ60s.js.map} +1 -1
  20. package/extensions/dashboard-client/dist/assets/CacheTab-Qj_NYKw3.js +2 -0
  21. package/extensions/dashboard-client/dist/assets/{CacheTab-BS_G9_YV.js.map → CacheTab-Qj_NYKw3.js.map} +1 -1
  22. package/extensions/dashboard-client/dist/assets/{EventsTab-CDFk5d3P.js → EventsTab-3QlIPMFj.js} +2 -2
  23. package/extensions/dashboard-client/dist/assets/{EventsTab-CDFk5d3P.js.map → EventsTab-3QlIPMFj.js.map} +1 -1
  24. package/extensions/dashboard-client/dist/assets/{HealthTab-zFyPJb5b.js → HealthTab-DTwitpIb.js} +2 -2
  25. package/extensions/dashboard-client/dist/assets/{HealthTab-zFyPJb5b.js.map → HealthTab-DTwitpIb.js.map} +1 -1
  26. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DxJX4lXh.js → MaintenanceTab-DkVlBnp7.js} +2 -2
  27. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DxJX4lXh.js.map → MaintenanceTab-DkVlBnp7.js.map} +1 -1
  28. package/extensions/dashboard-client/dist/assets/MemoryMapTab-B-pHH85r.js +2 -0
  29. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CRy5I6mg.js.map → MemoryMapTab-B-pHH85r.js.map} +1 -1
  30. package/extensions/dashboard-client/dist/assets/{MetricsTab-K4kPea0Q.js → MetricsTab-C8VpwhJN.js} +2 -2
  31. package/extensions/dashboard-client/dist/assets/{MetricsTab-K4kPea0Q.js.map → MetricsTab-C8VpwhJN.js.map} +1 -1
  32. package/extensions/dashboard-client/dist/assets/{OverviewTab-BZoJ1oOv.js → OverviewTab-C_JZlM_Q.js} +2 -2
  33. package/extensions/dashboard-client/dist/assets/{OverviewTab-BZoJ1oOv.js.map → OverviewTab-C_JZlM_Q.js.map} +1 -1
  34. package/extensions/dashboard-client/dist/assets/{ReposTab-uMFKxT0a.js → ReposTab-rRMlub-E.js} +2 -2
  35. package/extensions/dashboard-client/dist/assets/{ReposTab-uMFKxT0a.js.map → ReposTab-rRMlub-E.js.map} +1 -1
  36. package/extensions/dashboard-client/dist/assets/{SessionsTab-DDzmcCuH.js → SessionsTab-CL6S094G.js} +2 -2
  37. package/extensions/dashboard-client/dist/assets/{SessionsTab-DDzmcCuH.js.map → SessionsTab-CL6S094G.js.map} +1 -1
  38. package/extensions/dashboard-client/dist/assets/SetupTab-C6b8YuBK.js +3 -0
  39. package/extensions/dashboard-client/dist/assets/SetupTab-C6b8YuBK.js.map +1 -0
  40. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-D_mBYAzE.js → TimeSavedCard-B-8NCvzd.js} +2 -2
  41. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-D_mBYAzE.js.map → TimeSavedCard-B-8NCvzd.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/{TurnsTab-C6xr2lsO.js → TurnsTab-C2MVgG4N.js} +3 -3
  43. package/extensions/dashboard-client/dist/assets/{TurnsTab-C6xr2lsO.js.map → TurnsTab-C2MVgG4N.js.map} +1 -1
  44. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-J0DVlxH0.js → VcStatusBadge-CvlS_fvh.js} +2 -2
  45. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-J0DVlxH0.js.map → VcStatusBadge-CvlS_fvh.js.map} +1 -1
  46. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-AvL73DpJ.js → VectorCortexTab-BXCAzwQm.js} +2 -2
  47. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-AvL73DpJ.js.map → VectorCortexTab-BXCAzwQm.js.map} +1 -1
  48. package/extensions/dashboard-client/dist/assets/{WikiTab-DVcIWjLT.js → WikiTab-DUivYmJ5.js} +2 -2
  49. package/extensions/dashboard-client/dist/assets/{WikiTab-DVcIWjLT.js.map → WikiTab-DUivYmJ5.js.map} +1 -1
  50. package/extensions/dashboard-client/dist/assets/{button-Cl_gFaIf.js → button-dTpuinxr.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{button-Cl_gFaIf.js.map → button-dTpuinxr.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/{card-DMzHffVs.js → card-DNwZ7dSg.js} +2 -2
  53. package/extensions/dashboard-client/dist/assets/{card-DMzHffVs.js.map → card-DNwZ7dSg.js.map} +1 -1
  54. package/extensions/dashboard-client/dist/assets/client-extra-CQuCpjyO.js +2 -0
  55. package/extensions/dashboard-client/dist/assets/{client-extra-Dkkppa5C.js.map → client-extra-CQuCpjyO.js.map} +1 -1
  56. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-Bi8zQmZN.js → generateCategoricalChart-D1HV4d79.js} +2 -2
  57. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-Bi8zQmZN.js.map → generateCategoricalChart-D1HV4d79.js.map} +1 -1
  58. package/extensions/dashboard-client/dist/assets/{index-mZRDesSq.js → index-Bi5YNsLx.js} +11 -11
  59. package/extensions/dashboard-client/dist/assets/index-Bi5YNsLx.js.map +1 -0
  60. package/extensions/dashboard-client/dist/assets/{switch-K_lsOl6x.js → switch-DiKc2q4n.js} +2 -2
  61. package/extensions/dashboard-client/dist/assets/{switch-K_lsOl6x.js.map → switch-DiKc2q4n.js.map} +1 -1
  62. package/extensions/dashboard-client/dist/assets/{toggle-67IU2JLn.js → toggle-BBYD10JO.js} +2 -2
  63. package/extensions/dashboard-client/dist/assets/{toggle-67IU2JLn.js.map → toggle-BBYD10JO.js.map} +1 -1
  64. package/extensions/dashboard-client/dist/assets/{useSSE-DiLgyP7C.js → useSSE-CWUSDhIb.js} +2 -2
  65. package/extensions/dashboard-client/dist/assets/{useSSE-DiLgyP7C.js.map → useSSE-CWUSDhIb.js.map} +1 -1
  66. package/extensions/dashboard-client/dist/index.html +1 -1
  67. package/extensions/dashboard-client/src/tabs/SetupTab/CortexSetup.tsx +2 -0
  68. package/extensions/dashboard-client/src/tabs/SetupTab/VectorCortexCosineFpCard.tsx +130 -0
  69. package/extensions/dashboard-server/api-contracts/cosine-fp.ts +81 -0
  70. package/extensions/dashboard-server/api-contracts/endpoints/registry-ext.ts +11 -0
  71. package/extensions/dashboard-server/api-contracts/index.ts +14 -0
  72. package/extensions/dashboard-server/assets/cosine-fp-report.json +1 -0
  73. package/extensions/dashboard-server/route-dispatch.ts +2 -0
  74. package/extensions/dashboard-server/routes-cosine-fp.ts +288 -0
  75. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +8 -0
  76. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
  77. package/extensions/dashboard-server/routes.ts +1 -0
  78. package/package.json +1 -1
  79. package/src/config/dedup.ts +17 -0
  80. package/src/config/vector-cortex-cosfp.ts +34 -0
  81. package/src/config/vector-cortex.ts +2 -0
  82. package/src/config.ts +1 -0
  83. package/extensions/dashboard-client/dist/assets/CacheTab-BS_G9_YV.js +0 -2
  84. package/extensions/dashboard-client/dist/assets/MemoryMapTab-CRy5I6mg.js +0 -2
  85. package/extensions/dashboard-client/dist/assets/SetupTab-KLTE-VKV.js +0 -3
  86. package/extensions/dashboard-client/dist/assets/SetupTab-KLTE-VKV.js.map +0 -1
  87. package/extensions/dashboard-client/dist/assets/client-extra-Dkkppa5C.js +0 -2
  88. package/extensions/dashboard-client/dist/assets/index-mZRDesSq.js.map +0 -1
@@ -0,0 +1,81 @@
1
+ /**
2
+ * api-contracts/cosine-fp.ts — cosine-FP report API contract (COS-FP-A).
3
+ *
4
+ * Reader-only shape for GET /api/cosine-fp-report: the last written synthetic
5
+ * bench aggregate + report digest + recommendation. The bench emits counts +
6
+ * fractions + digests ONLY (EVAL-REDACT-002) — never template text. No `any`.
7
+ */
8
+
9
+ /** Derived endpoint status (from vc-status.ts). */
10
+ export type CosineFpStatus = "live" | "awaiting_data" | "deferred" | "structural" | "off";
11
+
12
+ /** The bench aggregate's own outcome label. */
13
+ export type CosineFpBenchStatus = "ok" | "no_data" | "off" | "awaiting_data";
14
+
15
+ export interface CosineFpGrid {
16
+ readonly lo: number;
17
+ readonly hi: number;
18
+ readonly step: number;
19
+ readonly points: number;
20
+ }
21
+
22
+ export interface CosineFpPerTypeCounts {
23
+ readonly items: number;
24
+ readonly dup: number;
25
+ readonly near: number;
26
+ readonly clean: number;
27
+ }
28
+
29
+ export interface CosineFpPerTypeResult {
30
+ readonly fp: number;
31
+ readonly fpRate: number | null;
32
+ readonly fn: number;
33
+ readonly fnRate: number | null;
34
+ }
35
+
36
+ export interface CosineFpPerTypeRow {
37
+ readonly code: CosineFpPerTypeResult;
38
+ readonly prose: CosineFpPerTypeResult;
39
+ readonly mixed: CosineFpPerTypeResult;
40
+ }
41
+
42
+ export interface CosineFpRow {
43
+ readonly threshold: number;
44
+ readonly overallFpRate: number | null;
45
+ readonly overallFnRate: number | null;
46
+ readonly overallF1: number | null;
47
+ readonly perType: CosineFpPerTypeRow;
48
+ }
49
+
50
+ export interface CosineFpCorpusSummary {
51
+ readonly items: number;
52
+ readonly pairs: number;
53
+ readonly types: string[];
54
+ readonly perType: Readonly<Record<string, CosineFpPerTypeCounts>>;
55
+ readonly digest: string | null;
56
+ }
57
+
58
+ export interface CosineFpOverrides {
59
+ readonly code: number | null;
60
+ readonly prose: number | null;
61
+ readonly mixed: number | null;
62
+ }
63
+
64
+ /** Response body for GET /api/cosine-fp-report. */
65
+ export interface CosineFpReportV1 {
66
+ /** Derived endpoint status (deriveVcStatus): off (flag) / awaiting_data (no run). */
67
+ readonly status: CosineFpStatus;
68
+ /** Outcome the bench itself recorded: ok / no_data / off / awaiting_data. */
69
+ readonly benchStatus: CosineFpBenchStatus;
70
+ readonly seed: number | null;
71
+ readonly grid: CosineFpGrid | null;
72
+ readonly corpusSummary: CosineFpCorpusSummary | null;
73
+ readonly rows: CosineFpRow[] | null;
74
+ readonly recommendedDefault: number | null;
75
+ readonly shippedDefault: number;
76
+ readonly overrides: CosineFpOverrides | null;
77
+ readonly fpBudget: number | null;
78
+ readonly digest: string | null;
79
+ /** mtime (epoch ms) of the bench-run aggregate file backing this response. */
80
+ readonly reportFileMtime: number | null;
81
+ }
@@ -31,6 +31,7 @@ import type {
31
31
  CortexImproveStatus,
32
32
  } from "../cortex-improve.js";
33
33
  import type { DedupTierAttributionResponse } from "../dedup-attribution.js";
34
+ import type { CosineFpReportV1 } from "../cosine-fp.js";
34
35
 
35
36
  /** Additive endpoint entries spread into the ENDPOINTS registry. */
36
37
  export const EXTRA_ENDPOINTS = {
@@ -169,4 +170,14 @@ export const EXTRA_ENDPOINTS = {
169
170
  undefined,
170
171
  DedupTierAttributionResponse
171
172
  >,
173
+
174
+ // ─── COS-FP-A cosine threshold report ────────────────────────────
175
+
176
+ /** GET /api/cosine-fp-report — reader-only synthetic FP bench recommendation. */
177
+ cosineFpReport: {
178
+ method: "GET",
179
+ path: "/api/cosine-fp-report",
180
+ description:
181
+ "Reader-only cosine-FP report (COS-FP-A): the last written synthetic bench aggregate — grid summary, per-content-type FP/FN/F1, recommended default, report digest. Counts + fractions + digests only, never template text. Missing report → awaiting_data; flag-off returns 404.",
182
+ } as const satisfies EndpointDef<"GET", undefined, CosineFpReportV1>,
172
183
  } as const;
@@ -343,3 +343,17 @@ export type {
343
343
  DedupTierAccumulator,
344
344
  DedupTierAttributionResponse,
345
345
  } from "./dedup-attribution.js";
346
+
347
+ // COS-FP-A — synthetic cosine threshold report
348
+ export type {
349
+ CosineFpStatus,
350
+ CosineFpBenchStatus,
351
+ CosineFpGrid,
352
+ CosineFpPerTypeCounts,
353
+ CosineFpPerTypeResult,
354
+ CosineFpPerTypeRow,
355
+ CosineFpRow,
356
+ CosineFpCorpusSummary,
357
+ CosineFpOverrides,
358
+ CosineFpReportV1,
359
+ } from "./cosine-fp.js";
@@ -0,0 +1 @@
1
+ {"corpusSummary":{"digest":"53f87630c2fa1ee5c4020d025fda56caae5020110bb12521e6c9a85981e180c5","items":72,"pairs":2556,"perType":{"code":{"clean":8,"dup":8,"items":24,"near":8},"mixed":{"clean":8,"dup":8,"items":24,"near":8},"prose":{"clean":8,"dup":8,"items":24,"near":8}},"types":["code","prose","mixed"]},"digest":"2312c120c43c75ff46db9fc8f362c25df879a54862790d8d2cbac2e88466d646","fpBudget":0.05,"grid":{"hi":0.98,"lo":0.8,"points":37,"step":0.005},"overrides":{"code":0.915,"mixed":0.955,"prose":0.95},"recommendedDefault":0.815,"rows":[{"overallF1":0.1279,"overallFnRate":0.9314,"overallFpRate":0.0612,"perType":{"code":{"fn":128,"fnRate":0.8205,"fp":23,"fpRate":0.1917},"mixed":{"fn":135,"fnRate":0.8654,"fp":13,"fpRate":0.1083},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.8},{"overallF1":0.1255,"overallFnRate":0.9328,"overallFpRate":0.0576,"perType":{"code":{"fn":128,"fnRate":0.8205,"fp":22,"fpRate":0.1833},"mixed":{"fn":135,"fnRate":0.8654,"fp":12,"fpRate":0.1},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.805},{"overallF1":0.1182,"overallFnRate":0.937,"overallFpRate":0.0532,"perType":{"code":{"fn":131,"fnRate":0.8397,"fp":21,"fpRate":0.175},"mixed":{"fn":136,"fnRate":0.8718,"fp":10,"fpRate":0.0833},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.81},{"overallF1":0.1133,"overallFnRate":0.9398,"overallFpRate":0.0488,"perType":{"code":{"fn":131,"fnRate":0.8397,"fp":18,"fpRate":0.15},"mixed":{"fn":139,"fnRate":0.891,"fp":9,"fpRate":0.075},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.815},{"overallF1":0.112,"overallFnRate":0.9405,"overallFpRate":0.0488,"perType":{"code":{"fn":131,"fnRate":0.8397,"fp":18,"fpRate":0.15},"mixed":{"fn":139,"fnRate":0.891,"fp":9,"fpRate":0.075},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.82},{"overallF1":0.1108,"overallFnRate":0.9412,"overallFpRate":0.0479,"perType":{"code":{"fn":132,"fnRate":0.8462,"fp":18,"fpRate":0.15},"mixed":{"fn":139,"fnRate":0.891,"fp":9,"fpRate":0.075},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.825},{"overallF1":0.1096,"overallFnRate":0.9419,"overallFpRate":0.0461,"perType":{"code":{"fn":132,"fnRate":0.8462,"fp":17,"fpRate":0.1417},"mixed":{"fn":139,"fnRate":0.891,"fp":9,"fpRate":0.075},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.83},{"overallF1":0.0945,"overallFnRate":0.9503,"overallFpRate":0.0399,"perType":{"code":{"fn":136,"fnRate":0.8718,"fp":15,"fpRate":0.125},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.835},{"overallF1":0.0844,"overallFnRate":0.9559,"overallFpRate":0.0337,"perType":{"code":{"fn":139,"fnRate":0.891,"fp":13,"fpRate":0.1083},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.84},{"overallF1":0.0805,"overallFnRate":0.958,"overallFpRate":0.0301,"perType":{"code":{"fn":140,"fnRate":0.8974,"fp":10,"fpRate":0.0833},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.845},{"overallF1":0.0805,"overallFnRate":0.958,"overallFpRate":0.0293,"perType":{"code":{"fn":140,"fnRate":0.8974,"fp":10,"fpRate":0.0833},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.85},{"overallF1":0.078,"overallFnRate":0.9594,"overallFpRate":0.0284,"perType":{"code":{"fn":140,"fnRate":0.8974,"fp":9,"fpRate":0.075},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.855},{"overallF1":0.0728,"overallFnRate":0.9622,"overallFpRate":0.0248,"perType":{"code":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.86},{"overallF1":0.0728,"overallFnRate":0.9622,"overallFpRate":0.0239,"perType":{"code":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.865},{"overallF1":0.0689,"overallFnRate":0.9643,"overallFpRate":0.023,"perType":{"code":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.87},{"overallF1":0.0689,"overallFnRate":0.9643,"overallFpRate":0.0222,"perType":{"code":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.875},{"overallF1":0.0689,"overallFnRate":0.9643,"overallFpRate":0.0222,"perType":{"code":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.88},{"overallF1":0.0689,"overallFnRate":0.9643,"overallFpRate":0.0222,"perType":{"code":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.885},{"overallF1":0.065,"overallFnRate":0.9664,"overallFpRate":0.0213,"perType":{"code":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.89},{"overallF1":0.065,"overallFnRate":0.9664,"overallFpRate":0.0213,"perType":{"code":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.895},{"overallF1":0.065,"overallFnRate":0.9664,"overallFpRate":0.0213,"perType":{"code":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.9},{"overallF1":0.0637,"overallFnRate":0.9671,"overallFpRate":0.0204,"perType":{"code":{"fn":141,"fnRate":0.9038,"fp":7,"fpRate":0.0583},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.905},{"overallF1":0.0624,"overallFnRate":0.9678,"overallFpRate":0.0204,"perType":{"code":{"fn":142,"fnRate":0.9103,"fp":7,"fpRate":0.0583},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.91},{"overallF1":0.0624,"overallFnRate":0.9678,"overallFpRate":0.0195,"perType":{"code":{"fn":142,"fnRate":0.9103,"fp":6,"fpRate":0.05},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.915},{"overallF1":0.0611,"overallFnRate":0.9685,"overallFpRate":0.0186,"perType":{"code":{"fn":143,"fnRate":0.9167,"fp":5,"fpRate":0.0417},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.92},{"overallF1":0.0611,"overallFnRate":0.9685,"overallFpRate":0.0186,"perType":{"code":{"fn":143,"fnRate":0.9167,"fp":5,"fpRate":0.0417},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.925},{"overallF1":0.0611,"overallFnRate":0.9685,"overallFpRate":0.0186,"perType":{"code":{"fn":143,"fnRate":0.9167,"fp":5,"fpRate":0.0417},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.93},{"overallF1":0.0598,"overallFnRate":0.9692,"overallFpRate":0.0177,"perType":{"code":{"fn":144,"fnRate":0.9231,"fp":4,"fpRate":0.0333},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.935},{"overallF1":0.0598,"overallFnRate":0.9692,"overallFpRate":0.0177,"perType":{"code":{"fn":144,"fnRate":0.9231,"fp":4,"fpRate":0.0333},"mixed":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.94},{"overallF1":0.0558,"overallFnRate":0.9713,"overallFpRate":0.0151,"perType":{"code":{"fn":146,"fnRate":0.9359,"fp":2,"fpRate":0.0167},"mixed":{"fn":141,"fnRate":0.9038,"fp":7,"fpRate":0.0583},"prose":{"fn":140,"fnRate":0.8974,"fp":8,"fpRate":0.0667}},"threshold":0.945},{"overallF1":0.0505,"overallFnRate":0.9741,"overallFpRate":0.0115,"perType":{"code":{"fn":148,"fnRate":0.9487,"fp":0,"fpRate":0},"mixed":{"fn":141,"fnRate":0.9038,"fp":7,"fpRate":0.0583},"prose":{"fn":142,"fnRate":0.9103,"fp":6,"fpRate":0.05}},"threshold":0.95},{"overallF1":0.0465,"overallFnRate":0.9762,"overallFpRate":0.0089,"perType":{"code":{"fn":148,"fnRate":0.9487,"fp":0,"fpRate":0},"mixed":{"fn":143,"fnRate":0.9167,"fp":5,"fpRate":0.0417},"prose":{"fn":143,"fnRate":0.9167,"fp":5,"fpRate":0.0417}},"threshold":0.955},{"overallF1":0.0438,"overallFnRate":0.9776,"overallFpRate":0.0071,"perType":{"code":{"fn":148,"fnRate":0.9487,"fp":0,"fpRate":0},"mixed":{"fn":143,"fnRate":0.9167,"fp":5,"fpRate":0.0417},"prose":{"fn":145,"fnRate":0.9295,"fp":3,"fpRate":0.025}},"threshold":0.96},{"overallF1":0.0411,"overallFnRate":0.979,"overallFpRate":0.0053,"perType":{"code":{"fn":148,"fnRate":0.9487,"fp":0,"fpRate":0},"mixed":{"fn":144,"fnRate":0.9231,"fp":4,"fpRate":0.0333},"prose":{"fn":146,"fnRate":0.9359,"fp":2,"fpRate":0.0167}},"threshold":0.965},{"overallF1":0.0385,"overallFnRate":0.9804,"overallFpRate":0.0035,"perType":{"code":{"fn":148,"fnRate":0.9487,"fp":0,"fpRate":0},"mixed":{"fn":145,"fnRate":0.9295,"fp":3,"fpRate":0.025},"prose":{"fn":147,"fnRate":0.9423,"fp":1,"fpRate":0.0083}},"threshold":0.97},{"overallF1":0.0344,"overallFnRate":0.9825,"overallFpRate":0.0009,"perType":{"code":{"fn":148,"fnRate":0.9487,"fp":0,"fpRate":0},"mixed":{"fn":148,"fnRate":0.9487,"fp":0,"fpRate":0},"prose":{"fn":147,"fnRate":0.9423,"fp":1,"fpRate":0.0083}},"threshold":0.975},{"overallF1":0.0331,"overallFnRate":0.9832,"overallFpRate":0,"perType":{"code":{"fn":148,"fnRate":0.9487,"fp":0,"fpRate":0},"mixed":{"fn":148,"fnRate":0.9487,"fp":0,"fpRate":0},"prose":{"fn":148,"fnRate":0.9487,"fp":0,"fpRate":0}},"threshold":0.98}],"seed":20260806,"shippedDefault":0.85,"status":"ok"}
@@ -63,6 +63,7 @@ import {
63
63
  handleImproveCortex,
64
64
  handleImproveCortexStatus,
65
65
  handleDedupTierAttribution,
66
+ handleCosineFpReport,
66
67
  } from "./routes.js";
67
68
  // VC6C repair lives in its own module (routes-vector-cortex-repair.ts) so the
68
69
  // heal route file stays well under the 400-line extension soft limit.
@@ -160,5 +161,6 @@ export function dispatchRoutes(
160
161
  if (handleImproveCortex(req, res, ctx)) return true;
161
162
  if (handleImproveCortexStatus(req, res, ctx)) return true;
162
163
  if (handleDedupTierAttribution(req, res, ctx)) return true;
164
+ if (handleCosineFpReport(req, res, ctx)) return true;
163
165
  return false;
164
166
  }
@@ -0,0 +1,288 @@
1
+ /**
2
+ * dashboard-server/routes-cosine-fp.ts — COS-FP-A cosine-FP report route.
3
+ *
4
+ * GET /api/cosine-fp-report — reader-only aggregate answering "what did the last
5
+ * synthetic bench recommend for the L2 cosine threshold?". Reads the last
6
+ * written bench-run aggregate (scripts/cosine-fp/bench-run/cosine-fp-report.json,
7
+ * located by walking up to the repo root) + its mtime, memoized by
8
+ * {mtime,size} (mirrors routes-vector-cortex-health.ts memoized-facts), and
9
+ * serves the report digest + recommendation + grid summary. Reader-only: emits
10
+ * counts + fractions + digests, never template text (EVAL-REDACT-002).
11
+ *
12
+ * Flag-off (MEGACOMPACT_COSINE_FP_BENCH=0) returns 404 — byte-identical
13
+ * predecessor. Missing report file is reported as `awaiting_data` with
14
+ * benchStatus awaiting_data — never a fabricated zero row (COS-FP-A-003).
15
+ *
16
+ * Guardrails: PREVENT-PI-004 (local filesystem read only), PREVENT-001
17
+ * (JSON.parse guarded), PREVENT-011 (no `any`).
18
+ */
19
+
20
+ import type { IncomingMessage, ServerResponse } from "node:http";
21
+ import { readFileSync, statSync } from "node:fs";
22
+ import { join, dirname } from "node:path";
23
+ import { fileURLToPath } from "node:url";
24
+ import type { RouteContext } from "./routes-core.js";
25
+ import { sendJson } from "./routes-vector-cortex-shared.js";
26
+ import { COSINE_FP_BENCH_ENABLED } from "../../src/config.js";
27
+ import { deriveVcStatus } from "./vc-status.js";
28
+ import type {
29
+ CosineFpReportV1,
30
+ CosineFpBenchStatus,
31
+ CosineFpPerTypeCounts,
32
+ } from "./api-contracts/cosine-fp.js";
33
+
34
+ const MEMO_TTL_MS = 5000; // ≤5s per file state
35
+ const REPORT_FILENAME = "cosine-fp-report.json";
36
+
37
+ interface MemoEntry {
38
+ key: string;
39
+ at: number;
40
+ body: CosineFpReportV1;
41
+ }
42
+
43
+ let memo: MemoEntry | null = null;
44
+
45
+ /**
46
+ * Resolve the dir containing the cosine-fp-report.json aggregate. Two locations
47
+ * are probed in order:
48
+ *
49
+ * 1. The bundled asset `extensions/dashboard-server/assets/cosine-fp-report.json`.
50
+ * This is the PRODUCTION path: the `extensions/` source tree ships at the
51
+ * npm package root, so `/api/cosine-fp-report` serves the recommendation on
52
+ * an installed package (PREVENT-DIST-001). tsc does NOT copy `.json` assets
53
+ * into `dist/`, so the compiled module probes both the co-located
54
+ * `assets/` (source tree) and the repo-root `extensions/.../assets/`
55
+ * (reached from `dist/extensions/dashboard-server/`). Without the bundled
56
+ * asset the endpoint would always return `awaiting_data` on devices,
57
+ * because `scripts/` (the bench-run path) is NOT shipped.
58
+ *
59
+ * 2. The git-checkout bench-run dir (`scripts/cosine-fp/bench-run/`) for dev
60
+ * use, walked up to from this module — this is where `bench.mjs` writes the
61
+ * report.
62
+ *
63
+ * The test seam MEGACOMPACT_COSINE_FP_BENCH_RUN_DIR (read lazily so a test can
64
+ * set it post-import) overrides BOTH so route tests can point at a temp dir
65
+ * without touching the repo or the bundled asset.
66
+ */
67
+ export function benchRunDir(): string | null {
68
+ const testRunDir = process.env.MEGACOMPACT_COSINE_FP_BENCH_RUN_DIR;
69
+ if (testRunDir) return testRunDir;
70
+ const here = dirname(fileURLToPath(import.meta.url));
71
+ // 1. Bundled asset candidates — the PRODUCTION path (PREVENT-DIST-001). tsc
72
+ // does NOT copy `.json` assets into dist/, and the `extensions/` source
73
+ // tree ships at the package root next to `dist/`. Probe both relative to
74
+ // this module's location (mirrors server.ts clientDist candidates):
75
+ // here = extensions/dashboard-server (source tree, dev run)
76
+ // here = dist/extensions/dashboard-server (compiled, published)
77
+ const bundledCandidates = [
78
+ join(here, "assets"), // source tree / co-located
79
+ join(here, "..", "..", "..", "extensions", "dashboard-server", "assets"), // repo-root source from compiled dist/
80
+ ];
81
+ for (const bundled of bundledCandidates) {
82
+ try {
83
+ // guardrails-allow PREVENT-PI-004: local bundled report stat (loopback)
84
+ statSync(join(bundled, REPORT_FILENAME));
85
+ return bundled;
86
+ } catch {
87
+ /* try next candidate, then fall through to git-checkout walk */
88
+ }
89
+ }
90
+ // 2. Git-checkout bench-run dir — dev path only.
91
+ let dir = here;
92
+ const rel = join("scripts", "cosine-fp", "bench-run");
93
+ for (let i = 0; i < 8; i++) {
94
+ const candidate = join(dir, rel);
95
+ try {
96
+ // guardrails-allow PREVENT-PI-004: local bench-run dir stat (loopback)
97
+ statSync(candidate);
98
+ return candidate;
99
+ } catch {
100
+ /* keep walking */
101
+ }
102
+ const next = dirname(dir);
103
+ if (next === dir) break;
104
+ dir = next;
105
+ }
106
+ return null;
107
+ }
108
+
109
+ /** Read + minimally validate the aggregate JSON (PREVENT-001). Returns null on
110
+ * any parse/shape failure — the caller treats that as awaiting_data, never as
111
+ * a fabricated zero row. */
112
+ function readAggregate(runDir: string): Record<string, unknown> | null {
113
+ const p = join(runDir, "cosine-fp-report.json");
114
+ let raw: string;
115
+ try {
116
+ raw = readFileSync(p, "utf8"); // guardrails-allow PREVENT-PI-004: local bench-run report read (loopback)
117
+ } catch {
118
+ return null;
119
+ }
120
+ let parsed: unknown;
121
+ try {
122
+ parsed = JSON.parse(raw);
123
+ } catch {
124
+ return null;
125
+ }
126
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
127
+ return parsed as Record<string, unknown>;
128
+ }
129
+
130
+ function toNum(v: unknown): number | null {
131
+ return typeof v === "number" && Number.isFinite(v) ? v : null;
132
+ }
133
+
134
+ /** Project per-content-type count entries into the contract shape (PREVENT-011). */
135
+ function projectPerType(
136
+ raw: unknown,
137
+ ): Readonly<Record<string, CosineFpPerTypeCounts>> {
138
+ const out: Record<string, CosineFpPerTypeCounts> = {};
139
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return out;
140
+ for (const [key, v] of Object.entries(raw as Record<string, unknown>)) {
141
+ if (!v || typeof v !== "object" || Array.isArray(v)) continue;
142
+ const o = v as Record<string, unknown>;
143
+ out[key] = {
144
+ items: toNum(o.items) ?? 0,
145
+ dup: toNum(o.dup) ?? 0,
146
+ near: toNum(o.near) ?? 0,
147
+ clean: toNum(o.clean) ?? 0,
148
+ };
149
+ }
150
+ return out;
151
+ }
152
+
153
+ /** Project the raw aggregate into the full CosineFpReportV1 (reader-only). */
154
+ function buildBody(runDir: string, aggregate: Record<string, unknown>): CosineFpReportV1 {
155
+ let mtime: number | null = null;
156
+ try {
157
+ const s = statSync(join(runDir, "cosine-fp-report.json")); // guardrails-allow PREVENT-PI-004: local stat (loopback)
158
+ mtime = s.mtimeMs;
159
+ } catch {
160
+ mtime = null;
161
+ }
162
+ const benchStatus: CosineFpBenchStatus = aggregate.status === "ok" ? "ok" : "no_data";
163
+ const hasData = benchStatus === "ok";
164
+ const status = deriveVcStatus({ enabled: true, hasData, structuralOnly: false });
165
+
166
+ const grid = aggregate.grid;
167
+ const overrides = aggregate.overrides;
168
+ const corpus = aggregate.corpusSummary;
169
+ const rows = aggregate.rows;
170
+
171
+ return {
172
+ status,
173
+ benchStatus,
174
+ seed: toNum(aggregate.seed),
175
+ grid:
176
+ grid && typeof grid === "object"
177
+ ? {
178
+ lo: toNum((grid as Record<string, unknown>).lo) ?? 0,
179
+ hi: toNum((grid as Record<string, unknown>).hi) ?? 0,
180
+ step: toNum((grid as Record<string, unknown>).step) ?? 0,
181
+ points: toNum((grid as Record<string, unknown>).points) ?? 0,
182
+ }
183
+ : null,
184
+ corpusSummary:
185
+ corpus && typeof corpus === "object"
186
+ ? {
187
+ items: toNum((corpus as Record<string, unknown>).items) ?? 0,
188
+ pairs: toNum((corpus as Record<string, unknown>).pairs) ?? 0,
189
+ types: Array.isArray((corpus as Record<string, unknown>).types)
190
+ ? ((corpus as Record<string, unknown>).types as string[])
191
+ : [],
192
+ perType: projectPerType((corpus as Record<string, unknown>).perType),
193
+ digest:
194
+ typeof (corpus as Record<string, unknown>).digest === "string"
195
+ ? ((corpus as Record<string, unknown>).digest as string)
196
+ : null,
197
+ }
198
+ : null,
199
+ rows: Array.isArray(rows) ? (rows as CosineFpReportV1["rows"]) : null,
200
+ recommendedDefault: toNum(aggregate.recommendedDefault),
201
+ shippedDefault: toNum(aggregate.shippedDefault) ?? 0.85,
202
+ overrides:
203
+ overrides && typeof overrides === "object"
204
+ ? {
205
+ code: toNum((overrides as Record<string, unknown>).code),
206
+ prose: toNum((overrides as Record<string, unknown>).prose),
207
+ mixed: toNum((overrides as Record<string, unknown>).mixed),
208
+ }
209
+ : null,
210
+ fpBudget: toNum(aggregate.fpBudget),
211
+ digest: typeof aggregate.digest === "string" ? (aggregate.digest as string) : null,
212
+ reportFileMtime: mtime,
213
+ };
214
+ }
215
+
216
+ function awaitingDataBody(reason: "flag" | "missing"): CosineFpReportV1 {
217
+ const status = deriveVcStatus({
218
+ enabled: reason !== "flag",
219
+ hasData: false,
220
+ structuralOnly: false,
221
+ });
222
+ return {
223
+ status,
224
+ benchStatus: reason === "flag" ? "off" : "awaiting_data",
225
+ seed: null,
226
+ grid: null,
227
+ corpusSummary: null,
228
+ rows: null,
229
+ recommendedDefault: null,
230
+ shippedDefault: 0.85,
231
+ overrides: null,
232
+ fpBudget: null,
233
+ digest: null,
234
+ reportFileMtime: null,
235
+ };
236
+ }
237
+
238
+ export function handleCosineFpReport(
239
+ req: IncomingMessage,
240
+ res: ServerResponse,
241
+ _ctx: RouteContext,
242
+ ): boolean {
243
+ const url = req.url ?? "";
244
+ if (url !== "/api/cosine-fp-report") return false;
245
+ if (req.method !== "GET") {
246
+ sendJson(res, 405, { error: "method_not_allowed" });
247
+ return true;
248
+ }
249
+ if (!COSINE_FP_BENCH_ENABLED()) {
250
+ // Flag-off: 404 — byte-identical predecessor.
251
+ sendJson(res, 404, { error: "not_found" });
252
+ return true;
253
+ }
254
+
255
+ const runDir = benchRunDir();
256
+ if (runDir === null) {
257
+ sendJson(res, 200, awaitingDataBody("missing"));
258
+ return true;
259
+ }
260
+
261
+ // Memoized-facts pattern: serve a still-fresh entry keyed on {mtime,size}.
262
+ let size = 0;
263
+ let mtime = 0;
264
+ try {
265
+ const s = statSync(join(runDir, "cosine-fp-report.json")); // guardrails-allow PREVENT-PI-004: local stat (loopback)
266
+ size = s.size;
267
+ mtime = s.mtimeMs;
268
+ } catch {
269
+ // no report on disk yet
270
+ }
271
+ if (size === 0 && mtime === 0) {
272
+ sendJson(res, 200, awaitingDataBody("missing"));
273
+ return true;
274
+ }
275
+
276
+ const key = `${mtime}:${size}`;
277
+ const now = Date.now();
278
+ if (memo !== null && memo.key === key && now - memo.at <= MEMO_TTL_MS) {
279
+ sendJson(res, 200, memo.body);
280
+ return true;
281
+ }
282
+
283
+ const aggregate = readAggregate(runDir);
284
+ const body = aggregate === null ? awaitingDataBody("missing") : buildBody(runDir, aggregate);
285
+ memo = { key, at: now, body };
286
+ sendJson(res, 200, body);
287
+ return true;
288
+ }
@@ -331,4 +331,12 @@ export const EXCLUDED_SETTINGS: readonly string[] = [
331
331
  "MEGACOMPACT_RAPTOR_LEVEL_WEIGHTS",
332
332
  // Internal calibration, not user-facing.
333
333
  "MEGACOMPACT_FTS5_MAX_BM25",
334
+ // COS-FP-A: per-content-type L2 override landing slots. Null/unset by
335
+ // default and NOT wired into the live L2 decision this sprint — the
336
+ // top-level MEGACOMPACT_L2_THRESHOLD is the single runtime firing point. A
337
+ // default-OFF numeric slot is not yet an adjustable firing knob, so it is
338
+ // excluded (the report carries the recommendation; adoption is a separate
339
+ // gated sprint).
340
+ "MEGACOMPACT_L2_THRESHOLD_CODE",
341
+ "MEGACOMPACT_L2_THRESHOLD_PROSE",
334
342
  ];
@@ -312,5 +312,11 @@ export const VECTOR_CORTEX_SETTINGS: SettingGroup = {
312
312
  "ENC-2a exposes the runtime operator knob MEGACOMPACT_NATIVE_ORT_BUDGET_MIB (default 300 MiB, clamp 8192) as a dashboard Settings field. The knob gates budgetOk in the encoder runtime decision (decision.ts installBudgetMib + runtime-select.ts selectRuntimeBackend). Persisted to the per-repo .mega-compact.env via the setup-configure writer branch; the GET status route surfaces the persisted value AND the effective integer installBudgetMib() resolves to (so the dashboard shows the runtime's actual operand). OFF = no new GET fields, no writer branch, no new Settings rows (byte-identical ENC-1b predecessor).",
313
313
  true,
314
314
  ),
315
+ boolDirect(
316
+ "MEGACOMPACT_COSINE_FP_BENCH",
317
+ "COS-FP-A Synthetic FP Bench",
318
+ "Synthetic FP harness + L2 cosine threshold calibration (COS-FP-A): drives the deterministic synthetic-corpus grid sweep (scripts/cosine-fp/bench.mjs) that measures L2 cosine false-positive rates and emits a recommended default, and serves the reader-only GET /api/cosine-fp-report. Synthetic-corpus-only — never reads real session/ledger bytes. OFF = bench inert, endpoint 404s, no report write, L2_COSINE plain MEGACOMPACT_L2_THRESHOLD (byte-identical predecessor).",
319
+ true,
320
+ ),
315
321
  ],
316
322
  };
@@ -67,3 +67,4 @@ export {
67
67
  handleImproveCortexStatus,
68
68
  } from "./routes-cortex-improve.js";
69
69
  export { handleDedupTierAttribution } from "./routes-dedup-attribution.js";
70
+ export { handleCosineFpReport } from "./routes-cosine-fp.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.20.53",
3
+ "version": "0.20.55",
4
4
  "description": "Layered, local, vector-backed context compressor for pi — supersede/collapse/cluster compaction with deduped inline recall.",
5
5
  "type": "module",
6
6
  "license": "BSD-3-Clause",
@@ -28,6 +28,15 @@ function envNum(name: string, def: number): number {
28
28
  return Number.isFinite(n) ? n : def;
29
29
  }
30
30
 
31
+ /** COS-FP-A: numeric env var that is `null` when unset ("off") — a dormant
32
+ * override whose absence must mean "no behavior change". */
33
+ function envNumOrNull(name: string): number | null {
34
+ const v = process.env[name];
35
+ if (v === undefined || v === "off" || v === "") return null;
36
+ const n = Number(v);
37
+ return Number.isFinite(n) ? n : null;
38
+ }
39
+
31
40
  /** S42B: parse a comma-separated numeric env var (e.g. "1.0,0.9,0.8"). */
32
41
  function envNumArray(name: string, def: number[]): number[] {
33
42
  const v = process.env[name];
@@ -50,6 +59,12 @@ export interface DedupConfigShape {
50
59
  MINILM_EMBEDDER: boolean;
51
60
  // Thresholds.
52
61
  L2_COSINE: number; // semantic dedup firing point
62
+ /** COS-FP-A: optional per-content-type L2 override — null when unset (the
63
+ * top-level L2_COSINE remains the single runtime firing point; these are a
64
+ * default-OFF landing slot for the compliance report's recommendation, NOT
65
+ * wired into the live L2 decision this sprint). */
66
+ L2_COSINE_CODE: number | null;
67
+ L2_COSINE_PROSE: number | null;
53
68
  L1_JACCARD: number; // MinHash/LSH near-dup verification
54
69
  DEDUP_SIM: number; // legacy content-similarity fallback
55
70
  MMR_LAMBDA: number; // retrieval diversity
@@ -105,6 +120,8 @@ export function loadDedupConfig(): DedupConfigShape {
105
120
  MARK_ONLY_L2: envBool("MEGACOMPACT_MARK_ONLY_L2", false),
106
121
  MINILM_EMBEDDER: envBool("MEGACOMPACT_MINILM", false),
107
122
  L2_COSINE: envNum("MEGACOMPACT_L2_THRESHOLD", 0.85),
123
+ L2_COSINE_CODE: envNumOrNull("MEGACOMPACT_L2_THRESHOLD_CODE"),
124
+ L2_COSINE_PROSE: envNumOrNull("MEGACOMPACT_L2_THRESHOLD_PROSE"),
108
125
  L1_JACCARD: envNum("MEGACOMPACT_L1_JACCARD", 0.8),
109
126
  DEDUP_SIM: envNum("MEGACOMPACT_DEDUP_SIM", 0.9),
110
127
  MMR_LAMBDA: envNum("MEGACOMPACT_MMR_LAMBDA", 0.5),
@@ -0,0 +1,34 @@
1
+ /**
2
+ * config/vector-cortex-cosfp.ts — COS-FP-A synthetic FP harness sprint flag.
3
+ *
4
+ * Extracted from vector-cortex.ts so that barrel stays under the 300-line soft
5
+ * limit (soft-as-hard gate), exactly as vector-cortex-vc9a.ts and the other
6
+ * per-sprint flag siblings were. Gating the synthetic cosine false-positive
7
+ * harness: the corpus/bench scripts, the derived report, and the reader-only
8
+ * GET /api/cosine-fp-report endpoint. A pure-local, synthetic-corpus-only tool
9
+ * (EVAL-REDACT-002) — it never reads real session/ledger bytes.
10
+ *
11
+ * The split is mechanical: COSINE_FP_BENCH_ENABLED is byte-identical in name,
12
+ * semantics, and default to the sibling flag convention (positive, default ON),
13
+ * and vector-cortex.ts re-exports it so every existing
14
+ * `from "./config/vector-cortex.js"` import keeps resolving unchanged.
15
+ *
16
+ * Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
17
+ */
18
+
19
+ import { sprintFlag } from "./vector-cortex-flag.js";
20
+
21
+ /**
22
+ * COS-FP-A — synthetic FP harness + L2 cosine threshold calibration.
23
+ * Default ON. `MEGACOMPACT_COSINE_FP_BENCH=0` disables and is byte-identical to
24
+ * the predecessor: `scripts/cosine-fp/bench.mjs` is inert (gates report
25
+ * emission + the endpoint to 404/absent), no report is (re)written, and
26
+ * `L2_COSINE` continues to be plain `MEGACOMPACT_L2_THRESHOLD`. The two
27
+ * content-type override fields added to config/dedup.ts are null when unset and
28
+ * are NOT wired into the live L2 decision this sprint (a declared landing slot
29
+ * for the report's per-content-type recommendation). This flag MUST also be a
30
+ * dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS).
31
+ * The harness + endpoint perform only local filesystem reads (PREVENT-PI-004).
32
+ */
33
+ export const COSINE_FP_BENCH_ENABLED = (): boolean =>
34
+ sprintFlag("MEGACOMPACT_COSINE_FP_BENCH");
@@ -62,6 +62,8 @@ export { ENC_0D_ENABLED } from "./vector-cortex-enc0d.js";
62
62
  export { ENC_0E_ENABLED } from "./vector-cortex-enc0e.js";
63
63
  export { ENC_0F_ENABLED } from "./vector-cortex-enc0f.js";
64
64
  export { ENC_0G_ENABLED } from "./vector-cortex-enc0g.js";
65
+ // COS-FP-A synthetic FP harness flag extracted to vector-cortex-cosfp.ts.
66
+ export { COSINE_FP_BENCH_ENABLED } from "./vector-cortex-cosfp.js";
65
67
  export { ENC_1A_ENABLED } from "./vector-cortex-enc1a.js";
66
68
  export {
67
69
  ENC_1B_ENABLED,
package/src/config.ts CHANGED
@@ -202,6 +202,7 @@ export {
202
202
  ENC_2BUDGET_NATIVE_ORT_BUDGET_ENV,
203
203
  ENC_2BUDGET_MAX_MIB,
204
204
  ENC_2BUDGET_DEFAULT_MIB,
205
+ COSINE_FP_BENCH_ENABLED,
205
206
  BREAKER_WINDOW_MS,
206
207
  BREAKER_MIN_ATTEMPTS,
207
208
  BREAKER_PERF_FAILURES,
@@ -1,2 +0,0 @@
1
- import{j as e,r as h,u as m,x as I,y as k,w,l as D}from"./index-mZRDesSq.js";import{C as W,T as F}from"./TimeSavedCard-D_mBYAzE.js";import{b as u,c as v,d as _,e as O,f as x}from"./format-COjDIVGj.js";import{l as G}from"./pricing-DkYp8-vi.js";import{C as f,a as C,b as S,c as N}from"./card-DMzHffVs.js";import{B}from"./button-Cl_gFaIf.js";import{a as K}from"./client-extra-Dkkppa5C.js";function L(t){return t>=80?"ov-stat-value-green":t>=50?"ov-stat-value-yellow":"ov-stat-value-red"}function U(t){return t==null?"":t>=0?"ov-stat-value-green":"ov-stat-value-red"}function n({label:t,value:a,className:s,title:l}){return e.jsxs("div",{className:"flex items-baseline justify-between border-b border-border py-1 text-sm",children:[e.jsx("span",{className:"text-xs text-muted-foreground",title:l,children:t}),e.jsx("span",{className:`font-semibold${s?` ${s}`:""}`,children:a})]})}function V(t){return e.jsxs(f,{className:"electric-hover",children:[e.jsx(C,{children:e.jsx(S,{children:"Provider Prompt Cache"})}),e.jsxs(N,{children:[e.jsx(n,{label:"Cache Hit Rate",value:`${t.hitPct.toFixed(1)}%`,className:L(t.hitPct)}),e.jsx(n,{label:"Turns Recorded",value:String(t.turnCount)}),e.jsx(n,{label:"Cache Read Tokens",value:u(t.totalCacheRead)}),e.jsx(n,{label:"Cache Write Tokens",value:u(t.totalCacheWrite)}),e.jsx(n,{label:"Input Tokens",value:u(t.totalInput)}),e.jsx(n,{label:"$ Saved (reads)",value:v(t.cacheReadSaved)}),e.jsx(n,{label:"Write Investment",value:v(t.cacheWriteCost)}),e.jsx(n,{label:"Net Saved",value:v(t.netSaved),className:U(t.netSaved)}),e.jsx(n,{label:"Model",value:t.modelLabel??"—"}),e.jsx(n,{label:"Tracked Since",value:_(t.firstTurnAt)}),e.jsx(n,{label:"Last Updated",value:O(t.latestTurnAt)}),t.byModel.length>0&&e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"mt-2 text-xs font-semibold text-muted-foreground",children:"Per Model"}),t.byModel.map(a=>{const s=G(a.model),l=s!=null?a.totalCacheRead/1e6*s:null,r=s!=null?a.totalCacheWrite/1e6*s:null;return e.jsxs("div",{className:"mt-2",children:[e.jsx(n,{label:"Model",value:a.model}),e.jsx(n,{label:"Hit Rate",value:`${a.hitPct.toFixed(1)}%`,className:L(a.hitPct)}),e.jsx(n,{label:"Cache Read",value:u(a.totalCacheRead)}),e.jsx(n,{label:"Cache Write",value:u(a.totalCacheWrite)}),e.jsx(n,{label:"Samples",value:String(a.sampleCount)}),e.jsx(n,{label:"$ Saved (reads)",value:l!=null?v(l):"—"}),e.jsx(n,{label:"Write Investment",value:r!=null?v(r):"—"})]},a.model)})]})]})]})}const q=["ov-bar-green","ov-bar-blue","ov-bar-teal","ov-bar-yellow","ov-bar-gray"],z=["Permanent","Epoch","Topic","Thread","Volatile"],J={good:"ov-text-green",fair:"ov-text-yellow",degraded:"ov-text-orange",poor:"ov-text-red"};function Q({data:t}){const a=Math.max(...t.buckets.map(l=>l.count),1),s=J[t.health.label]??"ov-text-gray";return e.jsxs("div",{className:"ov-card",children:[e.jsx("h3",{className:"ov-card-title",children:"Cache Stripe Distribution"}),e.jsxs("div",{className:"ov-stripe-health-summary",children:[e.jsx("span",{className:`ov-stripe-health-badge ${s}`,children:t.health.label}),e.jsx("span",{className:"ov-stripe-health-score",children:x(t.health.score)}),e.jsxs("span",{className:"ov-stripe-churn",children:["Churn: ",x(t.health.churnRate)]}),e.jsxs("span",{className:"ov-stripe-total",children:[t.totalChunks," total chunks"]})]}),e.jsx("div",{className:"ov-stripe-bars",children:t.buckets.map(l=>{const r=a>0?Math.max(0,Math.min(100,l.count/a*100)):0,i=q[l.stripe]??"ov-bar-gray",o=l.label||(z[l.stripe]??`Stripe ${l.stripe}`);return e.jsxs("div",{className:"ov-stripe-row",children:[e.jsx("span",{className:"ov-stripe-label",title:o,children:o}),e.jsx("div",{className:"ov-bar-track",children:e.jsx("div",{className:`ov-bar-fill ${i}`,style:{width:`${r}%`}})}),e.jsx("span",{className:"ov-stripe-count",children:l.count}),e.jsx("span",{className:"ov-stripe-stability",title:`min ${x(l.minStability)} / max ${x(l.maxStability)}`,children:x(l.avgStability)})]},l.stripe)})}),e.jsxs("div",{className:"ov-stripe-footer",children:["Epoch: ",t.epochId??"—","· Dominant tier: ",x(t.health.dominantTier)]})]})}function g(t){return`${t.toFixed(1)}%`}function X(t){return t>=80?"ov-bar-green":t>=60?"ov-bar-teal":t>=40?"ov-bar-yellow":t>=20?"ov-bar-orange":"ov-bar-red"}const Y=({perf:t})=>{const{cache_hit_pct:a}=t,s=a.samples&&a.samples.length>0?a.samples:[];return e.jsxs(f,{className:"electric-hover",children:[e.jsx(C,{children:e.jsx(S,{children:"Cache Hit-Rate Trend"})}),e.jsxs(N,{children:[e.jsxs("div",{className:"mb-3 flex gap-4 text-sm text-muted-foreground",children:[e.jsxs("div",{children:["Avg: ",e.jsx("strong",{className:"text-foreground",children:g(a.avg)})]}),e.jsxs("div",{children:["Latest: ",e.jsx("strong",{className:"text-foreground",children:g(a.latest)})]}),e.jsxs("div",{children:["Samples: ",e.jsx("strong",{className:"text-foreground",children:a.n})]})]}),s.length>0?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"ov-sparkline",role:"img","aria-label":`Cache hit rate trend: ${a.n} samples, average ${g(a.avg)}, latest ${g(a.latest)}`,children:s.map((l,r)=>{const i=Math.max(2,Math.round(l.pct*.6)),o=X(l.pct),c=r===s.length-1;return e.jsx("div",{className:`ov-sparkline-bar ${o}${c?" ov-bar-latest":""}`,style:{height:`${i}px`},title:`${g(l.pct)} at ${new Date(l.ts).toLocaleTimeString()}`},r)})}),e.jsxs("div",{className:"mt-1 text-xs text-muted-foreground",children:[s.length," sample",s.length!==1?"s":""," over"," ",s.length>1?`${Math.round((s[s.length-1].ts-s[0].ts)/1e3)}s`:"latest turn"]})]}):e.jsx("div",{className:"text-sm text-muted-foreground",children:"No cache-hit samples yet"})]})]})};function T(t){return`${(t*100).toFixed(1)}%`}function Z(t){const a=t*100;return a>=80?"ov-bar-green":a>=60?"ov-bar-teal":a>=40?"ov-bar-yellow":a>=20?"ov-bar-orange":"ov-bar-red"}const A={improving:"Improving",stable:"Stable",degrading:"Degrading"},ee=({data:t})=>{const{turns:a,avgRatio:s,trend:l}=t;return e.jsxs(f,{className:"electric-hover",children:[e.jsx(C,{children:e.jsx(S,{children:"Per-Turn Stable Prefix"})}),e.jsxs(N,{children:[e.jsxs("div",{className:"mb-3 flex gap-4 text-sm text-muted-foreground",children:[e.jsxs("div",{children:["Avg:"," ",e.jsx("strong",{className:"text-foreground",children:T(s)})]}),e.jsxs("div",{children:["Trend:"," ",e.jsx("strong",{className:"text-foreground",children:A[l]})]}),e.jsxs("div",{children:["Turns: ",e.jsx("strong",{className:"text-foreground",children:a.length})]})]}),a.length>0?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"ov-sparkline",role:"img","aria-label":`Per-turn stable-prefix ratio trend: ${a.length} turns, average ${T(s)}, trend ${A[l]}`,children:a.map((r,i)=>{const o=Math.max(2,Math.round(r.ratio*60)),c=Z(r.ratio),p=i===a.length-1;return e.jsx("div",{className:`ov-sparkline-bar ${c}${p?" ov-bar-latest":""}`,style:{height:`${o}px`},title:`Turn ${r.turnIndex}: ${T(r.ratio)} (${r.stablePrefix}/${r.totalMessages} msg) @ ${new Date(r.timestamp).toLocaleTimeString()}`},i)})}),e.jsx("div",{className:"mt-1 text-xs text-muted-foreground",children:"Shared prompt-cache prefix ratio per turn — higher is better."})]}):e.jsx("div",{className:"text-sm text-muted-foreground",children:"No stable-prefix samples yet"})]})]})};function oe(){const[t,a]=h.useState(!1),{data:s,loading:l,error:r}=m(h.useCallback(()=>I(),[]),{pollInterval:5e3}),{data:i,loading:o,error:c}=m(h.useCallback(()=>k(),[]),{pollInterval:15e3}),{data:p,loading:E,error:y}=m(h.useCallback(()=>w({minutes:30}),[]),{pollInterval:15e3}),{data:P,loading:M}=m(h.useCallback(()=>K(50),[]),{pollInterval:15e3}),{data:d,loading:H,error:$}=m(h.useCallback(()=>D(),[]),{pollInterval:5e3});return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsxs("div",{children:[e.jsx(B,{type:"button",variant:"outline",onClick:()=>a(!t),children:t?"Hide Guidance":"Show Cache-Friendly Prompt Ordering Guidance"}),t&&e.jsxs(f,{className:"mt-3",children:[e.jsx(C,{children:e.jsx(S,{children:"Cache-Friendly Prompt Ordering"})}),e.jsxs(N,{children:[e.jsxs("ul",{className:"list-disc space-y-1 pl-5",children:[e.jsx("li",{children:"Keep system prompts and stable context at the top of your conversation — the provider caches the leading prefix."}),e.jsxs("li",{children:["Tool results and volatile content are automatically moved to the tail by message separation (",e.jsx("code",{children:"MEGACOMPACT_MESSAGE_SEPARATION"}),") so they don't invalidate the cache prefix."]}),e.jsxs("li",{children:["Cache striping (",e.jsx("code",{children:"MEGACOMPACT_CACHE_STRIPING"}),") further orders stable context by a stability score so the most durable chunks lead."]}),e.jsx("li",{children:"Avoid inserting new instructions mid-conversation — prepend them instead."})]}),d&&e.jsxs("div",{className:"mt-2 text-sm",children:[e.jsx("strong",{children:"Status:"})," ",d.config.auto?"Auto-compaction Enabled":"Auto-compaction Disabled"," |"," ","fast-gate ",d.config.fastGatePct,"% / threshold"," ",d.config.thresholdTokens.toLocaleString()," tokens"]})]})]})]}),e.jsx("h2",{className:"font-heading text-lg font-semibold",children:"Provider Prompt Cache"}),l?e.jsx("p",{className:"tab-stub",children:"Loading..."}):r?e.jsxs("p",{className:"tab-stub",children:["Provider cache unavailable: ",r.message]}):s?e.jsx(V,{hitPct:s.cache.avgHitPct,turnCount:s.cache.turnCount,totalCacheRead:s.cache.totalCacheRead,totalCacheWrite:s.cache.totalCacheWrite,totalInput:s.cache.totalInput,cacheReadSaved:s.savings?.cacheReadSaved??null,cacheWriteCost:s.savings?.cacheWriteCost??null,netSaved:s.savings?.netSaved??null,modelLabel:s.savings?.model??null,firstTurnAt:s.cache.firstTurnAt,latestTurnAt:s.cache.latestTurnAt,byModel:s.cache.byModel??[]}):null,e.jsx("h2",{className:"font-heading text-lg font-semibold",children:"Cache Stripe Distribution"}),o?e.jsx("p",{className:"tab-stub",children:"Loading..."}):c?e.jsxs("p",{className:"tab-stub",children:["Cache stripe data unavailable: ",c.message]}):i?e.jsx(Q,{data:i}):null,e.jsx("h2",{className:"font-heading text-lg font-semibold",children:"Cache Hit-Rate Trend"}),E?e.jsx("p",{className:"tab-stub",children:"Loading..."}):y?e.jsxs("p",{className:"tab-stub",children:["Hit-rate trend unavailable: ",y.message]}):p?e.jsx(Y,{perf:p}):null,P&&!M?e.jsxs("div",{children:[e.jsx("h2",{className:"font-heading text-lg font-semibold",children:"Per-Turn Stable Prefix"}),e.jsx(ee,{data:P})]}):null,e.jsx("h2",{className:"font-heading text-lg font-semibold",children:"Mega-Compact Dedup Cache"}),H?e.jsx("p",{className:"tab-stub",children:"Loading..."}):$?e.jsxs("p",{className:"tab-stub",children:["Snapshot unavailable: ",$.message]}):d&&(()=>{const{cacheHits:b,compacts:R,timeSaved:j}=d;return e.jsxs("div",{className:"grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3",children:[e.jsx(W,{cacheHitsSession:b.session,cacheHitsTotal:b.total,tokensSavedSession:b.sessionTokensSaved,tokensSavedTotal:b.totalTokensSaved,compactionsSession:R.session,compactionsTotal:R.total}),e.jsx(F,{compactSessionSec:j.compact.sessionSec,compactTotalSec:j.compact.totalSec,cacheHitSessionSec:j.cacheHit.sessionSec,cacheHitTotalSec:j.cacheHit.totalSec})]})})()]})}export{oe as default};
2
- //# sourceMappingURL=CacheTab-BS_G9_YV.js.map