@metamynd/agentsafe-guard 0.6.3 → 0.6.6

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 CHANGED
@@ -25,7 +25,7 @@ re-implement the gate:
25
25
  ([markdown](https://metamynd.ai/specs/magp-v1.0.md))
26
26
 
27
27
  It defines agent identity, the canonical signed message (§8.3), the sixteen-stage order of
28
- checks (§8.5), all 62 reason codes (Appendix A), delegation narrowing (§5.4), and evidence
28
+ checks (§8.5), all 64 reason codes (Appendix A), delegation narrowing (§5.4), and evidence
29
29
  you can verify offline without MetaMynd (§13.4). If you are writing a client in a language
30
30
  other than JavaScript, read §8.3.3–8.3.5 first: key encoding, number stringification and
31
31
  signed-vs-sent field identity each surface only as `SIGNATURE_INVALID`.
@@ -91,6 +91,27 @@ negative value could clear a spend cap for free, or erode a cumulative-spend tra
91
91
  sums signed amounts over time. `amount-unknown` now fires on any non-finite, non-numeric,
92
92
  **or negative** amount; a genuine `0` still passes.
93
93
 
94
+ **0.6.4 — a mandate's currency check no longer lets a PROHIBITION be dodged by relabeling
95
+ the currency.** A payAmount/cumulativeSpend constraint issued with a `unit` (currency) is
96
+ only satisfied in that currency — correct for a PERMISSION (fail closed to deny on a
97
+ mismatch), but a prohibition only fires when every one of its own constraints is satisfied,
98
+ so the identical "mismatch → not satisfied" rule let a prohibition like `payAmount gteq 1000
99
+ unit USD` be silently skipped by declaring any other currency, including a mere case
100
+ difference (`'usd'` vs `'USD'`). The currency comparison is also now case-insensitive.
101
+
102
+ **0.6.5 — `IDENTITY_KEY_MISMATCH`, a new reason code (now 63 total).** The backend gate
103
+ now cross-checks a resolved agent's registered public key against the key embedded in its
104
+ own self-certifying DID (`did:hedera` / `did:key`) before trusting a signature against it —
105
+ free, local, no network round-trip, since the DID already carries its own proof. Nothing in
106
+ this package's own evaluation changes; documented here because the count in this README and
107
+ the linked spec moved.
108
+
109
+ **0.6.6 — `SPEND_PATTERN_ANOMALY`, a new reason code (now 64 total).** The backend gate can
110
+ now escalate an otherwise-permitted decision whose amount deviates sharply from an agent's
111
+ OWN recorded spend history — a baseline the platform's trust-graph engine learns, not a
112
+ threshold a human pre-sets. Opt-in (`SPEND_ANOMALY_MODE=on`), off by default. Nothing in this
113
+ package's own evaluation changes; documented here because the count moved again.
114
+
94
115
  ```yaml
95
116
  # .github/workflows/governance.yml
96
117
  name: Governance
@@ -127,7 +148,7 @@ jobs:
127
148
  runs-on: ubuntu-latest
128
149
  steps:
129
150
  - uses: actions/checkout@v5
130
- - uses: Metamynd/agentsafe-guard/packages/agentsafe-guard@v0.6.2
151
+ - uses: Metamynd/agentsafe-guard/packages/agentsafe-guard@v0.6.4
131
152
  with:
132
153
  config: ./agent.metamynd.json
133
154
  require: merchants,perTxn
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamynd/agentsafe-guard",
3
- "version": "0.6.3",
3
+ "version": "0.6.6",
4
4
  "description": "Zero-dependency runtime governance for any Node AI agent \u2014 gate tool calls through MetaMynd/AgentSafe (allow / block / escalate) against the agent's mandate, enforced Standards, and SOPs. Ed25519-signed, deterministic, fail-closed.",
5
5
  "type": "module",
6
6
  "main": "./agentsafe-guard.mjs",
package/policy-core.mjs CHANGED
@@ -351,13 +351,14 @@ function reasonFor(constraint) {
351
351
  if (!constraint) return "CONSTRAINT_FAILED";
352
352
  return REASON_BY_OPERAND[constraint.leftOperand] ?? `CONSTRAINT_FAILED:${constraint.leftOperand}`;
353
353
  }
354
- function constraintSatisfied(c, req) {
354
+ function constraintSatisfied(c, req, strict) {
355
355
  const op = OPERATORS[c.operator];
356
356
  if (!op) return false;
357
357
  const left = Object.prototype.hasOwnProperty.call(req.values, c.leftOperand) ? req.values[c.leftOperand] : void 0;
358
- if (!op(left, c.rightOperand)) return false;
359
- if (c.unit && req.values["mm:currency"] !== c.unit) return false;
360
- return true;
358
+ if (!c.unit) return op(left, c.rightOperand);
359
+ const currency = req.values["mm:currency"];
360
+ const unitMatches = typeof currency === "string" && currency.toUpperCase() === c.unit.toUpperCase();
361
+ return unitMatches ? op(left, c.rightOperand) : !strict;
361
362
  }
362
363
  function targetOf(rule, mandate) {
363
364
  return rule.target ?? mandate.target;
@@ -379,7 +380,7 @@ function evaluateMandate(mandate, req) {
379
380
  }
380
381
  for (const p of mandate.prohibition ?? []) {
381
382
  if (targetOf(p, mandate) !== req.target) continue;
382
- const fires = (p.constraint ?? []).every((c) => constraintSatisfied(c, req));
383
+ const fires = (p.constraint ?? []).every((c) => constraintSatisfied(c, req, false));
383
384
  if (fires) {
384
385
  return {
385
386
  decision: p.enforcement ?? "block",
@@ -397,10 +398,10 @@ function evaluateMandate(mandate, req) {
397
398
  };
398
399
  }
399
400
  for (const p of perms) {
400
- const failing = (p.constraint ?? []).find((c) => !constraintSatisfied(c, req));
401
+ const failing = (p.constraint ?? []).find((c) => !constraintSatisfied(c, req, true));
401
402
  if (!failing) return { decision: "allow", reasonCode: "AUTHORIZED" };
402
403
  }
403
- const firstFail = (perms[0].constraint ?? []).find((c) => !constraintSatisfied(c, req));
404
+ const firstFail = (perms[0].constraint ?? []).find((c) => !constraintSatisfied(c, req, true));
404
405
  return {
405
406
  decision: firstFail?.onFail ?? "block",
406
407
  reasonCode: reasonFor(firstFail),