@mneme-ai/core 2.51.0 → 2.53.0

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 (69) hide show
  1. package/dist/agent_manifest.d.ts.map +1 -1
  2. package/dist/agent_manifest.js +28 -0
  3. package/dist/agent_manifest.js.map +1 -1
  4. package/dist/catalog_count.d.ts +47 -0
  5. package/dist/catalog_count.d.ts.map +1 -0
  6. package/dist/catalog_count.js +76 -0
  7. package/dist/catalog_count.js.map +1 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +6 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/nemesis/capillary.d.ts +68 -0
  13. package/dist/nemesis/capillary.d.ts.map +1 -0
  14. package/dist/nemesis/capillary.js +465 -0
  15. package/dist/nemesis/capillary.js.map +1 -0
  16. package/dist/nemesis/colosseum.d.ts +111 -0
  17. package/dist/nemesis/colosseum.d.ts.map +1 -0
  18. package/dist/nemesis/colosseum.js +275 -0
  19. package/dist/nemesis/colosseum.js.map +1 -0
  20. package/dist/nemesis/corpus_augmenter.d.ts +69 -0
  21. package/dist/nemesis/corpus_augmenter.d.ts.map +1 -0
  22. package/dist/nemesis/corpus_augmenter.js +164 -0
  23. package/dist/nemesis/corpus_augmenter.js.map +1 -0
  24. package/dist/nemesis/eu_ai_act_stamp.d.ts.map +1 -1
  25. package/dist/nemesis/eu_ai_act_stamp.js +14 -0
  26. package/dist/nemesis/eu_ai_act_stamp.js.map +1 -1
  27. package/dist/nemesis/index.d.ts +9 -0
  28. package/dist/nemesis/index.d.ts.map +1 -1
  29. package/dist/nemesis/index.js +30 -0
  30. package/dist/nemesis/index.js.map +1 -1
  31. package/dist/nemesis/janus.d.ts +93 -0
  32. package/dist/nemesis/janus.d.ts.map +1 -0
  33. package/dist/nemesis/janus.js +160 -0
  34. package/dist/nemesis/janus.js.map +1 -0
  35. package/dist/nemesis/key_setup.d.ts +65 -0
  36. package/dist/nemesis/key_setup.d.ts.map +1 -0
  37. package/dist/nemesis/key_setup.js +173 -0
  38. package/dist/nemesis/key_setup.js.map +1 -0
  39. package/dist/nemesis/molt.d.ts +104 -0
  40. package/dist/nemesis/molt.d.ts.map +1 -0
  41. package/dist/nemesis/molt.js +220 -0
  42. package/dist/nemesis/molt.js.map +1 -0
  43. package/dist/nemesis/sibyl.d.ts +122 -0
  44. package/dist/nemesis/sibyl.d.ts.map +1 -0
  45. package/dist/nemesis/sibyl.js +179 -0
  46. package/dist/nemesis/sibyl.js.map +1 -0
  47. package/dist/nemesis/stealth_score.d.ts +97 -0
  48. package/dist/nemesis/stealth_score.d.ts.map +1 -0
  49. package/dist/nemesis/stealth_score.js +204 -0
  50. package/dist/nemesis/stealth_score.js.map +1 -0
  51. package/dist/nemesis/themis.d.ts +109 -0
  52. package/dist/nemesis/themis.d.ts.map +1 -0
  53. package/dist/nemesis/themis.js +194 -0
  54. package/dist/nemesis/themis.js.map +1 -0
  55. package/dist/release_gate/probe_coverage.d.ts +16 -1
  56. package/dist/release_gate/probe_coverage.d.ts.map +1 -1
  57. package/dist/release_gate/probe_coverage.js +13 -4
  58. package/dist/release_gate/probe_coverage.js.map +1 -1
  59. package/dist/release_gate/wiring_lag.d.ts +65 -0
  60. package/dist/release_gate/wiring_lag.d.ts.map +1 -0
  61. package/dist/release_gate/wiring_lag.js +116 -0
  62. package/dist/release_gate/wiring_lag.js.map +1 -0
  63. package/dist/truth_gate/claims.d.ts.map +1 -1
  64. package/dist/truth_gate/claims.js +26 -0
  65. package/dist/truth_gate/claims.js.map +1 -1
  66. package/dist/truth_gate/probes.d.ts.map +1 -1
  67. package/dist/truth_gate/probes.js +209 -0
  68. package/dist/truth_gate/probes.js.map +1 -1
  69. package/package.json +1 -1
@@ -32,6 +32,36 @@ export { reconcileVendor, } from "./vendor_reconcile.js";
32
32
  export { computeWeightDelta, applyToConclave, } from "./nemesis_to_conclave.js";
