@kybernesis/arp-scope-catalog 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -0
  3. package/dist/index.cjs +518 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +144 -0
  6. package/dist/index.d.ts +144 -0
  7. package/dist/index.js +501 -0
  8. package/dist/index.js.map +1 -0
  9. package/generated/manifest.json +1542 -0
  10. package/generated/scopes.json +1536 -0
  11. package/package.json +49 -0
  12. package/scopes/calendar.availability.read.yaml +35 -0
  13. package/scopes/calendar.events.cancel.yaml +24 -0
  14. package/scopes/calendar.events.create.yaml +31 -0
  15. package/scopes/calendar.events.modify.yaml +24 -0
  16. package/scopes/calendar.events.propose.yaml +35 -0
  17. package/scopes/calendar.events.read.yaml +38 -0
  18. package/scopes/connection.extend.yaml +28 -0
  19. package/scopes/connection.rescope.request.yaml +21 -0
  20. package/scopes/contacts.attributes.read.yaml +25 -0
  21. package/scopes/contacts.introduce.yaml +21 -0
  22. package/scopes/contacts.search.yaml +26 -0
  23. package/scopes/contacts.share.yaml +30 -0
  24. package/scopes/credentials.present.request.yaml +29 -0
  25. package/scopes/credentials.proof.zk.request.yaml +31 -0
  26. package/scopes/delegation.forward.task.yaml +36 -0
  27. package/scopes/files.project.files.delete.yaml +31 -0
  28. package/scopes/files.project.files.list.yaml +22 -0
  29. package/scopes/files.project.files.read.yaml +35 -0
  30. package/scopes/files.project.files.summarize.yaml +30 -0
  31. package/scopes/files.project.files.write.yaml +34 -0
  32. package/scopes/files.project.metadata.read.yaml +21 -0
  33. package/scopes/files.projects.list.yaml +18 -0
  34. package/scopes/files.share.external.yaml +39 -0
  35. package/scopes/identity.card.read.yaml +18 -0
  36. package/scopes/identity.introduction.request.yaml +24 -0
  37. package/scopes/identity.principal.verify.yaml +19 -0
  38. package/scopes/knowledge.query.yaml +31 -0
  39. package/scopes/messaging.chat.send.yaml +27 -0
  40. package/scopes/messaging.email.draft.compose.yaml +23 -0
  41. package/scopes/messaging.email.send.reviewed.yaml +36 -0
  42. package/scopes/messaging.email.summary.yaml +26 -0
  43. package/scopes/messaging.email.thread.read.yaml +29 -0
  44. package/scopes/messaging.relay.to_principal.yaml +22 -0
  45. package/scopes/notes.read.yaml +25 -0
  46. package/scopes/notes.search.yaml +24 -0
  47. package/scopes/notes.write.yaml +32 -0
  48. package/scopes/payments.authorize.capped.yaml +37 -0
  49. package/scopes/payments.history.read.yaml +28 -0
  50. package/scopes/payments.quote.request.yaml +18 -0
  51. package/scopes/payments.refund.request.yaml +24 -0
  52. package/scopes/tasks.assign.yaml +27 -0
  53. package/scopes/tasks.create.yaml +31 -0
  54. package/scopes/tasks.list.yaml +21 -0
  55. package/scopes/tasks.read.yaml +22 -0
  56. package/scopes/tasks.status.update.yaml +22 -0
  57. package/scopes/tools.invoke.mutating.yaml +37 -0
  58. package/scopes/tools.invoke.read.yaml +28 -0
  59. package/scopes/work.projects.list.yaml +18 -0
  60. package/scopes/work.reports.summary.yaml +29 -0
  61. package/scopes/work.status.read.yaml +18 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kybernesis AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @kybernesis/arp-scope-catalog
