Sub of #16706. A first sync that legitimately has nothing to ingest is rejected as a proof failure, so the corpus can never leave zero.
Context
A plane whose Knowledge Base has never ingested (count: 0, tenant repos backoff-locked at the cadence cap) is the grounding observation of #16706, and #16799 explicitly does not explain it: that defect is post-first-ingest by construction, since a null baseRevision takes a clean first-sync path.
This ticket is the first-sync arm, found by reading the path rather than by measuring an external plane.
The Problem
assertFullMaterializationEffect (ai/daemons/orchestrator/services/TenantRepoSyncService.mjs:483):
hasEffect = [summary.ingested, summary.deleted]
.some(value => Number.isSafeInteger(value) && value > 0);
if ((hasEffect && !provesCurrentAttempt) || (!hasEffect && !provesUncommittedRetry)) {
throw new TenantRepoSyncError(KB_TENANT_REPO_SYNC_EMPTY_MATERIALIZATION, ...)
}Zero effect is treated as missing proof. But a full materialization can have zero effect for an entirely ordinary reason: the repo contains no ingestable paths. buildFullEnvelope returns files: [] with manifestSnapshot.pathsAfterPush: [], ingestion reports ingested: 0, deleted: 0, errors: [], and the run is thrown.
The consequences compound:
- The run fails, so
lastIngestedRev is never persisted.
consecutiveFailures climbs to the 2-hour cadence cap.
- Every subsequent cycle repeats it — the input never changes, so the outcome never changes. It cannot self-clear.
- The corpus stays at
count: 0 indefinitely.
And the message is wrong about the cause. "Tenant-repo full materialization produced no durable positive-effect proof" describes a receipt/proof problem. The actual condition is "this repo had nothing to ingest" — a fact about content. An operator reading that message investigates receipts, attempt ids and contract versions; none of them are involved.
ingested: 0 is reachable ordinarily: an empty repo, a repo whose files are all filtered by rootKind/parser selection, or one whose every chunk was skipped as oversized (summary.skippedOversized — counted, and not part of hasEffect).
The guard itself is correct and must stay. It exists because a real effect without proof of it is a genuine defect, observed live at ingested=50, embeddings=50, errors=0 with no receipt. The bug is that it cannot distinguish "no effect because nothing was expected" from "no effect because the proof is missing".
The Architectural Reality
| surface |
file:line |
today |
| the assertion |
TenantRepoSyncService.mjs:483 |
zero effect ⇒ throw unless it proves an uncommitted retry |
| the discriminator, already present and unused |
envelope.manifestSnapshot.pathsAfterPush |
the paths the materialization claimed would exist |
| envelope producer |
tenantRepoIngestEnvelopeBuilder.buildFullEnvelope |
emits pathsAfterPush: paths, empty when the repo has none |
The evidence needed is already in the envelope: pathsAfterPush is the claim, ingested is the outcome. Zero against an empty claim is agreement; zero against a non-empty claim is the failure the guard was built for.
The Fix
Discriminate on the claim, not on the effect alone.
pathsAfterPush.length === 0 and no errors ⇒ a legitimately empty materialization. Complete the run, persist the checkpoint, do not charge a failure.
pathsAfterPush.length > 0 with zero effect ⇒ unchanged, still throws. The guard keeps its teeth for the case it exists for.
hasEffect without proof ⇒ unchanged.
Contract Ledger
| Target surface |
Source of authority |
Proposed behavior |
Fallback |
Docs |
Evidence |
assertFullMaterializationEffect |
TenantRepoSyncService.mjs:483 |
admit zero-effect only when pathsAfterPush is empty and no errors |
non-empty claim ⇒ throw as today |
— |
verified: hasEffect reads only ingested/deleted |
pathsAfterPush |
buildFullEnvelope |
read as the expectation |
absent manifest ⇒ returns null early, unchanged |
— |
verified present on every full envelope |
Decision Record impact: none.
Acceptance Criteria
Out of Scope
- Whether a given external plane exhibits this. It is unmeasured there, and this ticket does not claim it.
#16799 (the post-first-ingest revision-boundary arm) and #16780 (embedding work bounds).
- Changing what counts as ingestable — parser selection and oversize skipping are unchanged.
Avoided Traps
- Admitting every zero-effect materialization. The obvious one-line "fix", and it retires a guard built for a live defect where 50 ingested rows had no proof. The discriminator has to be the claim, not the effect.
- Treating
skippedOversized > 0 as effect. Skipping is not ingesting; a repo whose every file is too large has genuinely produced nothing and should say so rather than report success.
- Fixing the message only. A clearer error still leaves the repo permanently unsyncable and the corpus at zero.
Related
Origin Session ID: d8332b13-5d97-4839-ac11-d2de4602a989
Retrieval Hint: query_raw_memories("EMPTY_MATERIALIZATION zero effect empty pathsAfterPush first sync corpus stays zero")
🖖 Grace (Claude Opus 5, Claude Code)
Sub of #16706. A first sync that legitimately has nothing to ingest is rejected as a proof failure, so the corpus can never leave zero.
Context
A plane whose Knowledge Base has never ingested (
count: 0, tenant repos backoff-locked at the cadence cap) is the grounding observation of #16706, and #16799 explicitly does not explain it: that defect is post-first-ingest by construction, since a nullbaseRevisiontakes a clean first-sync path.This ticket is the first-sync arm, found by reading the path rather than by measuring an external plane.
The Problem
assertFullMaterializationEffect(ai/daemons/orchestrator/services/TenantRepoSyncService.mjs:483):hasEffect = [summary.ingested, summary.deleted] .some(value => Number.isSafeInteger(value) && value > 0); if ((hasEffect && !provesCurrentAttempt) || (!hasEffect && !provesUncommittedRetry)) { throw new TenantRepoSyncError(KB_TENANT_REPO_SYNC_EMPTY_MATERIALIZATION, ...) }Zero effect is treated as missing proof. But a full materialization can have zero effect for an entirely ordinary reason: the repo contains no ingestable paths.
buildFullEnvelopereturnsfiles: []withmanifestSnapshot.pathsAfterPush: [], ingestion reportsingested: 0, deleted: 0, errors: [], and the run is thrown.The consequences compound:
lastIngestedRevis never persisted.consecutiveFailuresclimbs to the 2-hour cadence cap.count: 0indefinitely.And the message is wrong about the cause. "Tenant-repo full materialization produced no durable positive-effect proof" describes a receipt/proof problem. The actual condition is "this repo had nothing to ingest" — a fact about content. An operator reading that message investigates receipts, attempt ids and contract versions; none of them are involved.
ingested: 0is reachable ordinarily: an empty repo, a repo whose files are all filtered byrootKind/parser selection, or one whose every chunk was skipped as oversized (summary.skippedOversized— counted, and not part ofhasEffect).The guard itself is correct and must stay. It exists because a real effect without proof of it is a genuine defect, observed live at
ingested=50, embeddings=50, errors=0with no receipt. The bug is that it cannot distinguish "no effect because nothing was expected" from "no effect because the proof is missing".The Architectural Reality
TenantRepoSyncService.mjs:483envelope.manifestSnapshot.pathsAfterPushtenantRepoIngestEnvelopeBuilder.buildFullEnvelopepathsAfterPush: paths, empty when the repo has noneThe evidence needed is already in the envelope:
pathsAfterPushis the claim,ingestedis the outcome. Zero against an empty claim is agreement; zero against a non-empty claim is the failure the guard was built for.The Fix
Discriminate on the claim, not on the effect alone.
pathsAfterPush.length === 0and no errors ⇒ a legitimately empty materialization. Complete the run, persist the checkpoint, do not charge a failure.pathsAfterPush.length > 0with zero effect ⇒ unchanged, still throws. The guard keeps its teeth for the case it exists for.hasEffectwithout proof ⇒ unchanged.Contract Ledger
assertFullMaterializationEffectTenantRepoSyncService.mjs:483pathsAfterPushis empty and no errorshasEffectreads onlyingested/deletedpathsAfterPushbuildFullEnvelopenullearly, unchangedDecision Record impact:
none.Acceptance Criteria
pathsAfterPush: [],ingested: 0,deleted: 0, no errors completes and returns without throwing.consecutiveFailuresis not incremented for it.pathsAfterPush: ['a.md']withingested: 0still throwsKB_TENANT_REPO_SYNC_EMPTY_MATERIALIZATION. This is the load-bearing AC — a change that admits every zero-effect run would silently retire the guard, which exists for an observed live defect.Out of Scope
#16799(the post-first-ingest revision-boundary arm) and#16780(embedding work bounds).Avoided Traps
skippedOversized > 0as effect. Skipping is not ingesting; a repo whose every file is too large has genuinely produced nothing and should say so rather than report success.Related
Origin Session ID: d8332b13-5d97-4839-ac11-d2de4602a989
Retrieval Hint:
query_raw_memories("EMPTY_MATERIALIZATION zero effect empty pathsAfterPush first sync corpus stays zero")🖖 Grace (Claude Opus 5, Claude Code)