feat(id): brand WorkspaceID through Drizzle and Zod schemas (#16964)

This commit is contained in:
Kit Langton
2026-03-11 19:30:17 -04:00
committed by GitHub
parent f1c3a44190
commit 16a6d6feba
49 changed files with 205 additions and 157 deletions

View File

@@ -1,14 +1,14 @@
import { test, expect, describe } from "bun:test"
import { extractResponseText, formatPromptTooLargeError } from "../../src/cli/cmd/github"
import type { MessageV2 } from "../../src/session/message-v2"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
// Helper to create minimal valid parts
function createTextPart(text: string): MessageV2.Part {
return {
id: "1",
sessionID: SessionID.make("s"),
messageID: "m",
messageID: MessageID.make("m"),
type: "text" as const,
text,
}
@@ -18,7 +18,7 @@ function createReasoningPart(text: string): MessageV2.Part {
return {
id: "1",
sessionID: SessionID.make("s"),
messageID: "m",
messageID: MessageID.make("m"),
type: "reasoning" as const,
text,
time: { start: 0 },
@@ -30,7 +30,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn
return {
id: "1",
sessionID: SessionID.make("s"),
messageID: "m",
messageID: MessageID.make("m"),
type: "tool" as const,
callID: "c1",
tool,
@@ -47,7 +47,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn
return {
id: "1",
sessionID: SessionID.make("s"),
messageID: "m",
messageID: MessageID.make("m"),
type: "tool" as const,
callID: "c1",
tool,
@@ -63,7 +63,7 @@ function createStepStartPart(): MessageV2.Part {
return {
id: "1",
sessionID: SessionID.make("s"),
messageID: "m",
messageID: MessageID.make("m"),
type: "step-start" as const,
}
}
@@ -72,7 +72,7 @@ function createStepFinishPart(): MessageV2.Part {
return {
id: "1",
sessionID: SessionID.make("s"),
messageID: "m",
messageID: MessageID.make("m"),
type: "step-finish" as const,
reason: "done",
cost: 0,

View File

@@ -1,5 +1,5 @@
import { afterEach, describe, expect, mock, test } from "bun:test"
import { Identifier } from "../../src/id/id"
import { WorkspaceID } from "../../src/control-plane/schema"
import { Hono } from "hono"
import { tmpdir } from "../fixture/fixture"
import { Project } from "../../src/project/project"
@@ -64,8 +64,8 @@ async function setup(state: State) {
await using tmp = await tmpdir({ git: true })
const { project } = await Project.fromDirectory(tmp.path)
const id1 = Identifier.descending("workspace")
const id2 = Identifier.descending("workspace")
const id1 = WorkspaceID.ascending()
const id2 = WorkspaceID.ascending()
Database.use((db) =>
db

View File

@@ -1,5 +1,5 @@
import { afterEach, describe, expect, mock, test } from "bun:test"
import { Identifier } from "../../src/id/id"
import { WorkspaceID } from "../../src/control-plane/schema"
import { Log } from "../../src/util/log"
import { tmpdir } from "../fixture/fixture"
import { Project } from "../../src/project/project"
@@ -52,8 +52,8 @@ describe("control-plane/workspace.startSyncing", () => {
await using tmp = await tmpdir({ git: true })
const { project } = await Project.fromDirectory(tmp.path)
const id1 = Identifier.descending("workspace")
const id2 = Identifier.descending("workspace")
const id1 = WorkspaceID.ascending()
const id2 = WorkspaceID.ascending()
Database.use((db) =>
db

View File

@@ -2,13 +2,13 @@ import { describe, test, expect } from "bun:test"
import path from "path"
import { Instance } from "../../src/project/instance"
import { WebFetchTool } from "../../src/tool/webfetch"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
const projectRoot = path.join(__dirname, "../..")
const ctx = {
sessionID: SessionID.make("ses_test"),
messageID: "",
messageID: MessageID.make(""),
callID: "",
agent: "build",
abort: new AbortController().signal,

View File

@@ -11,7 +11,7 @@ import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
import type { Agent } from "../../src/agent/agent"
import type { MessageV2 } from "../../src/session/message-v2"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
describe("session.llm.hasToolCalls", () => {
test("returns false for empty messages array", () => {
@@ -277,7 +277,7 @@ describe("session.llm.stream", () => {
} satisfies Agent.Info
const user = {
id: "user-1",
id: MessageID.make("user-1"),
sessionID,
role: "user",
time: { created: Date.now() },
@@ -406,7 +406,7 @@ describe("session.llm.stream", () => {
} satisfies Agent.Info
const user = {
id: "user-2",
id: MessageID.make("user-2"),
sessionID,
role: "user",
time: { created: Date.now() },
@@ -529,7 +529,7 @@ describe("session.llm.stream", () => {
} satisfies Agent.Info
const user = {
id: "user-3",
id: MessageID.make("user-3"),
sessionID,
role: "user",
time: { created: Date.now() },
@@ -630,7 +630,7 @@ describe("session.llm.stream", () => {
} satisfies Agent.Info
const user = {
id: "user-4",
id: MessageID.make("user-4"),
sessionID,
role: "user",
time: { created: Date.now() },

View File

@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"
import { APICallError } from "ai"
import { MessageV2 } from "../../src/session/message-v2"
import type { Provider } from "../../src/provider/provider"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
const sessionID = SessionID.make("session")
const model: Provider.Model = {
@@ -100,7 +100,7 @@ function basePart(messageID: string, id: string) {
return {
id,
sessionID,
messageID,
messageID: MessageID.make(messageID),
}
}

View File

@@ -7,6 +7,7 @@ import { MessageV2 } from "../../src/session/message-v2"
import { Log } from "../../src/util/log"
import { Instance } from "../../src/project/instance"
import { Identifier } from "../../src/id/id"
import { MessageID } from "../../src/session/schema"
import { tmpdir } from "../fixture/fixture"
const projectRoot = path.join(__dirname, "../..")
@@ -24,7 +25,7 @@ describe("revert + compact workflow", () => {
// Create a user message
const userMsg1 = await Session.updateMessage({
id: Identifier.ascending("message"),
id: MessageID.ascending(),
role: "user",
sessionID,
agent: "default",
@@ -48,7 +49,7 @@ describe("revert + compact workflow", () => {
// Create an assistant response message
const assistantMsg1: MessageV2.Assistant = {
id: Identifier.ascending("message"),
id: MessageID.ascending(),
role: "assistant",
sessionID,
mode: "default",
@@ -85,7 +86,7 @@ describe("revert + compact workflow", () => {
// Create another user message
const userMsg2 = await Session.updateMessage({
id: Identifier.ascending("message"),
id: MessageID.ascending(),
role: "user",
sessionID,
agent: "default",
@@ -108,7 +109,7 @@ describe("revert + compact workflow", () => {
// Create another assistant response
const assistantMsg2: MessageV2.Assistant = {
id: Identifier.ascending("message"),
id: MessageID.ascending(),
role: "assistant",
sessionID,
mode: "default",
@@ -200,7 +201,7 @@ describe("revert + compact workflow", () => {
// Create initial messages
const userMsg = await Session.updateMessage({
id: Identifier.ascending("message"),
id: MessageID.ascending(),
role: "user",
sessionID,
agent: "default",
@@ -222,7 +223,7 @@ describe("revert + compact workflow", () => {
})
const assistantMsg: MessageV2.Assistant = {
id: Identifier.ascending("message"),
id: MessageID.ascending(),
role: "assistant",
sessionID,
mode: "default",

View File

@@ -6,6 +6,7 @@ import { Log } from "../../src/util/log"
import { Instance } from "../../src/project/instance"
import { MessageV2 } from "../../src/session/message-v2"
import { Identifier } from "../../src/id/id"
import { MessageID } from "../../src/session/schema"
const projectRoot = path.join(__dirname, "../..")
Log.init({ print: false })
@@ -81,7 +82,7 @@ describe("step-finish token propagation via Bus event", () => {
fn: async () => {
const session = await Session.create({})
const messageID = Identifier.ascending("message")
const messageID = MessageID.ascending()
await Session.updateMessage({
id: messageID,
sessionID: session.id,

View File

@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test"
import { MessageV2 } from "../../src/session/message-v2"
import { SessionPrompt } from "../../src/session/prompt"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
describe("structured-output.OutputFormat", () => {
test("parses text format", () => {
@@ -96,7 +96,7 @@ describe("structured-output.StructuredOutputError", () => {
describe("structured-output.UserMessage", () => {
test("user message accepts outputFormat", () => {
const result = MessageV2.User.safeParse({
id: "test-id",
id: MessageID.ascending(),
sessionID: SessionID.descending(),
role: "user",
time: { created: Date.now() },
@@ -112,7 +112,7 @@ describe("structured-output.UserMessage", () => {
test("user message works without outputFormat (optional)", () => {
const result = MessageV2.User.safeParse({
id: "test-id",
id: MessageID.ascending(),
sessionID: SessionID.descending(),
role: "user",
time: { created: Date.now() },
@@ -125,10 +125,10 @@ describe("structured-output.UserMessage", () => {
describe("structured-output.AssistantMessage", () => {
const baseAssistantMessage = {
id: "test-id",
id: MessageID.ascending(),
sessionID: SessionID.descending(),
role: "assistant" as const,
parentID: "parent-id",
parentID: MessageID.ascending(),
modelID: "claude-3",
providerID: "anthropic",
mode: "default",

View File

@@ -11,7 +11,7 @@ import { ProjectTable } from "../../src/project/project.sql"
import { ProjectID } from "../../src/project/schema"
import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../../src/session/session.sql"
import { SessionShareTable } from "../../src/share/share.sql"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
// Test fixtures
const fixtures = {
@@ -255,7 +255,7 @@ describe("JSON to SQLite migration", () => {
const db = drizzle({ client: sqlite })
const messages = db.select().from(MessageTable).all()
expect(messages.length).toBe(1)
expect(messages[0].id).toBe("msg_test789ghi")
expect(messages[0].id).toBe(MessageID.make("msg_test789ghi"))
const parts = db.select().from(PartTable).all()
expect(parts.length).toBe(1)
@@ -295,7 +295,7 @@ describe("JSON to SQLite migration", () => {
const db = drizzle({ client: sqlite })
const messages = db.select().from(MessageTable).all()
expect(messages.length).toBe(1)
expect(messages[0].id).toBe("msg_test789ghi")
expect(messages[0].id).toBe(MessageID.make("msg_test789ghi"))
expect(messages[0].session_id).toBe(SessionID.make("ses_test456def"))
expect(messages[0].data).not.toHaveProperty("id")
expect(messages[0].data).not.toHaveProperty("sessionID")
@@ -303,7 +303,7 @@ describe("JSON to SQLite migration", () => {
const parts = db.select().from(PartTable).all()
expect(parts.length).toBe(1)
expect(parts[0].id).toBe("prt_testabc123")
expect(parts[0].message_id).toBe("msg_test789ghi")
expect(parts[0].message_id).toBe(MessageID.make("msg_test789ghi"))
expect(parts[0].session_id).toBe(SessionID.make("ses_test456def"))
expect(parts[0].data).not.toHaveProperty("id")
expect(parts[0].data).not.toHaveProperty("messageID")
@@ -336,7 +336,7 @@ describe("JSON to SQLite migration", () => {
const db = drizzle({ client: sqlite })
const messages = db.select().from(MessageTable).all()
expect(messages.length).toBe(1)
expect(messages[0].id).toBe("msg_from_filename") // Uses filename, not JSON id
expect(messages[0].id).toBe(MessageID.make("msg_from_filename")) // Uses filename, not JSON id
expect(messages[0].session_id).toBe(SessionID.make("ses_test456def"))
})
@@ -375,7 +375,7 @@ describe("JSON to SQLite migration", () => {
const parts = db.select().from(PartTable).all()
expect(parts.length).toBe(1)
expect(parts[0].id).toBe("prt_from_filename") // Uses filename, not JSON id
expect(parts[0].message_id).toBe("msg_realmsgid") // Uses parent dir, not JSON messageID
expect(parts[0].message_id).toBe(MessageID.make("msg_realmsgid")) // Uses parent dir, not JSON messageID
})
test("skips orphaned sessions (no parent project)", async () => {

View File

@@ -4,11 +4,11 @@ import * as fs from "fs/promises"
import { ApplyPatchTool } from "../../src/tool/apply_patch"
import { Instance } from "../../src/project/instance"
import { tmpdir } from "../fixture/fixture"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
const baseCtx = {
sessionID: SessionID.make("ses_test"),
messageID: "",
messageID: MessageID.make(""),
callID: "",
agent: "build",
abort: AbortSignal.any([]),

View File

@@ -7,11 +7,11 @@ import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
import type { PermissionNext } from "../../src/permission/next"
import { Truncate } from "../../src/tool/truncation"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
const ctx = {
sessionID: SessionID.make("ses_test"),
messageID: "",
messageID: MessageID.make(""),
callID: "",
agent: "build",
abort: AbortSignal.any([]),

View File

@@ -5,11 +5,11 @@ import { EditTool } from "../../src/tool/edit"
import { Instance } from "../../src/project/instance"
import { tmpdir } from "../fixture/fixture"
import { FileTime } from "../../src/file/time"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
const ctx = {
sessionID: SessionID.make("ses_test-edit-session"),
messageID: "",
messageID: MessageID.make(""),
callID: "",
agent: "build",
abort: AbortSignal.any([]),

View File

@@ -4,11 +4,11 @@ import type { Tool } from "../../src/tool/tool"
import { Instance } from "../../src/project/instance"
import { assertExternalDirectory } from "../../src/tool/external-directory"
import type { PermissionNext } from "../../src/permission/next"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
const baseCtx: Omit<Tool.Context, "ask"> = {
sessionID: SessionID.make("ses_test"),
messageID: "",
messageID: MessageID.make(""),
callID: "",
agent: "build",
abort: AbortSignal.any([]),

View File

@@ -3,11 +3,11 @@ import path from "path"
import { GrepTool } from "../../src/tool/grep"
import { Instance } from "../../src/project/instance"
import { tmpdir } from "../fixture/fixture"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
const ctx = {
sessionID: SessionID.make("ses_test"),
messageID: "",
messageID: MessageID.make(""),
callID: "",
agent: "build",
abort: AbortSignal.any([]),

View File

@@ -2,11 +2,11 @@ import { describe, expect, test, spyOn, beforeEach, afterEach } from "bun:test"
import { z } from "zod"
import { QuestionTool } from "../../src/tool/question"
import * as QuestionModule from "../../src/question"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
const ctx = {
sessionID: SessionID.make("ses_test-session"),
messageID: "test-message",
messageID: MessageID.make("test-message"),
callID: "test-call",
agent: "test-agent",
abort: AbortSignal.any([]),

View File

@@ -6,13 +6,13 @@ import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
import { PermissionNext } from "../../src/permission/next"
import { Agent } from "../../src/agent/agent"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
const FIXTURES_DIR = path.join(import.meta.dir, "fixtures")
const ctx = {
sessionID: SessionID.make("ses_test"),
messageID: "",
messageID: MessageID.make(""),
callID: "",
agent: "build",
abort: AbortSignal.any([]),

View File

@@ -6,11 +6,11 @@ import type { Tool } from "../../src/tool/tool"
import { Instance } from "../../src/project/instance"
import { SkillTool } from "../../src/tool/skill"
import { tmpdir } from "../fixture/fixture"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
const baseCtx: Omit<Tool.Context, "ask"> = {
sessionID: SessionID.make("ses_test"),
messageID: "",
messageID: MessageID.make(""),
callID: "",
agent: "build",
abort: AbortSignal.any([]),

View File

@@ -2,13 +2,13 @@ import { describe, expect, test } from "bun:test"
import path from "path"
import { Instance } from "../../src/project/instance"
import { WebFetchTool } from "../../src/tool/webfetch"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
const projectRoot = path.join(import.meta.dir, "../..")
const ctx = {
sessionID: SessionID.make("ses_test"),
messageID: "message",
messageID: MessageID.make("message"),
callID: "",
agent: "build",
abort: AbortSignal.any([]),

View File

@@ -4,11 +4,11 @@ import fs from "fs/promises"
import { WriteTool } from "../../src/tool/write"
import { Instance } from "../../src/project/instance"
import { tmpdir } from "../fixture/fixture"
import { SessionID } from "../../src/session/schema"
import { SessionID, MessageID } from "../../src/session/schema"
const ctx = {
sessionID: SessionID.make("ses_test-write-session"),
messageID: "",
messageID: MessageID.make(""),
callID: "",
agent: "build",
abort: AbortSignal.any([]),