Curated, product-agnostic snapshot of the post-story-04 tree: demo content deleted, auth-only reference feature, web-next shell, all gates green. Product-specific docs, ADRs 027-029, PRDs/epics/archive, editor library traces, and product naming are curated out; generic template repairs (coverage provider devDeps, root test:coverage script, live lint fixes, root-only release-please) are kept. See TEMPLATE.md for provenance, curation list, and usage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
111 lines
7.3 KiB
Markdown
111 lines
7.3 KiB
Markdown
# Library Evaluation Policy — Quick Reference
|
|
|
|
> Authoritative source: `docs/decisions/adr-022-library-evaluation-policy.md`
|
|
> Authoritative runbook: `.claude/skills/evaluate-library/SKILL.md`
|
|
|
|
---
|
|
|
|
## Why this policy exists
|
|
|
|
The repo ships with a deliberately narrow runtime surface (six deps per feature package). That discipline is uncodified. Three signals exposed the gap: a near-miss adding `trpc-to-openapi` for hypothetical REST consumers; three ADRs recording library choices _after_ adoption; and no EU-residency gate before a library could silently transmit user data to a US-only SaaS endpoint. ADR-022 codifies the discipline and makes it agent-runnable.
|
|
|
|
---
|
|
|
|
## Tier trigger
|
|
|
|
The policy applies to **direct runtime dependencies** in feature- and core-tier packages. Devdeps and app-tier deps are exempt.
|
|
|
|
| Where the dep lands | Process required | Companion record |
|
|
| -------------------------- | ------------------------- | ---------------- |
|
|
| `apps/<x>` | Author's call — no policy | — |
|
|
| `packages/<feature>` | Trace required | — |
|
|
| `packages/core-*` | Trace required | ADR required |
|
|
| New optional-core category | Trace required | ADR required |
|
|
|
|
The trigger maps onto the existing ESLint `boundaries` tag system (ADR-006, ADR-010) — no new mental model.
|
|
|
|
---
|
|
|
|
## Eight hard auto-reject filters
|
|
|
|
**Phase 1 — cheap (always run to completion)**
|
|
|
|
| # | Filter | Auto-reject condition |
|
|
| --- | ---------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
| 1 | **license** | Outside `MIT`, `Apache-2.0`, `BSD-*`, `ISC`, `MPL-2.0` |
|
|
| 2 | **types** | No `.d.ts` and no `@types/<pkg>` |
|
|
| 3 | **shadow-check** | Functional parallel to a locked must-have (`zod`, `inversify`, `payload`, `@trpc/server`, `superjson`, `reflect-metadata`) |
|
|
| 4 | **boundary-fit** | Dep would violate ESLint boundary rules for the target tier (e.g., `@sentry/node` in a feature package — ADR-017 §4) |
|
|
|
|
**Phase 2 — expensive (short-circuit after first reject)**
|
|
|
|
| # | Filter | Auto-reject condition |
|
|
| --- | ------------------ | ---------------------------------------------------------------------------------------------------------------------- |
|
|
| 5 | **maintenance** | Last release ≥ 18 months OR activity gap ≥ 12 months (`abandoned`) |
|
|
| 6 | **cve-scan** | Open advisory at `moderate` severity or above (via `pnpm audit`) |
|
|
| 7 | **eu-residency** | Library transmits user data/telemetry to a vendor endpoint with no EU data region available or not configured |
|
|
| 8 | **named-consumer** | No concrete call site exists today and no feature is blocked waiting for it — hypothetical future use does not qualify |
|
|
|
|
A single failure in any filter → `decision: rejected`. Cheap filters always run; expensive filters stop at the first fail.
|
|
|
|
---
|
|
|
|
## Three discussion prompts
|
|
|
|
Not auto-reject filters — any answer is acceptable with justification. Required in every trace.
|
|
|
|
1. **replaces** — What existing approach does this replace? Parallel adoption of the same capability is a smell.
|
|
2. **migration-cost-out** — Rate the removal cost 18 months from now: mechanical / hard / impossible.
|
|
3. **alternatives-considered** — Two named alternatives minimum. For core-tier, also duplicated into the companion ADR.
|
|
|
|
---
|
|
|
|
## Trace artifact
|
|
|
|
Every decision — approved or rejected — produces a file at `docs/library-decisions/<YYYY-MM-DD>-<package-name>.md`.
|
|
|
|
**Required frontmatter fields:**
|
|
|
|
| Field | Values |
|
|
| ------------------------------- | ------------------------------------------ |
|
|
| `package` | npm package name |
|
|
| `version` | semver range |
|
|
| `tier` | `app` \| `feature` \| `core` |
|
|
| `decision` | `approved` \| `rejected` |
|
|
| `date` | `YYYY-MM-DD` |
|
|
| `deciders` | list of authors (human and/or agent) |
|
|
| `adr` | `adr-NNN` or `null` |
|
|
| `filter-results.license` | SPDX id |
|
|
| `filter-results.types` | `native` \| `@types/<x>` \| `none` |
|
|
| `filter-results.maintenance` | `active` \| `dormant` \| `abandoned` |
|
|
| `filter-results.boundary-fit` | `pass` \| `fail` |
|
|
| `filter-results.shadow-check` | `pass` \| `fail` \| `"shadows <x>"` |
|
|
| `filter-results.eu-residency` | `ok` \| `n/a` \| `self-hostable` \| `fail` |
|
|
| `filter-results.cve-scan` | `clean` \| advisory ID \| `fail` |
|
|
| `filter-results.named-consumer` | `pass` \| `fail` |
|
|
| `verification-commands` | list of literal commands run |
|
|
| `accepted-cves` | list of accepted advisory IDs (optional) |
|
|
|
|
Skipped expensive filters (short-circuited by an earlier reject) → write `skip` as the frontmatter value and note "Not evaluated" in the prose section.
|
|
|
|
The trace lands in **the same commit** as the `package.json` change. The pre-commit hook validates this for approved traces.
|
|
|
|
---
|
|
|
|
## Four-layer enforcement stack
|
|
|
|
| Layer | Latency | Catches |
|
|
| ----------------------------------------------------------- | ---------- | ----------------------------------------------------------------- |
|
|
| Claude `PreToolUse`/`PostToolUse` hook | inline | Agent skipping the skill before `pnpm add` or `package.json` edit |
|
|
| `/evaluate-library` skill | seconds | The decision itself + writes the trace |
|
|
| Git pre-commit hook (`scripts/library-decisions/check.mjs`) | pre-commit | Humans or agents bypassing the skill |
|
|
| Sandcastle reviewer prompt | per-slice | Bypasses that slipped past pre-commit |
|
|
|
|
The Claude hook injects a `<system-reminder>` pointing to this skill. It is non-blocking — devdep additions and app-tier changes trigger the reminder but do not require a trace. The pre-commit hook is the deterministic gate.
|
|
|
|
---
|
|
|
|
## Composition with generators
|
|
|
|
`pnpm turbo gen core-package <name>` emits **pre-shipped traces** — one per direct runtime dep of the new core package — pre-marked `decision: approved` and citing the relevant ADR (ADR-015 for events, ADR-016 for realtime, ADR-018 for audit). No separate evaluation needed for scaffolded optional cores.
|