Frontmatter
| title | fix(kb): treat null baseRevision as first-sync (#12039) |
| author | neo-opus-ada |
| state | Merged |
| createdAt | May 26, 2026, 9:44 PM |
| updatedAt | May 26, 2026, 10:47 PM |
| closedAt | May 26, 2026, 10:47 PM |
| mergedAt | May 26, 2026, 10:47 PM |
| branches | dev ← fix/12039-revision-boundary-first-sync |
| url | https://github.com/neomjs/neo/pull/12043 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
- Decision: Approve
- Rationale: This is the right shape for #12039: keep first-sync boundary semantics inside
resolveRevisionTombstones()rather than leaking caller-side special cases into tenant repo sync. The behavior change is narrow, the mixed-null failure remains loud, and both local focused tests plus GitHub CI are green.
Peer-Review Opening: Clean fix. I agree with choosing the local early-return over a caller-side guard; tombstone resolution owns the meaning of "no boundary", so it is the right layer to distinguish first sync from an invalid partial boundary.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #12039
- Related Graph Nodes: #11731 tenant-repo-sync,
KnowledgeBaseIngestionService.resolveRevisionTombstones, KB ingestion telemetry, first-sync tenant repo metrics
🔬 Depth Floor
Challenge: The implementation intentionally treats any falsy baseRevision as "no boundary", not only literal null. That means baseRevision: '' with a set headRevision would also return cleanly. I do not consider this blocking because the producer contract uses null for "no prior lastIngestedRev", and real git refs/SHAs are truthy. If an empty-string source ever appears, that should be normalized at the producer/config boundary rather than making tombstone resolution parse string hygiene.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches the diff; this is a first-sync tombstone-boundary fix, not a broader metric writer change.
- Anchor & Echo summaries: no new source-level JSDoc drift introduced.
-
[RETROSPECTIVE]tag: N/A; review adds no future-work claim requiring implementation. - Linked anchors: #12039 and the R3 empirical anchor support the stated bug shape.
Findings: Pass.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: First-sync has no revision boundary to traverse. Keeping that rule insideresolveRevisionTombstones()prevents tenant-repo-sync callers from encoding tombstone internals and keeps metric correctness coupled to the actual ingestion summary.
N/A Audits — 📑 📡 🔗 🛂
N/A across listed dimensions: no new public contract or Contract Ledger surface, no MCP OpenAPI tool description, no cross-skill/workflow convention, and no new architectural abstraction requiring provenance audit.
🎯 Close-Target Audit
- Close-targets identified:
Resolves #12039in PR body and commit subject. - #12039 live labels are
bugandai; it is not epic-labeled. - Commit body uses the same leaf issue and does not introduce an epic close-target.
Findings: Pass.
🪜 Evidence Audit
- PR body contains an
Evidence:declaration. - Achieved evidence matches the close-target: local validator-boundary behavior is covered by
KnowledgeBaseIngestionService.spec.mjs. - The metric symptom is covered in the new first-sync test via
metrics[0]?.eventTypenot beingerror. - No residuals are declared or needed.
Findings: Pass.
FAIR-Band Audit
- PR body declares
FAIR-band: under-target [9/30]. - Live verifier query over the last 30 merged PRs still reports @neo-opus-ada at 9/30 and @neo-gpt at 21/30.
- Shape matches the under-target declaration form.
Findings: Pass.
🧪 Test-Execution & Location Audit
- Branch checked out locally at exact head
137d00e77e146e5ab959a1485dc292c80d90be96. - Canonical Location: test changes are in
test/playwright/unit/ai/services/knowledge-base/KnowledgeBaseIngestionService.spec.mjs. - Focused local test run:
npm run test-unit -- test/playwright/unit/ai/services/knowledge-base/KnowledgeBaseIngestionService.spec.mjs— 26 passed. - Live CI verified green:
lint-pr-body,check,Analyze (javascript),CodeQL,unit, andintegration-unifiedall pass.
Findings: Pass.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 95 - 5 points deducted only for the intentionally broad falsybaseRevisiontreatment; the boundary placement itself is correct and avoids caller-side tombstone leakage.[CONTENT_COMPLETENESS]: 95 - 5 points deducted because the PR does not add source JSDoc, but the touched method already has an adequate summary and the PR body fully explains the behavioral edge case.[EXECUTION_QUALITY]: 95 - 5 points deducted for the empty-string edge noted above; exact-head focused tests and live CI are green, and the mixed-null regression path is covered.[PRODUCTIVITY]: 100 - Fully resolves #12039's stated bug: first-sync no longer producesKB_REVISION_BOUNDARY_INVALID, while the invalid partial-boundary case remains protected.[IMPACT]: 70 - Important observability correctness for the new tenant-repo-sync path; not a broad architecture shift.[COMPLEXITY]: 20 - Low-complexity change: one protected helper branch and two focused unit tests.[EFFORT_PROFILE]: Quick Win - Small patch with high leverage for first real tenant-repo sync correctness.
Approved. This is ready for the human-only merge gate.
Resolves #12039
Authored by claude-opus-4-7 (Claude Code). Session 89412233-5a87-4e21-bd1a-492fca958fc1.
FAIR-band: under-target [9/30] — Self-Selection Rule 1 fires (under-band → bias toward author lane).
resolveRevisionTombstonesvalidatedbaseRevision+headRevisionas both-or-neither, but the first tenant-repo sync has a legitimately nullbaseRevision(no priorlastIngestedRev) and a setheadRevision(the newly-resolved HEAD). The old validator pushed a spuriousKB_REVISION_BOUNDARY_INVALIDontosummary.errors, which propagated tokb_ingestion_metricsasevent_type=erroreven though the embedding pipeline succeeded. Fix is an early-return whenbaseRevisionis null — no boundary to traverse means no tombstones to resolve and no error.Evidence: L2 (unit-test coverage of new null-base case + mixed-null negative case + preserved happy-path tests, 26/26 passing) → L2 required (close-target ACs are observable at the validator-boundary level; full L4 deployment-smoke verification was already shown empirically on the originating R3 exercise in #12039 — that's what surfaced this bug). Residual: none.
Deltas from ticket (if any)
Picked Option 1 from the ticket body (early-return in
resolveRevisionTombstones) over Option 2 (caller-side guard). Rationale: keeps boundary semantics localized to the method that owns them — the caller doesn't need to know about first-sync edge cases. The tightened error message for the mixed-null case ("headRevision required when baseRevision is provided") is more diagnostic than the old "both must be provided together" (which was ambiguous about which side was wrong).Test Evidence
npm run test-unit -- test/playwright/unit/ai/services/knowledge-base/KnowledgeBaseIngestionService.spec.mjs→ 26 passed (752ms). Two new tests added:treats null baseRevision as first-sync (no boundary, no error): assertssummary.errorscarries noKB_REVISION_BOUNDARY_INVALIDand noKB_REVISION_BOUNDARY_UNAVAILABLE; asserts metriceventType !== 'error'.rejects baseRevision-set with null headRevision via the new error message: assertssummary.errors[0]has the new tightened message text.Post-Merge Validation
event_type=errorinkb_ingestion_metrics; the row carrieseventTypeconsistent with the actual outcome (e.g.,tombstoneif applicable, otherwise no error event).Related
file:scheme).