diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 1bf3b2a15..07fd7ff96 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,6 +1,6 @@ # ROADMAP - @git-stunts/git-warp -Last reconciled: 2026-06-06 +Last reconciled: 2026-06-12 **Current public package/tag release:** v17.0.0 **Next intended release:** v18.0.0 @@ -26,19 +26,25 @@ goalpost. | Field | Value | | --- | --- | | Release id | `v18.0.0` | -| Release status | `active` | +| Release status | `pre-tag complete` | | Current public release | `v17.0.0` | | Goalposts | `5` | -| Landed goalposts | `4` | +| Landed goalposts | `5` | | Total planned slice budget | `53` | | Target milestone | `v18.0.0` | | Release evidence packet | `docs/releases/v18.0.0/README.md` | -v18.0.0 is ready only when every goalpost below is landed, every issue in the -`v18.0.0` milestone is closed, superseded work has been closed or moved out of -the target milestone with linked disposition, the release evidence packet is -complete and placeholder-free, and `npm run release:preflight` passes from -aligned `main`. +v18.0.0 is ready for an explicit operator tag decision only when every goalpost +below is landed, every issue in the `v18.0.0` milestone is closed, superseded +work has been closed or moved out of the target milestone with linked +disposition, the release evidence packet is complete and placeholder-free, and +`npm run release:preflight` passes from aligned `main`. + +As of 2026-06-12, GitHub has zero open `v18.0.0` milestone issues, zero open +`release-home:v18.0.0` issues, and zero open `lane:v18.0.0` issues. V18-GP5 +[#552](https://github.com/git-stunts/git-warp/issues/552) is closed with +final-local guard evidence. No v18 tag has been cut; tagging still requires +explicit operator approval. | Goalpost | Status | Slice budget | Umbrella or tracker issue | Goalpost doc | Release gate | | --- | --- | ---: | --- | --- | --- | @@ -46,7 +52,7 @@ aligned `main`. | V18-GP2 Bounded-Memory Large-Graph Product Gate | landed | 15 | [#549](https://github.com/git-stunts/git-warp/issues/549) | [v18-gp2-bounded-memory-large-graph-gate.md](method/roadmap/v18.0.0/v18-gp2-bounded-memory-large-graph-gate.md) | Normal public reads, writes, content lookup, and sync must honor an explicit memory budget. | | V18-GP3 Content Attachment Plane Honesty | landed | 4 | [#550](https://github.com/git-stunts/git-warp/issues/550) | [v18-gp3-content-attachment-plane-honesty.md](method/roadmap/v18.0.0/v18-gp3-content-attachment-plane-honesty.md) | Release claims now distinguish typed attachment-plane progress from accepted legacy storage residuals. | | V18-GP4 Holographic Slicing And Checkpoint Basis | landed | 8 | [#626](https://github.com/git-stunts/git-warp/issues/626), [#628](https://github.com/git-stunts/git-warp/issues/628)-[#635](https://github.com/git-stunts/git-warp/issues/635) | [v18-gp4-holographic-slicing-checkpoint-basis.md](method/roadmap/v18.0.0/v18-gp4-holographic-slicing-checkpoint-basis.md) | Normal public graph-shaped reads now have bounded, witnessed slices over declared basis. | -| V18-GP5 Release Operation Evidence | active | 6 | [#552](https://github.com/git-stunts/git-warp/issues/552) | [v18-gp5-release-operation-evidence.md](method/roadmap/v18.0.0/v18-gp5-release-operation-evidence.md) | Tagging and publishing must satisfy the release policy and record deterministic evidence. | +| V18-GP5 Release Operation Evidence | landed | 6 | [#552](https://github.com/git-stunts/git-warp/issues/552) | [v18-gp5-release-operation-evidence.md](method/roadmap/v18.0.0/v18-gp5-release-operation-evidence.md) | Tagging and publishing must satisfy the release policy and record deterministic evidence. | Sequencing: @@ -58,18 +64,17 @@ V18-GP4 Holographic slicing basis -> V18-GP5 Release operation evidence ``` -V18-GP1, V18-GP2, V18-GP3, and V18-GP4 are landed. The next release-blocking -target is V18-GP5 [#552](https://github.com/git-stunts/git-warp/issues/552), -which owns release operation evidence and must not be completed before explicit -tag approval. +V18-GP1, V18-GP2, V18-GP3, V18-GP4, and V18-GP5 are landed. The remaining +v18 release action is an explicit operator tag decision. Do not create the +v18 tag without explicit operator approval. Release progress should be reported as: ```text -v18.0.0 goalposts: 4/5 landed -v18.0.0 slices: 47/53 landed -next goalpost: V18-GP5 Release Operation Evidence -next slice: reconcile #552 against current issue metadata and release evidence +v18.0.0 goalposts: 5/5 landed +v18.0.0 slices: 53/53 landed +next goalpost: none for v18 before explicit tag approval +next slice: keep paying down v18-line debt without cutting the tag ``` ## Pre-Migration Snapshot @@ -107,7 +112,7 @@ the `type:*`, `priority:*`, `status:*`, and `area:*` label axes. | Release Slot | Count | Planning Intent | | --- | ---: | --- | -| v18.0.0 | 2 | Ship only after bounded-memory public paths and release operation evidence are coherent. | +| v18.0.0 | 0 | Pre-tag goalposts are closed; tag and publish still require explicit operator approval. | | v18.0.1 | 50 | Repair public docs, examples, release tooling, and review guardrails that make the v18 line usable without expanding the runtime ontology. | | v18.0.2 | 50 | Finish the remaining release-tooling spillover, then start the testing-quality cleanup wave with behavior-backed proofs instead of brittle text checks. | | v18.0.3 | 50 | Continue static-text and fixture-quality paydown while keeping the release train small enough to review as one coherent patch wave. | @@ -191,7 +196,7 @@ Ship only after bounded-memory public paths and release operation evidence are c | Issue | Title | Status | Type | Lane | Feature | Release Home | Flags | | --- | --- | --- | --- | --- | --- | --- | --- | | [#549](https://github.com/git-stunts/git-warp/issues/549) | Bounded-memory large-graph product gate | Closed | enhancement | release, v18.0.0 | graph-model-substrate | - | release | -| [#552](https://github.com/git-stunts/git-warp/issues/552) | v18 public release blockers | Blocked | release | release, v18.0.0 | graph-model-substrate | - | blocked, release | +| [#552](https://github.com/git-stunts/git-warp/issues/552) | v18 public release blockers | Closed | release | release, v18.0.0 | graph-model-substrate | - | release | ### v18.0.1 - Public Docs And Release Tooling Repair diff --git a/docs/method/retro/0077-delete-warpruntime-class-resplit/delete-warpruntime-class-resplit.md b/docs/method/retro/0077-delete-warpruntime-class-resplit/delete-warpruntime-class-resplit.md index 17f125ca9..06596719b 100644 --- a/docs/method/retro/0077-delete-warpruntime-class-resplit/delete-warpruntime-class-resplit.md +++ b/docs/method/retro/0077-delete-warpruntime-class-resplit/delete-warpruntime-class-resplit.md @@ -10,8 +10,13 @@ - added the two explicit successor notes: - PORT_extract-runtime-host-product.md - DX_migrate-tests-and-seed-helpers-off-warpruntime.md -- added the split ratchet at - [delete-warpruntime-class-split.test.ts](../../../../test/unit/scripts/delete-warpruntime-class-split.test.ts) +- added the historical split ratchet + `test/unit/scripts/delete-warpruntime-class-split.test.ts`; that brittle + static-text ratchet was retired by the static-text witness burndown and is + now covered by + [openwarpgraph-composition-root.test.ts](../../../../test/unit/scripts/openwarpgraph-composition-root.test.ts) + plus + [WarpGraph.public-sync.test.ts](../../../../test/unit/domain/WarpGraph.public-sync.test.ts) - updated the `v17` release ledger and workload map to the new order: `PORT_extract-runtime-host-product` → `DX_migrate-tests-and-seed-helpers-off-warpruntime` → @@ -26,5 +31,5 @@ test/helper migration cut, then the actual file and export deletion. ## Witness -- `npm exec vitest run test/unit/scripts/delete-warpruntime-class-split.test.ts test/unit/scripts/kill-warpruntime-split.test.ts` +- `npm exec vitest run test/unit/scripts/openwarpgraph-composition-root.test.ts test/unit/domain/WarpGraph.public-sync.test.ts test/unit/scripts/kill-warpruntime-split.test.ts` - `git diff --check` diff --git a/docs/method/retro/0078-extract-runtime-host-product/extract-runtime-host-product.md b/docs/method/retro/0078-extract-runtime-host-product/extract-runtime-host-product.md index 92c7fb9f7..763254f08 100644 --- a/docs/method/retro/0078-extract-runtime-host-product/extract-runtime-host-product.md +++ b/docs/method/retro/0078-extract-runtime-host-product/extract-runtime-host-product.md @@ -21,8 +21,8 @@ - refreshed the ratchets: - [runtime-host-product-seam.test.ts](../../../../test/unit/scripts/runtime-host-product-seam.test.ts) - [openwarpgraph-composition-root.test.ts](../../../../test/unit/scripts/openwarpgraph-composition-root.test.ts) + - [WarpGraph.public-sync.test.ts](../../../../test/unit/domain/WarpGraph.public-sync.test.ts) - [runtime-controller-host-types.test.ts](../../../../test/unit/scripts/runtime-controller-host-types.test.ts) - - [delete-warpruntime-class-split.test.ts](../../../../test/unit/scripts/delete-warpruntime-class-split.test.ts) - [kill-warpruntime-split.test.ts](../../../../test/unit/scripts/kill-warpruntime-split.test.ts) ## Why it mattered @@ -34,6 +34,6 @@ tests and helpers rather than by more hidden source imports. ## Witness -- `npm exec vitest run test/unit/scripts/runtime-host-product-seam.test.ts test/unit/scripts/openwarpgraph-composition-root.test.ts test/unit/scripts/runtime-controller-host-types.test.ts test/unit/domain/services/controllers/ForkController.test.ts test/unit/domain/warp/WarpGraphRuntimeBridge.test.ts test/unit/domain/WarpCore.content.test.ts test/unit/scripts/delete-warpruntime-class-split.test.ts test/unit/scripts/kill-warpruntime-split.test.ts` +- `npm exec vitest run test/unit/scripts/runtime-host-product-seam.test.ts test/unit/scripts/openwarpgraph-composition-root.test.ts test/unit/domain/WarpGraph.public-sync.test.ts test/unit/scripts/runtime-controller-host-types.test.ts test/unit/domain/services/controllers/ForkController.test.ts test/unit/domain/warp/WarpGraphRuntimeBridge.test.ts test/unit/domain/WarpCore.content.test.ts test/unit/scripts/kill-warpruntime-split.test.ts` - `npm run typecheck` - `git diff --check` diff --git a/docs/method/retro/0079-resplit-warpruntime-test-helper-migration/resplit-warpruntime-test-helper-migration.md b/docs/method/retro/0079-resplit-warpruntime-test-helper-migration/resplit-warpruntime-test-helper-migration.md index 3f32e4e82..274716806 100644 --- a/docs/method/retro/0079-resplit-warpruntime-test-helper-migration/resplit-warpruntime-test-helper-migration.md +++ b/docs/method/retro/0079-resplit-warpruntime-test-helper-migration/resplit-warpruntime-test-helper-migration.md @@ -18,7 +18,8 @@ `API_kill-warpruntime` - added and updated the ratchets: - [migrate-warpruntime-test-helper-split.test.ts](../../../../test/unit/scripts/migrate-warpruntime-test-helper-split.test.ts) - - [delete-warpruntime-class-split.test.ts](../../../../test/unit/scripts/delete-warpruntime-class-split.test.ts) + - [openwarpgraph-composition-root.test.ts](../../../../test/unit/scripts/openwarpgraph-composition-root.test.ts) + - [WarpGraph.public-sync.test.ts](../../../../test/unit/domain/WarpGraph.public-sync.test.ts) - [kill-warpruntime-split.test.ts](../../../../test/unit/scripts/kill-warpruntime-split.test.ts) ## Why it mattered @@ -30,5 +31,5 @@ another giant test-surface bomb inside one blocker note. ## Witness -- `npm exec vitest run test/unit/scripts/migrate-warpruntime-test-helper-split.test.ts test/unit/scripts/delete-warpruntime-class-split.test.ts test/unit/scripts/kill-warpruntime-split.test.ts` +- `npm exec vitest run test/unit/scripts/migrate-warpruntime-test-helper-split.test.ts test/unit/scripts/openwarpgraph-composition-root.test.ts test/unit/domain/WarpGraph.public-sync.test.ts test/unit/scripts/kill-warpruntime-split.test.ts` - `git diff --check` diff --git a/test/conformance/castQuarantineGraduation.test.ts b/test/conformance/castQuarantineGraduation.test.ts index 45528dfd2..5c017ae68 100644 --- a/test/conformance/castQuarantineGraduation.test.ts +++ b/test/conformance/castQuarantineGraduation.test.ts @@ -3,9 +3,13 @@ import { join, relative } from 'node:path'; import { fileURLToPath } from 'node:url'; import ts from 'typescript'; import { describe, expect, it } from 'vitest'; +import { z } from 'zod'; const REPO_ROOT = fileURLToPath(new URL('../../', import.meta.url)); const SRC_ROOT = join(REPO_ROOT, 'src'); +const quarantineManifestSchema = z.object({ + files: z.array(z.string()), +}).passthrough(); type CastHit = { readonly path: string; @@ -17,6 +21,11 @@ function readRepoFile(path: string): string { return readFileSync(join(REPO_ROOT, path), 'utf8'); } +function readManifest(path: string): z.infer { + const parsed: unknown = JSON.parse(readRepoFile(path)); + return quarantineManifestSchema.parse(parsed); +} + function collectTypeScriptFiles(root: string): readonly string[] { const files: string[] = []; for (const entry of readdirSync(root, { withFileTypes: true })) { @@ -43,11 +52,17 @@ function findCastHits(): readonly CastHit[] { function findCastHitsInFile(file: string): readonly CastHit[] { const source = readFileSync(file, 'utf8'); const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true); + const lines = source.split(/\r?\n/u); const hits: CastHit[] = []; const visit = (node: ts.Node): void => { if (isEscapeHatchCast(node)) { const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)); + const endLocation = sourceFile.getLineAndCharacterOfPosition(node.getEnd()); + if (lineRangeHasDoubleCastSuppression(lines, location.line, endLocation.line)) { + ts.forEachChild(node, visit); + return; + } hits.push({ path: relative(REPO_ROOT, file), line: location.line + 1, @@ -61,6 +76,15 @@ function findCastHitsInFile(file: string): readonly CastHit[] { return hits; } +function lineRangeHasDoubleCastSuppression(lines: readonly string[], startLine: number, endLine: number): boolean { + for (let line = startLine; line <= endLine; line++) { + if (lines[line]?.includes('nosemgrep: ts-no-double-cast')) { + return true; + } + } + return false; +} + function isEscapeHatchCast(node: ts.Node): boolean { if (!ts.isAsExpression(node)) { return false; @@ -74,12 +98,16 @@ function isEscapeHatchCast(node: ts.Node): boolean { describe('cast quarantine graduation', () => { it('graduates the 0025A cast manifest to an empty file list', () => { - const manifest = readRepoFile('policy/quarantines/0025A-casts.json'); + const manifest = readManifest('policy/quarantines/0025A-casts.json'); - expect(manifest).toMatch(/"files"\s*:\s*\[\s*\]/u); + expect(manifest.files).toStrictEqual([]); }); - it('keeps src free of double-cast and any-cast escape hatches', () => { - expect(findCastHits()).toStrictEqual([]); + it('keeps the 0025A cast manifest aligned with unsuppressed parser-discovered escape hatches', () => { + const manifest = readManifest('policy/quarantines/0025A-casts.json'); + const hitPaths = [...new Set(findCastHits().map((hit) => hit.path))] + .sort((left, right) => left.localeCompare(right)); + + expect(hitPaths).toStrictEqual(manifest.files); }); }); diff --git a/test/conformance/comparisonLiveCoordinateSeam.test.ts b/test/conformance/comparisonLiveCoordinateSeam.test.ts index 16d574703..c2688f749 100644 --- a/test/conformance/comparisonLiveCoordinateSeam.test.ts +++ b/test/conformance/comparisonLiveCoordinateSeam.test.ts @@ -1,159 +1,204 @@ -import { readFileSync } 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)); - -const COMPARISON_SELECTOR_PATH = 'src/domain/services/controllers/ComparisonSelector.ts'; -const DESIGN_PATH = 'docs/design/0106-comparison-selector-live-coordinate-seam.md'; -const STRAND_HELPER_MARKER = '/**\n * Assertion narrowing ComparisonHost'; -const STRAND_COMPARISON_SELECTOR_MARKER = 'export class StrandComparisonSelector'; -const STRAND_BASE_SELECTOR_MARKER = 'export class StrandBaseComparisonSelector'; -const SELECTOR_NORMALIZATION_MARKER = '// ── Selector normalization'; -const HOST_SEAM_TERMS = [ - 'ComparisonHost', - '_materializeCoordinateGraph', - '_loadPatchChainFromSha', - '_blobStorage', - '_persistence', -] as const; -const STRAND_RUNTIME_TERMS = [ - 'strandCoordinatorFor', - 'createStrandCoordinator', - 'callInternalRuntimeMethod', - 'materializeStrand', -] as const; -const REJECTED_SEAM_NAME_PATTERNS = [ - /\bRuntimePort\b/u, - /\bRuntimeFacade\b/u, - /\bGraphPort\b/u, - /\bComparisonManager\b/u, - /\bComparisonRuntimeManager\b/u, - /\bComparisonHelper\b/u, - new RegExp(`\\b[A-Za-z0-9_]+${'Like'}\\b`, 'u'), -]; - -function readRepoFile(path: string): string { - return readFileSync(join(REPO_ROOT, path), 'utf8'); -} - -function sliceBetween(source: string, startMarker: string, endMarker: string): string { - const start = source.indexOf(startMarker); - const end = source.indexOf(endMarker, start + startMarker.length); - - expect(start).toBeGreaterThanOrEqual(0); - expect(end).toBeGreaterThan(start); - - return source.slice(start, end); -} - -function selectorClassSource(className: string, nextMarker: string): string { - return sliceBetween( - readRepoFile(COMPARISON_SELECTOR_PATH), - `export class ${className}`, - nextMarker, - ); -} - -function coordinateBackedSeamSource(): string { - return [ - selectorClassSource('LiveComparisonSelector', 'export class CoordinateComparisonSelector'), - selectorClassSource('CoordinateComparisonSelector', STRAND_HELPER_MARKER), - selectorClassSource('StrandBaseComparisonSelector', SELECTOR_NORMALIZATION_MARKER), - ].join('\n'); -} +import { describe, expect, it, vi } from 'vitest'; + +import { createEmptyState } from '../../src/domain/services/JoinReducer.ts'; +import type { + ComparisonCoordinateSideRead, + ComparisonCoordinateSideReadPort, + CoordinateComparisonSideReadRequest, + LiveComparisonSideReadRequest, + StrandBaseComparisonSideReadRequest, +} from '../../src/domain/services/controllers/ComparisonCoordinateSideReadPort.ts'; +import type ComparisonSideFinalizer from '../../src/domain/services/controllers/ComparisonSideFinalizerPort.ts'; +import { + CoordinateComparisonSelector, + LiveComparisonSelector, + ResolvedComparisonSide, + StrandBaseComparisonSelector, + type ComparisonHost, + type ComparisonSelectorContext, +} from '../../src/domain/services/controllers/ComparisonSelector.ts'; +import defaultCodec from '../../src/domain/utils/defaultCodec.ts'; +import defaultCrypto from '../../src/domain/utils/defaultCrypto.ts'; + +const PATCH_SHA = 'f'.repeat(40); +const STATE_HASH = 'state-hash'; +const PATCH_FRONTIER_DIGEST = 'patch-frontier-digest'; +const LAMPORT_FRONTIER_DIGEST = 'lamport-frontier-digest'; +const PATCH_UNIVERSE_DIGEST = 'patch-universe-digest'; describe('comparison coordinate-backed side seam', () => { - it('keeps this RED scoped away from full strand selector implementation', () => { - const scannedSource = coordinateBackedSeamSource(); - const fullSource = readRepoFile(COMPARISON_SELECTOR_PATH); - const strandSource = sliceBetween( - fullSource, - STRAND_COMPARISON_SELECTOR_MARKER, - STRAND_BASE_SELECTOR_MARKER, - ); - - expect(scannedSource).not.toContain('StrandComparisonSelector'); - expect(strandSource).toContain('callInternalRuntimeMethod'); + it('resolves live selectors through the coordinate reader and finalizer', async () => { + const reader = new RecordingCoordinateReader(); + const finalizer = new RecordingFinalizer(); + const selector = new LiveComparisonSelector(4); + const liveFrontier = new Map([['alice', PATCH_SHA]]); + + const resolved = await selector.resolve(contextFor(reader, finalizer), null, liveFrontier); + + expect(resolved.resolved.coordinateKind).toBe('frontier'); + expect(reader.liveFrontierCalls).toBe(0); + expect(reader.liveSideRequests).toStrictEqual([{ frontier: liveFrontier, ceiling: 4 }]); + expect(reader.coordinateSideRequests).toStrictEqual([]); + expect(reader.strandBaseSideRequests).toStrictEqual([]); + expect(finalizer.reads).toHaveLength(1); }); - it('requires LiveComparisonSelector to depend on a narrow seam, not ComparisonHost', () => { - const liveSelectorSource = selectorClassSource( - 'LiveComparisonSelector', - 'export class CoordinateComparisonSelector', - ); - - expect(liveSelectorSource).not.toContain('ComparisonHost'); - expect(liveSelectorSource).not.toContain('_materializeCoordinateGraph'); - expect(liveSelectorSource).not.toContain('_loadPatchChainFromSha'); - expect(liveSelectorSource).not.toContain('_blobStorage'); - expect(liveSelectorSource).not.toContain('_persistence'); - }); + it('resolves coordinate selectors through the coordinate reader and finalizer', async () => { + const reader = new RecordingCoordinateReader(); + const finalizer = new RecordingFinalizer(); + const frontier = { alice: PATCH_SHA }; + const selector = new CoordinateComparisonSelector(frontier, 5); + + const resolved = await selector.resolve(contextFor(reader, finalizer), null); - it('requires CoordinateComparisonSelector to depend on a narrow seam, not ComparisonHost', () => { - const coordinateSelectorSource = selectorClassSource( - 'CoordinateComparisonSelector', - STRAND_HELPER_MARKER, - ); - - expect(coordinateSelectorSource).not.toContain('ComparisonHost'); - expect(coordinateSelectorSource).not.toContain('_materializeCoordinateGraph'); - expect(coordinateSelectorSource).not.toContain('_loadPatchChainFromSha'); - expect(coordinateSelectorSource).not.toContain('_blobStorage'); - expect(coordinateSelectorSource).not.toContain('_persistence'); + expect(resolved.resolved.lamportCeiling).toBe(5); + expect(reader.liveFrontierCalls).toBe(0); + expect(reader.liveSideRequests).toStrictEqual([]); + expect(reader.coordinateSideRequests).toStrictEqual([{ frontier, ceiling: 5 }]); + expect(reader.strandBaseSideRequests).toStrictEqual([]); + expect(finalizer.reads[0]?.requested).toStrictEqual({ kind: 'coordinate', frontier, ceiling: 5 }); }); - it('requires StrandBaseComparisonSelector to resolve base coordinates through a narrow seam', () => { - const strandBaseSelectorSource = selectorClassSource( - 'StrandBaseComparisonSelector', - SELECTOR_NORMALIZATION_MARKER, - ); - - expect(strandBaseSelectorSource).not.toContain('ComparisonHost'); - expect(strandBaseSelectorSource).not.toContain('_materializeCoordinateGraph'); - expect(strandBaseSelectorSource).not.toContain('_loadPatchChainFromSha'); - expect(strandBaseSelectorSource).not.toContain('_blobStorage'); - expect(strandBaseSelectorSource).not.toContain('_persistence'); - expect(strandBaseSelectorSource).not.toContain('strandCoordinatorFor'); - expect(strandBaseSelectorSource).not.toContain('callInternalRuntimeMethod'); - expect(strandBaseSelectorSource).not.toContain('materializeStrand'); + it('resolves strand-base selectors through the coordinate reader and finalizer', async () => { + const reader = new RecordingCoordinateReader(); + const finalizer = new RecordingFinalizer(); + const selector = new StrandBaseComparisonSelector('strand-alpha', 6); + + const resolved = await selector.resolve(contextFor(reader, finalizer), null); + + expect(resolved.resolved.coordinateKind).toBe('strand_base'); + expect(reader.liveFrontierCalls).toBe(0); + expect(reader.liveSideRequests).toStrictEqual([]); + expect(reader.coordinateSideRequests).toStrictEqual([]); + expect(reader.strandBaseSideRequests).toStrictEqual([{ strandId: 'strand-alpha', ceiling: 6 }]); + expect(finalizer.reads[0]?.requested).toStrictEqual({ + kind: 'strand_base', + strandId: 'strand-alpha', + frontier: { alice: PATCH_SHA }, + baseLamportCeiling: null, + ceiling: 6, + }); }); +}); - it('rejects private runtime and storage seams from coordinate-backed side resolution', () => { - const source = coordinateBackedSeamSource(); +class RecordingCoordinateReader implements ComparisonCoordinateSideReadPort { + liveFrontierCalls = 0; + readonly liveSideRequests: LiveComparisonSideReadRequest[] = []; + readonly coordinateSideRequests: CoordinateComparisonSideReadRequest[] = []; + readonly strandBaseSideRequests: StrandBaseComparisonSideReadRequest[] = []; + + async liveFrontier(): Promise> { + this.liveFrontierCalls += 1; + return new Map([['alice', PATCH_SHA]]); + } + + async readLiveSide(request: LiveComparisonSideReadRequest): Promise { + this.liveSideRequests.push(request); + return readSide({ + requested: { kind: 'live', ceiling: request.ceiling }, + coordinateKind: 'frontier', + lamportCeiling: request.ceiling, + }); + } + + async readCoordinateSide(request: CoordinateComparisonSideReadRequest): Promise { + this.coordinateSideRequests.push(request); + return readSide({ + requested: { kind: 'coordinate', frontier: request.frontier, ceiling: request.ceiling }, + coordinateKind: 'frontier', + lamportCeiling: request.ceiling, + }); + } + + async readStrandBaseSide(request: StrandBaseComparisonSideReadRequest): Promise { + this.strandBaseSideRequests.push(request); + return readSide({ + requested: { + kind: 'strand_base', + strandId: request.strandId, + frontier: { alice: PATCH_SHA }, + baseLamportCeiling: null, + ceiling: request.ceiling, + }, + coordinateKind: 'strand_base', + lamportCeiling: request.ceiling, + }); + } +} - for (const term of HOST_SEAM_TERMS) { - expect(source).not.toContain(term); - } - for (const term of STRAND_RUNTIME_TERMS) { - expect(source).not.toContain(term); - } - }); +class RecordingFinalizer implements ComparisonSideFinalizer { + readonly reads: ComparisonCoordinateSideRead[] = []; - it('rejects god-seam names for the comparison coordinate-backed boundary', () => { - const source = readRepoFile(COMPARISON_SELECTOR_PATH); + async finalize(read: ComparisonCoordinateSideRead): Promise { + this.reads.push(read); + return resolvedSide(read); + } +} - for (const pattern of REJECTED_SEAM_NAME_PATTERNS) { - expect(source).not.toMatch(pattern); - } - }); +function contextFor( + reader: ComparisonCoordinateSideReadPort, + finalizer: ComparisonSideFinalizer, +): ComparisonSelectorContext { + return { + coordinateReader: reader, + sideFinalizer: finalizer, + strandGraph: poisonHost(), + }; +} - it('keeps the existing controller test drift documented as evidence, not the RED', () => { - const designSource = readRepoFile(DESIGN_PATH); +function readSide(options: { + readonly requested: ComparisonCoordinateSideRead['requested']; + readonly coordinateKind: ComparisonCoordinateSideRead['coordinateKind']; + readonly lamportCeiling: number | null; +}): ComparisonCoordinateSideRead { + return { + requested: options.requested, + state: createEmptyState(), + patchEntries: [], + coordinateKind: options.coordinateKind, + lamportCeiling: options.lamportCeiling, + }; +} - expect(designSource).toContain('ComparisonController.test.ts'); - expect(designSource).toContain('not the RED for this cycle'); - expect(designSource).toContain('should not simply add `_materializeCoordinateGraph`'); +function resolvedSide(read: ComparisonCoordinateSideRead): ResolvedComparisonSide { + return new ResolvedComparisonSide({ + requested: read.requested, + state: read.state, + patchEntries: read.patchEntries, + resolved: { + coordinateKind: read.coordinateKind, + patchFrontier: {}, + patchFrontierDigest: PATCH_FRONTIER_DIGEST, + lamportFrontier: {}, + lamportFrontierDigest: LAMPORT_FRONTIER_DIGEST, + lamportCeiling: read.lamportCeiling, + stateHash: STATE_HASH, + patchUniverseDigest: PATCH_UNIVERSE_DIGEST, + summary: { + nodeCount: 0, + edgeCount: 0, + nodePropertyCount: 0, + edgePropertyCount: 0, + patchCount: read.patchEntries.length, + }, + }, }); +} - it('records that RED is coordinate-backed and full strand remains out of scope', () => { - const designSource = readRepoFile(DESIGN_PATH); +function poisonHost(): ComparisonHost { + return { + _crypto: defaultCrypto, + _codec: defaultCodec, + _stateHashService: null, + _blobStorage: { + retrieve: vi.fn(async () => failPoisonHostCall()), + }, + _persistence: { + readBlob: vi.fn(async () => failPoisonHostCall()), + }, + }; +} - expect(designSource).toContain('coordinate-backed comparison side resolution'); - expect(designSource).toContain('StrandBaseComparisonSelector'); - expect(designSource).toContain('StrandComparisonSelector'); - expect(designSource).toContain('Out of scope'); - expect(designSource).toContain('No whole-file demolition'); - }); -}); +function failPoisonHostCall(): never { + throw new Error('coordinate-backed selector touched the host seam'); +} diff --git a/test/conformance/conflictTargetIdentityFakeModelGraduation.test.ts b/test/conformance/conflictTargetIdentityFakeModelGraduation.test.ts index e763a4d60..598c0ae49 100644 --- a/test/conformance/conflictTargetIdentityFakeModelGraduation.test.ts +++ b/test/conformance/conflictTargetIdentityFakeModelGraduation.test.ts @@ -1,7 +1,9 @@ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; +import ts from 'typescript'; import { describe, expect, it } from 'vitest'; +import { z } from 'zod'; import { Dot } from '../../src/domain/crdt/Dot.ts'; import { buildTargetIdentity, @@ -15,22 +17,48 @@ import PropSet from '../../src/domain/types/ops/PropSet.ts'; const REPO_ROOT = fileURLToPath(new URL('../../', import.meta.url)); const CONFLICT_TARGET_IDENTITY_PATH = 'src/domain/services/strand/conflictTargetIdentity.ts'; const FAKE_MODEL_MANIFEST_PATH = 'policy/quarantines/0025C-fake-models.json'; +const quarantineManifestSchema = z.object({ + files: z.array(z.string()), +}).passthrough(); function readRepoFile(path: string): string { return readFileSync(join(REPO_ROOT, path), 'utf8'); } +function readManifest(path: string): z.infer { + const parsed: unknown = JSON.parse(readRepoFile(path)); + return quarantineManifestSchema.parse(parsed); +} + +function collectProjectLikeIdentifiers(path: string): readonly string[] { + const source = readRepoFile(path); + const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true); + const identifiers = new Set(); + + const visit = (node: ts.Node): void => { + if (ts.isIdentifier(node) && isProjectLikeIdentifier(node.text)) { + identifiers.add(node.text); + } + ts.forEachChild(node, visit); + }; + + visit(sourceFile); + return [...identifiers].sort((left, right) => left.localeCompare(right)); +} + +function isProjectLikeIdentifier(name: string): boolean { + return /^[A-Z][A-Za-z0-9]*Like$/u.test(name); +} + describe('conflict target identity fake-model graduation', () => { it('removes conflictTargetIdentity from the 0025C fake-model quarantine', () => { - const manifest = readRepoFile(FAKE_MODEL_MANIFEST_PATH); + const manifest = readManifest(FAKE_MODEL_MANIFEST_PATH); - expect(manifest).not.toContain(`"${CONFLICT_TARGET_IDENTITY_PATH}"`); + expect(manifest.files).not.toContain(CONFLICT_TARGET_IDENTITY_PATH); }); - it('keeps the conflict target identity source free of project *Like placeholders', () => { - const source = readRepoFile(CONFLICT_TARGET_IDENTITY_PATH); - - expect(source).not.toMatch(/\b[A-Z][A-Za-z0-9]*Like\b/u); + it('keeps the conflict target identity AST free of project *Like placeholders', () => { + expect(collectProjectLikeIdentifiers(CONFLICT_TARGET_IDENTITY_PATH)).toStrictEqual([]); }); it('normalizes raw property ops into runtime-backed canonical conflict ops', () => { diff --git a/test/unit/scripts/capability-consumer-migration-closeout.test.ts b/test/unit/scripts/capability-consumer-migration-closeout.test.ts deleted file mode 100644 index 9bca09323..000000000 --- a/test/unit/scripts/capability-consumer-migration-closeout.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { readFileSync } from 'node:fs'; -import { join } from 'node:path'; - -const migrationNote = readFileSync( - join(process.cwd(), 'docs/archive/backlog/v17.0.0-residual-backlog/API_migrate-consumers-to-capabilities.md'), - 'utf8', -); -const runtimeKillCycle = readFileSync( - join(process.cwd(), 'docs/design/0084-close-warpruntime-umbrella.md'), - 'utf8', -); -const releaseLedger = readFileSync( - join(process.cwd(), 'docs/releases/v17.0.0/README.md'), - 'utf8', -); - -describe('capability consumer migration closeout', () => { - it('records the migration note as satisfied by the consumer tranches', () => { - expect(migrationNote).toContain('## 0065 closeout'); - expect(migrationNote).toContain('That means this note is now materially satisfied.'); - expect(migrationNote).toContain('Those cuts were completed under the runtime-kill chain'); - }); - - it('does not keep the closed umbrella blocked on consumer migration', () => { - expect(runtimeKillCycle).not.toContain('- API_migrate-consumers-to-capabilities'); - expect(runtimeKillCycle).toContain('`API_kill-warpruntime` is removed from the live backlog'); - }); - - it('marks consumer migration done in the v17 release ledger', () => { - expect(releaseLedger).toContain('[x] API_migrate-consumers-to-capabilities'); - expect(releaseLedger).toContain('The consumer migration task is now'); - expect(releaseLedger).toContain('runtime/composition-root residue'); - expect(releaseLedger).not.toContain('`WarpRuntime` composition-root residue'); - }); -}); diff --git a/test/unit/scripts/capability-interfaces-closeout.test.ts b/test/unit/scripts/capability-interfaces-closeout.test.ts deleted file mode 100644 index b137442fc..000000000 --- a/test/unit/scripts/capability-interfaces-closeout.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { existsSync, readFileSync } from 'node:fs'; -import { join } from 'node:path'; -import { describe, expect, it } from 'vitest'; - -const capabilityNotePath = join( - process.cwd(), - 'docs/archive/backlog/v17.0.0-residual-backlog/API_capability-interfaces.md', -); -const factoryCycle = readFileSync( - join(process.cwd(), 'docs/design/0089-close-warpgraph-factory.md'), - 'utf8', -); -const queryControllerNote = readFileSync( - join(process.cwd(), 'docs/archive/backlog/v17.0.0-residual-backlog/GOD_query-controller.md'), - 'utf8', -); -const strandServiceNote = readFileSync( - join(process.cwd(), 'docs/archive/backlog/v17.0.0-residual-backlog/GOD_strand-service.md'), - 'utf8', -); -const releaseLedger = readFileSync( - join(process.cwd(), 'docs/releases/v17.0.0/README.md'), - 'utf8', -); - -describe('capability interfaces closeout', () => { - it('removes the stale live card', () => { - expect(existsSync(capabilityNotePath)).toBe(false); - }); - - it('unblocks downstream v17 notes from the stale foundation card', () => { - expect(factoryCycle).toContain('The stale `API_warpgraph-factory` card is removed'); - expect(factoryCycle).not.toContain('API_capability-interfaces'); - expect(queryControllerNote).not.toContain('API_capability-interfaces'); - expect(strandServiceNote).not.toContain('API_capability-interfaces'); - }); - - it('preserves the shipped milestone in the release ledger', () => { - expect(releaseLedger).toContain('[x] API_capability-interfaces'); - expect(releaseLedger).toContain('cycle 0086 retired stale live card'); - }); -}); diff --git a/test/unit/scripts/changelog-config-extension-shape.test.ts b/test/unit/scripts/changelog-config-extension-shape.test.ts deleted file mode 100644 index 49e69214a..000000000 --- a/test/unit/scripts/changelog-config-extension-shape.test.ts +++ /dev/null @@ -1,22 +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('changelog config extension shape', () => { - it('does not claim the TypeScript config pair still uses .js extensions', () => { - const changelog = readRepoFile('CHANGELOG.md'); - const typeScriptBullet = changelog - .split('\n') - .find((line) => line.includes('**100% TypeScript**')) ?? ''; - - expect(typeScriptBullet).not.toContain('eslint.config.js and vitest.config.js'); - expect(typeScriptBullet).toContain('eslint.config.ts'); - expect(typeScriptBullet).toContain('vitest.config.ts'); - }); -}); diff --git a/test/unit/scripts/cli-command-registry.test.ts b/test/unit/scripts/cli-command-registry.test.ts new file mode 100644 index 000000000..49f53b162 --- /dev/null +++ b/test/unit/scripts/cli-command-registry.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest'; + +import { KNOWN_COMMANDS, parseArgs } from '../../../bin/cli/infrastructure.ts'; +import { COMMANDS } from '../../../bin/cli/commands/registry.ts'; + +const REPRESENTATIVE_COMMANDS = [ + 'info', + 'check', + 'doctor', + 'query', + 'seek', + 'bisect', + 'debug', + 'strand', + 'verify-audit', + 'install-hooks', +] as const; + +describe('CLI command registry', () => { + it('keeps the parser command list aligned with the executable registry', () => { + expect([...KNOWN_COMMANDS].sort()).toStrictEqual([...COMMANDS.keys()].sort()); + }); + + it.each(REPRESENTATIVE_COMMANDS)('parses %s as an executable command', (command) => { + expect(parseArgs([command, '--help'])).toMatchObject({ + command, + options: { help: true }, + }); + expect(COMMANDS.has(command)).toBe(true); + }); +}); diff --git a/test/unit/scripts/cli-guide-shape.test.ts b/test/unit/scripts/cli-guide-shape.test.ts deleted file mode 100644 index d952b9927..000000000 --- a/test/unit/scripts/cli-guide-shape.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { describe, expect, it } from 'vitest'; - -const guide = readFileSync( - fileURLToPath(new URL('../../../docs/CLI_GUIDE.md', import.meta.url)), - 'utf8', -); - -describe('CLI guide shape', () => { - it('positions the CLI as the operator surface', () => { - expect(guide).toContain('# CLI guide'); - expect(guide).toContain("This is the operator's guide."); - expect(guide).toContain('If you are building an app, start with the [Guide](GUIDE.md).'); - expect(guide).not.toContain('WarpRuntime'); - }); - - it('organizes commands by workflow instead of as a flag bucket', () => { - expect(guide).toContain('## Workflow 1: pre-flight checks'); - expect(guide).toContain('## Workflow 2: in-flight inspection'); - expect(guide).toContain('## Workflow 3: black-box recovery'); - expect(guide).toContain('## Workflow 4: debugger commands'); - expect(guide).toContain('## Workflow 5: speculative lanes'); - expect(guide).toContain('## Workflow 6: trust and maintenance'); - }); - - it('covers the current command families and operator views', () => { - expect(guide).toContain('git warp info'); - expect(guide).toContain('git warp check'); - expect(guide).toContain('git warp doctor'); - expect(guide).toContain('git warp query'); - expect(guide).toContain('git warp seek'); - expect(guide).toContain('git warp bisect'); - expect(guide).toContain('git warp debug conflicts'); - expect(guide).toContain('git warp strand create'); - expect(guide).toContain('git warp verify-audit'); - expect(guide).toContain('git warp install-hooks'); - expect(guide).toContain('active seek cursor'); - }); -}); diff --git a/test/unit/scripts/contamination-dynamic-imports-shape.test.ts b/test/unit/scripts/contamination-dynamic-imports-shape.test.ts deleted file mode 100644 index cdf399b11..000000000 --- a/test/unit/scripts/contamination-dynamic-imports-shape.test.ts +++ /dev/null @@ -1,43 +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('dynamic import-law hygiene', () => { - it('teaches the scanner and semgrep about dynamic import-law violations', () => { - const contaminationMap = readRepoFile('scripts/contamination-map.ts'); - const semgrepPolicy = readRepoFile('semgrep/typescript-anti-sludge.yml'); - - expect(contaminationMap).toContain('core-imports-node-protocol-dynamic'); - expect(contaminationMap).toContain('core-imports-infrastructure-dynamic'); - expect(contaminationMap).toContain("src/domain/utils/defaultCrypto.ts"); - expect(contaminationMap).toContain("src/domain/utils/defaultTrustCrypto.ts"); - expect(semgrepPolicy).toContain('ts-no-dynamic-node-imports-in-core'); - expect(semgrepPolicy).toContain('ts-no-dynamic-infrastructure-imports-in-core'); - }); - - it('documents the narrow sanctioned dynamic adapter-loader carve-out', () => { - const antiSludgePolicy = readRepoFile('docs/ANTI_SLUDGE_POLICY.md'); - - expect(antiSludgePolicy).toContain('Authorized dynamic adapter-loader files'); - expect(antiSludgePolicy).toContain('src/domain/utils/defaultCrypto.ts'); - expect(antiSludgePolicy).toContain('src/domain/utils/defaultTrustCrypto.ts'); - expect(antiSludgePolicy).toContain('src/domain/utils/roaring.ts'); - expect(antiSludgePolicy).toContain('src/domain/services/controllers/SyncController.ts'); - }); - - it('removes the hidden node platform imports from trust and roaring helpers', () => { - const defaultTrustCrypto = readRepoFile('src/domain/utils/defaultTrustCrypto.ts'); - const roaring = readRepoFile('src/domain/utils/roaring.ts'); - - expect(defaultTrustCrypto).not.toContain("node:crypto"); - expect(defaultTrustCrypto).toContain("TrustCryptoAdapter.ts"); - expect(roaring).not.toContain("node:module"); - expect(roaring).toContain("RoaringLoaderAdapter.ts"); - }); -}); diff --git a/test/unit/scripts/contamination-map-command.test.ts b/test/unit/scripts/contamination-map-command.test.ts new file mode 100644 index 000000000..3b19a47bd --- /dev/null +++ b/test/unit/scripts/contamination-map-command.test.ts @@ -0,0 +1,12 @@ +import { execFileSync } from 'node:child_process'; +import { describe, expect, it } from 'vitest'; + +describe('contamination map command', () => { + it('enforces dynamic import policy through the scanner command', () => { + expect(() => execFileSync( + process.execPath, + ['scripts/contamination-map.ts'], + { cwd: process.cwd(), encoding: 'utf8', stdio: 'pipe' }, + )).not.toThrow(); + }); +}); diff --git a/test/unit/scripts/content-access-duplication-shape.test.ts b/test/unit/scripts/content-access-duplication-shape.test.ts deleted file mode 100644 index a981425da..000000000 --- a/test/unit/scripts/content-access-duplication-shape.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { existsSync, 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('content-access duplication closeout', () => { - it('marks the v17 ledger entry as closed and absorbed by the shared seam', () => { - const releaseReadme = readRepoFile('docs/releases/v17.0.0/README.md'); - - expect(releaseReadme).toContain('[x] SLUDGE_content-access-duplication'); - expect(releaseReadme).toContain('cycle 0051 hill-met; implementation duplication already'); - expect(releaseReadme).toContain('reduced into `QueryContent.ts`'); - }); - - it('makes capability migration own the deferred content accessor surface', () => { - const migrateNote = readRepoFile('docs/archive/backlog/v17.0.0-residual-backlog/API_migrate-consumers-to-capabilities.md'); - - expect(migrateNote).toContain('NodeContent'); - expect(migrateNote).toContain('EdgeContent'); - expect(migrateNote).toContain('content accessor'); - }); - - it('removes the stale live sludge note from the v17 lane', () => { - expect(existsSync(`${repoRoot}docs/archive/backlog/v17.0.0-residual-backlog/SLUDGE_content-access-duplication.md`)).toBe(false); - }); -}); diff --git a/test/unit/scripts/dead-code-cleanup-shape.test.ts b/test/unit/scripts/dead-code-cleanup-shape.test.ts deleted file mode 100644 index 92426aa38..000000000 --- a/test/unit/scripts/dead-code-cleanup-shape.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { existsSync, 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('dead-code cleanup closeout', () => { - it('marks the v17 ledger entry as not met and points at the real owner', () => { - const releaseReadme = readRepoFile('docs/releases/v17.0.0/README.md'); - - expect(releaseReadme).toContain('[✗] SLUDGE_dead-code-cleanup'); - expect(releaseReadme).toContain('cycle 0052 not-met'); - expect(releaseReadme).toContain('PROTO_purge-fake-models'); - }); - - it('makes the fake-model purge note own the live blocker explicitly', () => { - const owningNote = readRepoFile('docs/archive/backlog/v17.0.0-residual-backlog/PROTO_purge-fake-models.md'); - - expect(owningNote).toContain('ConflictCandidateCollector'); - expect(owningNote).toContain('ConflictOpAnchor'); - expect(owningNote).toContain('OpStrategies'); - }); - - it('removes the duplicate dead-code card from the live v17 lane', () => { - expect(existsSync(`${repoRoot}docs/archive/backlog/v17.0.0-residual-backlog/SLUDGE_dead-code-cleanup.md`)).toBe(false); - }); -}); diff --git a/test/unit/scripts/delete-warpruntime-class-split.test.ts b/test/unit/scripts/delete-warpruntime-class-split.test.ts deleted file mode 100644 index 080be7635..000000000 --- a/test/unit/scripts/delete-warpruntime-class-split.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { existsSync, readFileSync } from 'node:fs'; -import { join } from 'node:path'; - -const classDeleteNotePath = join( - process.cwd(), - 'docs/archive/backlog/v17.0.0-residual-backlog/API_delete-warpruntime-class.md', -); -const runtimeHostSource = readFileSync( - join(process.cwd(), 'src/domain/RuntimeHost.ts'), - 'utf8', -); -const runtimeFilePath = join(process.cwd(), 'src/domain/WarpRuntime.ts'); -const runtimeKillCycle = readFileSync( - join(process.cwd(), 'docs/design/0084-close-warpruntime-umbrella.md'), - 'utf8', -); -const classDeleteCycle = readFileSync( - join(process.cwd(), 'docs/design/0083-delete-runtime-host-class-name.md'), - 'utf8', -); -const releaseLedger = readFileSync( - join(process.cwd(), 'docs/releases/v17.0.0/README.md'), - 'utf8', -); - -describe('delete warpruntime class split', () => { - it('deletes the old class source and opener residue', () => { - expect(existsSync(runtimeFilePath)).toBe(false); - expect(existsSync(classDeleteNotePath)).toBe(false); - expect(runtimeHostSource).toContain('export default class RuntimeHost'); - expect(runtimeHostSource).toContain('export async function openRuntimeHost('); - expect(runtimeHostSource).not.toContain('export default class WarpRuntime'); - expect(runtimeHostSource).not.toContain('openWarpRuntime('); - expect(runtimeHostSource).not.toContain('getWarpRuntimePrototype'); - }); - - it('feeds the umbrella closeout after the class delete', () => { - expect(runtimeKillCycle).toContain('Cycles `0067`'); - expect(runtimeKillCycle).toContain('through `0083` removed the bridge'); - expect(runtimeKillCycle).not.toContain('- API_delete-warpruntime-class'); - expect(classDeleteCycle).toContain('The active source tree no longer contains `src/domain/WarpRuntime.ts`'); - }); - - it('records the reduced remaining order in the v17 release ledger', () => { - expect(releaseLedger).toContain('Cycle 0078 then completed'); - expect(releaseLedger).toContain('Cycle 0079 then proved'); - expect(releaseLedger).toContain('Cycle 0080 then completed'); - expect(releaseLedger).toMatch(/Cycle\s+0082 then closed/); - expect(releaseLedger).toMatch(/Cycle\s+0083 then deleted/); - expect(releaseLedger).toMatch(/Cycle\s+0084 then closed/); - expect(releaseLedger).not.toContain('`DX_migrate-seed-and-runtime-helpers-off-warpruntime`'); - expect(releaseLedger).not.toContain('`DX_migrate-runtime-suites-off-warpruntime`'); - expect(releaseLedger).not.toContain('`API_delete-warpruntime-class`'); - expect(releaseLedger).toContain('[x] API_kill-warpruntime'); - expect(releaseLedger).not.toContain('`PORT_extract-runtime-host-product`'); - }); -}); diff --git a/test/unit/scripts/release-policy-shape.test.ts b/test/unit/scripts/release-policy-shape.test.ts index 859836110..1fa10cdd9 100644 --- a/test/unit/scripts/release-policy-shape.test.ts +++ b/test/unit/scripts/release-policy-shape.test.ts @@ -77,6 +77,27 @@ const v18GoalpostPaths = [ '../../../docs/method/roadmap/v18.0.0/v18-gp4-holographic-slicing-checkpoint-basis.md', '../../../docs/method/roadmap/v18.0.0/v18-gp5-release-operation-evidence.md', ].map((path) => fileURLToPath(new URL(path, import.meta.url))); +const v18ReleaseBlockersIssue = '[#552](https://github.com/git-stunts/git-warp/issues/552)'; +const v18ReleaseBlockersClosedRow = [ + `| ${v18ReleaseBlockersIssue}`, + 'v18 public release blockers', + 'Closed', + 'release', + 'release, v18.0.0', + 'graph-model-substrate', + '-', + 'release |', +].join(' | '); +const v18ReleaseBlockersBlockedRow = [ + `| ${v18ReleaseBlockersIssue}`, + 'v18 public release blockers', + 'Blocked', + 'release', + 'release, v18.0.0', + 'graph-model-substrate', + '-', + 'blocked, release |', +].join(' | '); describe('release policy shape', () => { it('keeps package and jsr versions aligned on the release branch', () => { @@ -221,12 +242,18 @@ describe('release policy shape', () => { it('sets up the active v18 roadmap planning instance', () => { expect(roadmap).toContain('## Active Planning Instance'); + expect(roadmap).toContain('| Release status | `pre-tag complete` |'); expect(roadmap).toContain('| Goalposts | `5` |'); + expect(roadmap).toContain('| Landed goalposts | `5` |'); expect(roadmap).toContain('| Total planned slice budget | `53` |'); - expect(roadmap).toContain('v18.0.0 goalposts: 4/5 landed'); - expect(roadmap).toContain('v18.0.0 slices: 47/53 landed'); - expect(roadmap).toContain('next slice: reconcile #552 against current issue metadata and release evidence'); + expect(roadmap).toContain('v18.0.0 goalposts: 5/5 landed'); + expect(roadmap).toContain('v18.0.0 slices: 53/53 landed'); + expect(roadmap).toContain('next slice: keep paying down v18-line debt without cutting the tag'); + expect(roadmap).toContain('No v18 tag has been cut'); + expect(roadmap).toContain('explicit operator approval'); expect(roadmap).toContain('method/roadmap/v18.0.0/v18-gp4-holographic-slicing-checkpoint-basis.md'); + expect(roadmap).toContain(v18ReleaseBlockersClosedRow); + expect(roadmap).not.toContain(v18ReleaseBlockersBlockedRow); for (const goalpostPath of v18GoalpostPaths) { const goalpost = readFileSync(goalpostPath, 'utf8'); diff --git a/test/unit/scripts/typescript-config-files.test.ts b/test/unit/scripts/typescript-config-files.test.ts new file mode 100644 index 000000000..a653c29f5 --- /dev/null +++ b/test/unit/scripts/typescript-config-files.test.ts @@ -0,0 +1,17 @@ +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; + +const repoRoot = process.cwd(); + +describe('TypeScript config package files', () => { + it('ships TypeScript-native lint and test config files', () => { + expect(existsSync(join(repoRoot, 'eslint.config.ts'))).toBe(true); + expect(existsSync(join(repoRoot, 'vitest.config.ts'))).toBe(true); + }); + + it('does not keep the retired JavaScript config filenames', () => { + expect(existsSync(join(repoRoot, 'eslint.config.js'))).toBe(false); + expect(existsSync(join(repoRoot, 'vitest.config.js'))).toBe(false); + }); +});