@tokenoftrust/storefront-runner 2.0.2 → 2.0.3

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 (117) hide show
  1. package/package.json +1 -1
  2. package/scripts/lib/tenant-dirs.mjs +80 -0
  3. package/scripts/tenant/lint-embeds.mjs +180 -0
  4. package/scripts/tenant/materialize-tenant-content.mjs +533 -0
  5. package/scripts/tenant/validate.mjs +837 -0
  6. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +0 -68
  7. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +0 -111
  8. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +0 -2
  9. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +0 -247
  10. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +0 -73
  11. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +0 -79
  12. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +0 -10982
  13. package/apps/storefront/scripts/import-v84-through-ws2.ts +0 -38
  14. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +0 -2
  15. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +0 -4
  16. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +0 -185
  17. package/apps/storefront/scripts/prove-clone-isolation.ts +0 -506
  18. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +0 -148
  19. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +0 -35
  20. package/apps/storefront/scripts/replay-v84-clean-clone.ts +0 -214
  21. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +0 -18
  22. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +0 -18
  23. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +0 -18
  24. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +0 -17
  25. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +0 -15
  26. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +0 -21
  27. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +0 -42
  28. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +0 -145
  29. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +0 -18
  30. package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +0 -531
  31. package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +0 -878
  32. package/apps/storefront/src/components/admin/AdminBlogTab.astro +0 -1435
  33. package/apps/storefront/src/components/admin/AdminCheckoutSettingsPanel.astro +0 -226
  34. package/apps/storefront/src/components/admin/AdminCustomersTab.astro +0 -593
  35. package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +0 -465
  36. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +0 -105
  37. package/apps/storefront/src/components/admin/AdminProductsTab.astro +0 -901
  38. package/apps/storefront/src/components/admin/AdminPublishTab.astro +0 -2983
  39. package/apps/storefront/src/components/admin/AdminReportingTab.astro +0 -435
  40. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +0 -782
  41. package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +0 -611
  42. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +0 -1223
  43. package/apps/storefront/src/lib/dev/cockpitStore.ts +0 -57
  44. package/apps/storefront/src/lib/publish/adminPublishTab.ts +0 -4625
  45. package/apps/storefront/src/lib/publish/apex-readiness.ts +0 -360
  46. package/apps/storefront/src/lib/publish/buildStream.ts +0 -269
  47. package/apps/storefront/src/lib/publish/collapsePersistence.ts +0 -72
  48. package/apps/storefront/src/lib/publish/dispatchHealth.ts +0 -267
  49. package/apps/storefront/src/lib/publish/domainState.ts +0 -360
  50. package/apps/storefront/src/lib/publish/lifecycleState.ts +0 -369
  51. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +0 -29
  52. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +0 -78
  53. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +0 -62
  54. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +0 -124
  55. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +0 -75
  56. package/apps/storefront/src/lib/publish/panelPolling.ts +0 -78
  57. package/apps/storefront/src/lib/publish/publish-guard.ts +0 -88
  58. package/apps/storefront/src/lib/publish/publishFormat.ts +0 -165
  59. package/apps/storefront/src/lib/publish/publishHero.ts +0 -177
  60. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +0 -66
  61. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +0 -141
  62. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +0 -41
  63. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +0 -47
  64. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +0 -42
  65. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +0 -59
  66. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +0 -440
  67. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +0 -161
  68. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +0 -325
  69. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +0 -241
  70. package/apps/storefront/src/lib/publish/shipWorkspace.ts +0 -439
  71. package/apps/storefront/src/lib/publish/sseRoute.ts +0 -119
  72. package/apps/storefront/src/lib/publish/stageBundleUpload.ts +0 -215
  73. package/apps/storefront/src/lib/publish/streamLogConsole.ts +0 -142
  74. package/apps/storefront/src/lib/publish/versionNumber.ts +0 -52
  75. package/apps/storefront/src/pages/admin/ops-timeline.astro +0 -420
  76. package/apps/storefront/src/pages/admin/review-release.astro +0 -265
  77. package/apps/storefront/src/pages/admin/select.astro +0 -96
  78. package/apps/storefront/src/pages/admin.astro +0 -570
  79. package/apps/storefront/src/pages/api/apps/admin/credentials/rotate.ts +0 -86
  80. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +0 -174
  81. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +0 -96
  82. package/apps/storefront/src/pages/api/apps/admin/health.ts +0 -44
  83. package/apps/storefront/src/pages/api/apps/admin/install.ts +0 -125
  84. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +0 -118
  85. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +0 -89
  86. package/apps/storefront/src/pages/api/apps/admin/list.ts +0 -26
  87. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +0 -85
  88. package/apps/storefront/src/pages/api/apps/admin/resume.ts +0 -79
  89. package/apps/storefront/src/pages/api/apps/admin/suspend.ts +0 -80
  90. package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +0 -98
  91. package/apps/storefront/src/pages/api/apps/admin/update.ts +0 -126
  92. package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +0 -70
  93. package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries.ts +0 -51
  94. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +0 -255
  95. package/apps/storefront/src/pages/api/apps/oauth/token.ts +0 -87
  96. package/apps/storefront/src/pages/api/apps/v1/attribution.ts +0 -162
  97. package/apps/storefront/src/pages/api/apps/v1/catalog/products/[handle].ts +0 -39
  98. package/apps/storefront/src/pages/api/apps/v1/catalog/products.ts +0 -49
  99. package/apps/storefront/src/pages/api/apps/v1/health.ts +0 -32
  100. package/apps/storefront/src/pages/api/apps/v1/inventory.ts +0 -59
  101. package/apps/storefront/src/pages/api/apps/v1/orders/[id].ts +0 -48
  102. package/apps/storefront/src/pages/api/apps/v1/orders.ts +0 -73
  103. package/apps/storefront/src/pages/api/apps/v1/reports.ts +0 -21
  104. package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +0 -74
  105. package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries.ts +0 -47
  106. package/apps/storefront/src/pages/api/cache-purge.ts +0 -55
  107. package/apps/storefront/src/pages/api/compliance/tamper.ts +0 -88
  108. package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +0 -154
  109. package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +0 -120
  110. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +0 -108
  111. package/apps/storefront/src/pages/api/publish/stage-bundle.ts +0 -70
  112. package/apps/storefront/src/pages/api/test/dev-session.ts +0 -133
  113. package/apps/storefront/src/pages/cockpit.astro +0 -1548
  114. package/apps/storefront/src/pages/dashboard/[appDomain]/apps/index.astro +0 -119
  115. package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +0 -105
  116. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +0 -177
  117. package/apps/storefront/src/pages/dashboard/index.astro +0 -160
