@metamynd/agentsafe-guard 0.6.3 → 0.6.4

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
@@ -91,6 +91,14 @@ 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
+
94
102
  ```yaml
95
103
  # .github/workflows/governance.yml
96
104
  name: Governance
@@ -127,7 +135,7 @@ jobs:
127
135
  runs-on: ubuntu-latest
128
136
  steps:
129
137
  - uses: actions/checkout@v5
130
- - uses: Metamynd/agentsafe-guard/packages/agentsafe-guard@v0.6.2
138
+ - uses: Metamynd/agentsafe-guard/packages/agentsafe-guard@v0.6.4
131
139
  with:
132
140
  config: ./agent.metamynd.json
133
141
  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.4",
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),