DOB-1: Resolver — drop scope='builtin', fall through to code-side defaults #793

Closed
opened 2026-05-03 18:04:16 +00:00 by claude-desktop · 0 comments
Collaborator

As a platform engineer, I want every per-kind resolver to walk a 3-layer ladder (instance > agent_type > global) and fall back to a typed code-side default const when no row matches, so that nothing inherits from a scope='builtin' row anymore — DB rows hold operator data, code holds the bare-minimum default.

This is the foundation story for the milestone. Once the resolver no longer consults builtin rows, the boot-time JSON-sync path and the rows themselves become safe to delete (DOB-2 → DOB-5).

Acceptance criteria

Resolver

  • pickFromLadder (or per-kind equivalent) skips scope='builtin' rows when ranking. The SCOPE_PRIORITY map drops the builtin: 0 entry. New ladder: global=0, agent_type=1, instance=2.
  • When the 3-layer walk yields no row, the resolver returns a code-side default. One default per kind, all in a single apps/server/src/domain/agent-config/defaults.ts module exporting typed Readonly<T> consts.
  • Per-kind resolvers updated: resolveSkill, resolveSystemPrompt, resolvePlugins (incl. enabled-shadow logic), resolveMcpServers, resolveAgentTypeConfig, getServiceConfig, getLabelCatalog. Each has explicit unit-test coverage of the new fallback path.
  • getAgentType() likewise: when no scope='global' row exists for a type, return null (or a typed AGENT_TYPE_DEFAULTS-shaped row, depending on caller contract — pick one and document inline).

Defaults module

  • apps/server/src/domain/agent-config/defaults.ts ships with: SERVICE_CONFIG_DEFAULTS, SPEECH_CONFIG_DEFAULTS (move from resolver.ts), LABEL_CATALOG_DEFAULTS, MCP_SERVER_DEFAULTS per built-in MCP (forge, penpot, …), and any other kind currently seeded by syncBuiltinsFromRepo.
  • Each const is as const satisfies T so a future schema change to T fails the build at the const, not at runtime.

Tests

  • Add a test per resolver: empty DB → resolver returns the default const verbatim.
  • Add a test per resolver: only scope='global' row present → resolver returns that row, defaults ignored.
  • Existing tests that depend on scope='builtin' rows being implicitly seeded must be updated to write scope='global' rows in beforeEach (the actual data shape is unchanged — the scope label flips).

Out of scope

  • Deleting the scope='builtin' rows already in the DB — DOB-2 owns the migration.
  • Removing syncBuiltinsFromRepo itself — DOB-4. (Until DOB-4 ships, the loader still runs at boot but the resolver no longer reads its rows; this is intentional so the rollout can land in stages.)
  • Deleting config/*.json — DOB-5.
  • Setup wizard wiring — DOB-3.

References

  • Spec: specs/config-to-db.md (parent), specs/agent-config-customization.md § Resolver.
  • Code: apps/server/src/domain/agent-config/resolver.ts (pickFromLadder, SCOPE_PRIORITY, per-kind resolvers).
  • Decision: operator confirmed 2026-05-03 — defaults are bare-minimum to start; user replaces everything via dashboard. Code holds the consts, DB holds user data, no builtin middle tier.
As a platform engineer, I want every per-kind resolver to walk a 3-layer ladder (`instance > agent_type > global`) and fall back to a typed code-side default const when no row matches, so that nothing inherits from a `scope='builtin'` row anymore — DB rows hold operator data, code holds the bare-minimum default. This is the foundation story for the milestone. Once the resolver no longer consults `builtin` rows, the boot-time JSON-sync path and the rows themselves become safe to delete (DOB-2 → DOB-5). ## Acceptance criteria ### Resolver - [ ] `pickFromLadder` (or per-kind equivalent) skips `scope='builtin'` rows when ranking. The `SCOPE_PRIORITY` map drops the `builtin: 0` entry. New ladder: `global=0, agent_type=1, instance=2`. - [ ] When the 3-layer walk yields no row, the resolver returns a code-side default. One default per kind, all in a single `apps/server/src/domain/agent-config/defaults.ts` module exporting typed `Readonly<T>` consts. - [ ] Per-kind resolvers updated: `resolveSkill`, `resolveSystemPrompt`, `resolvePlugins` (incl. enabled-shadow logic), `resolveMcpServers`, `resolveAgentTypeConfig`, `getServiceConfig`, `getLabelCatalog`. Each has explicit unit-test coverage of the new fallback path. - [ ] `getAgentType()` likewise: when no `scope='global'` row exists for a type, return `null` (or a typed `AGENT_TYPE_DEFAULTS`-shaped row, depending on caller contract — pick one and document inline). ### Defaults module - [ ] `apps/server/src/domain/agent-config/defaults.ts` ships with: `SERVICE_CONFIG_DEFAULTS`, `SPEECH_CONFIG_DEFAULTS` (move from `resolver.ts`), `LABEL_CATALOG_DEFAULTS`, `MCP_SERVER_DEFAULTS` per built-in MCP (forge, penpot, …), and any other kind currently seeded by `syncBuiltinsFromRepo`. - [ ] Each const is `as const satisfies T` so a future schema change to T fails the build at the const, not at runtime. ### Tests - [ ] Add a test per resolver: empty DB → resolver returns the default const verbatim. - [ ] Add a test per resolver: only `scope='global'` row present → resolver returns that row, defaults ignored. - [ ] Existing tests that depend on `scope='builtin'` rows being implicitly seeded must be updated to write `scope='global'` rows in `beforeEach` (the actual data shape is unchanged — the scope label flips). ## Out of scope - Deleting the `scope='builtin'` rows already in the DB — DOB-2 owns the migration. - Removing `syncBuiltinsFromRepo` itself — DOB-4. (Until DOB-4 ships, the loader still runs at boot but the resolver no longer reads its rows; this is intentional so the rollout can land in stages.) - Deleting `config/*.json` — DOB-5. - Setup wizard wiring — DOB-3. ## References - Spec: `specs/config-to-db.md` (parent), `specs/agent-config-customization.md` § Resolver. - Code: `apps/server/src/domain/agent-config/resolver.ts` (`pickFromLadder`, `SCOPE_PRIORITY`, per-kind resolvers). - Decision: operator confirmed 2026-05-03 — defaults are bare-minimum to start; user replaces everything via dashboard. Code holds the consts, DB holds user data, no `builtin` middle tier.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
charles/claude-hooks#793
No description provided.