From 1a90cf977d60f98b840eeefa23d2dc72ef6edaf0 Mon Sep 17 00:00:00 2001 From: murashit Date: Fri, 26 Jun 2026 07:40:16 +0900 Subject: [PATCH] Split app-server protocol Grit boundaries --- biome.jsonc | 22 ++++- ...-app-server-protocol-boundary-imports.grit | 32 +----- ...t-app-server-request-protocol-imports.grit | 26 +++++ ...chat-app-server-turn-protocol-imports.grit | 26 +++++ tests/scripts/grit-policy.test.mjs | 99 +++++++++++-------- 5 files changed, 130 insertions(+), 75 deletions(-) create mode 100644 scripts/lint/no-chat-app-server-request-protocol-imports.grit create mode 100644 scripts/lint/no-chat-app-server-turn-protocol-imports.grit diff --git a/biome.jsonc b/biome.jsonc index ad12f89b..001f3a81 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -13,7 +13,27 @@ }, { "path": "./scripts/lint/no-app-server-protocol-boundary-imports.grit", - "includes": ["**/src/**/*.ts", "**/src/**/*.tsx", "!**/src/app-server/**/*.ts", "!**/src/app-server/**/*.tsx"] + "includes": [ + "**/src/**/*.ts", + "**/src/**/*.tsx", + "!**/src/app-server/**/*.ts", + "!**/src/app-server/**/*.tsx", + "!**/src/features/chat/app-server/inbound/notification-plan.ts", + "!**/src/features/chat/app-server/mappers/message-stream/turn-items.ts", + "!**/src/features/chat/app-server/inbound/handler.ts", + "!**/src/features/chat/app-server/inbound/routing.ts" + ] + }, + { + "path": "./scripts/lint/no-chat-app-server-turn-protocol-imports.grit", + "includes": [ + "**/src/features/chat/app-server/inbound/notification-plan.ts", + "**/src/features/chat/app-server/mappers/message-stream/turn-items.ts" + ] + }, + { + "path": "./scripts/lint/no-chat-app-server-request-protocol-imports.grit", + "includes": ["**/src/features/chat/app-server/inbound/handler.ts", "**/src/features/chat/app-server/inbound/routing.ts"] }, { "path": "./scripts/lint/no-generated-app-server-boundary-imports.grit", diff --git a/scripts/lint/no-app-server-protocol-boundary-imports.grit b/scripts/lint/no-app-server-protocol-boundary-imports.grit index be663d67..fbbbe21c 100644 --- a/scripts/lint/no-app-server-protocol-boundary-imports.grit +++ b/scripts/lint/no-app-server-protocol-boundary-imports.grit @@ -15,40 +15,10 @@ or { $stmt <: contains `$source` where { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol|src/app-server/protocol)/[^/\"']+(?:/.*)?[\"']$" }, - or { - { - not { $filename <: r".*/src/features/chat/app-server/(?:inbound/notification-plan|mappers/message-stream/turn-items)\.ts$" }, - not { $filename <: r".*/src/features/chat/app-server/inbound/(?:handler|routing)\.ts$" }, - register_diagnostic(span=$stmt, message="Source modules outside app-server must use domain models and app-server services instead of app-server protocol modules. Chat ingestion and message-stream conversion may consume app-server turn protocol at the boundary; feature state and UI must use Panel-owned models.", severity="error") - }, - { - $filename <: r".*/src/features/chat/app-server/(?:inbound/notification-plan|mappers/message-stream/turn-items)\.ts$", - not { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/turn|src/app-server/protocol/turn)(?:/.*)?[\"']$" }, - register_diagnostic(span=$stmt, message="Chat app-server ingestion and message-stream conversion may consume the app-server turn protocol only. Convert other protocol payloads to local or domain models at the boundary.", severity="error") - }, - { - $filename <: r".*/src/features/chat/app-server/inbound/(?:handler|routing)\.ts$", - not { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/server-requests|src/app-server/protocol/server-requests)(?:/.*)?[\"']$" }, - register_diagnostic(span=$stmt, message="Chat app-server request handling may consume server request protocol projections only. Convert app-server payloads to chat pending request domain models at this boundary.", severity="error") - } - } - }, - `require($source)` as $stmt where { - $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol|src/app-server/protocol)/[^/\"']+(?:/.*)?[\"']$", - not { $filename <: r".*/src/features/chat/app-server/(?:inbound/notification-plan|mappers/message-stream/turn-items)\.ts$" }, - not { $filename <: r".*/src/features/chat/app-server/inbound/(?:handler|routing)\.ts$" }, register_diagnostic(span=$stmt, message="Source modules outside app-server must use domain models and app-server services instead of app-server protocol modules. Chat ingestion and message-stream conversion may consume app-server turn protocol at the boundary; feature state and UI must use Panel-owned models.", severity="error") }, `require($source)` as $stmt where { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol|src/app-server/protocol)/[^/\"']+(?:/.*)?[\"']$", - $filename <: r".*/src/features/chat/app-server/(?:inbound/notification-plan|mappers/message-stream/turn-items)\.ts$", - not { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/turn|src/app-server/protocol/turn)(?:/.*)?[\"']$" }, - register_diagnostic(span=$stmt, message="Chat app-server ingestion and message-stream conversion may consume the app-server turn protocol only. Convert other protocol payloads to local or domain models at the boundary.", severity="error") - }, - `require($source)` as $stmt where { - $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol|src/app-server/protocol)/[^/\"']+(?:/.*)?[\"']$", - $filename <: r".*/src/features/chat/app-server/inbound/(?:handler|routing)\.ts$", - not { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/server-requests|src/app-server/protocol/server-requests)(?:/.*)?[\"']$" }, - register_diagnostic(span=$stmt, message="Chat app-server request handling may consume server request protocol projections only. Convert app-server payloads to chat pending request domain models at this boundary.", severity="error") + register_diagnostic(span=$stmt, message="Source modules outside app-server must use domain models and app-server services instead of app-server protocol modules. Chat ingestion and message-stream conversion may consume app-server turn protocol at the boundary; feature state and UI must use Panel-owned models.", severity="error") } } diff --git a/scripts/lint/no-chat-app-server-request-protocol-imports.grit b/scripts/lint/no-chat-app-server-request-protocol-imports.grit new file mode 100644 index 00000000..244d0c86 --- /dev/null +++ b/scripts/lint/no-chat-app-server-request-protocol-imports.grit @@ -0,0 +1,26 @@ +language js + +private pattern js_module_reference() { + or { + JsImport(), + JsExportNamedFromClause(), + JsExportFromClause(), + TsImportType(), + JsImportCallExpression() + } +} + +or { + js_module_reference() as $stmt where { + $stmt <: contains `$source` where { + $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol|src/app-server/protocol)/[^/\"']+(?:/.*)?[\"']$", + not { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/server-requests|src/app-server/protocol/server-requests)(?:/.*)?[\"']$" } + }, + register_diagnostic(span=$stmt, message="Chat app-server request handling may consume server request protocol projections only. Convert app-server payloads to chat pending request domain models at this boundary.", severity="error") + }, + `require($source)` as $stmt where { + $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol|src/app-server/protocol)/[^/\"']+(?:/.*)?[\"']$", + not { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/server-requests|src/app-server/protocol/server-requests)(?:/.*)?[\"']$" }, + register_diagnostic(span=$stmt, message="Chat app-server request handling may consume server request protocol projections only. Convert app-server payloads to chat pending request domain models at this boundary.", severity="error") + } +} diff --git a/scripts/lint/no-chat-app-server-turn-protocol-imports.grit b/scripts/lint/no-chat-app-server-turn-protocol-imports.grit new file mode 100644 index 00000000..205856a8 --- /dev/null +++ b/scripts/lint/no-chat-app-server-turn-protocol-imports.grit @@ -0,0 +1,26 @@ +language js + +private pattern js_module_reference() { + or { + JsImport(), + JsExportNamedFromClause(), + JsExportFromClause(), + TsImportType(), + JsImportCallExpression() + } +} + +or { + js_module_reference() as $stmt where { + $stmt <: contains `$source` where { + $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol|src/app-server/protocol)/[^/\"']+(?:/.*)?[\"']$", + not { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/turn|src/app-server/protocol/turn)(?:/.*)?[\"']$" } + }, + register_diagnostic(span=$stmt, message="Chat app-server ingestion and message-stream conversion may consume the app-server turn protocol only. Convert other protocol payloads to local or domain models at the boundary.", severity="error") + }, + `require($source)` as $stmt where { + $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol|src/app-server/protocol)/[^/\"']+(?:/.*)?[\"']$", + not { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/turn|src/app-server/protocol/turn)(?:/.*)?[\"']$" }, + register_diagnostic(span=$stmt, message="Chat app-server ingestion and message-stream conversion may consume the app-server turn protocol only. Convert other protocol payloads to local or domain models at the boundary.", severity="error") + } +} diff --git a/tests/scripts/grit-policy.test.mjs b/tests/scripts/grit-policy.test.mjs index d31940b6..0fd97ac2 100644 --- a/tests/scripts/grit-policy.test.mjs +++ b/tests/scripts/grit-policy.test.mjs @@ -452,6 +452,49 @@ import type { TurnItem } from "../../../../app-server/protocol/turn"; export type Item = TurnItem; `.trimStart(), ); + await writeFile( + path.join(cwd, "src/features/chat/panel/surface/message-stream-presenter.ts"), + ` +import { appServerUserInputResponse } from "../../../../../app-server/protocol/server-requests"; + +export const response = appServerUserInputResponse; +`.trimStart(), + ); + await writeFile( + path.join(cwd, "src/features/chat/app-server/inbound/app-server-logs.ts"), + ` +import { appServerUserInputResponse } from "../../../../app-server/protocol/server-requests"; + +export const response = appServerUserInputResponse; +`.trimStart(), + ); + + const report = biomeLint( + [ + "src/features/chat/application/pending-requests/pending-request-actions.ts", + "src/features/chat/application/threads/history-controller.ts", + "src/features/chat/panel/surface/message-stream-presenter.ts", + "src/features/chat/app-server/inbound/app-server-logs.ts", + ], + cwd, + ); + + expect(pluginMessages(report, "src/features/chat/application/pending-requests/pending-request-actions.ts")).toEqual([ + "Source modules outside app-server must use domain models and app-server services instead of app-server protocol modules. Chat ingestion and message-stream conversion may consume app-server turn protocol at the boundary; feature state and UI must use Panel-owned models.", + ]); + expect(pluginMessages(report, "src/features/chat/application/threads/history-controller.ts")).toEqual([ + "Source modules outside app-server must use domain models and app-server services instead of app-server protocol modules. Chat ingestion and message-stream conversion may consume app-server turn protocol at the boundary; feature state and UI must use Panel-owned models.", + ]); + expect(pluginMessages(report, "src/features/chat/panel/surface/message-stream-presenter.ts")).toEqual([ + "Source modules outside app-server must use domain models and app-server services instead of app-server protocol modules. Chat ingestion and message-stream conversion may consume app-server turn protocol at the boundary; feature state and UI must use Panel-owned models.", + ]); + expect(pluginMessages(report, "src/features/chat/app-server/inbound/app-server-logs.ts")).toEqual([ + "Source modules outside app-server must use domain models and app-server services instead of app-server protocol modules. Chat ingestion and message-stream conversion may consume app-server turn protocol at the boundary; feature state and UI must use Panel-owned models.", + ]); + }); + + it("keeps chat app-server ingestion on app-server turn protocol only", async () => { + const cwd = await tempBiomeWorkspace(["no-chat-app-server-turn-protocol-imports.grit"]); await writeFile( path.join(cwd, "src/features/chat/app-server/inbound/notification-plan.ts"), ` @@ -470,22 +513,21 @@ import type { TurnItem } from "../../../../../app-server/protocol/turn"; export type Item = TurnItem; `.trimStart(), ); - await writeFile( - path.join(cwd, "src/features/chat/panel/surface/message-stream-presenter.ts"), - ` -import { appServerUserInputResponse } from "../../../../../app-server/protocol/server-requests"; -export const response = appServerUserInputResponse; -`.trimStart(), + const report = biomeLint( + ["src/features/chat/app-server/inbound/notification-plan.ts", "src/features/chat/app-server/mappers/message-stream/turn-items.ts"], + cwd, ); - await writeFile( - path.join(cwd, "src/features/chat/app-server/inbound/app-server-logs.ts"), - ` -import { appServerUserInputResponse } from "../../../../app-server/protocol/server-requests"; -export const response = appServerUserInputResponse; -`.trimStart(), - ); + expect(pluginMessages(report, "src/features/chat/app-server/inbound/notification-plan.ts")).toEqual([ + "Chat app-server ingestion and message-stream conversion may consume the app-server turn protocol only. Convert other protocol payloads to local or domain models at the boundary.", + "Chat app-server ingestion and message-stream conversion may consume the app-server turn protocol only. Convert other protocol payloads to local or domain models at the boundary.", + ]); + expect(pluginDiagnostics(report, "src/features/chat/app-server/mappers/message-stream/turn-items.ts")).toEqual([]); + }); + + it("keeps chat app-server request handling on server request protocol only", async () => { + const cwd = await tempBiomeWorkspace(["no-chat-app-server-request-protocol-imports.grit"]); await writeFile( path.join(cwd, "src/features/chat/app-server/inbound/handler.ts"), ` @@ -503,37 +545,8 @@ export const response = appServerUserInputResponse; `.trimStart(), ); - const report = biomeLint( - [ - "src/features/chat/application/pending-requests/pending-request-actions.ts", - "src/features/chat/application/threads/history-controller.ts", - "src/features/chat/app-server/inbound/notification-plan.ts", - "src/features/chat/app-server/mappers/message-stream/turn-items.ts", - "src/features/chat/panel/surface/message-stream-presenter.ts", - "src/features/chat/app-server/inbound/app-server-logs.ts", - "src/features/chat/app-server/inbound/handler.ts", - "src/features/chat/app-server/inbound/routing.ts", - ], - cwd, - ); + const report = biomeLint(["src/features/chat/app-server/inbound/handler.ts", "src/features/chat/app-server/inbound/routing.ts"], cwd); - expect(pluginMessages(report, "src/features/chat/application/pending-requests/pending-request-actions.ts")).toEqual([ - "Source modules outside app-server must use domain models and app-server services instead of app-server protocol modules. Chat ingestion and message-stream conversion may consume app-server turn protocol at the boundary; feature state and UI must use Panel-owned models.", - ]); - expect(pluginMessages(report, "src/features/chat/application/threads/history-controller.ts")).toEqual([ - "Source modules outside app-server must use domain models and app-server services instead of app-server protocol modules. Chat ingestion and message-stream conversion may consume app-server turn protocol at the boundary; feature state and UI must use Panel-owned models.", - ]); - expect(pluginMessages(report, "src/features/chat/app-server/inbound/notification-plan.ts")).toEqual([ - "Chat app-server ingestion and message-stream conversion may consume the app-server turn protocol only. Convert other protocol payloads to local or domain models at the boundary.", - "Chat app-server ingestion and message-stream conversion may consume the app-server turn protocol only. Convert other protocol payloads to local or domain models at the boundary.", - ]); - expect(pluginDiagnostics(report, "src/features/chat/app-server/mappers/message-stream/turn-items.ts")).toEqual([]); - expect(pluginMessages(report, "src/features/chat/panel/surface/message-stream-presenter.ts")).toEqual([ - "Source modules outside app-server must use domain models and app-server services instead of app-server protocol modules. Chat ingestion and message-stream conversion may consume app-server turn protocol at the boundary; feature state and UI must use Panel-owned models.", - ]); - expect(pluginMessages(report, "src/features/chat/app-server/inbound/app-server-logs.ts")).toEqual([ - "Source modules outside app-server must use domain models and app-server services instead of app-server protocol modules. Chat ingestion and message-stream conversion may consume app-server turn protocol at the boundary; feature state and UI must use Panel-owned models.", - ]); expect(pluginMessages(report, "src/features/chat/app-server/inbound/handler.ts")).toEqual([ "Chat app-server request handling may consume server request protocol projections only. Convert app-server payloads to chat pending request domain models at this boundary.", ]);