@xemahq/kernel-contracts 0.40.0 → 0.42.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 (110) hide show
  1. package/dist/agent-composition/lib/model-resolution-matrix.d.ts +5 -2
  2. package/dist/agent-composition/lib/model-resolution-matrix.d.ts.map +1 -1
  3. package/dist/agent-composition/lib/model-resolution-matrix.js +1 -0
  4. package/dist/agent-composition/lib/model-resolution-matrix.js.map +1 -1
  5. package/dist/connector/lib/canonical-operation-binding.d.ts +1 -1
  6. package/dist/connector/lib/canonical-operation-binding.d.ts.map +1 -1
  7. package/dist/connector/lib/canonical-operation-binding.js +2 -2
  8. package/dist/connector/lib/canonical-operation-binding.js.map +1 -1
  9. package/dist/connector/lib/integration-provider-contribution.d.ts +2 -2
  10. package/dist/contribution/lib/contribution-kind.d.ts +2 -1
  11. package/dist/contribution/lib/contribution-kind.d.ts.map +1 -1
  12. package/dist/contribution/lib/contribution-kind.js +1 -0
  13. package/dist/contribution/lib/contribution-kind.js.map +1 -1
  14. package/dist/enterprise-context/index.d.ts +5 -0
  15. package/dist/enterprise-context/index.d.ts.map +1 -0
  16. package/dist/enterprise-context/index.js +21 -0
  17. package/dist/enterprise-context/index.js.map +1 -0
  18. package/dist/enterprise-context/lib/context-bundle.d.ts +13 -0
  19. package/dist/enterprise-context/lib/context-bundle.d.ts.map +1 -0
  20. package/dist/enterprise-context/lib/context-bundle.js +3 -0
  21. package/dist/enterprise-context/lib/context-bundle.js.map +1 -0
  22. package/dist/enterprise-context/lib/context-document.d.ts +9 -0
  23. package/dist/enterprise-context/lib/context-document.d.ts.map +1 -0
  24. package/dist/enterprise-context/lib/context-document.js +3 -0
  25. package/dist/enterprise-context/lib/context-document.js.map +1 -0
  26. package/dist/enterprise-context/lib/context-mount-entry.d.ts +8 -0
  27. package/dist/enterprise-context/lib/context-mount-entry.d.ts.map +1 -0
  28. package/dist/enterprise-context/lib/context-mount-entry.js +5 -0
  29. package/dist/enterprise-context/lib/context-mount-entry.js.map +1 -0
  30. package/dist/enterprise-context/lib/dropped-context.d.ts +9 -0
  31. package/dist/enterprise-context/lib/dropped-context.d.ts.map +1 -0
  32. package/dist/enterprise-context/lib/dropped-context.js +8 -0
  33. package/dist/enterprise-context/lib/dropped-context.js.map +1 -0
  34. package/dist/ingestion-source/index.d.ts +2 -0
  35. package/dist/ingestion-source/index.d.ts.map +1 -0
  36. package/dist/ingestion-source/index.js +18 -0
  37. package/dist/ingestion-source/index.js.map +1 -0
  38. package/dist/ingestion-source/lib/ingestion-source-contribution.d.ts +62 -0
  39. package/dist/ingestion-source/lib/ingestion-source-contribution.d.ts.map +1 -0
  40. package/dist/ingestion-source/lib/ingestion-source-contribution.js +78 -0
  41. package/dist/ingestion-source/lib/ingestion-source-contribution.js.map +1 -0
  42. package/dist/llm-gateway/lib/errors.d.ts +5 -0
  43. package/dist/llm-gateway/lib/errors.d.ts.map +1 -1
  44. package/dist/llm-gateway/lib/errors.js +21 -1
  45. package/dist/llm-gateway/lib/errors.js.map +1 -1
  46. package/dist/output/index.d.ts +1 -0
  47. package/dist/output/index.d.ts.map +1 -1
  48. package/dist/output/index.js +1 -0
  49. package/dist/output/index.js.map +1 -1
  50. package/dist/output/lib/output-version-dto.d.ts +2 -0
  51. package/dist/output/lib/output-version-dto.d.ts.map +1 -1
  52. package/dist/output/lib/point-of-attention.d.ts +19 -0
  53. package/dist/output/lib/point-of-attention.d.ts.map +1 -0
  54. package/dist/output/lib/point-of-attention.js +24 -0
  55. package/dist/output/lib/point-of-attention.js.map +1 -0
  56. package/dist/worker-runtime/index.d.ts +1 -0
  57. package/dist/worker-runtime/index.d.ts.map +1 -1
  58. package/dist/worker-runtime/index.js +1 -0
  59. package/dist/worker-runtime/index.js.map +1 -1
  60. package/dist/worker-runtime/lib/enums.d.ts +5 -0
  61. package/dist/worker-runtime/lib/enums.d.ts.map +1 -1
  62. package/dist/worker-runtime/lib/enums.js +7 -1
  63. package/dist/worker-runtime/lib/enums.js.map +1 -1
  64. package/dist/worker-runtime/lib/runtime-layout-profile.d.ts +4 -1
  65. package/dist/worker-runtime/lib/runtime-layout-profile.d.ts.map +1 -1
  66. package/dist/worker-runtime/lib/runtime-layout-profile.js +12 -0
  67. package/dist/worker-runtime/lib/runtime-layout-profile.js.map +1 -1
  68. package/dist/worker-runtime/lib/workspace-sandbox.d.ts +7 -0
  69. package/dist/worker-runtime/lib/workspace-sandbox.d.ts.map +1 -0
  70. package/dist/worker-runtime/lib/workspace-sandbox.js +36 -0
  71. package/dist/worker-runtime/lib/workspace-sandbox.js.map +1 -0
  72. package/dist/workflow/index.d.ts +1 -0
  73. package/dist/workflow/index.d.ts.map +1 -1
  74. package/dist/workflow/index.js +1 -0
  75. package/dist/workflow/index.js.map +1 -1
  76. package/dist/workflow/lib/enums.d.ts +1 -0
  77. package/dist/workflow/lib/enums.d.ts.map +1 -1
  78. package/dist/workflow/lib/enums.js +1 -0
  79. package/dist/workflow/lib/enums.js.map +1 -1
  80. package/dist/workflow/lib/model-ref.d.ts +17 -0
  81. package/dist/workflow/lib/model-ref.d.ts.map +1 -1
  82. package/dist/workflow/lib/model-ref.js +19 -1
  83. package/dist/workflow/lib/model-ref.js.map +1 -1
  84. package/dist/workflow/lib/workflow-control.d.ts +91 -0
  85. package/dist/workflow/lib/workflow-control.d.ts.map +1 -0
  86. package/dist/workflow/lib/workflow-control.js +84 -0
  87. package/dist/workflow/lib/workflow-control.js.map +1 -0
  88. package/package.json +1 -1
  89. package/src/agent-composition/lib/model-resolution-matrix.ts +16 -1
  90. package/src/connector/lib/canonical-operation-binding.ts +1 -1
  91. package/src/contribution/lib/contribution-kind.ts +18 -0
  92. package/src/enterprise-context/index.ts +4 -0
  93. package/src/enterprise-context/lib/context-bundle.ts +35 -0
  94. package/src/enterprise-context/lib/context-document.ts +23 -0
  95. package/src/enterprise-context/lib/context-mount-entry.ts +29 -0
  96. package/src/enterprise-context/lib/dropped-context.ts +24 -0
  97. package/src/ingestion-source/index.ts +20 -0
  98. package/src/ingestion-source/lib/ingestion-source-contribution.ts +238 -0
  99. package/src/llm-gateway/lib/errors.ts +46 -0
  100. package/src/output/index.ts +1 -0
  101. package/src/output/lib/output-version-dto.ts +9 -0
  102. package/src/output/lib/point-of-attention.ts +69 -0
  103. package/src/worker-runtime/index.ts +1 -0
  104. package/src/worker-runtime/lib/enums.ts +29 -0
  105. package/src/worker-runtime/lib/runtime-layout-profile.ts +61 -1
  106. package/src/worker-runtime/lib/workspace-sandbox.ts +129 -0
  107. package/src/workflow/index.ts +1 -0
  108. package/src/workflow/lib/enums.ts +8 -0
  109. package/src/workflow/lib/model-ref.ts +45 -0
  110. package/src/workflow/lib/workflow-control.ts +198 -0
