@vectros-ai/blueprints 0.5.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,1257 @@
1
+ // src/types.ts
2
+ import { z } from "zod";
3
+ var CONTEXT_ID_RE = /^[a-z][a-z0-9-]{2,30}$/;
4
+ var ValidationRulesSchema = z.object({
5
+ required: z.boolean().optional(),
6
+ minLength: z.number().int().optional(),
7
+ maxLength: z.number().int().optional(),
8
+ min: z.number().int().optional(),
9
+ max: z.number().int().optional(),
10
+ pattern: z.string().optional(),
11
+ email: z.boolean().optional(),
12
+ url: z.boolean().optional(),
13
+ phone: z.boolean().optional(),
14
+ step: z.number().int().optional(),
15
+ multipleOf: z.number().int().optional(),
16
+ minItems: z.number().int().optional(),
17
+ maxItems: z.number().int().optional()
18
+ }).strict();
19
+ var RenderHintsSchema = z.object({
20
+ label: z.string().optional(),
21
+ widget: z.enum(["text", "textarea", "select", "date", "checkbox"]).optional(),
22
+ order: z.number().int().optional(),
23
+ section: z.string().optional(),
24
+ helpText: z.string().optional(),
25
+ // Marks this field as the record's headline (display) field — the linked
26
+ // primary column in the records list + the title on the detail view. At most
27
+ // one per schema (the platform takes the first by order). Format passthrough →
28
+ // SchemaRequest.renderHints[fieldId].displayField.
29
+ displayField: z.boolean().optional()
30
+ }).strict();
31
+ var BlueprintFieldDefSchema = z.object({
32
+ fieldId: z.string().min(1),
33
+ fieldType: z.string().min(1),
34
+ required: z.boolean().optional(),
35
+ searchable: z.boolean().optional(),
36
+ filterable: z.boolean().optional(),
37
+ enumValues: z.array(z.string()).optional(),
38
+ description: z.string().optional(),
39
+ // NEW (format passthrough) — the loader stopped dropping these.
40
+ validation: ValidationRulesSchema.optional(),
41
+ renderHints: RenderHintsSchema.optional(),
42
+ // Marks the field as sensitive (PHI/PII): the platform redacts it from
43
+ // logs/audit/errors AT WRITE TIME, blind-indexes it for lookups, EXCLUDES it
44
+ // from the search index, and masks it in responses unless the token carries
45
+ // the `s` reveal scope for this record type (SchemaRequest.FieldDef.sensitive).
46
+ // Format passthrough — the loader forwards it to createSchema. Default false.
47
+ sensitive: z.boolean().optional(),
48
+ // Reference-field surface — a typed foreign-key link to another record. The
49
+ // platform (SchemaRequest.FieldDef) requires BOTH targetTypeName AND
50
+ // targetSurface on a reference field; the blueprint format uses the SAME names
51
+ // as the SDK so they forward 1:1, and the loader provisions a real reference.
52
+ // (Write-time existence enforcement is on by default platform-side; the target
53
+ // must exist when a referencing record is written — order your seed accordingly.)
54
+ targetTypeName: z.string().min(1).optional(),
55
+ // The field on the target record used to resolve the link; defaults (platform
56
+ // side) to the target's externalId/lookup key when omitted. Must name a UNIQUE
57
+ // lookup on the target type.
58
+ targetField: z.string().min(1).optional(),
59
+ // Which surface the target lives on. REQUIRED on a reference field: the same
60
+ // typeName can exist on more than one surface, so this disambiguates which
61
+ // lookup resolves the link (SchemaRequest.FieldDef.targetSurface).
62
+ targetSurface: z.enum(["record", "document", "user", "org", "client"]).optional(),
63
+ cardinality: z.enum(["one", "many"]).optional()
64
+ }).strict().superRefine((field, ctx) => {
65
+ const isReference = field.fieldType === "reference";
66
+ const hasRefKeys = field.targetTypeName !== void 0 || field.targetField !== void 0 || field.targetSurface !== void 0 || field.cardinality !== void 0;
67
+ if (isReference && field.targetTypeName === void 0) {
68
+ ctx.addIssue({
69
+ code: z.ZodIssueCode.custom,
70
+ path: ["targetTypeName"],
71
+ message: "a 'reference' field requires 'targetTypeName' (the typeName it points to)"
72
+ });
73
+ }
74
+ if (isReference && field.targetSurface === void 0) {
75
+ ctx.addIssue({
76
+ code: z.ZodIssueCode.custom,
77
+ path: ["targetSurface"],
78
+ message: "a 'reference' field requires 'targetSurface' (which surface the target lives on: record | document | user | org | client)"
79
+ });
80
+ }
81
+ if (!isReference && hasRefKeys) {
82
+ ctx.addIssue({
83
+ code: z.ZodIssueCode.custom,
84
+ path: ["fieldType"],
85
+ message: "targetTypeName/targetField/targetSurface/cardinality are only valid on a field with fieldType: 'reference'"
86
+ });
87
+ }
88
+ });
89
+ var BlueprintLookupFieldSchema = z.union([
90
+ z.string().min(1),
91
+ z.object({
92
+ fieldName: z.string().min(1),
93
+ unique: z.boolean().optional(),
94
+ // Opt this field into ordered range + prefix lookups (from/to/prefix) on
95
+ // top of exact match. Billed at the range-index rate; not valid on a
96
+ // sensitive field (a blind index is not orderable). Locked at create.
97
+ rangeEnabled: z.boolean().optional(),
98
+ // Sort key for the exact-match index: 'createdAt' (default), 'lastUpdated',
99
+ // or a declared field on this schema. Locked at create.
100
+ sortBy: z.string().min(1).optional(),
101
+ // Opt a field past the fixed fast-index budget into a higher-cost
102
+ // secondary index. No effect on a field that fits within the budget.
103
+ allowOverflow: z.boolean().optional()
104
+ }).strict()
105
+ ]);
106
+ var BlueprintCapabilitiesSchema = z.object({
107
+ auditHistory: z.boolean().optional()
108
+ }).strict();
109
+ var BlueprintSchemaSchema = z.object({
110
+ typeName: z.string().min(1),
111
+ displayName: z.string().min(1),
112
+ description: z.string().optional(),
113
+ indexMode: z.enum(["HYBRID", "SEMANTIC", "TEXT"]).optional(),
114
+ fields: z.array(BlueprintFieldDefSchema).default([]),
115
+ lookupFields: z.array(BlueprintLookupFieldSchema).max(10).optional(),
116
+ // Which typed surfaces may bind this schema. REQUIRED + non-empty on
117
+ // the platform `SchemaRequest` (0.23+); the loader defaults it to ['record']
118
+ // when a blueprint omits it (blueprints provision record types + seed records).
119
+ allowedSurfaces: z.array(z.enum(["record", "document", "user", "org", "client"])).min(1).optional(),
120
+ // NEW (format passthrough) — mirror the platform `SchemaRequest` shape 1:1.
121
+ capabilities: BlueprintCapabilitiesSchema.optional(),
122
+ // Whether the schema is active; inactive schemas reject new record creation.
123
+ active: z.boolean().optional(),
124
+ // Schema-level ownership defaults — flat, matching `SchemaRequest`
125
+ // userId/orgId/clientId. With a scoped token these must be consistent with
126
+ // the profile's dataScope (a cross-consistency lint is deferred to the lint slice).
127
+ userId: z.string().min(1).optional(),
128
+ orgId: z.string().min(1).optional(),
129
+ clientId: z.string().min(1).optional()
130
+ }).strict();
131
+ var BlueprintAccessProfileSchema = z.object({
132
+ // Validated structurally here; the SCOPE GATE (in @vectros-ai/cli) is
133
+ // what enforces the data-plane-only security boundary.
134
+ allowedActions: z.array(z.string().min(1)).min(1),
135
+ // Optional ownership binding: { userId: [...], orgId: [...], clientId: [...] }.
136
+ // A `null` element in a value list is the documented NULL SENTINEL — it
137
+ // grants access to TENANT-LEVEL (owner-less) records IN ADDITION to the
138
+ // listed owner ids. `null` is the literal matched value: a tenant-level
139
+ // record has a genuinely-null ownership field, and the platform's scope
140
+ // matcher tests `allowedValues.contains(null)` against the tenant-level
141
+ // null sentinel (+ ScopeClause). e.g. `{ orgId: ["org_x", null] }` =
142
+ // "org_x's records AND tenant-shared records". Omitting null restricts to
143
+ // the listed owners ONLY (the key will NOT see tenant-level/seed records).
144
+ dataScope: z.record(z.array(z.union([z.string().min(1), z.null()]))).optional()
145
+ }).strict();
146
+ var BlueprintRoleClauseSchema = z.object({
147
+ allowedActions: z.array(z.string().min(1)).min(1),
148
+ dataScope: z.record(z.array(z.union([z.string().min(1), z.null()]))).optional()
149
+ }).strict();
150
+ var BlueprintRolesSchema = z.record(z.array(BlueprintRoleClauseSchema).min(1));
151
+ var IdentityDeclSchema = z.object({
152
+ kind: z.enum(["user", "org", "client"]),
153
+ externalId: z.string().min(1),
154
+ displayName: z.string().min(1).optional(),
155
+ metadata: z.record(z.unknown()).optional()
156
+ }).strict();
157
+ var IdentitiesDeclSchema = z.record(IdentityDeclSchema);
158
+ var BlueprintServicePrincipalSchema = z.object({
159
+ externalId: z.string().min(1),
160
+ displayName: z.string().min(1)
161
+ }).strict();
162
+ var BlueprintSeedRecordSchema = z.object({
163
+ typeName: z.string().min(1),
164
+ externalId: z.string().min(1),
165
+ fields: z.record(z.unknown())
166
+ }).strict();
167
+ var SELF_TOKEN_RE = /\$\{\{\s*self\.[A-Za-z_]\w*\s*\}\}/;
168
+ function lintSelfTokenPlacement(value, ctx) {
169
+ const walk = (node, path, inRoleDataScope) => {
170
+ if (typeof node === "string") {
171
+ if (!inRoleDataScope && SELF_TOKEN_RE.test(node)) {
172
+ ctx.addIssue({
173
+ code: z.ZodIssueCode.custom,
174
+ path,
175
+ message: "'${{ self.* }}' is a runtime per-principal placeholder \u2014 it is only valid inside a roles[].dataScope value"
176
+ });
177
+ }
178
+ return;
179
+ }
180
+ if (Array.isArray(node)) {
181
+ node.forEach((v, i) => walk(v, [...path, i], inRoleDataScope));
182
+ return;
183
+ }
184
+ if (node && typeof node === "object") {
185
+ for (const [k, v] of Object.entries(node)) {
186
+ const entering = inRoleDataScope || path.length === 3 && path[0] === "roles" && k === "dataScope";
187
+ walk(v, [...path, k], entering);
188
+ }
189
+ }
190
+ };
191
+ walk(value, [], false);
192
+ }
193
+ var IDENTITY_REF_RE = /\$\{\{\s*identities\.([A-Za-z_]\w*)\s*\}\}/g;
194
+ function lintIdentityRefsDeclared(value, ctx) {
195
+ const declared = new Set(
196
+ value && typeof value === "object" && "identities" in value && value.identities ? Object.keys(value.identities) : []
197
+ );
198
+ const walk = (node, path) => {
199
+ if (typeof node === "string") {
200
+ for (const m of node.matchAll(IDENTITY_REF_RE)) {
201
+ if (!declared.has(m[1])) {
202
+ ctx.addIssue({
203
+ code: z.ZodIssueCode.custom,
204
+ path,
205
+ message: `'\${{ identities.${m[1]} }}' references an undeclared identity \u2014 add '${m[1]}' to the top-level 'identities' block`
206
+ });
207
+ }
208
+ }
209
+ return;
210
+ }
211
+ if (Array.isArray(node)) {
212
+ node.forEach((v, i) => walk(v, [...path, i]));
213
+ } else if (node && typeof node === "object") {
214
+ for (const [k, v] of Object.entries(node)) {
215
+ if (path.length === 0 && k === "identities") continue;
216
+ walk(v, [...path, k]);
217
+ }
218
+ }
219
+ };
220
+ walk(value, []);
221
+ }
222
+ var BlueprintSchema = z.object({
223
+ /** Stable blueprint id (the `--blueprint <name>` selector + idempotency key). */
224
+ name: z.string().min(1),
225
+ version: z.string().min(1),
226
+ description: z.string().min(1),
227
+ /** The app-context the profile + scoped key bind to (e.g. "mcp"). */
228
+ contextId: z.string().regex(CONTEXT_ID_RE, {
229
+ message: "contextId must be 3-31 chars, start with a lowercase letter, then lowercase letters/digits/dashes (e.g. 'mcp')"
230
+ }),
231
+ /** Human-readable app-context name; defaults to `MCP — <name>` (see {@link contextNameOf}) when absent. */
232
+ contextName: z.string().min(1).optional(),
233
+ schemas: z.array(BlueprintSchemaSchema).default([]),
234
+ accessProfile: BlueprintAccessProfileSchema,
235
+ servicePrincipal: BlueprintServicePrincipalSchema,
236
+ seed: z.array(BlueprintSeedRecordSchema).optional(),
237
+ /** Optional multi-clause roles, bound to principals via `access grant --role`. */
238
+ roles: BlueprintRolesSchema.optional(),
239
+ /** Optional principals ensured-exist at apply; referenced via ${{ identities.* }}. */
240
+ identities: IdentitiesDeclSchema.optional()
241
+ }).strict().superRefine((bp, ctx) => {
242
+ lintSelfTokenPlacement(bp, ctx);
243
+ lintIdentityRefsDeclared(bp, ctx);
244
+ });
245
+ function formatIssuePath(path) {
246
+ let out = "";
247
+ for (const seg of path) {
248
+ if (typeof seg === "number") out += `[${seg}]`;
249
+ else out += out.length ? `.${seg}` : seg;
250
+ }
251
+ return out.length ? out : "(root)";
252
+ }
253
+ function toBlueprintIssues(error) {
254
+ return error.issues.map((i) => ({ path: formatIssuePath(i.path), message: i.message }));
255
+ }
256
+ function renderIssues(issues) {
257
+ return issues.map((i) => ` \u2022 ${i.path}: ${i.message}`).join("\n");
258
+ }
259
+ var BlueprintValidationError = class extends Error {
260
+ /**
261
+ * Structured per-field issues. Populated for STRUCTURAL failures (a bad
262
+ * shape); empty for a JSON/YAML *parse* failure (where there's no field path,
263
+ * just a syntax error in {@link Error.message}).
264
+ */
265
+ issues;
266
+ constructor(message, issues = []) {
267
+ super(message);
268
+ this.name = "BlueprintValidationError";
269
+ this.issues = issues;
270
+ }
271
+ };
272
+ function parseBlueprint(input) {
273
+ const result = BlueprintSchema.safeParse(input);
274
+ if (!result.success) {
275
+ const issues = toBlueprintIssues(result.error);
276
+ throw new BlueprintValidationError(`Malformed blueprint:
277
+ ${renderIssues(issues)}`, issues);
278
+ }
279
+ return result.data;
280
+ }
281
+ function parseBlueprintJson(json) {
282
+ let parsed;
283
+ try {
284
+ parsed = JSON.parse(json);
285
+ } catch (err) {
286
+ throw new BlueprintValidationError(
287
+ `Blueprint is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
288
+ );
289
+ }
290
+ return parseBlueprint(parsed);
291
+ }
292
+ function contextNameOf(blueprint) {
293
+ return blueprint.contextName ?? `MCP \u2014 ${blueprint.name}`;
294
+ }
295
+
296
+ // src/identities.ts
297
+ var IDENTITY_TOKEN_RE = /\$\{\{\s*identities\.([A-Za-z_]\w*)\s*\}\}/g;
298
+ var BlueprintIdentityError = class extends Error {
299
+ issues;
300
+ constructor(message, issues = []) {
301
+ super(message);
302
+ this.name = "BlueprintIdentityError";
303
+ this.issues = issues;
304
+ }
305
+ };
306
+ function collectIdentityReferences(value) {
307
+ const found = /* @__PURE__ */ new Set();
308
+ const walk = (node) => {
309
+ if (typeof node === "string") {
310
+ for (const m of node.matchAll(IDENTITY_TOKEN_RE)) found.add(m[1]);
311
+ } else if (Array.isArray(node)) {
312
+ node.forEach(walk);
313
+ } else if (node && typeof node === "object") {
314
+ Object.values(node).forEach(walk);
315
+ }
316
+ };
317
+ walk(value);
318
+ return [...found];
319
+ }
320
+ function substitute(node, idMap) {
321
+ if (typeof node === "string") {
322
+ return node.replace(IDENTITY_TOKEN_RE, (_full, name) => idMap[name]);
323
+ }
324
+ if (Array.isArray(node)) return node.map((v) => substitute(v, idMap));
325
+ if (node && typeof node === "object") {
326
+ const out = {};
327
+ for (const [k, v] of Object.entries(node)) out[k] = substitute(v, idMap);
328
+ return out;
329
+ }
330
+ return node;
331
+ }
332
+ async function resolveBlueprintIdentities(raw, resolve) {
333
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
334
+ const { identities: rawIdentities, ...body } = raw;
335
+ const refs = collectIdentityReferences(body);
336
+ if (rawIdentities === void 0 && refs.length === 0) return raw;
337
+ const parsed = IdentitiesDeclSchema.safeParse(rawIdentities ?? {});
338
+ if (!parsed.success) {
339
+ throw new BlueprintIdentityError(
340
+ "Blueprint identities block is invalid",
341
+ parsed.error.issues.map((i) => ({
342
+ path: i.path.length ? `identities.${i.path.join(".")}` : "identities",
343
+ message: i.message
344
+ }))
345
+ );
346
+ }
347
+ const declared = parsed.data;
348
+ const undeclared = refs.filter((name) => !(name in declared));
349
+ if (undeclared.length) {
350
+ throw new BlueprintIdentityError(
351
+ `Blueprint references undeclared identities: ${undeclared.join(", ")}`,
352
+ undeclared.map((name) => ({
353
+ path: `identities.${name}`,
354
+ message: `'\${{ identities.${name} }}' is referenced but not declared in the 'identities' block`
355
+ }))
356
+ );
357
+ }
358
+ const idMap = {};
359
+ for (const [name, decl] of Object.entries(declared)) {
360
+ try {
361
+ const id = await resolve(name, decl);
362
+ if (typeof id !== "string" || id.length === 0) {
363
+ throw new Error(`resolver returned a non-string id (${JSON.stringify(id)})`);
364
+ }
365
+ idMap[name] = id;
366
+ } catch (err) {
367
+ throw new BlueprintIdentityError(
368
+ `Failed to resolve identity '${name}' (${decl.kind} externalId=${decl.externalId}): ${err instanceof Error ? err.message : String(err)}`,
369
+ [{ path: `identities.${name}`, message: err instanceof Error ? err.message : String(err) }]
370
+ );
371
+ }
372
+ }
373
+ return substitute(body, idMap);
374
+ }
375
+
376
+ // src/inputs.ts
377
+ import { z as z2 } from "zod";
378
+ var INPUT_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
379
+ var InputDeclSchema = z2.object({
380
+ type: z2.enum(["string", "number", "boolean"]),
381
+ required: z2.boolean().optional(),
382
+ default: z2.union([z2.string(), z2.number(), z2.boolean()]).optional(),
383
+ description: z2.string().optional()
384
+ }).strict();
385
+ var InputsDeclSchema = z2.record(InputDeclSchema);
386
+ var BlueprintInputError = class extends Error {
387
+ issues;
388
+ constructor(message, issues = []) {
389
+ super(message);
390
+ this.name = "BlueprintInputError";
391
+ this.issues = issues;
392
+ }
393
+ };
394
+ function renderIssues2(issues) {
395
+ return issues.map((i) => ` \u2022 ${i.path}: ${i.message}`).join("\n");
396
+ }
397
+ function scalarType(v) {
398
+ if (typeof v === "string") return "string";
399
+ if (typeof v === "number") return "number";
400
+ if (typeof v === "boolean") return "boolean";
401
+ return "other";
402
+ }
403
+ function deriveSuffix(contextId) {
404
+ let h = 2166136261;
405
+ for (let i = 0; i < contextId.length; i++) {
406
+ h ^= contextId.charCodeAt(i);
407
+ h = Math.imul(h, 16777619);
408
+ }
409
+ return (h >>> 0).toString(36).padStart(7, "0");
410
+ }
411
+ var WHOLE_TOKEN_RE = /^\$\{\{\s*([A-Za-z_]\w*)\.([A-Za-z_]\w*)\s*\}\}$/;
412
+ var TOKEN_SCAN_RE = /\$\$\{\{|\$\{\{([\s\S]*?)\}\}|\$\{\{/g;
413
+ var INNER_REF_RE = /^([A-Za-z_]\w*)\.([A-Za-z_]\w*)$/;
414
+ var DEFERRED_NAMESPACES = /* @__PURE__ */ new Set(["self", "identities"]);
415
+ function resolveRef(ns, name, ctx, path) {
416
+ if (ns === "inputs") {
417
+ if (!(name in ctx.declared)) {
418
+ ctx.issues.push({
419
+ path,
420
+ message: `unknown input 'inputs.${name}' \u2014 declare it in the top-level 'inputs:' block`
421
+ });
422
+ return void 0;
423
+ }
424
+ if (!(name in ctx.values)) {
425
+ ctx.issues.push({
426
+ path,
427
+ message: `input 'inputs.${name}' has no value \u2014 supply it (--set ${name}=\u2026 or --values) or give it a default`
428
+ });
429
+ return void 0;
430
+ }
431
+ return ctx.values[name];
432
+ }
433
+ if (ns === "vectros") {
434
+ if (name !== "context" && name !== "suffix") {
435
+ ctx.issues.push({
436
+ path,
437
+ message: `unknown built-in 'vectros.${name}' (available: vectros.context, vectros.suffix)`
438
+ });
439
+ return void 0;
440
+ }
441
+ if (!ctx.builtins) {
442
+ ctx.issues.push({
443
+ path,
444
+ message: `'vectros.${name}' is unavailable: contextId must be a literal string to derive built-ins`
445
+ });
446
+ return void 0;
447
+ }
448
+ return ctx.builtins[name];
449
+ }
450
+ ctx.issues.push({
451
+ path,
452
+ message: `unknown namespace '${ns}' in '\${{ ${ns}.${name} }}' (available: inputs, vectros)`
453
+ });
454
+ return void 0;
455
+ }
456
+ function interpolate(str, ctx, path) {
457
+ let out = "";
458
+ let last = 0;
459
+ TOKEN_SCAN_RE.lastIndex = 0;
460
+ let m;
461
+ while ((m = TOKEN_SCAN_RE.exec(str)) !== null) {
462
+ out += str.slice(last, m.index);
463
+ if (m[0] === "$${{") {
464
+ out += "${{";
465
+ } else if (m[0] === "${{" && m[1] === void 0) {
466
+ ctx.issues.push({
467
+ path,
468
+ message: `unterminated token '\${{' \u2014 expected a closing '}}' (escape a literal with '$\${{')`
469
+ });
470
+ } else {
471
+ const inner = (m[1] ?? "").trim();
472
+ const ref = inner.match(INNER_REF_RE);
473
+ if (!ref) {
474
+ ctx.issues.push({
475
+ path,
476
+ message: `malformed reference '\${{ ${inner} }}' \u2014 expected '\${{ namespace.name }}' (escape a literal with '$\${{')`
477
+ });
478
+ } else if (DEFERRED_NAMESPACES.has(ref[1])) {
479
+ out += m[0];
480
+ } else {
481
+ const v = resolveRef(ref[1], ref[2], ctx, path);
482
+ if (v !== void 0) out += String(v);
483
+ }
484
+ }
485
+ last = TOKEN_SCAN_RE.lastIndex;
486
+ }
487
+ out += str.slice(last);
488
+ return out;
489
+ }
490
+ function substituteValue(val, ctx, path) {
491
+ if (typeof val === "string") {
492
+ const whole = val.match(WHOLE_TOKEN_RE);
493
+ if (whole) {
494
+ if (DEFERRED_NAMESPACES.has(whole[1])) return val;
495
+ const v = resolveRef(whole[1], whole[2], ctx, path);
496
+ return v === void 0 ? val : v;
497
+ }
498
+ return interpolate(val, ctx, path);
499
+ }
500
+ if (Array.isArray(val)) {
501
+ return val.map((v, i) => substituteValue(v, ctx, `${path}[${i}]`));
502
+ }
503
+ if (val !== null && typeof val === "object") {
504
+ const out = {};
505
+ for (const [k, v] of Object.entries(val)) {
506
+ out[k] = substituteValue(v, ctx, path ? `${path}.${k}` : k);
507
+ }
508
+ return out;
509
+ }
510
+ return val;
511
+ }
512
+ function coerce(raw, type, name, issues) {
513
+ const path = `inputs.${name}`;
514
+ if (type === "string") {
515
+ if (typeof raw === "string") return raw;
516
+ if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
517
+ issues.push({ path, message: `expected a string value` });
518
+ return void 0;
519
+ }
520
+ if (type === "number") {
521
+ if (typeof raw === "number") return raw;
522
+ if (typeof raw === "string") {
523
+ const n = Number(raw.trim());
524
+ if (raw.trim() === "" || Number.isNaN(n)) {
525
+ issues.push({ path, message: `expected a number, got '${raw}'` });
526
+ return void 0;
527
+ }
528
+ return n;
529
+ }
530
+ issues.push({ path, message: `expected a number` });
531
+ return void 0;
532
+ }
533
+ if (typeof raw === "boolean") return raw;
534
+ if (typeof raw === "string") {
535
+ const t = raw.trim().toLowerCase();
536
+ if (t === "true") return true;
537
+ if (t === "false") return false;
538
+ issues.push({ path, message: `expected a boolean ('true'|'false'), got '${raw}'` });
539
+ return void 0;
540
+ }
541
+ issues.push({ path, message: `expected a boolean` });
542
+ return void 0;
543
+ }
544
+ function resolveValues(declared, supplied, issues) {
545
+ const values = {};
546
+ for (const k of Object.keys(supplied)) {
547
+ if (!(k in declared)) {
548
+ issues.push({ path: `inputs.${k}`, message: `no input named '${k}' is declared (cannot set it)` });
549
+ }
550
+ }
551
+ for (const [name, decl] of Object.entries(declared)) {
552
+ if (name in supplied) {
553
+ const c = coerce(supplied[name], decl.type, name, issues);
554
+ if (c !== void 0) values[name] = c;
555
+ } else if (decl.default !== void 0) {
556
+ values[name] = decl.default;
557
+ } else if (decl.required) {
558
+ issues.push({
559
+ path: `inputs.${name}`,
560
+ message: `required input '${name}' was not supplied (--set ${name}=\u2026 or in --values)`
561
+ });
562
+ }
563
+ }
564
+ return values;
565
+ }
566
+ function resolveBlueprintInputs(raw, supplied = {}) {
567
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
568
+ const issues = [];
569
+ const { inputs: rawInputs, ...body } = raw;
570
+ let declared = {};
571
+ if (rawInputs !== void 0) {
572
+ const parsed = InputsDeclSchema.safeParse(rawInputs);
573
+ if (!parsed.success) {
574
+ for (const i of parsed.error.issues) {
575
+ const p = i.path.length ? `inputs.${i.path.join(".")}` : "inputs";
576
+ issues.push({ path: p, message: i.message });
577
+ }
578
+ } else {
579
+ declared = parsed.data;
580
+ for (const [name, decl] of Object.entries(declared)) {
581
+ if (!INPUT_NAME_RE.test(name)) {
582
+ issues.push({
583
+ path: `inputs.${name}`,
584
+ message: `invalid input name '${name}' \u2014 letters/digits/underscore, not starting with a digit`
585
+ });
586
+ }
587
+ if (decl.default !== void 0 && scalarType(decl.default) !== decl.type) {
588
+ issues.push({
589
+ path: `inputs.${name}.default`,
590
+ message: `default is a ${scalarType(decl.default)} but the declared type is '${decl.type}'`
591
+ });
592
+ }
593
+ }
594
+ }
595
+ }
596
+ let builtins;
597
+ const ctxId = body.contextId;
598
+ if (typeof ctxId === "string" && ctxId.length > 0) {
599
+ if (ctxId.includes("${{")) {
600
+ issues.push({
601
+ path: "contextId",
602
+ message: `contextId must be a literal \u2014 it cannot use '\${{ \u2026 }}' (it is the source of vectros.* built-ins)`
603
+ });
604
+ } else {
605
+ builtins = { context: ctxId, suffix: deriveSuffix(ctxId) };
606
+ }
607
+ }
608
+ const values = resolveValues(declared, supplied, issues);
609
+ const ctx = { declared, values, builtins, issues };
610
+ const substituted = substituteValue(body, ctx, "");
611
+ if (issues.length) {
612
+ throw new BlueprintInputError(`Blueprint variable resolution failed:
613
+ ${renderIssues2(issues)}`, issues);
614
+ }
615
+ return substituted;
616
+ }
617
+
618
+ // src/blueprints/task-management.ts
619
+ var taskManagement = {
620
+ name: "task-management",
621
+ version: "1.0.0",
622
+ description: "Structured task tracking, shareable across sessions, agents, and users.",
623
+ contextId: "task-management",
624
+ contextName: "Task Management",
625
+ schemas: [
626
+ {
627
+ typeName: "task",
628
+ displayName: "Task",
629
+ indexMode: "HYBRID",
630
+ // keyword on titles + semantic on descriptions
631
+ fields: [
632
+ {
633
+ fieldId: "title",
634
+ fieldType: "string",
635
+ required: true,
636
+ searchable: true,
637
+ validation: { minLength: 1, maxLength: 200 },
638
+ renderHints: { label: "Title", widget: "text", order: 1, section: "Task", displayField: true }
639
+ },
640
+ {
641
+ fieldId: "description",
642
+ fieldType: "string",
643
+ searchable: true,
644
+ description: "Free-text detail \u2014 the RAG-able body.",
645
+ validation: { maxLength: 8e3 },
646
+ renderHints: { label: "Description", widget: "textarea", order: 2, section: "Task" }
647
+ },
648
+ {
649
+ fieldId: "status",
650
+ fieldType: "enum",
651
+ filterable: true,
652
+ enumValues: ["todo", "in_progress", "blocked", "done"],
653
+ renderHints: { label: "Status", widget: "select", order: 3, section: "Tracking" }
654
+ },
655
+ {
656
+ fieldId: "priority",
657
+ fieldType: "enum",
658
+ filterable: true,
659
+ enumValues: ["low", "medium", "high", "urgent"],
660
+ // Equality, NOT range: the values are an ordinal vocabulary but they sort
661
+ // LEXICALLY (high < low < medium < urgent), not by severity — a range
662
+ // index here would be permanently wrong. Filter by exact priority instead.
663
+ renderHints: { label: "Priority", widget: "select", order: 4, section: "Tracking" }
664
+ },
665
+ {
666
+ fieldId: "assignee",
667
+ fieldType: "string",
668
+ filterable: true,
669
+ description: "userId or display name.",
670
+ renderHints: { label: "Assignee", widget: "text", order: 5, section: "Tracking" }
671
+ },
672
+ {
673
+ fieldId: "project",
674
+ fieldType: "string",
675
+ filterable: true,
676
+ description: "Grouping key for cross-session continuity.",
677
+ renderHints: { label: "Project", widget: "text", order: 6, section: "Tracking" }
678
+ },
679
+ {
680
+ fieldId: "dueDate",
681
+ fieldType: "date",
682
+ description: "ISO-8601. Range-queryable (see lookupFields) \u2014 list tasks due in a window.",
683
+ renderHints: { label: "Due date", widget: "date", order: 7, section: "Tracking" }
684
+ },
685
+ {
686
+ fieldId: "tags",
687
+ fieldType: "array",
688
+ filterable: true,
689
+ renderHints: { label: "Tags", order: 8, section: "Tracking" }
690
+ }
691
+ // externalId is the record's FIRST-CLASS identifier (the dedup/lookup key + the
692
+ // value a `reference` resolves against) — not a payload field; the loader sends
693
+ // it top-level on the RecordRequest.
694
+ ],
695
+ // Two lookup shapes, chosen deliberately because the equality-vs-range choice
696
+ // per field is MIGRATION-LOCKED once the schema is live (you cannot flip a
697
+ // field slot↔range later, even by removing and re-adding it):
698
+ // • `project` — EQUALITY (a grouping key, not ordered): enumerate one
699
+ // project's tasks directly, no search. Each equality lookup uses 1 of the
700
+ // schema's 7 fast index slots.
701
+ // • `dueDate` — RANGE: ordered `from`/`to`/`prefix` queries ("tasks due this
702
+ // week", "due in 2026-06"). Range lookups use a relationship row, not a
703
+ // slot, and are billed at the range rate. ISO-8601 sorts chronologically,
704
+ // so the order is correct.
705
+ // `externalId` has a built-in first-class finder and must NOT be redeclared here.
706
+ lookupFields: ["project", { fieldName: "dueDate", rangeEnabled: true }]
707
+ }
708
+ ],
709
+ // Least-privilege profile — MUST pass the CLI scope gate.
710
+ // r/c/u + search + schema discovery. NOT records:d (least privilege).
711
+ accessProfile: {
712
+ allowedActions: ["records:r", "records:c", "records:u", "search:r", "schemas:r"]
713
+ // dataScope omitted → tenant-level shared tracker. Bind to an orgId
714
+ // for per-org isolation.
715
+ },
716
+ servicePrincipal: {
717
+ externalId: "task-management",
718
+ displayName: "Task Management"
719
+ },
720
+ seed: [
721
+ {
722
+ typeName: "task",
723
+ externalId: "seed-welcome",
724
+ fields: {
725
+ title: "Welcome to your Vectros task tracker",
726
+ description: "Created by the bootstrap loader. Ask your agent to add tasks.",
727
+ status: "todo",
728
+ priority: "low",
729
+ project: "getting-started",
730
+ dueDate: "2026-06-30"
731
+ }
732
+ }
733
+ ]
734
+ };
735
+ var task_management_default = taskManagement;
736
+
737
+ // src/blueprints/coding-agent-memory.ts
738
+ var codingAgentMemory = {
739
+ name: "coding-agent-memory",
740
+ version: "1.0.0",
741
+ description: "Persistent project memory for a coding agent \u2014 decisions, conventions, and gotchas that survive across sessions.",
742
+ contextId: "coding-memory",
743
+ contextName: "Coding Agent \u2014 Project Memory",
744
+ schemas: [
745
+ {
746
+ // A durable architectural/product decision: what was decided, and WHY.
747
+ // The rationale is the high-value, RAG-able body — "why did we do X?"
748
+ // is the question a cold-context agent most needs answered.
749
+ typeName: "decision",
750
+ displayName: "Decision",
751
+ indexMode: "HYBRID",
752
+ // keyword on titles + semantic on statement/rationale
753
+ fields: [
754
+ {
755
+ fieldId: "title",
756
+ fieldType: "string",
757
+ required: true,
758
+ searchable: true,
759
+ validation: { minLength: 1, maxLength: 200 },
760
+ renderHints: { label: "Title", widget: "text", order: 1, displayField: true }
761
+ },
762
+ {
763
+ fieldId: "statement",
764
+ fieldType: "string",
765
+ searchable: true,
766
+ description: "What was decided, in one or two sentences.",
767
+ renderHints: { label: "Statement", widget: "textarea", order: 2 }
768
+ },
769
+ {
770
+ fieldId: "rationale",
771
+ fieldType: "string",
772
+ searchable: true,
773
+ description: "Why \u2014 the trade-offs and the context. The most-recalled field.",
774
+ renderHints: { label: "Rationale", widget: "textarea", order: 3 }
775
+ },
776
+ {
777
+ fieldId: "status",
778
+ fieldType: "enum",
779
+ filterable: true,
780
+ enumValues: ["proposed", "active", "superseded"],
781
+ renderHints: { label: "Status", widget: "select", order: 4 }
782
+ },
783
+ {
784
+ fieldId: "area",
785
+ fieldType: "string",
786
+ filterable: true,
787
+ description: 'Subsystem / module the decision applies to (e.g. "auth", "search").',
788
+ renderHints: { label: "Area", widget: "text", order: 5 }
789
+ },
790
+ {
791
+ fieldId: "decidedOn",
792
+ fieldType: "date",
793
+ description: 'ISO-8601 date. Range-queryable \u2014 "decisions made this quarter".',
794
+ renderHints: { label: "Decided on", widget: "date", order: 6 }
795
+ }
796
+ // externalId is the record's FIRST-CLASS identifier (the dedup/upsert key + the
797
+ // value a `reference` resolves against) — it is NOT a payload field, so it is not
798
+ // declared here; the loader sends it top-level on the RecordRequest.
799
+ ],
800
+ // `externalId` is a first-class identifier with its own finder (exact
801
+ // get/upsert) — look it up directly, never redeclare it as a schema lookup.
802
+ // The choice below is MIGRATION-LOCKED once the schema is live:
803
+ // • `area`/`status` — EQUALITY (categorical): "list active decisions",
804
+ // "all decisions in the auth area". 2 of the 7 fast index slots.
805
+ // • `decidedOn` — RANGE: ordered `from`/`to`/`prefix` over the date.
806
+ lookupFields: ["area", "status", { fieldName: "decidedOn", rangeEnabled: true }]
807
+ },
808
+ {
809
+ // A coding convention the team follows — the agent reads these before it
810
+ // writes code so it matches the surrounding style instead of inventing one.
811
+ typeName: "convention",
812
+ displayName: "Convention",
813
+ indexMode: "HYBRID",
814
+ fields: [
815
+ {
816
+ fieldId: "name",
817
+ fieldType: "string",
818
+ required: true,
819
+ searchable: true,
820
+ validation: { minLength: 1, maxLength: 200 },
821
+ renderHints: { label: "Name", widget: "text", order: 1, displayField: true }
822
+ },
823
+ {
824
+ fieldId: "rule",
825
+ fieldType: "string",
826
+ searchable: true,
827
+ description: "The convention itself, stated as an imperative.",
828
+ renderHints: { label: "Rule", widget: "textarea", order: 2 }
829
+ },
830
+ {
831
+ fieldId: "area",
832
+ fieldType: "string",
833
+ filterable: true,
834
+ renderHints: { label: "Area", widget: "text", order: 3 }
835
+ },
836
+ {
837
+ // Parallels `decision.status` so a convention can be RETIRED rather than
838
+ // deleted (the blueprint omits records:d — memory is superseded, not lost).
839
+ fieldId: "status",
840
+ fieldType: "enum",
841
+ filterable: true,
842
+ enumValues: ["active", "retired"],
843
+ renderHints: { label: "Status", widget: "select", order: 4 }
844
+ },
845
+ {
846
+ // A typed REFERENCE to the decision that established this convention — a
847
+ // foreign-key link between record types (the knowledge-graph edge). The
848
+ // platform requires both the target type AND its surface; the value is the
849
+ // target decision's externalId (the default `targetField`). Provenance:
850
+ // "why does this convention exist? → open the decision behind it." Not
851
+ // searchable (a foreign-key id is search noise); declared as an equality
852
+ // lookup below so you can also enumerate "conventions from decision X".
853
+ fieldId: "establishedByDecision",
854
+ fieldType: "reference",
855
+ targetTypeName: "decision",
856
+ targetSurface: "record",
857
+ targetField: "externalId",
858
+ cardinality: "one",
859
+ renderHints: { label: "Established by (decision)", order: 5 }
860
+ },
861
+ {
862
+ fieldId: "adoptedOn",
863
+ fieldType: "date",
864
+ description: "ISO-8601 \u2014 when this convention was adopted. Range-queryable.",
865
+ renderHints: { label: "Adopted on", widget: "date", order: 6 }
866
+ }
867
+ // externalId: first-class identifier, not a payload field (see `decision`).
868
+ ],
869
+ // externalId has its own first-class finder for exact get — look it up
870
+ // directly, not as a schema lookup. Locked equality-vs-range choices:
871
+ // • `area`/`status` — EQUALITY: "active conventions for auth".
872
+ // • `establishedByDecision` — EQUALITY on the reference value: enumerate
873
+ // "conventions established by decision X" (a forward link query; the
874
+ // platform has no reverse-reference index on this surface).
875
+ // • `adoptedOn` — RANGE over the adoption date.
876
+ // 3 equality lookups (of 7 fast slots) + 1 range row.
877
+ lookupFields: ["area", "status", "establishedByDecision", { fieldName: "adoptedOn", rangeEnabled: true }]
878
+ },
879
+ {
880
+ // A gotcha / sharp edge: a symptom, its cause, and the fix. Saves the
881
+ // agent (and the human) from re-discovering the same trap.
882
+ typeName: "gotcha",
883
+ displayName: "Gotcha",
884
+ indexMode: "HYBRID",
885
+ fields: [
886
+ {
887
+ fieldId: "symptom",
888
+ fieldType: "string",
889
+ required: true,
890
+ searchable: true,
891
+ validation: { minLength: 1, maxLength: 500 },
892
+ renderHints: { label: "Symptom", widget: "textarea", order: 1, displayField: true }
893
+ },
894
+ {
895
+ fieldId: "cause",
896
+ fieldType: "string",
897
+ searchable: true,
898
+ renderHints: { label: "Cause", widget: "textarea", order: 2 }
899
+ },
900
+ {
901
+ fieldId: "fix",
902
+ fieldType: "string",
903
+ searchable: true,
904
+ renderHints: { label: "Fix", widget: "textarea", order: 3 }
905
+ },
906
+ {
907
+ fieldId: "area",
908
+ fieldType: "string",
909
+ filterable: true,
910
+ renderHints: { label: "Area", widget: "text", order: 4 }
911
+ },
912
+ {
913
+ // A gotcha can be RETIRED once the underlying trap is fixed for good —
914
+ // superseded, not deleted (the blueprint omits records:d).
915
+ fieldId: "status",
916
+ fieldType: "enum",
917
+ filterable: true,
918
+ enumValues: ["active", "retired"],
919
+ renderHints: { label: "Status", widget: "select", order: 5 }
920
+ },
921
+ {
922
+ fieldId: "discoveredOn",
923
+ fieldType: "date",
924
+ description: "ISO-8601 \u2014 when this gotcha was first hit. Range-queryable.",
925
+ renderHints: { label: "Discovered on", widget: "date", order: 6 }
926
+ }
927
+ // externalId: first-class identifier, not a payload field (see `decision`).
928
+ ],
929
+ // externalId has its own first-class finder for exact get — look it up
930
+ // directly, not as a schema lookup. `area`/`status` EQUALITY ("active
931
+ // gotchas in search"); `discoveredOn` RANGE over the discovery date.
932
+ lookupFields: ["area", "status", { fieldName: "discoveredOn", rangeEnabled: true }]
933
+ }
934
+ ],
935
+ // Least-privilege profile — MUST pass the CLI scope gate.
936
+ // r/c/u + search + schema discovery + inference:r (grounded "why did we do X?"
937
+ // recall over the captured rationale). NOT records:d: memory is superseded,
938
+ // not deleted, so the project's decision history stays auditable.
939
+ accessProfile: {
940
+ allowedActions: ["records:r", "records:c", "records:u", "search:r", "schemas:r", "inference:r"]
941
+ },
942
+ servicePrincipal: {
943
+ externalId: "coding-agent-memory",
944
+ displayName: "Coding Agent \u2014 Project Memory"
945
+ },
946
+ seed: [
947
+ {
948
+ // Seeded FIRST so the convention below resolves its reference to it — the
949
+ // loader creates seeds in array order, and a reference target must exist when
950
+ // the referencing record is written.
951
+ typeName: "decision",
952
+ externalId: "seed-use-vectros-for-memory",
953
+ fields: {
954
+ title: "Use Vectros as the coding agent\u2019s project memory",
955
+ statement: "Persist decisions, conventions, and gotchas as Vectros records so they survive across agent sessions.",
956
+ rationale: "A coding agent loses context every cold start. A governed, searchable memory lets it recall prior decisions by meaning instead of re-asking \u2014 and the version history shows how the thinking evolved.",
957
+ status: "active",
958
+ area: "getting-started",
959
+ decidedOn: "2026-06-14"
960
+ }
961
+ },
962
+ {
963
+ // Demonstrates a live typed link at bootstrap: this convention references the
964
+ // decision above by its externalId, so "open the decision behind this rule"
965
+ // works the moment the blueprint is applied.
966
+ typeName: "convention",
967
+ externalId: "seed-record-the-why",
968
+ fields: {
969
+ name: "Record the why, not just the what",
970
+ rule: "When you record a decision or convention, always capture the rationale \u2014 the trade-offs a future session cannot re-derive from the code.",
971
+ area: "getting-started",
972
+ status: "active",
973
+ establishedByDecision: "seed-use-vectros-for-memory",
974
+ adoptedOn: "2026-06-14"
975
+ }
976
+ }
977
+ ]
978
+ };
979
+ var coding_agent_memory_default = codingAgentMemory;
980
+
981
+ // src/blueprints/second-brain.ts
982
+ var secondBrain = {
983
+ name: "second-brain",
984
+ version: "1.0.0",
985
+ description: "A personal knowledge base \u2014 capture notes, ideas, and links, then ask them anything.",
986
+ contextId: "second-brain",
987
+ contextName: "Second Brain",
988
+ schemas: [
989
+ {
990
+ typeName: "note",
991
+ displayName: "Note",
992
+ indexMode: "HYBRID",
993
+ // keyword on title + semantic on body — recall by meaning
994
+ fields: [
995
+ {
996
+ fieldId: "title",
997
+ fieldType: "string",
998
+ required: true,
999
+ searchable: true,
1000
+ validation: { minLength: 1, maxLength: 200 },
1001
+ renderHints: { label: "Title", widget: "text", order: 1, section: "Note", displayField: true }
1002
+ },
1003
+ {
1004
+ fieldId: "body",
1005
+ fieldType: "string",
1006
+ searchable: true,
1007
+ description: "The note itself \u2014 the RAG-able body you ask questions against.",
1008
+ validation: { maxLength: 2e4 },
1009
+ renderHints: { label: "Body", widget: "textarea", order: 2, section: "Note" }
1010
+ },
1011
+ {
1012
+ fieldId: "tags",
1013
+ fieldType: "array",
1014
+ filterable: true,
1015
+ description: 'Freeform string labels for filtering (e.g. "idea", "reading", "work").',
1016
+ renderHints: { label: "Tags", order: 3, section: "Organize" }
1017
+ },
1018
+ {
1019
+ // An enum (not freeform) so enumeration-by-source is reliable — a
1020
+ // `record_query` lookup on `source` only works if values are consistent.
1021
+ // `other` keeps it from being a straitjacket for a personal brain-dump.
1022
+ fieldId: "source",
1023
+ fieldType: "enum",
1024
+ filterable: true,
1025
+ enumValues: ["thought", "web", "meeting", "book", "article", "other"],
1026
+ description: "Where it came from.",
1027
+ renderHints: { label: "Source", widget: "select", order: 4, section: "Organize" }
1028
+ },
1029
+ {
1030
+ fieldId: "status",
1031
+ fieldType: "enum",
1032
+ filterable: true,
1033
+ enumValues: ["active", "archived"],
1034
+ description: "Archive instead of delete, so nothing is ever lost.",
1035
+ renderHints: { label: "Status", widget: "select", order: 5, section: "Organize" }
1036
+ },
1037
+ {
1038
+ fieldId: "capturedAt",
1039
+ fieldType: "date",
1040
+ description: 'ISO-8601 capture date. Range-queryable \u2014 "notes from last week".',
1041
+ renderHints: { label: "Captured", widget: "date", order: 6, section: "Organize" }
1042
+ }
1043
+ // externalId is the record's FIRST-CLASS identifier (the dedup/upsert key + the
1044
+ // value a `reference` resolves against) — not a payload field; the loader sends
1045
+ // it top-level on the RecordRequest.
1046
+ ],
1047
+ // Lookup shapes are MIGRATION-LOCKED once the schema is live (a field cannot
1048
+ // flip equality↔range later), so each is chosen on purpose:
1049
+ // • `source` — EQUALITY, sorted by `lastUpdated`: enumerate notes from one
1050
+ // source, most-recently-touched first. `lastUpdated` is always present, so
1051
+ // the sort never drops a note (sorting an equality lookup by an OPTIONAL
1052
+ // field would silently exclude rows that lack it).
1053
+ // • `status` — EQUALITY: "show archived notes".
1054
+ // • `capturedAt` — RANGE: ordered `from`/`to`/`prefix` ("captured in 2026-06").
1055
+ // `externalId` has a built-in first-class finder — look it up directly, never
1056
+ // redeclare it here. Equality lookups use the 7 fast index slots (2 used here);
1057
+ // range lookups use a relationship row, billed at the range rate.
1058
+ lookupFields: [
1059
+ { fieldName: "source", sortBy: "lastUpdated" },
1060
+ "status",
1061
+ { fieldName: "capturedAt", rangeEnabled: true }
1062
+ ]
1063
+ }
1064
+ ],
1065
+ // Least-privilege profile — r/c/u + search + schema discovery + inference:r
1066
+ // (so the agent can `rag_ask` a grounded, cited question over your notes). No
1067
+ // records:d: notes are archived (status → 'archived'), never deleted.
1068
+ accessProfile: {
1069
+ allowedActions: ["records:r", "records:c", "records:u", "search:r", "schemas:r", "inference:r"]
1070
+ },
1071
+ servicePrincipal: {
1072
+ externalId: "second-brain",
1073
+ displayName: "Second Brain"
1074
+ },
1075
+ seed: [
1076
+ {
1077
+ typeName: "note",
1078
+ externalId: "seed-welcome",
1079
+ fields: {
1080
+ title: "Welcome to your Second Brain",
1081
+ body: 'Capture anything here \u2014 ideas, links, meeting notes \u2014 then ask your agent things like "what did I note about onboarding?" and it recalls by meaning, not just keywords.',
1082
+ tags: ["getting-started"],
1083
+ source: "thought",
1084
+ status: "active",
1085
+ capturedAt: "2026-06-14"
1086
+ }
1087
+ }
1088
+ ]
1089
+ };
1090
+ var second_brain_default = secondBrain;
1091
+
1092
+ // src/blueprints/clinical-intake.ts
1093
+ var clinicalIntake = {
1094
+ name: "clinical-intake",
1095
+ version: "1.0.0",
1096
+ description: "Behavioral-health intake with PHI fields \u2014 demonstrates redact-at-write, audit history, blind-index lookup, and search exclusion. Synthetic data only.",
1097
+ contextId: "clinical-intake",
1098
+ contextName: "Clinical Intake",
1099
+ schemas: [
1100
+ {
1101
+ typeName: "intake",
1102
+ displayName: "Intake",
1103
+ indexMode: "HYBRID",
1104
+ capabilities: { auditHistory: true },
1105
+ // self-documenting compliance posture
1106
+ fields: [
1107
+ // --- Non-sensitive, searchable/filterable working fields ---
1108
+ {
1109
+ fieldId: "caseId",
1110
+ fieldType: "string",
1111
+ required: true,
1112
+ description: "Caller-stable intake id; the dedup/lookup key.",
1113
+ validation: { minLength: 1, maxLength: 64 },
1114
+ renderHints: { label: "Case ID", widget: "text", order: 1, section: "Intake", displayField: true }
1115
+ },
1116
+ {
1117
+ fieldId: "presentingConcern",
1118
+ fieldType: "string",
1119
+ searchable: true,
1120
+ description: "Non-PHI summary of the presenting concern \u2014 safe to index + search.",
1121
+ validation: { maxLength: 2e3 },
1122
+ renderHints: { label: "Presenting concern", widget: "textarea", order: 2, section: "Intake" }
1123
+ },
1124
+ {
1125
+ fieldId: "program",
1126
+ fieldType: "string",
1127
+ filterable: true,
1128
+ description: "Program / service line the intake is routed to.",
1129
+ renderHints: { label: "Program", widget: "text", order: 3, section: "Intake" }
1130
+ },
1131
+ {
1132
+ fieldId: "status",
1133
+ fieldType: "enum",
1134
+ filterable: true,
1135
+ enumValues: ["new", "in_review", "scheduled", "closed"],
1136
+ renderHints: { label: "Status", widget: "select", order: 4, section: "Intake" }
1137
+ },
1138
+ {
1139
+ fieldId: "submittedAt",
1140
+ fieldType: "date",
1141
+ description: 'ISO-8601. Range-queryable \u2014 a coordinator can pull "intakes submitted this week".',
1142
+ renderHints: { label: "Submitted", widget: "date", order: 5, section: "Intake" }
1143
+ },
1144
+ // --- Sensitive (PHI) fields: redacted-at-write, search-excluded, masked-on-read ---
1145
+ {
1146
+ fieldId: "clientName",
1147
+ fieldType: "string",
1148
+ sensitive: true,
1149
+ description: "PHI. Blind-indexed for EXACT lookup (a lookup field below); never in the search index or audit log.",
1150
+ renderHints: { label: "Client name", widget: "text", order: 10, section: "Client (PHI)" }
1151
+ },
1152
+ {
1153
+ fieldId: "dateOfBirth",
1154
+ fieldType: "string",
1155
+ sensitive: true,
1156
+ description: "PHI. ISO-8601. Redacted from audit history at write time.",
1157
+ renderHints: { label: "Date of birth", widget: "date", order: 11, section: "Client (PHI)" }
1158
+ },
1159
+ {
1160
+ fieldId: "ssn",
1161
+ fieldType: "string",
1162
+ sensitive: true,
1163
+ description: "PHI. Destroyed before the audit snapshot is written \u2014 unrecoverable.",
1164
+ validation: { pattern: "^\\d{3}-\\d{2}-\\d{4}$" },
1165
+ renderHints: { label: "SSN", widget: "text", order: 12, section: "Client (PHI)" }
1166
+ },
1167
+ {
1168
+ fieldId: "clinicalNote",
1169
+ fieldType: "string",
1170
+ sensitive: true,
1171
+ description: "PHI free-text. Excluded from search so a note phrase never leaks via results.",
1172
+ renderHints: { label: "Clinical note", widget: "textarea", order: 13, section: "Client (PHI)" }
1173
+ }
1174
+ ],
1175
+ // Lookup fields back access patterns without a search. The equality-vs-range
1176
+ // (and sensitive-blind-index) choice per field is MIGRATION-LOCKED once the
1177
+ // schema is live — chosen deliberately:
1178
+ // • `caseId` (unique) — EQUALITY: exact get by the stable intake id.
1179
+ // • `program`/`status` — EQUALITY (categorical): a coordinator ENUMERATES a
1180
+ // worklist ("all outpatient-counseling intakes", "all new intakes").
1181
+ // • `clientName` — a SENSITIVE equality lookup: the value is HMAC'd into a
1182
+ // per-tenant BLIND INDEX, so "find the intake for this exact client name"
1183
+ // works WITHOUT the name ever being stored in the clear or entering search.
1184
+ // A sensitive lookup is equality-only — a blind hash is not orderable, so
1185
+ // it can never be range-enabled.
1186
+ // • `submittedAt` — RANGE: ordered `from`/`to`/`prefix` for a date worklist.
1187
+ // 4 equality lookups (of 7 fast slots) + 1 range row. The OTHER PHI fields
1188
+ // (dateOfBirth/ssn/clinicalNote) are deliberately NOT lookups here.
1189
+ lookupFields: [
1190
+ { fieldName: "caseId", unique: true },
1191
+ "program",
1192
+ "status",
1193
+ "clientName",
1194
+ { fieldName: "submittedAt", rangeEnabled: true }
1195
+ ]
1196
+ }
1197
+ ],
1198
+ // Least-privilege, data-plane only. No records:d (retain intakes), NO `s`
1199
+ // reveal qualifier (the demo key literally cannot un-redact the PHI fields),
1200
+ // and NO inference:r (we never point a RAG capability at a PHI corpus here).
1201
+ accessProfile: {
1202
+ allowedActions: ["records:r", "records:c", "records:u", "search:r", "schemas:r"]
1203
+ },
1204
+ servicePrincipal: {
1205
+ externalId: "clinical-intake",
1206
+ displayName: "Clinical Intake"
1207
+ },
1208
+ seed: [
1209
+ {
1210
+ typeName: "intake",
1211
+ externalId: "seed-synthetic-intake",
1212
+ fields: {
1213
+ caseId: "seed-synthetic-intake",
1214
+ presentingConcern: "Sleep difficulty and low mood over the past month; seeking counseling.",
1215
+ program: "outpatient-counseling",
1216
+ status: "new",
1217
+ submittedAt: "2026-06-14",
1218
+ // SYNTHETIC PHI — illustrative only, not a real person.
1219
+ clientName: "Jordan Sample",
1220
+ dateOfBirth: "1990-01-01",
1221
+ ssn: "000-00-0000",
1222
+ clinicalNote: "Synthetic note for demonstration. Reports difficulty sleeping; no acute safety concerns noted."
1223
+ }
1224
+ }
1225
+ ]
1226
+ };
1227
+ var clinical_intake_default = clinicalIntake;
1228
+
1229
+ // src/index.ts
1230
+ var BUNDLED_BLUEPRINTS = [
1231
+ task_management_default,
1232
+ coding_agent_memory_default,
1233
+ second_brain_default,
1234
+ clinical_intake_default
1235
+ ];
1236
+ var BLUEPRINT_NAMES = BUNDLED_BLUEPRINTS.map((b) => b.name);
1237
+ function getBlueprint(name) {
1238
+ return BUNDLED_BLUEPRINTS.find((b) => b.name === name);
1239
+ }
1240
+ export {
1241
+ BLUEPRINT_NAMES,
1242
+ BUNDLED_BLUEPRINTS,
1243
+ BlueprintIdentityError,
1244
+ BlueprintInputError,
1245
+ BlueprintSchema,
1246
+ BlueprintValidationError,
1247
+ InputsDeclSchema,
1248
+ collectIdentityReferences,
1249
+ contextNameOf,
1250
+ deriveSuffix,
1251
+ getBlueprint,
1252
+ parseBlueprint,
1253
+ parseBlueprintJson,
1254
+ resolveBlueprintIdentities,
1255
+ resolveBlueprintInputs
1256
+ };
1257
+ //# sourceMappingURL=index.mjs.map