Files
suaveplan/openspec/archive/2026-05-25-enterprise-overview/specs/overview/spec.md
2026-05-27 23:29:53 +01:00

10 KiB

Capability Spec Delta: overview (overview)

Package: @suaveplan/overview Status: PROPOSED

Every requirement below carries a **Verified by:** line at authoring time.

ADDED Requirements

Requirement: Stable error registry

The package SHALL expose OVERVIEW_CODES registered via defineCodes("OVERVIEW", [...]) and a OverviewError class extending @suaveplan/error's SuaveplanError. The registry SHALL contain exactly six entries: PARSE_FAILED, INVALID_RECORD, CYCLE_DETECTED, UNKNOWN_PACKAGE, DISCOVERY_ABORTED, INVALID_TIER. Each code MUST be reachable from a real call site exercised by tests; no dead entries.

Scenario: error code uniqueness

  • WHEN the test enumerates Object.values(OVERVIEW_CODES)
  • THEN every value is a unique string in the form "OVERVIEW.<NAME>" and new OverviewError(code, "msg") produces an instance whose code field equals the input

Scenario: every code is thrown somewhere

  • WHEN the test asserts each code appears as a throw new OverviewError(...) argument in at least one src/**/*.ts file
  • THEN the assertion passes — PARSE_FAILED from discover, INVALID_RECORD from discover/schemas, CYCLE_DETECTED from graph, UNKNOWN_PACKAGE from graph, DISCOVERY_ABORTED from discover, INVALID_TIER from schemas

Verified by: src/errors/errors.test.ts::"every OVERVIEW code is unique" AND src/errors/errors.test.ts::"every code has a real call site in src/"


Requirement: Zod-validated data model

The package SHALL export Zod schemas TierSchema, PackageRecordSchema, PluginSummarySchema, and SuavePlanManifestSchema, each paired with an inferred TypeScript type. Schemas SHALL reject malformed inputs at the package boundary and INVALID_RECORD SHALL be thrown when a discovered package.json fails to parse against PackageRecordSchema.

Scenario: valid PackageRecord parses

  • WHEN PackageRecordSchema.safeParse({ name: "@suaveplan/x", short: "x", category: "foundation", plugin: "core", dirName: "x", version: "0.1.0", description: "d", tier: "universal", workspaceDeps: [], hasBin: false, hasBrowserField: false }) runs
  • THEN success === true

Scenario: malformed tier rejected

  • WHEN TierSchema.safeParse("rust") runs
  • THEN success === false

Verified by: src/schemas/schemas.test.ts::"TierSchema accepts every tier" AND src/schemas/schemas.test.ts::"PackageRecordSchema rejects bad input per field"


Requirement: Tier inference

The package SHALL provide inferTier(short, pkgJson, hints?) that returns a Tier using the priority order: python → r3f → react → browser → server → universal. The hint set for the server-tier walk SHALL be exported as SERVER_DEP_HINTS and SHALL be extensible per call via an optional hints: Set<string> parameter.

Scenario: python wins

  • WHEN inferTier("python-foo", { python: {} }) runs
  • THEN the return value is "python"

Scenario: server hint matches a Node-only dep

  • WHEN inferTier("x", { dependencies: { "node:fs": "*" } }) runs
  • THEN the return value is "server"

Scenario: universal fallback

  • WHEN inferTier("x", {}) runs
  • THEN the return value is "universal"

Verified by: src/tier/tier.test.ts::"inferTier prioritises python > r3f > react > browser > server > universal" AND src/tier/tier.test.ts::"inferTier honours extension hints"


Requirement: Filesystem discovery (server sub-path)

The package SHALL expose discoverPackages(options) from the ./discover sub-path export. The function SHALL walk <root>/packages/<category>/<plugin>/<pkg>/package.json exactly three folder levels deep, parse each file as JSON, validate against PackageRecordSchema, and return SuavePlanManifest. Invalid JSON SHALL produce OVERVIEW.PARSE_FAILED; validation failure SHALL produce OVERVIEW.INVALID_RECORD (default behaviour: throw; opt-in onError: "skip" collects errors into a returned array).

Scenario: temp-dir fixture discovers two packages

  • WHEN the test creates a temp packages/foundation/core/{a,b}/package.json fixture and calls discoverPackages({ root: tmp })
  • THEN the returned manifest contains two records sorted by name

Scenario: invalid JSON throws PARSE_FAILED

  • WHEN a fixture package.json is unparseable and onError: "throw"
  • THEN an OverviewError with code === "OVERVIEW.PARSE_FAILED" is thrown

Verified by: src/discover/discover.test.ts::"discovers a 3-level fixture" AND src/discover/discover.test.ts::"throws PARSE_FAILED on invalid JSON" AND src/discover/discover.test.ts::"throws INVALID_RECORD when fields missing"


Requirement: AbortSignal-aware async discovery

The package SHALL export discoverPackagesIter(options) returning an AsyncGenerator<PackageRecord> that honours options.signal. On abort the generator SHALL throw OVERVIEW.DISCOVERY_ABORTED.

