@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. 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.13.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 have = RISK_RANK[String(c.riskLevel)];
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
- if (moleculeFires(m, ctx)) {
311
- if (!best || PRECEDENCE[m.decision] > PRECEDENCE[best.decision]) {
312
- best = { decision: m.decision, reasonCode: m.reasonCode, id: m.id };
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 { decision: best.decision, reasonCode: best.reasonCode, firedMoleculeId: best.id, standardKey };
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
- function reasonFor(constraint) {
580
+ var AMOUNT_OPERANDS = /* @__PURE__ */ new Set(["mm:payAmount", "mm:cumulativeSpend"]);
581
+ function reasonFor(constraint, req) {
410
582
  if (!constraint) return "CONSTRAINT_FAILED";
411
- return REASON_BY_OPERAND[constraint.leftOperand] ?? `CONSTRAINT_FAILED:${constraint.leftOperand}`;
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
  };