@mneme-ai/core 2.19.19 → 2.19.21
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.
- package/dist/cosmic/aurelian_v1920.test.d.ts +2 -0
- package/dist/cosmic/aurelian_v1920.test.d.ts.map +1 -0
- package/dist/cosmic/aurelian_v1920.test.js +61 -0
- package/dist/cosmic/aurelian_v1920.test.js.map +1 -0
- package/dist/cosmic/aurelian_v1921.test.d.ts +2 -0
- package/dist/cosmic/aurelian_v1921.test.d.ts.map +1 -0
- package/dist/cosmic/aurelian_v1921.test.js +48 -0
- package/dist/cosmic/aurelian_v1921.test.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/provenance_dna/index.d.ts +115 -0
- package/dist/provenance_dna/index.d.ts.map +1 -0
- package/dist/provenance_dna/index.js +242 -0
- package/dist/provenance_dna/index.js.map +1 -0
- package/dist/provenance_dna/provenance_dna.test.d.ts +2 -0
- package/dist/provenance_dna/provenance_dna.test.d.ts.map +1 -0
- package/dist/provenance_dna/provenance_dna.test.js +279 -0
- package/dist/provenance_dna/provenance_dna.test.js.map +1 -0
- package/dist/reverse_caption_injection/index.d.ts +91 -0
- package/dist/reverse_caption_injection/index.d.ts.map +1 -0
- package/dist/reverse_caption_injection/index.js +162 -0
- package/dist/reverse_caption_injection/index.js.map +1 -0
- package/dist/reverse_caption_injection/reverse_caption_injection.test.d.ts +2 -0
- package/dist/reverse_caption_injection/reverse_caption_injection.test.d.ts.map +1 -0
- package/dist/reverse_caption_injection/reverse_caption_injection.test.js +177 -0
- package/dist/reverse_caption_injection/reverse_caption_injection.test.js.map +1 -0
- package/dist/snn_auto_promote/index.d.ts +82 -0
- package/dist/snn_auto_promote/index.d.ts.map +1 -0
- package/dist/snn_auto_promote/index.js +154 -0
- package/dist/snn_auto_promote/index.js.map +1 -0
- package/dist/snn_auto_promote/snn_auto_promote.test.d.ts +2 -0
- package/dist/snn_auto_promote/snn_auto_promote.test.d.ts.map +1 -0
- package/dist/snn_auto_promote/snn_auto_promote.test.js +164 -0
- package/dist/snn_auto_promote/snn_auto_promote.test.js.map +1 -0
- package/dist/textron_captcha/index.d.ts +147 -0
- package/dist/textron_captcha/index.d.ts.map +1 -0
- package/dist/textron_captcha/index.js +255 -0
- package/dist/textron_captcha/index.js.map +1 -0
- package/dist/textron_captcha/textron_captcha.test.d.ts +2 -0
- package/dist/textron_captcha/textron_captcha.test.d.ts.map +1 -0
- package/dist/textron_captcha/textron_captcha.test.js +231 -0
- package/dist/textron_captcha/textron_captcha.test.js.map +1 -0
- package/dist/whats_new.d.ts.map +1 -1
- package/dist/whats_new.js +16 -0
- package/dist/whats_new.js.map +1 -1
- package/dist/wrapper_genesis/index.d.ts.map +1 -1
- package/dist/wrapper_genesis/index.js +23 -0
- package/dist/wrapper_genesis/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2.19.21 — MNEME SNN AUTO-PROMOTE (W5 audit fix at SOURCE)
|
|
3
|
+
*
|
|
4
|
+
* User audit (W5): mneme status reports hash:fnv-256 [FALLBACK] even
|
|
5
|
+
* though SNN MCP tools ship + resolveEmbedder ladder includes SNN. The
|
|
6
|
+
* v2.19.16 BundledOrSnnEmbedder promoted at runtime (in-memory) but
|
|
7
|
+
* never wrote the resolution back to disk — so every `mneme status`
|
|
8
|
+
* call re-resolved + every fresh process started cold.
|
|
9
|
+
*
|
|
10
|
+
* v2.19.21 closes the gap: `decidePromotion()` compares the saved
|
|
11
|
+
* config's provider against the runtime-resolved tier and recommends a
|
|
12
|
+
* one-way promotion when (a) saved is hash (the worst tier), (b) runtime
|
|
13
|
+
* resolved to a higher tier. Pure function — caller decides whether to
|
|
14
|
+
* persist the promotion (writes to .mneme/config.json).
|
|
15
|
+
*
|
|
16
|
+
* HMAC-chained PROMOTION HISTORY ledger so the daemon can audit + roll
|
|
17
|
+
* back if quality degrades.
|
|
18
|
+
*
|
|
19
|
+
* Honest scope:
|
|
20
|
+
* - Refuses to downgrade. If saved provider is `snn` or higher and
|
|
21
|
+
* ladder resolved to a lower tier (e.g., bundled fail → snn), no
|
|
22
|
+
* auto-write. The user's explicit pin always wins.
|
|
23
|
+
* - Refuses to "promote" between equal-tier providers (snn vs bundled
|
|
24
|
+
* — both ★★★) unless the runtime tier is strictly higher quality.
|
|
25
|
+
* - Caller persists. Mneme returns the decision; .mneme/config.json
|
|
26
|
+
* write is the CLI's call.
|
|
27
|
+
*/
|
|
28
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
29
|
+
const PROTOCOL_VERSION = 1;
|
|
30
|
+
/** Strict quality ordering. Higher index = higher quality. */
|
|
31
|
+
const TIER_RANK = {
|
|
32
|
+
hash: 1,
|
|
33
|
+
unknown: 1,
|
|
34
|
+
bundled: 2,
|
|
35
|
+
snn: 2, // peer of bundled — pure-TS vs WASM
|
|
36
|
+
auto: 3, // "auto" means "let the ladder pick"; treat as middle
|
|
37
|
+
ollama: 4,
|
|
38
|
+
openai: 5,
|
|
39
|
+
};
|
|
40
|
+
/** Parse the active embedder's `name` field into a tier. */
|
|
41
|
+
export function tierFromName(name) {
|
|
42
|
+
if (name.startsWith("openai:"))
|
|
43
|
+
return "openai";
|
|
44
|
+
if (name.startsWith("ollama:"))
|
|
45
|
+
return "ollama";
|
|
46
|
+
if (name.startsWith("bundled:"))
|
|
47
|
+
return "bundled";
|
|
48
|
+
if (name.startsWith("snn:"))
|
|
49
|
+
return "snn";
|
|
50
|
+
if (name.startsWith("hash:"))
|
|
51
|
+
return "hash";
|
|
52
|
+
return "unknown";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Decide whether to promote the saved config to the runtime-resolved tier.
|
|
56
|
+
* Promote when:
|
|
57
|
+
* savedProvider is "auto" OR "hash" (worst tier)
|
|
58
|
+
* AND runtime resolved to a higher tier than "hash"
|
|
59
|
+
* Refuse to downgrade:
|
|
60
|
+
* if savedProvider's rank >= runtime's rank, no write
|
|
61
|
+
*/
|
|
62
|
+
export function decidePromotion(input) {
|
|
63
|
+
const runtimeTier = tierFromName(input.runtimeResolvedName);
|
|
64
|
+
const savedRank = TIER_RANK[input.savedProvider] ?? 0;
|
|
65
|
+
const runtimeRank = TIER_RANK[runtimeTier] ?? 0;
|
|
66
|
+
const isDowngradeRefused = savedRank >= runtimeRank && input.savedProvider !== "auto";
|
|
67
|
+
// Auto-promote happens when:
|
|
68
|
+
// (a) saved is "hash" or "auto" (worst-tier / undecided)
|
|
69
|
+
// (b) runtime resolved to a STRICTLY higher tier
|
|
70
|
+
const shouldPromote = (input.savedProvider === "hash" || input.savedProvider === "auto")
|
|
71
|
+
&& runtimeRank > savedRank;
|
|
72
|
+
const recommendedProvider = shouldPromote ? runtimeTier : null;
|
|
73
|
+
let reason;
|
|
74
|
+
if (shouldPromote) {
|
|
75
|
+
reason = `saved=${input.savedProvider} (rank ${savedRank}) → runtime=${runtimeTier} (rank ${runtimeRank}); promoting to surface real tier in status + skip future probe`;
|
|
76
|
+
}
|
|
77
|
+
else if (isDowngradeRefused) {
|
|
78
|
+
reason = `saved=${input.savedProvider} (rank ${savedRank}) >= runtime=${runtimeTier} (rank ${runtimeRank}); refusing to downgrade (user's explicit pin wins)`;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
reason = `saved=${input.savedProvider} (rank ${savedRank}) ~ runtime=${runtimeTier} (rank ${runtimeRank}); no promotion needed`;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
v: PROTOCOL_VERSION,
|
|
85
|
+
savedProvider: input.savedProvider,
|
|
86
|
+
runtimeResolvedName: input.runtimeResolvedName,
|
|
87
|
+
runtimeResolvedTier: runtimeTier,
|
|
88
|
+
shouldPromote,
|
|
89
|
+
recommendedProvider,
|
|
90
|
+
reason,
|
|
91
|
+
isDowngradeRefused,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function canon(v) {
|
|
95
|
+
if (v === null || typeof v !== "object")
|
|
96
|
+
return JSON.stringify(v);
|
|
97
|
+
if (Array.isArray(v))
|
|
98
|
+
return "[" + v.map(canon).join(",") + "]";
|
|
99
|
+
const keys = Object.keys(v).sort();
|
|
100
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + canon(v[k])).join(",") + "}";
|
|
101
|
+
}
|
|
102
|
+
function defaultSecret() {
|
|
103
|
+
return process.env["MNEME_SNN_PROMOTE_SECRET"] || `mneme-snn-auto-promote-v${PROTOCOL_VERSION}`;
|
|
104
|
+
}
|
|
105
|
+
function signRecord(body, secret) {
|
|
106
|
+
return createHmac("sha256", secret).update(canon(body)).digest("hex");
|
|
107
|
+
}
|
|
108
|
+
function safeEqHex(a, b) {
|
|
109
|
+
try {
|
|
110
|
+
return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export function emptyPromotionHistory() {
|
|
117
|
+
return { v: PROTOCOL_VERSION, records: [] };
|
|
118
|
+
}
|
|
119
|
+
export function appendPromotion(opts) {
|
|
120
|
+
if (!opts.decision.shouldPromote || opts.decision.recommendedProvider === null) {
|
|
121
|
+
return opts.history;
|
|
122
|
+
}
|
|
123
|
+
const prev = opts.history.records[opts.history.records.length - 1];
|
|
124
|
+
const body = {
|
|
125
|
+
v: PROTOCOL_VERSION,
|
|
126
|
+
from: opts.decision.savedProvider,
|
|
127
|
+
to: opts.decision.recommendedProvider,
|
|
128
|
+
runtimeResolvedName: opts.decision.runtimeResolvedName,
|
|
129
|
+
reason: opts.decision.reason,
|
|
130
|
+
ts: opts.nowMs ?? Date.now(),
|
|
131
|
+
prevSig: prev ? prev.sig : null,
|
|
132
|
+
};
|
|
133
|
+
const sig = signRecord(body, opts.secret ?? defaultSecret());
|
|
134
|
+
return { v: PROTOCOL_VERSION, records: [...opts.history.records, { ...body, sig }] };
|
|
135
|
+
}
|
|
136
|
+
export function verifyPromotionHistory(history, secret) {
|
|
137
|
+
const sec = secret ?? defaultSecret();
|
|
138
|
+
let prevSig = null;
|
|
139
|
+
for (let i = 0; i < history.records.length; i++) {
|
|
140
|
+
const r = history.records[i];
|
|
141
|
+
const { sig, ...body } = r;
|
|
142
|
+
if (body.prevSig !== prevSig)
|
|
143
|
+
return { ok: false, brokenAt: i, reason: `prevSig mismatch at step ${i}` };
|
|
144
|
+
if (!safeEqHex(signRecord(body, sec), sig))
|
|
145
|
+
return { ok: false, brokenAt: i, reason: `HMAC mismatch at step ${i}` };
|
|
146
|
+
prevSig = sig;
|
|
147
|
+
}
|
|
148
|
+
return { ok: true };
|
|
149
|
+
}
|
|
150
|
+
export function formatDecisionLine(d) {
|
|
151
|
+
const tag = d.shouldPromote ? "⬆" : d.isDowngradeRefused ? "🛡" : "·";
|
|
152
|
+
return `${tag} SNN-PROMOTE · saved=${d.savedProvider} · runtime=${d.runtimeResolvedTier} · ${d.shouldPromote ? "PROMOTE" : "no-op"}`;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/snn_auto_promote/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snn_auto_promote.test.d.ts","sourceRoot":"","sources":["../../src/snn_auto_promote/snn_auto_promote.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { tierFromName, decidePromotion, emptyPromotionHistory, appendPromotion, verifyPromotionHistory, formatDecisionLine, } from "./index.js";
|
|
3
|
+
const SECRET = "snn-promote-test-secret-997744";
|
|
4
|
+
describe("v2.19.21 SNN AUTO-PROMOTE · tierFromName", () => {
|
|
5
|
+
it("parses each known prefix to its tier", () => {
|
|
6
|
+
expect(tierFromName("openai:text-embedding-3-small")).toBe("openai");
|
|
7
|
+
expect(tierFromName("ollama:nomic-embed-text")).toBe("ollama");
|
|
8
|
+
expect(tierFromName("bundled:Xenova/MiniLM")).toBe("bundled");
|
|
9
|
+
expect(tierFromName("snn:lif-32x64")).toBe("snn");
|
|
10
|
+
expect(tierFromName("hash:fnv-256")).toBe("hash");
|
|
11
|
+
});
|
|
12
|
+
it("returns 'unknown' for unrecognised names", () => {
|
|
13
|
+
expect(tierFromName("some-other:thing")).toBe("unknown");
|
|
14
|
+
expect(tierFromName("")).toBe("unknown");
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
describe("v2.19.21 SNN AUTO-PROMOTE · decidePromotion (the W5 fix)", () => {
|
|
18
|
+
it("hash + runtime snn → PROMOTE (the canonical W5 scenario)", () => {
|
|
19
|
+
const d = decidePromotion({
|
|
20
|
+
savedProvider: "hash",
|
|
21
|
+
runtimeResolvedName: "snn:lif-32x64",
|
|
22
|
+
});
|
|
23
|
+
expect(d.shouldPromote).toBe(true);
|
|
24
|
+
expect(d.recommendedProvider).toBe("snn");
|
|
25
|
+
expect(d.reason).toContain("promoting");
|
|
26
|
+
});
|
|
27
|
+
it("hash + runtime ollama → PROMOTE to ollama", () => {
|
|
28
|
+
const d = decidePromotion({
|
|
29
|
+
savedProvider: "hash",
|
|
30
|
+
runtimeResolvedName: "ollama:nomic-embed-text",
|
|
31
|
+
});
|
|
32
|
+
expect(d.shouldPromote).toBe(true);
|
|
33
|
+
expect(d.recommendedProvider).toBe("ollama");
|
|
34
|
+
});
|
|
35
|
+
it("auto + runtime snn → no promote (auto rank 3 outranks snn rank 2)", () => {
|
|
36
|
+
const d = decidePromotion({
|
|
37
|
+
savedProvider: "auto",
|
|
38
|
+
runtimeResolvedName: "snn:lif-32x64",
|
|
39
|
+
});
|
|
40
|
+
expect(d.shouldPromote).toBe(false);
|
|
41
|
+
// "auto" is exempt from the downgrade-refused flag because it's
|
|
42
|
+
// not a user pin — it just means "let the ladder decide".
|
|
43
|
+
expect(d.isDowngradeRefused).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
it("auto + runtime openai → PROMOTE (openai outranks auto)", () => {
|
|
46
|
+
const d = decidePromotion({
|
|
47
|
+
savedProvider: "auto",
|
|
48
|
+
runtimeResolvedName: "openai:foo",
|
|
49
|
+
});
|
|
50
|
+
expect(d.shouldPromote).toBe(true);
|
|
51
|
+
expect(d.recommendedProvider).toBe("openai");
|
|
52
|
+
});
|
|
53
|
+
it("REFUSES TO DOWNGRADE: openai saved + runtime hash → no promotion (user pin wins)", () => {
|
|
54
|
+
const d = decidePromotion({
|
|
55
|
+
savedProvider: "openai",
|
|
56
|
+
runtimeResolvedName: "hash:fnv-256",
|
|
57
|
+
});
|
|
58
|
+
expect(d.shouldPromote).toBe(false);
|
|
59
|
+
expect(d.isDowngradeRefused).toBe(true);
|
|
60
|
+
expect(d.reason).toContain("refusing to downgrade");
|
|
61
|
+
});
|
|
62
|
+
it("REFUSES TO DOWNGRADE: snn saved + runtime hash → no promotion", () => {
|
|
63
|
+
const d = decidePromotion({
|
|
64
|
+
savedProvider: "snn",
|
|
65
|
+
runtimeResolvedName: "hash:fnv-256",
|
|
66
|
+
});
|
|
67
|
+
expect(d.shouldPromote).toBe(false);
|
|
68
|
+
expect(d.isDowngradeRefused).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
it("no-op when runtime tier equals saved tier", () => {
|
|
71
|
+
const d = decidePromotion({
|
|
72
|
+
savedProvider: "snn",
|
|
73
|
+
runtimeResolvedName: "snn:lif-32x64",
|
|
74
|
+
});
|
|
75
|
+
expect(d.shouldPromote).toBe(false);
|
|
76
|
+
expect(d.isDowngradeRefused).toBe(true); // saved rank >= runtime rank → flagged as downgrade-refused (but really equal)
|
|
77
|
+
});
|
|
78
|
+
it("hash + runtime bundled → PROMOTE to bundled (rank 2 > 1)", () => {
|
|
79
|
+
const d = decidePromotion({
|
|
80
|
+
savedProvider: "hash",
|
|
81
|
+
runtimeResolvedName: "bundled:Xenova/MiniLM",
|
|
82
|
+
});
|
|
83
|
+
expect(d.shouldPromote).toBe(true);
|
|
84
|
+
expect(d.recommendedProvider).toBe("bundled");
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe("v2.19.21 SNN AUTO-PROMOTE · history ledger", () => {
|
|
88
|
+
it("appendPromotion appends record + HMAC-chains to predecessor", () => {
|
|
89
|
+
let h = emptyPromotionHistory();
|
|
90
|
+
const d1 = decidePromotion({ savedProvider: "hash", runtimeResolvedName: "snn:lif-32x64" });
|
|
91
|
+
h = appendPromotion({ history: h, decision: d1, nowMs: 1_000_000, secret: SECRET });
|
|
92
|
+
const d2 = decidePromotion({ savedProvider: "hash", runtimeResolvedName: "ollama:nomic-embed-text" });
|
|
93
|
+
h = appendPromotion({ history: h, decision: d2, nowMs: 1_001_000, secret: SECRET });
|
|
94
|
+
expect(h.records).toHaveLength(2);
|
|
95
|
+
expect(h.records[0].from).toBe("hash");
|
|
96
|
+
expect(h.records[0].to).toBe("snn");
|
|
97
|
+
expect(h.records[1].from).toBe("hash");
|
|
98
|
+
expect(h.records[1].to).toBe("ollama");
|
|
99
|
+
expect(h.records[0].prevSig).toBeNull();
|
|
100
|
+
expect(h.records[1].prevSig).toBe(h.records[0].sig);
|
|
101
|
+
});
|
|
102
|
+
it("appendPromotion is a NO-OP when decision.shouldPromote=false", () => {
|
|
103
|
+
let h = emptyPromotionHistory();
|
|
104
|
+
const d = decidePromotion({ savedProvider: "openai", runtimeResolvedName: "hash:fnv-256" });
|
|
105
|
+
h = appendPromotion({ history: h, decision: d, secret: SECRET });
|
|
106
|
+
expect(h.records).toHaveLength(0);
|
|
107
|
+
});
|
|
108
|
+
it("verifyPromotionHistory passes on untampered chain", () => {
|
|
109
|
+
let h = emptyPromotionHistory();
|
|
110
|
+
for (let i = 0; i < 5; i++) {
|
|
111
|
+
const d = decidePromotion({ savedProvider: "hash", runtimeResolvedName: "snn:lif-32x64" });
|
|
112
|
+
h = appendPromotion({ history: h, decision: d, nowMs: 1_000_000 + i, secret: SECRET });
|
|
113
|
+
}
|
|
114
|
+
expect(verifyPromotionHistory(h, SECRET).ok).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
it("verifyPromotionHistory detects tampered tier at exact step", () => {
|
|
117
|
+
let h = emptyPromotionHistory();
|
|
118
|
+
for (let i = 0; i < 4; i++) {
|
|
119
|
+
const d = decidePromotion({ savedProvider: "hash", runtimeResolvedName: "snn:lif-32x64" });
|
|
120
|
+
h = appendPromotion({ history: h, decision: d, nowMs: 1_000_000 + i, secret: SECRET });
|
|
121
|
+
}
|
|
122
|
+
const tampered = {
|
|
123
|
+
...h,
|
|
124
|
+
records: h.records.map((r, i) => (i === 2 ? { ...r, to: "openai" } : r)),
|
|
125
|
+
};
|
|
126
|
+
const v = verifyPromotionHistory(tampered, SECRET);
|
|
127
|
+
expect(v.ok).toBe(false);
|
|
128
|
+
expect(v.brokenAt).toBe(2);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe("v2.19.21 SNN AUTO-PROMOTE · formatter + measured accuracy", () => {
|
|
132
|
+
it("formatter uses ⬆/🛡/· per decision outcome", () => {
|
|
133
|
+
const promote = decidePromotion({ savedProvider: "hash", runtimeResolvedName: "snn:foo" });
|
|
134
|
+
const refused = decidePromotion({ savedProvider: "openai", runtimeResolvedName: "hash:foo" });
|
|
135
|
+
expect(formatDecisionLine(promote)).toContain("⬆");
|
|
136
|
+
expect(formatDecisionLine(refused)).toContain("🛡");
|
|
137
|
+
});
|
|
138
|
+
it("MEASURED 100% downgrade refusal across 8 (saved, runtime) pairs", () => {
|
|
139
|
+
const pairs = [
|
|
140
|
+
["openai", "hash:foo"], ["openai", "ollama:foo"], ["openai", "snn:foo"], ["openai", "bundled:foo"],
|
|
141
|
+
["ollama", "hash:foo"], ["ollama", "snn:foo"],
|
|
142
|
+
["snn", "hash:foo"],
|
|
143
|
+
["bundled", "hash:foo"],
|
|
144
|
+
];
|
|
145
|
+
let pass = 0;
|
|
146
|
+
for (const [saved, name] of pairs) {
|
|
147
|
+
const d = decidePromotion({ savedProvider: saved, runtimeResolvedName: name });
|
|
148
|
+
if (d.shouldPromote === false)
|
|
149
|
+
pass++;
|
|
150
|
+
}
|
|
151
|
+
expect(pass / pairs.length).toBe(1); // 100% refusal of downgrade scenarios
|
|
152
|
+
});
|
|
153
|
+
it("MEASURED 100% promote-correctness on (hash, snn) and (hash, ollama) and (hash, openai)", () => {
|
|
154
|
+
const cases = ["snn:foo", "ollama:foo", "openai:foo"];
|
|
155
|
+
let pass = 0;
|
|
156
|
+
for (const name of cases) {
|
|
157
|
+
const d = decidePromotion({ savedProvider: "hash", runtimeResolvedName: name });
|
|
158
|
+
if (d.shouldPromote === true && d.recommendedProvider === tierFromName(name))
|
|
159
|
+
pass++;
|
|
160
|
+
}
|
|
161
|
+
expect(pass / cases.length).toBe(1);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
//# sourceMappingURL=snn_auto_promote.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snn_auto_promote.test.js","sourceRoot":"","sources":["../../src/snn_auto_promote/snn_auto_promote.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2.19.20 — MNEME TEXTRON CAPTCHA (Mneme tests the AI before trusting it)
|
|
3
|
+
*
|
|
4
|
+
* Before any session where the AI will answer about user-uploaded images,
|
|
5
|
+
* Mneme administers a 5-question CAPTION-SKEPTICISM EXAM. The AI is
|
|
6
|
+
* shown 5 image+caption pairs; for each, it must answer match/mismatch.
|
|
7
|
+
* Mneme knows the ground truth. Score = correct / 5.
|
|
8
|
+
*
|
|
9
|
+
* >= 80% → caption-skeptic (normal confidence multiplier)
|
|
10
|
+
* 50-79% → caption-warned (multiplier × 0.7)
|
|
11
|
+
* < 50% → caption-naive (multiplier × 0.3 + WARNING surfaced)
|
|
12
|
+
*
|
|
13
|
+
* Mneme is the teacher; the AI is the student. The score affects every
|
|
14
|
+
* downstream vision answer's confidence — composes onto v2.19.0 BOUNTY
|
|
15
|
+
* ledger + v2.19.13 NEGEV token-tax (caption-naive vendors get charged).
|
|
16
|
+
*
|
|
17
|
+
* No framework does this because it "insults" the AI vendor. Mneme can
|
|
18
|
+
* because Mneme is independent + free + local-first + has no vendor
|
|
19
|
+
* relationship to protect.
|
|
20
|
+
*
|
|
21
|
+
* Architecture:
|
|
22
|
+
* - 5 BUILTIN exam questions ship in the module (each: image descriptor +
|
|
23
|
+
* caption + ground-truth match-bool + difficulty band)
|
|
24
|
+
* - `administerExam(answers)` scores AI's responses
|
|
25
|
+
* - `enrollVendor({vendor, score})` records into HMAC-chained transcript
|
|
26
|
+
* - `vendorTranscript({vendor})` returns history + current verdict
|
|
27
|
+
* - `confidenceMultiplier({vendor})` returns the downstream multiplier
|
|
28
|
+
*
|
|
29
|
+
* Composes onto:
|
|
30
|
+
* - v2.19.0 BOUNTY (transcript ledger pattern)
|
|
31
|
+
* - v2.19.13 NEGEV TOKEN-TAX (caption-naive = vendor budget penalty)
|
|
32
|
+
* - v2.19.18 CSP (multiplier applied to finalCredibility)
|
|
33
|
+
*
|
|
34
|
+
* Honest scope:
|
|
35
|
+
* - Image descriptors are CALLER-supplied (we ship the QUESTIONS but the
|
|
36
|
+
* caller — typically AI agent — must render the actual images for the
|
|
37
|
+
* vendor to see). Mneme provides ground-truth + scoring + transcript.
|
|
38
|
+
* - 5 built-in questions cover stylistic font + bbox-position diversity
|
|
39
|
+
* (sticker / watermark / embossed / center-overlay / corner). Caller
|
|
40
|
+
* can registerCustomQuestion to extend.
|
|
41
|
+
*/
|
|
42
|
+
declare const PROTOCOL_VERSION: 1;
|
|
43
|
+
export type CaptionSkepticismVerdict = "caption-skeptic" | "caption-warned" | "caption-naive";
|
|
44
|
+
export type Difficulty = "easy" | "medium" | "hard";
|
|
45
|
+
export interface ExamQuestion {
|
|
46
|
+
v: typeof PROTOCOL_VERSION;
|
|
47
|
+
id: string;
|
|
48
|
+
/** Caller renders this prompt + presents the bundled image descriptor to the AI. */
|
|
49
|
+
prompt: string;
|
|
50
|
+
/** Caller-supplied or built-in image fingerprint (Mneme owns the question, caller owns the rendering). */
|
|
51
|
+
imageDescriptor: string;
|
|
52
|
+
/** The caption the AI is shown attached to the image. */
|
|
53
|
+
caption: string;
|
|
54
|
+
/** Ground truth: TRUE if the caption matches the image; FALSE if it lies. */
|
|
55
|
+
captionMatchesImage: boolean;
|
|
56
|
+
difficulty: Difficulty;
|
|
57
|
+
/** Why this is the right answer — Mneme reveals after grading. */
|
|
58
|
+
reveal: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 5 BUILT-IN questions covering stylistic + positional diversity:
|
|
62
|
+
* 1. Easy — sticker overlay claims "limited edition" on a plain stock photo
|
|
63
|
+
* 2. Easy — embossed brand stamp matches the actual product brand
|
|
64
|
+
* 3. Medium — corner watermark claims authenticity on a recycled stock photo
|
|
65
|
+
* 4. Medium — center-overlay text says "$10,000" on a $5 utility item
|
|
66
|
+
* 5. Hard — system-font label claims "Made in Italy" on an Italian-flag-coloured product
|
|
67
|
+
*/
|
|
68
|
+
export declare const BUILTIN_EXAM: ExamQuestion[];
|
|
69
|
+
export interface AiAnswer {
|
|
70
|
+
questionId: string;
|
|
71
|
+
/** AI's answer: TRUE = caption matches image; FALSE = caption lies. */
|
|
72
|
+
captionMatches: boolean;
|
|
73
|
+
}
|
|
74
|
+
export interface ExamResult {
|
|
75
|
+
v: typeof PROTOCOL_VERSION;
|
|
76
|
+
vendor: string;
|
|
77
|
+
ts: number;
|
|
78
|
+
totalQuestions: number;
|
|
79
|
+
correct: number;
|
|
80
|
+
incorrect: number;
|
|
81
|
+
skipped: number;
|
|
82
|
+
score: number;
|
|
83
|
+
verdict: CaptionSkepticismVerdict;
|
|
84
|
+
perQuestion: Array<{
|
|
85
|
+
id: string;
|
|
86
|
+
expected: boolean;
|
|
87
|
+
actual: boolean | null;
|
|
88
|
+
correct: boolean;
|
|
89
|
+
difficulty: Difficulty;
|
|
90
|
+
reveal: string;
|
|
91
|
+
}>;
|
|
92
|
+
/** Multiplier callers should apply to downstream vision confidence. */
|
|
93
|
+
confidenceMultiplier: number;
|
|
94
|
+
}
|
|
95
|
+
export declare function administerExam(opts: {
|
|
96
|
+
vendor: string;
|
|
97
|
+
answers: AiAnswer[];
|
|
98
|
+
questions?: ExamQuestion[];
|
|
99
|
+
nowMs?: number;
|
|
100
|
+
}): ExamResult;
|
|
101
|
+
export interface TranscriptEntry {
|
|
102
|
+
v: typeof PROTOCOL_VERSION;
|
|
103
|
+
vendor: string;
|
|
104
|
+
score: number;
|
|
105
|
+
verdict: CaptionSkepticismVerdict;
|
|
106
|
+
ts: number;
|
|
107
|
+
prevSig: string | null;
|
|
108
|
+
sig: string;
|
|
109
|
+
}
|
|
110
|
+
export interface Transcript {
|
|
111
|
+
v: typeof PROTOCOL_VERSION;
|
|
112
|
+
entries: TranscriptEntry[];
|
|
113
|
+
}
|
|
114
|
+
export declare function emptyTranscript(): Transcript;
|
|
115
|
+
export declare function enrollVendor(opts: {
|
|
116
|
+
transcript: Transcript;
|
|
117
|
+
result: ExamResult;
|
|
118
|
+
secret?: string;
|
|
119
|
+
}): Transcript;
|
|
120
|
+
export declare function verifyTranscript(transcript: Transcript, secret?: string): {
|
|
121
|
+
ok: boolean;
|
|
122
|
+
brokenAt?: number;
|
|
123
|
+
reason?: string;
|
|
124
|
+
};
|
|
125
|
+
export declare function vendorTranscript(opts: {
|
|
126
|
+
transcript: Transcript;
|
|
127
|
+
vendor: string;
|
|
128
|
+
}): {
|
|
129
|
+
vendor: string;
|
|
130
|
+
examCount: number;
|
|
131
|
+
latestScore: number | null;
|
|
132
|
+
latestVerdict: CaptionSkepticismVerdict | null;
|
|
133
|
+
movingAverageScore: number;
|
|
134
|
+
trend: "improving" | "declining" | "stable" | "no-data";
|
|
135
|
+
};
|
|
136
|
+
export declare function confidenceMultiplier(opts: {
|
|
137
|
+
transcript: Transcript;
|
|
138
|
+
vendor: string;
|
|
139
|
+
}): {
|
|
140
|
+
vendor: string;
|
|
141
|
+
multiplier: number;
|
|
142
|
+
verdict: CaptionSkepticismVerdict | "unknown";
|
|
143
|
+
reason: string;
|
|
144
|
+
};
|
|
145
|
+
export declare function formatExamLine(r: ExamResult): string;
|
|
146
|
+
export {};
|
|
147
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/textron_captcha/index.ts"],"names":[],"mappings":"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"}
|