@noodleseed/one 0.155.0 → 0.156.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 (136) hide show
  1. package/README.md +5 -1
  2. package/dist/assistant-embed-django-tests.d.ts +3 -0
  3. package/dist/assistant-embed-django-tests.d.ts.map +1 -0
  4. package/dist/assistant-embed-django-tests.js +140 -0
  5. package/dist/assistant-embed-django-tests.js.map +1 -0
  6. package/dist/assistant-embed-django.d.ts +4 -0
  7. package/dist/assistant-embed-django.d.ts.map +1 -0
  8. package/dist/assistant-embed-django.js +259 -0
  9. package/dist/assistant-embed-django.js.map +1 -0
  10. package/dist/assistant-embed-installation.d.ts +29 -0
  11. package/dist/assistant-embed-installation.d.ts.map +1 -0
  12. package/dist/assistant-embed-installation.js +124 -0
  13. package/dist/assistant-embed-installation.js.map +1 -0
  14. package/dist/assistant-embed-profile.d.ts +8 -0
  15. package/dist/assistant-embed-profile.d.ts.map +1 -0
  16. package/dist/assistant-embed-profile.js +150 -0
  17. package/dist/assistant-embed-profile.js.map +1 -0
  18. package/dist/assistant-embed-scaffold-template.d.ts +3 -2
  19. package/dist/assistant-embed-scaffold-template.d.ts.map +1 -1
  20. package/dist/assistant-embed-scaffold-template.js +39 -84
  21. package/dist/assistant-embed-scaffold-template.js.map +1 -1
  22. package/dist/assistant-embed-tests.d.ts +5 -0
  23. package/dist/assistant-embed-tests.d.ts.map +1 -0
  24. package/dist/assistant-embed-tests.js +106 -0
  25. package/dist/assistant-embed-tests.js.map +1 -0
  26. package/dist/assistant-embed-vue.d.ts +3 -0
  27. package/dist/assistant-embed-vue.d.ts.map +1 -0
  28. package/dist/assistant-embed-vue.js +77 -0
  29. package/dist/assistant-embed-vue.js.map +1 -0
  30. package/dist/commands/assistant-embed-ops.d.ts.map +1 -1
  31. package/dist/commands/assistant-embed-ops.js +112 -63
  32. package/dist/commands/assistant-embed-ops.js.map +1 -1
  33. package/dist/commands/author-loop.d.ts.map +1 -1
  34. package/dist/commands/author-loop.js +24 -28
  35. package/dist/commands/author-loop.js.map +1 -1
  36. package/dist/commands/deploy-first-flow.d.ts.map +1 -1
  37. package/dist/commands/deploy-first-flow.js +26 -15
  38. package/dist/commands/deploy-first-flow.js.map +1 -1
  39. package/dist/commands/deploy-preflight.d.ts +5 -0
  40. package/dist/commands/deploy-preflight.d.ts.map +1 -1
  41. package/dist/commands/deploy-preflight.js +44 -5
  42. package/dist/commands/deploy-preflight.js.map +1 -1
  43. package/dist/commands/local-readiness.d.ts +9 -1
  44. package/dist/commands/local-readiness.d.ts.map +1 -1
  45. package/dist/commands/local-readiness.js +74 -4
  46. package/dist/commands/local-readiness.js.map +1 -1
  47. package/dist/commands/project-setup.d.ts.map +1 -1
  48. package/dist/commands/project-setup.js +10 -15
  49. package/dist/commands/project-setup.js.map +1 -1
  50. package/dist/control-plane-request.d.ts +4 -0
  51. package/dist/control-plane-request.d.ts.map +1 -1
  52. package/dist/control-plane-request.js +40 -20
  53. package/dist/control-plane-request.js.map +1 -1
  54. package/dist/project-scaffold-model.d.ts +14 -0
  55. package/dist/project-scaffold-model.d.ts.map +1 -0
  56. package/dist/project-scaffold-model.js +36 -0
  57. package/dist/project-scaffold-model.js.map +1 -0
  58. package/dist/project-scaffold-templates.d.ts.map +1 -1
  59. package/dist/project-scaffold-templates.js +73 -39
  60. package/dist/project-scaffold-templates.js.map +1 -1
  61. package/dist/project-scaffold-test-template.d.ts +4 -0
  62. package/dist/project-scaffold-test-template.d.ts.map +1 -0
  63. package/dist/project-scaffold-test-template.js +89 -0
  64. package/dist/project-scaffold-test-template.js.map +1 -0
  65. package/dist/project.d.ts.map +1 -1
  66. package/dist/project.js +1 -2
  67. package/dist/project.js.map +1 -1
  68. package/dist/widget-focused-template.d.ts +1 -1
  69. package/dist/widget-focused-template.d.ts.map +1 -1
  70. package/dist/widget-focused-template.js +34 -12
  71. package/dist/widget-focused-template.js.map +1 -1
  72. package/dist/widget-scaffold-template.d.ts +1 -1
  73. package/dist/widget-scaffold-template.d.ts.map +1 -1
  74. package/dist/widget-scaffold-template.js +75 -43
  75. package/dist/widget-scaffold-template.js.map +1 -1
  76. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +17 -3
  77. package/node_modules/@noodle-borg/agent-kit/dist/skill-app-playbook-ref.js +2 -2
  78. package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +9 -12
  79. package/node_modules/@noodle-borg/agent-kit/dist/skill-connect-refs.js +2 -2
  80. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-operations-ref.js +1 -1
  81. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +32 -26
  82. package/node_modules/@noodle-borg/agent-kit/dist/skill-server-playbook-ref.js +2 -2
  83. package/node_modules/@noodle-borg/agent-kit/dist/skill-verification-ref.js +2 -1
  84. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  85. package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
  86. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +6 -0
  87. package/node_modules/@noodle-borg/authoring/dist/recording.d.ts +1 -1
  88. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-assistant.js +10 -4
  89. package/node_modules/@noodle-borg/compiler/package.json +0 -1
  90. package/node_modules/@noodle-borg/deploy-client/dist/deploy-payload-size.js +6 -2
  91. package/node_modules/@noodle-borg/service/dist/registry-helpers.d.ts +1 -1
  92. package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +193 -96
  93. package/node_modules/@noodle-borg/service/package.json +1 -1
  94. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +3 -0
  95. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +3 -0
  96. package/node_modules/@noodleseed/assistant/README.md +21 -23
  97. package/node_modules/@noodleseed/assistant/package.json +1 -1
  98. package/package.json +5 -4
  99. package/dist/saas-scaffold-template.d.ts +0 -4
  100. package/dist/saas-scaffold-template.d.ts.map +0 -1
  101. package/dist/saas-scaffold-template.js +0 -56
  102. package/dist/saas-scaffold-template.js.map +0 -1
  103. package/node_modules/tailwindcss/LICENSE +0 -21
  104. package/node_modules/tailwindcss/README.md +0 -36
  105. package/node_modules/tailwindcss/dist/chunk-5JIJA4QV.mjs +0 -1
  106. package/node_modules/tailwindcss/dist/chunk-HMCCH6MG.mjs +0 -1
  107. package/node_modules/tailwindcss/dist/chunk-X4GG3EDV.mjs +0 -1
  108. package/node_modules/tailwindcss/dist/colors-C__qRT83.d.ts +0 -347
  109. package/node_modules/tailwindcss/dist/colors.d.mts +0 -347
  110. package/node_modules/tailwindcss/dist/colors.d.ts +0 -5
  111. package/node_modules/tailwindcss/dist/colors.js +0 -1
  112. package/node_modules/tailwindcss/dist/colors.mjs +0 -1
  113. package/node_modules/tailwindcss/dist/default-theme.d.mts +0 -1199
  114. package/node_modules/tailwindcss/dist/default-theme.d.ts +0 -1199
  115. package/node_modules/tailwindcss/dist/default-theme.js +0 -1
  116. package/node_modules/tailwindcss/dist/default-theme.mjs +0 -1
  117. package/node_modules/tailwindcss/dist/flatten-color-palette.d.mts +0 -6
  118. package/node_modules/tailwindcss/dist/flatten-color-palette.d.ts +0 -6
  119. package/node_modules/tailwindcss/dist/flatten-color-palette.js +0 -1
  120. package/node_modules/tailwindcss/dist/flatten-color-palette.mjs +0 -1
  121. package/node_modules/tailwindcss/dist/lib.d.mts +0 -379
  122. package/node_modules/tailwindcss/dist/lib.d.ts +0 -3
  123. package/node_modules/tailwindcss/dist/lib.js +0 -38
  124. package/node_modules/tailwindcss/dist/lib.mjs +0 -38
  125. package/node_modules/tailwindcss/dist/plugin.d.mts +0 -11
  126. package/node_modules/tailwindcss/dist/plugin.d.ts +0 -134
  127. package/node_modules/tailwindcss/dist/plugin.js +0 -1
  128. package/node_modules/tailwindcss/dist/plugin.mjs +0 -1
  129. package/node_modules/tailwindcss/dist/resolve-config-B4yBzhca.d.ts +0 -29
  130. package/node_modules/tailwindcss/dist/resolve-config-QUZ9b-Gn.d.mts +0 -190
  131. package/node_modules/tailwindcss/dist/types-DWdTiksJ.d.mts +0 -128
  132. package/node_modules/tailwindcss/index.css +0 -944
  133. package/node_modules/tailwindcss/package.json +0 -89
  134. package/node_modules/tailwindcss/preflight.css +0 -393
  135. package/node_modules/tailwindcss/theme.css +0 -510
  136. package/node_modules/tailwindcss/utilities.css +0 -1
