pi-mega-compact 0.20.55 → 0.20.57

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 (83) hide show
  1. package/dist/config/repo-corpus.js +20 -0
  2. package/dist/config/vector-cortex-cosfp-real.js +34 -0
  3. package/dist/config/vector-cortex.js +5 -0
  4. package/dist/config.js +1 -1
  5. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry-ext.js +7 -0
  6. package/dist/extensions/dashboard-server/api-contracts/repo-corpus.js +10 -0
  7. package/dist/extensions/dashboard-server/route-dispatch.js +3 -1
  8. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +2 -0
  9. package/dist/extensions/dashboard-server/routes-repo-corpus.js +258 -0
  10. package/dist/extensions/dashboard-server/routes.js +1 -0
  11. package/dist/src/config/repo-corpus.js +20 -0
  12. package/dist/src/config/vector-cortex-cosfp-real.js +34 -0
  13. package/dist/src/config/vector-cortex.js +5 -0
  14. package/dist/src/config.js +1 -1
  15. package/extensions/dashboard-client/dist/assets/{AreaChart-DlndN0p2.js → AreaChart-CtJ6dSMT.js} +2 -2
  16. package/extensions/dashboard-client/dist/assets/{AreaChart-DlndN0p2.js.map → AreaChart-CtJ6dSMT.js.map} +1 -1
  17. package/extensions/dashboard-client/dist/assets/{BarChart-DH6rZ60s.js → BarChart-DJ3j3xDz.js} +2 -2
  18. package/extensions/dashboard-client/dist/assets/{BarChart-DH6rZ60s.js.map → BarChart-DJ3j3xDz.js.map} +1 -1
  19. package/extensions/dashboard-client/dist/assets/{CacheTab-Qj_NYKw3.js → CacheTab-CXU1_9lR.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{CacheTab-Qj_NYKw3.js.map → CacheTab-CXU1_9lR.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/{EventsTab-3QlIPMFj.js → EventsTab-DF-Q3o7x.js} +2 -2
  22. package/extensions/dashboard-client/dist/assets/{EventsTab-3QlIPMFj.js.map → EventsTab-DF-Q3o7x.js.map} +1 -1
  23. package/extensions/dashboard-client/dist/assets/{HealthTab-DTwitpIb.js → HealthTab-Dr1HGOkl.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{HealthTab-DTwitpIb.js.map → HealthTab-Dr1HGOkl.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DkVlBnp7.js → MaintenanceTab-zKhR18_s.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DkVlBnp7.js.map → MaintenanceTab-zKhR18_s.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-B-pHH85r.js → MemoryMapTab-DekIzfn2.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-B-pHH85r.js.map → MemoryMapTab-DekIzfn2.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{MetricsTab-C8VpwhJN.js → MetricsTab-CEKajY38.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{MetricsTab-C8VpwhJN.js.map → MetricsTab-CEKajY38.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{OverviewTab-C_JZlM_Q.js → OverviewTab-24sG8xiE.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{OverviewTab-C_JZlM_Q.js.map → OverviewTab-24sG8xiE.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/{ReposTab-rRMlub-E.js → ReposTab-70gkV8l9.js} +2 -2
  34. package/extensions/dashboard-client/dist/assets/{ReposTab-rRMlub-E.js.map → ReposTab-70gkV8l9.js.map} +1 -1
  35. package/extensions/dashboard-client/dist/assets/{SessionsTab-CL6S094G.js → SessionsTab-B3quvYPZ.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{SessionsTab-CL6S094G.js.map → SessionsTab-B3quvYPZ.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/SetupTab-tbPrrfYx.js +3 -0
  38. package/extensions/dashboard-client/dist/assets/SetupTab-tbPrrfYx.js.map +1 -0
  39. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B-8NCvzd.js → TimeSavedCard-B6mdtCWE.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B-8NCvzd.js.map → TimeSavedCard-B6mdtCWE.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{TurnsTab-C2MVgG4N.js → TurnsTab-CmhRJJqr.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{TurnsTab-C2MVgG4N.js.map → TurnsTab-CmhRJJqr.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-CvlS_fvh.js → VcStatusBadge-SDThp0R0.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-CvlS_fvh.js.map → VcStatusBadge-SDThp0R0.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BXCAzwQm.js → VectorCortexTab-DDjCkTjk.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BXCAzwQm.js.map → VectorCortexTab-DDjCkTjk.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{WikiTab-DUivYmJ5.js → WikiTab-Cki48_8N.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{WikiTab-DUivYmJ5.js.map → WikiTab-Cki48_8N.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/{button-dTpuinxr.js → button-CojFYd7q.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{button-dTpuinxr.js.map → button-CojFYd7q.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/{card-DNwZ7dSg.js → card-Cxeeje9o.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{card-DNwZ7dSg.js.map → card-Cxeeje9o.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{client-extra-CQuCpjyO.js → client-extra-DLxyGxW8.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{client-extra-CQuCpjyO.js.map → client-extra-DLxyGxW8.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-D1HV4d79.js → generateCategoricalChart-DRlqBks2.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-D1HV4d79.js.map → generateCategoricalChart-DRlqBks2.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/{index-Bi5YNsLx.js → index-CkbdlPBm.js} +14 -14
  58. package/extensions/dashboard-client/dist/assets/index-CkbdlPBm.js.map +1 -0
  59. package/extensions/dashboard-client/dist/assets/{switch-DiKc2q4n.js → switch-Cx_nl-Ba.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{switch-DiKc2q4n.js.map → switch-Cx_nl-Ba.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{toggle-BBYD10JO.js → toggle-CfIFzp6P.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{toggle-BBYD10JO.js.map → toggle-CfIFzp6P.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{useSSE-CWUSDhIb.js → useSSE-B9_N7ccG.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{useSSE-CWUSDhIb.js.map → useSSE-B9_N7ccG.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/index.html +1 -1
  66. package/extensions/dashboard-client/src/tabs/SetupTab/CortexSetup.tsx +2 -0
  67. package/extensions/dashboard-client/src/tabs/SetupTab/VectorCortexRepoCorpusCard.tsx +126 -0
  68. package/extensions/dashboard-client/src/types/repo-corpus.ts +25 -0
  69. package/extensions/dashboard-server/api-contracts/endpoints/registry-ext.ts +11 -0
  70. package/extensions/dashboard-server/api-contracts/index.ts +12 -0
  71. package/extensions/dashboard-server/api-contracts/repo-corpus.ts +76 -0
  72. package/extensions/dashboard-server/route-dispatch.ts +2 -0
  73. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +12 -0
  74. package/extensions/dashboard-server/routes-repo-corpus.ts +286 -0
  75. package/extensions/dashboard-server/routes.ts +1 -0
  76. package/package.json +1 -1
  77. package/src/config/repo-corpus.ts +23 -0
  78. package/src/config/vector-cortex-cosfp-real.ts +37 -0
  79. package/src/config/vector-cortex.ts +5 -0
  80. package/src/config.ts +2 -0
  81. package/extensions/dashboard-client/dist/assets/SetupTab-C6b8YuBK.js +0 -3
  82. package/extensions/dashboard-client/dist/assets/SetupTab-C6b8YuBK.js.map +0 -1
  83. package/extensions/dashboard-client/dist/assets/index-Bi5YNsLx.js.map +0 -1
@@ -357,3 +357,15 @@ export type {
357
357
  CosineFpOverrides,
358
358
  CosineFpReportV1,
359
359
  } from "./cosine-fp.js";
360
+
361
+ // REPO-A — cross-repo corpus status (reader-only)
362
+ export type {
363
+ RepoCorpusStatus,
364
+ RepoCorpusRepoV1,
365
+ RepoCorpusOverlapV1,
366
+ RepoCorpusManifestV1,
367
+ RepoCorpusConsentStateV1,
368
+ RepoCorpusConsentRowV1,
369
+ RepoCorpusPerRepoStatusV1,
370
+ RepoCorpusStatusV1,
371
+ } from "./repo-corpus.js";
@@ -0,0 +1,76 @@
1
+ /**
2
+ * api-contracts/repo-corpus.ts — REPO-A cross-repo corpus API contract.
3
+ *
4
+ * Reader-only shapes for GET /api/repo-corpus. The corpus manifest is written by
5
+ * the CLI corpus-builder (scripts/repo-corpus/build.mjs) as a PSEUDONYMOUS
6
+ * artifact — repo IDs/counts/digests/cross-repo-overlap descriptors only, never
7
+ * payload content (EVAL-REDACT-002). The canonicalRemote→pseudonym mapping is a
8
+ * pure in-memory function and never leaves the builder. No `any`.
9
+ */
10
+
11
+ /** Derived endpoint status (from vc-status.ts). */
12
+ export type RepoCorpusStatus =
13
+ | "live"
14
+ | "awaiting_data"
15
+ | "deferred"
16
+ | "structural"
17
+ | "off";
18
+
19
+ /** One pseudonymous repo in the corpus manifest. */
20
+ export interface RepoCorpusRepoV1 {
21
+ readonly repoPseudonym: string;
22
+ readonly sessions: number;
23
+ readonly sessionIds: readonly string[];
24
+ readonly events: number;
25
+ /** SHA-256 over the repo's session-id slice + count (content-free). */
26
+ readonly digest: string;
27
+ }
28
+
29
+ /** A recorded cross-repo session overlap descriptor (IDs only, never text). */
30
+ export interface RepoCorpusOverlapV1 {
31
+ readonly repoA: string;
32
+ readonly repoB: string;
33
+ readonly sharedSessions: number;
34
+ readonly sharedIds: readonly string[];
35
+ }
36
+
37
+ /** The corpus manifest the builder emits (pseudonymous). */
38
+ export interface RepoCorpusManifestV1 {
39
+ readonly schema: "repo-corpus-manifest-v1";
40
+ readonly ownerPseudonym: string;
41
+ readonly datasetVersion: string;
42
+ readonly effectiveSeq: number;
43
+ readonly totalEvents: number;
44
+ readonly repos: readonly RepoCorpusRepoV1[];
45
+ readonly overlaps: readonly RepoCorpusOverlapV1[];
46
+ }
47
+
48
+ /** Per-repo consent state projection used by the reader route (pseudonymous). */
49
+ export interface RepoCorpusConsentStateV1 {
50
+ readonly schema: "repo-corpus-consent-state-v1";
51
+ readonly perRepo: readonly RepoCorpusConsentRowV1[];
52
+ }
53
+
54
+ /** One repo's consent row (never payload content). */
55
+ export interface RepoCorpusConsentRowV1 {
56
+ readonly repoPseudonym: string;
57
+ readonly consentedCrossRepo: boolean;
58
+ readonly revokedAt?: string;
59
+ }
60
+
61
+ /** Per-repo status row in RepoCorpusStatusV1. */
62
+ export interface RepoCorpusPerRepoStatusV1 {
63
+ readonly repoPseudonym: string;
64
+ readonly sessions: number;
65
+ readonly consentedCrossRepo: boolean;
66
+ readonly revokedAt?: string;
67
+ }
68
+
69
+ /** Response body for GET /api/repo-corpus. */
70
+ export interface RepoCorpusStatusV1 {
71
+ readonly schema: "repo-corpus-status-v1";
72
+ readonly corpus: RepoCorpusManifestV1 | null;
73
+ readonly perRepo: readonly RepoCorpusPerRepoStatusV1[];
74
+ readonly totalEvents: number;
75
+ readonly status: RepoCorpusStatus;
76
+ }
@@ -64,6 +64,7 @@ import {
64
64
  handleImproveCortexStatus,
65
65
  handleDedupTierAttribution,
66
66
  handleCosineFpReport,
67
+ handleRepoCorpus,
67
68
  } from "./routes.js";
68
69
  // VC6C repair lives in its own module (routes-vector-cortex-repair.ts) so the
69
70
  // heal route file stays well under the 400-line extension soft limit.
@@ -162,5 +163,6 @@ export function dispatchRoutes(
162
163
  if (handleImproveCortexStatus(req, res, ctx)) return true;
163
164
  if (handleDedupTierAttribution(req, res, ctx)) return true;
164
165
  if (handleCosineFpReport(req, res, ctx)) return true;
166
+ if (handleRepoCorpus(req, res, ctx)) return true;
165
167
  return false;
166
168
  }
@@ -318,5 +318,17 @@ export const VECTOR_CORTEX_SETTINGS: SettingGroup = {
318
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
319
  true,
320
320
  ),
321
+ boolDirect(
322
+ "MEGACOMPACT_COSINE_FP_REAL",
323
+ "COS-FP-R Real-Corpus Validation",
324
+ "Real-corpus L2 cosine FP validation (COS-FP-R): runs the real-corpus grid sweep (scripts/cosine-fp/real-bench.mjs) against donated, consent-approved sessions only (SECURITY_PRIVACY Lifecycle/Consent), scoring FP/FN with Wilson intervals + session-grouped bootstrap, and appends CI-backed per-content-type recommendations to the cosine-threshold report. Runs only when a valid consented corpus exists; absent corpus = no_corpus (normative pre-donation state, nothing written). OFF = script inert, nothing executes, no writes (byte-identical predecessor).",
325
+ true,
326
+ ),
327
+ boolDirect(
328
+ "MEGACOMPACT_REPO_CORPUS",
329
+ "Cross-Repo Corpus (REPO-A)",
330
+ "Cross-repo corpus preparation (REPO-A): the governed, consent-gated reader-only path that builds a pseudonymous corpus manifest from per-repo events.log slices and serves GET /api/repo-corpus answering which pseudonymous repos/sessions are in the corpus, what cross-repo overlap exists, and whether consent is active for each — counts + IDs + status only, never payload content. Consent is append-only (consent.mjs, CLI/ops-only); revocation freezes the builder + route. OFF = builder refuses to run, reader route 404s, single-repo recall untouched (byte-identical predecessor).",
331
+ true,
332
+ ),
321
333
  ],
322
334
  };
@@ -0,0 +1,286 @@
1
+ /**
2
+ * dashboard-server/routes-repo-corpus.ts — REPO-A cross-repo corpus status route.
3
+ *
4
+ * GET /api/repo-corpus — reader-only aggregate answering "which pseudonymous
5
+ * repos/sessions are in the consented corpus, what cross-repo overlap exists,
6
+ * and is consent active for each". Reads the corpus manifest + consent-state the
7
+ * CLI corpus-builder (scripts/repo-corpus/build.mjs) produced into the resolved
8
+ * corpus dir, memoized by {mtime,size} key with a 5s TTL (mirrors
9
+ * routes-cosine-fp.ts). Serves counts + IDs + status only — never payload
10
+ * content (EVAL-REDACT-002).
11
+ *
12
+ * IMPORTANT (distribution): this route does NOT import the builder scripts
13
+ * (scripts/ is excluded from package.json `files`). It reads the manifest files
14
+ * the builder wrote; the corpus dir is resolved from MEGACOMPACT_REPO_CORPUS_DIR
15
+ * (operator/test seam) or a bundled asset dir. The contract types ship in
16
+ * api-contracts/repo-corpus.ts.
17
+ *
18
+ * Flag-off (MEGACOMPACT_REPO_CORPUS=0) returns 404 — byte-identical
19
+ * predecessor. Absent corpus → awaiting_data (never a fabricated zero row).
20
+ *
21
+ * Guardrails: PREVENT-PI-004 (local filesystem read only), PREVENT-001
22
+ * (JSON.parse guarded), PREVENT-011 (no `any`).
23
+ */
24
+
25
+ import type { IncomingMessage, ServerResponse } from "node:http";
26
+ import { readFileSync, statSync } from "node:fs";
27
+ import { join, dirname } from "node:path";
28
+ import { fileURLToPath } from "node:url";
29
+ import type { RouteContext } from "./routes-core.js";
30
+ import { sendJson } from "./routes-vector-cortex-shared.js";
31
+ import { REPO_CORPUS_ENABLED } from "../../src/config.js";
32
+ import { deriveVcStatus } from "./vc-status.js";
33
+ import type {
34
+ RepoCorpusManifestV1,
35
+ RepoCorpusStatusV1,
36
+ RepoCorpusConsentStateV1,
37
+ RepoCorpusPerRepoStatusV1,
38
+ } from "./api-contracts/repo-corpus.js";
39
+
40
+ const MEMO_TTL_MS = 5000; // ≤5s per file state
41
+ const MANIFEST_FILENAME = "manifest.json";
42
+ const CONSENT_STATE_FILENAME = "consent-state.json";
43
+
44
+ interface MemoEntry {
45
+ key: string;
46
+ at: number;
47
+ body: RepoCorpusStatusV1;
48
+ }
49
+
50
+ let memo: MemoEntry | null = null;
51
+
52
+ /**
53
+ * Resolve the directory containing the corpus manifest + consent-state.
54
+ * 1. MEGACOMPACT_REPO_CORPUS_DIR (operator/test seam) — the canonical runtime
55
+ * location where an operator ran the builder (or a test points a temp dir).
56
+ * 2. Bundled asset extension/dashboard-server/assets/ — the shipped location,
57
+ * so a pre-built corpus can be shipped in the tarball (PREVENT-DIST-001);
58
+ * tsc does not copy .json into dist, so probe co-located + repo-root.
59
+ * 3. Git-checkout well-known dir for dev use (scripts/repo-corpus/corpus-run/).
60
+ * Returns null when unresolvable — caller serves awaiting_data.
61
+ */
62
+ export function repoCorpusDir(): string | null {
63
+ const override = process.env.MEGACOMPACT_REPO_CORPUS_DIR;
64
+ if (override) return override;
65
+ const here = dirname(fileURLToPath(import.meta.url));
66
+ const bundledCandidates = [
67
+ join(here, "assets"),
68
+ join(here, "..", "..", "..", "extensions", "dashboard-server", "assets"),
69
+ ];
70
+ for (const bundled of bundledCandidates) {
71
+ try {
72
+ // guardrails-allow PREVENT-PI-004: local bundled manifest stat (loopback)
73
+ statSync(join(bundled, MANIFEST_FILENAME));
74
+ return bundled;
75
+ } catch {
76
+ /* try next candidate, then fall through to git-checkout walk */
77
+ }
78
+ }
79
+ // Git-checkout corpus dir — dev path only.
80
+ let dir = here;
81
+ const rel = join("scripts", "repo-corpus", "corpus-run");
82
+ for (let i = 0; i < 8; i++) {
83
+ const candidate = join(dir, rel);
84
+ try {
85
+ // guardrails-allow PREVENT-PI-004: local corpus-run dir stat (loopback)
86
+ statSync(candidate);
87
+ return candidate;
88
+ } catch {
89
+ /* keep walking */
90
+ }
91
+ const next = dirname(dir);
92
+ if (next === dir) break;
93
+ dir = next;
94
+ }
95
+ return null;
96
+ }
97
+
98
+ /** Guarded JSON file read → parsed value or null (PREVENT-001, PREVENT-011). */
99
+ function readJsonFile(p: string): unknown {
100
+ let raw: string;
101
+ try {
102
+ raw = readFileSync(p, "utf8"); // guardrails-allow PREVENT-PI-004: local corpus file read (loopback)
103
+ } catch {
104
+ return null;
105
+ }
106
+ let parsed: unknown;
107
+ try {
108
+ parsed = JSON.parse(raw);
109
+ } catch {
110
+ return null;
111
+ }
112
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
113
+ return parsed;
114
+ }
115
+
116
+ function isRecord(v: unknown): v is Record<string, unknown> {
117
+ return !!v && typeof v === "object" && !Array.isArray(v);
118
+ }
119
+
120
+ /** Project a raw consent-state file (structurally guarded, PREVENT-011). */
121
+ function readConsentState(corpusDir: string): RepoCorpusConsentStateV1 | null {
122
+ const raw = readJsonFile(join(corpusDir, CONSENT_STATE_FILENAME));
123
+ if (!isRecord(raw)) return null;
124
+ const perRepo = Array.isArray(raw.perRepo) ? raw.perRepo : [];
125
+ const rows = perRepo
126
+ .filter(isRecord)
127
+ .map((r) => ({
128
+ repoPseudonym:
129
+ typeof r.repoPseudonym === "string" ? r.repoPseudonym : "",
130
+ consentedCrossRepo: r.consentedCrossRepo === true || r.consentedCrossRepo === "true",
131
+ revokedAt: typeof r.revokedAt === "string" ? r.revokedAt : undefined,
132
+ }))
133
+ .filter((r) => r.repoPseudonym.length > 0);
134
+ return { schema: "repo-corpus-consent-state-v1", perRepo: rows };
135
+ }
136
+
137
+ /** Project a raw manifest file (structurally guarded, PREVENT-011). */
138
+ function readManifest(corpusDir: string): RepoCorpusManifestV1 | null {
139
+ const raw = readJsonFile(join(corpusDir, MANIFEST_FILENAME));
140
+ if (!isRecord(raw)) return null;
141
+ if (raw.schema !== "repo-corpus-manifest-v1") return null;
142
+ const repos = Array.isArray(raw.repos)
143
+ ? raw.repos
144
+ .filter(isRecord)
145
+ .map((r) => ({
146
+ repoPseudonym:
147
+ typeof r.repoPseudonym === "string" ? r.repoPseudonym : "",
148
+ sessions: typeof r.sessions === "number" ? r.sessions : 0,
149
+ sessionIds: Array.isArray(r.sessionIds)
150
+ ? r.sessionIds.filter((x): x is string => typeof x === "string")
151
+ : [],
152
+ events: typeof r.events === "number" ? r.events : 0,
153
+ digest: typeof r.digest === "string" ? r.digest : "",
154
+ }))
155
+ .filter((r) => r.repoPseudonym.length > 0)
156
+ : [];
157
+ const overlaps = Array.isArray(raw.overlaps)
158
+ ? raw.overlaps
159
+ .filter(isRecord)
160
+ .map((o) => ({
161
+ repoA: typeof o.repoA === "string" ? o.repoA : "",
162
+ repoB: typeof o.repoB === "string" ? o.repoB : "",
163
+ sharedSessions: typeof o.sharedSessions === "number" ? o.sharedSessions : 0,
164
+ sharedIds: Array.isArray(o.sharedIds)
165
+ ? o.sharedIds.filter((x): x is string => typeof x === "string")
166
+ : [],
167
+ }))
168
+ .filter((o) => o.repoA.length > 0 && o.repoB.length > 0)
169
+ : [];
170
+ return {
171
+ schema: "repo-corpus-manifest-v1",
172
+ ownerPseudonym:
173
+ typeof raw.ownerPseudonym === "string" ? raw.ownerPseudonym : "",
174
+ datasetVersion:
175
+ typeof raw.datasetVersion === "string" ? raw.datasetVersion : "",
176
+ effectiveSeq: typeof raw.effectiveSeq === "number" ? raw.effectiveSeq : 0,
177
+ totalEvents: typeof raw.totalEvents === "number" ? raw.totalEvents : 0,
178
+ repos,
179
+ overlaps,
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Build the perRepo status rows. A repo whose consent cannot be confirmed
185
+ * (missing from consent-state, or missing stateDir at the builder) degrades to
186
+ * consentedCrossRepo:false — never a crash, never a silent inclusion.
187
+ */
188
+ function buildPerRepo(
189
+ manifest: RepoCorpusManifestV1,
190
+ consent: RepoCorpusConsentStateV1 | null,
191
+ ): RepoCorpusPerRepoStatusV1[] {
192
+ const consentByRepo = new Map<string, RepoCorpusConsentStateV1["perRepo"][number]>();
193
+ if (consent) {
194
+ for (const row of consent.perRepo) consentByRepo.set(row.repoPseudonym, row);
195
+ }
196
+ return manifest.repos.map((r) => {
197
+ const c = consentByRepo.get(r.repoPseudonym);
198
+ return {
199
+ repoPseudonym: r.repoPseudonym,
200
+ sessions: r.sessions,
201
+ consentedCrossRepo: c ? c.consentedCrossRepo : false,
202
+ revokedAt: c && c.revokedAt ? c.revokedAt : undefined,
203
+ };
204
+ });
205
+ }
206
+
207
+ function buildBody(corpusDir: string, manifest: RepoCorpusManifestV1): RepoCorpusStatusV1 {
208
+ const consent = readConsentState(corpusDir);
209
+ const perRepo = buildPerRepo(manifest, consent);
210
+ const hasData = manifest.repos.length > 0;
211
+ const status = deriveVcStatus({ enabled: true, hasData, structuralOnly: false });
212
+ return {
213
+ schema: "repo-corpus-status-v1",
214
+ corpus: manifest,
215
+ perRepo,
216
+ totalEvents: manifest.totalEvents,
217
+ status,
218
+ };
219
+ }
220
+
221
+ function awaitingDataBody(): RepoCorpusStatusV1 {
222
+ const status = deriveVcStatus({
223
+ enabled: true,
224
+ hasData: false,
225
+ structuralOnly: false,
226
+ });
227
+ return {
228
+ schema: "repo-corpus-status-v1",
229
+ corpus: null,
230
+ perRepo: [],
231
+ totalEvents: 0,
232
+ status,
233
+ };
234
+ }
235
+
236
+ export function handleRepoCorpus(
237
+ req: IncomingMessage,
238
+ res: ServerResponse,
239
+ _ctx: RouteContext,
240
+ ): boolean {
241
+ const url = req.url ?? "";
242
+ if (url !== "/api/repo-corpus") return false;
243
+ if (req.method !== "GET") {
244
+ sendJson(res, 405, { error: "method_not_allowed" });
245
+ return true;
246
+ }
247
+ if (!REPO_CORPUS_ENABLED()) {
248
+ // Flag-off: 404 — byte-identical predecessor.
249
+ sendJson(res, 404, { error: "not_found" });
250
+ return true;
251
+ }
252
+
253
+ const corpusDir = repoCorpusDir();
254
+ if (corpusDir === null) {
255
+ sendJson(res, 200, awaitingDataBody());
256
+ return true;
257
+ }
258
+
259
+ // Memoized-facts pattern: serve a still-fresh entry keyed on {mtime,size}.
260
+ let size = 0;
261
+ let mtime = 0;
262
+ try {
263
+ const s = statSync(join(corpusDir, MANIFEST_FILENAME)); // guardrails-allow PREVENT-PI-004: local stat (loopback)
264
+ size = s.size;
265
+ mtime = s.mtimeMs;
266
+ } catch {
267
+ // no manifest on disk yet
268
+ }
269
+ if (size === 0 && mtime === 0) {
270
+ sendJson(res, 200, awaitingDataBody());
271
+ return true;
272
+ }
273
+
274
+ const key = `${mtime}:${size}`;
275
+ const now = Date.now();
276
+ if (memo !== null && memo.key === key && now - memo.at <= MEMO_TTL_MS) {
277
+ sendJson(res, 200, memo.body);
278
+ return true;
279
+ }
280
+
281
+ const manifest = readManifest(corpusDir);
282
+ const body = manifest === null ? awaitingDataBody() : buildBody(corpusDir, manifest);
283
+ memo = { key, at: now, body };
284
+ sendJson(res, 200, body);
285
+ return true;
286
+ }
@@ -68,3 +68,4 @@ export {
68
68
  } from "./routes-cortex-improve.js";
69
69
  export { handleDedupTierAttribution } from "./routes-dedup-attribution.js";
70
70
  export { handleCosineFpReport } from "./routes-cosine-fp.js";
71
+ export { handleRepoCorpus } from "./routes-repo-corpus.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.20.55",
3
+ "version": "0.20.57",
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",
@@ -0,0 +1,23 @@
1
+ /**
2
+ * config/repo-corpus.ts — REPO-A cross-repo corpus preparation sprint flag.
3
+ *
4
+ * Positive sprint flag sibling (mirrors config/vector-cortex-cosfp.ts), NOT under
5
+ * a vector-cortex- prefix per the REPO-A spec: the reader route + corpus-builder
6
+ * gate on this flag. Root src/config.ts re-exports it so every existing import
7
+ * keeps resolving unchanged. Pi-agnostic, dependency-free (PREVENT-PI-004 /
8
+ * PREVENT-011).
9
+ */
10
+
11
+ import { sprintFlag } from "./vector-cortex-flag.js";
12
+
13
+ /**
14
+ * REPO-A — cross-repo corpus preparation. Default ON.
15
+ * `MEGACOMPACT_REPO_CORPUS=0` disables and is byte-identical to the predecessor:
16
+ * the reader route GET /api/repo-corpus 404s (no derived artifact written), the
17
+ * corpus-builder refuses to run, and the existing single-repo recall path is
18
+ * untouched. This flag MUST also be a dashboard SETTINGS toggle (visible in the
19
+ * config UI, never in EXCLUDED_SETTINGS). The builder + route perform only local
20
+ * filesystem reads / local `git` introspection (PREVENT-PI-004).
21
+ */
22
+ export const REPO_CORPUS_ENABLED = (): boolean =>
23
+ sprintFlag("MEGACOMPACT_REPO_CORPUS");
@@ -0,0 +1,37 @@
1
+ /**
2
+ * config/vector-cortex-cosfp-real.ts — COS-FP-R real-corpus FP validation 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-cosfp.ts and the other
6
+ * per-sprint flag siblings were. Gating the real-corpus L2 cosine false-positive
7
+ * validation: the `scripts/cosine-fp/real-bench.mjs` execution harness that runs
8
+ * only when a consented donated corpus exists, the execution-time report rows,
9
+ * and the evidence record. A pure-local, consent-gated tool (SECURITY_PRIVACY
10
+ * §Lifecycle/§Consent + EVAL-REDACT-002) — it never reads real session/ledger
11
+ * bytes without an explicit per-session consent record.
12
+ *
13
+ * The split is mechanical: COSINE_FP_REAL_ENABLED is byte-identical in name,
14
+ * semantics, and default to the sibling flag convention (positive, default ON),
15
+ * and vector-cortex.ts re-exports it so every existing
16
+ * `from "./config/vector-cortex.js"` import keeps resolving unchanged.
17
+ *
18
+ * Pi-agnostic, dependency-free (PREVENT-PI-004 / PREVENT-011).
19
+ */
20
+
21
+ import { sprintFlag } from "./vector-cortex-flag.js";
22
+
23
+ /**
24
+ * COS-FP-R — real-corpus L2 cosine FP validation against donated sessions.
25
+ * Default ON. `MEGACOMPACT_COSINE_FP_REAL=0` disables and is byte-identical to
26
+ * the predecessor: `scripts/cosine-fp/real-bench.mjs` is inert (nothing
27
+ * executes, no writes), no real-corpus report rows or evidence are written, and
28
+ * no threshold is adopted. When ON (default), execution still only proceeds when
29
+ * a valid consented corpus exists under `scripts/cosine-fp/corpus/` — absent
30
+ * corpus is the normative `no_corpus` pre-donation state, not a failure. Corpus
31
+ * bytes are read only for sessions with an explicit, append-only, revocable
32
+ * consent record (SECURITY_PRIVACY §Lifecycle/§Consent). This flag MUST also be
33
+ * a dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS).
34
+ * The harness performs only local filesystem reads (PREVENT-PI-004).
35
+ */
36
+ export const COSINE_FP_REAL_ENABLED = (): boolean =>
37
+ sprintFlag("MEGACOMPACT_COSINE_FP_REAL");
@@ -64,6 +64,11 @@ export { ENC_0F_ENABLED } from "./vector-cortex-enc0f.js";
64
64
  export { ENC_0G_ENABLED } from "./vector-cortex-enc0g.js";
65
65
  // COS-FP-A synthetic FP harness flag extracted to vector-cortex-cosfp.ts.
66
66
  export { COSINE_FP_BENCH_ENABLED } from "./vector-cortex-cosfp.js";
67
+ // COS-FP-R real-corpus FP validation flag extracted to vector-cortex-cosfp-real.ts.
68
+ export { COSINE_FP_REAL_ENABLED } from "./vector-cortex-cosfp-real.js";
69
+ // REPO-A cross-repo corpus prep flag extracted to repo-corpus.ts (no vector-cortex
70
+ // prefix per spec); re-exported so root src/config.ts barrel resolves unchanged.
71
+ export { REPO_CORPUS_ENABLED } from "./repo-corpus.js";
67
72
  export { ENC_1A_ENABLED } from "./vector-cortex-enc1a.js";
68
73
  export {
69
74
  ENC_1B_ENABLED,
package/src/config.ts CHANGED
@@ -203,6 +203,8 @@ export {
203
203
  ENC_2BUDGET_MAX_MIB,
204
204
  ENC_2BUDGET_DEFAULT_MIB,
205
205
  COSINE_FP_BENCH_ENABLED,
206
+ COSINE_FP_REAL_ENABLED,
207
+ REPO_CORPUS_ENABLED,
206
208
  BREAKER_WINDOW_MS,
207
209
  BREAKER_MIN_ATTEMPTS,
208
210
  BREAKER_PERF_FAILURES,