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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -246,5 +246,5 @@ jobs:
node-version: '22'
cache: 'npm'
- run: npm ci
- name: 'Gate 9: Unit coverage threshold'
- name: 'Gate 9: Unit and V18 optic coverage threshold'
run: npm run test:coverage:ci
25 changes: 25 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,31 @@ DTO and stop there**. Do not hallucinate fake domain models.
- Retros live in `docs/method/retro/<NNNN-slug>/`.
- Signposts are `docs/BEARING.md` and `docs/VISION.md`; update them at cycle boundaries, not mid-cycle.
- Zero tolerance for brokenness: if you encounter an error or warning in your path, fix it or surface it explicitly.
- End every turn with the compact progress footer:

```text
═══ ⋆★⋆ Progress Report ⋆★⋆ ═══

<goalpost name>
<progress bar> <percent> (<done>/<total> slices)

- [x] <completed slice>
- [ ] <open slice>

⎇ <branch> +<ahead>/-<behind>
<pr-icon> <pr-status>
```

Compute `+<ahead>/-<behind>` against the active merge target, normally
`origin/main`, using local refs unless the turn already fetched. Keep the PR
line compact:

- `🚫 none` when no PR exists.
- `📤 [#N](url)` when a PR is open.
- `📝 [#N](url)` when a PR is draft.
- `🏁 [#N](url)` when a PR was merged.
- `🐇 [#N](url)` when waiting for Code Rabbit.
- `🧪 [#N](url)` when CI is not finished yet.

## Engineering Doctrine

Expand Down
278 changes: 278 additions & 0 deletions bin/cli/commands/optic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
import { openWarpWorldline } from '../../../index.ts';
import QueryError from '../../../src/domain/errors/QueryError.ts';
import type ReadIdentity from '../../../src/domain/services/optic/ReadIdentity.ts';
import type WorldlineOptic from '../../../src/domain/services/optic/WorldlineOptic.ts';
import type NodeOpticReadResult from '../../../src/domain/services/optic/NodeOpticReadResult.ts';
import type NodePropertyOpticReadResult from '../../../src/domain/services/optic/NodePropertyOpticReadResult.ts';
import WebCryptoAdapter from '../../../src/infrastructure/adapters/WebCryptoAdapter.ts';
import { EXIT_CODES, notFoundError, parseCommandArgs, usageError } from '../infrastructure.ts';
import { opticWitnessSchema } from '../schemas.ts';
import { createPersistence, listGraphNames, resolveGraphName } from '../shared.ts';
import type { CliOptions, Persistence } from '../types.ts';

const OPTIC_WITNESS_OPTIONS = {
property: { type: 'string' },
};

type OpticWitnessSelection = {
readonly nodeId: string;
readonly propertyKey: string | null;
};

type OpticWitnessResult = NodeOpticReadResult | NodePropertyOpticReadResult;

type OpticCommandResult = {
readonly payload: unknown;
readonly exitCode: number;
};

type OpticBasisEvidence = {
readonly basisId: string;
readonly checkpointSha: string;
};

type CompleteWitnessOptions = {
readonly graphName: string;
readonly basis: OpticBasisEvidence;
readonly result: OpticWitnessResult;
readonly selection: OpticWitnessSelection;
};

type ObstructedWitnessOptions = {
readonly graphName: string;
readonly basis: OpticBasisEvidence | null;
readonly selection: OpticWitnessSelection;
readonly error: unknown;
};

export default async function handleOptic({
options,
args,
}: {
readonly options: CliOptions;
readonly args: string[];
}): Promise<OpticCommandResult> {
const selection = parseOpticWitnessArgs(args);
const opened = await openOpticWorldline(options);

return await runOpticWitness(opened, selection);
}

async function openOpticWorldline(options: CliOptions): Promise<{
readonly graphName: string;
readonly worldline: Awaited<ReturnType<typeof openWarpWorldline>>;
}> {
const { persistence } = await createPersistence(options.repo);
const graphName = await resolveOpticGraphName(persistence, options.graph);
return {
graphName,
worldline: await openWarpWorldline({
persistence,
worldlineName: graphName,
writerId: options.writer,
crypto: new WebCryptoAdapter(),
}),
};
}

