Skip to content

Commit 3d9886f

Browse files
committed
feat: publish charts and testing surfaces
1 parent 209eb86 commit 3d9886f

17 files changed

Lines changed: 495 additions & 18 deletions

charts.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Bounded graph-shaped observations.
3+
*
4+
* Charts are derived views over a Lane, never a mutable graph store or durable
5+
* ontology. Each builder returns a validated Observer that emits canonical
6+
* `Reading.value` data.
7+
*/
8+
9+
export { graph } from './src/domain/api/GraphChartObservers.ts';
10+
export type {
11+
GraphChartObservers,
12+
GraphNeighborhoodChart,
13+
GraphNeighborhoodEdge,
14+
GraphNeighborhoodOptions,
15+
} from './src/domain/api/GraphChartObservers.ts';

docs/migrations/v19/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
> **Status:** Pre-release. The canonical Runtime, worldline Lane, Observer,
44
> streaming Observation, Reading, Receipt, and write-admission core has landed.
5-
> Fork/settlement, generated SDK publication, charts/testing subpaths, and
6-
> CLI/MCP vocabulary convergence remain tracked by issue #712.
5+
> Fork/settlement, generated SDK publication, and CLI/MCP vocabulary
6+
> convergence remain tracked by issue #712.
77
88
v19 replaces the transitional storage- and timeline-shaped facade with one
99
application grammar:
@@ -304,8 +304,9 @@ The intended v19 expert surfaces are:
304304
```
305305

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

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

347348
## Validation
348349

docs/topics/api/README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -524,14 +524,19 @@ import { graph } from '@git-stunts/git-warp/charts';
524524
const observation = events.observe(
525525
graph.neighborhood({
526526
around: 'user:alice',
527-
depth: 2,
527+
direction: 'both',
528+
limit: 100,
528529
})
529530
);
530531
```
531532

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

536541
`/charts` is absent from the first-use README path. It exists for users who
537542
actually need graph-shaped correlation and coordination.

docs/topics/reference.md

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ public API export, CLI command, package entrypoint, or public error class.
1515
| npm export | `./storage` | `types=./dist/storage.d.ts; import=./dist/storage.js; default=./dist/storage.js` | `package.json#L33` |
1616
| npm export | `./advanced` | `types=./dist/advanced.d.ts; import=./dist/advanced.js; default=./dist/advanced.js` | `package.json#L38` |
1717
| npm export | `./diagnostics` | `types=./dist/diagnostics.d.ts; import=./dist/diagnostics.js; default=./dist/diagnostics.js` | `package.json#L43` |
18-
| npm export | `./package.json` | `./package.json` | `package.json#L48` |
18+
| npm export | `./charts` | `types=./dist/charts.d.ts; import=./dist/charts.js; default=./dist/charts.js` | `package.json#L48` |
19+
| npm export | `./testing` | `types=./dist/testing.d.ts; import=./dist/testing.js; default=./dist/testing.js` | `package.json#L53` |
20+
| npm export | `./package.json` | `./package.json` | `package.json#L58` |
1921
| JSR export | `.` | `./index.ts` | `jsr.json#L8` |
2022
| JSR export | `./storage` | `./storage.ts` | `jsr.json#L9` |
2123
| JSR export | `./advanced` | `./advanced.ts` | `jsr.json#L10` |
2224
| JSR export | `./diagnostics` | `./diagnostics.ts` | `jsr.json#L11` |
25+
| JSR export | `./charts` | `./charts.ts` | `jsr.json#L12` |
26+
| JSR export | `./testing` | `./testing.ts` | `jsr.json#L13` |
2327

2428
## Root API export surface
2529

