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
17 changes: 17 additions & 0 deletions charts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Bounded graph-shaped observations.
*
* Charts are derived views over a Lane, never a mutable graph store or durable
* ontology. Each builder returns a validated Observer that emits canonical
* `Reading.value` data.
*/

export { graph } from './src/domain/api/GraphChartObservers.ts';
export { default as GraphNeighborhoodChart } from './src/domain/api/GraphNeighborhoodChart.ts';
export { default as GraphNeighborhoodEdge } from './src/domain/api/GraphNeighborhoodEdge.ts';
export type {
GraphChartObservers,
GraphNeighborhoodOptions,
} from './src/domain/api/GraphChartObservers.ts';
export type { GraphNeighborhoodChartOptions } from './src/domain/api/GraphNeighborhoodChart.ts';
export type { GraphNeighborhoodEdgeOptions } from './src/domain/api/GraphNeighborhoodEdge.ts';
13 changes: 7 additions & 6 deletions docs/migrations/v19/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

> **Status:** Pre-release. The canonical Runtime, worldline Lane, Observer,
> streaming Observation, Reading, Receipt, and write-admission core has landed.
> Fork/settlement, generated SDK publication, charts/testing subpaths, and
> CLI/MCP vocabulary convergence remain tracked by issue #712.
> Fork/settlement, generated SDK publication, and CLI/MCP vocabulary
> convergence remain tracked by issue #712.

v19 replaces the transitional storage- and timeline-shaped facade with one
application grammar:
Expand Down Expand Up @@ -304,8 +304,9 @@ The intended v19 expert surfaces are:
```

`/charts` provides graph-shaped derived observations. It does not describe the
durable ontology as a graph. `/testing` owns dependency injection and fakes.
Both remain open v19 implementation work at the time of this guide.
durable ontology as a graph. Its first shipped Observer is a one-hop, bounded,
cursor-page neighborhood chart. `/testing` provides an isolated real-Git
`Runtime` harness without exposing storage construction at package root.

There is no public `/graph`, `/browser`, or `/legacy` package. The transitional
`/storage` export remains only until testing and diagnostics no longer require
Expand Down Expand Up @@ -341,8 +342,8 @@ the explicit handle; ordinary v19 application code must use `Runtime.open()`.
6. Move receipt handling from each Reading to the Observation terminal path.
7. Match all four admission variants exhaustively.
8. Keep existing cross-lane join code isolated until settlement plans land.
9. Replace graph-shaped reads with `/charts` once that subpath ships.
10. Remove imports from `/storage` after diagnostics/testing migration lands.
9. Replace graph-shaped reads with bounded `/charts` observers.
10. Remove imports from `/storage` when explicit diagnostics work is complete.

## Validation

Expand Down
13 changes: 9 additions & 4 deletions docs/topics/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -524,14 +524,19 @@ import { graph } from '@git-stunts/git-warp/charts';
const observation = events.observe(
graph.neighborhood({
around: 'user:alice',
depth: 2,
direction: 'both',
limit: 100,
})
);
```

This surface may expose node, edge, neighborhood, topology, and graph-diff
Observers. It must describe their results as charts or readings, not as the
durable territory or a mutable graph store.
The shipped neighborhood chart is one hop and cursor-page bounded: it defaults
to 100 edges and accepts at most 1,000. Follow `Reading.value.cursor` with
another Observer when `Reading.value.completeness` is `truncated`.

This surface may grow node, edge, topology, and graph-diff Observers. It must
describe their results as charts or readings, not as the durable territory or
a mutable graph store.

`/charts` is absent from the first-use README path. It exists for users who
actually need graph-shaped correlation and coordination.
Expand Down
54 changes: 53 additions & 1 deletion docs/topics/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ public API export, CLI command, package entrypoint, or public error class.
| npm export | `./storage` | `types=./dist/storage.d.ts; import=./dist/storage.js; default=./dist/storage.js` | `package.json#L33` |
| npm export | `./advanced` | `types=./dist/advanced.d.ts; import=./dist/advanced.js; default=./dist/advanced.js` | `package.json#L38` |
| npm export | `./diagnostics` | `types=./dist/diagnostics.d.ts; import=./dist/diagnostics.js; default=./dist/diagnostics.js` | `package.json#L43` |
| npm export | `./package.json` | `./package.json` | `package.json#L48` |
| npm export | `./charts` | `types=./dist/charts.d.ts; import=./dist/charts.js; default=./dist/charts.js` | `package.json#L48` |
| npm export | `./testing` | `types=./dist/testing.d.ts; import=./dist/testing.js; default=./dist/testing.js` | `package.json#L53` |
| npm export | `./package.json` | `./package.json` | `package.json#L58` |
| JSR export | `.` | `./index.ts` | `jsr.json#L8` |
| JSR export | `./storage` | `./storage.ts` | `jsr.json#L9` |
| JSR export | `./advanced` | `./advanced.ts` | `jsr.json#L10` |
| JSR export | `./diagnostics` | `./diagnostics.ts` | `jsr.json#L11` |
| JSR export | `./charts` | `./charts.ts` | `jsr.json#L12` |
| JSR export | `./testing` | `./testing.ts` | `jsr.json#L13` |