async function runOpticWitness(
opened: {
readonly graphName: string;
readonly worldline: Awaited<ReturnType<typeof openWarpWorldline>>;
},
selection: OpticWitnessSelection,
): Promise<OpticCommandResult> {
let basisEvidence: OpticBasisEvidence | null = null;
try {
basisEvidence = await prepareBasisEvidence(opened);
const coordinate = await opened.worldline.coordinate();
const result = await readSelection(coordinate.optic(), selection);
return completeWitnessResult({
graphName: opened.graphName,
basis: basisEvidence,
result,
selection,
});
} catch (error) {
return obstructedWitnessResult({
graphName: opened.graphName,
basis: basisEvidence,
selection,
error,
});
}
}

async function prepareBasisEvidence(opened: {
readonly graphName: string;
readonly worldline: Awaited<ReturnType<typeof openWarpWorldline>>;
}): Promise<OpticBasisEvidence> {
const basis = await opened.worldline.prepareOpticBasis();
return {
basisId: `checkpoint-tail:${opened.graphName}:${basis.checkpointSha}`,
checkpointSha: basis.checkpointSha,
};
}

function completeWitnessResult(options: CompleteWitnessOptions): OpticCommandResult {
return {
payload: witnessPayload(options),
exitCode: EXIT_CODES.OK,
};
}

function obstructedWitnessResult(options: ObstructedWitnessOptions): OpticCommandResult {
if (!(options.error instanceof QueryError)) {
throw options.error;
}
return {
payload: obstructedWitnessPayload({
graphName: options.graphName,
basis: options.basis,
selection: options.selection,
error: options.error,
}),
exitCode: EXIT_CODES.INTERNAL,
};
}

function parseOpticWitnessArgs(args: readonly string[]): OpticWitnessSelection {
const subcommand = args[0];
if (subcommand !== 'witness') {
throw usageError('optic requires subcommand: witness');
}
const { values, positionals } = parseCommandArgs(
args.slice(1),
OPTIC_WITNESS_OPTIONS,
opticWitnessSchema,
{ allowPositionals: true },
);
const nodeId = positionals[0];
if (nodeId === undefined || nodeId.length === 0) {
throw usageError('optic witness requires a node id');
}
if (positionals.length > 1) {
throw usageError('optic witness accepts exactly one node id');
}
return { nodeId, propertyKey: values.propertyKey };
}

async function resolveOpticGraphName(
persistence: Persistence,
graph: string | null,
): Promise<string> {
const graphName = await resolveGraphName(persistence, graph);
if (typeof graph === 'string' && graph.length > 0) {
const graphNames = await listGraphNames(persistence);
if (!graphNames.includes(graph)) {
throw notFoundError(`Graph not found: ${graph}`);
}
}
return graphName;
}

async function readSelection(
optic: WorldlineOptic,
selection: OpticWitnessSelection,
): Promise<OpticWitnessResult> {
const node = optic.node(selection.nodeId);
if (selection.propertyKey === null) {
return await node.read();
}
return await node.prop(selection.propertyKey).read();
}

function witnessPayload(options: {
readonly graphName: string;
readonly basis: OpticBasisEvidence;
readonly result: OpticWitnessResult;
readonly selection: OpticWitnessSelection;
}): object {
const { readIdentity } = options.result;
return {
command: 'optic witness',
graph: options.graphName,
selection: selectionPayload(options.selection),
basisId: options.basis.basisId,
checkpointSha: options.basis.checkpointSha,
completeness: 'complete',
obstruction: null,
read: readPayload(options.result),
evidence: evidencePayload(readIdentity),
readIdentity,
};
}

function obstructedWitnessPayload(options: {
readonly graphName: string;
readonly basis: OpticBasisEvidence | null;
readonly selection: OpticWitnessSelection;
readonly error: QueryError;
}): object {
return {
command: 'optic witness',
graph: options.graphName,
selection: selectionPayload(options.selection),
basisId: options.basis?.basisId ?? null,
checkpointSha: options.basis?.checkpointSha ?? null,
completeness: 'obstructed',
obstruction: {
code: options.error.code,
message: options.error.message,
context: options.error.context,
},
read: null,
evidence: {
touchedShardIds: [],
tailWitnessRange: {
count: 0,
first: null,
last: null,
},
tailWitnesses: [],
},
};
}

function selectionPayload(selection: OpticWitnessSelection): object {
return {
nodeId: selection.nodeId,
propertyKey: selection.propertyKey,
};
}

function readPayload(result: OpticWitnessResult): object {
if ('key' in result) {
return {
kind: 'node-property',
nodeId: result.nodeId,
key: result.key,
exists: result.exists,
value: result.value,
};
}
return {
kind: 'node',
nodeId: result.nodeId,
alive: result.alive,
};
}

