Skip to content

fix(bisect): harden CLI validation and remove dead code (B148)#62

Merged
flyingrobots merged 17 commits intomainfrom
fix/b148-code-review-fixes
Mar 4, 2026
Merged

fix(bisect): harden CLI validation and remove dead code (B148)#62
flyingrobots merged 17 commits intomainfrom
fix/b148-code-review-fixes

Conversation

@flyingrobots
Copy link
Member

@flyingrobots flyingrobots commented Mar 3, 2026

Summary

  • BisectService dead code removal — deleted unused BisectTestFn typedef and unreachable candidates.length === 0 guard
  • Bisect CLI hardening — added --writer validation (throws usageError when default cli writer used), fixed exit code from USAGE+1 to named NOT_FOUND constant
  • 3 new bisect test vectors — all-bad, testFn-throws, empty-writer-chain (6 → 9 tests)
  • TrustPayloadParity cleanup — removed unused vi/beforeEach imports and CLI_TRUST_OVERRIDES constant
  • CHANGELOG fix — corrected stale getNodeProps return type description (MapRecord<string, unknown>)
  • ROADMAP housekeeping — archived M11 COMPASS II to COMPLETED.md, added B148 to inventory

Test plan

  • npm run lint — ESLint clean
  • npm run test:local — 252 files, 4612 tests passed (including 3 new bisect vectors)
  • WarpGraph.noCoordination.test.js — passed
  • Pre-commit + pre-push hooks (IRONCLAD gates) — passed
  • npm run release:preflight — all checks green (dirty-tree expected before commit)

Summary by CodeRabbit

  • New Features

    • CLI and programmatic bisect to find the first bad patch (git warp bisect, BisectService)
    • Batch patching API patchMany that applies patches sequentially and returns commit SHAs
    • Observer API methods subscribe() and watch() promoted to stable
    • Release bumped to v13.0.0
  • Breaking Changes

    • getNodeProps() (Graph & ObserverView) now returns plain objects (Record) instead of Maps — access via property lookup
    • Removed PerformanceClockAdapter and GlobalClockAdapter; watch() now accepts string or string[] patterns

BREAKING CHANGE: getNodeProps() now returns Record<string, unknown>
instead of Map<string, unknown>, aligning with getEdgeProps() which
already returns a plain object.

Migration: replace .get('key') with .key or ['key'],
.has('key') with 'key' in props, .size with Object.keys(props).length.

Updated source (query.methods.js, ObserverView.js, QueryBuilder.js),
type definitions (_wiredMethods.d.ts, index.d.ts, type-surface.m8.json),
15 test files, examples, README, and GUIDE.
Replace duplicate FullPersistence typedef in WarpGraph.js with an
import of CorePersistence from types/WarpPersistence.js. Both were
identical four-port intersections (Commit & Blob & Tree & Ref).
Promote subscribe() and watch() to @stability stable with @SInCE 13.0.0
annotations. Fix onError callback type from (error: Error) to
(error: unknown) in _wiredMethods.d.ts and index.d.ts to match runtime
catch semantics. Correct watch() pattern type to string | string[].
Sequential batch helper that applies multiple patch callbacks in order.
Each callback sees state from the prior commit, enabling dependent
patches. Returns array of commit SHAs.

Inherits CAS, eager re-materialize, and reentrancy guard from patch().
Implements causality bisect: given a known-good and known-bad commit SHA
on a writer's patch chain, finds the first bad patch via binary search.
Materializes state at each midpoint using ceiling-bounded materialization
and delegates to a user-supplied test function.

Includes 6 test vectors covering the linear chain, same-SHA, single-step,
reversed-order, missing-SHA, and SHA-passthrough cases.
BisectService performs binary search over a writer's patch chain to
find the first bad patch between a known-good and known-bad commit.
O(log N) materializations via ceiling-based seek.

CLI: git warp bisect --good <sha> --bad <sha> --test <cmd> --writer <id>
Exit codes: 0=found, 2=range error. Test command receives
WARP_BISECT_SHA and WARP_BISECT_GRAPH env vars.

Includes 6 test vectors from BISECT_V1 spec: linear chain, same SHA,
single step, reversed order, unknown SHA, and testFn validation.
Three new edge-case tests:
- Multi-handler cross-unsubscribe (A unsubscribes B mid-loop)
- Subscribe new handler during callback (should not fire for current diff)
- Unsubscribe during onError callback (no infinite loop)
22 tests verifying shape parity between CLI trust output and
AuditVerifierService.evaluateTrust() across happy path, error path,
and not-configured paths. Validates CLI-specific overrides (status,
source, sourceDetail) and structural invariants.
Three tests documenting the intentional null = "no value" sentinel
contract: null triggers recomputation, getWithMetadata shows
fromCache=false, hasValue returns false.
BREAKING CHANGES:
- getNodeProps() returns Record<string, unknown> instead of Map (B100)
- Removed PerformanceClockAdapter and GlobalClockAdapter exports (B140)

New features:
- graph.patchMany() batch patch API (B11)
- git warp bisect — causality bisect CLI and BisectService (B2)
- Observer API (subscribe/watch) stabilized at @SInCE 13.0.0 (B3)

Refactors:
- Unified CorePersistence/FullPersistence typedefs (B146)

Tests:
- Unsubscribe-during-callback E2E tests (B44)
- Trust payload parity tests (B124)
- CachedValue null-payload semantic tests (B125)
…13.0.0

- Fix WarpGraphInstance.materialize return type (void → WarpStateV5) in CLI types
- Use structural BisectGraph typedef instead of WarpGraph import in BisectService
- Update index.d.ts BisectService constructor to match structural type
- Add BisectService and BisectResult to type-surface.m8.json manifest
- Add --writer validation to bisect CLI handler
- Fix exit code constant (USAGE+1 → NOT_FOUND)
- Remove unreachable candidates guard and unused BisectTestFn typedef
- Add all-bad, testFn-throws, and empty-chain bisect test vectors
- Remove unused imports/constants from TrustPayloadParity test
- Fix stale getNodeProps return type in CHANGELOG
- Archive M11 COMPASS II to COMPLETED.md
@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

Warning

Rate limit exceeded

@flyingrobots has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 47 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Run ID: 5858e731-ca7b-4925-a5b4-3156dac30e5b

📥 Commits

Reviewing files that changed from the base of the PR and between 000b447 and 65f0f56.

