@mnemom/mnemom 0.8.0 → 0.9.1

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.
@@ -3,7 +3,7 @@ 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 { getAlignmentCard, putAlignmentCard, resolveAgentId, } from "../lib/api.js";
6
+ import { ALIGNMENT_CARD_MAX_BYTES, getAlignmentCard, putAlignmentCard, 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";
@@ -26,133 +26,491 @@ export function parseCardFile(filePath) {
26
26
  }
27
27
  return { format, parsed: JSON.parse(raw), raw };
28
28
  }
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
- ]);
29
+ const ALIGNMENT_MODES = ["off", "observe", "nudge", "enforce"];
30
+ const PRINCIPAL_TYPES = ["human", "organization", "agent", "unspecified"];
31
+ const PRINCIPAL_RELATIONSHIPS = ["delegated_authority", "advisory", "autonomous"];
32
+ const VALUE_HIERARCHIES = ["lexicographic", "weighted", "contextual"];
33
+ const ESCALATION_ACTIONS = ["escalate", "deny", "log"];
34
+ const CONSCIENCE_MODES = ["augment", "replace"];
35
+ const CONSCIENCE_VALUE_TYPES = ["BOUNDARY", "FEAR", "COMMITMENT", "BELIEF", "HOPE"];
36
+ const CONSCIENCE_SEVERITIES = ["advisory", "mandatory"];
37
+ const TAMPER_EVIDENCE = ["append_only", "signed", "merkle"];
38
+ const UNMAPPED_SEVERITIES = ["low", "medium", "high", "critical"];
39
+ function isObj(v) {
40
+ return typeof v === "object" && v !== null && !Array.isArray(v);
41
+ }
35
42
  /**
36
- * Validate a unified alignment card object against ADR-008 schema.
37
- * Sections: principal, values, conscience, integrity, autonomy,
38
- * capabilities, enforcement, audit, extensions
43
+ * Validate a unified alignment card against ADR-039 canonical form.
44
+ *
45
+ * Required: card_version, agent_id, autonomy_mode, integrity_mode,
46
+ * principal (with identifier when type != unspecified),
47
+ * values.declared (non-empty), autonomy.bounded_actions (non-empty,
48
+ * disjoint from forbidden_actions), audit (retention_days, queryable,
49
+ * query_endpoint when queryable=true).
50
+ *
51
+ * Rejected legacy locations: integrity.enforcement_mode,
52
+ * enforcement.{mode, unmapped_tool_action, fail_open}, _composition.
39
53
  */