2
+
3
+ The ARP scope catalog v1 — 50 scope templates authored as YAML — plus the Handlebars→Cedar compiler that turns a selection of scopes into a compiled policy set.
4
+
5
+ Humans never write Cedar. They pick scopes from this catalog, and a compiler produces the policy. This package is both the source of truth (the `scopes/*.yaml` files) and the tooling (the loader + compiler) for doing that.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @kybernesis/arp-scope-catalog
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Load the catalog + compile a single scope
16
+
17
+ ```ts
18
+ import { loadScopesFromDirectory, compileScope } from '@kybernesis/arp-scope-catalog';
19
+
20
+ const catalog = loadScopesFromDirectory('./node_modules/@kybernesis/arp-scope-catalog/scopes');
21
+
22
+ const permitCalendar = compileScope({
23
+ scope: catalog.find((s) => s.id === 'calendar.availability.read')!,
24
+ audienceDid: 'did:web:ghost.agent',
25
+ params: { days_ahead: 14 },
26
+ });
27
+
28
+ console.log(permitCalendar);
29
+ // permit (
30
+ // principal == Agent::"did:web:ghost.agent",
31
+ // action == Action::"check_availability",
32
+ // resource == Calendar::"primary"
33
+ // ) when { context.query_window_days <= 14 };
34
+ // forbid ( ... ) when { action != Action::"check_availability" };
35
+ ```
36
+
37
+ ### Compile a bundle of scopes
38
+
39
+ ```ts
40
+ import { compileBundle, BUNDLES, findBundle } from '@kybernesis/arp-scope-catalog';
41
+
42
+ const bundle = findBundle('bundle.scheduling_assistant.v1')!;
43
+
44
+ const compiled = compileBundle({
45
+ scopeIds: bundle.scopes.map((s) => s.id),
46
+ paramsMap: {
47
+ 'calendar.availability.read': { days_ahead: 14 },
48
+ 'calendar.events.propose': { max_attendees: 10, max_duration_min: 60 },
49
+ 'contacts.search': { attribute_allowlist: ['name', 'email'] },
50
+ },
51
+ audienceDid: 'did:web:ghost.agent',
52
+ catalog,
53
+ });
54
+
55
+ console.log(compiled.policies); // string[] — one compiled policy per scope
56
+ console.log(compiled.obligations); // Obligation[] — aggregated post-allow requirements
57
+ console.log(compiled.expandedScopeIds); // string[] — including implied scopes
58
+ ```
59
+
60
+ The bundle compiler:
61
+ - Transitively expands `implies` relations so the consent UI doesn't have to ask for prerequisites twice.
62
+ - Detects `conflicts_with` pairs and throws before compilation.
63
+ - Inherits parameters along implication edges (e.g., a `project_id` on `files.project.files.read` propagates to its implied `files.project.files.list`).
64
+ - Concatenates `obligations_forced` across the expanded set.
65
+
66
+ ## The 50 scopes
67
+
68
+ Authored as one YAML file per scope under `scopes/`. The `generated/manifest.json` file is the public manifest served at `/.well-known/scope-catalog.json` — see `ARP-scope-catalog-v1.md` for the full list.
69
+
70
+ Scope categories: identity, calendar, messaging, files, contacts, tasks, notes, payments, work, credentials, tools, delegation.
71
+
72
+ Risk tiers: low, medium, high, critical (see §2 of the catalog doc for default obligations by tier).
73
+
74
+ ## Phase
75
+
76
+ Shipped as part of Phase 1. See [`docs/ARP-phase-0-roadmap.md`](../../docs/ARP-phase-0-roadmap.md).
77
+
78
+ ## License
79
+
80
+ MIT.
package/dist/index.cjs ADDED
@@ -0,0 +1,518 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+ var yaml = require('yaml');
6
+ var arpSpec = require('@kybernesis/arp-spec');
7
+ var crypto = require('crypto');
8
+ var Handlebars = require('handlebars');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var Handlebars__default = /*#__PURE__*/_interopDefault(Handlebars);
13
+
14
+ // src/loader.ts
15
+ var ScopeLoadError = class extends Error {
16
+ file;
17
+ issues;
18
+ constructor(message, opts) {
19
+ super(message);
20
+ this.name = "ScopeLoadError";
21
+ if (opts?.file !== void 0) this.file = opts.file;
22
+ if (opts?.issues !== void 0) this.issues = opts.issues;
23
+ }
24
+ };
25
+ function loadScopeFile(filePath) {
26
+ let raw;
27
+ try {
28
+ raw = fs.readFileSync(filePath, "utf8");
29
+ } catch (e) {
30
+ throw new ScopeLoadError(`cannot read ${filePath}: ${e.message}`, {
31
+ file: filePath
32
+ });
33
+ }
34
+ let parsed;
35
+ try {
36
+ parsed = yaml.parse(raw);
37
+ } catch (e) {
38
+ throw new ScopeLoadError(`invalid YAML in ${filePath}: ${e.message}`, {
39
+ file: filePath
40
+ });
41
+ }
42
+ const result = arpSpec.ScopeTemplateSchema.safeParse(parsed);
43
+ if (!result.success) {
44
+ throw new ScopeLoadError(`scope ${filePath} failed schema validation`, {
45
+ file: filePath,
46
+ issues: result.error.issues
47
+ });
48
+ }
49
+ return result.data;
50
+ }
51
+ function loadScopesFromDirectory(scopesDir) {
52
+ const st = fs.statSync(scopesDir);
53
+ if (!st.isDirectory()) {
54
+ throw new ScopeLoadError(`${scopesDir} is not a directory`);
55
+ }
56
+ const files = fs.readdirSync(scopesDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml")).sort();
57
+ const seen = /* @__PURE__ */ new Set();
58
+ const scopes = [];
59
+ for (const file of files) {
60
+ const full = path.resolve(scopesDir, file);
61
+ const scope = loadScopeFile(full);
62
+ const expected = `${scope.id}.yaml`;
63
+ if (file !== expected && !(file === `${scope.id}.yml`)) {
64
+ throw new ScopeLoadError(
65
+ `filename ${file} does not match scope id ${scope.id} (expected ${expected})`,
66
+ { file: full }
67
+ );
68
+ }
69
+ if (seen.has(scope.id)) {
70
+ throw new ScopeLoadError(`duplicate scope id ${scope.id}`, { file: full });
71
+ }
72
+ seen.add(scope.id);
73
+ scopes.push(scope);
74
+ }
75
+ scopes.sort((a, b) => a.id.localeCompare(b.id));
76
+ return scopes;
77
+ }
78
+ function canonicalize(value) {
79
+ if (value === null) return "null";
80
+ if (typeof value === "number" && !Number.isFinite(value)) {
81
+ throw new Error("canonicalize: non-finite numbers are not allowed");
82
+ }
83
+ if (typeof value !== "object") {
84
+ return JSON.stringify(value);
85
+ }
86
+ if (Array.isArray(value)) {
87
+ return `[${value.map(canonicalize).join(",")}]`;
88
+ }
89
+ const obj = value;
90
+ const keys = Object.keys(obj).sort();
91
+ const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalize(obj[k])}`);
92
+ return `{${parts.join(",")}}`;
93
+ }
94
+ function sha256Hex(input) {
95
+ return crypto.createHash("sha256").update(input, "utf8").digest("hex");
96
+ }
97
+ function buildCatalogManifest(scopes, options = {}) {
98
+ const sorted = [...scopes].sort((a, b) => a.id.localeCompare(b.id));
99
+ const canonical = canonicalize(sorted);
100
+ const checksum = `sha256:${sha256Hex(canonical)}`;
101
+ const manifest = {
102
+ version: options.version ?? "v1",
103
+ updated_at: options.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
104
+ scope_count: sorted.length,
105
+ checksum,
106
+ scopes: sorted
107
+ };
108
+ const parsed = arpSpec.ScopeCatalogManifestSchema.parse(manifest);
109
+ return parsed;
110
+ }
111
+ var ScopeCompileError = class extends Error {
112
+ scopeId;
113
+ parameter;
114
+ constructor(message, opts) {
115
+ super(message);
116
+ this.name = "ScopeCompileError";
117
+ if (opts?.scopeId !== void 0) this.scopeId = opts.scopeId;
118
+ if (opts?.parameter !== void 0) this.parameter = opts.parameter;
119
+ }
120
+ };
121
+ function parseRangeValidation(validation) {
122
+ const match = /^(-?\d+(?:\.\d+)?)\.\.(-?\d+(?:\.\d+)?)$/.exec(validation);
123
+ if (!match) return null;
124
+ return { min: Number(match[1]), max: Number(match[2]) };
125
+ }
126
+ function coerceToNumber(value) {
127
+ if (typeof value === "number" && Number.isFinite(value)) return value;
128
+ if (typeof value === "string" && value.trim() !== "") {
129
+ const n = Number(value);
130
+ if (Number.isFinite(n)) return n;
131
+ }
132
+ return null;
133
+ }
134
+ function validateParameter(scopeId, def, initial) {
135
+ let value = initial;
136
+ const present = value !== void 0 && value !== null;
137
+ if (!present) {
138
+ if (def.default !== void 0) {
139
+ value = def.default;
140
+ } else if (def.required) {
141
+ throw new ScopeCompileError(
142
+ `missing required parameter '${def.name}' for scope ${scopeId}`,
143
+ { scopeId, parameter: def.name }
144
+ );
145
+ } else {
146
+ return void 0;
147
+ }
148
+ }
149
+ switch (def.type) {
150
+ case "Integer": {
151
+ const n = coerceToNumber(value);
152
+ if (n === null || !Number.isInteger(n)) {
153
+ throw new ScopeCompileError(
154
+ `parameter '${def.name}' must be an integer`,
155
+ { scopeId, parameter: def.name }
156
+ );
157
+ }
158
+ if (typeof def.validation === "string") {
159
+ const range = parseRangeValidation(def.validation);
160
+ if (range && (n < range.min || n > range.max)) {
161
+ throw new ScopeCompileError(
162
+ `parameter '${def.name}'=${n} out of range ${def.validation}`,
163
+ { scopeId, parameter: def.name }
164
+ );
165
+ }
166
+ }
167
+ return n;
168
+ }
169
+ case "Decimal": {
170
+ const n = coerceToNumber(value);
171
+ if (n === null) {
172
+ throw new ScopeCompileError(
173
+ `parameter '${def.name}' must be a number`,
174
+ { scopeId, parameter: def.name }
175
+ );
176
+ }
177
+ if (typeof def.validation === "string") {
178
+ const range = parseRangeValidation(def.validation);
179
+ if (range && (n < range.min || n > range.max)) {
180
+ throw new ScopeCompileError(
181
+ `parameter '${def.name}'=${n} out of range ${def.validation}`,
182
+ { scopeId, parameter: def.name }
183
+ );
184
+ }
185
+ }
186
+ return n;
187
+ }
188
+ case "Enum": {
189
+ if (Array.isArray(def.validation) && !def.validation.includes(String(value))) {
190
+ throw new ScopeCompileError(
191
+ `parameter '${def.name}'='${String(value)}' is not one of [${def.validation.join(", ")}]`,
192
+ { scopeId, parameter: def.name }
193
+ );
194
+ }
195
+ return value;
196
+ }
197
+ case "AgentDID": {
198
+ if (typeof value !== "string" || !arpSpec.DID_URI_REGEX.test(value)) {
199
+ throw new ScopeCompileError(
200
+ `parameter '${def.name}' must be a valid DID URI`,
201
+ { scopeId, parameter: def.name }
202
+ );
203
+ }
204
+ return value;
205
+ }
206
+ case "AgentDIDList": {
207
+ if (!Array.isArray(value) || value.length === 0) {
208
+ throw new ScopeCompileError(
209
+ `parameter '${def.name}' must be a non-empty array of DID URIs`,
210
+ { scopeId, parameter: def.name }
211
+ );
212
+ }
213
+ for (const entry of value) {
214
+ if (typeof entry !== "string" || !arpSpec.DID_URI_REGEX.test(entry)) {
215
+ throw new ScopeCompileError(
216
+ `parameter '${def.name}' contains an invalid DID URI: ${String(entry)}`,
217
+ { scopeId, parameter: def.name }
218
+ );
219
+ }
220
+ }
221
+ return value;
222
+ }
223
+ case "ToolIDList":
224
+ case "AttributeList":
225
+ case "EmailList": {
226
+ if (!Array.isArray(value)) {
227
+ throw new ScopeCompileError(
228
+ `parameter '${def.name}' must be an array`,
229
+ { scopeId, parameter: def.name }
230
+ );
231
+ }
232
+ return value;
233
+ }
234
+ case "ProjectID": {
235
+ if (typeof value !== "string" || value.trim() === "") {
236
+ throw new ScopeCompileError(
237
+ `parameter '${def.name}' must be a non-empty string`,
238
+ { scopeId, parameter: def.name }
239
+ );
240
+ }
241
+ return value;
242
+ }
243
+ case "Duration":
244
+ case "Timezone":
245
+ case "IANATimezone": {
246
+ if (typeof value !== "string" || value.trim() === "") {
247
+ throw new ScopeCompileError(
248
+ `parameter '${def.name}' must be a non-empty string`,
249
+ { scopeId, parameter: def.name }
250
+ );
251
+ }
252
+ return value;
253
+ }
254
+ }
255
+ }
256
+ function buildHandlebarsContext(audienceDid, scope, validated) {
257
+ const ctx = {
258
+ audience_did: audienceDid,
259
+ ...validated
260
+ };
261
+ for (const [key, value] of Object.entries(validated)) {
262
+ if (Array.isArray(value)) {
263
+ ctx[`${key}_json`] = JSON.stringify(value);
264
+ ctx[`${key}_display`] = value.map((v) => String(v)).join(", ");
265
+ }
266
+ }
267
+ if (scope.id === "calendar.events.read") {
268
+ ctx.include_private_flag = (validated.include_private ?? scope.parameters.find((p) => p.name === "include_private")?.default) === "yes";
269
+ }
270
+ return ctx;
271
+ }
272
+ function compileScope({
273
+ scope,
274
+ params = {},
275
+ audienceDid
276
+ }) {
277
+ if (!arpSpec.DID_URI_REGEX.test(audienceDid)) {
278
+ throw new ScopeCompileError(`audienceDid '${audienceDid}' is not a valid DID URI`, {
279
+ scopeId: scope.id
280
+ });
281
+ }
282
+ const validated = {};
283
+ for (const def of scope.parameters) {
284
+ validated[def.name] = validateParameter(scope.id, def, params[def.name]);
285
+ }
286
+ const ctx = buildHandlebarsContext(audienceDid, scope, validated);
287
+ const template = Handlebars__default.default.compile(scope.cedar_template, { noEscape: true });
288
+ const rendered = template(ctx);
289
+ return normalizeBareEntityTypes(rendered.trim());
290
+ }
291
+ function normalizeBareEntityTypes(cedar) {
292
+ return cedar.replace(
293
+ /(principal|resource)\s*==\s*([A-Z][A-Za-z0-9_]*)(?=\s*[,)\n])/g,
294
+ "$1 is $2"
295
+ );
296
+ }
297
+
298
+ // src/bundle-compiler.ts
299
+ var BundleCompileError = class extends Error {
300
+ scopeId;
301
+ conflict;
302
+ constructor(message, opts) {
303
+ super(message);
304
+ this.name = "BundleCompileError";
305
+ if (opts?.scopeId !== void 0) this.scopeId = opts.scopeId;
306
+ if (opts?.conflict !== void 0) this.conflict = opts.conflict;
307
+ }
308
+ };
309
+ function indexCatalog(catalog) {
310
+ const map = /* @__PURE__ */ new Map();
311
+ for (const scope of catalog) {
312
+ map.set(scope.id, scope);
313
+ }
314
+ return map;
315
+ }
316
+ function expandImplications(seed, catalog) {
317
+ const order = [];
318
+ const visited = /* @__PURE__ */ new Set();
319
+ const impliedBy = /* @__PURE__ */ new Map();
320
+ const queue = seed.map((id) => ({
321
+ id,
322
+ parent: null
323
+ }));
324
+ while (queue.length > 0) {
325
+ const { id, parent } = queue.shift();
326
+ if (visited.has(id)) continue;
327
+ visited.add(id);
328
+ const scope = catalog.get(id);
329
+ if (!scope) {
330
+ throw new BundleCompileError(`unknown scope id '${id}'`, { scopeId: id });
331
+ }
332
+ order.push(id);
333
+ if (parent !== null && !impliedBy.has(id)) impliedBy.set(id, parent);
334
+ for (const implied of scope.implies) {
335
+ if (!visited.has(implied)) {
336
+ queue.push({ id: implied, parent: id });
337
+ }
338
+ }
339
+ }
340
+ return { order, impliedBy };
341
+ }
342
+ function detectConflicts(expanded, catalog) {
343
+ const set = new Set(expanded);
344
+ for (const id of expanded) {
345
+ const scope = catalog.get(id);
346
+ if (!scope) continue;
347
+ for (const conflict of scope.conflicts_with) {
348
+ if (set.has(conflict)) {
349
+ throw new BundleCompileError(
350
+ `scope '${id}' conflicts with '${conflict}' \u2014 cannot coexist in the same bundle`,
351
+ { conflict: [id, conflict] }
352
+ );
353
+ }
354
+ }
355
+ }
356
+ }
357
+ function resolveParamsForScope(scope, paramsMap, impliedBy, idx) {
358
+ const own = { ...paramsMap[scope.id] ?? {} };
359
+ let parentId = impliedBy.get(scope.id);
360
+ while (parentId) {
361
+ const parentParams = paramsMap[parentId];
362
+ if (parentParams) {
363
+ for (const [k, v] of Object.entries(parentParams)) {
364
+ if (own[k] === void 0) own[k] = v;
365
+ }
366
+ }
367
+ const grandparentId = impliedBy.get(parentId);
368
+ parentId = grandparentId && grandparentId !== parentId ? grandparentId : void 0;
369
+ }
370
+ return own;
371
+ }
372
+ function compileBundle({
373
+ scopeIds,
374
+ paramsMap = {},
375
+ audienceDid,
376
+ catalog
377
+ }) {
378
+ const idx = indexCatalog(catalog);
379
+ const { order, impliedBy } = expandImplications(scopeIds, idx);
380
+ detectConflicts(order, idx);
381
+ const policies = [];
382
+ const obligations = [];
383
+ for (const id of order) {
384
+ const scope = idx.get(id);
385
+ if (!scope) continue;
386
+ const params = resolveParamsForScope(scope, paramsMap, impliedBy);
387
+ try {
388
+ policies.push(
389
+ compileScope({
390
+ scope,
391
+ audienceDid,
392
+ params
393
+ })
394
+ );
395
+ } catch (e) {
396
+ if (e instanceof ScopeCompileError) throw e;
397
+ throw new BundleCompileError(
398
+ `failed to compile scope '${id}': ${e.message}`,
399
+ { scopeId: id }
400
+ );
401
+ }
402
+ for (const ob of scope.obligations_forced) {
403
+ obligations.push({ type: ob.type, params: ob.params });
404
+ }
405
+ }
406
+ return { policies, obligations, expandedScopeIds: order };
407
+ }
408
+
409
+ // src/bundles.ts
410
+ var BUNDLES = [
411
+ {
412
+ id: "bundle.project_collaboration.v1",
413
+ version: "1.0.0",
414
+ label: "Project collaboration",
415
+ description: "Collaborate on a project \u2014 read files, task status, and notes; no writes or external sharing.",
416
+ scopes: [
417
+ { id: "files.projects.list" },
418
+ { id: "files.project.metadata.read", params: { project_id: "<user-picks>" } },
419
+ { id: "files.project.files.read", params: { project_id: "<user-picks>", max_size_mb: 25 } },
420
+ { id: "files.project.files.summarize", params: { project_id: "<user-picks>", max_output_words: 2e3 } },
421
+ { id: "tasks.list", params: { project_id: "<user-picks>" } },
422
+ { id: "tasks.read", params: { project_id: "<user-picks>" } },
423
+ { id: "tasks.status.update", params: { project_id: "<user-picks>" } },
424
+ { id: "notes.search", params: { collection_id: "<user-picks>" } },
425
+ { id: "notes.read", params: { collection_id: "<user-picks>" } }
426
+ ]
427
+ },
428
+ {
429
+ id: "bundle.scheduling_assistant.v1",
430
+ version: "1.0.0",
431
+ label: "Scheduling assistant",
432
+ description: "Coordinate meetings on your calendar.",
433
+ scopes: [
434
+ { id: "calendar.availability.read", params: { days_ahead: 14 } },
435
+ {
436
+ id: "calendar.events.propose",
437
+ params: { max_attendees: 10, max_duration_min: 60 }
438
+ },
439
+ { id: "contacts.search", params: { attribute_allowlist: ["name", "email"] } },
440
+ { id: "messaging.relay.to_principal" }
441
+ ]
442
+ },
443
+ {
444
+ id: "bundle.research_agent.v1",
445
+ version: "1.0.0",
446
+ label: "Research agent",
447
+ description: "Pull research without writing.",
448
+ scopes: [
449
+ { id: "files.projects.list" },
450
+ { id: "files.project.files.read", params: { project_id: "<user-picks>", max_size_mb: 25 } },
451
+ { id: "files.project.files.summarize", params: { project_id: "<user-picks>", max_output_words: 2e3 } },
452
+ { id: "notes.search", params: { collection_id: "<user-picks>" } },
453
+ { id: "notes.read", params: { collection_id: "<user-picks>" } },
454
+ { id: "knowledge.query", params: { kb_id: "<user-picks>", max_tokens: 8e3 } },
455
+ { id: "credentials.proof.zk.request", params: { attribute: "verified_human" } }
456
+ ]
457
+ },
458
+ {
459
+ id: "bundle.procurement_agent.v1",
460
+ version: "1.0.0",
461
+ label: "Procurement agent",
462
+ description: "Buy things under tight caps.",
463
+ scopes: [
464
+ { id: "payments.quote.request" },
465
+ {
466
+ id: "payments.authorize.capped",
467
+ params: { max_per_txn_usd: 25, max_per_30d_usd: 200 }
468
+ },
469
+ { id: "payments.history.read", params: { days_back: 90 } },
470
+ { id: "messaging.relay.to_principal" }
471
+ ]
472
+ },
473
+ {
474
+ id: "bundle.executive_assistant.v1",
475
+ version: "1.0.0",
476
+ label: "Executive assistant",
477
+ description: "Broad assistant; step-up on anything external-facing.",
478
+ scopes: [
479
+ { id: "calendar.availability.read", params: { days_ahead: 14 } },
480
+ {
481
+ id: "calendar.events.propose",
482
+ params: { max_attendees: 10, max_duration_min: 60 }
483
+ },
484
+ { id: "calendar.events.modify" },
485
+ { id: "messaging.email.summary" },
486
+ { id: "messaging.email.draft.compose" },
487
+ { id: "messaging.email.send.reviewed", params: { recipient_allowlist: [] } },
488
+ { id: "contacts.search", params: { attribute_allowlist: ["name", "email"] } },
489
+ { id: "tasks.list", params: { project_id: "<user-picks>" } },
490
+ { id: "tasks.read", params: { project_id: "<user-picks>" } },
491
+ { id: "tasks.create", params: { project_id: "<user-picks>", max_per_day: 50 } },
492
+ { id: "tasks.status.update", params: { project_id: "<user-picks>" } },
493
+ { id: "notes.search", params: { collection_id: "<user-picks>" } },
494
+ { id: "notes.read", params: { collection_id: "<user-picks>" } },
495
+ { id: "notes.write", params: { collection_id: "<user-picks>", max_per_day: 100 } },
496
+ { id: "work.status.read" },
497
+ { id: "work.reports.summary", params: { period: "week" } }
498
+ ]
499
+ }
500
+ ];
501
+ function findBundle(id) {
502
+ return BUNDLES.find((b) => b.id === id);
503
+ }
504
+
505
+ exports.BUNDLES = BUNDLES;
506
+ exports.BundleCompileError = BundleCompileError;
507
+ exports.ScopeCompileError = ScopeCompileError;
508
+ exports.ScopeLoadError = ScopeLoadError;
509
+ exports.buildCatalogManifest = buildCatalogManifest;
510
+ exports.canonicalize = canonicalize;
511
+ exports.compileBundle = compileBundle;
512
+ exports.compileScope = compileScope;
513
+ exports.findBundle = findBundle;
514
+ exports.loadScopeFile = loadScopeFile;
515
+ exports.loadScopesFromDirectory = loadScopesFromDirectory;
516
+ exports.sha256Hex = sha256Hex;
517
+ //# sourceMappingURL=index.cjs.map
518
+ //# sourceMappingURL=index.cjs.map