Skip to content

Commit ae76d1b

Browse files
authored
Merge pull request #62 from git-stunts/fix/b148-code-review-fixes
Resolved 13 review issues across 5 rounds; v13.0.0. Fixes: prototype-pollution hardening (Object.create(null) × 5 sites), bisect CLI ENOENT/EACCES handling, getNodeProps null-masking, ROADMAP M11 sequencing, BisectResult discriminated union, SHA format validation, propsMap→propsRecord rename, CHANGELOG restructuring. All 8 CI checks green. 4619 tests passing. CodeRabbit verified all items clean.
2 parents 000b447 + 65f0f56 commit ae76d1b

45 files changed

Lines changed: 1703 additions & 176 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [13.0.0] — 2026-03-03
9+
10+
### Added
11+
12+
- **Observer API stabilized (B3)**`subscribe()` and `watch()` promoted to `@stability stable` with `@since 13.0.0` annotations. Fixed `onError` callback type from `(error: Error)` to `(error: unknown)` to match runtime catch semantics. `watch()` pattern param now correctly typed as `string | string[]` in `_wiredMethods.d.ts`.
13+
- **`graph.patchMany()` batch patch API (B11)** — applies multiple patch callbacks sequentially. Each callback sees state from prior commits. Returns array of commit SHAs. Inherits reentrancy guard from `graph.patch()`.
14+
- **Causality bisect (B2)**`BisectService` performs binary search over a writer's patch chain to find the first bad patch. CLI: `git warp bisect --good <sha> --bad <sha> --test <cmd> --writer <id>`. O(log N) materializations. Exit codes: 0=found, 1=usage, 2=range error, 3=internal.
915

1016
### Changed
1117

18+
- **BREAKING: `getNodeProps()` returns `Record<string, unknown>` instead of `Map<string, unknown>` (B100)** — aligns with `getEdgeProps()` which already returns a plain object. Callers must replace `.get('key')` with `.key` or `['key']`, `.has('key')` with `'key' in props`, and `.size` with `Object.keys(props).length`. `ObserverView.getNodeProps()` follows the same change.
1219
- **GraphPersistencePort narrowing (B145)** — domain services now declare focused port intersections (`CommitPort & BlobPort`, etc.) in JSDoc instead of the 23-method composite `GraphPersistencePort`. Removed `ConfigPort` from the composite (23 → 21 methods); adapters still implement `configGet`/`configSet` on their prototypes. Zero behavioral change.
1320
- **Codec trailer validation extraction (B134, B138)** — created `TrailerValidation.js` with `requireTrailer()`, `parsePositiveIntTrailer()`, `validateKindDiscriminator()`. All 4 message codec decoders now use shared helpers exclusively. Patch and Checkpoint decoders now also perform semantic field validation (graph name, writer ID, OID, SHA-256) matching the Audit decoder pattern. Internal refactor for valid inputs, with stricter rejection of malformed messages.
1421
- **HTTP adapter shared utilities (B135)** — created `httpAdapterUtils.js` with `MAX_BODY_BYTES`, `readStreamBody()`, `noopLogger`. Eliminates duplication across Node/Bun/Deno HTTP adapters. Internal refactor, no behavioral change.
@@ -25,6 +32,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2532
- **Fake timer lifecycle (B131)** — moved `vi.useFakeTimers()` from `beforeAll` to `beforeEach` and `vi.useRealTimers()` into `afterEach` in `WarpGraph.watch.test.js`.
2633
- **Test determinism (B132)** — seeded `Math.random()` in benchmarks with Mulberry32 RNG (`0xDEADBEEF`), added `seed: 42` to all fast-check property tests, replaced random delays in stress test with deterministic values.
2734
- **Global mutation documentation (B133)** — documented intentional `globalThis.Buffer` mutation in `noBufferGlobal.test.js` and `crypto.randomUUID()` usage in `SyncAuthService.test.js`.
35+
- **Code review fixes (B148):**
36+
- **CLI hardening** — added `--writer` validation to bisect, SHA format regex on `--good`/`--bad`, rethrow ENOENT/EACCES from test command runner instead of swallowing.
37+
- **BisectService cleanup** — removed dead code, added invariant comment, replaced `BisectResult` interface with discriminated union type, fixed exit code constant.
38+
- **Prototype-pollution hardening**`Object.create(null)` for property bags in `getNodeProps`, `getEdgeProps`, `getEdges`, `buildPropsSnapshot`; fixed indexed-path null masking in `getNodeProps`.
39+
- **Docs housekeeping** — reconciled ROADMAP inventory counts (24→29 done), fixed M11 sequencing, removed done items from priority tiers, fixed stale test vector counts (6→9), corrected Deno test name, moved B100 to `### Changed`.
2840

