Initial Commit
This commit is contained in:
73
openspec/archive/2026-05-25-api-manifest/proposal.md
Normal file
73
openspec/archive/2026-05-25-api-manifest/proposal.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# `@suaveplan/api-manifest` — Zod manifest schema, validators, dep + capability resolvers
|
||||
|
||||
**Package**: `@suaveplan/api-manifest`
|
||||
**Status**: PROPOSED
|
||||
**Type**: new-package
|
||||
**Stage**: 1 (Contracts) — see umbrella `openspec/changes/api-plugin-foundation/`
|
||||
|
||||
## Why
|
||||
|
||||
Every plugin in the API foundation ships a `plugin.json` (design §3). The host validates that manifest at install AND at every boot — a manifest that drifted between install and reload is a hard failure. Without a single Zod-validated source of truth for the manifest shape, every consumer (host loader, admin API, CLI scaffolder, OpenAPI emitter, `definePlugin` typings) would re-derive the schema and the surface would drift instantly. `api-manifest` is that source of truth: runtime Zod schemas + parsed types + the two graph resolvers (`dependsOn` → topological install order; `requiredCapabilities` / `optionalCapabilities` → capability resolution against the registered adapters).
|
||||
|
||||
Per design §3: "Manifest is the source of truth for declarative facts... The host validates and acts on these *before* loading the plugin's code."
|
||||
|
||||
## What changes
|
||||
|
||||
Net-new package with both runtime Zod schemas and graph algorithms. Subpath exports per concern.
|
||||
|
||||
- ADDED: `./schema` — Zod schemas for every top-level manifest key (`identity`, `compat`, `isolation`, `permissions`, `dependsOn`, `routes`, `graphql`, `websockets`, `sse`, `sagas`, `twoPhaseCommit`, `database`, `jobs`, `events`, `secrets`, `config`, `scopes`, `telemetry`, `openapi`, `errors`, `uninstall`) plus the composite `pluginManifestSchema`.
|
||||
- ADDED: `./parse` — `parseManifest(input: unknown): PluginManifest` (throws `MANIFEST.PARSE_FAILED` with Zod issues attached); `parseManifestSafe(input)` returning a discriminated result.
|
||||
- ADDED: `./validators` — cross-field validators that Zod alone can't express: `validateSagaOwnership` (every step plugin is listed in `dependsOn` or is self), `validatePermissionsWhenSandboxed` (sandboxed isolation requires `permissions.budgets`), `validateOpenApiRequiresGraphqlOrRoutes`, `validateErrorStatusMapShape` (every code matches `PLUGIN.<UPPER>.<UPPER>` and status is 4xx/5xx).
|
||||
- ADDED: `./dep-graph` — `resolveInstallOrder(manifests): readonly PluginManifest[]` (topological sort over `dependsOn`); detects cycles → `MANIFEST.DEPENDENCY_CYCLE`; detects missing deps → `MANIFEST.MISSING_DEPENDENCY`; detects version mismatch (semver) → `MANIFEST.DEPENDENCY_VERSION_MISMATCH`.
|
||||
- ADDED: `./capability-resolver` — `resolveCapabilities({ manifest, registered }): CapabilityResolution`; reports missing required capabilities (`MANIFEST.CAPABILITY_REQUIRED_MISSING`), missing optional capabilities (warning bucket, no error), version mismatches (`MANIFEST.CAPABILITY_VERSION_MISMATCH`).
|
||||
- ADDED: `./hash` — `hashManifest(manifest): string` returning `"sha256:<hex>"` over a canonical JSON form (sorted keys, stable separators) — used by `RollbackJournal` entries and the plugin ledger to detect drift across reload.
|
||||
- ADDED: `./error-codes` — frozen `MANIFEST_CODES` registry via `defineCodes`.
|
||||
- ADDED: `./testing` — `runManifestConformance(parse)` reusable harness for downstream consumers that want to verify parser equivalence.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Loading `plugin.json` from disk — that's `api-loader`. This package only parses an `unknown` input.
|
||||
- `definePlugin` runtime — lives in `api-host`. `api-manifest` only exports the *manifest* side of the contract.
|
||||
- Manifest migrations (v1 → v2) — forward-compat only at v1. Migrators will land when v2 ships.
|
||||
|
||||
## Source mapping
|
||||
|
||||
Genesis-original. Source of design intent: `docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`.
|
||||
|
||||
| Requirement | Design doc section |
|
||||
|---|---|
|
||||
| Top-level manifest shape | §3 (full JSON example, lines 213-368) |
|
||||
| `isolation` enum + `permissions` budgets | §2.2, §3 (lines 229-242) |
|
||||
| `dependsOn` resolver semantics | §3, §4 (install order constraints) |
|
||||
| `requiredCapabilities` / `optionalCapabilities` | §3, §5.2 |
|
||||
| Sagas ownership validation | §3 ("sagas" block), §9.1 |
|
||||
| Errors `statusMap` shape | §3 ("errors" block), §12.1 |
|
||||
| Manifest hash for RollbackJournal | §8.1a (`manifestHash` field) |
|
||||
|
||||
### Requirements with no source line
|
||||
|
||||
| Requirement | Justification |
|
||||
|---|---|
|
||||
| `runManifestConformance` harness | Pattern established by `i18n-shared` conformance harnesses; downstream tooling (admin CLI, IDE plugin) needs a way to assert parser equivalence. |
|
||||
|
||||
## Verification approach
|
||||
|
||||
- `src/schema/*.test.ts` — golden manifest fixtures (minimal, maximal, every edge case from §3); `expectTypeOf` on inferred `PluginManifest` matching the type imported from `@suaveplan/api-types`.
|
||||
- `src/parse/parse.test.ts` — accept/reject suites; verifies Zod issues are surfaced verbatim on `PARSE_FAILED`.
|
||||
- `src/validators/*.test.ts` — every cross-field validator: accept path, every rejection branch with the right code.
|
||||
- `src/dep-graph/dep-graph.test.ts` — topological-order property tests (Fisher-Yates shuffled input → identical sorted output); cycle detection; missing-dep detection.
|
||||
- `src/capability-resolver/capability-resolver.test.ts` — required vs optional, version semver matching, missing capability surfacing.
|
||||
- `src/hash/hash.test.ts` — key-order independence; nested-object stability; sha256 KAT vector.
|
||||
- `tests/dep-graph.test.ts` — pins this package depends only on `@suaveplan/api-types`, `@suaveplan/error`, `zod`, and `@suaveplan/types` (logger contract for warnings).
|
||||
|
||||
## Migration impact
|
||||
|
||||
None — additive only. Nothing in the monorepo depends on `@suaveplan/api-manifest` yet. The umbrella's Stage-2 (`api-host`) is the first consumer.
|
||||
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
Run `bunx turbo ls --filter=...@suaveplan/api-manifest` to enumerate. No dependents at Stage 1 — every consumer lands in Stage 2 or later.
|
||||
|
||||
- (none) — [x] no action needed (reason: net-new package; no existing consumers in the genesis branch)
|
||||
Reference in New Issue
Block a user