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 |
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
GitMirrorhas produced a revision diff, that diff must become an ingestion call. The good news (Discussion #11782 V-B-A): the existingingestSourceFiles()envelope (#11726/#11743) already carriesbaseRevision/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 aGitMirrorrevision-diff into the envelope, plus the bootstrap-vs-incremental + force-push decision logic.The Fix
git diff <lastIngestedRev>..<newHead>→ envelope with changed files +deletedtombstones +baseRevision/headRevision.lastIngestedRevis an ancestor ofnewHead. If not (force-push / history rewrite) → fall back to a full re-ingest + manifest reconciliation.ingestSourceFiles()with the server-stampedtenantId.Acceptance Criteria
deleted+baseRevision/headRevision).ingestSourceFiles()tenant-scoped; unit coverage for bootstrap / incremental / force-push-fallback.Out of Scope
GitMirror(sub 3 / #11731).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 existingingestSourceFiles()envelope (#11726/#11743). Downstream consumers (#11790 scheduler lane) must V-B-A against this matrix.buildIngestEnvelope({tenantId, repoSlug, mirrorRoot, lastIngestedRev?, newHead})builder APIingestSourceFiles()payload using the existingfiles,deleted,manifestSnapshot,baseRevision, andheadRevisionenvelope fields.KB_INGEST_ENVELOPE_*error code on builder failure (mirror missing, ref not found, file read failure, etc.).TenantIngestionModel.mdlastIngestedRevabsent or force-push fallback fired)newHead, enumerates all repo-relative files, and produces{tenantId, files:[{sourcePath, content, repoSlug?}], manifestSnapshot:{repoSlug, pathsAfterPush}, headRevision:newHead}.baseRevisionand explicitdeletedare omitted.files: []plus an emptypathsAfterPush; invalidnewHead->KB_INGEST_ENVELOPE_REF_INVALID.lastIngestedRevpresent and ancestor ofnewHead)GitMirror.diffRevisions({baseRevision:lastIngestedRev, headRevision:newHead})becomes{files: addedOrChanged file payloads, deleted: deleted.map(sourcePath => ({sourcePath, repoSlug})), baseRevision:lastIngestedRev, headRevision:newHead}.files: [],deleted: [], and caller may skip ingestion if no manifest/revision persistence work is needed.GitMirror.isAncestor({ancestor:lastIngestedRev, descendant:newHead});trueuses the incremental path,falseuses the bootstrap/full re-ingest path with manifest reconciliation.TenantIngestionModel.mdoperator note on rebase-detection behaviordeleted)ingestSourceFiles()contract (#11726/#11633)KnowledgeBaseIngestionService.ingestSourceFiles()({sourcePath, repoSlug?}).deleted; cleanup is driven bymanifestSnapshot.pathsAfterPushreconciliation instead.GitMirror.diffRevisions().deleted; this sub proves mapping to ingestion tombstone objectslastIngestedRevhandofflastIngestedRevfrom the caller and returnsheadRevisionin the envelope; after successful ingestion, the caller owns durablelastIngestedRev = headRevisionpersistence.ingestSourceFiles({tenantId, files, ...})contract + #11787 tenant contexttenantIdand cleanrepoSlug;ingestSourceFiles()enforces tenant context through the existingRequestContextServiceboundary.KB_INGEST_ENVELOPE_*errors with redacted context (tenantId,repoSlug, attempted ref/path) and without secret-bearing git stderr.GitMirror.diffRevisions/file reads to throw, verify stable error code and no manifest mutationBehavioral invariants:
lastIngestedRevpersistence is caller responsibility; the builder neither reads nor writes durable manifest state.KB_INGEST_ENVELOPE_*prefix, symmetric withKB_GITMIRROR_*(sub 3) andKB_TENANT_REPO_*(sub 2) for consumer branching.Out of contract scope:
ingestSourceFiles()) -> reused unchanged per existing contract #11726/#11633.lastIngestedRevmanifest persistence orchestration -> sub 5 #11790.Related
ingestSourceFiles()).Origin Session ID
39185c66-a107-46ea-b0bf-eb4fa1137257