@ggui-ai/mcp-server-handlers 0.8.0 → 0.10.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 (94) hide show
  1. package/dist/blueprints/describe-data-contract-format.d.ts.map +1 -1
  2. package/dist/blueprints/describe-data-contract-format.js +48 -6
  3. package/dist/credits/get-credit-balance.d.ts +11 -6
  4. package/dist/credits/get-credit-balance.d.ts.map +1 -1
  5. package/dist/credits/get-credit-balance.js +17 -8
  6. package/dist/credits/list-credit-transactions.d.ts +4 -2
  7. package/dist/credits/list-credit-transactions.d.ts.map +1 -1
  8. package/dist/credits/list-credit-transactions.js +9 -4
  9. package/dist/ops-blueprint/app-access.d.ts +37 -0
  10. package/dist/ops-blueprint/app-access.d.ts.map +1 -0
  11. package/dist/ops-blueprint/app-access.js +45 -0
  12. package/dist/ops-blueprint/delete.d.ts +26 -5
  13. package/dist/ops-blueprint/delete.d.ts.map +1 -1
  14. package/dist/ops-blueprint/delete.js +18 -10
  15. package/dist/ops-blueprint/generate.d.ts +17 -5
  16. package/dist/ops-blueprint/generate.d.ts.map +1 -1
  17. package/dist/ops-blueprint/generate.js +24 -13
  18. package/dist/ops-blueprint/index.d.ts +12 -1
  19. package/dist/ops-blueprint/index.d.ts.map +1 -1
  20. package/dist/ops-blueprint/index.js +12 -1
  21. package/dist/ops-blueprint/list.d.ts +11 -0
  22. package/dist/ops-blueprint/list.d.ts.map +1 -1
  23. package/dist/ops-blueprint/list.js +10 -6
  24. package/dist/ops-blueprint/register.d.ts +11 -0
  25. package/dist/ops-blueprint/register.d.ts.map +1 -1
  26. package/dist/ops-blueprint/register.js +15 -11
  27. package/dist/ops-blueprint/update.d.ts +25 -4
  28. package/dist/ops-blueprint/update.d.ts.map +1 -1
  29. package/dist/ops-blueprint/update.js +16 -9
  30. package/dist/renders/amend.d.ts +57 -0
  31. package/dist/renders/amend.d.ts.map +1 -0
  32. package/dist/renders/amend.js +64 -0
  33. package/dist/renders/apply-ggui-session-patch.d.ts +4 -0
  34. package/dist/renders/apply-ggui-session-patch.d.ts.map +1 -1
  35. package/dist/renders/apply-ggui-session-patch.js +1 -1
  36. package/dist/renders/assert-props-contract.d.ts +5 -4
  37. package/dist/renders/assert-props-contract.d.ts.map +1 -1
  38. package/dist/renders/assert-props-contract.js +6 -5
  39. package/dist/renders/blueprint-durability.d.ts +10 -1
  40. package/dist/renders/blueprint-durability.d.ts.map +1 -1
  41. package/dist/renders/blueprint-durability.js +39 -0
  42. package/dist/renders/blueprint-registry.d.ts +33 -0
  43. package/dist/renders/blueprint-registry.d.ts.map +1 -1
  44. package/dist/renders/blueprint-registry.js +78 -42
  45. package/dist/renders/consume.js +1 -1
  46. package/dist/renders/decide-handshake.d.ts +20 -4
  47. package/dist/renders/decide-handshake.d.ts.map +1 -1
  48. package/dist/renders/decide-handshake.js +64 -33
  49. package/dist/renders/declare-tool-catalog.js +1 -1
  50. package/dist/renders/generation-cache.d.ts +11 -0
  51. package/dist/renders/generation-cache.d.ts.map +1 -1
  52. package/dist/renders/get-render-source.d.ts +48 -0
  53. package/dist/renders/get-render-source.d.ts.map +1 -0
  54. package/dist/renders/get-render-source.js +75 -0
  55. package/dist/renders/handshake-fallbacks.d.ts +27 -0
  56. package/dist/renders/handshake-fallbacks.d.ts.map +1 -0
  57. package/dist/renders/handshake-fallbacks.js +110 -0
  58. package/dist/renders/handshake.d.ts +76 -17
  59. package/dist/renders/handshake.d.ts.map +1 -1
  60. package/dist/renders/handshake.js +270 -81
  61. package/dist/renders/index.d.ts +7 -1
  62. package/dist/renders/index.d.ts.map +1 -1
  63. package/dist/renders/index.js +7 -1
  64. package/dist/renders/props-mutation-core.d.ts +60 -0
  65. package/dist/renders/props-mutation-core.d.ts.map +1 -0
  66. package/dist/renders/props-mutation-core.js +366 -0
  67. package/dist/renders/render-source-envelope.d.ts +61 -0
  68. package/dist/renders/render-source-envelope.d.ts.map +1 -0
  69. package/dist/renders/render-source-envelope.js +27 -0
  70. package/dist/renders/render-telemetry.d.ts +73 -0
  71. package/dist/renders/render-telemetry.d.ts.map +1 -0
  72. package/dist/renders/render-telemetry.js +80 -0
  73. package/dist/renders/render.d.ts +45 -10
  74. package/dist/renders/render.d.ts.map +1 -1
  75. package/dist/renders/render.js +280 -77
  76. package/dist/renders/runtime-pull.d.ts +122 -0
  77. package/dist/renders/runtime-pull.d.ts.map +1 -0
  78. package/dist/renders/runtime-pull.js +178 -0
  79. package/dist/renders/runtime-telemetry.d.ts +60 -0
  80. package/dist/renders/runtime-telemetry.d.ts.map +1 -0
  81. package/dist/renders/runtime-telemetry.js +76 -0
  82. package/dist/renders/slice-meta-derivation.d.ts +107 -12
  83. package/dist/renders/slice-meta-derivation.d.ts.map +1 -1
  84. package/dist/renders/slice-meta-derivation.js +119 -16
  85. package/dist/renders/submit-action.d.ts +9 -0
  86. package/dist/renders/submit-action.d.ts.map +1 -1
  87. package/dist/renders/submit-action.js +70 -1
  88. package/dist/renders/update.d.ts +44 -43
  89. package/dist/renders/update.d.ts.map +1 -1
  90. package/dist/renders/update.js +98 -233
  91. package/dist/types.d.ts +30 -5
  92. package/dist/types.d.ts.map +1 -1
  93. package/dist/types.js +10 -4
  94. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"describe-data-contract-format.d.ts","sourceRoot":"","sources":["../../src/blueprints/describe-data-contract-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,QAAA,MAAM,WAAW,IAAK,CAAC;AAEvB,QAAA,MAAM,YAAY;;;;;;;CASjB,CAAC;AAEF,UAAU,gCAAgC;IACxC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;QAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC5C,CAAC,CAAC;CACJ;AA+RD,wBAAgB,uCAAuC,IAAI,aAAa,CACtE,OAAO,WAAW,EAClB,OAAO,YAAY,EACnB,gCAAgC,CACjC,CAiBA"}
