Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
392d4fc
feat: WorldlineSelector class hierarchy — RED→GREEN
flyingrobots Apr 4, 2026
7dd54eb
refactor: migrate Worldline/Observer/QueryController to WorldlineSele…
flyingrobots Apr 4, 2026
924e62e
fix: resolve tsc errors from WorldlineSelector migration
flyingrobots Apr 4, 2026
9cbf41c
fix: address self-review — toDTO ceiling, double-clone, registry seal
flyingrobots Apr 4, 2026
9b1a827
docs: defaultCodec → infrastructure design (P5 fix)
flyingrobots Apr 4, 2026
3052bc0
docs: redesign defaultCodec migration — inject at root, no shim
flyingrobots Apr 4, 2026
22a0098
docs: rewrite defaultCodec backlog item — the real P5 violation
flyingrobots Apr 4, 2026
1e76c48
docs: cycle 0007 retro — partial (WorldlineSelector shipped, defaultC…
flyingrobots Apr 4, 2026
6860653
docs: correct defaultCodec fix — dissolve, don't relocate
flyingrobots Apr 4, 2026
18ea06b
docs: two-stage boundary for P5 codec dissolution
flyingrobots Apr 4, 2026
ca2db85
docs: add cool ideas from P5 codec dissolution planning
flyingrobots Apr 4, 2026
4cf9ed9
test: add hex tripwire + golden fixture for patch serialization (RED)
flyingrobots Apr 4, 2026
a28d81a
feat: add PatchJournalPort + CborPatchJournalAdapter
flyingrobots Apr 4, 2026
1da9e42
refactor: wire PatchBuilderV2 through PatchJournalPort
flyingrobots Apr 4, 2026
2b53d1a
refactor: wire SyncProtocol through PatchJournalPort
flyingrobots Apr 4, 2026
359ea14
refactor: remove defaultCodec from Writer + wire patchJournal through…
flyingrobots Apr 4, 2026
6955db8
fix: add eslint-disable for untyped WarpRuntime _patchJournal access
flyingrobots Apr 4, 2026
5756abe
fix: wire patchJournal into 6 test files missing it after PatchJourna…
flyingrobots Apr 4, 2026
412c6f6
test: extend tripwire + golden fixture for checkpoint serialization (…
flyingrobots Apr 4, 2026
39cb85b
feat: add CheckpointStorePort + CborCheckpointStoreAdapter
flyingrobots Apr 4, 2026
3520de2
feat: wire CheckpointStorePort through checkpoint create/load pipeline
flyingrobots Apr 4, 2026
b5da62a
refactor: scope checkpoint tripwire to CheckpointService only
flyingrobots Apr 4, 2026
e71bc56
docs: stream architecture cycle proposal + P5 progress update
flyingrobots Apr 4, 2026
c197d4c
feat: WarpStream + Transform + Sink — composable async stream primitives
flyingrobots Apr 4, 2026
825ef6d
test: WarpStream core tests — 40 tests covering full API surface
flyingrobots Apr 4, 2026
a95ad58
feat: infrastructure stream adapters + pipeline integration tests
flyingrobots Apr 4, 2026
8fabdce
feat: LogicalBitmapIndexBuilder.yieldShards() — stream-compatible output
flyingrobots Apr 4, 2026
1bc79ed
docs: cycle 0008 — stream architecture design doc + backlog items
flyingrobots Apr 4, 2026
69d98df
docs: rewrite cycle 0008 design — ports for meaning, streams for scale
flyingrobots Apr 4, 2026
d854d61
docs: finalize cycle 0008 design — all six rulings locked
flyingrobots Apr 4, 2026
4a42421
feat: artifact record classes — CheckpointArtifact, IndexShard, Patch…
flyingrobots Apr 4, 2026
1b2e614
feat: PatchJournalPort.scanPatchRange() → WarpStream<PatchEntry>
flyingrobots Apr 4, 2026
7d5af95
refactor: collapse CheckpointStorePort to writeCheckpoint/readCheckpoint
flyingrobots Apr 4, 2026
1bee37f
refactor: wire SyncProtocol.processSyncRequest to use scanPatchRange
flyingrobots Apr 4, 2026
8f79554
feat: IndexShard records in yieldShards() + IndexShardEncodeTransform
flyingrobots Apr 4, 2026
3134b31
feat: PropertyIndexBuilder.yieldShards() emitting PropertyShard records
flyingrobots Apr 4, 2026
590a7c7
feat: LogicalIndexBuildService.buildStream() — WarpStream of IndexSha…
flyingrobots Apr 4, 2026
b7f6af2
chore: Phase 3 cleanup — CHANGELOG, delete canonicalCbor (dead code)
flyingrobots Apr 4, 2026
9aeeb52
fix: three bugs in WarpStream — tee cache leak, demux error swallowin…
flyingrobots Apr 4, 2026
bf9eccf
fix: add constructor validation for required dependencies
flyingrobots Apr 4, 2026
6e95376
fix: address review issues — double-iteration, null guards, detached …
flyingrobots Apr 4, 2026
b1b687e
fix: address CodeRabbit review — encryption guard, required patchJour…
flyingrobots Apr 4, 2026
7c88755
fix: eliminate all tsc errors in source files (61 errors across 12 fi…
flyingrobots Apr 4, 2026
d72d97d
fix: tsc zero — 144 errors eliminated across 26 files
flyingrobots Apr 4, 2026
097fa3d
fix: add WorldlineSelector classes to type-surface manifest
flyingrobots Apr 4, 2026
d2d8d8d
docs: lock TRAVERSAL-TRUTH invariant
flyingrobots Apr 4, 2026
5cb8e1d
docs: add language specifiers to fenced code blocks in stream-archite…
flyingrobots Apr 4, 2026
88ba040
fix: reject empty IndexShard shardKey (CodeRabbit round 2)
flyingrobots Apr 4, 2026
86ff6dd
fix: change pseudocode fences from js/ts to text in design docs
flyingrobots Apr 4, 2026
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
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **WarpStream** — composable async stream primitive built on `AsyncIterable<T>`. Domain concept for "data flow over time." Supports `pipe`, `tee`, `mux`, `demux`, `drain`, `reduce`, `forEach`, `collect`. Natural backpressure via `for await`, error propagation via async iterator protocol, cooperative cancellation via `AbortSignal`.
- **Stream transforms** — `CborEncodeTransform`, `CborDecodeTransform`, `GitBlobWriteTransform`, `TreeAssemblerSink`, `IndexShardEncodeTransform` — composable infrastructure pipeline stages for encode → blobWrite → treeAssemble.
- **Artifact record classes** — `CheckpointArtifact` family (`StateArtifact`, `FrontierArtifact`, `AppliedVVArtifact`), `IndexShard` family (`MetaShard`, `EdgeShard`, `LabelShard`, `PropertyShard`, `ReceiptShard`), `PatchEntry`, `ProvenanceEntry`. Runtime-backed domain nouns with constructor validation and `instanceof` dispatch.
- **`PatchJournalPort.scanPatchRange()`** — streaming alternative to `loadPatchRange()`. Returns `WarpStream<PatchEntry>` for incremental patch consumption. Commit walking moved from SyncProtocol into the adapter.
- **`StateHashService`** — standalone canonical state hash computation. Separately callable by checkpoint creation, comparison, materialization, and verification.
- **Index builder `yieldShards()`** — `LogicalBitmapIndexBuilder` and `PropertyIndexBuilder` yield `IndexShard` record instances via generators. Proven byte-identical to legacy `serialize()` path.
- **`LogicalIndexBuildService.buildStream()`** — returns `WarpStream<IndexShard>` merging both builders via `mux()`.
- **Hex tripwire test** — 36 automated checks scanning domain files for forbidden codec imports/usage. Fails loud if domain touches `defaultCodec`, `cbor-x`, or `codec.encode()`/`codec.decode()`.
- **Golden fixtures** — known CBOR bytes for patches, checkpoints, VV, frontier, index shards. Wire format stability proven across refactor.

### Fixed

- **CborPatchJournalAdapter.readPatch()** — now throws `EncryptionError` when `encrypted=true` but no `patchBlobStorage` is configured. Previously fell through to the unencrypted `blobPort.readBlob()` path, returning wrong data.
- **Writer constructor** — `patchJournal` is now a required parameter. Previously optional, which let `beginPatch()` succeed but `commit()` hard-fail with a confusing `PersistenceError` from `PatchBuilderV2`.

### Changed

- **CheckpointStorePort collapsed** — 7 micro-methods (`writeState`, `readState`, etc.) replaced with 2 semantic operations: `writeCheckpoint(record)` and `readCheckpoint(treeOids)`. A checkpoint is one domain event, not a bag of individual blob writes.
- **SyncProtocol uses stream scan** — `processSyncRequest()` prefers `patchJournal.scanPatchRange()` (streaming) over `loadPatchRange()` (array). Falls back to legacy path when unavailable.
- **PatchBuilderV2, SyncProtocol, Writer** — codec-free. Patch persistence goes through `PatchJournalPort`; domain never imports `defaultCodec` or calls `codec.encode()`/`codec.decode()`.
- **CheckpointService** — routes through `CheckpointStorePort` when available. Legacy codec-based paths remain as fallback.
- **The Method** — introduced `METHOD.md` as the development process framework. Filesystem-native backlog (`docs/method/backlog/`) with lane directories (`inbox/`, `asap/`, `up-next/`, `cool-ideas/`, `bad-code/`). Legend-prefixed filenames (`PROTO_`, `TRUST_`, `VIZ_`, `TUI_`, `DX_`, `PERF_`). Sequential cycle numbering (`docs/design/<NNNN-slug>/`). Dual-audience design docs (sponsor human + sponsor agent). Replaced B-number system entirely.
- **Backlog migration** — all 49 B-number and OG items migrated from `BACKLOG/` to `docs/method/backlog/` lanes. Tech debt journal (`.claude/bad_code.md`) split into 10 individual files in `bad-code/`. Cool ideas journal split into 13 individual files in `cool-ideas/`. `docs/release.md` moved to `docs/method/release.md`. `BACKLOG/` directory removed.

Expand Down
12 changes: 12 additions & 0 deletions contracts/type-surface.m8.json
Original file line number Diff line number Diff line change
Expand Up @@ -1237,6 +1237,18 @@
"WriterError": {
"kind": "class"
},
"WorldlineSelector": {
"kind": "class"
},
"LiveSelector": {
"kind": "class"
},
"CoordinateSelector": {
"kind": "class"
},
"StrandSelector": {
"kind": "class"
},
"buildWarpStateIndex": {
"kind": "function",
"async": true
Expand Down
22 changes: 19 additions & 3 deletions docs/BEARING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

Updated at cycle boundaries. Not mid-cycle.

## Invariants

1. **HEXAGONAL** — domain never imports infrastructure
2. **DETERMINISTIC** — same patches, any order → same materialized state
3. **APPEND-ONLY** — Git history never rewritten
4. **MULTI-WRITER** — each writer owns its own ref, no coordination
5. **RUNTIME-TRUTH** — domain concepts are classes with validated invariants (SSJS P1)
6. **BOUNDARY-HONESTY** — untrusted input validated at boundaries
7. **TRAVERSAL-TRUTH** — unbounded data flows through streams (traversal);
bounded truth crosses ports (contracts). Never conflated. Persistence
ordering is canonical regardless of stream timing.

## Where are we going?

Structural decomposition of `domain/services/` — 83 files in a flat
Expand All @@ -15,9 +27,13 @@ analysis, 10 cohesive groups identified, no circular dependencies.

## What feels wrong?

- WorldlineSource is still a tagged object, not a subclass hierarchy.
- `defaultCodec.js` lives in `domain/utils/` but imports `cbor-x`
directly — a hexagonal boundary violation.
- ~~WorldlineSource~~ Shipped as WorldlineSelector hierarchy (cycle 0007).
- 20 domain services do serialization directly (`codec.encode()`/
`codec.decode()`). The fix is a two-stage boundary: artifact-level
ports (PatchJournalPort, CheckpointStorePort, etc.) that speak
domain types, backed by codec-owning adapters over the raw Git
ports. Strangler refactor, patches first.
See `NDNM_defaultcodec-to-infrastructure.md`.
- The two legends (CLEAN_CODE, NO_DOGS_NO_MASTERS) overlap
significantly. May need consolidation or clearer boundaries.
- JoinReducer is imported by 8 of 10 service clusters — it is the
Expand Down
12 changes: 6 additions & 6 deletions docs/design/0007-viewpoint-design/viewpoint-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ change shape.
The inbound boundary factory. Accepts plain objects with a `kind`
discriminant and returns the appropriate class instance:

```javascript
```text
WorldlineSelector.from({ kind: 'live' })
→ new LiveSelector()

Expand Down Expand Up @@ -526,31 +526,31 @@ const selector = WorldlineSelector.from(source).clone();

**Before (materializeSource dispatch):**

```javascript
```text
if (source.kind === 'live') { ... }
if (source.kind === 'coordinate') { ... }
return await materializeStrandSource(...);
```

**After:**

```javascript
```text
if (source instanceof LiveSelector) { ... }
if (source instanceof CoordinateSelector) { ... }
return await materializeStrandSource(...);
```

**Before (Worldline.source getter):**

```javascript
```text
get source() {
return cloneWorldlineSource(this._source);
}
```

**After:**

```javascript
```text
get source() {
return this._source.toDTO(); // plain object for public API
}
Expand Down Expand Up @@ -611,7 +611,7 @@ export {
Add the selector class declarations. Keep the existing
`WorldlineSource` type and interfaces unchanged:

```typescript
```text
// NEW — selector classes
export class WorldlineSelector {
clone(): WorldlineSelector;
Expand Down
234 changes: 234 additions & 0 deletions docs/design/0008-stream-architecture/stream-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# Cycle 0008 — Stream Architecture

**Sponsor (human):** James
**Sponsor (agent):** Claude
**Status:** DESIGN

## Hill

A developer can pipe domain objects through a composable stream
pipeline where encoding, persistence, and tree assembly are transforms
and sinks — never called directly by domain code. Semantic ports
remain for bounded single-artifact operations. Artifact records carry
runtime identity. The system is memory-bounded for unbounded datasets.

## The Rule

Streams are for scale. Ports are for meaning. Artifacts are the nouns.
Paths are infrastructure.

## Playback Questions

1. Does the pipeline produce byte-identical output to the legacy path?
2. Does a constrained-heap test complete for a dataset that would
otherwise OOM?
3. Do semantic ports still tell you WHAT is being persisted and WHAT
lifecycle rules apply?
4. Is CBOR vocabulary absent from domain nouns?
5. Does every artifact record class add runtime identity, not just a name?

## Non-Goals

- CborStream or any codec-named class in the domain
- Marker stream subclasses that don't add flow behavior
- Melting separate ports/services into one generic pipe
- Replacing bounded single-artifact reads with streams

---

## Architecture

### One Stream Container

```text
WarpStream<T> — domain primitive
pipe / tee / mux / demux / drain / reduce / forEach / collect
[Symbol.asyncIterator]()
```

No domain subclasses. Identity lives on elements, not the container.

### Semantic Ports

Ports define what is being persisted and what lifecycle rules apply.
Bounded operations stay `Promise<T>`. Unbounded operations return or
accept `WarpStream<SemanticUnit>`.

**PatchJournalPort** (keep, extend)
```text
writePatch(patch) → Promise<string> bounded write
readPatch(oid) → Promise<PatchV2> bounded read
scanPatchRange(...) → WarpStream<PatchEntry> unbounded scan (NEW)
```

**CheckpointStorePort** (collapse micro-methods)
```text
writeCheckpoint(record) → Promise<CheckpointWriteResult> one call
readCheckpoint(sha) → Promise<CheckpointData> bounded read
```
Adapter internally streams artifacts through the pipeline.

**IndexStorePort** (NEW, streaming)
```text
writeShards(stream) → Promise<string> WarpStream<IndexShard> → tree OID
scanShards(...) → WarpStream<IndexShard> unbounded read
```

**ProvenanceStorePort** (NEW, separate concept)
```text
scanEntries(...) → WarpStream<ProvenanceEntry>
writeIndex(index) → Promise<string>
```
Own port. Physical colocation under checkpoint tree ≠ semantic
ownership. Checkpoint = recovery. Provenance = causal/query/verification.
Different jobs, different lifecycle, different consumers.

**StateHashService** (separate callable, not buried in adapter)
```text
compute(state) → Promise<string>
```
Used by verification, comparison, detached checks, AND checkpoint
creation. Not exclusively inside writeCheckpoint().

### Artifact Records

Runtime identity on elements, not containers (P1/P7).

**CheckpointArtifact** — closed subclass family
```text
CheckpointArtifact (abstract base)
common: checkpointRef, schemaVersion

StateArtifact extends CheckpointArtifact
payload: { state: WarpStateV5 }

FrontierArtifact extends CheckpointArtifact
payload: { frontier: Map<string, string> }

AppliedVVArtifact extends CheckpointArtifact
payload: { appliedVV: VersionVector }
```
No paths. No CBOR. No blob OIDs. No adapter trivia.

**IndexShard** — subtype family (not one generic class)
```text
IndexShard (base)
common: indexFamily, shardId, schemaVersion

MetaShard extends IndexShard
payload: { nodeToGlobal, alive, nextLocalId }

EdgeShard extends IndexShard
payload: { direction, shardKey, buckets }

LabelShard extends IndexShard
payload: { labels: [string, number][] }

PropertyShard extends IndexShard
payload: { entries: [string, Record][] }
```
The code already treats shard families differently (isMetaShard,
isEdgeShard, classifyShards). One mega-shard class is just `any`
with better PR.

**PatchEntry** — `{ patch: PatchV2, sha: string }`

**ProvenanceEntry** — `{ nodeId, patchShas }`

### Path Mapping

Adapter owns it. Full stop. Domain produces artifact records.
Adapter maps to Git tree paths at the last responsible moment.

```text
StateArtifact → 'state.cbor'
FrontierArtifact → 'frontier.cbor'
MetaShard → 'meta_XX.cbor'
EdgeShard → '{fwd|rev}_XX.cbor'
```

Static mapping table or instanceof dispatcher in the adapter.
No `.path()` on domain objects. Paths are storage convention.

Domain owns meaning. Adapter owns layout.

### Infrastructure Transforms

```text
CborEncodeTransform artifact → [path, bytes]
CborDecodeTransform [path, bytes] → artifact
GitBlobWriteTransform [path, bytes] → [path, oid]
TreeAssemblerSink [path, oid] → finalize → treeOid
```

Encode → blobWrite → treeAssemble stays entirely in infrastructure.
CBOR is boundary vocabulary — never a domain noun.

### Pipeline Examples

```js
// Index write (unbounded, streaming)
await indexStore.writeShards(
WarpStream.from(builder.yieldShards())
);
// Adapter internally: stream → encode → blobWrite → treeAssemble

// Checkpoint write (bounded, one call)
await checkpointStore.writeCheckpoint({
state, frontier, appliedVV, stateHash, provenanceIndex
});
// Adapter internally: yield artifacts → encode → blobWrite → tree

// Patch scan (unbounded)
const patches = patchJournal.scanPatchRange(writerRef, fromSha, toSha);
for await (const entry of patches) {
reducer.apply(entry.patch);
}
```

### Ordering Guarantee

`WarpStream.mux()` interleaves by arrival order. Async completion
timing must not bleed into tree assembly. `TreeAssemblerSink` sorts
entries before `writeTree()`. Deterministic Git trees don't care
which blob write finished first.

---

## Migration Plan

### Phase 1 — Artifact records + streaming ports

- CheckpointArtifact family (StateArtifact, FrontierArtifact,
AppliedVVArtifact)
- IndexShard family (MetaShard, EdgeShard, LabelShard, PropertyShard)
- PatchEntry, ProvenanceEntry records
- IndexStorePort with writeShards/scanShards
- PatchJournalPort.scanPatchRange()
- StateHashService
- ProvenanceStorePort

### Phase 2 — Wire write paths

- CheckpointStorePort collapse → writeCheckpoint(record)
- Index builders: yieldShards() returns IndexShard subclass instances
- SyncProtocol: consume scanPatchRange() instead of loadPatchRange()

### Phase 3 — P5 cleanup

- Remove defaultCodec from all domain files
- Delete defaultCodec.js, canonicalCbor.js
- Expand tripwire to all migrated files

### Phase 4 — Memory-bounded witnesses

- Constrained-heap tests
- Naming audit for slurp APIs

---

## Accessibility / Localization / Agent-Inspectability

- **Agent-Inspectability**: Artifact records are `instanceof`-
dispatchable. WarpStream carries AbortSignal. Sink.consume()
returns typed results.
Loading
Loading