Skip to content

AP-129: Typed RepositoryError replaces stringly-typed Error.message across the spine#177

Merged
isuttell merged 3 commits into
mainfrom
cursor/typed-repository-error-114b
Jun 2, 2026
Merged

AP-129: Typed RepositoryError replaces stringly-typed Error.message across the spine#177
isuttell merged 3 commits into
mainfrom
cursor/typed-repository-error-114b

Conversation

@isuttell

@isuttell isuttell commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Domain failures in packages/db now throw a typed RepositoryError with a discriminated kind, and HTTP workers map them through a single repositoryErrorToAppError function instead of duplicated string switches and inline error.message checks.

Changes

  • packages/db: Added RepositoryError, RepositoryErrorCode, and repositoryErrorToAppError; converted workflow, validation, policy, access-link, and repository throw sites; folded AccessLinkInactiveError / AccessLinkLockdownError into typed kinds that map to generic not_found at the HTTP boundary (ADR 0036).
  • packages/worker-runtime: unknownErrorToCode delegates to repositoryErrorToAppError first so registrar-mounted routes get mapping for free.
  • apps/api / apps/upload: Removed both mapRepositoryError copies and route-level error.message === "..." checks; added executeRepositoryRoute / extended runIdempotent and global onError handlers to use the central mapper.
  • Tests: Added repository-error.test.ts and worker-runtime mapping tests; updated route/db tests to throw RepositoryError where they mock repository failures.

Verification

  • pnpm verify
  • pnpm test:coverage
  • pnpm openapi:check (unchanged)

Notes

  • Self-revoke uses current_api_key_not_foundnot_authenticated; member web revoke uses not_found (preserves prior HTTP behavior).
  • Unmapped infrastructure kinds (e.g. lockdown_insert_conflict) still fall through to internal_error.

Linear Issue: AP-129

Open in Web Open in Cursor 

…129)

Introduce RepositoryError with a discriminated kind union and a single
repositoryErrorToAppError mapping in packages/db. Convert all domain throw
sites in the db package, fold access-link inactive/lockdown errors into the
typed scheme, and route HTTP surfaces through that mapper via worker-runtime
unknownErrorToCode, API/upload onError handlers, runIdempotent, and
executeRepositoryRoute. Remove duplicate mapRepositoryError copies and inline
error.message checks from api and upload routes.
@linear-code

linear-code Bot commented Jun 2, 2026

Copy link
Copy Markdown
AP-129 Typed RepositoryError replaces stringly-typed Error.message across the spine

Parent: AP-126. Architecture review finding #1. HOT SEAM (db core) — run alone, no other db-touching slice concurrently. Do this first; the contract.errors slice builds on the typed mapping.

Outcome

The Repository interface publishes its failure vocabulary as a typed, discriminated error instead of an undocumented set of ~32 magic Error.message strings that every HTTP surface must reverse-map. Adding a new domain failure becomes a one-file, compiler-checked change; no app reverse-maps strings.

Context docs

  • docs/ops/architecture-review-2026-06-02.md (finding feat(apex): add marketing worker at agent-paste.sh #1)
  • docs/adr/0070-repository-core-ports-and-adapters.md (complements, does not reopen)
  • docs/adr/0036-error-envelope-and-generic-404-boundary.md (error envelope must be preserved)
  • CONTEXT.md (domain terms: Repository, Workspace, Artifact)

Problem

~32 distinct codes thrown as throw new Error("...") across packages/db/src/ (workflows/*.ts, validation.ts, policy.ts). They are reverse-mapped by TWO near-identical mapRepositoryError copies — apps/api/src/responses.ts:77-103 and apps/upload/src/index.ts:579-599 — plus ~13 scattered inline error.message === "..." checks in apps/api/src/routes/{access-links,member-artifacts,operator,account,web}.ts. The error vocabulary has no locality and the inline checks compete with the centralized map, so the same string is handled inconsistently. A typo'd or unmapped string silently falls through to internal_error.

Likely files / packages

  • packages/db/src/ — new RepositoryError class + RepositoryErrorCode enum/union; swap throw sites
  • packages/db/src/repository/interface.ts — document the thrown error type
  • apps/api/src/responses.ts, apps/upload/src/index.ts — delete both mapRepositoryError copies
  • apps/api/src/routes/{access-links,member-artifacts,operator,account,web}.ts — delete inline error.message === checks
  • packages/worker-runtime/src/errors.ts — route the typed error through the existing unknownErrorToCode hook so every worker maps it for free
  • existing AccessLinkInactiveError / AccessLinkLockdownError — fold into the new typed scheme

In scope

  • Define RepositoryError (carrying kind: RepositoryErrorCode) and one repositoryErrorToAppError(err): AppErrorCode | null.
  • Convert all packages/db throw sites to the typed error.
  • Delete the two mapRepositoryError copies and the inline route-level string checks; route everything through the single mapping.

Out of scope

  • Changing the external error envelope shape or the generic-404 boundary (ADR 0036) — behavior stays identical.
  • Splitting RepositoryCore (AP-92 already did; out of scope here).
  • Wiring contract.errors validation — that is a separate AP-126 slice.

Acceptance criteria

  • No mapRepositoryError remains in apps/api or apps/upload.
  • No inline error.message === "..." repository-error checks remain in apps/api/src/routes/*.
  • All packages/db domain failures throw RepositoryError; the HTTP error codes returned to clients are unchanged (same envelope, same status, same generic-404 behavior).
  • A new db failure mode can be added by adding one enum member + one mapping entry, enforced by the type checker (exhaustiveness).

Required checks

pnpm verify + pnpm test:coverage green. pnpm openapi:check unchanged. Add/extend tests asserting the typed-error → AppErrorCode mapping and that representative routes still return the same envelopes.

Security / data / operational invariants

  • Cross-workspace reads must still fail closed as generic not-found (ADR 0036) — do not leak the new error kind to unauthenticated callers beyond what the envelope already exposes.
  • No new error detail in logs/audit that wasn't already there; do not log secrets or signed-URL fragments.

Dependencies / blockers

None. Must run alone on the db hot seam. Blocks the contract.errors slice.

Review in Linear

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b4057911-8636-491a-aa52-7428d779931b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/typed-repository-error-114b

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

Replace Partial<Record<…>> with Record<RepositoryErrorCode, ErrorCode | null>
so every kind requires an explicit mapping entry at compile time. Map
infrastructure failures to null (intentional internal_error) instead of
omitting them. Add tests covering all kinds and fix worker-runtime dep order.
@isuttell isuttell marked this pull request as ready for review June 2, 2026 23:26
@isuttell isuttell merged commit 73e1f64 into main Jun 2, 2026
5 checks passed
@isuttell isuttell deleted the cursor/typed-repository-error-114b branch June 2, 2026 23:27
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

agent-paste PR preview resources were cleaned up. The shared Preview GitHub Environment is retained for future preview deploys.

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.

2 participants