@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,89 +0,0 @@
1
- /**
2
- * GET /api/apps/admin/integrations — the effective integration admin read model
3
- * (ext-admin-02): the tenant-scoped data behind ext-admin-01's `/admin#integrations`
4
- * IA. Combines each install's desired config, resolved effective plan, compiled CSP,
5
- * consent, assurance, candidate, and rollback availability into ONE safe view.
6
- *
7
- * Auth mirrors `list.ts` exactly: the `?tenant=` query param is only ever a CANDIDATE
8
- * into `resolveOperatorSession` — the tenant actually used is `auth.tenantId` from the
9
- * resolved session, never the raw param — so another tenant, or a vendor with no
10
- * membership, is denied (401/403) before any read model is assembled. Read-only: any
11
- * membership role admits. The pure {@link assembleIntegrationReadModel} is the second,
12
- * defense-in-depth gate: it re-asserts every materialized input belongs to
13
- * `auth.tenantId` and redacts secrets, so a mis-plumbed control plane cannot leak.
14
- *
15
- * The control-plane materialization of effective plans, candidate evidence, and
16
- * assurance records is resolved per install where present; an install with none yet
17
- * surfaces honestly as `configuring` (no effective authority, no rollback), never a
18
- * fabricated live state.
19
- */
20
- import type { APIContext } from "astro";
21
- import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
22
- import { getRegistryService, json } from "@/lib/apps/adminService.js";
23
- import { parseEnvironment, type IntegrationEnvironment } from "@tot/public-runtime";
24
- import {
25
- assembleIntegrationReadModel,
26
- type IntegrationReadModelInputItem,
27
- } from "@/lib/apps/integrationReadModel.js";
28
- import { tenantLocalReadModelItems } from "@/lib/apps/tenantIntegrationMaterialization.js";
29
- import { resolveAdminTenant } from "@/config/adminTenantLookup";
30
-
31
- export const prerender = false;
32
-
33
- /** A merchant-legible purpose line for an install, never an internal adapter name. */
34
- function purposeFor(appId: string): string {
35
- return `Integration: ${appId}`;
36
- }
37
-
38
- export async function GET(context: APIContext): Promise<Response> {
39
- const url = new URL(context.request.url);
40
- const requestedTenant = url.searchParams.get("tenant");
41
- const auth = await resolveOperatorSession(context, requestedTenant);
42
- if (!auth.ok) return json({ error: auth.message }, auth.status);
43
-
44
- const registry = await getRegistryService();
45
- if (registry instanceof Response) return registry;
46
-
47
- const stage: IntegrationEnvironment = parseEnvironment(url.searchParams.get("stage")) ?? "staged";
48
-
49
- const installs = await registry.listByTenant(auth.tenantId);
50
- const items: IntegrationReadModelInputItem[] = installs
51
- .filter((install) => install.status !== "uninstalled")
52
- .map((install) => ({
53
- install: {
54
- installId: install.installId,
55
- appId: install.appId,
56
- appVersion: install.appVersion,
57
- tenantId: install.tenantId,
58
- status: install.status,
59
- },
60
- purpose: purposeFor(install.appId),
61
- // The desired config is the tenant's own input; when the manifest snapshot is not
62
- // a structured object it contributes nothing — never a fabricated value.
63
- desiredConfig: {},
64
- }));
65
-
66
- // ext-kernel-09: a tenant's OWN (tenant-local) declarations are materialized through
67
- // the interim bridge and appended with their REAL effective plan + compiled CSP, so a
68
- // declared integration surfaces effective state — not `configuring`. Resolved off the
69
- // auth-bound tenant config only (the tenant id is `auth.tenantId`, never the param).
70
- // Best-effort + additive: a resolver hiccup degrades to the managed-app view rather
71
- // than failing the whole read model closed.
72
- try {
73
- const tenantConfig = await resolveAdminTenant(auth.tenantId);
74
- if (tenantConfig && tenantConfig.tenant_id === auth.tenantId) {
75
- items.push(...tenantLocalReadModelItems(tenantConfig, stage));
76
- }
77
- } catch (err) {
78
- console.error(`[admin-integrations] tenant-local materialization skipped for ${auth.tenantId}: ${String(err)}`);
79
- }
80
-
81
- const result = assembleIntegrationReadModel({ tenantPublicId: auth.tenantId, stage, items });
82
- if (!result.ok) {
83
- // A cross-tenant input or surviving secret is a server-side integrity failure, not
84
- // something to echo back field-by-field.
85
- return json({ error: "integration read model failed integrity checks" }, 500);
86
- }
87
-
88
- return json({ readModel: result.value });
89
- }
@@ -1,26 +0,0 @@
1
- /**
2
- * GET /api/apps/admin/list — list the app installs for the resolved operator
3
- * tenant (PrivateApps epic, D2 Chunk C; D8 console-session auth added). The
4
- * `?tenant=` query param (read by console callers with several memberships,
5
- * mirroring `../../grants/team-members.ts`) is only ever a CANDIDATE into
6
- * `resolveOperatorSession` — the tenant actually used below is `auth.tenantId`
7
- * from the resolved session, never the raw param, so a caller can only ever
8
- * see their own tenant's installs. Read-only: any membership role admits.
9
- */
10
- import type { APIContext } from "astro";
11
- import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
12
- import { getRegistryService, json } from "@/lib/apps/adminService.js";
13
-
14
- export const prerender = false;
15
-
16
- export async function GET(context: APIContext): Promise<Response> {
17
- const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
18
- const auth = await resolveOperatorSession(context, requestedTenant);
19
- if (!auth.ok) return json({ error: auth.message }, auth.status);
20
-
21
- const registry = await getRegistryService();
22
- if (registry instanceof Response) return registry;
23
-
24
- const installs = await registry.listByTenant(auth.tenantId);
25
- return json({ installs });
26
- }
@@ -1,85 +0,0 @@
1
- /**
2
- * POST /api/apps/admin/platform-adapters — the first-class native adapter
3
- * surface (ext-admin-04). Two actions over the pure, edge-safe adapter core
4
- * ({@link platformAdapterCatalog} / {@link buildPlatformAdapterDeclaration}):
5
- *
6
- * - `catalog` — the first-class adapter cards (consent purposes + performance
7
- * budget + CSP origins + per-stage destination behavior), so a merchant sees the
8
- * material effects before configuring anything.
9
- * - `build` — turn a merchant's public destination ids (per environment) into a
10
- * tenant-scoped declaration + resolved effective plan + read-model card, or
11
- * refuse. Fail-closed: an unknown adapter, a missing/invalid live id, or a
12
- * non-live stage that reuses the live id blocks with violations.
13
- *
14
- * Auth mirrors the other `/api/apps/admin/*` routes: the tenant is `auth.tenantId`
15
- * from the resolved operator session — NEVER the request body — so a build is only
16
- * ever scoped to the caller's own tenant. The build environment is derived server-side
17
- * as a `candidate` stage, so the browser can never forge a live-stage declaration.
18
- * Configuring is a manage action ({@link requireManageRole}). The route does NOT
19
- * persist — staging/publishing a produced candidate is ext-admin-07's lifecycle.
20
- */
21
- import type { APIContext } from "astro";
22
- import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
23
- import { json, requireManageRole } from "@/lib/apps/adminService.js";
24
- import {
25
- platformAdapterCatalog,
26
- buildPlatformAdapterDeclaration,
27
- resolveBrowserEnvironment,
28
- INTEGRATION_SITE_SURFACES,
29
- type IntegrationSiteSurface,
30
- } from "@tot/public-runtime";
31
-
32
- export const prerender = false;
33
-
34
- export async function POST(context: APIContext): Promise<Response> {
35
- const url = new URL(context.request.url);
36
- const requestedTenant = url.searchParams.get("tenant");
37
- const auth = await resolveOperatorSession(context, requestedTenant);
38
- if (!auth.ok) return json({ error: auth.message }, auth.status);
39
- const roleDenied = requireManageRole(auth);
40
- if (roleDenied) return roleDenied;
41
-
42
- let body: unknown;
43
- try {
44
- body = await context.request.json();
45
- } catch {
46
- return json({ error: "request body must be JSON" }, 400);
47
- }
48
- if (typeof body !== "object" || body === null) {
49
- return json({ error: "request body must be a JSON object" }, 400);
50
- }
51
- const action = (body as { action?: unknown }).action;
52
-
53
- if (action === "catalog") {
54
- return json({ catalog: platformAdapterCatalog() });
55
- }
56
-
57
- if (action === "build") {
58
- const fields = (body as { fields?: unknown }).fields;
59
- const base = typeof fields === "object" && fields !== null ? (fields as Record<string, unknown>) : {};
60
- const surfaceParam = (base["siteSurface"] as IntegrationSiteSurface | undefined) ?? "storefront";
61
- const siteSurface = INTEGRATION_SITE_SURFACES.includes(surfaceParam) ? surfaceParam : "storefront";
62
- // A build always targets a candidate; the environment is server-derived, so a
63
- // browser can never forge a live-stage declaration.
64
- const environment = resolveBrowserEnvironment({
65
- siteSurface,
66
- canonicalHost: url.host,
67
- revision: typeof base["revision"] === "string" ? (base["revision"] as string) : "adapter-candidate",
68
- dev: false,
69
- localHost: false,
70
- candidate: true,
71
- previewHost: false,
72
- customizationEnv: "test",
73
- });
74
- const result = buildPlatformAdapterDeclaration({
75
- adapterId: base["adapterId"],
76
- ids: base["ids"],
77
- tenantPublicId: auth.tenantId,
78
- environment,
79
- });
80
- const status = result.ok ? 200 : result.reason === "unknown-adapter" ? 404 : 422;
81
- return json({ result }, status);
82
- }
83
-
84
- return json({ error: 'action must be "catalog" or "build"' }, 400);
85
- }
@@ -1,79 +0,0 @@
1
- /**
2
- * POST /api/apps/admin/resume — resume a suspended install back to active
3
- * (PrivateApps epic, D2 Chunk C; D8 console-session auth added).
4
- *
5
- * `registryService.resume()` takes a bare `installId` with no tenant check of
6
- * its own (Chunk A assumption #1) — so this route resolves the install via
7
- * `requireOwnedInstall` BEFORE calling it, rejecting a foreign-tenant
8
- * `installId` with the SAME 404 a truly-missing one gets (no cross-tenant
9
- * existence oracle). Mutating: `requireManageRole` gates a console
10
- * `developer` role out — the host-bound/Bearer path is exempt (no `role`).
11
- */
12
- import type { APIContext } from "astro";
13
- import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
14
- import {
15
- getRegistryService,
16
- registryErrorResponse,
17
- requireOwnedInstall,
18
- requireManageRole,
19
- parseInstallId,
20
- json,
21
- } from "@/lib/apps/adminService.js";
22
- import { RegistryError } from "@/lib/apps/registryService.js";
23
- import { resolveUiActor } from "@/lib/activity/uiActor";
24
- import { recordActivity } from "@/lib/activity/recordActivity";
25
-
26
- export const prerender = false;
27
-
28
- export async function POST(context: APIContext): Promise<Response> {
29
- const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
30
- const auth = await resolveOperatorSession(context, requestedTenant);
31
- if (!auth.ok) return json({ error: auth.message }, auth.status);
32
- const manageDenied = requireManageRole(auth);
33
- if (manageDenied) return manageDenied;
34
-
35
- let body: unknown;
36
- try {
37
- body = await context.request.json();
38
- } catch {
39
- return json({ error: "invalid JSON body" }, 400);
40
- }
41
-
42
- const installId = parseInstallId(body);
43
- if (!installId) return json({ error: "installId is required" }, 400);
44
-
45
- const registry = await getRegistryService();
46
- if (registry instanceof Response) return registry;
47
-
48
- const owned = await requireOwnedInstall(registry, installId, auth.tenantId);
49
- if (owned instanceof Response) return owned;
50
-
51
- // D10: acting cohort (merchant/admin), emitted source "server" per the app.* catalog.
52
- const activityActor = await resolveUiActor(context, { owner: auth.tenantId });
53
-
54
- try {
55
- const install = await registry.resume(installId, auth.actor);
56
- recordActivity({
57
- action: "app.resume",
58
- actor: activityActor,
59
- source: "server",
60
- outcome: { status: "succeeded" },
61
- scope: { tenantId: auth.tenantId },
62
- payload: { args: { appId: owned.appId, env: owned.env } },
63
- });
64
- return json({ install });
65
- } catch (err) {
66
- if (err instanceof RegistryError) {
67
- recordActivity({
68
- action: "app.resume",
69
- actor: activityActor,
70
- source: "server",
71
- outcome: { status: err.code === "invalid_state" ? "refused" : "failed", errorClass: err.code },
72
- scope: { tenantId: auth.tenantId },
73
- payload: { args: { appId: owned.appId, env: owned.env } },
74
- });
75
- return registryErrorResponse(err);
76
- }
77
- throw err;
78
- }
79
- }
@@ -1,80 +0,0 @@
1
- /**
2
- * POST /api/apps/admin/suspend — suspend an active install: immediately
3
- * disables its API tokens, webhook delivery, and widget placements
4
- * (PrivateApps epic, D2 Chunk C; D8 console-session auth added).
5
- *
6
- * `registryService.suspend()` takes a bare `installId` with no tenant check
7
- * of its own (Chunk A assumption #1) — so this route resolves the install via
8
- * `requireOwnedInstall` BEFORE calling it, rejecting a foreign-tenant
9
- * `installId` with the SAME 404 a truly-missing one gets (no cross-tenant
10
- * existence oracle). Mutating: `requireManageRole` gates a console
11
- * `developer` role out — the host-bound/Bearer path is exempt (no `role`).
12
- */
13
- import type { APIContext } from "astro";
14
- import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
15
- import {
16
- getRegistryService,
17
- registryErrorResponse,
18
- requireOwnedInstall,
19
- requireManageRole,
20
- parseInstallId,
21
- json,
22
- } from "@/lib/apps/adminService.js";
23
- import { RegistryError } from "@/lib/apps/registryService.js";
24
- import { resolveUiActor } from "@/lib/activity/uiActor";
25
- import { recordActivity } from "@/lib/activity/recordActivity";
26
-
27
- export const prerender = false;
28
-
29
- export async function POST(context: APIContext): Promise<Response> {
30
- const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
31
- const auth = await resolveOperatorSession(context, requestedTenant);
32
- if (!auth.ok) return json({ error: auth.message }, auth.status);
33
- const manageDenied = requireManageRole(auth);
34
- if (manageDenied) return manageDenied;
35
-
36
- let body: unknown;
37
- try {
38
- body = await context.request.json();
39
- } catch {
40
- return json({ error: "invalid JSON body" }, 400);
41
- }
42
-
43
- const installId = parseInstallId(body);
44
- if (!installId) return json({ error: "installId is required" }, 400);
45
-
46
- const registry = await getRegistryService();
47
- if (registry instanceof Response) return registry;
48
-
49
- const owned = await requireOwnedInstall(registry, installId, auth.tenantId);
50
- if (owned instanceof Response) return owned;
51
-
52
- // D10: acting cohort (merchant/admin), emitted source "server" per the app.* catalog.
53
- const activityActor = await resolveUiActor(context, { owner: auth.tenantId });
54
-
55
- try {
56
- const install = await registry.suspend(installId, auth.actor);
57
- recordActivity({
58
- action: "app.suspend",
59
- actor: activityActor,
60
- source: "server",
61
- outcome: { status: "succeeded" },
62
- scope: { tenantId: auth.tenantId },
63
- payload: { args: { appId: owned.appId, env: owned.env } },
64
- });
65
- return json({ install });
66
- } catch (err) {
67
- if (err instanceof RegistryError) {
68
- recordActivity({
69
- action: "app.suspend",
70
- actor: activityActor,
71
- source: "server",
72
- outcome: { status: err.code === "invalid_state" ? "refused" : "failed", errorClass: err.code },
73
- scope: { tenantId: auth.tenantId },
74
- payload: { args: { appId: owned.appId, env: owned.env } },
75
- });
76
- return registryErrorResponse(err);
77
- }
78
- throw err;
79
- }
80
- }
@@ -1,98 +0,0 @@
1
- /**
2
- * POST /api/apps/admin/uninstall — uninstall an app: revokes credentials and
3
- * clears placements, but retains the install row + audit trail (PrivateApps
4
- * epic, D2 Chunk C; D8 console-session auth added).
5
- *
6
- * `registryService.uninstall()` takes a bare `installId` with no tenant check
7
- * of its own (Chunk A assumption #1) — so this route resolves the install via
8
- * `requireOwnedInstall` BEFORE calling it, rejecting a foreign-tenant
9
- * `installId` with the SAME 404 a truly-missing one gets (no cross-tenant
10
- * existence oracle). Mutating: `requireManageRole` gates a console
11
- * `developer` role out — the host-bound/Bearer path is exempt (no `role`).
12
- */
13
- import type { APIContext } from "astro";
14
- import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
15
- import {
16
- getRegistryService,
17
- registryErrorResponse,
18
- requireOwnedInstall,
19
- requireManageRole,
20
- parseInstallId,
21
- json,
22
- } from "@/lib/apps/adminService.js";
23
- import { RegistryError } from "@/lib/apps/registryService.js";
24
- import { emitWebhookEvent } from "@/lib/webhooks/emit.js";
25
- import { resolveUiActor } from "@/lib/activity/uiActor";
26
- import { recordActivity } from "@/lib/activity/recordActivity";
27
-
28
- export const prerender = false;
29
-
30
- export async function POST(context: APIContext): Promise<Response> {
31
- const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
32
- const auth = await resolveOperatorSession(context, requestedTenant);
33
- if (!auth.ok) return json({ error: auth.message }, auth.status);
34
- const manageDenied = requireManageRole(auth);
35
- if (manageDenied) return manageDenied;
36
-
37
- let body: unknown;
38
- try {
39
- body = await context.request.json();
40
- } catch {
41
- return json({ error: "invalid JSON body" }, 400);
42
- }
43
-
44
- const installId = parseInstallId(body);
45
- if (!installId) return json({ error: "installId is required" }, 400);
46
-
47
- const registry = await getRegistryService();
48
- if (registry instanceof Response) return registry;
49
-
50
- const owned = await requireOwnedInstall(registry, installId, auth.tenantId);
51
- if (owned instanceof Response) return owned;
52
-
53
- // D10: acting cohort (merchant/admin), emitted source "server" per the app.*
54
- // catalog. Best-effort, never alters control flow.
55
- const activityActor = await resolveUiActor(context, { owner: auth.tenantId });
56
-
57
- try {
58
- const install = await registry.uninstall(installId, auth.actor);
59
- try {
60
- // Best-effort, and deliberately AFTER uninstall has already flipped the
61
- // install to "uninstalled" — this is the final farewell delivery; see
62
- // emit.ts / deliveryEngine.ts's matching status exemption for it.
63
- await emitWebhookEvent("app.uninstalled", auth.tenantId, {}, { installId });
64
- } catch (cause) {
65
- console.warn(
66
- "[diag:webhooks:emit]",
67
- JSON.stringify({
68
- topic: "app.uninstalled",
69
- tenantId: auth.tenantId,
70
- installId,
71
- error: cause instanceof Error ? cause.message : String(cause),
72
- }),
73
- );
74
- }
75
- recordActivity({
76
- action: "app.uninstall",
77
- actor: activityActor,
78
- source: "server",
79
- outcome: { status: "succeeded" },
80
- scope: { tenantId: auth.tenantId },
81
- payload: { args: { appId: owned.appId, env: owned.env } },
82
- });
83
- return json({ install });
84
- } catch (err) {
85
- if (err instanceof RegistryError) {
86
- recordActivity({
87
- action: "app.uninstall",
88
- actor: activityActor,
89
- source: "server",
90
- outcome: { status: "failed", errorClass: err.code },
91
- scope: { tenantId: auth.tenantId },
92
- payload: { args: { appId: owned.appId, env: owned.env } },
93
- });
94
- return registryErrorResponse(err);
95
- }
96
- throw err;
97
- }
98
- }
@@ -1,126 +0,0 @@
1
- /**
2
- * POST /api/apps/admin/update — patch an existing install's version, scopes,
3
- * manifest snapshot, or webhook endpoint (PrivateApps epic, D2 Chunk C; D8
4
- * console-session auth added).
5
- *
6
- * `registryService.update()` takes a bare `installId` with no tenant check of
7
- * its own (Chunk A assumption #1) — so this route resolves the install via
8
- * `requireOwnedInstall` BEFORE calling it, rejecting a foreign-tenant
9
- * `installId` with the SAME 404 a truly-missing one gets (no cross-tenant
10
- * existence oracle). Mutating: `requireManageRole` gates a console
11
- * `developer` role out — the host-bound/Bearer path is exempt (no `role`).
12
- */
13
- import type { APIContext } from "astro";
14
- import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
15
- import {
16
- getRegistryService,
17
- registryErrorResponse,
18
- requireOwnedInstall,
19
- requireManageRole,
20
- parseInstallId,
21
- json,
22
- } from "@/lib/apps/adminService.js";
23
- import { RegistryError, type AppInstallPatch } from "@/lib/apps/registryService.js";
24
- import { resolveUiActor } from "@/lib/activity/uiActor";
25
- import { recordActivity } from "@/lib/activity/recordActivity";
26
-
27
- export const prerender = false;
28
-
29
- interface UpdateBody {
30
- installId?: unknown;
31
- appVersion?: unknown;
32
- scopes?: unknown;
33
- manifest?: unknown;
34
- webhookEndpoint?: unknown;
35
- widgetPlacements?: unknown;
36
- }
37
-
38
- /** Validate + narrow the given fields into `AppInstallPatch`, or a 400 message. */
39
- function parsePatch(body: UpdateBody): AppInstallPatch | string {
40
- const patch: AppInstallPatch = {};
41
- if (body.appVersion !== undefined) {
42
- if (typeof body.appVersion !== "string" || !body.appVersion.trim()) {
43
- return "appVersion, when given, must be a non-empty string";
44
- }
45
- patch.appVersion = body.appVersion;
46
- }
47
- if (body.scopes !== undefined) {
48
- if (!Array.isArray(body.scopes) || body.scopes.some((s) => typeof s !== "string")) {
49
- return "scopes, when given, must be a string[]";
50
- }
51
- patch.scopes = body.scopes as string[];
52
- }
53
- if (body.manifest !== undefined) {
54
- if (body.manifest === null || typeof body.manifest !== "object" || Array.isArray(body.manifest)) {
55
- return "manifest, when given, must be an object";
56
- }
57
- patch.manifest = body.manifest;
58
- }
59
- if (body.webhookEndpoint !== undefined) {
60
- if (body.webhookEndpoint !== null && typeof body.webhookEndpoint !== "string") {
61
- return "webhookEndpoint, when given, must be a string or null";
62
- }
63
- patch.webhookEndpoint = body.webhookEndpoint as string | null;
64
- }
65
- if (body.widgetPlacements !== undefined) {
66
- if (!Array.isArray(body.widgetPlacements)) return "widgetPlacements, when given, must be an array";
67
- patch.widgetPlacements = body.widgetPlacements;
68
- }
69
- return patch;
70
- }
71
-
72
- export async function POST(context: APIContext): Promise<Response> {
73
- const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
74
- const auth = await resolveOperatorSession(context, requestedTenant);
75
- if (!auth.ok) return json({ error: auth.message }, auth.status);
76
- const manageDenied = requireManageRole(auth);
77
- if (manageDenied) return manageDenied;
78
-
79
- let body: UpdateBody;
80
- try {
81
- body = (await context.request.json()) as UpdateBody;
82
- } catch {
83
- return json({ error: "invalid JSON body" }, 400);
84
- }
85
-
86
- const installId = parseInstallId(body);
87
- if (!installId) return json({ error: "installId is required" }, 400);
88
-
89
- const patch = parsePatch(body);
90
- if (typeof patch === "string") return json({ error: patch }, 400);
91
-
92
- const registry = await getRegistryService();
93
- if (registry instanceof Response) return registry;
94
-
95
- const owned = await requireOwnedInstall(registry, installId, auth.tenantId);
96
- if (owned instanceof Response) return owned;
97
-
98
- // D10: acting cohort (merchant/admin), emitted source "server" per the app.* catalog.
99
- const activityActor = await resolveUiActor(context, { owner: auth.tenantId });
100
-
101
- try {
102
- const install = await registry.update(installId, patch, auth.actor);
103
- recordActivity({
104
- action: "app.update",
105
- actor: activityActor,
106
- source: "server",
107
- outcome: { status: "succeeded" },
108
- scope: { tenantId: auth.tenantId },
109
- payload: { args: { appId: owned.appId, env: owned.env } },
110
- });
111
- return json({ install });
112
- } catch (err) {
113
- if (err instanceof RegistryError) {
114
- recordActivity({
115
- action: "app.update",
116
- actor: activityActor,
117
- source: "server",
118
- outcome: { status: err.code === "invalid_state" ? "refused" : "failed", errorClass: err.code },
119
- scope: { tenantId: auth.tenantId },
120
- payload: { args: { appId: owned.appId, env: owned.env } },
121
- });
122
- return registryErrorResponse(err);
123
- }
124
- throw err;
125
- }
126
- }
@@ -1,70 +0,0 @@
1
- /**
2
- * POST /api/apps/admin/webhooks/deliveries/{deliveryId}/replay — the
3
- * ops/support replay (PrivateApps epic, D4 Chunk E; D8 console-session auth
4
- * added). Operator session only; `tenantId` comes from `auth.tenantId`
5
- * (`resolveOperatorSession`), never the request. A `deliveryId` whose
6
- * `tenantId` doesn't match the operator's resolved tenant 404s identically to
7
- * a truly-missing one — same no-cross-tenant-oracle discipline
8
- * `requireOwnedInstall` keeps for install ids. `webhook_deliveries` already
9
- * carries its own `tenantId` (set at creation, mirroring the owning
10
- * install's), so this checks that column directly rather than round-tripping
11
- * through the registry. Mutating (re-enqueues a delivery): `requireManageRole`
12
- * gates a console `developer` role out — the host-bound/Bearer path is
13
- * exempt (no `role`).
14
- *
15
- * Not part of the app-facing `openapi.yaml` contract (same as the rest of
16
- * `admin/*`) — no `Idempotency-Key` requirement; an authenticated operator
17
- * retriggering a replay isn't the untrusted-client-retry case that header
18
- * guards against.
19
- *
20
- * Same `requeueForReplay` + same-`deliveryId` re-enqueue as the app-facing
21
- * replay — see that route's doc comment for the full rationale.
22
- */
23
- import type { APIContext } from "astro";
24
- import { resolveOperatorSession } from "@/lib/apps/adminSession.js";
25
- import { json, requireManageRole } from "@/lib/apps/adminService.js";
26
- import { WebhookDeliveryStore } from "@/lib/webhooks/deliveryStore.js";
27
- import { getDispatcher } from "@/lib/webhooks/getDispatcher.js";
28
- import { fromD1 } from "@/lib/d1/catalog.js";
29
- import { readD1 } from "@/lib/env";
30
- import { resolveUiActor, recordUiActivity } from "@/lib/activity/uiActor";
31
-
32
- export const prerender = false;
33
-
34
- export async function POST(context: APIContext): Promise<Response> {
35
- const requestedTenant = new URL(context.request.url).searchParams.get("tenant");
36
- const auth = await resolveOperatorSession(context, requestedTenant);
37
- if (!auth.ok) return json({ error: auth.message }, auth.status);
38
- const manageDenied = requireManageRole(auth);
39
- if (manageDenied) return manageDenied;
40
-
41
- const deliveryId = context.params.deliveryId;
42
- if (!deliveryId) return json({ error: "Delivery not found.", code: "not_found" }, 404);
43
-
44
- const d1 = await readD1("STOREFRONT_APPS_DB");
45
- if (!d1) return json({ error: "webhook store not configured" }, 503);
46
- const store = new WebhookDeliveryStore(fromD1(d1));
47
-
48
- const delivery = await store.getDelivery(deliveryId);
49
- if (!delivery || delivery.tenantId !== auth.tenantId) {
50
- return json({ error: "Delivery not found.", code: "not_found" }, 404);
51
- }
52
-
53
- const dispatcher = await getDispatcher();
54
- if (dispatcher instanceof Response) return dispatcher;
55
-
56
- const requeued = await store.requeueForReplay(deliveryId);
57
- await dispatcher.enqueue({ deliveryId });
58
-
59
- // D11: ops/support console replay. Resolve the acting cohort (admin/merchant per
60
- // resolveOperatorSession) from the session; `surface: "admin"` disambiguates from the
61
- // app-facing v1 call site that shares this key. deliveryId is the opaque wire id.
62
- const activityActor = await resolveUiActor(context, {});
63
- recordUiActivity(activityActor, {
64
- action: "webhook.delivery.replayed",
65
- outcome: { status: "succeeded" },
66
- scope: { tenantId: auth.tenantId },
67
- payload: { args: { deliveryId, surface: "admin" } },
68
- });
69
- return json({ delivery: requeued }, 202);
70
- }