@nullsquare/agent-authority 0.4.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.
Files changed (58) hide show
  1. package/CONTRIBUTING.md +93 -0
  2. package/LICENSE +201 -0
  3. package/README.md +390 -0
  4. package/ROADMAP.md +149 -0
  5. package/SECURITY.md +116 -0
  6. package/docs/account-connections.md +173 -0
  7. package/docs/announcement-draft.md +13 -0
  8. package/docs/architecture.md +106 -0
  9. package/docs/assets/agent-authority-cover.svg +41 -0
  10. package/docs/clear-path.md +53 -0
  11. package/docs/cli.md +130 -0
  12. package/docs/evidence.md +143 -0
  13. package/docs/harness-bridge-mode.md +136 -0
  14. package/docs/harness-integration.md +223 -0
  15. package/docs/integration-contract.md +132 -0
  16. package/docs/integrations/vercel-ai-sdk.md +161 -0
  17. package/docs/launch-checklist.md +29 -0
  18. package/docs/npm-release.md +19 -0
  19. package/docs/openclaw-integration.md +97 -0
  20. package/docs/package-consumer-validation.md +18 -0
  21. package/docs/release-candidate-status.md +3 -0
  22. package/docs/release-guardrails.md +8 -0
  23. package/docs/release-notes-v0.4.md +26 -0
  24. package/docs/release-scope.md +3 -0
  25. package/docs/ship-criteria.md +3 -0
  26. package/docs/task-leases.md +253 -0
  27. package/docs/validation.md +124 -0
  28. package/examples/demo.js +19 -0
  29. package/examples/direct-guard.js +50 -0
  30. package/examples/harness-managed-connectors.js +72 -0
  31. package/examples/live-github-derived-mutation.js +208 -0
  32. package/examples/live-github-task-lease.js +80 -0
  33. package/examples/mission.json +20 -0
  34. package/examples/missions/chatgpt-web-validation.json +33 -0
  35. package/examples/openclaw-tool-wrapper.js +49 -0
  36. package/examples/task-lease-demo.js +98 -0
  37. package/examples/validation-mcp-upstream.js +112 -0
  38. package/package.json +80 -0
  39. package/src/agent-auth.js +135 -0
  40. package/src/approvals.js +157 -0
  41. package/src/cli.js +335 -0
  42. package/src/connections.js +203 -0
  43. package/src/execution.js +174 -0
  44. package/src/guard.js +79 -0
  45. package/src/harness-bridge.js +131 -0
  46. package/src/idempotency.js +118 -0
  47. package/src/index.js +291 -0
  48. package/src/integrations/ai-sdk.js +59 -0
  49. package/src/keys.js +15 -0
  50. package/src/mcp-gateway.js +142 -0
  51. package/src/mcp-remote.js +102 -0
  52. package/src/mcp-server.js +102 -0
  53. package/src/providers/github.js +149 -0
  54. package/src/runtime-env.js +53 -0
  55. package/src/sdk.js +75 -0
  56. package/src/server.js +146 -0
  57. package/src/storage.js +213 -0
  58. package/src/task-lease.js +266 -0
