@actuarial-ts/agents 0.5.0 → 0.6.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.
@@ -0,0 +1,71 @@
1
+ import { assertCompiledDiagnosticDefinition, type CompiledDiagnosticDefinition, type DiagnosticDeepReadonly } from "@actuarial-ts/core";
2
+ import { assertVerifiedDiagnosticRunProvenance, type VerifiedDiagnosticRunProvenance } from "@actuarial-ts/compliance";
3
+ import { z } from "zod";
4
+ import { AgentsError } from "./errors.js";
5
+ import { defineActuarialTool, type DefinedActuarialTool, type ToolEnvelopeFailure } from "./tools.js";
6
+
7
+ export const diagnosticAgentToolInputSchema = z.object({
8
+ runPresetId:z.string().min(1),
9
+ instanceIds:z.array(z.string().min(1)).min(1),
10
+ view:z.enum(["emergence","triangles","latest-diagonal"]),
11
+ }).strict();
12
+ export type DiagnosticAgentToolInput=z.input<typeof diagnosticAgentToolInputSchema>;
13
+
14
+ export interface DiagnosticAgentRunPreset {
15
+ readonly id:string;
16
+ readonly definitionIntegrity:string;
17
+ readonly allowedInstanceIds:readonly string[];
18
+ readonly execute:(input:{readonly tenant:string;readonly instanceIds:readonly string[]})=>Promise<VerifiedDiagnosticRunProvenance>;
19
+ }
20
+ export interface CreateDiagnosticSelectionToolInput { readonly definition:CompiledDiagnosticDefinition;readonly presets:readonly DiagnosticAgentRunPreset[];readonly id?:string;readonly description?:string;readonly tenantContextKey?:string }
21
+ export type DiagnosticAgentDisplayProjection=
22
+ | {readonly view:"emergence";readonly value:VerifiedDiagnosticRunProvenance["result"]["emergence"]}
23
+ | {readonly view:"triangles";readonly value:VerifiedDiagnosticRunProvenance["result"]["triangles"]}
24
+ | {readonly view:"latest-diagonal";readonly value:VerifiedDiagnosticRunProvenance["result"]["latestDiagonal"]};
25
+ export type DiagnosticAgentDisplayPoint=
26
+ | VerifiedDiagnosticRunProvenance["result"]["emergence"][number]
27
+ | VerifiedDiagnosticRunProvenance["result"]["triangles"][number]
28
+ | VerifiedDiagnosticRunProvenance["result"]["latestDiagonal"][number];
29
+ export interface DiagnosticAgentToolSuccess {
30
+ readonly success:true;readonly runPresetId:string;readonly instanceIds:readonly string[];
31
+ readonly definitionIntegrity:string;readonly formulaFingerprints:Readonly<Record<string,string>>;
32
+ readonly calculationFingerprints:Readonly<Record<string,string>>;readonly runFingerprint:string;
33
+ readonly resultFingerprint:string;readonly runResultFingerprint:string;
34
+ readonly review:VerifiedDiagnosticRunProvenance["review"];readonly display:DiagnosticAgentDisplayProjection;
35
+ }
36
+ export type DiagnosticAgentToolResult=DiagnosticDeepReadonly<DiagnosticAgentToolSuccess>|ToolEnvelopeFailure;
37
+
38
+ const toolFailureSchema=z.object({success:z.literal(false),error:z.object({code:z.string(),message:z.string()}).strict()}).strict();
39
+ const displaySchema=z.discriminatedUnion("view",[
40
+ z.object({view:z.literal("emergence"),value:z.custom<Extract<DiagnosticAgentDisplayProjection,{readonly view:"emergence"}>["value"]>()}).strict(),
41
+ z.object({view:z.literal("triangles"),value:z.custom<Extract<DiagnosticAgentDisplayProjection,{readonly view:"triangles"}>["value"]>()}).strict(),
42
+ z.object({view:z.literal("latest-diagonal"),value:z.custom<Extract<DiagnosticAgentDisplayProjection,{readonly view:"latest-diagonal"}>["value"]>()}).strict(),
43
+ ]);
44
+ const toolSuccessSchema=z.object({
45
+ success:z.literal(true),runPresetId:z.string(),instanceIds:z.array(z.string()),definitionIntegrity:z.string(),
46
+ formulaFingerprints:z.record(z.string()),calculationFingerprints:z.record(z.string()),runFingerprint:z.string(),
47
+ resultFingerprint:z.string(),runResultFingerprint:z.string(),review:z.custom<VerifiedDiagnosticRunProvenance["review"]>(),
48
+ display:displaySchema,
49
+ }).strict();
50
+ /** Strict model-visible output schema, including the wrapper's failure branch. */
51
+ export const diagnosticAgentToolResultSchema:z.ZodType<DiagnosticAgentToolResult>=z.union([toolSuccessSchema,toolFailureSchema]);
52
+ export type DiagnosticSelectionTool=DefinedActuarialTool<DiagnosticAgentToolInput,DiagnosticAgentToolResult>;
53
+
54
+ function token(value:string,label:string):void{if(value.length===0||/^[\t-\r ]|[\t-\r ]$/.test(value)||value.includes("\0"))throw new AgentsError("BAD_DIAGNOSTIC_CATALOG",`${label} must be a nonempty token`)}
55
+ function sortUniqueRequested(values:readonly string[]):string[]{const result=[...new Set(values)];result.sort((a,b)=>a<b?-1:a>b?1:0);return result}
56
+
57
+ export function createDiagnosticSelectionTool(input:CreateDiagnosticSelectionToolInput):DiagnosticSelectionTool{
58
+ try{assertCompiledDiagnosticDefinition(input.definition)}catch{throw new AgentsError("BAD_DIAGNOSTIC_CATALOG","definition must be an authentic compiled diagnostic definition")}
59
+ const id=input.id??"run_diagnostic_selection";const description=input.description??"Run a host-approved diagnostic preset for selected registered metric instances.";const tenantKey=input.tenantContextKey??"projectId";
60
+ token(id,"tool id");token(tenantKey,"tenant context key");if(description.trim().length===0)throw new AgentsError("BAD_DIAGNOSTIC_CATALOG","description must be nonblank");if(input.presets.length===0)throw new AgentsError("BAD_DIAGNOSTIC_CATALOG","at least one approved diagnostic preset is required");
61
+ const known=new Set(input.definition.definition.instances.map((item)=>item.id));const catalog=new Map<string,{definitionIntegrity:string;allowedInstanceIds:readonly string[];execute:DiagnosticAgentRunPreset["execute"]}>();
62
+ for(const preset of input.presets){token(preset.id,"preset id");if(catalog.has(preset.id))throw new AgentsError("BAD_DIAGNOSTIC_CATALOG",`duplicate preset ${preset.id}`);if(preset.definitionIntegrity!==input.definition.definitionIntegrity)throw new AgentsError("BAD_DIAGNOSTIC_CATALOG",`preset ${preset.id} targets another definition`);if(typeof preset.execute!=="function")throw new AgentsError("BAD_DIAGNOSTIC_CATALOG",`preset ${preset.id} has no executor`);const seen=new Set<string>();for(const instanceId of preset.allowedInstanceIds){token(instanceId,"allowed instance id");if(seen.has(instanceId))throw new AgentsError("BAD_DIAGNOSTIC_CATALOG",`preset ${preset.id} repeats ${instanceId}`);if(!known.has(instanceId))throw new AgentsError("BAD_DIAGNOSTIC_CATALOG",`preset ${preset.id} references unknown instance ${instanceId}`);seen.add(instanceId)}if(seen.size===0)throw new AgentsError("BAD_DIAGNOSTIC_CATALOG",`preset ${preset.id} has no allowed instances`);catalog.set(preset.id,Object.freeze({definitionIntegrity:preset.definitionIntegrity,allowedInstanceIds:Object.freeze([...seen].sort()),execute:preset.execute}))}
63
+ return defineActuarialTool({id,description,kind:"read",tenant:"required",tenantKey,inputSchema:diagnosticAgentToolInputSchema,outputSchema:diagnosticAgentToolResultSchema,execute:async(raw,tenant):Promise<DiagnosticAgentToolSuccess>=>{
64
+ const preset=catalog.get(raw.runPresetId);if(!preset)throw new AgentsError("UNKNOWN_DIAGNOSTIC_PRESET",`Unknown diagnostic preset ${raw.runPresetId}`);const selected=sortUniqueRequested(raw.instanceIds);if(selected.some((item)=>!preset.allowedInstanceIds.includes(item)))throw new AgentsError("UNAPPROVED_DIAGNOSTIC_INSTANCE","One or more diagnostic instances are not approved by the selected preset");
65
+ const provenance=await preset.execute({tenant,instanceIds:selected});try{assertVerifiedDiagnosticRunProvenance(provenance)}catch{throw new AgentsError("DIAGNOSTIC_RUN_MISMATCH","Preset executor returned unauthenticated diagnostic provenance")}
66
+ const filter=provenance.manifest.preparation.filter;if(provenance.definitionIdentities.definition!==input.definition.definitionIntegrity||provenance.manifest.runPresetId!==raw.runPresetId||!filter||JSON.stringify(filter.instanceIds??[])!==JSON.stringify(selected))throw new AgentsError("DIAGNOSTIC_RUN_MISMATCH","Verified run does not match the selected definition, preset, and exact instance set");
67
+ const instances=input.definition.definition.instances.filter((item)=>selected.includes(item.id));const formulaIds=[...new Set(instances.map((item)=>item.formulaId))].sort();const formulaFingerprints=Object.fromEntries(formulaIds.map((formulaId)=>[formulaId,provenance.definitionIdentities.formulaById[formulaId]!]));const calculationFingerprints=Object.fromEntries(selected.map((instanceId)=>[instanceId,provenance.definitionIdentities.calculationByInstanceId[instanceId]!]));
68
+ const display=raw.view==="emergence"?{view:"emergence" as const,value:provenance.result.emergence}:raw.view==="triangles"?{view:"triangles" as const,value:provenance.result.triangles}:{view:"latest-diagonal" as const,value:provenance.result.latestDiagonal};
69
+ return {success:true,runPresetId:raw.runPresetId,instanceIds:selected,definitionIntegrity:provenance.definitionIdentities.definition,formulaFingerprints,calculationFingerprints,runFingerprint:provenance.runFingerprint,resultFingerprint:provenance.resultFingerprint,runResultFingerprint:provenance.runResultFingerprint,review:provenance.review,display};
70
+ }});
71
+ }
package/src/errors.ts CHANGED
@@ -43,6 +43,13 @@ export const AGENTS_ERROR_CODES = [
43
43
  "REMOTE_RESULT_INVALID",
44
44
  /** The boot self-test found an MCP-exposed probe tool that did NOT fail closed without tenant context; the MCP tenant seam is not wired up and the server must abort startup. */
45
45
  "MCP_SELF_TEST_FAILED",
46
+ "BAD_DIAGNOSTIC_CATALOG",
47
+ "UNKNOWN_DIAGNOSTIC_PRESET",
48
+ "UNAPPROVED_DIAGNOSTIC_INSTANCE",
49
+ "DIAGNOSTIC_RUN_MISMATCH",
50
+ "BAD_OUTPUT_SCHEMA",
51
+ "TOOL_INPUT_INVALID",
52
+ "TOOL_OUTPUT_INVALID",
46
53
  ] as const;
