@tokenoftrust/storefront-runner 2.0.2 → 2.0.4
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.
- package/apps/storefront/.astro/content.d.ts +159 -0
- package/apps/storefront/.astro/integrations/_astrojs_cloudflare/cloudflare.d.ts +1 -0
- package/apps/storefront/.astro/types.d.ts +3 -0
- package/apps/storefront/node_modules/.bin/acorn +43 -0
- package/apps/storefront/node_modules/.bin/astro +43 -0
- package/apps/storefront/node_modules/.bin/astro-check +43 -0
- package/apps/storefront/node_modules/.bin/browserslist +43 -0
- package/apps/storefront/node_modules/.bin/cf-wrangler +43 -0
- package/apps/storefront/node_modules/.bin/depcruise +43 -0
- package/apps/storefront/node_modules/.bin/depcruise-baseline +43 -0
- package/apps/storefront/node_modules/.bin/depcruise-fmt +43 -0
- package/apps/storefront/node_modules/.bin/depcruise-wrap-stream-in-html +43 -0
- package/apps/storefront/node_modules/.bin/dependency-cruise +43 -0
- package/apps/storefront/node_modules/.bin/dependency-cruiser +43 -0
- package/apps/storefront/node_modules/.bin/drizzle-kit +43 -0
- package/apps/storefront/node_modules/.bin/esbuild +43 -0
- package/apps/storefront/node_modules/.bin/jiti +43 -0
- package/apps/storefront/node_modules/.bin/playwright +43 -0
- package/apps/storefront/node_modules/.bin/playwright-core +43 -0
- package/apps/storefront/node_modules/.bin/prettier +43 -0
- package/apps/storefront/node_modules/.bin/rollup +43 -0
- package/apps/storefront/node_modules/.bin/tsc +43 -0
- package/apps/storefront/node_modules/.bin/tsserver +43 -0
- package/apps/storefront/node_modules/.bin/tsx +43 -0
- package/apps/storefront/node_modules/.bin/vite +43 -0
- package/apps/storefront/node_modules/.bin/vitest +43 -0
- package/apps/storefront/node_modules/.bin/workerd +43 -0
- package/apps/storefront/node_modules/.bin/wrangler +43 -0
- package/apps/storefront/node_modules/.bin/wrangler2 +43 -0
- package/apps/storefront/node_modules/.bin/yaml +43 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_client__js.js +58 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_client__js.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_n_@preact_signals.js +741 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_n_@preact_signals.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_server__js.js +2549 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_server__js.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/_metadata.json +150 -0
- package/apps/storefront/node_modules/.vite/deps/astro-BIHuWFxm.js +434 -0
- package/apps/storefront/node_modules/.vite/deps/astro-BIHuWFxm.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_axobject-query.js +2705 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
- package/apps/storefront/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/audit-DKDpcdEI.js +1535 -0
- package/apps/storefront/node_modules/.vite/deps/audit-DKDpcdEI.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/highlight-B9UoX1wa.js +48 -0
- package/apps/storefront/node_modules/.vite/deps/highlight-B9UoX1wa.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/icons-BWi2wI9n.js +42 -0
- package/apps/storefront/node_modules/.vite/deps/icons-BWi2wI9n.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/package.json +3 -0
- package/apps/storefront/node_modules/.vite/deps/preact.js +310 -0
- package/apps/storefront/node_modules/.vite/deps/preact.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_compat.js +420 -0
- package/apps/storefront/node_modules/.vite/deps/preact_compat.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_compat_jsx-runtime.js +4 -0
- package/apps/storefront/node_modules/.vite/deps/preact_debug.js +236 -0
- package/apps/storefront/node_modules/.vite/deps/preact_debug.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_devtools.js +12 -0
- package/apps/storefront/node_modules/.vite/deps/preact_devtools.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_hooks.js +192 -0
- package/apps/storefront/node_modules/.vite/deps/preact_hooks.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_jsx-dev-runtime.js +3 -0
- package/apps/storefront/node_modules/.vite/deps/preact_jsx-runtime.js +92 -0
- package/apps/storefront/node_modules/.vite/deps/preact_jsx-runtime.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_test-utils.js +59 -0
- package/apps/storefront/node_modules/.vite/deps/preact_test-utils.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/rolldown-runtime-BPOCksWG.js +24 -0
- package/apps/storefront/node_modules/.vite/deps/settings-BVDV0Roj.js +189 -0
- package/apps/storefront/node_modules/.vite/deps/settings-BVDV0Roj.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/static-html-DveYqwMo.js +13 -0
- package/apps/storefront/node_modules/.vite/deps/static-html-DveYqwMo.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/toolbar-B3VIHCXg.js +458 -0
- package/apps/storefront/node_modules/.vite/deps/toolbar-B3VIHCXg.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/ui-library-BK6ricUd.js +1016 -0
- package/apps/storefront/node_modules/.vite/deps/ui-library-BK6ricUd.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/window-BvxDenKN.js +32 -0
- package/apps/storefront/node_modules/.vite/deps/window-BvxDenKN.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/xray-YFiRUU0Z.js +113 -0
- package/apps/storefront/node_modules/.vite/deps/xray-YFiRUU0Z.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps_ssr/_metadata.json +8 -0
- package/apps/storefront/node_modules/.vite/deps_ssr/package.json +3 -0
- package/apps/storefront/src/layouts/Layout.astro +1 -1
- package/apps/storefront/src/lib/apps/registryAccess.ts +45 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +4 -4
- package/apps/storefront/src/lib/shared/capability.ts +68 -0
- package/apps/storefront/src/lib/shared/developerInvites.ts +1250 -0
- package/apps/storefront/src/lib/shared/invitePreflight.ts +123 -0
- package/apps/storefront/src/lib/webhooks/deliveryEngine.ts +38 -4
- package/apps/storefront/src/lib/webhooks/emit.ts +1 -1
- package/apps/storefront/src/lib/webhooks/getDispatcher.ts +1 -1
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +1 -1
- package/apps/storefront/src/pages/index.astro +1 -1
- package/apps/storefront/src/pages/products/[handle].astro +1 -1
- package/package.json +1 -1
- package/packages/public-runtime/node_modules/.bin/tsc +43 -0
- package/packages/public-runtime/node_modules/.bin/tsserver +43 -0
- package/packages/public-runtime/node_modules/.bin/vitest +43 -0
- package/pnpm-workspace.yaml +3 -0
- package/scripts/lib/tenant-dirs.mjs +80 -0
- package/scripts/tenant/lint-embeds.mjs +180 -0
- package/scripts/tenant/materialize-tenant-content.mjs +533 -0
- package/scripts/tenant/validate.mjs +837 -0
- package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +0 -68
- package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +0 -111
- package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +0 -2
- package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +0 -247
- package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +0 -73
- package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +0 -79
- package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +0 -10982
- package/apps/storefront/scripts/import-v84-through-ws2.ts +0 -38
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +0 -2
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +0 -4
- package/apps/storefront/scripts/install-v84-native-through-ws1.ts +0 -185
- package/apps/storefront/scripts/prove-clone-isolation.ts +0 -506
- package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +0 -148
- package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +0 -35
- package/apps/storefront/scripts/replay-v84-clean-clone.ts +0 -214
- package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +0 -18
- package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +0 -18
- package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +0 -18
- package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +0 -17
- package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +0 -15
- package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +0 -21
- package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +0 -42
- package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +0 -145
- package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +0 -18
- package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +0 -531
- package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +0 -878
- package/apps/storefront/src/components/admin/AdminBlogTab.astro +0 -1435
- package/apps/storefront/src/components/admin/AdminCheckoutSettingsPanel.astro +0 -226
- package/apps/storefront/src/components/admin/AdminCustomersTab.astro +0 -593
- package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +0 -465
- package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +0 -105
- package/apps/storefront/src/components/admin/AdminProductsTab.astro +0 -901
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +0 -2983
- package/apps/storefront/src/components/admin/AdminReportingTab.astro +0 -435
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +0 -782
- package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +0 -611
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +0 -1223
- package/apps/storefront/src/lib/apps/adminService.ts +0 -116
- package/apps/storefront/src/lib/apps/adminSession.ts +0 -214
- package/apps/storefront/src/lib/apps/apiAuth.ts +0 -91
- package/apps/storefront/src/lib/apps/apiRoute.ts +0 -35
- package/apps/storefront/src/lib/apps/catalogMapper.ts +0 -39
- package/apps/storefront/src/lib/apps/consentConfigStore.ts +0 -228
- package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +0 -447
- package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +0 -367
- package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +0 -315
- package/apps/storefront/src/lib/apps/healthAggregate.ts +0 -66
- package/apps/storefront/src/lib/apps/integrationLifecycle.ts +0 -614
- package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +0 -125
- package/apps/storefront/src/lib/apps/integrationReadModel.ts +0 -563
- package/apps/storefront/src/lib/apps/integrationsTab.ts +0 -547
- package/apps/storefront/src/lib/apps/integrationsView.ts +0 -106
- package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +0 -599
- package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +0 -170
- package/apps/storefront/src/lib/apps/tokenIssuer.ts +0 -121
- package/apps/storefront/src/lib/apps/tokenVerifier.ts +0 -148
- package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +0 -16
- package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +0 -105
- package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +0 -177
- package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +0 -251
- package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +0 -241
- package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +0 -211
- package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +0 -409
- package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +0 -368
- package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +0 -188
- package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +0 -291
- package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +0 -95
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +0 -91
- package/apps/storefront/src/lib/content-edit/client.ts +0 -286
- package/apps/storefront/src/lib/content-edit/index.ts +0 -14
- package/apps/storefront/src/lib/content-edit/route.ts +0 -25
- package/apps/storefront/src/lib/content-edit/types.ts +0 -19
- package/apps/storefront/src/lib/dev/cockpitStore.ts +0 -57
- package/apps/storefront/src/lib/publish/adminPublishTab.ts +0 -4625
- package/apps/storefront/src/lib/publish/apex-readiness.ts +0 -360
- package/apps/storefront/src/lib/publish/buildStream.ts +0 -269
- package/apps/storefront/src/lib/publish/collapsePersistence.ts +0 -72
- package/apps/storefront/src/lib/publish/dispatchHealth.ts +0 -267
- package/apps/storefront/src/lib/publish/domainState.ts +0 -360
- package/apps/storefront/src/lib/publish/lifecycleState.ts +0 -369
- package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +0 -29
- package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +0 -78
- package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +0 -62
- package/apps/storefront/src/lib/publish/mergeRowFindings.ts +0 -124
- package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +0 -75
- package/apps/storefront/src/lib/publish/panelPolling.ts +0 -78
- package/apps/storefront/src/lib/publish/publish-guard.ts +0 -88
- package/apps/storefront/src/lib/publish/publishFormat.ts +0 -165
- package/apps/storefront/src/lib/publish/publishHero.ts +0 -177
- package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +0 -66
- package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +0 -141
- package/apps/storefront/src/lib/publish/reviewRelease/index.ts +0 -41
- package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +0 -47
- package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +0 -42
- package/apps/storefront/src/lib/publish/reviewRelease/types.ts +0 -59
- package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +0 -440
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +0 -161
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +0 -325
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +0 -241
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +0 -439
- package/apps/storefront/src/lib/publish/sseRoute.ts +0 -119
- package/apps/storefront/src/lib/publish/stageBundleUpload.ts +0 -215
- package/apps/storefront/src/lib/publish/streamLogConsole.ts +0 -142
- package/apps/storefront/src/lib/publish/versionNumber.ts +0 -52
- package/apps/storefront/src/pages/admin/ops-timeline.astro +0 -420
- package/apps/storefront/src/pages/admin/review-release.astro +0 -265
- package/apps/storefront/src/pages/admin/select.astro +0 -96
- package/apps/storefront/src/pages/admin.astro +0 -570
- package/apps/storefront/src/pages/api/apps/admin/credentials/rotate.ts +0 -86
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +0 -174
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +0 -96
- package/apps/storefront/src/pages/api/apps/admin/health.ts +0 -44
- package/apps/storefront/src/pages/api/apps/admin/install.ts +0 -125
- package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +0 -118
- package/apps/storefront/src/pages/api/apps/admin/integrations.ts +0 -89
- package/apps/storefront/src/pages/api/apps/admin/list.ts +0 -26
- package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +0 -85
- package/apps/storefront/src/pages/api/apps/admin/resume.ts +0 -79
- package/apps/storefront/src/pages/api/apps/admin/suspend.ts +0 -80
- package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +0 -98
- package/apps/storefront/src/pages/api/apps/admin/update.ts +0 -126
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +0 -70
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries.ts +0 -51
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +0 -255
- package/apps/storefront/src/pages/api/apps/oauth/token.ts +0 -87
- package/apps/storefront/src/pages/api/apps/v1/attribution.ts +0 -162
- package/apps/storefront/src/pages/api/apps/v1/catalog/products/[handle].ts +0 -39
- package/apps/storefront/src/pages/api/apps/v1/catalog/products.ts +0 -49
- package/apps/storefront/src/pages/api/apps/v1/health.ts +0 -32
- package/apps/storefront/src/pages/api/apps/v1/inventory.ts +0 -59
- package/apps/storefront/src/pages/api/apps/v1/orders/[id].ts +0 -48
- package/apps/storefront/src/pages/api/apps/v1/orders.ts +0 -73
- package/apps/storefront/src/pages/api/apps/v1/reports.ts +0 -21
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +0 -74
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries.ts +0 -47
- package/apps/storefront/src/pages/api/cache-purge.ts +0 -55
- package/apps/storefront/src/pages/api/compliance/tamper.ts +0 -88
- package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +0 -164
- package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +0 -154
- package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +0 -120
- package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +0 -108
- package/apps/storefront/src/pages/api/publish/stage-bundle.ts +0 -70
- package/apps/storefront/src/pages/api/test/dev-session.ts +0 -133
- package/apps/storefront/src/pages/cockpit.astro +0 -1548
- package/apps/storefront/src/pages/dashboard/[appDomain]/apps/index.astro +0 -119
- package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +0 -105
- package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +0 -177
- package/apps/storefront/src/pages/dashboard/index.astro +0 -160
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* POST /api/apps/admin/credentials/rotate — rotate an install's client
|
|
3
|
-
* secret, keeping the same `client_id` (PrivateApps epic, D2 Chunk C; D8
|
|
4
|
-
* console-session auth added). The new secret is returned ONCE here and never
|
|
5
|
-
* again — the same one-time-secret guarantee `install()` gives (see
|
|
6
|
-
* `registryService.ts`'s `InstallResult` doc).
|
|
7
|
-
*
|
|
8
|
-
* `registryService.rotateCredentials()` takes a bare `installId` with no
|
|
9
|
-
* tenant check of its own (Chunk A assumption #1) — so this route resolves
|
|
10
|
-
* the install via `requireOwnedInstall` BEFORE calling it, rejecting a
|
|
11
|
-
* foreign-tenant `installId` with the SAME 404 a truly-missing one gets (no
|
|
12
|
-
* cross-tenant existence oracle). Mutating: `requireManageRole` gates a
|
|
13
|
-
* console `developer` role out — the host-bound/Bearer path is exempt (no
|
|
14
|
-
* `role`).
|
|
15
|
-
*/
|
|
16
|
-
import type { APIContext } from "astro";
|
|
17
|
-
import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
|
|
18
|
-
import {
|
|
19
|
-
getRegistryService,
|
|
20
|
-
registryErrorResponse,
|
|
21
|
-
requireOwnedInstall,
|
|
22
|
-
requireManageRole,
|
|
23
|
-
parseInstallId,
|
|
24
|
-
json,
|
|
25
|
-
} from "@/lib/apps/adminService.js";
|
|
26
|
-
import { RegistryError } from "@/lib/apps/registryService.js";
|
|
27
|
-
import { resolveUiActor } from "@/lib/activity/uiActor";
|
|
28
|
-
import { recordActivity } from "@/lib/activity/recordActivity";
|
|
29
|
-
|
|
30
|
-
export const prerender = false;
|
|
31
|
-
|
|
32
|
-
export async function POST(context: APIContext): Promise<Response> {
|
|
33
|
-
const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
|
|
34
|
-
const auth = await resolveOperatorSession(context, requestedTenant);
|
|
35
|
-
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
36
|
-
const manageDenied = requireManageRole(auth);
|
|
37
|
-
if (manageDenied) return manageDenied;
|
|
38
|
-
|
|
39
|
-
let body: unknown;
|
|
40
|
-
try {
|
|
41
|
-
body = await context.request.json();
|
|
42
|
-
} catch {
|
|
43
|
-
return json({ error: "invalid JSON body" }, 400);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const installId = parseInstallId(body);
|
|
47
|
-
if (!installId) return json({ error: "installId is required" }, 400);
|
|
48
|
-
|
|
49
|
-
const registry = await getRegistryService();
|
|
50
|
-
if (registry instanceof Response) return registry;
|
|
51
|
-
|
|
52
|
-
const owned = await requireOwnedInstall(registry, installId, auth.tenantId);
|
|
53
|
-
if (owned instanceof Response) return owned;
|
|
54
|
-
|
|
55
|
-
// D10: acting cohort (merchant/admin), emitted source "server". SECURITY: the
|
|
56
|
-
// rotated secret (`issued.secret`) is NEVER placed in the payload — the allowlist
|
|
57
|
-
// names only the non-sensitive app handle (appId/env, from the owned install) +
|
|
58
|
-
// rotation time.
|
|
59
|
-
const activityActor = await resolveUiActor(context, { owner: auth.tenantId });
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const issued = await registry.rotateCredentials(installId, auth.actor);
|
|
63
|
-
recordActivity({
|
|
64
|
-
action: "app.credential.rotated",
|
|
65
|
-
actor: activityActor,
|
|
66
|
-
source: "server",
|
|
67
|
-
outcome: { status: "succeeded" },
|
|
68
|
-
scope: { tenantId: auth.tenantId },
|
|
69
|
-
payload: { args: { appId: owned.appId, env: owned.env, rotatedAt: new Date().toISOString() } },
|
|
70
|
-
});
|
|
71
|
-
return json({ credential: { clientId: issued.clientId, secret: issued.secret } });
|
|
72
|
-
} catch (err) {
|
|
73
|
-
if (err instanceof RegistryError) {
|
|
74
|
-
recordActivity({
|
|
75
|
-
action: "app.credential.rotated",
|
|
76
|
-
actor: activityActor,
|
|
77
|
-
source: "server",
|
|
78
|
-
outcome: { status: "failed", errorClass: err.code },
|
|
79
|
-
scope: { tenantId: auth.tenantId },
|
|
80
|
-
payload: { args: { appId: owned.appId, env: owned.env } },
|
|
81
|
-
});
|
|
82
|
-
return registryErrorResponse(err);
|
|
83
|
-
}
|
|
84
|
-
throw err;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* POST /api/apps/admin/gtm-migration-install — the review->install surface of
|
|
3
|
-
* the GTM migration assistant. It takes a reviewed draft's `draftId`, LOADS the
|
|
4
|
-
* stored draft server-side, and converts it into a DISABLED desired
|
|
5
|
-
* managed-analytics install + draft revision, strictly through WS1's pinned
|
|
6
|
-
* PUBLIC contract. It returns the resulting disabled install/revision + a
|
|
7
|
-
* correlation id.
|
|
8
|
-
*
|
|
9
|
-
* Auth mirrors the read-only analyze route (`gtm-migration.ts`): the tenant is
|
|
10
|
-
* `auth.tenantId` from the resolved operator session — NEVER the request body.
|
|
11
|
-
*
|
|
12
|
-
* SECURITY BOUNDARY (a reviewer enforces this):
|
|
13
|
-
* - LOAD-BY-ID ONLY: the body carries a `draftId` (+ optional
|
|
14
|
-
* `targetInstallId` and its original `expectedRevision`), never a draft
|
|
15
|
-
* object. The stable mutation identity comes from the required
|
|
16
|
-
* `Idempotency-Key` header. A body that tries to smuggle draft data
|
|
17
|
-
* (`proposedConfiguration`/`proposedAdapterId`/`findings`/`draft`/inline
|
|
18
|
-
* `configuration`) is rejected — the stored draft is loaded from WS1 and
|
|
19
|
-
* re-validated against the pinned validator; untrusted draft data never gets
|
|
20
|
-
* past review.
|
|
21
|
-
* - DISABLED-ONLY: the produced install is `status:"disabled"` with a
|
|
22
|
-
* `status:"draft"` revision. Any attempt to force enable/publish/activate in
|
|
23
|
-
* the body is refused. This route imports NO registry/D1/R2/materializer/
|
|
24
|
-
* lifecycle dependency and writes NOTHING to storefront's own stores — every
|
|
25
|
-
* effect goes through the pinned WS1 client, which exposes no enable op.
|
|
26
|
-
*/
|
|
27
|
-
import type { APIContext } from "astro";
|
|
28
|
-
import {
|
|
29
|
-
validateGetIntegrationInstallRequest,
|
|
30
|
-
validateGetMigrationDraftRequest,
|
|
31
|
-
validateMutationRequest,
|
|
32
|
-
} from "@tot/public-runtime";
|
|
33
|
-
import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
|
|
34
|
-
import { json } from "@/lib/apps/adminService.js";
|
|
35
|
-
import {
|
|
36
|
-
reviewInstallDraft,
|
|
37
|
-
type ReviewInstallResult,
|
|
38
|
-
type Ws1ManagedAnalyticsClient,
|
|
39
|
-
} from "@/lib/apps/gtmMigrationInstall.js";
|
|
40
|
-
import { resolveWs1ManagedAnalyticsClient } from "@/lib/apps/ws1ManagedAnalyticsClient.js";
|
|
41
|
-
|
|
42
|
-
export const prerender = false;
|
|
43
|
-
|
|
44
|
-
/** Fields that would smuggle inline draft data past review — a body may carry only ids. */
|
|
45
|
-
const DRAFT_DATA_FIELDS = ["draft", "proposedConfiguration", "proposedAdapterId", "findings", "configuration"] as const;
|
|
46
|
-
/** Fields that would try to enable/publish/activate — this path produces a disabled draft only. */
|
|
47
|
-
const ENABLE_FIELDS = ["status", "enabled", "enable", "publish", "publishes", "activate"] as const;
|
|
48
|
-
|
|
49
|
-
interface ParsedRequest {
|
|
50
|
-
readonly draftId: string;
|
|
51
|
-
readonly targetInstallId?: string;
|
|
52
|
-
readonly expectedRevision?: number;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Parse ids plus optional revise CAS state, and NOTHING that could enable or resubmit a draft. */
|
|
56
|
-
function parseRequest(body: unknown): ParsedRequest | { error: string } {
|
|
57
|
-
if (!body || typeof body !== "object") return { error: "A JSON object body with a draftId is required." };
|
|
58
|
-
const o = body as Record<string, unknown>;
|
|
59
|
-
|
|
60
|
-
for (const field of DRAFT_DATA_FIELDS) {
|
|
61
|
-
if (field in o) {
|
|
62
|
-
return { error: "Submit a draftId only; the reviewed draft is loaded server-side and cannot be supplied inline." };
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
for (const field of ENABLE_FIELDS) {
|
|
66
|
-
if (field in o) {
|
|
67
|
-
return { error: "This path produces a disabled draft only; it cannot enable, publish, or activate." };
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const rawDraftId = o.draftId;
|
|
72
|
-
if (typeof rawDraftId !== "string" || !rawDraftId.trim()) return { error: "A draftId is required." };
|
|
73
|
-
const draftCheck = validateGetMigrationDraftRequest({
|
|
74
|
-
apiVersion: "v1",
|
|
75
|
-
query: { draftId: rawDraftId },
|
|
76
|
-
});
|
|
77
|
-
if (!draftCheck.ok) {
|
|
78
|
-
return { error: "draftId must be a valid opaque identifier." };
|
|
79
|
-
}
|
|
80
|
-
const draftId = draftCheck.value.query.draftId;
|
|
81
|
-
|
|
82
|
-
let targetInstallId: string | undefined;
|
|
83
|
-
if ("targetInstallId" in o) {
|
|
84
|
-
const t = o.targetInstallId;
|
|
85
|
-
if (typeof t !== "string" || !t.trim()) return { error: "targetInstallId must be a non-empty string when present." };
|
|
86
|
-
const installCheck = validateGetIntegrationInstallRequest({
|
|
87
|
-
apiVersion: "v1",
|
|
88
|
-
query: { installId: t },
|
|
89
|
-
});
|
|
90
|
-
if (!installCheck.ok) {
|
|
91
|
-
return { error: "targetInstallId must be a valid opaque identifier." };
|
|
92
|
-
}
|
|
93
|
-
targetInstallId = installCheck.value.query.installId;
|
|
94
|
-
}
|
|
95
|
-
let expectedRevision: number | undefined;
|
|
96
|
-
if ("expectedRevision" in o) {
|
|
97
|
-
if (!Number.isInteger(o.expectedRevision) || (o.expectedRevision as number) < 0) {
|
|
98
|
-
return { error: "expectedRevision must be a non-negative integer when present." };
|
|
99
|
-
}
|
|
100
|
-
expectedRevision = o.expectedRevision as number;
|
|
101
|
-
}
|
|
102
|
-
if (targetInstallId !== undefined && expectedRevision === undefined) {
|
|
103
|
-
return { error: "expectedRevision is required when targetInstallId is present so retries preserve the original CAS request." };
|
|
104
|
-
}
|
|
105
|
-
if (targetInstallId === undefined && expectedRevision !== undefined) {
|
|
106
|
-
return { error: "expectedRevision is only accepted when targetInstallId is present." };
|
|
107
|
-
}
|
|
108
|
-
return { draftId, targetInstallId, expectedRevision };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export async function POST(context: APIContext): Promise<Response> {
|
|
112
|
-
const url = new URL(context.request.url);
|
|
113
|
-
const requestedTenant = url.searchParams.get("tenant");
|
|
114
|
-
const auth = await resolveOperatorSession(context, requestedTenant);
|
|
115
|
-
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
116
|
-
|
|
117
|
-
const correlationId = crypto.randomUUID();
|
|
118
|
-
|
|
119
|
-
let body: unknown;
|
|
120
|
-
try {
|
|
121
|
-
body = await context.request.json();
|
|
122
|
-
} catch {
|
|
123
|
-
return json({ ok: false, correlationId, error: "The request body must be JSON." }, 400);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const parsed = parseRequest(body);
|
|
127
|
-
if ("error" in parsed) return json({ ok: false, correlationId, error: parsed.error }, 400);
|
|
128
|
-
|
|
129
|
-
const idempotencyKey = context.request.headers.get("idempotency-key")?.trim();
|
|
130
|
-
const keyCheck = validateMutationRequest({
|
|
131
|
-
apiVersion: "v1",
|
|
132
|
-
idempotencyKey,
|
|
133
|
-
expectedRevision: 0,
|
|
134
|
-
body: {},
|
|
135
|
-
});
|
|
136
|
-
if (!keyCheck.ok) {
|
|
137
|
-
return json({ ok: false, correlationId, error: "A valid Idempotency-Key header is required." }, 400);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
let client: Ws1ManagedAnalyticsClient | null;
|
|
141
|
-
try {
|
|
142
|
-
client = await resolveWs1ManagedAnalyticsClient(context, auth.tenantId);
|
|
143
|
-
} catch {
|
|
144
|
-
return json({ ok: false, correlationId, error: "Managed analytics service resolution failed safely.", code: "internal_error" }, 502);
|
|
145
|
-
}
|
|
146
|
-
if (!client) return json({ ok: false, correlationId, error: "Managed analytics service is not configured." }, 503);
|
|
147
|
-
|
|
148
|
-
let result: ReviewInstallResult;
|
|
149
|
-
try {
|
|
150
|
-
result = await reviewInstallDraft(client, {
|
|
151
|
-
draftId: parsed.draftId,
|
|
152
|
-
targetInstallId: parsed.targetInstallId,
|
|
153
|
-
expectedRevision: parsed.expectedRevision,
|
|
154
|
-
idempotencyKey: keyCheck.value.idempotencyKey,
|
|
155
|
-
correlationId,
|
|
156
|
-
});
|
|
157
|
-
} catch {
|
|
158
|
-
return json({ ok: false, correlationId, error: "Managed analytics review failed safely.", code: "internal_error" }, 502);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (!result.ok) {
|
|
162
|
-
return json({ ok: false, correlationId, error: result.error, code: result.code }, result.status);
|
|
163
|
-
}
|
|
164
|
-
return json(
|
|
165
|
-
{
|
|
166
|
-
ok: true,
|
|
167
|
-
correlationId,
|
|
168
|
-
install: result.install,
|
|
169
|
-
revision: result.revision,
|
|
170
|
-
idempotencyReplayed: result.idempotencyReplayed,
|
|
171
|
-
},
|
|
172
|
-
200,
|
|
173
|
-
);
|
|
174
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* POST /api/apps/admin/gtm-migration — the READ-ONLY "Migrate from GTM" analyze
|
|
3
|
-
* surface. Accepts an uploaded GTM container export (a JSON request body, or a
|
|
4
|
-
* `multipart/form-data` file part), runs the SAFE `@tot/public-runtime` pipeline
|
|
5
|
-
* (parse -> graph -> classify) over it, and returns a REDACTED report: object
|
|
6
|
-
* accounting, the disposition summary, per-object dispositions, the dependency
|
|
7
|
-
* graph, unresolved findings, and recommendations. `?format=bundle` returns the
|
|
8
|
-
* same report as a downloadable JSON attachment with a provenance stamp.
|
|
9
|
-
*
|
|
10
|
-
* Auth mirrors the read-only `integrations` route: the tenant is `auth.tenantId`
|
|
11
|
-
* from the resolved operator session — NEVER the request body — and any tenant
|
|
12
|
-
* membership admits (analyze is a diagnostic read, not a manage action).
|
|
13
|
-
*
|
|
14
|
-
* READ-ONLY BOUNDARY (a reviewer enforces this): this route writes NOTHING,
|
|
15
|
-
* builds no integration or draft, publishes nothing, activates nothing, and
|
|
16
|
-
* emits no markup/script/network-to-third-parties/event-listener. It imports
|
|
17
|
-
* only the operator-session gate, the JSON responder, and the pure, inert
|
|
18
|
-
* pipeline + report builder — no registry, D1, R2, or lifecycle dependency is
|
|
19
|
-
* imported or reachable. It parses inert data and returns a report. Ids from a
|
|
20
|
-
* foreign container are reflected back only; they are never used to look
|
|
21
|
-
* anything up about this tenant.
|
|
22
|
-
*/
|
|
23
|
-
import type { APIContext } from "astro";
|
|
24
|
-
import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
|
|
25
|
-
import { json } from "@/lib/apps/adminService.js";
|
|
26
|
-
import { analyzeGtmExport } from "@/lib/apps/gtmMigrationReport.js";
|
|
27
|
-
|
|
28
|
-
export const prerender = false;
|
|
29
|
-
|
|
30
|
-
/** The multipart field name the upload UI posts the export file under. */
|
|
31
|
-
const FILE_FIELD = "file";
|
|
32
|
-
|
|
33
|
-
/** Read the uploaded export text from a JSON body or a multipart file part. */
|
|
34
|
-
async function readUploadText(request: Request): Promise<string | { error: string }> {
|
|
35
|
-
const contentType = (request.headers.get("content-type") ?? "").toLowerCase();
|
|
36
|
-
if (contentType.includes("multipart/form-data")) {
|
|
37
|
-
let form: FormData;
|
|
38
|
-
try {
|
|
39
|
-
form = await request.formData();
|
|
40
|
-
} catch {
|
|
41
|
-
return { error: "could not read the uploaded form" };
|
|
42
|
-
}
|
|
43
|
-
const part = form.get(FILE_FIELD);
|
|
44
|
-
if (part == null) return { error: `no "${FILE_FIELD}" part in the upload` };
|
|
45
|
-
if (typeof part === "string") return part;
|
|
46
|
-
return await part.text();
|
|
47
|
-
}
|
|
48
|
-
// Any other content type: the raw request body IS the export document.
|
|
49
|
-
return await request.text();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function POST(context: APIContext): Promise<Response> {
|
|
53
|
-
const url = new URL(context.request.url);
|
|
54
|
-
const requestedTenant = url.searchParams.get("tenant");
|
|
55
|
-
const auth = await resolveOperatorSession(context, requestedTenant);
|
|
56
|
-
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
57
|
-
|
|
58
|
-
const upload = await readUploadText(context.request);
|
|
59
|
-
const correlationId = crypto.randomUUID();
|
|
60
|
-
const generatedAt = new Date().toISOString();
|
|
61
|
-
|
|
62
|
-
if (typeof upload !== "string") {
|
|
63
|
-
return json({ ok: false, correlationId, error: upload.error, violations: [] }, 400);
|
|
64
|
-
}
|
|
65
|
-
if (upload.trim() === "") {
|
|
66
|
-
return json(
|
|
67
|
-
{ ok: false, correlationId, error: "The upload was empty.", violations: [] },
|
|
68
|
-
400,
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const result = analyzeGtmExport(upload, { tenantId: auth.tenantId, correlationId, generatedAt });
|
|
73
|
-
|
|
74
|
-
if (!result.ok) {
|
|
75
|
-
// Structured parse violations + a stable, safe error and the correlation id.
|
|
76
|
-
return json(
|
|
77
|
-
{ ok: false, correlationId, error: result.error, violations: result.violations },
|
|
78
|
-
422,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const wantsBundle = url.searchParams.get("format") === "bundle";
|
|
83
|
-
const responseBody = { ok: true, correlationId, report: result.report };
|
|
84
|
-
if (!wantsBundle) return json(responseBody, 200);
|
|
85
|
-
|
|
86
|
-
// Support-bundle download: the same redacted report as a JSON attachment.
|
|
87
|
-
const filename = `gtm-migration-report-${correlationId}.json`;
|
|
88
|
-
return new Response(JSON.stringify(responseBody, null, 2), {
|
|
89
|
-
status: 200,
|
|
90
|
-
headers: {
|
|
91
|
-
"content-type": "application/json",
|
|
92
|
-
"cache-control": "no-store",
|
|
93
|
-
"content-disposition": `attachment; filename="${filename}"`,
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GET /api/apps/admin/health — the console's per-install health rollup for
|
|
3
|
-
* the resolved operator tenant (PrivateApps epic, D8 Chunk C). Read-only:
|
|
4
|
-
* any membership role admits (mirrors `list.ts`'s `?tenant=`-as-candidate
|
|
5
|
-
* scoping — the tenant actually queried is always `auth.tenantId`, never the
|
|
6
|
-
* raw param).
|
|
7
|
-
*
|
|
8
|
-
* Folds `registryService.listByTenant` with `deliveryStore.getInstallHealth`
|
|
9
|
-
* per install through `healthAggregate.ts`'s `buildInstallHealthView` — that
|
|
10
|
-
* view is what data-minimizes `webhookEndpoint` down to `hasWebhookEndpoint`,
|
|
11
|
-
* so this route never even holds the raw endpoint value to leak.
|
|
12
|
-
*/
|
|
13
|
-
import type { APIContext } from "astro";
|
|
14
|
-
import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
|
|
15
|
-
import { getRegistryService, json } from "@/lib/apps/adminService.js";
|
|
16
|
-
import { WebhookDeliveryStore } from "@/lib/webhooks/deliveryStore.js";
|
|
17
|
-
import { buildInstallHealthView, type AppInstallHealthView } from "@/lib/apps/healthAggregate.js";
|
|
18
|
-
import { fromD1 } from "@/lib/d1/catalog.js";
|
|
19
|
-
import { readD1 } from "@/lib/env";
|
|
20
|
-
|
|
21
|
-
export const prerender = false;
|
|
22
|
-
|
|
23
|
-
export async function GET(context: APIContext): Promise<Response> {
|
|
24
|
-
const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
|
|
25
|
-
const auth = await resolveOperatorSession(context, requestedTenant);
|
|
26
|
-
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
27
|
-
|
|
28
|
-
const registry = await getRegistryService();
|
|
29
|
-
if (registry instanceof Response) return registry;
|
|
30
|
-
|
|
31
|
-
const d1 = await readD1("STOREFRONT_APPS_DB");
|
|
32
|
-
if (!d1) return json({ error: "webhook store not configured" }, 503);
|
|
33
|
-
const deliveryStore = new WebhookDeliveryStore(fromD1(d1));
|
|
34
|
-
|
|
35
|
-
const installs = await registry.listByTenant(auth.tenantId);
|
|
36
|
-
const apps: AppInstallHealthView[] = await Promise.all(
|
|
37
|
-
installs.map(async (install) => {
|
|
38
|
-
const health = await deliveryStore.getInstallHealth(install.installId);
|
|
39
|
-
return buildInstallHealthView(install, health);
|
|
40
|
-
}),
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
return json({ apps });
|
|
44
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* POST /api/apps/admin/install — install a private app for the resolved
|
|
3
|
-
* operator tenant + a caller-chosen env (PrivateApps epic, D2 Chunk C).
|
|
4
|
-
* Session-authed (see `@/lib/apps/adminSession`) — NOT the app JWT (D3's data
|
|
5
|
-
* surface). `tenantId` comes only from the resolved session, never the body.
|
|
6
|
-
*
|
|
7
|
-
* The response's `credential.secret` is the ONE time the plaintext client
|
|
8
|
-
* secret is ever surfaced — never logged, never returned again (see
|
|
9
|
-
* `registryService.ts`'s `InstallResult` doc).
|
|
10
|
-
*/
|
|
11
|
-
import type { APIContext } from "astro";
|
|
12
|
-
import { resolveAdminSession } from "@/lib/apps/adminSession.js";
|
|
13
|
-
import { getRegistryService, registryErrorResponse, json } from "@/lib/apps/adminService.js";
|
|
14
|
-
import { RegistryError, type InstallInput } from "@/lib/apps/registryService.js";
|
|
15
|
-
import { emitWebhookEvent } from "@/lib/webhooks/emit.js";
|
|
16
|
-
import { resolveUiActor } from "@/lib/activity/uiActor";
|
|
17
|
-
import { recordActivity } from "@/lib/activity/recordActivity";
|
|
18
|
-
|
|
19
|
-
export const prerender = false;
|
|
20
|
-
|
|
21
|
-
interface InstallBody {
|
|
22
|
-
appId?: unknown;
|
|
23
|
-
appVersion?: unknown;
|
|
24
|
-
env?: unknown;
|
|
25
|
-
scopes?: unknown;
|
|
26
|
-
manifest?: unknown;
|
|
27
|
-
webhookEndpoint?: unknown;
|
|
28
|
-
widgetPlacements?: unknown;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Validate + narrow the request body into `InstallInput`, or a 400 message. */
|
|
32
|
-
function parseInstallInput(body: InstallBody): InstallInput | string {
|
|
33
|
-
if (typeof body.appId !== "string" || !body.appId.trim()) return "appId is required";
|
|
34
|
-
if (typeof body.appVersion !== "string" || !body.appVersion.trim()) return "appVersion is required";
|
|
35
|
-
if (typeof body.env !== "string" || !body.env.trim()) return "env is required";
|
|
36
|
-
if (!Array.isArray(body.scopes) || body.scopes.some((s) => typeof s !== "string")) {
|
|
37
|
-
return "scopes must be a string[]";
|
|
38
|
-
}
|
|
39
|
-
if (body.manifest === null || typeof body.manifest !== "object" || Array.isArray(body.manifest)) {
|
|
40
|
-
return "manifest is required and must be an object";
|
|
41
|
-
}
|
|
42
|
-
if (
|
|
43
|
-
body.webhookEndpoint !== undefined &&
|
|
44
|
-
body.webhookEndpoint !== null &&
|
|
45
|
-
typeof body.webhookEndpoint !== "string"
|
|
46
|
-
) {
|
|
47
|
-
return "webhookEndpoint, when given, must be a string or null";
|
|
48
|
-
}
|
|
49
|
-
if (body.widgetPlacements !== undefined && !Array.isArray(body.widgetPlacements)) {
|
|
50
|
-
return "widgetPlacements, when given, must be an array";
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
appId: body.appId,
|
|
54
|
-
appVersion: body.appVersion,
|
|
55
|
-
env: body.env,
|
|
56
|
-
scopes: body.scopes as string[],
|
|
57
|
-
manifest: body.manifest,
|
|
58
|
-
webhookEndpoint: (body.webhookEndpoint as string | null | undefined) ?? undefined,
|
|
59
|
-
widgetPlacements: body.widgetPlacements as unknown[] | undefined,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export async function POST(context: APIContext): Promise<Response> {
|
|
64
|
-
const auth = await resolveAdminSession(context);
|
|
65
|
-
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
66
|
-
|
|
67
|
-
let body: InstallBody;
|
|
68
|
-
try {
|
|
69
|
-
body = (await context.request.json()) as InstallBody;
|
|
70
|
-
} catch {
|
|
71
|
-
return json({ error: "invalid JSON body" }, 400);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const input = parseInstallInput(body);
|
|
75
|
-
if (typeof input === "string") return json({ error: input }, 400);
|
|
76
|
-
|
|
77
|
-
const registry = await getRegistryService();
|
|
78
|
-
if (registry instanceof Response) return registry;
|
|
79
|
-
|
|
80
|
-
// D10: resolve the acting cohort ONCE (merchant/admin — a store owner managing
|
|
81
|
-
// their own app, or ToT staff operating-as). Emitted as source "server" (the
|
|
82
|
-
// app.* catalog source), best-effort, never alters control flow.
|
|
83
|
-
const activityActor = await resolveUiActor(context, { owner: auth.tenantId });
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const result = await registry.install(input, { tenantId: auth.tenantId, actor: auth.actor });
|
|
87
|
-
try {
|
|
88
|
-
// Best-effort: a webhook-plumbing hiccup must never turn a successful
|
|
89
|
-
// install into a failed response — see emit.ts's header.
|
|
90
|
-
await emitWebhookEvent("app.installed", auth.tenantId, {}, { installId: result.install.installId });
|
|
91
|
-
} catch (cause) {
|
|
92
|
-
console.warn(
|
|
93
|
-
"[diag:webhooks:emit]",
|
|
94
|
-
JSON.stringify({
|
|
95
|
-
topic: "app.installed",
|
|
96
|
-
tenantId: auth.tenantId,
|
|
97
|
-
installId: result.install.installId,
|
|
98
|
-
error: cause instanceof Error ? cause.message : String(cause),
|
|
99
|
-
}),
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
recordActivity({
|
|
103
|
-
action: "app.install",
|
|
104
|
-
actor: activityActor,
|
|
105
|
-
source: "server",
|
|
106
|
-
outcome: { status: "succeeded" },
|
|
107
|
-
scope: { tenantId: auth.tenantId },
|
|
108
|
-
payload: { args: { appId: input.appId, appVersion: input.appVersion, env: input.env } },
|
|
109
|
-
});
|
|
110
|
-
return json(result, 201);
|
|
111
|
-
} catch (err) {
|
|
112
|
-
if (err instanceof RegistryError) {
|
|
113
|
-
recordActivity({
|
|
114
|
-
action: "app.install",
|
|
115
|
-
actor: activityActor,
|
|
116
|
-
source: "server",
|
|
117
|
-
outcome: { status: err.code === "scope_denied" ? "refused" : "failed", errorClass: err.code },
|
|
118
|
-
scope: { tenantId: auth.tenantId },
|
|
119
|
-
payload: { args: { appId: input.appId, appVersion: input.appVersion, env: input.env } },
|
|
120
|
-
});
|
|
121
|
-
return registryErrorResponse(err);
|
|
122
|
-
}
|
|
123
|
-
throw err;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* POST /api/apps/admin/integration-lifecycle — execute an integration-config
|
|
3
|
-
* lifecycle write action (ext-admin-07): `preview` a candidate (its effective
|
|
4
|
-
* code/config/CSP/consent/assurance diff), `publish` it to live THROUGH the
|
|
5
|
-
* policy-06 promotion gate, or `rollback` live to a known-good revision. The
|
|
6
|
-
* install-status actions (`suspend` / `resume` / `uninstall`) keep their own
|
|
7
|
-
* registry-backed routes; this route owns the three that ride the
|
|
8
|
-
* candidate/change-control substrate.
|
|
9
|
-
*
|
|
10
|
-
* Authorization mirrors every mutating admin route: `resolveOperatorSession`
|
|
11
|
-
* resolves the tenant from the host/console session (never the body), and
|
|
12
|
-
* `requireManageRole` denies a read-only console `developer` — so a developer or
|
|
13
|
-
* vendor never holds tenant publish authority. `requireOwnedInstall` rejects a
|
|
14
|
-
* foreign-tenant `installId` with the same 404 a missing one gets.
|
|
15
|
-
*
|
|
16
|
-
* The pure {@link handleLifecycleRequest} decides and persists ONLY on success —
|
|
17
|
-
* a blocked publish leaves the ACTUAL last-good state untouched. The lifecycle
|
|
18
|
-
* state + candidate-evidence + gate-input materialization is the control plane's
|
|
19
|
-
* job (the same materialization ext-admin-02's read model resolves per install);
|
|
20
|
-
* until an install is materialized the controller answers a 409, never a
|
|
21
|
-
* fabricated success.
|
|
22
|
-
*/
|
|
23
|
-
import type { APIContext } from "astro";
|
|
24
|
-
import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
|
|
25
|
-
import {
|
|
26
|
-
getRegistryService,
|
|
27
|
-
requireOwnedInstall,
|
|
28
|
-
requireManageRole,
|
|
29
|
-
parseInstallId,
|
|
30
|
-
json,
|
|
31
|
-
} from "@/lib/apps/adminService.js";
|
|
32
|
-
import {
|
|
33
|
-
handleLifecycleRequest,
|
|
34
|
-
type LifecycleDeps,
|
|
35
|
-
type LifecycleWriteAction,
|
|
36
|
-
} from "@/lib/apps/integrationLifecycleController.js";
|
|
37
|
-
import { parseEnvironment, type IntegrationEnvironment } from "@tot/public-runtime";
|
|
38
|
-
import { loadTenantLocalLifecycleState } from "@/lib/apps/tenantIntegrationMaterialization.js";
|
|
39
|
-
import { resolveAdminTenant } from "@/config/adminTenantLookup";
|
|
40
|
-
|
|
41
|
-
export const prerender = false;
|
|
42
|
-
|
|
43
|
-
const WRITE_ACTIONS: ReadonlySet<string> = new Set(["preview", "publish", "rollback"]);
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Build the control-plane materialization seam for one tenant's installs. A tenant's
|
|
47
|
-
* OWN (tenant-local) declaration is materialized through the interim bridge (ext-kernel-09):
|
|
48
|
-
* `load` returns its REAL live-by-config lifecycle state instead of the former blanket
|
|
49
|
-
* `null`, so a declared install is no longer reported `not_materialized`. It carries no
|
|
50
|
-
* separate candidate/promotion gate — an on-by-default declaration is already effective —
|
|
51
|
-
* so `resolveCandidateEvidence`/`resolveGate` stay null and a `preview`/`publish` refuses
|
|
52
|
-
* fail-closed (no candidate) rather than fabricating one. A managed-app install with a
|
|
53
|
-
* persisted candidate lifecycle remains the control plane's separate concern.
|
|
54
|
-
*/
|
|
55
|
-
function integrationLifecycleDeps(_context: APIContext, tenantId: string, stage: IntegrationEnvironment): LifecycleDeps {
|
|
56
|
-
return {
|
|
57
|
-
load: async (installId) => {
|
|
58
|
-
// Best-effort: a resolver hiccup answers "unmaterialized" (fail-closed 409),
|
|
59
|
-
// never a 500 — an install with no materialized declaration is null either way.
|
|
60
|
-
try {
|
|
61
|
-
const tenant = await resolveAdminTenant(tenantId);
|
|
62
|
-
if (!tenant || tenant.tenant_id !== tenantId) return null;
|
|
63
|
-
return loadTenantLocalLifecycleState(tenant, installId, stage);
|
|
64
|
-
} catch (err) {
|
|
65
|
-
console.error(`[integration-lifecycle] materialization skipped for ${tenantId}: ${String(err)}`);
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
save: async () => {},
|
|
70
|
-
resolveCandidateEvidence: async () => null,
|
|
71
|
-
resolveGate: async () => null,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export async function POST(context: APIContext): Promise<Response> {
|
|
76
|
-
const requestUrl = new URL(context.request.url);
|
|
77
|
-
const requestedTenant = requestUrl.searchParams.get("tenant");
|
|
78
|
-
const auth = await resolveOperatorSession(context, requestedTenant);
|
|
79
|
-
if (!auth.ok) return json({ error: auth.message }, auth.status);
|
|
80
|
-
const manageDenied = requireManageRole(auth);
|
|
81
|
-
if (manageDenied) return manageDenied;
|
|
82
|
-
|
|
83
|
-
let body: unknown;
|
|
84
|
-
try {
|
|
85
|
-
body = await context.request.json();
|
|
86
|
-
} catch {
|
|
87
|
-
return json({ error: "invalid JSON body" }, 400);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const action = (body as { action?: unknown }).action;
|
|
91
|
-
if (typeof action !== "string" || !WRITE_ACTIONS.has(action)) {
|
|
92
|
-
return json({ error: "action must be one of: preview, publish, rollback" }, 400);
|
|
93
|
-
}
|
|
94
|
-
const installId = parseInstallId(body);
|
|
95
|
-
if (!installId) return json({ error: "installId is required" }, 400);
|
|
96
|
-
|
|
97
|
-
const registry = await getRegistryService();
|
|
98
|
-
if (registry instanceof Response) return registry;
|
|
99
|
-
|
|
100
|
-
const owned = await requireOwnedInstall(registry, installId, auth.tenantId);
|
|
101
|
-
if (owned instanceof Response) return owned;
|
|
102
|
-
|
|
103
|
-
const { expectedLiveRevision, targetRevision } = body as {
|
|
104
|
-
expectedLiveRevision?: string | null;
|
|
105
|
-
targetRevision?: string;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const stage: IntegrationEnvironment = parseEnvironment(requestUrl.searchParams.get("stage")) ?? "live";
|
|
109
|
-
const deps = integrationLifecycleDeps(context, auth.tenantId, stage);
|
|
110
|
-
const result = await handleLifecycleRequest(deps, {
|
|
111
|
-
action: action as LifecycleWriteAction,
|
|
112
|
-
installId,
|
|
113
|
-
actor: auth.actor,
|
|
114
|
-
...(expectedLiveRevision !== undefined ? { expectedLiveRevision } : {}),
|
|
115
|
-
...(typeof targetRevision === "string" ? { targetRevision } : {}),
|
|
116
|
-
});
|
|
117
|
-
return json(result.body, result.status);
|
|
118
|
-
}
|