@@ -20,7 +20,7 @@
20
20
  // is where the platform renders the system overlay per WS7.
21
21
  // ═══════════════════════════════════════════════════════════════════════════
22
22
 
23
- import { WorkerRuntimeKind } from './enums';
23
+ import { RuntimeCommandFormat, WorkerRuntimeKind } from './enums';
24
24
 
25
25
  /**
26
26
  * The workspace slots whose on-disk location differs per orchestrator. The
@@ -48,6 +48,43 @@ export interface RuntimeLayoutProfile {
48
48
  * fail-fast contract as `agentBundlesDir`.
49
49
  */
50
50
  readonly commandsDir: string | null;
51
+ /**
52
+ * How this runtime consumes a per-skill `/`-launch command — the capability
53
+ * the composer/proxy switch on to decide WHETHER (and in which format) to
54
+ * materialise a launcher file. `Markdown` runtimes get a launcher written at
55
+ * `commandsDir`; `Toml`/`None` runtimes get NOTHING (skills still mount as
56
+ * bundles and launch via the runtime's own `/skills` surface / skill tool),
57
+ * so no stray file lands where the runtime would silently ignore it.
58
+ *
59
+ * INVARIANT: `commandFormat === Markdown` ⟺ `commandsDir !== null`. A runtime
60
+ * with no command surface (`commandsDir: null`) MUST declare `None`.
61
+ */
62
+ readonly commandFormat: RuntimeCommandFormat;
63
+ /**
64
+ * Absolute path to the runtime's PRIMARY config file — the file the
65
+ * orchestrator's bundle composer renders and the supervisor materialises on
66
+ * the PVC. It carries the LLM-gateway credential (OpenCode reads
67
+ * `.opencode/opencode.jsonc`; Qwen `.qwen/settings.json` → `env.OPENAI_API_KEY`;
68
+ * Claude `.claude/settings.json` → `env.ANTHROPIC_AUTH_TOKEN`; Hermes
69
+ * `.hermes/config.json` → `provider.api_key`) and/or merged MCP bearer headers.
70
+ *
71
+ * This is the single source of truth for the config-file location: both the
72
+ * proxy's on-disk layout and the workspace-sandbox protected-path contract
73
+ * (`deriveWorkspaceSandboxProtection`) source it here rather than each
74
+ * re-deriving a per-runtime basename literal.
75
+ */
76
+ readonly runtimeConfigFile: string;
77
+ /**
78
+ * Absolute path to a DEDICATED MCP-server file the runtime reads separately
79
+ * from its primary config (Claude Code: `.mcp.json` at the workspace root),
80
+ * or `null` when the runtime merges MCP into {@link runtimeConfigFile}
81
+ * (OpenCode/Qwen/Hermes: `InConfig`). When non-null the file carries per-server
82
+ * bearer headers and is therefore part of the secret read-deny set.
83
+ *
84
+ * INVARIANT: `mcpConfigFile !== null` ⟺ the runtime places MCP in a separate
85
+ * file. A runtime that folds MCP into its primary config MUST declare `null`.
86
+ */
87
+ readonly mcpConfigFile: string | null;
51
88
  }
