@nullsquare/agent-authority 0.4.0 → 0.4.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 +10 -2
- package/ROADMAP.md +7 -2
- package/docs/announcement-draft.md +7 -1
- package/docs/assets/agent-authority-cover-editorial-v2.png +0 -0
- package/docs/evidence.md +99 -0
- package/docs/live-google-validation.md +100 -0
- package/docs/npm-release.md +10 -0
- package/examples/live-google-cross-provider.js +256 -0
- package/package.json +5 -3
- package/src/providers/google.js +201 -0
- package/docs/assets/agent-authority-cover.svg +0 -41
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
# Agent Authority
|
|
6
6
|
|
|
@@ -10,10 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
[Task Leases](docs/task-leases.md) · [Validate](docs/validation.md) · [Integration contract](docs/integration-contract.md) · [CLI](docs/cli.md) · [Architecture](docs/architecture.md) · [Roadmap](ROADMAP.md) · [Contributing](CONTRIBUTING.md)
|
|
12
12
|
|
|
13
|
-
> **Status: public pre-alpha / v0.4
|
|
13
|
+
> **Status: public pre-alpha / v0.4.0 Developer Preview.** Published on npm as `@nullsquare/agent-authority`. The repository has a working policy runtime, protocol-neutral guard, Task Lease prototype with provenance-bound derived facts, approvals, revocation, idempotency, credential isolation, MCP v2 gateway, GitHub brokered execution, CI and CodeQL. It is not production-ready yet.
|
|
14
14
|
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Requires Node.js 20+.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @nullsquare/agent-authority
|
|
23
|
+
```
|
|
24
|
+
|
|
17
25
|
## The problem
|
|
18
26
|
|
|
19
27
|
AI agents increasingly receive broad provider permissions so they can complete narrow human tasks.
|
package/ROADMAP.md
CHANGED
|
@@ -55,10 +55,15 @@ never silently grow
|
|
|
55
55
|
- [x] independent task-lease expiry
|
|
56
56
|
- [x] task-lease ID/hash in receipts
|
|
57
57
|
- [x] self-contained cross-system demo: Gmail-thread fact -> Calendar attendee
|
|
58
|
-
- [
|
|
58
|
+
- [x] reusable Google REST adapter for Gmail thread reads and Calendar event mutations
|
|
59
|
+
- [x] adversarial Gmail -> Calendar test proving unrelated and post-completion effects never invoke provider callbacks
|
|
60
|
+
- [x] real connected-account smoke: Gmail self-test fixture -> exact attendee -> temporary Calendar event -> cleanup
|
|
61
|
+
- [ ] rerun the same proof as a public GitHub Actions job after repository Google OAuth secrets are configured
|
|
59
62
|
|
|
60
63
|
**Success criterion:** a real multi-step agent workflow discovers a resource during an authorized read and can use exactly that resource in a later side effect, while an unrelated resource is technically blocked without requiring approval for every normal task step.
|
|
61
64
|
|
|
65
|
+
The core cross-provider behavior is now implemented and exercised against connected Gmail and Calendar accounts. Public Actions reproducibility remains the final M1 evidence gate because GitHub cannot reuse an interactive ChatGPT connector credential; the manual workflow is committed and expects a refresh token stored only as repository secrets.
|
|
66
|
+
|
|
62
67
|
## M2 — Durable task execution
|
|
63
68
|
|
|
64
69
|
Build only what the real M1 workflow proves necessary.
|
|
@@ -117,7 +122,7 @@ Only after the task-bound enforcement model is validated.
|
|
|
117
122
|
- [ ] framework integration examples
|
|
118
123
|
- [ ] `good first issue` tasks based on real mappings/tests
|
|
119
124
|
- [ ] independent contributor implementation of one adapter
|
|
120
|
-
- [
|
|
125
|
+
- [x] release packaging and npm publication
|
|
121
126
|
- [ ] documentation site only when README/docs become too large
|
|
122
127
|
|
|
123
128
|
## M7 — Standards interoperability
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Announcement draft —
|
|
1
|
+
# Announcement draft — v0.4.0 Developer Preview
|
|
2
2
|
|
|
3
3
|
NullSquare is open-sourcing **Agent Authority**, an experimental runtime for task-bounded AI agent side effects.
|
|
4
4
|
|
|
@@ -8,6 +8,12 @@ A Task Lease can follow resources discovered through authorized execution withou
|
|
|
8
8
|
|
|
9
9
|
The developer preview also includes a Vercel AI SDK integration that wraps ordinary `ToolLoopAgent` tools at their existing `execute` boundary.
|
|
10
10
|
|
|
11
|
+
Install the published developer preview with:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @nullsquare/agent-authority@0.4.0
|
|
15
|
+
```
|
|
16
|
+
|
|
11
17
|
**Give your agent a task, not your account.**
|
|
12
18
|
|
|
13
19
|
This is a developer preview, not a claim that NullSquare invented task-scoped authorization and not a production-ready security control. The repository documents the current trust assumptions and bypass boundaries explicitly.
|
|
Binary file
|
package/docs/evidence.md
CHANGED
|
@@ -10,6 +10,102 @@ This page records the strongest properties the repository currently demonstrates
|
|
|
10
10
|
|
|
11
11
|
The guarantee applies to effects that actually pass through the Agent Authority enforcement boundary. A separate unguarded provider path is outside this guarantee.
|
|
12
12
|
|
|
13
|
+
## Cross-provider derived authority — Gmail → Calendar
|
|
14
|
+
|
|
15
|
+
Agent Authority now includes a real Google provider mapping, an adversarial cross-provider test, a live validation script, and an opt-in GitHub Actions workflow.
|
|
16
|
+
|
|
17
|
+
The task shape is:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
Task root
|
|
21
|
+
one Gmail thread
|
|
22
|
+
|
|
|
23
|
+
v
|
|
24
|
+
ALLOW Gmail thread.read
|
|
25
|
+
|
|
|
26
|
+
v
|
|
27
|
+
discover sender email
|
|
28
|
+
|
|
|
29
|
+
v
|
|
30
|
+
derive fact: sender_email
|
|
31
|
+
|
|
|
32
|
+
v
|
|
33
|
+
ALLOW Calendar event.create
|
|
34
|
+
attendee == derived sender
|
|
35
|
+
|
|
|
36
|
+
+--> different attendee
|
|
37
|
+
| -> authority_delta_required
|
|
38
|
+
| -> Calendar mutation callback does not run
|
|
39
|
+
|
|
|
40
|
+
v
|
|
41
|
+
complete Task Lease
|
|
42
|
+
|
|
|
43
|
+
+--> same attendee again
|
|
44
|
+
-> task_lease_completed
|
|
45
|
+
-> Calendar mutation callback does not run
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Connected-account smoke — 2026-08-22
|
|
49
|
+
|
|
50
|
+
A controlled self-test was exercised against the connected NullSquare Gmail and Google Calendar accounts:
|
|
51
|
+
|
|
52
|
+
1. a temporary validation message was sent from the validation account to itself;
|
|
53
|
+
2. Gmail returned the self-test message with sender `null@nullsquare.net`;
|
|
54
|
+
3. one private, transparent Calendar event was created with exactly `null@nullsquare.net` as attendee;
|
|
55
|
+
4. the temporary Calendar event was deleted immediately after the proof;
|
|
56
|
+
5. the validation email was moved to Trash after cleanup.
|
|
57
|
+
|
|
58
|
+
No unrelated external person was invited. The smoke test establishes that the concrete Gmail and Calendar operations used by the validation are available and compatible with the intended data shape.
|
|
59
|
+
|
|
60
|
+
The live connected-account smoke is not presented as a public CI proof of the repository script. The Task Lease zero-call assertions are separately executable in `test/google-cross-provider.test.js`, and `.github/workflows/live-google-validation.yml` is provided for a rerunnable provider-backed proof once repository Google OAuth secrets are configured.
|
|
61
|
+
|
|
62
|
+
### Executable local/CI assertions
|
|
63
|
+
|
|
64
|
+
`test/google-cross-provider.test.js` proves deterministically that:
|
|
65
|
+
|
|
66
|
+
- the approved Gmail thread callback runs exactly once;
|
|
67
|
+
- its sender becomes a same-lease derived authority fact;
|
|
68
|
+
- one Calendar mutation for that sender runs exactly once;
|
|
69
|
+
- a different attendee produces `authority_delta_required` and the Calendar callback is not invoked;
|
|
70
|
+
- after lease completion, the previously valid attendee produces `task_lease_completed` and the Calendar callback is not invoked again.
|
|
71
|
+
|
|
72
|
+
`test/google-provider.test.js` separately validates the Google REST mappings, metadata-only Gmail sender extraction, Calendar attendee body, default `sendUpdates=none`, credential redaction, unsupported-action failure, and mutation classification.
|
|
73
|
+
|
|
74
|
+
### Public workflow
|
|
75
|
+
|
|
76
|
+
The manual workflow `Live Google cross-provider validation` mints a short-lived access token from repository secrets and runs `npm run demo:live-google`.
|
|
77
|
+
|
|
78
|
+
It expects:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
AA_GOOGLE_OAUTH_CLIENT_ID
|
|
82
|
+
AA_GOOGLE_OAUTH_CLIENT_SECRET
|
|
83
|
+
AA_GOOGLE_OAUTH_REFRESH_TOKEN
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The refresh token should be restricted to the practical Gmail read-only and Calendar event scopes documented in `docs/live-google-validation.md`.
|
|
87
|
+
|
|
88
|
+
The connected GitHub tool does not expose repository Actions-secret management, so those secrets are intentionally not installed automatically from this development session.
|
|
89
|
+
|
|
90
|
+
### What this proves
|
|
91
|
+
|
|
92
|
+
Taken together, the deterministic Task Lease test plus the controlled provider smoke show that:
|
|
93
|
+
|
|
94
|
+
- the Gmail → Calendar derived-authority shape is implemented, not just diagrammed;
|
|
95
|
+
- the exact attendee binding is enforced before the Calendar mutation callback;
|
|
96
|
+
- provider operations exist and work against real Gmail and Calendar accounts;
|
|
97
|
+
- cleanup can keep the live proof reversible and self-contained.
|
|
98
|
+
|
|
99
|
+
### What this does not prove yet
|
|
100
|
+
|
|
101
|
+
- a public GitHub Actions run has executed the full repository live script;
|
|
102
|
+
- host-side extraction of `sender_email` is cryptographically attested by Gmail;
|
|
103
|
+
- the agent cannot bypass Agent Authority if it independently holds a Google credential;
|
|
104
|
+
- Task Lease state is durable across process failure;
|
|
105
|
+
- the refresh-token setup is production credential onboarding.
|
|
106
|
+
|
|
107
|
+
See [Live Gmail → Calendar validation](live-google-validation.md).
|
|
108
|
+
|
|
13
109
|
## Live derived-authority mutation — GitHub
|
|
14
110
|
|
|
15
111
|
Public fixture: [issue #9](https://github.com/Null-Square/agent-authority/issues/9)
|
|
@@ -129,8 +225,11 @@ Current pull requests run:
|
|
|
129
225
|
- coverage;
|
|
130
226
|
- live GitHub read validation;
|
|
131
227
|
- live derived GitHub mutation validation for trusted in-repository branches;
|
|
228
|
+
- Google provider and cross-provider adversarial tests;
|
|
132
229
|
- CodeQL.
|
|
133
230
|
|
|
231
|
+
The live Google provider mutation workflow is manual because it requires repository-owned Google OAuth secrets. It should be added to the public evidence list after its first successful run.
|
|
232
|
+
|
|
134
233
|
## Evidence standard for new claims
|
|
135
234
|
|
|
136
235
|
A new security claim should ideally include all four:
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Live Gmail → Calendar validation
|
|
2
|
+
|
|
3
|
+
This validation turns the README's Gmail → Calendar Task Lease example into a real cross-provider proof.
|
|
4
|
+
|
|
5
|
+
The narrow claim is:
|
|
6
|
+
|
|
7
|
+
> A sender discovered from one authorized Gmail thread can become exact Task Lease authority for one Calendar event attendee, while a different attendee and any post-completion retry are blocked before the Calendar mutation callback runs.
|
|
8
|
+
|
|
9
|
+
It is intentionally not a general Google OAuth product or a production onboarding flow.
|
|
10
|
+
|
|
11
|
+
## Safe fixture
|
|
12
|
+
|
|
13
|
+
Use a one-message thread sent from the validation account to itself. This keeps the proof real without inviting an unrelated person.
|
|
14
|
+
|
|
15
|
+
The live script:
|
|
16
|
+
|
|
17
|
+
1. reads the approved Gmail thread through `users.threads.get` using metadata format and only the `From` header;
|
|
18
|
+
2. derives `fact:sender-email` from the same-lease `ALLOW` receipt;
|
|
19
|
+
3. creates one private, transparent Calendar event whose attendee is exactly that derived sender;
|
|
20
|
+
4. attempts a different attendee and requires `authority_delta_required` before the provider callback;
|
|
21
|
+
5. completes the Task Lease and proves the previously authorized attendee can no longer be used;
|
|
22
|
+
6. deletes the temporary event as harness cleanup outside the agent-authority proof.
|
|
23
|
+
|
|
24
|
+
The event uses `sendUpdates=none` and is deleted after validation. The cleanup call is counted separately from task-side provider mutations.
|
|
25
|
+
|
|
26
|
+
## Required Google OAuth scopes
|
|
27
|
+
|
|
28
|
+
The validation credential needs only the Google permissions required for the two provider operations:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
https://www.googleapis.com/auth/gmail.readonly
|
|
32
|
+
https://www.googleapis.com/auth/calendar.events
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Do not put an access token in the repository or workflow inputs.
|
|
36
|
+
|
|
37
|
+
## GitHub Actions secrets
|
|
38
|
+
|
|
39
|
+
The manual workflow `.github/workflows/live-google-validation.yml` mints a short-lived access token at runtime from these repository secrets:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
AA_GOOGLE_OAUTH_CLIENT_ID
|
|
43
|
+
AA_GOOGLE_OAUTH_CLIENT_SECRET
|
|
44
|
+
AA_GOOGLE_OAUTH_REFRESH_TOKEN
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The refresh token should have been granted the Gmail read-only and Calendar event scopes above. The access token is masked and exists only for the workflow run.
|
|
48
|
+
|
|
49
|
+
## Run from GitHub Actions
|
|
50
|
+
|
|
51
|
+
Open **Actions → Live Google cross-provider validation → Run workflow** and provide:
|
|
52
|
+
|
|
53
|
+
- `gmail_thread_id`: the approved self-test Gmail thread ID;
|
|
54
|
+
- `calendar_id`: normally `primary`;
|
|
55
|
+
- `expected_sender`: optional, but recommended for a deterministic fixture.
|
|
56
|
+
|
|
57
|
+
A successful run should record the following shape:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
ALLOW -> Gmail returned sender <fixture account>
|
|
61
|
+
Derived authority -> Calendar attendee <fixture account>
|
|
62
|
+
ALLOW -> real Calendar event mutation executed
|
|
63
|
+
STEP-UP -> unrelated attendee blocked before Calendar provider mutation
|
|
64
|
+
DENY -> post-completion Calendar mutation blocked
|
|
65
|
+
Provider calls observed before cleanup: gmail_reads=1, calendar_task_mutations=1
|
|
66
|
+
Cleanup -> deleted temporary Calendar event
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Run locally with a short-lived token
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
export GOOGLE_ACCESS_TOKEN='...'
|
|
73
|
+
export AA_GOOGLE_GMAIL_THREAD_ID='...'
|
|
74
|
+
export AA_GOOGLE_CALENDAR_ID='primary'
|
|
75
|
+
export AA_GOOGLE_EXPECTED_SENDER='validation@example.com'
|
|
76
|
+
npm run demo:live-google
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Prefer the refresh-token GitHub Actions workflow for repeatable validation. Do not persist the short-lived access token in shell history, source files, screenshots, or issue comments.
|
|
80
|
+
|
|
81
|
+
## What this proves
|
|
82
|
+
|
|
83
|
+
When the real provider calls are placed inside the Task Lease guard callbacks, the validation demonstrates:
|
|
84
|
+
|
|
85
|
+
- cross-provider derived authority from Gmail to Calendar;
|
|
86
|
+
- one exact derived attendee can be used for the real Calendar effect;
|
|
87
|
+
- a different attendee does not silently inherit authority;
|
|
88
|
+
- blocked attempts create zero additional task-side Calendar provider mutations;
|
|
89
|
+
- task completion removes the previously valid derived authority;
|
|
90
|
+
- the Google credential can remain broader than the Task Lease.
|
|
91
|
+
|
|
92
|
+
## What this does not prove
|
|
93
|
+
|
|
94
|
+
- the current host-side extraction of `sender_email` is cryptographically attested by Gmail;
|
|
95
|
+
- Task Lease state survives process failure;
|
|
96
|
+
- an agent holding an independent Google credential cannot bypass the guard;
|
|
97
|
+
- the validation refresh-token setup is a production credential-onboarding design;
|
|
98
|
+
- `sendUpdates=none` is a general invitation-delivery strategy for user-facing calendar workflows.
|
|
99
|
+
|
|
100
|
+
The purpose of this proof is to close the real cross-provider M1 validation gap and expose the next implementation problem with evidence rather than speculation.
|
package/docs/npm-release.md
CHANGED
|
@@ -17,3 +17,13 @@ npm install @nullsquare/agent-authority@0.4.0
|
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Then run the same consumer smoke flow through the registry-installed package. Registry verification is part of the release gate; a successful `npm publish` command alone is not sufficient.
|
|
20
|
+
|
|
21
|
+
## npm vs GitHub release surfaces
|
|
22
|
+
|
|
23
|
+
Publishing to the public npm registry does not automatically create either a GitHub Release or a GitHub Packages entry.
|
|
24
|
+
|
|
25
|
+
- **npm registry** — `npm publish --access public` publishes `@nullsquare/agent-authority` to `registry.npmjs.org` / npmjs.com. This is the package users install with `npm install`.
|
|
26
|
+
- **GitHub Releases** — a separate GitHub object, normally backed by a Git tag such as `v0.4.0`. A release must be created explicitly or by release automation.
|
|
27
|
+
- **GitHub Packages** — a separate package registry. It only appears when the package is published to GitHub's npm registry (`npm.pkg.github.com`); publishing to npmjs.com does not populate it.
|
|
28
|
+
|
|
29
|
+
Agent Authority currently uses npmjs.com as its public package registry. Therefore an empty GitHub **Packages** section is expected unless the project intentionally adopts dual publication. A GitHub **Release** is still useful for source-release discoverability and should track published versions, but it is independent from npm publication.
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { AuthorityRuntime } from '../src/index.js';
|
|
2
|
+
import {
|
|
3
|
+
AuthorityApprovalRequiredError,
|
|
4
|
+
AuthorityDeniedError,
|
|
5
|
+
createTaskLeaseGuard
|
|
6
|
+
} from '../src/guard.js';
|
|
7
|
+
import { gmailThreadSenderEmail } from '../src/providers/google.js';
|
|
8
|
+
import { createTaskLease } from '../src/task-lease.js';
|
|
9
|
+
|
|
10
|
+
const token = process.env.GOOGLE_ACCESS_TOKEN;
|
|
11
|
+
const threadId = process.env.AA_GOOGLE_GMAIL_THREAD_ID;
|
|
12
|
+
const calendarId = process.env.AA_GOOGLE_CALENDAR_ID || 'primary';
|
|
13
|
+
const expectedSender = process.env.AA_GOOGLE_EXPECTED_SENDER?.trim().toLowerCase() || null;
|
|
14
|
+
|
|
15
|
+
if (!token) throw new Error('GOOGLE_ACCESS_TOKEN is required for live Google validation');
|
|
16
|
+
if (!threadId) throw new Error('AA_GOOGLE_GMAIL_THREAD_ID is required for live Google validation');
|
|
17
|
+
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
const startTime = new Date(now + 10 * 60_000).toISOString();
|
|
20
|
+
const endTime = new Date(now + 25 * 60_000).toISOString();
|
|
21
|
+
const marker = `Agent Authority live Google validation ${new Date(now).toISOString()}`;
|
|
22
|
+
|
|
23
|
+
const mission = {
|
|
24
|
+
version: '0.1',
|
|
25
|
+
mission_id: `mission:live-google-cross-provider:${now}`,
|
|
26
|
+
principal: { id: 'user:google-validation' },
|
|
27
|
+
agent: { id: 'agent:google-cross-provider-validation' },
|
|
28
|
+
objective: 'Read one authorized Gmail thread and create one Calendar event only for the sender discovered from that thread',
|
|
29
|
+
resources: [
|
|
30
|
+
{
|
|
31
|
+
service: 'gmail',
|
|
32
|
+
allow: ['thread.read'],
|
|
33
|
+
deny: ['message.send', 'message.delete', 'thread.delete'],
|
|
34
|
+
constraints: { thread_id: [threadId] }
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
service: 'calendar',
|
|
38
|
+
allow: ['event.create'],
|
|
39
|
+
deny: ['event.delete', 'calendar.delete'],
|
|
40
|
+
constraints: { calendar_id: [calendarId] }
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
constraints: { expires_at: new Date(now + 15 * 60_000).toISOString() }
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const lease = createTaskLease({
|
|
47
|
+
mission,
|
|
48
|
+
request: 'Schedule one temporary validation event with the sender in the approved Gmail thread',
|
|
49
|
+
roots: [
|
|
50
|
+
{
|
|
51
|
+
fact_id: 'fact:gmail-thread',
|
|
52
|
+
kind: 'gmail.thread',
|
|
53
|
+
value: threadId,
|
|
54
|
+
source: 'validation-task'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
fact_id: 'fact:calendar',
|
|
58
|
+
kind: 'google.calendar',
|
|
59
|
+
value: calendarId,
|
|
60
|
+
source: 'validation-task'
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
bindings: [
|
|
64
|
+
{
|
|
65
|
+
service: 'gmail',
|
|
66
|
+
action: 'thread.read',
|
|
67
|
+
context_field: 'thread_id',
|
|
68
|
+
fact_id: 'fact:gmail-thread'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
service: 'calendar',
|
|
72
|
+
action: 'event.create',
|
|
73
|
+
context_field: 'calendar_id',
|
|
74
|
+
fact_id: 'fact:calendar'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
service: 'calendar',
|
|
78
|
+
action: 'event.create',
|
|
79
|
+
context_field: 'attendee_email',
|
|
80
|
+
fact_id: 'fact:sender-email'
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
expires_at: new Date(now + 10 * 60_000).toISOString()
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const guard = createTaskLeaseGuard({ lease, runtime: new AuthorityRuntime() });
|
|
87
|
+
const providerHeaders = {
|
|
88
|
+
accept: 'application/json',
|
|
89
|
+
authorization: `Bearer ${token}`,
|
|
90
|
+
'content-type': 'application/json',
|
|
91
|
+
'user-agent': 'agent-authority-live-google-validation/0.4'
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
let providerReadCalls = 0;
|
|
95
|
+
let providerMutationCalls = 0;
|
|
96
|
+
let cleanupCalls = 0;
|
|
97
|
+
let createdEventId = null;
|
|
98
|
+
|
|
99
|
+
async function googleResponse(url, options = {}) {
|
|
100
|
+
const response = await fetch(url, {
|
|
101
|
+
...options,
|
|
102
|
+
headers: { ...providerHeaders, ...(options.headers || {}) }
|
|
103
|
+
});
|
|
104
|
+
const text = await response.text();
|
|
105
|
+
let body = null;
|
|
106
|
+
if (text) {
|
|
107
|
+
try { body = JSON.parse(text); } catch { body = text; }
|
|
108
|
+
}
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
throw new Error(`Google API ${response.status}: ${typeof body === 'string' ? body.slice(0, 300) : JSON.stringify(body).slice(0, 300)}`);
|
|
111
|
+
}
|
|
112
|
+
return body;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function readAuthorizedThread() {
|
|
116
|
+
return guard.run(
|
|
117
|
+
{
|
|
118
|
+
service: 'gmail',
|
|
119
|
+
action: 'thread.read',
|
|
120
|
+
context: { thread_id: threadId }
|
|
121
|
+
},
|
|
122
|
+
async () => {
|
|
123
|
+
providerReadCalls += 1;
|
|
124
|
+
const query = new URLSearchParams({ format: 'metadata' });
|
|
125
|
+
query.append('metadataHeaders', 'From');
|
|
126
|
+
const thread = await googleResponse(
|
|
127
|
+
`https://gmail.googleapis.com/gmail/v1/users/me/threads/${encodeURIComponent(threadId)}?${query}`
|
|
128
|
+
);
|
|
129
|
+
const sender = gmailThreadSenderEmail(thread);
|
|
130
|
+
return {
|
|
131
|
+
thread_id: thread.id,
|
|
132
|
+
sender_email: sender.email,
|
|
133
|
+
sender_raw: sender.raw,
|
|
134
|
+
sender_message_id: sender.message_id,
|
|
135
|
+
message_count: Array.isArray(thread.messages) ? thread.messages.length : 0
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function createCalendarEvent(attendeeEmail, suffix) {
|
|
142
|
+
const request = {
|
|
143
|
+
service: 'calendar',
|
|
144
|
+
action: 'event.create',
|
|
145
|
+
context: {
|
|
146
|
+
calendar_id: calendarId,
|
|
147
|
+
attendee_email: attendeeEmail,
|
|
148
|
+
start_time: startTime,
|
|
149
|
+
end_time: endTime,
|
|
150
|
+
summary: `${marker} — ${suffix}`,
|
|
151
|
+
transparency: 'transparent',
|
|
152
|
+
visibility: 'private',
|
|
153
|
+
send_updates: 'none'
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return guard.run(request, async () => {
|
|
158
|
+
providerMutationCalls += 1;
|
|
159
|
+
const event = await googleResponse(
|
|
160
|
+
`https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events?sendUpdates=none`,
|
|
161
|
+
{
|
|
162
|
+
method: 'POST',
|
|
163
|
+
body: JSON.stringify({
|
|
164
|
+
summary: request.context.summary,
|
|
165
|
+
description: 'Temporary event created by the Agent Authority live Gmail → Calendar validation. Cleanup deletes it after the proof.',
|
|
166
|
+
start: { dateTime: startTime },
|
|
167
|
+
end: { dateTime: endTime },
|
|
168
|
+
attendees: [{ email: attendeeEmail }],
|
|
169
|
+
transparency: 'transparent',
|
|
170
|
+
visibility: 'private'
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
return { event_id: event.id, html_link: event.htmlLink || null };
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function cleanupEvent(eventId) {
|
|
179
|
+
cleanupCalls += 1;
|
|
180
|
+
const response = await fetch(
|
|
181
|
+
`https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}?sendUpdates=none`,
|
|
182
|
+
{ method: 'DELETE', headers: providerHeaders }
|
|
183
|
+
);
|
|
184
|
+
if (!response.ok && response.status !== 404) {
|
|
185
|
+
const body = await response.text();
|
|
186
|
+
throw new Error(`Google Calendar cleanup ${response.status}: ${body.slice(0, 300)}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
console.log(`Task root Gmail thread: ${threadId}`);
|
|
192
|
+
console.log(`Task root Calendar: ${calendarId}`);
|
|
193
|
+
console.log('1. Read the approved Gmail thread through the Task Lease');
|
|
194
|
+
const discovered = await readAuthorizedThread();
|
|
195
|
+
console.log(` ALLOW -> Gmail returned sender ${discovered.output.sender_email}`);
|
|
196
|
+
|
|
197
|
+
if (expectedSender && discovered.output.sender_email !== expectedSender) {
|
|
198
|
+
throw new Error(`expected sender ${expectedSender}, got ${discovered.output.sender_email}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
lease.derive({
|
|
202
|
+
fact_id: 'fact:sender-email',
|
|
203
|
+
kind: 'email.address',
|
|
204
|
+
value: discovered.output.sender_email,
|
|
205
|
+
from: ['fact:gmail-thread'],
|
|
206
|
+
receipt: discovered.receipt,
|
|
207
|
+
selector: 'output.sender_email'
|
|
208
|
+
});
|
|
209
|
+
console.log(`2. Derived authority -> Calendar attendee ${discovered.output.sender_email}`);
|
|
210
|
+
|
|
211
|
+
const allowed = await createCalendarEvent(discovered.output.sender_email, 'authorized');
|
|
212
|
+
createdEventId = allowed.output.event_id;
|
|
213
|
+
console.log(`3. ALLOW -> real Calendar event mutation executed (${createdEventId})`);
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
await createCalendarEvent('blocked@example.invalid', 'must-never-run');
|
|
217
|
+
throw new Error('unrelated attendee mutation unexpectedly executed');
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (!(error instanceof AuthorityApprovalRequiredError) || error.code !== 'authority_delta_required') {
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
console.log('4. STEP-UP -> unrelated attendee blocked before Calendar provider mutation');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (providerMutationCalls !== 1) {
|
|
226
|
+
throw new Error(`expected exactly one task-side Calendar mutation before completion, got ${providerMutationCalls}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
lease.complete('live Gmail to Calendar validation complete');
|
|
230
|
+
try {
|
|
231
|
+
await createCalendarEvent(discovered.output.sender_email, 'must-not-run-after-completion');
|
|
232
|
+
throw new Error('post-completion Calendar mutation unexpectedly executed');
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (!(error instanceof AuthorityDeniedError) || error.code !== 'task_lease_completed') {
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
console.log('5. DENY -> post-completion Calendar mutation blocked');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (providerReadCalls !== 1) {
|
|
241
|
+
throw new Error(`expected exactly one Gmail provider read, got ${providerReadCalls}`);
|
|
242
|
+
}
|
|
243
|
+
if (providerMutationCalls !== 1) {
|
|
244
|
+
throw new Error(`expected exactly one Calendar provider mutation after blocked attempts, got ${providerMutationCalls}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
console.log('PASS -> a sender discovered from real Gmail became exact derived authority for one real Calendar mutation');
|
|
248
|
+
console.log('PASS -> unrelated and post-completion attempts caused zero additional Calendar provider mutations');
|
|
249
|
+
} finally {
|
|
250
|
+
if (createdEventId) {
|
|
251
|
+
await cleanupEvent(createdEventId);
|
|
252
|
+
console.log(`Cleanup -> deleted temporary Calendar event ${createdEventId} outside the agent authority proof`);
|
|
253
|
+
}
|
|
254
|
+
console.log(`Provider calls observed before cleanup: gmail_reads=${providerReadCalls}, calendar_task_mutations=${providerMutationCalls}`);
|
|
255
|
+
console.log(`Harness cleanup calls: ${cleanupCalls}`);
|
|
256
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nullsquare/agent-authority",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Task-bounded authority runtime for AI agents: give agents tasks, not standing account permissions.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -35,12 +35,13 @@
|
|
|
35
35
|
"demo:task-lease": "node examples/task-lease-demo.js",
|
|
36
36
|
"demo:live-github": "node examples/live-github-task-lease.js",
|
|
37
37
|
"demo:live-derived-github": "node examples/live-github-derived-mutation.js",
|
|
38
|
+
"demo:live-google": "node examples/live-google-cross-provider.js",
|
|
38
39
|
"demo:mcp-upstream": "node examples/validation-mcp-upstream.js",
|
|
39
40
|
"demo:guard": "node examples/direct-guard.js",
|
|
40
41
|
"test": "node --test test/*.test.js",
|
|
41
42
|
"test:ai-sdk": "node --test test/integrations/ai-sdk.integration.mjs",
|
|
42
43
|
"test:coverage": "node --experimental-test-coverage --test test/*.test.js",
|
|
43
|
-
"check:syntax": "node --check src/index.js && node --check src/connections.js && node --check src/execution.js && node --check src/providers/github.js && node --check src/storage.js && node --check src/runtime-env.js && node --check src/sdk.js && node --check src/server.js && node --check src/cli.js && node --check src/agent-auth.js && node --check src/approvals.js && node --check src/idempotency.js && node --check src/keys.js && node --check src/harness-bridge.js && node --check src/guard.js && node --check src/task-lease.js && node --check src/mcp-gateway.js && node --check src/mcp-remote.js && node --check src/mcp-server.js && node --check src/integrations/ai-sdk.js && node --check examples/validation-mcp-upstream.js && node --check examples/direct-guard.js && node --check examples/task-lease-demo.js && node --check examples/live-github-task-lease.js && node --check examples/live-github-derived-mutation.js",
|
|
44
|
+
"check:syntax": "node --check src/index.js && node --check src/connections.js && node --check src/execution.js && node --check src/providers/github.js && node --check src/providers/google.js && node --check src/storage.js && node --check src/runtime-env.js && node --check src/sdk.js && node --check src/server.js && node --check src/cli.js && node --check src/agent-auth.js && node --check src/approvals.js && node --check src/idempotency.js && node --check src/keys.js && node --check src/harness-bridge.js && node --check src/guard.js && node --check src/task-lease.js && node --check src/mcp-gateway.js && node --check src/mcp-remote.js && node --check src/mcp-server.js && node --check src/integrations/ai-sdk.js && node --check examples/validation-mcp-upstream.js && node --check examples/direct-guard.js && node --check examples/task-lease-demo.js && node --check examples/live-github-task-lease.js && node --check examples/live-github-derived-mutation.js && node --check examples/live-google-cross-provider.js",
|
|
44
45
|
"check:package": "npm pack --dry-run",
|
|
45
46
|
"check": "npm run check:syntax && npm test && npm run demo:task-lease && npm run check:package"
|
|
46
47
|
},
|
|
@@ -71,7 +72,8 @@
|
|
|
71
72
|
"./sdk": "./src/sdk.js",
|
|
72
73
|
"./storage": "./src/storage.js",
|
|
73
74
|
"./task-lease": "./src/task-lease.js",
|
|
74
|
-
"./providers/github": "./src/providers/github.js"
|
|
75
|
+
"./providers/github": "./src/providers/github.js",
|
|
76
|
+
"./providers/google": "./src/providers/google.js"
|
|
75
77
|
},
|
|
76
78
|
"files": ["src", "docs", "examples", "README.md", "LICENSE", "SECURITY.md", "ROADMAP.md", "CONTRIBUTING.md"],
|
|
77
79
|
"repository": { "type": "git", "url": "git+https://github.com/Null-Square/agent-authority.git" },
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { brokeredProviderAdapter } from '../connections.js';
|
|
2
|
+
|
|
3
|
+
const MUTATING_ACTIONS = new Set(['event.create', 'event.delete']);
|
|
4
|
+
const SEND_UPDATES = new Set(['all', 'externalOnly', 'none']);
|
|
5
|
+
|
|
6
|
+
function required(value, name) {
|
|
7
|
+
if (value === undefined || value === null || value === '') throw new Error(`${name} is required`);
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function providerError(code, message) {
|
|
12
|
+
const error = new Error(message);
|
|
13
|
+
error.code = code;
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function fromHeader(message) {
|
|
18
|
+
const headers = message?.payload?.headers;
|
|
19
|
+
if (!Array.isArray(headers)) return null;
|
|
20
|
+
const header = headers.find((item) => String(item?.name || '').toLowerCase() === 'from');
|
|
21
|
+
return typeof header?.value === 'string' ? header.value : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function extractEmailAddress(value) {
|
|
25
|
+
if (typeof value !== 'string') return null;
|
|
26
|
+
const match = value.match(/([A-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Z0-9.-]+\.[A-Z]{2,})/i);
|
|
27
|
+
return match ? match[1].trim().toLowerCase() : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function gmailThreadSenderEmail(thread) {
|
|
31
|
+
const messages = thread?.messages;
|
|
32
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
33
|
+
throw providerError('gmail_thread_empty', 'Gmail thread does not contain any messages');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const message of messages) {
|
|
37
|
+
const raw = fromHeader(message);
|
|
38
|
+
const email = extractEmailAddress(raw);
|
|
39
|
+
if (email) return { email, raw, message_id: message.id || null };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw providerError('gmail_sender_missing', 'Gmail thread does not contain a usable From header');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function validateSendUpdates(value) {
|
|
46
|
+
const normalized = value || 'none';
|
|
47
|
+
if (!SEND_UPDATES.has(normalized)) {
|
|
48
|
+
throw providerError('invalid_send_updates', 'context.send_updates must be all, externalOnly, or none');
|
|
49
|
+
}
|
|
50
|
+
return normalized;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildOperation(request, {
|
|
54
|
+
gmailBaseUrl = 'https://gmail.googleapis.com',
|
|
55
|
+
calendarBaseUrl = 'https://www.googleapis.com/calendar/v3'
|
|
56
|
+
} = {}) {
|
|
57
|
+
const context = request?.context || {};
|
|
58
|
+
|
|
59
|
+
if (request?.service === 'gmail' && request?.action === 'thread.read') {
|
|
60
|
+
const threadId = required(context.thread_id, 'context.thread_id');
|
|
61
|
+
const query = new URLSearchParams({ format: 'metadata' });
|
|
62
|
+
query.append('metadataHeaders', 'From');
|
|
63
|
+
return {
|
|
64
|
+
method: 'GET',
|
|
65
|
+
url: `${gmailBaseUrl}/gmail/v1/users/me/threads/${encodeURIComponent(threadId)}?${query}`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (request?.service === 'calendar' && request?.action === 'event.create') {
|
|
70
|
+
const calendarId = context.calendar_id || 'primary';
|
|
71
|
+
const attendeeEmail = required(context.attendee_email, 'context.attendee_email');
|
|
72
|
+
const startTime = required(context.start_time, 'context.start_time');
|
|
73
|
+
const endTime = required(context.end_time, 'context.end_time');
|
|
74
|
+
const sendUpdates = validateSendUpdates(context.send_updates);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
url: `${calendarBaseUrl}/calendars/${encodeURIComponent(calendarId)}/events?sendUpdates=${encodeURIComponent(sendUpdates)}`,
|
|
79
|
+
body: {
|
|
80
|
+
summary: context.summary || 'Agent Authority task event',
|
|
81
|
+
description: context.description || undefined,
|
|
82
|
+
start: { dateTime: startTime },
|
|
83
|
+
end: { dateTime: endTime },
|
|
84
|
+
attendees: [{ email: attendeeEmail }],
|
|
85
|
+
transparency: context.transparency || undefined,
|
|
86
|
+
visibility: context.visibility || undefined
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (request?.service === 'calendar' && request?.action === 'event.delete') {
|
|
92
|
+
const calendarId = context.calendar_id || 'primary';
|
|
93
|
+
const eventId = required(context.event_id, 'context.event_id');
|
|
94
|
+
const sendUpdates = validateSendUpdates(context.send_updates);
|
|
95
|
+
return {
|
|
96
|
+
method: 'DELETE',
|
|
97
|
+
url: `${calendarBaseUrl}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}?sendUpdates=${encodeURIComponent(sendUpdates)}`
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw providerError(
|
|
102
|
+
'unsupported_action',
|
|
103
|
+
`Google action ${request?.service || 'unknown'}:${request?.action || 'unknown'} has no provider operation mapping`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function accessToken(credential) {
|
|
108
|
+
const token = typeof credential === 'string' ? credential : credential?.access_token;
|
|
109
|
+
if (!token) throw providerError('credential_invalid', 'Google credential does not contain an access token');
|
|
110
|
+
return token;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function responseBody(response) {
|
|
114
|
+
const text = await response.text();
|
|
115
|
+
if (!text) return null;
|
|
116
|
+
try { return JSON.parse(text); } catch { return text; }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function normalizedOutput(request, response, body) {
|
|
120
|
+
const common = {
|
|
121
|
+
provider: request.service,
|
|
122
|
+
status: response.status,
|
|
123
|
+
ok: response.ok,
|
|
124
|
+
request_id: response.headers?.get?.('x-request-id') || null
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (request.service === 'gmail' && request.action === 'thread.read') {
|
|
128
|
+
const sender = gmailThreadSenderEmail(body);
|
|
129
|
+
return {
|
|
130
|
+
...common,
|
|
131
|
+
thread_id: body?.id || request.context?.thread_id || null,
|
|
132
|
+
message_count: Array.isArray(body?.messages) ? body.messages.length : 0,
|
|
133
|
+
sender_email: sender.email,
|
|
134
|
+
sender_raw: sender.raw,
|
|
135
|
+
sender_message_id: sender.message_id
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (request.service === 'calendar' && request.action === 'event.create') {
|
|
140
|
+
return {
|
|
141
|
+
...common,
|
|
142
|
+
event_id: body?.id || null,
|
|
143
|
+
html_link: body?.htmlLink || null,
|
|
144
|
+
attendees: Array.isArray(body?.attendees)
|
|
145
|
+
? body.attendees.map((item) => item?.email).filter(Boolean)
|
|
146
|
+
: []
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (request.service === 'calendar' && request.action === 'event.delete') {
|
|
151
|
+
return { ...common, deleted: response.ok };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { ...common, body };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function createGoogleProviderAdapter({
|
|
158
|
+
broker,
|
|
159
|
+
fetchImpl = globalThis.fetch,
|
|
160
|
+
gmailBaseUrl = 'https://gmail.googleapis.com',
|
|
161
|
+
calendarBaseUrl = 'https://www.googleapis.com/calendar/v3'
|
|
162
|
+
} = {}) {
|
|
163
|
+
if (!broker) throw new Error('credential broker is required');
|
|
164
|
+
if (typeof fetchImpl !== 'function') throw new Error('fetch implementation is required');
|
|
165
|
+
|
|
166
|
+
const operationFor = (request) => buildOperation(request, { gmailBaseUrl, calendarBaseUrl });
|
|
167
|
+
|
|
168
|
+
const adapter = brokeredProviderAdapter({
|
|
169
|
+
kind: 'google-rest',
|
|
170
|
+
services: ['gmail', 'calendar'],
|
|
171
|
+
broker,
|
|
172
|
+
async execute({ request, credential }) {
|
|
173
|
+
const operation = operationFor(request);
|
|
174
|
+
const response = await fetchImpl(operation.url, {
|
|
175
|
+
method: operation.method,
|
|
176
|
+
headers: {
|
|
177
|
+
accept: 'application/json',
|
|
178
|
+
authorization: `Bearer ${accessToken(credential)}`,
|
|
179
|
+
'content-type': 'application/json',
|
|
180
|
+
'user-agent': 'nullsquare-agent-authority/0.4'
|
|
181
|
+
},
|
|
182
|
+
body: operation.body ? JSON.stringify(operation.body) : undefined
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const body = await responseBody(response);
|
|
186
|
+
const output = normalizedOutput(request, response, body);
|
|
187
|
+
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
const error = providerError('provider_error', `${request.service} API ${response.status}`);
|
|
190
|
+
error.provider_output = output;
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return output;
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
adapter.validateRequest = (request) => operationFor(request);
|
|
199
|
+
adapter.isMutation = (request) => MUTATING_ACTIONS.has(request?.action);
|
|
200
|
+
return adapter;
|
|
201
|
+
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="640" viewBox="0 0 1600 640" role="img" aria-label="Agent Authority — mission scoped authorization for AI agents">
|
|
2
|
-
<defs>
|
|
3
|
-
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
-
<stop offset="0" stop-color="#07111f"/><stop offset="1" stop-color="#121a36"/>
|
|
5
|
-
</linearGradient>
|
|
6
|
-
<linearGradient id="accent" x1="0" y1="0" x2="1" y2="0">
|
|
7
|
-
<stop offset="0" stop-color="#7c5cff"/><stop offset="1" stop-color="#4ecbff"/>
|
|
8
|
-
</linearGradient>
|
|
9
|
-
</defs>
|
|
10
|
-
<rect width="1600" height="640" rx="36" fill="url(#bg)"/>
|
|
11
|
-
<g font-family="Arial, Helvetica, sans-serif" fill="#fff">
|
|
12
|
-
<text x="100" y="120" font-size="28" font-weight="700" letter-spacing="3">NULLSQUARE / OPEN SOURCE</text>
|
|
13
|
-
<text x="100" y="220" font-size="78" font-weight="800">Agent Authority</text>
|
|
14
|
-
<text x="100" y="285" font-size="34" fill="#b9c7e6">Mission-scoped authorization for AI agents</text>
|
|
15
|
-
<text x="100" y="355" font-size="27" fill="#8fe1ff">One mission · Any agent · Any auth system</text>
|
|
16
|
-
</g>
|
|
17
|
-
<g transform="translate(930 95)" font-family="Arial, Helvetica, sans-serif">
|
|
18
|
-
<rect x="120" y="0" width="330" height="72" rx="16" fill="#111c34" stroke="#6d85ff"/>
|
|
19
|
-
<text x="285" y="44" text-anchor="middle" font-size="24" fill="#fff">Human-approved mission</text>
|
|
20
|
-
<path d="M285 72 V120" stroke="#7786a8" stroke-width="3"/>
|
|
21
|
-
<rect x="80" y="120" width="410" height="90" rx="18" fill="#171842" stroke="#8b67ff" stroke-width="2"/>
|
|
22
|
-
<text x="285" y="158" text-anchor="middle" font-size="25" font-weight="700" fill="#fff">AUTHORITY RUNTIME</text>
|
|
23
|
-
<text x="285" y="190" text-anchor="middle" font-size="20" fill="#b9c7e6">allow · deny · approve · revoke</text>
|
|
24
|
-
<path d="M285 210 V260" stroke="#7786a8" stroke-width="3"/>
|
|
25
|
-
<rect x="80" y="260" width="410" height="72" rx="16" fill="#101b30" stroke="#4ecbff"/>
|
|
26
|
-
<text x="285" y="304" text-anchor="middle" font-size="23" fill="#fff">Adapters / connectors</text>
|
|
27
|
-
<path d="M285 332 V382" stroke="#7786a8" stroke-width="3"/>
|
|
28
|
-
<g fill="#101b30" stroke="#4b628c">
|
|
29
|
-
<rect x="0" y="382" width="135" height="64" rx="14"/><rect x="145" y="382" width="135" height="64" rx="14"/>
|
|
30
|
-
<rect x="290" y="382" width="135" height="64" rx="14"/><rect x="435" y="382" width="135" height="64" rx="14"/>
|
|
31
|
-
</g>
|
|
32
|
-
<g font-size="20" fill="#fff" text-anchor="middle">
|
|
33
|
-
<text x="67" y="422">OAuth</text><text x="212" y="422">MCP</text><text x="357" y="422">API / CLI</text><text x="502" y="422">Legacy</text>
|
|
34
|
-
</g>
|
|
35
|
-
</g>
|
|
36
|
-
<rect x="100" y="430" width="690" height="2" fill="url(#accent)"/>
|
|
37
|
-
<g font-family="Arial, Helvetica, sans-serif" font-size="22" fill="#b9c7e6">
|
|
38
|
-
<text x="100" y="485">Delegation attenuation</text><text x="330" y="485">Human approvals</text><text x="555" y="485">Action receipts</text>
|
|
39
|
-
<text x="100" y="530">Credential isolation</text><text x="330" y="530">Legacy compatibility</text><text x="555" y="530">Protocol neutral</text>
|
|
40
|
-
</g>
|
|
41
|
-
</svg>
|