@noodleseed/one 0.137.0 → 0.138.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 (123) hide show
  1. package/dist/commands/assistant-ops.d.ts.map +1 -1
  2. package/dist/commands/assistant-ops.js +30 -1
  3. package/dist/commands/assistant-ops.js.map +1 -1
  4. package/dist/commands/deploy-first-flow.d.ts +2 -0
  5. package/dist/commands/deploy-first-flow.d.ts.map +1 -1
  6. package/dist/commands/deploy-first-flow.js +46 -14
  7. package/dist/commands/deploy-first-flow.js.map +1 -1
  8. package/dist/commands/deploy-ops.d.ts.map +1 -1
  9. package/dist/commands/deploy-ops.js +27 -1
  10. package/dist/commands/deploy-ops.js.map +1 -1
  11. package/dist/commands/deploy-preflight.d.ts +1 -0
  12. package/dist/commands/deploy-preflight.d.ts.map +1 -1
  13. package/dist/commands/deploy-preflight.js +1 -0
  14. package/dist/commands/deploy-preflight.js.map +1 -1
  15. package/dist/commands/deploy-status-ops.d.ts +1 -0
  16. package/dist/commands/deploy-status-ops.d.ts.map +1 -1
  17. package/dist/commands/deploy-status-ops.js +59 -4
  18. package/dist/commands/deploy-status-ops.js.map +1 -1
  19. package/dist/commands/deployments-ops.d.ts.map +1 -1
  20. package/dist/commands/deployments-ops.js +2 -0
  21. package/dist/commands/deployments-ops.js.map +1 -1
  22. package/dist/commands/diagnostics-ops.d.ts +1 -0
  23. package/dist/commands/diagnostics-ops.d.ts.map +1 -1
  24. package/dist/commands/diagnostics-ops.js +3 -0
  25. package/dist/commands/diagnostics-ops.js.map +1 -1
  26. package/dist/deploy.d.ts +3 -0
  27. package/dist/deploy.d.ts.map +1 -1
  28. package/dist/deploy.js +4 -0
  29. package/dist/deploy.js.map +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/index.js.map +1 -1
  34. package/node_modules/@noodle-borg/admission-limits/dist/envelope.js +1 -1
  35. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
  36. package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +1 -0
  37. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +6 -7
  38. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  39. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +6 -2
  40. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +5 -2
  41. package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.js +37 -0
  42. package/node_modules/@noodle-borg/assistant-gateway/dist/model-runtime.js +4 -0
  43. package/node_modules/@noodle-borg/{service/dist/assistant-model-stream.js → assistant-gateway/dist/model-stream.js} +34 -2
  44. package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.d.ts +3 -0
  45. package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.js +2 -1
  46. package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.d.ts +4 -1
  47. package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.js +2 -1
  48. package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.d.ts +6 -1
  49. package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.js +11 -5
  50. package/node_modules/@noodle-borg/assistant-gateway/package.json +5 -1
  51. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +10 -2
  52. package/node_modules/@noodle-borg/authoring/dist/assistant.js +4 -0
  53. package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -1
  54. package/node_modules/@noodle-borg/authoring/dist/index.js +1 -1
  55. package/node_modules/@noodle-borg/authoring/dist/load-authored.js +1 -1
  56. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-assistant.js +17 -0
  57. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-hosted-deployment.js +14 -0
  58. package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +1 -7
  59. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +20 -8
  60. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +12 -8
  61. package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -0
  62. package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
  63. package/node_modules/@noodle-borg/control-plane/dist/rollback-deployment.d.ts +61 -0
  64. package/node_modules/@noodle-borg/{service/dist/operations → control-plane/dist}/rollback-deployment.js +3 -2
  65. package/node_modules/@noodle-borg/control-plane/dist/rollback-result.d.ts +19 -0
  66. package/node_modules/@noodle-borg/control-plane/package.json +3 -1
  67. package/node_modules/@noodle-borg/developer-mcp/dist/contracts.js +4 -0
  68. package/node_modules/@noodle-borg/developer-mcp/dist/widgets/app-overview.js +1 -1
  69. package/node_modules/@noodle-borg/developer-mcp/dist/widgets/deployment-detail.js +1 -1
  70. package/node_modules/@noodle-borg/module/dist/contract.d.ts +1 -0
  71. package/node_modules/@noodle-borg/module/dist/deployment-automation.d.ts +1 -1
  72. package/node_modules/@noodle-borg/module/dist/endpoint-url.d.ts +11 -0
  73. package/node_modules/@noodle-borg/module/dist/endpoint-url.js +20 -0
  74. package/node_modules/@noodle-borg/module/dist/index.d.ts +1 -0
  75. package/node_modules/@noodle-borg/module/dist/index.js +1 -0
  76. package/node_modules/@noodle-borg/observability/dist/assistant-usage.js +217 -0
  77. package/node_modules/@noodle-borg/observability/dist/index.js +2 -0
  78. package/node_modules/@noodle-borg/{service → observability}/dist/request-event-metrics.js +5 -2
  79. package/node_modules/@noodle-borg/observability/dist/request-events-postgres.js +6 -0
  80. package/node_modules/@noodle-borg/observability/dist/request-events.js +2 -0
  81. package/node_modules/@noodle-borg/service/dist/assistant-bindings.js +3 -1
  82. package/node_modules/@noodle-borg/service/dist/developer-mcp/control-plane-adapter.js +3 -3
  83. package/node_modules/@noodle-borg/service/dist/developer-mcp/inspection.js +2 -1
  84. package/node_modules/@noodle-borg/service/dist/registry-access.js +73 -24
  85. package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +5 -2
  86. package/node_modules/@noodle-borg/service/dist/registry-helpers.d.ts +5 -9
  87. package/node_modules/@noodle-borg/service/dist/registry-helpers.js +4 -18
  88. package/node_modules/@noodle-borg/service/dist/registry-rollback.js +3 -1
  89. package/node_modules/@noodle-borg/service/dist/registry-status.js +4 -1
  90. package/node_modules/@noodle-borg/service/dist/registry-targets.js +4 -4
  91. package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +11 -16
  92. package/node_modules/@noodle-borg/service/dist/registry.d.ts +8 -5
  93. package/node_modules/@noodle-borg/service/dist/registry.js +8 -5
  94. package/node_modules/@noodle-borg/service/dist/routes/access.js +19 -1
  95. package/node_modules/@noodle-borg/service/dist/routes/analytics-dispatch.js +17 -1
  96. package/node_modules/@noodle-borg/service/dist/routes/analytics.js +1 -1
  97. package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +73 -59
  98. package/node_modules/@noodle-borg/service/dist/routes/assistant-doctor.js +1 -1
  99. package/node_modules/@noodle-borg/service/dist/routes/assistant-model-binding.js +28 -0
  100. package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +18 -0
  101. package/node_modules/@noodle-borg/service/dist/routes/assistant-public-turn.js +6 -0
  102. package/node_modules/@noodle-borg/service/dist/routes/assistant-usage.js +42 -0
  103. package/node_modules/@noodle-borg/service/dist/routes/assistant.js +20 -3
  104. package/node_modules/@noodle-borg/service/dist/routes/auth-doctor-live.js +1 -2
  105. package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +49 -4
  106. package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +11 -1
  107. package/node_modules/@noodle-borg/service/dist/routes/endpoint-enrichment.js +2 -1
  108. package/node_modules/@noodle-borg/service/dist/routes/paths.js +4 -0
  109. package/node_modules/@noodle-borg/service/dist/routes/rollback.js +1 -1
  110. package/node_modules/@noodle-borg/service/dist/service.js +6 -0
  111. package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +2 -5
  112. package/node_modules/@noodle-borg/service/dist/store/in-memory.js +9 -21
  113. package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +2 -5
  114. package/node_modules/@noodle-borg/service/dist/store/json-file.js +8 -20
  115. package/node_modules/@noodle-borg/service/dist/store/records.js +39 -0
  116. package/node_modules/@noodle-borg/service/dist/store.d.ts +21 -10
  117. package/node_modules/@noodle-borg/service/package.json +5 -1
  118. package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.d.ts +120 -0
  119. package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.js +123 -1
  120. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +4 -0
  121. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +15 -1
  122. package/node_modules/@noodleseed/assistant/package.json +1 -1
  123. package/package.json +2 -2
