fix: add plugin config schema helper
This commit is contained in:
78
docs/refactor/strict-config.md
Normal file
78
docs/refactor/strict-config.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
summary: "Strict config validation + doctor-only migrations"
|
||||||
|
read_when:
|
||||||
|
- Designing or implementing config validation behavior
|
||||||
|
- Working on config migrations or doctor workflows
|
||||||
|
- Handling plugin config schemas or plugin load gating
|
||||||
|
---
|
||||||
|
# Strict config validation (doctor-only migrations)
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
- **Reject unknown config keys everywhere** (root + nested).
|
||||||
|
- **Reject plugin config without a schema**; don’t load that plugin.
|
||||||
|
- **Remove legacy auto-migration on load**; migrations run via doctor only.
|
||||||
|
- **Auto-run doctor (dry-run) on startup**; if invalid, block non-diagnostic commands.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
- Backward compatibility on load (legacy keys do not auto-migrate).
|
||||||
|
- Silent drops of unrecognized keys.
|
||||||
|
|
||||||
|
## Strict validation rules
|
||||||
|
- Config must match the schema exactly at every level.
|
||||||
|
- Unknown keys are validation errors (no passthrough at root or nested).
|
||||||
|
- `plugins.entries.<id>.config` must be validated by the plugin’s schema.
|
||||||
|
- If a plugin lacks a schema, **reject plugin load** and surface a clear error.
|
||||||
|
|
||||||
|
## Plugin schema enforcement
|
||||||
|
- Each plugin provides a strict schema for its config (no passthrough).
|
||||||
|
- Plugin load flow:
|
||||||
|
1) Resolve plugin schema by plugin id.
|
||||||
|
2) Validate config against the schema.
|
||||||
|
3) If missing schema or invalid config: block plugin load, record error.
|
||||||
|
- Error message includes:
|
||||||
|
- Plugin id
|
||||||
|
- Reason (missing schema / invalid config)
|
||||||
|
- Path(s) that failed validation
|
||||||
|
|
||||||
|
## Doctor flow
|
||||||
|
- Doctor runs **every time** config is loaded (dry-run by default).
|
||||||
|
- If config invalid:
|
||||||
|
- Print a summary + actionable errors.
|
||||||
|
- Instruct: `clawdbot doctor --fix`.
|
||||||
|
- `clawdbot doctor --fix`:
|
||||||
|
- Applies migrations.
|
||||||
|
- Removes unknown keys.
|
||||||
|
- Writes updated config.
|
||||||
|
|
||||||
|
## Command gating (when config is invalid)
|
||||||
|
Allowed (diagnostic-only):
|
||||||
|
- `clawdbot doctor`
|
||||||
|
- `clawdbot logs`
|
||||||
|
- `clawdbot health`
|
||||||
|
- `clawdbot help`
|
||||||
|
- `clawdbot status`
|
||||||
|
- `clawdbot service`
|
||||||
|
|
||||||
|
Everything else must hard-fail with: “Config invalid. Run `clawdbot doctor --fix`.”
|
||||||
|
|
||||||
|
## Error UX format
|
||||||
|
- Single summary header.
|
||||||
|
- Grouped sections:
|
||||||
|
- Unknown keys (full paths)
|
||||||
|
- Legacy keys / migrations needed
|
||||||
|
- Plugin load failures (plugin id + reason + path)
|
||||||
|
|
||||||
|
## Implementation touchpoints
|
||||||
|
- `src/config/zod-schema.ts`: remove root passthrough; strict objects everywhere.
|
||||||
|
- `src/config/zod-schema.providers.ts`: ensure strict channel schemas.
|
||||||
|
- `src/config/validation.ts`: fail on unknown keys; do not apply legacy migrations.
|
||||||
|
- `src/config/io.ts`: remove legacy auto-migrations; always run doctor dry-run.
|
||||||
|
- `src/config/legacy*.ts`: move usage to doctor only.
|
||||||
|
- `src/plugins/*`: add schema registry + gating.
|
||||||
|
- CLI command gating in `src/cli`.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- Unknown key rejection (root + nested).
|
||||||
|
- Plugin missing schema → plugin load blocked with clear error.
|
||||||
|
- Invalid config → gateway startup blocked except diagnostic commands.
|
||||||
|
- Doctor dry-run auto; `doctor --fix` writes corrected config.
|
||||||
31
src/plugins/config-schema.ts
Normal file
31
src/plugins/config-schema.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import type { ClawdbotPluginConfigSchema } from "./types.js";
|
||||||
|
|
||||||
|
type Issue = { path: Array<string | number>; message: string };
|
||||||
|
|
||||||
|
type SafeParseResult =
|
||||||
|
| { success: true; data?: unknown }
|
||||||
|
| { success: false; error: { issues: Issue[] } };
|
||||||
|
|
||||||
|
function error(message: string): SafeParseResult {
|
||||||
|
return { success: false, error: { issues: [{ path: [], message }] } };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function emptyPluginConfigSchema(): ClawdbotPluginConfigSchema {
|
||||||
|
return {
|
||||||
|
safeParse(value: unknown): SafeParseResult {
|
||||||
|
if (value === undefined) return { success: true, data: undefined };
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||||
|
return error("expected config object");
|
||||||
|
}
|
||||||
|
if (Object.keys(value as Record<string, unknown>).length > 0) {
|
||||||
|
return error("config must be empty");
|
||||||
|
}
|
||||||
|
return { success: true, data: value };
|
||||||
|
},
|
||||||
|
jsonSchema: {
|
||||||
|
type: "object",
|
||||||
|
additionalProperties: false,
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user