Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ roadmap state.
│ intent · reading · tick · receipt │
├──────────────────────────────────────────────────┤
│ Opaque storage composition │
│ GitStorage.open() · MemoryStorage.create()
│ GitStorage.open()
├──────────┬───────────┬────────────┬──────────────┤
│ Query │ Patch │ Materialize│ Sync │
│Controller│Controller │ Controller │ Controller │
Expand Down Expand Up @@ -206,7 +206,6 @@ Abstract contracts between domain and infrastructure:
- **CryptoPort** — hash, hmac, sign, verify
- **ClockPort** — wall clock (injected, not ambient)
- **LoggerPort** — structured logging
- **SeekCachePort** — persistent seek cache for time-travel
- **PatchJournalPort** — streamed patch-entry scans
- **CheckpointStorePort** — folded checkpoint state storage
- **IndexStorePort** — streamed index shard storage
Expand All @@ -218,11 +217,12 @@ Concrete implementations of ports:
- **GitTimelineHistoryAdapter** — timeline-history Git commands via
@git-stunts/plumbing
- **GitCasRepositoryAdapter** — repository-scoped git-cas service composition
- **InMemoryGraphAdapter** — in-memory Maps for testing
- **CborCodec** — CBOR encoding via cbor-x
- **NodeCryptoAdapter / WebCryptoAdapter** — hash/sign via node:crypto or SubtleCrypto
- **CasBlobAdapter** — content-addressable blob storage via @git-stunts/git-cas
- **CasSeekCacheAdapter** — persistent seek cache on git-cas

In-memory persistence implementations live under `test/helpers/`; they are not
production adapters or package exports.

## Git storage model

Expand Down
27 changes: 23 additions & 4 deletions bin/cli/commands/check.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
import HealthCheckService from '../../../src/domain/services/HealthCheckService.ts';
import { buildCheckpointRef, buildCoverageRef } from '../../../src/domain/utils/RefLayout.ts';
import { EXIT_CODES } from '../infrastructure.ts';
import { openGraph, applyCursorCeiling, emitCursorWarning, readCheckpointDate, createHookInstaller } from '../shared.ts';
import type { CliOptions, Persistence, WarpGraphInstance } from '../types.ts';
import type HookPathPort from '../../../src/ports/HookPathPort.ts';

