diff --git a/cocoscreator_projects/YouleNexus/assets/framework/sdk/runtime/game-session-host.ts b/cocoscreator_projects/YouleNexus/assets/framework/sdk/runtime/game-session-host.ts index 8ac0dda..0f35ced 100644 --- a/cocoscreator_projects/YouleNexus/assets/framework/sdk/runtime/game-session-host.ts +++ b/cocoscreator_projects/YouleNexus/assets/framework/sdk/runtime/game-session-host.ts @@ -5,9 +5,8 @@ import type { GameServerMessage, PlatformToGameEvent, } from '../contracts/index.ts'; -import { Route } from '../../core/constants.ts'; -const RESERVED_ROUTES = new Set(Object.values(Route)); +const RESERVED_GAME_ENTRY_ROUTES = new Set(['platform', 'agent', 'room']); const GAME_SESSION_OPEN_FAULT_BRAND = Symbol.for( 'youle.framework.sdk.GameSessionOpenFault', ); @@ -104,7 +103,7 @@ export function assertGameEntry(entry: GameEntry): void { /** The single GameEntry route admission check shared by runtime adapters. */ export function assertGameRoute(route: unknown): asserts route is string { assertNonEmptyText(route, 'route'); - if (RESERVED_ROUTES.has(route)) { + if (RESERVED_GAME_ENTRY_ROUTES.has(route)) { throw new Error(`GameEntry reserved route: ${route}`); } } diff --git a/cocoscreator_projects/framework-tests/architecture/import-boundaries.test.mjs b/cocoscreator_projects/framework-tests/architecture/import-boundaries.test.mjs index 2a4d07b..10c8ae9 100644 --- a/cocoscreator_projects/framework-tests/architecture/import-boundaries.test.mjs +++ b/cocoscreator_projects/framework-tests/architecture/import-boundaries.test.mjs @@ -187,6 +187,36 @@ test('scanner rejects sdk contracts importing a framework platform path', async assert.match(scan(root)[0].message, /sdk.*platform/); }); +test('scanner rejects every sdk runtime dependency on framework implementation layers', async () => { + const layers = [ + 'core', + 'platform', + 'net', + 'config', + 'native', + 'protocol', + 'application', + 'domain', + 'presentation', + 'ui', + 'compat', + ]; + + for (const layer of layers) { + const root = await createFixtureRoot(); + await writeFixture(root, `framework/${layer}/internal.ts`, 'export const value = true\n'); + await writeFixture( + root, + 'framework/sdk/runtime/bad.ts', + `import { value } from '../../${layer}/internal.ts'; void value\n`, + ); + + const violations = scan(root); + assert.equal(violations.length, 1, layer); + assert.match(violations[0].message, /sdk.*framework implementation/, layer); + } +}); + test('scanner rejects game imports of framework net internals', async () => { const root = await createFixtureRoot(); await writeFixture(root, 'games/a/assets/game/bad.ts', "import '../../../framework/net/net-client.ts'\n"); diff --git a/cocoscreator_projects/framework-tests/platform/game-host-adapter.test.ts b/cocoscreator_projects/framework-tests/platform/game-host-adapter.test.ts index 31f22a0..281707e 100644 --- a/cocoscreator_projects/framework-tests/platform/game-host-adapter.test.ts +++ b/cocoscreator_projects/framework-tests/platform/game-host-adapter.test.ts @@ -122,14 +122,6 @@ test('Task 1 route validator is the shared authority for game-route admission', for (const route of ['platform', 'agent', 'room']) { assert.throws(() => assertGameRoute(route), /reserved.*route/i); } - - const validatorSource = readFileSync(new URL( - '../../YouleNexus/assets/framework/sdk/runtime/game-session-host.ts', - import.meta.url, - ), 'utf8'); - assert.match(validatorSource, /import \{ Route \} from '\.\.\/\.\.\/core\/constants\.ts';/); - assert.match(validatorSource, /new Set\(Object\.values\(Route\)\)/); - assert.doesNotMatch(validatorSource, /new Set\(\[['"]platform['"]/); }); test('execute exposes only the GameHost command union and delegates semantic commands', () => { diff --git a/cocoscreator_projects/scripts/lib/import-boundaries.mjs b/cocoscreator_projects/scripts/lib/import-boundaries.mjs index 9a71be0..7a03049 100644 --- a/cocoscreator_projects/scripts/lib/import-boundaries.mjs +++ b/cocoscreator_projects/scripts/lib/import-boundaries.mjs @@ -221,6 +221,7 @@ function findViolation(context) { const filePath = displayPath(file); const resolvedPath = resolved ? displayPath(resolved) : specifier; const isContract = CONTRACTS_PATH.test(filePath); + const isSdk = SDK_ALLOWED.test(filePath); const isGame = isInside(file, gamesDir); const isFramework = isInside(file, frameworkDir); @@ -232,6 +233,14 @@ function findViolation(context) { return violation(filePath, specifier, `sdk/contracts may import only sibling contracts; sdk path resolved to ${resolvedPath}`); } + if (isSdk + && !isSdkMigrationBarrel(filePath, frameworkDir) + && resolved + && isInside(resolved, frameworkDir) + && !SDK_ALLOWED.test(resolvedPath)) { + return violation(filePath, specifier, `sdk cannot import framework implementation ${resolvedPath}`); + } + if (isGame && resolved && isFrameworkReference(resolved, frameworkDir) && !SDK_ALLOWED.test(resolvedPath)) { return violation(filePath, specifier, `game code may import only framework/sdk; game import resolved to ${resolvedPath}`); } @@ -264,6 +273,10 @@ function isLegacyRuntimeImporter(filePath, frameworkDir) { return filePath === `${normalizedFrameworkDir}/sdk/index.ts` || LEGACY_RUNTIME.test(filePath); } +function isSdkMigrationBarrel(filePath, frameworkDir) { + return filePath === `${displayPath(frameworkDir)}/sdk/index.ts`; +} + /** * ActiveGame is the retained compatibility owner of IGameModule until Task 11 * removes both it and the migration-only declarations from sdk/index.ts.