⛔ Files ignored due to path filters (1)
  • test/unit/domain/__snapshots__/WarpGraph.apiSurface.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (44)
  • CHANGELOG.md
  • README.md
  • ROADMAP.md
  • bin/cli/commands/bisect.js
  • bin/cli/commands/registry.js
  • bin/cli/infrastructure.js
  • bin/cli/schemas.js
  • bin/cli/types.js
  • contracts/type-surface.m8.json
  • docs/GUIDE.md
  • docs/ROADMAP/COMPLETED.md
  • examples/scripts/explore.js
  • examples/scripts/setup.js
  • index.d.ts
  • index.js
  • jsr.json
  • package.json
  • src/domain/WarpGraph.js
  • src/domain/services/BisectService.js
  • src/domain/services/ObserverView.js
  • src/domain/services/QueryBuilder.js
  • src/domain/warp/_wiredMethods.d.ts
  • src/domain/warp/patch.methods.js
  • src/domain/warp/query.methods.js
  • src/domain/warp/subscribe.methods.js
  • test/integration/api/edge-cases.test.js
  • test/integration/api/fork.test.js
  • test/integration/api/lifecycle.test.js
  • test/runtime/deno/lifecycle.test.ts
  • test/type-check/consumer.ts
  • test/unit/cli/schemas.test.js
  • test/unit/domain/WarpGraph.edgeProps.test.js
  • test/unit/domain/WarpGraph.invalidation.test.js
  • test/unit/domain/WarpGraph.lazyMaterialize.test.js
  • test/unit/domain/WarpGraph.noCoordination.test.js
  • test/unit/domain/WarpGraph.patchMany.test.js
  • test/unit/domain/WarpGraph.query.test.js
  • test/unit/domain/WarpGraph.subscribe.test.js
  • test/unit/domain/WarpGraph.watch.test.js
  • test/unit/domain/WarpGraph.writerInvalidation.test.js
  • test/unit/domain/services/BisectService.test.js
  • test/unit/domain/services/ObserverView.test.js
  • test/unit/domain/services/TrustPayloadParity.test.js
  • test/unit/domain/utils/CachedValue.test.js
📝 Walkthrough

Walkthrough

Adds v13.0.0: introduces BisectService and CLI bisect, adds graph.patchMany(), stabilizes Observer API (subscribe/watch), and changes getNodeProps() return type from Map to plain Record across code, docs, types, and tests.

Changes