function evidencePayload(readIdentity: ReadIdentity): object {
return {
touchedShardIds: readIdentity.checkpointIndexShards,
tailWitnessRange: tailWitnessRange(readIdentity),
tailWitnesses: readIdentity.tailWitnesses,
};
}

function tailWitnessRange(readIdentity: ReadIdentity): object {
const witnesses = readIdentity.tailWitnesses;
const first = witnesses[0] ?? null;
const last = witnesses[witnesses.length - 1] ?? null;
return {
count: witnesses.length,
first,
last,
};
}
2 changes: 2 additions & 0 deletions bin/cli/commands/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import handleMaterialize from './materialize.ts';
import handleSeek from './seek.ts';
import handleQuery from './query.ts';
import handlePath from './path.ts';
import handleOptic from './optic.ts';
import handleHistory from './history.ts';
import handleDebug from './debug.ts';
import handleStrand from './strand.ts';
Expand Down Expand Up @@ -38,6 +39,7 @@ export const COMMANDS: ReadonlyMap<string, CommandHandler> = new Map<string, Com
['seek', handleSeek],
['query', handleQuery],
['path', handlePath],
['optic', handleOptic],
['history', handleHistory],
['debug', handleDebug],
['strand', handleStrand],
Expand Down
7 changes: 6 additions & 1 deletion bin/cli/infrastructure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Commands:
trust Evaluate writer trust from signed evidence
materialize Diagnostic replay/checkpoint for graph state
seek Time-travel: step through graph history by Lamport tick
optic Emit checkpoint-tail optic witness output
patch Decode and inspect raw patches
tree ASCII tree traversal from root nodes
bisect Binary search for first bad patch in writer history
Expand Down Expand Up @@ -138,6 +139,10 @@ Path options:
--label <label> Filter by edge label (repeatable, comma-separated)
--max-depth <n> Maximum depth

Optic options:
witness <node-id> Read a node or property with checkpoint-tail evidence
--property <key> Read a single node property

History options:
--node <id> Filter patches touching node id

Expand Down Expand Up @@ -225,7 +230,7 @@ export function notFoundError(message: string): CliError {
return new CliError(message, { code: 'E_NOT_FOUND', exitCode: EXIT_CODES.NOT_FOUND });
}

export const KNOWN_COMMANDS = ['info', 'check', 'doctor', 'debug', 'strand', 'materialize', 'seek', 'query', 'path', 'history', 'verify-audit', 'verify-index', 'reindex', 'trust', 'patch', 'tree', 'bisect', 'install-hooks'];
export const KNOWN_COMMANDS = ['info', 'check', 'doctor', 'debug', 'strand', 'materialize', 'seek', 'query', 'path', 'optic', 'history', 'verify-audit', 'verify-index', 'reindex', 'trust', 'patch', 'tree', 'bisect', 'install-hooks'];

const BASE_OPTIONS = {
repo: { type: 'string', short: 'r' },
Expand Down
10 changes: 10 additions & 0 deletions bin/cli/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ export const querySchema = z.object({
select: val.select,
}));

// ============================================================================
// Optic
// ============================================================================

export const opticWitnessSchema = z.object({
property: z.string().min(1, 'Missing value for --property').optional(),
}).strict().transform((val) => ({
propertyKey: val.property ?? null,
}));

// ============================================================================
// Trust
// ============================================================================
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@
"test": "sh -c 'if [ \"$GIT_STUNTS_DOCKER\" = \"1\" ]; then vitest run test/unit \"$@\"; else docker compose -f docker/docker-compose.yml run --build --rm test npm run test:local -- \"$@\"; fi' --",
"test:local": "vitest run test/unit",
"test:watch": "vitest",
"test:coverage": "GIT_WARP_UPDATE_COVERAGE_RATCHET=1 vitest run --coverage test/unit",
"test:coverage:ci": "vitest run --coverage test/unit",
"test:coverage": "GIT_WARP_UPDATE_COVERAGE_RATCHET=1 vitest run --coverage test/unit test/conformance/v18*Optic*.test.ts",
"test:coverage:ci": "vitest run --coverage test/unit test/conformance/v18*Optic*.test.ts",
"benchmark": "sh -c 'if [ \"$GIT_STUNTS_DOCKER\" = \"1\" ]; then vitest bench --run test/benchmark \"$@\"; else docker compose -f docker/docker-compose.yml run --build --rm test npm run benchmark:local -- \"$@\"; fi' --",
"benchmark:local": "vitest bench --run test/benchmark",
"benchmark:detached-reads": "vitest run test/benchmark/DetachedReadBoundary.benchmark.ts",
Expand Down
Loading
Loading