Skip to content

refactor(shared): decompose shared type contracts - #945

Merged
ColeMurray merged 1 commit into
mainfrom
refactor/shared-types-decomposition
Jul 10, 2026
Merged

refactor(shared): decompose shared type contracts#945
ColeMurray merged 1 commit into
mainfrom
refactor/shared-types-decomposition

Conversation

@ColeMurray

@ColeMurray ColeMurray commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • decompose the 1,493-line shared types module into focused repository, session, protocol, environment, automation, artifact, catalog, and analytics modules
  • keep types/index.ts as a 165-line compatibility-only barrel with the existing package-root API unchanged
  • remove shared-internal barrel back-edges and enforce an acyclic runtime module graph
  • add schema alias identity tests, compile-time boundary contracts, and import-graph enforcement

Why

The multi-repository sessions rollout expanded packages/shared/src/types/index.ts into a runtime-heavy protocol module whose Zod schemas, DTOs, normalizers, and domain contracts had several unrelated owners. The environment contracts could not be cleanly extracted without first establishing a dependency leaf for canonical repository-list schemas; importing those values back through the barrel would risk ESM initialization cycles.

This refactor makes the ownership and dependency direction explicit without changing consumer imports, wire behavior, schema optionality, normalization, or legacy compatibility.

Compatibility and quality guardrails

  • verified all 300 TypeScript package-root exports and 146 runtime exports remain unchanged
  • preserves repository/environment/automation Zod schema object identities
  • preserves the single RepositoryPairValidationError constructor used by instanceof
  • rejects internal imports through types/index.ts, the package root, or @open-inspect/shared
  • detects runtime dependency cycles between shared implementation modules
  • strictly typechecks the new API, type-contract, and module-boundary tests

The thermo-nuclear code-quality review found two guardrail defects during iteration: the module-boundary test was initially omitted from strict test typechecking, and bare @open-inspect/shared self-imports were not rejected. Both were fixed and re-reviewed. The final thermo verdict approved the full diff with no remaining findings.

After review feedback, the local implementation-plan document and generated Vitest .snap artifact were removed from the PR. The compatibility suite keeps direct type-contract and runtime identity assertions without introducing a new snapshot-file convention. AutomationTriggerType also moved into automations.ts instead of living in a standalone primitive module.

Validation

  • npm run build -w @open-inspect/shared
  • npm run typecheck
  • npm test -w @open-inspect/shared — 372 tests
  • npm test -w @open-inspect/control-plane — 1,717 tests
  • npm run test:integration -w @open-inspect/control-plane — 525 tests
  • npm test -w @open-inspect/web — 620 tests
  • npm test -w @open-inspect/github-bot — 123 tests
  • npm test -w @open-inspect/slack-bot — 227 tests
  • npm test -w @open-inspect/linear-bot — 136 tests
  • production builds for control-plane, web, GitHub bot, Slack bot, and Linear bot
  • npm run lint -w @open-inspect/shared
  • git diff --check

Summary by CodeRabbit

  • New Features
    • Added shared validation and data contracts for analytics, artifacts, automations, environments, repositories, sessions, events, and server messages.
    • Added support for validating environment configuration, repository targets, session requests, artifact metadata, and automation workflows.
  • Bug Fixes
    • Improved consistency and compatibility of public package exports and runtime validation.
  • Tests
    • Added checks for type contracts, public API compatibility, module boundaries, and circular dependencies.
    • Expanded typechecking to include dedicated test configurations.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The shared package separates its monolithic type barrel into domain-specific schema and type modules, preserves compatibility re-exports, updates internal imports, and adds public API, module-boundary, type-contract, and dedicated test-compilation checks.

Changes

Shared types decomposition

