GitMirror.cloneIfMissing({tenantId, repoSlug, cloneUrl, credentialRef}) |
#11788 AC1; #11731 AC3; #11787 credentialed repo-access contract; ADR 0014 tenant-repo-sync amendment |
Clone via injected credential only when the mirror path is absent; existing clean mirror is idempotent no-op. |
Git subprocess failure throws KB_GITMIRROR_CLONE_FAILED with stderr redacted through redactTenantRepoSecrets; invalid mirror path throws KB_GITMIRROR_MIRROR_PATH_INVALID. |
JSDoc on GitMirror; TenantIngestionModel.md operator note if behavior is user-visible. |
Unit coverage using local fixture repos for clone/no-op; no-secret assertions for argv/log/output. |
GitMirror.fetch({tenantId, repoSlug, credentialRef}) |
#11788 AC1; #11731 persistent mirror model; ADR 0014 tenant-repo-sync lane |
Run git fetch --all --prune inside an existing mirror and return {fetchedAt, newRevisions} or equivalent implementation payload. |
Missing mirror is not silently cloned unless caller explicitly invoked clone path; git failure throws KB_GITMIRROR_FETCH_FAILED with redacted stderr. |
JSDoc on GitMirror. |
Unit coverage for successful fetch, missing mirror behavior, and redacted failure text. |
GitMirror.resolveHead({tenantId, repoSlug, ref}) |
#11788 AC1; #11731 update model |
Resolve branch, tag, or SHA to a full SHA in the mirror. |
Unknown ref throws KB_GITMIRROR_REF_NOT_FOUND; callers branch on .code, not .message. |
JSDoc on GitMirror. |
Unit coverage for branch/tag/SHA resolution and missing-ref failure. |
GitMirror.isAncestor({tenantId, repoSlug, ancestor, descendant}) |
#11788 AC1; #11731 AC4 force-push fallback model |
Return true iff ancestor is reachable from descendant, enabling fast-forward safety and skip-no-op detection. |
Git failure throws stable KB_GITMIRROR_* error with redacted stderr; false is reserved for valid non-ancestor results. |
JSDoc on GitMirror. |
Unit coverage for ancestor, non-ancestor, and failure cases. |
GitMirror.diffRevisions({tenantId, repoSlug, baseRevision, headRevision}) |
#11788 AC1; #11789 envelope-builder dependency; #11731 AC4 |
Return repo-relative {addedOrChanged: String[], deleted: String[]} paths between two revisions. |
Git failure throws KB_GITMIRROR_DIFF_FAILED; invalid paths are never emitted outside the mirror root. |
JSDoc on GitMirror; #11789 owns envelope-shape docs. |
Unit coverage for added, changed, deleted, and empty diffs using local fixture repos. |
Mirror path layout via deriveTenantRepoMirrorPath({mirrorRoot, tenantId, repoSlug}) |
#11787 shipped contract; #11788 AC2; ADR 0014 volume persistence |
Map {tenantId, repoSlug} to <mirrorRoot>/tenant-repos/<tenantId>/<repoSlugSegments> using clean, path-traversal-safe segments; credential-free by construction. |
Invalid tenant/repo identity throws KB_GITMIRROR_MIRROR_PATH_INVALID or existing path-validation error; direct consumer path manipulation is forbidden. |
TenantIngestionModel.md; JSDoc remains with the helper. |
Path derivation tests proving traversal rejection and no secret material in paths. |
| Credential injection - HTTPS |
#11787 credentialed repo-access contract; #11788 AC4 |
Use GIT_ASKPASS plus scoped git credential configuration to resolve credentialRef per git invocation; never persist secret in URL, argv, graph config, or instance state. |
Missing or invalid credential reference fails before durable logging; stderr is redacted before logging/telemetry. |
TenantIngestionModel.md; GitMirror JSDoc. |
No-leak tests with fake secrets absent from argv/log/output/persisted state. |
| Credential injection - SSH |
#11787 credentialed repo-access contract; #11788 AC4 |
Use GIT_SSH_COMMAND referencing the deploy-key path resolved from credentialRef; never place key material in URL or argv. |
Missing deploy key / invalid reference fails with stable redacted error; no key material in logs. |
TenantIngestionModel.md; GitMirror JSDoc. |
No-leak tests with fake key path/reference behavior and redacted failures. |
| Persistent repo-mirror volume |
#11788 AC3; ADR 0014 volume topology; ai/deploy/docker-compose.yml existing volume pattern |
Add a deployment compose volume mounted at mirrorRoot, sibling to chroma-data and shared-sqlite-data; mirror contents survive container redeploy. |
Local/unit tests may use temp dirs; production compose owns persistent volume wiring. |
Deployment compose docs / cloud deployment guide update. |
Compose/static assertion or documented verification showing the named volume and mount path. |
| Logged stderr / telemetry |
#11787 redaction contract; #11788 AC4; #11791 telemetry follow-up |
All captured git stdout/stderr and thrown errors pass through redactTenantRepoSecrets({secretHints}) before durable logging or telemetry emission. |
Redaction is a backstop for git error text; stable error .code remains branchable after redaction. |
GitMirror JSDoc; #11791 owns broader operator telemetry docs. |
No-secret tests proving fake tokens are absent from all durable/error surfaces. |
| Stable error codes |
#11788 API contract; downstream #11789/#11790 consumers |
Use KB_GITMIRROR_* codes: CLONE_FAILED, FETCH_FAILED, REF_NOT_FOUND, DIFF_FAILED, MIRROR_PATH_INVALID, CREDENTIAL_REF_INVALID unless implementation discovers a narrower code list and updates this ledger before PR open. |
Consumers branch on .code; .message stays human-readable and redactable. |
JSDoc on errors thrown by GitMirror. |
Unit coverage asserting error codes for representative failure modes. |
Context
Sub 3 of Epic #11731 (Server-side tenant-repo ingestion for cloud Agent OS deployments), graduated from Discussion #11782. This sub builds the
GitMirrorprimitive + the persistent mirror acquisition layer — the structural core of the design (Discussion #11782 OQ1 → Option A, withGitMirroras the disciplined form of Option C).The Problem
The deployment must keep tenant repos as persistent mirrors — clone-once into a container volume, then
fetchfor updates — rather than re-cloning each refresh (wasteful) or re-pointing the local-onlykbSync/primary-dev-synclanes at tenant content (ADR 0014 §5.2 anti-pattern).The Architectural Reality
PrimaryRepoSyncServicedoes NOT clone and isdev-hardcoded + local-only — it is not reused or rewritten.GitMirroris a new lower-level primitive;TenantRepoSyncService(sub 5's lane consumes it). File placement validated viastructural-pre-flightat implementation — candidate:ai/daemons/services/(sibling ofPrimaryRepoSyncService.mjs,DreamService.mjs); theGitMirrorprimitive's exact home gets its own structural-pre-flight pass.The Fix
GitMirrorprimitive owning: clone-if-missing (via the credentialed reference from sub 2),fetch, resolve-head, ancestor-check, and changed+deleted file diff between two revisions.{tenantId, repoSlug}mirror paths, computed from the clean normalized identity only (never from a credentialed URL).chroma-data/shared-sqlite-data); redeploy-survival.GIT_ASKPASS/credential.helperresolving sub 2'scredentialRef; SSH viaGIT_SSH_COMMAND+ the referenced deploy-key. No token in URL or argv.Acceptance Criteria
GitMirrorprimitive: clone-if-missing / fetch / resolve-head / ancestor-check / revision-diff.{tenantId, repoSlug}mirror paths, derived from clean identity only.structural-pre-flightapplied to all new.mjsfile placements.Out of Scope
ingestSourceFiles()envelope (sub 4 / #11731).Contract Ledger
Per the Contract Completeness Gate for public/consumed surfaces, #11788 introduces the canonical
GitMirrorprimitive consumed by thetenant-repo-syncscheduler lane (#11790), the diff-to-ingest envelope builder (#11789), and the deployment volume layer (ADR 0014). Downstream consumers MUST V-B-A against this matrix.GitMirror.cloneIfMissing({tenantId, repoSlug, cloneUrl, credentialRef})KB_GITMIRROR_CLONE_FAILEDwith stderr redacted throughredactTenantRepoSecrets; invalid mirror path throwsKB_GITMIRROR_MIRROR_PATH_INVALID.GitMirror;TenantIngestionModel.mdoperator note if behavior is user-visible.GitMirror.fetch({tenantId, repoSlug, credentialRef})git fetch --all --pruneinside an existing mirror and return{fetchedAt, newRevisions}or equivalent implementation payload.KB_GITMIRROR_FETCH_FAILEDwith redacted stderr.GitMirror.GitMirror.resolveHead({tenantId, repoSlug, ref})KB_GITMIRROR_REF_NOT_FOUND; callers branch on.code, not.message.GitMirror.GitMirror.isAncestor({tenantId, repoSlug, ancestor, descendant})trueiffancestoris reachable fromdescendant, enabling fast-forward safety and skip-no-op detection.KB_GITMIRROR_*error with redacted stderr; false is reserved for valid non-ancestor results.GitMirror.GitMirror.diffRevisions({tenantId, repoSlug, baseRevision, headRevision}){addedOrChanged: String[], deleted: String[]}paths between two revisions.KB_GITMIRROR_DIFF_FAILED; invalid paths are never emitted outside the mirror root.GitMirror; #11789 owns envelope-shape docs.deriveTenantRepoMirrorPath({mirrorRoot, tenantId, repoSlug}){tenantId, repoSlug}to<mirrorRoot>/tenant-repos/<tenantId>/<repoSlugSegments>using clean, path-traversal-safe segments; credential-free by construction.KB_GITMIRROR_MIRROR_PATH_INVALIDor existing path-validation error; direct consumer path manipulation is forbidden.TenantIngestionModel.md; JSDoc remains with the helper.GIT_ASKPASSplus scoped git credential configuration to resolvecredentialRefper git invocation; never persist secret in URL, argv, graph config, or instance state.TenantIngestionModel.md;GitMirrorJSDoc.GIT_SSH_COMMANDreferencing the deploy-key path resolved fromcredentialRef; never place key material in URL or argv.TenantIngestionModel.md;GitMirrorJSDoc.ai/deploy/docker-compose.ymlexisting volume patternmirrorRoot, sibling tochroma-dataandshared-sqlite-data; mirror contents survive container redeploy.redactTenantRepoSecrets({secretHints})before durable logging or telemetry emission..coderemains branchable after redaction.GitMirrorJSDoc; #11791 owns broader operator telemetry docs.KB_GITMIRROR_*codes:CLONE_FAILED,FETCH_FAILED,REF_NOT_FOUND,DIFF_FAILED,MIRROR_PATH_INVALID,CREDENTIAL_REF_INVALIDunless implementation discovers a narrower code list and updates this ledger before PR open..code;.messagestays human-readable and redactable.GitMirror.Behavioral invariants (cross-contract):
{tenantId, repoSlug}to filesystem path; consumers MUST go throughderiveTenantRepoMirrorPathfrom #11787.GitMirrorinstance state;credentialRefis the only durable handle.{env, cwd}overrides and do not rely on inherited shell state for credentials.GitMirrorinstance recreations and container redeploys; persistence is the volume contract, not in-memory state.Out of contract scope (covered by sibling subs):
ingestSourceFiles()envelope shape → sub 4 #11789.Related
Origin Session ID
39185c66-a107-46ea-b0bf-eb4fa1137257