Cohort / File(s) Summary
Release & Metadata
CHANGELOG.md, README.md, ROADMAP.md, jsr.json, package.json
Version bump to v13.0.0 and release notes documenting Bisect, patchMany, Observer API stabilization, and breaking changes.
Bisect CLI & Service
src/domain/services/BisectService.js, bin/cli/commands/bisect.js, bin/cli/commands/registry.js, bin/cli/schemas.js, bin/cli/infrastructure.js
Adds BisectService (binary-search over a writer's patch history) and CLI integration (bisect command, schema, registration, help/options, test-runner).
Batch Patch API
src/domain/warp/patch.methods.js, src/domain/warp/_wiredMethods.d.ts, index.d.ts, test/unit/domain/WarpGraph.patchMany.test.js
New patchMany(...builds) that applies patches sequentially and returns commit SHAs; tests cover ordering, reentrancy, and error behavior.
getNodeProps: Map → Record
src/domain/warp/query.methods.js, src/domain/services/ObserverView.js, src/domain/services/QueryBuilder.js, src/domain/warp/_wiredMethods.d.ts, index.d.ts, contracts/type-surface.m8.json
Breaking change: node property representations moved from Map<string, unknown> to Record<string, unknown>; runtime code, docs, typings, and tests updated for direct property access.
Type Surface & Exports
index.d.ts, index.js, contracts/type-surface.m8.json, bin/cli/types.js, src/domain/WarpGraph.js
Adds BisectService and BisectResult to public surface; refines materialize return type; broadens onError typings for subscribe/watch; adjusts persistence typedefs.
Docs, Examples & Roadmap
docs/GUIDE.md, docs/ROADMAP/COMPLETED.md, examples/scripts/setup.js, examples/scripts/explore.js, README.md
Docs and examples updated to reflect getNodeProps return-type change, roadmap M11 completion, and new features (bisect, patchMany).
CLI Types & Schemas
bin/cli/schemas.js, bin/cli/types.js
Adds bisectSchema (validates full 40-char SHA args) and adjusts CLI type annotations (materialize return type).
Subscribe/Watch Stability & Tests
src/domain/warp/subscribe.methods.js, test/unit/domain/*
JSDoc marks subscribe()/watch() stable; onError types broadened to unknown; unsubscribe/watch edge-case tests added.
Tests — Access Pattern Updates
test/** (many files), test/type-check/consumer.ts
Updated tests to use object property access (e.g., props.name, 'key' in props, Object.keys) instead of Map APIs.
New & Extended Tests
test/unit/domain/services/BisectService.test.js, test/unit/domain/services/TrustPayloadParity.test.js, test/unit/domain/utils/CachedValue.test.js
Comprehensive tests for BisectService, trust payload parity, and cached-value null semantics; many new/expanded unit tests.

Sequence Diagram

sequenceDiagram
    rect rgba(220,240,255,0.5)
    actor CLI as Client/CLI
    end
    rect rgba(200,255,220,0.5)
    participant Bisect as BisectService
    participant Graph as WarpGraph
    participant History as PatchHistory
    participant TestFn as Test Function
    end

    CLI->>Bisect: run({good, bad, writerId, testFn})
    Bisect->>Graph: getWriterPatches(writerId)
    Graph->>History: fetch writer patches
    History-->>Graph: patches[]
    Graph-->>Bisect: patches slice (candidates)

    Bisect->>Bisect: resolve candidates & validate range
    alt single candidate
        Bisect-->>CLI: return found result (firstBadPatch, 0 steps)
    else binary search
        loop until found
            Bisect->>Bisect: pick midpoint SHA
            Bisect->>Graph: materialize(midpoint SHA)
            Graph-->>Bisect: WarpStateV5
            Bisect->>TestFn: testFn(state, midpoint SHA)
            TestFn-->>Bisect: boolean (good/bad)
            Bisect->>Bisect: narrow candidate range
        end
        Bisect-->>CLI: return found result (firstBadPatch, steps)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hop through commits, bisecting the trail,
Patches line up neatly — sequential without fail,
Maps shed their leaves for plain-object cheer,
Subscribe and watch stand steady, stable, clear,
v13 emerges — the burrow claps, hip-hip hooray! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly describes the main change: hardening CLI validation and removing dead code related to the bisect feature (B148).
Description check ✅ Passed The PR description covers all required sections: a clear summary of changes, a detailed test plan with verification steps, and ADR checks as specified in the template.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/b148-code-review-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Mar 3, 2026

Release Preflight

  • package version: 13.0.0
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v13.0.0, release workflow will publish.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
src/domain/services/QueryBuilder.js (1)

310-316: Rename propsMap to propsRecord for consistency with the new model.

The type is already Record<string, unknown>, but variable naming still says Map, which makes the migration harder to follow.

♻️ Suggested rename
- * `@param` {{ id: string, propsMap: Record<string, unknown>, edgesOut: Array<{label: string, neighborId: string}>, edgesIn: Array<{label: string, neighborId: string}> }} params - Node data
+ * `@param` {{ id: string, propsRecord: Record<string, unknown>, edgesOut: Array<{label: string, neighborId: string}>, edgesIn: Array<{label: string, neighborId: string}> }} params - Node data
@@
-function createNodeSnapshot({ id, propsMap, edgesOut, edgesIn }) {
-  const props = buildPropsSnapshot(propsMap);
+function createNodeSnapshot({ id, propsRecord, edgesOut, edgesIn }) {
+  const props = buildPropsSnapshot(propsRecord);
@@
-          const propsMap = await getProps(nodeId);
+          const propsRecord = await getProps(nodeId);
@@
-            snapshot: createNodeSnapshot({ id: nodeId, propsMap, edgesOut, edgesIn }),
+            snapshot: createNodeSnapshot({ id: nodeId, propsRecord, edgesOut, edgesIn }),
@@
-        const propsMap = await getProps(nodeId);
-        const props = buildPropsSnapshot(propsMap);
+        const propsRecord = await getProps(nodeId);
+        const props = buildPropsSnapshot(propsRecord);

Also applies to: 686-692, 750-752

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/services/QueryBuilder.js` around lines 310 - 316, Rename the
parameter and local variable named propsMap to propsRecord for consistency:
update the JSDoc param name and the function signature of createNodeSnapshot({
id, propsMap, edgesOut, edgesIn }) to use propsRecord, change the local const
props = buildPropsSnapshot(propsMap) to buildPropsSnapshot(propsRecord), and
update every internal reference and any callers in this file that pass or
destructure propsMap (including the other functions around the other occurrences
mentioned) to use propsRecord instead; ensure you also rename any related
variable declarations and type annotations so the names are consistent across
createNodeSnapshot and the other similar functions referenced.
test/runtime/deno/lifecycle.test.ts (1)

39-49: Rename the test case title to remove “via Map.”

The assertion now validates object/Record access (props?.k), so keeping “via Map” in the test name is confusing during triage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/runtime/deno/lifecycle.test.ts` around lines 39 - 49, The test title
"lifecycle: node properties via Map" is misleading because the assertion checks
object/Record access (props?.k); update the Deno.test call to a clearer name
such as "lifecycle: node properties" by changing the first argument string in
the Deno.test invocation so it no longer mentions "via Map" (locate the
Deno.test with the same anonymous async function that creates
repo/openGraph/createPatch/addNode/setProperty/materialize/getNodeProps).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/cli/commands/bisect.js`:
- Around line 29-42: The catch in runTestCommand currently swallows all execSync
errors and returns false; change it to only treat a non-zero child exit as a
test failure and rethrow actual command execution errors (like ENOENT/EACCES) so
typos/missing binaries surface; specifically, in runTestCommand's catch block
inspect the caught error (e.g., err.code and err.status): if err.code is
'ENOENT' or 'EACCES' (or err.errno indicates an exec failure) rethrow or throw a
new Error with the original error attached, otherwise (normal non-zero exit
where err.status is a number) return false to mark the test as failing. Ensure
the thrown error preserves the original message for debugging.

In `@docs/ROADMAP/COMPLETED.md`:
- Around line 307-308: The archived ROADMAP entry for the bisect feature still
lists "6 test vectors" but the PR test plan increased the bisect suite to 9
vectors; update the text that mentions BisectService and the `git warp bisect`
CLI to say "9 test vectors" (replace the "6" count), and search the file for any
other occurrences (e.g., the second archived mention) to make the counts
consistent across the document.

In `@ROADMAP.md`:
- Around line 168-170: Update the downstream roadmap text so sequencing matches
the new status of Milestone 11: locate the heading "## Milestone 11 — COMPASS II
✅ COMPLETE (v13.0.0)" and any occurrences of the phrase "M11 follows after M14"
and change them to reflect M11 is already complete (e.g., remove the “follows
after” language or reword to show M11 precedes or is archived after completion);
ensure links referencing M11 (such as the archive to COMPLETED.md) remain
correct and that all mentions of milestone order across the document present a
consistent execution sequence.

In `@src/domain/services/QueryBuilder.js`:
- Around line 267-273: The buildPropsSnapshot function and the _runAggregate
block should use defensive object-key handling: change the empty object
initializations to Object.create(null) (e.g., replace const props = {} with a
null-prototype object) and when iterating source records check ownership via
hasOwnProperty (or Object.prototype.hasOwnProperty.call(...)) before copying
keys; update both buildPropsSnapshot and the _runAggregate logic (referencing
those function names) to follow this pattern to prevent prototype-pollution
paths and match PropertyIndexBuilder/IncrementalIndexUpdater patterns.

In `@src/domain/warp/query.methods.js`:
- Around line 49-50: The function getNodeProps currently returns record || {}
which turns a null (missing node) into an empty object and violates the
documented contract; update the return to preserve null semantics by returning
record directly (or explicitly checking if record === null and returning null)
instead of using the || fallback. Locate the call to
this._propertyReader.getNodeProps in getNodeProps and mirror the null-preserving
behavior used by ObserverView.getNodeProps (lines referencing
ObserverView.getNodeProps) so callers relying on Promise<Record<string,
unknown>|null> receive null for missing nodes.
- Around line 63-69: The property-bag objects are created as plain {} and accept
user-controlled keys (decoded.propKey), allowing prototype pollution via keys
like "__proto__"; update the three initializations to use zero-prototype objects
(Object.create(null)) so they don't inherit Object.prototype: change the props
in getNodeProps (currently "const props = {}" at the start of that loop), the
similar bag in getEdgeProps (the "const props = {}" at that function), and the
property bag used in the neighbor-edges query function (the initialization at
line ~267) to use Object.create(null) instead of {}.

---

Nitpick comments:
In `@src/domain/services/QueryBuilder.js`:
- Around line 310-316: Rename the parameter and local variable named propsMap to
propsRecord for consistency: update the JSDoc param name and the function
signature of createNodeSnapshot({ id, propsMap, edgesOut, edgesIn }) to use
propsRecord, change the local const props = buildPropsSnapshot(propsMap) to
buildPropsSnapshot(propsRecord), and update every internal reference and any
callers in this file that pass or destructure propsMap (including the other
functions around the other occurrences mentioned) to use propsRecord instead;
ensure you also rename any related variable declarations and type annotations so
the names are consistent across createNodeSnapshot and the other similar
functions referenced.

In `@test/runtime/deno/lifecycle.test.ts`:
- Around line 39-49: The test title "lifecycle: node properties via Map" is
misleading because the assertion checks object/Record access (props?.k); update
the Deno.test call to a clearer name such as "lifecycle: node properties" by
changing the first argument string in the Deno.test invocation so it no longer
mentions "via Map" (locate the Deno.test with the same anonymous async function
that creates
repo/openGraph/createPatch/addNode/setProperty/materialize/getNodeProps).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 000b447 and b87b83b.

⛔ Files ignored due to path filters (1)
  • test/unit/domain/__snapshots__/WarpGraph.apiSurface.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (43)
  • CHANGELOG.md
  • README.md
  • ROADMAP.md
  • bin/cli/commands/bisect.js
  • bin/cli/commands/registry.js
  • bin/cli/infrastructure.js
  • bin/cli/schemas.js
  • bin/cli/types.js
  • contracts/type-surface.m8.json
  • docs/GUIDE.md
  • docs/ROADMAP/COMPLETED.md
  • examples/scripts/explore.js
  • examples/scripts/setup.js
  • index.d.ts
  • index.js
  • jsr.json
  • package.json
  • src/domain/WarpGraph.js
  • src/domain/services/BisectService.js
  • src/domain/services/ObserverView.js
  • src/domain/services/QueryBuilder.js
  • src/domain/warp/_wiredMethods.d.ts
  • src/domain/warp/patch.methods.js
  • src/domain/warp/query.methods.js
  • src/domain/warp/subscribe.methods.js
  • test/integration/api/edge-cases.test.js
  • test/integration/api/fork.test.js
  • test/integration/api/lifecycle.test.js
  • test/runtime/deno/lifecycle.test.ts
  • test/type-check/consumer.ts
  • test/unit/domain/WarpGraph.edgeProps.test.js
  • test/unit/domain/WarpGraph.invalidation.test.js
  • test/unit/domain/WarpGraph.lazyMaterialize.test.js
  • test/unit/domain/WarpGraph.noCoordination.test.js
  • test/unit/domain/WarpGraph.patchMany.test.js
  • test/unit/domain/WarpGraph.query.test.js
  • test/unit/domain/WarpGraph.subscribe.test.js
  • test/unit/domain/WarpGraph.watch.test.js
  • test/unit/domain/WarpGraph.writerInvalidation.test.js
  • test/unit/domain/services/BisectService.test.js
  • test/unit/domain/services/ObserverView.test.js
  • test/unit/domain/services/TrustPayloadParity.test.js
  • test/unit/domain/utils/CachedValue.test.js

- Fix ROADMAP.md inventory: move B44/B100/B124/B125/B146 to done (24→29)
- Remove M11 from Early Milestones table in COMPLETED.md (scope mismatch)
- Reorder M11 section in COMPLETED.md to numerical position (after M10)
- Fix stale Deno test name ("via Map" → generic)
- Add candidates-nonempty invariant comment in BisectService
@github-actions
Copy link

github-actions bot commented Mar 4, 2026

Release Preflight

  • package version: 13.0.0
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v13.0.0, release workflow will publish.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
ROADMAP.md (1)

168-170: ⚠️ Potential issue | 🟡 Minor

Reconcile M11 completion with downstream sequencing text.

These lines now correctly mark M11 as complete/archived, but downstream sections still say M11 comes after M14 (Line 287, Line 293, Line 434). Please align those sections so milestone order/status is internally consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ROADMAP.md` around lines 168 - 170, The milestone heading "Milestone 11 —
COMPASS II ✅ COMPLETE (v13.0.0)" has been archived but other downstream
references still place M11 after M14; find every occurrence of "M11", "COMPASS
II", and the textual sequencing that mentions milestone order (including the
paragraphs referencing M11 after M14) and update them to reflect that M11 is
completed/archived and its correct position in the sequence (either move the
mentions to the archived/COMPLETED.md section or rewrite the downstream
sentences so milestone numbering/order is consistent with the heading). Ensure
all references and ordering text match the archived status and the intended
milestone sequence.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ROADMAP.md`:
- Around line 184-187: The roadmap shows B44, B124, and B125 as completed in the
top table but they still appear as active "Near-term" priorities later; update
the standalone priority tiers by removing or marking B44, B124, and B125 as
completed and replace them with open items (e.g., B127 / DENO SMOKE TEST or
other backlog IDs) so the "Near-term" list remains actionable and consistent
with the table; ensure references to the specific IDs (B44, B124, B125, B127 and
the DENO SMOKE TEST entry) are updated or substituted accordingly.

In `@src/domain/services/BisectService.js`:
- Around line 129-130: The midpoint materialization uses a global Lamport
ceiling (this._graph.materialize({ ceiling: candidate.patch.lamport })) which
can include other writers' patches and skew testFn results; change to a
writer-scoped materialization so the state reflects only that writer's history
up to the candidate. Update BisectService to call a writer-scoped primitive
(e.g., this._graph.materializeForWriter or this._graph.materialize with
writer/seq args) using the candidate's author/writer id and the writer-local
sequence/timestamp from candidate.patch (not the global Lamport ceiling), so
testFn(state, candidate.sha) runs against the writer-only prefix for that
candidate.

---

Duplicate comments:
In `@ROADMAP.md`:
- Around line 168-170: The milestone heading "Milestone 11 — COMPASS II ✅
COMPLETE (v13.0.0)" has been archived but other downstream references still
place M11 after M14; find every occurrence of "M11", "COMPASS II", and the
textual sequencing that mentions milestone order (including the paragraphs
referencing M11 after M14) and update them to reflect that M11 is
completed/archived and its correct position in the sequence (either move the
mentions to the archived/COMPLETED.md section or rewrite the downstream
sentences so milestone numbering/order is consistent with the heading). Ensure
all references and ordering text match the archived status and the intended
milestone sequence.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b87b83b and c1d73ad.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • ROADMAP.md
  • docs/ROADMAP/COMPLETED.md
  • src/domain/services/BisectService.js
  • test/runtime/deno/lifecycle.test.ts

Comment on lines +129 to +130
const state = await this._graph.materialize({ ceiling: candidate.patch.lamport });
const isGood = await testFn(state, candidate.sha);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bisect midpoint materialization is writer-ambiguous in multi-writer histories.

Line 129 materializes by Lamport ceiling, which can include patches from other writers. That can skew testFn outcomes and return the wrong first bad patch for the selected writer.

💡 Suggested direction (writer-scoped materialization primitive)
- * `@property` {(opts: {ceiling: number}) => Promise<import('./JoinReducer.js').WarpStateV5>} materialize
+ * `@property` {(opts: {writerId: string, sha: string}) => Promise<import('./JoinReducer.js').WarpStateV5>} materializeAtWriterPatch
- const { index, steps } = await this._binarySearch(candidates, testFn);
+ const { index, steps } = await this._binarySearch(writerId, candidates, testFn);
- async _binarySearch(candidates, testFn) {
+ async _binarySearch(writerId, candidates, testFn) {
   ...
-  const state = await this._graph.materialize({ ceiling: candidate.patch.lamport });
+  const state = await this._graph.materializeAtWriterPatch({ writerId, sha: candidate.sha });
   const isGood = await testFn(state, candidate.sha);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/services/BisectService.js` around lines 129 - 130, The midpoint
materialization uses a global Lamport ceiling (this._graph.materialize({
ceiling: candidate.patch.lamport })) which can include other writers' patches
and skew testFn results; change to a writer-scoped materialization so the state
reflects only that writer's history up to the candidate. Update BisectService to
call a writer-scoped primitive (e.g., this._graph.materializeForWriter or
this._graph.materialize with writer/seq args) using the candidate's
author/writer id and the writer-local sequence/timestamp from candidate.patch
(not the global Lamport ceiling), so testFn(state, candidate.sha) runs against
the writer-only prefix for that candidate.

- Move B100 from non-standard ### Breaking to ### Changed (CHANGELOG)
- Remove done items B44/B124/B125 from priority sequence (ROADMAP)
- Fix stale "6 test vectors" → "9 test vectors" in M11 gate (COMPLETED)
- Replace BisectResult interface with discriminated union type (index.d.ts)
- Add SHA format validation to bisect --good/--bad (schemas.js)
- Remove spurious double-blank in COMPLETED.md
- Add bisect schema unit tests for SHA validation
@github-actions
Copy link

github-actions bot commented Mar 4, 2026

Release Preflight

  • package version: 13.0.0
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v13.0.0, release workflow will publish.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
src/domain/services/BisectService.js (1)

137-138: ⚠️ Potential issue | 🟠 Major

Bisect midpoint materialization is still writer-ambiguous.

Line 137 materializes by global Lamport ceiling, so midpoint states can include other writers’ patches and skew testFn outcomes for the selected writer chain.

Suggested direction
- const { index, steps } = await this._binarySearch(candidates, testFn);
+ const { index, steps } = await this._binarySearch(writerId, candidates, testFn);

- async _binarySearch(candidates, testFn) {
+ async _binarySearch(writerId, candidates, testFn) {
   ...
-  const state = await this._graph.materialize({ ceiling: candidate.patch.lamport });
+  const state = await this._graph.materializeAtWriterPatch({ writerId, sha: candidate.sha });
   const isGood = await testFn(state, candidate.sha);
 /**
  * `@typedef` {Object} BisectGraph
  * `@property` {(writerId: string) => Promise<Array<{patch: {lamport: number}, sha: string}>>} getWriterPatches
- * `@property` {(opts: {ceiling: number}) => Promise<import('./JoinReducer.js').WarpStateV5>} materialize
+ * `@property` {(opts: {writerId: string, sha: string}) => Promise<import('./JoinReducer.js').WarpStateV5>} materializeAtWriterPatch
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/services/BisectService.js` around lines 137 - 138, The midpoint
materialization uses a global Lamport ceiling and can include other writers'
patches, skewing testFn results: change the call in BisectService where you call
this._graph.materialize({ ceiling: candidate.patch.lamport }) so it materializes
up to the candidate's writer-specific ceiling (use the candidate patch's
writer/chain identifier and its lamport as the ceiling for that writer) before
invoking testFn(state, candidate.sha); locate the materialization call and
ensure you pass a writer-scoped ceiling (e.g., ceiling object that includes
writerId/chainId plus lamport or use the graph API method that accepts
per-writer ceilings) so the midpoint state contains only patches from the
selected writer chain.
ROADMAP.md (1)

168-170: ⚠️ Potential issue | 🟡 Minor

Roadmap sequence still contradicts M11 completion status.

Line 168 marks M11 complete, but later execution-order text still says M11 comes after M14 (Line 293, Line 430, Line 434). Please reconcile those sections to keep milestone ordering internally consistent.

Suggested doc patch
-### Milestones: M10 → M12 → M13 → M14 → M11
+### Milestones: M10 → M12 → M13 → M14 (M11 complete, archived)

-5. **M11 COMPASS II** — Developer experience (B2 impl, B3, B11) — after M14
+5. **M11 COMPASS II** — Developer experience (B2 impl, B3, B11) — ✅ DONE (v13.0.0), archived

-Execution: M10 SENTINEL → **M12 SCALPEL** → **M13 SCALPEL II** → **M14 HYGIENE** → M11 COMPASS II. Standalone items fill the gaps.
+Execution: M10 SENTINEL → **M12 SCALPEL** → **M13 SCALPEL II** → **M14 HYGIENE**. M11 COMPASS II is complete and archived. Standalone items fill the gaps.

-M14 HYGIENE is the current priority — test hardening, DRY extraction, and SOLID quick-wins from the HEX_AUDIT. M11 follows after M14.
+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.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ROADMAP.md` around lines 168 - 170, The document declares "Milestone 11 —
COMPASS II ✅ COMPLETE (v13.0.0)" but later execution-order text still places M11
after M14; update the later sections so milestone ordering is consistent: locate
the execution-order paragraphs that mention "M11" coming after "M14" and either
move/relabel those references so M11 appears before M14 or mark them as
referencing historical order (e.g., change phrasing to "previously M11" or
remove the incorrect ordering), and ensure the milestone heading "Milestone 11 —
COMPASS II ✅ COMPLETE (v13.0.0)" remains unchanged; check all occurrences of
"M11" and "M14" in the document and reconcile them to a single, internally
consistent sequence.
🧹 Nitpick comments (1)
CHANGELOG.md (1)

35-35: Consider splitting this mega-bullet for readability.

Line 35 is accurate but very dense. Splitting into 2–4 sub-bullets (CLI hardening, ROADMAP housekeeping, type/schema updates) would make release-note scanning much easier.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` at line 35, Split the single dense CHANGELOG bullet into 2–4
sub-bullets grouped by theme to improve readability: create a CLI hardening
bullet referencing the added `--writer` validation and SHA format validation for
the bisect CLI, a BisectService/type/schema bullet mentioning removal of dead
code in `BisectService` and the replacement of the `BisectResult` interface with
a discriminated union, and a housekeeping bullet for ROADMAP/COMPLETED.md/test
fixes (reconciled ROADMAP counts, moved B100 from Breaking to Changed, fixed M11
placement, updated Deno test name and stale test vector counts). Use short
phrases and keep each sub-bullet one line so the release notes scan easily.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@ROADMAP.md`:
- Around line 168-170: The document declares "Milestone 11 — COMPASS II ✅
COMPLETE (v13.0.0)" but later execution-order text still places M11 after M14;
update the later sections so milestone ordering is consistent: locate the
execution-order paragraphs that mention "M11" coming after "M14" and either
move/relabel those references so M11 appears before M14 or mark them as
referencing historical order (e.g., change phrasing to "previously M11" or
remove the incorrect ordering), and ensure the milestone heading "Milestone 11 —
COMPASS II ✅ COMPLETE (v13.0.0)" remains unchanged; check all occurrences of
"M11" and "M14" in the document and reconcile them to a single, internally
consistent sequence.

In `@src/domain/services/BisectService.js`:
- Around line 137-138: The midpoint materialization uses a global Lamport
ceiling and can include other writers' patches, skewing testFn results: change
the call in BisectService where you call this._graph.materialize({ ceiling:
candidate.patch.lamport }) so it materializes up to the candidate's
writer-specific ceiling (use the candidate patch's writer/chain identifier and
its lamport as the ceiling for that writer) before invoking testFn(state,
candidate.sha); locate the materialization call and ensure you pass a
writer-scoped ceiling (e.g., ceiling object that includes writerId/chainId plus
lamport or use the graph API method that accepts per-writer ceilings) so the
midpoint state contains only patches from the selected writer chain.

---

Nitpick comments:
In `@CHANGELOG.md`:
- Line 35: Split the single dense CHANGELOG bullet into 2–4 sub-bullets grouped
by theme to improve readability: create a CLI hardening bullet referencing the
added `--writer` validation and SHA format validation for the bisect CLI, a
BisectService/type/schema bullet mentioning removal of dead code in
`BisectService` and the replacement of the `BisectResult` interface with a
discriminated union, and a housekeeping bullet for ROADMAP/COMPLETED.md/test
fixes (reconciled ROADMAP counts, moved B100 from Breaking to Changed, fixed M11
placement, updated Deno test name and stale test vector counts). Use short
phrases and keep each sub-bullet one line so the release notes scan easily.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d146b406-d31e-4f97-9a23-fd2bf717a04f

📥 Commits

Reviewing files that changed from the base of the PR and between c1d73ad and 367efdf.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • ROADMAP.md
  • bin/cli/schemas.js
  • contracts/type-surface.m8.json
  • docs/ROADMAP/COMPLETED.md
  • index.d.ts
  • src/domain/services/BisectService.js
  • test/unit/cli/schemas.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • bin/cli/schemas.js

- Rethrow ENOENT/EACCES from bisect test command runner instead of
  swallowing all execSync errors (bin/cli/commands/bisect.js)
- Use Object.create(null) for property bags in getNodeProps, getEdgeProps,
  getEdges, and buildPropsSnapshot to prevent prototype pollution
  (src/domain/warp/query.methods.js, src/domain/services/QueryBuilder.js)
- Fix indexed-path null masking in getNodeProps — fall through to linear
  scan when reader returns null instead of returning empty object
- Rename propsMap → propsRecord in QueryBuilder for consistency with
  Record<string, unknown> type
- Reconcile M11 completion status with downstream sequencing text in
  ROADMAP.md (3 locations still said "after M14")
- Split dense B148 CHANGELOG bullet into themed sub-bullets
@flyingrobots
Copy link
Member Author

Review Feedback — Round 3 Response

Addressed in 1b3cfcc

# File Issue Fix
1 bin/cli/commands/bisect.js runTestCommand swallows ENOENT/EACCES Rethrow spawn failures; only return false for non-zero exit status
2 src/domain/warp/query.methods.js {} property bags vulnerable to prototype pollution Object.create(null) in getNodeProps, getEdgeProps, getEdges
3 src/domain/warp/query.methods.js record || {} masks null in indexed fast path Fall through to linear scan when reader returns null
4 src/domain/services/QueryBuilder.js buildPropsSnapshot uses {} Object.create(null)
5 src/domain/services/QueryBuilder.js propsMap naming stale after Map→Record migration Renamed to propsRecord (4 sites)
6 ROADMAP.md M11 "after M14" contradicts completed status Reconciled 4 locations: heading, list item, execution paragraph, final command
7 CHANGELOG.md Dense B148 mega-bullet Split into 4 themed sub-bullets

Previously addressed

  • docs/ROADMAP/COMPLETED.md "6 test vectors" → "9 test vectors" ✅ 367efdf
  • ROADMAP.md B44/B124/B125 in priority tiers ✅ 367efdf
  • test/runtime/deno/lifecycle.test.ts "via Map" test rename ✅ d7cf956

By design — BisectService global Lamport ceiling (flagged Major × 2 rounds)

The materialize({ ceiling }) call is intentionally global, not writer-scoped. Bisect answers: "Which patch, applied in the merged multi-writer state, first introduced the issue?" — this is the state users actually observe.

Writer-isolated materialization would:

  1. Produce states that never existed in the real system
  2. Miss bugs caused by cross-writer interactions (the most common class of CRDT merge issues)
  3. Contradict Paper II's tick semantics, where ticks materialize the full state at a causal frontier

If a future use case needs writer-isolated bisect, that would be a separate --isolated mode, not a change to the default. Logging this to cool_ideas.md for consideration.

Verification

  • ESLint: ✅ clean
  • tsc --noEmit: ✅ 0 errors
  • IRONCLAD ratchet: ✅ 0/0 wildcards
  • Unit tests: ✅ 4619 passed (252 files)

@coderabbitai — ready for another look 🥕

@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me verify the fixes applied in 1b3cfcc before giving you the final sign-off.


🏁 Script executed:

#!/bin/bash

echo "=== bin/cli/commands/bisect.js (runTestCommand fix) ==="
cat bin/cli/commands/bisect.js

echo ""
echo "=== src/domain/warp/query.methods.js (Object.create(null) + null fix) ==="
cat src/domain/warp/query.methods.js

echo ""
echo "=== src/domain/services/QueryBuilder.js (Object.create(null) + rename) ==="
cat src/domain/services/QueryBuilder.js

echo ""
echo "=== ROADMAP.md M11 references ==="
grep -n "M11\|after M14\|COMPASS" ROADMAP.md | head -40

echo ""
echo "=== CHANGELOG.md B148 entry ==="
grep -n -A 20 "B148" CHANGELOG.md | head -50

Length of output: 51928


@flyingrobots — appreciate the detailed response and the design rationale on global Lamport ceiling (that reasoning is airtight — multi-writer causal frontier semantics make this the only correct default). That one's closed on my end.

However, after checking out the current PR branch, the 7 fixes from 1b3cfcc are not reflected in the working tree — either the commit wasn't pushed, or the branch pointer hasn't updated. Here's what I found for each item:


❌ Item #1runTestCommand still swallows ENOENT/EACCES

// bin/cli/commands/bisect.js — current state
} catch {
  return false;  // ← bare catch, ENOENT/EACCES silently return false
}

Expected: inspect err.code and rethrow for spawn failures.


❌ Items #2 & #4Object.create(null) not applied

// query.methods.js — getNodeProps linear scan, getEdgeProps, getEdges
const props = {};   // ← still plain object
bag = {};           // ← still plain object

// QueryBuilder.js — buildPropsSnapshot
const props = {};   // ← still plain object

❌ Item #3record || {} still in indexed fast path

// query.methods.js — getNodeProps
const record = await this._propertyReader.getNodeProps(nodeId);
return record || {};  // ← null still masked, no fall-through to linear scan

⚠️ Item #5propsMap rename is partial (2 of 4 sites done)

// QueryBuilder.js — still uses propsMap in 2 places:
function createNodeSnapshot({ id, propsMap, edgesOut, edgesIn }) { ... }  // ← line ~330
const propsMap = await getProps(nodeId);  // ← in run()
// _runAggregate uses propsRecord correctly ✓

❌ Item #6 — ROADMAP.md M11 contradictions still present at 4 locations

Line 287: ### Milestones: M10 → M12 → M13 → M14 → M11
Line 293: **M11 COMPASS II** — ... — after M14
Line 430: Execution: ... → **M14 HYGIENE** → M11 COMPASS II.
Line 434: M11 follows after M14.

(M11 is marked ✅ COMPLETE (v13.0.0) at line 168, so all four remain contradictory.)


❌ Item #7 — CHANGELOG.md B148 is still a single mega-bullet (Line 35)


Could you double-check whether 1b3cfcc was pushed to fix/b148-code-review-fixes? git log --oneline -5 on that branch should confirm. Happy to re-review immediately once the commit is visible on the remote.

@github-actions
Copy link

github-actions bot commented Mar 4, 2026

Release Preflight

  • package version: 13.0.0
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v13.0.0, release workflow will publish.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
ROADMAP.md (1)

287-294: ⚠️ Potential issue | 🟡 Minor

Milestone order is still inconsistent within the same section.

Line 287/430 place M11 before M14, but Lines 292-294 enumerate M14 as #4 and M11 as #5. Please make the numbered list match the declared sequence to avoid roadmap ambiguity.

Also applies to: 430-434

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ROADMAP.md` around lines 287 - 294, The milestone sequence in the header
"Milestones: M10 → M12 → M13 → M11 → M14" does not match the numbered list
order; update the numbered list under that header so its entries follow the
declared sequence (M10, M12, M13, M11, M14) — reorder the bullets so M11 appears
before M14 in the inline list to match the header (or alternatively change the
header to match the list), and make the same correction for the duplicate block
referenced (the other occurrence around the 430–434 section) so both header and
enumerations are consistent.
🧹 Nitpick comments (2)
ROADMAP.md (1)

328-328: Clarify what “(29 done)” refers to in total tracking.

Total tracked | 123 (29 done) reads like global done-count, but 29 appears to be the standalone-done bucket. Consider labeling it explicitly (e.g., 123 total; 29 standalone done) for readability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ROADMAP.md` at line 328, The table row showing "Total tracked | 123 (29
done)" is ambiguous; update the cell text for the "Total tracked" row (the table
line containing the string "Total tracked") to explicitly label which counts are
overall vs standalone—e.g., change the cell to "123 total; 29 standalone done"
or "123 total (29 standalone done)" so readers immediately know that 29 refers
to standalone-done rather than the global done count.
src/domain/services/QueryBuilder.js (1)

675-677: Consider using Object.create(null) for the fallback to maintain defensive consistency.

The fallback || {} creates a standard object with a prototype chain, which is inconsistent with the Object.create(null) pattern now used in buildPropsSnapshot and getNodeProps (per PR comments). If getNodeProps returns null, this memoized fallback flows to _runAggregate where user-specified property paths are accessed.

Suggested change
-      const propsRecord = (await this._graph.getNodeProps(nodeId)) || {};
+      const propsRecord = (await this._graph.getNodeProps(nodeId)) || Object.create(null);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/services/QueryBuilder.js` around lines 675 - 677, The memo
fallback uses {} which creates a prototypeful object; change the fallback in the
props retrieval to use Object.create(null) so the memoized propsRecord is
prototype-less and consistent with buildPropsSnapshot/getNodeProps
patterns—update the expression where propsRecord is assigned from await
this._graph.getNodeProps(nodeId) (and where propsMemo.set(nodeId, propsRecord)
follows) so that when getNodeProps returns null the code assigns
Object.create(null) instead, preventing prototype inheritance leaks into
_runAggregate and any user-specified property path access.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@ROADMAP.md`:
- Around line 287-294: The milestone sequence in the header "Milestones: M10 →
M12 → M13 → M11 → M14" does not match the numbered list order; update the
numbered list under that header so its entries follow the declared sequence
(M10, M12, M13, M11, M14) — reorder the bullets so M11 appears before M14 in the
inline list to match the header (or alternatively change the header to match the
list), and make the same correction for the duplicate block referenced (the
other occurrence around the 430–434 section) so both header and enumerations are
consistent.

---

Nitpick comments:
In `@ROADMAP.md`:
- Line 328: The table row showing "Total tracked | 123 (29 done)" is ambiguous;
update the cell text for the "Total tracked" row (the table line containing the
string "Total tracked") to explicitly label which counts are overall vs
standalone—e.g., change the cell to "123 total; 29 standalone done" or "123
total (29 standalone done)" so readers immediately know that 29 refers to
standalone-done rather than the global done count.

In `@src/domain/services/QueryBuilder.js`:
- Around line 675-677: The memo fallback uses {} which creates a prototypeful
object; change the fallback in the props retrieval to use Object.create(null) so
the memoized propsRecord is prototype-less and consistent with
buildPropsSnapshot/getNodeProps patterns—update the expression where propsRecord
is assigned from await this._graph.getNodeProps(nodeId) (and where
propsMemo.set(nodeId, propsRecord) follows) so that when getNodeProps returns
null the code assigns Object.create(null) instead, preventing prototype
inheritance leaks into _runAggregate and any user-specified property path
access.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd1cb266-0c45-4ec8-8bf5-3afa3dd9ce13

📥 Commits

Reviewing files that changed from the base of the PR and between 367efdf and 1b3cfcc.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • ROADMAP.md
  • bin/cli/commands/bisect.js
  • src/domain/services/QueryBuilder.js
  • src/domain/warp/query.methods.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/domain/warp/query.methods.js
  • CHANGELOG.md

- Reorder milestone list so M11 (#4) precedes M14 (#5), matching the
  header sequence M10 → M12 → M13 → M11 → M14
- Clarify inventory total: "123 (29 done)" → "123 total; 29 standalone done"
- Use Object.create(null) for memoized props fallback in QueryBuilder
  to maintain null-prototype consistency
@github-actions
Copy link

github-actions bot commented Mar 4, 2026

Release Preflight

  • package version: 13.0.0
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v13.0.0, release workflow will publish.

@flyingrobots
Copy link
Member Author

All 3 findings from round 4 addressed in `65f0f56`:

  1. M11/M14 list order — reordered to M11 (docs: Remix documentation with VitePress #4) before M14 (v3.0.0 #5), matching header
  2. Inventory label — "123 (29 done)" → "123 total; 29 standalone done"
  3. QueryBuilder memo fallback|| {}|| Object.create(null)

@coderabbitai review please 🥕

@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

🧩 Analysis chain

Let me verify the full state of the branch, including both round 3 and round 4 fixes.


🏁 Script executed:

#!/bin/bash

echo "=== git log --oneline -6 ==="
git log --oneline -6

echo ""
echo "=== bin/cli/commands/bisect.js (runTestCommand — round 3 `#1`) ==="
cat bin/cli/commands/bisect.js

echo ""
echo "=== src/domain/warp/query.methods.js (Object.create(null) + null fix — round 3 `#2/`#3) ==="
cat src/domain/warp/query.methods.js

echo ""
echo "=== src/domain/services/QueryBuilder.js (Object.create(null) + propsMap rename + memo fallback — round 3 `#4/`#5, round 4 `#3`) ==="
cat src/domain/services/QueryBuilder.js

echo ""
echo "=== ROADMAP.md — M11/M14 list order + inventory label (round 4 `#1/`#2) + M11 contradictions (round 3 `#6`) ==="
grep -n "M11\|M14\|after M14\|COMPASS\|inventory\|standalone\|123" ROADMAP.md | head -60

echo ""
echo "=== CHANGELOG.md — B148 mega-bullet split (round 3 `#7`) ==="
grep -n -A 30 "B148" CHANGELOG.md | head -60

Length of output: 56024


@flyingrobots — all 10 items verified clean across both commits:

Round 3 (1b3cfcc) — all 7 confirmed ✅

# Item Verified state
1 runTestCommand ENOENT/EACCES Checks typeof asRecord.status === 'number'; rethrows anything else
2 Object.create(null) in query.methods.js Applied in getNodeProps (linear scan), getEdgeProps, and getEdges bag
3 Null masking in indexed fast path if (record !== null) with explicit fall-through comment
4 buildPropsSnapshot in QueryBuilder.js Object.create(null)
5 propsMappropsRecord rename All 4 sites updated (createNodeSnapshot param, run() variable, _runAggregate call, JSDoc)
6 ROADMAP.md M11 contradictions All 4 locations reconciled: header sequence, numbered list, execution paragraph, closing line
7 CHANGELOG.md B148 4 themed sub-bullets (CLI hardening, BisectService cleanup, prototype-pollution, docs housekeeping)

Round 4 (65f0f56) — all 3 confirmed ✅

# Item Verified state
1 M11/M14 list order M10 → M12 → M13 → M11 → M14 with M11 as item 4, M14 as item 5
2 Inventory label "123 total; 29 standalone done"
3 QueryBuilder memo fallback `

Triggering the full review now.

✅ Actions performed

Full review triggered.

@flyingrobots flyingrobots merged commit ae76d1b into main Mar 4, 2026
8 checks passed
@flyingrobots flyingrobots deleted the fix/b148-code-review-fixes branch March 4, 2026 02:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant