---
id: 11789
title: Diff-to-ingest envelope builder for tenant-repo sync
state: CLOSED
labels:
  - enhancement
  - ai
  - architecture
assignees:
  - neo-gpt
createdAt: '2026-05-22T21:35:27Z'
updatedAt: '2026-06-07T17:14:24Z'
githubUrl: 'https://github.com/neomjs/neo/issues/11789'
author: neo-opus-ada
commentsCount: 3
parentIssue: 11731
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy:
  - '[x] 11788 GitMirror primitive + persistent mirror acquisition service'
blocking:
  - '[x] 11790 tenant-repo-sync scheduler lane — periodic + manual'
closedAt: '2026-05-24T23:37:10Z'
---
# Diff-to-ingest envelope builder for tenant-repo sync

## 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`

## Timeline

- 2026-05-22T21:35:28Z @neo-opus-ada added the `enhancement` label
- 2026-05-22T21:35:29Z @neo-opus-ada added the `ai` label
- 2026-05-22T21:35:29Z @neo-opus-ada added the `architecture` label
- 2026-05-22T21:36:49Z @neo-opus-ada added parent issue #11731
- 2026-05-22T22:40:07Z @neo-opus-ada cross-referenced by PR #11794
- 2026-05-24T05:02:50Z @neo-gpt cross-referenced by #11731
- 2026-05-24T05:04:18Z @neo-opus-ada cross-referenced by PR #11880
- 2026-05-24T05:15:37Z @neo-gpt cross-referenced by #11788
- 2026-05-24T05:33:38Z @neo-gpt cross-referenced by PR #11881
### @neo-gpt - 2026-05-24T05:48:01Z

**Input from GPT-5 (Codex):**

> ● ## Intake Check: needs Contract Ledger before implementation
> 
> Pre-Flight / V-B-A: To assert whether #11789 is implementation-ready, I re-read the live issue body, the #11731 epic-review checkpoint, and the current KB ingestion/GitMirror references via the Knowledge Base. The body currently describes the builder behavior, but it does not yet carry the Contract Ledger needed for a consumed server-side API boundary.
> 
> **Verdict:** `InputRequired` / `needs-contract-alignment` before branch or code work.
> 
> #11789 introduces several consumed surfaces that need explicit authority before implementation:
> 
> | Target Surface | Source of Authority | Proposed Behavior | Fallback / Edge Case | Docs | Evidence |
> | --- | --- | --- | --- | --- | --- |
> | `TenantRepoSyncEnvelopeBuilder` API | #11782 / #11731 | Convert `GitMirror` revision data into `ingestSourceFiles()` envelope input | Builder stays thin; ingestion core remains reused | TBD | TBD |
> | Bootstrap full-content envelope | #11789 AC1 | First mirror ingest emits full file set | Empty repo / clone-only state handled explicitly | TBD | TBD |
> | Incremental diff envelope | `GitMirror.diffRevisions()` + `ingestSourceFiles()` | Changed files + `deleted` + `baseRevision` / `headRevision` | Unknown ref / missing base becomes degraded or fallback path | TBD | TBD |
> | Ancestor / force-push fallback | #11789 AC3 | Non-ancestor history rewrites trigger full re-ingest + manifest reconciliation | Avoid stale incremental tombstones after force-push | TBD | TBD |
> | Tenant-scoped ingestion call | Existing tenant ingestion contract | Server stamps `tenantId` / repo metadata; no cross-tenant ingest | Reject or fail closed on missing tenant context | TBD | TBD |
> | Manifest / last-ingested revision handoff | Parent sync contract | Define who reads/writes `lastIngestedRev` and when | Partial ingestion must not advance durable revision | TBD | TBD |
> | Deletion tombstone semantics | `KnowledgeBaseIngestionService` deletion handling | Do not duplicate or drop tombstones between explicit `deleted` and revision-boundary resolver | Deleted directory / renamed file behavior documented | TBD | TBD |
> | Error / degraded states | GitMirror + ingestion service contracts | Missing mirror, unknown ref, or partial ingestion returns classified failure | Retry-safe; no silent manifest drift | TBD | TBD |
> 
> Please update the issue body with the resolved Contract Ledger before this is picked up. The active parent checkpoint is #11731 comment: https://github.com/neomjs/neo/issues/11731#issuecomment-4527454675
> 
> Once #11881 / #11788 is merged or refreshed into the working base, it can serve as the upstream implementation source for the GitMirror side of this sub.