40
54
  export function validateUnifiedCard(card) {
41
55
  const checks = [];
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" });
56
+ // ── card_version (required) ──
57
+ if (typeof card.card_version !== "string" || card.card_version.length === 0) {
58
+ checks.push({
59
+ name: "card_version",
60
+ passed: false,
61
+ message: 'Required (string, e.g. "unified/2026-04-26").',
62
+ });
63
+ }
64
+ else {
65
+ checks.push({ name: "card_version", passed: true, message: card.card_version });
66
+ }
67
+ // ── agent_id (required) ──
68
+ if (typeof card.agent_id !== "string" || card.agent_id.length === 0) {
69
+ checks.push({ name: "agent_id", passed: false, message: "Required (string)." });
70
+ }
71
+ else {
72
+ checks.push({ name: "agent_id", passed: true, message: card.agent_id });
73
+ }
74
+ // ── autonomy_mode (top-level master switch, ADR-039 Decision 1) ──
75
+ if (typeof card.autonomy_mode !== "string") {
76
+ checks.push({
77
+ name: "autonomy_mode",
78
+ passed: false,
79
+ message: `Required (top-level master switch). Must be one of: ${ALIGNMENT_MODES.join(" | ")}. Per ADR-039 the legacy enforcement.mode location is no longer accepted.`,
80
+ });
81
+ }
82
+ else if (!ALIGNMENT_MODES.includes(card.autonomy_mode)) {
83
+ checks.push({
84
+ name: "autonomy_mode",
85
+ passed: false,
86
+ message: `Invalid: "${card.autonomy_mode}". Must be one of: ${ALIGNMENT_MODES.join(" | ")}.`,
87
+ });
88
+ }
89
+ else {
90
+ checks.push({ name: "autonomy_mode", passed: true, message: card.autonomy_mode });
91
+ }
92
+ // ── integrity_mode (top-level master switch, ADR-039 Decision 1) ──
93
+ if (typeof card.integrity_mode !== "string") {
94
+ checks.push({
95
+ name: "integrity_mode",
96
+ passed: false,
97
+ message: `Required (top-level master switch). Must be one of: ${ALIGNMENT_MODES.join(" | ")}. Per ADR-039 the legacy integrity.enforcement_mode location is no longer accepted.`,
98
+ });
99
+ }
100
+ else if (!ALIGNMENT_MODES.includes(card.integrity_mode)) {
101
+ checks.push({
102
+ name: "integrity_mode",
103
+ passed: false,
104
+ message: `Invalid: "${card.integrity_mode}". Must be one of: ${ALIGNMENT_MODES.join(" | ")}.`,
105
+ });
106
+ }
107
+ else {
108
+ checks.push({ name: "integrity_mode", passed: true, message: card.integrity_mode });
109
+ }
110
+ // ── ADR-039 cutover: reject legacy locations with a pointer to the new field ──
111
+ const integ = card.integrity;
112
+ if (integ && typeof integ === "object" && integ.enforcement_mode !== undefined) {
113
+ checks.push({
114
+ name: "integrity.enforcement_mode",
115
+ passed: false,
116
+ message: "Legacy field rejected. Use top-level integrity_mode instead (ADR-039).",
117
+ });
118
+ }
119
+ const enf = card.enforcement;
120
+ if (enf && typeof enf === "object") {
121
+ if (enf.mode !== undefined) {
122
+ checks.push({
123
+ name: "enforcement.mode",
124
+ passed: false,
125
+ message: "Legacy field rejected. Use top-level autonomy_mode instead (ADR-039).",
126
+ });
47
127
  }
48
- else {
49
- checks.push({ name: `Section: ${section}`, passed: false, message: "Missing or invalid" });
128
+ if (enf.unmapped_tool_action !== undefined) {
129
+ checks.push({
130
+ name: "enforcement.unmapped_tool_action",
131
+ passed: false,
132
+ message: "Legacy field rejected. Derived from enforcement.allow_unmapped_tools instead (ADR-039).",
133
+ });
50
134
  }
51
- }
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
- }
135
+ if (enf.fail_open !== undefined) {
136
+ checks.push({
137
+ name: "enforcement.fail_open",
138
+ passed: false,
139
+ message: "Legacy field rejected. fail_open is a runtime safety knob (gateway env config), not a card field (ADR-039).",
140
+ });
62
141
  }
63
142
  }
