@nullsquare/agent-authority 0.4.5 → 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.
- package/README.md +296 -295
- package/ROADMAP.md +145 -105
- package/benchmarks/task-utility.mjs +130 -0
- package/docs/connected-execution-api.md +60 -0
- package/docs/connected-github.md +140 -0
- package/docs/npm-release.md +30 -10
- package/docs/product-proof.md +311 -0
- package/docs/quickstart.md +147 -0
- package/docs/release-v0.4.7.md +29 -0
- package/examples/live-github-derived-mutation.js +47 -54
- package/examples/quickstart-github-connected.mjs +77 -0
- package/examples/quickstart-github-live.mjs +84 -0
- package/examples/quickstart.mjs +101 -0
- package/examples/task-first-finance.js +223 -0
- package/examples/task-first-github.js +102 -0
- package/examples/task-first-support.js +127 -0
- package/package.json +9 -4
- package/src/cli.js +3 -3
- package/src/connections.js +19 -4
- package/src/task.js +279 -0
package/ROADMAP.md
CHANGED
|
@@ -1,26 +1,79 @@
|
|
|
1
1
|
# Agent Authority Roadmap
|
|
2
2
|
|
|
3
|
-
Agent Authority is implementation-first
|
|
3
|
+
Agent Authority is implementation-first, but the current bottleneck is now **product proof**, not another authorization subsystem.
|
|
4
4
|
|
|
5
|
-
The product thesis
|
|
5
|
+
The product thesis is:
|
|
6
6
|
|
|
7
7
|
> **Give an agent a task, not standing account permissions.**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
The differentiated mechanism is:
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
> **Authority may follow exact resources discovered through already-authorized execution, without becoming ambient account authority.**
|
|
12
|
+
|
|
13
|
+
## Core invariant
|
|
12
14
|
|
|
13
15
|
```text
|
|
14
16
|
Task Lease authority <= Mission authority
|
|
15
17
|
```
|
|
16
18
|
|
|
17
|
-
Across delegation and
|
|
19
|
+
Across delegation, transports and durable state:
|
|
18
20
|
|
|
19
21
|
```text
|
|
20
22
|
authority may stay the same or shrink
|
|
21
23
|
never silently grow
|
|
22
24
|
```
|
|
23
25
|
|
|
26
|
+
## P0 — Task-first product proof — current priority
|
|
27
|
+
|
|
28
|
+
The engine has enough depth to test whether developers actually want this layer. Product work now outranks additional distributed/crypto infrastructure unless a real workflow proves the missing infrastructure is blocking adoption or safety.
|
|
29
|
+
|
|
30
|
+
- [x] task-first facade over Mission + Task Lease + Guard
|
|
31
|
+
- [x] explicit service permissions without requiring hand-authored Mission JSON
|
|
32
|
+
- [x] named task authority roots
|
|
33
|
+
- [x] `task.run()` guarded effect boundary
|
|
34
|
+
- [x] `task.authorityFrom()` strict evidence-derived authority
|
|
35
|
+
- [x] task-first binding of named authority to later effects
|
|
36
|
+
- [x] human-readable authority-delta explanation
|
|
37
|
+
- [x] same task-first calls can opt into durable local state by adding a store
|
|
38
|
+
- [x] self-contained GitHub-shaped task-first demo
|
|
39
|
+
- [x] deterministic utility regression benchmark
|
|
40
|
+
- [x] first live provider proof through task-first API: GitHub issue discovery -> exact issue comment
|
|
41
|
+
- [x] self-contained support/communications proof: Gmail thread -> exact Calendar attendee
|
|
42
|
+
- [x] self-contained operations/finance proof: ticket -> order -> payment -> exact full refund
|
|
43
|
+
- [x] automated blank-project quickstart against the current published npm package
|
|
44
|
+
- [x] blank-project real-provider quickstart: broad standing `repo.read` -> one task-authorized public GitHub repository
|
|
45
|
+
- [ ] coding workflow: issue -> branch -> files -> PR, with merge/deploy outside authority
|
|
46
|
+
- [ ] support/communications expansion: customer -> meeting + reply/CRM, or live task-first Google Actions proof
|
|
47
|
+
- [ ] bounded finance refund: derived payment amount can authorize a smaller refund without authorizing an over-refund
|
|
48
|
+
- [ ] first-time developer can complete a meaningful integration in under 10 minutes
|
|
49
|
+
- [ ] at least one external developer adopts the package without project-author assistance
|
|
50
|
+
|
|
51
|
+
The live GitHub task-first proof selected issue #9 through the reviewed provider mapping, established that issue through `task.authorityFrom()`, executed exactly one real comment mutation, blocked unrelated issue #1 with `authority_delta_required`, surfaced the established-vs-requested explanation, denied the same issue after task completion, and observed `reads=1` / `task_mutations=1` before cleanup.
|
|
52
|
+
|
|
53
|
+
The support/communications proof uses the same task-first API across Gmail and Calendar: one authorized `thread_id` establishes one canonical `sender_email` through the reviewed Google extractor; only that attendee can be used for the task-bound Calendar event, while another thread or attendee executes zero provider-shaped callbacks. The example mirrors the real Google adapter contract but does not replace the still-open public Google Actions evidence gate.
|
|
54
|
+
|
|
55
|
+
The operations/finance proof keeps one evidence-derived chain from support ticket -> order -> payment -> exact refund. The exact payment ID, amount in minor units, and currency are all bound before the refund callback can execute. Unrelated payment, over-refund, wrong currency, partial refund under the current equality model, and post-completion refund all execute zero additional refund callbacks. This proof exposed a deliberate product gap: current bindings are exact equality, so a legitimate partial refund also steps up. Do not add a general expression language; add a narrow derived numeric ceiling only when real workflow/adoption evidence shows partial refunds are required.
|
|
56
|
+
|
|
57
|
+
The fixture fresh-install quickstart is independently exercised from a blank Node 20 project against the latest public npm package. The passing proof installed `@nullsquare/agent-authority@0.4.6`, did not install the optional AI SDK, allowed the exact issue #42 effect, stepped up unrelated issue #7, and observed no unrelated callback. This is automated compatibility evidence, not a substitute for the still-open first-time-human under-10-minute test.
|
|
58
|
+
|
|
59
|
+
The live fresh-install quickstart goes one step further from another blank Node 20 project: Mission-level `github:repo.read` remains broader than the task, Task authority binds that action to `Null-Square/agent-authority`, one real public GitHub API call succeeds, and `octocat/Hello-World` produces `authority_delta_required` before a second `fetch()` can run. No credential is required for the default public-repository path. Authenticated/private-repository onboarding remains separate follow-on work.
|
|
60
|
+
|
|
61
|
+
Current utility regression metrics:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
normal task completion rate
|
|
65
|
+
false approval rate
|
|
66
|
+
true authority-delta step-up rate
|
|
67
|
+
unauthorized effect rate
|
|
68
|
+
provider effects per completed task
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The deterministic fixture target is 100% normal completion, 0% false approvals, 100% true-delta step-up and 0% unauthorized effects. It is a regression fixture, not a real-world benchmark.
|
|
72
|
+
|
|
73
|
+
See `docs/product-proof.md`.
|
|
74
|
+
|
|
75
|
+
**Product gate:** do not prioritize deeper distributed persistence, provider-attestation protocols, new token formats, broad OAuth platform work, A2A, a policy DSL, another MCP control plane, or connector-count expansion until the product proof moves or a concrete workflow shows one of those items is necessary.
|
|
76
|
+
|
|
24
77
|
## M0 — Enforcement foundation — complete
|
|
25
78
|
|
|
26
79
|
- [x] mission validation
|
|
@@ -31,7 +84,7 @@ never silently grow
|
|
|
31
84
|
- [x] delegation attenuation
|
|
32
85
|
- [x] mission revocation
|
|
33
86
|
- [x] action receipts and request hashes
|
|
34
|
-
- [x] protocol-neutral
|
|
87
|
+
- [x] protocol-neutral guard enforcement
|
|
35
88
|
- [x] one-time approvals
|
|
36
89
|
- [x] mutation idempotency
|
|
37
90
|
- [x] short-lived local agent-instance auth
|
|
@@ -41,143 +94,130 @@ never silently grow
|
|
|
41
94
|
- [x] harness-managed connector grant proof
|
|
42
95
|
- [x] Node 20/22 CI, coverage, package checks and CodeQL
|
|
43
96
|
|
|
44
|
-
## M1 — Task Lease / derived-authority proof —
|
|
97
|
+
## M1 — Task Lease / derived-authority proof — implementation established
|
|
45
98
|
|
|
46
|
-
- [x] Task Lease
|
|
99
|
+
- [x] Task Lease around an existing Mission
|
|
47
100
|
- [x] explicit authority roots
|
|
48
|
-
- [x] derived facts anchored to same-
|
|
101
|
+
- [x] derived facts anchored to same-Mission ALLOW receipts
|
|
49
102
|
- [x] parent-fact lineage
|
|
50
103
|
- [x] exact context-field bindings
|
|
51
104
|
- [x] unresolved facts fail closed
|
|
52
|
-
- [x] resource mismatch becomes an authority-delta step-up
|
|
53
|
-
- [x] explicit
|
|
105
|
+
- [x] resource mismatch becomes an authority-delta step-up
|
|
106
|
+
- [x] explicit Mission deny remains the ceiling
|
|
54
107
|
- [x] task completion immediately removes authority
|
|
55
|
-
- [x] independent
|
|
56
|
-
- [x]
|
|
57
|
-
- [x] self-contained
|
|
58
|
-
- [x] reusable Google
|
|
59
|
-
- [x] adversarial Gmail -> Calendar
|
|
60
|
-
- [x]
|
|
61
|
-
- [ ]
|
|
108
|
+
- [x] independent Task Lease expiry
|
|
109
|
+
- [x] Task Lease ID/hash in receipts
|
|
110
|
+
- [x] self-contained Gmail-thread -> Calendar-attendee demo
|
|
111
|
+
- [x] reusable Google Gmail/Calendar adapter
|
|
112
|
+
- [x] adversarial Gmail -> Calendar zero-provider-call tests
|
|
113
|
+
- [x] connected-account Gmail -> Calendar smoke proof
|
|
114
|
+
- [ ] public GitHub Actions Gmail -> Calendar proof after repository OAuth secrets are configured
|
|
62
115
|
|
|
63
|
-
**Success criterion:**
|
|
116
|
+
**Success criterion:** an authorized read can establish exactly one later resource as task authority while an unrelated resource is technically blocked without requiring approval for every normal task step.
|
|
64
117
|
|
|
65
|
-
The
|
|
118
|
+
The implementation criterion is met. Public Actions reproducibility remains a separate evidence gate because the interactive Google connector credential cannot be reused as repository secrets.
|
|
66
119
|
|
|
67
|
-
## M2 — Durable task execution — durable session established
|
|
68
|
-
|
|
69
|
-
Build only what the real Task Lease workflows prove necessary.
|
|
120
|
+
## M2 — Durable task execution — local durable session established
|
|
70
121
|
|
|
71
122
|
- [x] authenticated local Task Lease persistence/recovery
|
|
123
|
+
- [x] exact Mission-hash binding on recovery
|
|
72
124
|
- [x] atomic authenticated fact/binding/status transaction primitive
|
|
73
|
-
- [x] stale-writer compare-and-swap protection
|
|
74
|
-
- [x] local per-lease transaction lock
|
|
75
|
-
- [x]
|
|
76
|
-
- [x]
|
|
77
|
-
- [
|
|
78
|
-
- [x] completion
|
|
79
|
-
- [ ]
|
|
80
|
-
- [ ]
|
|
81
|
-
- [ ]
|
|
82
|
-
|
|
83
|
-
`JsonFileTaskLeaseStore` writes the complete lease snapshot atomically and authenticates it with a purpose-derived HMAC key from the local Agent Authority master key. Recovery verifies the envelope before authority hydration, binds the snapshot to the exact mission hash, validates status/timestamps and the authority-fact DAG, restores evidence-derived provenance hashes, and verifies the reconstructed Task Lease hash.
|
|
84
|
-
|
|
85
|
-
`JsonFileTaskLeaseStore.transact()` is the local durable mutation boundary: acquire one per-lease filesystem lock, reload authenticated current state, compare an optional expected lease hash, apply one synchronous Task Lease mutation, validate the complete authority graph, and atomically replace the authenticated snapshot. Stale writers receive `task_lease_state_conflict`; overlapping local transactions receive `task_lease_state_locked`.
|
|
86
|
-
|
|
87
|
-
`DurableTaskLeaseSession` is the normal mutation facade over that primitive. `addRoot()`, legacy `derive()`, strict `deriveFromEvidence()`, `bind()` and `complete()` commit through compare-and-swap and update the session only after durable success. The session exposes detached mission/snapshot/fact views rather than its mutable internal Task Lease. It never auto-replays a stale semantic mutation after a conflict.
|
|
125
|
+
- [x] stale-writer compare-and-swap protection
|
|
126
|
+
- [x] local per-lease transaction lock
|
|
127
|
+
- [x] mission-alias hardening inside transactions
|
|
128
|
+
- [x] automatic durable Task Lease session
|
|
129
|
+
- [x] security-critical session refresh before authority evaluation
|
|
130
|
+
- [x] durable completion/expiry across restart
|
|
131
|
+
- [ ] approved authority delta safely updates a live durable task
|
|
132
|
+
- [ ] durable lineage query for one exact authorization decision
|
|
133
|
+
- [ ] stronger multi-process stress/recovery tooling
|
|
134
|
+
- [ ] crash-safe remote-effect/receipt/state coupling
|
|
88
135
|
|
|
89
|
-
The
|
|
136
|
+
The existing local durability layer is sufficient for product proof. The unchecked items remain research/follow-on work unless a real workflow demonstrates that they block useful adoption or safety.
|
|
90
137
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
**Success criterion:** a Task Lease survives restart without gaining authority or losing provenance, cooperating local workers cannot silently overwrite newer authority, and ordinary durable mutations use the CAS boundary by default through the session API. Authenticated recovery, local transactions and the durable session are now demonstrated; approved deltas, stronger multi-process stress and remote-effect coupling remain open.
|
|
138
|
+
**Success criterion:** restart and cooperating local workers do not expand authority or silently overwrite newer task state. This is established for the reference local backend.
|
|
94
139
|
|
|
95
140
|
## M3 — Trustworthy derived facts — two-provider proof established
|
|
96
141
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
- [x]
|
|
100
|
-
- [x]
|
|
101
|
-
- [x]
|
|
102
|
-
- [x]
|
|
103
|
-
- [x]
|
|
104
|
-
- [
|
|
105
|
-
- [
|
|
106
|
-
- [ ] define provider/result attestation stronger than a trusted host output hash where practical
|
|
107
|
-
- [ ] define freshness/invalidation rules when a source resource changes
|
|
142
|
+
- [x] reviewed adapter extractor contract
|
|
143
|
+
- [x] successful guarded output bound to exact ALLOW receipt/request/output hash
|
|
144
|
+
- [x] strict `deriveFromEvidence()` where caller cannot provide the authority value
|
|
145
|
+
- [x] Gmail sender -> Calendar attendee strict derivation
|
|
146
|
+
- [x] GitHub issue discovery -> comment strict derivation
|
|
147
|
+
- [x] substitution/tamper/replay/cross-lease/wrong-operation/dangerous-selector tests
|
|
148
|
+
- [x] shared Google/GitHub authority-extractor conformance fixtures
|
|
149
|
+
- [ ] stronger provider/result attestation where a real provider makes it practical
|
|
150
|
+
- [ ] source freshness/invalidation semantics where a real workflow requires it
|
|
108
151
|
|
|
109
|
-
|
|
152
|
+
Do **not** build a general semantic policy language around this primitive.
|
|
110
153
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
**Success criterion:** provider-derived authority cannot be established through the strict path unless the exact guarded output, ALLOW receipt and reviewed extractor contract agree on the selected value. This behavior is now exercised across two provider mappings. Stronger provider attestation and source invalidation remain separate follow-on problems.
|
|
154
|
+
**Success criterion:** strict provider-derived authority requires agreement between the exact guarded output, ALLOW receipt and reviewed extractor. This is demonstrated across Google and GitHub.
|
|
114
155
|
|
|
115
156
|
## M4 — Same task, multiple transports — complete
|
|
116
157
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
- [x] same Task Lease through ordinary `guard.run()` SDK call
|
|
158
|
+
- [x] same Task Lease through direct guard/SDK execution
|
|
120
159
|
- [x] same Task Lease through MCP gateway
|
|
121
160
|
- [x] same Task Lease through brokered provider execution
|
|
122
|
-
- [x]
|
|
123
|
-
- [x] interoperability
|
|
161
|
+
- [x] real Vercel AI SDK protected-tool path
|
|
162
|
+
- [x] interoperability/adversarial vectors across transports
|
|
124
163
|
|
|
125
|
-
|
|
164
|
+
Changing transport or configured harness execution path does not expand task authority in the demonstrated paths.
|
|
126
165
|
|
|
127
|
-
|
|
166
|
+
## M5 — Adoption UX — follows product proof, not infrastructure breadth
|
|
128
167
|
|
|
129
|
-
|
|
168
|
+
Prioritize only the UX needed by successful P0 workflows.
|
|
130
169
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
## M5 — Production credential and approval UX
|
|
136
|
-
|
|
137
|
-
Only after the task-bound enforcement model is validated.
|
|
138
|
-
|
|
139
|
-
- [ ] GitHub browser/App onboarding instead of token-stdin
|
|
140
|
-
- [ ] reusable OAuth/OIDC connection engine
|
|
141
|
-
- [ ] OS keychain / KMS-backed secret backend
|
|
142
|
-
- [ ] automatic short-lived agent session bootstrap
|
|
170
|
+
- [x] credential-free fresh-install quickstart validated against the current public npm package
|
|
171
|
+
- [x] one low-friction real provider onboarding path: public GitHub read from a blank npm project
|
|
172
|
+
- [ ] authenticated/private-provider onboarding path
|
|
143
173
|
- [ ] compact approval UI showing the exact authority delta
|
|
144
|
-
- [ ]
|
|
174
|
+
- [ ] automatic short-lived agent session bootstrap where needed
|
|
175
|
+
- [ ] framework integration starter focused on task-first API
|
|
176
|
+
- [ ] external-developer quickstart feedback loop
|
|
145
177
|
|
|
146
|
-
|
|
178
|
+
Items such as reusable OAuth/OIDC engines, KMS backends and signed-receipt experiments remain optional until product usage justifies them.
|
|
147
179
|
|
|
148
|
-
|
|
180
|
+
**Success criterion:** an external developer can install Agent Authority, connect one real provider, authorize one meaningful task and complete it without exposing a long-lived credential to the model or learning the internal authority machinery first.
|
|
149
181
|
|
|
150
|
-
|
|
151
|
-
- [ ] framework integration examples
|
|
152
|
-
- [ ] `good first issue` tasks based on real mappings/tests
|
|
153
|
-
- [ ] independent contributor implementation of one adapter
|
|
154
|
-
- [x] release packaging and npm publication
|
|
155
|
-
- [ ] documentation site only when README/docs become too large
|
|
182
|
+
## M6 — Ecosystem only after repeatable adoption
|
|
156
183
|
|
|
157
|
-
|
|
184
|
+
- [ ] adapter/conformance starter template
|
|
185
|
+
- [ ] framework examples driven by real user requests
|
|
186
|
+
- [ ] good-first-issue tasks based on proven workflows
|
|
187
|
+
- [ ] independent contributor implementation of one mapping/adapter
|
|
188
|
+
- [x] npm release packaging and registry verification
|
|
189
|
+
- [ ] documentation site only when the current README/docs become genuinely limiting
|
|
158
190
|
|
|
159
|
-
|
|
191
|
+
## M7 — Standards interoperability only after operational evidence
|
|
160
192
|
|
|
161
193
|
- [ ] map Task Lease concepts to emerging task/intent authorization work
|
|
162
194
|
- [ ] accept external authorization envelopes where useful
|
|
163
195
|
- [ ] avoid creating a competing identity/token standard
|
|
164
|
-
- [ ] publish stable
|
|
165
|
-
- [ ] evaluate
|
|
166
|
-
|
|
167
|
-
##
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
196
|
+
- [ ] publish stable non-amplification/authority-lineage test vectors
|
|
197
|
+
- [ ] evaluate standards participation only after independent implementations/users exist
|
|
198
|
+
|
|
199
|
+
## Freeze list
|
|
200
|
+
|
|
201
|
+
Unless a real workflow proves one is necessary now:
|
|
202
|
+
|
|
203
|
+
- distributed Task Lease databases
|
|
204
|
+
- generic storage abstraction layers
|
|
205
|
+
- provider-signed attestation protocol design
|
|
206
|
+
- new token or identity formats
|
|
207
|
+
- a general delegation protocol
|
|
208
|
+
- proprietary universal policy DSL
|
|
209
|
+
- broad OAuth platform work
|
|
210
|
+
- another MCP control plane
|
|
211
|
+
- A2A protocol implementation
|
|
212
|
+
- connector-count expansion for its own sake
|
|
213
|
+
- full distributed transactions across arbitrary remote providers
|
|
214
|
+
- dashboard-first enterprise product work
|
|
176
215
|
|
|
177
216
|
## Research questions
|
|
178
217
|
|
|
179
|
-
1.
|
|
180
|
-
2.
|
|
181
|
-
3.
|
|
182
|
-
4.
|
|
183
|
-
5.
|
|
218
|
+
1. Can a first-time developer understand and integrate the task-first model in under 10 minutes?
|
|
219
|
+
2. Which real workflows benefit enough from derived authority that an `if` statement is no longer sufficient?
|
|
220
|
+
3. Where does Agent Authority create unnecessary approval friction or reduce useful task completion?
|
|
221
|
+
4. How should an explicitly approved authority delta update a running task without opening wildcard authority?
|
|
222
|
+
5. Which source-data changes actually require downstream authority invalidation in real workflows?
|
|
223
|
+
6. What remote-effect coupling is necessary in practice, and which providers already offer idempotency/transaction primitives we can reuse instead of inventing our own protocol?
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { createTask } from '../src/task.js';
|
|
2
|
+
import { AuthorityApprovalRequiredError } from '../src/guard.js';
|
|
3
|
+
|
|
4
|
+
const NORMAL_TASKS = 40;
|
|
5
|
+
const DELTA_ATTACKS = 10;
|
|
6
|
+
|
|
7
|
+
function selectedItemExtractor({ receipt, output } = {}) {
|
|
8
|
+
if (receipt?.service !== 'demo' || receipt?.action !== 'item.discover') {
|
|
9
|
+
const error = new Error('wrong operation');
|
|
10
|
+
error.code = 'trusted_extractor_operation_mismatch';
|
|
11
|
+
throw error;
|
|
12
|
+
}
|
|
13
|
+
if (typeof output?.selected_item !== 'string') {
|
|
14
|
+
const error = new Error('invalid selected item');
|
|
15
|
+
error.code = 'trusted_extractor_output_invalid';
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
return { extractor_id: 'benchmark.selected-item.v1', selector: 'output.selected_item' };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function newTask(index) {
|
|
22
|
+
const catalog = `catalog:${index}`;
|
|
23
|
+
return {
|
|
24
|
+
catalog,
|
|
25
|
+
task: createTask({
|
|
26
|
+
principal: 'user:benchmark',
|
|
27
|
+
agent: 'agent:benchmark',
|
|
28
|
+
request: `Discover and update the selected item for benchmark task ${index}`,
|
|
29
|
+
permissions: {
|
|
30
|
+
demo: {
|
|
31
|
+
allow: ['item.discover', 'item.update'],
|
|
32
|
+
deny: ['item.delete'],
|
|
33
|
+
constraints: { catalog: [catalog] }
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
authority: {
|
|
37
|
+
catalog: { kind: 'demo.catalog', value: catalog }
|
|
38
|
+
},
|
|
39
|
+
bindings: [
|
|
40
|
+
{ service: 'demo', action: 'item.discover', field: 'catalog', authority: 'catalog' },
|
|
41
|
+
{ service: 'demo', action: 'item.update', field: 'catalog', authority: 'catalog' }
|
|
42
|
+
]
|
|
43
|
+
})
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const metrics = {
|
|
48
|
+
normal_tasks: NORMAL_TASKS,
|
|
49
|
+
completed_normal_tasks: 0,
|
|
50
|
+
normal_approval_interruptions: 0,
|
|
51
|
+
delta_attacks: DELTA_ATTACKS,
|
|
52
|
+
delta_step_ups: 0,
|
|
53
|
+
unauthorized_effects: 0,
|
|
54
|
+
provider_effects: 0
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
for (let index = 0; index < NORMAL_TASKS; index += 1) {
|
|
58
|
+
const { task, catalog } = newTask(index);
|
|
59
|
+
const item = `item:${index}`;
|
|
60
|
+
|
|
61
|
+
const discovery = await task.run({
|
|
62
|
+
service: 'demo',
|
|
63
|
+
action: 'item.discover',
|
|
64
|
+
context: { catalog }
|
|
65
|
+
}, async () => {
|
|
66
|
+
metrics.provider_effects += 1;
|
|
67
|
+
return { selected_item: item };
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
task.authorityFrom(discovery, {
|
|
71
|
+
name: 'item',
|
|
72
|
+
kind: 'demo.item',
|
|
73
|
+
from: 'catalog',
|
|
74
|
+
extractor: selectedItemExtractor
|
|
75
|
+
});
|
|
76
|
+
task.bind({ service: 'demo', action: 'item.update', field: 'item', authority: 'item' });
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await task.run({
|
|
80
|
+
service: 'demo',
|
|
81
|
+
action: 'item.update',
|
|
82
|
+
context: { catalog, item }
|
|
83
|
+
}, async () => {
|
|
84
|
+
metrics.provider_effects += 1;
|
|
85
|
+
return { updated: item };
|
|
86
|
+
});
|
|
87
|
+
metrics.completed_normal_tasks += 1;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (error instanceof AuthorityApprovalRequiredError) metrics.normal_approval_interruptions += 1;
|
|
90
|
+
else throw error;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (index < DELTA_ATTACKS) {
|
|
94
|
+
try {
|
|
95
|
+
await task.run({
|
|
96
|
+
service: 'demo',
|
|
97
|
+
action: 'item.update',
|
|
98
|
+
context: { catalog, item: `unrelated:${index}` }
|
|
99
|
+
}, async () => {
|
|
100
|
+
metrics.provider_effects += 1;
|
|
101
|
+
metrics.unauthorized_effects += 1;
|
|
102
|
+
return { updated: `unrelated:${index}` };
|
|
103
|
+
});
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error instanceof AuthorityApprovalRequiredError && error.code === 'authority_delta_required') {
|
|
106
|
+
metrics.delta_step_ups += 1;
|
|
107
|
+
} else {
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const percent = (numerator, denominator) => denominator === 0 ? 0 : (numerator / denominator) * 100;
|
|
115
|
+
const report = {
|
|
116
|
+
...metrics,
|
|
117
|
+
normal_task_completion_rate: percent(metrics.completed_normal_tasks, metrics.normal_tasks),
|
|
118
|
+
false_approval_rate: percent(metrics.normal_approval_interruptions, metrics.normal_tasks),
|
|
119
|
+
delta_step_up_rate: percent(metrics.delta_step_ups, metrics.delta_attacks),
|
|
120
|
+
unauthorized_effect_rate: percent(metrics.unauthorized_effects, metrics.delta_attacks)
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
console.log(JSON.stringify(report, null, 2));
|
|
124
|
+
|
|
125
|
+
if (report.normal_task_completion_rate !== 100) throw new Error('normal task completion regressed');
|
|
126
|
+
if (report.false_approval_rate !== 0) throw new Error('normal tasks were interrupted by approval');
|
|
127
|
+
if (report.delta_step_up_rate !== 100) throw new Error('authority deltas were not consistently surfaced');
|
|
128
|
+
if (report.unauthorized_effect_rate !== 0) throw new Error('an unrelated effect executed');
|
|
129
|
+
|
|
130
|
+
console.log('PASS -> deterministic product utility benchmark preserved useful task completion and blocked unrelated effects');
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Task-owned vs broker-owned execution
|
|
2
|
+
|
|
3
|
+
The task-first facade supports two effect ownership modes with the same Task Lease semantics.
|
|
4
|
+
|
|
5
|
+
## Application-owned effect
|
|
6
|
+
|
|
7
|
+
Use `task.run(request, callback)` when the application already owns the SDK or provider call:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
const result = await task.run(request, () => existingSdkCall());
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The callback runs only after the Task Lease returns ALLOW. The task receives execution evidence for the exact callback output.
|
|
14
|
+
|
|
15
|
+
## Agent Authority connected-provider effect
|
|
16
|
+
|
|
17
|
+
Use `task.execute(request)` when the provider credential and provider adapter should stay behind Agent Authority's broker boundary:
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
const result = await task.execute(request);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This requires the task to be created with an `ExecutingAuthorityRuntime`, such as the runtime produced by the local `createRuntimeEnvironment()` helper.
|
|
24
|
+
|
|
25
|
+
The connected path performs:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
Task Lease evaluation
|
|
29
|
+
|
|
|
30
|
+
+--> DENY / STEP-UP -> stop before provider readiness or credential resolution
|
|
31
|
+
|
|
|
32
|
+
v
|
|
33
|
+
connected-provider readiness
|
|
34
|
+
|
|
|
35
|
+
v
|
|
36
|
+
credential broker resolves secret internally
|
|
37
|
+
|
|
|
38
|
+
v
|
|
39
|
+
provider adapter executes
|
|
40
|
+
|
|
|
41
|
+
v
|
|
42
|
+
sanitized output + ALLOW receipt + execution evidence
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`task.execute()` converts broker runtime `deny` and `require_approval` results into the same `AuthorityDeniedError` and `AuthorityApprovalRequiredError` classes used by `task.run()`.
|
|
46
|
+
|
|
47
|
+
Successful connected execution can therefore feed directly into `task.authorityFrom()` when a reviewed provider extractor exists.
|
|
48
|
+
|
|
49
|
+
## Credential boundary
|
|
50
|
+
|
|
51
|
+
The provider credential belongs to the broker/runtime, not the task request. It should not be copied into:
|
|
52
|
+
|
|
53
|
+
- Mission or Task Lease authority facts;
|
|
54
|
+
- model/tool arguments;
|
|
55
|
+
- action receipts;
|
|
56
|
+
- execution evidence;
|
|
57
|
+
- provider-normalized output;
|
|
58
|
+
- public connection listings.
|
|
59
|
+
|
|
60
|
+
The local runtime's encrypted file vault is a developer/trusted-host reference implementation. Production applications should use an appropriate secret manager/KMS and provider-native credential lifecycle.
|