@mnemom/mnemom 0.8.0 → 0.9.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.
- package/dist/commands/card.d.ts +16 -5
- package/dist/commands/card.js +476 -89
- package/dist/commands/integrity.js +7 -6
- package/dist/commands/protection.d.ts +12 -4
- package/dist/commands/protection.js +288 -54
- package/dist/commands/status.js +6 -4
- package/dist/index.js +12 -8
- package/dist/lib/api.d.ts +98 -17
- package/dist/lib/api.js +177 -21
- package/dist/lib/auth.d.ts +16 -0
- package/dist/lib/auth.js +53 -3
- package/package.json +1 -1
|
@@ -6,15 +6,16 @@ export async function integrityCommand(agentName) {
|
|
|
6
6
|
console.log("\nFetching integrity score...\n");
|
|
7
7
|
try {
|
|
8
8
|
const integrity = await getIntegrity(agentId);
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// Field names match the docs.mnemom.ai canonical IntegrityScore schema:
|
|
10
|
+
// integrity_score (in [0,1]), total_traces, verified_traces, violation_count.
|
|
11
|
+
const scorePercent = (integrity.integrity_score * 100).toFixed(1);
|
|
12
|
+
const scoreBar = generateScoreBar(integrity.integrity_score);
|
|
11
13
|
console.log(fmt.header("Integrity Score"));
|
|
12
14
|
console.log(` ${fmt.label("Score: ", `${scorePercent}% ${scoreBar}`)}`);
|
|
13
15
|
console.log(` ${fmt.label("Total: ", `${integrity.total_traces} traces`)}`);
|
|
14
|
-
console.log(` ${fmt.label("Verified: ", `${integrity.
|
|
15
|
-
console.log(` ${fmt.label("Violations:", ` ${integrity.
|
|
16
|
-
|
|
17
|
-
if (integrity.violations > 0) {
|
|
16
|
+
console.log(` ${fmt.label("Verified: ", `${integrity.verified_traces}`)}`);
|
|
17
|
+
console.log(` ${fmt.label("Violations:", ` ${integrity.violation_count}`)}`);
|
|
18
|
+
if (integrity.violation_count > 0) {
|
|
18
19
|
console.log("\n" + fmt.warn("You have integrity violations. Run `mnemom logs` to investigate.") + "\n");
|
|
19
20
|
}
|
|
20
21
|
else if (integrity.total_traces === 0) {
|
|
@@ -4,11 +4,19 @@ export interface ValidationCheck {
|
|
|
4
4
|
message: string;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* Validate a protection card
|
|
8
|
-
*
|
|
7
|
+
* Validate a protection card against ADR-037 canonical form.
|
|
8
|
+
*
|
|
9
|
+
* Required: card_version, agent_id, mode (off|observe|nudge|enforce).
|
|
10
|
+
* Optional: thresholds (warn ≤ quarantine ≤ block, all in [0,1]),
|
|
11
|
+
* screen_surfaces (object of bools with the four named keys),
|
|
12
|
+
* trusted_sources (object of typed buckets, per-bucket deny-lists).
|
|
9
13
|
*/
|
|
10
14
|
export declare function validateProtectionCard(card: Record<string, unknown>): ValidationCheck[];
|
|
11
15
|
export declare function protectionShowCommand(agentName?: string): Promise<void>;
|
|
12
|
-
export declare function protectionPublishCommand(file: string, agentName?: string
|
|
16
|
+
export declare function protectionPublishCommand(file: string, agentName?: string, options?: {
|
|
17
|
+
idempotencyKey?: string;
|
|
18
|
+
}): Promise<void>;
|
|
13
19
|
export declare function protectionValidateCommand(file: string): Promise<void>;
|
|
14
|
-
export declare function protectionEditCommand(agentName?: string
|
|
20
|
+
export declare function protectionEditCommand(agentName?: string, options?: {
|
|
21
|
+
idempotencyKey?: string;
|
|
22
|
+
}): Promise<void>;
|
|
@@ -3,76 +3,283 @@ import * as path from "node:path";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import yaml from "js-yaml";
|
|
6
|
-
import { getProtectionCard, putProtectionCard, resolveAgentId, } from "../lib/api.js";
|
|
6
|
+
import { PROTECTION_CARD_MAX_BYTES, getProtectionCard, putProtectionCard, resolveAgentId, } from "../lib/api.js";
|
|
7
7
|
import { requireAuth } from "../lib/auth.js";
|
|
8
8
|
import { fmt } from "../lib/format.js";
|
|
9
9
|
import { askYesNo, isInteractive } from "../lib/prompt.js";
|
|
10
|
-
const
|
|
10
|
+
const PROTECTION_MODES = ["off", "observe", "nudge", "enforce"];
|
|
11
|
+
const SURFACE_KEYS = ["incoming", "outgoing", "tool_calls", "tool_responses"];
|
|
12
|
+
// Per ADR-037 Decision 4: deny public LLM endpoints + public DNS providers,
|
|
13
|
+
// and the any-host CIDRs, at write time.
|
|
14
|
+
const DENY_DOMAINS = new Set([
|
|
15
|
+
"api.openai.com",
|
|
16
|
+
"api.anthropic.com",
|
|
17
|
+
"generativelanguage.googleapis.com",
|
|
18
|
+
"api.cohere.ai",
|
|
19
|
+
"api.mistral.ai",
|
|
20
|
+
"api.groq.com",
|
|
21
|
+
"cloud.google.com",
|
|
22
|
+
"dns.google",
|
|
23
|
+
"cloudflare-dns.com",
|
|
24
|
+
"one.one.one.one",
|
|
25
|
+
"dns.quad9.net",
|
|
26
|
+
]);
|
|
27
|
+
const DENY_IP_PREFIXES = [
|
|
28
|
+
"0.0.0.0/0",
|
|
29
|
+
"::/0",
|
|
30
|
+
"8.8.8.0/24",
|
|
31
|
+
"8.8.4.0/24",
|
|
32
|
+
"1.1.1.0/24",
|
|
33
|
+
"1.0.0.0/24",
|
|
34
|
+
"9.9.9.0/24",
|
|
35
|
+
];
|
|
36
|
+
const DOMAIN_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+(:\d{1,5})?$/i;
|
|
37
|
+
const AGENT_ID_RE = /^mnm-[a-z0-9-]{4,}$/i;
|
|
38
|
+
const CIDR_RE = /^([0-9]{1,3}(\.[0-9]{1,3}){3})\/([0-9]|[12][0-9]|3[0-2])$|^([0-9a-f:]+)\/(\d{1,3})$/i;
|
|
39
|
+
function isObject(v) {
|
|
40
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
41
|
+
}
|
|
42
|
+
function validateDomain(d) {
|
|
43
|
+
const lower = d.toLowerCase();
|
|
44
|
+
if (DENY_DOMAINS.has(lower.split(":")[0])) {
|
|
45
|
+
return "domain is on the static deny-list (public LLM/DNS endpoint)";
|
|
46
|
+
}
|
|
47
|
+
if (!DOMAIN_RE.test(lower))
|
|
48
|
+
return "not a valid DNS name (or host:port)";
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
function validateAgentId(a) {
|
|
52
|
+
if (!AGENT_ID_RE.test(a))
|
|
53
|
+
return "must match Mnemom agent ID format (mnm-*)";
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function validateCidr(c) {
|
|
57
|
+
if (DENY_IP_PREFIXES.includes(c)) {
|
|
58
|
+
return "CIDR is on the static deny-list (public DNS / 0.0.0.0/0 / ::/0)";
|
|
59
|
+
}
|
|
60
|
+
if (!CIDR_RE.test(c))
|
|
61
|
+
return "not a valid CIDR notation";
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function validateTrustedBucket(name, bucket, checks, perEntry) {
|
|
65
|
+
if (bucket === undefined)
|
|
66
|
+
return;
|
|
67
|
+
if (!Array.isArray(bucket)) {
|
|
68
|
+
checks.push({
|
|
69
|
+
name: `trusted_sources.${name}`,
|
|
70
|
+
passed: false,
|
|
71
|
+
message: `Must be an array of strings (got ${typeof bucket}). Per ADR-037 trusted_sources is an object of typed buckets — not an array of objects.`,
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let bad = 0;
|
|
76
|
+
bucket.forEach((entry, i) => {
|
|
77
|
+
if (typeof entry !== "string") {
|
|
78
|
+
checks.push({
|
|
79
|
+
name: `trusted_sources.${name}[${i}]`,
|
|
80
|
+
passed: false,
|
|
81
|
+
message: "Must be a string",
|
|
82
|
+
});
|
|
83
|
+
bad++;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const err = perEntry(entry);
|
|
87
|
+
if (err) {
|
|
88
|
+
checks.push({
|
|
89
|
+
name: `trusted_sources.${name}[${i}]`,
|
|
90
|
+
passed: false,
|
|
91
|
+
message: `${entry}: ${err}`,
|
|
92
|
+
});
|
|
93
|
+
bad++;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
if (bad === 0) {
|
|
97
|
+
checks.push({
|
|
98
|
+
name: `trusted_sources.${name}`,
|
|
99
|
+
passed: true,
|
|
100
|
+
message: `${bucket.length} entr${bucket.length === 1 ? "y" : "ies"}`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
11
104
|
/**
|
|
12
|
-
* Validate a protection card
|
|
13
|
-
*
|
|
105
|
+
* Validate a protection card against ADR-037 canonical form.
|
|
106
|
+
*
|
|
107
|
+
* Required: card_version, agent_id, mode (off|observe|nudge|enforce).
|
|
108
|
+
* Optional: thresholds (warn ≤ quarantine ≤ block, all in [0,1]),
|
|
109
|
+
* screen_surfaces (object of bools with the four named keys),
|
|
110
|
+
* trusted_sources (object of typed buckets, per-bucket deny-lists).
|
|
14
111
|
*/
|
|
15
112
|
export function validateProtectionCard(card) {
|
|
16
113
|
const checks = [];
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
114
|
+
// ── card_version (required) ──
|
|
115
|
+
if (typeof card.card_version !== "string" || card.card_version.length === 0) {
|
|
116
|
+
checks.push({
|
|
117
|
+
name: "card_version",
|
|
118
|
+
passed: false,
|
|
119
|
+
message: 'Required (string, e.g. "protection/2026-04-26"). See ADR-037.',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
checks.push({ name: "card_version", passed: true, message: card.card_version });
|
|
124
|
+
}
|
|
125
|
+
// ── agent_id (required) ──
|
|
126
|
+
if (typeof card.agent_id !== "string" || card.agent_id.length === 0) {
|
|
127
|
+
checks.push({
|
|
128
|
+
name: "agent_id",
|
|
129
|
+
passed: false,
|
|
130
|
+
message: "Required (string).",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
checks.push({ name: "agent_id", passed: true, message: card.agent_id });
|
|
21
135
|
}
|
|
22
|
-
|
|
23
|
-
|
|
136
|
+
// ── mode (required, ADR-037 canonical enum) ──
|
|
137
|
+
const mode = card.mode;
|
|
138
|
+
if (typeof mode !== "string") {
|
|
139
|
+
checks.push({
|
|
140
|
+
name: "mode",
|
|
141
|
+
passed: false,
|
|
142
|
+
message: `Required. Must be one of: ${PROTECTION_MODES.join(" | ")}. Per ADR-037 the legacy "block"/"warn" values are no longer accepted.`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
else if (!PROTECTION_MODES.includes(mode)) {
|
|
146
|
+
checks.push({
|
|
147
|
+
name: "mode",
|
|
148
|
+
passed: false,
|
|
149
|
+
message: `Invalid: "${mode}". Must be one of: ${PROTECTION_MODES.join(" | ")}. (Per ADR-037 the legacy "block"/"warn" values are no longer accepted; use "enforce"/"nudge".)`,
|
|
150
|
+
});
|
|
24
151
|
}
|
|
25
152
|
else {
|
|
26
|
-
checks.push({ name: "mode", passed:
|
|
153
|
+
checks.push({ name: "mode", passed: true, message: mode });
|
|
27
154
|
}
|
|
28
|
-
// thresholds
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
checks.push({ name: "thresholds", passed: false, message: "Must be an object" });
|
|
155
|
+
// ── thresholds (optional) ──
|
|
156
|
+
if (card.thresholds !== undefined) {
|
|
157
|
+
if (!isObject(card.thresholds)) {
|
|
158
|
+
checks.push({ name: "thresholds", passed: false, message: "Must be an object if present" });
|
|
33
159
|
}
|
|
34
160
|
else {
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
161
|
+
const t = card.thresholds;
|
|
162
|
+
const nums = {};
|
|
163
|
+
let bad = false;
|
|
164
|
+
for (const key of ["warn", "quarantine", "block"]) {
|
|
165
|
+
const v = t[key];
|
|
166
|
+
if (v === undefined) {
|
|
167
|
+
checks.push({
|
|
168
|
+
name: `thresholds.${key}`,
|
|
169
|
+
passed: false,
|
|
170
|
+
message: "Required when thresholds is present",
|
|
171
|
+
});
|
|
172
|
+
bad = true;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (typeof v !== "number" || v < 0 || v > 1 || Number.isNaN(v)) {
|
|
176
|
+
checks.push({
|
|
177
|
+
name: `thresholds.${key}`,
|
|
178
|
+
passed: false,
|
|
179
|
+
message: "Must be a number in [0, 1]",
|
|
180
|
+
});
|
|
181
|
+
bad = true;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
nums[key] = v;
|
|
40
185
|
}
|
|
41
|
-
|
|
42
|
-
checks.push({
|
|
186
|
+
if (nums.warn !== undefined && nums.quarantine !== undefined && nums.warn > nums.quarantine) {
|
|
187
|
+
checks.push({
|
|
188
|
+
name: "thresholds.warn",
|
|
189
|
+
passed: false,
|
|
190
|
+
message: `Must be ≤ thresholds.quarantine (warn=${nums.warn} > quarantine=${nums.quarantine})`,
|
|
191
|
+
});
|
|
192
|
+
bad = true;
|
|
43
193
|
}
|
|
44
|
-
|
|
45
|
-
checks.push({
|
|
194
|
+
if (nums.quarantine !== undefined && nums.block !== undefined && nums.quarantine > nums.block) {
|
|
195
|
+
checks.push({
|
|
196
|
+
name: "thresholds.quarantine",
|
|
197
|
+
passed: false,
|
|
198
|
+
message: `Must be ≤ thresholds.block (quarantine=${nums.quarantine} > block=${nums.block})`,
|
|
199
|
+
});
|
|
200
|
+
bad = true;
|
|
46
201
|
}
|
|
47
|
-
|
|
48
|
-
checks.push({
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
202
|
+
if (!bad) {
|
|
203
|
+
checks.push({
|
|
204
|
+
name: "thresholds",
|
|
205
|
+
passed: true,
|
|
206
|
+
message: `warn=${nums.warn}, quarantine=${nums.quarantine}, block=${nums.block}`,
|
|
207
|
+
});
|
|
52
208
|
}
|
|
53
209
|
}
|
|
54
210
|
}
|
|
55
|
-
// screen_surfaces
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
211
|
+
// ── screen_surfaces (optional, object of bools) ──
|
|
212
|
+
if (card.screen_surfaces !== undefined) {
|
|
213
|
+
if (Array.isArray(card.screen_surfaces)) {
|
|
214
|
+
checks.push({
|
|
215
|
+
name: "screen_surfaces",
|
|
216
|
+
passed: false,
|
|
217
|
+
message: 'Must be an object of booleans, not an array. Per ADR-037 use { incoming: true, outgoing: true, tool_calls: true, tool_responses: true }.',
|
|
218
|
+
});
|
|
60
219
|
}
|
|
61
|
-
else if (
|
|
62
|
-
checks.push({ name: "screen_surfaces", passed: false, message: "
|
|
220
|
+
else if (!isObject(card.screen_surfaces)) {
|
|
221
|
+
checks.push({ name: "screen_surfaces", passed: false, message: "Must be an object if present" });
|
|
63
222
|
}
|
|
64
223
|
else {
|
|
65
|
-
|
|
224
|
+
const s = card.screen_surfaces;
|
|
225
|
+
let bad = false;
|
|
226
|
+
for (const key of SURFACE_KEYS) {
|
|
227
|
+
const v = s[key];
|
|
228
|
+
if (v !== undefined && typeof v !== "boolean") {
|
|
229
|
+
checks.push({
|
|
230
|
+
name: `screen_surfaces.${key}`,
|
|
231
|
+
passed: false,
|
|
232
|
+
message: "Must be a boolean",
|
|
233
|
+
});
|
|
234
|
+
bad = true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
for (const key of Object.keys(s)) {
|
|
238
|
+
if (!SURFACE_KEYS.includes(key)) {
|
|
239
|
+
checks.push({
|
|
240
|
+
name: `screen_surfaces.${key}`,
|
|
241
|
+
passed: false,
|
|
242
|
+
message: `Unknown surface (allowed: ${SURFACE_KEYS.join(", ")})`,
|
|
243
|
+
});
|
|
244
|
+
bad = true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (!bad) {
|
|
248
|
+
const enabled = SURFACE_KEYS.filter(k => s[k] === true).length;
|
|
249
|
+
checks.push({
|
|
250
|
+
name: "screen_surfaces",
|
|
251
|
+
passed: true,
|
|
252
|
+
message: `${enabled}/${SURFACE_KEYS.length} surfaces enabled`,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
66
255
|
}
|
|
67
256
|
}
|
|
68
|
-
// trusted_sources
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
257
|
+
// ── trusted_sources (optional, typed buckets) ──
|
|
258
|
+
if (card.trusted_sources !== undefined) {
|
|
259
|
+
if (Array.isArray(card.trusted_sources)) {
|
|
260
|
+
checks.push({
|
|
261
|
+
name: "trusted_sources",
|
|
262
|
+
passed: false,
|
|
263
|
+
message: 'Must be an object of typed buckets, not an array. Per ADR-037 use { domains: [...], agent_ids: [...], ip_ranges: [...] } — the legacy [{pattern, ...}] shape is no longer accepted.',
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
else if (!isObject(card.trusted_sources)) {
|
|
267
|
+
checks.push({ name: "trusted_sources", passed: false, message: "Must be an object if present" });
|
|
73
268
|
}
|
|
74
269
|
else {
|
|
75
|
-
|
|
270
|
+
const ts = card.trusted_sources;
|
|
271
|
+
validateTrustedBucket("domains", ts.domains, checks, validateDomain);
|
|
272
|
+
validateTrustedBucket("agent_ids", ts.agent_ids, checks, validateAgentId);
|
|
273
|
+
validateTrustedBucket("ip_ranges", ts.ip_ranges, checks, validateCidr);
|
|
274
|
+
for (const key of Object.keys(ts)) {
|
|
275
|
+
if (!["domains", "agent_ids", "ip_ranges"].includes(key)) {
|
|
276
|
+
checks.push({
|
|
277
|
+
name: `trusted_sources.${key}`,
|
|
278
|
+
passed: false,
|
|
279
|
+
message: "Unknown bucket (allowed: domains, agent_ids, ip_ranges)",
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
76
283
|
}
|
|
77
284
|
}
|
|
78
285
|
return checks;
|
|
@@ -125,7 +332,7 @@ export async function protectionShowCommand(agentName) {
|
|
|
125
332
|
process.exit(1);
|
|
126
333
|
}
|
|
127
334
|
}
|
|
128
|
-
export async function protectionPublishCommand(file, agentName) {
|
|
335
|
+
export async function protectionPublishCommand(file, agentName, options = {}) {
|
|
129
336
|
const agentId = await resolveAgentId(agentName);
|
|
130
337
|
const filePath = path.resolve(file);
|
|
131
338
|
if (!fs.existsSync(filePath)) {
|
|
@@ -172,11 +379,19 @@ export async function protectionPublishCommand(file, agentName) {
|
|
|
172
379
|
console.log("\nPublishing protection card...");
|
|
173
380
|
const contentType = parsed.format === "yaml" ? "text/yaml" : "application/json";
|
|
174
381
|
const body = parsed.format === "yaml" ? parsed.raw : JSON.stringify(parsed.parsed);
|
|
175
|
-
const
|
|
382
|
+
const bodyBytes = Buffer.byteLength(body, "utf-8");
|
|
383
|
+
if (bodyBytes > PROTECTION_CARD_MAX_BYTES) {
|
|
384
|
+
console.log("\n" +
|
|
385
|
+
fmt.error(`Protection card is ${bodyBytes} bytes; limit is ${PROTECTION_CARD_MAX_BYTES} bytes (64 KB). The API will return 413.`) +
|
|
386
|
+
"\n");
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
const result = await putProtectionCard(agentId, body, contentType, {
|
|
390
|
+
idempotencyKey: options.idempotencyKey,
|
|
391
|
+
});
|
|
176
392
|
console.log(fmt.success("Protection card published!"));
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
console.log(fmt.success("Canonical protection card recomposed"));
|
|
393
|
+
if (typeof result.card_id === "string" && result.card_id.length > 0) {
|
|
394
|
+
console.log(fmt.label(" Card ID:", ` ${result.card_id}`));
|
|
180
395
|
}
|
|
181
396
|
console.log();
|
|
182
397
|
}
|
|
@@ -227,7 +442,7 @@ export async function protectionValidateCommand(file) {
|
|
|
227
442
|
process.exit(1);
|
|
228
443
|
}
|
|
229
444
|
}
|
|
230
|
-
export async function protectionEditCommand(agentName) {
|
|
445
|
+
export async function protectionEditCommand(agentName, options = {}) {
|
|
231
446
|
const agentId = await resolveAgentId(agentName);
|
|
232
447
|
await requireAuth();
|
|
233
448
|
console.log("\nFetching current protection card...\n");
|
|
@@ -236,10 +451,17 @@ export async function protectionEditCommand(agentName) {
|
|
|
236
451
|
console.log(fmt.warn("No protection card found. Creating a template..."));
|
|
237
452
|
}
|
|
238
453
|
const cardYaml = original || yaml.dump({
|
|
454
|
+
card_version: "protection/2026-04-26",
|
|
455
|
+
agent_id: agentId,
|
|
239
456
|
mode: "observe",
|
|
240
457
|
thresholds: { warn: 0.3, quarantine: 0.6, block: 0.9 },
|
|
241
|
-
screen_surfaces:
|
|
242
|
-
|
|
458
|
+
screen_surfaces: {
|
|
459
|
+
incoming: true,
|
|
460
|
+
outgoing: true,
|
|
461
|
+
tool_calls: true,
|
|
462
|
+
tool_responses: true,
|
|
463
|
+
},
|
|
464
|
+
trusted_sources: { domains: [], agent_ids: [], ip_ranges: [] },
|
|
243
465
|
}, { lineWidth: 120, noRefs: true });
|
|
244
466
|
const tmpDir = os.tmpdir();
|
|
245
467
|
const tmpFile = path.join(tmpDir, `mnemom-protection-${agentId}.yaml`);
|
|
@@ -296,9 +518,21 @@ export async function protectionEditCommand(agentName) {
|
|
|
296
518
|
}
|
|
297
519
|
try {
|
|
298
520
|
console.log("\nPublishing protection card...");
|
|
299
|
-
const
|
|
521
|
+
const editedBytes = Buffer.byteLength(edited, "utf-8");
|
|
522
|
+
if (editedBytes > PROTECTION_CARD_MAX_BYTES) {
|
|
523
|
+
console.log("\n" +
|
|
524
|
+
fmt.error(`Protection card is ${editedBytes} bytes; limit is ${PROTECTION_CARD_MAX_BYTES} bytes (64 KB). The API will return 413.`) +
|
|
525
|
+
"\n");
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
const putResult = await putProtectionCard(agentId, edited, "text/yaml", {
|
|
529
|
+
idempotencyKey: options.idempotencyKey,
|
|
530
|
+
});
|
|
300
531
|
console.log(fmt.success("Protection card published!"));
|
|
301
|
-
|
|
532
|
+
if (typeof putResult.card_id === "string" && putResult.card_id.length > 0) {
|
|
533
|
+
console.log(fmt.label(" Card ID:", ` ${putResult.card_id}`));
|
|
534
|
+
}
|
|
535
|
+
console.log();
|
|
302
536
|
}
|
|
303
537
|
catch (error) {
|
|
304
538
|
const message = error instanceof Error ? error.message : String(error);
|
package/dist/commands/status.js
CHANGED
|
@@ -331,12 +331,14 @@ async function showTraceSummary(agentId) {
|
|
|
331
331
|
console.log();
|
|
332
332
|
if (integrityResult.status === "fulfilled") {
|
|
333
333
|
const integrity = integrityResult.value;
|
|
334
|
-
|
|
334
|
+
// Field names per docs.mnemom.ai IntegrityScore: integrity_score
|
|
335
|
+
// (in [0,1]), verified_traces, violation_count.
|
|
336
|
+
const score = (integrity.integrity_score * 100).toFixed(1);
|
|
335
337
|
console.log(`Integrity Score: ${score}%`);
|
|
336
338
|
console.log(`Total Traces: ${integrity.total_traces}`);
|
|
337
|
-
console.log(`Verified: ${integrity.
|
|
338
|
-
if (integrity.
|
|
339
|
-
console.log(`Violations: ${integrity.
|
|
339
|
+
console.log(`Verified: ${integrity.verified_traces}`);
|
|
340
|
+
if (integrity.violation_count > 0) {
|
|
341
|
+
console.log(`Violations: ${integrity.violation_count}`);
|
|
340
342
|
}
|
|
341
343
|
}
|
|
342
344
|
else {
|
package/dist/index.js
CHANGED
|
@@ -116,10 +116,11 @@ cardCmd
|
|
|
116
116
|
cardCmd
|
|
117
117
|
.command("edit")
|
|
118
118
|
.description("Edit alignment card in $EDITOR")
|
|
119
|
-
.
|
|
119
|
+
.option("--idempotency-key <uuid>", "Reuse a specific Idempotency-Key (for retries; default: auto)")
|
|
120
|
+
.action(async (subOpts) => {
|
|
120
121
|
try {
|
|
121
122
|
const opts = program.opts();
|
|
122
|
-
await cardEditCommand(opts.agent);
|
|
123
|
+
await cardEditCommand(opts.agent, { idempotencyKey: subOpts.idempotencyKey });
|
|
123
124
|
}
|
|
124
125
|
catch (error) {
|
|
125
126
|
console.error("Error:", error instanceof Error ? error.message : error);
|
|
@@ -130,10 +131,11 @@ cardCmd
|
|
|
130
131
|
.command("publish")
|
|
131
132
|
.argument("<file>", "Path to alignment card file (YAML or JSON)")
|
|
132
133
|
.description("Publish alignment card")
|
|
133
|
-
.
|
|
134
|
+
.option("--idempotency-key <uuid>", "Reuse a specific Idempotency-Key (for retries; default: auto)")
|
|
135
|
+
.action(async (file, subOpts) => {
|
|
134
136
|
try {
|
|
135
137
|
const opts = program.opts();
|
|
136
|
-
await cardPublishCommand(file, opts.agent);
|
|
138
|
+
await cardPublishCommand(file, opts.agent, { idempotencyKey: subOpts.idempotencyKey });
|
|
137
139
|
}
|
|
138
140
|
catch (error) {
|
|
139
141
|
console.error("Error:", error instanceof Error ? error.message : error);
|
|
@@ -191,10 +193,11 @@ protectionCmd
|
|
|
191
193
|
protectionCmd
|
|
192
194
|
.command("edit")
|
|
193
195
|
.description("Edit protection card in $EDITOR")
|
|
194
|
-
.
|
|
196
|
+
.option("--idempotency-key <uuid>", "Reuse a specific Idempotency-Key (for retries; default: auto)")
|
|
197
|
+
.action(async (subOpts) => {
|
|
195
198
|
try {
|
|
196
199
|
const opts = program.opts();
|
|
197
|
-
await protectionEditCommand(opts.agent);
|
|
200
|
+
await protectionEditCommand(opts.agent, { idempotencyKey: subOpts.idempotencyKey });
|
|
198
201
|
}
|
|
199
202
|
catch (error) {
|
|
200
203
|
console.error("Error:", error instanceof Error ? error.message : error);
|
|
@@ -205,10 +208,11 @@ protectionCmd
|
|
|
205
208
|
.command("publish")
|
|
206
209
|
.argument("<file>", "Path to protection card file (YAML or JSON)")
|
|
207
210
|
.description("Publish protection card")
|
|
208
|
-
.
|
|
211
|
+
.option("--idempotency-key <uuid>", "Reuse a specific Idempotency-Key (for retries; default: auto)")
|
|
212
|
+
.action(async (file, subOpts) => {
|
|
209
213
|
try {
|
|
210
214
|
const opts = program.opts();
|
|
211
|
-
await protectionPublishCommand(file, opts.agent);
|
|
215
|
+
await protectionPublishCommand(file, opts.agent, { idempotencyKey: subOpts.idempotencyKey });
|
|
212
216
|
}
|
|
213
217
|
catch (error) {
|
|
214
218
|
console.error("Error:", error instanceof Error ? error.message : error);
|