fix(ingest): verify repair outcomes and reject dangling join targets (#292)

One ingest integration hiccup no longer discards a whole source:

- Replace the duplicated gate-repair and textual-resolver loops with one
  shared constrained-repair loop whose success criterion is re-running
  the failed check (verify), not whether the agent edited files. Verify
  failures feed the retry prompt; maxAttempts is 2.
- Let the resolver declare a conflicting patch redundant: a verified
  no-change resolution is accepted as subsumed instead of failing the
  source (duplicate wiki-page creation from parallel work units).
- Carry per-source validation errors through validateWuTouchedSources
  into gate messages and work-unit failure reasons instead of
  discarding them.
- Move join-neighbor expansion into the shared validation path so
  work-unit validation and integration gates check the same set.
- Reject joins whose target resolves to no source, at sl_write time and
  in the gates, attributed to the declaring source. Resolution mirrors
  the Python engine exactly (case-sensitive name within the
  connection), with a case-mismatch hint for the writing agent.
This commit is contained in:
Andrey Avtomonov 2026-06-11 14:39:51 +02:00 committed by GitHub
parent 00cdf2de90
commit a278d2f7d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 1119 additions and 705 deletions

View file

@ -1901,13 +1901,13 @@ describe('IngestBundleRunner isolated diff path', () => {
});
deps.agentRunner.runLoop = vi.fn(async (params: any) => {
if (params.telemetryTags.operationName === 'ingest-isolated-diff-textual-resolver') {
const current = await params.toolSet.read_integration_file.execute({
const current = await params.toolSet.read_repair_file.execute({
path: 'semantic-layer/warehouse/mart_account_segments.yaml',
});
expect(current.markdown).toContain('total_contract_arr_cents');
const patch = await params.toolSet.read_failed_patch.execute({});
expect(patch.markdown).toContain('account_count');
await params.toolSet.write_integration_file.execute({
await params.toolSet.write_repair_file.execute({
path: 'semantic-layer/warehouse/mart_account_segments.yaml',
content:
'name: mart_account_segments\n' +
@ -2105,7 +2105,6 @@ describe('IngestBundleRunner isolated diff path', () => {
});
const trace = await readFile(join(runtime.configDir, '.ktx/ingest-traces/job-final-gate-repair/trace.jsonl'), 'utf-8');
expect(trace).toContain('gate_repair_repaired');
expect(trace).toContain('final_artifact_gates_after_gate_repair_finished');
expect(trace).toContain('final_gate_repair_committed');
} finally {
await rm(runtime.homeDir, { recursive: true, force: true });
@ -2191,7 +2190,8 @@ describe('IngestBundleRunner isolated diff path', () => {
const reportCreate = vi.mocked(deps.reports.create).mock.calls.at(-1)?.[0] as any;
expect(reportCreate.body.status).toBe('failed');
expect(reportCreate.body.isolatedDiff).toMatchObject({
gateRepairAttempts: 1,
// Both attempts of the verify-based repair loop ran without an edit.
gateRepairAttempts: 2,
gateRepairs: 0,
gateRepairFailures: 1,
});