Initial Commit
This commit is contained in:
63
openspec/archive/2026-04-19-enterprise-diff/proposal.md
Normal file
63
openspec/archive/2026-04-19-enterprise-diff/proposal.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# OpenSpec Change: enterprise-diff
|
||||
|
||||
- **Change ID**: enterprise-diff
|
||||
- **Package**: `@suaveplan/diff`
|
||||
- **Category**: universal
|
||||
- **Target path**: `universal/primitives/diff`
|
||||
- **Status**: PROPOSED
|
||||
- **Date**: 2026-04-19
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/diff.md`
|
||||
|
||||
## Summary
|
||||
|
||||
Greenfield implementation of `@suaveplan/diff` for the genesis branch. Ships a zero-dependency RFC 6902 JSON Patch engine with structural diffing, patch application/inversion, a linear history manager (undo/redo with branch handling), a sync manager for optimistic concurrency, and a rich path utility module. The package is a leaf — no `@suaveplan/*` workspace deps — so it unblocks `@suaveplan/undo`, `@suaveplan/crdt`, and event-sourcing packages downstream.
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
Package does not exist on genesis yet — fresh build.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Workspace: none (leaf)
|
||||
- External (via catalog): `zod` → `catalog:forms` (for wire-format schemas); `tslib` → `catalog:build`; `@opentelemetry/api` → `catalog:observability`
|
||||
- Dev (via catalog): `vitest`, `@vitest/coverage-istanbul`, `fast-check`, `mitata` → `catalog:testing`
|
||||
|
||||
## Preserved public API surface (from main)
|
||||
|
||||
### Core
|
||||
- `diff(left, right, options?)` — structural diff producing an RFC 6902 `Patch`
|
||||
- `applyPatch(doc, patch, options?)` — returns `PatchResult` with transformed document
|
||||
- `revertPatch(doc, patch)` — reverts a previously applied patch
|
||||
- `invertPatch(patch)`, `invertOperation(op)`
|
||||
|
||||
### Operations
|
||||
- `createAdd`, `createCopy`, `createMove`, `createRemove`, `createReplace`, `createTest`
|
||||
- `operationsConflict(a, b)`, `validateOperation(op)`, `validatePatch(patch)`
|
||||
|
||||
### History / sync
|
||||
- `createHistory(options?)`, class `History` — linear undo/redo with branch truncation
|
||||
- `createSync(options?)`, class `SyncManager` — push/pull deltas with conflict resolution
|
||||
|
||||
### Path utilities
|
||||
- `parsePath`, `formatPath`, `appendPath`, `concatPaths`, `comparePaths`, `pathEquals`, `isPrefix`, `parentPath`, `lastSegment`, `escapeSegment`, `unescapeSegment`
|
||||
|
||||
### Utility functions
|
||||
- `deepClone`, `deepEqual`, `hashValue`, `stringify`, `isObject`, `isPrimitive`
|
||||
- `getValueAtPath`, `setValueAtPath`, `setValueAtPathMut`, `insertValueAtPath`, `insertValueAtPathMut`, `deleteValueAtPath`, `deleteValueAtPathMut`
|
||||
|
||||
### Types
|
||||
`AddOperation`, `CopyOperation`, `MoveOperation`, `RemoveOperation`, `ReplaceOperation`, `TestOperation`, `BaseOperation`, `Operation`, `OperationType`, `Patch`, `PatchOptions`, `PatchResult`, `PatchError`, `DiffOptions`, `ArrayStrategy`, `Path`, `PathSegment`, `JsonPrimitive`, `JsonValue`, `HistoryEntry`, `HistoryState`, `HistoryOptions`, `SyncDelta`, `SyncState`, `SyncListener`, `SyncOptions`
|
||||
|
||||
## New genesis additions
|
||||
|
||||
- Structured errors: `DiffError` base extends `SuaveplanError`; codes via `defineCodes` under `DIFF.*` (`DIFF.INVALID_PATCH`, `DIFF.TEST_FAILED`, `DIFF.PATH_INVALID`, `DIFF.APPLY_FAILED`, `DIFF.INVERT_FAILED`).
|
||||
- Zod schemas for every operation type, `Patch`, `PatchOptions`, `DiffOptions`, `HistoryState`, `SyncDelta` (`*.schemas.ts` siblings).
|
||||
- OTel counters `diff.apply.total`, `diff.apply.failed`, histogram `diff.apply.duration_ms`; `diff.diff.duration_ms` for diff computation.
|
||||
- Property-based tests (fast-check): `applyPatch(doc, diff(a, b)) === b`; `revertPatch(applyPatch(doc, p), p) === doc`; `invertPatch(invertPatch(p)) === p`.
|
||||
- mitata benchmarks for `diff`, `applyPatch`, `revertPatch`, `SyncManager.push`.
|
||||
|
||||
## Impact
|
||||
|
||||
- Adds one new package: `universal/primitives/diff`
|
||||
- Adds one new spec capability: `diff`
|
||||
- Unblocks Tier C: `undo`, `crdt`, `event-sourcing`
|
||||
@@ -0,0 +1,33 @@
|
||||
# Capability Spec: Diff
|
||||
|
||||
**Package**: `@suaveplan/diff`
|
||||
**Version**: 0.0.0 (genesis)
|
||||
**Status**: ADDED
|
||||
|
||||
---
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### DIFF-01 — RFC 6902 JSON Patch operations
|
||||
The package MUST implement all six RFC 6902 operation types (`add`, `remove`, `replace`, `move`, `copy`, `test`) with typed factories `createAdd`/`createRemove`/`createReplace`/`createMove`/`createCopy`/`createTest`. Operations MUST be validated via `validateOperation`; invalid shapes MUST throw `DiffError` with code `DIFF.INVALID_PATCH`.
|
||||
|
||||
### DIFF-02 — Structural diff round-trip
|
||||
`diff(a, b, options?)` MUST produce a `Patch` such that `applyPatch(a, diff(a, b)).doc` is structurally equal (`deepEqual`) to `b` for any JSON-serializable pair. `DiffOptions.arrayStrategy` MUST accept `'index'` (positional replacement) or `'lcs'` (longest-common-subsequence minimal edits).
|
||||
|
||||
### DIFF-03 — Apply / revert contract
|
||||
`applyPatch(doc, patch, options?)` MUST return `PatchResult { doc, applied, errors }` where `applied` is the count of successfully applied operations. When a `test` operation fails, the patch MUST abort and throw `DiffError` with code `DIFF.TEST_FAILED`, leaving `doc` unchanged. `revertPatch(doc, patch)` MUST be implemented such that `revertPatch(applyPatch(doc, p).doc, p)` equals `doc` for any patch that does not contain `copy` or `move` operations affecting removed data.
|
||||
|
||||
### DIFF-04 — Patch inversion
|
||||
`invertPatch(patch)` MUST produce a patch that, when applied to the result of the original, yields the original document. `invertOperation(op)` MUST satisfy `invertOperation(invertOperation(op))` deep-equals `op` for all operation types. Operations that cannot be losslessly inverted (`remove` without prior value capture) MUST throw `DiffError` with code `DIFF.INVERT_FAILED`.
|
||||
|
||||
### DIFF-05 — Path utilities
|
||||
`parsePath` and `formatPath` MUST implement RFC 6901 JSON Pointer grammar. `parsePath(formatPath(p))` MUST deep-equal `p` for every valid `Path`. Escape/unescape MUST handle `~0` (`~`) and `~1` (`/`). `isPrefix`, `pathEquals`, `parentPath`, `lastSegment` MUST be pure functions with zero allocation on the hot path where possible.
|
||||
|
||||
### DIFF-06 — History manager
|
||||
`History` MUST implement bounded linear undo/redo with branch truncation: pushing a new entry after an undo MUST discard all redo entries. Size limits MUST be configurable via `HistoryOptions.maxEntries`. `HistoryState` MUST be serializable via `JSON.stringify`. Listeners MUST be notified on every state transition.
|
||||
|
||||
### DIFF-07 — Sync manager
|
||||
`SyncManager` MUST accept local deltas via `push` and remote deltas via `pull`, emitting `SyncDelta` objects to registered listeners. Conflicts MUST be detected via `operationsConflict` and resolved per the configured `SyncOptions.conflictResolution` strategy (`'local-wins' | 'remote-wins' | 'reject'`).
|
||||
|
||||
### DIFF-08 — Observability
|
||||
Every `applyPatch` call MUST emit OTel counters `diff.apply.total` (labels: `outcome: 'success' | 'failure'`) and histogram `diff.apply.duration_ms`. Every `diff` call MUST emit `diff.diff.duration_ms`. Meters MUST use `@opentelemetry/api` and be no-ops when no exporter is registered.
|
||||
64
openspec/archive/2026-04-19-enterprise-diff/tasks.md
Normal file
64
openspec/archive/2026-04-19-enterprise-diff/tasks.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Tasks: enterprise-diff
|
||||
|
||||
## 1. Scaffold
|
||||
- [x] 1.1 Create `universal/primitives/diff/` with `package.json`, `tsconfig.json`, `tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`, `README.md` skeleton.
|
||||
- [x] 1.2 Register 100/100/100/100 coverage thresholds in `vitest.config.ts`; `provider: 'istanbul'`.
|
||||
- [x] 1.3 Cross-cutting guard tests in `tests/`: dep-graph, no-disallowed, tree-shake, file-size budget.
|
||||
- [x] 1.4 Add package to root `CLAUDE.md` listing, `README.md` table, `FOLDERS.yml`.
|
||||
|
||||
## 2. Errors + schemas
|
||||
- [x] 2.1 `src/errors/diff-error.ts` — `DiffError` extends `SuaveplanError`; `defineCodes` for `DIFF.INVALID_PATCH`, `DIFF.TEST_FAILED`, `DIFF.PATH_INVALID`, `DIFF.APPLY_FAILED`, `DIFF.INVERT_FAILED`.
|
||||
- [x] 2.2 Auto-register codes via side-effect import in `src/index.ts`.
|
||||
- [x] 2.3 Zod schemas: `OperationSchema`, `PatchSchema`, `PatchOptionsSchema`, `DiffOptionsSchema`, `HistoryStateSchema`, `SyncDeltaSchema` in `*.schemas.ts` siblings.
|
||||
- [x] 2.4 Tests: error construction, code narrowing, `{ path?, operation?, reason? }` context fields.
|
||||
|
||||
## 3. Path module
|
||||
- [x] 3.1 `src/path/path.ts` — `parsePath`, `formatPath`, `appendPath`, `concatPaths`, `comparePaths`, `pathEquals`, `isPrefix`, `parentPath`, `lastSegment`, `escapeSegment`, `unescapeSegment`.
|
||||
- [x] 3.2 Co-located `src/path/path.md`.
|
||||
- [x] 3.3 Unit tests + property tests: `parsePath(formatPath(p)) === p`; escape round-trip.
|
||||
|
||||
## 4. Utils module
|
||||
- [x] 4.1 `src/utils/utils.ts` — `deepClone`, `deepEqual`, `hashValue`, `stringify`, `isObject`, `isPrimitive`, `getValueAtPath`, `setValueAtPath`, `setValueAtPathMut`, `insertValueAtPath`, `insertValueAtPathMut`, `deleteValueAtPath`, `deleteValueAtPathMut`.
|
||||
- [x] 4.2 Co-located `src/utils/utils.md`.
|
||||
- [x] 4.3 Unit tests covering edge cases: root path, array index beyond length, circular refs.
|
||||
|
||||
## 5. Operations
|
||||
- [x] 5.1 `src/operations/operations.ts` — `createAdd`, `createCopy`, `createMove`, `createRemove`, `createReplace`, `createTest`, `invertOperation`, `invertPatch`, `operationsConflict`, `validateOperation`, `validatePatch`.
|
||||
- [x] 5.2 Co-located `src/operations/operations.md`.
|
||||
- [x] 5.3 Unit tests for each factory + invert round-trip + conflict detection.
|
||||
|
||||
## 6. Diff algorithm
|
||||
- [x] 6.1 `src/diff/diff.ts` — structural diff with configurable `ArrayStrategy` (`'index' | 'lcs'`).
|
||||
- [x] 6.2 Co-located `src/diff/diff.md`.
|
||||
- [x] 6.3 Property tests: `applyPatch(doc, diff(a, b)) === b`.
|
||||
|
||||
## 7. Patch apply/revert
|
||||
- [x] 7.1 `src/patch/patch.ts` — `applyPatch`, `revertPatch`; OTel counters + histogram.
|
||||
- [x] 7.2 Co-located `src/patch/patch.md`.
|
||||
- [x] 7.3 Unit tests: each operation type, failure modes, `test` op semantics; `PatchResult` shape.
|
||||
|
||||
## 8. History
|
||||
- [x] 8.1 `src/history/history.ts` — `History` class with `push`, `undo`, `redo`, branch truncation; `createHistory` factory.
|
||||
- [x] 8.2 Co-located `src/history/history.md`.
|
||||
- [x] 8.3 Unit tests: linear undo/redo, branch-truncation on push, bounded size, listener notifications.
|
||||
|
||||
## 9. Sync manager
|
||||
- [x] 9.1 `src/sync/sync.ts` — `SyncManager` class; `createSync` factory; delta push/pull; optimistic rebasing.
|
||||
- [x] 9.2 Co-located `src/sync/sync.md`.
|
||||
- [x] 9.3 Unit tests: concurrent deltas, conflict resolution, listener notifications.
|
||||
|
||||
## 10. Documentation + index
|
||||
- [x] 10.1 Public `src/index.ts` — re-exports only; no side effects besides code-registry import.
|
||||
- [x] 10.2 Subpath exports in `package.json` per feature module.
|
||||
- [x] 10.3 Root `README.md` with Install / Quick-start / Modules links.
|
||||
|
||||
## 11. Benchmarks
|
||||
- [x] 11.1 `src/__benchmarks__/diff.bench.ts`, `apply.bench.ts`, `revert.bench.ts`, `sync-push.bench.ts`.
|
||||
- [x] 11.2 Publish baseline throughput numbers in README.
|
||||
|
||||
## 12. Ship gate
|
||||
- [x] 12.1 `bunx turbo lint typecheck test --filter=@suaveplan/diff` — green.
|
||||
- [x] 12.2 `bunx turbo test:coverage --filter=@suaveplan/diff` — 100/100/100/100.
|
||||
- [x] 12.3 `bunx turbo build --filter=@suaveplan/diff` — green.
|
||||
- [x] 12.4 Banned-pattern scan + missing-`.md` scan return zero output.
|
||||
- [x] 12.5 Fold spec delta into `openspec/specs/diff/spec.md`; archive change folder.
|
||||
Reference in New Issue
Block a user