@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.
- 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 +13 -9
- 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 +2 -2
package/dist/commands/card.js
CHANGED
|
@@ -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
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
49
|
-
checks.push({
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
//
|
|
65
|
-
|
|
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: "
|
|
70
|
-
passed:
|
|
71
|
-
message:
|
|
146
|
+
name: "_composition",
|
|
147
|
+
passed: false,
|
|
148
|
+
message: "System-managed field — cannot be set on inbound cards.",
|
|
72
149
|
});
|
|
73
150
|
}
|
|
74
|
-
|
|
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: "
|
|
191
|
+
message: "Required (non-empty array of strings).",
|
|
79
192
|
});
|
|
80
193
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
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: "
|
|
89
|
-
passed:
|
|
90
|
-
message:
|
|
91
|
-
|
|
92
|
-
|
|
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: "
|
|
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: `
|
|
245
|
+
message: `Must be one of: ${VALUE_HIERARCHIES.join(", ")}.`,
|
|
100
246
|
});
|
|
101
247
|
}
|
|
102
248
|
}
|
|
103
|
-
// autonomy.bounded_actions
|
|
104
|
-
|
|
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:
|
|
110
|
-
message:
|
|
253
|
+
passed: false,
|
|
254
|
+
message: "Required (non-empty array of strings).",
|
|
111
255
|
});
|
|
112
256
|
}
|
|
113
|
-
else
|
|
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: "
|
|
334
|
+
name: "audit",
|
|
116
335
|
passed: false,
|
|
117
|
-
message: "
|
|
336
|
+
message: "Required (object with retention_days, queryable, trace_format).",
|
|
118
337
|
});
|
|
119
338
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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:
|
|
366
|
+
name: "audit.tamper_evidence",
|
|
128
367
|
passed: false,
|
|
129
|
-
message:
|
|
368
|
+
message: `Must be one of: ${TAMPER_EVIDENCE.join(", ")}, or null.`,
|
|
130
369
|
});
|
|
131
370
|
}
|
|
132
|
-
|
|
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:
|
|
394
|
+
name: "conscience.mode",
|
|
135
395
|
passed: false,
|
|
136
|
-
message:
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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:
|
|
470
|
+
name: "enforcement.default_unmapped_severity",
|
|
150
471
|
passed: false,
|
|
151
|
-
message:
|
|
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
|
|
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
|
-
|
|
249
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
|
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
|
-
|
|
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);
|