Evalkeeper
    Preparing search index...

    Function createSettings

    • Versioned settings: each feature's settings as numbered versions, with every change, promotion, rollback, and deploy logged. Its route is the runner's route.

      Parameters

      • opts: {
            defaults: (operation: string) => Settings | null;
            promptVersion?: (operation: string) => string | Promise<string>;
            readActual?: (
                operation: string,
            ) => Settings | Promise<Settings | null> | null;
            store: SettingsStore;
        }
        • defaults: (operation: string) => Settings | null

          Each feature's settings in code, used until it's first changed. Null: the feature has no default, and reading its settings throws NoSettingsError until a version is recorded.

        • OptionalpromptVersion?: (operation: string) => string | Promise<string>

          The instructions' current hash, so a version records what it ran. It must cover everything the model receives: the instructions in code, and any admin override or guidance added at call time. Join the two with promptVersionOf, so syncPrompt can tell a runtime edit from a deploy.

        • OptionalreadActual?: (operation: string) => Settings | Promise<Settings | null> | null

          The settings the app's call sites actually use, read from wherever they read them (the app's own settings row), or null when it has none. When given, reading or saving settings first compares them with the live version, and records a mismatch as an OUTSIDE version, so the history holds every state that ran. Your save must update or roll back here first and write the row after, or the new settings read as changed outside. Without it, nothing is compared.

        • store: SettingsStore

      Returns {
          change: (
              operation: string,
              next: Settings,
              by: { by: string | null; evidence?: unknown; reason: string | null },
          ) => Promise<SettingsVersion | null>;
          current: (
              operation: string,
          ) => Promise<{ settings: Settings; version: SettingsVersion | null }>;
          history: (operation: string, limit?: number) => Promise<HistoryEntry[]>;
          rollBack: (
              operation: string,
              versionId: string,
              by: { by: string | null; reason: string },
          ) => Promise<SettingsVersion>;
          route: (operation: string) => Promise<Route>;
          seed: (
              operation: string,
              initial: Settings,
              by: {
                  by: string | null;
                  promptVersion?: string | null;
                  reason: string | null;
              },
          ) => Promise<SettingsVersion | null>;
          syncPrompt: (operation: string) => Promise<SettingsVersion | null>;
          update: (
              operation: string,
              patch: Partial<Settings>,
              by: { by: string | null; evidence?: unknown; reason: string | null },
          ) => Promise<SettingsVersion | null>;
      }

      • change: (
            operation: string,
            next: Settings,
            by: { by: string | null; evidence?: unknown; reason: string | null },
        ) => Promise<SettingsVersion | null>
      • current: (
            operation: string,
        ) => Promise<{ settings: Settings; version: SettingsVersion | null }>
      • history: (operation: string, limit?: number) => Promise<HistoryEntry[]>
      • rollBack: (
            operation: string,
            versionId: string,
            by: { by: string | null; reason: string },
        ) => Promise<SettingsVersion>
      • route: (operation: string) => Promise<Route>
      • seed: (
            operation: string,
            initial: Settings,
            by: {
                by: string | null;
                promptVersion?: string | null;
                reason: string | null;
            },
        ) => Promise<SettingsVersion | null>
      • syncPrompt: (operation: string) => Promise<SettingsVersion | null>
      • update: (
            operation: string,
            patch: Partial<Settings>,
            by: { by: string | null; evidence?: unknown; reason: string | null },
        ) => Promise<SettingsVersion | null>
      const settings = createSettings({
      store: prismaSettingsStore(prisma),
      defaults: (op) => DEFAULTS[op],
      promptVersion: (op) => PROMPT_VERSIONS[op],
      });
      await settings.update("ticket_triage", { model: "claude-haiku-5-5" }, {
      by: admin.name,
      reason: "About as accurate at half the cost",
      evidence: { runId },
      });
      await settings.rollBack("ticket_triage", versionId, { by: admin.name, reason: "Worse in production" });

      When models are chosen in an admin page rather than in code, return null from defaults and record each feature's existing config as version 1 with seed:

      const settings = createSettings({ store, defaults: () => null });
      for (const row of await db.aiConfig.findMany())
      await settings.seed(row.operation, settingsFromRow(row), {
      by: null,
      reason: "Imported from the admin config",
      });

      When call sites read the app's own settings row (which a script, SQL, or an older admin page can change without a version), pass readActual, so a change made there is recorded (OUTSIDE) before the next save, and a rollback can always get back to it:

      const settings = createSettings({
      store,
      defaults: () => null,
      readActual: async (op) => {
      const row = await db.aiConfig.findUnique({ where: { operation: op } });
      return row ? settingsFromRow(row) : null;
      },
      });