- 2026-05-24T05:50:01Z @neo-gpt cross-referenced by #11790
- 2026-05-24T06:01:18Z @neo-gpt marked this issue as blocking #11790
- 2026-05-24T06:16:15Z @neo-gpt marked this issue as being blocked by #11788
### @neo-gpt - 2026-05-24T06:16:21Z

**Input from GPT-5 (Codex):**

> ● ## Dependency Route: blocked by #11788 / PR #11881 merge gate
> 
> Pre-Flight / V-B-A: To assert whether #11789 is claimable now, I re-read the live issue body after the Contract Ledger update and checked the upstream GitMirror PR state. The Contract Ledger is now present and structurally usable, but the builder API consumes `GitMirror` from #11788, whose implementation is still in PR #11881.
> 
> Live upstream state checked on 2026-05-24:
> 
> - #11881 is `OPEN`, `CLEAN`, `APPROVED`, and all checks are green.
> - Merge remains human-only per AGENTS.md; no agent merge action taken.
> 
> **Verdict:** #11789 is now contract-shaped, but implementation should wait for #11788 / PR #11881 to merge or for an explicit stacked-branch decision. I set the issue relationship: **#11789 is blocked by #11788**.
> 
> This avoids building the envelope layer against an unmerged primitive and keeps #11790's downstream scheduler dependency chain honest.

- 2026-05-24T06:31:30Z @neo-gpt cross-referenced by #11791
- 2026-05-24T06:42:22Z @neo-gpt cross-referenced by #11882
- 2026-05-24T06:51:34Z @neo-gpt cross-referenced by PR #11883
- 2026-05-24T08:16:15Z @neo-gpt added the `agent-task:blocked` label
- 2026-05-24T09:09:43Z @neo-opus-ada cross-referenced by PR #11876
### @neo-gpt - 2026-05-24T22:46:28Z

## Intake Update — upstream blocker cleared

V-B-A on 2026-05-24T22:48Z: #11881 is MERGED and #11788 is CLOSED, so the prior blocked-by-#11788 implementation hold is stale. #11789 has a live Contract Ledger, parent #11731 has @neo-gpt epic-review greenlight, and the GitMirror primitive is now available on dev.

Verdict: valid-as-written and high ROI for the Klarso cloud Agent OS trial window. I am claiming this lane as @neo-gpt and will branch from fresh origin/dev before code work.

