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
18 changes: 16 additions & 2 deletions docs/topics/cas-first-memoized-materialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ The bounded-memory read path is optic/worldline/query work over a sharded or
streamed basis. The state cache is the replay-skipping compatibility bridge for
legacy materialization and checkpoint flows.

`RuntimeHost.hasNode()` is the first compatibility read to consume the
handle-first path directly when the runtime uses the built-in trie-backed state
session and matching materialization reader. On an exact retained-coordinate
hit, it acquires the materialization, opens only the node-liveness trie through
a bounded page cache, answers one membership question, and releases the
acquisition. It does not hydrate `WarpState`, build adjacency, publish a state
snapshot, or populate `_cachedState`. A cold handle miss still performs the
current materialization path before retaining and reading the new root. A
custom state-session opener owns its root storage and encoding, so git-warp does
not pair it with the default reader and instead preserves the compatibility
fallback.

## `git-cas` Encapsulation

Materialization-root retention routes through the formal
Expand Down Expand Up @@ -163,8 +175,10 @@ lost payload bytes, or run Git garbage collection.

## Current Limitations

- RuntimeHost and checkpoint creation do not yet consume the handle-first
result, so their compatibility path still owns process-resident whole state.
- RuntimeHost exact node-liveness reads consume the handle-first result when
the built-in trie session and reader pair is active. Custom session openers,
properties, neighborhoods, list reads, checkpoint creation, and other
compatibility operations still own process-resident whole state.
- Exact state-cache hits bypass replay, but full materialization still hydrates
a full `WarpState`, scans retained node/edge tries, and builds full adjacency.
- Retained materialization descriptors currently carry node/edge trie roots;
Expand Down
7 changes: 7 additions & 0 deletions src/domain/RuntimeHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,9 @@ export default class RuntimeHost {
persistence: this._persistence,
checkpointStore,
materializations,
...(options.materializationRead === undefined
? {}
: { materializationRead: options.materializationRead }),
getStateCache: () => this._stateCache ?? null,
...(openStateSession === undefined ? {} : { openStateSession }),
patches: new RuntimePatchCollector(this),
Expand All @@ -422,6 +425,10 @@ export default class RuntimeHost {
this._auditService = auditService || null;
}

_readLiveNodePresence(nodeId: string): Promise<boolean | null> {
return this._materializeController.readLiveNodePresence(nodeId);
}

/**
* Advanced substrate replay primitive over the live frontier.
*/
Expand Down
96 changes: 96 additions & 0 deletions src/domain/materialization/TrieMaterializationReader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type CodecPort from '../../ports/CodecPort.ts';
import MaterializationReadPort from '../../ports/MaterializationReadPort.ts';
import BundleHandle from '../storage/BundleHandle.ts';
import WarpError from '../errors/WarpError.ts';
import PageCache from '../orset/trie/PageCache.ts';
import TrieCursor from '../orset/trie/TrieCursor.ts';
import TrieGeometry from '../orset/trie/TrieGeometry.ts';
import type TrieStorePort from '../orset/trie/TrieStorePort.ts';

const MAX_RESIDENT_READ_PAGES = 256;

/** Reads retained liveness roots without reconstructing a complete WarpState. */
export default class TrieMaterializationReader extends MaterializationReadPort {
readonly #store: TrieStorePort;
readonly #codec: CodecPort;
readonly #geometry: TrieGeometry;

constructor(options: {
readonly store: TrieStorePort;
readonly codec: CodecPort;
readonly geometry?: TrieGeometry;
}) {
super();
requireOptions(options);
this.#store = requireStore(options.store);
this.#codec = requireCodec(options.codec);
this.#geometry = options.geometry === undefined
? TrieGeometry.default16way()
: requireGeometry(options.geometry);
Object.freeze(this);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

override async hasNode(nodeAliveRoot: BundleHandle, nodeId: string): Promise<boolean> {
if (!(nodeAliveRoot instanceof BundleHandle)) {
throw new WarpError(
'Materialization node-liveness root must be a BundleHandle',
'E_MATERIALIZATION_RESUME'
);
}
const cursor = new TrieCursor({
rootOid: nodeAliveRoot.toString(),
store: this.#store,
geometry: this.#geometry,
codec: this.#codec,
pageCache: new PageCache({ maxResident: MAX_RESIDENT_READ_PAGES }),
});
return await cursor.contains(nodeId);
}
}

function requireOptions(options: object): void {
if (options === null || typeof options !== 'object' || Array.isArray(options)) {
throw readerError('options must be an object');
}
}

function requireStore(store: TrieStorePort): TrieStorePort {
if (
store === null
|| typeof store !== 'object'
|| !hasTrieOperations(store)
) {
throw readerError('store must provide trie read/write operations');
}
return store;
}

function hasTrieOperations(store: TrieStorePort): boolean {
return typeof store.readLeaf === 'function'
&& typeof store.readBranch === 'function'
&& typeof store.writeLeaf === 'function'
&& typeof store.writeBranch === 'function';
}

function requireCodec(codec: CodecPort): CodecPort {
if (
codec === null
|| typeof codec !== 'object'
|| typeof codec.encode !== 'function'
|| typeof codec.decode !== 'function'
) {
throw readerError('codec must provide encode/decode operations');
}
return codec;
}

function requireGeometry(geometry: TrieGeometry): TrieGeometry {
if (!(geometry instanceof TrieGeometry)) {
throw readerError('geometry must be a TrieGeometry instance');
}
return geometry;
}

function readerError(message: string): WarpError {
return new WarpError(`Materialization reader ${message}`, 'E_MATERIALIZATION_RESUME');
}
33 changes: 7 additions & 26 deletions src/domain/services/controllers/MaterializeController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
import {
materializationSessionOpen,
reduceSessionBackedState,
type MaterializeSessionOpener,
} from './MaterializeSessionBridge.ts';
import MaterializeLiveStrategy from './MaterializeLiveStrategy.ts';
import MaterializeCoordinateStrategy from './MaterializeCoordinateStrategy.ts';
Expand All @@ -27,20 +26,12 @@ import {
shouldPublishMaterializeSnapshot,
type MaterializeSnapshotPublicationOptions,
} from './MaterializeSnapshotPublication.ts';
import type LoggerPort from '../../../ports/LoggerPort.ts';
import type CodecPort from '../../../ports/CodecPort.ts';
import type CryptoPort from '../../../ports/CryptoPort.ts';
import type CheckpointStorePort from '../../../ports/CheckpointStorePort.ts';
import type {
default as WarpStateCachePort,
WarpStateCoordinate,
WarpStateSnapshotProvenancePosture,
} from '../../../ports/WarpStateCachePort.ts';
import type MaterializationStorePort from '../../../ports/MaterializationStorePort.ts';
import type MaterializationWorkspacePort from '../../../ports/MaterializationWorkspacePort.ts';
import type PatchCollector from '../../capabilities/PatchCollector.ts';
import type { PatchWithSha } from '../../capabilities/PatchCollector.ts';
import type DetachedGraphFactory from '../../capabilities/DetachedGraphFactory.ts';
import PatchEntry from '../../artifacts/PatchEntry.ts';
import type WarpState from '../state/WarpState.ts';
import type { TickReceipt } from '../../types/TickReceipt.ts';
Expand All @@ -61,23 +52,9 @@ import {
releaseAcquisitionAfterFailure,
releaseWorkspaceAfterFailure,
} from './MaterializationWorkspaceCleanup.ts';
export type MaterializePersistence = {
readRef(ref: string): Promise<string | null>;
};
/** Constructor dependencies for MaterializeController. */
export type MaterializeDeps = {
logger: LoggerPort;
codec: CodecPort;
crypto: CryptoPort;
persistence: MaterializePersistence;
checkpointStore: CheckpointStorePort;
materializations: MaterializationStorePort;
getStateCache?: () => WarpStateCachePort | null;
openStateSession?: MaterializeSessionOpener;
patches: PatchCollector;
graphCloner: DetachedGraphFactory;
graphName: string;
};
import type { MaterializeDeps } from './MaterializeDeps.ts';

export type { MaterializeDeps, MaterializePersistence } from './MaterializeDeps.ts';

/** Full result of a materialization, returned to the caller. */
export type MaterializeResult = {
Expand All @@ -99,6 +76,7 @@ type ReducerInput = Parameters<typeof reduceJoinedPatches>[0];
function toReducerInput(patches: PatchWithSha[]): ReducerInput {
return patches as ReducerInput;
}

export type MaterializeReduceOutput = {
state: WarpState;
adjacency?: MaterializeAdjacency;
Expand Down Expand Up @@ -183,6 +161,9 @@ export default class MaterializeController {
resolveLiveMaterialization(): Promise<LiveMaterializationResolution> {
return this._liveStrategy.resolveMaterialization();
}
readLiveNodePresence(nodeId: string): Promise<boolean | null> {
return this._liveStrategy.readNodePresence(nodeId);
}

/** Coordinate materialization — explicit frontier. */
async materializeCoordinate(
Expand Down
30 changes: 30 additions & 0 deletions src/domain/services/controllers/MaterializeDeps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type CheckpointStorePort from '../../../ports/CheckpointStorePort.ts';
import type CodecPort from '../../../ports/CodecPort.ts';
import type CryptoPort from '../../../ports/CryptoPort.ts';
import type LoggerPort from '../../../ports/LoggerPort.ts';
import type MaterializationReadPort from '../../../ports/MaterializationReadPort.ts';
import type MaterializationStorePort from '../../../ports/MaterializationStorePort.ts';
import type WarpStateCachePort from '../../../ports/WarpStateCachePort.ts';
import type DetachedGraphFactory from '../../capabilities/DetachedGraphFactory.ts';
import type PatchCollector from '../../capabilities/PatchCollector.ts';
import type { MaterializeSessionOpener } from './MaterializeSessionBridge.ts';

export type MaterializePersistence = {
readRef(ref: string): Promise<string | null>;
};

/** Constructor dependencies for retained-handle materialization operations. */
export type MaterializeDeps = {
logger: LoggerPort;
codec: CodecPort;
crypto: CryptoPort;
persistence: MaterializePersistence;
checkpointStore: CheckpointStorePort;
materializations: MaterializationStorePort;
materializationRead?: MaterializationReadPort;
getStateCache?: () => WarpStateCachePort | null;
openStateSession?: MaterializeSessionOpener;
patches: PatchCollector;
graphCloner: DetachedGraphFactory;
graphName: string;
};
40 changes: 40 additions & 0 deletions src/domain/services/controllers/MaterializeLiveStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import type {
PatchWithSha,
} from '../../capabilities/PatchCollector.ts';
import type WarpStateCachePort from '../../../ports/WarpStateCachePort.ts';
import type MaterializationReadPort from '../../../ports/MaterializationReadPort.ts';
import type MaterializationHandle from '../../materialization/MaterializationHandle.ts';
import type {
WarpStateCoordinate,
} from '../../../ports/WarpStateCachePort.ts';
Expand Down Expand Up @@ -67,6 +69,23 @@ export default class MaterializeLiveStrategy {
return await this.materializeAndAcquire(coordinate, materializationCoordinate);
}

async readNodePresence(nodeId: string): Promise<boolean | null> {
const reader = this.runtime.deps.materializationRead;
if (reader === undefined) {
return null;
}
const resolution = await this.resolveMaterialization();
let presence: boolean | null;
try {
presence = await readNodePresence(reader, resolution.materialization, nodeId);
} catch (raw) {
await releaseAcquisitionAfterFailure(resolution, this.runtime.deps.logger);
throw raw;
}
await resolution.release();
return presence;
}

private async resolveRetainedMaterialization(
retained: MaterializationAcquisition,
coordinate: MaterializationCoordinate,
Expand Down Expand Up @@ -327,6 +346,27 @@ export default class MaterializeLiveStrategy {
}
}

async function readNodePresence(
reader: MaterializationReadPort,
materialization: MaterializationHandle | null,
nodeId: string,
): Promise<boolean | null> {
if (materialization === null) {
return false;
}
const root = materialization.roots.nodeAlive;
if (root.status === 'unavailable') {
return null;
}
if (root.status === 'empty') {
return false;
}
if (root.handle === null) {
throw resolutionError('retained node-liveness root has no handle');
}
return await reader.hasNode(root.handle, nodeId);
}

function emptyResolution(): LiveMaterializationResolution {
return new LiveMaterializationResolution({
materialization: null,
Expand Down
6 changes: 6 additions & 0 deletions src/domain/services/controllers/QueryReads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ async function ensureAndGetState(host: QueryReadHost): Promise<WarpState> {
// ── Read implementations ────────────────────────────────────────────

export async function hasNodeImpl(host: QueryReadHost, nodeId: string): Promise<boolean> {
if (host._readLiveNodePresence !== undefined) {
const retainedPresence = await host._readLiveNodePresence(nodeId);
if (retainedPresence !== null) {
return retainedPresence;
}
}
const state = await ensureAndGetState(host);
return state.nodeAlive.contains(nodeId);
}
Expand Down
1 change: 1 addition & 0 deletions src/domain/services/controllers/ReadGraphHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type FreshStateHost = {
};

export type QueryReadHost = FreshStateHost & {
_readLiveNodePresence?(nodeId: string): Promise<boolean | null>;
_propertyReader: PropertyIndexReader | null;
_logicalIndex: LogicalIndex | null;
_materializedGraph: MaterializedReadGraph | null;
Expand Down
14 changes: 14 additions & 0 deletions src/domain/warp/RuntimeHostBoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import StateHashService from '../services/state/StateHashService.ts';
import StateSession from '../orset/session/StateSession.ts';
import PageCache from '../orset/trie/PageCache.ts';
import TrieGeometry from '../orset/trie/TrieGeometry.ts';
import TrieMaterializationReader from '../materialization/TrieMaterializationReader.ts';
import WarpError from '../errors/WarpError.ts';
import {
buildEffectPipeline,
Expand Down Expand Up @@ -34,6 +35,7 @@ import type CheckpointStorePort from '../../ports/CheckpointStorePort.ts';
import type IndexStorePort from '../../ports/IndexStorePort.ts';
import type IntentStorePort from '../../ports/IntentStorePort.ts';
import type MaterializationStorePort from '../../ports/MaterializationStorePort.ts';
import type MaterializationReadPort from '../../ports/MaterializationReadPort.ts';
import type EffectSinkPort from '../../ports/EffectSinkPort.ts';
import type RuntimeStorageProviderPort from '../../ports/RuntimeStorageProviderPort.ts';
import type SchedulerPort from '../../ports/SchedulerPort.ts';
Expand Down Expand Up @@ -71,6 +73,7 @@ export type RuntimeHostConstructionOptions = {
indexStore: IndexStorePort;
intentStore: IntentStorePort;
materializations: MaterializationStorePort;
materializationRead?: MaterializationReadPort;
viewService: MaterializedViewService;
stateHashService?: StateHashService;
auditService?: AuditReceiptService;
Expand Down Expand Up @@ -334,6 +337,7 @@ export async function resolveRuntimeHostConstructionOptions(
}

let resolvedOpenStateSession: MaterializeSessionOpener | undefined;
let resolvedMaterializationRead: MaterializationReadPort | undefined;
if (openStateSession !== undefined) {
resolvedOpenStateSession = openStateSession;
} else if (storageServices.trie !== undefined) {
Expand All @@ -349,6 +353,13 @@ export async function resolveRuntimeHostConstructionOptions(
pageCache: new PageCache({ maxResident: 256 }),
workspace: sessionOptions.workspace,
});
// A custom session opener owns its root encoding; pair this reader only
// with the built-in session that shares its store and geometry.
resolvedMaterializationRead = new TrieMaterializationReader({
store,
codec: resolvedCodec,
geometry,
});
}

return {
Expand Down Expand Up @@ -378,6 +389,9 @@ export async function resolveRuntimeHostConstructionOptions(
indexStore: resolvedIndexStore,
intentStore: storageServices.intents,
materializations: storageServices.materializations,
...(resolvedMaterializationRead === undefined
? {}
: { materializationRead: resolvedMaterializationRead }),
viewService: resolvedViewService,
stateHashService: resolvedStateHashService,
...(resolvedAuditService !== undefined ? { auditService: resolvedAuditService } : {}),
Expand Down
6 changes: 6 additions & 0 deletions src/ports/MaterializationReadPort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type BundleHandle from '../domain/storage/BundleHandle.ts';

/** Bounded reads over independently retained materialization roots. */
export default abstract class MaterializationReadPort {
abstract hasNode(_nodeAliveRoot: BundleHandle, _nodeId: string): Promise<boolean>;
}
Loading
Loading