@metamynd/agentsafe-signer 0.13.0 → 0.14.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/package.json +1 -1
- package/policy-core.mjs +205 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamynd/agentsafe-signer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Local signer daemon for AgentSafe agent/service keys \u2014 the key never enters the calling guard's own process. See docs/design/agent-key-custody-local-signer-daemon-plan.md.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./daemon.mjs",
|
package/policy-core.mjs
CHANGED
|
@@ -1,5 +1,129 @@
|
|
|
1
1
|
// GENERATED from backend/src/policy-core — do not edit. Regenerate: npm run build:signer-core
|
|
2
2
|
|
|
3
|
+
// src/policy-core/provenance.ts
|
|
4
|
+
var PROVENANCE_LEVELS = ["agent_asserted", "agent_signed", "gateway_derived", "authoritative", "attested"];
|
|
5
|
+
var PROVENANCE_RANK = {
|
|
6
|
+
agent_asserted: 0,
|
|
7
|
+
agent_signed: 1,
|
|
8
|
+
gateway_derived: 2,
|
|
9
|
+
authoritative: 3,
|
|
10
|
+
attested: 4
|
|
11
|
+
};
|
|
12
|
+
function isProvenance(v) {
|
|
13
|
+
return typeof v === "string" && Object.prototype.hasOwnProperty.call(PROVENANCE_RANK, v);
|
|
14
|
+
}
|
|
15
|
+
var PROVENANCE_KEY = Symbol.for("magp.context.provenance");
|
|
16
|
+
function provenanceOf(ctx, field) {
|
|
17
|
+
const map = ctx?.[PROVENANCE_KEY];
|
|
18
|
+
const p = map && typeof map === "object" ? map[field] : void 0;
|
|
19
|
+
return isProvenance(p) ? p : "agent_asserted";
|
|
20
|
+
}
|
|
21
|
+
function meetsProvenance(actual, minimum) {
|
|
22
|
+
return PROVENANCE_RANK[actual] >= PROVENANCE_RANK[minimum];
|
|
23
|
+
}
|
|
24
|
+
var RISK_LEVELS = ["low", "medium", "high", "critical"];
|
|
25
|
+
var RISK_ORDER = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
26
|
+
function normalizeRiskLevel(v) {
|
|
27
|
+
if (typeof v !== "string") return null;
|
|
28
|
+
const s = v.trim().toLowerCase();
|
|
29
|
+
return Object.prototype.hasOwnProperty.call(RISK_ORDER, s) ? s : null;
|
|
30
|
+
}
|
|
31
|
+
function maxRisk(...levels) {
|
|
32
|
+
let best = null;
|
|
33
|
+
for (const l of levels) if (l && (best === null || RISK_ORDER[l] > RISK_ORDER[best])) best = l;
|
|
34
|
+
return best;
|
|
35
|
+
}
|
|
36
|
+
function riskFloorFor(mandate, target) {
|
|
37
|
+
if (!mandate) return null;
|
|
38
|
+
let floor = null;
|
|
39
|
+
for (const p of mandate.permission ?? []) {
|
|
40
|
+
if (!p || typeof p !== "object") continue;
|
|
41
|
+
if ((p.target ?? mandate.target) !== target) continue;
|
|
42
|
+
floor = maxRisk(floor, normalizeRiskLevel(p.riskTier));
|
|
43
|
+
}
|
|
44
|
+
return floor;
|
|
45
|
+
}
|
|
46
|
+
var FIELD_KINDS = {
|
|
47
|
+
riskLevel: "risk",
|
|
48
|
+
consent: "boolean",
|
|
49
|
+
piiPresent: "boolean",
|
|
50
|
+
amount: "number",
|
|
51
|
+
cumulativeSpend: "number",
|
|
52
|
+
callCount: "number",
|
|
53
|
+
evidenceConfidence: "number",
|
|
54
|
+
holTrustScore: "number",
|
|
55
|
+
dataSourceId: "string",
|
|
56
|
+
jurisdiction: "string",
|
|
57
|
+
dataResidency: "string",
|
|
58
|
+
model: "string",
|
|
59
|
+
tool: "string",
|
|
60
|
+
currency: "string",
|
|
61
|
+
action: "string",
|
|
62
|
+
prompt: "string",
|
|
63
|
+
output: "string",
|
|
64
|
+
evidenceTypes: "string[]"
|
|
65
|
+
};
|
|
66
|
+
function contextFieldProblem(ctx, field) {
|
|
67
|
+
const v = ctx?.[field];
|
|
68
|
+
if (v === void 0 || v === null) return "missing";
|
|
69
|
+
if (typeof v === "string" && v.trim() === "") return "missing";
|
|
70
|
+
switch (FIELD_KINDS[field]) {
|
|
71
|
+
case "risk":
|
|
72
|
+
return normalizeRiskLevel(v) === null ? "malformed" : null;
|
|
73
|
+
case "boolean":
|
|
74
|
+
return typeof v === "boolean" ? null : "malformed";
|
|
75
|
+
case "number":
|
|
76
|
+
return typeof v === "number" && Number.isFinite(v) ? null : "malformed";
|
|
77
|
+
case "string":
|
|
78
|
+
return typeof v === "string" ? null : "malformed";
|
|
79
|
+
case "string[]":
|
|
80
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string") ? null : "malformed";
|
|
81
|
+
default:
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
var ATOM_DEFAULT_REQUIRED_CONTEXT = {
|
|
86
|
+
"risk-at-or-above": ["riskLevel"]
|
|
87
|
+
};
|
|
88
|
+
function buildRuleContext(src) {
|
|
89
|
+
const ctx = {};
|
|
90
|
+
const prov = /* @__PURE__ */ Object.create(null);
|
|
91
|
+
const put = (k, v, level) => {
|
|
92
|
+
Object.defineProperty(ctx, k, { value: v, enumerable: true, writable: true, configurable: true });
|
|
93
|
+
prov[k] = level;
|
|
94
|
+
};
|
|
95
|
+
const layers = [
|
|
96
|
+
[src.unsigned, "agent_asserted"],
|
|
97
|
+
[src.signed, "agent_signed"],
|
|
98
|
+
[src.gatewayDerived, "gateway_derived"],
|
|
99
|
+
[src.serverDerived, "authoritative"]
|
|
100
|
+
];
|
|
101
|
+
for (const [layer, level] of layers) {
|
|
102
|
+
for (const [k, v] of Object.entries(layer ?? {})) {
|
|
103
|
+
if (k === "riskLevel" && (level === "gateway_derived" || level === "authoritative") && normalizeRiskLevel(v) === null) continue;
|
|
104
|
+
put(k, v, level);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const floors = [];
|
|
108
|
+
const addFloor = (v, source) => {
|
|
109
|
+
const n = normalizeRiskLevel(v);
|
|
110
|
+
if (n) floors.push({ level: n, source });
|
|
111
|
+
};
|
|
112
|
+
addFloor(src.riskFloor, "authoritative");
|
|
113
|
+
addFloor(src.gatewayDerived?.riskLevel, "gateway_derived");
|
|
114
|
+
addFloor(src.serverDerived?.riskLevel, "authoritative");
|
|
115
|
+
const assertedUnsigned = normalizeRiskLevel(src.unsigned?.riskLevel);
|
|
116
|
+
const assertedSigned = normalizeRiskLevel(src.signed?.riskLevel);
|
|
117
|
+
const asserted = maxRisk(assertedUnsigned, assertedSigned);
|
|
118
|
+
if (floors.length > 0) {
|
|
119
|
+
put("riskLevel", maxRisk(asserted, ...floors.map((f) => f.level)), floors.reduce((best, f) => PROVENANCE_RANK[f.source] > PROVENANCE_RANK[best] ? f.source : best, "agent_asserted"));
|
|
120
|
+
} else if (asserted) {
|
|
121
|
+
put("riskLevel", asserted, assertedSigned ? "agent_signed" : "agent_asserted");
|
|
122
|
+
}
|
|
123
|
+
Object.defineProperty(ctx, PROVENANCE_KEY, { value: prov, enumerable: true, writable: false });
|
|
124
|
+
return ctx;
|
|
125
|
+
}
|
|
126
|
+
|
|
3
127
|
// src/policy-core/atom-registry.ts
|
|
4
128
|
var RISK_RANK = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
5
129
|
function currencyOutOfScope(ctx, cfgCurrency) {
|
|
@@ -14,7 +138,8 @@ var ATOM_REGISTRY = {
|
|
|
14
138
|
"data-source-not-approved": (c, cfg) => !!c.dataSourceId && !(cfg?.approved ?? []).includes(String(c.dataSourceId)),
|
|
15
139
|
"consent-missing": (c) => c.consent === false,
|
|
16
140
|
"risk-at-or-above": (c, cfg) => {
|
|
17
|
-
const
|
|
141
|
+
const haveLevel = normalizeRiskLevel(c.riskLevel);
|
|
142
|
+
const have = haveLevel === null ? void 0 : RISK_RANK[haveLevel];
|
|
18
143
|
const need = RISK_RANK[String(cfg?.level ?? "high")];
|
|
19
144
|
return have !== void 0 && need !== void 0 && have >= need;
|
|
20
145
|
},
|
|
@@ -276,6 +401,7 @@ function requiredContextFor(predicates) {
|
|
|
276
401
|
}
|
|
277
402
|
|
|
278
403
|
// src/policy-core/standards-rules.ts
|
|
404
|
+
var CONTEXT_UNVERIFIABLE = "CONTEXT_UNVERIFIABLE";
|
|
279
405
|
var PRECEDENCE = { allow: 0, observe: 1, escalate: 2, block: 3, suspend: 4, quarantine: 5, decommission: 6 };
|
|
280
406
|
function atomFires(atom, ctx) {
|
|
281
407
|
const pred = ATOM_REGISTRY[atom.predicate];
|
|
@@ -304,17 +430,49 @@ function moleculeFires(m, ctx) {
|
|
|
304
430
|
return false;
|
|
305
431
|
}
|
|
306
432
|
}
|
|
433
|
+
function requiredContextOf(m) {
|
|
434
|
+
const required = /* @__PURE__ */ new Map();
|
|
435
|
+
const need = (field, level) => {
|
|
436
|
+
const have = required.get(field);
|
|
437
|
+
if (!have || PROVENANCE_RANK[level] > PROVENANCE_RANK[have]) required.set(field, level);
|
|
438
|
+
};
|
|
439
|
+
for (const a of m.atoms ?? []) {
|
|
440
|
+
if (!Object.prototype.hasOwnProperty.call(ATOM_DEFAULT_REQUIRED_CONTEXT, a.predicate)) continue;
|
|
441
|
+
for (const f of ATOM_DEFAULT_REQUIRED_CONTEXT[a.predicate]) need(f, "agent_asserted");
|
|
442
|
+
}
|
|
443
|
+
for (const [f, level] of Object.entries(m.requireProvenance ?? {})) {
|
|
444
|
+
need(f, isProvenance(level) ? level : "attested");
|
|
445
|
+
}
|
|
446
|
+
return required;
|
|
447
|
+
}
|
|
448
|
+
function moleculeUnverifiable(m, ctx) {
|
|
449
|
+
const bad = [];
|
|
450
|
+
for (const [field, minimum] of requiredContextOf(m)) {
|
|
451
|
+
if (contextFieldProblem(ctx, field) !== null || !meetsProvenance(provenanceOf(ctx, field), minimum)) bad.push(field);
|
|
452
|
+
}
|
|
453
|
+
return bad.sort();
|
|
454
|
+
}
|
|
307
455
|
function evaluateStandardRules(molecules, ctx, standardKey = null) {
|
|
308
456
|
let best = null;
|
|
309
457
|
for (const m of molecules ?? []) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
458
|
+
const fired = moleculeFires(m, ctx);
|
|
459
|
+
const unverifiable = moleculeUnverifiable(m, ctx);
|
|
460
|
+
if (!fired && unverifiable.length === 0) continue;
|
|
461
|
+
let decision = fired ? m.decision : "escalate";
|
|
462
|
+
if (unverifiable.length > 0 && PRECEDENCE[decision] < PRECEDENCE.escalate) decision = "escalate";
|
|
463
|
+
const reasonCode = fired ? m.reasonCode : CONTEXT_UNVERIFIABLE;
|
|
464
|
+
if (!best || PRECEDENCE[decision] > PRECEDENCE[best.decision]) {
|
|
465
|
+
best = { decision, reasonCode, id: m.id, unverifiable: unverifiable.length > 0 ? unverifiable : void 0 };
|
|
314
466
|
}
|
|
315
467
|
}
|
|
316
468
|
if (!best) return { decision: "allow", reasonCode: null, firedMoleculeId: null, standardKey };
|
|
317
|
-
return {
|
|
469
|
+
return {
|
|
470
|
+
decision: best.decision,
|
|
471
|
+
reasonCode: best.reasonCode,
|
|
472
|
+
firedMoleculeId: best.id,
|
|
473
|
+
standardKey,
|
|
474
|
+
...best.unverifiable ? { unverifiableContext: best.unverifiable } : {}
|
|
475
|
+
};
|
|
318
476
|
}
|
|
319
477
|
function evaluateBoundStandards(standards, ctx) {
|
|
320
478
|
let best = { decision: "allow", reasonCode: null, firedMoleculeId: null, standardKey: null };
|
|
@@ -369,6 +527,19 @@ function validateMolecules(molecules) {
|
|
|
369
527
|
if (!m.atoms || m.atoms.length === 0) {
|
|
370
528
|
issues.push({ moleculeId: m.id, message: "molecule has no atoms" });
|
|
371
529
|
}
|
|
530
|
+
if (m.requireProvenance !== void 0) {
|
|
531
|
+
const rp = m.requireProvenance;
|
|
532
|
+
if (rp === null || typeof rp !== "object" || Array.isArray(rp)) {
|
|
533
|
+
issues.push({ moleculeId: m.id, message: "requireProvenance must be an object of { field: level }" });
|
|
534
|
+
} else {
|
|
535
|
+
for (const [field, level] of Object.entries(rp)) {
|
|
536
|
+
if (field.trim() === "") issues.push({ moleculeId: m.id, message: "requireProvenance has an empty field name" });
|
|
537
|
+
if (!isProvenance(level)) {
|
|
538
|
+
issues.push({ moleculeId: m.id, message: `requireProvenance '${field}' must be one of agent_asserted|agent_signed|gateway_derived|authoritative|attested` });
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
372
543
|
for (const a of m.atoms ?? []) {
|
|
373
544
|
if (!ATOM_REGISTRY[a.predicate]) {
|
|
374
545
|
issues.push({ moleculeId: m.id, message: `unknown atom predicate '${a.predicate}'` });
|
|
@@ -406,9 +577,14 @@ var REASON_BY_OPERAND = {
|
|
|
406
577
|
"mm:route": "ROUTE_NOT_ALLOWED",
|
|
407
578
|
"mm:counterparty": "COUNTERPARTY_NOT_ALLOWED"
|
|
408
579
|
};
|
|
409
|
-
|
|
580
|
+
var AMOUNT_OPERANDS = /* @__PURE__ */ new Set(["mm:payAmount", "mm:cumulativeSpend"]);
|
|
581
|
+
function reasonFor(constraint, req) {
|
|
410
582
|
if (!constraint) return "CONSTRAINT_FAILED";
|
|
411
|
-
|
|
583
|
+
const { leftOperand } = constraint;
|
|
584
|
+
if (AMOUNT_OPERANDS.has(leftOperand) && !Object.prototype.hasOwnProperty.call(req.values, leftOperand)) {
|
|
585
|
+
return "AMOUNT_NOT_DETERMINABLE";
|
|
586
|
+
}
|
|
587
|
+
return REASON_BY_OPERAND[leftOperand] ?? `CONSTRAINT_FAILED:${leftOperand}`;
|
|
412
588
|
}
|
|
413
589
|
function constraintSatisfied(c, req, strict) {
|
|
414
590
|
const op = OPERATORS[c.operator];
|
|
@@ -464,7 +640,7 @@ function evaluateMandate(mandate, req) {
|
|
|
464
640
|
const firstFail = (perms[0].constraint ?? []).find((c) => !constraintSatisfied(c, req, true));
|
|
465
641
|
return {
|
|
466
642
|
decision: firstFail?.onFail ?? "block",
|
|
467
|
-
reasonCode: reasonFor(firstFail),
|
|
643
|
+
reasonCode: reasonFor(firstFail, req),
|
|
468
644
|
matched: { kind: "permission", target: perms[0].target, constraint: firstFail }
|
|
469
645
|
};
|
|
470
646
|
}
|
|
@@ -516,6 +692,9 @@ function escapeField(v) {
|
|
|
516
692
|
function buildAuthMessage(f) {
|
|
517
693
|
return [f.agentDid, f.action, f.amount, f.currency, f.merchant ?? "", f.resource ?? "", f.nonce, f.issuedAt].map((v) => escapeField(String(v))).join("|");
|
|
518
694
|
}
|
|
695
|
+
function buildLegacyAuthMessageV1(f) {
|
|
696
|
+
return [f.agentDid, f.action, f.amount, f.currency, f.merchant ?? "", f.nonce, f.issuedAt].map((v) => escapeField(String(v))).join("|");
|
|
697
|
+
}
|
|
519
698
|
function buildLocalDecisionMessage(f) {
|
|
520
699
|
return [f.agentDid, f.action, f.decision, f.reasonCode, f.nonce, f.issuedAt].map((v) => escapeField(String(v))).join("|");
|
|
521
700
|
}
|
|
@@ -569,11 +748,17 @@ function operatingModeGate(mode, ctx) {
|
|
|
569
748
|
}
|
|
570
749
|
}
|
|
571
750
|
export {
|
|
751
|
+
ATOM_DEFAULT_REQUIRED_CONTEXT,
|
|
572
752
|
ATOM_REGISTRY,
|
|
573
753
|
ATOM_SPECS,
|
|
574
754
|
CATALOGUED_ATOMS,
|
|
755
|
+
CONTEXT_UNVERIFIABLE,
|
|
575
756
|
MODES_BY_RANK,
|
|
576
757
|
MODE_RANK,
|
|
758
|
+
PROVENANCE_KEY,
|
|
759
|
+
PROVENANCE_LEVELS,
|
|
760
|
+
PROVENANCE_RANK,
|
|
761
|
+
RISK_LEVELS,
|
|
577
762
|
SUPERVISED_AMOUNT_CAP,
|
|
578
763
|
applyCapture,
|
|
579
764
|
applyHold,
|
|
@@ -582,20 +767,31 @@ export {
|
|
|
582
767
|
authorityFailure,
|
|
583
768
|
buildAuthMessage,
|
|
584
769
|
buildCheckpointAnchorMessage,
|
|
770
|
+
buildLegacyAuthMessageV1,
|
|
585
771
|
buildLocalDecisionMessage,
|
|
772
|
+
buildRuleContext,
|
|
586
773
|
canAuthorize,
|
|
774
|
+
contextFieldProblem,
|
|
587
775
|
evaluate,
|
|
588
776
|
evaluateBoundStandards,
|
|
589
777
|
evaluateMandate,
|
|
590
778
|
evaluateStandardRules,
|
|
591
779
|
isAuthorityFailure,
|
|
592
780
|
isOperatingMode,
|
|
781
|
+
isProvenance,
|
|
782
|
+
maxRisk,
|
|
783
|
+
meetsProvenance,
|
|
593
784
|
moleculeFires,
|
|
785
|
+
moleculeUnverifiable,
|
|
594
786
|
moreRestrictive,
|
|
787
|
+
normalizeRiskLevel,
|
|
595
788
|
operatingModeGate,
|
|
789
|
+
provenanceOf,
|
|
596
790
|
releaseHold,
|
|
597
791
|
remainingBudget,
|
|
598
792
|
requiredContextFor,
|
|
793
|
+
requiredContextOf,
|
|
794
|
+
riskFloorFor,
|
|
599
795
|
sumEventField,
|
|
600
796
|
validateMolecules
|
|
601
797
|
};
|