axis-platform-sdk 0.2.0 → 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 -97
- 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 -48
- package/src/authorizer.d.ts +1 -0
- package/src/authorizer.js +101 -101
- package/src/blocklist.d.ts +9 -0
- package/src/blocklist.js +183 -183
- package/src/express.d.ts +30 -0
- package/src/express.js +68 -0
- package/src/gate.d.ts +1 -0
- package/src/index.d.ts +288 -0
- package/src/ledger.d.ts +10 -0
- package/src/ledger.js +170 -171
- package/src/reportback.d.ts +15 -0
- package/src/scope.d.ts +1 -0
- 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
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Case study: gating comments on a live news site
|
|
2
|
+
|
|
3
|
+
> **This is a case study — an example of using `axis-platform-sdk`, not the
|
|
4
|
+
> product.** The product is the SDK and the drop-in starters in
|
|
5
|
+
> [`templates/`](templates/). This page shows what one real integration looks
|
|
6
|
+
> like, so you can picture your own.
|
|
7
|
+
|
|
8
|
+
## The platform
|
|
9
|
+
|
|
10
|
+
**Offworld News** is a (fictional) news site that wanted to let AI agents post
|
|
11
|
+
comments — but only agents with a real, accountable identity and explicit
|
|
12
|
+
permission to comment, not anonymous bots. Classic bouncer problem: an API key
|
|
13
|
+
can't say *which operator stands behind this agent* or *whether this agent was
|
|
14
|
+
actually allowed to comment*, and you can't revoke one bad agent without
|
|
15
|
+
disrupting everyone.
|
|
16
|
+
|
|
17
|
+
## The integration
|
|
18
|
+
|
|
19
|
+
The comment endpoint is wrapped exactly the way the starters are:
|
|
20
|
+
|
|
21
|
+
1. **Published a door policy** at `/.well-known/axis-access` declaring the
|
|
22
|
+
platform's audience and that commenting requires the standard
|
|
23
|
+
`content:comment` scope at `email` tier or above.
|
|
24
|
+
2. **Gated the accept-comment path** with the SDK: pull the agent's AIT off the
|
|
25
|
+
request → verify against the public registry → check it holds
|
|
26
|
+
`content:comment` against the trustworthy `effective_scope` → accept or bounce.
|
|
27
|
+
3. **Kept the bouncer state** — an arrivals ledger and a runtime blocklist — using
|
|
28
|
+
the same record shapes the SDK defines, so a moderator can see who commented
|
|
29
|
+
and boot a bad agent without a redeploy.
|
|
30
|
+
|
|
31
|
+
The adapter depends on the **published** package (`axis-platform-sdk@^0.2.1`) — it
|
|
32
|
+
does not vendor or fork the engine. That's the whole point of shipping the SDK as
|
|
33
|
+
a product: a real platform consumes it like any other dependency.
|
|
34
|
+
|
|
35
|
+
## Two things this case study illustrates
|
|
36
|
+
|
|
37
|
+
- **Standard scopes matter.** The integration uses `content:comment` (the standard
|
|
38
|
+
AXIS scope for commenting), not a bespoke `comments:write`. A non-standard scope
|
|
39
|
+
would fail to match the agent's proven `effective_scope` and the agent would be
|
|
40
|
+
denied. When you pick `requireScopes`, use the standard vocabulary where one
|
|
41
|
+
fits.
|
|
42
|
+
- **Self-host is the product; the cloud-hosted version is the same engine.**
|
|
43
|
+
Offworld runs the gate on its own infrastructure (a Cloudflare Worker) — that is
|
|
44
|
+
the shipping, day-one path. It also pilots the in-development cloud-hosted version
|
|
45
|
+
for hosted arrival history and a moderator console. Because the SDK is the port
|
|
46
|
+
and the cloud-hosted version is being built as an adapter over it, those are the
|
|
47
|
+
same verification path — not a different system. For everyone else today,
|
|
48
|
+
self-host is the whole story; the cloud-hosted version is in alpha (Q3 2026).
|
|
49
|
+
|
|
50
|
+
## What "the product" is, again
|
|
51
|
+
|
|
52
|
+
If you run a platform and want this, you do **not** start from Offworld's code.
|
|
53
|
+
You start from [`templates/node-express/`](templates/node-express/) or
|
|
54
|
+
[`templates/cloudflare-worker/`](templates/cloudflare-worker/), follow
|
|
55
|
+
[QUICKSTART.md](QUICKSTART.md), and you have your own bouncer in about ten
|
|
56
|
+
minutes. Offworld is just proof that the shape works on a real site.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,97 +1,179 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to `axis-platform-sdk`.
|
|
4
|
-
|
|
5
|
-
## [0.
|
|
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
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `axis-platform-sdk`.
|
|
4
|
+
|
|
5
|
+
## [0.3.0] — Unreleased
|
|
6
|
+
|
|
7
|
+
Productization pass: repackages the published SDK as a free, drop-in **product
|
|
8
|
+
for platforms** — the platform-side equivalent of the AXIS Prime MCP's tester
|
|
9
|
+
packaging — and adds a first-class Express/Connect adapter. The engine
|
|
10
|
+
(`verify` / `authorizer` / `ledger` / `blocklist` / `reportback`) is unchanged.
|
|
11
|
+
|
|
12
|
+
### Added — Express/Connect middleware (new subpath export)
|
|
13
|
+
|
|
14
|
+
- **`axis-platform-sdk/express`** exports **`axisGate(opts)`** (and `extractToken`)
|
|
15
|
+
— the same verify-and-bounce logic as `aitGate`, but as Express/Connect
|
|
16
|
+
middleware `(req, res, next)`. On accept it sets `req.axis` and calls `next()`;
|
|
17
|
+
on deny it responds `401` (no token) / `403` (policy) / `503` (unexpected verify
|
|
18
|
+
error) with `{ error, message }`. Zero-dependency: it imports nothing from
|
|
19
|
+
Express, so it also runs on Connect, restify, and bare `http`. Ships
|
|
20
|
+
`src/express.d.ts` and an `exports` map entry. 6 new unit tests (suite now 46).
|
|
21
|
+
This is the first-class home for the Node drop-in (previously a copy-paste
|
|
22
|
+
template file).
|
|
23
|
+
|
|
24
|
+
### Added — adoption tiers, badge kit, forward-compat docs
|
|
25
|
+
|
|
26
|
+
- **Adoption tiers (A/B/C)** documented in the README: identity acceptance →
|
|
27
|
+
access policy → scope/tier enforcement, all the same `verifyAgent` call with more
|
|
28
|
+
options set.
|
|
29
|
+
- **"Verified by AXIS" badge kit** (`badges/`): three zero-dependency SVGs (light,
|
|
30
|
+
dark, compact) for platforms that show verification status on agent-authored
|
|
31
|
+
content, plus a usage README. Shipped in the package `files`.
|
|
32
|
+
- **Forward-compatibility note** on provenance gating: account-age / signup-method /
|
|
33
|
+
abuse-flag signals are protocol-defined but not yet registry-exposed; when they
|
|
34
|
+
ship they arrive as additive optional `verifyAgent` options + additive verdict
|
|
35
|
+
fields — no breaking change for existing integrations.
|
|
36
|
+
- **"Staying up to date"** section: self-host is pull-based (semver + GitHub
|
|
37
|
+
Releases + CHANGELOG; Dependabot/Renovate); an opt-in integrator updates list is
|
|
38
|
+
planned.
|
|
39
|
+
|
|
40
|
+
### Added — product wrapper (docs + drop-in starters)
|
|
41
|
+
|
|
42
|
+
- **README rewritten as a product pitch** ("let verified agents into your
|
|
43
|
+
platform; boot the bad ones; free, drop-in, no account required"). The full API
|
|
44
|
+
reference is preserved lower in the same file. Headlines the free, standalone,
|
|
45
|
+
registry-only path; positions the cloud-hosted version (in alpha, Q3 2026) as an
|
|
46
|
+
optional upgrade, not a requirement.
|
|
47
|
+
- **`QUICKSTART.md`** — "gate your platform in 10 minutes": pick a starter →
|
|
48
|
+
decide audience + scope → drop in the gate → publish `/.well-known/axis-access`
|
|
49
|
+
→ test a deny → test an admit.
|
|
50
|
+
- **`templates/node-express/`** — a complete, runnable Express drop-in: a ~30-line
|
|
51
|
+
`axisGate(...)` middleware (`axis-gate.js`), a full worked server with door
|
|
52
|
+
policy + arrivals ledger + runtime blocklist + `/admin` console, and a `smoke`
|
|
53
|
+
test that asserts the deny paths against the live registry (verified passing).
|
|
54
|
+
- **`templates/cloudflare-worker/`** — the same as a deployable Worker
|
|
55
|
+
(`wrangler dev` / `wrangler deploy`), promoting `examples/bouncer-worker.js`
|
|
56
|
+
into a clean starter with `wrangler.toml` + `package.json`.
|
|
57
|
+
- **`CASE-STUDY-offworld.md`** — the Offworld News comment-gating integration,
|
|
58
|
+
explicitly labeled as a case study (an example of using the tool), not the
|
|
59
|
+
product.
|
|
60
|
+
|
|
61
|
+
### Notes
|
|
62
|
+
|
|
63
|
+
- Standard scope vocabulary: starters and docs use `content:comment` (the standard
|
|
64
|
+
AXIS scope for commenting), not the older non-standard `comments:write`.
|
|
65
|
+
- `examples/` are left in place as teaching references; `templates/` are the
|
|
66
|
+
copy-paste starting point. `templates/` is added to the published `files` list.
|
|
67
|
+
- The only code change is the additive `src/express.js` adapter + its `.d.ts` and
|
|
68
|
+
`exports` entry; the existing engine modules are untouched. The 40 prior tests
|
|
69
|
+
are unchanged (suite now 46 with the Express tests). The previously published
|
|
70
|
+
surface remains fully backward-compatible — `/express` is purely additive.
|
|
71
|
+
|
|
72
|
+
## [0.2.1] — 2026-06-25
|
|
73
|
+
|
|
74
|
+
### Added — TypeScript declarations
|
|
75
|
+
|
|
76
|
+
- The package now ships `.d.ts` types (it stays authored in plain JS). A
|
|
77
|
+
complete `src/index.d.ts` covers the full main-entry surface (verify,
|
|
78
|
+
authorizer, scope, gate, client, ledger, blocklist, reportback); each subpath
|
|
79
|
+
export (`./scope`, `./gate`, `./authorizer`, `./ledger`, `./blocklist`,
|
|
80
|
+
`./reportback`) re-exports its slice. `package.json` exposes them via a
|
|
81
|
+
top-level `types` field and per-subpath `types` conditions in `exports`.
|
|
82
|
+
- This lets TypeScript consumers (e.g. the cloud-hosted version, swapping its
|
|
83
|
+
vendored copy for the npm dependency) drop their hand-maintained declaration and get
|
|
84
|
+
types from the package. Verified by a clean-room install + strict `tsc` of a
|
|
85
|
+
consumer importing from the root and a subpath.
|
|
86
|
+
|
|
87
|
+
## [0.2.0] — 2026-06-25
|
|
88
|
+
|
|
89
|
+
First npm publish + public repo. Adds the stateful half (ledger, blocklist,
|
|
90
|
+
reputation report-back) and reconciles it with the cloud-hosted version so the
|
|
91
|
+
SDK and the cloud product share one arrival/block shape.
|
|
92
|
+
|
|
93
|
+
### Added — the stateful half (ledger, blocklist, reputation report-back)
|
|
94
|
+
|
|
95
|
+
- **`AccessLedger` (`src/ledger.js`)** — the platform's "who showed up" record.
|
|
96
|
+
Logs every verdict to a pluggable store (default in-memory `MemoryLedgerStore`;
|
|
97
|
+
documented adapter shape for D1 / SQLite / Postgres). `recent()` / `byOperator()`
|
|
98
|
+
query helpers. `loggedGate(gate, ledger, fields)` wraps any gate so verdicts are
|
|
99
|
+
logged as a side effect (a store failure never changes the verdict). Only the
|
|
100
|
+
trustworthy `effective_scope` is recorded.
|
|
101
|
+
- **`Blocklist` (`src/blocklist.js`)** — a runtime, stateful block list over the
|
|
102
|
+
same adapter shape. Blocks by `operator_id` AND by `agent_id` (agent-level
|
|
103
|
+
blocking is the SDK's superset over the cloud-hosted version's operator-only `operator_blocks`).
|
|
104
|
+
`blockedOperatorIds()` feeds verifyAgent's `blockedOperators`; `checkVerdict()`
|
|
105
|
+
enforces agent-level blocks post-verify (needs the resolved agent_id).
|
|
106
|
+
`gatedWithBlocklist(gate, blocklist)` wraps a gate.
|
|
107
|
+
|
|
108
|
+
### Reconciled with the cloud-hosted version (single source of truth, no duplication)
|
|
109
|
+
|
|
110
|
+
- The ledger entry shape is now byte-compatible with the cloud-hosted version's `ArrivalRecord`
|
|
111
|
+
/ `arrivals` columns: carries `tier`, `delegation_valid`, `gate_id`,
|
|
112
|
+
`requested_action`, `display_name`; `created_at` is epoch ms (was an ISO `ts`);
|
|
113
|
+
`decision` uses the `auto_allow | denied | held | approved | booted` vocabulary
|
|
114
|
+
(was `accepted | denied`), with the manual-review states available via a
|
|
115
|
+
`recordEntry(..., { decision })` override.
|
|
116
|
+
- Blocklist meta is `{ reason, created_at }` (epoch ms), matching `operator_blocks`.
|
|
117
|
+
- The SDK is positioned as the **port + in-memory default**; the cloud-hosted
|
|
118
|
+
version is the canonical **D1-backed adapter**. README documents the mapping
|
|
119
|
+
table. The SDK ships the genuinely-new pieces the cloud-hosted version lacks
|
|
120
|
+
(agent-level blocking, reputation emit); the cloud-hosted version keeps its own
|
|
121
|
+
D1 state layer and, post-publish, depends on this package instead of vendoring it.
|
|
122
|
+
- **Reputation report-back (`src/reportback.js`)** — `reportFlag()` builds a
|
|
123
|
+
protocol-shaped negative **Trust Attestation** (AXIS Layer 3; SPEC §4.5),
|
|
124
|
+
signs it with the platform's own Ed25519 key (`getPlatformKey()`, WebCrypto,
|
|
125
|
+
generated + persisted via a pluggable key store), and POSTs `{ attestation,
|
|
126
|
+
platform_public_key, signature }` to a configurable `reputationUrl`. OFF by
|
|
127
|
+
default — unconfigured is a graceful no-op (never throws). `blockAndReport()`
|
|
128
|
+
blocks locally + reports. `buildAttestation` / `signAttestation` /
|
|
129
|
+
`verifyAttestation` exposed. Zero-dep (no Buffer; WebCrypto + inline base64url
|
|
130
|
+
+ inline JCS, byte-for-byte matching axis-protocol-sdk's signing convention).
|
|
131
|
+
- **`examples/bouncer-worker.js`** — reference stateful bouncer: a Worker admin
|
|
132
|
+
over the ledger + blocklist (`/admin/arrivals` enriched for display,
|
|
133
|
+
`/admin/boot` = block + report, plus a tiny HTML console). A reference, not a
|
|
134
|
+
product; in-memory stores.
|
|
135
|
+
|
|
136
|
+
### Notes
|
|
137
|
+
|
|
138
|
+
- The reputation index is a SEPARATE, future, commercial service — NOT the
|
|
139
|
+
canonical registry, which stays identity-only (Layer 1 + Layer 2). The
|
|
140
|
+
`axis-reputation` stub receiver accepts-and-discards until the index is built.
|
|
141
|
+
- New tests for ledger / blocklist / reportback + the Door-compatible shape.
|
|
142
|
+
Full suite: 40 passing (`node --test`).
|
|
143
|
+
|
|
144
|
+
## [0.1.0] — 2026-06-16 (unreleased)
|
|
145
|
+
|
|
146
|
+
First cut of the platform/verifier ("bouncer") side of AXIS.
|
|
147
|
+
|
|
148
|
+
### Added
|
|
149
|
+
|
|
150
|
+
- **`verifyAgent(token, opts)`** — verifies an AIT against the registry
|
|
151
|
+
(signature + revocation + delegation chain, all server-side) and applies a
|
|
152
|
+
platform's policy (audience match, required scopes against the trustworthy
|
|
153
|
+
`effective_scope`, blocked/approved operators, minimum verification tier).
|
|
154
|
+
Returns one structured verdict.
|
|
155
|
+
- **`aitGate(opts)` / `extractToken()` / `denialResponse()`** — a drop-in
|
|
156
|
+
request gate for Cloudflare Workers (and Request-like objects). Pulls the AIT
|
|
157
|
+
from `Authorization: Bearer`, `X-AXIS-Token`, or `?ait=`.
|
|
158
|
+
- **`SwitchAuthorizer`** — the free-tier gate engine and the first implementation
|
|
159
|
+
of the Authorizer port. Config-driven on/off gates with optional minimum tier,
|
|
160
|
+
required scopes, and operator allow/block lists. Its `policy` object is what a
|
|
161
|
+
"Door policy" screen edits and saves. The port is engine-agnostic so a paid
|
|
162
|
+
`EngineAuthorizer` (Permify / OpenFGA sidecar) can drop into the same slot.
|
|
163
|
+
- **`scopeCovers` / `coversAll`** — AXIS scope matcher, ported verbatim from the
|
|
164
|
+
operator-side gateway so both sides agree on scope semantics.
|
|
165
|
+
- **`enrich()`**, **`loadAccessPolicy()`**, **`decodeAitPayload()`** helpers.
|
|
166
|
+
- Worked example: `examples/toy-platform-worker.js` (a bouncer comments service
|
|
167
|
+
gated by a `SwitchAuthorizer`).
|
|
168
|
+
|
|
169
|
+
### Tested
|
|
170
|
+
|
|
171
|
+
- 15 unit tests (`node --test`) across scope matching, the verify verdict
|
|
172
|
+
matrix, and the SwitchAuthorizer gate logic. `loadAccessPolicy` additionally
|
|
173
|
+
verified live against `registry.axisprime.ai`.
|
|
174
|
+
|
|
175
|
+
### Notes
|
|
176
|
+
|
|
177
|
+
- Zero runtime dependencies; runs in Node 20+, Cloudflare Workers, browsers.
|
|
178
|
+
- Identity verification is fixed/core; only the authorization decision is
|
|
179
|
+
pluggable. The demo and the free tier need no external policy engine.
|
package/QUICKSTART.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Gate your platform in 10 minutes
|
|
2
|
+
|
|
3
|
+
Goal: an endpoint on your platform that only accepts **AXIS-verified agents** with
|
|
4
|
+
the right permission, turns everyone else away with a clear reason, and lets you
|
|
5
|
+
boot a bad actor without a redeploy.
|
|
6
|
+
|
|
7
|
+
You need: Node 20+ (or a Cloudflare Workers project). You do **not** need an
|
|
8
|
+
account with us, a Cloudflare account (unless you choose the Worker path), a
|
|
9
|
+
database, or any hosted service. The free path is one outbound HTTPS call to the
|
|
10
|
+
public AXIS registry.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 0. Pick a starter (1 min)
|
|
15
|
+
|
|
16
|
+
Copy the folder that matches your stack and work inside it:
|
|
17
|
+
|
|
18
|
+
- **Node / Express** → [`templates/node-express/`](templates/node-express/)
|
|
19
|
+
- **Cloudflare Workers** → [`templates/cloudflare-worker/`](templates/cloudflare-worker/)
|
|
20
|
+
|
|
21
|
+
Already have an app? Skip the copy and just `npm install axis-platform-sdk`, then
|
|
22
|
+
follow the inline code below.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
npm install axis-platform-sdk # zero dependencies
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 1. Decide two things (1 min)
|
|
31
|
+
|
|
32
|
+
- **Your audience** — a stable id for *your* platform, e.g. `comments.mysite.com`.
|
|
33
|
+
Agents address their token to this; you reject tokens addressed to anyone else.
|
|
34
|
+
- **The scope an agent must hold** — e.g. `content:comment`. Use a standard AXIS
|
|
35
|
+
scope where one fits (`content:comment` for commenting). This is checked against
|
|
36
|
+
the agent's **proven** permission, not what its token claims.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 2. Drop the gate in front of your action (3 min)
|
|
41
|
+
|
|
42
|
+
**Express:**
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
import express from 'express';
|
|
46
|
+
import { axisGate } from 'axis-platform-sdk/express'; // first-class middleware export
|
|
47
|
+
|
|
48
|
+
const app = express();
|
|
49
|
+
app.use(express.json());
|
|
50
|
+
|
|
51
|
+
app.post('/comments',
|
|
52
|
+
axisGate({ audience: 'comments.mysite.com', requireScopes: ['content:comment'] }),
|
|
53
|
+
(req, res) => {
|
|
54
|
+
// req.axis.agent_id is a VERIFIED agent id. Run your real handler.
|
|
55
|
+
res.json({ ok: true, by: req.axis.agent_id });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
app.listen(8787);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Cloudflare Worker:**
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import { aitGate, denialResponse } from 'axis-platform-sdk';
|
|
65
|
+
|
|
66
|
+
const gate = aitGate({ audience: 'comments.mysite.com', requireScopes: ['content:comment'] });
|
|
67
|
+
|
|
68
|
+
export default {
|
|
69
|
+
async fetch(request) {
|
|
70
|
+
const verdict = await gate(request);
|
|
71
|
+
if (!verdict.accepted) return denialResponse(verdict);
|
|
72
|
+
return Response.json({ ok: true, by: verdict.agent_id });
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The gate pulls the token from `Authorization: Bearer <ait>`, `X-AXIS-Token`, or
|
|
78
|
+
`?ait=`, verifies it against the registry, applies your policy, and either runs
|
|
79
|
+
your handler or returns `401` (no token) / `403` (bounced) with a real reason.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 3. Publish your door policy (2 min)
|
|
84
|
+
|
|
85
|
+
Tell agents (and their operators) what your platform requires by serving a small
|
|
86
|
+
JSON document at **`/.well-known/axis-access`**:
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
// GET /.well-known/axis-access
|
|
90
|
+
{
|
|
91
|
+
"axis_version": "0.3",
|
|
92
|
+
"platform_id": "comments.mysite.com",
|
|
93
|
+
"audience": "comments.mysite.com",
|
|
94
|
+
"access_policy": {
|
|
95
|
+
"minimum_verification_level": "email",
|
|
96
|
+
"required_scopes": ["content:comment"],
|
|
97
|
+
"allow_unverified": false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Both starters already serve this. An agent's tooling can read it with
|
|
103
|
+
`loadAccessPolicy('https://comments.mysite.com')` to know how to get in. (For a
|
|
104
|
+
live example, `curl https://registry.axisprime.ai/.well-known/axis-access`.)
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 4. Test the deny path — right now, no agent needed (1 min)
|
|
109
|
+
|
|
110
|
+
A request with no identity must be turned away:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
curl -i -X POST localhost:8787/comments -H 'content-type: application/json' -d '{"text":"hi"}'
|
|
114
|
+
# HTTP/1.1 401 Unauthorized
|
|
115
|
+
# {"error":"no_token","message":"No AIT presented"}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The Express starter automates this:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
npm run smoke
|
|
122
|
+
# PASS — no AIT -> 401 no_token
|
|
123
|
+
# PASS — invalid AIT -> 403 denied
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If you see those, your bouncer is live and refusing unidentified traffic. That's
|
|
127
|
+
the free path working end-to-end against the public registry.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 5. Test the accept path — let a real agent in (2 min)
|
|
132
|
+
|
|
133
|
+
To see a `200`, you need an agent that has actually been **delegated**
|
|
134
|
+
`content:comment` by its operator and presents a token addressed to your
|
|
135
|
+
`audience`. Agents get their identity from the
|
|
136
|
+
**[AXIS Prime MCP](https://github.com/MachinesOfDesire/axis-mcp)** (the agent
|
|
137
|
+
side):
|
|
138
|
+
|
|
139
|
+
1. The agent onboards via the MCP and registers with an operator.
|
|
140
|
+
2. The operator **grants** it `content:comment` (`axis grant content:comment`).
|
|
141
|
+
3. The agent mints an AIT with `aud = comments.mysite.com` and presents it:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
curl -X POST https://comments.mysite.com/comments \
|
|
145
|
+
-H "Authorization: Bearer <the-agents-AIT>" \
|
|
146
|
+
-H 'content-type: application/json' \
|
|
147
|
+
-d '{"text":"hello from a verified agent"}'
|
|
148
|
+
# {"ok":true,"posted_by":"axis:acme:bot","operator":"axis:acme:op","scope":["content:comment"], ... }
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
In the starter's `/admin` console, that arrival turns **green** — and you can
|
|
152
|
+
**boot** it (block + optionally report) with one click if it misbehaves.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Where to go next
|
|
157
|
+
|
|
158
|
+
- **Require stronger identity:** add `minTier: 'domain'` (or `verified`,
|
|
159
|
+
`kyb_organization`) to the gate to demand domain- or KYB-verified operators.
|
|
160
|
+
- **Persist arrivals + blocks:** the in-memory stores reset on restart. Implement
|
|
161
|
+
the store adapter shape (see `ledger.js` / `blocklist.js` in the SDK) against
|
|
162
|
+
your database. The record shape matches the cloud-hosted version, so moving to
|
|
163
|
+
it later won't mean reshaping data.
|
|
164
|
+
- **Turn on reputation report-back:** set a `reputationUrl` so booting a bad agent
|
|
165
|
+
emits a signed Trust Attestation onward. Off by default.
|
|
166
|
+
- **Don't want to host the console + state yourself?** A cloud-hosted version is in
|
|
167
|
+
alpha testing (planned for Q3 2026) over this exact engine. For now, self-host is
|
|
168
|
+
the way — and it's the whole product.
|