@mnemom/mnemom 0.7.1 → 0.8.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/README.md +35 -31
- package/dist/commands/agents.d.ts +0 -14
- package/dist/commands/agents.js +35 -295
- package/dist/commands/auth.js +2 -7
- package/dist/commands/card.d.ts +21 -8
- package/dist/commands/card.js +328 -275
- package/dist/commands/integrity.js +6 -6
- package/dist/commands/license.js +14 -29
- package/dist/commands/logs.js +8 -8
- package/dist/commands/policy.d.ts +10 -23
- package/dist/commands/policy.js +20 -533
- package/dist/commands/protection.d.ts +14 -0
- package/dist/commands/protection.js +308 -0
- package/dist/commands/status.js +42 -50
- package/dist/index.js +114 -160
- package/dist/lib/api.d.ts +41 -0
- package/dist/lib/api.js +131 -1
- package/dist/lib/auth.d.ts +30 -21
- package/dist/lib/auth.js +95 -49
- package/dist/lib/config.d.ts +11 -98
- package/dist/lib/config.js +12 -220
- package/dist/lib/model-cache.js +5 -6
- package/dist/smoltbot-shim.js +1 -1
- package/package.json +2 -2
- package/dist/commands/init.d.ts +0 -7
- package/dist/commands/init.js +0 -763
- package/dist/commands/migrate-config.d.ts +0 -2
- package/dist/commands/migrate-config.js +0 -72
- package/dist/commands/register.d.ts +0 -6
- package/dist/commands/register.js +0 -362
package/dist/commands/card.js
CHANGED
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
3
5
|
import yaml from "js-yaml";
|
|
4
|
-
import {
|
|
5
|
-
import { getCard, updateCard, reverifyAgent, } from "../lib/api.js";
|
|
6
|
+
import { getAlignmentCard, putAlignmentCard, resolveAgentId, } from "../lib/api.js";
|
|
6
7
|
import { requireAuth } from "../lib/auth.js";
|
|
7
8
|
import { fmt } from "../lib/format.js";
|
|
8
9
|
import { askYesNo, isInteractive } from "../lib/prompt.js";
|
|
9
|
-
|
|
10
|
-
const STANDARD_VALUES = new Set([
|
|
11
|
-
"transparency",
|
|
12
|
-
"honesty",
|
|
13
|
-
"safety",
|
|
14
|
-
"privacy",
|
|
15
|
-
"fairness",
|
|
16
|
-
"accountability",
|
|
17
|
-
"beneficence",
|
|
18
|
-
"non-maleficence",
|
|
19
|
-
"autonomy",
|
|
20
|
-
"justice",
|
|
21
|
-
"reliability",
|
|
22
|
-
"security",
|
|
23
|
-
"human_oversight",
|
|
24
|
-
"explainability",
|
|
25
|
-
]);
|
|
10
|
+
import { evaluatePolicy, } from "@mnemom/policy-engine";
|
|
26
11
|
function detectFormat(filePath) {
|
|
27
12
|
const ext = path.extname(filePath).toLowerCase();
|
|
28
13
|
if (ext === ".yaml" || ext === ".yml")
|
|
@@ -37,37 +22,48 @@ export function parseCardFile(filePath) {
|
|
|
37
22
|
if (!parsed || typeof parsed !== "object") {
|
|
38
23
|
throw new Error("YAML did not produce a valid object");
|
|
39
24
|
}
|
|
40
|
-
return { format, parsed };
|
|
25
|
+
return { format, parsed, raw };
|
|
41
26
|
}
|
|
42
|
-
return { format, parsed: JSON.parse(raw) };
|
|
27
|
+
return { format, parsed: JSON.parse(raw), raw };
|
|
43
28
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
29
|
+
// Standard AAP values that do not require custom definitions
|
|
30
|
+
const STANDARD_VALUES = new Set([
|
|
31
|
+
"transparency", "honesty", "safety", "privacy", "fairness",
|
|
32
|
+
"accountability", "beneficence", "non-maleficence", "autonomy",
|
|
33
|
+
"justice", "reliability", "security", "human_oversight", "explainability",
|
|
34
|
+
]);
|
|
35
|
+
/**
|
|
36
|
+
* Validate a unified alignment card object against ADR-008 schema.
|
|
37
|
+
* Sections: principal, values, conscience, integrity, autonomy,
|
|
38
|
+
* capabilities, enforcement, audit, extensions
|
|
39
|
+
*/
|
|
40
|
+
export function validateUnifiedCard(card) {
|
|
47
41
|
const checks = [];
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
card
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
catch (e) {
|
|
55
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
56
|
-
checks.push({ name: "Valid JSON", passed: false, message: `Parse error: ${msg}` });
|
|
57
|
-
return checks; // Can't continue without valid JSON
|
|
58
|
-
}
|
|
59
|
-
// Check 2: Required blocks
|
|
60
|
-
const requiredBlocks = ["principal", "values", "autonomy_envelope", "audit_commitment"];
|
|
61
|
-
for (const block of requiredBlocks) {
|
|
62
|
-
if (card[block] && typeof card[block] === "object") {
|
|
63
|
-
checks.push({ name: `Block: ${block}`, passed: true, message: "Present" });
|
|
42
|
+
// Required sections
|
|
43
|
+
const requiredSections = ["principal", "values", "autonomy"];
|
|
44
|
+
for (const section of requiredSections) {
|
|
45
|
+
if (card[section] && typeof card[section] === "object") {
|
|
46
|
+
checks.push({ name: `Section: ${section}`, passed: true, message: "Present" });
|
|
64
47
|
}
|
|
65
48
|
else {
|
|
66
|
-
checks.push({ name: `
|
|
49
|
+
checks.push({ name: `Section: ${section}`, passed: false, message: "Missing or invalid" });
|
|
67
50
|
}
|
|
68
51
|
}
|
|
69
|
-
//
|
|
70
|
-
const
|
|
52
|
+
// Optional sections — validate shape if present
|
|
53
|
+
const optionalSections = ["conscience", "integrity", "capabilities", "enforcement", "audit", "extensions"];
|
|
54
|
+
for (const section of optionalSections) {
|
|
55
|
+
if (card[section] !== undefined) {
|
|
56
|
+
if (typeof card[section] === "object" && card[section] !== null) {
|
|
57
|
+
checks.push({ name: `Section: ${section}`, passed: true, message: "Present" });
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
checks.push({ name: `Section: ${section}`, passed: false, message: "Must be an object" });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// values.declared is non-empty array
|
|
65
|
+
const values = card.values;
|
|
66
|
+
const declared = values?.declared;
|
|
71
67
|
if (Array.isArray(declared) && declared.length > 0) {
|
|
72
68
|
checks.push({
|
|
73
69
|
name: "values.declared",
|
|
@@ -75,16 +71,16 @@ export function validateCard(raw) {
|
|
|
75
71
|
message: `${declared.length} value(s) declared`,
|
|
76
72
|
});
|
|
77
73
|
}
|
|
78
|
-
else {
|
|
74
|
+
else if (values) {
|
|
79
75
|
checks.push({
|
|
80
76
|
name: "values.declared",
|
|
81
77
|
passed: false,
|
|
82
78
|
message: "Must be a non-empty array",
|
|
83
79
|
});
|
|
84
80
|
}
|
|
85
|
-
//
|
|
81
|
+
// Custom values need definitions
|
|
86
82
|
if (Array.isArray(declared)) {
|
|
87
|
-
const definitions =
|
|
83
|
+
const definitions = (values?.definitions ?? {});
|
|
88
84
|
const customValues = declared.filter((v) => !STANDARD_VALUES.has(v));
|
|
89
85
|
const missingDefs = customValues.filter((v) => !definitions[v]);
|
|
90
86
|
if (missingDefs.length === 0) {
|
|
@@ -104,220 +100,90 @@ export function validateCard(raw) {
|
|
|
104
100
|
});
|
|
105
101
|
}
|
|
106
102
|
}
|
|
107
|
-
//
|
|
108
|
-
const
|
|
103
|
+
// autonomy.bounded_actions is non-empty
|
|
104
|
+
const autonomy = card.autonomy;
|
|
105
|
+
const bounded = autonomy?.bounded_actions;
|
|
109
106
|
if (Array.isArray(bounded) && bounded.length > 0) {
|
|
110
107
|
checks.push({
|
|
111
|
-
name: "bounded_actions",
|
|
108
|
+
name: "autonomy.bounded_actions",
|
|
112
109
|
passed: true,
|
|
113
110
|
message: `${bounded.length} bounded action(s)`,
|
|
114
111
|
});
|
|
115
112
|
}
|
|
116
|
-
else {
|
|
113
|
+
else if (autonomy) {
|
|
117
114
|
checks.push({
|
|
118
|
-
name: "bounded_actions",
|
|
115
|
+
name: "autonomy.bounded_actions",
|
|
119
116
|
passed: false,
|
|
120
117
|
message: "Must be a non-empty array",
|
|
121
118
|
});
|
|
122
119
|
}
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
message: `${triggers.length} trigger(s), all valid`,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
checks.push({
|
|
139
|
-
name: "escalation_triggers",
|
|
140
|
-
passed: false,
|
|
141
|
-
message: `${invalidTriggers.length} trigger(s) with invalid conditions`,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
else if (Array.isArray(triggers) && triggers.length === 0) {
|
|
146
|
-
checks.push({
|
|
147
|
-
name: "escalation_triggers",
|
|
148
|
-
passed: true,
|
|
149
|
-
message: "No triggers defined",
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
// Check 7: expires_at not already expired
|
|
153
|
-
if (card.expires_at) {
|
|
154
|
-
const expiresDate = new Date(card.expires_at);
|
|
155
|
-
if (isNaN(expiresDate.getTime())) {
|
|
156
|
-
checks.push({
|
|
157
|
-
name: "expires_at",
|
|
158
|
-
passed: false,
|
|
159
|
-
message: "Invalid date format",
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
else if (expiresDate.getTime() < Date.now()) {
|
|
163
|
-
checks.push({
|
|
164
|
-
name: "expires_at",
|
|
165
|
-
passed: false,
|
|
166
|
-
message: `Already expired: ${card.expires_at}`,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
checks.push({
|
|
171
|
-
name: "expires_at",
|
|
172
|
-
passed: true,
|
|
173
|
-
message: `Valid until ${card.expires_at}`,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return checks;
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Validate a pre-parsed card object (used for YAML cards).
|
|
181
|
-
* Re-serializes to JSON so the same checks run identically.
|
|
182
|
-
*/
|
|
183
|
-
export function validateCardObject(card) {
|
|
184
|
-
return validateCard(JSON.stringify(card));
|
|
185
|
-
}
|
|
186
|
-
// ============================================================================
|
|
187
|
-
// Card display
|
|
188
|
-
// ============================================================================
|
|
189
|
-
function displayCard(cardResponse) {
|
|
190
|
-
const card = cardResponse.card_json;
|
|
191
|
-
console.log(fmt.header("Alignment Card"));
|
|
192
|
-
console.log();
|
|
193
|
-
// Header info
|
|
194
|
-
console.log(fmt.label(" Card ID: ", cardResponse.card_id));
|
|
195
|
-
if (card.version) {
|
|
196
|
-
console.log(fmt.label(" Version: ", card.version));
|
|
197
|
-
}
|
|
198
|
-
if (card.issued_at) {
|
|
199
|
-
console.log(fmt.label(" Issued: ", card.issued_at));
|
|
200
|
-
}
|
|
201
|
-
if (card.expires_at) {
|
|
202
|
-
console.log(fmt.label(" Expires: ", card.expires_at));
|
|
203
|
-
}
|
|
204
|
-
// Principal
|
|
205
|
-
if (card.principal) {
|
|
206
|
-
console.log(fmt.section("Principal"));
|
|
207
|
-
console.log();
|
|
208
|
-
if (card.principal.name) {
|
|
209
|
-
console.log(fmt.label(" Name: ", card.principal.name));
|
|
210
|
-
}
|
|
211
|
-
if (card.principal.type) {
|
|
212
|
-
console.log(fmt.label(" Type: ", card.principal.type));
|
|
213
|
-
}
|
|
214
|
-
if (card.principal.organization) {
|
|
215
|
-
console.log(fmt.label(" Organization:", ` ${card.principal.organization}`));
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
// Values
|
|
219
|
-
if (card.values) {
|
|
220
|
-
console.log(fmt.section("Values"));
|
|
221
|
-
console.log();
|
|
222
|
-
if (Array.isArray(card.values.declared) && card.values.declared.length > 0) {
|
|
223
|
-
const badges = card.values.declared.map((v) => {
|
|
224
|
-
if (STANDARD_VALUES.has(v)) {
|
|
225
|
-
return fmt.badge(v, "cyan");
|
|
226
|
-
}
|
|
227
|
-
return fmt.badge(v, "magenta");
|
|
228
|
-
});
|
|
229
|
-
console.log(` ${badges.join(" ")}`);
|
|
230
|
-
// Show definitions for custom values
|
|
231
|
-
const definitions = card.values.definitions || {};
|
|
232
|
-
const customValues = card.values.declared.filter((v) => !STANDARD_VALUES.has(v));
|
|
233
|
-
if (customValues.length > 0) {
|
|
234
|
-
console.log();
|
|
235
|
-
for (const v of customValues) {
|
|
236
|
-
if (definitions[v]) {
|
|
237
|
-
console.log(fmt.label(` ${v}:`, ` ${definitions[v]}`));
|
|
238
|
-
}
|
|
239
|
-
}
|
|
120
|
+
// capabilities shape validation (if present)
|
|
121
|
+
const capabilities = card.capabilities;
|
|
122
|
+
if (capabilities && typeof capabilities === "object") {
|
|
123
|
+
for (const [name, mapping] of Object.entries(capabilities)) {
|
|
124
|
+
const m = mapping;
|
|
125
|
+
if (!Array.isArray(m?.tools)) {
|
|
126
|
+
checks.push({
|
|
127
|
+
name: `capabilities.${name}.tools`,
|
|
128
|
+
passed: false,
|
|
129
|
+
message: "Must be an array",
|
|
130
|
+
});
|
|
240
131
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const bounded = card.autonomy_envelope.bounded_actions;
|
|
248
|
-
if (Array.isArray(bounded) && bounded.length > 0) {
|
|
249
|
-
console.log(" Bounded actions:");
|
|
250
|
-
for (const action of bounded) {
|
|
251
|
-
console.log(` ${fmt.success(action)}`);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
const forbidden = card.autonomy_envelope.forbidden_actions;
|
|
255
|
-
if (Array.isArray(forbidden) && forbidden.length > 0) {
|
|
256
|
-
console.log(" Forbidden actions:");
|
|
257
|
-
for (const action of forbidden) {
|
|
258
|
-
console.log(` ${fmt.error(action)}`);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
const triggers = card.autonomy_envelope.escalation_triggers;
|
|
262
|
-
if (Array.isArray(triggers) && triggers.length > 0) {
|
|
263
|
-
console.log(" Escalation triggers:");
|
|
264
|
-
for (const trigger of triggers) {
|
|
265
|
-
const action = trigger.action ? ` -> ${trigger.action}` : "";
|
|
266
|
-
console.log(` ${fmt.warn(`${trigger.condition}${action}`)}`);
|
|
132
|
+
if (!Array.isArray(m?.required_actions)) {
|
|
133
|
+
checks.push({
|
|
134
|
+
name: `capabilities.${name}.required_actions`,
|
|
135
|
+
passed: false,
|
|
136
|
+
message: "Must be an array",
|
|
137
|
+
});
|
|
267
138
|
}
|
|
268
139
|
}
|
|
269
140
|
}
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
141
|
+
// enforcement.forbidden_tools shape validation (if present)
|
|
142
|
+
const enforcement = card.enforcement;
|
|
143
|
+
const forbidden = enforcement?.forbidden_tools;
|
|
144
|
+
if (Array.isArray(forbidden)) {
|
|
145
|
+
for (let i = 0; i < forbidden.length; i++) {
|
|
146
|
+
const rule = forbidden[i];
|
|
147
|
+
if (!rule?.pattern || !rule?.reason) {
|
|
148
|
+
checks.push({
|
|
149
|
+
name: `enforcement.forbidden_tools[${i}]`,
|
|
150
|
+
passed: false,
|
|
151
|
+
message: "Must have 'pattern' and 'reason' fields",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
282
154
|
}
|
|
283
155
|
}
|
|
284
|
-
|
|
285
|
-
if (card.extensions && Object.keys(card.extensions).length > 0) {
|
|
286
|
-
console.log(fmt.section("Extensions"));
|
|
287
|
-
console.log();
|
|
288
|
-
console.log(fmt.json(card.extensions));
|
|
289
|
-
}
|
|
290
|
-
console.log();
|
|
156
|
+
return checks;
|
|
291
157
|
}
|
|
158
|
+
/** @deprecated Use validateUnifiedCard instead */
|
|
159
|
+
export const validateCardJson = (raw) => validateUnifiedCard(JSON.parse(raw));
|
|
292
160
|
// ============================================================================
|
|
293
161
|
// Subcommands
|
|
294
162
|
// ============================================================================
|
|
295
163
|
export async function cardShowCommand(agentName) {
|
|
296
|
-
|
|
297
|
-
console.log("\n" + fmt.error("smoltbot is not initialized") + "\n");
|
|
298
|
-
console.log("Run `smoltbot init` to get started.\n");
|
|
299
|
-
process.exit(1);
|
|
300
|
-
}
|
|
301
|
-
const config = loadConfig();
|
|
302
|
-
if (!config) {
|
|
303
|
-
console.log("\n" + fmt.error("Failed to load configuration") + "\n");
|
|
304
|
-
process.exit(1);
|
|
305
|
-
}
|
|
306
|
-
const agent = await requireAgent(agentName);
|
|
307
|
-
if (!agent) {
|
|
308
|
-
console.log("\n" + fmt.error(`Agent not found${agentName ? `: ${agentName}` : ""}`) + "\n");
|
|
309
|
-
process.exit(1);
|
|
310
|
-
}
|
|
164
|
+
const agentId = await resolveAgentId(agentName);
|
|
311
165
|
console.log("\nFetching alignment card...\n");
|
|
312
166
|
try {
|
|
313
|
-
const
|
|
314
|
-
if (!
|
|
315
|
-
console.log(fmt.warn("No
|
|
316
|
-
console.log("\nPublish
|
|
317
|
-
console.log("
|
|
167
|
+
const { body, contentType } = await getAlignmentCard(agentId);
|
|
168
|
+
if (!body) {
|
|
169
|
+
console.log(fmt.warn("No alignment card found"));
|
|
170
|
+
console.log("\nPublish one with:\n");
|
|
171
|
+
console.log(" mnemom card publish <file.yaml>\n");
|
|
318
172
|
return;
|
|
319
173
|
}
|
|
320
|
-
|
|
174
|
+
// If the API returned YAML, print directly; otherwise convert
|
|
175
|
+
if (contentType.includes("yaml") || contentType.includes("text/yaml")) {
|
|
176
|
+
console.log(fmt.header("Alignment Card"));
|
|
177
|
+
console.log();
|
|
178
|
+
console.log(body);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
// JSON response — convert to YAML for display
|
|
182
|
+
const parsed = JSON.parse(body);
|
|
183
|
+
console.log(fmt.header("Alignment Card"));
|
|
184
|
+
console.log();
|
|
185
|
+
console.log(yaml.dump(parsed, { lineWidth: 120, noRefs: true }));
|
|
186
|
+
}
|
|
321
187
|
}
|
|
322
188
|
catch (error) {
|
|
323
189
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -326,21 +192,7 @@ export async function cardShowCommand(agentName) {
|
|
|
326
192
|
}
|
|
327
193
|
}
|
|
328
194
|
export async function cardPublishCommand(file, agentName) {
|
|
329
|
-
|
|
330
|
-
console.log("\n" + fmt.error("smoltbot is not initialized") + "\n");
|
|
331
|
-
console.log("Run `smoltbot init` to get started.\n");
|
|
332
|
-
process.exit(1);
|
|
333
|
-
}
|
|
334
|
-
const config = loadConfig();
|
|
335
|
-
if (!config) {
|
|
336
|
-
console.log("\n" + fmt.error("Failed to load configuration") + "\n");
|
|
337
|
-
process.exit(1);
|
|
338
|
-
}
|
|
339
|
-
const agent = await requireAgent(agentName);
|
|
340
|
-
if (!agent) {
|
|
341
|
-
console.log("\n" + fmt.error(`Agent not found${agentName ? `: ${agentName}` : ""}`) + "\n");
|
|
342
|
-
process.exit(1);
|
|
343
|
-
}
|
|
195
|
+
const agentId = await resolveAgentId(agentName);
|
|
344
196
|
// Resolve file path
|
|
345
197
|
const filePath = path.resolve(file);
|
|
346
198
|
if (!fs.existsSync(filePath)) {
|
|
@@ -349,11 +201,8 @@ export async function cardPublishCommand(file, agentName) {
|
|
|
349
201
|
}
|
|
350
202
|
// Parse file (JSON or YAML)
|
|
351
203
|
let parsed;
|
|
352
|
-
let format;
|
|
353
204
|
try {
|
|
354
|
-
|
|
355
|
-
parsed = result.parsed;
|
|
356
|
-
format = result.format;
|
|
205
|
+
parsed = parseCardFile(filePath);
|
|
357
206
|
}
|
|
358
207
|
catch (e) {
|
|
359
208
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -361,10 +210,10 @@ export async function cardPublishCommand(file, agentName) {
|
|
|
361
210
|
process.exit(1);
|
|
362
211
|
}
|
|
363
212
|
// Validate locally
|
|
364
|
-
const checks =
|
|
213
|
+
const checks = validateUnifiedCard(parsed.parsed);
|
|
365
214
|
const allPassed = checks.every((c) => c.passed);
|
|
366
215
|
console.log(fmt.header("Card Validation"));
|
|
367
|
-
console.log(fmt.label(" Format:", ` ${format.toUpperCase()}`));
|
|
216
|
+
console.log(fmt.label(" Format:", ` ${parsed.format.toUpperCase()}`));
|
|
368
217
|
console.log();
|
|
369
218
|
for (const check of checks) {
|
|
370
219
|
if (check.passed) {
|
|
@@ -383,27 +232,24 @@ export async function cardPublishCommand(file, agentName) {
|
|
|
383
232
|
await requireAuth();
|
|
384
233
|
// Confirm with user
|
|
385
234
|
if (isInteractive()) {
|
|
386
|
-
const confirm = await askYesNo(`Publish this card for agent ${
|
|
235
|
+
const confirm = await askYesNo(`Publish this alignment card for agent ${agentId}?`, false);
|
|
387
236
|
if (!confirm) {
|
|
388
237
|
console.log("\nPublish cancelled.\n");
|
|
389
238
|
return;
|
|
390
239
|
}
|
|
391
240
|
}
|
|
392
|
-
// Publish
|
|
241
|
+
// Publish — send in source format
|
|
393
242
|
try {
|
|
394
|
-
console.log("\nPublishing card...");
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
console.log(fmt.success(`Re-verification started (${reverifyResult.reverified} traces queued)`) + "\n");
|
|
403
|
-
}
|
|
404
|
-
catch {
|
|
405
|
-
console.log(fmt.warn("Could not trigger re-verification (non-critical)") + "\n");
|
|
243
|
+
console.log("\nPublishing alignment card...");
|
|
244
|
+
const contentType = parsed.format === "yaml" ? "text/yaml" : "application/json";
|
|
245
|
+
const body = parsed.format === "yaml" ? parsed.raw : JSON.stringify(parsed.parsed);
|
|
246
|
+
const result = await putAlignmentCard(agentId, body, contentType);
|
|
247
|
+
console.log(fmt.success("Alignment card published!"));
|
|
248
|
+
console.log(fmt.label(" Card ID:", ` ${result.card_id}`));
|
|
249
|
+
if (result.composed) {
|
|
250
|
+
console.log(fmt.success("Canonical card recomposed"));
|
|
406
251
|
}
|
|
252
|
+
console.log();
|
|
407
253
|
}
|
|
408
254
|
catch (error) {
|
|
409
255
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -420,11 +266,8 @@ export async function cardValidateCommand(file) {
|
|
|
420
266
|
}
|
|
421
267
|
// Parse file (JSON or YAML)
|
|
422
268
|
let parsed;
|
|
423
|
-
let format;
|
|
424
269
|
try {
|
|
425
|
-
|
|
426
|
-
parsed = result.parsed;
|
|
427
|
-
format = result.format;
|
|
270
|
+
parsed = parseCardFile(filePath);
|
|
428
271
|
}
|
|
429
272
|
catch (e) {
|
|
430
273
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -432,14 +275,14 @@ export async function cardValidateCommand(file) {
|
|
|
432
275
|
process.exit(1);
|
|
433
276
|
}
|
|
434
277
|
// Validate the parsed object
|
|
435
|
-
const checks =
|
|
278
|
+
const checks = validateUnifiedCard(parsed.parsed);
|
|
436
279
|
const allPassed = checks.every((c) => c.passed);
|
|
437
280
|
const passCount = checks.filter((c) => c.passed).length;
|
|
438
281
|
const failCount = checks.filter((c) => !c.passed).length;
|
|
439
282
|
console.log(fmt.header("Card Validation Report"));
|
|
440
283
|
console.log();
|
|
441
284
|
console.log(fmt.label(" File:", ` ${filePath}`));
|
|
442
|
-
console.log(fmt.label(" Format:", ` ${format.toUpperCase()}`));
|
|
285
|
+
console.log(fmt.label(" Format:", ` ${parsed.format.toUpperCase()}`));
|
|
443
286
|
console.log();
|
|
444
287
|
for (const check of checks) {
|
|
445
288
|
if (check.passed) {
|
|
@@ -458,3 +301,213 @@ export async function cardValidateCommand(file) {
|
|
|
458
301
|
process.exit(1);
|
|
459
302
|
}
|
|
460
303
|
}
|
|
304
|
+
export async function cardEditCommand(agentName) {
|
|
305
|
+
const agentId = await resolveAgentId(agentName);
|
|
306
|
+
await requireAuth();
|
|
307
|
+
// Fetch current card as YAML
|
|
308
|
+
console.log("\nFetching current alignment card...\n");
|
|
309
|
+
const { body: original } = await getAlignmentCard(agentId);
|
|
310
|
+
if (!original) {
|
|
311
|
+
console.log(fmt.warn("No alignment card found. Creating a template..."));
|
|
312
|
+
}
|
|
313
|
+
const cardYaml = original || yaml.dump({
|
|
314
|
+
principal: { name: "", type: "ai_agent", organization: "" },
|
|
315
|
+
values: { declared: ["transparency", "safety", "honesty"] },
|
|
316
|
+
autonomy: { bounded_actions: [], forbidden_actions: [], escalation_triggers: [] },
|
|
317
|
+
}, { lineWidth: 120, noRefs: true });
|
|
318
|
+
// Write to temp file
|
|
319
|
+
const tmpDir = os.tmpdir();
|
|
320
|
+
const tmpFile = path.join(tmpDir, `mnemom-card-${agentId}.yaml`);
|
|
321
|
+
fs.writeFileSync(tmpFile, cardYaml);
|
|
322
|
+
// Open in editor
|
|
323
|
+
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
324
|
+
console.log(`Opening ${editor}...`);
|
|
325
|
+
const result = spawnSync(editor, [tmpFile], { stdio: "inherit" });
|
|
326
|
+
if (result.status !== 0) {
|
|
327
|
+
console.log("\n" + fmt.error("Editor exited with an error") + "\n");
|
|
328
|
+
try {
|
|
329
|
+
fs.unlinkSync(tmpFile);
|
|
330
|
+
}
|
|
331
|
+
catch { /* ignore */ }
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
// Read back and compare
|
|
335
|
+
const edited = fs.readFileSync(tmpFile, "utf-8");
|
|
336
|
+
try {
|
|
337
|
+
fs.unlinkSync(tmpFile);
|
|
338
|
+
}
|
|
339
|
+
catch { /* ignore */ }
|
|
340
|
+
if (edited === cardYaml) {
|
|
341
|
+
console.log("\nNo changes made.\n");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
// Validate
|
|
345
|
+
let parsed;
|
|
346
|
+
try {
|
|
347
|
+
parsed = yaml.load(edited);
|
|
348
|
+
if (!parsed || typeof parsed !== "object")
|
|
349
|
+
throw new Error("Invalid YAML");
|
|
350
|
+
}
|
|
351
|
+
catch (e) {
|
|
352
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
353
|
+
console.log("\n" + fmt.error(`Invalid YAML: ${msg}`) + "\n");
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
const checks = validateUnifiedCard(parsed);
|
|
357
|
+
const allPassed = checks.every((c) => c.passed);
|
|
358
|
+
if (!allPassed) {
|
|
359
|
+
console.log(fmt.header("Validation Errors"));
|
|
360
|
+
console.log();
|
|
361
|
+
for (const check of checks.filter((c) => !c.passed)) {
|
|
362
|
+
console.log(fmt.error(`${check.name}: ${check.message}`));
|
|
363
|
+
}
|
|
364
|
+
console.log();
|
|
365
|
+
console.log(fmt.error("Validation failed. Card not published.") + "\n");
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
// Confirm and publish
|
|
369
|
+
if (isInteractive()) {
|
|
370
|
+
const confirm = await askYesNo("Publish updated alignment card?", true);
|
|
371
|
+
if (!confirm) {
|
|
372
|
+
console.log("\nPublish cancelled.\n");
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
console.log("\nPublishing alignment card...");
|
|
378
|
+
const putResult = await putAlignmentCard(agentId, edited, "text/yaml");
|
|
379
|
+
console.log(fmt.success("Alignment card published!"));
|
|
380
|
+
console.log(fmt.label(" Card ID:", ` ${putResult.card_id}`) + "\n");
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
384
|
+
console.log("\n" + fmt.error(`Failed to publish card: ${message}`) + "\n");
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* mnemom card evaluate <card-file> --tools <tools> -- local CI/CD evaluation
|
|
390
|
+
*
|
|
391
|
+
* Runs entirely locally using the embedded policy engine. No API key needed.
|
|
392
|
+
* The card IS the policy source -- capabilities and enforcement sections
|
|
393
|
+
* are extracted by @mnemom/policy-engine 0.3.0's evaluatePolicy().
|
|
394
|
+
*/
|
|
395
|
+
export async function cardEvaluateCommand(file, options) {
|
|
396
|
+
// 1. Read + validate card file
|
|
397
|
+
const cardPath = path.resolve(file);
|
|
398
|
+
if (!fs.existsSync(cardPath)) {
|
|
399
|
+
console.log("\n" + fmt.error(`Card file not found: ${cardPath}`) + "\n");
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
let parsed;
|
|
403
|
+
try {
|
|
404
|
+
parsed = parseCardFile(cardPath);
|
|
405
|
+
}
|
|
406
|
+
catch (e) {
|
|
407
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
408
|
+
console.log("\n" + fmt.error(`Could not parse card file: ${msg}`) + "\n");
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
const checks = validateUnifiedCard(parsed.parsed);
|
|
412
|
+
const allPassed = checks.every((c) => c.passed);
|
|
413
|
+
if (!allPassed) {
|
|
414
|
+
console.log(fmt.error("Card validation failed:"));
|
|
415
|
+
for (const check of checks.filter((c) => !c.passed)) {
|
|
416
|
+
console.log(fmt.error(` ${check.name}: ${check.message}`));
|
|
417
|
+
}
|
|
418
|
+
console.log();
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
// 2. Parse tool list from --tools or --tool-manifest
|
|
422
|
+
let tools = [];
|
|
423
|
+
if (options.tools) {
|
|
424
|
+
tools = options.tools.split(",").map((t) => ({ name: t.trim() })).filter((t) => t.name);
|
|
425
|
+
}
|
|
426
|
+
else if (options.toolManifest) {
|
|
427
|
+
const manifestPath = path.resolve(options.toolManifest);
|
|
428
|
+
if (!fs.existsSync(manifestPath)) {
|
|
429
|
+
console.log("\n" + fmt.error(`Tool manifest file not found: ${manifestPath}`) + "\n");
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
try {
|
|
433
|
+
const manifestRaw = fs.readFileSync(manifestPath, "utf-8");
|
|
434
|
+
const manifest = JSON.parse(manifestRaw);
|
|
435
|
+
if (Array.isArray(manifest)) {
|
|
436
|
+
tools = manifest.map((t) => typeof t === "string" ? { name: t } : { name: t.name });
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch (e) {
|
|
440
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
441
|
+
console.log("\n" + fmt.error(`Could not read tool manifest: ${msg}`) + "\n");
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (tools.length === 0) {
|
|
446
|
+
console.log("\n" + fmt.error("No tools specified. Use --tools or --tool-manifest") + "\n");
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
// 3. Run evaluation -- card IS the policy source
|
|
450
|
+
const result = evaluatePolicy({
|
|
451
|
+
context: "cicd",
|
|
452
|
+
card: parsed.parsed,
|
|
453
|
+
tools,
|
|
454
|
+
});
|
|
455
|
+
// 4. Display results
|
|
456
|
+
console.log(fmt.header("Card Policy Evaluation"));
|
|
457
|
+
console.log();
|
|
458
|
+
const principal = parsed.parsed.principal;
|
|
459
|
+
console.log(fmt.label(" Card:", ` ${principal?.name ?? path.basename(cardPath)}`));
|
|
460
|
+
console.log(fmt.label(" Context:", " cicd"));
|
|
461
|
+
console.log(fmt.label(" Tools:", ` ${tools.length} (${tools.map((t) => t.name).join(", ")})`));
|
|
462
|
+
console.log();
|
|
463
|
+
// Verdict
|
|
464
|
+
if (result.verdict === "pass") {
|
|
465
|
+
console.log(fmt.success("PASS -- all tools comply with card policy"));
|
|
466
|
+
}
|
|
467
|
+
else if (result.verdict === "warn") {
|
|
468
|
+
console.log(fmt.warn("WARN -- policy warnings detected"));
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
console.log(fmt.error("FAIL -- policy violations detected"));
|
|
472
|
+
}
|
|
473
|
+
console.log();
|
|
474
|
+
// Violations
|
|
475
|
+
if (result.violations.length > 0) {
|
|
476
|
+
console.log(fmt.section("Violations"));
|
|
477
|
+
console.log();
|
|
478
|
+
for (const v of result.violations) {
|
|
479
|
+
console.log(fmt.error(` ${v.tool} [${v.severity}] -- ${v.type}`));
|
|
480
|
+
console.log(` ${v.reason}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// Warnings
|
|
484
|
+
if (result.warnings.length > 0) {
|
|
485
|
+
console.log(fmt.section("Warnings"));
|
|
486
|
+
console.log();
|
|
487
|
+
for (const w of result.warnings) {
|
|
488
|
+
console.log(fmt.warn(` ${w.tool} -- ${w.type}`));
|
|
489
|
+
console.log(` ${w.reason}`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// Coverage
|
|
493
|
+
const cov = result.coverage;
|
|
494
|
+
console.log(fmt.section("Coverage"));
|
|
495
|
+
console.log();
|
|
496
|
+
console.log(fmt.label(" Card actions:", ` ${cov.total_card_actions}`));
|
|
497
|
+
console.log(fmt.label(" Mapped: ", ` ${cov.mapped_card_actions.length}`));
|
|
498
|
+
console.log(fmt.label(" Unmapped: ", ` ${cov.unmapped_card_actions.length}`));
|
|
499
|
+
console.log(fmt.label(" Coverage: ", ` ${cov.coverage_pct.toFixed(1)}%`));
|
|
500
|
+
if (cov.unmapped_card_actions.length > 0) {
|
|
501
|
+
console.log(fmt.label(" Unmapped list:", ` ${cov.unmapped_card_actions.join(", ")}`));
|
|
502
|
+
}
|
|
503
|
+
console.log();
|
|
504
|
+
console.log(fmt.label(" Duration:", ` ${result.duration_ms}ms`));
|
|
505
|
+
console.log();
|
|
506
|
+
// 5. Exit code
|
|
507
|
+
if (result.verdict === "fail") {
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
if (options.strict && result.verdict === "warn") {
|
|
511
|
+
process.exit(1);
|
|
512
|
+
}
|
|
513
|
+
}
|