/** Performs a health check on the graph persistence. */
async function getHealth(persistence: Persistence): Promise<{ status: string; components: { repository: { status: string; latencyMs: number }; index: { status: string; loaded: boolean; shardCount?: number } }; cachedAt?: string }> {
const healthService = new HealthCheckService({ persistence });
return await healthService.getHealth(0);
async function getHealth(persistence: Persistence): Promise<{ status: string; components: { repository: { status: string; latencyMs: number }; index: { status: string; loaded: boolean; shardCount?: number } } }> {
try {
const ping = await persistence.ping();
const repositoryStatus = ping.ok ? 'healthy' : 'unhealthy';
return {
status: ping.ok ? 'degraded' : 'unhealthy',
components: {
repository: {
status: repositoryStatus,
latencyMs: Math.round(ping.latencyMs * 100) / 100,
},
index: { status: 'degraded', loaded: false },
},
};
} catch {
return {
status: 'unhealthy',
components: {
repository: { status: 'unhealthy', latencyMs: 0 },
index: { status: 'degraded', loaded: false },
},
};
}
}

/** Collects garbage collection metrics for the graph. */
Expand Down
9 changes: 3 additions & 6 deletions bin/cli/commands/doctor/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
* @module cli/commands/doctor/checks
*/

import HealthCheckService from '../../../../src/domain/services/HealthCheckService.ts';
import type { WarpStateSnapshotRecord } from '../../../../src/ports/WarpStateCachePort.ts';
import type { CorePersistence } from '../../../../src/domain/types/WarpPersistence.ts';
import {
buildCheckpointRef,
buildCoverageRef,
Expand All @@ -35,12 +33,11 @@ function internalError(id: string, err: unknown): DoctorFinding {

// ── repo-accessible ─────────────────────────────────────────────────────────

/** Verify the repository is reachable via HealthCheckService. */
/** Verify the repository is reachable. */
export async function checkRepoAccessible(ctx: DoctorContext): Promise<DoctorFinding> {
try {
const svc = new HealthCheckService({ persistence: ctx.persistence as unknown as CorePersistence });
const health = await svc.getHealth(0);
if (health.components.repository.status === 'unhealthy') {
const health = await ctx.persistence.ping();
if (!health.ok) {
return {
id: 'repo-accessible', status: 'fail', code: CODES.REPO_UNREACHABLE,
impact: 'operability', message: 'Repository is not accessible',
Expand Down
18 changes: 2 additions & 16 deletions bin/cli/commands/seek.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from '../time-travel-shared.ts';
import { EXIT_CODES, usageError, notFoundError, parseCommandArgs } from '../infrastructure.ts';
import { seekSchema } from '../schemas.ts';
import { openGraph, readActiveCursor, writeActiveCursor, wireSeekCache } from '../shared.ts';
import { openGraph, readActiveCursor, writeActiveCursor } from '../shared.ts';
import type { WarpState } from '../../../src/domain/services/JoinReducer.ts';
import type { CliOptions, Persistence, WarpGraphInstance, WriterTickInfo, CursorBlob, SeekSpec } from '../types.ts';

Expand All @@ -29,8 +29,6 @@ const SEEK_OPTIONS = {
load: { type: 'string' },
list: { type: 'boolean', default: false },
drop: { type: 'string' },
'clear-cache': { type: 'boolean', default: false },
'no-persistent-cache': { type: 'boolean', default: false },
diff: { type: 'boolean', default: false },
'diff-limit': { type: 'string', default: '2000' },
};
Expand Down Expand Up @@ -248,19 +246,7 @@ async function handleSeekStatus({ graph, graphName, persistence, activeCursor, t
/** Handles the `git warp seek` command across all sub-actions. */
export default async function handleSeek({ options, args }: { options: CliOptions; args: string[] }): Promise<{ payload: unknown; exitCode: number }> {
const seekSpec = parseSeekArgs(args);
const { graph, graphName, persistence, createSeekCache } = await openGraph(options);
void wireSeekCache({ graph, createSeekCache, graphName, seekSpec });

// Handle --clear-cache before discovering ticks (no materialization needed)
if (seekSpec.action === 'clear-cache') {
if (graph.seekCache) {
await graph.seekCache.clear();
}
return {
payload: { graph: graphName, action: 'clear-cache', message: 'Seek cache cleared.' },
exitCode: EXIT_CODES.OK,
};
}
const { graph, graphName, persistence } = await openGraph(options);

const activeCursor = await readActiveCursor(persistence, graphName);
const { ticks, maxTick, perWriter } = await graph.discoverTicks();
Expand Down
9 changes: 1 addition & 8 deletions bin/cli/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,6 @@ type SeekInput = {
load?: string | undefined;
list: boolean;
drop?: string | undefined;
'clear-cache': boolean;
'no-persistent-cache': boolean;
diff: boolean;
'diff-limit': number;
};
Expand All @@ -161,7 +159,6 @@ function countSeekActions(val: SeekInput): number {
val.load !== undefined,
val.list,
val.drop !== undefined,
val['clear-cache'],
].filter(Boolean).length;
}

Expand All @@ -186,7 +183,7 @@ function issueIf(ctx: z.RefinementCtx, condition: boolean, message: string): voi
*/
function refineSeekActions(val: SeekInput, ctx: z.RefinementCtx): void {
issueIf(ctx, countSeekActions(val) > 1,
'Only one seek action flag allowed at a time (--tick, --latest, --save, --load, --list, --drop, --clear-cache)');
'Only one seek action flag allowed at a time (--tick, --latest, --save, --load, --list, --drop)');
issueIf(ctx, val.diff && !hasDiffCompatibleAction(val),
'--diff cannot be used without --tick, --latest, or --load');
issueIf(ctx, val['diff-limit'] !== 2000 && !val.diff,
Expand All @@ -205,7 +202,6 @@ const SEEK_ACTION_TABLE: Array<{ key: keyof SeekInput; action: string; isBool: b
{ key: 'load', action: 'load', isBool: false },
{ key: 'list', action: 'list', isBool: true },
{ key: 'drop', action: 'drop', isBool: false },
{ key: 'clear-cache', action: 'clear-cache', isBool: true },
];

/**
Expand Down Expand Up @@ -244,7 +240,6 @@ function transformSeek(val: SeekInput) {
action,
tickValue,
name,
noPersistentCache: val['no-persistent-cache'],
diff: val.diff,
diffLimit: val['diff-limit'],
};
Expand All @@ -259,8 +254,6 @@ export const seekSchema = z.object({
load: z.string().min(1, 'Missing value for --load').optional(),
list: z.boolean().default(false),
drop: z.string().min(1, 'Missing value for --drop').optional(),
'clear-cache': z.boolean().default(false),
'no-persistent-cache': z.boolean().default(false),
diff: z.boolean().default(false),
'diff-limit': z.coerce.number().int({ message: '--diff-limit must be a positive integer' }).positive({ message: '--diff-limit must be a positive integer' }).refine(n => Number.isFinite(n), { message: '--diff-limit must be a finite number' }).default(2000),
}).strict().superRefine(refineSeekActions).transform((val) => transformSeek(val));
Expand Down
27 changes: 4 additions & 23 deletions bin/cli/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,15 @@ import { usageError, notFoundError } from './infrastructure.ts';
import { GitStorage } from '../../storage.ts';
import { resolveWarpStorage } from '../../src/application/WarpStorageRegistry.ts';
import type RuntimeStorageProviderPort from '../../src/ports/RuntimeStorageProviderPort.ts';
import type SeekCachePort from '../../src/ports/SeekCachePort.ts';
import type TrustChainPort from '../../src/ports/TrustChainPort.ts';
import type CryptoPort from '../../src/ports/CryptoPort.ts';
import type HookPathPort from '../../src/ports/HookPathPort.ts';

import type { Persistence, WarpGraphInstance, CursorBlob, CliOptions, SeekSpec } from './types.ts';
import type { Persistence, WarpGraphInstance, CursorBlob, CliOptions } from './types.ts';

export type CliStorageBinding = {
readonly persistence: Persistence;
readonly runtimeStorage: RuntimeStorageProviderPort;
readonly createSeekCache: (timelineName: string) => SeekCachePort;
readonly createTrustChain: (crypto: CryptoPort) => TrustChainPort;
readonly hookPaths: HookPathPort;
};
Expand All @@ -39,15 +37,13 @@ export async function createPersistence(repoPath: string): Promise<CliStorageBin
const storage = await GitStorage.open({ cwd: repoPath });
const binding = resolveWarpStorage(storage);
if (!(binding.history instanceof GitTimelineHistoryAdapter)
|| binding.createSeekCache === undefined
|| binding.createTrustChain === undefined
|| binding.hookPaths === undefined) {
throw usageError('GitStorage returned an incomplete CLI storage binding');
}
return {
persistence: binding.history,
runtimeStorage: binding.runtimeStorage,
createSeekCache: binding.createSeekCache,
createTrustChain: binding.createTrustChain,
hookPaths: binding.hookPaths,
};
Expand Down Expand Up @@ -98,8 +94,8 @@ export async function resolveGraphName(persistence: Persistence, explicitGraph:
/**
* Opens a WarpCore for the given CLI options.
*/
export async function openGraph(options: CliOptions): Promise<{ graph: WarpGraphInstance; graphName: string; persistence: Persistence; runtimeStorage: RuntimeStorageProviderPort; createSeekCache: (timelineName: string) => SeekCachePort; hookPaths: HookPathPort }> {
const { persistence, runtimeStorage, createSeekCache, hookPaths } = await createPersistence(options.repo);
export async function openGraph(options: CliOptions): Promise<{ graph: WarpGraphInstance; graphName: string; persistence: Persistence; runtimeStorage: RuntimeStorageProviderPort; hookPaths: HookPathPort }> {
const { persistence, runtimeStorage, hookPaths } = await createPersistence(options.repo);
const graphName = await resolveGraphName(persistence, options.graph);
if (typeof options.graph === 'string' && options.graph.length > 0) {
const graphNames = await listGraphNames(persistence);
Expand All @@ -114,7 +110,7 @@ export async function openGraph(options: CliOptions): Promise<{ graph: WarpGraph
writerId: options.writer,
crypto: new WebCryptoAdapter(),
});
return { graph, graphName, persistence, runtimeStorage, createSeekCache, hookPaths };
return { graph, graphName, persistence, runtimeStorage, hookPaths };
}

/**
Expand Down Expand Up @@ -240,18 +236,3 @@ function readPackageVersion(rawJson: string): string {
const obj = raw as { version: string };
return obj.version;
}

/**
* Attaches a persistent seek cache to a graph instance unless disabled by flags.
*/
export function wireSeekCache({ graph, createSeekCache, graphName, seekSpec }: {
graph: WarpGraphInstance;
createSeekCache: (timelineName: string) => SeekCachePort;
graphName: string;
seekSpec: SeekSpec;
}): void {
if (seekSpec.noPersistentCache) {
return;
}
graph.setSeekCache(createSeekCache(graphName));
}
1 change: 0 additions & 1 deletion bin/cli/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export type SeekSpec = {
action: string;
tickValue: string | null;
name: string | null;
noPersistentCache: boolean;
diff: boolean;
diffLimit: number;
};
Expand Down
1 change: 0 additions & 1 deletion bin/warp-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ async function main(): Promise<void> {
return; // Keep the process alive
}

// Use process.exit() to avoid waiting for fire-and-forget I/O (e.g. seek cache writes).
process.exit(normalized.exitCode);
}

Expand Down
33 changes: 11 additions & 22 deletions docs/TECHNICAL_TEARDOWN.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,32 +168,24 @@ Application code usually starts with `index.ts`.

At import time, `index.ts` does three major things:

1. Imports the public API classes, functions, ports, adapters, error classes,
and compatibility exports.
2. Calls `installDefaultRuntimeHostNodePorts()`.
3. Exports the API surface, including `GitGraphAdapter`,
`InMemoryGraphAdapter`, `openWarpWorldline()`, `openWarpGraph()`,
`WarpApp`, `WarpCore`, `PatchBuilder`, query/observer types, sync helpers,
trust and provenance helpers, and error classes.
1. Exports the first-use values `openWarp`, `intent`, and `reading`.
2. Exports application types for timelines, intents, readings, receipts, and
opaque storage handles.
3. Leaves storage construction, advanced optics, and diagnostics on explicit
package subpaths.

```mermaid
flowchart TB
app["Application import"] --> index["index.ts"]
index --> defaults["installDefaultRuntimeHostNodePorts()"]
defaults --> codec["default CBOR codec resolver"]
defaults --> crypto["NodeCryptoAdapter resolver"]
defaults --> trust["TrustCryptoAdapter resolver"]
defaults --> message["default commit-message codec"]
index --> exports["Public exports"]
exports --> worldline["openWarpWorldline()"]
exports --> graph["openWarpGraph()"]
exports --> adapters["GitGraphAdapter / InMemoryGraphAdapter"]
exports --> warp["openWarp()"]
exports --> intents["intent builders"]
exports --> readings["reading builders"]
storage["storage subpath"] --> gitStorage["GitStorage"]
```

This is a bootstrapping side effect, but a bounded one. The runtime still
constructs real graph objects only when the caller opens a worldline or graph.
The import installs resolver defaults so callers do not have to inject a codec
or crypto implementation in normal Node usage.
The root import has no storage bootstrap side effect. `GitStorage.open()` owns
the Node Git and git-cas composition used by first-use applications.

## System Architecture

Expand Down Expand Up @@ -281,7 +273,6 @@ classDiagram
_versionVector
_materializedGraph
_lastFrontier
_seekCache
_stateCache
_patchJournal
_checkpointStore
Expand Down Expand Up @@ -1470,8 +1461,6 @@ ambient environment variables.
| `autoMaterialize` | Controls runtime eager materialization behavior. | Faster reads versus less background work. |
| `checkpointPolicy.every` | Auto-create checkpoints after enough patches. | More checkpoint storage for shorter future replay. |
| `gcPolicy` | Tombstone compaction thresholds and enablement. | Lower storage/scan cost versus risk of doing extra compaction work. |
| `adjacencyCacheSize` | Size of adjacency cache. Default is `3`. | More memory for fewer rebuilds. |
| `seekCache` | Persistent seek cache. | Faster time-travel reads versus cache storage. |
| `stateCache` | Durable materialized snapshot cache. | More CAS storage for faster materialization. |
| `blobStorage` | Content/CAS storage. | Enables attachments and git-cas patch storage. |
| `patchJournal` | Patch serialization and storage boundary. | Custom storage can replace default CBOR journal. |
Expand Down
2 changes: 1 addition & 1 deletion docs/migrations/v19/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ and joins first.
| `JoinReceiptOutcome` | root `JoinOutcome` | operation-specific outcome alias rename |
| `EdgePropertyIntentFields` | removed | no edge-property intent ships in the v19 root |
| `GitGraphAdapter` | `storage` `GitStorage.open({ cwd })` | plumbing and CAS composition are internal |
| `InMemoryGraphAdapter` | `storage` `MemoryStorage.create()` | graph name and adapter constructor removed |
| `InMemoryGraphAdapter` | removed | tests may use test helpers; apps use `GitStorage` |
| `GraphPersistencePort` | root `WarpStorage` for app options | old graph-shaped port removed from public API |
| `commit((patch) => ...)` | `timeline.write(intent.*)` | receipt-returning |
| `PatchBuilder` | removed | replace with intent builders |
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ Each old root symbol needs one explicit disposition:
| ------------------------ | ---------------------------------------------------------- |
| `openWarpWorldline()` | `openWarp().timeline(name)` |
| `GitGraphAdapter` | `GitStorage.open({ cwd })` from `storage` |
| `InMemoryGraphAdapter` | `MemoryStorage.create()` from `storage` |
| `InMemoryGraphAdapter` | removed; use `GitStorage` with a temporary repository |
| `commit((patch) => ...)` | `timeline.write(intent.*)` |
| `coordinate()` | `tick()` publicly; use advanced `captureCoordinate()` |
| `optic()` | `timeline.read(reading.*)` or `advanced` |
Expand Down
11 changes: 5 additions & 6 deletions docs/topics/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,23 +87,22 @@ WriteReceiptOptions @ index.ts#L62

## Storage export surface

Git-backed and in-memory adapters for first-use applications.
Git-backed storage for first-use applications.

### Value exports

Source: `storage.ts`. Count: 2.
Source: `storage.ts`. Count: 1.

```text
GitStorage @ storage.ts#L18
MemoryStorage @ storage.ts#L43
GitStorage @ storage.ts#L16
```

### Type exports

Source: `storage.ts`. Count: 1.

```text
GitStorageOptions @ storage.ts#L14
GitStorageOptions @ storage.ts#L12
```

## Advanced export surface
Expand Down Expand Up @@ -193,7 +192,7 @@ ReceiptSubstrateInspection @ diagnostics.ts#L15
Structured CLI errors for `--json` and `--ndjson` use the payload shape
`{ error: { code, message, cause? } }` from the CLI entry point.

Source: `bin/warp-graph.ts#L132`.
Source: `bin/warp-graph.ts#L131`.

## Public error classes

Expand Down
Loading
Loading