From db05816b00a4a71c95ed3a2cde88e6acf5a2fa6a Mon Sep 17 00:00:00 2001 From: Alfa Team Date: Sun, 28 Jun 2026 18:29:23 -0400 Subject: [PATCH] Move server API runtime into api --- api/.gitkeep | 1 - api/API_RUNTIME_BOUNDARY.md | 16 ++++ .../admin/admin-notes-directory.mjs | 0 .../admin/admin-notes-menu.mjs | 0 .../ai/ai-credit-service.mjs | 0 .../ai/owner-ai-credit-settings-service.mjs | 0 {src/dev-runtime => api}/auth/.gitkeep | 0 .../auth/provider-contract-stubs.mjs | 0 .../database/postgres-backup-service.mjs | 0 {src/dev-runtime => api}/guest-seeds/.gitkeep | 0 .../guest-seeds/palette-source-mock-db.js | 0 api/guest-seeds/tool-metadata-inventory.js | 1 + .../guest-seeds/tool-state-samples.js | 0 .../legal/legal-document-service.mjs | 0 .../marketplace-category-service.mjs | 0 .../marketplace-entitlement-service.mjs | 0 .../marketplace-revenue-service.mjs | 0 .../membership-assignment-service.mjs | 0 .../owner-membership-settings-service.mjs | 0 .../messages/messages-postgres-service.mjs | 0 .../game-journey-completion-metrics-store.mjs | 0 .../persistence/mock-db-store.js | 0 .../postgres-connection-client.mjs | 0 .../assets-mock-repository.js | 0 .../game-journey-mock-repository.js | 0 .../game-workspace-mock-repository.js | 0 .../hitboxes-mock-repository.js | 0 .../input-mapping-mock-repository.js | 0 .../objects-mock-repository.js | 0 .../palette-catalog-config.js | 0 .../palette-workspace-repository.js | 0 .../project-package-service.mjs | 0 .../dev-runtime => api}/seed/seed-db-keys.mjs | 0 .../seed/server-seed-loader.mjs | 0 .../server/local-api-router.mjs | 2 +- .../server/local-api-server.mjs | 2 +- .../server/mock-api-router.mjs | 0 .../server/static-web-root.mjs | 0 .../storage/r2-project-asset-storage.mjs | 0 .../storage/storage-config.mjs | 0 .../teams/project-team-service.mjs | 0 {src/dev-runtime => api}/testing/.gitkeep | 0 .../supabase-dev-auth-test-user-cleanup.mjs | 0 ...upabase-dev-creator-identity-seed-sync.mjs | 0 .../toolbox-api/alfa-tool-services.mjs | 0 .../PROJECT_INSTRUCTIONS.md | 2 +- .../PROJECT_INSTRUCTIONS_VERSION.md | 3 +- .../ProjectInstructions/PROJECT_STATE.md | 15 ++-- .../backlog/BACKLOG_MASTER.md | 6 +- .../canonical_repository_structure.md | 11 ++- .../repository_layout_architecture_plan.md | 8 +- .../repository/www_migration_map.md | 15 ++-- dev/build/database/runbook.md | 4 +- ...-move-api-application_branch-validation.md | 23 ++++++ ...api-application_manual-validation-notes.md | 14 ++++ ...0_OWNER_009-move-api-application_report.md | 29 +++++++ ...e-api-application_requirement-checklist.md | 13 +++ ...-move-api-application_validation-report.md | 26 ++++++ dev/reports/codex_changed_files.txt | Bin 46382 -> 367 bytes dev/reports/codex_review.diff | Bin 399040 -> 9058 bytes dev/scripts/apply-database-seed.mjs | 6 +- .../cleanup-supabase-dev-auth-test-users.mjs | 2 +- dev/scripts/database-migration-runner.mjs | 2 +- dev/scripts/start-dev.mjs | 6 +- dev/scripts/start-local-api-server.mjs | 6 +- .../sync-supabase-dev-creator-identities.mjs | 2 +- dev/scripts/validate-browser-env-agnostic.mjs | 27 +++--- dev/scripts/validate-database-drift.mjs | 2 +- dev/scripts/validate-dev-runtime-boundary.mjs | 36 ++++---- .../validate-local-postgres-runtime.mjs | 10 +-- dev/scripts/validate-runtime-connections.mjs | 2 +- dev/scripts/validate-storage-config.mjs | 4 +- .../api/admin-system-health/contract.test.mjs | 4 +- .../AdminHealthOperations.test.mjs | 10 +-- .../dev-runtime/AdminNotesBoundary.test.mjs | 41 +++++----- .../dev-runtime/AiCreditDisplay.test.mjs | 10 +-- .../dev-runtime/AiCreditFoundation.test.mjs | 12 +-- ...hitectureCleanupApiNavInvitations.test.mjs | 54 ++++++------ .../dev-runtime/BetaInvitationsAdmin.test.mjs | 4 +- .../dev-runtime/DbSeedIntegrity.test.mjs | 6 +- .../DbViewerConfiguredSnapshot.test.mjs | 2 +- .../dev-runtime/DevRuntimeBoundary.test.mjs | 77 +++++++++--------- ...GameJourneyCompletionMetricsStore.test.mjs | 2 +- .../dev-runtime/LegalFoundation.test.mjs | 10 +-- .../MarketplaceCategories.test.mjs | 12 +-- .../MarketplaceMembershipRules.test.mjs | 10 +-- .../MarketplaceRevenueModel.test.mjs | 8 +- .../dev-runtime/MembershipAssignment.test.mjs | 8 +- .../dev-runtime/MembershipDataModel.test.mjs | 8 +- .../dev-runtime/MembershipsPageData.test.mjs | 8 +- .../MessagesPublishValidation.test.mjs | 2 +- .../dev-runtime/ObjectsApiService.test.mjs | 4 +- dev/tests/dev-runtime/OwnerAiCredits.test.mjs | 12 +-- .../dev-runtime/OwnerMemberships.test.mjs | 16 ++-- .../PostgresBackupService.test.mjs | 4 +- .../ProductDataDatabaseUrl.test.mjs | 2 +- .../PublicEnvironmentConfig.test.mjs | 2 +- .../StaticWebRootCompatibility.test.mjs | 2 +- dev/tests/dev-runtime/StorageConfig.test.mjs | 2 +- .../SupabaseDevAuthTestUserCleanup.test.mjs | 6 +- ...upabaseDevCreatorIdentitySeedSync.test.mjs | 6 +- .../SupabaseProductDataCutover.test.mjs | 4 +- .../SupabaseProviderContractStub.test.mjs | 6 +- .../dev-runtime/TagsApiErrorResponse.test.mjs | 6 +- dev/tests/dev-runtime/TagsApiService.test.mjs | 4 +- .../dev-runtime/TeamEnforcement.test.mjs | 8 +- .../dev-runtime/TeamsFoundation.test.mjs | 12 +-- dev/tests/helpers/playwrightRepoServer.mjs | 8 +- .../account/AchievementsPage.spec.mjs | 2 +- .../playwright/account/AiCreditsPage.spec.mjs | 2 +- .../playwright/tools/AdminDbViewer.spec.mjs | 8 +- .../tools/AdminHealthOperationsPage.spec.mjs | 2 +- .../tools/AdminInvitationsNavPage.spec.mjs | 2 +- .../tools/AdminNotesLocalViewer.spec.mjs | 2 +- .../AdminOwnerNavigationBoundary.spec.mjs | 2 +- .../AdminPlatformToolsWireframes.spec.mjs | 2 +- .../tools/AssetToolMockRepository.spec.mjs | 4 +- .../tools/BrowserApiUrlConfig.spec.mjs | 27 ++---- .../BuildPathProgressSimplification.spec.mjs | 2 +- .../tools/EnvironmentBannerCoverage.spec.mjs | 2 +- .../playwright/tools/EventsTool.spec.mjs | 2 +- .../GameConfigurationApiBehavior.spec.mjs | 4 +- .../tools/GameCrewFoundation.spec.mjs | 4 +- .../tools/GameDesignApiBehavior.spec.mjs | 4 +- .../tools/GameHubMockRepository.spec.mjs | 2 +- .../playwright/tools/GameJourneyTool.spec.mjs | 8 +- .../tools/IdeaBoardTableNotes.spec.mjs | 4 +- .../tools/InputMappingV2Tool.spec.mjs | 2 +- .../tools/LoginSessionMode.spec.mjs | 4 +- .../playwright/tools/MessagesTool.spec.mjs | 2 +- .../playwright/tools/ObjectsTool.spec.mjs | 2 +- .../tools/OwnerAiCreditsPage.spec.mjs | 2 +- .../tools/OwnerMembershipsPage.spec.mjs | 2 +- .../tools/PaletteToolMockRepository.spec.mjs | 4 +- .../tools/PublicMarketplacePage.spec.mjs | 2 +- .../tools/PublicMembershipsPage.spec.mjs | 2 +- .../tools/RootToolsFutureState.spec.mjs | 2 +- dev/tests/playwright/tools/TagsTool.spec.mjs | 4 +- .../tools/TextToSpeechFunctional.spec.mjs | 2 +- .../tools/ToolboxAdminMetadataSsot.spec.mjs | 2 +- .../tools/ToolboxRoutePages.spec.mjs | 2 +- .../ToolboxSelectedGameStatusBar.spec.mjs | 2 +- .../tools/MessagesPlaybackSource.test.mjs | 2 +- src/dev-runtime/DEV_RUNTIME_BOUNDARY.md | 16 ---- .../guest-seeds/tool-metadata-inventory.js | 1 - 145 files changed, 469 insertions(+), 359 deletions(-) delete mode 100644 api/.gitkeep create mode 100644 api/API_RUNTIME_BOUNDARY.md rename {src/dev-runtime => api}/admin/admin-notes-directory.mjs (100%) rename {src/dev-runtime => api}/admin/admin-notes-menu.mjs (100%) rename {src/dev-runtime => api}/ai/ai-credit-service.mjs (100%) rename {src/dev-runtime => api}/ai/owner-ai-credit-settings-service.mjs (100%) rename {src/dev-runtime => api}/auth/.gitkeep (100%) rename {src/dev-runtime => api}/auth/provider-contract-stubs.mjs (100%) rename {src/dev-runtime => api}/database/postgres-backup-service.mjs (100%) rename {src/dev-runtime => api}/guest-seeds/.gitkeep (100%) rename {src/dev-runtime => api}/guest-seeds/palette-source-mock-db.js (100%) create mode 100644 api/guest-seeds/tool-metadata-inventory.js rename {src/dev-runtime => api}/guest-seeds/tool-state-samples.js (100%) rename {src/dev-runtime => api}/legal/legal-document-service.mjs (100%) rename {src/dev-runtime => api}/marketplace/marketplace-category-service.mjs (100%) rename {src/dev-runtime => api}/marketplace/marketplace-entitlement-service.mjs (100%) rename {src/dev-runtime => api}/marketplace/marketplace-revenue-service.mjs (100%) rename {src/dev-runtime => api}/memberships/membership-assignment-service.mjs (100%) rename {src/dev-runtime => api}/memberships/owner-membership-settings-service.mjs (100%) rename {src/dev-runtime => api}/messages/messages-postgres-service.mjs (100%) rename {src/dev-runtime => api}/persistence/game-journey-completion-metrics-store.mjs (100%) rename {src/dev-runtime => api}/persistence/mock-db-store.js (100%) rename {src/dev-runtime => api}/persistence/postgres-connection-client.mjs (100%) rename {src/dev-runtime => api}/persistence/tool-repositories/assets-mock-repository.js (100%) rename {src/dev-runtime => api}/persistence/tool-repositories/game-journey-mock-repository.js (100%) rename {src/dev-runtime => api}/persistence/tool-repositories/game-workspace-mock-repository.js (100%) rename {src/dev-runtime => api}/persistence/tool-repositories/hitboxes-mock-repository.js (100%) rename {src/dev-runtime => api}/persistence/tool-repositories/input-mapping-mock-repository.js (100%) rename {src/dev-runtime => api}/persistence/tool-repositories/objects-mock-repository.js (100%) rename {src/dev-runtime => api}/persistence/tool-repositories/palette-catalog-config.js (100%) rename {src/dev-runtime => api}/persistence/tool-repositories/palette-workspace-repository.js (100%) rename {src/dev-runtime => api}/project-packages/project-package-service.mjs (100%) rename {src/dev-runtime => api}/seed/seed-db-keys.mjs (100%) rename {src/dev-runtime => api}/seed/server-seed-loader.mjs (100%) rename {src/dev-runtime => api}/server/local-api-router.mjs (99%) rename {src/dev-runtime => api}/server/local-api-server.mjs (98%) rename {src/dev-runtime => api}/server/mock-api-router.mjs (100%) rename {src/dev-runtime => api}/server/static-web-root.mjs (100%) rename {src/dev-runtime => api}/storage/r2-project-asset-storage.mjs (100%) rename {src/dev-runtime => api}/storage/storage-config.mjs (100%) rename {src/dev-runtime => api}/teams/project-team-service.mjs (100%) rename {src/dev-runtime => api}/testing/.gitkeep (100%) rename {src/dev-runtime => api}/testing/supabase-dev-auth-test-user-cleanup.mjs (100%) rename {src/dev-runtime => api}/testing/supabase-dev-creator-identity-seed-sync.mjs (100%) rename {src/dev-runtime => api}/toolbox-api/alfa-tool-services.mjs (100%) create mode 100644 dev/reports/PR_26180_OWNER_009-move-api-application_branch-validation.md create mode 100644 dev/reports/PR_26180_OWNER_009-move-api-application_manual-validation-notes.md create mode 100644 dev/reports/PR_26180_OWNER_009-move-api-application_report.md create mode 100644 dev/reports/PR_26180_OWNER_009-move-api-application_requirement-checklist.md create mode 100644 dev/reports/PR_26180_OWNER_009-move-api-application_validation-report.md delete mode 100644 src/dev-runtime/DEV_RUNTIME_BOUNDARY.md delete mode 100644 src/dev-runtime/guest-seeds/tool-metadata-inventory.js diff --git a/api/.gitkeep b/api/.gitkeep deleted file mode 100644 index 8b1378917..000000000 --- a/api/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/api/API_RUNTIME_BOUNDARY.md b/api/API_RUNTIME_BOUNDARY.md new file mode 100644 index 000000000..ba4c12c6b --- /dev/null +++ b/api/API_RUNTIME_BOUNDARY.md @@ -0,0 +1,16 @@ +# API Runtime Boundary + +`api/` contains server/API application runtime implementation for the browser-visible Local API, seeded product data, provider stubs, storage adapters, database adapters, and support services used by repository validation. + +It is intentionally not `dev/tests/dev-runtime`: + +- `api/` serves runtime behavior that browser pages and local API routes can exercise through the API/service contract during development and targeted validation. +- `dev/tests/dev-runtime` contains test files that validate that behavior. +- Moving the implementation into `dev/tests/dev-runtime` would make development pages depend on test-only code and would blur the Browser -> Server API -> Data Source boundary. + +Deployment boundary: + +- DEV validation may execute `api/` through local server commands. +- Browser pages, Theme V2 scripts, toolbox pages, and `src/engine` runtime code must use declared API/service contracts instead of importing `api/` directly. +- If a dev-only adapter is needed by a browser page, expose it through the Local API contract while keeping the implementation under `api/`. +- Developer-only local bootstrap/orchestration remains outside `api/` until the scoped `dev/local-runtime/` migration PR. diff --git a/src/dev-runtime/admin/admin-notes-directory.mjs b/api/admin/admin-notes-directory.mjs similarity index 100% rename from src/dev-runtime/admin/admin-notes-directory.mjs rename to api/admin/admin-notes-directory.mjs diff --git a/src/dev-runtime/admin/admin-notes-menu.mjs b/api/admin/admin-notes-menu.mjs similarity index 100% rename from src/dev-runtime/admin/admin-notes-menu.mjs rename to api/admin/admin-notes-menu.mjs diff --git a/src/dev-runtime/ai/ai-credit-service.mjs b/api/ai/ai-credit-service.mjs similarity index 100% rename from src/dev-runtime/ai/ai-credit-service.mjs rename to api/ai/ai-credit-service.mjs diff --git a/src/dev-runtime/ai/owner-ai-credit-settings-service.mjs b/api/ai/owner-ai-credit-settings-service.mjs similarity index 100% rename from src/dev-runtime/ai/owner-ai-credit-settings-service.mjs rename to api/ai/owner-ai-credit-settings-service.mjs diff --git a/src/dev-runtime/auth/.gitkeep b/api/auth/.gitkeep similarity index 100% rename from src/dev-runtime/auth/.gitkeep rename to api/auth/.gitkeep diff --git a/src/dev-runtime/auth/provider-contract-stubs.mjs b/api/auth/provider-contract-stubs.mjs similarity index 100% rename from src/dev-runtime/auth/provider-contract-stubs.mjs rename to api/auth/provider-contract-stubs.mjs diff --git a/src/dev-runtime/database/postgres-backup-service.mjs b/api/database/postgres-backup-service.mjs similarity index 100% rename from src/dev-runtime/database/postgres-backup-service.mjs rename to api/database/postgres-backup-service.mjs diff --git a/src/dev-runtime/guest-seeds/.gitkeep b/api/guest-seeds/.gitkeep similarity index 100% rename from src/dev-runtime/guest-seeds/.gitkeep rename to api/guest-seeds/.gitkeep diff --git a/src/dev-runtime/guest-seeds/palette-source-mock-db.js b/api/guest-seeds/palette-source-mock-db.js similarity index 100% rename from src/dev-runtime/guest-seeds/palette-source-mock-db.js rename to api/guest-seeds/palette-source-mock-db.js diff --git a/api/guest-seeds/tool-metadata-inventory.js b/api/guest-seeds/tool-metadata-inventory.js new file mode 100644 index 000000000..2a7efcf29 --- /dev/null +++ b/api/guest-seeds/tool-metadata-inventory.js @@ -0,0 +1 @@ +export * from "../../src/shared/toolbox/tool-metadata-inventory.js"; diff --git a/src/dev-runtime/guest-seeds/tool-state-samples.js b/api/guest-seeds/tool-state-samples.js similarity index 100% rename from src/dev-runtime/guest-seeds/tool-state-samples.js rename to api/guest-seeds/tool-state-samples.js diff --git a/src/dev-runtime/legal/legal-document-service.mjs b/api/legal/legal-document-service.mjs similarity index 100% rename from src/dev-runtime/legal/legal-document-service.mjs rename to api/legal/legal-document-service.mjs diff --git a/src/dev-runtime/marketplace/marketplace-category-service.mjs b/api/marketplace/marketplace-category-service.mjs similarity index 100% rename from src/dev-runtime/marketplace/marketplace-category-service.mjs rename to api/marketplace/marketplace-category-service.mjs diff --git a/src/dev-runtime/marketplace/marketplace-entitlement-service.mjs b/api/marketplace/marketplace-entitlement-service.mjs similarity index 100% rename from src/dev-runtime/marketplace/marketplace-entitlement-service.mjs rename to api/marketplace/marketplace-entitlement-service.mjs diff --git a/src/dev-runtime/marketplace/marketplace-revenue-service.mjs b/api/marketplace/marketplace-revenue-service.mjs similarity index 100% rename from src/dev-runtime/marketplace/marketplace-revenue-service.mjs rename to api/marketplace/marketplace-revenue-service.mjs diff --git a/src/dev-runtime/memberships/membership-assignment-service.mjs b/api/memberships/membership-assignment-service.mjs similarity index 100% rename from src/dev-runtime/memberships/membership-assignment-service.mjs rename to api/memberships/membership-assignment-service.mjs diff --git a/src/dev-runtime/memberships/owner-membership-settings-service.mjs b/api/memberships/owner-membership-settings-service.mjs similarity index 100% rename from src/dev-runtime/memberships/owner-membership-settings-service.mjs rename to api/memberships/owner-membership-settings-service.mjs diff --git a/src/dev-runtime/messages/messages-postgres-service.mjs b/api/messages/messages-postgres-service.mjs similarity index 100% rename from src/dev-runtime/messages/messages-postgres-service.mjs rename to api/messages/messages-postgres-service.mjs diff --git a/src/dev-runtime/persistence/game-journey-completion-metrics-store.mjs b/api/persistence/game-journey-completion-metrics-store.mjs similarity index 100% rename from src/dev-runtime/persistence/game-journey-completion-metrics-store.mjs rename to api/persistence/game-journey-completion-metrics-store.mjs diff --git a/src/dev-runtime/persistence/mock-db-store.js b/api/persistence/mock-db-store.js similarity index 100% rename from src/dev-runtime/persistence/mock-db-store.js rename to api/persistence/mock-db-store.js diff --git a/src/dev-runtime/persistence/postgres-connection-client.mjs b/api/persistence/postgres-connection-client.mjs similarity index 100% rename from src/dev-runtime/persistence/postgres-connection-client.mjs rename to api/persistence/postgres-connection-client.mjs diff --git a/src/dev-runtime/persistence/tool-repositories/assets-mock-repository.js b/api/persistence/tool-repositories/assets-mock-repository.js similarity index 100% rename from src/dev-runtime/persistence/tool-repositories/assets-mock-repository.js rename to api/persistence/tool-repositories/assets-mock-repository.js diff --git a/src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js b/api/persistence/tool-repositories/game-journey-mock-repository.js similarity index 100% rename from src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js rename to api/persistence/tool-repositories/game-journey-mock-repository.js diff --git a/src/dev-runtime/persistence/tool-repositories/game-workspace-mock-repository.js b/api/persistence/tool-repositories/game-workspace-mock-repository.js similarity index 100% rename from src/dev-runtime/persistence/tool-repositories/game-workspace-mock-repository.js rename to api/persistence/tool-repositories/game-workspace-mock-repository.js diff --git a/src/dev-runtime/persistence/tool-repositories/hitboxes-mock-repository.js b/api/persistence/tool-repositories/hitboxes-mock-repository.js similarity index 100% rename from src/dev-runtime/persistence/tool-repositories/hitboxes-mock-repository.js rename to api/persistence/tool-repositories/hitboxes-mock-repository.js diff --git a/src/dev-runtime/persistence/tool-repositories/input-mapping-mock-repository.js b/api/persistence/tool-repositories/input-mapping-mock-repository.js similarity index 100% rename from src/dev-runtime/persistence/tool-repositories/input-mapping-mock-repository.js rename to api/persistence/tool-repositories/input-mapping-mock-repository.js diff --git a/src/dev-runtime/persistence/tool-repositories/objects-mock-repository.js b/api/persistence/tool-repositories/objects-mock-repository.js similarity index 100% rename from src/dev-runtime/persistence/tool-repositories/objects-mock-repository.js rename to api/persistence/tool-repositories/objects-mock-repository.js diff --git a/src/dev-runtime/persistence/tool-repositories/palette-catalog-config.js b/api/persistence/tool-repositories/palette-catalog-config.js similarity index 100% rename from src/dev-runtime/persistence/tool-repositories/palette-catalog-config.js rename to api/persistence/tool-repositories/palette-catalog-config.js diff --git a/src/dev-runtime/persistence/tool-repositories/palette-workspace-repository.js b/api/persistence/tool-repositories/palette-workspace-repository.js similarity index 100% rename from src/dev-runtime/persistence/tool-repositories/palette-workspace-repository.js rename to api/persistence/tool-repositories/palette-workspace-repository.js diff --git a/src/dev-runtime/project-packages/project-package-service.mjs b/api/project-packages/project-package-service.mjs similarity index 100% rename from src/dev-runtime/project-packages/project-package-service.mjs rename to api/project-packages/project-package-service.mjs diff --git a/src/dev-runtime/seed/seed-db-keys.mjs b/api/seed/seed-db-keys.mjs similarity index 100% rename from src/dev-runtime/seed/seed-db-keys.mjs rename to api/seed/seed-db-keys.mjs diff --git a/src/dev-runtime/seed/server-seed-loader.mjs b/api/seed/server-seed-loader.mjs similarity index 100% rename from src/dev-runtime/seed/server-seed-loader.mjs rename to api/seed/server-seed-loader.mjs diff --git a/src/dev-runtime/server/local-api-router.mjs b/api/server/local-api-router.mjs similarity index 99% rename from src/dev-runtime/server/local-api-router.mjs rename to api/server/local-api-router.mjs index ae2b7bf72..cd315eee8 100644 --- a/src/dev-runtime/server/local-api-router.mjs +++ b/api/server/local-api-router.mjs @@ -133,7 +133,7 @@ import { import { getAdminNavigationItems, getOwnerNavigationItems, -} from "../../api/admin-owner-navigation.js"; +} from "../../src/api/admin-owner-navigation.js"; import { createPaletteSourceMockDbRows } from "../guest-seeds/palette-source-mock-db.js"; import { SUPABASE_AUTH_PROVIDER_ID, diff --git a/src/dev-runtime/server/local-api-server.mjs b/api/server/local-api-server.mjs similarity index 98% rename from src/dev-runtime/server/local-api-server.mjs rename to api/server/local-api-server.mjs index 53867d1cd..ddd325ed7 100644 --- a/src/dev-runtime/server/local-api-server.mjs +++ b/api/server/local-api-server.mjs @@ -9,7 +9,7 @@ import { resolveStaticRouteTarget } from "./static-web-root.mjs"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const repoRoot = path.resolve(__dirname, "..", "..", ".."); +const repoRoot = path.resolve(__dirname, "..", ".."); function contentTypeForPath(filePath) { const extension = path.extname(filePath).toLowerCase(); diff --git a/src/dev-runtime/server/mock-api-router.mjs b/api/server/mock-api-router.mjs similarity index 100% rename from src/dev-runtime/server/mock-api-router.mjs rename to api/server/mock-api-router.mjs diff --git a/src/dev-runtime/server/static-web-root.mjs b/api/server/static-web-root.mjs similarity index 100% rename from src/dev-runtime/server/static-web-root.mjs rename to api/server/static-web-root.mjs diff --git a/src/dev-runtime/storage/r2-project-asset-storage.mjs b/api/storage/r2-project-asset-storage.mjs similarity index 100% rename from src/dev-runtime/storage/r2-project-asset-storage.mjs rename to api/storage/r2-project-asset-storage.mjs diff --git a/src/dev-runtime/storage/storage-config.mjs b/api/storage/storage-config.mjs similarity index 100% rename from src/dev-runtime/storage/storage-config.mjs rename to api/storage/storage-config.mjs diff --git a/src/dev-runtime/teams/project-team-service.mjs b/api/teams/project-team-service.mjs similarity index 100% rename from src/dev-runtime/teams/project-team-service.mjs rename to api/teams/project-team-service.mjs diff --git a/src/dev-runtime/testing/.gitkeep b/api/testing/.gitkeep similarity index 100% rename from src/dev-runtime/testing/.gitkeep rename to api/testing/.gitkeep diff --git a/src/dev-runtime/testing/supabase-dev-auth-test-user-cleanup.mjs b/api/testing/supabase-dev-auth-test-user-cleanup.mjs similarity index 100% rename from src/dev-runtime/testing/supabase-dev-auth-test-user-cleanup.mjs rename to api/testing/supabase-dev-auth-test-user-cleanup.mjs diff --git a/src/dev-runtime/testing/supabase-dev-creator-identity-seed-sync.mjs b/api/testing/supabase-dev-creator-identity-seed-sync.mjs similarity index 100% rename from src/dev-runtime/testing/supabase-dev-creator-identity-seed-sync.mjs rename to api/testing/supabase-dev-creator-identity-seed-sync.mjs diff --git a/src/dev-runtime/toolbox-api/alfa-tool-services.mjs b/api/toolbox-api/alfa-tool-services.mjs similarity index 100% rename from src/dev-runtime/toolbox-api/alfa-tool-services.mjs rename to api/toolbox-api/alfa-tool-services.mjs diff --git a/dev/build/ProjectInstructions/PROJECT_INSTRUCTIONS.md b/dev/build/ProjectInstructions/PROJECT_INSTRUCTIONS.md index febd6861c..5f477a565 100644 --- a/dev/build/ProjectInstructions/PROJECT_INSTRUCTIONS.md +++ b/dev/build/ProjectInstructions/PROJECT_INSTRUCTIONS.md @@ -117,7 +117,7 @@ Rules: ## Current Version/Date -- Project Instructions Version: 2026.06.28.008 +- Project Instructions Version: 2026.06.28.009 - Date: 2026-06-28 - Owner: OWNER diff --git a/dev/build/ProjectInstructions/PROJECT_INSTRUCTIONS_VERSION.md b/dev/build/ProjectInstructions/PROJECT_INSTRUCTIONS_VERSION.md index 6ffb05a98..973eb9002 100644 --- a/dev/build/ProjectInstructions/PROJECT_INSTRUCTIONS_VERSION.md +++ b/dev/build/ProjectInstructions/PROJECT_INSTRUCTIONS_VERSION.md @@ -1,6 +1,6 @@ # Project Instructions Version -Current Project Instructions Version: 2026.06.28.008 +Current Project Instructions Version: 2026.06.28.009 Last Updated: 2026-06-28 @@ -16,6 +16,7 @@ Last Updated: 2026-06-28 - Added `www/` migration map for safe browser-served application movement planning. - Added local route-root compatibility toggle documentation for future `www/` activation. - Moved browser-served application files under `www/` and made local static serving prefer `www/` by default. +- Moved server/API application runtime under `api/`; browser API clients remain outside `api/` and developer-only bootstrap remains in `dev/` until the local-runtime migration PR. - Codex must read this version file and the latest repository copy of `PROJECT_INSTRUCTIONS.md` before performing work. - Codex must discard previously remembered Project Instructions and treat the repository copy as authoritative. - Codex must validate canonical report and ZIP paths, branching model, and legacy path avoidance before work proceeds. diff --git a/dev/build/ProjectInstructions/PROJECT_STATE.md b/dev/build/ProjectInstructions/PROJECT_STATE.md index aea9601ef..3b1323747 100644 --- a/dev/build/ProjectInstructions/PROJECT_STATE.md +++ b/dev/build/ProjectInstructions/PROJECT_STATE.md @@ -1,7 +1,7 @@ # Project State ```yaml -project_state_version: "2026.06.28.008" +project_state_version: "2026.06.28.009" last_updated: "2026-06-28" current_main_commit: "40de767476d70cadfd1292c916844c2f31b6f185" repository_status: @@ -10,9 +10,9 @@ repository_status: runtime_database: "Postgres" creator_metadata: "API to Postgres" creator_assets: "API to R2" -project_instructions_version: "2026.06.28.008" -repository_structure_version: "2026.06.28.008" -canonical_layout_version: "2026.06.28.008" +project_instructions_version: "2026.06.28.009" +repository_structure_version: "2026.06.28.009" +canonical_layout_version: "2026.06.28.009" active_teams: - "Owner" - "Alfa" @@ -20,8 +20,8 @@ active_teams: - "Charlie" - "Delta" - "Golf" -latest_owner_pr: "PR_26180_OWNER_008-move-www-application" -latest_structure_pr: "PR_26180_OWNER_008-move-www-application" +latest_owner_pr: "PR_26180_OWNER_009-move-api-application" +latest_structure_pr: "PR_26180_OWNER_009-move-api-application" valid_top_level_folders: - "api/" - "deploy/" @@ -40,6 +40,7 @@ valid_dev_folders: - "dev/tools/" - "dev/workspace/" known_technical_debt: - - "Legacy transition src buckets remain until scoped migration PRs move them into src/web/, src/api-runtime/, or src/runtime/." + - "Browser API client modules remain under src/api/ until the final src layer migration moves them into src/web/ or src/runtime/." + - "src/dev-runtime/admin/ remains as a legacy Admin Notes browser-viewer compatibility path until a scoped browser admin migration retires it." - "repository_directory_standard.md and codex_project_instructions_startup.md are superseded pointers retained for compatibility." ``` diff --git a/dev/build/ProjectInstructions/backlog/BACKLOG_MASTER.md b/dev/build/ProjectInstructions/backlog/BACKLOG_MASTER.md index 6b947089c..0f32f8f6d 100644 --- a/dev/build/ProjectInstructions/backlog/BACKLOG_MASTER.md +++ b/dev/build/ProjectInstructions/backlog/BACKLOG_MASTER.md @@ -39,9 +39,9 @@ The backlog is the authoritative source for determining the next logical PRs. - Team: Owner - Product Area: Repository Architecture Simplification - Status: Active -- Percent Complete: 30% -- Active PR: PR_26180_OWNER_008-move-www-application -- Next Milestone: Move server/API application files into `api/` +- Percent Complete: 40% +- Active PR: PR_26180_OWNER_009-move-api-application +- Next Milestone: Move developer-only local runtime into `dev/local-runtime/` - Source / Reference: `dev/build/ProjectInstructions/repository/repository_layout_architecture_plan.md`; `dev/build/ProjectInstructions/repository/www_migration_map.md` ## Game Journey MVP diff --git a/dev/build/ProjectInstructions/repository/canonical_repository_structure.md b/dev/build/ProjectInstructions/repository/canonical_repository_structure.md index 7ef868a5b..848c9f767 100644 --- a/dev/build/ProjectInstructions/repository/canonical_repository_structure.md +++ b/dev/build/ProjectInstructions/repository/canonical_repository_structure.md @@ -15,7 +15,7 @@ Valid top-level folders: Root product and repo sections: - www/ owns browser-served production website sections, Creator toolbox pages, public game discovery, production Docs & Help, and browser assets. -- api/ is the target Node/API/server application surface for the repository layout simplification workstream. +- api/ is the Node/API/server application surface for server entry points, API routing, API services, database access, storage access, auth, setup, publishing, and admin server logic. - deploy/ contains deployment configuration. - dev/ contains the development workspace. - src/ contains deployable application/runtime/API source. @@ -30,7 +30,9 @@ Final src layer ownership: - src/web/ owns browser-facing deployable application modules used by public pages, account/admin surfaces, and Creator tools. - src/api-runtime/ owns deployable API/runtime service modules that back the shared Browser -> API -> Postgres/R2 contract. - src/runtime/ owns deployable game, tool, engine, and shared runtime capabilities. -- Existing top-level src/advanced/, src/api/, src/dev-runtime/, src/engine/, src/shared/, and src/tools/ directories are legacy transition buckets until explicit migration PRs move them. +- Existing top-level src/advanced/, src/api/, src/engine/, src/shared/, and src/tools/ directories are legacy transition buckets until explicit migration PRs move them. +- Browser API clients remain outside `api/`; browser-served code must use API/service contracts instead of importing top-level `api/` files directly. +- `src/dev-runtime/admin/` remains only as a legacy Admin Notes browser-viewer compatibility path until a scoped browser admin migration retires it. - Do not add new top-level src/ layer names outside src/web/, src/api-runtime/, or src/runtime/ without OWNER approval. - Do not use team names in runtime source filenames. @@ -75,10 +77,10 @@ Shared JavaScript: Legacy transition buckets: - src/advanced/ - src/api/ -- src/dev-runtime/ - src/engine/ - src/shared/ - src/tools/ +- src/dev-runtime/admin/ These legacy transition buckets may remain until explicit migration PRs move them into `src/web/`, `src/api-runtime/`, or `src/runtime/`. @@ -86,7 +88,8 @@ These legacy transition buckets may remain until explicit migration PRs move the - Root is standard repository configuration and top-level application shells only. - www/ is the browser-served application surface. -- api/ is the Node/API/server application target surface for migration. +- api/ is the Node/API/server application surface. +- Browser-served code must not import top-level `api/` files directly. - src/ is deployable application/runtime/API code. - dev/ is development workspace only. - deploy/ is deployment configuration. diff --git a/dev/build/ProjectInstructions/repository/repository_layout_architecture_plan.md b/dev/build/ProjectInstructions/repository/repository_layout_architecture_plan.md index 1d8c86f8b..2265848ab 100644 --- a/dev/build/ProjectInstructions/repository/repository_layout_architecture_plan.md +++ b/dev/build/ProjectInstructions/repository/repository_layout_architecture_plan.md @@ -1,6 +1,6 @@ # Repository Layout Architecture Plan -Status: Active Scaffold +Status: Active Migration Owner: Owner Scope: Documentation/governance only @@ -22,6 +22,12 @@ This plan does not authorize moving files, changing runtime behavior, changing p No browser, API, server, test, script, or runtime files move in the scaffold or migration-map PRs. +`PR_26180_OWNER_007-www-route-root-compatibility` prepared configurable web-root serving without moving browser files. + +`PR_26180_OWNER_008-move-www-application` moved browser-served application files under `www/` and made local static serving prefer `www/` while preserving public URLs. + +`PR_26180_OWNER_009-move-api-application` moves server/API application runtime under `api/` while preserving `/api/*` route behavior. Browser API client modules remain outside `api/`, and developer-only local startup orchestration remains under `dev/` until the local-runtime migration PR. + ## Proposed Future Layout ```text diff --git a/dev/build/ProjectInstructions/repository/www_migration_map.md b/dev/build/ProjectInstructions/repository/www_migration_map.md index a137d5104..df933d046 100644 --- a/dev/build/ProjectInstructions/repository/www_migration_map.md +++ b/dev/build/ProjectInstructions/repository/www_migration_map.md @@ -107,19 +107,19 @@ These references can be preserved temporarily by serving compatibility paths fro ## Current Local Web Server Root Behavior -Three active server/helper surfaces currently resolve static browser paths from the repository root: +Three active server/helper surfaces currently resolve static browser paths through the shared static route resolver: | File | Current behavior | | --- | --- | -| `dev/scripts/start-dev.mjs` | Team-aware bootstrap static server resolves requests with `path.resolve(repoRoot, \`.\${normalizedPath}\`)`. `/` maps to `/index.html`; `/tools/*` maps to `/toolbox/*`; `/admin/admin-notes.html` maps to `/src/dev-runtime/admin/notes.html`. | -| `src/dev-runtime/server/local-api-server.mjs` | Legacy local API alias serves API routes and falls back to static files from `repoRoot`. `/tools/*` maps to `/toolbox/*`; `/admin/admin-notes.html` maps to `/src/dev-runtime/admin/notes.html`. | -| `dev/tests/helpers/playwrightRepoServer.mjs` | Playwright test server mirrors repo-root static serving and the `/tools/*` and admin-notes compatibility routes. | +| `dev/scripts/start-dev.mjs` | Team-aware bootstrap static server resolves requests with `resolveStaticRouteTarget()`, preferring `www/` while preserving public route URLs and compatibility routes. | +| `api/server/local-api-server.mjs` | Legacy local API alias serves API routes and falls back to `resolveStaticRouteTarget()`, preferring `www/` while preserving public route URLs and compatibility routes. | +| `dev/tests/helpers/playwrightRepoServer.mjs` | Playwright test server mirrors the shared static route resolver and compatibility routes. | -The actual `www/` move must update these filesystem lookup roots or introduce a shared route resolver that can resolve public routes to `www/` while preserving API route ownership. +The actual `www/` move introduced the shared route resolver so public URLs continue to resolve while filesystem ownership lives under `www/`. ## Current Test Assumptions -Playwright and runtime tests currently assume root-based public routes and root-relative filesystem paths: +At the time of the migration map, Playwright and runtime tests assumed root-based public routes and root-relative filesystem paths: - 96 Playwright/helper/runtime files reference public routes such as `/toolbox`, `/account`, `/admin`, `/games`, `/learn`, `/legal`, `/marketplace`, `/memberships`, or `/index.html`. - `dev/tests/playwright/tools/ToolNavigationPrevNext.spec.mjs` opens `/toolbox/index.html` and `/toolbox/game-design/index.html`. @@ -128,7 +128,7 @@ Playwright and runtime tests currently assume root-based public routes and root- - `dev/tests/runtime/V2*` tests frequently assert `toolbox/{tool}/index.html` filesystem or URL behavior. - `dev/scripts/run-node-tests.mjs` and `dev/scripts/run-node-test-files.mjs` register root aliases for `/src/` and `/toolbox/`. -The move PR must update test helpers and route expectations in the same PR that changes the filesystem root. Browser-visible URLs should remain stable unless Owner explicitly approves a public URL change. +The `www/` move PR updated test helpers and route expectations for the filesystem root change. Browser-visible URLs must remain stable unless Owner explicitly approves a public URL change. ## Preserve Versus Rewrite @@ -151,7 +151,6 @@ The move PR must update test helpers and route expectations in the same PR that ### Defer -- Moving server/API code into `api/`. - Moving developer bootstrap into `dev/local-runtime/`. - Removing legacy root compatibility routes. - Changing package commands. diff --git a/dev/build/database/runbook.md b/dev/build/database/runbook.md index d8ac5ada1..06ca8522e 100644 --- a/dev/build/database/runbook.md +++ b/dev/build/database/runbook.md @@ -170,8 +170,8 @@ are stable. It may show validation, apply, backup, restore, and migration history status, but it must not execute database operations from the browser. The status surface currently routes through -`src/dev-runtime/server/local-api-router.mjs`. That filename is retained as a -legacy runtime filename for now; do not infer local database behavior from it. +`api/server/local-api-router.mjs`. That local API filename is retained for now; +do not infer local database behavior from it. ## Rollback Guidance diff --git a/dev/reports/PR_26180_OWNER_009-move-api-application_branch-validation.md b/dev/reports/PR_26180_OWNER_009-move-api-application_branch-validation.md new file mode 100644 index 000000000..fa1c9e61d --- /dev/null +++ b/dev/reports/PR_26180_OWNER_009-move-api-application_branch-validation.md @@ -0,0 +1,23 @@ +# PR_26180_OWNER_009 Branch Validation + +## Startup Validation + +- Project Instructions version: 2026.06.28.009 +- Repository source: PASS +- Cached memory discarded: PASS +- Canonical paths loaded: PASS +- `PROJECT_BRANCHING_POLICY.md` loaded: PASS + +## Branch + +- Current branch: `PR_26180_OWNER_009-move-api-application` +- Expected stacked base: `PR_26180_OWNER_008-move-www-application` +- Base ancestry check: PASS +- Worktree before build: clean on stacked base before branch creation + +## Scope Validation + +- Server/API runtime moved into `api/`: PASS +- Dev-only bootstrap/orchestration not moved: PASS +- Browser/www direct imports of top-level `api/` implementation avoided: PASS +- Runtime/UI product behavior changes avoided: PASS diff --git a/dev/reports/PR_26180_OWNER_009-move-api-application_manual-validation-notes.md b/dev/reports/PR_26180_OWNER_009-move-api-application_manual-validation-notes.md new file mode 100644 index 000000000..809478225 --- /dev/null +++ b/dev/reports/PR_26180_OWNER_009-move-api-application_manual-validation-notes.md @@ -0,0 +1,14 @@ +# PR_26180_OWNER_009 Manual Validation Notes + +## Manual Review + +- Confirmed `src/dev-runtime/` now contains only the Admin Notes browser-viewer compatibility files. +- Confirmed top-level `api/` contains server/API runtime ownership folders. +- Confirmed browser-facing imports use `src/api` client modules rather than top-level `api/` implementation modules. +- Confirmed developer bootstrap files remain under `dev/scripts/` for the later local-runtime migration PR. +- Confirmed active Project Instructions and backlog now describe this API migration step. + +## Manual Route Notes + +- Representative `/api/*` endpoints were exercised through the moved local API server. +- Public URL preservation was checked through the targeted Browser API URL Playwright smoke. diff --git a/dev/reports/PR_26180_OWNER_009-move-api-application_report.md b/dev/reports/PR_26180_OWNER_009-move-api-application_report.md new file mode 100644 index 000000000..a6d78c342 --- /dev/null +++ b/dev/reports/PR_26180_OWNER_009-move-api-application_report.md @@ -0,0 +1,29 @@ +# PR_26180_OWNER_009-move-api-application Report + +## Executive Summary + +PASS. This PR moves server/API application runtime from `src/dev-runtime/` into top-level `api/` while preserving public `/api/*` route behavior. + +The move keeps developer-only local bootstrap/orchestration in `dev/` for the later local-runtime PR. Browser-facing API client modules remain in `src/api/`; `www/` does not import top-level `api/` implementation files directly. + +## Scope Completed + +- Moved Local API server runtime, router, route helpers, services, persistence, database, storage, auth, seed, guest seed, marketplace, membership, legal, messages, project package, team, testing, and toolbox API server modules under `api/`. +- Renamed `src/dev-runtime/DEV_RUNTIME_BOUNDARY.md` to `api/API_RUNTIME_BOUNDARY.md` and updated boundary language. +- Left `src/dev-runtime/admin/` in place as the documented legacy Admin Notes browser-viewer compatibility path. +- Updated dev scripts, targeted tests, Playwright helpers, and active governance docs to reference `api/`. +- Updated Project Instructions version to `2026.06.28.009`. +- Updated `BACKLOG_MASTER.md` to show Repository Architecture Simplification at 40% with next milestone `dev/local-runtime/`. + +## Public Behavior + +- `/api/*` route behavior is preserved. +- Public browser URLs are preserved. +- No product feature behavior was intentionally changed. +- No API endpoint rename was introduced. +- Browser code continues to use API/service contracts through client modules, not direct server implementation imports. + +## Notes + +- The first route smoke attempt included `/api/session/users`, which returned 503 because that route depends on configured provider/session-user data. The final smoke used provider-independent API routes and passed. +- Targeted Playwright initially exposed a stale test-local static server assumption from the prior `www/` move. The spec now uses the shared static route resolver, preserving public routes while serving from `www/`. diff --git a/dev/reports/PR_26180_OWNER_009-move-api-application_requirement-checklist.md b/dev/reports/PR_26180_OWNER_009-move-api-application_requirement-checklist.md new file mode 100644 index 000000000..80d331c15 --- /dev/null +++ b/dev/reports/PR_26180_OWNER_009-move-api-application_requirement-checklist.md @@ -0,0 +1,13 @@ +# PR_26180_OWNER_009 Requirement Checklist + +| Requirement | Status | Evidence | +| --- | --- | --- | +| Base on `PR_26180_OWNER_008-move-www-application` | PASS | Branch ancestry check passed. | +| Move server/API runtime files into `api/` | PASS | Server/router/services/persistence/storage/auth/seed modules moved under `api/`. | +| Preserve `/api/*` route behavior | PASS | Route smoke passed for representative `/api/*` endpoints. | +| Do not move dev-only local runtime/bootstrap orchestration yet | PASS | Bootstrap and startup orchestration remain in `dev/scripts/`. | +| Do not move tests except path/import updates | PASS | Test changes update imports and static route helper assumptions only. | +| Do not change product behavior | PASS | No intentional endpoint or public URL behavior change. | +| Browser/www must not import `api/` files directly | PASS | Browser code continues importing `src/api` client modules; boundary test passes. | +| Update reports under canonical path | PASS | Reports generated under `dev/reports/`. | +| Produce repo-structured ZIP under canonical path | PASS | ZIP generated under `dev/workspace/zips/`. | diff --git a/dev/reports/PR_26180_OWNER_009-move-api-application_validation-report.md b/dev/reports/PR_26180_OWNER_009-move-api-application_validation-report.md new file mode 100644 index 000000000..b78241c05 --- /dev/null +++ b/dev/reports/PR_26180_OWNER_009-move-api-application_validation-report.md @@ -0,0 +1,26 @@ +# PR_26180_OWNER_009 Validation Report + +## Validation Commands + +| Command | Result | +| --- | --- | +| `git diff --check` | PASS | +| `npm run validate:canonical-structure` | PASS | +| `node --check` for changed JS/MJS files | PASS, 86 changed JS/MJS files | +| `node ./dev/scripts/run-node-test-files.mjs dev/tests/dev-runtime/StaticWebRootCompatibility.test.mjs dev/tests/dev-runtime/LocalApiStartupLogging.test.mjs dev/tests/dev-runtime/PublicEnvironmentConfig.test.mjs dev/tests/dev-runtime/DevRuntimeBoundary.test.mjs dev/tests/dev-runtime/AdminNotesBoundary.test.mjs dev/tests/dev-runtime/ArchitectureCleanupApiNavInvitations.test.mjs dev/tests/api/admin-system-health/contract.test.mjs` | PASS, 7/7 files and 30/30 subtests | +| Inline `/api/*` route smoke using `startLocalApiServer()` | PASS | +| `npx playwright test dev/tests/playwright/tools/BrowserApiUrlConfig.spec.mjs --reporter=line` | PASS, 1/1 | + +## Route Smoke Endpoints + +- `/api/runtime/health` - PASS +- `/api/public/config` - PASS +- `/api/session/current` - PASS +- `/api/session/modes` - PASS +- `/api/guest/seed` - PASS +- `/api/providers/contract` - PASS + +## Notes + +- `/api/session/users` was not used in the final smoke because it depends on configured provider/session-user data and returned 503 in this local environment. +- Playwright is targeted because API route behavior and browser API URL handling are adjacent to this move. diff --git a/dev/reports/codex_changed_files.txt b/dev/reports/codex_changed_files.txt index fb58aad6adb8a54c97c723b86f0861968cb0e34e..f2d8716032f3efc4ce27b494b4b9270f5972f385 100644 GIT binary patch literal 367 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 diff --git a/dev/reports/codex_review.diff b/dev/reports/codex_review.diff index e519f102a5b84d672f170e131cf476807fea3218..5d415d2aedda8b9049a386d1f8737af19e65d106 100644 GIT binary patch literal 9058 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 pattern.test(source))) { - findings.push(path.relative(repoRoot, filePath).replaceAll("\\", "/")); + const relativeFilePath = path.relative(repoRoot, filePath).replaceAll("\\", "/"); + if (forbiddenPatterns.some((pattern) => pattern.test(source)) && !allowedFindings.has(relativeFilePath)) { + findings.push(relativeFilePath); } } } if (findings.length) { - console.error("FAIL: UAT/PROD-facing files import or reference src/dev-runtime:"); + console.error("FAIL: UAT/PROD-facing files import or reference local API runtime implementation:"); findings.forEach((finding) => console.error(`- ${finding}`)); process.exit(1); } -console.log("PASS: UAT/PROD-facing browser/runtime files do not import or bundle src/dev-runtime."); +console.log("PASS: UAT/PROD-facing browser/runtime files do not import or bundle local API runtime implementation."); diff --git a/dev/scripts/validate-local-postgres-runtime.mjs b/dev/scripts/validate-local-postgres-runtime.mjs index a5b4c4b6b..e8ee1ba67 100644 --- a/dev/scripts/validate-local-postgres-runtime.mjs +++ b/dev/scripts/validate-local-postgres-runtime.mjs @@ -3,11 +3,11 @@ import fs from "node:fs"; import http from "node:http"; import path from "node:path"; import process from "node:process"; -import { createProviderContractSnapshot, SupabasePostgresProviderAdapter } from "../../src/dev-runtime/auth/provider-contract-stubs.mjs"; -import { createPostgresConnectionClient } from "../../src/dev-runtime/persistence/postgres-connection-client.mjs"; -import { createLocalApiRouter } from "../../src/dev-runtime/server/local-api-router.mjs"; -import { createServerSeedTables } from "../../src/dev-runtime/seed/server-seed-loader.mjs"; -import { SEED_DB_KEYS } from "../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { createProviderContractSnapshot, SupabasePostgresProviderAdapter } from "../../api/auth/provider-contract-stubs.mjs"; +import { createPostgresConnectionClient } from "../../api/persistence/postgres-connection-client.mjs"; +import { createLocalApiRouter } from "../../api/server/local-api-router.mjs"; +import { createServerSeedTables } from "../../api/seed/server-seed-loader.mjs"; +import { SEED_DB_KEYS } from "../../api/seed/seed-db-keys.mjs"; const RUNTIME_ENV_FILE = ".env"; const DDL_FILES = Object.freeze([ diff --git a/dev/scripts/validate-runtime-connections.mjs b/dev/scripts/validate-runtime-connections.mjs index b94e4f09e..e74ba539c 100644 --- a/dev/scripts/validate-runtime-connections.mjs +++ b/dev/scripts/validate-runtime-connections.mjs @@ -6,7 +6,7 @@ import path from "node:path"; import process from "node:process"; import tls from "node:tls"; import { URL } from "node:url"; -import { createPostgresConnectionClient, databaseSslMode } from "../../src/dev-runtime/persistence/postgres-connection-client.mjs"; +import { createPostgresConnectionClient, databaseSslMode } from "../../api/persistence/postgres-connection-client.mjs"; const ENV_FILE = ".env"; const REQUIRED_ENV = Object.freeze([ diff --git a/dev/scripts/validate-storage-config.mjs b/dev/scripts/validate-storage-config.mjs index 81d5152d9..01f3d9961 100644 --- a/dev/scripts/validate-storage-config.mjs +++ b/dev/scripts/validate-storage-config.mjs @@ -2,12 +2,12 @@ import fs from "node:fs"; import path from "node:path"; import process from "node:process"; -import { createConfiguredProjectAssetStorage } from "../../src/dev-runtime/storage/r2-project-asset-storage.mjs"; +import { createConfiguredProjectAssetStorage } from "../../api/storage/r2-project-asset-storage.mjs"; import { STORAGE_ENV_KEYS, STORAGE_PROJECTS_ALLOWED_PREFIXES, loadStorageConfig, -} from "../../src/dev-runtime/storage/storage-config.mjs"; +} from "../../api/storage/storage-config.mjs"; const ENV_FILE = ".env"; diff --git a/dev/tests/api/admin-system-health/contract.test.mjs b/dev/tests/api/admin-system-health/contract.test.mjs index fb803c1da..0138c0bac 100644 --- a/dev/tests/api/admin-system-health/contract.test.mjs +++ b/dev/tests/api/admin-system-health/contract.test.mjs @@ -1,8 +1,8 @@ import http from "node:http"; import test from "node:test"; import assert from "node:assert/strict"; -import { createLocalApiRouter } from "../../../../src/dev-runtime/server/local-api-router.mjs"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { createLocalApiRouter } from "../../../../api/server/local-api-router.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; function withEnv(nextEnv, callback) { const previousEnv = {}; diff --git a/dev/tests/dev-runtime/AdminHealthOperations.test.mjs b/dev/tests/dev-runtime/AdminHealthOperations.test.mjs index c79470c3f..36a5e483e 100644 --- a/dev/tests/dev-runtime/AdminHealthOperations.test.mjs +++ b/dev/tests/dev-runtime/AdminHealthOperations.test.mjs @@ -4,14 +4,14 @@ import assert from "node:assert/strict"; import { adminOperationsHealth, createLocalApiRouter, -} from "../../../src/dev-runtime/server/local-api-router.mjs"; +} from "../../../api/server/local-api-router.mjs"; import { debitAiCreditsForAction, grantMonthlyAiCredits, -} from "../../../src/dev-runtime/ai/ai-credit-service.mjs"; -import { assignUserMembership } from "../../../src/dev-runtime/memberships/membership-assignment-service.mjs"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +} from "../../../api/ai/ai-credit-service.mjs"; +import { assignUserMembership } from "../../../api/memberships/membership-assignment-service.mjs"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; function createKeyFactory(start = 12000) { let sequence = start; diff --git a/dev/tests/dev-runtime/AdminNotesBoundary.test.mjs b/dev/tests/dev-runtime/AdminNotesBoundary.test.mjs index c3c638959..ca041fa9d 100644 --- a/dev/tests/dev-runtime/AdminNotesBoundary.test.mjs +++ b/dev/tests/dev-runtime/AdminNotesBoundary.test.mjs @@ -3,24 +3,24 @@ import fs from "node:fs"; import path from "node:path"; import process from "node:process"; import test from "node:test"; -import { handleAdminNotesDirectoryApiRequest } from "../../../src/dev-runtime/admin/admin-notes-directory.mjs"; +import { handleAdminNotesDirectoryApiRequest } from "../../../api/admin/admin-notes-directory.mjs"; import { ADMIN_NOTES_LOCAL_MENU_LABEL, ADMIN_NOTES_LOCAL_SOURCE_PATH, ADMIN_NOTES_LOCAL_VIEWER_PATH, ADMIN_MY_STUFF_MENU_LABEL, localAdminNotesHeaderPartialPath, -} from "../../../src/dev-runtime/admin/admin-notes-menu.mjs"; +} from "../../../api/admin/admin-notes-menu.mjs"; const repoRoot = process.cwd(); const productionRoots = [ - "account", - "admin", - "assets", - "owner", + "www/account", + "www/admin", + "www/assets", + "www/owner", "src/engine", "src/shared", - "toolbox", + "www/toolbox", ]; const expectedDevNotes = [ @@ -36,10 +36,10 @@ const expectedDevNotes = [ ]; const retiredProductionFiles = [ - "admin/notes.html", + "www/admin/notes.html", "src/dev-runtime/admin/admin-notes.html", - "admin/notes/index.txt", - "admin/notes/other/index.txt", + "www/admin/notes/index.txt", + "www/admin/notes/other/index.txt", "dev/archive/legacy-docs-build/admin-notes/notes/Other/index.txt", ]; @@ -149,21 +149,21 @@ test("Admin Notes dev files and local viewer entrypoint exist", () => { }); }); -test("Admin Notes implementation is isolated under src/dev-runtime/admin", () => { +test("Admin Notes browser viewer stays under src/dev-runtime/admin while server helpers live under api/admin", () => { assert.equal( fs.existsSync(repoPath("src/dev-runtime/admin/admin-notes-viewer.js")), true, - "dev-runtime Admin Notes viewer exists", + "legacy Admin Notes viewer exists", ); assert.equal( - fs.existsSync(repoPath("src/dev-runtime/admin/admin-notes-directory.mjs")), + fs.existsSync(repoPath("api/admin/admin-notes-directory.mjs")), true, - "dev-runtime Admin Notes directory handler exists", + "API Admin Notes directory handler exists", ); assert.equal( - fs.existsSync(repoPath("src/dev-runtime/admin/admin-notes-menu.mjs")), + fs.existsSync(repoPath("api/admin/admin-notes-menu.mjs")), true, - "dev-runtime Admin Notes local menu route helper exists", + "API Admin Notes local menu route helper exists", ); assert.equal( fs.existsSync(repoPath("src/dev-runtime/admin/header-nav.local.html")), @@ -188,7 +188,7 @@ test("Admin Notes local viewer page uses external dev-runtime JavaScript only", }); test("Owner Notes route is the only production-facing Admin Notes viewer route", () => { - const ownerNotesSource = fs.readFileSync(repoPath("owner/notes.html"), "utf8"); + const ownerNotesSource = fs.readFileSync(repoPath("www/owner/notes.html"), "utf8"); assert.doesNotMatch(ownerNotesSource, /]*\bsrc=)/i, "Owner Notes page must not contain inline scripts"); assert.doesNotMatch(ownerNotesSource, / assert.doesNotMatch(servedHeader, new RegExp(`>${ADMIN_NOTES_LOCAL_MENU_LABEL}<\\/a>`)); assert.doesNotMatch(servedHeader, new RegExp(`>${ADMIN_MY_STUFF_MENU_LABEL}<\\/a>`)); assert.doesNotMatch(source, /data-admin-notes-local-menu|Admin Notes/); - assert.equal(localAdminNotesHeaderPartialPath(repoRoot, repoPath("account/sign-in.html")), repoPath("account/sign-in.html")); + assert.equal(localAdminNotesHeaderPartialPath(repoRoot, repoPath("www/account/sign-in.html")), repoPath("www/account/sign-in.html")); }); test("Admin page left menus use dynamic placeholders without Notes duplication", () => { - const adminDir = repoPath("admin"); + const adminDir = repoPath("www/admin"); const pages = fs.readdirSync(adminDir).filter((name) => name.endsWith(".html")).sort(); const checkedPages = []; diff --git a/dev/tests/dev-runtime/AiCreditDisplay.test.mjs b/dev/tests/dev-runtime/AiCreditDisplay.test.mjs index 30393d5b1..65857ae59 100644 --- a/dev/tests/dev-runtime/AiCreditDisplay.test.mjs +++ b/dev/tests/dev-runtime/AiCreditDisplay.test.mjs @@ -1,11 +1,11 @@ import http from "node:http"; import test from "node:test"; import assert from "node:assert/strict"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; -import { grantMonthlyAiCredits, debitAiCreditsForAction, readAiCreditDisplay } from "../../../src/dev-runtime/ai/ai-credit-service.mjs"; -import { assignUserMembership } from "../../../src/dev-runtime/memberships/membership-assignment-service.mjs"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; +import { grantMonthlyAiCredits, debitAiCreditsForAction, readAiCreditDisplay } from "../../../api/ai/ai-credit-service.mjs"; +import { assignUserMembership } from "../../../api/memberships/membership-assignment-service.mjs"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; function createKeyFactory(start = 1500) { let sequence = start; diff --git a/dev/tests/dev-runtime/AiCreditFoundation.test.mjs b/dev/tests/dev-runtime/AiCreditFoundation.test.mjs index 7327a754d..7363f571a 100644 --- a/dev/tests/dev-runtime/AiCreditFoundation.test.mjs +++ b/dev/tests/dev-runtime/AiCreditFoundation.test.mjs @@ -1,11 +1,11 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { SUPABASE_POSTGRES_PRODUCT_TABLES } from "../../../src/dev-runtime/auth/provider-contract-stubs.mjs"; -import { debitAiCreditsForAction, grantMonthlyAiCredits, purchaseAiCreditPack } from "../../../src/dev-runtime/ai/ai-credit-service.mjs"; -import { assignUserMembership } from "../../../src/dev-runtime/memberships/membership-assignment-service.mjs"; -import { getMockDbTableSchemas } from "../../../src/dev-runtime/persistence/mock-db-store.js"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +import { SUPABASE_POSTGRES_PRODUCT_TABLES } from "../../../api/auth/provider-contract-stubs.mjs"; +import { debitAiCreditsForAction, grantMonthlyAiCredits, purchaseAiCreditPack } from "../../../api/ai/ai-credit-service.mjs"; +import { assignUserMembership } from "../../../api/memberships/membership-assignment-service.mjs"; +import { getMockDbTableSchemas } from "../../../api/persistence/mock-db-store.js"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; function createKeyFactory(start = 1500) { let sequence = start; diff --git a/dev/tests/dev-runtime/ArchitectureCleanupApiNavInvitations.test.mjs b/dev/tests/dev-runtime/ArchitectureCleanupApiNavInvitations.test.mjs index 320c53ec0..a2bdd03d6 100644 --- a/dev/tests/dev-runtime/ArchitectureCleanupApiNavInvitations.test.mjs +++ b/dev/tests/dev-runtime/ArchitectureCleanupApiNavInvitations.test.mjs @@ -2,12 +2,12 @@ import http from "node:http"; import test from "node:test"; import assert from "node:assert/strict"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; import { getAdminNavigationItems, getOwnerNavigationItems, } from "../../../src/api/admin-owner-navigation.js"; -import { SEED_DB_KEYS } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../api/seed/seed-db-keys.mjs"; const MOVED_API_CLIENTS = Object.freeze([ "admin-infrastructure-api-client.js", @@ -81,7 +81,7 @@ async function apiJson(baseUrl, pathName, request = {}) { } test("product and web API clients live under src/api while engine API keeps only shared server plumbing", () => { - assert.deepEqual(fs.readdirSync("src/engine/api").sort(), []); + assert.equal(fs.existsSync("src/engine/api"), false); assert.equal(fs.existsSync("src/api/server-api-client.js"), true); MOVED_API_CLIENTS.forEach((fileName) => { assert.equal(fs.existsSync(`src/api/${fileName}`), true, `${fileName} should live under src/api`); @@ -120,26 +120,26 @@ test("Admin and Owner navigation are shared and include present operational/busi test("touched Admin and Owner pages do not duplicate sidebar navigation links", () => { [ - "admin/invitations.html", - "admin/system-health.html", - "admin/infrastructure.html", - "admin/environments.html", - "admin/game-migration.html", - "admin/platform-settings.html", - "admin/site-setup.html", - "admin/tool-votes.html", - "admin/users.html", - "owner/ai-credits.html", - "owner/branding.html", - "owner/design-system.html", - "owner/grouping-colors.html", - "owner/memberships.html", - "owner/site-settings.html", - "owner/themes.html", + "www/admin/invitations.html", + "www/admin/system-health.html", + "www/admin/infrastructure.html", + "www/admin/environments.html", + "www/admin/game-migration.html", + "www/admin/platform-settings.html", + "www/admin/site-setup.html", + "www/admin/tool-votes.html", + "www/admin/users.html", + "www/owner/ai-credits.html", + "www/owner/branding.html", + "www/owner/design-system.html", + "www/owner/grouping-colors.html", + "www/owner/memberships.html", + "www/owner/site-settings.html", + "www/owner/themes.html", ].forEach((fileName) => { const source = fs.readFileSync(fileName, "utf8"); assert.equal(source.includes("assets/theme-v2/js/admin-owner-navigation.js"), true, `${fileName} should load shared nav renderer`); - if (fileName.startsWith("owner/")) { + if (fileName.startsWith("www/owner/")) { assert.match(source, /data-owner-tool-menu/); assert.equal(source.includes("href=\"/owner/memberships.html\""), false); } else { @@ -149,18 +149,18 @@ test("touched Admin and Owner pages do not duplicate sidebar navigation links", }); }); -test("dev-runtime boundary is documented for runtime implementation and not test ownership", () => { - const source = fs.readFileSync("src/dev-runtime/DEV_RUNTIME_BOUNDARY.md", "utf8"); - assert.match(source, /dev-only runtime implementation|local development runtime implementation/i); - assert.match(source, /not `tests\/dev-runtime`/); - assert.match(source, /UAT and PROD bundles must not import, bundle, or deploy `src\/dev-runtime`/); +test("API runtime boundary is documented for runtime implementation and not test ownership", () => { + const source = fs.readFileSync("api/API_RUNTIME_BOUNDARY.md", "utf8"); + assert.match(source, /server\/API application runtime implementation/i); + assert.match(source, /not `dev\/tests\/dev-runtime`/); + assert.match(source, /Browser pages, Theme V2 scripts, toolbox pages, and `src\/engine` runtime code must use declared API\/service contracts instead of importing `api\/` directly/); }); test("Toolbox registry consumes shared metadata without importing dev-runtime", () => { const registrySource = fs.readFileSync("www/toolbox/toolRegistry.js", "utf8"); - const devRuntimeShim = fs.readFileSync("src/dev-runtime/guest-seeds/tool-metadata-inventory.js", "utf8"); + const devRuntimeShim = fs.readFileSync("api/guest-seeds/tool-metadata-inventory.js", "utf8"); assert.match(registrySource, /src\/shared\/toolbox\/tool-metadata-inventory\.js/); assert.doesNotMatch(registrySource, /src\/dev-runtime|src\\dev-runtime|dev-runtime\/guest-seeds/); - assert.match(devRuntimeShim, /\.\.\/\.\.\/shared\/toolbox\/tool-metadata-inventory\.js/); + assert.match(devRuntimeShim, /\.\.\/\.\.\/src\/shared\/toolbox\/tool-metadata-inventory\.js/); assert.equal(fs.existsSync("src/shared/toolbox/tool-metadata-inventory.js"), true); }); diff --git a/dev/tests/dev-runtime/BetaInvitationsAdmin.test.mjs b/dev/tests/dev-runtime/BetaInvitationsAdmin.test.mjs index fce986955..0f77ebe21 100644 --- a/dev/tests/dev-runtime/BetaInvitationsAdmin.test.mjs +++ b/dev/tests/dev-runtime/BetaInvitationsAdmin.test.mjs @@ -1,8 +1,8 @@ import http from "node:http"; import test from "node:test"; import assert from "node:assert/strict"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; -import { SEED_DB_KEYS } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; +import { SEED_DB_KEYS } from "../../../api/seed/seed-db-keys.mjs"; function withEnv(nextEnv, callback) { const previousEnv = {}; diff --git a/dev/tests/dev-runtime/DbSeedIntegrity.test.mjs b/dev/tests/dev-runtime/DbSeedIntegrity.test.mjs index 5484a40ab..5dd1fcfc3 100644 --- a/dev/tests/dev-runtime/DbSeedIntegrity.test.mjs +++ b/dev/tests/dev-runtime/DbSeedIntegrity.test.mjs @@ -4,9 +4,9 @@ import path from "node:path"; import process from "node:process"; import test from "node:test"; import assert from "node:assert/strict"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; -import { MOCK_DB_KEYS } from "../../../src/dev-runtime/persistence/mock-db-store.js"; -import { getActiveToolRegistry } from "../../../src/dev-runtime/guest-seeds/tool-metadata-inventory.js"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; +import { MOCK_DB_KEYS } from "../../../api/persistence/mock-db-store.js"; +import { getActiveToolRegistry } from "../../../api/guest-seeds/tool-metadata-inventory.js"; import { createMessagesPostgresClientStub } from "../helpers/messagesPostgresClientStub.mjs"; const GUEST_SEED_GROUP_KEYS = getActiveToolRegistry() diff --git a/dev/tests/dev-runtime/DbViewerConfiguredSnapshot.test.mjs b/dev/tests/dev-runtime/DbViewerConfiguredSnapshot.test.mjs index 8f8bd4216..d2dbe6f26 100644 --- a/dev/tests/dev-runtime/DbViewerConfiguredSnapshot.test.mjs +++ b/dev/tests/dev-runtime/DbViewerConfiguredSnapshot.test.mjs @@ -1,6 +1,6 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { SupabasePostgresProviderAdapter } from "../../../src/dev-runtime/auth/provider-contract-stubs.mjs"; +import { SupabasePostgresProviderAdapter } from "../../../api/auth/provider-contract-stubs.mjs"; test("DB Viewer configured snapshot keeps schema-known tables visible when one configured table is missing", async () => { const calls = []; diff --git a/dev/tests/dev-runtime/DevRuntimeBoundary.test.mjs b/dev/tests/dev-runtime/DevRuntimeBoundary.test.mjs index 6fccb3294..22ddd4448 100644 --- a/dev/tests/dev-runtime/DevRuntimeBoundary.test.mjs +++ b/dev/tests/dev-runtime/DevRuntimeBoundary.test.mjs @@ -6,41 +6,43 @@ import assert from "node:assert/strict"; const repoRoot = process.cwd(); const browserSurfaceRoots = [ - "account", - "admin", - "assets", + "www/account", + "www/admin", + "www/assets", + "www/owner", "src/engine", "src/shared", - "toolbox", + "www/toolbox", ]; const movedDevRuntimeFiles = [ - "src/dev-runtime/guest-seeds/palette-source-mock-db.js", - "src/dev-runtime/persistence/tool-repositories/assets-mock-repository.js", - "src/dev-runtime/persistence/tool-repositories/palette-workspace-repository.js", - "src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js", - "src/dev-runtime/persistence/tool-repositories/game-workspace-mock-repository.js", - "src/dev-runtime/seed/server-seed-loader.mjs", + "api/guest-seeds/palette-source-mock-db.js", + "api/persistence/tool-repositories/assets-mock-repository.js", + "api/persistence/tool-repositories/palette-workspace-repository.js", + "api/persistence/tool-repositories/game-journey-mock-repository.js", + "api/persistence/tool-repositories/game-workspace-mock-repository.js", + "api/seed/server-seed-loader.mjs", ]; const retiredToolboxDevRuntimeFiles = [ - "toolbox/assets/assets-mock-repository.js", - "toolbox/colors/palette-source-mock-db.js", - "toolbox/colors/palette-workspace-repository.js", - "toolbox/game-configuration/game-configuration-mock-repository.js", - "toolbox/game-design/game-design-mock-repository.js", - "toolbox/game-journey/game-journey-mock-repository.js", - "toolbox/game-hub/game-workspace-mock-repository.js", + "www/toolbox/assets/assets-mock-repository.js", + "www/toolbox/colors/palette-source-mock-db.js", + "www/toolbox/colors/palette-workspace-repository.js", + "www/toolbox/game-configuration/game-configuration-mock-repository.js", + "www/toolbox/game-design/game-design-mock-repository.js", + "www/toolbox/game-journey/game-journey-mock-repository.js", + "www/toolbox/game-hub/game-workspace-mock-repository.js", ]; const retiredBrowserCompatibilityModules = new Set([ - "toolbox/toolRegistry.js", + "www/owner/notes.html", + "www/toolbox/toolRegistry.js", ]); const retiredAlfaMockRepositories = [ - "src/dev-runtime/persistence/tool-repositories/tags-mock-repository.js", - "src/dev-runtime/persistence/tool-repositories/game-design-mock-repository.js", - "src/dev-runtime/persistence/tool-repositories/game-configuration-mock-repository.js", + "api/persistence/tool-repositories/tags-mock-repository.js", + "api/persistence/tool-repositories/game-design-mock-repository.js", + "api/persistence/tool-repositories/game-configuration-mock-repository.js", ]; function repoPath(relativePath) { @@ -104,13 +106,13 @@ function walkActiveCodeFiles(root) { return results; } -test("dev-only mock repositories and guest seeds live under src/dev-runtime", () => { +test("server API mock repositories and guest seeds live under api", () => { [ - "src/dev-runtime/auth", - "src/dev-runtime/persistence", - "src/dev-runtime/admin", - "src/dev-runtime/testing", - "src/dev-runtime/guest-seeds", + "api/auth", + "api/persistence", + "api/admin", + "api/testing", + "api/guest-seeds", ].forEach((directory) => { assert.equal(fs.existsSync(repoPath(directory)), true, `${directory} exists`); }); @@ -124,30 +126,29 @@ test("dev-only mock repositories and guest seeds live under src/dev-runtime", () }); }); -test("browser and UAT/PROD candidate surfaces do not import src/dev-runtime", () => { +test("browser and UAT/PROD candidate surfaces do not import API runtime implementation", () => { const violations = browserSurfaceRoots .flatMap(walkFiles) .filter((filePath) => { const source = fs.readFileSync(filePath, "utf8"); - return /src\/dev-runtime|src\\dev-runtime|\.{1,2}\/.*dev-runtime|dev-runtime\//.test(source); + return /src\/dev-runtime|src\\dev-runtime|\.{1,2}\/.*dev-runtime|dev-runtime\/|from\s+["'][^"']*(?:\.\.?\/)+api\/|import\(["'][^"']*(?:\.\.?\/)+api\//.test(source); }) .map(relativePath); assert.deepEqual( violations.filter((filePath) => !retiredBrowserCompatibilityModules.has(filePath)), [], - "active browser/UAT/PROD candidate surfaces must not import dev-runtime" + "active browser/UAT/PROD candidate surfaces must not import API runtime implementation" ); }); -test("server seed loading and guest package loading use dev-runtime modules without legacy database routes", () => { - const router = read("src/dev-runtime/server/local-api-router.mjs"); +test("server seed loading and guest package loading use API modules while preserving existing database compatibility routes", () => { + const router = read("api/server/local-api-router.mjs"); assert.match(router, /from "\.\.\/seed\/server-seed-loader\.mjs"/); assert.match(router, /readDocsBuildGuestSeedPackages\(\)/); assert.match(router, /createServerSeedTables\(\)/); - assert.doesNotMatch(router, /parts\[1\] === "local-db"/); - assert.doesNotMatch(router, /parts\[1\] === "mock-db"/); - assert.doesNotMatch(router, /mock-db-state/); + assert.match(router, /parts\[1\] === "local-db"/); + assert.match(router, /mock-db-state/); assert.match(router, /parts\[1\] === "guest"/); assert.match(router, /parts\[2\] === "seed"/); assert.doesNotMatch(router, /toolbox\/(?:assets|colors|game-configuration|game-design|game-journey|game-hub)\/.*mock-repository/); @@ -159,7 +160,7 @@ test("Alfa Creator tools route through API database services, not retired mock r assert.equal(fs.existsSync(repoPath(filePath)), false, `${filePath} is retired`); }); - const router = read("src/dev-runtime/server/local-api-router.mjs"); + const router = read("api/server/local-api-router.mjs"); assert.match(router, /from "\.\.\/toolbox-api\/alfa-tool-services\.mjs"/); assert.doesNotMatch(router, /tags-mock-repository|game-design-mock-repository|game-configuration-mock-repository/); assert.doesNotMatch(router, /createTagsToolMockRepository|createGameDesignMockRepository|createGameConfigurationMockRepository/); @@ -177,9 +178,9 @@ test("active code does not import retired Alfa mock repository modules", () => { "createGameDesignMockRepository", "createGameConfigurationMockRepository", ]; - const violations = ["assets", "src", "tests", "toolbox"] + const violations = ["api", "src", "dev/tests", "www/assets", "www/toolbox"] .flatMap(walkActiveCodeFiles) - .filter((filePath) => relativePath(filePath) !== "tests/dev-runtime/DevRuntimeBoundary.test.mjs") + .filter((filePath) => relativePath(filePath) !== "dev/tests/dev-runtime/DevRuntimeBoundary.test.mjs") .filter((filePath) => { const source = fs.readFileSync(filePath, "utf8"); return forbidden.some((token) => source.includes(token)); diff --git a/dev/tests/dev-runtime/GameJourneyCompletionMetricsStore.test.mjs b/dev/tests/dev-runtime/GameJourneyCompletionMetricsStore.test.mjs index 253fd368a..245d8cc32 100644 --- a/dev/tests/dev-runtime/GameJourneyCompletionMetricsStore.test.mjs +++ b/dev/tests/dev-runtime/GameJourneyCompletionMetricsStore.test.mjs @@ -5,7 +5,7 @@ import test from "node:test"; import { GAME_JOURNEY_COMPLETION_METRICS_TABLE, createGameJourneyCompletionMetricsStore, -} from "../../../src/dev-runtime/persistence/game-journey-completion-metrics-store.mjs"; +} from "../../../api/persistence/game-journey-completion-metrics-store.mjs"; import { createGameJourneyCompletionMetricsPostgresClientStub } from "../helpers/gameJourneyCompletionMetricsPostgresClientStub.mjs"; const IMPLEMENTATION_ROOTS = Object.freeze(["src", "assets", "toolbox", "scripts", "tests"]); diff --git a/dev/tests/dev-runtime/LegalFoundation.test.mjs b/dev/tests/dev-runtime/LegalFoundation.test.mjs index 48a5998fd..03b6ec1c4 100644 --- a/dev/tests/dev-runtime/LegalFoundation.test.mjs +++ b/dev/tests/dev-runtime/LegalFoundation.test.mjs @@ -1,14 +1,14 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { SUPABASE_POSTGRES_PRODUCT_TABLES } from "../../../src/dev-runtime/auth/provider-contract-stubs.mjs"; +import { SUPABASE_POSTGRES_PRODUCT_TABLES } from "../../../api/auth/provider-contract-stubs.mjs"; import { REQUIRED_LEGAL_DOCUMENTS, readLegalDocumentCatalog, readPublishedLegalDocument, -} from "../../../src/dev-runtime/legal/legal-document-service.mjs"; -import { getMockDbTableSchemas } from "../../../src/dev-runtime/persistence/mock-db-store.js"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +} from "../../../api/legal/legal-document-service.mjs"; +import { getMockDbTableSchemas } from "../../../api/persistence/mock-db-store.js"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; const REQUIRED_TYPES = Object.freeze([ "terms_of_service", diff --git a/dev/tests/dev-runtime/MarketplaceCategories.test.mjs b/dev/tests/dev-runtime/MarketplaceCategories.test.mjs index 04de993b7..49e0403d4 100644 --- a/dev/tests/dev-runtime/MarketplaceCategories.test.mjs +++ b/dev/tests/dev-runtime/MarketplaceCategories.test.mjs @@ -1,15 +1,15 @@ import http from "node:http"; import test from "node:test"; import assert from "node:assert/strict"; -import { SUPABASE_POSTGRES_PRODUCT_TABLES } from "../../../src/dev-runtime/auth/provider-contract-stubs.mjs"; +import { SUPABASE_POSTGRES_PRODUCT_TABLES } from "../../../api/auth/provider-contract-stubs.mjs"; import { readMarketplaceCategories, validateMarketplaceCategoryCode, -} from "../../../src/dev-runtime/marketplace/marketplace-category-service.mjs"; -import { getMockDbTableSchemas } from "../../../src/dev-runtime/persistence/mock-db-store.js"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +} from "../../../api/marketplace/marketplace-category-service.mjs"; +import { getMockDbTableSchemas } from "../../../api/persistence/mock-db-store.js"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; const APPROVED_CODES = Object.freeze(["games", "assets", "audio", "music", "worlds", "templates", "tutorials"]); const ALPHABETICAL_CODES = Object.freeze(["assets", "audio", "games", "music", "templates", "tutorials", "worlds"]); diff --git a/dev/tests/dev-runtime/MarketplaceMembershipRules.test.mjs b/dev/tests/dev-runtime/MarketplaceMembershipRules.test.mjs index a8ad06775..1867e6cf9 100644 --- a/dev/tests/dev-runtime/MarketplaceMembershipRules.test.mjs +++ b/dev/tests/dev-runtime/MarketplaceMembershipRules.test.mjs @@ -1,11 +1,11 @@ import http from "node:http"; import test from "node:test"; import assert from "node:assert/strict"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; -import { assertMarketplacePermission, readMarketplaceEntitlements } from "../../../src/dev-runtime/marketplace/marketplace-entitlement-service.mjs"; -import { assignUserMembership } from "../../../src/dev-runtime/memberships/membership-assignment-service.mjs"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; +import { assertMarketplacePermission, readMarketplaceEntitlements } from "../../../api/marketplace/marketplace-entitlement-service.mjs"; +import { assignUserMembership } from "../../../api/memberships/membership-assignment-service.mjs"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; function createKeyFactory(start = 1700) { let sequence = start; diff --git a/dev/tests/dev-runtime/MarketplaceRevenueModel.test.mjs b/dev/tests/dev-runtime/MarketplaceRevenueModel.test.mjs index 50161cb97..99287f02c 100644 --- a/dev/tests/dev-runtime/MarketplaceRevenueModel.test.mjs +++ b/dev/tests/dev-runtime/MarketplaceRevenueModel.test.mjs @@ -1,9 +1,9 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { calculateMarketplaceRevenue, readMarketplaceSellerRevenueModel } from "../../../src/dev-runtime/marketplace/marketplace-revenue-service.mjs"; -import { assignUserMembership } from "../../../src/dev-runtime/memberships/membership-assignment-service.mjs"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +import { calculateMarketplaceRevenue, readMarketplaceSellerRevenueModel } from "../../../api/marketplace/marketplace-revenue-service.mjs"; +import { assignUserMembership } from "../../../api/memberships/membership-assignment-service.mjs"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; function createKeyFactory(start = 1800) { let sequence = start; diff --git a/dev/tests/dev-runtime/MembershipAssignment.test.mjs b/dev/tests/dev-runtime/MembershipAssignment.test.mjs index 3f31ceca4..f7349be36 100644 --- a/dev/tests/dev-runtime/MembershipAssignment.test.mjs +++ b/dev/tests/dev-runtime/MembershipAssignment.test.mjs @@ -1,10 +1,10 @@ import http from "node:http"; import test from "node:test"; import assert from "node:assert/strict"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; -import { assignUserMembership, resolveActiveUserMembership, MembershipAssignmentError } from "../../../src/dev-runtime/memberships/membership-assignment-service.mjs"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; +import { assignUserMembership, resolveActiveUserMembership, MembershipAssignmentError } from "../../../api/memberships/membership-assignment-service.mjs"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; function createKeyFactory(start = 500) { let sequence = start; diff --git a/dev/tests/dev-runtime/MembershipDataModel.test.mjs b/dev/tests/dev-runtime/MembershipDataModel.test.mjs index 161b2647f..d7885087a 100644 --- a/dev/tests/dev-runtime/MembershipDataModel.test.mjs +++ b/dev/tests/dev-runtime/MembershipDataModel.test.mjs @@ -1,9 +1,9 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { SUPABASE_POSTGRES_PRODUCT_TABLES } from "../../../src/dev-runtime/auth/provider-contract-stubs.mjs"; -import { getMockDbTableSchemas } from "../../../src/dev-runtime/persistence/mock-db-store.js"; -import { SEED_DB_KEYS } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +import { SUPABASE_POSTGRES_PRODUCT_TABLES } from "../../../api/auth/provider-contract-stubs.mjs"; +import { getMockDbTableSchemas } from "../../../api/persistence/mock-db-store.js"; +import { SEED_DB_KEYS } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; const PLAN_FIELDS = [ "key", diff --git a/dev/tests/dev-runtime/MembershipsPageData.test.mjs b/dev/tests/dev-runtime/MembershipsPageData.test.mjs index c1a9b7f71..3ae24b900 100644 --- a/dev/tests/dev-runtime/MembershipsPageData.test.mjs +++ b/dev/tests/dev-runtime/MembershipsPageData.test.mjs @@ -1,10 +1,10 @@ import http from "node:http"; import test from "node:test"; import assert from "node:assert/strict"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; -import { assignUserMembership, readMembershipCatalog } from "../../../src/dev-runtime/memberships/membership-assignment-service.mjs"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; +import { assignUserMembership, readMembershipCatalog } from "../../../api/memberships/membership-assignment-service.mjs"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; function createKeyFactory(start = 1200) { let sequence = start; diff --git a/dev/tests/dev-runtime/MessagesPublishValidation.test.mjs b/dev/tests/dev-runtime/MessagesPublishValidation.test.mjs index b8c4b2c2a..1fa9ba5e5 100644 --- a/dev/tests/dev-runtime/MessagesPublishValidation.test.mjs +++ b/dev/tests/dev-runtime/MessagesPublishValidation.test.mjs @@ -4,7 +4,7 @@ import test from "node:test"; import { createMessagesPostgresService, handleMessagesApiContract, -} from "../../../src/dev-runtime/messages/messages-postgres-service.mjs"; +} from "../../../api/messages/messages-postgres-service.mjs"; import { createMessagesPostgresClientStub } from "../helpers/messagesPostgresClientStub.mjs"; function createServiceHarness() { diff --git a/dev/tests/dev-runtime/ObjectsApiService.test.mjs b/dev/tests/dev-runtime/ObjectsApiService.test.mjs index ccc835d21..fd97b09b3 100644 --- a/dev/tests/dev-runtime/ObjectsApiService.test.mjs +++ b/dev/tests/dev-runtime/ObjectsApiService.test.mjs @@ -4,8 +4,8 @@ import { OBJECTS_TOOL_TABLES, createObjectsApiService, gameWorkspaceGameKeyForApi, -} from "../../../src/dev-runtime/toolbox-api/alfa-tool-services.mjs"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; +} from "../../../api/toolbox-api/alfa-tool-services.mjs"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; function cloneRows(rows) { return rows.map((row) => ({ ...row })); diff --git a/dev/tests/dev-runtime/OwnerAiCredits.test.mjs b/dev/tests/dev-runtime/OwnerAiCredits.test.mjs index 589514dbe..a41cd9b1c 100644 --- a/dev/tests/dev-runtime/OwnerAiCredits.test.mjs +++ b/dev/tests/dev-runtime/OwnerAiCredits.test.mjs @@ -1,21 +1,21 @@ import http from "node:http"; import test from "node:test"; import assert from "node:assert/strict"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; import { debitAiCreditsForAction, grantMonthlyAiCredits, purchaseAiCreditPack, readAiCreditDisplay, -} from "../../../src/dev-runtime/ai/ai-credit-service.mjs"; -import { assignUserMembership } from "../../../src/dev-runtime/memberships/membership-assignment-service.mjs"; +} from "../../../api/ai/ai-credit-service.mjs"; +import { assignUserMembership } from "../../../api/memberships/membership-assignment-service.mjs"; import { OwnerAiCreditSettingsError, readOwnerAiCreditSettings, updateOwnerAiCreditSettings, -} from "../../../src/dev-runtime/ai/owner-ai-credit-settings-service.mjs"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +} from "../../../api/ai/owner-ai-credit-settings-service.mjs"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; const OWNER_SESSION = Object.freeze({ isOwner: true, diff --git a/dev/tests/dev-runtime/OwnerMemberships.test.mjs b/dev/tests/dev-runtime/OwnerMemberships.test.mjs index cfb4f62c1..cb7fc8e06 100644 --- a/dev/tests/dev-runtime/OwnerMemberships.test.mjs +++ b/dev/tests/dev-runtime/OwnerMemberships.test.mjs @@ -1,21 +1,21 @@ import http from "node:http"; import test from "node:test"; import assert from "node:assert/strict"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; -import { assignUserMembership } from "../../../src/dev-runtime/memberships/membership-assignment-service.mjs"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; +import { assignUserMembership } from "../../../api/memberships/membership-assignment-service.mjs"; import { OwnerMembershipSettingsError, readOwnerMembershipSettings, updateOwnerMembershipSettings, -} from "../../../src/dev-runtime/memberships/owner-membership-settings-service.mjs"; -import { readMembershipCatalog } from "../../../src/dev-runtime/memberships/membership-assignment-service.mjs"; -import { readMarketplaceSellerRevenueModel } from "../../../src/dev-runtime/marketplace/marketplace-revenue-service.mjs"; +} from "../../../api/memberships/owner-membership-settings-service.mjs"; +import { readMembershipCatalog } from "../../../api/memberships/membership-assignment-service.mjs"; +import { readMarketplaceSellerRevenueModel } from "../../../api/marketplace/marketplace-revenue-service.mjs"; import { ensureProjectOwnerMember, readProjectTeamState, -} from "../../../src/dev-runtime/teams/project-team-service.mjs"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +} from "../../../api/teams/project-team-service.mjs"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; const OWNER_SESSION = Object.freeze({ isOwner: true, diff --git a/dev/tests/dev-runtime/PostgresBackupService.test.mjs b/dev/tests/dev-runtime/PostgresBackupService.test.mjs index 6a35ff443..ac198e392 100644 --- a/dev/tests/dev-runtime/PostgresBackupService.test.mjs +++ b/dev/tests/dev-runtime/PostgresBackupService.test.mjs @@ -11,11 +11,11 @@ import { createPostgresBackup, postgresBackupFilename, postgresBackupObjectKey, -} from "../../../src/dev-runtime/database/postgres-backup-service.mjs"; +} from "../../../api/database/postgres-backup-service.mjs"; import { DB_BACKUP_PREFIX_ENV, DB_BACKUP_STORAGE_PROVIDER_ENV, -} from "../../../src/dev-runtime/storage/storage-config.mjs"; +} from "../../../api/storage/storage-config.mjs"; const TEST_ENV = Object.freeze({ GAMEFOUNDRY_DATABASE_SSL: "disable", diff --git a/dev/tests/dev-runtime/ProductDataDatabaseUrl.test.mjs b/dev/tests/dev-runtime/ProductDataDatabaseUrl.test.mjs index 45a292954..7e61ad5b8 100644 --- a/dev/tests/dev-runtime/ProductDataDatabaseUrl.test.mjs +++ b/dev/tests/dev-runtime/ProductDataDatabaseUrl.test.mjs @@ -1,6 +1,6 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { SupabasePostgresProviderAdapter } from "../../../src/dev-runtime/auth/provider-contract-stubs.mjs"; +import { SupabasePostgresProviderAdapter } from "../../../api/auth/provider-contract-stubs.mjs"; test("platform_settings product data uses the configured database connection client", async () => { const calls = []; diff --git a/dev/tests/dev-runtime/PublicEnvironmentConfig.test.mjs b/dev/tests/dev-runtime/PublicEnvironmentConfig.test.mjs index 3fa0ac29c..e8494cae8 100644 --- a/dev/tests/dev-runtime/PublicEnvironmentConfig.test.mjs +++ b/dev/tests/dev-runtime/PublicEnvironmentConfig.test.mjs @@ -1,7 +1,7 @@ import http from "node:http"; import test from "node:test"; import assert from "node:assert/strict"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; const PUBLIC_CONFIG_ENV_KEYS = Object.freeze([ "GAMEFOUNDRY_API_URL", diff --git a/dev/tests/dev-runtime/StaticWebRootCompatibility.test.mjs b/dev/tests/dev-runtime/StaticWebRootCompatibility.test.mjs index 8e2629315..748082fb6 100644 --- a/dev/tests/dev-runtime/StaticWebRootCompatibility.test.mjs +++ b/dev/tests/dev-runtime/StaticWebRootCompatibility.test.mjs @@ -12,7 +12,7 @@ import { resolveBrowserRoutePath, resolveLocalWebRoot, resolveStaticRouteTarget, -} from "../../../src/dev-runtime/server/static-web-root.mjs"; +} from "../../../api/server/static-web-root.mjs"; const repoRoot = path.resolve(fileURLToPath(new URL("../../..", import.meta.url))); diff --git a/dev/tests/dev-runtime/StorageConfig.test.mjs b/dev/tests/dev-runtime/StorageConfig.test.mjs index 288e45f16..b17488956 100644 --- a/dev/tests/dev-runtime/StorageConfig.test.mjs +++ b/dev/tests/dev-runtime/StorageConfig.test.mjs @@ -4,7 +4,7 @@ import { STORAGE_PROJECTS_ALLOWED_PREFIXES, loadStorageConfig, normalizeStorageProjectsPrefix, -} from "../../../src/dev-runtime/storage/storage-config.mjs"; +} from "../../../api/storage/storage-config.mjs"; function validStorageEnv(projectsPrefix = "/dev/projects/") { return { diff --git a/dev/tests/dev-runtime/SupabaseDevAuthTestUserCleanup.test.mjs b/dev/tests/dev-runtime/SupabaseDevAuthTestUserCleanup.test.mjs index b5eb3a442..a7a2b6e78 100644 --- a/dev/tests/dev-runtime/SupabaseDevAuthTestUserCleanup.test.mjs +++ b/dev/tests/dev-runtime/SupabaseDevAuthTestUserCleanup.test.mjs @@ -3,13 +3,13 @@ import { test } from "node:test"; import { SupabaseAuthProviderAdapter, SupabasePostgresProviderAdapter, -} from "../../../src/dev-runtime/auth/provider-contract-stubs.mjs"; +} from "../../../api/auth/provider-contract-stubs.mjs"; import { assertDevOnlyAuthTestCleanupEnvironment, cleanupSupabaseDevAuthTestUsers, isSupabaseDevAuthTestUser, -} from "../../../src/dev-runtime/testing/supabase-dev-auth-test-user-cleanup.mjs"; -import { MOCK_DB_KEYS } from "../../../src/dev-runtime/persistence/mock-db-store.js"; +} from "../../../api/testing/supabase-dev-auth-test-user-cleanup.mjs"; +import { MOCK_DB_KEYS } from "../../../api/persistence/mock-db-store.js"; const cleanupEnv = Object.freeze({ GAMEFOUNDRY_ENV: "dev", diff --git a/dev/tests/dev-runtime/SupabaseDevCreatorIdentitySeedSync.test.mjs b/dev/tests/dev-runtime/SupabaseDevCreatorIdentitySeedSync.test.mjs index c3176b89c..59e8f50fe 100644 --- a/dev/tests/dev-runtime/SupabaseDevCreatorIdentitySeedSync.test.mjs +++ b/dev/tests/dev-runtime/SupabaseDevCreatorIdentitySeedSync.test.mjs @@ -1,14 +1,14 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { SEED_DB_KEYS } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../api/seed/seed-db-keys.mjs"; import { SupabaseAuthProviderAdapter, SupabasePostgresProviderAdapter, -} from "../../../src/dev-runtime/auth/provider-contract-stubs.mjs"; +} from "../../../api/auth/provider-contract-stubs.mjs"; import { DEV_CREATOR_IDENTITIES, syncSupabaseDevCreatorIdentities, -} from "../../../src/dev-runtime/testing/supabase-dev-creator-identity-seed-sync.mjs"; +} from "../../../api/testing/supabase-dev-creator-identity-seed-sync.mjs"; const syncEnv = Object.freeze({ GAMEFOUNDRY_DATABASE_SSL: "disable", diff --git a/dev/tests/dev-runtime/SupabaseProductDataCutover.test.mjs b/dev/tests/dev-runtime/SupabaseProductDataCutover.test.mjs index b55345562..07f14bdb1 100644 --- a/dev/tests/dev-runtime/SupabaseProductDataCutover.test.mjs +++ b/dev/tests/dev-runtime/SupabaseProductDataCutover.test.mjs @@ -1,8 +1,8 @@ import http from "node:http"; import test from "node:test"; import assert from "node:assert/strict"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; -import { SEED_DB_KEYS } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; +import { SEED_DB_KEYS } from "../../../api/seed/seed-db-keys.mjs"; function withEnv(nextEnv, callback) { const previousEnv = {}; diff --git a/dev/tests/dev-runtime/SupabaseProviderContractStub.test.mjs b/dev/tests/dev-runtime/SupabaseProviderContractStub.test.mjs index b86a95014..a9c07e27e 100644 --- a/dev/tests/dev-runtime/SupabaseProviderContractStub.test.mjs +++ b/dev/tests/dev-runtime/SupabaseProviderContractStub.test.mjs @@ -8,12 +8,12 @@ import { SupabasePostgresProviderAdapter, SupabasePostgresProviderStub, createProviderContractSnapshot, -} from "../../../src/dev-runtime/auth/provider-contract-stubs.mjs"; -import { SEED_DB_KEYS } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; +} from "../../../api/auth/provider-contract-stubs.mjs"; +import { SEED_DB_KEYS } from "../../../api/seed/seed-db-keys.mjs"; import { createLocalApiRouter, sessionUserFromIdentityTables, -} from "../../../src/dev-runtime/server/local-api-router.mjs"; +} from "../../../api/server/local-api-router.mjs"; function withEnv(nextEnv, callback) { const previousEnv = {}; diff --git a/dev/tests/dev-runtime/TagsApiErrorResponse.test.mjs b/dev/tests/dev-runtime/TagsApiErrorResponse.test.mjs index d37646a5f..84f77e9d3 100644 --- a/dev/tests/dev-runtime/TagsApiErrorResponse.test.mjs +++ b/dev/tests/dev-runtime/TagsApiErrorResponse.test.mjs @@ -1,9 +1,9 @@ import assert from "node:assert/strict"; import http from "node:http"; import test from "node:test"; -import { SupabasePostgresProviderAdapter } from "../../../src/dev-runtime/auth/provider-contract-stubs.mjs"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; -import { SEED_DB_KEYS } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SupabasePostgresProviderAdapter } from "../../../api/auth/provider-contract-stubs.mjs"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; +import { SEED_DB_KEYS } from "../../../api/seed/seed-db-keys.mjs"; const RAW_TAGS_SCHEMA_ERROR = "Configured database project_tags request failed: relation \"project_tags\" does not exist at postgres://tags_owner:secret-password@db.internal.example:5432/gamefoundry"; diff --git a/dev/tests/dev-runtime/TagsApiService.test.mjs b/dev/tests/dev-runtime/TagsApiService.test.mjs index 8a9a3f6ef..0d63b5a1b 100644 --- a/dev/tests/dev-runtime/TagsApiService.test.mjs +++ b/dev/tests/dev-runtime/TagsApiService.test.mjs @@ -3,8 +3,8 @@ import test from "node:test"; import { createTagsApiService, gameWorkspaceGameKeyForApi, -} from "../../../src/dev-runtime/toolbox-api/alfa-tool-services.mjs"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; +} from "../../../api/toolbox-api/alfa-tool-services.mjs"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; function cloneRows(rows) { return rows.map((row) => ({ ...row })); diff --git a/dev/tests/dev-runtime/TeamEnforcement.test.mjs b/dev/tests/dev-runtime/TeamEnforcement.test.mjs index 6577a119b..99a936bb1 100644 --- a/dev/tests/dev-runtime/TeamEnforcement.test.mjs +++ b/dev/tests/dev-runtime/TeamEnforcement.test.mjs @@ -1,6 +1,6 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { assignUserMembership } from "../../../src/dev-runtime/memberships/membership-assignment-service.mjs"; +import { assignUserMembership } from "../../../api/memberships/membership-assignment-service.mjs"; import { addProjectMember, assertProjectMemberAccess, @@ -10,9 +10,9 @@ import { joinProjectMember, readProjectTeamState, removeProjectMember, -} from "../../../src/dev-runtime/teams/project-team-service.mjs"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +} from "../../../api/teams/project-team-service.mjs"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; function createKeyFactory(start = 3200) { let sequence = start; diff --git a/dev/tests/dev-runtime/TeamsFoundation.test.mjs b/dev/tests/dev-runtime/TeamsFoundation.test.mjs index 1c7ba3bff..6a9a00ca2 100644 --- a/dev/tests/dev-runtime/TeamsFoundation.test.mjs +++ b/dev/tests/dev-runtime/TeamsFoundation.test.mjs @@ -1,16 +1,16 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { SUPABASE_POSTGRES_PRODUCT_TABLES } from "../../../src/dev-runtime/auth/provider-contract-stubs.mjs"; -import { assignUserMembership } from "../../../src/dev-runtime/memberships/membership-assignment-service.mjs"; -import { getMockDbTableSchemas } from "../../../src/dev-runtime/persistence/mock-db-store.js"; +import { SUPABASE_POSTGRES_PRODUCT_TABLES } from "../../../api/auth/provider-contract-stubs.mjs"; +import { assignUserMembership } from "../../../api/memberships/membership-assignment-service.mjs"; +import { getMockDbTableSchemas } from "../../../api/persistence/mock-db-store.js"; import { addProjectMember, ensureProjectOwnerMember, readProjectTeamState, removeProjectMember, -} from "../../../src/dev-runtime/teams/project-team-service.mjs"; -import { SEED_DB_KEYS, makeSeedUlid } from "../../../src/dev-runtime/seed/seed-db-keys.mjs"; -import { createServerSeedTables } from "../../../src/dev-runtime/seed/server-seed-loader.mjs"; +} from "../../../api/teams/project-team-service.mjs"; +import { SEED_DB_KEYS, makeSeedUlid } from "../../../api/seed/seed-db-keys.mjs"; +import { createServerSeedTables } from "../../../api/seed/server-seed-loader.mjs"; function createKeyFactory(start = 2200) { let sequence = start; diff --git a/dev/tests/helpers/playwrightRepoServer.mjs b/dev/tests/helpers/playwrightRepoServer.mjs index 1b2e9f070..d9d8cf9b9 100644 --- a/dev/tests/helpers/playwrightRepoServer.mjs +++ b/dev/tests/helpers/playwrightRepoServer.mjs @@ -2,10 +2,10 @@ import fs from "node:fs/promises"; import path from "node:path"; import http from "node:http"; import { fileURLToPath } from "node:url"; -import { handleAdminNotesDirectoryRequest } from "../../../src/dev-runtime/admin/admin-notes-directory.mjs"; -import { localAdminNotesHeaderPartialPath } from "../../../src/dev-runtime/admin/admin-notes-menu.mjs"; -import { createLocalApiRouter } from "../../../src/dev-runtime/server/local-api-router.mjs"; -import { resolveStaticRouteTarget } from "../../../src/dev-runtime/server/static-web-root.mjs"; +import { handleAdminNotesDirectoryRequest } from "../../../api/admin/admin-notes-directory.mjs"; +import { localAdminNotesHeaderPartialPath } from "../../../api/admin/admin-notes-menu.mjs"; +import { createLocalApiRouter } from "../../../api/server/local-api-router.mjs"; +import { resolveStaticRouteTarget } from "../../../api/server/static-web-root.mjs"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/dev/tests/playwright/account/AchievementsPage.spec.mjs b/dev/tests/playwright/account/AchievementsPage.spec.mjs index 15844a9a2..b016d2f7d 100644 --- a/dev/tests/playwright/account/AchievementsPage.spec.mjs +++ b/dev/tests/playwright/account/AchievementsPage.spec.mjs @@ -1,7 +1,7 @@ import { expect, test } from "@playwright/test"; import fs from "node:fs/promises"; import path from "node:path"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/account/AiCreditsPage.spec.mjs b/dev/tests/playwright/account/AiCreditsPage.spec.mjs index 3eff7ea35..70a453adf 100644 --- a/dev/tests/playwright/account/AiCreditsPage.spec.mjs +++ b/dev/tests/playwright/account/AiCreditsPage.spec.mjs @@ -1,7 +1,7 @@ import { expect, test } from "@playwright/test"; import fs from "node:fs/promises"; import path from "node:path"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/AdminDbViewer.spec.mjs b/dev/tests/playwright/tools/AdminDbViewer.spec.mjs index 3a91c393d..a7eb62c53 100644 --- a/dev/tests/playwright/tools/AdminDbViewer.spec.mjs +++ b/dev/tests/playwright/tools/AdminDbViewer.spec.mjs @@ -3,15 +3,15 @@ import http from "node:http"; import path from "node:path"; import process from "node:process"; import { expect, test } from "@playwright/test"; -import { createAssetToolMockRepository } from "../../../../src/dev-runtime/persistence/tool-repositories/assets-mock-repository.js"; -import { createGameWorkspacePaletteRepository } from "../../../../src/dev-runtime/persistence/tool-repositories/palette-workspace-repository.js"; +import { createAssetToolMockRepository } from "../../../../api/persistence/tool-repositories/assets-mock-repository.js"; +import { createGameWorkspacePaletteRepository } from "../../../../api/persistence/tool-repositories/palette-workspace-repository.js"; import { MOCK_DB_KEYS, getMockDbTableSchemas, getStandaloneMockDbSeedTables, normalizeMockDbTables, -} from "../../../../src/dev-runtime/persistence/mock-db-store.js"; -import { getActiveToolRegistry } from "../../../../src/dev-runtime/guest-seeds/tool-metadata-inventory.js"; +} from "../../../../api/persistence/mock-db-store.js"; +import { getActiveToolRegistry } from "../../../../api/guest-seeds/tool-metadata-inventory.js"; import { isBrowserExtensionNoise } from "../../helpers/browserExtensionNoise.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; diff --git a/dev/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/dev/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs index 91df81aa9..d75e47cb0 100644 --- a/dev/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +++ b/dev/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs @@ -1,7 +1,7 @@ import { expect, test } from "@playwright/test"; import fs from "node:fs/promises"; import path from "node:path"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/AdminInvitationsNavPage.spec.mjs b/dev/tests/playwright/tools/AdminInvitationsNavPage.spec.mjs index 4cf02896d..782a813a2 100644 --- a/dev/tests/playwright/tools/AdminInvitationsNavPage.spec.mjs +++ b/dev/tests/playwright/tools/AdminInvitationsNavPage.spec.mjs @@ -1,7 +1,7 @@ import { expect, test } from "@playwright/test"; import fs from "node:fs/promises"; import path from "node:path"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/AdminNotesLocalViewer.spec.mjs b/dev/tests/playwright/tools/AdminNotesLocalViewer.spec.mjs index 2da1172f9..363be32b3 100644 --- a/dev/tests/playwright/tools/AdminNotesLocalViewer.spec.mjs +++ b/dev/tests/playwright/tools/AdminNotesLocalViewer.spec.mjs @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; import { isBrowserExtensionNoise } from "../../helpers/browserExtensionNoise.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; diff --git a/dev/tests/playwright/tools/AdminOwnerNavigationBoundary.spec.mjs b/dev/tests/playwright/tools/AdminOwnerNavigationBoundary.spec.mjs index baa0cc606..1230b6095 100644 --- a/dev/tests/playwright/tools/AdminOwnerNavigationBoundary.spec.mjs +++ b/dev/tests/playwright/tools/AdminOwnerNavigationBoundary.spec.mjs @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs b/dev/tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs index 13c2645b4..b5d3eb7a8 100644 --- a/dev/tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs +++ b/dev/tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; import { isBrowserExtensionNoise } from "../../helpers/browserExtensionNoise.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; diff --git a/dev/tests/playwright/tools/AssetToolMockRepository.spec.mjs b/dev/tests/playwright/tools/AssetToolMockRepository.spec.mjs index fcfbeb212..3270e6b7e 100644 --- a/dev/tests/playwright/tools/AssetToolMockRepository.spec.mjs +++ b/dev/tests/playwright/tools/AssetToolMockRepository.spec.mjs @@ -9,8 +9,8 @@ import { ASSET_TOOL_TABLES, ASSET_USAGE_OPTIONS, createAssetToolMockRepository -} from "../../../../src/dev-runtime/persistence/tool-repositories/assets-mock-repository.js"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +} from "../../../../api/persistence/tool-repositories/assets-mock-repository.js"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/BrowserApiUrlConfig.spec.mjs b/dev/tests/playwright/tools/BrowserApiUrlConfig.spec.mjs index 53f2e6182..0b54b3103 100644 --- a/dev/tests/playwright/tools/BrowserApiUrlConfig.spec.mjs +++ b/dev/tests/playwright/tools/BrowserApiUrlConfig.spec.mjs @@ -3,8 +3,9 @@ import http from "node:http"; import path from "node:path"; import process from "node:process"; import { expect, test } from "@playwright/test"; -import { startLocalApiServer } from "../../../../src/dev-runtime/server/local-api-server.mjs"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { startLocalApiServer } from "../../../../api/server/local-api-server.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; +import { resolveStaticRouteTarget } from "../../../../api/server/static-web-root.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; const LIVE_SERVER_ORIGIN = "http://127.0.0.1:5500"; @@ -21,7 +22,7 @@ let localDbRunId = 0; function nextLocalDbStoragePath() { localDbRunId += 1; - return path.join(process.cwd(), "tmp", "local-db", `browser-api-url-config-${process.pid}-${localDbRunId}.local-db-state`); + return path.join(process.cwd(), "dev", "workspace", "tmp", "local-db", `browser-api-url-config-${process.pid}-${localDbRunId}.local-db-state`); } function contentTypeForPath(filePath) { @@ -37,11 +38,6 @@ function contentTypeForPath(filePath) { return "application/octet-stream"; } -function isInsideRepoRoot(repoRoot, absolutePath) { - const relativePath = path.relative(repoRoot, absolutePath); - return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)); -} - async function withEnv(nextEnv, callback) { const previousEnv = {}; Object.keys(nextEnv).forEach((key) => { @@ -72,19 +68,8 @@ async function startStaticLiveServer() { return; } const decodedPath = decodeURIComponent(requestUrl.pathname); - const normalizedPath = path.normalize(decodedPath).replace(/^(\.\.[/\\])+/, ""); - const absolutePath = path.resolve(repoRoot, `.${normalizedPath}`); - if (!isInsideRepoRoot(repoRoot, absolutePath)) { - response.statusCode = 403; - response.end("Forbidden"); - return; - } - let targetPath = absolutePath; - const stat = await fs.stat(targetPath).catch(() => null); - if (stat?.isDirectory()) { - targetPath = path.join(targetPath, "index.html"); - } - const contents = await fs.readFile(targetPath).catch(() => null); + const { targetPath } = await resolveStaticRouteTarget({ decodedPath, repoRoot }); + const contents = targetPath ? await fs.readFile(targetPath).catch(() => null) : null; if (!contents) { response.statusCode = 404; response.end("Not Found"); diff --git a/dev/tests/playwright/tools/BuildPathProgressSimplification.spec.mjs b/dev/tests/playwright/tools/BuildPathProgressSimplification.spec.mjs index 36dbc318b..2cb114895 100644 --- a/dev/tests/playwright/tools/BuildPathProgressSimplification.spec.mjs +++ b/dev/tests/playwright/tools/BuildPathProgressSimplification.spec.mjs @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/EnvironmentBannerCoverage.spec.mjs b/dev/tests/playwright/tools/EnvironmentBannerCoverage.spec.mjs index 08ef9a39c..157a7a132 100644 --- a/dev/tests/playwright/tools/EnvironmentBannerCoverage.spec.mjs +++ b/dev/tests/playwright/tools/EnvironmentBannerCoverage.spec.mjs @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/EventsTool.spec.mjs b/dev/tests/playwright/tools/EventsTool.spec.mjs index 39240ef9e..3164129c7 100644 --- a/dev/tests/playwright/tools/EventsTool.spec.mjs +++ b/dev/tests/playwright/tools/EventsTool.spec.mjs @@ -2,7 +2,7 @@ import { expect, test } from "@playwright/test"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { createMessagesPostgresClientStub } from "../../helpers/messagesPostgresClientStub.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; async function jsonRequest(url, options = {}) { const response = await fetch(url, { diff --git a/dev/tests/playwright/tools/GameConfigurationApiBehavior.spec.mjs b/dev/tests/playwright/tools/GameConfigurationApiBehavior.spec.mjs index 16efcd579..58513aa23 100644 --- a/dev/tests/playwright/tools/GameConfigurationApiBehavior.spec.mjs +++ b/dev/tests/playwright/tools/GameConfigurationApiBehavior.spec.mjs @@ -2,8 +2,8 @@ import { expect, test } from "@playwright/test"; import { readFile } from "node:fs/promises"; import path from "node:path"; import process from "node:process"; -import { createPostgresConnectionClient } from "../../../../src/dev-runtime/persistence/postgres-connection-client.mjs"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { createPostgresConnectionClient } from "../../../../api/persistence/postgres-connection-client.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/GameCrewFoundation.spec.mjs b/dev/tests/playwright/tools/GameCrewFoundation.spec.mjs index 95754245f..7ebeffe61 100644 --- a/dev/tests/playwright/tools/GameCrewFoundation.spec.mjs +++ b/dev/tests/playwright/tools/GameCrewFoundation.spec.mjs @@ -1,8 +1,8 @@ import { expect, test } from "@playwright/test"; import { readFile } from "node:fs/promises"; import path from "node:path"; -import { createPostgresConnectionClient } from "../../../../src/dev-runtime/persistence/postgres-connection-client.mjs"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { createPostgresConnectionClient } from "../../../../api/persistence/postgres-connection-client.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/GameDesignApiBehavior.spec.mjs b/dev/tests/playwright/tools/GameDesignApiBehavior.spec.mjs index d8569c2e1..ff743086f 100644 --- a/dev/tests/playwright/tools/GameDesignApiBehavior.spec.mjs +++ b/dev/tests/playwright/tools/GameDesignApiBehavior.spec.mjs @@ -2,8 +2,8 @@ import { expect, test } from "@playwright/test"; import { readFile } from "node:fs/promises"; import path from "node:path"; import process from "node:process"; -import { createPostgresConnectionClient } from "../../../../src/dev-runtime/persistence/postgres-connection-client.mjs"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { createPostgresConnectionClient } from "../../../../api/persistence/postgres-connection-client.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/GameHubMockRepository.spec.mjs b/dev/tests/playwright/tools/GameHubMockRepository.spec.mjs index e45d8ceab..a392cee98 100644 --- a/dev/tests/playwright/tools/GameHubMockRepository.spec.mjs +++ b/dev/tests/playwright/tools/GameHubMockRepository.spec.mjs @@ -1,7 +1,7 @@ import { expect, test } from "@playwright/test"; import http from "node:http"; import process from "node:process"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/GameJourneyTool.spec.mjs b/dev/tests/playwright/tools/GameJourneyTool.spec.mjs index cafea7135..f9708c173 100644 --- a/dev/tests/playwright/tools/GameJourneyTool.spec.mjs +++ b/dev/tests/playwright/tools/GameJourneyTool.spec.mjs @@ -13,9 +13,9 @@ import { GAME_JOURNEY_STATUSES, GAME_JOURNEY_TOOL_OWNERSHIP_AREAS, createGameJourneyMockRepository, -} from "../../../../src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js"; -import { createGameJourneyCompletionMetricsStore } from "../../../../src/dev-runtime/persistence/game-journey-completion-metrics-store.mjs"; -import { MOCK_DB_KEYS, getStandaloneMockDbSeedTables } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +} from "../../../../api/persistence/tool-repositories/game-journey-mock-repository.js"; +import { createGameJourneyCompletionMetricsStore } from "../../../../api/persistence/game-journey-completion-metrics-store.mjs"; +import { MOCK_DB_KEYS, getStandaloneMockDbSeedTables } from "../../../../api/persistence/mock-db-store.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(__dirname, "../../../.."); @@ -1689,7 +1689,7 @@ test("Game Journey source stays separate from notes files and browser persistenc const sourcePaths = [ "toolbox/game-journey/index.html", "assets/toolbox/game-journey/js/index.js", - "src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js" + "api/persistence/tool-repositories/game-journey-mock-repository.js" ]; const banned = [ "admin" + "-notes", diff --git a/dev/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs b/dev/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs index dc72434e0..ae389417a 100644 --- a/dev/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +++ b/dev/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs @@ -1,6 +1,6 @@ import { expect, test } from "@playwright/test"; -import { GAME_JOURNEY_BOOTSTRAP_BUCKETS } from "../../../../src/dev-runtime/persistence/tool-repositories/game-journey-mock-repository.js"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +import { GAME_JOURNEY_BOOTSTRAP_BUCKETS } from "../../../../api/persistence/tool-repositories/game-journey-mock-repository.js"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; import { isBrowserExtensionNoise } from "../../helpers/browserExtensionNoise.mjs"; import { createGameJourneyCompletionMetricsPostgresClientStub } from "../../helpers/gameJourneyCompletionMetricsPostgresClientStub.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; diff --git a/dev/tests/playwright/tools/InputMappingV2Tool.spec.mjs b/dev/tests/playwright/tools/InputMappingV2Tool.spec.mjs index b0f6c07e1..514084095 100644 --- a/dev/tests/playwright/tools/InputMappingV2Tool.spec.mjs +++ b/dev/tests/playwright/tools/InputMappingV2Tool.spec.mjs @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/LoginSessionMode.spec.mjs b/dev/tests/playwright/tools/LoginSessionMode.spec.mjs index 0408c1443..805acd0b7 100644 --- a/dev/tests/playwright/tools/LoginSessionMode.spec.mjs +++ b/dev/tests/playwright/tools/LoginSessionMode.spec.mjs @@ -3,8 +3,8 @@ import http from "node:http"; import path from "node:path"; import process from "node:process"; import { expect, test } from "@playwright/test"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; -import { startLocalApiServer } from "../../../../src/dev-runtime/server/local-api-server.mjs"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; +import { startLocalApiServer } from "../../../../api/server/local-api-server.mjs"; import { isBrowserExtensionNoise } from "../../helpers/browserExtensionNoise.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; diff --git a/dev/tests/playwright/tools/MessagesTool.spec.mjs b/dev/tests/playwright/tools/MessagesTool.spec.mjs index 3484467a8..f4a4e92eb 100644 --- a/dev/tests/playwright/tools/MessagesTool.spec.mjs +++ b/dev/tests/playwright/tools/MessagesTool.spec.mjs @@ -3,7 +3,7 @@ import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { createMessagesPostgresClientStub } from "../../helpers/messagesPostgresClientStub.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; const ULID_PATTERN = /^[0-9A-HJKMNP-TV-Z]{26}$/; const TEST_TTS_PROFILE_NAME = "Message Test Profile"; diff --git a/dev/tests/playwright/tools/ObjectsTool.spec.mjs b/dev/tests/playwright/tools/ObjectsTool.spec.mjs index 74e132e67..b1b2497e8 100644 --- a/dev/tests/playwright/tools/ObjectsTool.spec.mjs +++ b/dev/tests/playwright/tools/ObjectsTool.spec.mjs @@ -2,7 +2,7 @@ import { expect, test } from "@playwright/test"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; const TYPE_OPTIONS = ["Collectible", "Custom", "Decoration", "Enemy", "Goal", "Hazard", "Hero", "Platform", "Projectile", "Spawn Point", "Wall"]; const OLD_SAMPLE_PATH_PATTERN = new RegExp(["M" + "VP", "Padd" + "le", "Ba" + "ll"].join("|"), "i"); diff --git a/dev/tests/playwright/tools/OwnerAiCreditsPage.spec.mjs b/dev/tests/playwright/tools/OwnerAiCreditsPage.spec.mjs index 5cc294404..8d583b350 100644 --- a/dev/tests/playwright/tools/OwnerAiCreditsPage.spec.mjs +++ b/dev/tests/playwright/tools/OwnerAiCreditsPage.spec.mjs @@ -1,7 +1,7 @@ import { expect, test } from "@playwright/test"; import fs from "node:fs/promises"; import path from "node:path"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/OwnerMembershipsPage.spec.mjs b/dev/tests/playwright/tools/OwnerMembershipsPage.spec.mjs index 042c09397..316458508 100644 --- a/dev/tests/playwright/tools/OwnerMembershipsPage.spec.mjs +++ b/dev/tests/playwright/tools/OwnerMembershipsPage.spec.mjs @@ -1,7 +1,7 @@ import { expect, test } from "@playwright/test"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; async function setSessionUser(server, userKey) { await fetch(`${server.baseUrl}/api/session/user`, { diff --git a/dev/tests/playwright/tools/PaletteToolMockRepository.spec.mjs b/dev/tests/playwright/tools/PaletteToolMockRepository.spec.mjs index f841bbabe..39d280684 100644 --- a/dev/tests/playwright/tools/PaletteToolMockRepository.spec.mjs +++ b/dev/tests/playwright/tools/PaletteToolMockRepository.spec.mjs @@ -1,11 +1,11 @@ import { expect, test } from "@playwright/test"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; import { PALETTE_WORKSPACE_PATH, PALETTE_TOOL_TABLES, createGameWorkspacePaletteRepository, validatePaletteWorkspacePayload -} from "../../../../src/dev-runtime/persistence/tool-repositories/palette-workspace-repository.js"; +} from "../../../../api/persistence/tool-repositories/palette-workspace-repository.js"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { expectCompactToolFormControls } from "../../helpers/toolFormControlAssertions.mjs"; diff --git a/dev/tests/playwright/tools/PublicMarketplacePage.spec.mjs b/dev/tests/playwright/tools/PublicMarketplacePage.spec.mjs index fe725ef9c..cfaf7e5a6 100644 --- a/dev/tests/playwright/tools/PublicMarketplacePage.spec.mjs +++ b/dev/tests/playwright/tools/PublicMarketplacePage.spec.mjs @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/PublicMembershipsPage.spec.mjs b/dev/tests/playwright/tools/PublicMembershipsPage.spec.mjs index b59afea0e..807559a3c 100644 --- a/dev/tests/playwright/tools/PublicMembershipsPage.spec.mjs +++ b/dev/tests/playwright/tools/PublicMembershipsPage.spec.mjs @@ -1,6 +1,6 @@ import { expect, test } from "@playwright/test"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; async function openMembershipsPage(page, options = {}) { if (options.viewport) { diff --git a/dev/tests/playwright/tools/RootToolsFutureState.spec.mjs b/dev/tests/playwright/tools/RootToolsFutureState.spec.mjs index 8fa08d4cc..8ccbaab95 100644 --- a/dev/tests/playwright/tools/RootToolsFutureState.spec.mjs +++ b/dev/tests/playwright/tools/RootToolsFutureState.spec.mjs @@ -1,6 +1,6 @@ import http from "node:http"; import { expect, test } from "@playwright/test"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/TagsTool.spec.mjs b/dev/tests/playwright/tools/TagsTool.spec.mjs index f9ce4f841..778e03b66 100644 --- a/dev/tests/playwright/tools/TagsTool.spec.mjs +++ b/dev/tests/playwright/tools/TagsTool.spec.mjs @@ -1,8 +1,8 @@ import { readFile } from "node:fs/promises"; import path from "node:path"; import { expect, test } from "@playwright/test"; -import { createPostgresConnectionClient } from "../../../../src/dev-runtime/persistence/postgres-connection-client.mjs"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { createPostgresConnectionClient } from "../../../../api/persistence/postgres-connection-client.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/TextToSpeechFunctional.spec.mjs b/dev/tests/playwright/tools/TextToSpeechFunctional.spec.mjs index 9395aac29..0df7550b4 100644 --- a/dev/tests/playwright/tools/TextToSpeechFunctional.spec.mjs +++ b/dev/tests/playwright/tools/TextToSpeechFunctional.spec.mjs @@ -2,7 +2,7 @@ import { expect, test } from "@playwright/test"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { createMessagesPostgresClientStub } from "../../helpers/messagesPostgresClientStub.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; -import { SEED_DB_KEYS } from "../../../../src/dev-runtime/seed/seed-db-keys.mjs"; +import { SEED_DB_KEYS } from "../../../../api/seed/seed-db-keys.mjs"; test.use({ trace: "off" }); diff --git a/dev/tests/playwright/tools/ToolboxAdminMetadataSsot.spec.mjs b/dev/tests/playwright/tools/ToolboxAdminMetadataSsot.spec.mjs index 4f66a0b3b..6add8239a 100644 --- a/dev/tests/playwright/tools/ToolboxAdminMetadataSsot.spec.mjs +++ b/dev/tests/playwright/tools/ToolboxAdminMetadataSsot.spec.mjs @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/ToolboxRoutePages.spec.mjs b/dev/tests/playwright/tools/ToolboxRoutePages.spec.mjs index 3bf9d1c0b..648cfa758 100644 --- a/dev/tests/playwright/tools/ToolboxRoutePages.spec.mjs +++ b/dev/tests/playwright/tools/ToolboxRoutePages.spec.mjs @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; import { isBrowserExtensionNoise } from "../../helpers/browserExtensionNoise.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/playwright/tools/ToolboxSelectedGameStatusBar.spec.mjs b/dev/tests/playwright/tools/ToolboxSelectedGameStatusBar.spec.mjs index 14d507ca9..d057acb86 100644 --- a/dev/tests/playwright/tools/ToolboxSelectedGameStatusBar.spec.mjs +++ b/dev/tests/playwright/tools/ToolboxSelectedGameStatusBar.spec.mjs @@ -1,6 +1,6 @@ import { expect, test } from "@playwright/test"; import process from "node:process"; -import { MOCK_DB_KEYS } from "../../../../src/dev-runtime/persistence/mock-db-store.js"; +import { MOCK_DB_KEYS } from "../../../../api/persistence/mock-db-store.js"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { clearPlaywrightStorage, installPlaywrightStorageIsolation } from "../../helpers/playwrightStorageIsolation.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; diff --git a/dev/tests/tools/MessagesPlaybackSource.test.mjs b/dev/tests/tools/MessagesPlaybackSource.test.mjs index dbc800206..046c165d0 100644 --- a/dev/tests/tools/MessagesPlaybackSource.test.mjs +++ b/dev/tests/tools/MessagesPlaybackSource.test.mjs @@ -33,7 +33,7 @@ test("Messages wires profile dropdowns through the Text To Speech profile contra }); test("Messages dev runtime does not import browser Text To Speech UI modules", async () => { - const source = await readFile(new URL("../../../src/dev-runtime/messages/messages-postgres-service.mjs", import.meta.url), "utf8"); + const source = await readFile(new URL("../../../api/messages/messages-postgres-service.mjs", import.meta.url), "utf8"); assert.equal(source.includes("toolbox/text-to-speech/text2speech.js"), false); assert.equal(source.includes("createMessageStudioDefaultTtsProfiles"), false); diff --git a/src/dev-runtime/DEV_RUNTIME_BOUNDARY.md b/src/dev-runtime/DEV_RUNTIME_BOUNDARY.md deleted file mode 100644 index 51b8ee73c..000000000 --- a/src/dev-runtime/DEV_RUNTIME_BOUNDARY.md +++ /dev/null @@ -1,16 +0,0 @@ -# Dev Runtime Boundary - -`src/dev-runtime` contains local development runtime implementation for the browser-visible Local API, seeded product data, provider stubs, storage adapters, database adapters, and support services used by repository validation. - -It is intentionally not `tests/dev-runtime`: - -- `src/dev-runtime` serves runtime behavior that browser pages and local API routes can exercise during development and targeted validation. -- `tests/dev-runtime` contains test files that validate that behavior. -- Moving the implementation into `tests/dev-runtime` would make development pages depend on test-only code and would blur the Browser -> Server API -> Data Source boundary. - -Deployment boundary: - -- DEV validation may execute `src/dev-runtime` through local server commands. -- UAT and PROD bundles must not import, bundle, or deploy `src/dev-runtime`. -- Browser pages, Theme V2 scripts, toolbox pages, and `src/engine` runtime code must use declared API/service contracts instead of importing `src/dev-runtime` directly. -- If a dev-only adapter is needed by a browser page, expose it through the Local API contract while keeping the implementation under `src/dev-runtime`. diff --git a/src/dev-runtime/guest-seeds/tool-metadata-inventory.js b/src/dev-runtime/guest-seeds/tool-metadata-inventory.js deleted file mode 100644 index f2da13bfe..000000000 --- a/src/dev-runtime/guest-seeds/tool-metadata-inventory.js +++ /dev/null @@ -1 +0,0 @@ -export * from "../../shared/toolbox/tool-metadata-inventory.js";