## Root API export surface

Expand Down Expand Up @@ -137,6 +141,54 @@ ReceiptInspection @ diagnostics.ts#L29
ReceiptSubstrateInspection @ diagnostics.ts#L15
```

## Charts export surface

Bounded graph-shaped derived Observers and Reading values.

### Value exports

Source: `charts.ts`. Count: 3.

```text
graph @ charts.ts#L9
GraphNeighborhoodChart @ charts.ts#L10
GraphNeighborhoodEdge @ charts.ts#L11
```

### Type exports

Source: `charts.ts`. Count: 4.

```text
GraphChartObservers @ charts.ts#L13
GraphNeighborhoodChartOptions @ charts.ts#L16
GraphNeighborhoodEdgeOptions @ charts.ts#L17
GraphNeighborhoodOptions @ charts.ts#L14
```

## Testing export surface

Disposable real-Git Runtime harnesses for consumer tests.

### Value exports

Source: `testing.ts`. Count: 2.

```text
createRuntimeHarness @ testing.ts#L17
createRuntimeHarnessWithHost @ testing.ts#L23
```

### Type exports

Source: `testing.ts`. Count: 3.

```text
RuntimeHarness @ testing.ts#L25
RuntimeHarnessHost @ testing.ts#L26
RuntimeHarnessOptions @ testing.ts#L27
```

## CLI command registry

| Command | Handler | Source |
Expand Down
6 changes: 5 additions & 1 deletion jsr.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@
".": "./index.ts",
"./storage": "./storage.ts",
"./advanced": "./advanced.ts",
"./diagnostics": "./diagnostics.ts"
"./diagnostics": "./diagnostics.ts",
"./charts": "./charts.ts",
"./testing": "./testing.ts"
},
"publish": {
"include": [
"index.ts",
"storage.ts",
"advanced.ts",
"diagnostics.ts",
"charts.ts",
"testing.ts",
"src/**/*.ts",
"src/**/*.d.ts",
"README.md",
Expand Down
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@
"import": "./dist/diagnostics.js",
"default": "./dist/diagnostics.js"
},
"./charts": {
"types": "./dist/charts.d.ts",
"import": "./dist/charts.js",
"default": "./dist/charts.js"
},
"./testing": {
"types": "./dist/testing.d.ts",
"import": "./dist/testing.js",
"default": "./dist/testing.js"
},
"./package.json": "./package.json"
},
"files": [
Expand Down
9 changes: 5 additions & 4 deletions scripts/check-source-backed-reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ function exportSurface(title: string, source: SourceText, contract: string): rea
`Source: \`${source.path}\`. Count: ${typeExports.length}.`,
'',
codeList(typeExports),
'',
];
}

Expand All @@ -241,6 +242,8 @@ function generate(): string {
const storageSource = new SourceText('storage.ts');
const advancedSource = new SourceText('advanced.ts');
const diagnosticsSource = new SourceText('diagnostics.ts');
const chartsSource = new SourceText('charts.ts');
const testingSource = new SourceText('testing.ts');
const packageBins = captureObjectEntries(packageSource, 'bin');
const packageExports = captureExportEntries(packageSource, 'exports').filter((item) => item.name.startsWith('.'));
const jsrExports = captureExportEntries(jsrSource, 'exports').filter((item) => item.name.startsWith('.'));
Expand All @@ -263,13 +266,11 @@ function generate(): string {
]),
'',
...exportSurface('Root API export surface', rootSource, 'First-use product API: one `Runtime` value plus Lane, Intent, Observer, Observation, Reading, and Receipt types.'),
'',
...exportSurface('Storage export surface', storageSource, 'Transitional explicit storage composition; first-use applications use `Runtime.open()`.'),
'',
...exportSurface('Advanced export surface', advancedSource, 'Bounded coordinate capture, Optic, and Witness concepts for expert use.'),
'',
...exportSurface('Diagnostics export surface', diagnosticsSource, 'Operator inspection helpers that consume public receipt handles.'),
'',
...exportSurface('Charts export surface', chartsSource, 'Bounded graph-shaped derived Observers and Reading values.'),
...exportSurface('Testing export surface', testingSource, 'Disposable real-Git Runtime harnesses for consumer tests.'),
'## CLI command registry',
'',
table(['Command', 'Handler', 'Source'], commands.map((item) => [`\`${item.name}\``, `\`${item.detail}\``, `\`${item.source}\``])),
Expand Down
2 changes: 2 additions & 0 deletions scripts/storage-ownership-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export const PRODUCTION_ENTRYPOINTS = [
'storage.ts',
'advanced.ts',
'diagnostics.ts',
'charts.ts',
'testing.ts',
] as const;
export const DOMAIN_STORAGE_ROOTS = ['src/domain', 'src/ports'] as const;
export const STORAGE_ADAPTER_ROOT = 'src/infrastructure/adapters/';
Expand Down
133 changes: 133 additions & 0 deletions src/domain/api/GraphChartObservers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import type Observer from './Observer.ts';
import { createObserver } from './ObserverRuntime.ts';
import LegacyReading, { type NeighborhoodReadingFields, type ReadingDirection } from './Reading.ts';
import type { ReadingValue } from './ObservedReading.ts';
import WarpError from '../errors/WarpError.ts';
import GraphNeighborhoodChart from './GraphNeighborhoodChart.ts';
import GraphNeighborhoodEdge from './GraphNeighborhoodEdge.ts';

