LearnNewsExamplesServices
Frontmatter
id11789
titleDiff-to-ingest envelope builder for tenant-repo sync
stateClosed
labels
enhancementaiarchitecture
assigneesneo-gpt
createdAtMay 22, 2026, 11:35 PM
updatedAtJun 21, 2026, 3:47 PM
githubUrlhttps://github.com/neomjs/neo/issues/11789
authorneo-opus-ada
commentsCount3
parentIssue11731
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 11788 GitMirror primitive + persistent mirror acquisition service
blocking[x] 11790 tenant-repo-sync scheduler lane — periodic + manual
closedAtMay 25, 2026, 1:37 AM

Diff-to-ingest envelope builder for tenant-repo sync

Closed v13.0.0/archive-v13-0-0-chunk-13 enhancementaiarchitecture
neo-opus-ada
neo-opus-ada commented on May 22, 2026, 11:35 PM

Context

Sub 4 of Epic #11731 (Server-side tenant-repo ingestion for cloud Agent OS deployments), graduated from Discussion #11782. This sub bridges GitMirror (sub 3) to the existing KB ingestion core.

The Problem

Once GitMirror has produced a revision diff, that diff must become an ingestion call. The good news (Discussion #11782 V-B-A): the existing ingestSourceFiles() envelope (#11726/#11743) already carries baseRevision / headRevision / deleted — the incremental + deletion primitives. So this sub constructs that envelope server-side; it does not re-implement ingestion.

The Architectural Reality

KnowledgeBaseIngestionService.ingestSourceFiles() is reused as-is. The new work is the builder that turns a GitMirror revision-diff into the envelope, plus the bootstrap-vs-incremental + force-push decision logic.

The Fix

  • Bootstrap (mirror freshly cloned): build a full-content envelope — all files.
  • Update (mirror fetched): git diff <lastIngestedRev>..<newHead> → envelope with changed files + deleted tombstones + baseRevision/headRevision.
  • Ancestor-check: before an incremental diff, verify lastIngestedRev is an ancestor of newHead. If not (force-push / history rewrite) → fall back to a full re-ingest + manifest reconciliation.
  • Feed the envelope to ingestSourceFiles() with the server-stamped tenantId.

Acceptance Criteria

  • Bootstrap path: full-content envelope on first ingest of a mirror.
  • Incremental path: revision-diff → envelope (changed files + deleted + baseRevision/headRevision).
  • Ancestor-check before incremental diff; non-ancestor → full re-ingest + manifest reconciliation fallback.
  • Envelope feeds ingestSourceFiles() tenant-scoped; unit coverage for bootstrap / incremental / force-push-fallback.

Out of Scope

  • Repo acquisition / GitMirror (sub 3 / #11731).
  • The ingestion core itself — ingestSourceFiles() is reused unchanged (#11726/#11743).

Contract Ledger

Per the Contract Completeness Gate for tickets introducing public or consumed surfaces. #11789 introduces the diff-to-ingest envelope builder bridging GitMirror (sub 3 / #11788) to the existing ingestSourceFiles() envelope (#11726/#11743). Downstream consumers (#11790 scheduler lane) must V-B-A against this matrix.

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
buildIngestEnvelope({tenantId, repoSlug, mirrorRoot, lastIngestedRev?, newHead}) builder API Epic #11731 + Discussion #11782 V-B-A Returns an ingestSourceFiles() payload using the existing files, deleted, manifestSnapshot, baseRevision, and headRevision envelope fields. Throws a stable KB_INGEST_ENVELOPE_* error code on builder failure (mirror missing, ref not found, file read failure, etc.). JSDoc + reference from TenantIngestionModel.md Unit tests for bootstrap, incremental, and force-push fallback paths
Bootstrap full-content envelope (lastIngestedRev absent or force-push fallback fired) #11789 AC1 + AC3 Walks the mirror at newHead, enumerates all repo-relative files, and produces {tenantId, files:[{sourcePath, content, repoSlug?}], manifestSnapshot:{repoSlug, pathsAfterPush}, headRevision:newHead}. baseRevision and explicit deleted are omitted. Empty repo -> files: [] plus an empty pathsAfterPush; invalid newHead -> KB_INGEST_ENVELOPE_REF_INVALID. JSDoc Unit test: fresh mirror produces full-content envelope; manifest reconciliation removes orphaned prior paths
Incremental diff envelope (lastIngestedRev present and ancestor of newHead) #11789 AC2 GitMirror.diffRevisions({baseRevision:lastIngestedRev, headRevision:newHead}) becomes {files: addedOrChanged file payloads, deleted: deleted.map(sourcePath => ({sourcePath, repoSlug})), baseRevision:lastIngestedRev, headRevision:newHead}. Empty diff -> files: [], deleted: [], and caller may skip ingestion if no manifest/revision persistence work is needed. JSDoc Unit test: two-commit mirror produces expected added/changed payloads and tombstone objects
Ancestor-check pre-incremental gate #11789 AC3 Before the incremental path, call GitMirror.isAncestor({ancestor:lastIngestedRev, descendant:newHead}); true uses the incremental path, false uses the bootstrap/full re-ingest path with manifest reconciliation. Force-push, history rewrite, or branch replacement -> full re-ingest path; no stale incremental tombstone assumptions. JSDoc + TenantIngestionModel.md operator note on rebase-detection behavior Unit test: rewrite history, verify fallback to full envelope and manifest reconciliation
Deletion tombstone semantics (deleted) #11789 AC2 + existing ingestSourceFiles() contract (#11726/#11633) Incremental envelope maps deleted repo-relative path strings to explicit tombstone objects accepted by KnowledgeBaseIngestionService.ingestSourceFiles() ({sourcePath, repoSlug?}). Bootstrap envelope omits explicit deleted; cleanup is driven by manifestSnapshot.pathsAfterPush reconciliation instead. JSDoc Unit test: #11788 proves GitMirror.diffRevisions().deleted; this sub proves mapping to ingestion tombstone objects
Manifest / lastIngestedRev handoff Existing KB manifest contract (#11711/#11716) + this sub Builder accepts lastIngestedRev from the caller and returns headRevision in the envelope; after successful ingestion, the caller owns durable lastIngestedRev = headRevision persistence. Builder does not mutate the manifest; manifest update failure is caller-owned and must not be hidden as builder success. JSDoc Unit test: caller stub can update manifest/revision from returned envelope only after successful ingestion
Tenant-scoped ingestion call Existing ingestSourceFiles({tenantId, files, ...}) contract + #11787 tenant context Builder envelope includes the server-stamped tenantId and clean repoSlug; ingestSourceFiles() enforces tenant context through the existing RequestContextService boundary. Cross-tenant write attempt -> existing tenant mismatch path rejects or returns structured error; builder never derives tenant identity from client payload alone. Existing docs preserved; JSDoc where new builder accepts tenant context Unit test: envelope built for one tenant cannot be consumed under another active tenant context
Degraded / error state propagation #11789 AC4 + cross-sub failure isolation GitMirror or file-read failures rethrow as KB_INGEST_ENVELOPE_* errors with redacted context (tenantId, repoSlug, attempted ref/path) and without secret-bearing git stderr. Builder failure does not corrupt manifest/revision state; caller can retry on the next scheduler tick (sub 5 / #11790). JSDoc on error codes Unit test: stub GitMirror.diffRevisions/file reads to throw, verify stable error code and no manifest mutation

Behavioral invariants:

  • Builder is a pure construction boundary: accepts inputs, returns an envelope, or throws. It never mutates the KB graph, manifest, git mirror, or filesystem state.
  • Bootstrap and incremental paths intentionally produce different envelope shapes; consumers branch on explicit envelope fields, not on hidden builder state.
  • lastIngestedRev persistence is caller responsibility; the builder neither reads nor writes durable manifest state.
  • Stable error codes use the KB_INGEST_ENVELOPE_* prefix, symmetric with KB_GITMIRROR_* (sub 3) and KB_TENANT_REPO_* (sub 2) for consumer branching.

Out of contract scope:

  • Repo acquisition / GitMirror clone+fetch+diff -> sub 3 #11788.
  • Ingestion core (ingestSourceFiles()) -> reused unchanged per existing contract #11726/#11633.
  • Scheduler lane cadence + lastIngestedRev manifest persistence orchestration -> sub 5 #11790.
  • Tenant config / no-secret boundary -> sub 2 #11787.

Related

  • Parent epic: #11731.
  • Origin Discussion: #11782 (OQ4).
  • Reuses: #11726 / #11743 (the push envelope + ingestSourceFiles()).

Origin Session ID

39185c66-a107-46ea-b0bf-eb4fa1137257

tobiu referenced in commit c596bbb - "feat(kb): add tenant repo ingest envelope builder (#11789) (#11938) on May 25, 2026, 1:37 AM
tobiu closed this issue on May 25, 2026, 1:37 AM
tobiu referenced in commit 41a93c4 - "feat(orchestrator): cloud-deployable tenant-repo-sync scheduler lane (#11790) (#11940) on May 25, 2026, 7:48 AM
tobiu referenced in commit 1bfee42 - "docs(day0): rewrite Milestone 6 to first-class pull mode (#12052) (#12053) on May 27, 2026, 12:58 AM