@@ -137,6 +141,50 @@ ReceiptInspection @ diagnostics.ts#L29
137141
ReceiptSubstrateInspection @ diagnostics.ts#L15
138142
```
139143

144+
## Charts export surface
145+
146+
Bounded graph-shaped derived Observers and Reading values.
147+
148+
### Value exports
149+
150+
Source: `charts.ts`. Count: 1.
151+
152+
```text
153+
graph @ charts.ts#L9
154+
```
155+
156+
### Type exports
157+
158+
Source: `charts.ts`. Count: 4.
159+
160+
```text
161+
GraphChartObservers @ charts.ts#L11
162+
GraphNeighborhoodChart @ charts.ts#L12
163+
GraphNeighborhoodEdge @ charts.ts#L13
164+
GraphNeighborhoodOptions @ charts.ts#L14
165+
```
166+
167+
## Testing export surface
168+
169+
Disposable real-Git Runtime harnesses for consumer tests.
170+
171+
### Value exports
172+
173+
Source: `testing.ts`. Count: 1.
174+
175+
```text
176+
createRuntimeHarness @ testing.ts#L8
177+
```
178+
179+
### Type exports
180+
181+
Source: `testing.ts`. Count: 2.
182+
183+
```text
184+
RuntimeHarness @ testing.ts#L10
185+
RuntimeHarnessOptions @ testing.ts#L11
186+
```
187+
140188
## CLI command registry
141189

142190
| Command | Handler | Source |

jsr.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@
88
".": "./index.ts",
99
"./storage": "./storage.ts",
1010
"./advanced": "./advanced.ts",
11-
"./diagnostics": "./diagnostics.ts"
11+
"./diagnostics": "./diagnostics.ts",
12+
"./charts": "./charts.ts",
13+
"./testing": "./testing.ts"
1214
},
1315
"publish": {
1416
"include": [
1517
"index.ts",
1618
"storage.ts",
1719
"advanced.ts",
1820
"diagnostics.ts",
21+
"charts.ts",
22+
"testing.ts",
1923
"src/**/*.ts",
2024
"src/**/*.d.ts",
2125
"README.md",

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@
4545
"import": "./dist/diagnostics.js",
4646
"default": "./dist/diagnostics.js"
4747
},
48+
"./charts": {
49+
"types": "./dist/charts.d.ts",
50+
"import": "./dist/charts.js",
51+
"default": "./dist/charts.js"
52+
},
53+
"./testing": {
54+
"types": "./dist/testing.d.ts",
55+
"import": "./dist/testing.js",
56+
"default": "./dist/testing.js"
57+
},
4858
"./package.json": "./package.json"
4959
},
5060
"files": [

scripts/check-source-backed-reference.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ function exportSurface(title: string, source: SourceText, contract: string): rea
229229
`Source: \`${source.path}\`. Count: ${typeExports.length}.`,
230230
'',
231231
codeList(typeExports),
232+
'',
232233
];
233234
}
234235

@@ -241,6 +242,8 @@ function generate(): string {
241242
const storageSource = new SourceText('storage.ts');
242243
const advancedSource = new SourceText('advanced.ts');
243244
const diagnosticsSource = new SourceText('diagnostics.ts');
245+
const chartsSource = new SourceText('charts.ts');
246+
const testingSource = new SourceText('testing.ts');
244247
const packageBins = captureObjectEntries(packageSource, 'bin');
245248
const packageExports = captureExportEntries(packageSource, 'exports').filter((item) => item.name.startsWith('.'));
246249
const jsrExports = captureExportEntries(jsrSource, 'exports').filter((item) => item.name.startsWith('.'));
@@ -263,13 +266,11 @@ function generate(): string {
263266
]),
264267
'',
265268
...exportSurface('Root API export surface', rootSource, 'First-use product API: one `Runtime` value plus Lane, Intent, Observer, Observation, Reading, and Receipt types.'),
266-
'',
267269
...exportSurface('Storage export surface', storageSource, 'Transitional explicit storage composition; first-use applications use `Runtime.open()`.'),
268-
'',
269270
...exportSurface('Advanced export surface', advancedSource, 'Bounded coordinate capture, Optic, and Witness concepts for expert use.'),
270-
'',
271271
...exportSurface('Diagnostics export surface', diagnosticsSource, 'Operator inspection helpers that consume public receipt handles.'),
272-
'',
272+
...exportSurface('Charts export surface', chartsSource, 'Bounded graph-shaped derived Observers and Reading values.'),
273+
...exportSurface('Testing export surface', testingSource, 'Disposable real-Git Runtime harnesses for consumer tests.'),
273274
'## CLI command registry',
274275
'',
275276
table(['Command', 'Handler', 'Source'], commands.map((item) => [`\`${item.name}\``, `\`${item.detail}\``, `\`${item.source}\``])),