@@ -1,120 +0,0 @@
1
- /**
2
- * GET|POST /api/dashboard/vendor-search?q=<prefix> — the ToT-STAFF vendor search
3
- * proxy (#8425 staff access). Phase 1 of two-phase staff admission: a staff viewer
4
- * finds a vendor by prefix; there is no silent access to every host.
5
- *
6
- * AUTHORIZATION — two independent axes:
7
- * NEAR (this endpoint): a valid `tot_session` that carries ToT-STAFF scope
8
- * (`staff[]` non-empty). No session → 401; a session without staff scope →
9
- * 403 (a plain member has nothing to search across — they use their picker).
10
- * FAR (tot20): storefront calls `/clients/vendorSearch` as its OWN operator
11
- * identity, presenting a storefront-signed staff broker assertion in the
12
- * `X-ToT-Broker-Assertion` header (minted from the verified session email);
13
- * tot20 verifies it, derives the staff actor from its `sub`, and scopes results
14
- * to that user's staffRoles. No spoofable plaintext email crosses the wire.
15
- *
16
- * GET reads `q` (+ optional `limit`) from the query; POST reads them from a JSON
17
- * body — the autocomplete client uses GET, but POST is accepted so a no-JS/form
18
- * path works too. Returns `{ vendors: [{ appDomain, displayName }] }`.
19
- */
20
- import type { APIContext } from "astro";
21
- import { readViewerSession } from "@/lib/auth/route";
22
- import { isStaffSession } from "@/lib/dashboard/staffAdmission";
23
- import {
24
- createStaffVendorAccessClient,
25
- staffAccessHttpStatus,
26
- StaffVendorAccessClientError,
27
- } from "@/lib/dashboard/staffVendorAccess";
28
- import { mintBrokerAssertion, BrokerAssertionError } from "@/lib/auth/brokerAssertion";
29
- import {
30
- parseVendorQuery,
31
- ANCHORED_FETCH_LIMIT,
32
- ANCHORED_DISPLAY_LIMIT,
33
- } from "@/lib/dashboard/vendorQuery";
34
-
35
- export const prerender = false;
36
-
37
- /** JSON response with no-store (staff access state must never be edge-cached). */
38
- function json(body: unknown, status = 200): Response {
39
- return new Response(JSON.stringify(body), {
40
- status,
41
- headers: { "content-type": "application/json", "cache-control": "no-store" },
42
- });
43
- }
44
-
45
- async function handle(context: APIContext, q: string, limit: number | undefined): Promise<Response> {
46
- const session = await readViewerSession(context);
47
- if (!session) return json({ error: "not signed in" }, 401);
48
- if (!isStaffSession(session)) {
49
- return json({ error: "staff access required", reason: "notStaff" }, 403);
50
- }
51
-
52
- let client;
53
- try {
54
- client = await createStaffVendorAccessClient();
55
- } catch (err) {
56
- if (err instanceof StaffVendorAccessClientError) {
57
- return json({ error: "staff-access integration not configured" }, 503);
58
- }
59
- throw err;
60
- }
61
-
62
- // #8453 `staff-invite-proof-via-broker`: prove the staff actor with a fresh
63
- // storefront-signed assertion minted from the VERIFIED session email — tot20
64
- // derives the actor + scopes results from it. Fail closed (503) if unconfigured.
65
- let brokerAssertion: string;
66
- try {
67
- brokerAssertion = await mintBrokerAssertion(session.email);
68
- } catch (err) {
69
- if (err instanceof BrokerAssertionError) {
70
- return json({ error: "staff-proof signing not configured" }, 503);
71
- }
72
- throw err;
73
- }
74
-
75
- // Anchor syntax (^prefix / suffix$ / ^exact$) narrows tot20's substring search.
76
- // Send the anchor-stripped CORE term upstream (with a widened cap so the exact hit
77
- // is in the set), then post-filter by the anchor against appDomain. A plain query
78
- // is unchanged: sent as-is, no post-filter. See @/lib/dashboard/vendorQuery.
79
- const parsed = parseVendorQuery(q);
80
- const upstreamQuery = parsed.anchored ? parsed.core : q;
81
- const upstreamLimit = parsed.anchored ? ANCHORED_FETCH_LIMIT : limit;
82
-
83
- const result = await client.searchVendors(brokerAssertion, upstreamQuery, upstreamLimit);
84
- if (!result.ok) {
85
- return json(
86
- { error: result.message, reason: result.reason, code: result.code },
87
- staffAccessHttpStatus(result.reason, result.status),
88
- );
89
- }
90
- const vendors = parsed.anchored
91
- ? result.vendors
92
- .filter((v) => parsed.matches(v.appDomain))
93
- .slice(0, limit && limit > 0 ? limit : ANCHORED_DISPLAY_LIMIT)
94
- : result.vendors;
95
- return json({ vendors });
96
- }
97
-
98
- function parseLimit(raw: string | null | undefined): number | undefined {
99
- if (raw == null || raw === "") return undefined;
100
- const n = Number(raw);
101
- return Number.isFinite(n) && n > 0 ? Math.floor(n) : undefined;
102
- }
103
-
104
- export async function GET(context: APIContext): Promise<Response> {
105
- const url = new URL(context.request.url);
106
- const q = (url.searchParams.get("q") ?? "").trim();
107
- return handle(context, q, parseLimit(url.searchParams.get("limit")));
108
- }
109
-
110
- export async function POST(context: APIContext): Promise<Response> {
111
- let body: { q?: unknown; limit?: unknown };
112
- try {
113
- body = (await context.request.json()) as typeof body;
114
- } catch {
115
- return json({ error: "invalid JSON body" }, 400);
116
- }
117
- const q = typeof body.q === "string" ? body.q.trim() : "";
118
- const limit = typeof body.limit === "number" ? body.limit : undefined;
119
- return handle(context, q, limit);
120
- }
@@ -1,108 +0,0 @@
1
- /** Published managed analytics v1 RPC transport: basePath + client method name. */
2
- import type { APIContext } from "astro";
3
- import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
4
- import { getManagedAnalyticsService, json, requireManageRole } from "@/lib/apps/adminService.js";
5
- import {
6
- validateCreateIntegrationInstallRequest, validateCreateMigrationDraftRequest,
7
- validateDeleteIntegrationInstallRequest, validateDeleteMigrationDraftRequest,
8
- validateGetAdapterDefinitionRequest, validateGetIntegrationInstallRequest,
9
- validateGetIntegrationRevisionRequest, validateGetMigrationDraftRequest,
10
- validateListAdapterDefinitionsRequest, validateListIntegrationInstallsRequest,
11
- validateListIntegrationRevisionsRequest, validateReviseIntegrationInstallRequest,
12
- validateSetIntegrationInstallStatusRequest, validateUpdateMigrationDraftRequest,
13
- type CreateIntegrationInstallRequest, type CreateMigrationDraftRequest,
14
- type DeleteIntegrationInstallRequest, type DeleteMigrationDraftRequest,
15
- type GetAdapterDefinitionRequest, type GetIntegrationInstallRequest,
16
- type GetIntegrationRevisionRequest, type GetMigrationDraftRequest,
17
- type ListAdapterDefinitionsRequest, type ListIntegrationInstallsRequest,
18
- type ListIntegrationRevisionsRequest, type ReviseIntegrationInstallRequest,
19
- type SetIntegrationInstallStatusRequest, type UpdateMigrationDraftRequest,
20
- } from "@tot/public-runtime";
21
- import { MANAGED_ANALYTICS_READ_OPERATIONS, managedAnalyticsOpaqueId, sanitizeManagedAnalyticsOutbound, type ManagedAnalyticsOperation } from "@/lib/apps/managedAnalyticsService.js";
22
-
23
- export const prerender = false;
24
- type Validator = (input: unknown) => { ok: true; value: unknown } | { ok: false; violations: Array<{ path: string; code: string; message: string }> };
25
-
26
- // Null-prototype + own-property lookup: `constructor`/prototype names can never
27
- // enter dispatch, even if a runtime supplies them as a dynamic path segment.
28
- const VALIDATORS: Readonly<Record<string, Validator>> = Object.assign(Object.create(null) as Record<string, Validator>, {
29
- listAdapterDefinitions: validateListAdapterDefinitionsRequest,
30
- getAdapterDefinition: validateGetAdapterDefinitionRequest,
31
- listIntegrationInstalls: validateListIntegrationInstallsRequest,
32
- getIntegrationInstall: validateGetIntegrationInstallRequest,
33
- listIntegrationRevisions: validateListIntegrationRevisionsRequest,
34
- getIntegrationRevision: validateGetIntegrationRevisionRequest,
35
- getMigrationDraft: validateGetMigrationDraftRequest,
36
- createMigrationDraft: validateCreateMigrationDraftRequest,
37
- updateMigrationDraft: validateUpdateMigrationDraftRequest,
38
- deleteMigrationDraft: validateDeleteMigrationDraftRequest,
39
- createIntegrationInstall: validateCreateIntegrationInstallRequest,
40
- reviseIntegrationInstall: validateReviseIntegrationInstallRequest,
41
- setIntegrationInstallStatus: validateSetIntegrationInstallStatusRequest,
42
- deleteIntegrationInstall: validateDeleteIntegrationInstallRequest,
43
- });
44
-
45
- const STATUS: Record<string, number> = {
46
- invalid_request: 400, unauthorized: 401, forbidden: 403, not_found: 404,
47
- validation_failed: 422, unsupported_version: 400, stale_revision: 409,
48
- idempotency_conflict: 409, adapter_deprecated: 409, conflict: 409,
49
- internal_error: 500,
50
- };
51
- function response(body: unknown): Response {
52
- const code = body && typeof body === "object" && "error" in body
53
- ? (body as { error?: { code?: string } }).error?.code : undefined;
54
- return json(body, code ? STATUS[code] ?? 500 : 200);
55
- }
56
- function routeError(code: string, message: string, details?: Array<{ path: string; code: string; message: string }>): Response {
57
- return response({ apiVersion: "v1", requestId: managedAnalyticsOpaqueId("req"), error: { code, message, ...(details?.length ? { details } : {}) } });
58
- }
59
- function operationResponse(operation: ManagedAnalyticsOperation, body: unknown): Response {
60
- const safe = sanitizeManagedAnalyticsOutbound(operation, body);
61
- return safe === null ? routeError("internal_error", "The integration service could not complete the request.") : response(safe);
62
- }
63
-
64
- export async function POST(context: APIContext): Promise<Response> {
65
- try {
66
- const operation = context.params.operation;
67
- if (!operation || !Object.prototype.hasOwnProperty.call(VALIDATORS, operation)) {
68
- return routeError("invalid_request", "Unknown managed analytics v1 operation.");
69
- }
70
- const op = operation as ManagedAnalyticsOperation;
71
- const url = new URL(context.request.url);
72
- const auth = await resolveOperatorSession(context, url.searchParams.get("tenant"));
73
- if (!auth.ok) return routeError(auth.status === 401 ? "unauthorized" : "forbidden", "The request is not authorized.");
74
- if (!MANAGED_ANALYTICS_READ_OPERATIONS.has(op) && requireManageRole(auth)) {
75
- return routeError("forbidden", "The request is not authorized.");
76
- }
77
- let raw: unknown;
78
- try { raw = await context.request.json(); }
79
- catch { return routeError("invalid_request", "The request body must be valid JSON."); }
80
- const parsed = VALIDATORS[operation]!(raw);
81
- if (!parsed.ok) {
82
- const unsupportedVersion = parsed.violations.some(({ path, code }) => path === "apiVersion" && code === "unsupported_value");
83
- return routeError(unsupportedVersion ? "unsupported_version" : "invalid_request", unsupportedVersion ? "The API version is not supported." : "The request failed validation.", parsed.violations);
84
- }
85
- const service = await getManagedAnalyticsService();
86
- if (service instanceof Response) return routeError("internal_error", "The integration service is unavailable.");
87
- const ctx = { tenantId: auth.tenantId, actor: auth.actor };
88
- const request = parsed.value;
89
- switch (op) {
90
- case "listAdapterDefinitions": return operationResponse(op, service.listAdapterDefinitions(request as ListAdapterDefinitionsRequest));
91
- case "getAdapterDefinition": return operationResponse(op, service.getAdapterDefinition((request as GetAdapterDefinitionRequest).query.adapterId));
92
- case "listIntegrationInstalls": return operationResponse(op, await service.listIntegrationInstalls(ctx, request as ListIntegrationInstallsRequest));
93
- case "getIntegrationInstall": return operationResponse(op, await service.getIntegrationInstall(ctx, (request as GetIntegrationInstallRequest).query.installId));
94
- case "listIntegrationRevisions": { const value = request as ListIntegrationRevisionsRequest; return operationResponse(op, await service.listIntegrationRevisions(ctx, value.query.installId, value.query)); }
95
- case "getIntegrationRevision": { const value = request as GetIntegrationRevisionRequest; return operationResponse(op, await service.getIntegrationRevision(ctx, value.query.installId, value.query.revisionId)); }
96
- case "getMigrationDraft": return operationResponse(op, await service.getMigrationDraft(ctx, (request as GetMigrationDraftRequest).query.draftId));
97
- case "createMigrationDraft": return operationResponse(op, await service.createMigrationDraft(ctx, request as CreateMigrationDraftRequest));
98
- case "updateMigrationDraft": return operationResponse(op, await service.updateMigrationDraft(ctx, request as UpdateMigrationDraftRequest));
99
- case "deleteMigrationDraft": return operationResponse(op, await service.deleteMigrationDraft(ctx, request as DeleteMigrationDraftRequest));
100
- case "createIntegrationInstall": return operationResponse(op, await service.createIntegrationInstall(ctx, request as CreateIntegrationInstallRequest));
101
- case "reviseIntegrationInstall": return operationResponse(op, await service.reviseIntegrationInstall(ctx, request as ReviseIntegrationInstallRequest));
102
- case "setIntegrationInstallStatus": return operationResponse(op, await service.setIntegrationInstallStatus(ctx, request as SetIntegrationInstallStatusRequest));
103
- case "deleteIntegrationInstall": return operationResponse(op, await service.deleteIntegrationInstall(ctx, request as DeleteIntegrationInstallRequest));
104
- }
105
- } catch {
106
- return routeError("internal_error", "The integration service could not complete the request.");
107
- }
108
- }
@@ -1,70 +0,0 @@
1
- /**
2
- * PUT/HEAD /api/publish/stage-bundle?key=versions/<digest>/<rel> — the
3
- * authenticated Worker staging surface that writes bundle objects into the
4
- * `STATIC_BUNDLES` R2 binding, so CI stages digests WITHOUT a static R2
5
- * S3-compatible key pair. Auth (a CI `private_key_jwt` assertion, RFC 7523/8707,
6
- * one-time `jti`) + the write logic live in `@/lib/publish/stageBundleUpload`;
7
- * this route only resolves the runtime bindings/config.
8
- *
9
- * Fails CLOSED (503 "not configured") until a human wires the CI client's public
10
- * JWKS (`STATIC_BUNDLE_UPLOAD_JWKS`) + the endpoint's own audience
11
- * (`STATIC_BUNDLE_UPLOAD_AUDIENCE`) — the same "needs provisioning" gate the R2
12
- * buckets themselves sit behind. It is EXEMPT from the demo login gate
13
- * (loginGate.ts `isStaticBundleUploadPath`): it carries its own, stricter auth,
14
- * so it must answer server-to-server rather than being masked by a "Coming soon"
15
- * holding page.
16
- */
17
- import type { APIContext } from "astro";
18
- import { readEnv, readKv, readR2 } from "@/lib/env";
19
- import {
20
- handleStageBundleUpload,
21
- kvJtiReplayStore,
22
- DEFAULT_STAGE_BUNDLE_UPLOAD_ISSUER,
23
- type StageBundleUploadBucket,
24
- type StageBundleUploadConfig,
25
- type JtiKvLike,
26
- } from "@/lib/publish/stageBundleUpload";
27
-
28
- export const prerender = false;
29
-
30
- async function handle(context: APIContext): Promise<Response> {
31
- const bucket = (await readR2("STATIC_BUNDLES")) as StageBundleUploadBucket | undefined;
32
- if (!bucket) {
33
- return new Response(JSON.stringify({ error: "no_bundle_store" }), {
34
- status: 503,
35
- headers: { "content-type": "application/json" },
36
- });
37
- }
38
-
39
- const jwksRaw = await readEnv("STATIC_BUNDLE_UPLOAD_JWKS");
40
- const audience = await readEnv("STATIC_BUNDLE_UPLOAD_AUDIENCE");
41
- if (!jwksRaw || !audience) {
42
- return new Response(JSON.stringify({ error: "upload_not_configured" }), {
43
- status: 503,
44
- headers: { "content-type": "application/json" },
45
- });
46
- }
47
- let jwks: StageBundleUploadConfig["jwks"];
48
- try {
49
- jwks = JSON.parse(jwksRaw) as StageBundleUploadConfig["jwks"];
50
- } catch {
51
- return new Response(JSON.stringify({ error: "upload_not_configured" }), {
52
- status: 503,
53
- headers: { "content-type": "application/json" },
54
- });
55
- }
56
- const issuer =
57
- (await readEnv("STATIC_BUNDLE_UPLOAD_ISSUER")) ?? DEFAULT_STAGE_BUNDLE_UPLOAD_ISSUER;
58
-
59
- const kv = (await readKv("TENANT_CACHE")) as JtiKvLike | undefined;
60
-
61
- return handleStageBundleUpload({
62
- request: context.request,
63
- bucket,
64
- config: { jwks, issuer, audience },
65
- ...(kv ? { jtiStore: kvJtiReplayStore(kv) } : {}),
66
- });
67
- }
68
-
69
- export const PUT = handle;
70
- export const HEAD = handle;
@@ -1,133 +0,0 @@
1
- /**
2
- * POST /api/test/dev-session — E2E-ONLY hands-off sign-in for automated tests.
3
- *
4
- * WHY THIS EXISTS
5
- * ---------------
6
- * The hosted /cockpit `hostedActivity` flow can only be driven by a signed-in,
7
- * store-LINKED developer session, and the real sign-in paths (`/api/auth/verify`
8
- * OTP code, `/auth/magic` token) deliver their secret ONLY by email — so an
9
- * automated regression test can never bootstrap itself without a human relaying a
10
- * code/link out of an inbox. This endpoint mints that session directly for a test
11
- * developer + tenant so `e2e/cockpit-signal-rail.spec.ts` can run unattended.
12
- *
13
- * IT DOES NOT WEAKEN THE REAL AUTH FLOW. It is a SEPARATE, secret-gated door used
14
- * only as a test PRECONDITION — auth-flow fidelity (does invite→email→magic→session
15
- * actually work?) is a different concern and must still be tested against the real
16
- * path with a real/test inbox. See e2e/README.md §"Why a mint endpoint, not a flag".
17
- *
18
- * SAFETY — fail CLOSED, three independent gates (a env-detection bug alone can't open it):
19
- * 1. `E2E_TEST_SECRET` must be provisioned. Unset → 404 (route behaves as if it
20
- * does not exist). PROVISION IT ONLY IN THE TEST/PREVIEW TIER, NEVER PROD.
21
- * 2. `NODE_ENV === "production"` → 404 regardless of anything else (defense in depth;
22
- * preview/qa run NODE_ENV=test, so this permits them and blocks prod).
23
- * 3. The `x-e2e-secret` header must match `E2E_TEST_SECRET` (constant-time compare).
24
- * Blast radius is further bounded: the tenant must resolve via resolvePreviewTenant
25
- * (a known store), and the minted session/token are the ordinary revocable KV
26
- * artifacts (deleting the KV entry revokes them) — nothing new or long-lived.
27
- *
28
- * This mirrors the existing shared-secret seam the storefront already uses for
29
- * cli-signin-code (lib/dev/cliSignInCode.ts → MCP `MCP_SERVICE_SHARED_SECRET`): a
30
- * capability-gated internal door, not an ambient env-mode branch.
31
- */
32
- import type { APIContext } from "astro";
33
- import { getSessionStore, setSessionCookie } from "@/lib/auth/route";
34
- import { createSession, DEFAULT_SESSION_TTL_SECONDS } from "@/lib/auth/session";
35
- import { readEnv, readKv } from "@/lib/env";
36
- import { mintActivityIngestToken } from "@/lib/dev/activityIngestToken";
37
- import { resolvePreviewTenant } from "@/config/previewTenantLookup";
38
-
39
- export const prerender = false;
40
-
41
- function json(body: unknown, status = 200): Response {
42
- return new Response(JSON.stringify(body), {
43
- status,
44
- headers: { "content-type": "application/json", "cache-control": "no-store" },
45
- });
46
- }
47
-
48
- /** Length-independent constant-time string compare (avoids leaking the secret by timing). */
49
- function timingSafeEqual(a: string, b: string): boolean {
50
- // XOR the SHA-equalised inputs isn't necessary here; compare fixed-work over the
51
- // longer length so mismatched lengths still take constant time relative to input.
52
- const len = Math.max(a.length, b.length);
53
- let diff = a.length ^ b.length;
54
- for (let i = 0; i < len; i++) {
55
- diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
56
- }
57
- return diff === 0;
58
- }
59
-
60
- export async function POST(context: APIContext): Promise<Response> {
61
- // GATE 1 — secret must be provisioned; absent ⇒ pretend the route doesn't exist.
62
- const secret = await readEnv("E2E_TEST_SECRET");
63
- if (!secret) return json({ error: "not found" }, 404);
64
-
65
- // GATE 2 — never in the production tier, even if a secret leaked into it.
66
- const nodeEnv = ((await readEnv("NODE_ENV")) ?? "").toLowerCase();
67
- if (nodeEnv === "production") return json({ error: "not found" }, 404);
68
-
69
- // GATE 3 — present the secret.
70
- const presented = context.request.headers.get("x-e2e-secret") ?? "";
71
- if (!timingSafeEqual(presented, secret)) return json({ error: "forbidden" }, 403);
72
-
73
- let body: { email?: unknown; tenant?: unknown };
74
- try {
75
- body = (await context.request.json()) as typeof body;
76
- } catch {
77
- return json({ error: "invalid JSON body" }, 400);
78
- }
79
- const email = typeof body.email === "string" ? body.email.trim().toLowerCase() : "";
80
- const tenantId = typeof body.tenant === "string" ? body.tenant.trim().toLowerCase() : "";
81
- if (!email || !tenantId) return json({ error: "email and tenant are required" }, 400);
82
-
83
- // Bound to a KNOWN store — never mints against an arbitrary/unknown tenant.
84
- const tenant = await resolvePreviewTenant(tenantId);
85
- if (!tenant) return json({ error: "unknown tenant" }, 404);
86
-
87
- const store = await getSessionStore();
88
- if (!store) return json({ error: "session store unavailable" }, 503);
89
-
90
- // Mint a store-LINKED developer session (tenants[0].resource is what the
91
- // /api/dev/activity poll resolves — see api/dev/activity.ts GET).
92
- const now = Math.floor(Date.now() / 1000);
93
- const resource = tenant.appDomain;
94
- const { id } = await createSession(
95
- store,
96
- {
97
- email,
98
- resource,
99
- roles: ["developer"],
100
- capability: "developer",
101
- tenants: [{ resource, role: "developer" }],
102
- staff: [],
103
- },
104
- DEFAULT_SESSION_TTL_SECONDS,
105
- now,
106
- );
107
- setSessionCookie(context, id, DEFAULT_SESSION_TTL_SECONDS);
108
-
109
- // Also hand back an activity-ingest token so the test can POST heartbeat / save /
110
- // obstacle events the way the local `tot dev` loop would (bearer-authed, not cookie).
111
- const kv = await readKv("SESSION");
112
- let activityToken: string | null = null;
113
- let activityExpiresAt: string | null = null;
114
- if (kv) {
115
- const minted = await mintActivityIngestToken(kv, {
116
- email,
117
- tenant: tenant.tenant_id,
118
- capability: "developer",
119
- });
120
- activityToken = minted.token;
121
- activityExpiresAt = minted.expiresAt;
122
- }
123
-
124
- return json({
125
- ok: true,
126
- email,
127
- tenant: tenant.tenant_id,
128
- resource,
129
- activityToken,
130
- activityExpiresAt,
131
- activityUrl: new URL(context.request.url).origin,
132
- });
133
- }