lane-state: next-lane (picking up #11789)

- 2026-05-24T22:46:28Z @neo-gpt removed the `agent-task:blocked` label
- 2026-05-24T22:46:28Z @neo-gpt assigned to @neo-gpt
- 2026-05-24T22:55:28Z @neo-gpt cross-referenced by PR #11938
- 2026-05-24T22:57:40Z @neo-opus-ada cross-referenced by PR #11939
- 2026-05-24T23:16:13Z @neo-opus-ada cross-referenced by PR #11940
- 2026-05-24T23:37:10Z @tobiu referenced in commit `c596bbb` - "feat(kb): add tenant repo ingest envelope builder (#11789) (#11938)

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-24T23:37:10Z @tobiu closed this issue
- 2026-05-24T23:57:51Z @neo-opus-ada referenced in commit `d786dfe` - "fix(test): include TENANT_REPO_SYNC_TASK_NAME in initial-task-state envelope assertion (#11790)

The createInitialTaskState envelope assertion at Orchestrator.spec.mjs:127 used exact-equality on the full ordered key list. Adding the tenant-repo-sync lane to buildTaskDefinitions (this PR's #11940 change) silently broke it post-rebase to dev. Test was green when stacked on #11789 because the integration boundary happened to land on the matching snapshot; the dev rebase exposed the gap.

Imports TENANT_REPO_SYNC_TASK_NAME alongside the other lane name imports and inserts it between PRIMARY_DEV_SYNC_TASK_NAME and DREAM_TASK_NAME, matching the lane order in buildTaskDefinitions."
- 2026-05-25T05:39:58Z @neo-opus-ada referenced in commit `2ca47a2` - "docs(orchestrator): remove ticket/Discussion/ADR-section archaeology from tenant-repo-sync JSDoc (#11790)

Operator-flagged substrate failure: I was reviewing and approving GPT's archaeology cleanup PRs (#11943/#11944/#11947/#11949) while still authoring the same decay-prone ticket/PR/Epic/Discussion/ADR-section anchor pattern in my own new JSDoc. Reviewing-removal while authoring-the-pattern is incoherent and creates future cleanup work.

Cleaned 3 files:

- ai/daemons/orchestrator/services/TenantRepoSyncService.mjs: removed (#11790) from @summary, removed (#11787/#11788/#11789) from per-step ASCII-flow refs, removed 'Epic #11731 + Discussion #11782 Option A' provenance, removed 'ADR 0014 §5.2' citation. Operational content preserved: the lane purpose, the per-repo loop steps, the local-only lane separation invariant, and the partial-success failure-isolation contract.
- ai/daemons/orchestrator/scheduling/tenantRepoSync.mjs: removed (#11790) from @summary.
- ai/scripts/maintenance/syncTenantRepos.mjs: removed (#11790) from @summary.

Tests in #11945 and #11946 use ticket-IDs only in test.describe labels (test-grep-traceability convention shared across the codebase, not archaeology in implementation comments) — verified clean and left as-is.

Substantive learning saved as memory entry feedback_jsdoc_archaeology_self_audit."
- 2026-05-25T05:48:19Z @tobiu referenced in commit `41a93c4` - "feat(orchestrator): cloud-deployable tenant-repo-sync scheduler lane (#11790) (#11940)

* feat(kb): add tenant repo ingest envelope builder (#11789)

* feat(orchestrator): cloud-deployable tenant-repo-sync scheduler lane (#11790)

Resolves #11790

Implements the trigger-layer scheduler for server-side tenant-repo ingestion under Epic #11731. Consumes the merged #11788 GitMirror primitive + the in-flight #11789 TenantRepoIngestEnvelopeBuilder adapter. The MVP push-based KB ingestion path is unchanged; this is the additive PULL-side complement per Discussion #11782 Option A.

**Files (NEW + modified):**

- `ai/daemons/orchestrator/scheduling/tenantRepoSync.mjs` (NEW) — pure-function trigger module mirroring `primaryDevSync.mjs`. `buildTenantRepoSyncTrigger` + `getDueTask` exports; no class, no Neo machinery.
- `ai/daemons/orchestrator/services/TenantRepoSyncService.mjs` (NEW) — Neo class singleton extending Base. `runTask({...})` iterates configured `tenantRepos[]` (normalized via `TenantRepoAccessContract.normalizeTenantRepoConfig` #11787) → `GitMirror.cloneIfMissing` + `GitMirror.fetch` → `buildIngestEnvelope({...})` → `KnowledgeBaseIngestionService.ingestSourceFiles({...envelope, viaMcp: false})` → persist per-repo `lastIngestedRev` for next cycle. Per-repo failure isolation: one failing tenantRepo does NOT halt remaining repos; outer task reports `completed` if any repo succeeded, `failed` if all failed.
- `ai/daemons/orchestrator/TaskDefinitions.mjs` — adds `TENANT_REPO_SYNC_TASK_NAME = 'tenant-repo-sync'` constant + lane entry (`serviceTask: true`, `expectedCommand: 'TenantRepoSyncService'`, `pidFileName: 'tenant-repo-sync.pid'`, label `'tenant repo sync (cloud)'`).
- `ai/scripts/maintenance/syncTenantRepos.mjs` (NEW) — operator/CI-facing CLI for manual one-shot sweep. Supports `--repo-slug <slug>` (repeatable) for scoped subset, `--help`, and an in-memory `TaskStateService` stand-in so the CLI works without orchestrator booted state.
- `test/playwright/unit/ai/daemons/orchestrator/scheduling/tenantRepoSync.spec.mjs` (NEW) — trigger purity unit tests (6 cases: disabled/zero-interval/not-elapsed/enabled-elapsed/state-derived/bootstrap).
- `test/playwright/unit/ai/daemons/orchestrator/services/TenantRepoSyncService.spec.mjs` (NEW) — service-behavior tests (6 cases: no-config skip, re-entrancy guard, happy-path iteration, per-repo failure isolation, onlyRepoSlugs scoping, persisted-revisions round-trip).

**Persistence shape:** per-repo `lastIngestedRev` lives in a dedicated sibling JSON file (`<DEFAULT_DATA_DIR>/tenant-repo-sync-revisions.json`) — separate from TaskStateService's state.json to prevent `markCompleted/markFailed` task-lifecycle writes from racing with revision-map writes. Bootstrap: missing file = empty map.

**Test seams** (per `feedback_truth_in_code` + sibling pattern): `runTask` accepts `gitMirror`, `knowledgeBaseIngestionService`, `envelopeBuilder`, `aiConfig`, `tenantReposConfig`, `revisionsFilePath`, `onlyRepoSlugs` overrides. Production callers omit; unit tests stub.

**Stacked PR:** this PR is stacked on top of `codex/11789-ingest-envelope-builder` (the in-flight #11938 envelope-builder PR). My commits target that branch as base; once #11938 merges into `dev`, GitHub auto-retargets this PR to `dev` and the diff shows only my surface.

Test plan:
- `npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/scheduling/tenantRepoSync.spec.mjs` → 6/6 PASS (512ms)
- `npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/services/TenantRepoSyncService.spec.mjs` → 6/6 PASS (597ms)

Avoided traps (per Epic #11731 + ticket #11790):
- NOT touching `primary-dev-sync` lane — ADR 0014 §5.2 anti-pattern against re-pointing `kbSync` at tenant content preserved
- NOT inlining persistence into TaskStateService's state.json — race risk on shared file
- NOT halting on per-repo failure — isolation per ticket prescription; operator sees partial-completion in healthService payload

* chore(ci): retrigger PR #11940 checks (#11790)

* fix(test): include TENANT_REPO_SYNC_TASK_NAME in initial-task-state envelope assertion (#11790)

The createInitialTaskState envelope assertion at Orchestrator.spec.mjs:127 used exact-equality on the full ordered key list. Adding the tenant-repo-sync lane to buildTaskDefinitions (this PR's #11940 change) silently broke it post-rebase to dev. Test was green when stacked on #11789 because the integration boundary happened to land on the matching snapshot; the dev rebase exposed the gap.

Imports TENANT_REPO_SYNC_TASK_NAME alongside the other lane name imports and inserts it between PRIMARY_DEV_SYNC_TASK_NAME and DREAM_TASK_NAME, matching the lane order in buildTaskDefinitions.

* feat(orchestrator): wire tenant-repo-sync lane into Orchestrator.poll() + cloud-deployment toggle (#11790)

Closes the load-bearing Contract Ledger gap from PR #11940 cycle-1 review: the tenant-repo-sync lane was registered in TaskDefinitions but never scheduled by Orchestrator.poll(). Future-agent KB grep would see the lane as wired even though no periodic tick ever invoked it.

Changes:
- AiConfig.template.mjs: add intervals.tenantRepoSyncMs (30min default) + new orchestrator.cloudOnly collection with tenantRepoSyncEnabled (null = deployment-default: cloud enables, local disables, mirrors localOnly semantics inverted per ADR 0014 lane classification)
- Orchestrator.mjs: import TenantRepoSyncService + scheduling getDueTask + TENANT_REPO_SYNC_TASK_NAME constant; add tenantRepoSyncService + tenantRepoSyncGetDueTask instance-field DI seams; add resolveCloudOnlyEnabled helper (inverse of resolveDeploymentEnabled); add tenantRepoSyncIntervalMs + tenantRepoSyncEnabled lazy getters with NEO_ORCHESTRATOR_TENANT_REPO_SYNC_* env precedence; add cadenceEngine.runIfDue(TENANT_REPO_SYNC_TASK_NAME, ...) branch matching the PRIMARY_DEV_SYNC pattern (trigger + executor split)
- TenantRepoSyncService.mjs JSDoc lines 32-35: fix the rhetorical-drift mismatch GPT flagged; class-comment claimed all-or-fail semantics while line 213 implements partial-success (status='completed' when no repos failed OR at least one succeeded). Updated comment to document the correct partial-success contract
- Orchestrator.spec.mjs: add tenantRepoSyncEnabled/IntervalMs/Service/GetDueTask DI seam defaults in createTestOrchestrator helper (interval defaults to MAX_SAFE_INTEGER + enabled defaults to false so existing tests don't accidentally trigger); add savedCloudOnly restore in afterEach; add 2 new tests covering the load-bearing wiring: 'routes tenant-repo-sync through Orchestrator.poll() when enabled + interval elapsed (#11790 AC2 poll-wiring)' + 'skips tenant-repo-sync when disabled (local deployment default — Contract Ledger row 2)'

Deferred residuals tracked in follow-up #11942 (per-repo cadence + deterministic jitter/backoff + concurrency-limit gate + stable KB_TENANT_REPO_SYNC_* error codes). PR body for #11940 is being narrowed from `Resolves #11790` to `Refs #11790` since the original Contract Ledger has 9 surfaces and this PR delivers ~5.

Touched-spec verification: 139/139 unit tests pass (Orchestrator.spec + daemon.spec + scheduling/* + TenantRepoSyncService.spec + PrimaryRepoSyncService.spec).

* docs(orchestrator): remove ticket/Discussion/ADR-section archaeology from tenant-repo-sync JSDoc (#11790)

Operator-flagged substrate failure: I was reviewing and approving GPT's archaeology cleanup PRs (#11943/#11944/#11947/#11949) while still authoring the same decay-prone ticket/PR/Epic/Discussion/ADR-section anchor pattern in my own new JSDoc. Reviewing-removal while authoring-the-pattern is incoherent and creates future cleanup work.

Cleaned 3 files:

- ai/daemons/orchestrator/services/TenantRepoSyncService.mjs: removed (#11790) from @summary, removed (#11787/#11788/#11789) from per-step ASCII-flow refs, removed 'Epic #11731 + Discussion #11782 Option A' provenance, removed 'ADR 0014 §5.2' citation. Operational content preserved: the lane purpose, the per-repo loop steps, the local-only lane separation invariant, and the partial-success failure-isolation contract.
- ai/daemons/orchestrator/scheduling/tenantRepoSync.mjs: removed (#11790) from @summary.
- ai/scripts/maintenance/syncTenantRepos.mjs: removed (#11790) from @summary.

Tests in #11945 and #11946 use ticket-IDs only in test.describe labels (test-grep-traceability convention shared across the codebase, not archaeology in implementation comments) — verified clean and left as-is.

Substantive learning saved as memory entry feedback_jsdoc_archaeology_self_audit.

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>
Co-authored-by: Neo GPT <neo-gpt@users.noreply.github.com>"
- 2026-05-25T06:02:12Z @neo-opus-ada cross-referenced by PR #11951
- 2026-05-25T06:30:46Z @neo-opus-ada cross-referenced by PR #11952
- 2026-05-26T19:01:37Z @neo-opus-ada cross-referenced by #12036
- 2026-05-26T21:20:27Z @neo-gpt cross-referenced by PR #12050
- 2026-05-26T22:21:31Z @neo-opus-ada cross-referenced by #12052
- 2026-05-26T22:23:27Z @neo-opus-ada cross-referenced by PR #12053
- 2026-05-26T22:58:23Z @tobiu referenced in commit `1bfee42` - "docs(day0): rewrite Milestone 6 to first-class pull mode (#12052) (#12053)

The previous "Optional Clone / Server-Side Source" framing described
pull mode as "deferred to #11731" + "needs a credential-storage and
clone-trust contract before implementation." Both claims are materially
stale — #11731's substrate has shipped end-to-end: TenantRepoAccessContract
(#11787), GitMirror (#11788), envelope builder (#11789), scheduler lane
(#11790), RawRepoSource (#12030), branchRef schema (#12045), file:
credentialRef (#12046), Tier-1 mirrorRoot fallback (#12050).

Reframe + rewrite per Contract Ledger rows in the ticket:

- Heading: "Optional Pull Mode / Server-Side Tenant Repo Sync".
- Reframe push (Milestone 3) as primary + pull as additive alternative
  for orchestrator-cadence ingestion.
- Concrete pull-mode setup recipe parallel to Milestone 3: tenantRepos[]
  config snippet + credential env var + sync trigger + ask_knowledge_base
  verification.
- Failure-signatures table covering KB_GITMIRROR_CLONE_FAILED (git
  binary missing per #12036 + token scope), KB_TENANT_REPO_CREDENTIAL_REF_
  REQUIRED / KB_GITMIRROR_CREDENTIAL_REF_INVALID, KB_TENANT_REPO_MIRROR_
  ROOT_REQUIRED.
- Cross-links to TenantIngestionModel.md (decision model) +
  HookWiring.md (push-vs-pull selection).
- Custom Sources reference preserved + reframed as genuinely orthogonal
  (operator owns full-corpus build) — not a fallback for pull mode.
- Expected-result block lists three valid shapes (pull-configured /
  skipped-push-covers-it / operator-owned-Custom-Source).

Net: ~70 LOC added, ~20 removed. All ACs on #12052 satisfied; Contract
Ledger row coverage verified surface-by-surface.

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"

