LearnNewsExamplesServices
Frontmatter
titlefix(orchestrator): unblock tenant-repo CLI sync path (#12036)
authorneo-opus-ada
stateMerged
createdAtMay 26, 2026, 9:11 PM
updatedAtMay 26, 2026, 9:39 PM
closedAtMay 26, 2026, 9:39 PM
mergedAtMay 26, 2026, 9:39 PM
branchesdevfix/12036-cloud-tenant-sync-substrate
urlhttps://github.com/neomjs/neo/pull/12037
Merged
neo-opus-ada
neo-opus-ada commented on May 26, 2026, 9:11 PM

Refs #12036

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).

Fixes two of the three Day-0 substrate gaps captured in #12036 — the two mechanical fixes that unblock the operator-bulk CLI path node ai/scripts/maintenance/syncTenantRepos.mjs --repo-slug <slug> end-to-end. Bug C (NEO_TENANT_REPO_MIRROR_ROOT env var dead config) is deferred to a follow-up PR because it needs a design decision on global-default vs per-repo-override semantics before the mechanical fix.

Evidence: L4 (first cloud tenant-ingestion smoke — orchestrator-CLI sync over OAuth-gated stack ingested 5 chunks into neo-knowledge-base chroma collection, all correctly tagged tenantId / repoSlug / parserId / rootKind) → L4 required (AC6 first-time clean docker-compose path). Residual: Bug C ACs [#12036] deferred.

Deltas from ticket (if any)

  • Ticket #12036 captures three Day-0 substrate gaps; this PR addresses Bug A + Bug B. Bug C deferred — needs design call on whether NEO_TENANT_REPO_MIRROR_ROOT becomes a global default (with per-repo override semantics) or whether the canonical compose's env-var should simply be removed as a misleading dead config. Will surface in a separate PR after design review.
  • Two additional friction signals surfaced during R3 verification (KB_REVISION_BOUNDARY_INVALID first-sync edge case + missing branchRef in tenantRepos[] schema) will be filed as separate tickets — they're orthogonal to the CLI-unblock scope.

Bug A — Runtime image missing git

ai/deploy/Dockerfile runtime stage installed only libstdc++. node:24-alpine doesn't ship with git, but GitMirror.mjs shells out to git clone / git fetch. Failure: [Backup] failed to capture gitSha: spawn git ENOENT from orchestrator stderr; outer wrapper KB_TENANT_REPO_SYNC_SYNC_FAILED (GitMirror clone failed). Fix adds git to the runtime-stage apk install (~23 MiB image-size delta).

Bug B — resolveIngestionService wrong export name

TenantRepoSyncService.resolveIngestionService looked up services.KB_KnowledgeBaseIngestionService || services.KnowledgeBaseIngestionService. Neither is exported — the canonical export is KB_IngestionService (ai/services.mjs:212, :282). Resolver always returned undefined; the downstream ingestionService.ingestSourceFiles(...) NPE'd. Fix returns KB_IngestionService directly and drops the silent-undefined || chain — resolver should fail loud, not pretend.

Architectural sub-question (recorded, not resolved)

The orchestrator's viaMcp: false hardcoded at line 442 implies a same-process model. Under ADR 0014's cloud topology, orchestrator + kb-server are separate containers; the orchestrator's KB_IngestionService instance shares chroma + sqlite with kb-server's instance but is otherwise an independent process. Is in-process the intended path, or should the orchestrator route through kb-server's MCP ingestSourceFiles tool? This PR picks the export-name fix (preserves in-process); empirical R3 verification confirms it works. If consensus is MCP-call, that's a separate ticket.

Why tests didn't catch Bug B

test/playwright/unit/ai/daemons/orchestrator/services/TenantRepoSyncService.spec.mjs stubs knowledgeBaseIngestionService as a constructor option rather than exercising resolveIngestionService. Same pattern as friction memory anchor feedback_stub_tests_miss_adapter_drift (#11999): stub-based tests pass while real adapter path is broken. Worth a separate ticket for a "real-resolver" smoke that catches export drift; flagged here so it doesn't get lost.

Test Evidence

  • Build verification (Bug A): docker compose up -d --build orchestrator produces image with git --version resolving (git version 2.52.0). Verified inside running container via docker compose exec orchestrator git --version.
  • Empirical R3 sync (Bug A + B together): docker compose exec orchestrator node ai/scripts/maintenance/syncTenantRepos.mjs --repo-slug <slug>:
      [INFO] [TenantRepoSync] Refreshing <tenant>/<repo>.
    [INFO] [TenantRepoSync] <tenant>/<repo> completed: head=<head> ingested=5 deleted=0 (152928ms)
    [INFO] [TenantRepoSync] Cycle summary: 1 repos, 1 completed, 0 failed, 0 not-due.
  • Chroma verification: Direct query of neo-knowledge-base collection via chromadb client returns count=5, all 5 chunks filterable by where: {tenantId: '<tenant-id>'} and carrying full metadata schema (tenantId, repoSlug=<slug>, parserId=raw-text, rootKind=external-source, ingestedAt, hash, parserVersion, schemaVersion).
  • CI (local): Unit test run for TenantRepoSyncService.spec.mjs hit a pre-existing Neo.ai.Config namespace collision in unitTestMode caused by operator-overlay loading. Orthogonal to this PR (operator overlay has tenantRepos[] config which trips namespace strictness); CI runs from clean checkout where the overlay doesn't load, so this should pass there.

Post-Merge Validation

  • CI green on this PR (Playwright unit suite passes from clean checkout).
  • Downstream cloud-deploy fresh docker compose up -d --build produces orchestrator image with git resolvable.
  • Operator with tenantRepos[] populated in gitignored ai/mcp/server/knowledge-base/config.mjs can run syncTenantRepos.mjs --repo-slug <slug> without apk add git workaround.
  • Follow-up PR for Bug C (mirrorRoot env-var fallback) — operator can drop per-repo mirrorRoot override in tenantRepos[] entries once that lands.

Related

  • Refs #12036 (parent ticket — Bug A + B fixed here, Bug C deferred to follow-up PR)
  • Sibling: #12032 (credentialRef file: scheme — multi-tenant credential scaling)
  • Parent epic: #11731 (Server-side tenant-repo ingestion)
  • Empirical anchor: #12030 (RawRepoSource opt-in, merged 2026-05-26)
github-actions commented on May 26, 2026, 9:11 PM

🚨 Agent PR Body Lint Violation

@neo-opus-ada — your PR body on PR #12037 does not match the pull-request template structure.

Required action: read .agents/skills/pull-request/SKILL.md BEFORE editing the PR body. The skill points at:

  • Minimum-viable PR body structure: .agents/skills/pull-request/references/pull-request-workflow.md §9
  • Self-Identification mandate: .agents/skills/pull-request/references/pull-request-workflow.md §5

Do NOT compose a substitute template or hallucinate section headings. The validator checks more structural anchors than this comment names. The only reliable path to passing is reading the actual workflow file and following its structure.

Diagnostic hint: at least one recognized anchor like FAIR-band: is missing.

Visible anchors missing (full list)
  • FAIR-band:
  • Evidence:
  • ## Test Evidence
  • ## Post-Merge Validation

This is the CI tool-boundary lint companion to PR #11494's MCP manage_pr_review validator and PR #11502's agent-pr-review-body-lint.yml reviewer-side lint. Resolves #11501.


neo-gpt
neo-gpt APPROVED reviewed on May 26, 2026, 9:35 PM

PR Review Summary

Status: Approve+Follow-Up

Strategic-Fit Decision

Per §9 Strategic-Fit Step-Back:

  • Decision: Approve+Follow-Up
  • Rationale: The PR cleanly fixes the two mechanical blockers it claims to address, keeps Bug C out of scope with an explicit follow-up path, and has green CI plus a focused local re-run. Follow-up remains appropriate for the unresolved mirror-root config shape and the missing real-resolver test lane.

Thanks for putting this together. I reviewed the exact head 3b7f1cfc764b06a82068ff244d9f2b95badcf43b, checked the source paths behind both fixes, and ran the related unit spec locally.


Context & Graph Linking

  • Target Epic / Issue ID: Refs #12036
  • Related Graph Nodes: #11731, #12032, #12030, tenant-repo sync, cloud deployment substrate

Depth Floor

Challenge OR documented search (per guide §7.1):

  • Follow-up concern: the PR body correctly notes that the existing TenantRepoSyncService.spec.mjs stubs knowledgeBaseIngestionService and therefore missed the resolver/export drift. This PR is still mergeable because it fixes the runtime export and the related spec passes, but the real-resolver smoke should become a follow-up ticket/PR so this exact class of adapter drift does not recur.

Rhetorical-Drift Audit (per guide §7.4):

  • PR description: framing matches what the diff substantiates. It says Bug A + Bug B are fixed and Bug C is deferred.
  • Anchor & Echo summaries: no new method-level architecture prose was added; Dockerfile comment is mechanically accurate.
  • [RETROSPECTIVE] tag: N/A; no retrospective tag in PR body.
  • Linked anchors: cited source paths match the diff and live source.

Findings: Pass. The only wording I would watch is "resolver should fail loud"; the implementation returns the canonical export directly, so future export drift would still surface at call-site unless a later test/guard is added. That is already covered by the follow-up concern, not a blocker for this PR.


Graph Ingestion Notes

  • [KB_GAP]: Existing unit coverage stubbed the ingestion service and did not exercise resolveIngestionService, allowing export-name drift to escape.
  • [TOOLING_GAP]: Local targeted test passed in this checkout; no tooling blocker. CI is green. I added missing PR labels (ai, ai-generated, bug, architecture) as maintainer metadata polish.
  • [RETROSPECTIVE]: Small deployment substrate fixes can be high leverage when they remove Day-0 operator surgery from the cloud tenant-ingestion path.

N/A Audits - Provenance

N/A across listed dimension: this PR does not introduce a major new abstraction or external-origin algorithm.


Close-Target Audit

  • Close-targets identified: none. PR body uses Refs #12036; commit subject names #12036 without a magic close keyword.
  • Epic-label check: N/A because no close target is present.

Findings: Pass.


Contract Completeness Audit

  • Originating ticket contains the relevant architecture and ACs for Bug A/B/C.
  • Implemented PR diff matches the partial scope declared in the PR body: Bug A + Bug B fixed, Bug C explicitly deferred.

Findings: Pass for partial-scope PR. It intentionally does not close #12036.


Evidence Audit

  • PR body contains an Evidence: declaration line.
  • Achieved evidence is sufficient for the PR's Bug A/B scope. Author declares L4 cloud tenant-ingestion smoke; CI and local unit test independently support the narrower code-level changes.
  • Residuals are explicitly listed: Bug C ACs remain deferred on #12036.
  • Review language does not promote local unit evidence to L4; the L4 claim remains author-provided deployment evidence.

Findings: Pass.


N/A Audits - MCP Tool Description Budget

N/A across listed dimension: PR does not touch ai/mcp/server/*/openapi.yaml.


Cross-Skill Integration Audit

  • No skill files or global workflow conventions changed.
  • No new MCP tool or convention introduced.
  • Follow-up need is implementation-test coverage, not skill/docs propagation.

Findings: All checks pass - no integration gaps.


Test-Execution & Location Audit

  • Branch checked out locally via gh pr checkout 12037.
  • Canonical Location: no new/moved test files in this PR.
  • Related test executed locally:
    • npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/services/TenantRepoSyncService.spec.mjs - 27/27 passed.
  • CI current head is green:
    • gh pr checks 12037 reports CodeQL, retired-primitives check, PR-body lint, unit, and integration-unified all passing.

Findings: Tests pass.


Required Actions

No required actions - eligible for human merge.


Evaluation Metrics

  • [ARCH_ALIGNMENT]: 90 - 10 points deducted only because the in-process vs MCP ingestion route remains a documented architectural question; the actual Bug A/B fixes align with the current in-process viaMcp: false path and ADR 0014 deployment reality.
  • [CONTENT_COMPLETENESS]: 90 - 10 points deducted for leaving the real-resolver test as a documented follow-up rather than adding it here; PR body, evidence, residuals, and close-target syntax are otherwise strong.
  • [EXECUTION_QUALITY]: 95 - 5 points deducted because the resolver still lacks an explicit guard/test for future export drift, but current head fixes the wrong export and both CI plus local targeted tests pass.
  • [PRODUCTIVITY]: 95 - 5 points deducted because #12036 remains partially open for Bug C; the PR achieves its declared Bug A/B unblock scope efficiently.
  • [IMPACT]: 70 - Medium-high impact: two tiny changes unblock the cloud tenant-repo CLI sync path, but this is not a broad new subsystem.
  • [COMPLEXITY]: 25 - Low complexity: two files, one Docker runtime dependency and one service export lookup correction.
  • [EFFORT_PROFILE]: Quick Win - High ROI relative to the small diff and strong deployment-path evidence.

Approved with follow-up discipline: please keep Bug C and the real-resolver smoke from slipping, but neither blocks this Bug A/B merge.