Initial Commit
This commit is contained in:
166
openspec/changes/enterprise-wip/proposal.md
Normal file
166
openspec/changes/enterprise-wip/proposal.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# OpenSpec Change: enterprise-wip
|
||||
|
||||
- **Change ID**: enterprise-wip
|
||||
- **Package**: `@suaveplan/wip`
|
||||
- **Category**: universal/dev
|
||||
- **Target path**: `universal/dev/wip`
|
||||
- **Status**: PROPOSED
|
||||
- **Date**: 2026-04-24
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/wip.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/wip` on the genesis branch from scratch at enterprise quality. The package provides a work-in-progress experiments sandbox: a structured home for exploratory code, feature-flag-gated experiments, and proof-of-concept implementations that are not yet ready for a dedicated package. Genesis adds structured errors via `@suaveplan/error`, Zod schemas for experiment metadata, OTel instrumentation, `AbortSignal` on async experiments, and an `ExperimentRegistry` for tracking active experiments with lifecycle management (draft, active, graduated, abandoned).
|
||||
|
||||
---
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
The package does not exist on genesis yet — this is a fresh build.
|
||||
|
||||
---
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `@suaveplan/error` (`workspace:^`) | Base class for `WipError`; stable error codes |
|
||||
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock`, `EventBusLike` |
|
||||
| `@suaveplan/events` (`workspace:^`) | Event bus for experiment lifecycle events |
|
||||
| `@suaveplan/feature-flags` (`workspace:^`) | Feature-flag gating for experiments |
|
||||
| `@suaveplan/logger` (`workspace:^`) | Structured logging |
|
||||
| `zod` (`catalog:forms`) | Zod schemas for experiment metadata |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | Spans on experiment execution |
|
||||
| `tslib` (`catalog:build`) | Runtime helpers |
|
||||
|
||||
Dev deps: `vitest`, `@vitest/coverage-istanbul` (all `catalog:testing`).
|
||||
|
||||
---
|
||||
|
||||
## Public API surface
|
||||
|
||||
### Core
|
||||
|
||||
```ts
|
||||
function createExperimentRegistry(options?: ExperimentRegistryOptions): ExperimentRegistry
|
||||
|
||||
interface ExperimentRegistry {
|
||||
register(experiment: ExperimentDefinition): void
|
||||
get(id: string): ExperimentDefinition | null
|
||||
list(status?: ExperimentStatus): ReadonlyArray<ExperimentDefinition>
|
||||
run<T>(id: string, fn: ExperimentFn<T>, signal?: AbortSignal): Promise<ExperimentRun<T>>
|
||||
graduate(id: string, targetPackage: string): void
|
||||
abandon(id: string, reason: string): void
|
||||
}
|
||||
```
|
||||
|
||||
### Experiment definition
|
||||
|
||||
```ts
|
||||
interface ExperimentDefinition {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
author?: string
|
||||
createdAt?: number
|
||||
status: ExperimentStatus
|
||||
targetPackage?: string // where this will graduate to
|
||||
flagId?: string // optional feature flag gate
|
||||
tags?: string[]
|
||||
}
|
||||
|
||||
type ExperimentStatus = 'draft' | 'active' | 'graduated' | 'abandoned'
|
||||
|
||||
type ExperimentFn<T> = (signal?: AbortSignal) => Promise<T>
|
||||
|
||||
interface ExperimentRun<T> {
|
||||
readonly experimentId: string
|
||||
readonly result: T
|
||||
readonly durationMs: number
|
||||
readonly timestamp: number
|
||||
}
|
||||
|
||||
interface ExperimentRegistryOptions {
|
||||
logger?: LoggerLike
|
||||
clock?: Clock
|
||||
eventBus?: EventBusLike
|
||||
flagManager?: unknown // FlagManager from @suaveplan/feature-flags
|
||||
}
|
||||
```
|
||||
|
||||
### Utilities
|
||||
|
||||
```ts
|
||||
function wip<T>(description: string, fn: () => T): T
|
||||
function wipAsync<T>(description: string, fn: (signal?: AbortSignal) => Promise<T>, signal?: AbortSignal): Promise<T>
|
||||
function todo(message?: string): never
|
||||
function notImplemented(feature: string): never
|
||||
```
|
||||
|
||||
### Error codes
|
||||
|
||||
```ts
|
||||
type WipErrorCode =
|
||||
| 'WIP_EXPERIMENT_NOT_FOUND'
|
||||
| 'WIP_EXPERIMENT_NOT_ACTIVE'
|
||||
| 'WIP_FLAG_DISABLED'
|
||||
| 'WIP_NOT_IMPLEMENTED'
|
||||
| 'WIP_ABORT'
|
||||
|
||||
function registerWipErrorCodes(): void
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Genesis additions
|
||||
|
||||
1. **Structured `WipError`** extending `SuaveplanError` with stable `WIP.*` codes
|
||||
2. **`ExperimentRegistry`** — lifecycle management for WIP experiments
|
||||
3. **`wip()` / `wipAsync()` wrappers** — log a structured trace entry when entering WIP code; throw in production builds when `NODE_ENV === 'production'`
|
||||
4. **`todo()` / `notImplemented()` helpers** — throw `WipError(WIP_NOT_IMPLEMENTED)` with descriptive message, never silent
|
||||
5. **Feature-flag gating** — experiments can require a flag to be enabled before running
|
||||
6. **Injectable `Clock`** for experiment timestamps
|
||||
7. **OTel instrumentation** — span `wip.experiment.run`, counter `wip.experiments_run_total`
|
||||
8. **Zod schemas** for `ExperimentDefinition`, `ExperimentRegistryOptions`
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
universal/dev/wip/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
error-codes.ts
|
||||
constants/
|
||||
index.ts
|
||||
types/
|
||||
common.ts
|
||||
common.md
|
||||
core/
|
||||
registry/
|
||||
registry.ts
|
||||
registry.md
|
||||
registry.types.ts
|
||||
registry.test.ts
|
||||
index.ts
|
||||
utils/
|
||||
helpers.ts
|
||||
helpers.md
|
||||
helpers.test.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
**Downstream packages**: Developer tooling only. Should never be imported from production packages — the `wip()` helper throws in production builds as a safety guard.
|
||||
|
||||
**Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/feature-flags`, `@suaveplan/logger` must be published.
|
||||
101
openspec/changes/enterprise-wip/tasks.md
Normal file
101
openspec/changes/enterprise-wip/tasks.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Tasks: enterprise-wip
|
||||
|
||||
**Change ID**: enterprise-wip
|
||||
**Package**: `@suaveplan/wip`
|
||||
**Target path**: `universal/dev/wip`
|
||||
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/feature-flags`, `@suaveplan/logger`
|
||||
|
||||
---
|
||||
|
||||
## 0. Pre-flight (non-negotiable)
|
||||
|
||||
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
|
||||
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
|
||||
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [ ] Create `universal/dev/wip/` with `src/`, `tests/`, and feature subdirectories per the layout in `proposal.md`.
|
||||
- [ ] Write `package.json` at version `0.1.0`:
|
||||
- `"type": "module"`, `"sideEffects": false`
|
||||
- `exports` map: `"."` barrel + `"./core/registry/registry"`, `"./utils/helpers"`
|
||||
- `dependencies`: `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/events workspace:^`, `@suaveplan/feature-flags workspace:^`, `@suaveplan/logger workspace:^`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`
|
||||
- `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `@suaveplan/testing workspace:^`
|
||||
- [ ] Write `tsconfig.json` extending `@suaveplan/typescript-config/library.json`.
|
||||
- [ ] Write `vitest.config.ts` using `createTestConfig()`.
|
||||
- [ ] Add `lint`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check` scripts.
|
||||
|
||||
---
|
||||
|
||||
## 2. Types and error codes
|
||||
|
||||
- [ ] Define all public types in `src/types/common.ts`: `ExperimentDefinition`, `ExperimentStatus`, `ExperimentFn`, `ExperimentRun`, `ExperimentRegistryOptions`, `ExperimentRegistry`.
|
||||
- [ ] Write `src/types/common.md` (≥200 words, simple + advanced examples).
|
||||
- [ ] Define error codes in `src/error-codes.ts`: `WipErrorCode` union, metadata, `registerWipErrorCodes()`.
|
||||
- [ ] Write Zod schemas in `src/types/schemas.ts` + `src/types/schemas.md`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Helper utilities (`src/utils/helpers.ts`)
|
||||
|
||||
- [ ] Implement `wip<T>(description, fn)`:
|
||||
- In production (`process.env.NODE_ENV === 'production'`): throw `WipError(WIP_NOT_IMPLEMENTED)` immediately — WIP code must never reach production.
|
||||
- In development: log a `logger.warn` or `console.warn` with the description, then call `fn()`.
|
||||
- Return result of `fn()`.
|
||||
- [ ] Implement `wipAsync<T>(description, fn, signal?)`: async version; same production guard.
|
||||
- [ ] Implement `todo(message?)`: always throws `WipError(WIP_NOT_IMPLEMENTED)` with optional message. Return type is `never`.
|
||||
- [ ] Implement `notImplemented(feature)`: throws `WipError(WIP_NOT_IMPLEMENTED)` with feature name. Return type is `never`.
|
||||
- [ ] Write `helpers.test.ts` (≤500 lines): `wip` in dev executes fn, `wip` in prod throws, `wipAsync` in dev resolves, `todo` throws, `notImplemented` throws.
|
||||
- [ ] Write `helpers.md` (≥200 words).
|
||||
|
||||
---
|
||||
|
||||
## 4. Experiment registry (`src/core/registry/`)
|
||||
|
||||
- [ ] Implement `createExperimentRegistry(options?)`:
|
||||
- Stores experiments in `Map<string, ExperimentDefinition>`.
|
||||
- Validates options with Zod.
|
||||
- [ ] Implement `registry.register(experiment)`:
|
||||
- Validates with `ExperimentDefinitionSchema`.
|
||||
- Throws `WipError(WIP_EXPERIMENT_NOT_FOUND)` if ID already registered (idempotency: re-registering with same ID is a no-op).
|
||||
- [ ] Implement `registry.get(id)` — returns experiment or `null`.
|
||||
- [ ] Implement `registry.list(status?)` — returns experiments, optionally filtered by status.
|
||||
- [ ] Implement `registry.run<T>(id, fn, signal?)`:
|
||||
- Throws `WipError(WIP_EXPERIMENT_NOT_FOUND)` if experiment not registered.
|
||||
- Throws `WipError(WIP_EXPERIMENT_NOT_ACTIVE)` if experiment status is not `active`.
|
||||
- If `experiment.flagId` is set, check via `options.flagManager`; throw `WipError(WIP_FLAG_DISABLED)` if flag is off.
|
||||
- Calls `fn(signal)`, measures duration, returns `ExperimentRun<T>`.
|
||||
- OTel span `wip.experiment.run`, attribute `experiment.id`; counter `wip.experiments_run_total`.
|
||||
- [ ] Implement `registry.graduate(id, targetPackage)` — updates status to `graduated`, sets `targetPackage`.
|
||||
- [ ] Implement `registry.abandon(id, reason)` — updates status to `abandoned`.
|
||||
- [ ] Write `registry.test.ts` (≤500 lines): register, run, flag-gated run, graduate, abandon, status filtering.
|
||||
- [ ] Write `registry.md` (≥200 words).
|
||||
|
||||
---
|
||||
|
||||
## 5. Integration tests (`tests/integration.test.ts`)
|
||||
|
||||
- [ ] Register 3 experiments, run active one, verify result returned.
|
||||
- [ ] Graduate experiment, confirm it can no longer run (not active).
|
||||
- [ ] Abandon experiment, confirm it can no longer run.
|
||||
- [ ] Feature-flag-gated experiment: disabled flag → `WIP_FLAG_DISABLED`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Docs
|
||||
|
||||
- [ ] All per-file `.md` files: `common.md`, `schemas.md`, `helpers.md`, `registry.md`.
|
||||
- [ ] `universal/dev/wip/README.md`: purpose (development-only sandbox), installation, usage, WARNING about production guard, API section.
|
||||
|
||||
---
|
||||
|
||||
## 7. Verify and publish
|
||||
|
||||
- [ ] `bunx turbo lint --filter=@suaveplan/wip` — zero errors.
|
||||
- [ ] `bunx turbo typecheck --filter=@suaveplan/wip` — zero errors.
|
||||
- [ ] `bunx turbo test --filter=@suaveplan/wip` — all tests pass.
|
||||
- [ ] `bunx turbo test:coverage --filter=@suaveplan/wip` — 100% on all touched modules.
|
||||
- [ ] `bun run docs:check --filter=@suaveplan/wip` — zero errors.
|
||||
- [ ] `bun scripts/publish.ts --package @suaveplan/wip`.
|
||||
- [ ] Update `CLAUDE.md` package listing.
|
||||
- [ ] Archive: move `openspec/changes/enterprise-wip/` to `openspec/archive/2026-04-24-enterprise-wip/`.
|
||||
Reference in New Issue
Block a user