@nullsquare/agent-authority 0.4.6 → 0.4.7

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.
@@ -0,0 +1,147 @@
1
+ # Fresh-install quickstart
2
+
3
+ This quickstart is for a developer who wants to understand Agent Authority before connecting an account or learning Mission/Task Lease internals.
4
+
5
+ It uses the real public task-first API and the reviewed GitHub issue-number authority extractor from the published npm package. The first provider callback is a local provider-shaped fixture, so **no GitHub token, OAuth setup, repository checkout, or custom extractor is required**.
6
+
7
+ ## 1. Create a blank project
8
+
9
+ ```bash
10
+ mkdir agent-authority-quickstart
11
+ cd agent-authority-quickstart
12
+ npm init -y
13
+ npm install @nullsquare/agent-authority
14
+ ```
15
+
16
+ Requires Node.js 20+.
17
+
18
+ ## 2. Get the credential-free fixture quickstart
19
+
20
+ Download or copy `examples/quickstart.mjs` from this repository into the blank project as `quickstart.mjs`.
21
+
22
+ For example on macOS/Linux:
23
+
24
+ ```bash
25
+ curl -fsSL https://raw.githubusercontent.com/Null-Square/agent-authority/main/examples/quickstart.mjs -o quickstart.mjs
26
+ ```
27
+
28
+ The file imports only published package exports:
29
+
30
+ ```js
31
+ import { createTask } from '@nullsquare/agent-authority/task';
32
+ import { AuthorityApprovalRequiredError } from '@nullsquare/agent-authority/guard';
33
+ import { githubIssueListSelectedNumberAuthorityExtractor } from '@nullsquare/agent-authority/providers/github';
34
+ ```
35
+
36
+ ## 3. Run it
37
+
38
+ ```bash
39
+ node quickstart.mjs
40
+ ```
41
+
42
+ Expected shape:
43
+
44
+ ```text
45
+ ALLOW -> task discovered issue #42 and the exact comment effect ran
46
+ STEP-UP -> The task established authority for 42 but this action requested 7.
47
+ PASS -> useful task work ran; unrelated standing permission did not become task authority
48
+ ```
49
+
50
+ ## What happened
51
+
52
+ The task starts with authority over one repository and one task-selection marker:
53
+
54
+ ```text
55
+ repository + marker
56
+ |
57
+ v
58
+ authorized issue discovery
59
+ |
60
+ v
61
+ reviewed extractor + execution evidence
62
+ |
63
+ v
64
+ issue #42 becomes downstream task authority
65
+ |
66
+ +--> comment on #42 -> ALLOW
67
+ +--> comment on #7 -> STEP-UP before callback
68
+ ```
69
+
70
+ The important point is not the fixture itself. It is that the callback which represents the provider effect executes for the task-derived issue and does **not** execute for the unrelated issue even though the Mission-level GitHub permission includes `issue.comment`.
71
+
72
+ The quickstart counts callbacks and fails if the unrelated effect executes.
73
+
74
+ ## 4. Next step: call real GitHub with no credential
75
+
76
+ The second quickstart uses the same published package in the same blank project, but the callback now makes a real network request to GitHub's public API.
77
+
78
+ ```bash
79
+ curl -fsSL https://raw.githubusercontent.com/Null-Square/agent-authority/main/examples/quickstart-github-live.mjs -o quickstart-github-live.mjs
80
+ node quickstart-github-live.mjs
81
+ ```
82
+
83
+ Default behavior:
84
+
85
+ ```text
86
+ Standing GitHub permission -> repo.read
87
+ Task authority -> Null-Square/agent-authority
88
+ GitHub mode -> public API; no credential required
89
+ ALLOW -> real GitHub returned Null-Square/agent-authority
90
+ STEP-UP -> The task established authority for "Null-Square/agent-authority" but this action requested "octocat/Hello-World".
91
+ PASS -> broader standing repo.read permission could not reach an unrelated repository for this task
92
+ ```
93
+
94
+ This example deliberately models the **standing capability as broader than the task**. Mission-level `github:repo.read` is allowed without a repository constraint. The Task authority root then binds `repo.read` to exactly `Null-Square/agent-authority`.
95
+
96
+ The allowed request performs one real `fetch()` to GitHub. The unrelated repository request reaches the Task authority check, becomes `authority_delta_required`, and does not execute a second `fetch()`.
97
+
98
+ You can inspect another public repository by passing it as the first argument:
99
+
100
+ ```bash
101
+ node quickstart-github-live.mjs owner/repository
102
+ ```
103
+
104
+ An optional `GITHUB_TOKEN` may be supplied for authenticated GitHub API access, but no token is required for the default public-repository path.
105
+
106
+ ## Replace the fixture with your provider call
107
+
108
+ The first quickstart's discovery callback is the only intentionally fake provider piece:
109
+
110
+ ```js
111
+ const discovery = await task.run(request, async () => {
112
+ return providerShapedOutput;
113
+ });
114
+ ```
115
+
116
+ In an application, keep the Agent Authority request and replace the callback with the SDK/provider call you already use. For the built-in GitHub extractor, use the normalized output produced by the Agent Authority GitHub adapter. If your provider/output shape is different, use a reviewed extractor for that mapping rather than trusting arbitrary model-selected values.
117
+
118
+ The live GitHub quickstart shows the even simpler direct-boundary case: an application can put its existing `fetch()` or SDK call inside `task.run()` while Task authority remains narrower than the standing account/app capability.
119
+
120
+ ## Evidence boundaries
121
+
122
+ The credential-free fixture is an **adoption quickstart**, not a live-provider security proof. The live GitHub quickstart is a real-provider onboarding proof, but it is read-only and uses a public repository by default.
123
+
124
+ Separate repository evidence already covers:
125
+
126
+ - a real GitHub issue discovery -> exact issue comment mutation through the task-first API;
127
+ - Gmail sender -> Calendar attendee authority;
128
+ - SDK, MCP and broker transport invariance;
129
+ - durable local Task Lease recovery/session behavior;
130
+ - adversarial execution-evidence tests.
131
+
132
+ The public Gmail -> Calendar GitHub Actions proof remains separately gated on repository Google OAuth secrets. Authenticated/private-repository onboarding and production OAuth/KMS UX also remain separate product work.
133
+
134
+ ## Automated fresh-install gates
135
+
136
+ `.github/workflows/verify-quickstart.yml` repeats the fixture developer path in a blank temporary project:
137
+
138
+ 1. resolve the latest public `@nullsquare/agent-authority` version from npm;
139
+ 2. create a new empty npm project;
140
+ 3. install only that registry package;
141
+ 4. copy the quickstart file;
142
+ 5. confirm the optional AI SDK was not installed;
143
+ 6. run `node quickstart.mjs`.
144
+
145
+ `.github/workflows/verify-live-quickstart.yml` repeats the real-provider path from another blank Node 20 project and requires exactly one live GitHub request before the unrelated repository is blocked.
146
+
147
+ Both gates have passed against `@nullsquare/agent-authority@0.4.6`. They catch documentation/example drift against the actually published package. They do **not** substitute for timing a first-time external developer, so the roadmap's under-10-minute human adoption gate remains open until that evidence exists.
@@ -0,0 +1,29 @@
1
+ # v0.4.7 connected-execution candidate
2
+
3
+ This candidate closes a concrete adoption gap between the task-first API and the existing credential broker/provider runtime.
4
+
5
+ ## Candidate changes
6
+
7
+ - `task.execute(request)` executes through an `ExecutingAuthorityRuntime` while preserving the current Task Lease as the narrowest authority object.
8
+ - connected deny/step-up outcomes use the same public error classes as `task.run()`.
9
+ - successful connected execution returns sanitized provider output, receipt and execution evidence that can feed `task.authorityFrom()`.
10
+ - `@nullsquare/agent-authority/runtime-env` exposes the existing local encrypted runtime composition for developer onboarding.
11
+ - a sole active provider account can be resolved when requests omit `account_id`; multiple active accounts remain ambiguous and fail closed.
12
+ - default disconnect can remove that sole connection without requiring the caller to know an auto-detected provider account ID.
13
+ - the connected GitHub quickstart proves the local encrypted vault + credential broker + live provider path without copying credentials into task/model context.
14
+
15
+ ## Security boundary
16
+
17
+ This does not create a GitHub token, OAuth flow, GitHub App, KMS, or new identity format. Provider-side least privilege still comes from GitHub. Agent Authority adds a task boundary underneath that connected account authority.
18
+
19
+ The local encrypted vault remains a trusted-local-host developer reference backend.
20
+
21
+ ## Release gate
22
+
23
+ Before version publication:
24
+
25
+ - unit/adversarial tests must prove credential isolation and zero provider execution on task authority delta;
26
+ - packed consumer must import and execute the new public surfaces;
27
+ - live connected GitHub workflow must pass through the encrypted local runtime;
28
+ - Node 20/22, coverage, AI SDK, existing live GitHub proofs and CodeQL must remain green;
29
+ - registry verification must be performed after npm publication before the public release marker is updated.
@@ -1,12 +1,10 @@
1
1
  import { CredentialBroker } from '../src/connections.js';