52
89
 
53
90
  /**
@@ -63,6 +100,11 @@ export const RUNTIME_LAYOUT_PROFILES: Readonly<
63
100
  contextFile: '/workspace/AGENTS.md',
64
101
  agentBundlesDir: '/workspace/.opencode/agents',
65
102
  commandsDir: '/workspace/.opencode/command',
103
+ // OpenCode reads markdown commands from `{command,commands}/**.md`.
104
+ commandFormat: RuntimeCommandFormat.Markdown,
105
+ runtimeConfigFile: '/workspace/.opencode/opencode.jsonc',
106
+ // OpenCode pre-merges MCP into opencode.jsonc (`InConfig`) — no separate file.
107
+ mcpConfigFile: null,
66
108
  },
67
109
  [WorkerRuntimeKind.Qwen]: {
68
110
  kind: WorkerRuntimeKind.Qwen,
@@ -70,6 +112,13 @@ export const RUNTIME_LAYOUT_PROFILES: Readonly<
70
112
  contextFile: '/workspace/AGENTS.md',
71
113
  agentBundlesDir: '/workspace/.qwen/agents',
72
114
  commandsDir: '/workspace/.qwen/commands',
115
+ // Qwen custom commands are TOML, not markdown; skills launch via Qwen's
116
+ // own `/skills <name>` surface. A synthesised markdown `.md` under
117
+ // `.qwen/commands` would be silently ignored — so Xema emits NOTHING.
118
+ commandFormat: RuntimeCommandFormat.Toml,
119
+ runtimeConfigFile: '/workspace/.qwen/settings.json',
120
+ // Qwen reads its `mcpServers` map INSIDE `.qwen/settings.json` (`InConfig`).
121
+ mcpConfigFile: null,
73
122
  },
74
123
  [WorkerRuntimeKind.ClaudeCode]: {
75
124
  kind: WorkerRuntimeKind.ClaudeCode,
@@ -77,6 +126,12 @@ export const RUNTIME_LAYOUT_PROFILES: Readonly<
77
126
  contextFile: '/workspace/CLAUDE.md',
78
127
  agentBundlesDir: '/workspace/.claude/agents',
79
128
  commandsDir: '/workspace/.claude/commands',
129
+ // Claude Code custom slash-commands ARE markdown, under `.claude/commands`.
130
+ commandFormat: RuntimeCommandFormat.Markdown,
131
+ runtimeConfigFile: '/workspace/.claude/settings.json',
132
+ // Claude Code reads project-scoped MCP from a DEDICATED `.mcp.json` at the
133
+ // workspace root (SeparateFile) — NOT from `.claude/settings.json`.
134
+ mcpConfigFile: '/workspace/.mcp.json',
80
135
  },
81
136
  [WorkerRuntimeKind.Hermes]: {
82
137
  kind: WorkerRuntimeKind.Hermes,
@@ -89,6 +144,11 @@ export const RUNTIME_LAYOUT_PROFILES: Readonly<
89
144
  // is a portable `.md` surface, so the composer must not mount them as files.
90
145
  agentBundlesDir: null,
91
146
  commandsDir: null,
147
+ // No file-based slash-command surface at all — emit nothing.
148
+ commandFormat: RuntimeCommandFormat.None,
149
+ runtimeConfigFile: '/workspace/.hermes/config.json',
150
+ // Hermes reads its `mcpServers` map INSIDE `.hermes/config.json` (`InConfig`).
151
+ mcpConfigFile: null,
92
152
  },
93
153
  };
94
154
 
@@ -0,0 +1,129 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // ── Workspace-sandbox protection contract ──
3
+ //
4
+ // A FIRST-CLASS, per-runtime security invariant for the agent-workspace sandbox.
5
+ // The agent runs a code CLI (OpenCode/Qwen/Claude/Hermes) inside the workspace
6
+ // pod with its OWN file tools (read/edit/write). Those tools MUST NOT be able to:
7
+ //
8
+ // (a) READ the mounted platform secrets — the runtime config file carries the
9
+ // LLM-gateway credential (a signed JWT) and/or merged MCP bearer headers,
10
+ // and a dedicated `.mcp.json` (Claude) carries per-server bearer headers.
11
+ // (b) MUTATE the agent's OWN system-instructions / agent-definition bundles —
12
+ // the auto-loaded context file (`AGENTS.md`/`CLAUDE.md`), the file-based
13
+ // sub-agent definitions, the Xema-private system overlay + injected
14
+ // instruction sections, and the `context.json` capability-authority spec
15
+ // (rewriting it would let the agent self-grant tools).
16
+ //
17
+ // This module DECLARES that protected-path set ONCE, derived from the kernel
18
+ // `RuntimeLayoutProfile` (the SSOT for every per-runtime path). Each runtime's
19
+ // config builder translates the neutral glob set into its NATIVE permission
20
+ // vocabulary (OpenCode `permission.read`/`permission.edit` globs; Claude Code
21
+ // `permissions.deny: ["Read(...)","Edit(...)"]`). Runtimes with no per-file
22
+ // permission surface (Qwen/Hermes) cannot consume the deny set and fall back to
23
+ // the pod's uid + per-org mount confinement — see the residual note in each
24
+ // builder. The contract is declared here regardless so the invariant is
25
+ // single-sourced, not scattered across builders.
26
+ //
27
+ // Globs are runtime-syntax-NEUTRAL absolute paths anchored at `/workspace`
28
+ // (`*` matches any character; `<dir>/**` covers a whole slot). Callers adapt
29
+ // them to their runtime's matcher.
30
+ // ═══════════════════════════════════════════════════════════════════════════
31
+
32
+ import {
33
+ WorkspaceMount,
34
+ } from '../../agent-workspace/lib/workspace-layout';
35
+
36
+ import type { RuntimeLayoutProfile } from './runtime-layout-profile';
37
+
38
+ /**
39
+ * The resolved protected-path set for one runtime's workspace sandbox.
40
+ *
41
+ * - `secretReadDenyGlobs` — the agent's READ tool must DENY these (secrets). The
42
+ * agent may still read its own instructions/code; only credential-bearing
43
+ * files are denied.
44
+ * - `protectedEditDenyGlobs` — the agent's EDIT/WRITE tool must DENY these
45
+ * (own system-instructions, agent-definitions, authority spec, and — defense
46
+ * in depth — its own credential config). The agent may still edit workspace
47
+ * code, deliverables, repos, inputs, uploads, and tmp.
48
+ *
49
+ * Every entry is an absolute `/workspace/...` glob. Both sets are de-duplicated
50
+ * and stable-ordered.
51
+ */
52
+ export interface WorkspaceSandboxProtection {
53
+ readonly secretReadDenyGlobs: readonly string[];
54
+ readonly protectedEditDenyGlobs: readonly string[];
55
+ }
56
+
57
+ /**
58
+ * Constant, runtime-AGNOSTIC Xema-private system-instruction + authority slots.
59
+ * These are excluded from the per-runtime `RuntimeLayoutProfile` on purpose (the
60
+ * profile only carries the slots whose ON-DISK path differs per runtime), so
61
+ * they are sourced from the `WorkspaceMount` SSOT here — never re-literalled.
62
+ *
63
+ * All are EDIT-denied (never mutate the injected system overlay / instructions /
64
+ * capability-authority). They are NOT read-denied: the runtime legitimately
65
+ * loads the overlay + instructions as context, and the plugin reads
66
+ * `context.json`.
67
+ */
68
+ const XEMA_PRIVATE_INSTRUCTION_EDIT_GLOBS: readonly string[] = [
69
+ // /workspace/.xema/system-overlay.md — the injected system prompt overlay.
70
+ WorkspaceMount.SystemOverlay,
71
+ // /workspace/.xema/instructions/** — injected rule/policy instruction sections.
72
+ `${WorkspaceMount.Instructions}/**`,
73
+ // /workspace/context.json — carries authority.allowedXemaTools; rewriting it
74
+ // would let the agent escalate its own tool grants.
75
+ WorkspaceMount.ContextJson,
76
+ ];
77
+
78
+ function dedupeStable(globs: readonly string[]): string[] {
79
+ return [...new Set(globs)];
80
+ }
81
+
82
+ /**
83
+ * Derive the workspace-sandbox protected-path set for a runtime, from its
84
+ * kernel `RuntimeLayoutProfile`. Pure — no I/O, no defaults that mask input.
85
+ *
86
+ * The set is CLOSED and fail-closed: a config file always exists (every runtime
87
+ * declares `runtimeConfigFile`), and every credential-bearing / instruction path
88
+ * the profile exposes is protected. A runtime with no file-based sub-agent
89
+ * surface (`agentBundlesDir === null`, e.g. Hermes) contributes no agent-def
90
+ * glob — there is no such directory to protect.
91
+ */
92
+ export function deriveWorkspaceSandboxProtection(
93
+ profile: RuntimeLayoutProfile,
94
+ ): WorkspaceSandboxProtection {
95
+ const configFile = profile.runtimeConfigFile;
96
+ const mcpFile = profile.mcpConfigFile;
97
+
98
+ // ── (a) Secret READ deny ─────────────────────────────────────────────────
99
+ // The credential-bearing config file + (if separate) the bearer-header MCP
100
+ // file. Nothing else is read-denied — the agent must still read its own
101
+ // instructions + workspace code.
102
+ const secretReadDenyGlobs: string[] = [configFile];
103
+ if (mcpFile !== null) {
104
+ secretReadDenyGlobs.push(mcpFile);
105
+ }
106
+
107
+ // ── (b) Own-instruction / agent-def EDIT deny ────────────────────────────
108
+ const protectedEditDenyGlobs: string[] = [
109
+ // The auto-loaded context file (AGENTS.md / CLAUDE.md) IS the agent's own
110
+ // system instructions.
111
+ profile.contextFile,
112
+ // Defense in depth: never let the agent rewrite its own credential config
113
+ // (e.g. to strip its own deny rules or exfiltrate the token).
114
+ configFile,
115
+ ...XEMA_PRIVATE_INSTRUCTION_EDIT_GLOBS,
116
+ ];
117
+ if (mcpFile !== null) {
118
+ protectedEditDenyGlobs.push(mcpFile);
119
+ }
120
+ if (profile.agentBundlesDir !== null) {
121
+ // The whole file-based sub-agent-definition slot.
122
+ protectedEditDenyGlobs.push(`${profile.agentBundlesDir}/**`);
123
+ }
124
+
125
+ return {
126
+ secretReadDenyGlobs: dedupeStable(secretReadDenyGlobs),
127
+ protectedEditDenyGlobs: dedupeStable(protectedEditDenyGlobs),
128
+ };
129
+ }
@@ -8,6 +8,7 @@
8
8
  // ═══════════════════════════════════════════════════════════════════════════