scripts/storage-ownership-policy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export const PRODUCTION_ENTRYPOINTS = [
44
'storage.ts',
55
'advanced.ts',
66
'diagnostics.ts',
7+
'charts.ts',
8+
'testing.ts',
79
] as const;
810
export const DOMAIN_STORAGE_ROOTS = ['src/domain', 'src/ports'] as const;
911
export const STORAGE_ADAPTER_ROOT = 'src/infrastructure/adapters/';
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import type Observer from './Observer.ts';
2+
import {
3+
createObserver,
4+
} from './ObserverRuntime.ts';
5+
import LegacyReading, {
6+
type NeighborhoodReadingFields,
7+
type ReadingDirection,
8+
} from './Reading.ts';
9+
import type {
10+
ReadingValue,
11+
} from './ObservedReading.ts';
12+
import WarpError from '../errors/WarpError.ts';
13+
14+
export type GraphNeighborhoodOptions = {
15+
readonly around: string;
16+
readonly direction?: ReadingDirection;
17+
readonly labels?: readonly string[];
18+
readonly limit?: number;
19+
readonly cursor?: string;
20+
};
21+
22+
export type GraphNeighborhoodEdge = Readonly<{
23+
readonly direction: 'out' | 'in';
24+
readonly neighborId: string;
25+
readonly label: string;
26+
}> & Readonly<Record<string, ReadingValue>>;
27+
28+
export type GraphNeighborhoodChart = Readonly<{
29+
readonly subject: string;
30+
readonly direction: ReadingDirection;
31+
readonly edges: readonly GraphNeighborhoodEdge[];
32+
readonly completeness: 'complete' | 'truncated';
33+
readonly cursor: string | null;
34+
}> & Readonly<Record<string, ReadingValue>>;
35+
36+
export type GraphChartObservers = Readonly<{
37+
neighborhood(options: GraphNeighborhoodOptions): Observer<GraphNeighborhoodChart>;
38+
}>;
39+
40+
/** Bounded, graph-shaped derived observers. */
41+
export const graph: GraphChartObservers = Object.freeze({
42+
neighborhood(options: GraphNeighborhoodOptions): Observer<GraphNeighborhoodChart> {
43+
return createObserver<GraphNeighborhoodChart>(
44+
'charts.graph.neighborhood',
45+
neighborhoodReading(options),
46+
decodeNeighborhoodChart,
47+
);
48+
},
49+
});
50+
51+
function neighborhoodReading(options: GraphNeighborhoodOptions): LegacyReading {
52+
if (options === null || options === undefined) {
53+
throw chartError('graph.neighborhood options are required', 'E_CHART_OPTIONS');
54+
}
55+
const { around: subject, ...settings } = options;
56+
const fields: NeighborhoodReadingFields = { subject, ...settings };
57+
return LegacyReading.neighborhood(fields);
58+
}
59+
60+
function decodeNeighborhoodChart(value: ReadingValue): GraphNeighborhoodChart {
61+
const {
62+
subject: rawSubject,
63+
direction: rawDirection,
64+
edges: rawEdges,
65+
completeness: rawCompleteness,
66+
cursor: rawCursor,
67+
} = requireRecord(value, 'chart value');
68+
const subject = requireString(rawSubject, 'chart subject');
69+
const direction = requireReadingDirection(rawDirection);
70+
const edges = requireArray(rawEdges, 'chart edges').map(decodeNeighborhoodEdge);
71+
const completeness = requireCompleteness(rawCompleteness);
72+
const cursor = requireNullableString(rawCursor, 'chart cursor');
73+
return Object.freeze({
74+
subject,
75+
direction,
76+
edges: Object.freeze(edges),
77+
completeness,
78+
cursor,
79+
}) as GraphNeighborhoodChart;
80+
}
81+
82+
function decodeNeighborhoodEdge(value: ReadingValue): GraphNeighborhoodEdge {
83+
const {
84+
direction: rawDirection,
85+
neighborId: rawNeighborId,
86+
label: rawLabel,
87+
} = requireRecord(value, 'chart edge');
88+
const direction = requireEdgeDirection(rawDirection);
89+
const neighborId = requireString(rawNeighborId, 'chart edge neighborId');
90+
const label = requireString(rawLabel, 'chart edge label');
91+
return Object.freeze({
92+
direction,
93+
neighborId,
94+
label,
95+
}) as GraphNeighborhoodEdge;
96+
}
97+
98+
function requireRecord(
99+
value: ReadingValue | undefined,
100+
field: string,
101+
): Readonly<Record<string, ReadingValue>> {
102+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
103+
throw chartError(`graph.neighborhood received an invalid ${field}`, 'E_CHART_VALUE');
104+
}
105+
return value as Readonly<Record<string, ReadingValue>>;
106+
}
107+
108+
function requireArray(
109+
value: ReadingValue | undefined,
110+
field: string,
111+
): readonly ReadingValue[] {
112+
if (!Array.isArray(value)) {
113+
throw chartError(`graph.neighborhood received invalid ${field}`, 'E_CHART_VALUE');
114+
}
115+
return value as readonly ReadingValue[];
116+
}
117+
118+
function requireString(value: ReadingValue | undefined, field: string): string {
119+
if (typeof value !== 'string') {
120+
throw chartError(`graph.neighborhood received invalid ${field}`, 'E_CHART_VALUE');
121+
}
122+
return value;
123+
}
124+
125+
function requireNullableString(
126+
value: ReadingValue | undefined,
127+
field: string,
128+
): string | null {
129+
if (value !== null && typeof value !== 'string') {
130+
throw chartError(`graph.neighborhood received invalid ${field}`, 'E_CHART_VALUE');
131+
}
132+
return value;
133+
}
134+
135+
function requireReadingDirection(value: ReadingValue | undefined): ReadingDirection {
136+
if (value !== 'out' && value !== 'in' && value !== 'both') {
137+
throw chartError('graph.neighborhood received invalid chart direction', 'E_CHART_VALUE');
138+
}
139+
return value;
140+
}
141+
142+
function requireEdgeDirection(value: ReadingValue | undefined): 'out' | 'in' {
143+
if (value !== 'out' && value !== 'in') {
144+
throw chartError('graph.neighborhood received an invalid chart edge', 'E_CHART_VALUE');
145+
}
146+
return value;
147+
}
148+
149+
function requireCompleteness(
150+
value: ReadingValue | undefined,
151+
): 'complete' | 'truncated' {
152+
if (value !== 'complete' && value !== 'truncated') {
153+
throw chartError('graph.neighborhood received invalid chart completeness', 'E_CHART_VALUE');
154+
}
155+
return value;
156+
}
157+
158+
function chartError(message: string, code: string): WarpError {
159+
return new WarpError(message, code);
160+
}

0 commit comments

Comments
 (0)