@growthub/cli 0.14.10 → 0.14.11

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 (52) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/[providerId]/callback/route.js +35 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/[providerId]/failure/route.js +35 -0
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/[providerId]/schedule/route.js +423 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/connect/route.js +78 -0
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/credentials/route.js +276 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/products/[productId]/resources/route.js +173 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/products/sync/route.js +347 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/sync/route.js +293 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/upstash/provider/connect/route.js +7 -0
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/upstash/provider/sync/route.js +7 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/upstash/sync/route.js +197 -0
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/apps/route.js +1 -1
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +1 -49
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +3 -20
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-api-record/route.js +3 -20
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/workflow/publish/route.js +407 -290
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/workflows/[providerId]/route.js +209 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceAddOnsMarketplace.jsx +806 -0
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryActionCard.jsx +141 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/CeoCockpit.jsx +15 -3
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +42 -5
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphCanvas.jsx +5 -1
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +86 -20
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ScheduleCockpit.jsx +363 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +8 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +322 -1
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +2 -2
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/add-ons/add-ons-client.jsx +197 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/add-ons/page.jsx +23 -0
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/settings-shell.jsx +1 -0
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +734 -61
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +15 -10
  33. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/env-status.js +2 -7
  34. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +2 -19
  35. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-serverless-flow.js +8 -4
  36. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/schedule-cockpit-console.js +287 -0
  37. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/scheduler-orchestration.js +449 -0
  38. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-secrets.js +77 -0
  39. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/serverless-readiness.js +583 -0
  40. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-add-on-callback.js +63 -0
  41. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-add-on-scheduler.js +519 -0
  42. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-add-ons.js +957 -0
  43. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-config.js +607 -63
  44. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +21 -0
  45. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-operator-auth.js +32 -0
  46. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/provider.png +0 -0
  47. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/qstash.png +0 -0
  48. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/redis.png +0 -0
  49. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/search.png +0 -0
  50. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/vector.png +0 -0
  51. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/scripts/scheduler-ingress-smoke.mjs +26 -0
  52. package/package.json +1 -1