9
9
 
10
10
  export * from './lib/enums';
11
+ export * from './lib/workflow-control';
11
12
  export * from './lib/temporal-namespace';
12
13
  export * from './lib/platform-task-queue';
13
14
  export * from './lib/errors';
@@ -263,6 +263,14 @@ export enum BuiltInActionId {
263
263
  HTTP = 'xema/http',
264
264
  WAIT = 'xema/wait',
265
265
  EMIT_ARTIFACT = 'xema/emit-artifact',
266
+ /**
267
+ * Pull a declared external ingestion source into its local catalog — the
268
+ * generic runtime counterpart of a `ContributionKind.IngestionSource`
269
+ * declaration. Any sub-app that materializes an external dataset on a cadence
270
+ * dispatches this action; the caller resolves which source to pull. The
271
+ * implementation ships later in workflow-runtime-worker.
272
+ */
273
+ INGEST_SOURCE = 'xema/ingest-source',
266
274
  ENDPOINT_FETCH = 'xema/endpoint-fetch',
267
275
  SCM_POST_REVIEW = 'software-dev/scm-post-review',
268
276
  DISPATCH_WORKFLOW = 'xema/dispatch-workflow',
@@ -64,6 +64,51 @@ export const ModelClassSchema = z.enum(
64
64
  MODEL_CLASS_VALUES as readonly [ModelClass, ...ModelClass[]],
65
65
  );
66
66
 
67
+ /**
68
+ * A model's INTRINSIC capability / modality — what a concrete model can do
69
+ * regardless of which routing tier ({@link ModelClass}) selected it. The
70
+ * SINGLE source of truth for the closed set
71
+ * `vision | long_context | reasoning | function_calling | audio | embedding`.
72
+ *
73
+ * Consumed as a first-class dimension by the Model Resolution Matrix
74
+ * (`ResolutionDimension.ModelCapability` in `@xemahq/kernel-contracts/agent-composition`),
75
+ * so an org can route "any invocation that needs vision" to a capable model
76
+ * without pinning a provider. Domain-agnostic: describes the model, never a
77
+ * business use-case.
78
+ */
79
+ export enum ModelCapability {
80
+ /** Accepts image input (multimodal vision). */
81
+ VISION = 'vision',
82
+ /** Supports an extended context window beyond the baseline tier. */
83
+ LONG_CONTEXT = 'long_context',
84
+ /** Exposes explicit step-by-step / extended-thinking reasoning. */
85
+ REASONING = 'reasoning',
86
+ /** Supports structured tool / function calling. */
87
+ FUNCTION_CALLING = 'function_calling',
88
+ /** Accepts or produces audio. */
89
+ AUDIO = 'audio',
90
+ /** Produces embedding vectors (retrieval / similarity). */
91
+ EMBEDDING = 'embedding',
92
+ }
93
+
94
+ export const MODEL_CAPABILITY_VALUES: readonly ModelCapability[] = [
95
+ ModelCapability.VISION,
96
+ ModelCapability.LONG_CONTEXT,
97
+ ModelCapability.REASONING,
98
+ ModelCapability.FUNCTION_CALLING,
99
+ ModelCapability.AUDIO,
100
+ ModelCapability.EMBEDDING,
101
+ ];
102
+
103
+ /**
104
+ * Zod schema for {@link ModelCapability}. Built from the enum so the literal
105
+ * list lives in exactly one place. Use this in any YAML/wire schema that
106
+ * needs to validate a model-capability field.
107
+ */
108
+ export const ModelCapabilitySchema = z.enum(
109
+ MODEL_CAPABILITY_VALUES as readonly [ModelCapability, ...ModelCapability[]],
110
+ );
111
+
67
112
  export enum ModelRefKind {
68
113
  CONCRETE = 'concrete',
69
114
  STRATEGY = 'strategy',
@@ -0,0 +1,198 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // ── Governed Workflow-Control Contracts ──
3
+ //
4
+ // Wire contracts for the least-privilege "workflow-control" surface: an
5
+ // in-session agent invoking its OWN governing workflow run's control signals
6
+ // (decide a decision gate, answer a pending inquiry, change run status) as a
7
+ // governed tool.
8
+ //
9
+ // The agent NEVER supplies the target run or org — those are server-derived
10
+ // from the verified session/token and injected by the mcp-gateway bridge into
11
+ // `runId` / `orgId`. This file carries the closed action set, the settable
12
+ // run-status subset, the typed rejection codes, the engine-internal request
13
+ // shape, and the first-class structured accepted/rejected RESULT.
14
+ //
15
+ // The RESULT is deliberately a structured discriminated union (never an opaque
16
+ // 4xx) so the calling model reads a typed outcome rather than mis-parsing an
17
+ // HTTP error. The engine-internal control endpoint returns HTTP 200 carrying
18
+ // this shape for every BUSINESS outcome (accepted or rejected); only genuine
19
+ // server faults surface as non-2xx.
20
+ //
21
+ // Shared between workflow-engine-api (the control surface) and mcp-gateway-api
22
+ // (the governed tool). Runtime-framework-agnostic — no NestJS, no Prisma.
23
+ // ═══════════════════════════════════════════════════════════════════════════
24
+
25
+ import { z } from 'zod';
26
+
27
+ import { DecisionVerdict } from '../../decision-gate/lib/decision-gate';
28
+
29
+ /**
30
+ * The closed set of control actions an agent may take against its own
31
+ * governing run. Reuses the platform's existing signal/gate/inquiry
32
+ * mechanisms — no second control path.
33
+ *
34
+ * - `DECIDE_GATE` — submit a decision-gate verdict (approve/reject/abstain)
35
+ * on a `DECISION_GATE` inquiry belonging to the run.
36
+ * - `ANSWER_INQUIRY` — submit a reply to a pending non-gate inquiry
37
+ * (`AGENT_TOOL_INQUIRY` / `REVIEW`) belonging to the run.
38
+ * - `SET_RUN_STATUS` — request a run-status transition (pause / resume /
39
+ * cancel) via the existing run signal.
40
+ *
41
+ * Phase refine/restart is intentionally absent: the engine ships no
42
+ * server-side phase-refinement mechanism to reuse today (the run "redo"
43
+ * affordance is UI-metadata only). Adding it is a coordinated engine+worker
44
+ * change plus a member here — not a silent no-op action.
45
+ */
46
+ export enum WorkflowControlAction {
47
+ DECIDE_GATE = 'decide_gate',
48
+ ANSWER_INQUIRY = 'answer_inquiry',
49
+ SET_RUN_STATUS = 'set_run_status',
50
+ }
51
+
52
+ /**
53
+ * The settable run-status transitions an agent may request. A closed subset of
54
+ * `WorkflowRunStatus` — only the values that map 1:1 onto an existing run
55
+ * signal. The engine translates each to the corresponding signal:
56
+ *
57
+ * PAUSED → `pauseRun`
58
+ * RUNNING → `resumeRun`
59
+ * CANCELLED → `cancelRun`
60
+ *
61
+ * Terminal statuses (succeeded/failed/…) are NOT settable — a run reaches
62
+ * those only through its own execution, never an agent control signal.
63
+ */
64
+ export enum WorkflowRunStatusTarget {
65
+ PAUSED = 'paused',
66
+ RUNNING = 'running',
67
+ CANCELLED = 'cancelled',
68
+ }
69
+
70
+ /**
71
+ * Typed rejection codes for the structured control RESULT. Closed set — the
72
+ * calling model branches on these, so free-form strings are forbidden. Each
73
+ * corresponds to a pre-validation against live run/gate/inquiry state.
74
+ */
75
+ export enum WorkflowControlRejectionCode {
76
+ /** The calling session is not bound to any governing workflow run. */
77
+ NO_GOVERNING_RUN = 'NO_GOVERNING_RUN',
78
+ /** The request failed contract validation before any state was touched. */
79
+ INVALID_REQUEST = 'INVALID_REQUEST',
80
+ /** The bound run does not exist for the session's org (uniform anti-probe). */
81
+ RUN_NOT_FOUND = 'RUN_NOT_FOUND',
82
+ /** The run is in a terminal state and accepts no control signal. */
83
+ RUN_ALREADY_TERMINAL = 'RUN_ALREADY_TERMINAL',
84
+ /** The run's execution rejected the requested signal. */
85
+ RUN_SIGNAL_REJECTED = 'RUN_SIGNAL_REJECTED',
86
+ /** The referenced inquiry does not exist for the session's org. */
87
+ INQUIRY_NOT_FOUND = 'INQUIRY_NOT_FOUND',
88
+ /** The referenced inquiry does not belong to the session's governing run. */
89
+ INQUIRY_NOT_ON_RUN = 'INQUIRY_NOT_ON_RUN',
90
+ /** The inquiry kind does not match the action (gate vs non-gate). */
91
+ INQUIRY_WRONG_KIND = 'INQUIRY_WRONG_KIND',
92
+ /** The gate is no longer pending and accepts no further verdicts. */
93
+ GATE_NOT_PENDING = 'GATE_NOT_PENDING',
94
+ /** The inquiry is no longer pending and accepts no further replies. */
95
+ INQUIRY_NOT_PENDING = 'INQUIRY_NOT_PENDING',
96
+ /** No recipient with the given id exists on the inquiry. */
97
+ RECIPIENT_NOT_FOUND = 'RECIPIENT_NOT_FOUND',
98
+ /** The targeted recipient is not an AGENT recipient — an agent tool may
99
+ * only reply as an agent recipient, never forge a human/endpoint verdict. */
100
+ RECIPIENT_NOT_AGENT = 'RECIPIENT_NOT_AGENT',
101
+ /** The targeted recipient has already replied (idempotent-safe rejection). */
102
+ RECIPIENT_ALREADY_REPLIED = 'RECIPIENT_ALREADY_REPLIED',
103
+ }
104
+
105
+ // ─── Request (engine-internal wire shape) ────────────────────────────────────
106
+ //
107
+ // `runId` + `orgId` are the SERVER-derived tenancy/subject binding injected by
108
+ // the gateway from the verified session token — they are part of the wire
109
+ // shape the engine validates, but the AGENT never supplies them.
110
+
111
+ const RunBindingShape = {
112
+ /** The governing run, injected server-side from the session — not agent input. */
113
+ runId: z.string().min(1),
114
+ /** The session's org, injected server-side from the token — not agent input. */
115
+ orgId: z.string().min(1),
116
+ } as const;
117
+
118
+ const DecideGateRequestSchema = z.object({
119
+ ...RunBindingShape,
120
+ action: z.literal(WorkflowControlAction.DECIDE_GATE),
121
+ /** The DECISION_GATE inquiry to decide (must belong to the governing run). */
122
+ inquiryId: z.string().min(1),
123
+ /** The agent recipient row on the inquiry the caller replies AS. */
124
+ recipientId: z.string().min(1),
125
+ verdict: z.nativeEnum(DecisionVerdict),
126
+ /** Required (non-empty) when verdict is REJECT — enforced downstream by the
127
+ * reused `DecisionGateReplySchema`. */
128
+ reason: z.string().optional(),
129
+ });
130
+
131
+ const AnswerInquiryRequestSchema = z.object({
132
+ ...RunBindingShape,
133
+ action: z.literal(WorkflowControlAction.ANSWER_INQUIRY),
134
+ /** The non-gate inquiry to answer (must belong to the governing run). */
135
+ inquiryId: z.string().min(1),
136
+ /** The agent recipient row on the inquiry the caller replies AS. */
137
+ recipientId: z.string().min(1),
138
+ /** Kind-specific reply payload — validated by the inquiry kind's own schema. */
139
+ payload: z.record(z.string(), z.unknown()),
140
+ });
141
+
142
+ const SetRunStatusRequestSchema = z.object({
143
+ ...RunBindingShape,
144
+ action: z.literal(WorkflowControlAction.SET_RUN_STATUS),
145
+ status: z.nativeEnum(WorkflowRunStatusTarget),
146
+ reason: z.string().optional(),
147
+ });
148
+
149
+ /**
150
+ * The engine-internal control request. Discriminated on `action`; every member
151
+ * carries the server-derived `runId` + `orgId` binding.
152
+ */
153
+ export const WorkflowControlRequestSchema = z.discriminatedUnion('action', [
154
+ DecideGateRequestSchema,
155
+ AnswerInquiryRequestSchema,
156
+ SetRunStatusRequestSchema,
157
+ ]);
158
+ export type WorkflowControlRequest = z.infer<typeof WorkflowControlRequestSchema>;
159
+
160
+ export type DecideGateRequest = z.infer<typeof DecideGateRequestSchema>;
161
+ export type AnswerInquiryRequest = z.infer<typeof AnswerInquiryRequestSchema>;
162
+ export type SetRunStatusRequest = z.infer<typeof SetRunStatusRequestSchema>;
163
+
164
+ // ─── Result (first-class structured accepted/rejected outcome) ───────────────
165
+
166
+ export const WorkflowControlResultSchema = z.discriminatedUnion('ok', [
167
+ z.object({
168
+ ok: z.literal(true),
169
+ /** Optional human-readable note (e.g. which signal was sent). */
170
+ detail: z.string().optional(),
171
+ }),
172
+ z.object({
173
+ ok: z.literal(false),
174
+ code: z.nativeEnum(WorkflowControlRejectionCode),
175
+ message: z.string().min(1),
176
+ }),
177
+ ]);
178
+ export type WorkflowControlResult = z.infer<typeof WorkflowControlResultSchema>;
179
+
180
+ /** Construct an accepted result. */
181
+ export function workflowControlAccepted(detail?: string): WorkflowControlResult {
182
+ return detail === undefined ? { ok: true } : { ok: true, detail };
183
+ }
184
+
185
+ /** Construct a typed rejection result. */
186
+ export function workflowControlRejected(
187
+ code: WorkflowControlRejectionCode,
188
+ message: string,
189
+ ): WorkflowControlResult {
190
+ return { ok: false, code, message };
191
+ }
192
+
193
+ /**
194
+ * The canonical capability ref governing this surface. `workflow` is an
195
+ * existing first-party capability domain; the router dispatches this ref to the
196
+ * engine's internal control endpoint and authorization-api gates it by grant.
197
+ */
198
+ export const WORKFLOW_CONTROL_CAPABILITY_REF = 'workflow:control@1';