1
+ {"version":3,"file":"describe-data-contract-format.d.ts","sourceRoot":"","sources":["../../src/blueprints/describe-data-contract-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,QAAA,MAAM,WAAW,IAAK,CAAC;AAEvB,QAAA,MAAM,YAAY;;;;;;;CASjB,CAAC;AAEF,UAAU,gCAAgC;IACxC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;QAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC5C,CAAC,CAAC;CACJ;AAyUD,wBAAgB,uCAAuC,IAAI,aAAa,CACtE,OAAO,WAAW,EAClB,OAAO,YAAY,EACnB,gCAAgC,CACjC,CAiBA"}
@@ -8,6 +8,7 @@
8
8
  * what Claude composes and what `ggui_protocol_validate_blueprint` expects.
9
9
  */
10
10
  import { z } from 'zod';
11
+ import { RETIRED_CONTRACT_FIELDS } from '@ggui-ai/protocol';
11
12
  const inputSchema = {};
12
13
  const outputSchema = {
13
14
  format: z.literal('DataContract'),
@@ -17,6 +18,12 @@ const outputSchema = {
17
18
  contract: z.record(z.string(), z.unknown()),
18
19
  })),
19
20
  };
21
+ /** The retired-field table, derived from the protocol's own list. */
22
+ function retiredFieldsBlock() {
23
+ return Object.entries(RETIRED_CONTRACT_FIELDS)
24
+ .map(([field, replacement]) => `- \`${field}\` → \`${replacement}\``)
25
+ .join('\n');
26
+ }
20
27
  const DOCUMENTATION = `# DataContract format
21
28
 
22
29
  Top-level shape on every blueprint's \`contract\` field:
@@ -62,7 +69,7 @@ interface PropsSpec {
62
69
  description?: string;
63
70
  properties: Record<string, {
64
71
  schema: JsonSchema; // draft-07 subset: type, enum, items, properties
65
- required?: boolean; // default true
72
+ required?: boolean; // required at the wire gate ONLY when \`true\`; omitted ⇒ optional
66
73
  description?: string;
67
74
  default?: JsonValue;
68
75
  example?: JsonValue;
@@ -70,16 +77,19 @@ interface PropsSpec {
70
77
  }
71
78
  \`\`\`
72
79
 
73
- The boilerplate generator turns each \`properties\` entry into a typed
74
- TypeScript field on \`Props\`. \`required: false\` becomes \`field?:\`.
75
- \`schema.nullable: true\` becomes \`| null\`.
80
+ \`required\` is opt-in: the render gate (\`validateContract\` → props
81
+ validation) treats a prop as required only when the entry says
82
+ \`required: true\`; an omitted \`required\` means the render MAY leave
83
+ that prop out. Declare \`required: true\` on every prop the component
84
+ cannot paint without. (The boilerplate generator types the TypeScript
85
+ field from the same flag; \`schema.nullable: true\` becomes \`| null\`.)
76
86
 
77
87
  ## ActionSpec entry
78
88
 
79
89
  \`\`\`ts
80
90
  interface ActionEntry {
81
- /** Human label — drives button copy / accessibility name. */
82
- label?: string;
91
+ /** Human label — drives button copy / accessibility name. REQUIRED. */
92
+ label: string;
83
93
  description?: string;
84
94
  /** Optional payload schema. Action with a payload becomes useAction<T>. */
85
95
  schema?: JsonSchema;
@@ -209,6 +219,38 @@ UI-side lifecycle; the agent never invokes them. Values reach the
209
219
  agent only when threaded into a contextSpec slot or actionSpec
210
220
  payload.
211
221
 
222
+ ## Retired top-level fields (hard errors)
223
+
224
+ These names were retired by the flatten and are REJECTED by the
225
+ contract gate at handshake and render — not ignored, not migrated.
226
+ Each maps to where the same intent lives now:
227
+
228
+ ${retiredFieldsBlock()}
229
+
230
+ ## What the contract gate checks (handshake AND render, same gate)
231
+
232
+ The one deterministic gate (\`validateContract\`) runs at the handshake
233
+ (the forgiving path repairs what it can and reports the rest as
234
+ \`validationFindings\`; on the render's STRICT \`override.contract\` it
235
+ fails the call). Every rule below is enforced there:
236
+
237
+ - **Wrapper shape** — every entry under \`propsSpec.properties\` /
238
+ \`actionSpec\` / \`streamSpec\` / \`contextSpec\` is a WRAPPER with the
239
+ JSON Schema in its \`schema:\` field; wrappers reject unrecognized keys.
240
+ - **Inner schemas compile** — each \`schema:\` must be a valid draft-07
241
+ JSON Schema under strict meta-validation (\`type\` present, no unknown
242
+ keywords).
243
+ - **References resolve** — \`actionSpec[*].nextStep\` and
244
+ \`streamSpec[*].source.tool\` MUST name a key of
245
+ \`agentCapabilities.tools\` on the same contract.
246
+ - **Schema compat** — an \`actionSpec[*].schema\` must be a SUBSET of the
247
+ referenced tool's \`inputSchema\` (payloads travel UI → tool), and a
248
+ \`streamSpec[*].schema\` a SUPERSET of the referenced tool's
249
+ \`outputSchema\` (payloads travel tool → channel).
250
+ - **Reserved channels** — \`streamSpec\` names may not use the
251
+ \`_ggui:\` prefix (server-owned channels).
252
+ - **Retired fields** — the list above.
253
+
212
254
  ## defineContract helper (TypeScript only)
213
255
 
214
256
  \`\`\`ts
@@ -9,12 +9,17 @@
9
9
  * deployment backs it with a real credit-balance datastore; tests
10
10
  * inject an in-memory fake.
11
11
  *
12
- * Identity scope: the handler reads the caller's userId from
13
- * `ctx.appId`. The auth adapter resolves `appId = workspaceId ?? userId`,
14
- * which IS the userId for the end-user single-tenant default. See
15
- * `packages/mcp-server/src/auth.ts#defaultAppIdFromIdentity`. The tool
16
- * is registered on every deployment kind; callers without a credit
17
- * account (e.g. agent-builder identities) get the zero-row
12
+ * Identity scope: credit rows are keyed by USER identity, so the
13
+ * handler reads `ctx.userId` when the host's auth layer resolves a
14
+ * distinct per-user identity (multi-tenant hosts where `ctx.appId`
15
+ * names the caller's active app, not the caller), and falls back to
16
+ * `ctx.appId` for single-tenant hosts whose auth adapter folds the
17
+ * user into the app slot (`appId = workspaceId ?? userId` — see
18
+ * `packages/mcp-server/src/auth.ts#defaultAppIdFromIdentity`).
19
+ * Reading `ctx.appId` alone silently returns the zero-row fallback
20
+ * for every multi-tenant caller — an app id never keys a credit row.
21
+ * The tool is registered on every deployment kind; callers without a
22
+ * credit account (e.g. agent-builder identities) get the zero-row
18
23
  * fallback.
19
24
  */
20
25
  import { z } from 'zod';
@@ -1 +1 @@
1
- {"version":3,"file":"get-credit-balance.d.ts","sourceRoot":"","sources":["../../src/credits/get-credit-balance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,yFAAyF;IACzF,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,EAAE,mBAAmB,CAAC;CAC7C;AAED,QAAA,MAAM,WAAW,IAAqC,CAAC;AAEvD,QAAA,MAAM,YAAY;IAChB,yDAAyD;;IAEzD,0DAA0D;;IAE1D,4CAA4C;;IAE5C,+CAA+C;;CAEhD,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,oBAAoB,GACzB,aAAa,CAAC,OAAO,WAAW,EAAE,OAAO,YAAY,EAAE,sBAAsB,CAAC,CAwChF"}
1
+ {"version":3,"file":"get-credit-balance.d.ts","sourceRoot":"","sources":["../../src/credits/get-credit-balance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,yFAAyF;IACzF,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,EAAE,mBAAmB,CAAC;CAC7C;AAED,QAAA,MAAM,WAAW,IAAqC,CAAC;AAEvD,QAAA,MAAM,YAAY;IAChB,yDAAyD;;IAEzD,0DAA0D;;IAE1D,4CAA4C;;IAE5C,+CAA+C;;CAEhD,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,oBAAoB,GACzB,aAAa,CAAC,OAAO,WAAW,EAAE,OAAO,YAAY,EAAE,sBAAsB,CAAC,CA4ChF"}
@@ -9,12 +9,17 @@
9
9
  * deployment backs it with a real credit-balance datastore; tests
10
10
  * inject an in-memory fake.
11
11
  *
12
- * Identity scope: the handler reads the caller's userId from
13
- * `ctx.appId`. The auth adapter resolves `appId = workspaceId ?? userId`,
14
- * which IS the userId for the end-user single-tenant default. See
15
- * `packages/mcp-server/src/auth.ts#defaultAppIdFromIdentity`. The tool
16
- * is registered on every deployment kind; callers without a credit
17
- * account (e.g. agent-builder identities) get the zero-row
12
+ * Identity scope: credit rows are keyed by USER identity, so the
13
+ * handler reads `ctx.userId` when the host's auth layer resolves a
14
+ * distinct per-user identity (multi-tenant hosts where `ctx.appId`
15
+ * names the caller's active app, not the caller), and falls back to
16
+ * `ctx.appId` for single-tenant hosts whose auth adapter folds the
17
+ * user into the app slot (`appId = workspaceId ?? userId` — see
18
+ * `packages/mcp-server/src/auth.ts#defaultAppIdFromIdentity`).
19
+ * Reading `ctx.appId` alone silently returns the zero-row fallback
20
+ * for every multi-tenant caller — an app id never keys a credit row.
21
+ * The tool is registered on every deployment kind; callers without a
22
+ * credit account (e.g. agent-builder identities) get the zero-row
18
23
  * fallback.
19
24
  */
20
25
  import { z } from 'zod';
@@ -41,9 +46,13 @@ export function createGetCreditBalanceHandler(deps) {
41
46
  // without a credit account get the zero-row fallback below; no
42
47
  // separate "tool not registered" UX needed.
43
48
  async handler(_input, ctx) {
44
- const userId = ctx.appId;
49
+ // USER identity first — on multi-tenant hosts `ctx.appId` is the
50
+ // caller's active app, which never keys a credit row (see the
51
+ // module docstring). Single-tenant hosts leave `ctx.userId`
52
+ // unset and fold the user into the app slot.
53
+ const userId = ctx.userId ?? ctx.appId;
45
54
  if (!userId) {
46
- throw new Error('ggui_get_credit_balance: missing caller identity (ctx.appId unset)');
55
+ throw new Error('ggui_get_credit_balance: missing caller identity (ctx.userId and ctx.appId both unset)');
47
56
  }
48
57
  const balance = await deps.creditBalance.getBalance(userId);
49
58
  if (!balance) {
@@ -10,8 +10,10 @@
10
10
  * `(userId, transactionId)` key, newest-first sort); tests inject an
11
11
  * in-memory fake.
12
12
  *
13
- * Identity scope mirrors get-credit-balance: `ctx.appId` IS the
14
- * userId for end-user callers.
13
+ * Identity scope mirrors get-credit-balance: `ctx.userId` when the
14
+ * host resolves a distinct per-user identity, else `ctx.appId` (the
15
+ * single-tenant fold) — see that module's docstring for why reading
16
+ * `ctx.appId` alone loses every multi-tenant caller.
15
17
  */
16
18
  import { z } from 'zod';
17
19
  import type { SharedHandler } from '../types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"list-credit-transactions.d.ts","sourceRoot":"","sources":["../../src/credits/list-credit-transactions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,IAAI,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,oCAAoC;QACpC,KAAK,EAAE,MAAM,CAAC;QACd,4EAA4E;QAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC;QACV,YAAY,EAAE,qBAAqB,EAAE,CAAC;QACtC,6CAA6C;QAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,eAAe,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,kBAAkB,EAAE,uBAAuB,CAAC;CACtD;AAED,QAAA,MAAM,WAAW;IACf,2BAA2B;;IAE3B,kEAAkE;;CAEnE,CAAC;AAEF,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;IAYhB,oFAAoF;;CAErF,CAAC;AAEF,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,YAAY,EAAE,qBAAqB,EAAE,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,0BAA0B,GAC/B,aAAa,CAAC,OAAO,WAAW,EAAE,OAAO,YAAY,EAAE,4BAA4B,CAAC,CAkCtF"}
1
+ {"version":3,"file":"list-credit-transactions.d.ts","sourceRoot":"","sources":["../../src/credits/list-credit-transactions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,IAAI,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,oCAAoC;QACpC,KAAK,EAAE,MAAM,CAAC;QACd,4EAA4E;QAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC;QACV,YAAY,EAAE,qBAAqB,EAAE,CAAC;QACtC,6CAA6C;QAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,eAAe,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,kBAAkB,EAAE,uBAAuB,CAAC;CACtD;AAED,QAAA,MAAM,WAAW;IACf,2BAA2B;;IAE3B,kEAAkE;;CAEnE,CAAC;AAEF,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;IAYhB,oFAAoF;;CAErF,CAAC;AAEF,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,YAAY,EAAE,qBAAqB,EAAE,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,0BAA0B,GAC/B,aAAa,CAAC,OAAO,WAAW,EAAE,OAAO,YAAY,EAAE,4BAA4B,CAAC,CAqCtF"}
@@ -10,8 +10,10 @@
10
10
  * `(userId, transactionId)` key, newest-first sort); tests inject an
11
11
  * in-memory fake.
12
12
  *
13
- * Identity scope mirrors get-credit-balance: `ctx.appId` IS the
14
- * userId for end-user callers.
13
+ * Identity scope mirrors get-credit-balance: `ctx.userId` when the
14
+ * host resolves a distinct per-user identity, else `ctx.appId` (the
15
+ * single-tenant fold) — see that module's docstring for why reading
16
+ * `ctx.appId` alone loses every multi-tenant caller.
15
17
  */
16
18
  import { z } from 'zod';
17
19
  const inputSchema = {
@@ -44,9 +46,12 @@ export function createListCreditTransactionsHandler(deps) {
44
46
  // No `allowedFor` — same toolset on every pod kind. Callers
45
47
  // without a credit account see an empty ledger.
46
48
  async handler(rawInput, ctx) {
47
- const userId = ctx.appId;
49
+ // USER identity first — `ctx.appId` is the active app on
50
+ // multi-tenant hosts and never keys a ledger row (see
51
+ // get-credit-balance's module docstring).
52
+ const userId = ctx.userId ?? ctx.appId;
48
53
  if (!userId) {
49
- throw new Error('ggui_list_credit_transactions: missing caller identity (ctx.appId unset)');
54
+ throw new Error('ggui_list_credit_transactions: missing caller identity (ctx.userId and ctx.appId both unset)');
50
55
  }
51
56
  const parsed = z.object(inputSchema).parse(rawInput);
52
57
  const limit = parsed.limit ?? 20;
@@ -0,0 +1,37 @@
1
+ import type { HandlerContext } from "../types.js";
2
+ export type OpsBlueprintAppAccess = {
3
+ readonly allowed: true;
4
+ } | {
5
+ readonly allowed: false;
6
+ readonly reason: "not_found" | "not_owner";
7
+ };
8
+ export interface OpsBlueprintAppAuthorizer {
9
+ (ctx: HandlerContext, appId: string): Promise<OpsBlueprintAppAccess>;
10
+ }
11
+ export declare class CrossAppCurationUnavailableError extends Error {
12
+ readonly code: "cross_app_curation_unavailable";
13
+ constructor(toolName: string);
14
+ }
15
+ export declare class AppCurationDeniedError extends Error {
16
+ readonly reason: "not_found" | "not_owner";
17
+ readonly code: "app_curation_denied";
18
+ constructor(toolName: string, appId: string, reason: "not_found" | "not_owner");
19
+ }
20
+ export interface ResolveEffectiveAppIdArgs {
21
+ readonly toolName: string;
22
+ readonly inputAppId: string | undefined;
23
+ readonly ctx: HandlerContext;
24
+ readonly authorize?: OpsBlueprintAppAuthorizer;
25
+ }
26
+ /**
27
+ * One resolution rule for the whole ops-blueprint family.
28
+ *
29
+ * Seam bound → the authorizer is the single authority on app access:
30
+ * it is consulted on EVERY call (input omitted or equal included), so
31
+ * fast paths live inside the deployment's authorizer where its trust
32
+ * model is visible — never here.
33
+ *
34
+ * Seam unbound → legacy bound-only posture, fail closed on cross-app.
35
+ */
36
+ export declare function resolveEffectiveAppId(args: ResolveEffectiveAppIdArgs): Promise<string>;
37
+ //# sourceMappingURL=app-access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-access.d.ts","sourceRoot":"","sources":["../../src/ops-blueprint/app-access.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;CAAE,GAC1B;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAAA;CAAE,CAAC;AAE5E,MAAM,WAAW,yBAAyB;IACxC,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtE;AAED,qBAAa,gCAAiC,SAAQ,KAAK;IACzD,QAAQ,CAAC,IAAI,EAAG,gCAAgC,CAAU;gBAC9C,QAAQ,EAAE,MAAM;CAM7B;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAK7C,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW;IAJ5C,QAAQ,CAAC,IAAI,EAAG,qBAAqB,CAAU;gBAE7C,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACJ,MAAM,EAAE,WAAW,GAAG,WAAW;CAS7C;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,yBAAyB,CAAC;CAChD;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB5F"}
@@ -0,0 +1,45 @@
1
+ export class CrossAppCurationUnavailableError extends Error {
2
+ code = "cross_app_curation_unavailable";
3
+ constructor(toolName) {
4
+ super(`${toolName}: cross_app_curation_unavailable — this deployment does not accept an appId different from the caller's bound app. Bind an app-access authorizer to enable cross-app curation.`);
5
+ this.name = "CrossAppCurationUnavailableError";
6
+ }
7
+ }
8
+ export class AppCurationDeniedError extends Error {
9
+ reason;
10
+ code = "app_curation_denied";
11
+ constructor(toolName, appId, reason) {
12
+ super(reason === "not_found"
13
+ ? `${toolName}: app ${JSON.stringify(appId)} not found.`
14
+ : `${toolName}: app ${JSON.stringify(appId)} is not curatable by this caller — variant curation is limited to the app's operator.`);
15
+ this.reason = reason;
16
+ this.name = "AppCurationDeniedError";
17
+ }
18
+ }
19
+ /**
20
+ * One resolution rule for the whole ops-blueprint family.
21
+ *
22
+ * Seam bound → the authorizer is the single authority on app access:
23
+ * it is consulted on EVERY call (input omitted or equal included), so
24
+ * fast paths live inside the deployment's authorizer where its trust
25
+ * model is visible — never here.
26
+ *
27
+ * Seam unbound → legacy bound-only posture, fail closed on cross-app.
28
+ */
29
+ export async function resolveEffectiveAppId(args) {
30
+ const { toolName, inputAppId, ctx, authorize } = args;
31
+ const effective = inputAppId ?? (ctx.appId !== "" ? ctx.appId : undefined);
32
+ if (effective === undefined) {
33
+ throw new Error(`${toolName}: missing caller identity (appId empty) — bind an app identity or pass the appId input.`);
34
+ }
35
+ if (authorize !== undefined) {
36
+ const access = await authorize(ctx, effective);
37
+ if (!access.allowed)
38
+ throw new AppCurationDeniedError(toolName, effective, access.reason);
39
+ return effective;
40
+ }
41
+ if (inputAppId !== undefined && inputAppId !== ctx.appId) {
42
+ throw new CrossAppCurationUnavailableError(toolName);
43
+ }
44
+ return effective;
45
+ }
@@ -8,11 +8,15 @@
8
8
  * ## Tenancy
9
9
  *
10
10
  * The blueprint id is globally unique, but the delete path scopes
11
- * by `ctx.appId`: if the row exists AND its `appId` doesn't match
12
- * the caller's, the handler treats it as "not found from the
13
- * caller's perspective" and returns `{deleted: true}` — a uniform
14
- * shape that doesn't leak whether the id exists in another tenant.
15
- * The store's underlying delete is NOT invoked in that case.
11
+ * by the resolved effective appId (see {@link resolveEffectiveAppId}):
12
+ * if the row exists AND its `appId` doesn't match, the handler
13
+ * treats it as "not found from the caller's perspective" and
14
+ * returns `{deleted: true}` — a uniform shape that doesn't leak
15
+ * whether the id exists in another tenant. The store's underlying
16
+ * delete is NOT invoked in that case. This row-level uniformity is
17
+ * separate from (and downstream of) the app-level authorizer check:
18
+ * a foreign `appId` input the authorizer denies surfaces the denial
19
+ * error before the row lookup ever runs.
16
20
  *
17
21
  * ## Audience
18
22
  *
@@ -22,7 +26,9 @@ import { z } from 'zod';
22
26
  import { type OpsDeleteBlueprintOutput } from '@ggui-ai/protocol';
23
27
  import type { BlueprintStore } from '@ggui-ai/mcp-server-core';
24
28
  import type { SharedHandler } from '../types.js';
29
+ import { type OpsBlueprintAppAuthorizer } from './app-access.js';
25
30
  declare const opsInputSchema: {
31
+ appId: z.ZodOptional<z.ZodString>;
26
32
  blueprintId: z.ZodString;
27
33
  };
28
34
  declare const opsOutputSchema: {
@@ -33,6 +39,21 @@ declare const opsOutputSchema: {
33
39
  */
34
40
  export interface GguiOpsDeleteBlueprintDeps {
35
41
  readonly blueprintStore: BlueprintStore;
42
+ /**
43
+ * Optional app-access authorizer — when bound, consulted on EVERY
44
+ * resolution (see {@link resolveEffectiveAppId}) to decide whether
45
+ * the caller may curate the effective `appId`, including cross-app
46
+ * calls that supply an explicit `appId` input different from
47
+ * `ctx.appId`. Unbound: legacy bound-only posture, cross-app input
48
+ * fails closed with `CrossAppCurationUnavailableError`.
49
+ *
50
+ * This app-level check runs BEFORE the row-level tenancy lookup
51
+ * below: a foreign `appId` INPUT denied by the authorizer surfaces
52
+ * the denial error, while an authorizer-approved effective appId
53
+ * still hits the row-level uniform `{deleted: true}` posture when
54
+ * the target row belongs to a different app (no existence leak).
55
+ */
56
+ readonly authorizeAppAccess?: OpsBlueprintAppAuthorizer;
36
57
  }
37
58
  export declare function createGguiOpsDeleteBlueprintHandler(deps: GguiOpsDeleteBlueprintDeps): SharedHandler<typeof opsInputSchema, typeof opsOutputSchema, OpsDeleteBlueprintOutput>;
38
59
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../src/ops-blueprint/delete.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjE,QAAA,MAAM,cAAc;;CAAsC,CAAC;AAC3D,QAAA,MAAM,eAAe;;CAEX,CAAC;AAEX;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CACzC;AAED,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,0BAA0B,GAC/B,aAAa,CACd,OAAO,cAAc,EACrB,OAAO,eAAe,EACtB,wBAAwB,CACzB,CAqCA"}
1
+ {"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../src/ops-blueprint/delete.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAyB,KAAK,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAExF,QAAA,MAAM,cAAc;;;CAAsC,CAAC;AAC3D,QAAA,MAAM,eAAe;;CAEX,CAAC;AAEX;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,yBAAyB,CAAC;CACzD;AAED,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,0BAA0B,GAC/B,aAAa,CACd,OAAO,cAAc,EACrB,OAAO,eAAe,EACtB,wBAAwB,CACzB,CAuCA"}
@@ -8,11 +8,15 @@
8
8
  * ## Tenancy
9
9
  *
10
10
  * The blueprint id is globally unique, but the delete path scopes
11
- * by `ctx.appId`: if the row exists AND its `appId` doesn't match
12
- * the caller's, the handler treats it as "not found from the
13
- * caller's perspective" and returns `{deleted: true}` — a uniform
14
- * shape that doesn't leak whether the id exists in another tenant.
15
- * The store's underlying delete is NOT invoked in that case.
11
+ * by the resolved effective appId (see {@link resolveEffectiveAppId}):
12
+ * if the row exists AND its `appId` doesn't match, the handler
13
+ * treats it as "not found from the caller's perspective" and
14
+ * returns `{deleted: true}` — a uniform shape that doesn't leak
15
+ * whether the id exists in another tenant. The store's underlying
16
+ * delete is NOT invoked in that case. This row-level uniformity is
17
+ * separate from (and downstream of) the app-level authorizer check:
18
+ * a foreign `appId` input the authorizer denies surfaces the denial
19
+ * error before the row lookup ever runs.
16
20
  *
17
21
  * ## Audience
18
22
  *
@@ -20,6 +24,7 @@
20
24
  */
21
25
  import { z } from 'zod';
22
26
  import { opsDeleteBlueprintInputSchema, } from '@ggui-ai/protocol';
27
+ import { resolveEffectiveAppId } from './app-access.js';
23
28
  const opsInputSchema = opsDeleteBlueprintInputSchema.shape;
24
29
  const opsOutputSchema = {
25
30
  deleted: z.literal(true),
@@ -29,20 +34,23 @@ export function createGguiOpsDeleteBlueprintHandler(deps) {
29
34
  name: 'ggui_ops_delete_blueprint',
30
35
  title: 'Delete blueprint',
31
36
  audience: ['ops'],
32
- description: "Remove a blueprint row by id. Idempotent — a second delete for the same id returns `{deleted: true}` without throwing. Cross-tenant probes return the same shape (no existence leak across apps). Mirrors `BlueprintStore.delete`'s no-throw contract.",
37
+ description: "Remove a blueprint row by id. Idempotent — a second delete for the same id returns `{deleted: true}` without throwing. Cross-tenant probes return the same shape (no existence leak across apps). Mirrors `BlueprintStore.delete`'s no-throw contract. App-scoped variant curation for an app you operate — distinct from the personal saved-blueprint library (the _my_ tools).",
33
38
  inputSchema: opsInputSchema,
34
39
  outputSchema: opsOutputSchema,
35
40
  async handler(rawInput, ctx) {
36
- if (!ctx.appId) {
37
- throw new Error('ggui_ops_delete_blueprint: missing caller identity (appId empty)');
38
- }
39
41
  const parsed = opsDeleteBlueprintInputSchema.parse(rawInput);
42
+ const appId = await resolveEffectiveAppId({
43
+ toolName: 'ggui_ops_delete_blueprint',
44
+ inputAppId: parsed.appId,
45
+ ctx,
46
+ ...(deps.authorizeAppAccess ? { authorize: deps.authorizeAppAccess } : {}),
47
+ });
40
48
  const existing = await deps.blueprintStore.get(parsed.blueprintId);
41
49
  if (existing === null) {
42
50
  // Unknown id — idempotent. Return the success shape.
43
51
  return { deleted: true };
44
52
  }
45
- if (existing.appId !== ctx.appId) {
53
+ if (existing.appId !== appId) {
46
54
  // Cross-tenant probe — return the success shape WITHOUT
47
55
  // actually deleting. Uniform shape across "doesn't exist"
48
56
  // and "exists in another tenant" prevents id-existence
@@ -17,7 +17,8 @@
17
17
  * early-detection signal, not a gate.
18
18
  * 3. Compute `contractHash` via the canonical RFC 8785 (JCS)
19
19
  * helper `blueprintKey(contract)`.
20
- * 4. Resolve LLM credentials via `deps.resolveLlm(ctx)`. Throw
20
+ * 4. Resolve LLM credentials via `deps.resolveLlm({...ctx, appId})`
21
+ * — `ctx` widened with the resolved effective appId. Throw
21
22
  * {@link MissingCredentialsError} when `null`.
22
23
  * 5. Dispatch through `generator.generate({...})`.
23
24
  * 6. On `result.ok === true`, persist via `BlueprintStore.put({...})`
@@ -50,7 +51,9 @@ import { type Blueprint, type DataContract, type OpsGenerateBlueprintOutput } fr
50
51
  import { z } from "zod";
51
52
  import { type BlueprintRegistryDeps, type GenerationCredentials } from "../renders/index.js";
52
53
  import type { HandlerContext, SharedHandler } from "../types.js";
54
+ import { type OpsBlueprintAppAuthorizer } from "./app-access.js";
53
55
  declare const opsInputSchema: {
56
+ appId: z.ZodOptional<z.ZodString>;
54
57
  contract: z.ZodType<import("@ggui-ai/protocol").DataContract, unknown, z.core.$ZodTypeInternals<import("@ggui-ai/protocol").DataContract, unknown>>;
55
58
  generator: z.ZodOptional<z.ZodString>;
56
59
  persona: z.ZodOptional<z.ZodString>;
@@ -96,10 +99,10 @@ export interface GguiOpsGenerateBlueprintDeps {
96
99
  readonly blueprintStore: BlueprintStore;
97
100
  /**
98
101
  * Per-app blueprint enumerator — used for the persona near-dup
99
- * check. Reads every blueprint under `ctx.appId` to gather the
100
- * existing persona tag set. Optional — when omitted, the
101
- * near-dup check is skipped (cloud adapters that bind their own
102
- * AppListableBlueprintStore via the search seam supply this
102
+ * check. Reads every blueprint under the resolved `appId` to
103
+ * gather the existing persona tag set. Optional — when omitted,
104
+ * the near-dup check is skipped (cloud adapters that bind their
105
+ * own AppListableBlueprintStore via the search seam supply this
103
106
  * explicitly).
104
107
  */
105
108
  readonly listAllForApp?: (appId: string) => Promise<readonly Blueprint[]>;
@@ -171,6 +174,15 @@ export interface GguiOpsGenerateBlueprintDeps {
171
174
  * Tests override to produce stable blueprint ids.
172
175
  */
173
176
  readonly mintBlueprintId?: () => string;
177
+ /**
178
+ * Optional app-access authorizer — when bound, consulted on EVERY
179
+ * resolution (see {@link resolveEffectiveAppId}) to decide whether
180
+ * the caller may curate the effective `appId`, including cross-app
181
+ * calls that supply an explicit `appId` input different from
182
+ * `ctx.appId`. Unbound: legacy bound-only posture, cross-app input
183
+ * fails closed with `CrossAppCurationUnavailableError`.
184
+ */
185
+ readonly authorizeAppAccess?: OpsBlueprintAppAuthorizer;
174
186
  }
175
187
  export declare function createGguiOpsGenerateBlueprintHandler(deps: GguiOpsGenerateBlueprintDeps): SharedHandler<typeof opsInputSchema, typeof opsOutputSchema, OpsGenerateBlueprintOutput>;
176
188
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/ops-blueprint/generate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,aAAa,EAGd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,YAAY,EAGjB,KAAK,0BAA0B,EAChC,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAQjE,QAAA,MAAM,cAAc;;;;;;;;CAAwC,CAAC;AAC7D,QAAA,MAAM,eAAe;;;;;;;;;CAWb,CAAC;AAET;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxD;AAED;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAAC;IAC1E;;;;;;;OAOG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,CACnB,GAAG,EAAE,cAAc,KAChB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,GAAG,qBAAqB,GAAG,IAAI,CAAC;IAC1E;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,qBAAqB,CAAC;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;CACzC;AAmCD,wBAAgB,qCAAqC,CACnD,IAAI,EAAE,4BAA4B,GACjC,aAAa,CAAC,OAAO,cAAc,EAAE,OAAO,eAAe,EAAE,0BAA0B,CAAC,CAyP1F"}
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/ops-blueprint/generate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,aAAa,EAGd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,YAAY,EAGjB,KAAK,0BAA0B,EAChC,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAyB,KAAK,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAQxF,QAAA,MAAM,cAAc;;;;;;;;;CAAwC,CAAC;AAC7D,QAAA,MAAM,eAAe;;;;;;;;;CAWb,CAAC;AAET;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxD;AAED;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAAC;IAC1E;;;;;;;OAOG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,CACnB,GAAG,EAAE,cAAc,KAChB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,GAAG,qBAAqB,GAAG,IAAI,CAAC;IAC1E;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,qBAAqB,CAAC;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IACxC;;;;;;;OAOG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,yBAAyB,CAAC;CACzD;AAmCD,wBAAgB,qCAAqC,CACnD,IAAI,EAAE,4BAA4B,GACjC,aAAa,CAAC,OAAO,cAAc,EAAE,OAAO,eAAe,EAAE,0BAA0B,CAAC,CAmQ1F"}
@@ -17,7 +17,8 @@
17
17
  * early-detection signal, not a gate.
18
18
  * 3. Compute `contractHash` via the canonical RFC 8785 (JCS)
19
19
  * helper `blueprintKey(contract)`.
20
- * 4. Resolve LLM credentials via `deps.resolveLlm(ctx)`. Throw
20
+ * 4. Resolve LLM credentials via `deps.resolveLlm({...ctx, appId})`
21
+ * — `ctx` widened with the resolved effective appId. Throw
21
22
  * {@link MissingCredentialsError} when `null`.
22
23
  * 5. Dispatch through `generator.generate({...})`.
23
24
  * 6. On `result.ok === true`, persist via `BlueprintStore.put({...})`
@@ -52,6 +53,7 @@ import { z } from "zod";
52
53
  import { assertContractNoRetiredFields } from "../renders/assert-contract-no-retired-fields.js";
53
54
  import { assertGadgetsRegistered } from "../renders/assert-gadgets.js";
54
55
  import { registerBlueprint, } from "../renders/index.js";
56
+ import { resolveEffectiveAppId } from "./app-access.js";
55
57
  import { GenerationFailedError, GeneratorNotFoundError, MissingCredentialsError, } from "./errors.js";
56
58
  import { findNearDuplicatePersona, normalizePersona } from "./persona-normalization.js";
57
59
  const opsInputSchema = opsGenerateBlueprintInputSchema.shape;
@@ -107,14 +109,17 @@ export function createGguiOpsGenerateBlueprintHandler(deps) {
107
109
  name: "ggui_ops_generate_blueprint",
108
110
  title: "Generate blueprint",
109
111
  audience: ["ops"],
110
- description: "Author a new blueprint variant for the caller's app. Dispatches through the registry's selected generator (defaults to `registry.defaultGenerator()` when the slug is omitted) and persists the resulting code body + metadata against a fresh `blueprintId`. Optionally pins the new blueprint as the operator default for its `(appId, contractHash)` group. Persona tags are normalized (lowercase + trim); near-duplicates surface a warning via telemetry. Returns the new id + content-hash + validator score + the stamped provenance (`source`, llm arm: engine generator slug + model, from the engine's own metadata stamp) — code body lives in the bound store, fetched via render's fast-path on cache hit.",
112
+ description: "Author a new blueprint variant for the target app (the caller's bound app by default, or an explicitly named app the deployment's authorization policy accepts). Dispatches through the registry's selected generator (defaults to `registry.defaultGenerator()` when the slug is omitted) and persists the resulting code body + metadata against a fresh `blueprintId`. Optionally pins the new blueprint as the operator default for its `(appId, contractHash)` group. Persona tags are normalized (lowercase + trim); near-duplicates surface a warning via telemetry. Returns the new id + content-hash + validator score + the stamped provenance (`source`, llm arm: engine generator slug + model, from the engine's own metadata stamp) — code body lives in the bound store, fetched via render's fast-path on cache hit. App-scoped variant curation for an app you operate — distinct from the personal saved-blueprint library (the _my_ tools).",
111
113
  inputSchema: opsInputSchema,
112
114
  outputSchema: opsOutputSchema,
113
115
  async handler(rawInput, ctx) {
114
- if (!ctx.appId) {
115
- throw new Error("ggui_ops_generate_blueprint: missing caller identity (appId empty)");
116
- }
117
116
  const parsed = opsGenerateBlueprintInputSchema.parse(rawInput);
117
+ const appId = await resolveEffectiveAppId({
118
+ toolName: "ggui_ops_generate_blueprint",
119
+ inputAppId: parsed.appId,
120
+ ctx,
121
+ ...(deps.authorizeAppAccess ? { authorize: deps.authorizeAppAccess } : {}),
122
+ });
118
123
  // Reject retired top-level contract fields BEFORE any
119
124
  // persistence so an operator-supplied row can't smuggle
120
125
  // deprecated vocabulary (`terminal`, `consumeSpec`,
@@ -136,7 +141,7 @@ export function createGguiOpsGenerateBlueprintHandler(deps) {
136
141
  // instead of just the STDLIB seed.
137
142
  let resolvedAppLibraries;
138
143
  if (deps.appMetadataStore) {
139
- const appRecord = await deps.appMetadataStore.get(ctx.appId);
144
+ const appRecord = await deps.appMetadataStore.get(appId);
140
145
  assertGadgetsRegistered(parsed.contract, appRecord?.gadgets);
141
146
  resolvedAppLibraries = appRecord?.gadgets;
142
147
  }
@@ -146,7 +151,7 @@ export function createGguiOpsGenerateBlueprintHandler(deps) {
146
151
  const normalizedPersona = normalizePersona(parsed.persona);
147
152
  if (normalizedPersona !== undefined && deps.listAllForApp) {
148
153
  try {
149
- const allForApp = await deps.listAllForApp(ctx.appId);
154
+ const allForApp = await deps.listAllForApp(appId);
150
155
  const existingPersonas = new Set();
151
156
  for (const row of allForApp) {
152
157
  if (row.variance.persona)
@@ -161,7 +166,7 @@ export function createGguiOpsGenerateBlueprintHandler(deps) {
161
166
  name: "blueprint.near_duplicate_persona",
162
167
  at: Date.now(),
163
168
  attributes: {
164
- appId: ctx.appId,
169
+ appId,
165
170
  requestId: ctx.requestId,
166
171
  newPersona: check.newPersona,
167
172
  nearestExisting: check.nearestExisting ?? "",
@@ -184,7 +189,7 @@ export function createGguiOpsGenerateBlueprintHandler(deps) {
184
189
  const contract = parsed.contract;
185
190
  const contractHash = blueprintKey(contract);
186
191
  // 4. Resolve LLM credentials.
187
- const creds = await deps.resolveLlm(ctx);
192
+ const creds = await deps.resolveLlm({ ...ctx, appId });
188
193
  if (creds === null) {
189
194
  throw new MissingCredentialsError();
190
195
  }
@@ -198,6 +203,12 @@ export function createGguiOpsGenerateBlueprintHandler(deps) {
198
203
  contract,
199
204
  llm: creds.selection,
200
205
  providerKey: creds.providerKey,
206
+ // The EFFECTIVE app — the one being curated, which may differ
207
+ // from `ctx.appId`. Generators resolve their gadget catalog from
208
+ // it when `appGadgets` wasn't pre-fetched, and deployments that
209
+ // meter generation per app attribute the call by it. Always
210
+ // threaded: a generator that never reads it is unaffected.
211
+ appId,
201
212
  ...(resolvedAppLibraries !== undefined ? { appGadgets: resolvedAppLibraries } : {}),
202
213
  };
203
214
  let result;
@@ -232,7 +243,7 @@ export function createGguiOpsGenerateBlueprintHandler(deps) {
232
243
  const blueprint = {
233
244
  blueprintId,
234
245
  contractHash,
235
- appId: ctx.appId,
246
+ appId,
236
247
  codeHash,
237
248
  source,
238
249
  variance: {
@@ -261,7 +272,7 @@ export function createGguiOpsGenerateBlueprintHandler(deps) {
261
272
  const intentForCache = parsed.seedPrompt ??
262
273
  normalizedPersona ??
263
274
  `operator-authored blueprint (${blueprintId})`;
264
- await registerBlueprint(deps.cacheRegistry, ctx.appId, {
275
+ await registerBlueprint(deps.cacheRegistry, appId, {
265
276
  kind: "template",
266
277
  contract,
267
278
  intent: intentForCache,
@@ -281,7 +292,7 @@ export function createGguiOpsGenerateBlueprintHandler(deps) {
281
292
  name: "blueprint.cache_mirror_failed",
282
293
  at: Date.now(),
283
294
  attributes: {
284
- appId: ctx.appId,
295
+ appId,
285
296
  requestId: ctx.requestId,
286
297
  blueprintId,
287
298
  contractHash,
@@ -304,7 +315,7 @@ export function createGguiOpsGenerateBlueprintHandler(deps) {
304
315
  name: "blueprint.generated",
305
316
  at: Date.now(),
306
317
  attributes: {
307
- appId: ctx.appId,
318
+ appId,
308
319
  requestId: ctx.requestId,
309
320
  blueprintId,
310
321
  contractHash,