Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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