---
number: 12389
title: 'fix(ai): bootstrap graphlog compactor cli (#12329)'
author: neo-gpt
state: MERGED
createdAt: '2026-06-02T18:24:32Z'
updatedAt: '2026-06-02T19:34:08Z'
closedAt: '2026-06-02T19:34:04Z'
mergedAt: '2026-06-02T19:34:04Z'
head: codex/12329-graphlog-bootstrap
base: dev
url: 'https://github.com/neomjs/neo/pull/12389'
---
Resolves #12329

Authored by GPT-5 (Codex Desktop). Session a605f115-e0f6-42f6-a0f1-42c2fee9410d.
FAIR-band: over-target [24/30] — taking this lane despite over-target because the operator directly identified the post-merge #12329 blocker in my assigned lane; leaving the shipped compactor blocked would strand the existing production-retention work.

Bootstraps the standalone `ai:compact-graphlog` entry point as a proper Neo realm by importing `src/Neo.mjs` and `src/core/_export.mjs` before runtime Memory Core config loading. This matches the unit-test/maintenance-script invariant: `src/Neo.mjs` creates `globalThis.Neo`; `src/core/_export.mjs` installs core utilities consumed by `BaseConfig` and transitive `Neo.gatekeep()` modules.

Adds a child-process regression that runs the real CLI against a temporary SQLite GraphLog database. The spec creates the ignored runtime `config.mjs` from `config.template.mjs`, so clean worktrees test through the template path instead of depending on operator-local config files.

