HarperFast / HarperFast/harper
Typed component options: declare option types so components receive coerced values, not raw env-expanded strings
- Dominant language
- JavaScript
- Stars
- 89
- Forks
- 10
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 200
Description
## Problem
Components receive their config block raw (`scope.options`): YAML quoting and `${ENV_VAR}` expansion both deliver **strings** for documented booleans/numbers, and an *unset* env var leaves the literal placeholder string (`"${FLAG}"`) in place. Every component must re-implement type coercion at every consumption site — and a missed site silently flips whichever direction the gate happens to test, because junk strings are truthy.
Concrete motivating case: HarperFast/oauth#192 (review thread) — `mcp.refreshTokenRequiresOfflineAccess: ${FLAG}` with `FLAG` unset survived as the truthy string `"${FLAG}"` and activated a documented default-off gate. The oauth component now carries four bespoke coercers (`coerceConfigBoolean`, `coerceTtl`, `coerceInterval`, `resolveRateLimit`) plus a total normalizer pass to defend against this class — and that only protects one component.
## Proposal
Let a component declare types for its options (schema in its config manifest, or a schema export the loader picks up). Core's options layer (OptionsWatcher / `scope.options`) then, after env expansion:
- coerces declared booleans/numbers from documented string forms to real types;
- treats an unresolved `${VAR}` placeholder on a typed option as an explicit config error (or absent + warning) rather than passing the literal through;
- rejects undeclarable junk on typed options with a clear startup error naming the path.
Components then gate on plain truthiness with no per-site defensive typing; component-side normalizers shrink to genuinely domain-specific validation (e.g. oauth's `allowedHosts` shape rules).
## Relationship to existing issues
- #1057 — same principle for **root** config values on the CLI set path; this issue is the component-options surface.
- #671 — config-system overhaul umbrella; this could ride whatever module boundary that lands.
- #1889 — the plugin capability manifest would be a natural home for the option schema declaration.
## Acceptance sketch
- A component can declare `myFlag: boolean` and receive `true`/`false`/absent — never a string — from `scope.options`.
- `myFlag: ${UNSET_VAR}` produces a clear startup diagnostic, not a truthy literal.
- Undeclared options keep today's pass-through behavior (no forced migration).
🤖 Filed by Claude on behalf of Nathan, from the HarperFast/oauth#192 review discussion.
Contributor guide
Research direction
Start by tracing the OptionsWatcher and scope.options path, then review the config manifest and plugin capability manifest references in the issue. Define how a component declares option types and how env expansion reports unresolved placeholders or invalid values; done means the acceptance cases work while undeclared options retain pass-through behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, nodejs
- Domain
- backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100