64
- // values.declared is non-empty array
65
- const values = card.values;
66
- const declared = values?.declared;
67
- if (Array.isArray(declared) && declared.length > 0) {
143
+ // _composition is system-managed
144
+ if (card._composition !== undefined) {
68
145
  checks.push({
69
- name: "values.declared",
70
- passed: true,
71
- message: `${declared.length} value(s) declared`,
146
+ name: "_composition",
147
+ passed: false,
148
+ message: "System-managed field — cannot be set on inbound cards.",
72
149
  });
73
150
  }
74
- else if (values) {
151
+ // ── principal (required: type + relationship; identifier when type != unspecified) ──
152
+ if (!isObj(card.principal)) {
153
+ checks.push({
154
+ name: "principal",
155
+ passed: false,
156
+ message: "Required (object with at least type + relationship).",
157
+ });
158
+ }
159
+ else {
160
+ const p = card.principal;
161
+ if (!PRINCIPAL_TYPES.includes(String(p.type))) {
162
+ checks.push({
163
+ name: "principal.type",
164
+ passed: false,
165
+ message: `Must be one of: ${PRINCIPAL_TYPES.join(", ")}.`,
166
+ });
167
+ }
168
+ if (!PRINCIPAL_RELATIONSHIPS.includes(String(p.relationship))) {
169
+ checks.push({
170
+ name: "principal.relationship",
171
+ passed: false,
172
+ message: `Must be one of: ${PRINCIPAL_RELATIONSHIPS.join(", ")}.`,
173
+ });
174
+ }
175
+ // ADR-039 Decision 10: identifier required when type != unspecified
176
+ if (p.type !== "unspecified" &&
177
+ PRINCIPAL_TYPES.includes(String(p.type)) &&
178
+ (typeof p.identifier !== "string" || p.identifier.length === 0)) {
179
+ checks.push({
180
+ name: "principal.identifier",
181
+ passed: false,
182
+ message: 'Required when principal.type is not "unspecified" (ADR-039 Decision 10).',
183
+ });
184
+ }
185
+ }
186
+ // ── values.declared (required, non-empty array of strings) ──
187
+ if (!isObj(card.values) || !Array.isArray(card.values.declared)) {
75
188
  checks.push({
76
189
  name: "values.declared",
77
190
  passed: false,
78
- message: "Must be a non-empty array",
191
+ message: "Required (non-empty array of strings).",
79
192
  });
80
193
  }
81
- // Custom values need definitions
82
- if (Array.isArray(declared)) {
83
- const definitions = (values?.definitions ?? {});
84
- const customValues = declared.filter((v) => !STANDARD_VALUES.has(v));
85
- const missingDefs = customValues.filter((v) => !definitions[v]);
86
- if (missingDefs.length === 0) {
194
+ else {
195
+ const v = card.values;
196
+ const decl = v.declared;
197
+ if (decl.length === 0) {
87
198
  checks.push({
88
- name: "Custom value definitions",
89
- passed: true,
90
- message: customValues.length === 0
91
- ? "No custom values (all standard)"
92
- : `${customValues.length} custom value(s) defined`,
199
+ name: "values.declared",
200
+ passed: false,
201
+ message: "Must contain at least one value.",
202
+ });
203
+ }
204
+ else if (!decl.every(s => typeof s === "string")) {
205
+ checks.push({
206
+ name: "values.declared",
207
+ passed: false,
208
+ message: "All entries must be strings.",
93
209
  });
94
210
  }
95
211
  else {
96
212
  checks.push({
97
- name: "Custom value definitions",
213
+ name: "values.declared",
214
+ passed: true,
215
+ message: `${decl.length} value(s) declared`,
216
+ });
217
+ }
218
+ // definitions ⊆ declared (ADR-039 Decision 10)
219
+ if (v.definitions !== undefined) {
220
+ if (!isObj(v.definitions)) {
221
+ checks.push({
222
+ name: "values.definitions",
223
+ passed: false,
224
+ message: "Must be an object keyed by value names.",
225
+ });
226
+ }
227
+ else {
228
+ const declSet = new Set(decl.filter((s) => typeof s === "string"));
229
+ for (const key of Object.keys(v.definitions)) {
230
+ if (!declSet.has(key)) {
231
+ checks.push({
232
+ name: `values.definitions.${key}`,
233
+ passed: false,
234
+ message: "Definition key not present in values.declared (ADR-039 Decision 10).",
235
+ });
236
+ }
237
+ }
238
+ }
239
+ }
240
+ // hierarchy enum
241
+ if (v.hierarchy !== undefined && !VALUE_HIERARCHIES.includes(String(v.hierarchy))) {
242
+ checks.push({
243
+ name: "values.hierarchy",
98
244
  passed: false,
99
- message: `Missing definitions for: ${missingDefs.join(", ")}`,
245
+ message: `Must be one of: ${VALUE_HIERARCHIES.join(", ")}.`,
100
246
  });
101
247
  }
102
248
  }
103
- // autonomy.bounded_actions is non-empty
104
- const autonomy = card.autonomy;
105
- const bounded = autonomy?.bounded_actions;
106
- if (Array.isArray(bounded) && bounded.length > 0) {
249
+ // ── autonomy.bounded_actions (required, non-empty; disjoint from forbidden_actions) ──
250
+ if (!isObj(card.autonomy) || !Array.isArray(card.autonomy.bounded_actions)) {
107
251
  checks.push({
108
252
  name: "autonomy.bounded_actions",
109
- passed: true,
110
- message: `${bounded.length} bounded action(s)`,
253
+ passed: false,
254
+ message: "Required (non-empty array of strings).",
111
255
  });
112
256
  }
113
- else if (autonomy) {
257
+ else {
258
+ const a = card.autonomy;
259
+ const bounded = a.bounded_actions;
260
+ if (bounded.length === 0) {
261
+ checks.push({
262
+ name: "autonomy.bounded_actions",
263
+ passed: false,
264
+ message: "Must contain at least one action.",
265
+ });
266
+ }
267
+ else {
268
+ checks.push({
269
+ name: "autonomy.bounded_actions",
270
+ passed: true,
271
+ message: `${bounded.length} bounded action(s)`,
272
+ });
273
+ }
274
+ // Disjoint check (ADR-039 Decision 10)
275
+ if (Array.isArray(a.forbidden_actions)) {
276
+ const forbidden = new Set(a.forbidden_actions.filter((x) => typeof x === "string"));
277
+ const overlap = bounded.filter((x) => typeof x === "string" && forbidden.has(x));
278
+ if (overlap.length > 0) {
279
+ checks.push({
280
+ name: "autonomy.bounded_actions",
281
+ passed: false,
282
+ message: `bounded_actions and forbidden_actions must be disjoint; both contain: ${overlap.join(", ")} (ADR-039 Decision 10).`,
283
+ });
284
+ }
285
+ }
286
+ // escalation_triggers shape (ADR-039 Decision 10)
287
+ if (a.escalation_triggers !== undefined) {
288
+ if (!Array.isArray(a.escalation_triggers)) {
289
+ checks.push({
290
+ name: "autonomy.escalation_triggers",
291
+ passed: false,
292
+ message: "Must be an array (may be empty).",
293
+ });
294
+ }
295
+ else {
296
+ a.escalation_triggers.forEach((t, i) => {
297
+ if (!isObj(t)) {
298
+ checks.push({
299
+ name: `autonomy.escalation_triggers[${i}]`,
300
+ passed: false,
301
+ message: "Must be an object.",
302
+ });
303
+ return;
304
+ }
305
+ const tr = t;
306
+ if (typeof tr.condition !== "string" || tr.condition.length === 0) {
307
+ checks.push({
308
+ name: `autonomy.escalation_triggers[${i}].condition`,
309
+ passed: false,
310
+ message: "Required (string).",
311
+ });
312
+ }
313
+ if (!ESCALATION_ACTIONS.includes(String(tr.action))) {
314
+ checks.push({
315
+ name: `autonomy.escalation_triggers[${i}].action`,
316
+ passed: false,
317
+ message: `Must be one of: ${ESCALATION_ACTIONS.join(", ")}.`,
318
+ });
319
+ }
320
+ if (typeof tr.reason !== "string" || tr.reason.length === 0) {
321
+ checks.push({
322
+ name: `autonomy.escalation_triggers[${i}].reason`,
323
+ passed: false,
324
+ message: "Required (string).",
325
+ });
326
+ }
327
+ });
328
+ }
329
+ }
330
+ }
331
+ // ── audit (required: retention_days, queryable; query_endpoint when queryable=true) ──
332
+ if (!isObj(card.audit)) {
114
333
  checks.push({
115
- name: "autonomy.bounded_actions",
334
+ name: "audit",
116
335
  passed: false,
117
- message: "Must be a non-empty array",
336
+ message: "Required (object with retention_days, queryable, trace_format).",
118
337
  });
119
338
  }
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)) {
339
+ else {
340
+ const a = card.audit;
341
+ if (typeof a.retention_days !== "number" || a.retention_days < 0) {
342
+ checks.push({
343
+ name: "audit.retention_days",
344
+ passed: false,
345
+ message: "Required (non-negative number).",
346
+ });
347
+ }
348
+ if (typeof a.queryable !== "boolean") {
349
+ checks.push({
350
+ name: "audit.queryable",
351
+ passed: false,
352
+ message: "Required (boolean).",
353
+ });
354
+ }
355
+ if (a.queryable === true && typeof a.query_endpoint !== "string") {
356
+ checks.push({
357
+ name: "audit.query_endpoint",
358
+ passed: false,
359
+ message: "Required when audit.queryable is true.",
360
+ });
361
+ }
362
+ // ADR-039 Decision 7: tamper_evidence enum
363
+ if (a.tamper_evidence !== undefined && a.tamper_evidence !== null) {
364
+ if (!TAMPER_EVIDENCE.includes(String(a.tamper_evidence))) {
126
365
  checks.push({
127
- name: `capabilities.${name}.tools`,
366
+ name: "audit.tamper_evidence",
128
367
  passed: false,
129
- message: "Must be an array",
368
+ message: `Must be one of: ${TAMPER_EVIDENCE.join(", ")}, or null.`,
130
369
  });
131
370
  }
132
- if (!Array.isArray(m?.required_actions)) {
371
+ }
372
+ // ADR-039 cutover: audit.storage no longer accepted
373
+ if (a.storage !== undefined) {
374
+ checks.push({
375
+ name: "audit.storage",
376
+ passed: false,
377
+ message: "Legacy field rejected. audit.storage is no longer accepted (ADR-039).",
378
+ });
379
+ }
380
+ }
381
+ // ── conscience (optional, BOUNDARY+advisory rejected per ADR-039 Decision 10) ──
382
+ if (card.conscience !== undefined) {
383
+ if (!isObj(card.conscience)) {
384
+ checks.push({
385
+ name: "conscience",
386
+ passed: false,
387
+ message: "Must be an object if present.",
388
+ });
389
+ }
390
+ else {
391
+ const cns = card.conscience;
392
+ if (!CONSCIENCE_MODES.includes(String(cns.mode))) {
133
393
  checks.push({
134
- name: `capabilities.${name}.required_actions`,
394
+ name: "conscience.mode",
135
395
  passed: false,
136
- message: "Must be an array",
396
+ message: `Must be one of: ${CONSCIENCE_MODES.join(", ")}.`,
397
+ });
398
+ }
399
+ if (!Array.isArray(cns.values)) {
400
+ checks.push({
401
+ name: "conscience.values",
402
+ passed: false,
403
+ message: "Must be an array.",
404
+ });
405
+ }
406
+ else {
407
+ cns.values.forEach((v, i) => {
408
+ if (!isObj(v)) {
409
+ checks.push({
410
+ name: `conscience.values[${i}]`,
411
+ passed: false,
412
+ message: "Must be an object with type + content.",
413
+ });
414
+ return;
415
+ }
416
+ const cv = v;
417
+ if (!CONSCIENCE_VALUE_TYPES.includes(String(cv.type))) {
418
+ checks.push({
419
+ name: `conscience.values[${i}].type`,
420
+ passed: false,
421
+ message: `Must be one of: ${CONSCIENCE_VALUE_TYPES.join(", ")}.`,
422
+ });
423
+ }
424
+ if (typeof cv.content !== "string" || cv.content.length === 0) {
425
+ checks.push({
426
+ name: `conscience.values[${i}].content`,
427
+ passed: false,
428
+ message: "Required (non-empty string).",
429
+ });
430
+ }
431
+ if (cv.severity !== undefined && !CONSCIENCE_SEVERITIES.includes(String(cv.severity))) {
432
+ checks.push({
433
+ name: `conscience.values[${i}].severity`,
434
+ passed: false,
435
+ message: `Must be one of: ${CONSCIENCE_SEVERITIES.join(", ")}.`,
436
+ });
437
+ }
438
+ if (cv.type === "BOUNDARY" && cv.severity === "advisory") {
439
+ checks.push({
440
+ name: `conscience.values[${i}]`,
441
+ passed: false,
442
+ message: "BOUNDARY entries cannot have severity=advisory; BOUNDARY is inviolable by definition (ADR-039 Decision 10).",
443
+ });
444
+ }
137
445
  });
138
446
  }
139
447
  }
140
448
  }
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) {
449
+ // ── enforcement (optional, ADR-039 Decision 3 user-facing knobs) ──
450
+ if (card.enforcement !== undefined) {
451
+ if (!isObj(card.enforcement)) {
452
+ checks.push({
453
+ name: "enforcement",
454
+ passed: false,
455
+ message: "Must be an object if present.",
456
+ });
457
+ }
458
+ else {
459
+ const e = card.enforcement;
460
+ if (e.allow_unmapped_tools !== undefined && typeof e.allow_unmapped_tools !== "boolean") {
461
+ checks.push({
462
+ name: "enforcement.allow_unmapped_tools",
463
+ passed: false,
464
+ message: "Must be a boolean.",
465
+ });
466
+ }
467
+ if (e.default_unmapped_severity !== undefined &&
468
+ !UNMAPPED_SEVERITIES.includes(String(e.default_unmapped_severity))) {
148
469
  checks.push({
149
- name: `enforcement.forbidden_tools[${i}]`,
470
+ name: "enforcement.default_unmapped_severity",
150
471
  passed: false,
151
- message: "Must have 'pattern' and 'reason' fields",
472
+ message: `Must be one of: ${UNMAPPED_SEVERITIES.join(", ")}.`,
152
473
  });
153
474
  }
154
475
  }
155
476
  }
477
+ // ── capabilities (optional; ADR-039 cutover rejects required_actions) ──
478
+ if (card.capabilities !== undefined) {
479
+ if (!isObj(card.capabilities)) {
480
+ checks.push({
481
+ name: "capabilities",
482
+ passed: false,
483
+ message: "Must be an object if present.",
484
+ });
485
+ }
486
+ else {
487
+ for (const [name, mapping] of Object.entries(card.capabilities)) {
488
+ if (!isObj(mapping)) {
489
+ checks.push({
490
+ name: `capabilities.${name}`,
491
+ passed: false,
492
+ message: "Must be an object.",
493
+ });
494
+ continue;
495
+ }
496
+ const m = mapping;
497
+ if (m.tools !== undefined && !Array.isArray(m.tools)) {
498
+ checks.push({
499
+ name: `capabilities.${name}.tools`,
500
+ passed: false,
501
+ message: "Must be an array.",
502
+ });
503
+ }
504
+ if (m.required_actions !== undefined) {
505
+ checks.push({
506
+ name: `capabilities.${name}.required_actions`,
507
+ passed: false,
508
+ message: "Legacy field rejected. capabilities.<n>.required_actions is no longer accepted (ADR-039).",
509
+ });
510
+ }
511
+ }
512
+ }
513
+ }
156
514
  return checks;
157
515
  }
158
516
  /** @deprecated Use validateUnifiedCard instead */
@@ -191,7 +549,7 @@ export async function cardShowCommand(agentName) {
191
549
  process.exit(1);
192
550
  }
193
551
  }
194
- export async function cardPublishCommand(file, agentName) {
552
+ export async function cardPublishCommand(file, agentName, options = {}) {
195
553
  const agentId = await resolveAgentId(agentName);
196
554
  // Resolve file path
197
555
  const filePath = path.resolve(file);
@@ -243,11 +601,19 @@ export async function cardPublishCommand(file, agentName) {
243
601
  console.log("\nPublishing alignment card...");
244
602
  const contentType = parsed.format === "yaml" ? "text/yaml" : "application/json";
245
603
  const body = parsed.format === "yaml" ? parsed.raw : JSON.stringify(parsed.parsed);
246
- const result = await putAlignmentCard(agentId, body, contentType);
604
+ const bodyBytes = Buffer.byteLength(body, "utf-8");
605
+ if (bodyBytes > ALIGNMENT_CARD_MAX_BYTES) {
606
+ console.log("\n" +
607
+ fmt.error(`Alignment card is ${bodyBytes} bytes; limit is ${ALIGNMENT_CARD_MAX_BYTES} bytes (128 KB). The API will return 413.`) +
608
+ "\n");
609
+ process.exit(1);
610
+ }
611
+ const result = await putAlignmentCard(agentId, body, contentType, {
612
+ idempotencyKey: options.idempotencyKey,
613
+ });
247
614
  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"));
615
+ if (typeof result.card_id === "string" && result.card_id.length > 0) {
616
+ console.log(fmt.label(" Card ID:", ` ${result.card_id}`));
251
617
  }
252
618
  console.log();
253
619
  }
@@ -301,7 +667,7 @@ export async function cardValidateCommand(file) {
301
667
  process.exit(1);
302
668
  }
303
669
  }
304
- export async function cardEditCommand(agentName) {
670
+ export async function cardEditCommand(agentName, options = {}) {
305
671
  const agentId = await resolveAgentId(agentName);
306
672
  await requireAuth();
307
673
  // Fetch current card as YAML
@@ -311,9 +677,18 @@ export async function cardEditCommand(agentName) {
311
677
  console.log(fmt.warn("No alignment card found. Creating a template..."));
312
678
  }
313
679
  const cardYaml = original || yaml.dump({
314
- principal: { name: "", type: "ai_agent", organization: "" },
680
+ card_version: "unified/2026-04-26",
681
+ agent_id: agentId,
682
+ autonomy_mode: "observe",
683
+ integrity_mode: "observe",
684
+ principal: { type: "agent", identifier: agentId, relationship: "delegated_authority" },
315
685
  values: { declared: ["transparency", "safety", "honesty"] },
316
- autonomy: { bounded_actions: [], forbidden_actions: [], escalation_triggers: [] },
686
+ autonomy: {
687
+ bounded_actions: ["respond_to_prompts"],
688
+ forbidden_actions: [],
689
+ escalation_triggers: [],
690
+ },
691
+ audit: { retention_days: 30, queryable: false, trace_format: "otel" },
317
692
  }, { lineWidth: 120, noRefs: true });
318
693
  // Write to temp file
319
694
  const tmpDir = os.tmpdir();
@@ -375,9 +750,21 @@ export async function cardEditCommand(agentName) {
375
750
  }
376
751
  try {
377
752
  console.log("\nPublishing alignment card...");
378
- const putResult = await putAlignmentCard(agentId, edited, "text/yaml");
753
+ const editedBytes = Buffer.byteLength(edited, "utf-8");
754
+ if (editedBytes > ALIGNMENT_CARD_MAX_BYTES) {
755
+ console.log("\n" +
756
+ fmt.error(`Alignment card is ${editedBytes} bytes; limit is ${ALIGNMENT_CARD_MAX_BYTES} bytes (128 KB). The API will return 413.`) +
757
+ "\n");
758
+ process.exit(1);
759
+ }
760
+ const putResult = await putAlignmentCard(agentId, edited, "text/yaml", {
761
+ idempotencyKey: options.idempotencyKey,
762
+ });
379
763
  console.log(fmt.success("Alignment card published!"));
380
- console.log(fmt.label(" Card ID:", ` ${putResult.card_id}`) + "\n");
764
+ if (typeof putResult.card_id === "string" && putResult.card_id.length > 0) {
765
+ console.log(fmt.label(" Card ID:", ` ${putResult.card_id}`));
766
+ }
767
+ console.log();
381
768
  }
382
769
  catch (error) {
383
770
  const message = error instanceof Error ? error.message : String(error);