@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.
- package/dist/blueprints/describe-data-contract-format.d.ts.map +1 -1
- package/dist/blueprints/describe-data-contract-format.js +48 -6
- package/dist/credits/get-credit-balance.d.ts +11 -6
- package/dist/credits/get-credit-balance.d.ts.map +1 -1
- package/dist/credits/get-credit-balance.js +17 -8
- package/dist/credits/list-credit-transactions.d.ts +4 -2
- package/dist/credits/list-credit-transactions.d.ts.map +1 -1
- package/dist/credits/list-credit-transactions.js +9 -4
- package/dist/ops-blueprint/app-access.d.ts +37 -0
- package/dist/ops-blueprint/app-access.d.ts.map +1 -0
- package/dist/ops-blueprint/app-access.js +45 -0
- package/dist/ops-blueprint/delete.d.ts +26 -5
- package/dist/ops-blueprint/delete.d.ts.map +1 -1
- package/dist/ops-blueprint/delete.js +18 -10
- package/dist/ops-blueprint/generate.d.ts +17 -5
- package/dist/ops-blueprint/generate.d.ts.map +1 -1
- package/dist/ops-blueprint/generate.js +24 -13
- package/dist/ops-blueprint/index.d.ts +12 -1
- package/dist/ops-blueprint/index.d.ts.map +1 -1
- package/dist/ops-blueprint/index.js +12 -1
- package/dist/ops-blueprint/list.d.ts +11 -0
- package/dist/ops-blueprint/list.d.ts.map +1 -1
- package/dist/ops-blueprint/list.js +10 -6
- package/dist/ops-blueprint/register.d.ts +11 -0
- package/dist/ops-blueprint/register.d.ts.map +1 -1
- package/dist/ops-blueprint/register.js +15 -11
- package/dist/ops-blueprint/update.d.ts +25 -4
- package/dist/ops-blueprint/update.d.ts.map +1 -1
- package/dist/ops-blueprint/update.js +16 -9
- package/dist/renders/amend.d.ts +57 -0
- package/dist/renders/amend.d.ts.map +1 -0
- package/dist/renders/amend.js +64 -0
- package/dist/renders/apply-ggui-session-patch.d.ts +4 -0
- package/dist/renders/apply-ggui-session-patch.d.ts.map +1 -1
- package/dist/renders/apply-ggui-session-patch.js +1 -1
- package/dist/renders/assert-props-contract.d.ts +5 -4
- package/dist/renders/assert-props-contract.d.ts.map +1 -1
- package/dist/renders/assert-props-contract.js +6 -5
- package/dist/renders/blueprint-durability.d.ts +10 -1
- package/dist/renders/blueprint-durability.d.ts.map +1 -1
- package/dist/renders/blueprint-durability.js +39 -0
- package/dist/renders/blueprint-registry.d.ts +33 -0
- package/dist/renders/blueprint-registry.d.ts.map +1 -1
- package/dist/renders/blueprint-registry.js +78 -42
- package/dist/renders/consume.js +1 -1
- package/dist/renders/decide-handshake.d.ts +20 -4
- package/dist/renders/decide-handshake.d.ts.map +1 -1
- package/dist/renders/decide-handshake.js +64 -33
- package/dist/renders/declare-tool-catalog.js +1 -1
- package/dist/renders/generation-cache.d.ts +11 -0
- package/dist/renders/generation-cache.d.ts.map +1 -1
- package/dist/renders/get-render-source.d.ts +48 -0
- package/dist/renders/get-render-source.d.ts.map +1 -0
- package/dist/renders/get-render-source.js +75 -0
- package/dist/renders/handshake-fallbacks.d.ts +27 -0
- package/dist/renders/handshake-fallbacks.d.ts.map +1 -0
- package/dist/renders/handshake-fallbacks.js +110 -0
- package/dist/renders/handshake.d.ts +76 -17
- package/dist/renders/handshake.d.ts.map +1 -1
- package/dist/renders/handshake.js +270 -81
- package/dist/renders/index.d.ts +7 -1
- package/dist/renders/index.d.ts.map +1 -1
- package/dist/renders/index.js +7 -1
- package/dist/renders/props-mutation-core.d.ts +60 -0
- package/dist/renders/props-mutation-core.d.ts.map +1 -0
- package/dist/renders/props-mutation-core.js +366 -0
- package/dist/renders/render-source-envelope.d.ts +61 -0
- package/dist/renders/render-source-envelope.d.ts.map +1 -0
- package/dist/renders/render-source-envelope.js +27 -0
- package/dist/renders/render-telemetry.d.ts +73 -0
- package/dist/renders/render-telemetry.d.ts.map +1 -0
- package/dist/renders/render-telemetry.js +80 -0
- package/dist/renders/render.d.ts +45 -10
- package/dist/renders/render.d.ts.map +1 -1
- package/dist/renders/render.js +280 -77
- package/dist/renders/runtime-pull.d.ts +122 -0
- package/dist/renders/runtime-pull.d.ts.map +1 -0
- package/dist/renders/runtime-pull.js +178 -0
- package/dist/renders/runtime-telemetry.d.ts +60 -0
- package/dist/renders/runtime-telemetry.d.ts.map +1 -0
- package/dist/renders/runtime-telemetry.js +76 -0
- package/dist/renders/slice-meta-derivation.d.ts +107 -12
- package/dist/renders/slice-meta-derivation.d.ts.map +1 -1
- package/dist/renders/slice-meta-derivation.js +119 -16
- package/dist/renders/submit-action.d.ts +9 -0
- package/dist/renders/submit-action.d.ts.map +1 -1
- package/dist/renders/submit-action.js +70 -1
- package/dist/renders/update.d.ts +44 -43
- package/dist/renders/update.d.ts.map +1 -1
- package/dist/renders/update.js +98 -233
- package/dist/types.d.ts +30 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -4
- 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;
|
|
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; //
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
\`
|
|
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
|
|
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:
|
|
13
|
-
* `ctx.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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
|
|
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:
|
|
13
|
-
* `ctx.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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
|
-
|
|
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.
|
|
14
|
-
*
|
|
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
|
|
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.
|
|
14
|
-
*
|
|
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
|
-
|
|
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
|
|
12
|
-
*
|
|
13
|
-
* caller's perspective" and
|
|
14
|
-
* shape that doesn't leak
|
|
15
|
-
*
|
|
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
|
|
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
|
|
12
|
-
*
|
|
13
|
-
* caller's perspective" and
|
|
14
|
-
* shape that doesn't leak
|
|
15
|
-
*
|
|
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 !==
|
|
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)
|
|
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 `
|
|
100
|
-
* existing persona tag set. Optional — when omitted,
|
|
101
|
-
* near-dup check is skipped (cloud adapters that bind their
|
|
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
|
|
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)
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
318
|
+
appId,
|
|
308
319
|
requestId: ctx.requestId,
|
|
309
320
|
blueprintId,
|
|
310
321
|
contractHash,
|