export type GraphNeighborhoodOptions = {
readonly around: string;
readonly direction?: ReadingDirection;
readonly labels?: readonly string[];
readonly limit?: number;
readonly cursor?: string;
};

export type GraphChartObservers = Readonly<{
neighborhood(options: GraphNeighborhoodOptions): Observer<GraphNeighborhoodChart>;
}>;

/** Bounded, graph-shaped derived observers. */
export const graph: GraphChartObservers = Object.freeze({
neighborhood(options: GraphNeighborhoodOptions): Observer<GraphNeighborhoodChart> {
return createObserver<GraphNeighborhoodChart>(
'charts.graph.neighborhood',
neighborhoodReading(options),
decodeNeighborhoodChart
);
},
});

function neighborhoodReading(options: GraphNeighborhoodOptions): LegacyReading {
if (options === null || options === undefined) {
throw chartError('graph.neighborhood options are required', 'E_CHART_OPTIONS');
}
const { around: subject, ...settings } = options;
const fields: NeighborhoodReadingFields = { subject, ...settings };
return LegacyReading.neighborhood(fields);
}

function decodeNeighborhoodChart(value: ReadingValue): GraphNeighborhoodChart {
const {
subject: rawSubject,
direction: rawDirection,
edges: rawEdges,
completeness: rawCompleteness,
cursor: rawCursor,
} = requireRecord(value, 'chart value');
const subject = requireString(rawSubject, 'chart subject');
const direction = requireReadingDirection(rawDirection);
const edges = requireArray(rawEdges, 'chart edges').map(decodeNeighborhoodEdge);
const completeness = requireCompleteness(rawCompleteness);
const cursor = requireNullableString(rawCursor, 'chart cursor');
return new GraphNeighborhoodChart({
subject,
direction,
edges,
completeness,
cursor,
});
}

function decodeNeighborhoodEdge(value: ReadingValue): GraphNeighborhoodEdge {
const {
direction: rawDirection,
neighborId: rawNeighborId,
label: rawLabel,
} = requireRecord(value, 'chart edge');
const direction = requireEdgeDirection(rawDirection);
const neighborId = requireString(rawNeighborId, 'chart edge neighborId');
const label = requireString(rawLabel, 'chart edge label');
return new GraphNeighborhoodEdge({
direction,
neighborId,
label,
});
}

function requireRecord(
value: ReadingValue | undefined,
field: string
): Readonly<Record<string, ReadingValue>> {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
throw chartError(`graph.neighborhood received an invalid ${field}`, 'E_CHART_VALUE');
}
return value as Readonly<Record<string, ReadingValue>>;
}

function requireArray(value: ReadingValue | undefined, field: string): readonly ReadingValue[] {
if (!Array.isArray(value)) {
throw chartError(`graph.neighborhood received invalid ${field}`, 'E_CHART_VALUE');
}
return value as readonly ReadingValue[];
}

function requireString(value: ReadingValue | undefined, field: string): string {
if (typeof value !== 'string') {
throw chartError(`graph.neighborhood received invalid ${field}`, 'E_CHART_VALUE');
}
return value;
}

function requireNullableString(value: ReadingValue | undefined, field: string): string | null {
if (value !== null && typeof value !== 'string') {
throw chartError(`graph.neighborhood received invalid ${field}`, 'E_CHART_VALUE');
}
return value;
}

function requireReadingDirection(value: ReadingValue | undefined): ReadingDirection {
if (value !== 'out' && value !== 'in' && value !== 'both') {
throw chartError('graph.neighborhood received invalid chart direction', 'E_CHART_VALUE');
}
return value;
}

function requireEdgeDirection(value: ReadingValue | undefined): 'out' | 'in' {
if (value !== 'out' && value !== 'in') {
throw chartError('graph.neighborhood received an invalid chart edge', 'E_CHART_VALUE');
}
return value;
}

function requireCompleteness(value: ReadingValue | undefined): 'complete' | 'truncated' {
if (value !== 'complete' && value !== 'truncated') {
throw chartError('graph.neighborhood received invalid chart completeness', 'E_CHART_VALUE');
}
return value;
}

function chartError(message: string, code: string): WarpError {
return new WarpError(message, code);
}
Loading
Loading