33
33
  // v2.49.0 — B4 actual wire: write-path canonical record.
34
34
  export { recordActivityReconciled, } from "./activity_writer.js";
35
+ // v2.52.0 — STEALTH SCORE (Million Dollar Secret diamond #1) — inverse
36
+ // of fingerprint confidence + anonymity-credit HMAC ledger.
37
+ export { computeStealthScore, earnAnonymityCredits, spendAnonymityCredits, stealthCreditStatus, verifyStealthLedger, } from "./stealth_score.js";
38
+ // v2.52.0 — CAPILLARY (Million Dollar Secret diamond #2) — micro-tell
39
+ // fingerprinter at whitespace/quote/naming/comma level + ANTI-CAPILLARY
40
+ // style-rewrite suggestion engine (the first AI-coding-agent micro-fingerprinter).
41
+ export { extractMicroProfile, microDistance, suggestAntiCapillary, listCapillaryFeatures, } from "./capillary.js";
42
+ // v2.52.0 — COLOSSEUM (Million Dollar Secret diamond #3) — auto-tournament
43
+ // of N contenders × M disguise targets + 3-axis HMAC-signed ELO leaderboard
44
+ // (deception / detectability / mimicry) + spectator replay.
45
+ export { runTournament, readColosseumLeaderboard, verifyColosseumChain, spectatorReplay, } from "./colosseum.js";
46
+ // v2.52.0 — MOLT (Million Dollar Secret diamond #4) — silent model-rotation
47
+ // detector. Per-feature Welch-style comparison of pre/post fingerprint
48
+ // distributions; HMAC-signed forensic verdict + optional webhook emit.
49
+ export { detectMolt, verifyMoltVerdict, emitMoltWebhook, } from "./molt.js";
50
+ // v2.52.0 — THEMIS (Million Dollar Secret diamond #5) — alibi verifier
51
+ // ("I am NOT vendor X") + star-rated evidence + compliance bundle pairing.
52
+ export { verifyAlibi, verifyAlibiSignature, buildComplianceBundle, verifyComplianceBundle, } from "./themis.js";
53
+ // v2.52.0 — SIBYL (Million Dollar Secret diamond #6) — ZK-style identity
54
+ // commitment: commit at session-start (sealed), reveal at end. Hash-commitment
55
+ // branch (SHA-256(identity || nonce || sessionId)) — locks identity against
56
+ // mid-session switching. Supports nested commitments (vendor/model/version mask).
57
+ export { commitIdentity, revealIdentity, verifySibylChain, verifyCommitmentReveal, listOpenCommitments, } from "./sibyl.js";
58
+ // v2.53.0 — Open-wound patches (P0/P1).
59
+ // P0-1 HMAC key setup wizard + STRICT mode
60
+ // P1-2 CORPUS AUGMENTER (header-less / naturalistic 5x perturbation)
61
+ // P1-3 JANUS organ (cross-cluster identity-swap detector)
62
+ export { runKeyWizard, checkKeyPermissions, strictKeyCheck, } from "./key_setup.js";
63
+ export { applyAugmentation, buildAugmentedCorpus, evaluateAugmentedAccuracy, computeAugmentedStats, } from "./corpus_augmenter.js";
64
+ export { locateBasin, observe, detectIdentitySwap, verifyJanusResult, } from "./janus.js";
35
65
  // v2.50.0 — VENDOR ALLOWLIST GUARD: kills EMBEDDER-LEAK class structurally.
36
66
  // Central allowlist of real agent vendors; backend/embedder names (ollama,