2
- import { AuthorityRuntime } from '../src/index.js';
3
2
  import {
4
3
  AuthorityApprovalRequiredError,
5
- AuthorityDeniedError,
6
- createTaskLeaseGuard
4
+ AuthorityDeniedError
7
5
  } from '../src/guard.js';
8
6
  import { createGitHubProviderAdapter } from '../src/providers/github.js';
9
- import { createTaskLease } from '../src/task-lease.js';
7
+ import { createTask } from '../src/task.js';
10
8
 
11
9
  const repository = process.env.AA_VALIDATION_REPOSITORY || 'Null-Square/agent-authority';
12
10
  const marker = process.env.AA_VALIDATION_MARKER || 'agent-authority-live-fixture-v1';
@@ -19,69 +17,55 @@ if (!token) {
19
17
  const [owner, repo] = repository.split('/');
20
18
  if (!owner || !repo) throw new Error(`invalid repository: ${repository}`);
21
19
 
22
- const mission = {
23
- version: '0.1',
20
+ const task = createTask({
24
21
  mission_id: 'mission:live-derived-github-mutation',
25
- principal: { id: 'user:validation' },
26
- agent: { id: 'agent:github-actions-validation' },
22
+ principal: 'user:validation',
23
+ agent: 'agent:github-actions-validation',
24
+ request: 'Find the Agent Authority live validation fixture and leave one validation comment',
27
25
  objective: 'Discover the validation issue and comment only on that issue',
28
- resources: [
29
- {
30
- service: 'github',
26
+ permissions: {
27
+ github: {
31
28
  allow: ['issue.list', 'issue.comment'],
32
29
  deny: ['issue.close', 'issue.delete', 'repo.write', 'repo.delete'],
33
30
  constraints: { repository: [repository] }
34
31
  }
35
- ],
36
- constraints: { expires_at: '2099-01-01T00:00:00Z' }
37
- };
38
-
39
- const lease = createTaskLease({
40
- mission,
41
- request: 'Find the Agent Authority live validation fixture and leave one validation comment',
42
- roots: [
43
- {
44
- fact_id: 'fact:repository',
32
+ },
33
+ constraints: { expires_at: '2099-01-01T00:00:00Z' },
34
+ authority: {
35
+ repository: {
45
36
  kind: 'github.repository',
46
37
  value: repository,
47
38
  source: 'validation-task'
48
39
  },
49
- {
50
- fact_id: 'fact:fixture-marker',
40
+ fixtureMarker: {
51
41
  kind: 'github.issue.marker',
52
42
  value: marker,
53
43
  source: 'validation-task'
54
44
  }
55
- ],
45
+ },
56
46
  bindings: [
57
47
  {
58
48
  service: 'github',
59
49
  action: 'issue.list',
60
- context_field: 'repository',
61
- fact_id: 'fact:repository'
50
+ field: 'repository',
51
+ authority: 'repository'
62
52
  },
63
53
  {
64
54
  service: 'github',
65
55
  action: 'issue.list',
66
- context_field: 'fixture_marker',
67
- fact_id: 'fact:fixture-marker'
56
+ field: 'fixture_marker',
57
+ authority: 'fixtureMarker'
68
58
  },
69
59
  {
70
60
  service: 'github',
71
61
  action: 'issue.comment',
72
- context_field: 'repository',
73
- fact_id: 'fact:repository'
74
- },
75
- {
76
- service: 'github',
77
- action: 'issue.comment',
78
- context_field: 'issue_number',
79
- fact_id: 'fact:discovered-issue-number'
62
+ field: 'repository',
63
+ authority: 'repository'
80
64
  }
81
65
  ]
82
66
  });
83
67
 
84
- const guard = createTaskLeaseGuard({ lease, runtime: new AuthorityRuntime() });
68
+ const mission = task.mission;
85
69
  const broker = new CredentialBroker();
86
70
  broker.connect({
87
71
  principal_id: mission.principal.id,
@@ -95,7 +79,7 @@ const adapter = createGitHubProviderAdapter({ broker });
95
79
  const cleanupHeaders = {
96
80
  accept: 'application/vnd.github+json',
97
81
  authorization: `Bearer ${token}`,
98
- 'user-agent': 'agent-authority-derived-mutation-validation-cleanup',
82
+ 'user-agent': 'agent-authority-task-first-derived-mutation-validation-cleanup',
99
83
  'x-github-api-version': '2022-11-28'
100
84
  };
101
85
 
@@ -119,7 +103,7 @@ function discoveryRequest() {
119
103
 
120
104
  async function discoverFixtureIssue() {
121
105
  const request = discoveryRequest();
122
- return guard.run(request, async () => {
106
+ return task.run(request, async () => {
123
107
  providerReadCalls += 1;
124
108
  return adapter.execute({ mission, request });
125
109
  });
@@ -132,7 +116,7 @@ async function commentOnIssue(issueNumber, body) {
132
116
  context: { repository, issue_number: issueNumber, body }
133
117
  };
134
118
 
135
- return guard.run(request, async () => {
119
+ return task.run(request, async () => {
136
120
  providerMutationCalls += 1;
137
121
  return adapter.execute({ mission, request });
138
122
  });
@@ -151,9 +135,10 @@ async function cleanupComment(commentId) {
151
135
  }
152
136
 
153
137
  try {
154
- console.log(`Task root repository: ${repository}`);
155
- console.log(`Task root fixture marker: ${marker}`);
156
- console.log('1. Discover fixture through the reviewed GitHub provider adapter');
138
+ console.log('Task-first live workflow: discover one issue and comment only on that issue');
139
+ console.log(`Task root repository: ${task.authority('repository').value}`);
140
+ console.log(`Task root fixture marker: ${task.authority('fixtureMarker').value}`);
141
+ console.log('1. Discover fixture through task.run() and the reviewed GitHub provider adapter');
157
142
  const discovered = await discoverFixtureIssue();
158
143
 
159
144
  if (discovered.output.selected_issue_match_count !== 1) {
@@ -164,18 +149,21 @@ try {
164
149
  const extractor = adapter.authorityExtractor(discoveryRequest(), 'github.issue.number');
165
150
  if (!extractor) throw new Error('GitHub provider did not advertise the issue-number authority extractor');
166
151
 
167
- const issueFact = lease.deriveFromEvidence({
168
- fact_id: 'fact:discovered-issue-number',
152
+ const issueFact = task.authorityFrom(discovered, {
153
+ name: 'discoveredIssue',
169
154
  kind: 'github.issue.number',
170
- from: ['fact:repository', 'fact:fixture-marker'],
171
- receipt: discovered.receipt,
172
- evidence: discovered.evidence,
173
- output: discovered.output,
155
+ from: ['repository', 'fixtureMarker'],
174
156
  extractor
175
157
  });
176
- console.log(`2. Evidence-verified authority -> issue #${issueFact.value}`);
158
+ task.bind({
159
+ service: 'github',
160
+ action: 'issue.comment',
161
+ field: 'issue_number',
162
+ authority: 'discoveredIssue'
163
+ });
164
+ console.log(`2. task.authorityFrom() -> issue #${issueFact.value}`);
177
165
 
178
- const validationBody = `Agent Authority live evidence-derived authorization validation (${new Date().toISOString()}). Temporary comment; CI removes it after the proof.`;
166
+ const validationBody = `Agent Authority live task-first authorization validation (${new Date().toISOString()}). Temporary comment; CI removes it after the proof.`;
179
167
  const allowedMutation = await commentOnIssue(issueFact.value, validationBody);
180
168
  createdCommentId = allowedMutation.output.comment_id;
181
169
  console.log(`3. ALLOW -> real GitHub comment mutation executed (comment ${createdCommentId})`);
@@ -188,14 +176,19 @@ try {
188
176
  if (!(error instanceof AuthorityApprovalRequiredError) || error.code !== 'authority_delta_required') {
189
177
  throw error;
190
178
  }
179
+ const explanation = task.explain(error);
191
180
  console.log(`4. STEP-UP -> unrelated issue #${unrelatedIssue} blocked before provider mutation`);
181
+ console.log(` ${explanation.summary}`);
182
+ if (explanation.established_authority?.value !== issueFact.value) {
183
+ throw new Error('task-first authority-delta explanation lost the established issue authority');
184
+ }
192
185
  }
193
186
 
194
187
  if (providerMutationCalls !== 1) {
195
188
  throw new Error(`expected exactly one task-side provider mutation before completion, got ${providerMutationCalls}`);
196
189
  }
197
190
 
198
- lease.complete('live evidence-derived mutation validation complete');
191
+ task.complete('live task-first evidence-derived mutation validation complete');
199
192
  try {
200
193
  await commentOnIssue(issueFact.value, 'THIS MUST NOT RUN AFTER TASK COMPLETION');
201
194
  throw new Error('post-completion mutation unexpectedly executed');
@@ -213,8 +206,8 @@ try {
213
206
  throw new Error(`expected exactly one provider mutation after blocked attempts, got ${providerMutationCalls}`);
214
207
  }
215
208
 
216
- console.log('PASS -> GitHub provider output became downstream authority only through execution evidence and a reviewed extractor');
217
- console.log('PASS -> unrelated and post-completion mutations produced zero additional provider mutation calls');
209
+ console.log('PASS -> real GitHub provider output became task-first downstream authority through execution evidence and the reviewed extractor');
210
+ console.log('PASS -> unrelated and post-completion task-first calls produced zero additional provider mutations');
218
211
  } finally {
219
212
  if (createdCommentId) {
220
213
  await cleanupComment(createdCommentId);
@@ -0,0 +1,77 @@
1
+ import { createTask } from '@nullsquare/agent-authority/task';
2
+ import { AuthorityApprovalRequiredError } from '@nullsquare/agent-authority/guard';
3
+ import { createRuntimeEnvironment } from '@nullsquare/agent-authority/runtime-env';
4
+
5
+ const repository = process.argv[2] || process.env.GITHUB_REPOSITORY || 'Null-Square/agent-authority';
6
+ const unrelatedRepository = process.argv[3] || 'octocat/Hello-World';
7
+ const env = createRuntimeEnvironment({ home: process.env.AGENT_AUTHORITY_HOME });
8
+
9
+ const connection = env.broker.getConnection({
10
+ principal_id: env.config.principal_id,
11
+ service: 'github'
12
+ });
13
+
14
+ if (!connection) {
15
+ throw new Error(
16
+ 'No unambiguous GitHub connection. Run: printf %s "$GITHUB_TOKEN" | agent-authority connect github --token-stdin'
17
+ );
18
+ }
19
+
20
+ const task = createTask({
21
+ principal: env.config.principal_id,
22
+ agent: 'agent:connected-quickstart',
23
+ request: `Inspect only ${repository} through the connected GitHub account`,
24
+ permissions: {
25
+ github: {
26
+ allow: ['repo.read'],
27
+ deny: ['repo.write', 'repo.delete'],
28
+ constraints: {}
29
+ }
30
+ },
31
+ authority: {
32
+ repository: { kind: 'github.repository', value: repository }
33
+ },
34
+ bindings: [
35
+ { service: 'github', action: 'repo.read', field: 'repository', authority: 'repository' }
36
+ ],
37
+ runtime: env.runtime
38
+ });
39
+
40
+ console.log(`Connected account -> ${connection.metadata?.login || connection.account_id}`);
41
+ console.log('Credential location -> Agent Authority broker/vault (not task context)');
42
+ console.log('Standing GitHub permission -> repo.read');
43
+ console.log(`Task authority -> ${repository}`);
44
+
45
+ const allowed = await task.execute({
46
+ service: 'github',
47
+ action: 'repo.read',
48
+ context: { repository }
49
+ });
50
+
51
+ console.log(`ALLOW -> connected GitHub returned ${allowed.output.body.full_name}`);
52
+
53
+ try {
54
+ await task.execute({
55
+ service: 'github',
56
+ action: 'repo.read',
57
+ context: { repository: unrelatedRepository }
58
+ });
59
+ throw new Error('unrelated repository unexpectedly executed');
60
+ } catch (error) {
61
+ if (!(error instanceof AuthorityApprovalRequiredError) || error.code !== 'authority_delta_required') {
62
+ throw error;
63
+ }
64
+ console.log(`STEP-UP -> ${task.explain(error).summary}`);
65
+ }
66
+
67
+ const visibleState = JSON.stringify({
68
+ mission: task.mission,
69
+ authorities: task.authorities(),
70
+ connection: env.broker.listConnections(env.config.principal_id),
71
+ allowed_output: allowed.output
72
+ });
73
+ if (/github_pat_|gh[pousr]_|Bearer\s/i.test(visibleState)) {
74
+ throw new Error('credential-like value leaked into public task state');
75
+ }
76
+
77
+ console.log('PASS -> connected credential stayed broker-internal and unrelated repository stayed outside task authority');
@@ -0,0 +1,84 @@
1
+ import { createTask } from '@nullsquare/agent-authority/task';
2
+ import { AuthorityApprovalRequiredError } from '@nullsquare/agent-authority/guard';
3
+
4
+ const allowedRepository = process.argv[2] || 'Null-Square/agent-authority';
5
+ const blockedRepository = process.argv[3] || 'octocat/Hello-World';
6
+
7
+ const task = createTask({
8
+ principal: 'user:quickstart',
9
+ agent: 'agent:quickstart',
10
+ request: `Inspect only ${allowedRepository}`,
11
+ permissions: {
12
+ github: {
13
+ // Model the standing account/app capability as broader than this task.
14
+ // The Task authority root below narrows repo.read to one repository.
15
+ allow: ['repo.read'],
16
+ deny: ['repo.write', 'repo.delete'],
17
+ constraints: {}
18
+ }
19
+ },
20
+ authority: {
21
+ repository: { kind: 'github.repository', value: allowedRepository }
22
+ },
23
+ bindings: [
24
+ { service: 'github', action: 'repo.read', field: 'repository', authority: 'repository' }
25
+ ]
26
+ });
27
+
28
+ let outboundCalls = 0;
29
+
30
+ async function readRepository(repository) {
31
+ return task.run({
32
+ service: 'github',
33
+ action: 'repo.read',
34
+ context: { repository }
35
+ }, async () => {
36
+ outboundCalls += 1;
37
+
38
+ const headers = {
39
+ accept: 'application/vnd.github+json',
40
+ 'user-agent': 'agent-authority-live-quickstart',
41
+ 'x-github-api-version': '2022-11-28'
42
+ };
43
+ if (process.env.GITHUB_TOKEN) {
44
+ headers.authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
45
+ }
46
+
47
+ const response = await fetch(`https://api.github.com/repos/${repository}`, { headers });
48
+ if (!response.ok) {
49
+ throw new Error(`GitHub returned ${response.status} for ${repository}`);
50
+ }
51
+
52
+ const body = await response.json();
53
+ return {
54
+ full_name: body.full_name,
55
+ private: body.private,
56
+ html_url: body.html_url
57
+ };
58
+ });
59
+ }
60
+
61
+ console.log(`Standing GitHub permission -> repo.read`);
62
+ console.log(`Task authority -> ${allowedRepository}`);
63
+ console.log(`GitHub mode -> ${process.env.GITHUB_TOKEN ? 'authenticated token' : 'public API; no credential required'}`);
64
+
65
+ const allowed = await readRepository(allowedRepository);
66
+ console.log(`ALLOW -> real GitHub returned ${allowed.output.full_name}`);
67
+
68
+ try {
69
+ await readRepository(blockedRepository);
70
+ throw new Error('unrelated repository unexpectedly reached GitHub');
71
+ } catch (error) {
72
+ if (!(error instanceof AuthorityApprovalRequiredError) || error.code !== 'authority_delta_required') {
73
+ throw error;
74
+ }
75
+
76
+ console.log(`STEP-UP -> ${task.explain(error).summary}`);
77
+ }
78
+
79
+ if (outboundCalls !== 1) {
80
+ throw new Error(`expected exactly one outbound GitHub call, observed ${outboundCalls}`);
81
+ }
82
+
83
+ task.complete('live GitHub quickstart complete');
84
+ console.log('PASS -> broader standing repo.read permission could not reach an unrelated repository for this task');
@@ -0,0 +1,101 @@
1
+ import { createTask } from '@nullsquare/agent-authority/task';
2
+ import { AuthorityApprovalRequiredError } from '@nullsquare/agent-authority/guard';
3
+ import { githubIssueListSelectedNumberAuthorityExtractor } from '@nullsquare/agent-authority/providers/github';
4
+
5
+ const repository = 'acme/app';
6
+ const marker = 'quickstart-selected-issue';
7
+
8
+ const task = createTask({
9
+ principal: 'user:quickstart',
10
+ agent: 'agent:quickstart',
11
+ request: 'Handle the issue selected for this task and comment only on that issue',
12
+ permissions: {
13
+ github: {
14
+ allow: ['issue.list', 'issue.comment'],
15
+ deny: ['repo.delete'],
16
+ constraints: { repository: [repository] }
17
+ }
18
+ },
19
+ authority: {
20
+ repository: { kind: 'github.repository', value: repository },
21
+ marker: { kind: 'github.issue.marker', value: marker }
22
+ },
23
+ bindings: [
24
+ { service: 'github', action: 'issue.list', field: 'repository', authority: 'repository' },
25
+ { service: 'github', action: 'issue.list', field: 'fixture_marker', authority: 'marker' },
26
+ { service: 'github', action: 'issue.comment', field: 'repository', authority: 'repository' }
27
+ ]
28
+ });
29
+
30
+ let effects = 0;
31
+
32
+ const discovery = await task.run({
33
+ service: 'github',
34
+ action: 'issue.list',
35
+ context: { repository, fixture_marker: marker }
36
+ }, async () => {
37
+ effects += 1;
38
+
39
+ // This is provider-shaped fixture output so the quickstart needs no account.
40
+ // In a real app, replace only this callback with the SDK/provider call you already use.
41
+ return {
42
+ provider: 'github',
43
+ status: 200,
44
+ ok: true,
45
+ selected_issue_number: 42,
46
+ selected_issue_title: 'Quickstart issue',
47
+ selected_issue_match_count: 1,
48
+ selected_issue_marker: marker
49
+ };
50
+ });
51
+
52
+ const issue = task.authorityFrom(discovery, {
53
+ name: 'issue',
54
+ kind: 'github.issue.number',
55
+ from: ['repository', 'marker'],
56
+ extractor: githubIssueListSelectedNumberAuthorityExtractor
57
+ });
58
+
59
+ task.bind({
60
+ service: 'github',
61
+ action: 'issue.comment',
62
+ field: 'issue_number',
63
+ authority: 'issue'
64
+ });
65
+
66
+ await task.run({
67
+ service: 'github',
68
+ action: 'issue.comment',
69
+ context: { repository, issue_number: issue.value, body: 'Handled.' }
70
+ }, async () => {
71
+ effects += 1;
72
+ return { comment_id: 1001 };
73
+ });
74
+
75
+ console.log(`ALLOW -> task discovered issue #${issue.value} and the exact comment effect ran`);
76
+
77
+ try {
78
+ await task.run({
79
+ service: 'github',
80
+ action: 'issue.comment',
81
+ context: { repository, issue_number: 7, body: 'This must not run.' }
82
+ }, async () => {
83
+ effects += 1;
84
+ return { comment_id: 1002 };
85
+ });
86
+
87
+ throw new Error('unrelated issue unexpectedly executed');
88
+ } catch (error) {
89
+ if (!(error instanceof AuthorityApprovalRequiredError) || error.code !== 'authority_delta_required') {
90
+ throw error;
91
+ }
92
+
93
+ console.log(`STEP-UP -> ${task.explain(error).summary}`);
94
+ }
95
+
96
+ if (effects !== 2) {
97
+ throw new Error(`expected exactly 2 provider-shaped effects, observed ${effects}`);
98
+ }
99
+
100
+ task.complete('quickstart complete');
101
+ console.log('PASS -> useful task work ran; unrelated standing permission did not become task authority');