diff --git a/docs/design/0036-warp-drift-ledger-crosslinks.md b/docs/design/0036-warp-drift-ledger-crosslinks.md index a4a0d3d57..e776b5fc1 100644 --- a/docs/design/0036-warp-drift-ledger-crosslinks.md +++ b/docs/design/0036-warp-drift-ledger-crosslinks.md @@ -165,10 +165,12 @@ The witness for this cycle is small and explicit: - `docs/design/release-horizon-v20-v21.md` - the audit now says directly that it is the drift ledger rather than the canonical wall-chart -- the ratchet test exists at +- at cycle close, the ratchet coverage existed at `test/unit/scripts/warp-drift-crosslinks-shape.test.ts` +- current successor coverage lives at + `test/unit/scripts/warp-drift-doc-graph.test.ts` -Verification command: +Historical verification command at cycle close: ```sh npm exec vitest run \ @@ -177,6 +179,15 @@ npm exec vitest run \ test/unit/scripts/observer-geometry-ladder-shape.test.ts ``` +Current successor command: + +```sh +npm exec vitest run \ + test/unit/scripts/warp-drift-doc-graph.test.ts \ + test/unit/scripts/glossary-shape.test.ts \ + test/unit/scripts/observer-geometry-ladder-shape.test.ts +``` + ### Agent playback Question: diff --git a/docs/design/0037-remaining-warp-drift-release-slotting.md b/docs/design/0037-remaining-warp-drift-release-slotting.md index b20ef78c4..a76cc3266 100644 --- a/docs/design/0037-remaining-warp-drift-release-slotting.md +++ b/docs/design/0037-remaining-warp-drift-release-slotting.md @@ -269,9 +269,12 @@ The witness for this cycle is the alignment across four surfaces: Ratchet test: -- `test/unit/scripts/warp-drift-release-slotting-shape.test.ts` +- at cycle close, `test/unit/scripts/warp-drift-release-slotting-shape.test.ts` + and `test/unit/scripts/warp-drift-crosslinks-shape.test.ts` +- current successor coverage lives at + `test/unit/scripts/warp-drift-doc-graph.test.ts` -Verification command: +Historical verification command at cycle close: ```sh npm exec vitest run \ @@ -281,6 +284,15 @@ npm exec vitest run \ test/unit/scripts/observer-geometry-ladder-shape.test.ts ``` +Current successor command: + +```sh +npm exec vitest run \ + test/unit/scripts/warp-drift-doc-graph.test.ts \ + test/unit/scripts/glossary-shape.test.ts \ + test/unit/scripts/observer-geometry-ladder-shape.test.ts +``` + ### Agent playback Question: diff --git a/docs/method/retro/0036-warp-drift-ledger-crosslinks/warp-drift-ledger-crosslinks.md b/docs/method/retro/0036-warp-drift-ledger-crosslinks/warp-drift-ledger-crosslinks.md index 846e9f4b1..6b5f94478 100644 --- a/docs/method/retro/0036-warp-drift-ledger-crosslinks/warp-drift-ledger-crosslinks.md +++ b/docs/method/retro/0036-warp-drift-ledger-crosslinks/warp-drift-ledger-crosslinks.md @@ -48,19 +48,27 @@ The cycle succeeded because it kept those responsibilities distinct. ### The crosslink contract is now ratcheted The cycle added -[warp-drift-crosslinks-shape.test.ts](../../../../test/unit/scripts/warp-drift-crosslinks-shape.test.ts) -so the repo will fail loudly if those crosslinks disappear later. +`test/unit/scripts/warp-drift-crosslinks-shape.test.ts` so the repo would fail +loudly if those crosslinks disappeared later. + +That historical ratchet has since been retired. Current successor coverage lives +at +[warp-drift-doc-graph.test.ts](../../../../test/unit/scripts/warp-drift-doc-graph.test.ts). For a small docs hygiene slice, that is the right outcome: the fix is now structural instead of purely editorial. ## Verification -Passed: +Passed at cycle close: - `git diff --check` - `npm exec vitest run test/unit/scripts/warp-drift-crosslinks-shape.test.ts test/unit/scripts/glossary-shape.test.ts test/unit/scripts/observer-geometry-ladder-shape.test.ts` +Current successor coverage: + +- `npm exec vitest run test/unit/scripts/warp-drift-doc-graph.test.ts test/unit/scripts/glossary-shape.test.ts test/unit/scripts/observer-geometry-ladder-shape.test.ts` + Key witness commits: - `bf316356` — `docs(design): pull warp drift crosslink cycle` diff --git a/docs/method/retro/0037-remaining-warp-drift-release-slotting/remaining-warp-drift-release-slotting.md b/docs/method/retro/0037-remaining-warp-drift-release-slotting/remaining-warp-drift-release-slotting.md index b151bc1a6..7f5b7a473 100644 --- a/docs/method/retro/0037-remaining-warp-drift-release-slotting/remaining-warp-drift-release-slotting.md +++ b/docs/method/retro/0037-remaining-warp-drift-release-slotting/remaining-warp-drift-release-slotting.md @@ -61,11 +61,15 @@ That keeps `v19` from silently inflating into “everything after `v18`.” ## Verification -Passed: +Passed at cycle close: - `git diff --check` - `npm exec vitest run test/unit/scripts/warp-drift-release-slotting-shape.test.ts test/unit/scripts/warp-drift-crosslinks-shape.test.ts test/unit/scripts/glossary-shape.test.ts test/unit/scripts/observer-geometry-ladder-shape.test.ts` +Current successor coverage: + +- `npm exec vitest run test/unit/scripts/warp-drift-doc-graph.test.ts test/unit/scripts/glossary-shape.test.ts test/unit/scripts/observer-geometry-ladder-shape.test.ts` + Key witness commits: - `10387c11` — `docs(design): pull remaining warp drift slotting cycle` diff --git a/docs/method/retro/0076-delete-warpcore-runtime-bridge/delete-warpcore-runtime-bridge.md b/docs/method/retro/0076-delete-warpcore-runtime-bridge/delete-warpcore-runtime-bridge.md index 12d3716ca..b2b163129 100644 --- a/docs/method/retro/0076-delete-warpcore-runtime-bridge/delete-warpcore-runtime-bridge.md +++ b/docs/method/retro/0076-delete-warpcore-runtime-bridge/delete-warpcore-runtime-bridge.md @@ -12,11 +12,14 @@ so `WarpCore.open()` now adopts the explicit runtime product surface instead of linking onto the runtime bridge - updated the closeout and composition-root ratchets: - - [warpcore-runtime-bridge.test.ts](../../../../test/unit/scripts/warpcore-runtime-bridge.test.ts) + - `test/unit/scripts/warpcore-runtime-bridge.test.ts` - [openwarpgraph-composition-root.test.ts](../../../../test/unit/scripts/openwarpgraph-composition-root.test.ts) - advanced the runtime-kill chain so the remaining order is now: `API_delete-warpruntime-class` → `API_kill-warpruntime` +Current successor coverage for the retired closeout ratchet lives in +[publicReadingSurface.behavior.test.ts](../../../../test/unit/domain/publicReadingSurface.behavior.test.ts). + ## Why it mattered This removes the last `WarpCore` dependency on the bridge-era runtime aliasing @@ -25,6 +28,12 @@ the runtime class to masquerade as its substrate. ## Witness +Historical witness at cycle close: + - `npm exec vitest run test/unit/scripts/warpcore-runtime-bridge.test.ts test/unit/scripts/openwarpgraph-composition-root.test.ts test/unit/domain/WarpCore.content.test.ts test/unit/domain/WarpCore.effectPipeline.test.ts test/unit/domain/WarpCore.emit.test.ts test/unit/domain/WarpGraph.strands.test.ts test/unit/domain/WarpGraph.conflicts.test.ts test/unit/domain/WarpGraph.worldline.test.ts test/unit/domain/WarpGraph.observerBoundary.test.ts test/unit/scripts/kill-warpruntime-split.test.ts` - `npm run typecheck` - `git diff --check` + +Current successor witness: + +- `npm exec vitest run test/unit/domain/publicReadingSurface.behavior.test.ts test/unit/scripts/openwarpgraph-composition-root.test.ts` diff --git a/test/unit/domain/publicReadingSurface.behavior.test.ts b/test/unit/domain/publicReadingSurface.behavior.test.ts new file mode 100644 index 000000000..1510b390d --- /dev/null +++ b/test/unit/domain/publicReadingSurface.behavior.test.ts @@ -0,0 +1,106 @@ +import { TextDecoder } from 'node:util'; +import { describe, expect, it } from 'vitest'; +import WarpAppDefault, { + InMemoryGraphAdapter, + openWarpGraph, + openWarpWorldline, + WarpApp, + WarpCore, +} from '../../../index.ts'; + +function openOptions(graphName: string, writerId: string): Parameters[0] { + return { + persistence: new InMemoryGraphAdapter(), + graphName, + writerId, + }; +} + +describe('public reading surfaces', () => { + it('opens WarpGraph as a capability bag without public materialization or runtime escapes', async () => { + const graph = await openWarpGraph(openOptions('public-reading-graph', 'writer-graph')); + + expect(Object.isFrozen(graph)).toBe(true); + expect(graph.revelation.query).toBe(graph.query); + expect(graph.commitment.patches).toBe(graph.patches); + expect(typeof graph.query.worldline).toBe('function'); + expect(typeof graph.query.observer).toBe('function'); + expect(typeof graph.sync.syncWith).toBe('function'); + expect('materialize' in graph).toBe(false); + expect('_materializeGraph' in graph).toBe(false); + expect('_runtime' in graph).toBe(false); + + await (await graph.patches.createPatch()).addNode('node:capability').commit(); + + await expect(graph.query.hasNode('node:capability')).rejects.toMatchObject({ + code: 'E_NO_STATE', + }); + }); + + it('opens a worldline-first handle that commits and reads through live worldline objects', async () => { + const worldline = await openWarpWorldline({ + persistence: new InMemoryGraphAdapter(), + worldlineName: 'public-worldline', + writerId: 'writer-worldline', + }); + + await worldline.commit((patch) => { + patch.addNode('user:alice'); + patch.setProperty('user:alice', 'role', 'admin'); + }); + + expect('materialize' in worldline).toBe(false); + expect('materializeCoordinate' in worldline).toBe(false); + expect('_materializeGraph' in worldline).toBe(false); + await expect(worldline.live().hasNode('user:alice')).resolves.toBe(true); + + const result = await worldline.live().query().match('user:*').select(['id', 'props']).run(); + expect('nodes' in result).toBe(true); + if (!('nodes' in result)) { + return; + } + expect(result.nodes).toEqual([ + { id: 'user:alice', props: { role: 'admin' } }, + ]); + }); + + it('keeps WarpApp curated while WarpCore remains the explicit materialization escape hatch', async () => { + const app = await WarpApp.open(openOptions('public-reading-app', 'writer-app')); + + await app.patch(async (patch) => { + patch.addNode('doc:readme'); + await patch.attachContent('doc:readme', 'hello from the app facade', { + mime: 'text/plain', + }); + }); + + expect(WarpAppDefault).toBe(WarpApp); + expect(app).toBeInstanceOf(WarpApp); + expect(app.core()).toBeInstanceOf(WarpCore); + expect('materialize' in app).toBe(false); + expect('query' in app).toBe(false); + expect('traverse' in app).toBe(false); + + await app.core().materialize(); + const content = await app.getContent('doc:readme'); + expect(content).toBeInstanceOf(Uint8Array); + expect(new TextDecoder().decode(content ?? new Uint8Array())).toBe('hello from the app facade'); + }); + + it('keeps WarpCore as the explicit substrate escape hatch with representative writes and reads', async () => { + const core = await WarpCore.open(openOptions('public-reading-core', 'writer-core')); + + await core.patch((patch) => { + patch.addNode('core:node'); + patch.setProperty('core:node', 'status', 'open'); + }); + await core.materialize(); + + expect(core).toBeInstanceOf(WarpCore); + expect(typeof core.materialize).toBe('function'); + expect(typeof core.createPatch).toBe('function'); + expect(typeof core.syncWith).toBe('function'); + await expect(core.hasNode('core:node')).resolves.toBe(true); + await expect(core.getNodeProps('core:node')).resolves.toEqual({ status: 'open' }); + }); +}); diff --git a/test/unit/scripts/v17-migration-script-behavior.test.ts b/test/unit/scripts/v17-migration-script-behavior.test.ts new file mode 100644 index 000000000..34dd4647a --- /dev/null +++ b/test/unit/scripts/v17-migration-script-behavior.test.ts @@ -0,0 +1,131 @@ +import { execFile, type ExecFileException } from 'node:child_process'; +import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join, relative } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; +import { walkMigrationFiles } from '../../../scripts/migrations/v17.0.0/MigrationFileWalker.ts'; + +const REPO_ROOT = fileURLToPath(new URL('../../../', import.meta.url)); + +type ScriptResult = { + readonly exitCode: number; + readonly stdout: string; + readonly stderr: string; +}; + +class MigrationFixture { + readonly samplePath: string; + + constructor(readonly root: string) { + this.samplePath = join(root, 'src', 'sample.ts'); + } + + async sampleSource(): Promise { + return await readFile(this.samplePath, 'utf8'); + } +} + +async function withMigrationFixture( + test: (fixture: MigrationFixture) => Promise, +): Promise { + const root = await mkdtemp(join(tmpdir(), 'warp-v17-migration-')); + try { + await mkdir(join(root, 'src', 'nested'), { recursive: true }); + await mkdir(join(root, 'node_modules', 'ignored'), { recursive: true }); + await mkdir(join(root, '.git', 'ignored'), { recursive: true }); + await writeFile( + join(root, 'src', 'sample.ts'), + [ + "import { PatchBuilderV2, PatchV2, Lens } from '@git-stunts/git-warp/services/PatchBuilderV2.js';", + 'const patch: PatchV2 = new PatchBuilderV2();', + 'function useAperture(aperture: Lens): Lens { return aperture; }', + 'void patch;', + 'void useAperture;', + '', + ].join('\n'), + 'utf8', + ); + await writeFile(join(root, 'src', 'nested', 'extra.js'), 'const ok = true;\n', 'utf8'); + await writeFile(join(root, 'README.md'), 'PatchV2 should not be scanned here.\n', 'utf8'); + await writeFile(join(root, 'node_modules', 'ignored', 'package.ts'), 'const stale: PatchV2 = null;\n', 'utf8'); + await writeFile(join(root, '.git', 'ignored', 'hook.ts'), 'const stale: PatchV2 = null;\n', 'utf8'); + + await test(new MigrationFixture(root)); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +function runMigrationScript(scriptPath: string, args: readonly string[]): Promise { + return new Promise((resolve) => { + execFile( + process.execPath, + [join(REPO_ROOT, scriptPath), ...args], + { cwd: REPO_ROOT, encoding: 'utf8' }, + (error: ExecFileException | null, stdout: string, stderr: string) => { + const exitCode = typeof error?.code === 'number' ? error.code : 0; + resolve({ exitCode, stdout, stderr }); + }, + ); + }); +} + +describe('v17 migration script behavior', () => { + it('walks migration source files while skipping markdown, node_modules, and git internals', async () => { + await withMigrationFixture(async (fixture) => { + const visited: string[] = []; + + for await (const filePath of walkMigrationFiles(fixture.root)) { + visited.push(relative(fixture.root, filePath)); + } + + expect(visited.sort()).toEqual([ + 'src/nested/extra.js', + 'src/sample.ts', + ]); + }); + }); + + it('reports stale v16 APIs, rewrites them, and verifies the migrated fixture', async () => { + await withMigrationFixture(async (fixture) => { + const stale = await runMigrationScript('scripts/migrations/v17.0.0/verify.ts', [ + '--dir', + fixture.root, + ]); + expect(stale.exitCode).toBe(1); + expect(stale.stdout).toContain('PatchBuilderV2.js renamed to PatchBuilder.ts'); + expect(stale.stdout).toContain('PatchV2 renamed to Patch'); + + const imports = await runMigrationScript('scripts/migrations/v17.0.0/fix-imports.ts', [ + '--dir', + fixture.root, + ]); + expect(imports).toMatchObject({ exitCode: 0, stderr: '' }); + expect(imports.stdout).toContain('1 imports updated across 1 files'); + + const renames = await runMigrationScript('scripts/migrations/v17.0.0/fix-renames.ts', [ + '--dir', + fixture.root, + ]); + expect(renames).toMatchObject({ exitCode: 0, stderr: '' }); + expect(renames.stdout).toContain('3 renames across 1 files'); + + const migrated = await fixture.sampleSource(); + expect(migrated).toContain("from '@git-stunts/git-warp/services/PatchBuilder.ts'"); + expect(migrated).toContain('PatchBuilder'); + expect(migrated).toContain('Patch'); + expect(migrated).toContain('Aperture'); + expect(migrated).not.toContain('PatchBuilderV2'); + expect(migrated).not.toContain('PatchV2'); + expect(migrated).not.toContain('Lens'); + + const clean = await runMigrationScript('scripts/migrations/v17.0.0/verify.ts', [ + '--dir', + fixture.root, + ]); + expect(clean).toMatchObject({ exitCode: 0, stderr: '' }); + expect(clean.stdout).toContain('No v16 migration issues found'); + }); + }); +}); diff --git a/test/unit/scripts/v17-migration-script-hygiene.test.ts b/test/unit/scripts/v17-migration-script-hygiene.test.ts deleted file mode 100644 index cc4cb8ef4..000000000 --- a/test/unit/scripts/v17-migration-script-hygiene.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { describe, expect, it } from 'vitest'; - -const repoRoot = fileURLToPath(new URL('../../../', import.meta.url)); - -function readRepoFile(relativePath: string): string { - return readFileSync(`${repoRoot}${relativePath}`, 'utf8'); -} - -const migrationScriptPaths = [ - 'scripts/migrations/v17.0.0/fix-imports.ts', - 'scripts/migrations/v17.0.0/fix-renames.ts', - 'scripts/migrations/v17.0.0/verify.ts', -] as const; - -describe('v17 migration script hygiene', () => { - it('uses custom script errors instead of raw Error construction in touched release scripts', () => { - const paths = [ - ...migrationScriptPaths, - 'scripts/smoke-packed-artifact.sh', - ] as const; - - for (const path of paths) { - expect(readRepoFile(path), path).not.toContain('new Error('); - } - }); - - it('shares one migration file walker instead of duplicating recursive traversal', () => { - for (const path of migrationScriptPaths) { - const source = readRepoFile(path); - - expect(source, path).toContain("from './MigrationFileWalker.ts'"); - expect(source, path).not.toContain('async function* walkFiles'); - } - - expect(readRepoFile('scripts/migrations/v17.0.0/MigrationFileWalker.ts')).toContain( - 'export async function* walkMigrationFiles', - ); - }); -}); diff --git a/test/unit/scripts/v17-public-reading-surface.test.ts b/test/unit/scripts/v17-public-reading-surface.test.ts deleted file mode 100644 index 4fa70446a..000000000 --- a/test/unit/scripts/v17-public-reading-surface.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { describe, expect, it } from 'vitest'; - -const repoRoot = fileURLToPath(new URL('../../../', import.meta.url)); - -function readRepoFile(relativePath: string): string { - return readFileSync(`${repoRoot}${relativePath}`, 'utf8'); -} - -describe('v17 public reading surface', () => { - it('keeps openWarpGraph focused on readings instead of materialization', () => { - const warpGraphSource = readRepoFile('src/domain/WarpGraph.ts'); - const runtimeProductSource = readRepoFile('src/domain/warp/RuntimeHostProduct.ts'); - const runtimeBridgeSource = readRepoFile('src/domain/warp/WarpGraphRuntimeProduct.ts'); - - expect(warpGraphSource).not.toContain('MaterializeCapability'); - expect(warpGraphSource).not.toContain('bindMaterializeCapability'); - expect(warpGraphSource).not.toMatch(/\breadonly materialize:/u); - expect(warpGraphSource).not.toMatch(/\bfolding:\s*Object\.freeze\(\{\s*materialize/u); - expect(runtimeProductSource).not.toContain('MaterializeCapability'); - expect(runtimeBridgeSource).not.toMatch(/\bmaterialize(?:Coordinate|At)?\s*:/u); - }); - - it('documents the v17 migration as optic and query readings, not graph materialization', () => { - const migrationGuide = readRepoFile('docs/migrations/v17.0.0.md'); - const apiReference = readRepoFile('docs/API_REFERENCE.md'); - - expect(migrationGuide).toContain('graph.query'); - expect(migrationGuide).toContain('worldline'); - expect(migrationGuide).not.toContain('graph.materialize'); - expect(migrationGuide).not.toContain('SnapshotWarpState'); - expect(apiReference).not.toContain('| `graph.materialize` |'); - expect(apiReference).not.toContain('graph.materialize.materialize'); - }); -}); diff --git a/test/unit/scripts/v17-worldline-reading-surface.test.ts b/test/unit/scripts/v17-worldline-reading-surface.test.ts deleted file mode 100644 index 7f87ea772..000000000 --- a/test/unit/scripts/v17-worldline-reading-surface.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { describe, expect, it } from 'vitest'; - -const repoRoot = fileURLToPath(new URL('../../../', import.meta.url)); - -function readRepoFile(relativePath: string): string { - return readFileSync(`${repoRoot}${relativePath}`, 'utf8'); -} - -describe('v17 worldline reading surface', () => { - it('does not expose a public Worldline.materialize read path', () => { - const worldlineSource = readRepoFile('src/domain/services/Worldline.ts'); - const apiReference = readRepoFile('docs/API_REFERENCE.md'); - const migrationGuide = readRepoFile('docs/migrations/v17.0.0.md'); - - expect(worldlineSource).not.toMatch(/\basync\s+materialize\b/u); - expect(worldlineSource).not.toMatch(/\bmaterializeSource\b/u); - expect(apiReference).not.toContain('Worldline.materialize()'); - expect(migrationGuide).not.toContain('worldline.materialize'); - }); - -}); diff --git a/test/unit/scripts/v7-dead-file-guard.test.ts b/test/unit/scripts/v7-dead-file-guard.test.ts new file mode 100644 index 000000000..369f4def3 --- /dev/null +++ b/test/unit/scripts/v7-dead-file-guard.test.ts @@ -0,0 +1,44 @@ +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const REPO_ROOT = fileURLToPath(new URL('../../../', import.meta.url)); + +type DeadFileGuard = { + readonly path: string; + readonly reason: string; +}; + +const deletedV7Artifacts: readonly DeadFileGuard[] = Object.freeze([ + { + path: 'src/domain/services/Reducer.js', + reason: 'schema:1 LWW reducer replaced by schema:2 join reduction', + }, + { + path: 'src/domain/services/StateSerializer.js', + reason: 'schema:1 state serialization replaced by current state model', + }, + { + path: 'src/domain/EmptyGraphWrapper.js', + reason: 'legacy commit-per-node wrapper replaced by WarpCore', + }, + { + path: 'src/domain/services/GraphService.js', + reason: 'legacy commit-per-node engine replaced by WarpCore', + }, + { + path: 'src/domain/legacy', + reason: 'legacy module directory must not return', + }, +]); + +describe('V7 dead-file deletion guard', () => { + for (const artifact of deletedV7Artifacts) { + it(`keeps ${artifact.path} deleted`, () => { + const fullPath = join(REPO_ROOT, artifact.path); + + expect(existsSync(fullPath), artifact.reason).toBe(false); + }); + } +}); diff --git a/test/unit/scripts/warp-drift-crosslinks-shape.test.ts b/test/unit/scripts/warp-drift-crosslinks-shape.test.ts deleted file mode 100644 index bb6acfa72..000000000 --- a/test/unit/scripts/warp-drift-crosslinks-shape.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { describe, expect, it } from 'vitest'; - -function readDoc(relativePath: string): string { - return readFileSync(fileURLToPath(new URL(`../../../${relativePath}`, import.meta.url)), 'utf8'); -} - -const driftAudit = readDoc('docs/audits/WARP_DRIFT.md'); - -describe('WARP drift ledger crosslinks', () => { - it('keeps the audit as a drift ledger while pointing at the canonical noun and ladder docs', () => { - expect(driftAudit).toContain('This file is the **drift ledger**, not the canonical noun wall-chart or'); - expect(driftAudit).toContain('- [GLOSSARY](../GLOSSARY.md)'); - expect(driftAudit).toContain('- [observer-geometry-architecture-ladder](../design/0035-observer-geometry-architecture-ladder.md)'); - expect(driftAudit).toContain('- [release-horizon-v20-v21](../design/release-horizon-v20-v21.md)'); - }); - - it('references the new wall-chart artifacts in backlog capture status and relevant design context', () => { - expect(driftAudit).toContain('The canonical noun and runtime-planning surfaces for this drift now live in:'); - expect(driftAudit).toContain('## Relevant design context'); - expect(driftAudit).toContain('- [GLOSSARY](../GLOSSARY.md)'); - expect(driftAudit).toContain('- [observer-geometry-architecture-ladder](../design/0035-observer-geometry-architecture-ladder.md)'); - expect(driftAudit).toContain('- [release-horizon-v20-v21](../design/release-horizon-v20-v21.md)'); - }); -}); diff --git a/test/unit/scripts/warp-drift-doc-graph.test.ts b/test/unit/scripts/warp-drift-doc-graph.test.ts new file mode 100644 index 000000000..4aa2bbd77 --- /dev/null +++ b/test/unit/scripts/warp-drift-doc-graph.test.ts @@ -0,0 +1,108 @@ +import { existsSync } from 'node:fs'; +import { dirname, join, normalize } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; +import MarkdownDocument from '../../helpers/MarkdownDocument.ts'; + +const REPO_ROOT = fileURLToPath(new URL('../../../', import.meta.url)); + +type SlottingRow = { + readonly driftArea: string; + readonly releaseHome: string; +}; + +function readMarkdownDoc(relativePath: string): MarkdownDocument { + return MarkdownDocument.fromFile(join(REPO_ROOT, relativePath)); +} + +function readDoc(relativePath: string): string { + return readMarkdownDoc(relativePath).text; +} + +function resolveDocLink(sourcePath: string, href: string): string { + return normalize(join(dirname(sourcePath), href)); +} + +function linkTargets(sourcePath: string): Set { + return new Set( + readMarkdownDoc(sourcePath) + .links() + .filter((link) => !link.target.startsWith('http')) + .map((link) => resolveDocLink(sourcePath, link.target)), + ); +} + +function parseSlottingRows(markdown: string): readonly SlottingRow[] { + const rows: SlottingRow[] = []; + let inMatrix = false; + + for (const line of markdown.split('\n')) { + if (line.trim() === '## Slotting matrix') { + inMatrix = true; + continue; + } + if (inMatrix && line.startsWith('## ')) { + break; + } + if (!inMatrix || !line.startsWith('|')) { + continue; + } + const cells = line.split('|').slice(1, -1).map((cell) => cell.trim()); + if (cells.length !== 3 || cells[0] === 'Drift area' || cells[0]?.startsWith('---')) { + continue; + } + const driftArea = cells[0]; + const releaseHome = cells[1]; + if (driftArea !== undefined && releaseHome !== undefined) { + rows.push({ driftArea, releaseHome: releaseHome.replaceAll('`', '') }); + } + } + + return rows; +} + +describe('WARP drift documentation graph', () => { + it('resolves the drift ledger links to canonical noun, ladder, slotting, and horizon docs', () => { + const sourcePath = 'docs/audits/WARP_DRIFT.md'; + const targets = linkTargets(sourcePath); + const requiredTargets = [ + 'docs/GLOSSARY.md', + 'docs/design/0035-observer-geometry-architecture-ladder.md', + 'docs/design/0037-remaining-warp-drift-release-slotting.md', + 'docs/design/release-horizon-v20-v21.md', + ] as const; + + for (const target of requiredTargets) { + expect(targets.has(target), target).toBe(true); + expect(existsSync(join(REPO_ROOT, target)), target).toBe(true); + } + }); + + it('keeps release slotting as structured v19, v20, and v21 rows', () => { + const rows = parseSlottingRows(readDoc('docs/design/0037-remaining-warp-drift-release-slotting.md')); + + expect(rows).toEqual([ + { driftArea: 'Observer surface still snapshot/materialize/filter', releaseHome: 'v19' }, + { driftArea: 'Public noun split only partially realized in code', releaseHome: 'v19' }, + { driftArea: 'Slice-first runtime realization and fragment reuse', releaseHome: 'v20' }, + { driftArea: 'Strand semantics centered on frozen pinned base', releaseHome: 'v20 to v21' }, + { driftArea: 'Braiding as pinned-base equality', releaseHome: 'v21' }, + { driftArea: 'Sync as frontier + patches rather than witnessed admission', releaseHome: 'v19 to v21' }, + ]); + }); + + it('connects the horizon and v19 lane back to the slotting doc as data links', () => { + const horizonTargets = linkTargets('docs/design/release-horizon-v20-v21.md'); + const v19Targets = linkTargets( + 'docs/archive/backlog/github-issue-migration-2026-06-01/docs/method/backlog/v19.0.0/README.md', + ); + + expect(horizonTargets.has('docs/design/0037-remaining-warp-drift-release-slotting.md')).toBe(true); + expect(v19Targets.has( + 'docs/archive/backlog/github-issue-migration-2026-06-01/docs/method/backlog/v20.0.0/PROTO_playback-head-alignment.md', + )).toBe(true); + expect(v19Targets.has( + 'docs/archive/backlog/github-issue-migration-2026-06-01/docs/method/backlog/v21.0.0/PROTO_local-site-object-for-neighborhoods.md', + )).toBe(true); + }); +}); diff --git a/test/unit/scripts/warp-drift-release-slotting-shape.test.ts b/test/unit/scripts/warp-drift-release-slotting-shape.test.ts deleted file mode 100644 index 620e244f1..000000000 --- a/test/unit/scripts/warp-drift-release-slotting-shape.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { describe, expect, it } from 'vitest'; - -function readDoc(relativePath: string): string { - return readFileSync(fileURLToPath(new URL(`../../../${relativePath}`, import.meta.url)), 'utf8'); -} - -const slotting = readDoc('docs/design/0037-remaining-warp-drift-release-slotting.md'); -const driftAudit = readDoc('docs/audits/WARP_DRIFT.md'); -const horizon = readDoc('docs/design/release-horizon-v20-v21.md'); -const v19Lane = readDoc( - 'docs/archive/backlog/github-issue-migration-2026-06-01/docs/method/backlog/v19.0.0/README.md' -); - -describe('Remaining WARP drift release slotting docs', () => { - it('assigns the unresolved drift families to v19, v20, and v21 explicitly', () => { - expect(slotting).toContain('# Remaining WARP Drift Release Slotting'); - expect(slotting).toContain('| Observer surface still snapshot/materialize/filter'); - expect(slotting).toContain('| Slice-first runtime realization and fragment reuse'); - expect(slotting).toContain('| Braiding as pinned-base equality'); - expect(slotting).toContain('| Sync as frontier + patches rather than witnessed admission'); - }); - - it('is linked from the drift ledger as part of the relevant design context', () => { - expect(driftAudit).toContain( - '- [remaining-warp-drift-release-slotting](../design/0037-remaining-warp-drift-release-slotting.md)' - ); - }); - - it('sharpens the release horizon and v19 lane handoff', () => { - expect(horizon).toContain('Read this note together with:'); - expect(horizon).toContain( - '- [0037-remaining-warp-drift-release-slotting.md](./0037-remaining-warp-drift-release-slotting.md)' - ); - expect(horizon).toContain('- `v20` = operational slice-first runtime'); - expect(horizon).toContain( - '- `v21` = plural/distributed observer geometry and admission reality' - ); - expect(v19Lane).toContain('## Release handoff'); - expect(v19Lane).toContain( - '- `v19` owns doctrine/runtime correction and the first honest observer/admission surfaces' - ); - expect(v19Lane).toContain('- `v20` owns operational slice-first runtime realization'); - expect(v19Lane).toContain( - '- `v21` owns plural/distributed semantics such as common-basis braid and fuller admission reality' - ); - }); -}); diff --git a/test/unit/scripts/warpapp-capability-bridge.test.ts b/test/unit/scripts/warpapp-capability-bridge.test.ts deleted file mode 100644 index 30fcf096e..000000000 --- a/test/unit/scripts/warpapp-capability-bridge.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { readFileSync } from 'node:fs'; -import { join } from 'node:path'; - -const warpAppPath = join(process.cwd(), 'src/domain/WarpApp.ts'); -const warpAppSource = readFileSync(warpAppPath, 'utf8'); - -describe('WarpApp capability bridge', () => { - it('does not import WarpRuntime directly', () => { - expect(warpAppSource).not.toContain("import type WarpRuntime"); - expect(warpAppSource).not.toContain("import WarpRuntime"); - }); - - it('does not use callInternalRuntimeMethod for content reads', () => { - expect(warpAppSource).not.toContain('callInternalRuntimeMethod'); - }); -}); diff --git a/test/unit/scripts/warpcore-runtime-bridge.test.ts b/test/unit/scripts/warpcore-runtime-bridge.test.ts deleted file mode 100644 index c5b5c401d..000000000 --- a/test/unit/scripts/warpcore-runtime-bridge.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { readFileSync } from 'node:fs'; -import { existsSync } from 'node:fs'; -import { join } from 'node:path'; - -const warpCorePath = join(process.cwd(), 'src/domain/WarpCore.ts'); -const warpCoreSource = readFileSync(warpCorePath, 'utf8'); -const warpCoreBridgePath = join(process.cwd(), 'src/domain/warp/WarpCoreRuntimeBridge.ts'); - -describe('WarpCore runtime bridge', () => { - it('does not depend on the deleted runtime bridge file', () => { - expect(existsSync(warpCoreBridgePath)).toBe(false); - expect(warpCoreSource).not.toContain('./warp/WarpCoreRuntimeBridge.ts'); - }); - - it('does not route through callInternalRuntimeMethod', () => { - expect(warpCoreSource).not.toContain('callInternalRuntimeMethod'); - }); - - it('declares an explicit structural product surface instead of prototype linking', () => { - expect(warpCoreSource).toContain("from './warp/WarpCoreRuntimeProduct.ts'"); - expect(warpCoreSource).not.toContain('linkWarpCorePrototype'); - expect(warpCoreSource).not.toContain("from './warp/WarpCoreRuntimeBridge.ts'"); - }); -}); diff --git a/test/unit/scripts/warpgraph-capability-seam.test.ts b/test/unit/scripts/warpgraph-capability-seam.test.ts deleted file mode 100644 index 26f41ef6b..000000000 --- a/test/unit/scripts/warpgraph-capability-seam.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { describe, expect, it } from 'vitest'; - -const warpGraphSource = readFileSync( - fileURLToPath(new URL('../../../src/domain/WarpGraph.ts', import.meta.url)), - 'utf8', -); - -const apiReference = readFileSync( - fileURLToPath(new URL('../../../docs/API_REFERENCE.md', import.meta.url)), - 'utf8', -); - -describe('WarpGraph public capability seam', () => { - it('does not import WarpRuntime directly', () => { - expect(warpGraphSource).not.toContain("import type WarpRuntime"); - expect(warpGraphSource).not.toContain("import WarpRuntime"); - }); - - it('does not expose _runtime on the public WarpGraph surface', () => { - expect(warpGraphSource).not.toContain('readonly _runtime:'); - }); - - it('does not use as unknown as when wiring capabilities', () => { - expect(warpGraphSource).not.toContain('as unknown as'); - }); - - it('teaches direct sync through the capability bag, not graph._runtime', () => { - expect(apiReference).not.toContain('graphB._runtime'); - expect(apiReference).toContain('graphA.sync.syncWith(graphB)'); - }); -}); diff --git a/test/unit/scripts/warpgraph-factory-closeout.test.ts b/test/unit/scripts/warpgraph-factory-closeout.test.ts deleted file mode 100644 index 987feef12..000000000 --- a/test/unit/scripts/warpgraph-factory-closeout.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { existsSync, readFileSync } from 'node:fs'; -import { join } from 'node:path'; -import { describe, expect, it } from 'vitest'; - -const factoryNotePath = join( - process.cwd(), - 'docs/archive/backlog/v17.0.0-residual-backlog/API_warpgraph-factory.md' -); -const barrel = readFileSync(join(process.cwd(), 'index.ts'), 'utf8'); -const readme = readFileSync(join(process.cwd(), 'README.md'), 'utf8'); -const releaseLedger = readFileSync(join(process.cwd(), 'docs/releases/v17.0.0/README.md'), 'utf8'); -const workloads = readFileSync( - join( - process.cwd(), - 'docs/archive/backlog/github-issue-migration-2026-06-01/docs/method/backlog/WORKLOADS.md' - ), - 'utf8' -); - -describe('warpgraph factory closeout', () => { - it('removes the stale live factory card', () => { - expect(existsSync(factoryNotePath)).toBe(false); - }); - - it('keeps openWarpGraph as the advanced compatibility composition root', () => { - expect(barrel).toContain('openWarpGraph,'); - expect(barrel).toContain('First-use application code should open a named worldline'); - expect(readme).toContain( - "import { GitGraphAdapter, openWarpWorldline } from '@git-stunts/git-warp';" - ); - expect(readme).toContain('`openWarpGraph()`. That surface is supported for compatibility'); - }); - - it('removes completed factory work from the workload inventory', () => { - expect(workloads).not.toContain('API_warpgraph-factory'); - expect(workloads).not.toContain('WL-30-v17-provider-foundations'); - }); - - it('preserves shipped history without stale composition-root residue', () => { - expect(releaseLedger).toContain('[x] API_warpgraph-factory'); - expect(releaseLedger).toContain('cycle 0089 retired stale live card'); - expect(releaseLedger).toContain('runtime/composition-root residue'); - expect(releaseLedger).not.toContain( - 'The remaining\n work is the `openWarpGraph()`' - ); - expect(releaseLedger).not.toContain('`WarpRuntime` composition-root residue'); - }); -}); diff --git a/test/unit/v7-guards.test.ts b/test/unit/v7-guards.test.ts index 974973215..2eb31a3b6 100644 --- a/test/unit/v7-guards.test.ts +++ b/test/unit/v7-guards.test.ts @@ -1,150 +1,56 @@ -/** - * V7 Contract Guards - * - * These tests enforce V7 invariants by failing if legacy engine - * components are reintroduced. See docs/V7_CONTRACT.md. - * - * "Temporary things are forever. Delete, don't wrap." - */ - -import { describe, it, expect } from 'vitest'; -import { existsSync } from 'node:fs'; -import { dirname, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const srcDir = join(__dirname, '..', '..', 'src'); - -// Task 0.5 (Schema:1 Extermination) is complete: -// - Reducer.js, StateSerializer.js have been deleted -// - PatchBuilder.js is the v2 builder (renamed from PatchBuilderV2.js) -// - No schema:1 artifacts are exported from index.js -const SCHEMA1_EXTERMINATION_COMPLETE = true; - -// Task 4 (Delete split engine) is COMPLETE: -// - EmptyGraphWrapper.js, GraphService.js have been deleted -// - WarpCore is the only supported API -const ENGINE_DELETION_COMPLETE = true; - -describe('V7 Contract Guards', () => { - describe('Schema:1 files must not exist', () => { - const schema1Files = [ - { - path: 'domain/services/Reducer.js', - reason: 'Schema:1 LWW reducer (tombstones, not OR-Set)', - }, - { - path: 'domain/services/StateSerializer.js', - reason: 'Schema:1 state serialization', - }, - ]; - - const testFn = SCHEMA1_EXTERMINATION_COMPLETE ? it : it.skip; - - for (const { path, reason } of schema1Files) { - testFn(`should not contain ${path}`, () => { - const fullPath = join(srcDir, path); - const exists = existsSync(fullPath); - - expect(exists, `\n\nV7 CONTRACT VIOLATION\n\nFile exists: ${fullPath}\nReason banned: ${reason}\n\nSee docs/V7_CONTRACT.md for V7 invariants.\n`).toBe(false); - }); - } +import { describe, expect, it } from 'vitest'; +import * as publicApi from '../../index.ts'; +import { + InMemoryGraphAdapter, + PatchBuilder, + WarpCore, +} from '../../index.ts'; + +function openCore(graphName: string): Promise { + return WarpCore.open({ + persistence: new InMemoryGraphAdapter(), + graphName, + writerId: 'v7-writer', }); - - describe('Legacy engine files must not exist', () => { - const engineFiles = [ - { - path: 'domain/EmptyGraphWrapper.js', - reason: 'Legacy wrapper over commit-per-node engine', - }, - { - path: 'domain/services/GraphService.js', - reason: 'Commit-per-node engine (nodes are commits)', - }, - { - path: 'legacy', - reason: 'Legacy module directory', - }, - ]; - - const testFn = ENGINE_DELETION_COMPLETE ? it : it.skip; - - for (const { path, reason } of engineFiles) { - testFn(`should not contain ${path}`, () => { - const fullPath = join(srcDir, path); - const exists = existsSync(fullPath); - - expect(exists, `\n\nV7 CONTRACT VIOLATION\n\nFile exists: ${fullPath}\nReason banned: ${reason}\n\nSee docs/V7_CONTRACT.md for V7 invariants.\n`).toBe(false); - }); - } - +} + +describe('V7 schema-2 public contract', () => { + it('exports the schema-2 PatchBuilder without schema-1 public artifacts', () => { + expect(publicApi.PatchBuilder).toBe(PatchBuilder); + expect(Object.prototype.hasOwnProperty.call(publicApi, 'Reducer')).toBe(false); + expect(Object.prototype.hasOwnProperty.call(publicApi, 'StateSerializer')).toBe(false); + expect(Object.prototype.hasOwnProperty.call(publicApi, 'createPatchV1')).toBe(false); }); - describe('Schema:1 must not be exported', () => { - const exportTestFn = SCHEMA1_EXTERMINATION_COMPLETE ? it : it.skip; - - exportTestFn('should export PatchBuilder (schema:2, renamed from PatchBuilderV2)', async () => { - const indexModule = (await import('../../index.ts') as any); - expect(indexModule.PatchBuilder).toBeDefined(); - }); - - exportTestFn('should not export Reducer (schema:1)', async () => { - const indexModule = (await import('../../index.ts') as any); - expect(indexModule.Reducer).toBeUndefined(); - }); + it('builds schema-2 patches through the current public graph API', async () => { + const core = await openCore('v7-schema-patch'); + const builder = await core.createPatch(); + builder.addNode('v7:node'); + builder.setProperty('v7:node', 'status', 'open'); - exportTestFn('should not export createPatch with schema:1 support', async () => { - const indexModule = (await import('../../index.ts') as any); - // If createPatch exists, it should only support schema:2 - // This is tested elsewhere; here we just ensure no explicit schema:1 export - expect(indexModule.createPatchV1).toBeUndefined(); - }); + const patch = builder.build(); - exportTestFn('should not export StateSerializer (schema:1)', async () => { - const indexModule = (await import('../../index.ts') as any); - expect(indexModule.StateSerializer).toBeUndefined(); - }); + expect(patch.schema).toBe(2); + expect(patch.writer).toBe('v7-writer'); + expect(patch.ops.length).toBe(2); + expect(patch.context).toEqual({ 'v7-writer': 1 }); }); - describe('V7 required components must exist', () => { - const requiredFiles = [ - { - path: 'domain/services/PatchBuilder.ts', - reason: 'Schema:2 patch builder with dots and OR-Set', - }, - { - path: 'domain/services/JoinReducer.ts', - reason: 'Schema:2 OR-Set reducer', - }, - { - path: 'domain/WarpCore.ts', - reason: 'Main WARP API', - }, - { - path: 'domain/crdt/ORSet.ts', - reason: 'OR-Set CRDT implementation', - }, - { - path: 'domain/crdt/VersionVector.ts', - reason: 'Version vector for causality', - }, - { - path: 'domain/crdt/Dot.ts', - reason: 'Dot notation for unique events', - }, - { - path: 'domain/services/index/WarpStateIndexBuilder.ts', - reason: 'Task 6: Index built from WarpState, not commit DAG', - }, - ]; + it('applies schema-2 node, edge, and property operations through WarpCore', async () => { + const core = await openCore('v7-current-api'); - for (const { path, reason } of requiredFiles) { - it(`should contain ${path}`, () => { - const fullPath = join(srcDir, path); - const exists = existsSync(fullPath); + await core.patch((patch) => { + patch.addNode('v7:source'); + patch.addNode('v7:target'); + patch.addEdge('v7:source', 'v7:target', 'relates'); + patch.setProperty('v7:source', 'status', 'current'); + patch.setEdgeProperty('v7:source', 'v7:target', 'relates', 'weight', 7); + }); + await core.materialize(); - expect(exists, `\n\nV7 REQUIRED COMPONENT MISSING\n\nFile missing: ${fullPath}\nRequired for: ${reason}\n\nSee docs/V7_CONTRACT.md for V7 invariants.\n`).toBe(true); - }); - } + await expect(core.hasNode('v7:source')).resolves.toBe(true); + await expect(core.hasNode('v7:target')).resolves.toBe(true); + await expect(core.getNodeProps('v7:source')).resolves.toEqual({ status: 'current' }); + await expect(core.getEdgeProps('v7:source', 'v7:target', 'relates')).resolves.toEqual({ weight: 7 }); }); });