2941
## [12.4.1] — 2026-02-28
3042

@@ -1528,7 +1540,7 @@ Implements [Paper III](https://doi.org/10.5281/zenodo.17963669) (Computational H
15281540

15291541
#### Query API (V7 Task 7)
15301542
- **`graph.hasNode(nodeId)`** - Check if node exists in materialized state
1531-
- **`graph.getNodeProps(nodeId)`** - Get all properties for a node as Map
1543+
- **`graph.getNodeProps(nodeId)`** - Get all properties for a node (returns `Record<string, unknown>` since v13.0.0)
15321544
- **`graph.neighbors(nodeId, dir?, label?)`** - Get neighbors with direction/label filtering
15331545
- **`graph.getNodes()`** - Get all visible node IDs
15341546
- **`graph.getEdges()`** - Get all visible edges as `{from, to, label}` array

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
<img src="docs/images/hero.gif" alt="git-warp CLI demo" width="600">
99
</p>
1010

11-
## What's New in v12.4.1
11+
## What's New in v13.0.0
1212

13-
- **JSDoc total coverage** — eliminated all unsafe `{Object}`, `{Function}`, `{*}` type patterns across 135 files (190+ sites), replacing them with precise inline typed shapes.
14-
- **Zero tsc errors** — fixed tsconfig split-config includes and type divergences; 0 errors across all three tsconfig targets.
15-
- **JSR dry-run fix** — worked around a deno_ast 0.52.0 panic caused by overlapping text-change entries for duplicate import specifiers.
16-
- **`check-dts-surface.js` regex fix** — default-export parsing now correctly captures identifiers instead of keywords for `export default class/function` patterns.
13+
- **BREAKING: `getNodeProps()` returns `Record<string, unknown>`** — aligns with `getEdgeProps()`. Replace `.get('key')` with `.key`, `.has('key')` with `'key' in props`, `.size` with `Object.keys(props).length`.
14+
- **BREAKING: Removed `PerformanceClockAdapter` and `GlobalClockAdapter`** — use `ClockAdapter` directly.
15+
- **`graph.patchMany()`** — batch multiple patches sequentially; each callback sees prior state.
16+
- **`git warp bisect`** — binary search over writer patch history to find the first bad commit. O(log N) materializations.
17+
- **Observer API stable**`subscribe()` and `watch()` promoted to stable with `@since 13.0.0`.
18+
- **`BisectService`** — domain service exported for programmatic use.
1719

1820
See the [full changelog](CHANGELOG.md) for details.
1921

@@ -183,7 +185,7 @@ Query methods auto-materialize by default. Just open a graph and start querying:
183185
```javascript
184186
await graph.getNodes(); // ['user:alice', 'user:bob']
185187
await graph.hasNode('user:alice'); // true
186-
await graph.getNodeProps('user:alice'); // Map { 'name' => 'Alice', 'role' => 'admin' }
188+
await graph.getNodeProps('user:alice'); // { name: 'Alice', role: 'admin' }
187189
await graph.neighbors('user:alice', 'outgoing'); // [{ nodeId: 'user:bob', label: 'manages', direction: 'outgoing' }]
188190
await graph.getEdges(); // [{ from: 'user:alice', to: 'user:bob', label: 'manages', props: {} }]
189191
await graph.getEdgeProps('user:alice', 'user:bob', 'manages'); // { weight: 0.9 } or null
@@ -371,7 +373,7 @@ const view = await graph.observer('publicApi', {
371373
});
372374

373375
const users = await view.getNodes(); // only user:* nodes
374-
const props = await view.getNodeProps('user:alice'); // Map without ssn/password
376+
const props = await view.getNodeProps('user:alice'); // { name: 'Alice', ... } without ssn/password
375377
const result = await view.query().match('user:*').where({ role: 'admin' }).run();
376378

377379
// Measure information loss between two observer perspectives

ROADMAP.md

Lines changed: 23 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# ROADMAP — @git-stunts/git-warp
22

3-
> **Current version:** v12.4.1
4-
> **Last reconciled:** 2026-03-02 (M14 HYGIENE added from HEX_AUDIT; completed items archived to COMPLETED.md; BACKLOG.md retired)
3+
> **Current version:** v13.0.0
4+
> **Last reconciled:** 2026-03-03 (v13.0.0 release: M11 COMPASS II complete, B100/B140 breaking, B44/B124/B125/B146 done)
55
> **Completed milestones:** [docs/ROADMAP/COMPLETED.md](docs/ROADMAP/COMPLETED.md)
66
77
---
@@ -25,11 +25,11 @@
2525
2626
### M10.T4 — Causality Bisect Spec
2727

28-
- **Status:** `PENDING`
28+
- **Status:** `DONE` (spec existed; implementation completed in M11)
2929

3030
**Items:**
3131

32-
- **B2 (spec only)** (CAUSALITY BISECT) — design the bisect CLI contract + data model. Commit spec with test vectors. Full implementation deferred to M11 — but the spec lands here so bisect is available as a debugging tool during M10 trust hardening.
32+
- **B2 (spec only)** (CAUSALITY BISECT) — Spec committed at `docs/specs/BISECT_V1.md`. Full implementation shipped in M11/v13.0.0.
3333

3434
**M10 Gate:** Signed ingress enforced end-to-end; trust E2E receipts green; B63 GC isolation verified under concurrent writes; B64 sync payload validation green; B65 divergence logging verified; B2 spec committed with test vectors.
3535

@@ -165,37 +165,9 @@ Design-only items. RFCs filed — implementation deferred to future milestones.
165165

166166
---
167167

168-
## Milestone 11 — COMPASS II
168+
## Milestone 11 — COMPASS II ✅ COMPLETE (v13.0.0)
169169

170-
**Theme:** Developer experience
171-
**Objective:** Ship bisect, public observer API, and batch patch ergonomics.
172-
**Triage date:** 2026-02-17
173-
174-
### M11.T1 — Causality Bisect (Implementation)
175-
176-
- **Status:** `PENDING`
177-
178-
**Items:**
179-
180-
- **B2 (implementation)** (CAUSALITY BISECT) — full implementation building on M10 spec. Binary search for first bad tick/invariant failure. `git bisect` for WARP.
181-
182-
### M11.T2 — Observer API
183-
184-
- **Status:** `PENDING`
185-
186-
**Items:**
187-
188-
- **B3** (OBSERVER API) — public event contract. Internal soak period over (shipped in PULSE, used internally since). Stabilize the public surface.
189-
190-
### M11.T3 — Batch Patch API
191-
192-
- **Status:** `PENDING`
193-
194-
**Items:**
195-
196-
- **B11** (`graph.patchMany(fns)` BATCH API) — sequence multiple patch callbacks atomically, each seeing the ref left by the previous. Natural complement to `graph.patch()`.
197-
198-
**M11 Gate:** Bisect correctness verified on seeded regressions; observer contract snapshot-tested; patchMany passes no-coordination suite.
170+
Archived to [COMPLETED.md](docs/ROADMAP/COMPLETED.md#milestone-11--compass-ii).
199171

200172
---
201173

@@ -209,10 +181,10 @@ Items picked up opportunistically without blocking milestones. No milestone assi
209181

210182
| ID | Item |
211183
|----|------|
212-
| B124 | **TRUST PAYLOAD PARITY TESTS**assert CLI `trust` and `AuditVerifierService.evaluateTrust()` emit shape-compatible error payloads. From BACKLOG 2026-02-27. |
213-
| B125 | **`CachedValue` NULL-PAYLOAD SEMANTIC TESTS**document and test whether `null` is a valid cached value. From BACKLOG 2026-02-27. |
184+
| ~~B124~~ | ~~**TRUST PAYLOAD PARITY TESTS**~~22 tests verifying CLI vs service shape parity. Done in v13.0.0. |
185+
| ~~B125~~ | ~~**`CachedValue` NULL-PAYLOAD SEMANTIC TESTS**~~3 tests documenting null = "no value" sentinel. Done in v13.0.0. |
214186
| B127 | **DENO SMOKE TEST**`npm run test:deno:smoke` for fast local pre-push confidence without full Docker matrix. From BACKLOG 2026-02-25. |
215-
| B44 | **SUBSCRIBER UNSUBSCRIBE-DURING-CALLBACK E2E**event system edge case; known bug class that bites silently |
187+
| ~~B44~~ | ~~**SUBSCRIBER UNSUBSCRIBE-DURING-CALLBACK E2E**~~3 edge-case tests (cross-unsubscribe, subscribe-during-callback, unsubscribe-in-onError). Done in v13.0.0. |
216188
| B34 | **DOCS: SECURITY_SYNC.md** — extract threat model from JSDoc into operator doc |
217189
| B35 | **DOCS: README INSTALL SECTION** — Quick Install with Docker + native paths |
218190
| B36 | **FLUENT STATE BUILDER FOR TESTS**`StateBuilder` helper replacing manual `WarpStateV5` literals |
@@ -229,7 +201,7 @@ Items picked up opportunistically without blocking milestones. No milestone assi
229201
| B79 | **WARPGRAPH CONSTRUCTOR LIFECYCLE DOCS** — document cache invalidation strategy for 25 instance variables: which operations dirty which caches, which flush them. From B-AUDIT-16 (TSK TSK). **File:** `src/domain/WarpGraph.js:69-198` |
230202
| B80 | **CHECKPOINTSERVICE CONTENT BLOB UNBOUNDED MEMORY** — iterates all properties into single `Set` before tree serialization. Stream content OIDs in batches. From B-AUDIT-10 (JANK). **File:** `src/domain/services/CheckpointService.js:224-226` |
231203
| B81 | **`attachContent` ORPHAN BLOB GUARD**`attachContent()` unconditionally writes blob before `setProperty()`. Validate before push to prevent orphan blobs. From B-CODE-2. **File:** `src/domain/services/PatchBuilderV2.js` |
232-
| B146 | **UNIFY `CorePersistence` / `FullPersistence` TYPEDEFS**`CorePersistence` (`WarpPersistence.js`) and `FullPersistence` (`WarpGraph.js`) are identical `CommitPort & BlobPort & TreePort & RefPort` intersections. Consolidate into one canonical typedef and update all import sites. From B145 PR review. |
204+
| ~~B146~~ | ~~**UNIFY `CorePersistence` / `FullPersistence` TYPEDEFS**~~replaced `FullPersistence` with imported `CorePersistence`. Done in v13.0.0. |
233205
| B147 | **RFC FIELD COUNT DRIFT DETECTOR** — script that counts WarpGraph instance fields (grep `this._` in constructor) and warns if design RFC field counts diverge. Prevents stale numbers in `warpgraph-decomposition.md`. From B145 PR review. |
234206

235207
### CI & Tooling Pack
@@ -299,7 +271,7 @@ Items parked with explicit conditions for promotion.
299271
| B20 | **TRUST RECORD ROUND-TRIP SNAPSHOT TEST** | Promote if trust record schema changes |
300272
| B21 | **TRUST SCHEMA DISCRIMINATED UNION** | Promote if superRefine causes a bug or blocks a feature |
301273
| B27 | **`TrustKeyStore` PRE-VALIDATED KEY CACHE** | Promote when `verifySignature` appears in any p95 flame graph above 5% of call time |
302-
| B100 | **MAP vs RECORD ASYMMETRY**`getNodeProps()` returns Map, `getEdgeProps()` returns Record. Breaking change either way. From B-FEAT-3. | Promote with next major version RFC |
274+
| ~~B100~~ | ~~**MAP vs RECORD ASYMMETRY**~~`getNodeProps()` now returns `Record<string, unknown>`. Done in v13.0.0. | ~~Promote with next major version RFC~~ |
303275
| B101 | **MERMAID `~~~` INVISIBLE-LINK FRAGILITY** — undocumented Mermaid feature for positioning. From B-DIAG-3. | Promote if Mermaid renderer update breaks `~~~` positioning |
304276

305277
---
@@ -312,21 +284,21 @@ B5, B6, B13, B17, B18, B25, B45 — rejected 2026-02-17 with cause recorded in `
312284

313285
## Execution Order
314286

315-
### Milestones: M10 → M12 → M13 → M14M11
287+
### Milestones: M10 → M12 → M13 → M11M14
316288

317-
1. **M10 SENTINEL** — Trust + sync safety + correctness — DONE except B2 spec
289+
1. **M10 SENTINEL** — Trust + sync safety + correctness — **DONE**
318290
2. **M12 SCALPEL** — STANK audit cleanup (minus edge prop encoding) — **DONE** (all tasks complete, gate verified)
319291
3. **M13 SCALPEL II** — Edge property canonicalization — **DONE** (internal model complete; wire-format cutover deferred by ADR 3)
320-
4. **M14 HYGIENE**Test quality, DRY extraction, SOLID quick-wins**NEXT** (from HEX_AUDIT)
321-
5. **M11 COMPASS II**Developer experience (B2 impl, B3, B11)after M14
292+
4. **M11 COMPASS II**Developer experience (B2 impl, B3, B11)**DONE** (v13.0.0), archived
293+
5. **M14 HYGIENE**Test quality, DRY extraction, SOLID quick-wins**NEXT** (from HEX_AUDIT)
322294

323295
### Standalone Priority Sequence
324296

325297
Pick opportunistically between milestones. Recommended order within tiers:
326298

327299
1. ~~**Immediate** (B46, B47, B26, B71, B126)~~**ALL DONE.**
328-
2. **Near-term correctness** (B44, B76, B80, B81, B124) — prioritize items touching core services
329-
3. **Near-term DX** (B36, B37, B43, B125, B127) — test ergonomics and developer velocity
300+
2. **Near-term correctness** (B76, B80, B81) — prioritize items touching core services
301+
3. **Near-term DX** (B36, B37, B43, B127) — test ergonomics and developer velocity
330302
4. **Near-term docs/types** (B34, B35) — alignment and documentation
331303
5. **Near-term tooling** (B12, B48, B49, B53, B54, B57, B28) — remaining type safety items
332304
6. **CI & Tooling Pack** (B83, B85–B88, B119, B123, B128) — batch as one PR
@@ -349,11 +321,11 @@ Pick opportunistically between milestones. Recommended order within tiers:
349321
| **Milestone (M12)** | 18 | B66, B67, B70, B73, B75, B105–B115, B117, B118 |
350322
| **Milestone (M13)** | 1 | B116 (internal: DONE; wire-format: DEFERRED) |
351323
| **Milestone (M14)** | 16 | B130–B145 |
352-
| **Standalone** | 39 | B12, B19, B22, B28, B34–B37, B43, B44, B48, B49, B53, B54, B57, B76, B79–B81, B83, B85–B88, B95–B99, B102–B104, B119, B123–B125, B127–B129, B146, B147 |
353-
| **Standalone (done)** | 23 | B26, B46, B47, B50–B52, B55, B71, B72, B77, B78, B82, B84, B89–B94, B120–B122, B126 |
354-
| **Deferred** | 8 | B4, B7, B16, B20, B21, B27, B100, B101 |
324+
| **Standalone** | 35 | B12, B19, B22, B28, B34–B37, B43, B48, B49, B53, B54, B57, B76, B79–B81, B83, B85–B88, B95–B99, B102–B104, B119, B123, B127–B129, B147 |
325+
| **Standalone (done)** | 29 | B26, B44, B46, B47, B50–B52, B55, B71, B72, B77, B78, B82, B84, B89–B94, B100, B120–B122, B124, B125, B126, B146, B148 |
326+
| **Deferred** | 7 | B4, B7, B16, B20, B21, B27, B101 |
355327
| **Rejected** | 7 | B5, B6, B13, B17, B18, B25, B45 |
356-
| **Total tracked** | **122** (23 done) | |
328+
| **Total tracked** | **123** total; 29 standalone done | |
357329

358330
### STANK.md Cross-Reference
359331

@@ -455,11 +427,11 @@ Pick opportunistically between milestones. Recommended order within tiers:
455427
## Final Command
456428

457429
Every milestone has a hard gate. No milestone blurs into the next.
458-
Execution: M10 SENTINEL → **M12 SCALPEL****M13 SCALPEL II****M14 HYGIENE** → M11 COMPASS II. Standalone items fill the gaps.
430+
Execution: M10 SENTINEL → **M12 SCALPEL****M13 SCALPEL II****M11 COMPASS II****M14 HYGIENE**. M11 is complete and archived. Standalone items fill the gaps.
459431

460432
M12 is complete (including T8/T9). M13 internal canonicalization (ADR 1) is complete — canonical `NodePropSet`/`EdgePropSet` semantics, wire gate split, reserved-byte validation, version namespace separation. The persisted wire-format half of B116 is deferred by ADR 2 and governed by ADR 3 readiness gates.
461433

462-
M14 HYGIENE is the current priority — test hardening, DRY extraction, and SOLID quick-wins from the HEX_AUDIT. M11 follows after M14.
434+
M14 HYGIENE is the current priority — test hardening, DRY extraction, and SOLID quick-wins from the HEX_AUDIT. M11 is complete and archived in COMPLETED.md.
463435

464436
Rejected items live in `GRAVEYARD.md`. Resurrections require an RFC.
465437
`BACKLOG.md` retired — all intake goes directly into this file (policy in `CLAUDE.md`).

0 commit comments

Comments
 (0)