@@ -1,14 +1,24 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { cspFaultsInManifest } from '@noodle-borg/compiler';
2
3
  import { readDeployBody, sendJson } from '@noodle-borg/transport-http';
3
- import { deployPreflightRequestSchema, formatWireError } from '@noodle-borg/wire-contracts';
4
+ import { DEPLOY_PREFLIGHT_CHECK_TIMEOUT_MS, deployPreflightRequestSchema, formatWireError, } from '@noodle-borg/wire-contracts';
4
5
  import { manifestUsesUserRoot } from '../registry-access.js';
5
6
  import { isIdentityAccessMode } from './access-mode.js';
6
7
  import { authorizeDeploymentOwnerSelection, authorizeTenantControl, developerGrantRouteAccess, } from './control-plane.js';
8
+ // Per-process resource admission, not a tenant quota or distributed state authority.
9
+ const activePreflights = new WeakSet();
7
10
  /**
8
11
  * Read-only first-deploy readiness. It intentionally does not verify uploaded asset bytes:
9
12
  * the CLI calls this before it requests upload targets or sends any bytes.
10
13
  */
11
14
  export async function handleDeployPreflight(req, res, registry, options, maxBody, tenant, gate, controlPlane, audit) {
15
+ const startedAt = Date.now();
16
+ const suppliedId = req.headers['x-request-id'];
17
+ const requestId = typeof suppliedId === 'string' &&
18
+ /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test(suppliedId)
19
+ ? suppliedId
20
+ : randomUUID();
21
+ res.setHeader('x-request-id', requestId);
12
22
  const identity = await authorizeTenantControl(req, res, gate, controlPlane, tenant.org, developerGrantRouteAccess(options.developerGrantStore, 'deployments:write'));
13
23
  if (identity === false)
14
24
  return;
@@ -25,111 +35,198 @@ export async function handleDeployPreflight(req, res, registry, options, maxBody
25
35
  error: 'app is archived; restore it before deploying',
26
36
  });
