@agenticprimitives/fabric 0.0.0-alpha.3 → 0.0.0-alpha.30
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/CLAUDE.md +30 -29
- package/README.md +18 -16
- package/dist/adoption.d.ts +79 -0
- package/dist/adoption.d.ts.map +1 -0
- package/dist/adoption.js +95 -0
- package/dist/adoption.js.map +1 -0
- package/dist/case-emissions.d.ts +95 -0
- package/dist/case-emissions.d.ts.map +1 -0
- package/dist/case-emissions.js +208 -0
- package/dist/case-emissions.js.map +1 -0
- package/dist/case-machine.d.ts +28 -2
- package/dist/case-machine.d.ts.map +1 -1
- package/dist/case-machine.js +34 -7
- package/dist/case-machine.js.map +1 -1
- package/dist/cloudflare/authority-verdict-cache.d.ts +28 -1
- package/dist/cloudflare/authority-verdict-cache.d.ts.map +1 -1
- package/dist/cloudflare/authority-verdict-cache.js +32 -1
- package/dist/cloudflare/authority-verdict-cache.js.map +1 -1
- package/dist/cloudflare/index.d.ts +4 -1
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/index.js +7 -1
- package/dist/cloudflare/index.js.map +1 -1
- package/dist/cloudflare/interaction-store.d.ts +55 -0
- package/dist/cloudflare/interaction-store.d.ts.map +1 -0
- package/dist/cloudflare/interaction-store.js +230 -0
- package/dist/cloudflare/interaction-store.js.map +1 -0
- package/dist/cloudflare/principal-gateway-do.d.ts +136 -2
- package/dist/cloudflare/principal-gateway-do.d.ts.map +1 -1
- package/dist/cloudflare/principal-gateway-do.js +190 -8
- package/dist/cloudflare/principal-gateway-do.js.map +1 -1
- package/dist/cloudflare/sql-vault.d.ts.map +1 -1
- package/dist/cloudflare/sql-vault.js +56 -0
- package/dist/cloudflare/sql-vault.js.map +1 -1
- package/dist/cloudflare/vault-exchange-store.d.ts +47 -0
- package/dist/cloudflare/vault-exchange-store.d.ts.map +1 -0
- package/dist/cloudflare/vault-exchange-store.js +118 -0
- package/dist/cloudflare/vault-exchange-store.js.map +1 -0
- package/dist/cloudflare/vault-interaction-store.d.ts +44 -0
- package/dist/cloudflare/vault-interaction-store.d.ts.map +1 -0
- package/dist/cloudflare/vault-interaction-store.js +245 -0
- package/dist/cloudflare/vault-interaction-store.js.map +1 -0
- package/dist/communicative-act.d.ts +24 -1
- package/dist/communicative-act.d.ts.map +1 -1
- package/dist/communicative-act.js +28 -0
- package/dist/communicative-act.js.map +1 -1
- package/dist/delivery-envelope.d.ts.map +1 -1
- package/dist/delivery-envelope.js +0 -1
- package/dist/delivery-envelope.js.map +1 -1
- package/dist/email/index.d.ts +75 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +77 -0
- package/dist/email/index.js.map +1 -0
- package/dist/enum-retirement.d.ts +43 -0
- package/dist/enum-retirement.d.ts.map +1 -0
- package/dist/enum-retirement.js +93 -0
- package/dist/enum-retirement.js.map +1 -0
- package/dist/exchange-record.d.ts +35 -4
- package/dist/exchange-record.d.ts.map +1 -1
- package/dist/exchange-record.js.map +1 -1
- package/dist/exchange-reducer.d.ts +32 -4
- package/dist/exchange-reducer.d.ts.map +1 -1
- package/dist/exchange-reducer.js +75 -1
- package/dist/exchange-reducer.js.map +1 -1
- package/dist/exchange-state.d.ts +30 -8
- package/dist/exchange-state.d.ts.map +1 -1
- package/dist/exchange-state.js +12 -10
- package/dist/exchange-state.js.map +1 -1
- package/dist/inbox-doc.d.ts +2 -2
- package/dist/inbox-doc.d.ts.map +1 -1
- package/dist/inbox-doc.js.map +1 -1
- package/dist/inbox-projection.d.ts +2 -2
- package/dist/inbox-projection.d.ts.map +1 -1
- package/dist/inbox-projection.js.map +1 -1
- package/dist/inbox-revision.d.ts +42 -0
- package/dist/inbox-revision.d.ts.map +1 -0
- package/dist/inbox-revision.js +57 -0
- package/dist/inbox-revision.js.map +1 -0
- package/dist/index.d.ts +26 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -2
- package/dist/index.js.map +1 -1
- package/dist/interaction-machines.d.ts +136 -0
- package/dist/interaction-machines.d.ts.map +1 -0
- package/dist/interaction-machines.js +174 -0
- package/dist/interaction-machines.js.map +1 -0
- package/dist/interaction-profile.d.ts +136 -0
- package/dist/interaction-profile.d.ts.map +1 -0
- package/dist/interaction-profile.js +212 -0
- package/dist/interaction-profile.js.map +1 -0
- package/dist/interaction-record.d.ts +168 -0
- package/dist/interaction-record.d.ts.map +1 -0
- package/dist/interaction-record.js +108 -0
- package/dist/interaction-record.js.map +1 -0
- package/dist/interactions/grant-scopes.d.ts +21 -0
- package/dist/interactions/grant-scopes.d.ts.map +1 -0
- package/dist/interactions/grant-scopes.js +88 -0
- package/dist/interactions/grant-scopes.js.map +1 -0
- package/dist/interactions/index.d.ts +1 -0
- package/dist/interactions/index.d.ts.map +1 -1
- package/dist/interactions/index.js +3 -0
- package/dist/interactions/index.js.map +1 -1
- package/dist/interactions/machine.d.ts +2 -2
- package/dist/interactions/machine.d.ts.map +1 -1
- package/dist/interactions/machine.js +2 -2
- package/dist/interactions/machine.js.map +1 -1
- package/dist/interactions/open-case.d.ts +60 -0
- package/dist/interactions/open-case.d.ts.map +1 -0
- package/dist/interactions/open-case.js +83 -0
- package/dist/interactions/open-case.js.map +1 -0
- package/dist/interactions/store.d.ts +9 -0
- package/dist/interactions/store.d.ts.map +1 -1
- package/dist/interactions/store.js +57 -2
- package/dist/interactions/store.js.map +1 -1
- package/dist/messaging/board.d.ts +3 -3
- package/dist/messaging/board.d.ts.map +1 -1
- package/dist/messaging/board.js +2 -2
- package/dist/messaging/board.js.map +1 -1
- package/dist/messaging/consult-routing.d.ts +21 -14
- package/dist/messaging/consult-routing.d.ts.map +1 -1
- package/dist/messaging/consult-routing.js +34 -25
- package/dist/messaging/consult-routing.js.map +1 -1
- package/dist/messaging/consult.d.ts +4 -0
- package/dist/messaging/consult.d.ts.map +1 -1
- package/dist/messaging/consult.js +3 -1
- package/dist/messaging/consult.js.map +1 -1
- package/dist/messaging/conversation-interaction.d.ts +66 -0
- package/dist/messaging/conversation-interaction.d.ts.map +1 -0
- package/dist/messaging/conversation-interaction.js +139 -0
- package/dist/messaging/conversation-interaction.js.map +1 -0
- package/dist/messaging/conversation.d.ts +69 -1
- package/dist/messaging/conversation.d.ts.map +1 -1
- package/dist/messaging/conversation.js +131 -0
- package/dist/messaging/conversation.js.map +1 -1
- package/dist/messaging/delivery.d.ts +2 -2
- package/dist/messaging/delivery.d.ts.map +1 -1
- package/dist/messaging/delivery.js +8 -1
- package/dist/messaging/delivery.js.map +1 -1
- package/dist/messaging/envelope.d.ts +7 -3
- package/dist/messaging/envelope.d.ts.map +1 -1
- package/dist/messaging/envelope.js +18 -1
- package/dist/messaging/envelope.js.map +1 -1
- package/dist/messaging/hash.d.ts +8 -2
- package/dist/messaging/hash.d.ts.map +1 -1
- package/dist/messaging/hash.js +6 -0
- package/dist/messaging/hash.js.map +1 -1
- package/dist/messaging/inbox-assistant.d.ts +16 -4
- package/dist/messaging/inbox-assistant.d.ts.map +1 -1
- package/dist/messaging/inbox-assistant.js +27 -4
- package/dist/messaging/inbox-assistant.js.map +1 -1
- package/dist/messaging/index.d.ts +6 -2
- package/dist/messaging/index.d.ts.map +1 -1
- package/dist/messaging/index.js +10 -1
- package/dist/messaging/index.js.map +1 -1
- package/dist/messaging/outbound-message.d.ts +42 -0
- package/dist/messaging/outbound-message.d.ts.map +1 -0
- package/dist/messaging/outbound-message.js +64 -0
- package/dist/messaging/outbound-message.js.map +1 -0
- package/dist/messaging/projector.d.ts +3 -3
- package/dist/messaging/projector.d.ts.map +1 -1
- package/dist/messaging/projector.js.map +1 -1
- package/dist/messaging/receipts.d.ts +3 -3
- package/dist/messaging/receipts.d.ts.map +1 -1
- package/dist/messaging/receipts.js.map +1 -1
- package/dist/messaging/rules.d.ts +10 -3
- package/dist/messaging/rules.d.ts.map +1 -1
- package/dist/messaging/rules.js +8 -1
- package/dist/messaging/rules.js.map +1 -1
- package/dist/messaging/store.d.ts +4 -4
- package/dist/messaging/store.d.ts.map +1 -1
- package/dist/messaging/store.js +5 -3
- package/dist/messaging/store.js.map +1 -1
- package/dist/messaging/types.d.ts +30 -0
- package/dist/messaging/types.d.ts.map +1 -1
- package/dist/messaging/types.js +3 -0
- package/dist/messaging/types.js.map +1 -1
- package/dist/messaging/vault-store.d.ts +2 -2
- package/dist/messaging/vault-store.d.ts.map +1 -1
- package/dist/protocol-binding.d.ts +71 -0
- package/dist/protocol-binding.d.ts.map +1 -0
- package/dist/protocol-binding.js +0 -0
- package/dist/protocol-binding.js.map +1 -0
- package/dist/record-catalog.d.ts.map +1 -1
- package/dist/record-catalog.js +12 -0
- package/dist/record-catalog.js.map +1 -1
- package/package.json +28 -15
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// The profile mechanism (spec 340 W4) — what replaces the frozen `MessageKind`/`CaseState` enums.
|
|
2
|
+
//
|
|
3
|
+
// A profile declares what one KIND of interaction permits: its roles, its acts, the context every
|
|
4
|
+
// exchange in it must pin, which acts can create an obligation, and how it ends. A new interaction
|
|
5
|
+
// type is therefore a new profile — data — rather than a new enum member and a schema change. That is
|
|
6
|
+
// the whole extensibility story for anyone outside this repo.
|
|
7
|
+
//
|
|
8
|
+
// A PROFILE PERMITS NOTHING. `checkExchangeAgainstProfile` answers "is this move well-formed FOR THIS
|
|
9
|
+
// KIND of interaction", which is a shape question. Whether the actor was allowed to make it is
|
|
10
|
+
// `delegation` + `entitlements` + `tool-policy`, re-evaluated at the moment of acting (ADR-0041).
|
|
11
|
+
// The two are easy to conflate precisely because both can say no, so: a profile refusal means the
|
|
12
|
+
// record would be uninterpretable; an authority refusal means the party had no right. A profile
|
|
13
|
+
// saying yes has granted nothing.
|
|
14
|
+
//
|
|
15
|
+
// Versioning is the anti-downgrade control (spec 340 §6). `<name>/<semver>`, pinned onto every
|
|
16
|
+
// Exchange, and both halves required — a bare name would make the pin unfalsifiable.
|
|
17
|
+
import { isProfileVersion } from './interaction-record.js';
|
|
18
|
+
/**
|
|
19
|
+
* Every violation, not the first.
|
|
20
|
+
*
|
|
21
|
+
* A caller fixing one at a time needs N round trips to learn the exchange was never valid, and the
|
|
22
|
+
* usual outcome of that is a caller who stops checking.
|
|
23
|
+
*/
|
|
24
|
+
export function checkExchangeAgainstProfile(profile, exchange) {
|
|
25
|
+
const violations = [];
|
|
26
|
+
if (!isProfileVersion(exchange.profileVersion)) {
|
|
27
|
+
violations.push({ reason: 'malformed-profile-version', value: exchange.profileVersion });
|
|
28
|
+
}
|
|
29
|
+
else if (exchange.profileVersion !== profile.profileVersion) {
|
|
30
|
+
// Not automatically an attack — an interaction whose profile versioned mid-flight legitimately
|
|
31
|
+
// contains exchanges under both. It IS the signal, and the only thing that disagrees afterwards.
|
|
32
|
+
violations.push({
|
|
33
|
+
reason: 'profile-version-mismatch',
|
|
34
|
+
pinned: exchange.profileVersion,
|
|
35
|
+
expected: profile.profileVersion,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
for (const act of exchange.acts) {
|
|
39
|
+
if (!profile.permittedActs.includes(act))
|
|
40
|
+
violations.push({ reason: 'act-not-permitted', act });
|
|
41
|
+
}
|
|
42
|
+
for (const kind of profile.requiredContextKinds) {
|
|
43
|
+
if (!exchange.contextKinds.includes(kind))
|
|
44
|
+
violations.push({ reason: 'missing-required-context', kind });
|
|
45
|
+
}
|
|
46
|
+
if (exchange.createsCommitment) {
|
|
47
|
+
const creating = exchange.acts.filter((a) => profile.commitmentCreatingActs.includes(a));
|
|
48
|
+
if (creating.length === 0) {
|
|
49
|
+
// The structural half of "an Offer is not a Commitment": an exchange cannot claim to have
|
|
50
|
+
// created an obligation without realizing an act this profile says can create one.
|
|
51
|
+
violations.push({ reason: 'commitment-from-non-creating-act', act: exchange.acts[0] ?? 'INFORM' });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return violations;
|
|
55
|
+
}
|
|
56
|
+
// ─── The library ─────────────────────────────────────────────────────────────
|
|
57
|
+
/** Plain conversation. Creates no obligations, requires no context — and saying so explicitly is the
|
|
58
|
+
* point: the overwhelming majority of exchanges are this, and they should not carry ceremony. */
|
|
59
|
+
export const DIRECT_PROFILE = {
|
|
60
|
+
version: 'ap.interaction-profile.v1',
|
|
61
|
+
profileVersion: 'direct/1.0.0',
|
|
62
|
+
purpose: 'A conversation between two agents. Carries no obligation and evaluates no authority.',
|
|
63
|
+
roles: ['participant'],
|
|
64
|
+
permittedActs: ['INFORM', 'QUERY', 'NOTIFY', 'ACKNOWLEDGE'],
|
|
65
|
+
requiredContextKinds: [],
|
|
66
|
+
commitmentCreatingActs: [],
|
|
67
|
+
closeReasons: ['concluded', 'abandoned'],
|
|
68
|
+
};
|
|
69
|
+
/** Terms are proposed, countered and accepted. The only profile in this starter set where an ACCEPT
|
|
70
|
+
* can create an obligation, which is exactly why ACCEPT is not in the others. */
|
|
71
|
+
export const NEGOTIATION_PROFILE = {
|
|
72
|
+
version: 'ap.interaction-profile.v1',
|
|
73
|
+
profileVersion: 'negotiation/1.0.0',
|
|
74
|
+
purpose: 'Offer, counter-offer and acceptance over terms. A valid, authorized ACCEPT creates a Commitment.',
|
|
75
|
+
roles: ['proposer', 'counterparty'],
|
|
76
|
+
permittedActs: ['INFORM', 'QUERY', 'PROPOSE', 'COUNTER', 'ACCEPT', 'REJECT', 'RETRACT', 'ACKNOWLEDGE'],
|
|
77
|
+
requiredContextKinds: [],
|
|
78
|
+
commitmentCreatingActs: ['ACCEPT'],
|
|
79
|
+
closeReasons: ['agreed', 'declined', 'withdrawn', 'expired', 'abandoned'],
|
|
80
|
+
};
|
|
81
|
+
/** A question routed to a member and answered. `structured-only` by construction: the permitted acts
|
|
82
|
+
* contain no PROPOSE, so a consultation cannot quietly become a negotiation. */
|
|
83
|
+
export const CONSULTATION_PROFILE = {
|
|
84
|
+
version: 'ap.interaction-profile.v1',
|
|
85
|
+
profileVersion: 'consultation/1.0.0',
|
|
86
|
+
purpose: 'A routed question and its answers. Advice, never an offer — and structurally incapable of becoming one.',
|
|
87
|
+
roles: ['requester', 'router', 'consultant'],
|
|
88
|
+
permittedActs: ['QUERY', 'INFORM', 'ACKNOWLEDGE', 'REJECT'],
|
|
89
|
+
requiredContextKinds: ['delegation'],
|
|
90
|
+
commitmentCreatingActs: [],
|
|
91
|
+
closeReasons: ['answered', 'declined', 'expired', 'no-consultant-available'],
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Spec 336's pre-task provider qualification, expressed as a profile of this model rather than as a
|
|
95
|
+
* parallel one. This is the profile mechanism's actual test: an existing, independently-designed
|
|
96
|
+
* chain either fits or it does not.
|
|
97
|
+
*
|
|
98
|
+
* IT MOSTLY FITS, AND WHERE IT DOES NOT, THE FINDING IS REAL — see `ENGAGEMENT_RESPONSE_KIND_AXES`
|
|
99
|
+
* below. `requiredContextKinds` is `['intent-projection']` because §3.1's rule is that an offer binds
|
|
100
|
+
* to the projection the provider REVIEWED, not to undisclosed facts in the intent; an engagement
|
|
101
|
+
* exchange that pins no projection cannot later be checked against what was actually disclosed.
|
|
102
|
+
*
|
|
103
|
+
* `commitmentCreatingActs` is EMPTY, and that is the load-bearing line. Nine of the chain's ten steps
|
|
104
|
+
* grant nothing; only the Mandate authorizes, and a Mandate is issued by `delegation`, not created by
|
|
105
|
+
* an act in this interaction. An engagement that could mint an obligation would be spec 336's central
|
|
106
|
+
* claim undone by its own profile.
|
|
107
|
+
*/
|
|
108
|
+
export const ENGAGEMENT_PROFILE = {
|
|
109
|
+
version: 'ap.interaction-profile.v1',
|
|
110
|
+
profileVersion: 'engagement/1.0.0',
|
|
111
|
+
purpose: 'Pre-task provider qualification (spec 336): probe a candidate against a minimized intent projection and receive terms. Grants nothing — only the Mandate authorizes.',
|
|
112
|
+
roles: ['requester', 'candidate-provider'],
|
|
113
|
+
permittedActs: ['QUERY', 'INFORM', 'PROPOSE', 'COUNTER', 'REJECT', 'ACKNOWLEDGE', 'RETRACT'],
|
|
114
|
+
requiredContextKinds: ['intent-projection'],
|
|
115
|
+
commitmentCreatingActs: [],
|
|
116
|
+
closeReasons: [
|
|
117
|
+
'offer-selected',
|
|
118
|
+
'declined',
|
|
119
|
+
'referred',
|
|
120
|
+
'temporarily-unavailable',
|
|
121
|
+
'not-selected',
|
|
122
|
+
'expired',
|
|
123
|
+
'cancelled',
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* An org topic — the first profile that describes something already in production (spec 340 W11).
|
|
128
|
+
*
|
|
129
|
+
* Org topics were an Interaction in everything but name: the org SA owns the board, members join and
|
|
130
|
+
* leave, `facilitator`/`contributor` are its roles, and it opens and closes. This profile names that
|
|
131
|
+
* rather than building anything new.
|
|
132
|
+
*
|
|
133
|
+
* `commitmentCreatingActs` is empty and `PROPOSE`/`ACCEPT` are absent from the permitted set: a topic
|
|
134
|
+
* is a discussion board. If a negotiation happens in one, it belongs in its own Interaction under the
|
|
135
|
+
* negotiation profile — which is exactly ADR-0058 §14's rule that a thread is promoted only when it
|
|
136
|
+
* acquires an independent property, applied to the case that actually occurs.
|
|
137
|
+
*/
|
|
138
|
+
export const TOPIC_PROFILE = {
|
|
139
|
+
version: 'ap.interaction-profile.v1',
|
|
140
|
+
profileVersion: 'topic/1.0.0',
|
|
141
|
+
purpose: 'An organization discussion board. Members post, facilitators manage. Carries no obligation and is structurally incapable of becoming a negotiation.',
|
|
142
|
+
roles: ['facilitator', 'contributor'],
|
|
143
|
+
permittedActs: ['INFORM', 'QUERY', 'NOTIFY', 'ACKNOWLEDGE'],
|
|
144
|
+
requiredContextKinds: [],
|
|
145
|
+
commitmentCreatingActs: [],
|
|
146
|
+
// `archived` is deliberately NOT here. Shelving a board is an inbox/display concern and already
|
|
147
|
+
// exists as `InboxState.archived`; an interaction that is archived has still concluded or not.
|
|
148
|
+
closeReasons: ['concluded', 'abandoned'],
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Spec 410 §10 — A DISPUTE. The governed situation between "I disagree with what this receipt says happened" and a
|
|
152
|
+
* determination someone was named to make. The disputing party opens it citing the receipt; the counterparty
|
|
153
|
+
* answers; a STEWARD — named in the public steward record for the term or capability in question, never chosen
|
|
154
|
+
* by either party at the time — records a determination as a signed Exchange, and the run's provenance links it
|
|
155
|
+
* (`apexec:disputedBy`). It grants nothing and reverses nothing by itself: a reversal is a new act under a new
|
|
156
|
+
* mandate. `commitmentCreatingActs` is empty for that reason, and `requiredContextKinds` pins the receipt so a
|
|
157
|
+
* dispute cannot be about nothing in particular.
|
|
158
|
+
*/
|
|
159
|
+
export const DISPUTE_PROFILE = {
|
|
160
|
+
version: 'ap.interaction-profile.v1',
|
|
161
|
+
profileVersion: 'dispute/1.0.0',
|
|
162
|
+
purpose: 'A disagreement about a receipt, answered by the counterparty and determined by a named steward. Evidence and a determination; never a reversal.',
|
|
163
|
+
roles: ['disputant', 'counterparty', 'steward'],
|
|
164
|
+
permittedActs: ['REQUEST', 'INFORM', 'QUERY', 'ACKNOWLEDGE', 'ISSUE', 'REJECT'],
|
|
165
|
+
requiredContextKinds: ['receipt'],
|
|
166
|
+
commitmentCreatingActs: [],
|
|
167
|
+
closeReasons: ['determined', 'withdrawn', 'declined-by-steward', 'expired'],
|
|
168
|
+
};
|
|
169
|
+
export const PROFILE_LIBRARY = [
|
|
170
|
+
DIRECT_PROFILE,
|
|
171
|
+
NEGOTIATION_PROFILE,
|
|
172
|
+
CONSULTATION_PROFILE,
|
|
173
|
+
ENGAGEMENT_PROFILE,
|
|
174
|
+
TOPIC_PROFILE,
|
|
175
|
+
DISPUTE_PROFILE,
|
|
176
|
+
];
|
|
177
|
+
export function profileFor(version) {
|
|
178
|
+
return PROFILE_LIBRARY.find((p) => p.profileVersion === version);
|
|
179
|
+
}
|
|
180
|
+
// ─── The finding W4 was run to produce ───────────────────────────────────────
|
|
181
|
+
/**
|
|
182
|
+
* Spec 336 §3.5's six "response kinds" are NOT one axis.
|
|
183
|
+
*
|
|
184
|
+
* Four are performatives — WHAT the act does, its illocutionary force. Two are classifications of the
|
|
185
|
+
* CONTENT an INFORM carries. Modelling all six as one enumeration is the same defect spec 336 itself
|
|
186
|
+
* diagnosed one level up, when it split probe "mode" into three orthogonal axes because one
|
|
187
|
+
* enumeration doing three jobs made ordinary positions unsayable.
|
|
188
|
+
*
|
|
189
|
+
* The consequence if left merged: `human-ux-required` would need a performative of its own, and the
|
|
190
|
+
* scheme would grow a concept per business outcome until it was a catalog — exactly what SKOS act
|
|
191
|
+
* kinds exist to prevent. Keeping the split means a provider saying "I need a human channel" is an
|
|
192
|
+
* INFORM whose content says so, which is what it actually is.
|
|
193
|
+
*
|
|
194
|
+
* This is a finding against spec 336 §3.5, recorded here because the profile is where it surfaced.
|
|
195
|
+
* It changes no wire format: the response kind stays on the content, where it already lives.
|
|
196
|
+
*/
|
|
197
|
+
export const ENGAGEMENT_RESPONSE_KIND_AXES = {
|
|
198
|
+
offer: { axis: 'performative', performative: 'PROPOSE' },
|
|
199
|
+
decline: { axis: 'performative', performative: 'REJECT' },
|
|
200
|
+
'needs-clarification': { axis: 'performative', performative: 'QUERY' },
|
|
201
|
+
acknowledgement: { axis: 'performative', performative: 'ACKNOWLEDGE' },
|
|
202
|
+
// A referral is not a distinct force — it is an INFORM whose content names another provider.
|
|
203
|
+
// Promoting it would make "who to ask instead" an act rather than a fact.
|
|
204
|
+
referral: { axis: 'content', carriedBy: 'INFORM' },
|
|
205
|
+
// A fact about TIME, and the one spec 336 is most careful about: it must never be cached as
|
|
206
|
+
// absence, because a resolver that can only handle "yes" reads "not right now" as "no such provider".
|
|
207
|
+
'temporarily-unavailable': { axis: 'content', carriedBy: 'INFORM' },
|
|
208
|
+
// No performative, and it should not get one: needing a human channel is a property of HOW the
|
|
209
|
+
// answer must arrive, not of what was said.
|
|
210
|
+
'human-ux-required': { axis: 'content', carriedBy: 'INFORM' },
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=interaction-profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction-profile.js","sourceRoot":"","sources":["../src/interaction-profile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { CanonicalAgentId } from '@agenticprimitives/types';
|
|
2
|
+
import type { Hex32 } from './messaging/index.js';
|
|
3
|
+
import type { ProtocolBindingV1 } from './protocol-binding.js';
|
|
4
|
+
import type { InteractionState } from './interaction-machines.js';
|
|
5
|
+
/** The interaction spine id. Ours, never a protocol's — an A2A `contextId` or a DIDComm `thid` is a
|
|
6
|
+
* foreign key into someone else's namespace and is carried alongside, never used as this (spec 340 §11). */
|
|
7
|
+
export type InteractionId = `ixn_${string}`;
|
|
8
|
+
/** Content address of a `ContextSnapshotV1`. A snapshot is identified BY its bytes. */
|
|
9
|
+
export type ContextDigest = Hex32;
|
|
10
|
+
/**
|
|
11
|
+
* The version of an `InteractionProfile`, pinned onto every Exchange.
|
|
12
|
+
*
|
|
13
|
+
* A profile declares its own roles, permitted acts, required contexts, state machine and authority
|
|
14
|
+
* requirements — so a new interaction type is a NEW PROFILE, not a new `MessageKind` member and not a
|
|
15
|
+
* schema change (spec 340 §6). That is the extensibility mechanism external adopters get.
|
|
16
|
+
*
|
|
17
|
+
* `<name>/<semver>` and both halves are required. A bare name would make the pin unfalsifiable, which
|
|
18
|
+
* is the whole reason the pin exists.
|
|
19
|
+
*/
|
|
20
|
+
export type ProfileVersion = `${string}/${number}.${number}.${number}`;
|
|
21
|
+
export declare function isProfileVersion(value: string): value is ProfileVersion;
|
|
22
|
+
export declare function isFloatingVersion(value: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* ONE reference inside a snapshot — a delegation, an entitlement, a policy, a credential status.
|
|
25
|
+
*
|
|
26
|
+
* All four fields are required, and that is the point. A reference carrying only an id resolves to
|
|
27
|
+
* whatever exists when someone later asks, which means a historical record describes the present
|
|
28
|
+
* instead of the past. The failure is quiet and forensic: nothing breaks at write time, and a year
|
|
29
|
+
* later nobody can say what the evaluator actually saw.
|
|
30
|
+
*/
|
|
31
|
+
export interface ContextReferenceV1 {
|
|
32
|
+
/** What kind of thing this is — `delegation`, `entitlement`, `policy`, `status`, … */
|
|
33
|
+
kind: string;
|
|
34
|
+
/** Its canonical id in its OWN namespace. */
|
|
35
|
+
id: string;
|
|
36
|
+
/** The EXACT version. Never `current`/`latest` (see `assertReproducible`). */
|
|
37
|
+
version: string;
|
|
38
|
+
/** Digest of the referenced artifact as it stood. */
|
|
39
|
+
digest: Hex32;
|
|
40
|
+
/** When this reference was observed — not when the artifact was created. */
|
|
41
|
+
observedAt: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* An immutable, digest-addressed bundle of exactly what was known and evaluated when an Exchange was
|
|
45
|
+
* made. Replaces `contextRefs[]`, which were floating pointers at live state.
|
|
46
|
+
*/
|
|
47
|
+
export interface ContextSnapshotV1 {
|
|
48
|
+
version: 'ap.context-snapshot.v1';
|
|
49
|
+
/** sha256 over the canonical body (every field except `digest` itself). */
|
|
50
|
+
digest: ContextDigest;
|
|
51
|
+
/** The profile version in force when this was taken. */
|
|
52
|
+
profileVersion: ProfileVersion;
|
|
53
|
+
refs: readonly ContextReferenceV1[];
|
|
54
|
+
takenAt: string;
|
|
55
|
+
}
|
|
56
|
+
export type ContextReproducibilityFailure = {
|
|
57
|
+
reason: 'floating_version';
|
|
58
|
+
ref: ContextReferenceV1;
|
|
59
|
+
} | {
|
|
60
|
+
reason: 'floating_profile_version';
|
|
61
|
+
value: string;
|
|
62
|
+
} | {
|
|
63
|
+
reason: 'malformed_profile_version';
|
|
64
|
+
value: string;
|
|
65
|
+
} | {
|
|
66
|
+
reason: 'missing_field';
|
|
67
|
+
ref: ContextReferenceV1;
|
|
68
|
+
field: keyof ContextReferenceV1;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* The reproducibility gate, as a TOTAL function over the failures rather than a boolean.
|
|
72
|
+
*
|
|
73
|
+
* Returns every problem, not the first: a caller fixing one floating ref at a time would otherwise
|
|
74
|
+
* need N round trips to learn the record was never reproducible.
|
|
75
|
+
*/
|
|
76
|
+
export declare function checkReproducible(input: Pick<ContextSnapshotV1, 'profileVersion' | 'refs'>): readonly ContextReproducibilityFailure[];
|
|
77
|
+
/** Throwing form, for the write path. Fail-closed: an unreproducible snapshot is never stored. */
|
|
78
|
+
export declare function assertReproducible(input: Pick<ContextSnapshotV1, 'profileVersion' | 'refs'>): void;
|
|
79
|
+
/** Digest over the canonical body — every field except `digest`. */
|
|
80
|
+
export declare function hashContextSnapshot(snapshot: Omit<ContextSnapshotV1, 'digest'> | ContextSnapshotV1): Promise<ContextDigest>;
|
|
81
|
+
/** Build + seal. Refs are sorted so two callers observing the same context produce the same digest —
|
|
82
|
+
* a content address that depended on insertion order would not be one. */
|
|
83
|
+
export declare function buildContextSnapshot(input: {
|
|
84
|
+
profileVersion: ProfileVersion;
|
|
85
|
+
refs: readonly ContextReferenceV1[];
|
|
86
|
+
takenAt: string;
|
|
87
|
+
}): Promise<ContextSnapshotV1>;
|
|
88
|
+
/** Recompute and compare. A snapshot whose digest does not match its bytes is not evidence. */
|
|
89
|
+
export declare function verifyContextSnapshot(snapshot: ContextSnapshotV1): Promise<boolean>;
|
|
90
|
+
/**
|
|
91
|
+
* An agent's standing in an Interaction under a profile-defined role, over a time window.
|
|
92
|
+
*
|
|
93
|
+
* The role is a plain string BECAUSE the profile defines the role set. Enumerating roles here would
|
|
94
|
+
* reintroduce the frozen taxonomy profiles exist to replace — and a role, unlike a state, has no
|
|
95
|
+
* substrate-wide meaning to enumerate against.
|
|
96
|
+
*/
|
|
97
|
+
export interface ParticipationV1 {
|
|
98
|
+
agent: CanonicalAgentId;
|
|
99
|
+
/** Defined by the interaction's profile, not by this package. */
|
|
100
|
+
role: string;
|
|
101
|
+
joinedAt: string;
|
|
102
|
+
/** Absent while participating. Set on leaving; the record is never deleted. */
|
|
103
|
+
leftAt?: string;
|
|
104
|
+
}
|
|
105
|
+
/** Participants at an instant — a projection over join/leave windows, never a stored second copy. */
|
|
106
|
+
export declare function participantsAt(participations: readonly ParticipationV1[], at: string): readonly ParticipationV1[];
|
|
107
|
+
/**
|
|
108
|
+
* The durable governed situation. Deliberately does NOT carry its exchanges: they live in their own
|
|
109
|
+
* records and point INWARD via `ExchangeRecordV1.interactionId`. An aggregate that inlined its
|
|
110
|
+
* exchanges would have to be rewritten on every message, which is how an "immutable signed unit"
|
|
111
|
+
* quietly becomes a mutable row.
|
|
112
|
+
*/
|
|
113
|
+
export interface InteractionRecordV1 {
|
|
114
|
+
version: 'ap.interaction.v1';
|
|
115
|
+
interactionId: InteractionId;
|
|
116
|
+
/** The profile IN FORCE. Individual exchanges pin the version they were made under, which may be an
|
|
117
|
+
* older one — that difference is exactly what makes a downgrade detectable rather than invisible. */
|
|
118
|
+
profileVersion: ProfileVersion;
|
|
119
|
+
/** The gateway owner SA the interaction is custodied by. */
|
|
120
|
+
principal: CanonicalAgentId;
|
|
121
|
+
/** Where the interaction is in ITS OWN lifecycle — one of the six machines, and not the same
|
|
122
|
+
* question as any exchange's `exec`/`case`. Required rather than defaulted: a record that omitted
|
|
123
|
+
* it would be read as `forming` by whoever looked, including after it had concluded. */
|
|
124
|
+
lifecycleState: InteractionState;
|
|
125
|
+
/**
|
|
126
|
+
* HOW membership is determined — the distinction org topics forced into the model.
|
|
127
|
+
*
|
|
128
|
+
* `asserted`: the list below IS the membership. Someone joined, someone left, and the record says so.
|
|
129
|
+
*
|
|
130
|
+
* `derived`: membership is computed by the custodian from a source that already owns it — for an open
|
|
131
|
+
* org topic, the organization's own membership. `participations` MUST then stay EMPTY. Copying the
|
|
132
|
+
* member list per interaction is exactly the duplication `apmsg:openParticipation` forbids
|
|
133
|
+
* ("derived at read time — never stored per topic"), and a stale copy of a membership list is how a
|
|
134
|
+
* removed member keeps posting.
|
|
135
|
+
*/
|
|
136
|
+
participationMode: 'asserted' | 'derived';
|
|
137
|
+
/** Empty when `participationMode` is `derived` — see above. */
|
|
138
|
+
participations: readonly ParticipationV1[];
|
|
139
|
+
/** Foreign keys into other protocols' namespaces (A2A contextId, DIDComm thid, Matrix event id).
|
|
140
|
+
* NEVER the canonical id — see `InteractionId` (spec 340 §11). A LIST of typed bindings rather
|
|
141
|
+
* than a `Record<protocol, value>`: one protocol contributes several distinct correlators
|
|
142
|
+
* (`a2a:contextId` and `a2a:taskId` are not the same key), and each is a claim about a moment, so
|
|
143
|
+
* it carries its own `observedAt` like every other reference in this model. */
|
|
144
|
+
protocolBindings?: readonly ProtocolBindingV1[];
|
|
145
|
+
createdAt: string;
|
|
146
|
+
updatedAt: string;
|
|
147
|
+
/** Monotonic revision, mirroring `ExchangeRecordV1.rev`. */
|
|
148
|
+
rev: number;
|
|
149
|
+
}
|
|
150
|
+
export type ProfileDowngrade = {
|
|
151
|
+
exchangeId: string;
|
|
152
|
+
pinned: ProfileVersion;
|
|
153
|
+
inForce: ProfileVersion;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Detect exchanges pinned to a profile version OLDER than the one now in force.
|
|
157
|
+
*
|
|
158
|
+
* This is not an error — an interaction whose profile versioned mid-flight legitimately contains
|
|
159
|
+
* exchanges under both. It is a REPORT, because the attack it surfaces has no network component: an
|
|
160
|
+
* adversary who can edit the interaction's `profileVersion` reinterprets a Negotiation as a generic
|
|
161
|
+
* Conversation and takes its acceptance semantics with it. The pin on each Exchange is what makes
|
|
162
|
+
* that visible, and this is the function that looks.
|
|
163
|
+
*/
|
|
164
|
+
export declare function detectProfileDowngrades(interaction: Pick<InteractionRecordV1, 'profileVersion'>, exchanges: readonly {
|
|
165
|
+
exchangeId: string;
|
|
166
|
+
profileVersion?: ProfileVersion;
|
|
167
|
+
}[]): readonly ProfileDowngrade[];
|
|
168
|
+
//# sourceMappingURL=interaction-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction-record.d.ts","sourceRoot":"","sources":["../src/interaction-record.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// The durable governed situation (spec 340 W2; ADR-0058) — the aggregate `ExchangeRecordV1` has been
|
|
2
|
+
// living without.
|
|
3
|
+
//
|
|
4
|
+
// Today the model is Exchange-rooted: an exchange carries a `case?: CaseState` substate and there is no
|
|
5
|
+
// record of the interaction ITSELF. That holds exactly as long as an interaction is one request-shaped
|
|
6
|
+
// thing. It stops the moment participants join and leave, a profile versions, a context must be
|
|
7
|
+
// reproducible, or a commitment outlives any single exchange.
|
|
8
|
+
//
|
|
9
|
+
// Interaction the governed situation durable: participants, profile, state, commitments
|
|
10
|
+
// └── Exchange one immutable signed unit crossing its boundary
|
|
11
|
+
//
|
|
12
|
+
// An Exchange without an Interaction is a message. An Interaction without Exchanges is an empty room.
|
|
13
|
+
// Both are legitimate; neither is the other.
|
|
14
|
+
//
|
|
15
|
+
// WHAT THIS FILE CONFERS: nothing. An `InteractionRecordV1` records who is participating and under what
|
|
16
|
+
// profile; it never decides whether an act is permitted. Participation is not authority — a role
|
|
17
|
+
// explains WHY delegations were issued, it never substitutes for one (ADR-0048 §4). Authority stays in
|
|
18
|
+
// `delegation` + `entitlements` + `tool-policy`, re-evaluated at the moment of acting.
|
|
19
|
+
import { canonicalizeJson } from '@agenticprimitives/types';
|
|
20
|
+
import { sha256Hex32 } from './messaging/index.js';
|
|
21
|
+
const PROFILE_VERSION_RE = /^[a-z][a-z0-9-]*(?:\.[a-z0-9-]+)*\/\d+\.\d+\.\d+$/;
|
|
22
|
+
export function isProfileVersion(value) {
|
|
23
|
+
return PROFILE_VERSION_RE.test(value);
|
|
24
|
+
}
|
|
25
|
+
/** Values that name "whatever exists now". Forbidden inside any historical record (spec 340 §7). */
|
|
26
|
+
const FLOATING = new Set(['current', 'latest', 'head', 'main', 'tip', 'now']);
|
|
27
|
+
export function isFloatingVersion(value) {
|
|
28
|
+
return FLOATING.has(value.trim().toLowerCase());
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The reproducibility gate, as a TOTAL function over the failures rather than a boolean.
|
|
32
|
+
*
|
|
33
|
+
* Returns every problem, not the first: a caller fixing one floating ref at a time would otherwise
|
|
34
|
+
* need N round trips to learn the record was never reproducible.
|
|
35
|
+
*/
|
|
36
|
+
export function checkReproducible(input) {
|
|
37
|
+
const failures = [];
|
|
38
|
+
if (isFloatingVersion(input.profileVersion)) {
|
|
39
|
+
failures.push({ reason: 'floating_profile_version', value: input.profileVersion });
|
|
40
|
+
}
|
|
41
|
+
else if (!isProfileVersion(input.profileVersion)) {
|
|
42
|
+
failures.push({ reason: 'malformed_profile_version', value: input.profileVersion });
|
|
43
|
+
}
|
|
44
|
+
for (const ref of input.refs) {
|
|
45
|
+
for (const field of ['kind', 'id', 'version', 'digest', 'observedAt']) {
|
|
46
|
+
if (!ref[field])
|
|
47
|
+
failures.push({ reason: 'missing_field', ref, field });
|
|
48
|
+
}
|
|
49
|
+
if (ref.version && isFloatingVersion(ref.version)) {
|
|
50
|
+
failures.push({ reason: 'floating_version', ref });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return failures;
|
|
54
|
+
}
|
|
55
|
+
/** Throwing form, for the write path. Fail-closed: an unreproducible snapshot is never stored. */
|
|
56
|
+
export function assertReproducible(input) {
|
|
57
|
+
const failures = checkReproducible(input);
|
|
58
|
+
if (failures.length === 0)
|
|
59
|
+
return;
|
|
60
|
+
throw new Error(`ContextSnapshot is not reproducible: ${failures
|
|
61
|
+
.map((f) => f.reason === 'floating_version'
|
|
62
|
+
? `${f.ref.kind}:${f.ref.id} pins "${f.ref.version}"`
|
|
63
|
+
: f.reason === 'missing_field'
|
|
64
|
+
? `${f.ref.kind}:${f.ref.id} missing ${f.field}`
|
|
65
|
+
: `profileVersion "${f.value}" (${f.reason})`)
|
|
66
|
+
.join('; ')}`);
|
|
67
|
+
}
|
|
68
|
+
/** Digest over the canonical body — every field except `digest`. */
|
|
69
|
+
export async function hashContextSnapshot(snapshot) {
|
|
70
|
+
const { digest: _digest, ...body } = snapshot;
|
|
71
|
+
return sha256Hex32(canonicalizeJson(body));
|
|
72
|
+
}
|
|
73
|
+
/** Build + seal. Refs are sorted so two callers observing the same context produce the same digest —
|
|
74
|
+
* a content address that depended on insertion order would not be one. */
|
|
75
|
+
export async function buildContextSnapshot(input) {
|
|
76
|
+
assertReproducible(input);
|
|
77
|
+
const refs = [...input.refs].sort((a, b) => a.kind === b.kind ? (a.id === b.id ? a.digest.localeCompare(b.digest) : a.id.localeCompare(b.id)) : a.kind.localeCompare(b.kind));
|
|
78
|
+
const body = {
|
|
79
|
+
version: 'ap.context-snapshot.v1',
|
|
80
|
+
profileVersion: input.profileVersion,
|
|
81
|
+
refs,
|
|
82
|
+
takenAt: input.takenAt,
|
|
83
|
+
};
|
|
84
|
+
return { ...body, digest: await hashContextSnapshot(body) };
|
|
85
|
+
}
|
|
86
|
+
/** Recompute and compare. A snapshot whose digest does not match its bytes is not evidence. */
|
|
87
|
+
export async function verifyContextSnapshot(snapshot) {
|
|
88
|
+
return (await hashContextSnapshot(snapshot)) === snapshot.digest;
|
|
89
|
+
}
|
|
90
|
+
/** Participants at an instant — a projection over join/leave windows, never a stored second copy. */
|
|
91
|
+
export function participantsAt(participations, at) {
|
|
92
|
+
return participations.filter((p) => p.joinedAt <= at && (p.leftAt === undefined || p.leftAt > at));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Detect exchanges pinned to a profile version OLDER than the one now in force.
|
|
96
|
+
*
|
|
97
|
+
* This is not an error — an interaction whose profile versioned mid-flight legitimately contains
|
|
98
|
+
* exchanges under both. It is a REPORT, because the attack it surfaces has no network component: an
|
|
99
|
+
* adversary who can edit the interaction's `profileVersion` reinterprets a Negotiation as a generic
|
|
100
|
+
* Conversation and takes its acceptance semantics with it. The pin on each Exchange is what makes
|
|
101
|
+
* that visible, and this is the function that looks.
|
|
102
|
+
*/
|
|
103
|
+
export function detectProfileDowngrades(interaction, exchanges) {
|
|
104
|
+
return exchanges.flatMap((x) => x.profileVersion && x.profileVersion !== interaction.profileVersion
|
|
105
|
+
? [{ exchangeId: x.exchangeId, pinned: x.profileVersion, inForce: interaction.profileVersion }]
|
|
106
|
+
: []);
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=interaction-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction-record.js","sourceRoot":"","sources":["../src/interaction-record.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Ops the record-scope gate distinguishes: a tombstone (`delete`) is a DISTINCT op from `write`. */
|
|
2
|
+
export type RecordScopeOp = 'read' | 'write' | 'delete';
|
|
3
|
+
/** A group of resources granted the same ops, before a server is named. */
|
|
4
|
+
export interface RecordScopeSpec {
|
|
5
|
+
resources: ReadonlyArray<string>;
|
|
6
|
+
ops: ReadonlyArray<RecordScopeOp>;
|
|
7
|
+
}
|
|
8
|
+
/** One group bound to an MCP server — structurally the delegation package's `VaultRecordScopeGrant`, so it
|
|
9
|
+
* feeds `buildVaultRecordScopeCaveat` directly; fabric does not depend on delegation (ADR-0047 boundary). */
|
|
10
|
+
export interface RecordScopeGroup {
|
|
11
|
+
server: string;
|
|
12
|
+
resources: string[];
|
|
13
|
+
ops: RecordScopeOp[];
|
|
14
|
+
}
|
|
15
|
+
export declare const INTERACTIONS_GRANT_CORE_SCOPES: ReadonlyArray<RecordScopeSpec>;
|
|
16
|
+
/**
|
|
17
|
+
* The grant's scope groups for one MCP server: the core, then the product's extensions (its own app
|
|
18
|
+
* namespaces — read-only for the agent's grounding, read+write for steward seeding), in that order.
|
|
19
|
+
*/
|
|
20
|
+
export declare function interactionsGrantScopes(server: string, extensions?: ReadonlyArray<RecordScopeSpec>): RecordScopeGroup[];
|
|
21
|
+
//# sourceMappingURL=grant-scopes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grant-scopes.d.ts","sourceRoot":"","sources":["../../src/interactions/grant-scopes.ts"],"names":[],"mappings":"AAgBA,qGAAqG;AACrG,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAExD,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAAG,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAAC,GAAG,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;CAAE;AAExG;8GAC8G;AAC9G,MAAM,WAAW,gBAAgB;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,GAAG,EAAE,aAAa,EAAE,CAAA;CAAE;AAE/F,eAAO,MAAM,8BAA8B,EAAE,aAAa,CAAC,eAAe,CA+EzE,CAAC;AAEF;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,GAAE,aAAa,CAAC,eAAe,CAAM,GAAG,gBAAgB,EAAE,CAE3H"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export const INTERACTIONS_GRANT_CORE_SCOPES = [
|
|
2
|
+
{ resources: [
|
|
3
|
+
'vault:conversation.index', 'vault:conversation.topic:*', 'vault:message.body:topic:*',
|
|
4
|
+
'vault:inbox.data', 'vault:directory.data', 'vault:relationships.data', 'vault:member.profile:*',
|
|
5
|
+
'vault:org.membership:*', 'vault:org.applications', 'vault:impact-profile', 'vault:capabilities.data',
|
|
6
|
+
'vault:skills.data', 'vault:home.manifest', 'vault:control-events.data', 'vault:coordination.requests',
|
|
7
|
+
'vault:coordination.index', 'vault:coordination.endeavor:*', 'vault:content.*',
|
|
8
|
+
'vault:resolution.requests', 'vault:resolution.grants', 'vault:archetype.assignment', 'vault:payment.receipt:*',
|
|
9
|
+
'vault:household.data',
|
|
10
|
+
// Spec 370 P7 — the person's own recent asks, so "him" can mean whom they just meant.
|
|
11
|
+
'vault:conversation.recent',
|
|
12
|
+
// Spec 381 — a run's provenance, kept where the authority was spent (the DO copy is the rebuild).
|
|
13
|
+
'vault:run.provenance:*',
|
|
14
|
+
// Spec 391 — a step's raw result, too large for the run's record, kept where the run ran.
|
|
15
|
+
'vault:run.artifact:*',
|
|
16
|
+
// Spec 410 §4.4 — where the run's bundle is anchored (chain-qualified), beside it: a Home in another estate reads it.
|
|
17
|
+
'vault:run.anchor:*',
|
|
18
|
+
// Spec 414 A2 — a run's measurements (numbers from its trace alone), beside its provenance.
|
|
19
|
+
'vault:run.measures:*',
|
|
20
|
+
// Spec 410 §8 — the countersigned form of a relationship, each party's copy in its own vault; and its revocation.
|
|
21
|
+
'vault:relationships.credential:*', 'vault:relationships.revocation:*',
|
|
22
|
+
// Spec 410 §1.2 — a wire re-issued for a rotation, in the delegator's own vault (`supersedes` links the lineage).
|
|
23
|
+
'vault:delegation.lineage:*',
|
|
24
|
+
// Spec 410 §10 — a dispute over a receipt (both parties' copies) and the run's pointer to it.
|
|
25
|
+
'vault:interaction.dispute:*', 'vault:run.dispute:*',
|
|
26
|
+
// Spec 438 §5 — a judgment of a run, signed by its evaluator, stored BESIDE the run and never as an
|
|
27
|
+
// edit to it. Additive at the estate (the household precedent): an older grant cannot write one until
|
|
28
|
+
// re-issued, and is told so, rather than every grant going stale for a record few will write.
|
|
29
|
+
'vault:run.assessment:*',
|
|
30
|
+
// Spec 398 §9 (ap-build B3) — what a build run left, in the workspace's vault; the PR opened from it names it.
|
|
31
|
+
'vault:build.run:*',
|
|
32
|
+
'vault:build.promotion:*',
|
|
33
|
+
// P1.4 — the agent's declared budget (asks and vault calls per day), set by its steward.
|
|
34
|
+
'vault:agent.budget',
|
|
35
|
+
// Spec 404 — the external MCP servers the holder attached as connectors (the token is elsewhere, never a record).
|
|
36
|
+
'vault:connector.mcp:*',
|
|
37
|
+
// Spec 385 — the person's scoped confirmation memory (which "David", per word + capability + argument).
|
|
38
|
+
'vault:confirmation.preferences',
|
|
39
|
+
// Spec 394 — the person's standing instructions (a declared default per room + capability + argument).
|
|
40
|
+
'vault:standing.instructions',
|
|
41
|
+
// Spec 422 §9.3 — the person's Security section: the LABELS of what signs for her (the chain holds the set) and the
|
|
42
|
+
// channels that open her home. ADDITIVE, deliberately not in demo-a2a's REQUIRED_SCOPES: a grant signed before this
|
|
43
|
+
// line keeps working and is denied only for these two records until its owner re-issues it.
|
|
44
|
+
'vault:security.credentials', 'vault:security.channels',
|
|
45
|
+
// Spec 402 W1 — what the person's agent remembers about THEM (their own facts, in their own words).
|
|
46
|
+
'vault:memory.facts',
|
|
47
|
+
// Spec 402 W3 / 323 W6 — the person's declared routines, the record the agent's schedule rows are rebuilt from.
|
|
48
|
+
'vault:routines.data',
|
|
49
|
+
// Spec 403 W2/W4 — the person's preferences (nudges, how to answer).
|
|
50
|
+
'vault:person.preferences',
|
|
51
|
+
// What the playbook remembers about the subjects of a skill family (counts a review folds in).
|
|
52
|
+
'vault:playbook.memory:*',
|
|
53
|
+
// Spec 436 §5.5 — the day's pulse a context agent keeps (`apinf:TownPulse`): one record a day, written by the
|
|
54
|
+
// town under a wire scoped to this family alone, read back when a stranger asks the agent how things are going.
|
|
55
|
+
// ADDITIVE, deliberately not in demo-a2a's REQUIRED_SCOPES: a grant signed before this line keeps working and is
|
|
56
|
+
// denied only for these records until its owner re-issues it — which is how it failed first, silently, because
|
|
57
|
+
// every reader of a denied record catches and reads it as "no record".
|
|
58
|
+
'vault:pulse.day:*',
|
|
59
|
+
], ops: ['read', 'write'] },
|
|
60
|
+
// Spec 354 K3 — the PLAYBOOK may also be REMOVED, which is a real custodial act: an agent goes back to the
|
|
61
|
+
// bare harness. A tombstone is a DISTINCT op at the record-scope gate (spec 317 §3.2 / audit F1 — a
|
|
62
|
+
// write-only delegate must not be able to censor records), so the broad bucket above cannot express it.
|
|
63
|
+
{ resources: ['vault:archetype.assignment'], ops: ['read', 'write', 'delete'] },
|
|
64
|
+
// Spec 322 W3f — dm bodies are READ-only here: the DO serves the owner's mail reads, while only the
|
|
65
|
+
// (write-only) delivery plane may create them. Planes stay disjoint on writes.
|
|
66
|
+
{ resources: ['vault:message.body:dm:*'], ops: ['read'] },
|
|
67
|
+
// The org's own invitations, read-only — what makes membership-read's invited-member path live. Writes
|
|
68
|
+
// stay on the delivery plane.
|
|
69
|
+
{ resources: ['vault:org.invite:*'], ops: ['read'] },
|
|
70
|
+
// A governed workspace's pointer to the organization that governs it (the owner's rule, 2026-10-02), READ-only:
|
|
71
|
+
// the workspace's board gate asks that organization whether a caller is a member, and the club's standing and the
|
|
72
|
+
// `org.recordMembership` guard resolve the governor from it. Written by the Home under the steward's wire, never
|
|
73
|
+
// here. ADDITIVE, deliberately not in demo-a2a's REQUIRED_SCOPES: a grant signed before this line keeps working
|
|
74
|
+
// and reads as "no governor" until its owner re-issues it.
|
|
75
|
+
{ resources: ['vault:workspace.governor'], ops: ['read'] },
|
|
76
|
+
// Spec 401 — a PERSON's contacts: their own record of who they let in, written by their own agent under their own
|
|
77
|
+
// mandate (the act that mints the grant) and read back for their list and for standing. Read+write here because
|
|
78
|
+
// it is the person's own list in the person's own vault; nothing about it leaves it.
|
|
79
|
+
{ resources: ['vault:contact:*'], ops: ['read', 'write'] },
|
|
80
|
+
];
|
|
81
|
+
/**
|
|
82
|
+
* The grant's scope groups for one MCP server: the core, then the product's extensions (its own app
|
|
83
|
+
* namespaces — read-only for the agent's grounding, read+write for steward seeding), in that order.
|
|
84
|
+
*/
|
|
85
|
+
export function interactionsGrantScopes(server, extensions = []) {
|
|
86
|
+
return [...INTERACTIONS_GRANT_CORE_SCOPES, ...extensions].map((g) => ({ server, resources: [...g.resources], ops: [...g.ops] }));
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=grant-scopes.js.map
|