@@ -0,0 +1,37 @@
1
+ import { readModelCompletion } from './model-stream.js';
2
+ export async function requestModelCompletion(input) {
3
+ const { binding } = input;
4
+ const url = new URL(`${binding.baseUrl.replace(/\/$/, '')}/chat/completions`);
5
+ if (url.protocol !== 'https:' || url.username || url.password) {
6
+ throw new Error('unsafe model endpoint');
7
+ }
8
+ const completionLimit = input.maxCompletionTokens ?? binding.requestPolicy?.maxCompletionTokens;
9
+ const body = JSON.stringify({
10
+ ...binding.requestPolicy?.extraBody,
11
+ model: binding.model,
12
+ stream: true,
13
+ messages: input.messages,
14
+ tools: input.tools,
15
+ ...(completionLimit === undefined ? {} : { max_completion_tokens: completionLimit }),
16
+ });
17
+ const requestBytes = new TextEncoder().encode(body).byteLength;
18
+ const maxRequestBytes = binding.requestPolicy?.maxRequestBytes;
19
+ if (maxRequestBytes !== undefined && requestBytes > maxRequestBytes) {
20
+ throw new Error('model request too large');
21
+ }
22
+ const bearerToken = binding.apiKey ?? (await binding.bearerToken());
23
+ if (!/^[^\s]{1,8192}$/.test(bearerToken))
24
+ throw new Error('invalid model bearer token');
25
+ const timeoutSignal = AbortSignal.timeout(binding.requestPolicy?.timeoutMs ?? 30_000);
26
+ const response = await input.fetcher(url.href, {
27
+ method: 'POST',
28
+ headers: { Authorization: `Bearer ${bearerToken}`, 'content-type': 'application/json' },
29
+ body,
30
+ redirect: 'manual',
31
+ signal: input.signal === undefined ? timeoutSignal : AbortSignal.any([input.signal, timeoutSignal]),
32
+ });
33
+ if (!response.ok)
34
+ throw new Error(`model request failed (${response.status})`);
35
+ return readModelCompletion(response, input.onContent ?? (() => undefined), input.maxResponseBytes);
36
+ }
37
+ //# sourceMappingURL=model-request.js.map
@@ -0,0 +1,4 @@
1
+ /** Provider-neutral embedded-assistant model transport. */
2
+ export * from './model-request.js';
3
+ export * from './model-stream.js';
4
+ //# sourceMappingURL=model-runtime.js.map
@@ -2,7 +2,11 @@ export async function readModelCompletion(response, onContent, maxBytes = 1 << 2
2
2
  const contentType = response.headers.get('content-type') ?? '';
3
3
  if (!contentType.includes('text/event-stream')) {
4
4
  const text = await readBoundedText(response, maxBytes);
5
- const completion = JSON.parse(text);
5
+ const parsed = JSON.parse(text);
6
+ const completion = {
7
+ choices: parsed.choices,
8
+ ...normalizeUsage(parsed.usage),
9
+ };
6
10
  const message = completion.choices[0]?.message;
7
11
  if (!message?.tool_calls?.length && message?.content)
8
12
  onContent(message.content);
@@ -15,6 +19,7 @@ export async function readModelCompletion(response, onContent, maxBytes = 1 << 2
15
19
  const toolCalls = new Map();
16
20
  let pending = '';
17
21
  let content = '';
22
+ let usage;
18
23
  let bytes = 0;
19
24
  while (true) {
20
25
  const { done, value } = await reader.read();
@@ -36,6 +41,7 @@ export async function readModelCompletion(response, onContent, maxBytes = 1 << 2
36
41
  const parsed = JSON.parse(data);
37
42
  if (parsed.error !== undefined)
38
43
  throw new Error('model stream returned an error');
44
+ usage = normalizeUsageValue(parsed.usage) ?? usage;
39
45
  const delta = parsed.choices?.[0]?.delta;
40
46
  if (delta?.content) {
41
47
  content += delta.content;
@@ -80,8 +86,34 @@ export async function readModelCompletion(response, onContent, maxBytes = 1 << 2
80
86
  },
81
87
  },
82
88
  ],
89
+ ...(usage === undefined ? {} : { usage }),
83
90
  };
84
91
  }
92
+ function normalizeUsage(usage) {
93
+ const normalized = normalizeUsageValue(usage);
94
+ return normalized === undefined ? {} : { usage: normalized };
95
+ }
96
+ function normalizeUsageValue(usage) {
97
+ if (usage === undefined)
98
+ return undefined;
99
+ const promptTokens = nonNegativeInteger(usage.prompt_tokens);
100
+ const completionTokens = nonNegativeInteger(usage.completion_tokens);
101
+ const reportedTotalTokens = nonNegativeInteger(usage.total_tokens);
102
+ if (promptTokens === undefined || completionTokens === undefined) {
103
+ return undefined;
104
+ }
105
+ const totalTokens = reportedTotalTokens ?? promptTokens + completionTokens;
106
+ const reasoningTokens = nonNegativeInteger(usage.completion_tokens_details?.reasoning_tokens);
107
+ return {
108
+ promptTokens,
109
+ completionTokens,
110
+ totalTokens,
111
+ ...(reasoningTokens === undefined ? {} : { reasoningTokens }),
112
+ };
113
+ }
114
+ function nonNegativeInteger(value) {
115
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
116
+ }
85
117
  async function readBoundedText(response, maxBytes) {
86
118
  const text = await response.text();
87
119
  if (new TextEncoder().encode(text).byteLength > maxBytes) {
@@ -89,4 +121,4 @@ async function readBoundedText(response, maxBytes) {
89
121
  }
90
122
  return text;
91
123
  }
92
- //# sourceMappingURL=assistant-model-stream.js.map
124
+ //# sourceMappingURL=model-stream.js.map
@@ -1,6 +1,7 @@
1
1
  import type { AdmissionEnvelope, DailyCounterStore } from '@noodle-borg/admission-limits/portable';
2
2
  import { type PublicEmbedRecord, type PublicEmbedStore } from './embed-store.js';
3
3
  import type { PublicSurface } from './public-surface.js';
4
+ import { type SurfaceBudgetBounds } from './surface-budget.js';
4
5
  /**
5
6
  * Minting an anonymous session for a public page.
6
7
  *
@@ -17,6 +18,8 @@ export interface PublicSessionPorts {
17
18
  readonly counters: DailyCounterStore;
18
19
  /** The live surface for this tenant, read from the *active* deployment at mint time. */
19
20
  resolveActiveSurface(embed: PublicEmbedRecord): Promise<PublicSurface | undefined>;
21
+ /** Optional hosted entitlement; absent preserves the ordinary customer-funded envelope. */
22
+ readonly resolveBudgetBounds?: (embed: PublicEmbedRecord) => Promise<SurfaceBudgetBounds | undefined>;
20
23
  /** Persist the anonymous session and return its bearer token and expiry. */
21
24
  createSession(input: {
22
25
  readonly embed: PublicEmbedRecord;
@@ -30,7 +30,8 @@ export async function mintPublicSession(request, envelope, ports) {
30
30
  }
31
31
  // The surface's own budget, which an operator may have lowered — or set to zero, which is the kill
32
32
  // switch. Resolved after the embed is known and before anything is spent.
33
- const surfaceLimits = surfaceEnvelope(envelope, embed);
33
+ const budgetBounds = await ports.resolveBudgetBounds?.(embed);
34
+ const surfaceLimits = surfaceEnvelope(envelope, embed, budgetBounds);
34
35
  const now = ports.now();
35
36
  // Fairness before solvency: an address at its hourly ceiling is refused without spending any of the
36
37
  // surface's day, so one visitor cannot burn the customer's budget on refusals.
@@ -1,6 +1,7 @@
1
1
  import type { AdmissionEnvelope, DailyCounterStore } from '@noodle-borg/admission-limits/portable';
2
2
  import type { AssistantTurnConsumption } from './assistant-store.js';
3
- import type { PublicEmbedStore } from './embed-store.js';
3
+ import type { PublicEmbedRecord, PublicEmbedStore } from './embed-store.js';
4
+ import { type SurfaceBudgetBounds } from './surface-budget.js';
4
5
  /**
5
6
  * Whether an anonymous visitor's next turn runs.
6
7
  *
@@ -16,6 +17,8 @@ export interface PublicTurnPorts {
16
17
  readonly counters: DailyCounterStore;
17
18
  /** Re-read per turn: it carries the operator's live budget, and whether the surface still exists. */
18
19
  readonly embeds: Pick<PublicEmbedStore, 'lookup'>;
20
+ /** Optional hosted entitlement; resolved live so cohort removal lowers the envelope immediately. */
21
+ readonly resolveBudgetBounds?: (embed: PublicEmbedRecord) => Promise<SurfaceBudgetBounds | undefined>;
19
22
  /** Spend one of this session's turns, atomically (admission tier 1). */
20
23
  consumeTurn(id: string, limit: number): Promise<AssistantTurnConsumption>;
21
24
  now(): Date;
@@ -22,7 +22,8 @@ export async function admitPublicTurn(request, defaults, ports) {
22
22
  message: 'assistant is unavailable right now',
23
23
  };
24
24
  }
25
- const envelope = surfaceEnvelope(defaults, embed);
25
+ const budgetBounds = await ports.resolveBudgetBounds?.(embed);
26
+ const envelope = surfaceEnvelope(defaults, embed, budgetBounds);
26
27
  // Fairness before either of the budgets below: an address at its hourly ceiling is turned away
27
28
  // without spending the session's allowance or the customer's day, so one visitor cannot burn either
28
29
  // on turns that were never served. Mirrors the ordering the mint uses.
@@ -1,5 +1,10 @@
1
1
  import { type AdmissionEnvelope } from '@noodle-borg/admission-limits/portable';
2
2
  import type { PublicEmbedRecord } from './embed-store.js';
3
+ /** Hosted/operator entitlement layered between structural maxima and per-surface overrides. */
4
+ export interface SurfaceBudgetBounds {
5
+ readonly defaults: Pick<AdmissionEnvelope, 'turnsPerSession' | 'turnsPerDay' | 'mintsPerDay'>;
6
+ readonly ceiling: Pick<AdmissionEnvelope, 'turnsPerDay' | 'mintsPerDay'>;
7
+ }
3
8
  /**
4
9
  * One surface's daily budget, resolved from the operator's override and the deployment's defaults.
5
10
  *
@@ -12,5 +17,5 @@ import type { PublicEmbedRecord } from './embed-store.js';
12
17
  * operator any more than by app code, so an over-eager `--turns-per-day 999999999` degrades to the
13
18
  * ceiling instead of becoming the ceiling. Zero survives clamping and is the kill switch.
14
19
  */
15
- export declare function surfaceEnvelope(base: AdmissionEnvelope, embed: Pick<PublicEmbedRecord, 'turnsPerDay' | 'mintsPerDay'>): AdmissionEnvelope;
20
+ export declare function surfaceEnvelope(base: AdmissionEnvelope, embed: Pick<PublicEmbedRecord, 'turnsPerDay' | 'mintsPerDay'>, bounds?: SurfaceBudgetBounds): AdmissionEnvelope;
16
21
  //# sourceMappingURL=surface-budget.d.ts.map
@@ -11,13 +11,19 @@ import { clamp } from '@noodle-borg/admission-limits/portable';
11
11
  * operator any more than by app code, so an over-eager `--turns-per-day 999999999` degrades to the
12
12
  * ceiling instead of becoming the ceiling. Zero survives clamping and is the kill switch.
13
13
  */
14
- export function surfaceEnvelope(base, embed) {
15
- if (embed.turnsPerDay === undefined && embed.mintsPerDay === undefined)
16
- return base;
17
- return clamp({
18
- ...base,
14
+ export function surfaceEnvelope(base, embed, bounds) {
15
+ const effectiveBase = bounds === undefined ? base : clamp({ ...base, ...bounds.defaults });
16
+ const requested = clamp({
17
+ ...effectiveBase,
19
18
  ...(embed.turnsPerDay !== undefined ? { turnsPerDay: embed.turnsPerDay } : {}),
20
19
  ...(embed.mintsPerDay !== undefined ? { mintsPerDay: embed.mintsPerDay } : {}),
21
20
  });
21
+ if (bounds === undefined)
22
+ return requested;
23
+ return {
24
+ ...requested,
25
+ turnsPerDay: Math.min(requested.turnsPerDay, bounds.ceiling.turnsPerDay),
26
+ mintsPerDay: Math.min(requested.mintsPerDay, bounds.ceiling.mintsPerDay),
27
+ };
22
28
  }
23
29
  //# sourceMappingURL=surface-budget.js.map
@@ -17,6 +17,10 @@
17
17
  "types": "./dist/portable.d.ts",
18
18
  "import": "./dist/portable.js"
19
19
  },
20
+ "./model-runtime": {
21
+ "types": "./dist/model-runtime.d.ts",
22
+ "import": "./dist/model-runtime.js"
23
+ },
20
24
  "./postgres": {
21
25
  "types": "./dist/postgres.d.ts",
22
26
  "import": "./dist/postgres.js"
@@ -36,7 +40,7 @@
36
40
  "@noodle-borg/module": "0.0.0",
37
41
  "@noodle-borg/protocol": "0.0.0",
38
42
  "@noodle-borg/runtime": "0.0.0",
39
- "@noodleseed/assistant": "1.22.0"
43
+ "@noodleseed/assistant": "1.23.0"
40
44
  },
41
45
  "devDependencies": {
42
46
  "@types/node": "^24.13.1",
@@ -59,6 +59,7 @@ export interface AssistantLabels {
59
59
  readonly copy?: string;
60
60
  readonly newMessages?: string;
61
61
  readonly reconnect?: string;
62
+ readonly newConversation?: string;
62
63
  readonly retry?: string;
63
64
  readonly unavailable?: string;
64
65
  readonly sessionExpired?: string;
@@ -108,6 +109,11 @@ export interface OpenAICompatibleModel {
108
109
  /** Managed secret name, never the secret value. */
109
110
  readonly apiKey: string;
110
111
  }
112
+ /** Provider-neutral request for the model selected and operated by Noodle Seed Cloud. */
113
+ export interface NoodleManagedModel {
114
+ readonly kind: 'noodle-managed';
115
+ }
116
+ export type AssistantModel = OpenAICompatibleModel | NoodleManagedModel;
111
117
  /**
112
118
  * A verified session claim the embedding backend may pass at session exchange. Keys become
113
119
  * `${user.claims.<key>}` in tool fulfilment; `exposeToModel: true` additionally places the value
@@ -184,7 +190,7 @@ export interface PublicWebsiteInput {
184
190
  export declare function authenticatedWebsite(input: AuthenticatedWebsiteInput): AuthenticatedWebsiteAccess;
185
191
  export declare function publicWebsite(input: PublicWebsiteInput): PublicWebsiteAccess;
186
192
  export interface EmbeddedAssistantOptions extends AssistantUiOptions {
187
- readonly model: OpenAICompatibleModel;
193
+ readonly model: AssistantModel;
188
194
  /** One surface, or every front door this assistant serves. */
189
195
  readonly access: AssistantAccess | readonly AssistantAccess[];
190
196
  }
@@ -197,7 +203,7 @@ export interface AssistantSurfaceConfig {
197
203
  readonly sessionClaims?: Readonly<Record<string, SessionClaimDeclaration>>;
198
204
  }
199
205
  export interface EmbeddedAssistantConfig extends AssistantUiOptions {
200
- readonly model: OpenAICompatibleModel;
206
+ readonly model: AssistantModel;
201
207
  readonly surfaces: readonly AssistantSurfaceConfig[];
202
208
  /**
203
209
  * Union of every surface's origins. Kept at the top level so the service's origin check and the
@@ -208,5 +214,7 @@ export interface EmbeddedAssistantConfig extends AssistantUiOptions {
208
214
  readonly sessionClaims?: Readonly<Record<string, SessionClaimDeclaration>>;
209
215
  }
210
216
  export declare function openAICompatible(input: OpenAICompatibleModelInput): OpenAICompatibleModel;
217
+ /** Use Noodle Seed Cloud's operator-selected model. Provider details never enter authored source. */
218
+ export declare function noodleManaged(): NoodleManagedModel;
211
219
  export declare function embeddedAssistant(input: EmbeddedAssistantOptions): EmbeddedAssistantConfig;
212
220
  //# sourceMappingURL=assistant.d.ts.map
@@ -33,6 +33,10 @@ export function openAICompatible(input) {
33
33
  apiKey: serializeSecretRef(input.apiKey, 'assistant.model.apiKey'),
34
34
  };
35
35
  }
36
+ /** Use Noodle Seed Cloud's operator-selected model. Provider details never enter authored source. */
37
+ export function noodleManaged() {
38
+ return { kind: 'noodle-managed' };
39
+ }
36
40
  export function embeddedAssistant(input) {
37
41
  const { model, access, suggestedPrompts, ...ui } = input;
38
42
  assertAccessDeclared(input, access);
@@ -2,7 +2,7 @@ import type { ConnectorFile, HttpConnectorDef } from '@noodle-borg/connector-def
2
2
  import { z } from 'zod';
3
3
  export type { PackagedAssetReference } from '@noodle-borg/compiler';
4
4
  export type { AgentCapabilityKind, AgentCapabilityRef, AgentExampleSource, AgentGuideSource, AgentWorkflowSource, AgentWorkflowStepSource, } from './agent-guide.js';
5
- export { type AssistantAccess, type AssistantCapability, type AssistantLabels, type AssistantLayoutMode, type AssistantPosition, type AssistantPresentationOptions, type AssistantPresentationTone, type AssistantSurfaceConfig, type AssistantSurfaceMode, type AssistantThemeMode, type AssistantUiOptions, type AuthenticatedWebsiteAccess, type AuthenticatedWebsiteInput, authenticatedWebsite, type CapabilityRef, type EmbeddedAssistantConfig, type EmbeddedAssistantOptions, embeddedAssistant, type OpenAICompatibleModel, type OpenAICompatibleModelInput, openAICompatible, type PublicWebsiteAccess, type PublicWebsiteInput, publicWebsite, } from './assistant.js';
5
+ export { type AssistantAccess, type AssistantCapability, type AssistantLabels, type AssistantLayoutMode, type AssistantModel, type AssistantPosition, type AssistantPresentationOptions, type AssistantPresentationTone, type AssistantSurfaceConfig, type AssistantSurfaceMode, type AssistantThemeMode, type AssistantUiOptions, type AuthenticatedWebsiteAccess, type AuthenticatedWebsiteInput, authenticatedWebsite, type CapabilityRef, type EmbeddedAssistantConfig, type EmbeddedAssistantOptions, embeddedAssistant, type NoodleManagedModel, noodleManaged, type OpenAICompatibleModel, type OpenAICompatibleModelInput, openAICompatible, type PublicWebsiteAccess, type PublicWebsiteInput, publicWebsite, } from './assistant.js';
6
6
  export { type ConfigRef, type ConfigRefKind, secret, variable, } from './config.js';
7
7
  export { type BoundConnectorRef, bind, type ConnectionBinding, type ConnectionRef, type ConnectionSource, clientCredentials, connection, externalExchange, type GoogleWorkloadIdentityAccess, googleWorkloadIdentity, managedSecret, } from './connections.js';
8
8
  export { type ComputeHost, type ComputeLimits, type ComputeOperationOptions, ConnectorBuilder, type ConnectorCatalogDoc, type ConnectorOpDefinition, type ConnectorOperationOptions, type ConnectorRef, connector, type HttpConnectorOptions, } from './connectors.js';
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- export { authenticatedWebsite, embeddedAssistant, openAICompatible, publicWebsite, } from './assistant.js';
2
+ export { authenticatedWebsite, embeddedAssistant, noodleManaged, openAICompatible, publicWebsite, } from './assistant.js';
3
3
  export { secret, variable, } from './config.js';
4
4
  export { bind, clientCredentials, connection, externalExchange, googleWorkloadIdentity, managedSecret, } from './connections.js';
5
5
  export { ConnectorBuilder, connector, } from './connectors.js';
@@ -33,7 +33,7 @@ export function prepareAuthoringRuntimeDir(dir, options) {
33
33
  './react': './react.mjs',
34
34
  },
35
35
  }));
36
- writeFileSync(join(self, 'index.mjs'), "export { annotations, asset, authenticatedWebsite, bind, clientCredentials, connector, connection, customerAuth, customerEndpoint, embeddedAssistant, externalExchange, file, gmailConnector, googleWorkloadIdentity, handoffSession, knowledge, managedSecret, openAICompatible, prompt, publicWebsite, resource, secret, server, site, tool, variable, when, z } from '@noodle-borg/authoring';\n");
36
+ writeFileSync(join(self, 'index.mjs'), "export { annotations, asset, authenticatedWebsite, bind, clientCredentials, connector, connection, customerAuth, customerEndpoint, embeddedAssistant, externalExchange, file, gmailConnector, googleWorkloadIdentity, handoffSession, knowledge, managedSecret, noodleManaged, openAICompatible, prompt, publicWebsite, resource, secret, server, site, tool, variable, when, z } from '@noodle-borg/authoring';\n");
37
37
  writeFileSync(join(self, 'platform.mjs'), "export { noodlePlatform, noodlePlatformCatalog } from '@noodle-borg/authoring';\n");
38
38
  writeFileSync(join(self, 'react.mjs'), "export { generateHelpers } from '@noodle-borg/authoring/react';\n");
39
39
  }
@@ -203,6 +203,23 @@ export const ASSISTANT_COMMAND = {
203
203
  CLIENTS_SUBCOMMAND,
204
204
  EMBEDS_SUBCOMMAND,
205
205
  BUDGET_SUBCOMMAND,
206
+ subcommand({
207
+ ...SUBCOMMAND_FIELDS,
208
+ name: 'usage',
209
+ summary: 'Show assistant sessions, conversation depth, engagement, latency, and model tokens.',
210
+ flags: [
211
+ flag({
212
+ ...FLAG_FIELDS,
213
+ name: 'window',
214
+ type: 'string',
215
+ value: '<window>',
216
+ summary: 'Aggregation window.',
217
+ constraints: { choices: ['24h', '7d', '30d'], default: '7d' },
218
+ }),
219
+ ...TENANT_FLAGS,
220
+ ],
221
+ jsonOutput: { mode: 'single' },
222
+ }),
206
223
  subcommand({
207
224
  ...SUBCOMMAND_FIELDS,
208
225
  name: 'embed',
@@ -132,6 +132,13 @@ export const CATALOG_HOSTED_DEPLOYMENT = [
132
132
  summary: 'Identity access mode for the deployment.',
133
133
  constraints: { choices: ACCESS_MODES },
134
134
  }),
135
+ flag({
136
+ ...FLAG_FIELDS,
137
+ name: 'owner-subject',
138
+ type: 'string',
139
+ value: '<subject>',
140
+ summary: 'Exact OAuth subject bound when access is owner-only.',
141
+ }),
135
142
  flag({
136
143
  ...FLAG_FIELDS,
137
144
  name: 'connectors',
@@ -318,6 +325,13 @@ export const CATALOG_HOSTED_DEPLOYMENT = [
318
325
  value: '<version>',
319
326
  summary: 'Server version whose access mode is changed.',
320
327
  }),
328
+ flag({
329
+ ...FLAG_FIELDS,
330
+ name: 'owner-subject',
331
+ type: 'string',
332
+ value: '<subject>',
333
+ summary: 'Exact OAuth subject to bind or transfer for owner-only access.',
334
+ }),
321
335
  ...TENANT_TARGET_FLAGS,
322
336
  ],
323
337
  jsonOutput: { mode: 'single' },
@@ -227,13 +227,7 @@ export interface ArtifactServer {
227
227
  /** Compiled knowledge components (ADR 0202, Core v2): descriptors + generated tool metadata, never bytes. */
228
228
  readonly knowledge?: readonly CompiledKnowledgeComponent[];
229
229
  readonly assistant?: {
230
- readonly model: {
231
- readonly kind: 'openai-compatible';
232
- readonly baseUrl: string;
233
- readonly model: string;
234
- /** Managed secret name, never the secret value. */
235
- readonly apiKey: string;
236
- };
230
+ readonly model: NonNullable<Manifest['server']['assistant']>['model'];
237
231
  readonly allowedOrigins: readonly string[];
238
232
  } & Omit<NonNullable<Manifest['server']['assistant']>, 'model' | 'allowedOrigins'>;
239
233
  readonly branding?: {
@@ -231,6 +231,7 @@ export declare const manifestV1Schema: z.ZodObject<{
231
231
  copy: z.ZodOptional<z.ZodString>;
232
232
  newMessages: z.ZodOptional<z.ZodString>;
233
233
  reconnect: z.ZodOptional<z.ZodString>;
234
+ newConversation: z.ZodOptional<z.ZodString>;
234
235
  retry: z.ZodOptional<z.ZodString>;
235
236
  unavailable: z.ZodOptional<z.ZodString>;
236
237
  sessionExpired: z.ZodOptional<z.ZodString>;
@@ -325,12 +326,14 @@ export declare const manifestV1Schema: z.ZodObject<{
325
326
  ltr: "ltr";
326
327
  rtl: "rtl";
327
328
  }>>;
328
- model: z.ZodObject<{
329
+ model: z.ZodUnion<readonly [z.ZodObject<{
329
330
  kind: z.ZodLiteral<"openai-compatible">;
330
331
  baseUrl: z.ZodString;
331
332
  model: z.ZodString;
332
333
  apiKey: z.ZodString;
333
- }, z.core.$strict>;
334
+ }, z.core.$strict>, z.ZodObject<{
335
+ kind: z.ZodLiteral<"noodle-managed">;
336
+ }, z.core.$strict>]>;
334
337
  allowedOrigins: z.ZodArray<z.ZodString>;
335
338
  surfaces: z.ZodOptional<z.ZodArray<z.ZodObject<{
336
339
  mode: z.ZodEnum<{
@@ -682,6 +685,7 @@ export declare const manifestV2Schema: z.ZodObject<{
682
685
  copy: z.ZodOptional<z.ZodString>;
683
686
  newMessages: z.ZodOptional<z.ZodString>;
684
687
  reconnect: z.ZodOptional<z.ZodString>;
688
+ newConversation: z.ZodOptional<z.ZodString>;
685
689
  retry: z.ZodOptional<z.ZodString>;
686
690
  unavailable: z.ZodOptional<z.ZodString>;
687
691
  sessionExpired: z.ZodOptional<z.ZodString>;
@@ -776,12 +780,14 @@ export declare const manifestV2Schema: z.ZodObject<{
776
780
  ltr: "ltr";
777
781
  rtl: "rtl";
778
782
  }>>;
779
- model: z.ZodObject<{
783
+ model: z.ZodUnion<readonly [z.ZodObject<{
780
784
  kind: z.ZodLiteral<"openai-compatible">;
781
785
  baseUrl: z.ZodString;
782
786
  model: z.ZodString;
783
787
  apiKey: z.ZodString;
784
- }, z.core.$strict>;
788
+ }, z.core.$strict>, z.ZodObject<{
789
+ kind: z.ZodLiteral<"noodle-managed">;
790
+ }, z.core.$strict>]>;
785
791
  allowedOrigins: z.ZodArray<z.ZodString>;
786
792
  surfaces: z.ZodOptional<z.ZodArray<z.ZodObject<{
787
793
  mode: z.ZodEnum<{
@@ -1343,6 +1349,7 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1343
1349
  copy: z.ZodOptional<z.ZodString>;
1344
1350
  newMessages: z.ZodOptional<z.ZodString>;
1345
1351
  reconnect: z.ZodOptional<z.ZodString>;
1352
+ newConversation: z.ZodOptional<z.ZodString>;
1346
1353
  retry: z.ZodOptional<z.ZodString>;
1347
1354
  unavailable: z.ZodOptional<z.ZodString>;
1348
1355
  sessionExpired: z.ZodOptional<z.ZodString>;
@@ -1437,12 +1444,14 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1437
1444
  ltr: "ltr";
1438
1445
  rtl: "rtl";
1439
1446
  }>>;
1440
- model: z.ZodObject<{
1447
+ model: z.ZodUnion<readonly [z.ZodObject<{
1441
1448
  kind: z.ZodLiteral<"openai-compatible">;
1442
1449
  baseUrl: z.ZodString;
1443
1450
  model: z.ZodString;
1444
1451
  apiKey: z.ZodString;
1445
- }, z.core.$strict>;
1452
+ }, z.core.$strict>, z.ZodObject<{
1453
+ kind: z.ZodLiteral<"noodle-managed">;
1454
+ }, z.core.$strict>]>;
1446
1455
  allowedOrigins: z.ZodArray<z.ZodString>;
1447
1456
  surfaces: z.ZodOptional<z.ZodArray<z.ZodObject<{
1448
1457
  mode: z.ZodEnum<{
@@ -1793,6 +1802,7 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1793
1802
  copy: z.ZodOptional<z.ZodString>;
1794
1803
  newMessages: z.ZodOptional<z.ZodString>;
1795
1804
  reconnect: z.ZodOptional<z.ZodString>;
1805
+ newConversation: z.ZodOptional<z.ZodString>;
1796
1806
  retry: z.ZodOptional<z.ZodString>;
1797
1807
  unavailable: z.ZodOptional<z.ZodString>;
1798
1808
  sessionExpired: z.ZodOptional<z.ZodString>;
@@ -1887,12 +1897,14 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1887
1897
  ltr: "ltr";
1888
1898
  rtl: "rtl";
1889
1899
  }>>;
1890
- model: z.ZodObject<{
1900
+ model: z.ZodUnion<readonly [z.ZodObject<{
1891
1901
  kind: z.ZodLiteral<"openai-compatible">;
1892
1902
  baseUrl: z.ZodString;
1893
1903
  model: z.ZodString;
1894
1904
  apiKey: z.ZodString;
1895
- }, z.core.$strict>;
1905
+ }, z.core.$strict>, z.ZodObject<{
1906
+ kind: z.ZodLiteral<"noodle-managed">;
1907
+ }, z.core.$strict>]>;
1896
1908
  allowedOrigins: z.ZodArray<z.ZodString>;
1897
1909
  surfaces: z.ZodOptional<z.ZodArray<z.ZodObject<{
1898
1910
  mode: z.ZodEnum<{
@@ -310,6 +310,7 @@ const assistantUiSchema = z
310
310
  copy: z.string().trim().min(1).max(80).optional(),
311
311
  newMessages: z.string().trim().min(1).max(80).optional(),
312
312
  reconnect: z.string().trim().min(1).max(80).optional(),
313
+ newConversation: z.string().trim().min(1).max(80).optional(),
313
314
  retry: z.string().trim().min(1).max(80).optional(),
314
315
  unavailable: z.string().trim().min(1).max(240).optional(),
315
316
  sessionExpired: z.string().trim().min(1).max(240).optional(),
@@ -343,14 +344,17 @@ const MAX_SERVER_INSTRUCTIONS = 4000;
343
344
  const sessionClaimsSchema = z.record(z.string().regex(/^[A-Za-z][A-Za-z0-9_]{0,63}$/, 'claim keys are identifier-style'), z.object({ exposeToModel: z.boolean().optional() }).strict());
344
345
  const embeddedAssistantSchema = z
345
346
  .object({
346
- model: z
347
- .object({
348
- kind: z.literal('openai-compatible'),
349
- baseUrl: z.string().trim().min(1),
350
- model: z.string().trim().min(1),
351
- apiKey: z.string().regex(/^[A-Za-z0-9_]+$/),
352
- })
353
- .strict(),
347
+ model: z.union([
348
+ z
349
+ .object({
350
+ kind: z.literal('openai-compatible'),
351
+ baseUrl: z.string().trim().min(1),
352
+ model: z.string().trim().min(1),
353
+ apiKey: z.string().regex(/^[A-Za-z0-9_]+$/),
354
+ })
355
+ .strict(),
356
+ z.object({ kind: z.literal('noodle-managed') }).strict(),
357
+ ]),
354
358
  allowedOrigins: z.array(assistantOriginOrVariableSchema).min(1),
355
359
  // The front doors this assistant serves (ADR 0201, amended 2026-08-12). Absent means the pre-0201
356
360
  // authenticated shape, so released artifacts keep their meaning; `allowedOrigins` stays the union
@@ -4,5 +4,7 @@ export { InMemoryControlPlaneStore } from './in-memory-control-plane-store.js';
4
4
  export { bindInMemoryOrganizationStore, type InMemoryOrganizationOperations, InMemoryOrganizationStore, } from './in-memory-organization-store.js';
5
5
  export { InMemoryMcpSubdomainClaimStore, McpSubdomainCooldownError, McpSubdomainIdempotencyConflictError, McpSubdomainOwnerRequiredError, type McpSubdomainRouteRef, McpSubdomainUnavailableError, mcpSubdomainEndpointOptions, type ResolvedMcpTenantRef, resolveMcpSubdomainTenant, } from './mcp-subdomain-claims.js';
6
6
  export { domainFromEmail, domainKey, memberKey, normalizeSignupAllowlistValue, personalOrgSlug, personalOrgSlugSuffix, signupKey, } from './organization-helpers.js';
7
+ export { type RollbackControlPlane, type RollbackOperationDependencies, type RollbackOperationInput, type RollbackOperationResult, type RollbackRegistry, rollbackDeploymentOperation, } from './rollback-deployment.js';
8
+ export type { RollbackResult } from './rollback-result.js';
7
9
  export { DOMAIN_PATTERN, isSystemOwnedOrgSlug, OPENAI_APPS_CHALLENGE_MAX_LENGTH, SLUG_PATTERN, validateDomain, validateMcpSubdomain, validateOpenAIAppsChallenge, validateOrgMembershipDomain, validateOrgRole, validateSignupAllowlistKind, validateSlug, validateUserOwnedOrgSlug, } from './validation.js';
8
10
  //# sourceMappingURL=portable.d.ts.map
@@ -3,5 +3,6 @@ export { InMemoryControlPlaneStore } from './in-memory-control-plane-store.js';
3
3
  export { bindInMemoryOrganizationStore, InMemoryOrganizationStore, } from './in-memory-organization-store.js';
4
4
  export { InMemoryMcpSubdomainClaimStore, McpSubdomainCooldownError, McpSubdomainIdempotencyConflictError, McpSubdomainOwnerRequiredError, McpSubdomainUnavailableError, mcpSubdomainEndpointOptions, resolveMcpSubdomainTenant, } from './mcp-subdomain-claims.js';
5
5
  export { domainFromEmail, domainKey, memberKey, normalizeSignupAllowlistValue, personalOrgSlug, personalOrgSlugSuffix, signupKey, } from './organization-helpers.js';
6
+ export { rollbackDeploymentOperation, } from './rollback-deployment.js';
6
7
  export { DOMAIN_PATTERN, isSystemOwnedOrgSlug, OPENAI_APPS_CHALLENGE_MAX_LENGTH, SLUG_PATTERN, validateDomain, validateMcpSubdomain, validateOpenAIAppsChallenge, validateOrgMembershipDomain, validateOrgRole, validateSignupAllowlistKind, validateSlug, validateUserOwnedOrgSlug, } from './validation.js';
7
8
  //# sourceMappingURL=portable.js.map
@@ -0,0 +1,61 @@
1
+ import { type AuditSink, type DeploymentOperationTarget, type EndpointUrlOptions } from '@noodle-borg/module';
2
+ import type { RollbackResult } from './rollback-result.js';
3
+ export interface RollbackRegistry {
4
+ getAppArchivedAt(org: string, app: string): Promise<string | undefined>;
5
+ rollback(target: DeploymentOperationTarget, deploymentId: string): Promise<RollbackResult>;
6
+ }
7
+ export interface RollbackControlPlane {
8
+ getOrgMember(input: {
9
+ readonly org: string;
10
+ readonly subject: string;
11
+ }): Promise<{
12
+ readonly role: string;
13
+ } | undefined>;
14
+ }
15
+ export interface RollbackOperationDependencies {
16
+ readonly registry: RollbackRegistry;
17
+ readonly controlPlane: RollbackControlPlane;
18
+ readonly audit: AuditSink;
19
+ }
20
+ export interface RollbackOperationInput {
21
+ readonly actor: {
22
+ readonly subject: string;
23
+ readonly email?: string;
24
+ readonly superAdmin: boolean;
25
+ };
26
+ readonly target: DeploymentOperationTarget;
27
+ readonly deploymentId: string;
28
+ readonly reason?: string;
29
+ readonly publicBaseUrl: string;
30
+ readonly endpointOptions?: EndpointUrlOptions;
31
+ }
32
+ export type RollbackOperationResult = {
33
+ readonly ok: true;
34
+ readonly view: {
35
+ readonly target: DeploymentOperationTarget;
36
+ readonly rollback: {
37
+ readonly deploymentId: string;
38
+ readonly serverVersion?: string;
39
+ readonly previousDeploymentId?: string;
40
+ readonly alreadyActive: boolean;
41
+ readonly endpointUrl: string;
42
+ readonly accessMode: string;
43
+ readonly ownerSubject?: string;
44
+ readonly previousAccessMode?: string;
45
+ readonly serverName: string;
46
+ readonly createdAt: string;
47
+ };
48
+ };
49
+ } | {
50
+ readonly ok: false;
51
+ readonly status: 403 | 404 | 409 | 503;
52
+ readonly code: 'forbidden' | 'app_archived' | 'deployment_not_found' | 'deployment_incompatible' | 'deployment_locked' | 'production_app_limit_exceeded' | 'billing_enforcement_unavailable';
53
+ readonly message: string;
54
+ };
55
+ /**
56
+ * Activate a previous deployment through the service's canonical governance boundary.
57
+ * HTTP routes, agent tools, and MCP App widgets must all call this operation rather than
58
+ * duplicating owner checks, commercial admission, endpoint construction, or audit behavior.
59
+ */
60
+ export declare function rollbackDeploymentOperation(dependencies: RollbackOperationDependencies, input: RollbackOperationInput): Promise<RollbackOperationResult>;
61
+ //# sourceMappingURL=rollback-deployment.d.ts.map
@@ -1,5 +1,4 @@
1
- import { DeploymentActivationError } from '@noodle-borg/module';
2
- import { tenantMcpUrl } from '../registry-helpers.js';
1
+ import { DeploymentActivationError, tenantMcpUrl, } from '@noodle-borg/module';
3
2
  /**
4
3
  * Activate a previous deployment through the service's canonical governance boundary.
5
4
  * HTTP routes, agent tools, and MCP App widgets must all call this operation rather than
@@ -81,6 +80,7 @@ export async function rollbackDeploymentOperation(dependencies, input) {
81
80
  : {}),
82
81
  alreadyActive: result.alreadyActive,
83
82
  accessMode: result.accessMode,
83
+ ...(result.ownerSubject !== undefined ? { ownerSubject: result.ownerSubject } : {}),
84
84
  ...(result.previousAccessMode !== undefined
85
85
  ? { previousAccessMode: result.previousAccessMode }
86
86
  : {}),
@@ -101,6 +101,7 @@ export async function rollbackDeploymentOperation(dependencies, input) {
101
101
  alreadyActive: result.alreadyActive,
102
102
  endpointUrl,
103
103
  accessMode: result.accessMode,
104
+ ...(result.ownerSubject !== undefined ? { ownerSubject: result.ownerSubject } : {}),
104
105
  ...(result.previousAccessMode !== undefined
105
106
  ? { previousAccessMode: result.previousAccessMode }
106
107
  : {}),