@@ -0,0 +1,253 @@
1
+ # Task Leases and Derived Authority
2
+
3
+ Agent Authority's current product thesis is simple:
4
+
5
+ > Give an agent a task, not standing account permissions.
6
+
7
+ A **Task Lease** is a temporary enforcement context around an existing mission. The mission remains the maximum authority ceiling. The lease can only narrow that authority as the task discovers concrete resources.
8
+
9
+ ## Why this exists
10
+
11
+ A human request often does not contain every resource identifier the agent will need.
12
+
13
+ Example:
14
+
15
+ > Handle the demo request in this email thread.
16
+
17
+ At task start the runtime may know only the Gmail thread ID. During authorized execution it discovers the sender email. That sender may then become the only attendee the agent is allowed to use when creating a calendar event.
18
+
19
+ Without a task-aware authority layer, the application usually chooses between two bad options:
20
+
21
+ 1. give the agent broad `calendar.write` / `mail.send` permissions; or
22
+ 2. ask the human to approve every individual tool call.
23
+
24
+ Task Leases aim for a third option: **task-bounded autonomy**.
25
+
26
+ ## Core model
27
+
28
+ ```text
29
+ Human-approved task
30
+ |
31
+ v
32
+ authority roots
33
+ |
34
+ authorized execution
35
+ |
36
+ v
37
+ derived facts
38
+ |
39
+ exact action bindings
40
+ |
41
+ v
42
+ effect
43
+ ```
44
+
45
+ ### Authority root
46
+
47
+ A value explicitly trusted at task entry.
48
+
49
+ Examples:
50
+
51
+ - `gmail.thread = thread:91`
52
+ - `github.issue = 42`
53
+ - `invoice.id = INV-2026-18`
54
+
55
+ Roots do not require a prior execution receipt because they come from the task's trusted entry boundary.
56
+
57
+ ### Derived fact
58
+
59
+ A value learned while performing the task.
60
+
61
+ Examples:
62
+
63
+ - sender email discovered from an authorized Gmail thread read;
64
+ - customer ID discovered from an authorized support-ticket lookup;
65
+ - order ID discovered from an authorized customer lookup.
66
+
67
+ In v0.4, a derived fact must reference an `ALLOW` receipt from the same mission. It may also reference existing parent facts.
68
+
69
+ ### Binding
70
+
71
+ A binding narrows an otherwise permitted action to the exact value held by an authority fact.
72
+
73
+ ```js
74
+ {
75
+ service: 'calendar',
76
+ action: 'event.create',
77
+ context_field: 'attendee',
78
+ fact_id: 'fact:requester-email'
79
+ }
80
+ ```
81
+
82
+ If `fact:requester-email` has not been established, the action is denied.
83
+
84
+ If it has value `customer@example.com`, this request can proceed:
85
+
86
+ ```js
87
+ {
88
+ service: 'calendar',
89
+ action: 'event.create',
90
+ context: { attendee: 'customer@example.com' }
91
+ }
92
+ ```
93
+
94
+ This request does not proceed automatically:
95
+
96
+ ```js
97
+ {
98
+ service: 'calendar',
99
+ action: 'event.create',
100
+ context: { attendee: 'other@example.com' }
101
+ }
102
+ ```
103
+
104
+ It returns `REQUIRE_APPROVAL` with `authority_delta_required`.
105
+
106
+ ## Non-amplification rule
107
+
108
+ A Task Lease is not a second policy engine and cannot grant a new action class.
109
+
110
+ The existing mission is always evaluated first.
111
+
112
+ ```text
113
+ mission says DENY
114
+ |
115
+ v
116
+ Task Lease cannot override it
117
+ ```
118
+
119
+ The lease can only add restrictions to an action that the mission already allows.
120
+
121
+ This is the key invariant:
122
+
123
+ ```text
124
+ lease authority <= mission authority
125
+ ```
126
+
127
+ Authority may stay the same or shrink. It must never grow silently.
128
+
129
+ ## Example
130
+
131
+ ```js
132
+ import { AuthorityRuntime } from '@nullsquare/agent-authority';
133
+ import { createTaskLease } from '@nullsquare/agent-authority/task-lease';
134
+ import { createTaskLeaseGuard } from '@nullsquare/agent-authority/guard';
135
+
136
+ const lease = createTaskLease({
137
+ mission,
138
+ request: 'Handle the demo request in thread:demo-91',
139
+ roots: [
140
+ { fact_id: 'fact:thread', kind: 'gmail.thread', value: 'thread:demo-91' }
141
+ ],
142
+ bindings: [
143
+ {
144
+ service: 'calendar',
145
+ action: 'event.create',
146
+ context_field: 'attendee',
147
+ fact_id: 'fact:sender-email'
148
+ }
149
+ ]
150
+ });
151
+
152
+ const guard = createTaskLeaseGuard({
153
+ lease,
154
+ runtime: new AuthorityRuntime()
155
+ });
156
+
157
+ const read = await guard.run({
158
+ service: 'gmail',
159
+ action: 'thread.read',
160
+ context: { thread: 'thread:demo-91' }
161
+ }, () => gmail.readThread('thread:demo-91'));
162
+
163
+ lease.derive({
164
+ fact_id: 'fact:sender-email',
165
+ kind: 'email.address',
166
+ value: read.output.sender,
167
+ from: ['fact:thread'],
168
+ receipt: read.receipt,
169
+ selector: 'output.sender'
170
+ });
171
+
172
+ await guard.run({
173
+ service: 'calendar',
174
+ action: 'event.create',
175
+ context: { attendee: read.output.sender }
176
+ }, () => calendar.createEvent({ attendee: read.output.sender }));
177
+ ```
178
+
179
+ Run the self-contained example:
180
+
181
+ ```bash
182
+ npm run demo:task-lease
183
+ ```
184
+
185
+ ## Task completion
186
+
187
+ Task authority should not outlive the task.
188
+
189
+ ```js
190
+ lease.complete('demo request handled');
191
+ ```
192
+
193
+ After completion, every guarded action returns `DENY` with `task_lease_completed` even if the underlying OAuth token or provider connection still exists.
194
+
195
+ A lease may also have its own `expires_at` independent of provider credential expiry.
196
+
197
+ ## Authority delta
198
+
199
+ When the agent asks to use a concrete value outside an established binding, Agent Authority returns a step-up signal rather than silently broadening the lease.
200
+
201
+ ```text
202
+ current task authority
203
+ +
204
+ requested new resource
205
+ |
206
+ v
207
+ authority_delta_required
208
+ ```
209
+
210
+ The existing approval store can handle the human decision. Automatically applying approved deltas to a live Task Lease is a later milestone; v0.4 deliberately stops at the safe enforcement signal.
211
+
212
+ ## Current security properties
213
+
214
+ The v0.4 implementation tests that:
215
+
216
+ - a bound action cannot run before its fact exists;
217
+ - derived facts require an `ALLOW` receipt;
218
+ - the receipt must belong to the same mission;
219
+ - explicit mission denies cannot be overridden by lease bindings;
220
+ - an exact derived resource can execute;
221
+ - a different resource becomes an authority delta and the effect does not run;
222
+ - completed and expired leases stop execution;
223
+ - Task Lease receipts include the lease ID and lease hash.
224
+
225
+ ## Current limitations
226
+
227
+ This is still a validation implementation.
228
+
229
+ 1. **Extraction trust:** the trusted host/adapter supplies the derived value and selector. Agent Authority records lineage but does not yet cryptographically prove that the selected output field contained that value.
230
+ 2. **In-memory lease state:** TaskLease instances are currently process-local. Durable lease persistence/recovery is not implemented yet.
231
+ 3. **Top-level binding fields:** v0.4 binds top-level request context fields only. Nested JSON-path policy is intentionally deferred.
232
+ 4. **Step-up application:** authority deltas are surfaced but approved deltas are not yet automatically applied back into the lease.
233
+ 5. **Adapter semantics:** providers still need trustworthy mappings from an external operation to `service`, `action`, and resource context fields.
234
+
235
+ These constraints are deliberate. The next work should be driven by real integrations rather than by adding a general policy language.
236
+
237
+ ## Validation target
238
+
239
+ The product thesis is validated when the same Task Lease can safely govern a real multi-step workflow across more than one execution transport, for example:
240
+
241
+ ```text
242
+ one human task
243
+ |
244
+ +--> ordinary SDK through guard.run()
245
+ |
246
+ +--> MCP tool through Agent Authority gateway
247
+ |
248
+ +--> brokered provider execution
249
+
250
+ same authority lineage
251
+ same non-amplification rule
252
+ same completion boundary
253
+ ```
@@ -0,0 +1,124 @@
1
+ # Validate Agent Authority in 5 minutes
2
+
3
+ The goal of this validation is deliberately narrow:
4
+
5
+ > A real MCP host should only be able to call the tool + resource allowed by a human mission, while the upstream provider/tool remains unaware of Agent Authority.
6
+
7
+ This is a product-layer test, not a benchmark and not a claim of production readiness.
8
+
9
+ ## What this proves
10
+
11
+ The repository includes:
12
+
13
+ - an Agent Authority MCP gateway (`aauth mcp proxy`)
14
+ - a tiny validation upstream with one real read-only GitHub metadata tool
15
+ - one fake write-capable tool that must never pass the gateway in read-only mode
16
+ - a canonical mission restricted to `Null-Square/agent-authority`
17
+ - wire-level tests using the official MCP v2 client SDK
18
+
19
+ The path is:
20
+
21
+ ```text
22
+ MCP host
23
+ -> Agent Authority
24
+ -> mission policy
25
+ -> allowed read-only tool
26
+ -> validation MCP upstream
27
+ -> public GitHub API
28
+ ```
29
+
30
+ ## 1. Install and initialize
31
+
32
+ ```bash
33
+ npm install
34
+ npm link
35
+
36
+ aauth setup --principal user:local
37
+ aauth doctor
38
+ ```
39
+
40
+ ## 2. Run the tiny validation upstream
41
+
42
+ Terminal A:
43
+
44
+ ```bash
45
+ npm run demo:mcp-upstream
46
+ ```
47
+
48
+ It listens on `http://127.0.0.1:8791/mcp` and advertises two tools:
49
+
50
+ - `github_repo_metadata` — explicitly read-only; reads public GitHub repository metadata
51
+ - `dangerous_demo_write` — harmless fake mutation used only to prove the gateway blocks write-capable tools
52
+
53
+ ## 3. Put Agent Authority in front of it
54
+
55
+ Terminal B:
56
+
57
+ ```bash
58
+ aauth mcp proxy \
59
+ --upstream http://127.0.0.1:8791/mcp \
60
+ --mission examples/missions/chatgpt-web-validation.json \
61
+ --service mcp:validation-upstream
62
+ ```
63
+
64
+ The gateway listens on `http://127.0.0.1:8790/mcp`.
65
+
66
+ It intentionally binds loopback only and refuses a public bind in this release.
67
+
68
+ ## Expected behavior
69
+
70
+ A client connected to the Agent Authority gateway should:
71
+
72
+ 1. see `github_repo_metadata`
73
+ 2. not see the fake write-capable tool in read-only mode
74
+ 3. succeed for:
75
+
76
+ ```json
77
+ {
78
+ "name": "github_repo_metadata",
79
+ "arguments": {
80
+ "repository": "Null-Square/agent-authority"
81
+ }
82
+ }
83
+ ```
84
+
85
+ 4. be denied if it changes the repository to another value
86
+ 5. be denied if it tries to call the write-capable tool directly even if it knows the upstream tool name
87
+
88
+ The key property is #5: hiding a tool is UX; enforcing `tools/call` is the security boundary.
89
+
90
+ ## Automated proof
91
+
92
+ Run:
93
+
94
+ ```bash
95
+ npm test
96
+ ```
97
+
98
+ The MCP tests cover the gateway policy directly and use the official MCP v2 client against the actual Agent Authority handler. No external provider credentials are required.
99
+
100
+ ## ChatGPT / hosted OpenAI validation
101
+
102
+ ChatGPT cannot connect directly to a localhost MCP server. OpenAI supports Secure MCP Tunnel for connecting local/private MCP servers to supported OpenAI products without exposing the server publicly.
103
+
104
+ Use the official `openai/tunnel-client` quickstart (`tunnel-client help quickstart`) and point the tunnel at:
105
+
106
+ ```text
107
+ http://127.0.0.1:8790/mcp
108
+ ```
109
+
110
+ Then configure the resulting tunnel-backed MCP endpoint/app in the supported OpenAI product.
111
+
112
+ Product/plan availability for custom MCP apps changes over time; if the ChatGPT workspace does not expose custom MCP app creation, the same tunnel endpoint can be validated from another supported OpenAI surface or any MCP v2 client. Do not weaken Agent Authority security merely to work around a UI/plan limitation.
113
+
114
+ ## Pass/fail criterion
115
+
116
+ The validation passes only if all of these are true:
117
+
118
+ - the host discovers the authorized read-only tool
119
+ - the authorized repository read succeeds
120
+ - an out-of-mission repository is rejected before reaching the upstream tool
121
+ - a write-capable tool is rejected before reaching the upstream tool
122
+ - changing the host does not require changing the mission semantics
123
+
124
+ If those conditions hold from ChatGPT and from at least one non-OpenAI MCP client, we have useful validation that Agent Authority is the policy layer rather than an OpenAI-specific connector.
@@ -0,0 +1,19 @@
1
+ import fs from 'node:fs';
2
+ import { AdapterRegistry, AuthorityRuntime, descriptorAdapter } from '../src/index.js';
3
+
4
+ const mission = JSON.parse(fs.readFileSync(new URL('./mission.json', import.meta.url)));
5
+ const adapters = new AdapterRegistry()
6
+ .register(descriptorAdapter('oauth', ['github', 'google']))
7
+ .register(descriptorAdapter('api-key', ['cloudflare']));
8
+ const runtime = new AuthorityRuntime({ adapters });
9
+
10
+ for (const request of [
11
+ { service: 'github', action: 'repo.write' },
12
+ { service: 'github', action: 'repo.delete' },
13
+ { service: 'google', action: 'gmail.send' },
14
+ { service: 'cloudflare', action: 'workers.deploy' },
15
+ { service: 'stripe', action: 'payment.create' }
16
+ ]) {
17
+ const output = await runtime.prepare(mission, request);
18
+ console.log(`${request.service}:${request.action} -> ${output.result.decision}`);
19
+ }
@@ -0,0 +1,50 @@
1
+ import { AuthorityRuntime } from '../src/index.js';
2
+ import { createAuthorityGuard } from '../src/guard.js';
3
+
4
+ const mission = {
5
+ version: '0.1',
6
+ mission_id: 'mission:direct-guard-demo',
7
+ principal: { id: 'user:demo' },
8
+ agent: { id: 'agent:demo' },
9
+ objective: 'Inspect only the Agent Authority public repository',
10
+ resources: [{
11
+ service: 'github',
12
+ allow: ['repo.read'],
13
+ deny: ['repo.delete', 'repo.write'],
14
+ constraints: { repository: ['Null-Square/agent-authority'] }
15
+ }]
16
+ };
17
+
18
+ const guard = createAuthorityGuard({
19
+ mission,
20
+ runtime: new AuthorityRuntime(),
21
+ onDecision: ({ result, receipt }, request) => {
22
+ console.log(`${result.decision.toUpperCase()} ${request.service}:${request.action} receipt=${receipt.receipt_hash}`);
23
+ }
24
+ });
25
+
26
+ const repository = process.argv[2] || 'Null-Square/agent-authority';
27
+
28
+ try {
29
+ const { output } = await guard.run({
30
+ service: 'github',
31
+ action: 'repo.read',
32
+ context: { repository }
33
+ }, async () => {
34
+ const response = await fetch(`https://api.github.com/repos/${repository}`, {
35
+ headers: { accept: 'application/vnd.github+json', 'user-agent': 'agent-authority-direct-guard-demo' }
36
+ });
37
+ if (!response.ok) throw new Error(`GitHub returned ${response.status}`);
38
+ const repo = await response.json();
39
+ return {
40
+ full_name: repo.full_name,
41
+ visibility: repo.visibility,
42
+ default_branch: repo.default_branch
43
+ };
44
+ });
45
+
46
+ console.log(JSON.stringify(output, null, 2));
47
+ } catch (error) {
48
+ console.error(`${error.name}: ${error.message}`);
49
+ process.exitCode = 2;
50
+ }
@@ -0,0 +1,72 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { AuthorityRuntime } from '../src/index.js';
3
+ import { issueHarnessActionGrant, createHarnessConnectorGate } from '../src/harness-bridge.js';
4
+
5
+ const signingKey = randomBytes(32);
6
+ const authority = new AuthorityRuntime();
7
+ const gate = createHarnessConnectorGate({ key: signingKey });
8
+
9
+ const mission = {
10
+ version: '0.1',
11
+ mission_id: 'mission:hosted-harness-demo',
12
+ principal: { id: 'user:example' },
13
+ agent: { id: 'agent:hosted-harness:session-1' },
14
+ objective: 'Read an approved GitHub repository through a harness-managed connector',
15
+ resources: [
16
+ {
17
+ service: 'github',
18
+ allow: ['repo.read'],
19
+ deny: ['repo.write', 'repo.delete'],
20
+ constraints: { repository: ['Null-Square/agent-authority'] }
21
+ }
22
+ ]
23
+ };
24
+
25
+ const request = {
26
+ service: 'github',
27
+ action: 'repo.read',
28
+ context: { repository: 'Null-Square/agent-authority' }
29
+ };
30
+
31
+ // 1. Agent Authority evaluates the human-approved mission.
32
+ const evaluation = authority.evaluate(mission, request);
33
+ if (evaluation.result.decision !== 'allow') {
34
+ console.error(evaluation.result);
35
+ process.exit(1);
36
+ }
37
+
38
+ // 2. Trusted authority code issues a short-lived grant for the exact request.
39
+ const { token: grant } = issueHarnessActionGrant({
40
+ key: signingKey,
41
+ mission,
42
+ request,
43
+ ttl_seconds: 30
44
+ });
45
+
46
+ // 3. This represents trusted harness connector middleware. The provider token
47
+ // stays inside the hosted platform; Agent Authority never needs to see it.
48
+ async function harnessGitHubConnector({ grant, mission, request }) {
49
+ gate.verify({ grant, mission, request });
50
+
51
+ // Replace this stub with the harness-owned GitHub connector. For example,
52
+ // a hosted platform may already have the user's GitHub OAuth connection.
53
+ return {
54
+ provider: 'github',
55
+ repository: request.context.repository,
56
+ executed_by: 'harness-managed-connector',
57
+ provider_credential_exposed_to_agent_authority: false
58
+ };
59
+ }
60
+
61
+ console.log(await harnessGitHubConnector({ grant, mission, request }));
62
+
63
+ // Changing the resource after authorization must fail verification.
64
+ try {
65
+ await harnessGitHubConnector({
66
+ grant,
67
+ mission,
68
+ request: { ...request, context: { repository: 'Null-Square/other-repository' } }
69
+ });
70
+ } catch (error) {
71
+ console.log(`blocked substitution: ${error.code}`);
72
+ }