@metamynd/agentsafe-guard 0.5.0 → 0.6.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/README.md +37 -1
- package/cli.mjs +8 -2
- package/package.json +1 -1
- package/policy-core.mjs +19 -3
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
|
|
28
|
+
checks (§8.5), all 62 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`.
|
|
@@ -78,6 +78,12 @@ you state *our agents must carry a merchant allow-list* and find out when one do
|
|
|
78
78
|
`--json` for machine-readable output. Evaluation is local and pure: no holds are minted, no
|
|
79
79
|
nonces spent, no budget consumed, and a run costs one GET.
|
|
80
80
|
|
|
81
|
+
**0.6.0 — `amount-unknown`.** The same discipline applied to the amount itself: a spend cap
|
|
82
|
+
is only as good as the number it's checked against, and a signed-transaction or nested x402
|
|
83
|
+
payload can carry its amount somewhere a naive check never looks. `amount-unknown` is a
|
|
84
|
+
deny-by-default atom for exactly that case — an action whose value the gate can't determine
|
|
85
|
+
is blocked, not silently waved through an untested cap.
|
|
86
|
+
|
|
81
87
|
```yaml
|
|
82
88
|
# .github/workflows/governance.yml
|
|
83
89
|
name: Governance
|
|
@@ -98,6 +104,36 @@ jobs:
|
|
|
98
104
|
set `AGENT_KEY` in the environment — `AGENT_KEY`, `AGENT_DID` and `METAMYND_API` all
|
|
99
105
|
override the file when present.
|
|
100
106
|
|
|
107
|
+
### As a packaged Action
|
|
108
|
+
|
|
109
|
+
Same check, packaged so you don't hand-roll the workflow above — and it writes a
|
|
110
|
+
pass/fail table straight into the PR's checks summary instead of a log a reviewer has to
|
|
111
|
+
open:
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
# .github/workflows/governance.yml
|
|
115
|
+
name: Governance
|
|
116
|
+
on: [push, pull_request]
|
|
117
|
+
|
|
118
|
+
jobs:
|
|
119
|
+
mandate:
|
|
120
|
+
runs-on: ubuntu-latest
|
|
121
|
+
steps:
|
|
122
|
+
- uses: actions/checkout@v5
|
|
123
|
+
- uses: Metamynd/agentsafe-guard/packages/agentsafe-guard@v0.6.1
|
|
124
|
+
with:
|
|
125
|
+
config: ./agent.metamynd.json
|
|
126
|
+
require: merchants,perTxn
|
|
127
|
+
env:
|
|
128
|
+
AGENT_KEY: ${{ secrets.AGENT_KEY }}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Pinned to a released tag, the same way you'd pin any third-party action — not `@main`,
|
|
132
|
+
which moves under you every time this repository resyncs. Inputs: `config` (default
|
|
133
|
+
`./agent.metamynd.json`), `require`, `version` (the `@metamynd/agentsafe-guard` npm range
|
|
134
|
+
to run, default `latest`), `working-directory`. Output: `ok` (`"true"`/`"false"`), if a
|
|
135
|
+
later step needs to branch on the result.
|
|
136
|
+
|
|
101
137
|
## Install
|
|
102
138
|
|
|
103
139
|
```bash
|
package/cli.mjs
CHANGED
|
@@ -24,11 +24,17 @@ if (cmd === 'demo') {
|
|
|
24
24
|
require: (flag('require') ?? '').split(',').map((s) => s.trim()).filter(Boolean),
|
|
25
25
|
json: process.argv.includes('--json'),
|
|
26
26
|
});
|
|
27
|
-
process.exit(
|
|
27
|
+
// `process.exitCode`, never `process.exit()`. Calling exit() here aborts while the
|
|
28
|
+
// fetch keep-alive handle is still closing, which trips a libuv assertion on Windows
|
|
29
|
+
// (`!(handle->flags & UV_HANDLE_CLOSING)`) and returns 127 — AFTER printing that every
|
|
30
|
+
// control held. A CI command whose whole job is to fail a build honestly must not fail
|
|
31
|
+
// it dishonestly, and failing a PASSING agent is the one direction that teaches teams
|
|
32
|
+
// to delete the check. Setting the code and letting node drain exits cleanly.
|
|
33
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
28
34
|
} catch (e) {
|
|
29
35
|
// Failing to verify is not the same as verifying a pass, and CI must not read it as one.
|
|
30
36
|
console.error(`verify could not run: ${e.message}`);
|
|
31
|
-
process.
|
|
37
|
+
process.exitCode = 2;
|
|
32
38
|
}
|
|
33
39
|
} else if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
34
40
|
console.log(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamynd/agentsafe-guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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
|
@@ -11,6 +11,14 @@ var ATOM_REGISTRY = {
|
|
|
11
11
|
return have !== void 0 && need !== void 0 && have >= need;
|
|
12
12
|
},
|
|
13
13
|
"amount-over": (c, cfg) => typeof c.amount === "number" && c.amount > Number(cfg?.limit ?? 0),
|
|
14
|
+
// Deny-by-default primitive for value-moving actions. Fires on ABSENCE (like the
|
|
15
|
+
// evidence atoms below, and unlike `amount-over`): true when the context carries no
|
|
16
|
+
// usable amount — the gate cannot tell how much value the call would move, so a
|
|
17
|
+
// spend cap authored next to it would silently never fire. Author it with BLOCK as
|
|
18
|
+
// the FIRST rule of a spend policy; the cap that follows then only ever judges a
|
|
19
|
+
// known number. Opt-in: only a rule that keys it runs it, so actions that carry no
|
|
20
|
+
// amount by nature are unaffected.
|
|
21
|
+
"amount-unknown": (c) => !(typeof c.amount === "number" && Number.isFinite(c.amount)),
|
|
14
22
|
// Total budget: cumulativeSpend is a SERVER-derived, signed-last context field (never
|
|
15
23
|
// shadowable by the agent's itinerary), so this compares already-spent + this amount.
|
|
16
24
|
"cumulative-over": (c, cfg) => Number(c.cumulativeSpend ?? 0) + Number(c.amount ?? 0) > Number(cfg?.limit ?? 0),
|
|
@@ -58,8 +66,9 @@ ${c.output ?? ""}`.toLowerCase();
|
|
|
58
66
|
};
|
|
59
67
|
function notInAllowList(value, allowList) {
|
|
60
68
|
const v = value != null ? String(value).toLowerCase().trim() : "";
|
|
69
|
+
if (v === "") return false;
|
|
61
70
|
const allowed = (Array.isArray(allowList) ? allowList : []).map((x) => String(x).toLowerCase().trim());
|
|
62
|
-
return
|
|
71
|
+
return !allowed.includes(v);
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
// src/policy-core/atom-catalog.ts
|
|
@@ -71,6 +80,13 @@ var ATOM_SPECS = [
|
|
|
71
80
|
config: [{ key: "limit", type: "number", required: true, description: "Maximum allowed amount for one transaction" }],
|
|
72
81
|
requiredContext: ["amount"]
|
|
73
82
|
},
|
|
83
|
+
{
|
|
84
|
+
predicate: "amount-unknown",
|
|
85
|
+
label: "Amount not determinable",
|
|
86
|
+
description: "Fires when the action carries no usable amount \u2014 the gate cannot tell how much value it would move. A deny-by-default control for value-moving actions: author it with BLOCK ahead of a spend cap, otherwise an action whose amount is missing or unparseable passes the cap untested. Fires on ABSENCE, so only attach it to actions that must always carry an amount.",
|
|
87
|
+
config: [],
|
|
88
|
+
requiredContext: ["amount"]
|
|
89
|
+
},
|
|
74
90
|
{
|
|
75
91
|
predicate: "cumulative-over",
|
|
76
92
|
label: "Total budget over limit",
|
|
@@ -191,7 +207,7 @@ function requiredContextFor(predicates) {
|
|
|
191
207
|
}
|
|
192
208
|
|
|
193
209
|
// src/policy-core/standards-rules.ts
|
|
194
|
-
var PRECEDENCE = { allow: 0, observe: 1, escalate: 2, block: 3, suspend: 4, quarantine: 5 };
|
|
210
|
+
var PRECEDENCE = { allow: 0, observe: 1, escalate: 2, block: 3, suspend: 4, quarantine: 5, decommission: 6 };
|
|
195
211
|
function atomFires(atom, ctx) {
|
|
196
212
|
const pred = ATOM_REGISTRY[atom.predicate];
|
|
197
213
|
if (!pred) return false;
|
|
@@ -399,7 +415,7 @@ function sumEventField(events, type, field) {
|
|
|
399
415
|
}
|
|
400
416
|
|
|
401
417
|
// src/policy-core/evaluate.ts
|
|
402
|
-
var PRECEDENCE2 = { allow: 0, observe: 1, escalate: 2, block: 3, suspend: 4, quarantine: 5 };
|
|
418
|
+
var PRECEDENCE2 = { allow: 0, observe: 1, escalate: 2, block: 3, suspend: 4, quarantine: 5, decommission: 6 };
|
|
403
419
|
function evaluate(input) {
|
|
404
420
|
let decision = "allow";
|
|
405
421
|
let reasonCode = "AUTHORIZED";
|