@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,19 @@
1
+ import type { AccessMode } from '@noodle-borg/module';
2
+ export type RollbackResult = {
3
+ readonly ok: true;
4
+ readonly deploymentId: string;
5
+ readonly serverVersion?: string;
6
+ readonly previousDeploymentId?: string;
7
+ readonly alreadyActive: boolean;
8
+ readonly accessMode: AccessMode;
9
+ readonly ownerSubject?: string;
10
+ readonly previousAccessMode?: AccessMode;
11
+ readonly serverName: string;
12
+ readonly createdAt: string;
13
+ } | {
14
+ readonly ok: false;
15
+ readonly status: 404 | 409;
16
+ readonly code?: 'deployment_locked';
17
+ readonly error: string;
18
+ };
19
+ //# sourceMappingURL=rollback-result.d.ts.map
@@ -25,7 +25,9 @@
25
25
  "build": "tsc -p tsconfig.json",
26
26
  "typecheck": "tsc -p tsconfig.json --noEmit"
27
27
  },
28
- "dependencies": {},
28
+ "dependencies": {
29
+ "@noodle-borg/module": "0.0.0"
30
+ },
29
31
  "devDependencies": {
30
32
  "@types/node": "^24.13.1",
31
33
  "@types/pg": "^8.20.0"
@@ -23,6 +23,7 @@ export const tenantSlugSchema = z
23
23
  export const appSlugSchema = tenantSlugSchema;
24
24
  export const environmentNameSchema = tenantSlugSchema;
25
25
  export const boundedIdSchema = z.string().min(1).max(200);
26
+ const ownerSubjectSchema = z.string().min(1).max(512);
26
27
  export const developerMcpContextSchema = z.strictObject({
27
28
  subject: z.string().min(1).max(255),
28
29
  clientId: z.string().min(1).max(255),
@@ -122,6 +123,7 @@ export const deploymentSummarySchema = z.strictObject({
122
123
  serverName: z.string().min(1).max(200),
123
124
  createdAt: isoTimestampSchema,
124
125
  accessMode: z.string().min(1).max(64).optional(),
126
+ ownerSubject: ownerSubjectSchema.optional(),
125
127
  endpointUrl: z.url().optional(),
126
128
  });
127
129
  export const appSummaryViewSchema = z.strictObject({
@@ -163,6 +165,7 @@ export const deploymentInspectionViewSchema = z.strictObject({
163
165
  createdAt: isoTimestampSchema,
164
166
  createdByEmail: z.email().optional(),
165
167
  accessMode: z.string().min(1).max(64),
168
+ ownerSubject: ownerSubjectSchema.optional(),
166
169
  }),
167
170
  health: z.strictObject({
168
171
  state: z.string().min(1).max(100),
@@ -203,6 +206,7 @@ export const rollbackViewSchema = z.strictObject({
203
206
  alreadyActive: z.boolean(),
204
207
  endpointUrl: z.url(),
205
208
  accessMode: z.string().min(1).max(64),
209
+ ownerSubject: ownerSubjectSchema.optional(),
206
210
  previousAccessMode: z.string().min(1).max(64).optional(),
207
211
  serverName: z.string().min(1).max(200),
208
212
  createdAt: isoTimestampSchema,
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ActionStatus, BoundValue, EmptyState, Panel, WidgetHeader } from './shared.js';
3
3
  export function AppOverviewWidget() {
4
- return (_jsxs("main", { children: [_jsx(WidgetHeader, { eyebrow: "Noodle Cloud", title: "App overview", description: "Deployment state for the organization and environments selected at connection time.", statusPath: "data.active" }), _jsx(Panel, { title: "Selected app", description: "This view is limited to the active developer grant.", children: _jsxs("dl", { className: "facts", children: [_jsx(BoundValue, { label: "App", path: "data.app" }), _jsx(BoundValue, { label: "Environment", path: "data.selectedEnvironment" }), _jsx(BoundValue, { label: "Active", path: "data.active" }), _jsx(BoundValue, { label: "Last activity", path: "data.lastActivityAt" })] }) }), _jsx(Panel, { title: "Granted environments", children: _jsxs("div", { className: "tag-list", "data-collection": "data.environments", "data-collection-item": "environment", children: [_jsx("template", { "data-collection-template": "", children: _jsx("span", { className: "tag", "data-bind": "environment", children: "Loading" }) }), _jsx(EmptyState, { children: "No environment is available in this result." })] }) }), _jsxs(Panel, { title: "Active deployment", children: [_jsxs("dl", { className: "facts", children: [_jsx(BoundValue, { label: "Deployment", path: "data.latest.deploymentId" }), _jsx(BoundValue, { label: "Created", path: "data.latest.createdAt" }), _jsx(BoundValue, { label: "Access", path: "data.latest.accessMode" }), _jsx(BoundValue, { label: "Endpoint", path: "data.latest.endpointUrl" })] }), _jsx("p", { className: "notice", "data-bind-if": "data.active", children: "An active deployment is serving this environment." })] }), _jsx(ActionStatus, {})] }));
4
+ return (_jsxs("main", { children: [_jsx(WidgetHeader, { eyebrow: "Noodle Cloud", title: "App overview", description: "Deployment state for the organization and environments selected at connection time.", statusPath: "data.active" }), _jsx(Panel, { title: "Selected app", description: "This view is limited to the active developer grant.", children: _jsxs("dl", { className: "facts", children: [_jsx(BoundValue, { label: "App", path: "data.app" }), _jsx(BoundValue, { label: "Environment", path: "data.selectedEnvironment" }), _jsx(BoundValue, { label: "Active", path: "data.active" }), _jsx(BoundValue, { label: "Last activity", path: "data.lastActivityAt" })] }) }), _jsx(Panel, { title: "Granted environments", children: _jsxs("div", { className: "tag-list", "data-collection": "data.environments", "data-collection-item": "environment", children: [_jsx("template", { "data-collection-template": "", children: _jsx("span", { className: "tag", "data-bind": "environment", children: "Loading" }) }), _jsx(EmptyState, { children: "No environment is available in this result." })] }) }), _jsxs(Panel, { title: "Active deployment", children: [_jsxs("dl", { className: "facts", children: [_jsx(BoundValue, { label: "Deployment", path: "data.latest.deploymentId" }), _jsx(BoundValue, { label: "Created", path: "data.latest.createdAt" }), _jsx(BoundValue, { label: "Access", path: "data.latest.accessMode" }), _jsx(BoundValue, { label: "Owner", path: "data.latest.ownerSubject" }), _jsx(BoundValue, { label: "Endpoint", path: "data.latest.endpointUrl" })] }), _jsx("p", { className: "notice", "data-bind-if": "data.active", children: "An active deployment is serving this environment." })] }), _jsx(ActionStatus, {})] }));
5
5
  }
6
6
  //# sourceMappingURL=app-overview.js.map
@@ -2,6 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ActionStatus, BoundValue, EmptyState, Panel, WidgetHeader } from './shared.js';
3
3
  const ROLLBACK_ARGUMENTS = '{"app":"{{data.target.app}}","env":"{{data.target.env}}","deploymentId":"{{data.rollbackCandidate.deploymentId}}"}';
4
4
  export function DeploymentDetailWidget() {
5
- return (_jsxs("main", { children: [_jsx(WidgetHeader, { eyebrow: "Noodle Cloud", title: "Deployment detail", description: "Runtime health, surface compatibility, and governed rollback state.", statusPath: "data.health.state" }), _jsx(Panel, { title: "Deployment identity", children: _jsxs("dl", { className: "facts", children: [_jsx(BoundValue, { label: "App", path: "data.target.app" }), _jsx(BoundValue, { label: "Environment", path: "data.target.env" }), _jsx(BoundValue, { label: "Deployment", path: "data.deployment.deploymentId" }), _jsx(BoundValue, { label: "Created", path: "data.deployment.createdAt" }), _jsx(BoundValue, { label: "Access", path: "data.deployment.accessMode" }), _jsx(BoundValue, { label: "Endpoint", path: "data.deployment.endpointUrl" })] }) }), _jsxs(Panel, { title: "Runtime health", children: [_jsxs("dl", { className: "facts", children: [_jsx(BoundValue, { label: "State", path: "data.health.state" }), _jsx(BoundValue, { label: "MCP Apps", path: "data.surface.compatibility.mcpApps" }), _jsx(BoundValue, { label: "ChatGPT", path: "data.surface.compatibility.chatgpt" }), _jsx(BoundValue, { label: "Claude", path: "data.surface.compatibility.claude" })] }), _jsxs("div", { className: "collection", "data-collection": "data.health.missingSecrets", "data-collection-item": "missing", children: [_jsx("template", { "data-collection-template": "", children: _jsx("p", { className: "collection-card", "data-bind": "missing", children: "Loading" }) }), _jsx(EmptyState, { children: "No missing managed configuration was reported." })] })] }), _jsxs(Panel, { title: "Rollback", description: "Noodle Cloud rechecks ownership, selected environment, compatibility, and admission before activation.", children: [_jsxs("div", { className: "action-row", children: [_jsxs("dl", { className: "facts", children: [_jsx(BoundValue, { label: "Candidate", path: "data.rollbackCandidate.deploymentId" }), _jsx(BoundValue, { label: "Created", path: "data.rollbackCandidate.createdAt" })] }), _jsx("button", { type: "button", className: "primary-action danger-action", "data-action": "call", "data-action-tool": "rollback_deployment", "data-action-args": ROLLBACK_ARGUMENTS, "data-bind-if": "data.rollbackCandidate.deploymentId", hidden: true, children: "Roll back to candidate" })] }), _jsx("p", { className: "notice", children: "If no candidate is shown, use the headless deployment workflow to inspect an explicit deployment ID." })] }), _jsx(Panel, { title: "Rollback outcome", children: _jsxs("dl", { className: "facts", "data-bind-if": "data.rollback.deploymentId", children: [_jsx(BoundValue, { label: "Activated deployment", path: "data.rollback.deploymentId" }), _jsx(BoundValue, { label: "Previously active", path: "data.rollback.previousDeploymentId" }), _jsx(BoundValue, { label: "Already active", path: "data.rollback.alreadyActive" }), _jsx(BoundValue, { label: "Endpoint", path: "data.rollback.endpointUrl" })] }) }), _jsx(ActionStatus, {})] }));
5
+ return (_jsxs("main", { children: [_jsx(WidgetHeader, { eyebrow: "Noodle Cloud", title: "Deployment detail", description: "Runtime health, surface compatibility, and governed rollback state.", statusPath: "data.health.state" }), _jsx(Panel, { title: "Deployment identity", children: _jsxs("dl", { className: "facts", children: [_jsx(BoundValue, { label: "App", path: "data.target.app" }), _jsx(BoundValue, { label: "Environment", path: "data.target.env" }), _jsx(BoundValue, { label: "Deployment", path: "data.deployment.deploymentId" }), _jsx(BoundValue, { label: "Created", path: "data.deployment.createdAt" }), _jsx(BoundValue, { label: "Access", path: "data.deployment.accessMode" }), _jsx(BoundValue, { label: "Owner", path: "data.deployment.ownerSubject" }), _jsx(BoundValue, { label: "Endpoint", path: "data.deployment.endpointUrl" })] }) }), _jsxs(Panel, { title: "Runtime health", children: [_jsxs("dl", { className: "facts", children: [_jsx(BoundValue, { label: "State", path: "data.health.state" }), _jsx(BoundValue, { label: "MCP Apps", path: "data.surface.compatibility.mcpApps" }), _jsx(BoundValue, { label: "ChatGPT", path: "data.surface.compatibility.chatgpt" }), _jsx(BoundValue, { label: "Claude", path: "data.surface.compatibility.claude" })] }), _jsxs("div", { className: "collection", "data-collection": "data.health.missingSecrets", "data-collection-item": "missing", children: [_jsx("template", { "data-collection-template": "", children: _jsx("p", { className: "collection-card", "data-bind": "missing", children: "Loading" }) }), _jsx(EmptyState, { children: "No missing managed configuration was reported." })] })] }), _jsxs(Panel, { title: "Rollback", description: "Noodle Cloud rechecks ownership, selected environment, compatibility, and admission before activation.", children: [_jsxs("div", { className: "action-row", children: [_jsxs("dl", { className: "facts", children: [_jsx(BoundValue, { label: "Candidate", path: "data.rollbackCandidate.deploymentId" }), _jsx(BoundValue, { label: "Created", path: "data.rollbackCandidate.createdAt" })] }), _jsx("button", { type: "button", className: "primary-action danger-action", "data-action": "call", "data-action-tool": "rollback_deployment", "data-action-args": ROLLBACK_ARGUMENTS, "data-bind-if": "data.rollbackCandidate.deploymentId", hidden: true, children: "Roll back to candidate" })] }), _jsx("p", { className: "notice", children: "If no candidate is shown, use the headless deployment workflow to inspect an explicit deployment ID." })] }), _jsx(Panel, { title: "Rollback outcome", children: _jsxs("dl", { className: "facts", "data-bind-if": "data.rollback.deploymentId", children: [_jsx(BoundValue, { label: "Activated deployment", path: "data.rollback.deploymentId" }), _jsx(BoundValue, { label: "Previously active", path: "data.rollback.previousDeploymentId" }), _jsx(BoundValue, { label: "Already active", path: "data.rollback.alreadyActive" }), _jsx(BoundValue, { label: "Endpoint", path: "data.rollback.endpointUrl" }), _jsx(BoundValue, { label: "Owner", path: "data.rollback.ownerSubject" })] }) }), _jsx(ActionStatus, {})] }));
6
6
  }
7
7
  //# sourceMappingURL=deployment-detail.js.map
@@ -399,6 +399,7 @@ export interface RequestEventFilter {
399
399
  readonly org: string;
400
400
  readonly app?: string;
401
401
  readonly env?: string;
402
+ readonly method?: string;
402
403
  readonly outcome?: RequestOutcome;
403
404
  readonly toolName?: string;
404
405
  readonly clientName?: string;
@@ -16,7 +16,6 @@ export type DeploymentAutomationInput = {
16
16
  readonly previousAccessMode?: AccessMode;
17
17
  readonly declaredAccessMode?: string;
18
18
  readonly previousOwnerSubject?: string;
19
- readonly previousOwnerEmail?: string;
20
19
  };
21
20
  export type DeploymentAutomationAuthorization = {
22
21
  readonly kind: 'not-automation';
@@ -29,6 +28,7 @@ export type DeploymentAutomationAuthorization = {
29
28
  readonly kind: 'authorized';
30
29
  readonly automationId: string;
31
30
  readonly actor: ControlPlaneIdentity;
31
+ readonly ownerSubject?: string;
32
32
  readonly accessMode?: AccessMode;
33
33
  readonly accessModeSource?: 'previous' | 'declared';
34
34
  };
@@ -0,0 +1,11 @@
1
+ export interface DeploymentOperationTarget {
2
+ readonly org: string;
3
+ readonly app: string;
4
+ readonly env: string;
5
+ }
6
+ export interface EndpointUrlOptions {
7
+ readonly publicBaseDomain?: string;
8
+ readonly mcpSubdomain?: string;
9
+ }
10
+ export declare function tenantMcpUrl(base: string, tenant: DeploymentOperationTarget, serverVersion?: string, options?: EndpointUrlOptions): string;
11
+ //# sourceMappingURL=endpoint-url.d.ts.map
@@ -0,0 +1,20 @@
1
+ import { formatPublicMcpUrl, serverVersionPathSegment } from './contract.js';
2
+ export function tenantMcpUrl(base, tenant, serverVersion, options = {}) {
3
+ if (options.publicBaseDomain !== undefined) {
4
+ if (options.mcpSubdomain === undefined) {
5
+ throw new Error('MCP subdomain is required to format a public endpoint URL');
6
+ }
7
+ return formatPublicMcpUrl(options.publicBaseDomain, {
8
+ mcpSubdomain: options.mcpSubdomain,
9
+ app: tenant.app,
10
+ env: tenant.env,
11
+ ...(serverVersion !== undefined ? { serverVersion } : {}),
12
+ });
13
+ }
14
+ const versionPath = serverVersion !== undefined ? `/${serverVersionPathSegment(serverVersion)}` : '';
15
+ const origin = new URL(base).origin;
16
+ return tenant.env === 'prod'
17
+ ? `${origin}/o/${tenant.org}/${tenant.app}${versionPath}/mcp`
18
+ : `${origin}/o/${tenant.org}/${tenant.app}/${tenant.env}${versionPath}/mcp`;
19
+ }
20
+ //# sourceMappingURL=endpoint-url.js.map
@@ -2,6 +2,7 @@ export * from './asset-store.js';
2
2
  export * from './contract.js';
3
3
  export * from './deployment-activation.js';
4
4
  export * from './deployment-automation.js';
5
+ export * from './endpoint-url.js';
5
6
  export * from './intent.js';
6
7
  export * from './organization-provisioning.js';
7
8
  export * from './platform-human-identity.js';
@@ -2,6 +2,7 @@ export * from './asset-store.js';
2
2
  export * from './contract.js';
3
3
  export * from './deployment-activation.js';
4
4
  export * from './deployment-automation.js';
5
+ export * from './endpoint-url.js';
5
6
  export * from './intent.js';
6
7
  export * from './organization-provisioning.js';
7
8
  export * from './platform-human-identity.js';
@@ -0,0 +1,217 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export const ASSISTANT_USAGE_METHOD = 'assistant';
3
+ const EMPTY_TURN_COUNTERS = {
4
+ modelRequests: 0,
5
+ toolCalls: 0,
6
+ interactionCount: 0,
7
+ promptTokens: 0,
8
+ completionTokens: 0,
9
+ totalTokens: 0,
10
+ };
11
+ export function assistantSessionUsageRequestEvent(session, durationMs) {
12
+ return assistantUsageRequestEvent({
13
+ ...session.tenant,
14
+ deploymentId: session.deploymentId,
15
+ eventKind: 'session',
16
+ sessionId: session.id,
17
+ surface: assistantSurface(session),
18
+ ...(session.modelSource === undefined ? {} : { modelSource: session.modelSource }),
19
+ durationMs,
20
+ });
21
+ }
22
+ export function assistantTurnUsageRequestEvent(session, input) {
23
+ return assistantUsageRequestEvent({
24
+ ...session.tenant,
25
+ deploymentId: session.deploymentId,
26
+ eventKind: 'turn',
27
+ sessionId: session.id,
28
+ surface: assistantSurface(session),
29
+ ...(session.modelSource === undefined ? {} : { modelSource: session.modelSource }),
30
+ outcome: input.outcome,
31
+ turnNumber: input.turnNumber,
32
+ durationMs: input.durationMs,
33
+ ...(input.counters ?? EMPTY_TURN_COUNTERS),
34
+ ...(input.errorKind === undefined ? {} : { errorKind: input.errorKind }),
35
+ });
36
+ }
37
+ export function assistantRefusedTurnUsageRequestEvent(session, errorKind, durationMs) {
38
+ return assistantTurnUsageRequestEvent(session, {
39
+ outcome: 'refused',
40
+ turnNumber: session.turnCount,
41
+ durationMs,
42
+ errorKind,
43
+ });
44
+ }
45
+ function assistantSurface(session) {
46
+ return session.publicEmbedId === undefined ? 'authenticated' : 'public';
47
+ }
48
+ export function assistantTurnNumber(session) {
49
+ if (session.publicEmbedId !== undefined)
50
+ return session.turnCount + 1;
51
+ return session.history.filter((message) => message.role === 'user').length + 1;
52
+ }
53
+ export function captureAssistantUsage(capture, event) {
54
+ try {
55
+ capture?.(event);
56
+ }
57
+ catch {
58
+ // Telemetry is best effort and never changes the assistant response path.
59
+ }
60
+ }
61
+ /**
62
+ * Project one assistant lifecycle event onto the shared tenant request stream. The allowlist is
63
+ * deliberately scalar-only: conversation text, model messages, tool arguments/results, headers,
64
+ * credentials, and page context have no field to enter through.
65
+ */
66
+ export function assistantUsageRequestEvent(input) {
67
+ const details = {
68
+ eventKind: input.eventKind,
69
+ surface: input.surface,
70
+ ...(input.modelSource === undefined ? {} : { modelSource: input.modelSource }),
71
+ ...(input.eventKind === 'turn'
72
+ ? {
73
+ assistantOutcome: input.outcome,
74
+ turnNumber: input.turnNumber,
75
+ modelRequests: input.modelRequests,
76
+ toolCalls: input.toolCalls,
77
+ interactionCount: input.interactionCount,
78
+ promptTokens: input.promptTokens,
79
+ completionTokens: input.completionTokens,
80
+ totalTokens: input.totalTokens,
81
+ ...(input.reasoningTokens === undefined
82
+ ? {}
83
+ : { reasoningTokens: input.reasoningTokens }),
84
+ }
85
+ : {}),
86
+ };
87
+ return {
88
+ org: input.org,
89
+ app: input.app,
90
+ env: input.env,
91
+ deploymentId: input.deploymentId,
92
+ requestId: randomUUID(),
93
+ sessionId: input.sessionId,
94
+ sessionSource: 'synthetic',
95
+ subjectKind: input.surface === 'public' ? 'anonymous' : 'authenticated',
96
+ accessMode: input.surface === 'public' ? 'public' : 'authenticated',
97
+ method: ASSISTANT_USAGE_METHOD,
98
+ kind: 'usage',
99
+ outcome: input.eventKind === 'session' || input.outcome === 'delivered'
100
+ ? 'ok'
101
+ : input.outcome === 'refused'
102
+ ? 'tool_error'
103
+ : 'mcp_error',
104
+ ...(input.eventKind === 'turn' && input.errorKind !== undefined
105
+ ? { errorKind: input.errorKind }
106
+ : {}),
107
+ durationMs: finiteNonNegative(input.durationMs),
108
+ details,
109
+ };
110
+ }
111
+ export function aggregateAssistantUsage(events) {
112
+ let minted = 0;
113
+ let publicSessions = 0;
114
+ let authenticatedSessions = 0;
115
+ let delivered = 0;
116
+ let failed = 0;
117
+ let refused = 0;
118
+ let modelRequests = 0;
119
+ let toolTurns = 0;
120
+ let interactionTurns = 0;
121
+ let prompt = 0;
122
+ let completion = 0;
123
+ let reasoning = 0;
124
+ let total = 0;
125
+ let noodleManaged = 0;
126
+ let operator = 0;
127
+ const latencies = [];
128
+ const sessionDepth = new Map();
129
+ for (const event of events) {
130
+ if (event.method !== ASSISTANT_USAGE_METHOD)
131
+ continue;
132
+ const details = event.details;
133
+ const eventKind = scalarString(details?.eventKind);
134
+ const surface = scalarString(details?.surface);
135
+ if (eventKind === 'session') {
136
+ minted += 1;
137
+ if (surface === 'public')
138
+ publicSessions += 1;
139
+ if (surface === 'authenticated')
140
+ authenticatedSessions += 1;
141
+ continue;
142
+ }
143
+ if (eventKind !== 'turn')
144
+ continue;
145
+ const outcome = scalarString(details?.assistantOutcome);
146
+ if (outcome === 'delivered')
147
+ delivered += 1;
148
+ else if (outcome === 'refused')
149
+ refused += 1;
150
+ else if (outcome === 'failed')
151
+ failed += 1;
152
+ else
153
+ continue;
154
+ latencies.push(finiteNonNegative(event.durationMs));
155
+ modelRequests += scalarCount(details?.modelRequests);
156
+ if (scalarCount(details?.toolCalls) > 0)
157
+ toolTurns += 1;
158
+ if (scalarCount(details?.interactionCount) > 0)
159
+ interactionTurns += 1;
160
+ prompt += scalarCount(details?.promptTokens);
161
+ completion += scalarCount(details?.completionTokens);
162
+ reasoning += scalarCount(details?.reasoningTokens);
163
+ total += scalarCount(details?.totalTokens);
164
+ const source = scalarString(details?.modelSource);
165
+ if (source === 'noodle-managed')
166
+ noodleManaged += 1;
167
+ if (source === 'operator')
168
+ operator += 1;
169
+ const turnNumber = scalarCount(details?.turnNumber);
170
+ if (event.sessionId !== undefined && turnNumber > 0) {
171
+ sessionDepth.set(event.sessionId, Math.max(sessionDepth.get(event.sessionId) ?? 0, turnNumber));
172
+ }
173
+ }
174
+ const depths = [...sessionDepth.values()].sort((a, b) => a - b);
175
+ const sortedLatencies = latencies.sort((a, b) => a - b);
176
+ return {
177
+ sessions: { minted, public: publicSessions, authenticated: authenticatedSessions },
178
+ turns: {
179
+ attempted: delivered + failed + refused,
180
+ delivered,
181
+ failed,
182
+ refused,
183
+ },
184
+ depth: {
185
+ p50: percentile(depths, 50),
186
+ p90: percentile(depths, 90),
187
+ max: depths.at(-1) ?? 0,
188
+ atLeast10: depths.filter((value) => value >= 10).length,
189
+ atLeast20: depths.filter((value) => value >= 20).length,
190
+ atLeast30: depths.filter((value) => value >= 30).length,
191
+ atLeast40: depths.filter((value) => value >= 40).length,
192
+ },
193
+ engagement: { modelRequests, toolTurns, interactionTurns },
194
+ latency: {
195
+ p50Ms: percentile(sortedLatencies, 50),
196
+ p95Ms: percentile(sortedLatencies, 95),
197
+ },
198
+ tokens: { prompt, completion, reasoning, total },
199
+ byModelSource: { noodleManaged, operator },
200
+ };
201
+ }
202
+ function scalarString(value) {
203
+ return typeof value === 'string' ? value : undefined;
204
+ }
205
+ function scalarCount(value) {
206
+ return typeof value === 'number' ? Math.floor(finiteNonNegative(value)) : 0;
207
+ }
208
+ function finiteNonNegative(value) {
209
+ return Number.isFinite(value) ? Math.max(0, value) : 0;
210
+ }
211
+ function percentile(sorted, percentileValue) {
212
+ if (sorted.length === 0)
213
+ return 0;
214
+ const rank = Math.ceil((percentileValue / 100) * sorted.length);
215
+ return sorted[Math.min(Math.max(rank, 1), sorted.length) - 1] ?? 0;
216
+ }
217
+ //# sourceMappingURL=assistant-usage.js.map
@@ -1,5 +1,7 @@
1
+ export * from './assistant-usage.js';
1
2
  export * from './intent-capture.js';
2
3
  export * from './request-event-buffer.js';
4
+ export * from './request-event-metrics.js';
3
5
  export * from './request-events.js';
4
6
  export * from './request-events-postgres.js';
5
7
  export * from './runtime.js';
@@ -10,8 +10,11 @@ function hourBucket(iso) {
10
10
  return `${iso.slice(0, 13)}:00:00.000Z`;
11
11
  }
12
12
  export function aggregateRequestEvents(events) {
13
- const usage = events.filter((e) => e.kind === 'usage');
14
- const discovery = events.length - usage.length;
13
+ // Assistant lifecycle records share the durable tenant stream but have their own aggregate. Keep
14
+ // the long-standing MCP metrics contract stable rather than counting one user turn as an MCP call.
15
+ const mcpEvents = events.filter((event) => event.method !== 'assistant');
16
+ const usage = mcpEvents.filter((event) => event.kind === 'usage');
17
+ const discovery = mcpEvents.length - usage.length;
15
18
  const durations = usage.map((e) => e.durationMs).sort((a, b) => a - b);
16
19
  let ok = 0;
17
20
  let toolErrors = 0;
@@ -51,6 +51,10 @@ export async function ensureRequestEventSchema(pool) {
51
51
  await pool.query(`
52
52
  CREATE INDEX IF NOT EXISTS request_events_tenant_time_idx
53
53
  ON request_events (org_slug, app_slug, environment, seq DESC)
54
+ `);
55
+ await pool.query(`
56
+ CREATE INDEX IF NOT EXISTS request_events_tenant_method_time_idx
57
+ ON request_events (org_slug, app_slug, environment, method, seq DESC)
54
58
  `);
55
59
  await pool.query(`
56
60
  CREATE INDEX IF NOT EXISTS request_events_session_idx
@@ -128,6 +132,8 @@ export class PostgresRequestEventStore {
128
132
  add('app_slug', filter.app);
129
133
  if (filter.env !== undefined)
130
134
  add('environment', filter.env);
135
+ if (filter.method !== undefined)
136
+ add('method', filter.method);
131
137
  if (filter.outcome !== undefined)
132
138
  add('outcome', filter.outcome);
133
139
  if (filter.toolName !== undefined)
@@ -41,6 +41,8 @@ function matches(event, filter) {
41
41
  return false;
42
42
  if (filter.env !== undefined && event.env !== filter.env)
43
43
  return false;
44
+ if (filter.method !== undefined && event.method !== filter.method)
45
+ return false;
44
46
  if (filter.outcome !== undefined && event.outcome !== filter.outcome)
45
47
  return false;
46
48
  if (filter.toolName !== undefined && event.toolName !== filter.toolName)
@@ -3,7 +3,7 @@ import { missingSecretErrors, missingVariableErrors } from './registry-helpers.j
3
3
  import { serverAuthSecretBindings } from './server-auth-bindings.js';
4
4
  const VARIABLE = /\$\{env\.([A-Za-z0-9_]+)\}/g;
5
5
  export function assistantSecretBindings(assistant) {
6
- return assistant
6
+ return assistant?.model.kind === 'openai-compatible'
7
7
  ? [
8
8
  {
9
9
  connectorId: 'server.assistant',
@@ -17,6 +17,8 @@ export function assistantSecretBindings(assistant) {
17
17
  export function assistantVariableBindings(assistant) {
18
18
  if (!assistant)
19
19
  return [];
20
+ if (assistant.model.kind !== 'openai-compatible')
21
+ return [];
20
22
  const names = new Set();
21
23
  for (const value of [assistant.model.baseUrl, assistant.model.model]) {
22
24
  for (const match of value.matchAll(VARIABLE)) {
@@ -1,7 +1,6 @@
1
- import { mcpSubdomainEndpointOptions } from '@noodle-borg/control-plane/portable';
1
+ import { mcpSubdomainEndpointOptions, rollbackDeploymentOperation, } from '@noodle-borg/control-plane/portable';
2
2
  import { DeveloperControlPlaneError, } from '@noodle-borg/developer-mcp';
3
- import { rollbackDeploymentOperation } from '../operations/rollback-deployment.js';
4
- import { aggregateRequestEvents } from '../request-event-metrics.js';
3
+ import { aggregateRequestEvents } from '@noodle-borg/observability';
5
4
  import { inspectCompiledServiceDeployment, inspectServiceDeployment } from './inspection.js';
6
5
  const METRICS_SCAN_LIMIT = 50_000;
7
6
  const WINDOWS_MS = {
@@ -261,6 +260,7 @@ function mapDeployment(deployment) {
261
260
  serverName: deployment.serverName,
262
261
  createdAt: deployment.createdAt,
263
262
  accessMode: deployment.accessMode,
263
+ ...(deployment.ownerSubject !== undefined ? { ownerSubject: deployment.ownerSubject } : {}),
264
264
  ...(deployment.endpointUrl !== undefined ? { endpointUrl: deployment.endpointUrl } : {}),
265
265
  };
266
266
  }
@@ -1,4 +1,4 @@
1
- import { tenantMcpUrl } from '../registry-helpers.js';
1
+ import { tenantMcpUrl } from '@noodle-borg/module';
2
2
  export async function inspectServiceDeployment(registry, tenant, baseUrl, endpointOptions = {}) {
3
3
  const status = await registry.getStatus(tenant, baseUrl, undefined, endpointOptions);
4
4
  if (status === undefined)
@@ -109,6 +109,7 @@ export function inspectCompiledServiceDeployment(deployment, artifact, baseUrl,
109
109
  ? {}
110
110
  : { createdByEmail: deployment.createdByEmail }),
111
111
  accessMode: deployment.accessMode,
112
+ ...(deployment.ownerSubject !== undefined ? { ownerSubject: deployment.ownerSubject } : {}),
112
113
  },
113
114
  health: { state: 'ready', missingSecrets: [] },
114
115
  surface,