Layer / File(s) Summary
Leaf domain contracts
packages/shared/src/types/statuses.ts, repositories.ts, repository-catalog.ts, analytics.ts, environments.ts, automations.ts
Adds shared status, repository, catalog, analytics, environment, and automation contracts, including Zod schemas, validation helpers, response types, and repository targeting models.
Protocol and session contracts
packages/shared/src/types/artifacts.ts, sandbox-events.ts, server-messages.ts, sessions.ts, session-api.ts
Adds artifact, sandbox-event, server-message, session, callback, session-creation, child-session, and response schemas and interfaces.
Compatibility barrel and internal imports
packages/shared/src/types/index.ts, packages/shared/src/completion/extractor.ts, packages/shared/src/logger.ts, packages/shared/src/triggers/*
Converts the types barrel to re-exports, moves consumers to leaf type modules, and declares the exported logger interface locally.
Compatibility and boundary verification
packages/shared/src/module-boundaries.test.ts, public-api.test.ts, type-contracts.test.ts, packages/shared/tsconfig.test.json, packages/shared/package.json
Adds runtime API compatibility checks, dependency-boundary and cycle detection, schema/type contract checks, and a dedicated test typecheck script.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: decomposing shared type contracts into focused modules.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/shared-types-decomposition

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/shared/src/types/statuses.ts (1)

13-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive duplicated types from their Zod schemas via z.infer.

SessionStatus correctly uses z.infer<typeof sessionStatusSchema>, but SandboxStatus, GitSyncStatus, ArtifactType, and SpawnSource are manually written unions that duplicate the values in their corresponding Zod enums (sandboxStatusSchema, gitSyncStatusSchema, artifactTypeSchema, spawnSourceSchema). If a value is added to one but not the other, they will silently drift. Deriving the types from the schemas eliminates this risk.

♻️ Proposed refactor
-export type SandboxStatus =
-  | "pending"
-  | "spawning"
-  | "connecting"
-  | "warming"
-  | "syncing"
-  | "ready"
-  | "running"
-  | "stale"
-  | "snapshotting"
-  | "stopped"
-  | "failed";
 export type GitSyncStatus = "pending" | "in_progress" | "completed" | "failed";
 export type MessageStatus = "pending" | "processing" | "completed" | "failed";
 export type MessageSource = "web" | "slack" | "linear" | "extension" | "github" | "automation";
-export type ArtifactType = "pr" | "screenshot" | "video" | "preview" | "branch";
 export type EventType =
   | "heartbeat"
   | "ready"
   | "token"
   | "tool_call"
   | "step_start"
   | "step_finish"
   | "tool_result"
   | "git_sync"
   | "error"
   | "execution_complete"
   | "artifact"
   | "push_complete"
   | "push_error"
   | "warning"
   | "user_message";
 export type ParticipantRole = "owner" | "member";
-export type SpawnSource =
-  | "user"
-  | "agent"
-  | "automation"
-  | "github-bot"
-  | "linear-bot"
-  | "slack-bot";
 export type ConfidenceLevel = "high" | "medium" | "low";

 export const sandboxStatusSchema = z.enum([
   "pending",
   "spawning",
   "connecting",
   "warming",
   "syncing",
   "ready",
   "running",
   "stale",
   "snapshotting",
   "stopped",
   "failed",
 ]);
+export type SandboxStatus = z.infer<typeof sandboxStatusSchema>;
+
 export const gitSyncStatusSchema = z.enum(["pending", "in_progress", "completed", "failed"]);
+export type GitSyncStatus = z.infer<typeof gitSyncStatusSchema>;
+
 export const artifactTypeSchema = z.enum(["pr", "screenshot", "video", "preview", "branch"]);
+export type ArtifactType = z.infer<typeof artifactTypeSchema>;
+
 export const spawnSourceSchema = z.enum([
   "user",
   "agent",
   "automation",
   "github-bot",
   "linear-bot",
   "slack-bot",
 ]);
+export type SpawnSource = z.infer<typeof spawnSourceSchema>;

Also applies to: 55-77

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/types/statuses.ts` around lines 13 - 24, Replace the
manually defined SandboxStatus, GitSyncStatus, ArtifactType, and SpawnSource
unions with z.infer types referencing sandboxStatusSchema, gitSyncStatusSchema,
artifactTypeSchema, and spawnSourceSchema respectively, matching the existing
SessionStatus pattern; ensure the schemas are declared or imported before their
inferred types.
packages/shared/src/types/session-api.ts (1)

129-178: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared refinements to avoid duplication.

The three .refine() calls are duplicated identically between createSessionRequestSchema (lines 129-141) and createSessionInputSchema (lines 145-178). If a validation rule changes, both must be updated in sync. Extracting the refinements into a shared array or helper would eliminate this risk.

♻️ Proposed refactor
+const sessionTargetRefinements = [
+  { fn: hasMatchingRepositoryIdentifiers, opts: { message: "repoOwner and repoName must be provided together", path: ["repoName"] } },
+  { fn: hasRepositoryForBranch, opts: { message: "branch requires repoOwner and repoName", path: ["branch"] } },
+  { fn: hasExclusiveSessionTarget, opts: { message: "environmentId, repositories, and repoOwner/repoName/branch are mutually exclusive", path: ["repositories"] } },
+] as const;
+
+function withSessionTargetRefinements<S extends z.ZodObject>(schema: S) {
+  return sessionTargetRefinements.reduce(
+    (s, { fn, opts }) => s.refine(fn, opts),
+    schema as z.ZodTypeAny
+  );
+}

-export const createSessionRequestSchema = createSessionRequestBaseSchema
-  .refine(hasMatchingRepositoryIdentifiers, {
-    message: "repoOwner and repoName must be provided together",
-    path: ["repoName"],
-  })
-  .refine(hasRepositoryForBranch, {
-    message: "branch requires repoOwner and repoName",
-    path: ["branch"],
-  })
-  .refine(hasExclusiveSessionTarget, {
-    message: "environmentId, repositories, and repoOwner/repoName/branch are mutually exclusive",
-    path: ["repositories"],
-  });
+export const createSessionRequestSchema = withSessionTargetRefinements(createSessionRequestBaseSchema);

Then apply the same to createSessionInputSchema:

-export const createSessionInputSchema = createSessionRequestBaseSchema
-  .extend({
-    userId: z.string().optional(),
-    spawnSource: spawnSourceSchema.optional(),
-    authProvider: z.enum(["github", "google"]).optional(),
-    authUserId: z.string().optional(),
-    authEmail: z.string().optional(),
-    authName: z.string().optional(),
-    authAvatarUrl: z.string().optional(),
-    scmUserId: z.string().optional(),
-    scmLogin: z.string().optional(),
-    scmName: z.string().optional(),
-    scmEmail: z.string().optional(),
-    scmAvatarUrl: z.string().optional(),
-    actorUserId: z.string().optional(),
-    actorDisplayName: z.string().optional(),
-    actorEmail: z.string().optional(),
-    actorAvatarUrl: z.string().optional(),
-    scmToken: z.string().optional(),
-    scmRefreshToken: z.string().optional(),
-    scmTokenExpiresAt: z.number().optional(),
-  })
-  .refine(hasMatchingRepositoryIdentifiers, {
-    message: "repoOwner and repoName must be provided together",
-    path: ["repoName"],
-  })
-  .refine(hasRepositoryForBranch, {
-    message: "branch requires repoOwner and repoName",
-    path: ["branch"],
-  })
-  .refine(hasExclusiveSessionTarget, {
-    message: "environmentId, repositories, and repoOwner/repoName/branch are mutually exclusive",
-    path: ["repositories"],
-  });
+export const createSessionInputSchema = withSessionTargetRefinements(
+  createSessionRequestBaseSchema.extend({
+    userId: z.string().optional(),
+    spawnSource: spawnSourceSchema.optional(),
+    authProvider: z.enum(["github", "google"]).optional(),
+    authUserId: z.string().optional(),
+    authEmail: z.string().optional(),
+    authName: z.string().optional(),
+    authAvatarUrl: z.string().optional(),
+    scmUserId: z.string().optional(),
+    scmLogin: z.string().optional(),
+    scmName: z.string().optional(),
+    scmEmail: z.string().optional(),
+    scmAvatarUrl: z.string().optional(),
+    actorUserId: z.string().optional(),
+    actorDisplayName: z.string().optional(),
+    actorEmail: z.string().optional(),
+    actorAvatarUrl: z.string().optional(),
+    scmToken: z.string().optional(),
+    scmRefreshToken: z.string().optional(),
+    scmTokenExpiresAt: z.number().optional(),
+  })
+);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/types/session-api.ts` around lines 129 - 178, Extract the
duplicated refinements used by createSessionRequestSchema and
createSessionInputSchema into a shared helper or reusable refinement array
containing hasMatchingRepositoryIdentifiers, hasRepositoryForBranch, and
hasExclusiveSessionTarget with their existing messages and paths. Apply that
shared validation to both schemas while preserving their current behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/shared/src/types/session-api.ts`:
- Around line 129-178: Extract the duplicated refinements used by
createSessionRequestSchema and createSessionInputSchema into a shared helper or
reusable refinement array containing hasMatchingRepositoryIdentifiers,
hasRepositoryForBranch, and hasExclusiveSessionTarget with their existing
messages and paths. Apply that shared validation to both schemas while
preserving their current behavior.

In `@packages/shared/src/types/statuses.ts`:
- Around line 13-24: Replace the manually defined SandboxStatus, GitSyncStatus,
ArtifactType, and SpawnSource unions with z.infer types referencing
sandboxStatusSchema, gitSyncStatusSchema, artifactTypeSchema, and
spawnSourceSchema respectively, matching the existing SessionStatus pattern;
ensure the schemas are declared or imported before their inferred types.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 16aac12c-f04f-44e9-b663-02368154282c

📥 Commits

Reviewing files that changed from the base of the PR and between b4f43a9 and 4cd8fc2d3a0b413ddaf42ea01dcf1d4b86fe0eec.

⛔ Files ignored due to path filters (1)
  • packages/shared/src/__snapshots__/public-api.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (23)
  • docs/shared-types-decomposition-plan.md
  • packages/shared/package.json
  • packages/shared/src/completion/extractor.ts
  • packages/shared/src/logger.ts
  • packages/shared/src/module-boundaries.test.ts
  • packages/shared/src/public-api.test.ts
  • packages/shared/src/triggers/testing.ts
  • packages/shared/src/triggers/types.ts
  • packages/shared/src/types/analytics.ts
  • packages/shared/src/types/artifacts.ts
  • packages/shared/src/types/automation-primitives.ts
  • packages/shared/src/types/automations.ts
  • packages/shared/src/types/environments.ts
  • packages/shared/src/types/index.ts
  • packages/shared/src/types/repositories.ts
  • packages/shared/src/types/repository-catalog.ts
  • packages/shared/src/types/sandbox-events.ts
  • packages/shared/src/types/server-messages.ts
  • packages/shared/src/types/session-api.ts
  • packages/shared/src/types/sessions.ts
  • packages/shared/src/types/statuses.ts
  • packages/shared/src/types/type-contracts.test.ts
  • packages/shared/tsconfig.test.json

@ColeMurray
ColeMurray force-pushed the refactor/shared-types-decomposition branch from 4cd8fc2 to 9cea116 Compare July 10, 2026 00:16
@github-actions

Copy link
Copy Markdown

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant