axis-platform-sdk 0.2.1 → 0.3.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/CASE-STUDY-offworld.md +56 -0
- package/CHANGELOG.md +179 -112
- package/QUICKSTART.md +168 -0
- package/README.md +390 -218
- package/badges/README.md +41 -0
- package/badges/verified-by-axis-compact.svg +7 -0
- package/badges/verified-by-axis-dark.svg +7 -0
- package/badges/verified-by-axis.svg +7 -0
- package/examples/toy-platform-worker.js +60 -60
- package/package.json +54 -49
- package/src/authorizer.js +101 -101
- package/src/blocklist.js +183 -183
- package/src/express.d.ts +30 -0
- package/src/express.js +68 -0
- package/src/index.d.ts +288 -288
- package/src/ledger.js +170 -171
- package/src/scope.js +46 -46
- package/templates/cloudflare-worker/README.md +79 -0
- package/templates/cloudflare-worker/package.json +17 -0
- package/templates/cloudflare-worker/src/worker.js +162 -0
- package/templates/cloudflare-worker/wrangler.toml +20 -0
- package/templates/node-express/README.md +113 -0
- package/templates/node-express/package.json +19 -0
- package/templates/node-express/server.js +182 -0
- package/templates/node-express/smoke.js +63 -0
package/README.md
CHANGED
|
@@ -1,218 +1,390 @@
|
|
|
1
|
-
# axis-platform-sdk
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
##
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
the
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
1
|
+
# axis-platform-sdk
|
|
2
|
+
|
|
3
|
+
**Let verified agents into your platform. Boot the bad ones. Free, drop-in, no account required.**
|
|
4
|
+
|
|
5
|
+
AI agents are starting to show up at your platform — to post, to buy, to call your
|
|
6
|
+
API on a human's behalf. An API key can't tell you *which human is behind this
|
|
7
|
+
agent*, *whether they're allowed to do this*, or *let you revoke one bad agent
|
|
8
|
+
without nuking the key everyone shares*.
|
|
9
|
+
|
|
10
|
+
`axis-platform-sdk` is the **bouncer at your door**. When an AXIS agent shows up
|
|
11
|
+
and presents a token, this verifies — cryptographically, against a public
|
|
12
|
+
registry — **who it is, who's accountable for it, and exactly what it's been
|
|
13
|
+
authorized to do**, then lets it through or bounces it. A few lines of code.
|
|
14
|
+
|
|
15
|
+
On a Node/Express server:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import { axisGate } from 'axis-platform-sdk/express';
|
|
19
|
+
|
|
20
|
+
app.post('/comments',
|
|
21
|
+
axisGate({ audience: 'comments.mysite.com', requireScopes: ['content:comment'] }),
|
|
22
|
+
(req, res) => res.json({ ok: true, by: req.axis.agent_id })); // verified agent; proceed
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
On a Cloudflare Worker (or any `fetch` handler):
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { aitGate, denialResponse } from 'axis-platform-sdk';
|
|
29
|
+
|
|
30
|
+
const gate = aitGate({ audience: 'comments.mysite.com', requireScopes: ['content:comment'] });
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
async fetch(request) {
|
|
34
|
+
const verdict = await gate(request);
|
|
35
|
+
if (!verdict.accepted) return denialResponse(verdict); // 401/403 + a real reason
|
|
36
|
+
return Response.json({ ok: true, by: verdict.agent_id }); // verified agent; proceed
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That's the whole integration. No SDK account, no API key from us, no infra to run.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Why it's free, and what "no account required" means
|
|
46
|
+
|
|
47
|
+
The hard part — checking the signature, checking revocation, walking the
|
|
48
|
+
delegation chain to compute what the agent is *actually* allowed to do — is done
|
|
49
|
+
**server-side by the public AXIS registry** (`registry.axisprime.ai`). This SDK
|
|
50
|
+
is the thin, zero-dependency client that calls it and applies *your* policy. Your
|
|
51
|
+
platform makes **one outbound HTTPS call** and gets back a trustworthy verdict.
|
|
52
|
+
|
|
53
|
+
- **Zero dependencies.** Runs on Node 20+, Cloudflare Workers, and modern browsers.
|
|
54
|
+
- **Nothing to host.** No database, no key management, no service to deploy.
|
|
55
|
+
- **No relationship with us.** You verify against the public registry directly —
|
|
56
|
+
no signup, no key. The only thing that reaches us is the verification call your
|
|
57
|
+
server makes (the agent's token); we never see your content or your users.
|
|
58
|
+
- **Apache-2.0.** Use it, fork it, ship it.
|
|
59
|
+
|
|
60
|
+
Self-host is the whole product today. A cloud-hosted version — a hosted console,
|
|
61
|
+
durable arrival history, and a richer policy engine — is in alpha testing, planned
|
|
62
|
+
for release in Q3 2026, for teams who'd rather not run it themselves. You will
|
|
63
|
+
never need it to run the self-host path. See [Self-host today / cloud-hosted in
|
|
64
|
+
alpha](#self-host-today--cloud-hosted-in-alpha).
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Gate your platform in 10 minutes
|
|
69
|
+
|
|
70
|
+
There's a step-by-step guide in **[QUICKSTART.md](QUICKSTART.md)**, and two
|
|
71
|
+
complete, runnable drop-in starters — copy the one that matches your stack:
|
|
72
|
+
|
|
73
|
+
| Your stack | Starter | What it is |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| Node / Express (or any Node HTTP server) | **[`templates/node-express/`](templates/node-express/)** | The `axisGate(...)` middleware from `axis-platform-sdk/express` (one import, one line) + a full worked server (door policy, arrivals ledger, boot console). `npm install && npm start`. |
|
|
76
|
+
| Cloudflare Workers | **[`templates/cloudflare-worker/`](templates/cloudflare-worker/)** | The same, as a deployable Worker. `npx wrangler dev`. |
|
|
77
|
+
|
|
78
|
+
Both wrap the identical engine. **You do not need to adopt Cloudflare to use
|
|
79
|
+
AXIS** — the Worker template is just one runtime we provide a starter for. If you
|
|
80
|
+
run Node, Python, Go, or anything else, the integration is the same shape: pull
|
|
81
|
+
the token off the request, call the verifier, act on the verdict.
|
|
82
|
+
|
|
83
|
+
The Express starter ships a `smoke` test you can run right now to watch the gate
|
|
84
|
+
turn away an unidentified agent:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
$ cd templates/node-express && npm install && npm run smoke
|
|
88
|
+
PASS — no AIT -> 401 no_token
|
|
89
|
+
PASS — invalid AIT -> 403 denied
|
|
90
|
+
All checks passed.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## What a verdict gives you
|
|
96
|
+
|
|
97
|
+
`verifyAgent(token, opts)` (and the `aitGate` / middleware that wrap it) return a
|
|
98
|
+
single structured verdict:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
// accepted:
|
|
102
|
+
{ accepted: true, agent_id, operator_id, effective_scope, delegation_valid, tier, expires_at }
|
|
103
|
+
// or denied:
|
|
104
|
+
{ accepted: false, code, reason, ... } // code is stable: no_token | audience_mismatch |
|
|
105
|
+
// agent_revoked | insufficient_scope | insufficient_tier | ...
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
You decide the policy; the SDK enforces it:
|
|
109
|
+
|
|
110
|
+
- **`audience`** — the AIT's `aud` must equal *you*, so an agent's token for some
|
|
111
|
+
other site can't be replayed at yours.
|
|
112
|
+
- **`requireScopes`** — checked against the trustworthy **`effective_scope`** (the
|
|
113
|
+
registry's chain-walked result), never the token's self-declared scope.
|
|
114
|
+
- **`minTier`** — require `email` / `domain` / `verified` / `kyb_individual` /
|
|
115
|
+
`kyb_organization`-level operator verification.
|
|
116
|
+
- **`blockedOperators` / `approvedOperators`** — deny-list or allow-list by operator.
|
|
117
|
+
|
|
118
|
+
And the stateful half a real bouncer needs (all zero-infra by default):
|
|
119
|
+
|
|
120
|
+
- **Access ledger** — log every arrival, accepted or denied: "who's been using my platform."
|
|
121
|
+
- **Runtime blocklist** — boot one agent, or a whole operator, **without a deploy**.
|
|
122
|
+
- **Reputation report-back** — when you boot a bad actor, optionally sign a Trust
|
|
123
|
+
Attestation and emit it onward (off by default).
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Self-host today / cloud-hosted in alpha
|
|
128
|
+
|
|
129
|
+
Today this SDK *is* the product: you run it in your own backend, free. A
|
|
130
|
+
cloud-hosted version is in alpha testing (planned for release in Q3 2026) for
|
|
131
|
+
teams who'd rather not host the console and the state themselves.
|
|
132
|
+
|
|
133
|
+
| | **This SDK (free, self-hosted) — available now** | **Cloud-hosted — alpha, Q3 2026** |
|
|
134
|
+
| --- | --- | --- |
|
|
135
|
+
| Identity verification | ✅ full (against the public registry) | same engine |
|
|
136
|
+
| Scope / tier / operator policy | ✅ `SwitchAuthorizer` (on/off gates) | + granular relationship/attribute rules |
|
|
137
|
+
| Arrivals + blocklist | ✅ your store (in-memory default; D1/SQLite/Postgres adapter) | hosted, durable, multi-tenant |
|
|
138
|
+
| Admin console | ✅ a reference HTML page you own | a hosted console |
|
|
139
|
+
| Cost / setup | free, nothing to run | a hosted product (alpha) |
|
|
140
|
+
|
|
141
|
+
The SDK is designed as the **port**; the cloud-hosted version is built as an
|
|
142
|
+
**adapter** over the same engine, with byte-compatible arrival/block record
|
|
143
|
+
shapes. That's a deliberate design choice so that when the cloud version ships,
|
|
144
|
+
moving to it is a lift, not a rewrite — and you are never forced up.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Adoption tiers: start small, add as you need
|
|
149
|
+
|
|
150
|
+
You don't have to do everything at once. Most platforms start at **A** and stop
|
|
151
|
+
there; regulated or higher-stakes platforms add **B** and **C**.
|
|
152
|
+
|
|
153
|
+
| Tier | What you do | What it takes |
|
|
154
|
+
| --- | --- | --- |
|
|
155
|
+
| **A — Identity acceptance** | Accept any AXIS-verified agent the way you accept a signed-in human. Pass/fail at request time. | `verifyAgent(token, { audience })` — that's it. |
|
|
156
|
+
| **B — Access policy** | Also publish your requirements at `/.well-known/axis-access`, so agents and operators can check before they even call you. | A small JSON doc (the starters serve it). |
|
|
157
|
+
| **C — Scope + tier enforcement** | Additionally require specific permissions and a minimum verification level. | Add `requireScopes` / `minTier` (and `blockedOperators`) to the gate. |
|
|
158
|
+
|
|
159
|
+
All three are the same `verifyAgent` call with more of its options set — moving up
|
|
160
|
+
a tier is adding arguments, not re-architecting. (For an upstream you can't put the
|
|
161
|
+
SDK inside — a legacy service, a non-Node runtime — the separate
|
|
162
|
+
[`axis-gateway`](https://github.com/MachinesOfDesire/axis-gateway) reverse proxy
|
|
163
|
+
enforces Tier C in front of it.)
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Trust model (read this)
|
|
168
|
+
|
|
169
|
+
- **`effective_scope` is the only trustworthy scope.** It's the registry's
|
|
170
|
+
server-side chain-walk result, returned only when a valid delegation is
|
|
171
|
+
presented. The AIT's self-declared `scope` is **not** trusted and is never used
|
|
172
|
+
for `requireScopes`.
|
|
173
|
+
- **A direct AIT with no valid delegation has no proven scope.** Any non-empty
|
|
174
|
+
`requireScopes` will deny it. That's intentional.
|
|
175
|
+
- **Audience matching is the platform's job.** The registry guarantees `aud`
|
|
176
|
+
exists; *you* guarantee it equals you. (The starters do this for you.)
|
|
177
|
+
|
|
178
|
+
### Forward compatibility (gating signals coming later)
|
|
179
|
+
|
|
180
|
+
Today you can gate on **scope**, **operator verification tier**, and
|
|
181
|
+
**operator allow/block lists**. Richer **provenance** signals — operator account
|
|
182
|
+
age, signup method, prior abuse flags — are defined in the protocol but **not yet
|
|
183
|
+
exposed by the registry**, so they aren't available to gate on right now.
|
|
184
|
+
|
|
185
|
+
When they ship, they arrive **additively and backward-compatibly**:
|
|
186
|
+
|
|
187
|
+
- The verdict object only *gains* fields; it never changes existing ones. Your code
|
|
188
|
+
keeps working untouched.
|
|
189
|
+
- Provenance gating will be **new optional `verifyAgent` options** (e.g. a minimum
|
|
190
|
+
account age), exactly like `minTier` is today. Unknown options are ignored, so an
|
|
191
|
+
older integration is never broken by a newer registry.
|
|
192
|
+
- You opt in when you want it: bump the SDK and set the new option. Platforms that
|
|
193
|
+
don't care do nothing and are unaffected.
|
|
194
|
+
|
|
195
|
+
So adding provenance later requires a **registry update** (to populate and expose
|
|
196
|
+
the fields) and an **SDK minor** (to read them) — but **no breaking change and no
|
|
197
|
+
forced migration** for platforms already running. Build to Tier A/B/C now; the
|
|
198
|
+
provenance knobs slot into the same gate when they land.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Where it sits
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
agent (axis-protocol-sdk) ──presents AIT──> YOUR PLATFORM (axis-platform-sdk)
|
|
206
|
+
│
|
|
207
|
+
└── GET /verify ──> registry (does the crypto)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The [`axis-protocol-sdk`](https://github.com/MachinesOfDesire/axis-protocol-sdk)
|
|
211
|
+
and the [AXIS Prime MCP](https://github.com/MachinesOfDesire/axis-mcp) are what an
|
|
212
|
+
*agent* uses to get and present an identity. **This** SDK is the other end of the
|
|
213
|
+
wire — the inbound identity gate. It is distinct from the operator-side
|
|
214
|
+
*outbound* gateway, and from generic AI gateways (TrueFoundry, Portkey, …), which
|
|
215
|
+
govern an operator's outbound LLM calls. This is the *inbound*
|
|
216
|
+
bouncer.
|
|
217
|
+
|
|
218
|
+
A worked, real-world integration is documented in
|
|
219
|
+
**[CASE-STUDY-offworld.md](CASE-STUDY-offworld.md)** (gating comments on a live
|
|
220
|
+
news site). That's a *case study* — an example of using the tool, not the product
|
|
221
|
+
itself.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## API reference
|
|
226
|
+
|
|
227
|
+
- **`verifyAgent(token, opts)`** — the core. Verifies against the registry and
|
|
228
|
+
applies your policy. Returns a structured verdict.
|
|
229
|
+
- `audience` — your platform id. The AIT's `aud` must equal it. (Matched
|
|
230
|
+
locally: the registry only checks that `aud` is non-empty, not that it equals
|
|
231
|
+
you. That check is yours.)
|
|
232
|
+
- `requireScopes` — checked against the trustworthy `effective_scope`.
|
|
233
|
+
- `minTier` — `email | domain | verified | kyb_individual | kyb_organization`.
|
|
234
|
+
- `blockedOperators` / `approvedOperators` — deny/allow lists by operator id.
|
|
235
|
+
- `registryBaseUrl` — defaults to `https://registry.axisprime.ai`.
|
|
236
|
+
- **`aitGate(opts)`** — returns `(request) => Promise<verdict>`; binds your policy
|
|
237
|
+
to a request gate for Workers and any `fetch`-style `Request`. Pulls the AIT
|
|
238
|
+
from `Authorization: Bearer <ait>`, `X-AXIS-Token`, or `?ait=`.
|
|
239
|
+
- **`axisGate(opts)`** *(subpath `axis-platform-sdk/express`)* — the same as
|
|
240
|
+
Express/Connect middleware: `(req, res, next)`. On accept it sets `req.axis` and
|
|
241
|
+
calls `next()`; on deny it responds `401` / `403` / `503` with `{ error,
|
|
242
|
+
message }`. Imports nothing from Express (zero-dep), so it also works on Connect
|
|
243
|
+
and bare `http`.
|
|
244
|
+
- **`denialResponse(verdict)`** — turns a denied verdict into a 401/403 `Response`.
|
|
245
|
+
- **`scopeCovers(granted, required)` / `coversAll(granted, required[])`** — the
|
|
246
|
+
AXIS scope matcher (ported verbatim from the operator-side gateway's, so
|
|
247
|
+
operator and platform sides agree).
|
|
248
|
+
- **`enrich(agentId, token, opts)`** — fetch the agent's presentation layer
|
|
249
|
+
(display name, tier) for a console UI.
|
|
250
|
+
- **`loadAccessPolicy(platformBaseUrl)`** — read a platform's published
|
|
251
|
+
`/.well-known/axis-access` door policy.
|
|
252
|
+
- **`decodeAitPayload(token)`** — read the AIT payload (claims) without verifying.
|
|
253
|
+
For the `aud` check; never trust it for authorization.
|
|
254
|
+
- **`AccessLedger` / `MemoryLedgerStore` / `loggedGate(gate, ledger, opts)` /
|
|
255
|
+
`recordEntry(verdict, opts)`** — the access ledger (who showed up). `loggedGate`
|
|
256
|
+
wraps a gate so every verdict is logged.
|
|
257
|
+
- **`Blocklist` / `MemoryBlocklistStore` / `gatedWithBlocklist(gate, blocklist)`**
|
|
258
|
+
— the runtime block list (by operator and by agent). `blockOperator` /
|
|
259
|
+
`blockAgent` / `unblock*` / `isAgentBlocked` / `blockedOperatorIds` /
|
|
260
|
+
`checkVerdict`.
|
|
261
|
+
- **`reportFlag(args, opts)` / `blockAndReport(blocklist, args, opts)`** — sign a
|
|
262
|
+
negative Trust Attestation and send it to a reputation index (OFF by default).
|
|
263
|
+
- **`getPlatformKey(opts)` / `buildAttestation` / `signAttestation` /
|
|
264
|
+
`verifyAttestation` / `MemoryKeyStore`** — the platform's Ed25519 key + TA
|
|
265
|
+
build/sign/verify primitives (WebCrypto, zero-dep).
|
|
266
|
+
|
|
267
|
+
### Gates as policy: `SwitchAuthorizer` (the free-tier engine)
|
|
268
|
+
|
|
269
|
+
Identity verification is fixed and core. The authorization *decision* is a
|
|
270
|
+
pluggable layer — the **Authorizer port**. `SwitchAuthorizer` is the free-tier
|
|
271
|
+
implementation: config-driven on/off gates. Its `policy` object is exactly what a
|
|
272
|
+
console's "door policy" screen edits and saves.
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
import { SwitchAuthorizer, denialResponse } from 'axis-platform-sdk';
|
|
276
|
+
|
|
277
|
+
const door = new SwitchAuthorizer({
|
|
278
|
+
audience: 'comments.mysite.com',
|
|
279
|
+
defaultAllow: false,
|
|
280
|
+
gates: {
|
|
281
|
+
'content:comment': { enabled: true, requireScopes: ['content:comment'], minTier: 'domain' },
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const verdict = await door.gate('content:comment')(request);
|
|
286
|
+
if (!verdict.accepted) return denialResponse(verdict);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Flip `enabled: false` and the gate closes, no code change. The port is
|
|
290
|
+
engine-agnostic: a paid `EngineAuthorizer` (Permify / OpenFGA sidecar) for
|
|
291
|
+
granular relationship/attribute rules drops into the same slot with the same
|
|
292
|
+
`authorize(token, gateId, ctx)` shape. The demo and free tier need no engine.
|
|
293
|
+
|
|
294
|
+
### Stateful half: ledger, blocklist, reputation report-back
|
|
295
|
+
|
|
296
|
+
`verifyAgent` / `aitGate` are stateless verdict machines. A self-hosting platform
|
|
297
|
+
also needs **state it owns**: a record of who showed up, a runtime block list, and
|
|
298
|
+
a way to report a bad actor onward. These three modules add that, all zero-infra —
|
|
299
|
+
the platform runs the stores in its OWN store (default in-memory; plug in D1 /
|
|
300
|
+
SQLite / Postgres via a documented adapter shape).
|
|
301
|
+
|
|
302
|
+
```js
|
|
303
|
+
import { aitGate, AccessLedger, loggedGate } from 'axis-platform-sdk';
|
|
304
|
+
|
|
305
|
+
const ledger = new AccessLedger(); // default in-memory store
|
|
306
|
+
const gate = loggedGate(aitGate({ audience }), ledger, { audience });
|
|
307
|
+
|
|
308
|
+
const verdict = await gate(request); // every verdict is logged
|
|
309
|
+
await ledger.recent({ limit: 25 }); // newest-first arrivals
|
|
310
|
+
await ledger.byOperator('axis:acme:op'); // arrivals from one operator
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Each entry records `{ agent_id, operator_id, created_at, tier, delegation_valid,
|
|
314
|
+
effective_scope, gate_id, requested_action, display_name, decision, reason,
|
|
315
|
+
audience }` — the same shape the cloud-hosted version uses for its `arrivals`
|
|
316
|
+
record, so the SDK and the cloud product share one arrival definition. `decision`
|
|
317
|
+
is `auto_allow | denied |
|
|
318
|
+
held | approved | booted`; `created_at` is epoch ms. Only the trustworthy
|
|
319
|
+
`effective_scope` is recorded, never the AIT's self-declared scope. A ledger write
|
|
320
|
+
failure never changes the verdict.
|
|
321
|
+
|
|
322
|
+
```js
|
|
323
|
+
import { Blocklist, verifyAgent } from 'axis-platform-sdk';
|
|
324
|
+
|
|
325
|
+
const blocklist = new Blocklist();
|
|
326
|
+
await blocklist.blockAgent('axis:acme:bot', 'spammed'); // agent-level
|
|
327
|
+
await blocklist.blockOperator('axis:bad:op', 'whole op'); // operator-level
|
|
328
|
+
|
|
329
|
+
let verdict = await verifyAgent(token, {
|
|
330
|
+
audience,
|
|
331
|
+
blockedOperators: [...staticBlocked, ...(await blocklist.blockedOperatorIds())],
|
|
332
|
+
});
|
|
333
|
+
verdict = await blocklist.checkVerdict(verdict); // flips to denied if agent/op blocked
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
When you boot an agent, you know something the network doesn't. `reportFlag`
|
|
337
|
+
builds a protocol-shaped Trust Attestation (AXIS Layer 3; SPEC §4.5), signs it
|
|
338
|
+
with the platform's own Ed25519 key (generated + persisted on first use via
|
|
339
|
+
`getPlatformKey`, WebCrypto), and POSTs it to a configurable reputation index.
|
|
340
|
+
Report-back is **OFF by default** — unconfigured, it's a graceful no-op. The
|
|
341
|
+
reputation index is a separate, future, commercial service — NOT the canonical
|
|
342
|
+
registry (which stays identity-only). See `examples/bouncer-worker.js` for a
|
|
343
|
+
reference admin surface over the ledger + blocklist, and `templates/` for the
|
|
344
|
+
deployable starters.
|
|
345
|
+
|
|
346
|
+
### Single source of truth: the SDK is the port, the cloud-hosted version is the adapter
|
|
347
|
+
|
|
348
|
+
| SDK (this package, the port) | Cloud-hosted version (the D1-backed product adapter) |
|
|
349
|
+
| ----------------------------------- | ----------------------------------------------- |
|
|
350
|
+
| `AccessLedger` + `recordEntry` | `arrivals` table + `recordArrival()` |
|
|
351
|
+
| `Blocklist` (operator-level) | `operator_blocks` table + `blockedOperators()` |
|
|
352
|
+
| `SwitchAuthorizer` `policy` | `door_policy` table (serialized policy) |
|
|
353
|
+
| `Blocklist` agent-level *(superset)* | *(not yet — an additive `agent_blocks` table)* |
|
|
354
|
+
| `reportFlag` / reputation emit *(new)* | *(not yet — the open emit half is here)* |
|
|
355
|
+
|
|
356
|
+
The entry/meta shapes are deliberately byte-compatible with the cloud-hosted
|
|
357
|
+
version's columns so there is **one** arrival/block record across the SDK and the
|
|
358
|
+
cloud product — fold in, don't duplicate. A platform that needs its own store
|
|
359
|
+
implements the documented adapter shape; the cloud-hosted version (in alpha) is the
|
|
360
|
+
worked example of doing exactly that over Cloudflare D1.
|
|
361
|
+
|
|
362
|
+
## Install
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
npm install axis-platform-sdk
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Zero dependencies. Node 20+, Cloudflare Workers, modern browsers. Ships TypeScript
|
|
369
|
+
declarations (the package is authored in plain JS).
|
|
370
|
+
|
|
371
|
+
## Staying up to date
|
|
372
|
+
|
|
373
|
+
The self-host path needs no account, so we don't know who's running it (by design)
|
|
374
|
+
and can't push you notices. Updates are **pull-based**:
|
|
375
|
+
|
|
376
|
+
- **Versioning is semver.** Patch/minor releases are backward-compatible; anything
|
|
377
|
+
breaking is a major. Pin a caret range (`^`) and you get safe updates.
|
|
378
|
+
- **Watch the channel:** [GitHub Releases](https://github.com/MachinesOfDesire/axis-platform-sdk/releases)
|
|
379
|
+
and the [CHANGELOG](CHANGELOG.md) are the source of truth for what changed. Use
|
|
380
|
+
Dependabot or Renovate to get an automatic PR when a new version ships.
|
|
381
|
+
- **The verification protocol is versioned too.** The registry's `/verify` contract
|
|
382
|
+
is backward-compatible within a protocol major; deprecations are announced in
|
|
383
|
+
Releases ahead of removal.
|
|
384
|
+
|
|
385
|
+
An opt-in updates list for platform integrators (security and breaking-change
|
|
386
|
+
notices) is planned. Until then, watching the repo is the way.
|
|
387
|
+
|
|
388
|
+
## License
|
|
389
|
+
|
|
390
|
+
Apache-2.0. © Kipple Labs, Inc.
|