@@ -0,0 +1,209 @@
1
+ /**
2
+ * POST /api/workspace/workflows/[providerId]
3
+ *
4
+ * Serverless workflow destination. The provider's scheduler (e.g. QStash) calls
5
+ * this signed endpoint on the cron; it is a THIN ADAPTER over the existing
6
+ * governed orchestration loop — NOT a second workflow engine. It:
7
+ *
8
+ * 1. verifies the provider signature (raw body, never re-stringified),
9
+ * 2. parses the governed, non-secret run pointer { workspaceId, objectId, rowId },
10
+ * 3. resolves the SAME sandbox/workflow row the local sandbox-run path uses,
11
+ * 4. executes `runOrchestrationGraphIfPresent` (the existing runner),
12
+ * 5. appends an outcome receipt and returns a compact result.
13
+ *
14
+ * The provider forwards this response to the workspace callback URL, where it is
15
+ * synchronized into workspace config. Durability/retry/scheduling come from the
16
+ * provider; step semantics come from the existing orchestration graph.
17
+ */
18
+
19
+ import { NextResponse } from "next/server";
20
+ import { readWorkspaceConfig } from "@/lib/workspace-config";
21
+ import { getMarketplaceProvider, readTriggerScheduleBinding, liveGraphField } from "@/lib/workspace-add-ons";
22
+ import {
23
+ getSchedulerAdapter,
24
+ isSchedulerProduct,
25
+ resolveWorkspacePublicUrl,
26
+ buildSchedulerCallbackUrls,
27
+ } from "@/lib/workspace-add-on-scheduler";
28
+ import { runOrchestrationGraphIfPresent } from "@/lib/orchestration-graph-runner";
29
+ import { appendOutcomeReceipt } from "@/lib/workspace-outcome-receipts";
30
+
31
+ function clean(value) {
32
+ return String(value == null ? "" : value).trim();
33
+ }
34
+
35
+ function safeJsonParse(text) {
36
+ try {
37
+ return JSON.parse(text);
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ function normalizeScheduledRunInputs(payload, request) {
44
+ const direct = payload?.runInputs;
45
+ if (direct && typeof direct === "object" && !Array.isArray(direct)) {
46
+ if (direct.values && typeof direct.values === "object" && !Array.isArray(direct.values)) return direct;
47
+ return { kind: "growthub-workflow-run-inputs-v1", source: "serverless-scheduler", values: direct };
48
+ }
49
+ const raw = clean(payload?.triggerInput) || clean(request.headers.get("x-growthub-trigger-input"));
50
+ const parsed = raw ? safeJsonParse(raw) : null;
51
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
52
+ if (parsed.values && typeof parsed.values === "object" && !Array.isArray(parsed.values)) {
53
+ return {
54
+ kind: "growthub-workflow-run-inputs-v1",
55
+ ...parsed,
56
+ source: clean(parsed.source) || "serverless-scheduler"
57
+ };
58
+ }
59
+ return { kind: "growthub-workflow-run-inputs-v1", source: "serverless-scheduler", values: parsed };
60
+ }
61
+ return { kind: "growthub-workflow-run-inputs-v1", source: "serverless-scheduler", values: {} };
62
+ }
63
+
64
+ function requestOrigin(request) {
65
+ const forwardedHost = request.headers.get("x-forwarded-host");
66
+ const forwardedProto = request.headers.get("x-forwarded-proto") || "https";
67
+ if (forwardedHost) return `${forwardedProto}://${forwardedHost}`;
68
+ try {
69
+ return new URL(request.url).origin;
70
+ } catch {
71
+ return "";
72
+ }
73
+ }
74
+
75
+ function findSandboxRow(workspaceConfig, objectId, rowId) {
76
+ const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
77
+ const object = objects.find((entry) => entry?.id === objectId && entry?.objectType === "sandbox-environment");
78
+ if (!object) return null;
79
+ const rows = Array.isArray(object.rows) ? object.rows : [];
80
+ return rows.find((row) => clean(row?.Name) === clean(rowId)) || null;
81
+ }
82
+
83
+ async function POST(request, context) {
84
+ const params = await context?.params;
85
+ const provider = getMarketplaceProvider(clean(params?.providerId));
86
+ if (!provider) return NextResponse.json({ ok: false, error: "unknown marketplace provider" }, { status: 404 });
87
+
88
+ const product = (provider.products || []).find((p) => isSchedulerProduct(p));
89
+ if (!product) return NextResponse.json({ ok: false, error: "provider has no scheduler product" }, { status: 400 });
90
+ const adapter = getSchedulerAdapter(product);
91
+
92
+ const rawBody = await request.text();
93
+ const signature = request.headers.get("upstash-signature") || request.headers.get("Upstash-Signature") || "";
94
+ // Signature must be minted for THIS destination route (anti-replay).
95
+ const baseUrl = resolveWorkspacePublicUrl(process.env, requestOrigin(request));
96
+ const expectedUrl = buildSchedulerCallbackUrls(baseUrl, provider.providerId).destinationUrl;
97
+ const verdict = adapter.verifyCallback({ signature, rawBody, expectedUrl, env: process.env });
98
+ if (!verdict.ok) {
99
+ return NextResponse.json({ ok: false, error: "invalid signature", reason: verdict.reason }, { status: 401 });
100
+ }
101
+
102
+ // Run pointer: prefer the JSON body, fall back to the canonical forwarded
103
+ // headers. QStash strips the `Upstash-Forward-` prefix, so these arrive as
104
+ // `x-growthub-*` (NOT `upstash-forward-*`).
105
+ const payload = safeJsonParse(rawBody) || {};
106
+ const objectId = clean(payload.objectId || request.headers.get("x-growthub-object-id"));
107
+ const rowId = clean(payload.rowId || request.headers.get("x-growthub-row-id"));
108
+ if (!objectId || !rowId) {
109
+ return NextResponse.json({ ok: false, error: "missing objectId/rowId run pointer" }, { status: 400 });
110
+ }
111
+
112
+ const workspaceConfig = await readWorkspaceConfig();
113
+ const row = findSandboxRow(workspaceConfig, objectId, rowId);
114
+ if (!row) {
115
+ return NextResponse.json({ ok: false, error: `no sandbox row ${rowId} in object ${objectId}` }, { status: 404 });
116
+ }
117
+
118
+ // A valid signature proves QStash sent this; it does NOT prove the row is
119
+ // STILL bound to this schedule. The inbound scheduleId is REQUIRED (missing
120
+ // identity blocks, never runs), and we validate BOTH the row-level fields AND
121
+ // the live graph trigger node so a stale/rebound delivery cannot execute.
122
+ const scheduleId = clean(payload.scheduleId || request.headers.get("x-growthub-schedule-id"));
123
+ const triggerBinding = readTriggerScheduleBinding(row[liveGraphField(row)]);
124
+ const bindingOk =
125
+ Boolean(scheduleId) &&
126
+ clean(row.runLocality) === "serverless" &&
127
+ clean(row.schedulerRegistryId) === product.integrationId &&
128
+ clean(row.scheduleId) === scheduleId &&
129
+ triggerBinding?.triggerKind === "serverless-scheduler" &&
130
+ triggerBinding?.enabled === true &&
131
+ triggerBinding?.scheduleId === scheduleId &&
132
+ triggerBinding?.schedulerRegistryId === product.integrationId &&
133
+ (!triggerBinding?.providerId || triggerBinding.providerId === provider.providerId) &&
134
+ (!triggerBinding?.productId || triggerBinding.productId === product.productId);
135
+ if (!bindingOk) {
136
+ await appendOutcomeReceipt({
137
+ kind: "workspace-scheduled-run",
138
+ lane: "server-authoritative",
139
+ outcomeStatus: "blocked",
140
+ actor: provider.providerId,
141
+ objectRefs: [{ objectId, objectType: "sandbox-environment", rowName: rowId }],
142
+ policyVerdict: { ok: false, violationCodes: [scheduleId ? "scheduled_run_row_unbound" : "scheduled_run_missing_schedule_id"] },
143
+ summary: `Rejected scheduled run of ${rowId}: ${scheduleId ? `row/trigger not bound to ${product.integrationId} schedule ${scheduleId}` : "missing inbound scheduleId"} (locality=${clean(row.runLocality)}, row.scheduleId=${clean(row.scheduleId) || "none"}, trigger.scheduleId=${triggerBinding?.scheduleId || "none"}).`,
144
+ });
145
+ return NextResponse.json({ ok: false, error: scheduleId ? "row/trigger is not currently bound to this schedule" : "missing inbound scheduleId", scheduleId }, { status: scheduleId ? 409 : 400 });
146
+ }
147
+
148
+ const runId = scheduleId ? `sched_${scheduleId}_${Date.now().toString(36)}` : `sched_${Date.now().toString(36)}`;
149
+ let result;
150
+ try {
151
+ result = await runOrchestrationGraphIfPresent({
152
+ workspaceConfig,
153
+ row,
154
+ timeoutMs: 60000,
155
+ runInputs: normalizeScheduledRunInputs(payload, request),
156
+ executionContext: { runId, ranAt: new Date().toISOString(), sandboxName: row.Name },
157
+ });
158
+ } catch (err) {
159
+ result = { ok: false, exitCode: 1, error: err?.message || "orchestration threw", stdout: "", stderr: "" };
160
+ }
161
+
162
+ const ok = Boolean(result && result.ok !== false);
163
+ await appendOutcomeReceipt({
164
+ kind: "workspace-scheduled-run",
165
+ lane: "server-authoritative",
166
+ outcomeStatus: ok ? "published" : "failed",
167
+ actor: provider.providerId,
168
+ objectRefs: [{ objectId, objectType: "sandbox-environment", rowName: rowId }],
169
+ policyVerdict: { ok },
170
+ runId,
171
+ summary: ok
172
+ ? `Scheduled serverless run of ${rowId} completed via ${provider.label}.`
173
+ : `Scheduled serverless run of ${rowId} failed: ${clean(result?.error) || "unknown"}.`,
174
+ });
175
+
176
+ // Compact result — the provider forwards this (base64) to the callback URL.
177
+ // scheduleId is echoed so the callback can recover schedule identity even when
178
+ // QStash omits a top-level scheduleId on the callback envelope.
179
+ return NextResponse.json(
180
+ {
181
+ ok,
182
+ runId,
183
+ scheduleId,
184
+ objectId,
185
+ rowId,
186
+ exitCode: result?.exitCode ?? (ok ? 0 : 1),
187
+ durationMs: result?.durationMs ?? 0,
188
+ response: result?.response || null,
189
+ stdout: clean(result?.stdout).slice(0, 2000),
190
+ error: ok ? undefined : clean(result?.error) || "run failed",
191
+ },
192
+ { status: ok ? 200 : 502 },
193
+ );
194
+ }
195
+
196
+ function HEAD() {
197
+ return new Response(null, { status: 200 });
198
+ }
199
+
200
+ function OPTIONS() {
201
+ return new Response(null, {
202
+ status: 204,
203
+ headers: {
204
+ allow: "HEAD, OPTIONS, POST",
205
+ },
206
+ });
207
+ }
208
+
209
+ export { HEAD, OPTIONS, POST };