27
37
  }
28
- const body = await readDeployBody(req, maxBody);
29
- if (!body.ok)
30
- return sendJson(res, body.status, { error: body.error });
31
- let parsed;
32
- try {
33
- const raw = JSON.parse(body.text);
34
- const result = deployPreflightRequestSchema.safeParse(raw);
35
- if (!result.success)
36
- throw new Error(formatWireError(result.error));
37
- parsed = result.data;
38
- }
39
- catch (error) {
40
- return sendJson(res, 400, {
41
- code: 'invalid_deploy_request',
42
- error: `invalid deploy preflight request: ${error.message}`,
38
+ if (activePreflights.has(registry)) {
39
+ await audit.emit({
40
+ eventType: 'deploy.preflight.rejected',
41
+ ...tenant,
42
+ actorSubject: identity.subject,
43
+ decision: 'deny',
44
+ status: 503,
45
+ reasonCode: 'deploy_preflight_busy',
46
+ details: { requestId, phase: 'admission', elapsedMs: Date.now() - startedAt },
43
47
  });
44
- }
45
- const accessMode = parsed.accessMode ?? 'owner-only';
46
- if (isIdentityAccessMode(accessMode) && identity === undefined) {
47
- return sendJson(res, 401, {
48
- code: 'deployer_identity_required',
49
- error: `${accessMode} deployments require an authenticated deployer`,
48
+ res.setHeader('retry-after', '5');
49
+ return sendJson(res, 503, {
50
+ code: 'deploy_preflight_busy',
51
+ phase: 'admission',
52
+ error: 'This instance is already checking a deployment. Retry once after the indicated delay.',
50
53
  });
51
54
  }
52
- const ownerSelection = await authorizeDeploymentOwnerSelection(res, controlPlane, tenant.org, accessMode, identity, parsed.ownerSubject);
53
- if (!ownerSelection.ok)
54
- return;
55
- if (accessMode === 'public' && manifestUsesUserRoot(parsed.manifest)) {
56
- return sendJson(res, 400, {
57
- code: 'public_user_context_conflict',
58
- error: 'public access mode cannot reference ${user}; use mixed for optional identity',
55
+ activePreflights.add(registry);
56
+ let clientDisconnected = false;
57
+ let deadlineExceeded = false;
58
+ let phase = 'body';
59
+ let timer;
60
+ const onClose = () => {
61
+ if (!res.writableFinished)
62
+ clientDisconnected = true;
63
+ };
64
+ res.once('close', onClose);
65
+ try {
66
+ await audit.emit({
67
+ eventType: 'deploy.preflight.started',
68
+ ...tenant,
69
+ actorSubject: identity.subject,
70
+ decision: 'allow',
71
+ details: { requestId, phase, elapsedMs: Date.now() - startedAt },
59
72
  });
60
- }
61
- const [cspFault] = cspFaultsInManifest(parsed.manifest);
62
- if (cspFault !== undefined) {
63
- return sendJson(res, 400, {
64
- code: 'invalid_widget_csp',
65
- error: `widget "${cspFault.widget}" CSP ${cspFault.list} origin "${cspFault.value}" is not an ` +
66
- `absolute https:// origin and would be dropped by the host renderer` +
67
- (cspFault.suggestion !== undefined ? `; use "${cspFault.suggestion}"` : ''),
73
+ const body = await readDeployBody(req, maxBody);
74
+ if (req.aborted || res.destroyed)
75
+ return;
76
+ if (!body.ok)
77
+ return sendJson(res, body.status, { error: body.error });
78
+ phase = 'validation';
79
+ let parsed;
80
+ try {
81
+ const raw = JSON.parse(body.text);
82
+ const result = deployPreflightRequestSchema.safeParse(raw);
83
+ if (!result.success)
84
+ throw new Error(formatWireError(result.error));
85
+ parsed = result.data;
86
+ }
87
+ catch (error) {
88
+ return sendJson(res, 400, {
89
+ code: 'invalid_deploy_request',
90
+ error: `invalid deploy preflight request: ${error.message}`,
91
+ });
92
+ }
93
+ const accessMode = parsed.accessMode ?? 'owner-only';
94
+ if (isIdentityAccessMode(accessMode) && identity === undefined) {
95
+ return sendJson(res, 401, {
96
+ code: 'deployer_identity_required',
97
+ error: `${accessMode} deployments require an authenticated deployer`,
98
+ });
99
+ }
100
+ const ownerSelection = await authorizeDeploymentOwnerSelection(res, controlPlane, tenant.org, accessMode, identity, parsed.ownerSubject);
101
+ if (!ownerSelection.ok)
102
+ return;
103
+ if (accessMode === 'public' && manifestUsesUserRoot(parsed.manifest)) {
104
+ return sendJson(res, 400, {
105
+ code: 'public_user_context_conflict',
106
+ error: 'public access mode cannot reference ${user}; use mixed for optional identity',
107
+ });
108
+ }
109
+ const [cspFault] = cspFaultsInManifest(parsed.manifest);
110
+ if (cspFault !== undefined) {
111
+ return sendJson(res, 400, {
112
+ code: 'invalid_widget_csp',
113
+ error: `widget "${cspFault.widget}" CSP ${cspFault.list} origin "${cspFault.value}" is not an ` +
114
+ `absolute https:// origin and would be dropped by the host renderer` +
115
+ (cspFault.suggestion !== undefined ? `; use "${cspFault.suggestion}"` : ''),
116
+ });
117
+ }
118
+ phase = 'checks';
119
+ timer = setTimeout(() => {
120
+ deadlineExceeded = true;
121
+ if (!res.headersSent && !res.destroyed)
122
+ sendJson(res, 504, {
123
+ code: 'deploy_preflight_timeout',
124
+ phase,
125
+ error: `Preflight exceeded its ${DEPLOY_PREFLIGHT_CHECK_TIMEOUT_MS}ms checking budget. Publication did not start.`,
126
+ });
127
+ }, DEPLOY_PREFLIGHT_CHECK_TIMEOUT_MS);
128
+ const pending = [
129
+ registry.getApp(tenant.org, tenant.app),
130
+ registry.getEnvironment(tenant.org, tenant.app, tenant.env),
131
+ registry.preflightDeploy(tenant, parsed.manifest, {
132
+ ...(parsed.connectors !== undefined ? { connectors: parsed.connectors } : {}),
133
+ actor: identity,
134
+ accessMode,
135
+ ownerSubject: ownerSelection.ownerSubject,
136
+ ...(parsed.orgMembershipSources !== undefined
137
+ ? { orgMembershipSources: parsed.orgMembershipSources }
138
+ : {}),
139
+ ...(parsed.hostedAssets !== undefined ? { hostedAssets: parsed.hostedAssets } : {}),
140
+ ...(parsed.serverVersion !== undefined ? { serverVersion: parsed.serverVersion } : {}),
141
+ deploymentSource: parsed.deploymentSource ?? 'cli',
142
+ }),
143
+ ];
144
+ const checked = await Promise.all(pending)
145
+ .catch(async (error) => {
146
+ // Keep the resource slot until every already-started check settles, including after a timeout.
147
+ await Promise.allSettled(pending);
148
+ throw error;
149
+ })
150
+ .finally(() => clearTimeout(timer));
151
+ const [application, environment, preflight] = checked;
152
+ const errors = preflight.ok ? [] : preflight.errors;
153
+ const missingSecrets = configNames(errors, 'missing_secret', 'secrets.');
154
+ const missingVariables = configNames(errors, 'missing_variable', 'variables.');
155
+ const appState = application === undefined ? 'will-create' : 'existing';
156
+ const environmentState = environment === undefined ? 'will-create' : 'existing';
157
+ const ready = errors.length === 0;
158
+ const codes = [...new Set(errors.map((error) => error.code))].sort().join(',');
159
+ await audit.emit({
160
+ eventType: 'deploy.preflight.checked',
161
+ org: tenant.org,
162
+ app: tenant.app,
163
+ env: tenant.env,
164
+ decision: 'allow',
165
+ status: 200,
166
+ actorSubject: identity.subject,
167
+ ...(identity.email !== undefined ? { actorEmail: identity.email } : {}),
168
+ details: {
169
+ requestId,
170
+ phase,
171
+ elapsedMs: Date.now() - startedAt,
172
+ clientDisconnected,
173
+ deadlineExceeded,
174
+ ready,
175
+ appState,
176
+ environmentState,
177
+ ...(ownerSelection.ownerSubject !== undefined
178
+ ? { ownerSubject: ownerSelection.ownerSubject }
179
+ : {}),
180
+ errorCount: errors.length,
181
+ codes,
182
+ missingSecretCount: missingSecrets.length,
183
+ missingVariableCount: missingVariables.length,
184
+ },
68
185
  });
69
- }
70
- const [application, environment, preflight] = await Promise.all([
71
- registry.getApp(tenant.org, tenant.app),
72
- registry.getEnvironment(tenant.org, tenant.app, tenant.env),
73
- registry.preflightDeploy(tenant, parsed.manifest, {
74
- ...(parsed.connectors !== undefined ? { connectors: parsed.connectors } : {}),
75
- actor: identity,
76
- accessMode,
77
- ownerSubject: ownerSelection.ownerSubject,
78
- ...(parsed.orgMembershipSources !== undefined
79
- ? { orgMembershipSources: parsed.orgMembershipSources }
80
- : {}),
81
- ...(parsed.hostedAssets !== undefined ? { hostedAssets: parsed.hostedAssets } : {}),
82
- ...(parsed.serverVersion !== undefined ? { serverVersion: parsed.serverVersion } : {}),
83
- deploymentSource: parsed.deploymentSource ?? 'cli',
84
- }),
85
- ]);
86
- const errors = preflight.ok ? [] : preflight.errors;
87
- const missingSecrets = configNames(errors, 'missing_secret', 'secrets.');
88
- const missingVariables = configNames(errors, 'missing_variable', 'variables.');
89
- const appState = application === undefined ? 'will-create' : 'existing';
90
- const environmentState = environment === undefined ? 'will-create' : 'existing';
91
- const ready = errors.length === 0;
92
- const codes = [...new Set(errors.map((error) => error.code))].sort().join(',');
93
- await audit.emit({
94
- eventType: 'deploy.preflight.checked',
95
- org: tenant.org,
96
- app: tenant.app,
97
- env: tenant.env,
98
- decision: 'allow',
99
- status: 200,
100
- actorSubject: identity.subject,
101
- ...(identity.email !== undefined ? { actorEmail: identity.email } : {}),
102
- details: {
186
+ if (deadlineExceeded || clientDisconnected || res.destroyed)
187
+ return;
188
+ return sendJson(res, 200, {
189
+ ok: true,
103
190
  ready,
104
- appState,
105
- environmentState,
106
191
  ...(ownerSelection.ownerSubject !== undefined
107
192
  ? { ownerSubject: ownerSelection.ownerSubject }
108
193
  : {}),
109
- errorCount: errors.length,
110
- codes,
111
- missingSecretCount: missingSecrets.length,
112
- missingVariableCount: missingVariables.length,
113
- },
114
- });
115
- return sendJson(res, 200, {
116
- ok: true,
117
- ready,
118
- ...(ownerSelection.ownerSubject !== undefined
119
- ? { ownerSubject: ownerSelection.ownerSubject }
120
- : {}),
121
- target: {
194
+ target: {
195
+ ...tenant,
196
+ appState,
197
+ environmentState,
198
+ },
199
+ config: {
200
+ ready: missingSecrets.length === 0 && missingVariables.length === 0,
201
+ missingSecrets,
202
+ missingVariables,
203
+ },
204
+ errors,
205
+ });
206
+ }
207
+ catch (error) {
208
+ await audit.emit({
209
+ eventType: 'deploy.preflight.failed',
122
210
  ...tenant,
123
- appState,
124
- environmentState,
125
- },
126
- config: {
127
- ready: missingSecrets.length === 0 && missingVariables.length === 0,
128
- missingSecrets,
129
- missingVariables,
130
- },
131
- errors,
132
- });
211
+ actorSubject: identity.subject,
212
+ decision: 'deny',
213
+ status: res.headersSent ? res.statusCode : 500,
214
+ reasonCode: 'preflight_failed',
215
+ details: {
216
+ requestId,
217
+ phase,
218
+ elapsedMs: Date.now() - startedAt,
219
+ clientDisconnected,
220
+ deadlineExceeded,
221
+ },
222
+ });
223
+ throw error;
224
+ }
225
+ finally {
226
+ clearTimeout(timer);
227
+ res.removeListener('close', onClose);
228
+ activePreflights.delete(registry);
229
+ }
133
230
  }
134
231
  function configNames(errors, code, prefix) {
135
232
  return [
@@ -39,7 +39,7 @@
39
39
  "dependencies": {
40
40
  "@modelcontextprotocol/sdk": "^1.29.0",
41
41
  "@noodle-borg/admission-limits": "0.0.0",
42
- "@noodle-borg/agent-kit": "0.93.0",
42
+ "@noodle-borg/agent-kit": "0.94.0",
43
43
  "@noodle-borg/app-package": "0.0.0",
44
44
  "@noodle-borg/assistant-gateway": "0.0.0",
45
45
  "@noodle-borg/auth": "0.0.0",
@@ -137,6 +137,9 @@ export declare const deployErrorResponseSchema: z.ZodObject<{
137
137
  errors: z.ZodOptional<z.ZodUnknown>;
138
138
  }, z.core.$strip>;
139
139
  export type DeployErrorResponse = z.infer<typeof deployErrorResponseSchema>;
140
+ /** Checking budget after upload/authorization; the client allows additional upload/response time. */
141
+ export declare const DEPLOY_PREFLIGHT_CHECK_TIMEOUT_MS = 60000;
142
+ export declare const DEPLOY_PREFLIGHT_CLIENT_TIMEOUT_MS = 75000;
140
143
  /**
141
144
  * `POST /v1/orgs/{org}/apps/{app}/envs/{env}/deploy/preflight` accepts the same
142
145
  * declarative input as deploy, but performs no deployment or asset upload.
@@ -109,6 +109,9 @@ export const deployErrorResponseSchema = z.object({
109
109
  errors: z.unknown().optional(),
110
110
  });
111
111
  // ─── Deploy preflight ─────────────────────────────────────────────────────────────
112
+ /** Checking budget after upload/authorization; the client allows additional upload/response time. */
113
+ export const DEPLOY_PREFLIGHT_CHECK_TIMEOUT_MS = 60_000;
114
+ export const DEPLOY_PREFLIGHT_CLIENT_TIMEOUT_MS = 75_000;
112
115
  /**
113
116
  * `POST /v1/orgs/{org}/apps/{app}/envs/{env}/deploy/preflight` accepts the same
114
117
  * declarative input as deploy, but performs no deployment or asset upload.
@@ -6,7 +6,8 @@ The package exports the canonical `<noodle-assistant>` managed Web Component, th
6
6
  `<noodle-app-view>` MCP App host from `@noodleseed/assistant/app-view`, a managed React wrapper and
7
7
  `NoodleAppView` adapter from `@noodleseed/assistant/react`, a renderer-free React hook from
8
8
  `@noodleseed/assistant/react/client`, a DOM-free client from `@noodleseed/assistant/client`, and the
9
- backend-only `createAssistantSession` helper from `@noodleseed/assistant/server`. The component inherits the
9
+ backend-only `createAssistantSessionHandler` and lower-level `createAssistantSession` exchange from
10
+ `@noodleseed/assistant/server`. The component inherits the
10
11
  deployed MCP server's brand kit while slots, methods, events, and semantic CSS variables let a SaaS developer
11
12
  integrate it without depending on internal DOM selectors. Light, dark, automatic host-page matching, and
12
13
  host-page inversion work without replacing conversation or App DOM.
@@ -193,32 +194,29 @@ noodle assistant clients create --name web --org acme --app support --env prod
193
194
 
194
195
  The command saves `{ clientId, clientSecret }` to a mode-`0600` file and prints its path, never the secret.
195
196
  Move those values to the customer backend's secret manager without printing or committing them. A framework
196
- route can exchange the current signed-in user like this:
197
+ route can use the maintained same-origin handler. Generate the route, authentication adapter, mount and
198
+ contract tests with `noodle assistant embed --framework nextjs --surface authenticated` in the existing
199
+ application. Implement only `authenticateAssistantRequest` using its existing session and membership checks:
197
200
 
198
201
  ```ts
199
- import { createAssistantSession } from "@noodleseed/assistant/server";
200
-
201
- export async function POST(request: Request) {
202
- const user = await requireCurrentUser(request);
203
- const { context } = await request.json();
204
- const session = await createAssistantSession({
205
- serviceUrl: process.env.NOODLE_SERVICE_URL!,
206
- clientId: process.env.NOODLE_ASSISTANT_CLIENT_ID!,
207
- clientSecret: process.env.NOODLE_ASSISTANT_CLIENT_SECRET!,
208
- origin: process.env.PUBLIC_APP_ORIGIN!,
209
- user: {
210
- id: user.id,
211
- email: user.email,
212
- roles: user.roles,
213
- scopes: user.scopes,
214
- },
215
- context,
216
- preferences: { locale: user.locale, timeZone: user.timeZone },
217
- });
218
- return Response.json(session);
219
- }
202
+ import { createAssistantSessionHandler } from "@noodleseed/assistant/server";
203
+ import { authenticateAssistantRequest } from "../../../../lib/noodle-assistant-auth";
204
+
205
+ export const POST = createAssistantSessionHandler({
206
+ serviceUrl: process.env.NOODLE_SERVICE_URL,
207
+ clientId: process.env.NOODLE_ASSISTANT_CLIENT_ID,
208
+ clientSecret: process.env.NOODLE_ASSISTANT_CLIENT_SECRET,
209
+ origin: process.env.PUBLIC_APP_ORIGIN,
210
+ authenticate: authenticateAssistantRequest,
211
+ });
220
212
  ```
221
213
 
214
+ The handler owns exact-Origin and JSON checks, signed-out JSON 401, bounded context/body parsing, no-store
215
+ responses, a 15-second deadline, and sanitized exchange failures without retries or redirects. Your adapter
216
+ returns `AssistantSessionIdentity | null`; its `user`, claims, preferences and routing must be backend-verified.
217
+ Keep stricter application CSRF middleware. Run the generated contract suite and your real identity/tenant
218
+ acceptance tests; synthetic contract tests alone do not verify your login implementation.
219
+
222
220
  For a connector that uses `customerEndpoint("customer_api", ...)`, the authenticated backend may also pass
223
221
  `routing: { endpoints: { customer_api: account.clusterApiBaseUrl } }`. Resolve the URL from server-owned
224
222
  user/account membership after authentication; never accept it from the browser, page context, session
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodleseed/assistant",
3
- "version": "1.33.0",
3
+ "version": "1.34.0",
4
4
  "description": "Embed the Noodle Seed customer-branded assistant in your web app with managed or framework-owned UI, a framework-neutral MCP App host, a DOM-free client, and a backend session helper. Authoring and deploying the server is @noodleseed/one.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodleseed/one",
3
- "version": "0.155.0",
3
+ "version": "0.156.0",
4
4
  "private": false,
5
5
  "description": "Noodle CLI by Noodle Seed — author, run, and deploy declarative MCP servers. Embedding the assistant in your own web app is @noodleseed/assistant.",
6
6
  "license": "Apache-2.0",
@@ -205,7 +205,6 @@
205
205
  "shebang-command",
206
206
  "shebang-regex",
207
207
  "statuses",
208
- "tailwindcss",
209
208
  "tldts",
210
209
  "tldts-core",
211
210
  "toidentifier",
@@ -236,7 +235,7 @@
236
235
  "@modelcontextprotocol/client": "2.0.0",
237
236
  "@modelcontextprotocol/server": "2.0.0",
238
237
  "@noodle-borg/admission-limits": "0.0.0",
239
- "@noodle-borg/agent-kit": "0.93.0",
238
+ "@noodle-borg/agent-kit": "0.94.0",
240
239
  "@noodle-borg/app-audit": "0.0.0",
241
240
  "@noodle-borg/app-package": "0.0.0",
242
241
  "@noodle-borg/assistant-gateway": "0.0.0",
@@ -285,7 +284,9 @@
285
284
  "zod-to-json-schema": "3.25.2"
286
285
  },
287
286
  "devDependencies": {
288
- "@types/node": "24.13.1"
287
+ "@noodleseed/assistant": "@noodleseed/assistant@file:///home/runner/work/noodle-borg/noodle-borg/packages/assistant",
288
+ "@types/node": "24.13.1",
289
+ "next": "16.2.9"
289
290
  },
290
291
  "optionalDependencies": {}
291
292
  }
@@ -1,4 +0,0 @@
1
- import type { ProjectAgentTarget } from './project.js';
2
- /** Default SaaS starter: the portable Apps surface plus customer identity and verified context. */
3
- export declare function saasFiles(name: string, agentTargets: readonly ProjectAgentTarget[]): Record<string, string>;
4
- //# sourceMappingURL=saas-scaffold-template.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"saas-scaffold-template.d.ts","sourceRoot":"","sources":["../src/saas-scaffold-template.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGvD,mGAAmG;AACnG,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,SAAS,kBAAkB,EAAE,GAC1C,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA6DxB"}
@@ -1,56 +0,0 @@
1
- import { widgetFiles } from './widget-scaffold-template.js';
2
- /** Default SaaS starter: the portable Apps surface plus customer identity and verified context. */
3
- export function saasFiles(name, agentTargets) {
4
- const files = widgetFiles(name, agentTargets);
5
- const server = files['src/server.ts'];
6
- const config = files['noodle.json'];
7
- if (server === undefined || config === undefined)
8
- throw new Error('SaaS template source is incomplete');
9
- return {
10
- ...files,
11
- 'noodle.json': config.replace('"template": "widget"', '"template": "saas"'),
12
- 'src/server.ts': server
13
- .replace(' annotations,\n', ' annotations,\n customerAuth,\n')
14
- .replace(" instructions: 'Help users understand their workspace, update preferences, and plan a safe next step.',", ` instructions: 'Help users understand their workspace, update preferences, and plan a safe next step.',
15
- // Replace these placeholders with the customer IdP values used by ChatGPT, Claude, and your app.
16
- auth: customerAuth.federatedOidc({
17
- issuers: [{
18
- issuer: 'https://id.your-app.example.com',
19
- audience: 'https://your-noodle-mcp.example.com/mcp',
20
- }],
21
- }),`)
22
- .replace("description: 'List the current workspace items with concise status summaries.',", "description: 'List the current workspace items with concise status summaries.',\n contextProvider: true,")
23
- .replace("input: z.object({ status: z.enum(['all', 'active', 'done']).default('all').meta({ title: 'Status' }) }),", 'input: z.object({}),'),
24
- 'README.md': `${files['README.md'] ?? ''}
25
-
26
- ## Customer identity and delegated APIs
27
-
28
- The default server uses standards-based federated OIDC so the same MCP endpoint works from remote MCP clients
29
- and any embedded assistant that you explicitly configure. Your application team owns the authorization server; Noodle verifies its tokens
30
- but does not add OAuth discovery or registration endpoints in front of it.
31
-
32
- For every direct or federated issuer, publish all of the following:
33
-
34
- - The path-inserted RFC 8414 metadata URL as direct, unauthenticated HTTP 200 JSON. For issuer
35
- \`https://id.example.com/oauth\`, that URL is
36
- \`https://id.example.com/.well-known/oauth-authorization-server/oauth\`.
37
- - HTTPS \`authorization_endpoint\`, \`token_endpoint\`, \`jwks_uri\`, and RFC 7591
38
- \`registration_endpoint\` values in that metadata.
39
- - Authorization code and refresh-token grants, PKCE \`S256\`, and public clients by advertising
40
- \`code_challenge_methods_supported: ["S256"]\` and
41
- \`token_endpoint_auth_methods_supported: ["none"]\`.
42
- - RFC 8707 \`resource\` handling: validate the exact URL on authorize, code exchange, and refresh, then
43
- map each approved MCP resource to the stable app/environment audience configured in \`customerAuth\`.
44
- - A public JWKS containing only public signing keys.
45
-
46
- Run \`noodle auth doctor src/server.ts\` before sharing the endpoint. The doctor is read-only: it verifies
47
- discovery, metadata, capabilities, and JWKS without registering a client.
48
-
49
- Verified OIDC callers are classified as customers by the runtime; never add a caller-controlled identity
50
- classification claim. For a customer-owned API, declare connector auth as \`delegatedTokenExchange\` and
51
- store its client credential with \`noodle secrets set\`. Use \`noodle auth doctor --live\` before release to
52
- exercise one credential exchange without invoking a business tool.
53
- `,
54
- };
55
- }
56
- //# sourceMappingURL=saas-scaffold-template.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"saas-scaffold-template.js","sourceRoot":"","sources":["../src/saas-scaffold-template.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAE5D,mGAAmG;AACnG,MAAM,UAAU,SAAS,CACvB,IAAY,EACZ,YAA2C;IAE3C,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,eAAe,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC;IACpC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS;QAC9C,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,OAAO;QACL,GAAG,KAAK;QACR,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,sBAAsB,EAAE,oBAAoB,CAAC;QAC3E,eAAe,EAAE,MAAM;aACpB,OAAO,CAAC,kBAAkB,EAAE,mCAAmC,CAAC;aAChE,OAAO,CACN,0GAA0G,EAC1G;;;;;;;MAOF,CACC;aACA,OAAO,CACN,iFAAiF,EACjF,6GAA6G,CAC9G;aACA,OAAO,CACN,0GAA0G,EAC1G,sBAAsB,CACvB;QACH,WAAW,EAAE,GAAG,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6B3C;KACE,CAAC;AACJ,CAAC"}
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) Tailwind Labs, Inc.
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,36 +0,0 @@
1
- <p align="center">
2
- <a href="https://tailwindcss.com" target="_blank">
3
- <picture>
4
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/HEAD/.github/logo-dark.svg">
5
- <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/HEAD/.github/logo-light.svg">
6
- <img alt="Tailwind CSS" src="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/HEAD/.github/logo-light.svg" width="350" height="70" style="max-width: 100%;">
7
- </picture>
8
- </a>
9
- </p>
10
-
11
- <p align="center">
12
- A utility-first CSS framework for rapidly building custom user interfaces.
13
- </p>
14
-
15
- <p align="center">
16
- <a href="https://github.com/tailwindlabs/tailwindcss/actions"><img src="https://img.shields.io/github/actions/workflow/status/tailwindlabs/tailwindcss/ci.yml?branch=main" alt="Build Status"></a>
17
- <a href="https://www.npmjs.com/package/tailwindcss"><img src="https://img.shields.io/npm/dt/tailwindcss.svg" alt="Total Downloads"></a>
18
- <a href="https://github.com/tailwindlabs/tailwindcss/releases"><img src="https://img.shields.io/npm/v/tailwindcss.svg" alt="Latest Release"></a>
19
- <a href="https://github.com/tailwindlabs/tailwindcss/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/tailwindcss.svg" alt="License"></a>
20
- </p>
21
-
22
- ---
23
-
24
- ## Documentation
25
-
26
- For full documentation, visit [tailwindcss.com](https://tailwindcss.com).
27
-
28
- ## Community
29
-
30
- For help, discussion about best practices, or feature ideas:
31
-
32
- [Discuss Tailwind CSS on GitHub](https://github.com/tailwindlabs/tailwindcss/discussions)
33
-
34
- ## Contributing
35
-
36
- If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md) **before submitting a pull request**.
@@ -1 +0,0 @@
1
- function h(n){if(arguments.length===0)throw new TypeError("`CSS.escape` requires an argument.");let e=String(n),i=e.length,r=-1,t,s="",l=e.charCodeAt(0);if(i===1&&l===45)return"\\"+e;for(;++r<i;){if(t=e.charCodeAt(r),t===0){s+="\uFFFD";continue}if(t>=1&&t<=31||t===127||r===0&&t>=48&&t<=57||r===1&&t>=48&&t<=57&&l===45){s+="\\"+t.toString(16)+" ";continue}if(t>=128||t===45||t===95||t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122){s+=e.charAt(r);continue}s+="\\"+e.charAt(r)}return s}function a(n){return n.replace(/\\([\dA-Fa-f]{1,6}[\t\n\f\r ]?|[\S\s])/g,e=>{if(e.length<=2)return e[1];let i=Number.parseInt(e.slice(1).trim(),16);return i===0||i>1114111||i>=55296&&i<=57343?"\uFFFD":String.fromCodePoint(i)})}var c=new Map([["--font",["--font-weight","--font-size"]],["--inset",["--inset-shadow","--inset-ring"]],["--text",["--text-color","--text-decoration-color","--text-decoration-thickness","--text-indent","--text-shadow","--text-underline-offset"]],["--grid-column",["--grid-column-start","--grid-column-end"]],["--grid-row",["--grid-row-start","--grid-row-end"]]]);function g(n,e){return(c.get(e)??[]).some(i=>n===i||n.startsWith(`${i}-`))}var p=class{constructor(e=new Map,i=new Set([])){this.values=e;this.keyframes=i}values;keyframes;prefix=null;get size(){return this.values.size}add(e,i,r=0,t){if(e.endsWith("-*")){if(i!=="initial")throw new Error(`Invalid theme value \`${i}\` for namespace \`${e}\``);e==="--*"?this.values.clear():this.clearNamespace(e.slice(0,-2),0)}if(r&4){let s=this.values.get(e);if(s&&!(s.options&4))return}i==="initial"?this.values.delete(e):this.values.set(e,{value:i,options:r,src:t})}keysInNamespaces(e){let i=[];for(let r of e){let t=`${r}-`;for(let s of this.values.keys())s.startsWith(t)&&s.indexOf("--",2)===-1&&(g(s,r)||i.push(s.slice(t.length)))}return i}get(e){for(let i of e){let r=this.values.get(i);if(r)return r.value}return null}hasDefault(e){return(this.getOptions(e)&4)===4}getOptions(e){return e=a(this.#i(e)),this.values.get(e)?.options??0}entries(){return this.prefix?Array.from(this.values,e=>(e[0]=this.prefixKey(e[0]),e)):this.values.entries()}prefixKey(e){return this.prefix?`--${this.prefix}-${e.slice(2)}`:e}#i(e){return this.prefix?`--${e.slice(3+this.prefix.length)}`:e}clearNamespace(e,i){let r=c.get(e)??[];e:for(let t of this.values.keys())if(t.startsWith(e)){if(i!==0&&(this.getOptions(t)&i)!==i)continue;for(let s of r)if(t.startsWith(s))continue e;this.values.delete(t)}}#e(e,i){for(let r of i){let t=e!==null?`${r}-${e}`:r;if(!this.values.has(t))if(e!==null&&e.includes(".")){if(t=`${r}-${e.replaceAll(".","_")}`,!this.values.has(t))continue}else continue;if(!g(t,r))return t}return null}#t(e){let i=this.values.get(e);if(!i)return null;let r=null;return i.options&2&&(r=i.value),`var(${h(this.prefixKey(e))}${r?`, ${r}`:""})`}markUsedVariable(e){let i=a(this.#i(e)),r=this.values.get(i);if(!r)return!1;let t=r.options&16;return r.options|=16,!t}resolve(e,i,r=0){let t=this.#e(e,i);if(!t)return null;let s=this.values.get(t);return(r|s.options)&1?s.value:this.#t(t)}resolveValue(e,i){let r=this.#e(e,i);return r?this.values.get(r).value:null}resolveWith(e,i,r=[]){let t=this.#e(e,i);if(!t)return null;let s={};for(let u of r){let f=`${t}${u}`,o=this.values.get(f);o&&(o.options&1?s[u]=o.value:s[u]=this.#t(f))}let l=this.values.get(t);return l.options&1?[l.value,s]:[this.#t(t),s]}namespace(e){let i=new Map,r=`${e}-`;for(let[t,s]of this.values)t===e?i.set(null,s.value):t.startsWith(`${r}-`)?i.set(t.slice(e.length),s.value):t.startsWith(r)&&i.set(t.slice(r.length),s.value);return i}addKeyframes(e){this.keyframes.add(e)}getKeyframes(){return Array.from(this.keyframes)}};export{h as a,a as b,p as c};