47
54
 
48
55
  export type AgentsErrorCode = (typeof AGENTS_ERROR_CODES)[number];
package/src/index.ts CHANGED
@@ -7,3 +7,4 @@ export * from "./evals.js";
7
7
  export * from "./divergence.js";
8
8
  export * from "./remote.js";
9
9
  export * from "./mcp.js";
10
+ export * from "./diagnostics.js";
package/src/mcp.ts CHANGED
@@ -22,7 +22,7 @@
22
22
  * startup MUST abort.
23
23
  *
24
24
  * ---------------------------------------------------------------------------
25
- * VERIFIED against the installed @mastra/mcp 1.14.0 (house rule — types and
25
+ * VERIFIED against the lock-tested @mastra/mcp 1.17.3 (house rule — types and
26
26
  * compiled source, not memory):
27
27
  *
28
28
  * - MCPServer.executeTool(toolId, args, executionContext?: { messages?,
@@ -40,7 +40,7 @@
40
40
  * tool context TWO ways: directly at `context.mcp.extra`, and — via
41
41
  * createProxiedRequestContext — as INDIVIDUAL keys set on a fresh
42
42
  * RequestContext (`context.requestContext.get("authInfo")`). NOTE the
43
- * surprise: the installed 1.14.0 sets each extra key on the RequestContext
43
+ * surprise: the lock-tested 1.17.3 sets each extra key on the RequestContext
44
44
  * verbatim, so the tenant lives at `requestContext.get("authInfo")`, NOT
45
45
  * under a single `"mcp.extra"` key as the research draft documented. This
46
46
  * helper tries all three shapes so it is correct against both the installed
package/src/promotion.ts CHANGED
@@ -408,6 +408,7 @@ function structuralCheck(
408
408
  description,
409
409
  status: findings.length > 0 ? "warning" : "pass",
410
410
  details: capDetails(findings),
411
+ findings: findings.map((message) => ({ code: id, message, context: {} })),
411
412
  };
412
413
  }
413
414
 
@@ -472,6 +473,7 @@ function notEvaluatedReview(reason: string): DataReviewReport {
472
473
  description: "ASOP 23-oriented triangle review",
473
474
  status: "not-evaluated",
474
475
  details: [`not evaluated: ${reason}`],
476
+ findings: [],
475
477
  },
476
478
  ],
477
479
  summary: { pass: 0, warning: 0, fail: 0, notEvaluated: 1 },
package/src/tools.ts CHANGED
@@ -17,7 +17,8 @@
17
17
  * keep their code; everything else gets the fallback.
18
18
  */
19
19
 
20
- import { createTool } from "@mastra/core/tools";
20
+ import { createTool, type Tool } from "@mastra/core/tools";
21
+ import { toStandardSchema, type StandardSchemaWithJSON } from "@mastra/core/schema";
21
22
  import type { z } from "zod";
22
23
  import { AgentsError } from "./errors.js";
23
24
 
@@ -26,10 +27,30 @@ import { AgentsError } from "./errors.js";
26
27
 
27
28
  /** The uniform tool-failure shape: agents branch on success, hosts log code. */
28
29
  export type ToolEnvelopeFailure = {
29
- success: false;
30
- error: { code: string; message: string };
30
+ readonly success: false;
31
+ readonly error: { readonly code: string; readonly message: string };
31
32
  };
32
33
 
34
+ const TOOL_INPUT_INVALID: ToolEnvelopeFailure = Object.freeze({
35
+ success: false,
36
+ error: Object.freeze({ code: "TOOL_INPUT_INVALID", message: "Tool input failed schema validation" }),
37
+ });
38
+ const TOOL_OUTPUT_INVALID: ToolEnvelopeFailure = Object.freeze({
39
+ success: false,
40
+ error: Object.freeze({ code: "TOOL_OUTPUT_INVALID", message: "Tool output failed schema validation" }),
41
+ });
42
+
43
+ function readonlyFailure(code: string, message: string): ToolEnvelopeFailure {
44
+ return Object.freeze({ success: false, error: Object.freeze({ code, message }) });
45
+ }
46
+
47
+ function deepFreezeResult<T>(value: T, seen = new WeakSet<object>()): T {
48
+ if (value === null || typeof value !== "object" || seen.has(value)) return value;
49
+ seen.add(value);
50
+ for (const child of Object.values(value as Record<string, unknown>)) deepFreezeResult(child, seen);
51
+ return Object.freeze(value);
52
+ }
53
+
33
54
  /**
34
55
  * Converts anything thrown by a tool into the failure envelope. Never throws.
35
56
  * Error-like values with a non-empty string "code" property (HttpError,
@@ -60,7 +81,7 @@ export function envelopeFailure(err: unknown, fallbackCode = "TOOL_ERROR"): Tool
60
81
  } catch {
61
82
  // A hostile getter must not break the envelope; keep the fallbacks.
62
83
  }
63
- return { success: false, error: { code, message } };
84
+ return readonlyFailure(code, message);
64
85
  }
65
86
 
66
87
  // ---------------------------------------------------------------------------
@@ -116,7 +137,7 @@ export function resolveMcpAuthInfo(context: McpContextLike | undefined): McpAuth
116
137
  | undefined;
117
138
  if (proxiedExtra?.authInfo) return proxiedExtra.authInfo;
118
139
 
119
- // Installed @mastra/mcp 1.14.0: createProxiedRequestContext copies each
140
+ // Lock-tested @mastra/mcp 1.17.3: createProxiedRequestContext copies each
120
141
  // extra key onto the RequestContext verbatim, so authInfo is top-level.
121
142
  const topLevelAuthInfo = requestContext.get("authInfo") as McpAuthInfoLike | undefined;
122
143
  if (topLevelAuthInfo) return topLevelAuthInfo;
@@ -433,7 +454,7 @@ export type ActuarialToolKind = "read" | "action";
433
454
  */
434
455
  export type ActuarialToolContext = TenantToolContext;
435
456
 
436
- interface DefineActuarialToolCommon<TShape extends z.ZodRawShape> {
457
+ interface DefineActuarialToolCommon<TShape extends z.ZodRawShape, TResult> {
437
458
  /**
438
459
  * Exact schema paths (the lint's dot notation, rooted at "input") where an
439
460
  * uninspectable type — z.unknown(), z.any(), z.map() — is INTENTIONAL
@@ -456,6 +477,11 @@ interface DefineActuarialToolCommon<TShape extends z.ZodRawShape> {
456
477
  * AgentsError("TENANT_IN_SCHEMA") at definition time if it does.
457
478
  */
458
479
  inputSchema: z.ZodObject<TShape>;
480
+ /**
481
+ * Optional observable-result schema. It must admit the complete success /
482
+ * failure union because validation errors are ordinary tool results.
483
+ */
484
+ outputSchema?: z.ZodType<TResult | ToolEnvelopeFailure, z.ZodTypeDef, unknown>;
459
485
  }
460
486
 
461
487
  /**
@@ -474,7 +500,7 @@ interface DefineActuarialToolCommon<TShape extends z.ZodRawShape> {
474
500
  * reviewable at the definition site.
475
501
  */
476
502
  export type DefineActuarialToolOptions<TShape extends z.ZodRawShape, TResult> =
477
- | (DefineActuarialToolCommon<TShape> & {
503
+ | (DefineActuarialToolCommon<TShape, TResult> & {
478
504
  tenant: "required";
479
505
  /** Trusted source for the tenant id. Default "request-context". */
480
506
  tenantSource?: TenantSource;
@@ -487,27 +513,72 @@ export type DefineActuarialToolOptions<TShape extends z.ZodRawShape, TResult> =
487
513
  * envelope, never an exception.
488
514
  */
489
515
  execute: (
490
- input: z.infer<z.ZodObject<TShape>>,
516
+ input: z.output<z.ZodObject<TShape>>,
491
517
  tenant: string,
492
518
  context: ActuarialToolContext,
493
519
  ) => Promise<TResult>;
494
520
  })
495
- | (DefineActuarialToolCommon<TShape> & {
521
+ | (DefineActuarialToolCommon<TShape, TResult> & {
496
522
  tenant: "none";
497
523
  execute: (
498
- input: z.infer<z.ZodObject<TShape>>,
524
+ input: z.output<z.ZodObject<TShape>>,
499
525
  tenant: null,
500
526
  context: ActuarialToolContext,
501
527
  ) => Promise<TResult>;
502
528
  });
503
529
 
530
+ /**
531
+ * A Mastra-compatible tool whose direct execute boundary is fully owned by
532
+ * this SDK. Mastra metadata stays intentionally unknown: the real domain
533
+ * schemas are retained privately so framework validation cannot run their
534
+ * transforms a second time.
535
+ */
536
+ export type DefinedActuarialTool<TInput, TOutput> = Omit<
537
+ Tool<unknown, unknown>,
538
+ "execute"
539
+ > & {
540
+ readonly kind: ActuarialToolKind;
541
+ execute: (input: TInput, context: ActuarialToolContext) => Promise<TOutput>;
542
+ };
543
+
544
+ function metadataBridge(schema: z.ZodTypeAny): StandardSchemaWithJSON<unknown, unknown> {
545
+ const real = toStandardSchema(schema);
546
+ return {
547
+ "~standard": {
548
+ version: 1,
549
+ vendor: "actuarial-ts-metadata-bridge",
550
+ validate: (value: unknown) => ({ value }),
551
+ jsonSchema: {
552
+ input: (options) => real["~standard"].jsonSchema.input(options),
553
+ output: (options) => real["~standard"].jsonSchema.output(options),
554
+ },
555
+ },
556
+ };
557
+ }
558
+
559
+ function isFailure(value: unknown): value is ToolEnvelopeFailure {
560
+ if (value === null || typeof value !== "object") return false;
561
+ const candidate = value as { success?: unknown; error?: unknown };
562
+ if (candidate.success !== false || candidate.error === null || typeof candidate.error !== "object") return false;
563
+ const error = candidate.error as { code?: unknown; message?: unknown };
564
+ return typeof error.code === "string" && typeof error.message === "string";
565
+ }
566
+
567
+ function sameJson(left: unknown, right: unknown): boolean {
568
+ try {
569
+ return JSON.stringify(left) === JSON.stringify(right);
570
+ } catch {
571
+ return false;
572
+ }
573
+ }
574
+
504
575
  /**
505
576
  * Wraps Mastra's createTool with the envelope + tenant-seam guarantees and
506
577
  * tags the result with its kind for toolRegistry classification.
507
578
  */
508
579
  export function defineActuarialTool<TShape extends z.ZodRawShape, TResult>(
509
580
  options: DefineActuarialToolOptions<TShape, TResult>,
510
- ) {
581
+ ): DefinedActuarialTool<z.input<z.ZodObject<TShape>>, TResult | ToolEnvelopeFailure> {
511
582
  // FAIL CLOSED: a schema the seam cannot inspect is not definable, and the
512
583
  // tenant-key lint recurses through every container the model could reach.
513
584
  const shape = zodObjectShape(options.inputSchema);
@@ -538,28 +609,81 @@ export function defineActuarialTool<TShape extends z.ZodRawShape, TResult>(
538
609
  "relationship to the tenant seam explicitly",
539
610
  );
540
611
  }
612
+
613
+ if (options.outputSchema !== undefined) {
614
+ let probe;
615
+ try {
616
+ probe = options.outputSchema.safeParse(TOOL_OUTPUT_INVALID);
617
+ } catch {
618
+ throw new AgentsError(
619
+ "BAD_OUTPUT_SCHEMA",
620
+ `Tool "${options.id}": outputSchema threw while validating the required failure envelope`,
621
+ );
622
+ }
623
+ if (!probe.success || !sameJson(probe.data, TOOL_OUTPUT_INVALID)) {
624
+ throw new AgentsError(
625
+ "BAD_OUTPUT_SCHEMA",
626
+ `Tool "${options.id}": outputSchema must preserve the complete tool failure envelope`,
627
+ );
628
+ }
629
+ }
630
+
541
631
  const tool = createTool({
542
632
  id: options.id,
543
633
  description: options.description,
544
- inputSchema: options.inputSchema,
545
- execute: async (input: z.infer<z.ZodObject<TShape>>, context: unknown) => {
546
- try {
547
- if (options.tenant === "required") {
548
- // Resolve BEFORE the body runs: an unauthenticated call fails closed
549
- // here, and execute never sees it.
550
- const tenant = resolveTenant(context as ActuarialToolContext, {
551
- source: options.tenantSource,
552
- key: options.tenantKey,
553
- });
554
- return await options.execute(input, tenant, context as ActuarialToolContext);
555
- }
556
- return await options.execute(input, null, context as ActuarialToolContext);
557
- } catch (err) {
558
- return envelopeFailure(err);
559
- }
560
- },
634
+ inputSchema: metadataBridge(options.inputSchema),
635
+ ...(options.outputSchema === undefined ? {} : { outputSchema: metadataBridge(options.outputSchema) }),
561
636
  });
562
- return Object.assign(tool, { kind: options.kind });
637
+
638
+ const execute = async (
639
+ rawInput: z.input<z.ZodObject<TShape>>,
640
+ context: ActuarialToolContext,
641
+ ): Promise<TResult | ToolEnvelopeFailure> => {
642
+ let parsedInput;
643
+ try {
644
+ parsedInput = options.inputSchema.safeParse(rawInput);
645
+ } catch {
646
+ return TOOL_INPUT_INVALID;
647
+ }
648
+ if (!parsedInput.success) return TOOL_INPUT_INVALID;
649
+
650
+ let rawOutput: TResult | ToolEnvelopeFailure;
651
+ try {
652
+ if (options.tenant === "required") {
653
+ const tenant = resolveTenant(context, {
654
+ source: options.tenantSource,
655
+ key: options.tenantKey,
656
+ });
657
+ rawOutput = await options.execute(parsedInput.data, tenant, context);
658
+ } else {
659
+ rawOutput = await options.execute(parsedInput.data, null, context);
660
+ }
661
+ } catch (err) {
662
+ rawOutput = envelopeFailure(err);
663
+ }
664
+
665
+ if (options.outputSchema === undefined) {
666
+ return rawOutput === undefined ? TOOL_OUTPUT_INVALID : rawOutput;
667
+ }
668
+ let parsedOutput;
669
+ try {
670
+ parsedOutput = options.outputSchema.safeParse(rawOutput);
671
+ } catch {
672
+ return TOOL_OUTPUT_INVALID;
673
+ }
674
+ if (!parsedOutput.success) return TOOL_OUTPUT_INVALID;
675
+ if (isFailure(rawOutput) && !sameJson(parsedOutput.data, rawOutput)) return TOOL_OUTPUT_INVALID;
676
+ return deepFreezeResult(parsedOutput.data);
677
+ };
678
+
679
+ // The metadata bridges deliberately erase domain inference on the inherited
680
+ // Mastra surface. This is the single convergence assertion: the adapter
681
+ // above is the only executor and has the exact public input/output contract.
682
+ const defined = Object.assign(tool, { execute, kind: options.kind }) as DefinedActuarialTool<
683
+ z.input<z.ZodObject<TShape>>,
684
+ TResult | ToolEnvelopeFailure
685
+ >;
686
+ return defined;
563
687
  }
564
688
 
565
689
  // ---------------------------------------------------------------------------