Scenario: abort mid-walk

  • WHEN a controller is aborted between the first and second yielded record
  • THEN the second .next() rejects with OverviewError whose code is OVERVIEW.DISCOVERY_ABORTED

Verified by: src/discover/discover.test.ts::"discoverPackagesIter honours AbortSignal"


Requirement: Workspace graph utilities

The package SHALL export buildGraph(records), topologicalOrder(graph), detectCycles(graph), transitiveDependencies(graph, pkg), transitiveDependents(graph, pkg), leafPackages(graph), and rootPackages(graph). Graph edges SHALL only be recorded when both endpoints are nodes (orphan dep names are dropped). Topological order SHALL throw OVERVIEW.CYCLE_DETECTED carrying the offending cycle. Queries against unknown packages SHALL throw OVERVIEW.UNKNOWN_PACKAGE.

Scenario: topo on a linear chain

  • WHEN topologicalOrder runs over a → b → c
  • THEN the result is ["c", "b", "a"] (deps first)

Scenario: cycle detected

  • WHEN topologicalOrder runs over a → b → a
  • THEN an OverviewError with code === "OVERVIEW.CYCLE_DETECTED" is thrown whose context.cycle contains both a and b

Scenario: transitive closure

  • WHEN transitiveDependencies(graph, "a") runs over a → b → c, b → d
  • THEN the result is ["b", "c", "d"] sorted

Scenario: unknown package query

  • WHEN transitiveDependencies(graph, "missing") runs
  • THEN an OverviewError with code === "OVERVIEW.UNKNOWN_PACKAGE" is thrown

Verified by: src/graph/graph.test.ts::"topologicalOrder respects dependency direction" AND src/graph/graph.test.ts::"topologicalOrder throws CYCLE_DETECTED on a cycle" AND src/graph/graph.test.ts::"transitiveDependencies BFS closure" AND src/graph/graph.test.ts::"transitiveDependencies throws UNKNOWN_PACKAGE" AND src/graph/graph.test.ts::"leaves and roots on a star"


Requirement: Roll-up aggregates

The package SHALL export byPlugin(records), byCategory(records), and byTier(records). byPlugin SHALL return PluginSummary[] sorted by "<category>/<plugin>". byCategory SHALL return Record<string, number> of package counts. byTier SHALL return Record<Tier, PackageRecord[]> for all six tiers (empty arrays when absent).

Scenario: byPlugin aggregates counts and dep totals

  • WHEN byPlugin runs over three records across two (category, plugin) pairs with workspace-dep counts [2, 3, 1] and the two pairs have [2, 1] records respectively
  • THEN the summary rows carry the expected packages and workspaceDeps totals

Scenario: byTier covers all tiers

  • WHEN byTier runs over an empty list
  • THEN the returned record has every tier key with an empty array value

Verified by: src/rollups/rollups.test.ts::"byPlugin aggregates packages + workspace deps" AND src/rollups/rollups.test.ts::"byCategory counts packages" AND src/rollups/rollups.test.ts::"byTier covers every Tier key"


Requirement: OpenTelemetry instrumentation

The package SHALL emit three counters and one histogram via metrics.getMeter("@suaveplan/overview", "0.1.0"): overview.packages_discovered_total, overview.records_rejected_total{reason}, overview.graph_builds_total, and overview.discover_duration_ms. Counters and histograms SHALL increment / record during the normal API paths.

Scenario: counters move on discovery

  • WHEN an in-memory metric reader is installed and discoverPackages runs against a fixture
  • THEN overview.packages_discovered_total increases by records.length and overview.discover_duration_ms records one observation

Verified by: src/telemetry/telemetry.test.ts::"discoverPackages emits counter + histogram" AND src/telemetry/telemetry.test.ts::"buildGraph emits graph_builds_total"


Requirement: Tier purity at the root entry

The package root entry SHALL NOT import node:* modules. Node-only behaviour SHALL live exclusively under the ./discover sub-path export so universal-tier consumers (browser, R3F, edge) can import the root safely.

Scenario: root barrel has no node: imports

  • WHEN the test parses src/index.ts and every transitive import not under src/discover/
  • THEN none of the resolved files contain from "node: or require("node: literal strings

Verified by: src/discover/discover.test.ts::"root barrel imports no node:* modules"


Requirement: Co-located docs and 100% coverage

Every non-barrel, non-test, non-types .ts file under src/ SHALL have a sibling .md documentation file matching the required-section template. The package SHALL meet 100% statement, branch, function, and line coverage under Istanbul.

Scenario: docs:check passes

  • WHEN bun run docs:check --filter=@suaveplan/overview runs
  • THEN the exit code is 0

Scenario: coverage gate

  • WHEN bunx turbo test:coverage --filter=@suaveplan/overview runs
  • THEN the Istanbul report shows 100/100/100/100

Verified by: bunx turbo test:coverage --filter=@suaveplan/overview AND bun run docs:check --filter=@suaveplan/overview