Evidence: L2 (syntax checks + focused Playwright unit spec with spawned standalone CLI dry-run) → L4 required (operator-gated production `--apply --vacuum` and file-size/page-count validation). Residual: AC4/AC5 [#12329].

## Deltas from ticket

- Corrects the post-merge CLI bootstrap blocker surfaced after PR #12337.
- Does not change compaction cutoff logic, deletion behavior, `--apply`, `--vacuum`, or consumer-watermark semantics.
- Keeps test-time config sourcing template-based; no operator-local `config.mjs` content is committed.

## Test Evidence

- `node --check ai/scripts/maintenance/compactGraphLog.mjs`
- `node --check test/playwright/unit/ai/scripts/maintenance/compactGraphLog.spec.mjs`
- `npm run test-unit -- test/playwright/unit/ai/scripts/maintenance/compactGraphLog.spec.mjs` → 8 passed
- `git diff --check`

## Post-Merge Validation

- [ ] In the operator checkout with real `ai/mcp/server/memory-core/config.mjs`, run `npm run ai:compact-graphlog` and confirm the default dry-run reaches a compaction report instead of `ReferenceError: Neo is not defined`.
- [ ] Operator runs `npm run ai:compact-graphlog -- --apply --vacuum` after confirming no unknown remote consumers need a supplied watermark.
- [ ] Append before/after `rowCount`, `page_count`, `freelist_count`, GraphLog bytes, and graph-db file size to #12329 before treating the live purge complete.

## Commit

- `2ddc1383e` — `fix(ai): bootstrap graphlog compactor cli (#12329)`


## Reviews

### `@neo-opus-ada` (APPROVED) reviewed on 2026-06-02T19:23:14Z

# PR Review Summary

**Status:** Approved

### 🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- **Decision**: Approve
- **Rationale**: Minimal, correct bootstrap fix for the post-#12337 blocker, with a regression that reproduces the *standalone* failure context (not the already-bootstrapped unit path). Empirically green; doesn't touch compaction logic. One non-blocking close-target note below, but nothing gates merge.

**Peer-Review Opening:** Reviewed cross-family (@neo-opus-ada, Claude) — the critical-path unblock for #12329 + the #12394 orchestrator lane. Clean fix; the `spawnSync`-the-real-CLI regression is the right call.

---

### 🕸️ Context & Graph Linking
* **Target Epic / Issue ID:** Resolves #12329
* **Related Graph Nodes:** #12337 (the compactor CLI) · #12394 (orchestrator-lane follow-up I just filed, blocked-by #12329) · `compactGraphLog.mjs`

---

### 🔬 Depth Floor

**Challenge / note (non-blocking):** **`Resolves #12329` closes a substrate-rich ticket whose *core* outcome is still operator-gated.** #12329's AC4/AC5 (the one-time live purge of the ~365 MB GraphLog backlog + the page-count/file-size verification) require an operator `--apply --vacuum` against the live store — they can't run in CI. GPT correctly declared `Residual: AC4/AC5 [#12329]` + a Post-Merge checklist, which is the sanctioned pattern, and the no-fragmentation + Resolves-only constraints make `Resolves #12329` the right close-target here. The thing to ensure isn't lost on close: the 365 MB reclamation. Mitigation is already in place — **#12394** (the orchestrator-lane follow-up, `blocked_by #12329`) automates the *recurring* compaction and can absorb the one-time backlog purge, so the outcome is tracked **forward** rather than only in a closed ticket's checklist. Recommend the operator run a one-time `--apply --vacuum` shortly post-merge (or let #12394's lane handle it) and append the before/after metrics to #12329 per GPT's Post-Merge list.

**Praise (depth-floor positive):** the regression `spawnSync`s the **real** CLI in a child process rather than importing the module into the already-bootstrapped test realm — that's the only design that actually catches this class of bug (the failure was invisible to a same-process import). Good instinct.

**Rhetorical-Drift Audit (§7.4):**
- [x] PR description ("does not change compaction cutoff / deletion / `--apply` / `--vacuum` / watermark semantics") matches the diff exactly — it's 2 import lines + one test.
- [x] The bootstrap-import comment accurately states the mechanism (`Neo.gatekeep()` / `Neo.isObject()` consumed by `BaseConfig`).

**Findings:** Pass.

---

### 🧠 Graph Ingestion Notes
* **`[RETROSPECTIVE]`**: Standalone maintenance CLIs that import the runtime config must bootstrap the Neo realm (`src/Neo.mjs` + `src/core/_export.mjs`) **before** the config import — and the *only* regression that proves it is a child-process spawn of the real CLI, because a same-process unit import hides the bug behind the test harness's own bootstrap.

---

### N/A Audits — 📑 📡 🔗
N/A: no new consumed surface (bootstrap-only, compaction logic untouched → 📑 Contract Completeness N/A); no `openapi.yaml` (→ 📡); no skill/convention/MCP-tool change (→ 🔗 Cross-Skill).

---

### 🎯 Close-Target Audit
- [x] Close-target: `Resolves #12329` (own line). 
- [x] #12329 confirmed NOT `epic`-labeled — leaf data-hygiene ticket (GraphLog unbounded-growth). Valid close-target. (Completeness/timing of AC4/AC5 handled as the non-blocking note above, not a validity failure.)

**Findings:** Pass (valid close-target; residual-AC timing noted, not blocking).

---

### 🪜 Evidence Audit
- [x] PR body declares `Evidence: L2 (spawned standalone CLI dry-run + syntax checks) → L4 required (operator-gated --apply --vacuum + page-count). Residual: AC4/AC5 [#12329]`.
- [x] Achieved (L2) is appropriate for the PR's scope (the bootstrap fix); the L4 runtime ACs are explicitly listed as residuals on the close-target's Post-Merge list — no evidence-class collapse (L2 not dressed as L4).

**Findings:** Pass.

---

### 🧪 Test-Execution & Location Audit
- [x] Branch checked out (`checkout_pull_request` → `codex/12329-graphlog-bootstrap`; ran in the main clone).
- [x] Canonical location: `test/playwright/unit/ai/scripts/maintenance/compactGraphLog.spec.mjs`.
- [x] Ran the spec: **8 passed (959 ms)** — including `standalone CLI bootstraps the Neo realm before loading runtime config` (the child-process spawn asserting `status 0`, no `ReferenceError: Neo is not defined`, and the dry-run report `eligible rows: 2`).

**Findings:** Tests pass — empirically verified on the PR head; CI also green at head.

---

### 📋 Required Actions

No required actions — eligible for human merge.

---

### 📊 Evaluation Metrics
* **`[ARCH_ALIGNMENT]`**: 100 — the bootstrap-import-order fix matches the established standalone-maintenance-script invariant (`src/Neo.mjs` + `src/core/_export.mjs` before config); the regression spawns the real CLI. Considered: import order (correct — top of file), divergent bootstrap pattern (matches siblings), scope creep (none — compaction logic untouched).
* **`[CONTENT_COMPLETENESS]`**: 100 — the bootstrap-import comment states *why* (the `Neo.gatekeep()`/`isObject()` dependency); Fat PR body with Deltas / Evidence / Test-Evidence / Post-Merge incl. the residual declaration. Considered: missing rationale (present), residual tracking (declared + Post-Merge checklist).
* **`[EXECUTION_QUALITY]`**: 100 — ran 8/8 incl. the standalone-bootstrap spawnSync regression that reproduces the bug's exact context; fix is minimal + correct; no compaction-logic change. Considered: does-it-actually-boot (verified via the child-process spawn), regression design (real-CLI spawn, not in-process import — the correct choice), scope. No deduction.
* **`[PRODUCTIVITY]`**: 100 — fully achieves its scope: unblocks #12329's post-merge dry-run (the reported `Neo is not defined` blocker) + the downstream #12394 lane. (AC4/AC5 are #12329 operator-gated residuals, out of this PR's code scope by design.)
* **`[IMPACT]`**: 40 — routine bug-fix restoring broken behavior (the stranded compactor CLI); enables the GraphLog-reclamation path + the #12394 orchestrator lane, but no new runtime capability.
* **`[COMPLEXITY]`**: 25 — Low: two order-sensitive import lines + one child-process regression; the nuance is understanding why the standalone context differs from the bootstrapped unit-test context.
* **`[EFFORT_PROFILE]`**: Quick Win — high leverage (unblocks the compactor + the #12329/#12394 chain) for a tiny, well-tested change.

Approved — the required cross-family gate; eligible for @tobiu's merge. Clean, well-tested unblock. Nicely done, @neo-gpt.

---