37
67
  // openai-gpt, gemini, etc) coerced to "unknown" + logged to
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/nemesis/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,OAAO,EAAsB,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAiD,MAAM,yBAAyB,CAAC;AAC9G,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAwC,MAAM,qBAAqB,CAAC;AAC7H,OAAO,EAAE,kBAAkB,EAAqB,MAAM,oBAAoB,CAAC;AAE3E,qEAAqE;AACrE,wCAAwC;AACxC,OAAO,EACL,eAAe,EAAE,YAAY,EAAE,SAAS,GAEzC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EAAE,oBAAoB,GAE9C,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,sBAAsB,EAAE,qBAAqB,EAAE,cAAc,EAAE,iBAAiB,GAEjF,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,cAAc,EAAE,qBAAqB,GAEtC,MAAM,qBAAqB,CAAC;AAE7B,+CAA+C;AAC/C,OAAO,EACL,eAAe,GAChB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,kBAAkB,EAAE,eAAe,GAEpC,MAAM,0BAA0B,CAAC;AAElC,yDAAyD;AACzD,OAAO,EACL,wBAAwB,GAEzB,MAAM,sBAAsB,CAAC;AAE9B,4EAA4E;AAC5E,2EAA2E;AAC3E,4DAA4D;AAC5D,mDAAmD;AACnD,OAAO,EACL,sBAAsB,EAAE,wBAAwB,EAChD,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,aAAa,GAGvD,MAAM,uBAAuB,CAAC;AAE/B,sEAAsE;AACtE,sEAAsE;AACtE,6EAA6E;AAC7E,qEAAqE;AACrE,sEAAsE;AACtE,OAAO,EACL,cAAc,EACd,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,QAAQ,GAKT,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/nemesis/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,OAAO,EAAsB,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAiD,MAAM,yBAAyB,CAAC;AAC9G,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAwC,MAAM,qBAAqB,CAAC;AAC7H,OAAO,EAAE,kBAAkB,EAAqB,MAAM,oBAAoB,CAAC;AAE3E,qEAAqE;AACrE,wCAAwC;AACxC,OAAO,EACL,eAAe,EAAE,YAAY,EAAE,SAAS,GAEzC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EAAE,oBAAoB,GAE9C,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,sBAAsB,EAAE,qBAAqB,EAAE,cAAc,EAAE,iBAAiB,GAEjF,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,cAAc,EAAE,qBAAqB,GAEtC,MAAM,qBAAqB,CAAC;AAE7B,+CAA+C;AAC/C,OAAO,EACL,eAAe,GAChB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,kBAAkB,EAAE,eAAe,GAEpC,MAAM,0BAA0B,CAAC;AAElC,yDAAyD;AACzD,OAAO,EACL,wBAAwB,GAEzB,MAAM,sBAAsB,CAAC;AAE9B,uEAAuE;AACvE,4DAA4D;AAC5D,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EAAE,qBAAqB,EAC3C,mBAAmB,EAAE,mBAAmB,GAEzC,MAAM,oBAAoB,CAAC;AAE5B,sEAAsE;AACtE,wEAAwE;AACxE,mFAAmF;AACnF,OAAO,EACL,mBAAmB,EAAE,aAAa,EAClC,oBAAoB,EAAE,qBAAqB,GAE5C,MAAM,gBAAgB,CAAC;AAExB,2EAA2E;AAC3E,4EAA4E;AAC5E,4DAA4D;AAC5D,OAAO,EACL,aAAa,EACb,wBAAwB,EAAE,oBAAoB,EAAE,eAAe,GAEhE,MAAM,gBAAgB,CAAC;AAExB,4EAA4E;AAC5E,uEAAuE;AACvE,uEAAuE;AACvE,OAAO,EACL,UAAU,EAAE,iBAAiB,EAAE,eAAe,GAE/C,MAAM,WAAW,CAAC;AAEnB,uEAAuE;AACvE,2EAA2E;AAC3E,OAAO,EACL,WAAW,EAAE,oBAAoB,EACjC,qBAAqB,EAAE,sBAAsB,GAG9C,MAAM,aAAa,CAAC;AAErB,yEAAyE;AACzE,+EAA+E;AAC/E,4EAA4E;AAC5E,kFAAkF;AAClF,OAAO,EACL,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAChD,sBAAsB,EAAE,mBAAmB,GAG5C,MAAM,YAAY,CAAC;AAEpB,wCAAwC;AACxC,6CAA6C;AAC7C,uEAAuE;AACvE,4DAA4D;AAC5D,OAAO,EACL,YAAY,EAAE,mBAAmB,EAAE,cAAc,GAElD,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,iBAAiB,EAAE,oBAAoB,EACvC,yBAAyB,EAAE,qBAAqB,GAEjD,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,WAAW,EAAE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,GAG5D,MAAM,YAAY,CAAC;AAEpB,4EAA4E;AAC5E,2EAA2E;AAC3E,4DAA4D;AAC5D,mDAAmD;AACnD,OAAO,EACL,sBAAsB,EAAE,wBAAwB,EAChD,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,aAAa,GAGvD,MAAM,uBAAuB,CAAC;AAE/B,sEAAsE;AACtE,sEAAsE;AACtE,6EAA6E;AAC7E,qEAAqE;AACrE,sEAAsE;AACtE,OAAO,EACL,cAAc,EACd,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,QAAQ,GAKT,MAAM,cAAc,CAAC"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * v2.53.0 — JANUS organ: cross-vendor cluster-boundary detector.
3
+ *
4
+ * The two-faced Roman god — looks at past + future simultaneously.
5
+ *
6
+ * Closes Eve's identity-swap blind spot from the Million Dollar Secret
7
+ * simulation: MOLT (v2.52) detects INTRA-vendor drift (vendor X drifts
8
+ * over time) but misses CROSS-vendor swaps (vendor X mid-session
9
+ * starts behaving like vendor Y). The classifier might still pick Y as
10
+ * the new winner, but neither MOLT nor verify_identity surfaces the
11
+ * "you crossed a cluster boundary" event.
12
+ *
13
+ * Algorithm:
14
+ * 1. Build cluster centroids from seed corpus (per vendor, per
15
+ * discriminative feature).
16
+ * 2. For an observation O, compute distance d(O, centroid_V) for every
17
+ * known vendor V.
18
+ * 3. Assign O to the nearest centroid → "current basin".
19
+ * 4. Track per-session basin history. If basin changes between
20
+ * observations → cross-cluster transition event.
21
+ * 5. Confidence: distance to old basin / distance to new basin (lower
22
+ * ratio = sharper swap).
23
+ *
24
+ * Different from MOLT because MOLT compares pre/post WINDOWS of the
25
+ * SAME vendor's drift; JANUS detects the MOMENT a vendor's fingerprint
26
+ * crosses into a DIFFERENT vendor's basin.
27
+ *
28
+ * Composes: extractFingerprint + seedStats.
29
+ *
30
+ * Pure deterministic + defensive; never throws.
31
+ */
32
+ import type { Fingerprint } from "./types.js";
33
+ export interface ClusterDistance {
34
+ vendor: string;
35
+ distance: number;
36
+ }
37
+ export interface JanusBasin {
38
+ /** Nearest vendor centroid. */
39
+ basin: string;
40
+ /** Distance to nearest centroid. */
41
+ basinDistance: number;
42
+ /** Distance to SECOND-nearest centroid (margin signal). */
43
+ secondNearest: ClusterDistance | null;
44
+ /** Margin = secondNearest.distance - basinDistance. Larger = sharper assignment. */
45
+ margin: number;
46
+ /** All centroid distances, sorted ascending. */
47
+ allDistances: ClusterDistance[];
48
+ }
49
+ export interface JanusObservation {
50
+ fingerprint: Fingerprint;
51
+ basin: JanusBasin;
52
+ }
53
+ export interface JanusTransition {
54
+ fromBasin: string;
55
+ toBasin: string;
56
+ fromDistance: number;
57
+ toDistance: number;
58
+ /** Ratio of (distance to OLD basin from CURRENT obs) / (distance to NEW basin from CURRENT obs).
59
+ * > 1 means the obs is clearly closer to NEW than OLD → confident swap. */
60
+ swapConfidence: number;
61
+ /** Plain-English citation. */
62
+ citation: string;
63
+ }
64
+ export interface JanusSessionResult {
65
+ observations: JanusObservation[];
66
+ transitions: JanusTransition[];
67
+ /** Did at least one cross-cluster swap fire? */
68
+ swapDetected: boolean;
69
+ hmac: string;
70
+ }
71
+ /**
72
+ * Locate which vendor cluster an observation belongs to.
73
+ * Defensive: empty stats → returns basin="unknown" with infinity distance.
74
+ */
75
+ export declare function locateBasin(fingerprint: Fingerprint): JanusBasin;
76
+ /**
77
+ * Observe a fixture; return {fingerprint, basin}.
78
+ */
79
+ export declare function observe(fixture: {
80
+ diff: string;
81
+ prDescription: string;
82
+ commitMessages: string[];
83
+ } | Fingerprint): JanusObservation;
84
+ /**
85
+ * Walk a sequence of observations from the SAME session; surface
86
+ * cross-cluster transitions + an HMAC-signed verdict.
87
+ */
88
+ export declare function detectIdentitySwap(observations: JanusObservation[], opts?: {
89
+ minMargin?: number;
90
+ }): JanusSessionResult;
91
+ /** Verify a session-result's HMAC. */
92
+ export declare function verifyJanusResult(r: JanusSessionResult): boolean;
93
+ //# sourceMappingURL=janus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"janus.d.ts","sourceRoot":"","sources":["../../src/nemesis/janus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAY,MAAM,YAAY,CAAC;AASxD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,aAAa,EAAE,eAAe,GAAG,IAAI,CAAC;IACtC,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,YAAY,EAAE,eAAe,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,WAAW,CAAC;IACzB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB;gFAC4E;IAC5E,cAAc,EAAE,MAAM,CAAC;IACvB,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,gDAAgD;IAChD,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAoBD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,WAAW,GAAG,UAAU,CAyBhE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,WAAW,GAAG,gBAAgB,CAMlI;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,gBAAgB,EAAE,EAChC,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,kBAAkB,CAoCpB;AAWD,sCAAsC;AACtC,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAQhE"}
@@ -0,0 +1,160 @@
1
+ /**
2
+ * v2.53.0 — JANUS organ: cross-vendor cluster-boundary detector.
3
+ *
4
+ * The two-faced Roman god — looks at past + future simultaneously.
5
+ *
6
+ * Closes Eve's identity-swap blind spot from the Million Dollar Secret
7
+ * simulation: MOLT (v2.52) detects INTRA-vendor drift (vendor X drifts
8
+ * over time) but misses CROSS-vendor swaps (vendor X mid-session
9
+ * starts behaving like vendor Y). The classifier might still pick Y as
10
+ * the new winner, but neither MOLT nor verify_identity surfaces the
11
+ * "you crossed a cluster boundary" event.
12
+ *
13
+ * Algorithm:
14
+ * 1. Build cluster centroids from seed corpus (per vendor, per
15
+ * discriminative feature).
16
+ * 2. For an observation O, compute distance d(O, centroid_V) for every
17
+ * known vendor V.
18
+ * 3. Assign O to the nearest centroid → "current basin".
19
+ * 4. Track per-session basin history. If basin changes between
20
+ * observations → cross-cluster transition event.
21
+ * 5. Confidence: distance to old basin / distance to new basin (lower
22
+ * ratio = sharper swap).
23
+ *
24
+ * Different from MOLT because MOLT compares pre/post WINDOWS of the
25
+ * SAME vendor's drift; JANUS detects the MOMENT a vendor's fingerprint
26
+ * crosses into a DIFFERENT vendor's basin.
27
+ *
28
+ * Composes: extractFingerprint + seedStats.
29
+ *
30
+ * Pure deterministic + defensive; never throws.
31
+ */
32
+ import { createHmac } from "node:crypto";
33
+ import { extractFingerprint } from "./features.js";
34
+ import { seedStats } from "./calibration_corpus.js";
35
+ const KEY_ENV = "MNEME_JANUS_KEY";
36
+ const DEFAULT_KEY = "mneme-janus-v1";
37
+ function keyOf() {
38
+ return process.env[KEY_ENV] ?? DEFAULT_KEY;
39
+ }
40
+ /** Euclidean distance between fingerprint and a vendor's per-feature means. */
41
+ function distanceToCentroid(fp, stats) {
42
+ let sumSq = 0;
43
+ let count = 0;
44
+ for (const k of Object.keys(stats.features)) {
45
+ const v = fp[k];
46
+ if (typeof v !== "number" || !Number.isFinite(v))
47
+ continue;
48
+ const m = stats.features[k].mean;
49
+ const stdev = Math.max(stats.features[k].stdev, Math.abs(m) * 0.1, 1e-3);
50
+ // Normalized squared distance per feature
51
+ const z = (v - m) / stdev;
52
+ sumSq += z * z;
53
+ count++;
54
+ }
55
+ if (count === 0)
56
+ return Number.POSITIVE_INFINITY;
57
+ return Math.sqrt(sumSq / count);
58
+ }
59
+ /**
60
+ * Locate which vendor cluster an observation belongs to.
61
+ * Defensive: empty stats → returns basin="unknown" with infinity distance.
62
+ */
63
+ export function locateBasin(fingerprint) {
64
+ let stats;
65
+ try {
66
+ stats = seedStats();
67
+ }
68
+ catch {
69
+ return { basin: "unknown", basinDistance: Number.POSITIVE_INFINITY, secondNearest: null, margin: 0, allDistances: [] };
70
+ }
71
+ const distances = [];
72
+ for (const [vendor, s] of stats) {
73
+ distances.push({ vendor, distance: distanceToCentroid(fingerprint, s) });
74
+ }
75
+ distances.sort((a, b) => a.distance - b.distance);
76
+ if (distances.length === 0) {
77
+ return { basin: "unknown", basinDistance: Number.POSITIVE_INFINITY, secondNearest: null, margin: 0, allDistances: [] };
78
+ }
79
+ const nearest = distances[0];
80
+ const second = distances[1] ?? null;
81
+ const margin = second ? second.distance - nearest.distance : 0;
82
+ return {
83
+ basin: nearest.vendor,
84
+ basinDistance: nearest.distance,
85
+ secondNearest: second,
86
+ margin,
87
+ allDistances: distances,
88
+ };
89
+ }
90
+ /**
91
+ * Observe a fixture; return {fingerprint, basin}.
92
+ */
93
+ export function observe(fixture) {
94
+ const fp = "multiline_commit_ratio" in fixture
95
+ ? fixture
96
+ : extractFingerprint(fixture);
97
+ const basin = locateBasin(fp);
98
+ return { fingerprint: fp, basin };
99
+ }
100
+ /**
101
+ * Walk a sequence of observations from the SAME session; surface
102
+ * cross-cluster transitions + an HMAC-signed verdict.
103
+ */
104
+ export function detectIdentitySwap(observations, opts = {}) {
105
+ const minMargin = opts.minMargin ?? 0.5;
106
+ const transitions = [];
107
+ if (!Array.isArray(observations) || observations.length < 2) {
108
+ return signed({ observations: observations ?? [], transitions: [], swapDetected: false });
109
+ }
110
+ let prev = observations[0];
111
+ for (let i = 1; i < observations.length; i++) {
112
+ const cur = observations[i];
113
+ if (prev.basin.basin === cur.basin.basin) {
114
+ prev = cur;
115
+ continue;
116
+ }
117
+ // Only surface as a real swap when the new basin is clearly nearer.
118
+ if (cur.basin.margin < minMargin) {
119
+ prev = cur;
120
+ continue;
121
+ }
122
+ // Distance from CURRENT obs to PREVIOUS basin vs current basin
123
+ const prevBasinDistInCur = cur.basin.allDistances.find((d) => d.vendor === prev.basin.basin);
124
+ if (!prevBasinDistInCur || !Number.isFinite(prevBasinDistInCur.distance)) {
125
+ prev = cur;
126
+ continue;
127
+ }
128
+ const swapConfidence = prevBasinDistInCur.distance / Math.max(cur.basin.basinDistance, 1e-6);
129
+ transitions.push({
130
+ fromBasin: prev.basin.basin,
131
+ toBasin: cur.basin.basin,
132
+ fromDistance: prevBasinDistInCur.distance,
133
+ toDistance: cur.basin.basinDistance,
134
+ swapConfidence,
135
+ citation: `JANUS: identity swap detected — fingerprint crossed from ${prev.basin.basin} basin to ${cur.basin.basin} basin (swap confidence ${swapConfidence.toFixed(2)}x).`,
136
+ });
137
+ prev = cur;
138
+ }
139
+ return signed({ observations, transitions, swapDetected: transitions.length > 0 });
140
+ }
141
+ function signed(body) {
142
+ const hmac = createHmac("sha256", keyOf()).update(JSON.stringify({
143
+ transitions: body.transitions,
144
+ swapDetected: body.swapDetected,
145
+ obsCount: body.observations.length,
146
+ })).digest("hex");
147
+ return { ...body, hmac };
148
+ }
149
+ /** Verify a session-result's HMAC. */
150
+ export function verifyJanusResult(r) {
151
+ if (!r || typeof r.hmac !== "string")
152
+ return false;
153
+ const expected = createHmac("sha256", keyOf()).update(JSON.stringify({
154
+ transitions: r.transitions,
155
+ swapDetected: r.swapDetected,
156
+ obsCount: r.observations.length,
157
+ })).digest("hex");
158
+ return expected === r.hmac;
159
+ }
160
+ //# sourceMappingURL=janus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"janus.js","sourceRoot":"","sources":["../../src/nemesis/janus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * v2.53.0 — HMAC KEY SETUP WIZARD + STRICT mode.
3
+ *
4
+ * Closes P0-1 from the v2.52 session audit: pre-v2.53 NEMESIS warned LOUD
5
+ * when falling back to the public default key, but didn't auto-fix it
6
+ * + didn't refuse to operate. Result: every developer who ignored the
7
+ * warning shipped HMAC receipts an attacker can forge.
8
+ *
9
+ * v2.53 protocol:
10
+ * 1. First-run wizard auto-generates a 32-byte random key + writes it
11
+ * to .mneme/nemesis/hmac.key with 0o600 perms (owner-only).
12
+ * 2. MNEME_NEMESIS_STRICT=1 env var → key_management refuses to return
13
+ * the default-insecure key (throws instead). For CI / production /
14
+ * regulated environments.
15
+ * 3. Idempotent: re-running detects existing key + skips generation.
16
+ *
17
+ * Defensive: never throws on filesystem errors (best-effort), only the
18
+ * STRICT-mode refusal is intentional.
19
+ */
20
+ export interface KeyWizardResult {
21
+ ok: boolean;
22
+ action: "generated-repo" | "generated-user" | "already-present" | "skipped" | "failed";
23
+ path?: string;
24
+ /** Length of the resolved key (or 0 on fail). */
25
+ keyLength: number;
26
+ reason: string;
27
+ }
28
+ export interface KeyWizardOpts {
29
+ repoRoot: string;
30
+ /** "repo" → write to <repoRoot>/.mneme/nemesis/hmac.key (default).
31
+ * "user" → write to ~/.mneme/nemesis/hmac.key (cross-repo). */
32
+ target?: "repo" | "user";
33
+ /** Re-generate even if a key already exists. Default false. */
34
+ force?: boolean;
35
+ /** Don't write — only compute the would-be path + key length. */
36
+ dryRun?: boolean;
37
+ }
38
+ /**
39
+ * Run the key setup wizard. Idempotent; never overwrites an existing
40
+ * key unless force=true. Returns structured outcome.
41
+ */
42
+ export declare function runKeyWizard(opts: KeyWizardOpts): KeyWizardResult;
43
+ /**
44
+ * Check key file permissions (Unix only). Returns warning on world-readable
45
+ * files. On Windows, returns ok=true with note since chmod is a no-op.
46
+ */
47
+ export declare function checkKeyPermissions(repoRoot: string): {
48
+ ok: boolean;
49
+ mode?: number;
50
+ reason: string;
51
+ };
52
+ /**
53
+ * STRICT MODE check: throws when the user is operating with a
54
+ * default-insecure HMAC key AND has opted into strict enforcement
55
+ * via MNEME_NEMESIS_STRICT=1. Call this from any code path that
56
+ * issues forensic-grade receipts (EU stamp / cli-activity / SIBYL).
57
+ *
58
+ * Non-strict mode: returns { ok: false, message } so caller can decide.
59
+ */
60
+ export declare function strictKeyCheck(repoRoot: string): {
61
+ ok: boolean;
62
+ usingDefault: boolean;
63
+ message: string;
64
+ };
65
+ //# sourceMappingURL=key_setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key_setup.d.ts","sourceRoot":"","sources":["../../src/nemesis/key_setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAUH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,SAAS,GAAG,QAAQ,CAAC;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB;oEACgE;IAChE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iEAAiE;IACjE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAYD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,eAAe,CAyDjE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAiBpG;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAiCxG"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * v2.53.0 — HMAC KEY SETUP WIZARD + STRICT mode.
3
+ *
4
+ * Closes P0-1 from the v2.52 session audit: pre-v2.53 NEMESIS warned LOUD
5
+ * when falling back to the public default key, but didn't auto-fix it
6
+ * + didn't refuse to operate. Result: every developer who ignored the
7
+ * warning shipped HMAC receipts an attacker can forge.
8
+ *
9
+ * v2.53 protocol:
10
+ * 1. First-run wizard auto-generates a 32-byte random key + writes it
11
+ * to .mneme/nemesis/hmac.key with 0o600 perms (owner-only).
12
+ * 2. MNEME_NEMESIS_STRICT=1 env var → key_management refuses to return
13
+ * the default-insecure key (throws instead). For CI / production /
14
+ * regulated environments.
15
+ * 3. Idempotent: re-running detects existing key + skips generation.
16
+ *
17
+ * Defensive: never throws on filesystem errors (best-effort), only the
18
+ * STRICT-mode refusal is intentional.
19
+ */
20
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync, chmodSync } from "node:fs";
21
+ import { join } from "node:path";
22
+ import { randomBytes } from "node:crypto";
23
+ import { homedir } from "node:os";
24
+ const REPO_KEY_REL = ".mneme/nemesis/hmac.key";
25
+ const USER_KEY_REL = ".mneme/nemesis/hmac.key";
26
+ function keyPath(opts) {
27
+ if (opts.target === "user")
28
+ return join(homedir(), USER_KEY_REL);
29
+ return join(opts.repoRoot, REPO_KEY_REL);
30
+ }
31
+ function envHasValidKey() {
32
+ const k = process.env["MNEME_NEMESIS_KEY"];
33
+ return typeof k === "string" && k.length >= 16;
34
+ }
35
+ /**
36
+ * Run the key setup wizard. Idempotent; never overwrites an existing
37
+ * key unless force=true. Returns structured outcome.
38
+ */
39
+ export function runKeyWizard(opts) {
40
+ try {
41
+ if (envHasValidKey() && !opts.force) {
42
+ const env = process.env["MNEME_NEMESIS_KEY"];
43
+ return {
44
+ ok: true,
45
+ action: "skipped",
46
+ keyLength: env.length,
47
+ reason: "MNEME_NEMESIS_KEY already set — no file action needed",
48
+ };
49
+ }
50
+ const p = keyPath(opts);
51
+ if (existsSync(p) && !opts.force) {
52
+ try {
53
+ const k = readFileSync(p, "utf8").trim();
54
+ if (k.length >= 16) {
55
+ return {
56
+ ok: true,
57
+ action: "already-present",
58
+ path: p,
59
+ keyLength: k.length,
60
+ reason: `existing key at ${p} (${k.length} chars)`,
61
+ };
62
+ }
63
+ }
64
+ catch { /* fall through to regenerate */ }
65
+ }
66
+ if (opts.dryRun) {
67
+ return {
68
+ ok: true,
69
+ action: opts.target === "user" ? "generated-user" : "generated-repo",
70
+ path: p,
71
+ keyLength: 64,
72
+ reason: `dry-run: would write a fresh 64-char hex key to ${p}`,
73
+ };
74
+ }
75
+ // Generate + write
76
+ const dir = p.replace(/[\/\\][^\/\\]+$/, "");
77
+ try {
78
+ mkdirSync(dir, { recursive: true });
79
+ }
80
+ catch { /* ok */ }
81
+ const key = randomBytes(32).toString("hex");
82
+ writeFileSync(p, key + "\n", { mode: 0o600 });
83
+ // Best-effort chmod (Windows ignores)
84
+ try {
85
+ chmodSync(p, 0o600);
86
+ }
87
+ catch { /* */ }
88
+ return {
89
+ ok: true,
90
+ action: opts.target === "user" ? "generated-user" : "generated-repo",
91
+ path: p,
92
+ keyLength: key.length,
93
+ reason: `generated fresh 64-char hex key at ${p} (mode 0600)`,
94
+ };
95
+ }
96
+ catch (e) {
97
+ return {
98
+ ok: false,
99
+ action: "failed",
100
+ keyLength: 0,
101
+ reason: `wizard failed: ${e.message}`,
102
+ };
103
+ }
104
+ }
105
+ /**
106
+ * Check key file permissions (Unix only). Returns warning on world-readable
107
+ * files. On Windows, returns ok=true with note since chmod is a no-op.
108
+ */
109
+ export function checkKeyPermissions(repoRoot) {
110
+ const p = join(repoRoot, REPO_KEY_REL);
111
+ if (!existsSync(p))
112
+ return { ok: true, reason: "no key file present" };
113
+ try {
114
+ const s = statSync(p);
115
+ const mode = s.mode & 0o777;
116
+ if (process.platform === "win32") {
117
+ return { ok: true, mode, reason: "Windows: chmod is a no-op; key access controlled by NTFS ACLs" };
118
+ }
119
+ // Owner-only is the goal: 0o600
120
+ if (mode === 0o600 || mode === 0o400) {
121
+ return { ok: true, mode, reason: `mode ${mode.toString(8)} — owner-only (correct)` };
122
+ }
123
+ return { ok: false, mode, reason: `mode ${mode.toString(8)} is too permissive; should be 600 (owner read/write only)` };
124
+ }
125
+ catch (e) {
126
+ return { ok: false, reason: `stat failed: ${e.message}` };
127
+ }
128
+ }
129
+ /**
130
+ * STRICT MODE check: throws when the user is operating with a
131
+ * default-insecure HMAC key AND has opted into strict enforcement
132
+ * via MNEME_NEMESIS_STRICT=1. Call this from any code path that
133
+ * issues forensic-grade receipts (EU stamp / cli-activity / SIBYL).
134
+ *
135
+ * Non-strict mode: returns { ok: false, message } so caller can decide.
136
+ */
137
+ export function strictKeyCheck(repoRoot) {
138
+ // Resolve via the same path key_management uses.
139
+ const env = process.env["MNEME_NEMESIS_KEY"];
140
+ if (typeof env === "string" && env.length >= 16) {
141
+ return { ok: true, usingDefault: false, message: "MNEME_NEMESIS_KEY env var set" };
142
+ }
143
+ const repo = join(repoRoot, REPO_KEY_REL);
144
+ if (existsSync(repo)) {
145
+ try {
146
+ const k = readFileSync(repo, "utf8").trim();
147
+ if (k.length >= 16)
148
+ return { ok: true, usingDefault: false, message: `repo key at ${repo}` };
149
+ }
150
+ catch { /* */ }
151
+ }
152
+ const user = join(homedir(), USER_KEY_REL);
153
+ if (existsSync(user)) {
154
+ try {
155
+ const k = readFileSync(user, "utf8").trim();
156
+ if (k.length >= 16)
157
+ return { ok: true, usingDefault: false, message: `user key at ${user}` };
158
+ }
159
+ catch { /* */ }
160
+ }
161
+ const strict = process.env["MNEME_NEMESIS_STRICT"] === "1";
162
+ if (strict) {
163
+ throw new Error("STRICT MODE: MNEME_NEMESIS_STRICT=1 set + no production key configured. " +
164
+ `Run: node -e "require('@mneme-ai/core').nemesis.runKeyWizard({repoRoot:'${repoRoot}'})" ` +
165
+ "OR set MNEME_NEMESIS_KEY env var (≥16 chars).");
166
+ }
167
+ return {
168
+ ok: false,
169
+ usingDefault: true,
170
+ message: "using default-insecure HMAC key — receipts are forgeable. Set MNEME_NEMESIS_STRICT=1 to enforce.",
171
+ };
172
+ }
173
+ //# sourceMappingURL=key_setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key_setup.js","sourceRoot":"","sources":["../../src/nemesis/key_setup.ts"],"names":[],"mappings":"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"}