@kaelio/ktx 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/python/{kaelio_ktx-0.2.0-py3-none-any.whl → kaelio_ktx-0.4.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/admin-reindex.js +10 -17
- package/dist/admin-reindex.test.js +1 -1
- package/dist/cli-program.test.js +0 -2
- package/dist/cli-project.d.ts +18 -0
- package/dist/cli-project.js +52 -0
- package/dist/cli-project.test.js +149 -0
- package/dist/cli-runtime.d.ts +0 -2
- package/dist/cli-runtime.js +2 -8
- package/dist/commands/runtime-commands.js +2 -2
- package/dist/context-build-view.js +1 -1
- package/dist/index.test.js +21 -25
- package/dist/ingest.js +9 -2
- package/dist/ingest.test.js +27 -3
- package/dist/managed-local-embeddings.d.ts +0 -2
- package/dist/managed-local-embeddings.js +2 -5
- package/dist/managed-local-embeddings.test.js +5 -8
- package/dist/managed-python-daemon.js +2 -2
- package/dist/managed-python-daemon.test.js +1 -1
- package/dist/managed-python-http.js +3 -3
- package/dist/managed-python-http.test.js +6 -6
- package/dist/print-command-tree.js +0 -2
- package/dist/public-ingest.d.ts +4 -2
- package/dist/public-ingest.js +9 -3
- package/dist/release-version.d.ts +1 -5
- package/dist/release-version.js +2 -39
- package/dist/runtime-requirements.js +1 -1
- package/dist/runtime.js +6 -6
- package/dist/runtime.test.js +7 -7
- package/dist/scan.js +7 -2
- package/dist/scan.test.js +1 -1
- package/dist/setup-embeddings.js +1 -1
- package/dist/setup-embeddings.test.js +2 -2
- package/dist/setup-runtime.test.js +1 -1
- package/node_modules/@ktx/context/dist/core/git.service.d.ts +1 -0
- package/node_modules/@ktx/context/dist/core/git.service.js +12 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.d.ts +2 -1
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.js +18 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/local-ingest-acceptance.test.js +6 -6
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.d.ts +5 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.js +48 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.test.js +83 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/live-database/daemon-introspection.js +4 -1
- package/node_modules/@ktx/context/dist/ingest/adapters/live-database/daemon-introspection.test.js +32 -0
- package/node_modules/@ktx/context/dist/ingest/finalization-scope.d.ts +22 -0
- package/node_modules/@ktx/context/dist/ingest/finalization-scope.js +95 -0
- package/node_modules/@ktx/context/dist/ingest/finalization-scope.test.d.ts +1 -0
- package/node_modules/@ktx/context/dist/ingest/finalization-scope.test.js +114 -0
- package/node_modules/@ktx/context/dist/ingest/index.d.ts +1 -2
- package/node_modules/@ktx/context/dist/ingest/index.js +0 -1
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.d.ts +2 -0
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.isolated-diff.test.js +166 -0
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.js +235 -45
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.test.js +193 -38
- package/node_modules/@ktx/context/dist/ingest/local-bundle-ingest.test.js +22 -3
- package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.js +0 -4
- package/node_modules/@ktx/context/dist/ingest/local-ingest.js +0 -7
- package/node_modules/@ktx/context/dist/ingest/local-stage-ingest.js +15 -5
- package/node_modules/@ktx/context/dist/ingest/local-stage-ingest.test.js +29 -0
- package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.d.ts +2 -2
- package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.js +1 -1
- package/node_modules/@ktx/context/dist/ingest/memory-flow/types.d.ts +1 -1
- package/node_modules/@ktx/context/dist/ingest/ports.d.ts +1 -20
- package/node_modules/@ktx/context/dist/ingest/report-snapshot.d.ts +71 -0
- package/node_modules/@ktx/context/dist/ingest/report-snapshot.js +27 -0
- package/node_modules/@ktx/context/dist/ingest/reports.d.ts +23 -5
- package/node_modules/@ktx/context/dist/ingest/reports.js +7 -24
- package/node_modules/@ktx/context/dist/ingest/types.d.ts +33 -0
- package/node_modules/@ktx/context/dist/llm/index.d.ts +1 -1
- package/node_modules/@ktx/context/dist/llm/index.js +1 -1
- package/node_modules/@ktx/context/dist/llm/local-config.d.ts +0 -1
- package/node_modules/@ktx/context/dist/llm/local-config.js +2 -12
- package/node_modules/@ktx/context/dist/llm/local-config.test.js +2 -23
- package/node_modules/@ktx/context/dist/package-exports.test.js +2 -2
- package/node_modules/@ktx/context/dist/project/config.d.ts +16 -0
- package/node_modules/@ktx/context/dist/project/driver-schemas.d.ts +8 -0
- package/node_modules/@ktx/context/dist/project/driver-schemas.js +4 -0
- package/node_modules/@ktx/context/dist/scan/enabled-tables.d.ts +3 -0
- package/node_modules/@ktx/context/dist/scan/enabled-tables.js +15 -0
- package/node_modules/@ktx/context/dist/scan/local-scan.d.ts +2 -4
- package/node_modules/@ktx/context/dist/scan/local-scan.js +2 -15
- package/package.json +1 -1
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.d.ts +0 -4
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.js +0 -38
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.js +0 -63
- /package/{node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.d.ts → dist/cli-project.test.d.ts} +0 -0
|
@@ -104,6 +104,24 @@ function makeWikiService(root) {
|
|
|
104
104
|
content: content.trim(),
|
|
105
105
|
};
|
|
106
106
|
}),
|
|
107
|
+
writePage: vi.fn(async (_scope, _scopeId, key, frontmatter, content) => {
|
|
108
|
+
await mkdir(join(root, 'wiki/global'), { recursive: true });
|
|
109
|
+
const refs = (frontmatter.refs ?? []).map((ref) => ` - ${ref}`).join('\n');
|
|
110
|
+
const slRefs = (frontmatter.sl_refs ?? []).map((ref) => ` - ${ref}`).join('\n');
|
|
111
|
+
await writeFile(join(root, 'wiki/global', `${key}.md`), [
|
|
112
|
+
'---',
|
|
113
|
+
`summary: ${frontmatter.summary ?? key}`,
|
|
114
|
+
`usage_mode: ${frontmatter.usage_mode ?? 'auto'}`,
|
|
115
|
+
'refs:',
|
|
116
|
+
refs,
|
|
117
|
+
'sl_refs:',
|
|
118
|
+
slRefs,
|
|
119
|
+
'---',
|
|
120
|
+
'',
|
|
121
|
+
content,
|
|
122
|
+
'',
|
|
123
|
+
].join('\n'));
|
|
124
|
+
}),
|
|
107
125
|
syncFromCommit: vi.fn(),
|
|
108
126
|
};
|
|
109
127
|
}
|
|
@@ -1758,4 +1776,152 @@ describe('IngestBundleRunner isolated diff path', () => {
|
|
|
1758
1776
|
await rm(runtime.homeDir, { recursive: true, force: true });
|
|
1759
1777
|
}
|
|
1760
1778
|
});
|
|
1779
|
+
it('runs finalization before wiki sl-ref repair and final gates', async () => {
|
|
1780
|
+
const runtime = await makeRealGitRuntime();
|
|
1781
|
+
try {
|
|
1782
|
+
const { deps, adapter } = makeDeps(runtime);
|
|
1783
|
+
adapter.chunk.mockResolvedValue({
|
|
1784
|
+
workUnits: [{ unitKey: 'wiki-page', rawFiles: ['cards/source.json'], peerFileIndex: [], dependencyPaths: [] }],
|
|
1785
|
+
});
|
|
1786
|
+
adapter.finalize = vi.fn(async ({ workdir }) => {
|
|
1787
|
+
await mkdir(join(workdir, 'semantic-layer/warehouse'), { recursive: true });
|
|
1788
|
+
await mkdir(join(workdir, 'wiki/global'), { recursive: true });
|
|
1789
|
+
await writeFile(join(workdir, 'semantic-layer/warehouse/mart_account_segments.yaml'), 'name: mart_account_segments\ngrain: [account_id]\ncolumns: [{name: account_id, type: string}]\njoins: []\nmeasures:\n - name: total_contract_arr\n expr: sum(contract_arr)\n');
|
|
1790
|
+
await writeFile(join(workdir, 'wiki/global/finalized-accounts.md'), '---\nsummary: Finalized accounts\nusage_mode: auto\nsl_refs:\n - mart_account_segments\n - missing_source\n---\n\nAccounts use `mart_account_segments.total_contract_arr`.\n');
|
|
1791
|
+
return {
|
|
1792
|
+
warnings: [],
|
|
1793
|
+
errors: [],
|
|
1794
|
+
touchedSources: [{ connectionId: 'warehouse', sourceName: 'mart_account_segments' }],
|
|
1795
|
+
changedWikiPageKeys: ['finalized-accounts'],
|
|
1796
|
+
actions: [
|
|
1797
|
+
{
|
|
1798
|
+
target: 'sl',
|
|
1799
|
+
type: 'created',
|
|
1800
|
+
key: 'mart_account_segments',
|
|
1801
|
+
detail: 'Finalized accounts',
|
|
1802
|
+
targetConnectionId: 'warehouse',
|
|
1803
|
+
rawPaths: ['cards/source.json'],
|
|
1804
|
+
},
|
|
1805
|
+
{
|
|
1806
|
+
target: 'wiki',
|
|
1807
|
+
type: 'created',
|
|
1808
|
+
key: 'finalized-accounts',
|
|
1809
|
+
detail: 'Finalized wiki',
|
|
1810
|
+
rawPaths: ['cards/source.json'],
|
|
1811
|
+
},
|
|
1812
|
+
],
|
|
1813
|
+
};
|
|
1814
|
+
});
|
|
1815
|
+
deps.agentRunner.runLoop = vi.fn(async () => ({ stopReason: 'natural' }));
|
|
1816
|
+
const runner = new IngestBundleRunner(deps);
|
|
1817
|
+
await mockStageRawFiles(runner, runtime, [['cards/source.json', 'h1']]);
|
|
1818
|
+
await runner.run({
|
|
1819
|
+
jobId: 'job-finalization',
|
|
1820
|
+
connectionId: 'warehouse',
|
|
1821
|
+
sourceKey: 'metabase',
|
|
1822
|
+
trigger: 'upload',
|
|
1823
|
+
bundleRef: { kind: 'upload', uploadId: 'upload' },
|
|
1824
|
+
});
|
|
1825
|
+
const trace = await readFile(join(runtime.configDir, '.ktx/ingest-traces/job-finalization/trace.jsonl'), 'utf-8');
|
|
1826
|
+
expect(trace.indexOf('finalization_committed')).toBeLessThan(trace.indexOf('wiki_sl_refs_repaired'));
|
|
1827
|
+
expect(trace.indexOf('wiki_sl_refs_repaired')).toBeLessThan(trace.indexOf('final_artifact_gates'));
|
|
1828
|
+
await expect(readFile(join(runtime.configDir, 'wiki/global/finalized-accounts.md'), 'utf-8')).resolves.toContain('sl_refs:\n - mart_account_segments');
|
|
1829
|
+
}
|
|
1830
|
+
finally {
|
|
1831
|
+
await rm(runtime.homeDir, { recursive: true, force: true });
|
|
1832
|
+
}
|
|
1833
|
+
});
|
|
1834
|
+
it('fails when finalization edits a path already changed earlier in the run', async () => {
|
|
1835
|
+
const runtime = await makeRealGitRuntime();
|
|
1836
|
+
try {
|
|
1837
|
+
const { deps, adapter } = makeDeps(runtime);
|
|
1838
|
+
adapter.chunk.mockResolvedValue({
|
|
1839
|
+
workUnits: [{ unitKey: 'wiki-page', rawFiles: ['cards/source.json'], peerFileIndex: [], dependencyPaths: [] }],
|
|
1840
|
+
});
|
|
1841
|
+
let currentSession = null;
|
|
1842
|
+
deps.toolsetFactory.createIngestWuToolset = vi.fn((toolSession) => {
|
|
1843
|
+
currentSession = toolSession;
|
|
1844
|
+
return { toRuntimeTools: vi.fn(() => ({})) };
|
|
1845
|
+
});
|
|
1846
|
+
deps.agentRunner.runLoop = vi.fn(async () => {
|
|
1847
|
+
const root = rootOfConfig(currentSession.configService, runtime.configDir);
|
|
1848
|
+
await mkdir(join(root, 'wiki/global'), { recursive: true });
|
|
1849
|
+
await writeFile(join(root, 'wiki/global/orders.md'), '---\nsummary: Orders\nusage_mode: auto\n---\n\nWU body\n');
|
|
1850
|
+
currentSession.actions.push({
|
|
1851
|
+
target: 'wiki',
|
|
1852
|
+
type: 'created',
|
|
1853
|
+
key: 'orders',
|
|
1854
|
+
detail: 'WU orders',
|
|
1855
|
+
rawPaths: ['cards/source.json'],
|
|
1856
|
+
});
|
|
1857
|
+
await currentSession.gitService.commitFiles(['wiki/global/orders.md'], 'wu orders', 'KTX Test', 'system@ktx.local');
|
|
1858
|
+
return { stopReason: 'natural' };
|
|
1859
|
+
});
|
|
1860
|
+
adapter.finalize = vi.fn(async ({ workdir }) => {
|
|
1861
|
+
await writeFile(join(workdir, 'wiki/global/orders.md'), '---\nsummary: Orders\nusage_mode: auto\n---\n\nFinalized body\n');
|
|
1862
|
+
return {
|
|
1863
|
+
warnings: [],
|
|
1864
|
+
errors: [],
|
|
1865
|
+
touchedSources: [],
|
|
1866
|
+
changedWikiPageKeys: ['orders'],
|
|
1867
|
+
actions: [{ target: 'wiki', type: 'updated', key: 'orders', detail: 'Conflicting finalization' }],
|
|
1868
|
+
};
|
|
1869
|
+
});
|
|
1870
|
+
const runner = new IngestBundleRunner(deps);
|
|
1871
|
+
await mockStageRawFiles(runner, runtime, [['cards/source.json', 'h1']]);
|
|
1872
|
+
await expect(runner.run({
|
|
1873
|
+
jobId: 'job-finalization-overlap',
|
|
1874
|
+
connectionId: 'warehouse',
|
|
1875
|
+
sourceKey: 'metabase',
|
|
1876
|
+
trigger: 'upload',
|
|
1877
|
+
bundleRef: { kind: 'upload', uploadId: 'upload' },
|
|
1878
|
+
})).rejects.toThrow(/finalization modified path\(s\) already changed earlier in this run: wiki\/global\/orders\.md/);
|
|
1879
|
+
}
|
|
1880
|
+
finally {
|
|
1881
|
+
await rm(runtime.homeDir, { recursive: true, force: true });
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
it('rejects finalization writes to unauthorized semantic-layer targets', async () => {
|
|
1885
|
+
const runtime = await makeRealGitRuntime();
|
|
1886
|
+
try {
|
|
1887
|
+
const { deps, adapter } = makeDeps(runtime);
|
|
1888
|
+
adapter.chunk.mockResolvedValue({ workUnits: [] });
|
|
1889
|
+
adapter.finalize = vi.fn(async ({ workdir }) => {
|
|
1890
|
+
await mkdir(join(workdir, 'semantic-layer/other-warehouse'), { recursive: true });
|
|
1891
|
+
await writeFile(join(workdir, 'semantic-layer/other-warehouse/orders.yaml'), 'name: orders\ngrain: [order_id]\ncolumns: [{name: order_id, type: string}]\njoins: []\nmeasures: []\n');
|
|
1892
|
+
return {
|
|
1893
|
+
warnings: [],
|
|
1894
|
+
errors: [],
|
|
1895
|
+
touchedSources: [{ connectionId: 'other-warehouse', sourceName: 'orders' }],
|
|
1896
|
+
changedWikiPageKeys: [],
|
|
1897
|
+
actions: [
|
|
1898
|
+
{
|
|
1899
|
+
target: 'sl',
|
|
1900
|
+
type: 'created',
|
|
1901
|
+
key: 'orders',
|
|
1902
|
+
targetConnectionId: 'other-warehouse',
|
|
1903
|
+
detail: 'Forbidden target',
|
|
1904
|
+
rawPaths: ['cards/source.json'],
|
|
1905
|
+
},
|
|
1906
|
+
],
|
|
1907
|
+
};
|
|
1908
|
+
});
|
|
1909
|
+
const runner = new IngestBundleRunner(deps);
|
|
1910
|
+
await mockStageRawFiles(runner, runtime, [['cards/source.json', 'h1']]);
|
|
1911
|
+
await expect(runner.run({
|
|
1912
|
+
jobId: 'job-finalization-target-policy',
|
|
1913
|
+
connectionId: 'warehouse',
|
|
1914
|
+
sourceKey: 'metabase',
|
|
1915
|
+
trigger: 'upload',
|
|
1916
|
+
bundleRef: { kind: 'upload', uploadId: 'upload' },
|
|
1917
|
+
})).rejects.toThrow(/semantic-layer target connection not allowed/);
|
|
1918
|
+
const trace = await readFile(join(runtime.configDir, '.ktx/ingest-traces/job-finalization-target-policy/trace.jsonl'), 'utf-8');
|
|
1919
|
+
expect(trace).toContain('finalization_committed');
|
|
1920
|
+
expect(trace).toContain('semantic_layer_target_policy');
|
|
1921
|
+
expect(trace).toContain('ingest_failed');
|
|
1922
|
+
}
|
|
1923
|
+
finally {
|
|
1924
|
+
await rm(runtime.homeDir, { recursive: true, force: true });
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1761
1927
|
});
|
|
@@ -11,13 +11,14 @@ import { NOTION_DEFAULT_MAX_KNOWLEDGE_CREATES_PER_RUN } from './adapters/notion/
|
|
|
11
11
|
import { validateFinalIngestArtifacts, validateProvenanceRawPaths } from './artifact-gates.js';
|
|
12
12
|
import { selectRelevantCanonicalPins } from './canonical-pins.js';
|
|
13
13
|
import { finalGateRepairPaths, repairFinalGateFailure } from './final-gate-repair.js';
|
|
14
|
+
import { compareFinalizationDeclarations, deriveFinalizationTouchedSources, deriveFinalizationWikiPageKeys, } from './finalization-scope.js';
|
|
14
15
|
import { FileIngestTraceWriter, ingestTracePathForJob, traceTimed } from './ingest-trace.js';
|
|
15
16
|
import { integrateWorkUnitPatch } from './isolated-diff/patch-integrator.js';
|
|
16
17
|
import { resolveTextualConflict } from './isolated-diff/textual-conflict-resolver.js';
|
|
17
18
|
import { runIsolatedWorkUnit } from './isolated-diff/work-unit-executor.js';
|
|
18
19
|
import { sanitizeMemoryFlowError } from './memory-flow/live-buffer.js';
|
|
19
20
|
import { buildSyncId, rawSourcesDirForSync } from './raw-sources-paths.js';
|
|
20
|
-
import { buildStageIndexFromReportBody,
|
|
21
|
+
import { buildStageIndexFromReportBody, } from './reports.js';
|
|
21
22
|
import { buildReconcileSystemPrompt, buildReconcileToolSet, buildReconcileUserPrompt, } from './stages/build-reconcile-context.js';
|
|
22
23
|
import { buildWuSystemPrompt, buildWuToolSet, buildWuUserPrompt } from './stages/build-wu-context.js';
|
|
23
24
|
import { stageRawFilesStage1 } from './stages/stage-1-stage-raw-files.js';
|
|
@@ -286,6 +287,15 @@ export class IngestBundleRunner {
|
|
|
286
287
|
}
|
|
287
288
|
return false;
|
|
288
289
|
}
|
|
290
|
+
async loadSourcesByConnection(workdir, connectionIds) {
|
|
291
|
+
const service = this.deps.semanticLayerService.forWorktree(workdir);
|
|
292
|
+
const result = new Map();
|
|
293
|
+
for (const connectionId of connectionIds) {
|
|
294
|
+
const { sources } = await service.loadAllSources(connectionId);
|
|
295
|
+
result.set(connectionId, sources);
|
|
296
|
+
}
|
|
297
|
+
return result;
|
|
298
|
+
}
|
|
289
299
|
resolveContextCuratorBudget(bundleRef, stageIndex) {
|
|
290
300
|
const rawConfig = bundleRef.kind === 'scheduled_pull' && bundleRef.config && typeof bundleRef.config === 'object'
|
|
291
301
|
? bundleRef.config
|
|
@@ -374,6 +384,15 @@ export class IngestBundleRunner {
|
|
|
374
384
|
});
|
|
375
385
|
}
|
|
376
386
|
});
|
|
387
|
+
input.finalizationActions.forEach((action, actionIndex) => {
|
|
388
|
+
for (const rawPath of action.rawPaths ?? []) {
|
|
389
|
+
pushActionProvenance(rawPath, action, {
|
|
390
|
+
source: 'finalization_action',
|
|
391
|
+
actionIndex,
|
|
392
|
+
action,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
});
|
|
377
396
|
(input.stageIndex.artifactResolutions ?? []).forEach((resolution, resolutionIndex) => {
|
|
378
397
|
const hash = input.currentHashes.get(resolution.rawPath) ?? '';
|
|
379
398
|
pushRow({
|
|
@@ -412,6 +431,29 @@ export class IngestBundleRunner {
|
|
|
412
431
|
}
|
|
413
432
|
return { rows, diagnostics };
|
|
414
433
|
}
|
|
434
|
+
partitionFinalizationActionsForProvenance(input) {
|
|
435
|
+
const defensible = new Set([
|
|
436
|
+
...input.currentRawPaths,
|
|
437
|
+
...input.currentEvictionRawPaths,
|
|
438
|
+
...input.overrideEvictionRawPaths,
|
|
439
|
+
]);
|
|
440
|
+
const actions = [];
|
|
441
|
+
const exclusions = [];
|
|
442
|
+
for (const action of input.actions) {
|
|
443
|
+
const rawPaths = action.rawPaths ?? [];
|
|
444
|
+
if (rawPaths.length === 0) {
|
|
445
|
+
exclusions.push({ action, reason: 'missing_raw_paths' });
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
const invalidRawPaths = rawPaths.filter((rawPath) => !defensible.has(rawPath)).sort();
|
|
449
|
+
if (invalidRawPaths.length > 0) {
|
|
450
|
+
exclusions.push({ action, reason: 'raw_path_not_defensible', invalidRawPaths });
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
actions.push(action);
|
|
454
|
+
}
|
|
455
|
+
return { actions, exclusions };
|
|
456
|
+
}
|
|
415
457
|
toReportProvenanceRows(rows) {
|
|
416
458
|
return rows.map(({ rawPath, artifactKind, artifactKey, actionType, targetConnectionId }) => ({
|
|
417
459
|
rawPath,
|
|
@@ -697,6 +739,7 @@ export class IngestBundleRunner {
|
|
|
697
739
|
let latestEvictionInputs = [];
|
|
698
740
|
let latestUnresolvedCards = [];
|
|
699
741
|
let latestReportProvenanceRows = [];
|
|
742
|
+
let latestFinalizationOutcome;
|
|
700
743
|
let activeFailureDetails;
|
|
701
744
|
let latestIsolatedDiffSummary;
|
|
702
745
|
await trace.event('info', 'run', 'ingest_started', {
|
|
@@ -848,7 +891,7 @@ export class IngestBundleRunner {
|
|
|
848
891
|
let unresolvedCards;
|
|
849
892
|
let sourceContextReport;
|
|
850
893
|
let parseArtifacts;
|
|
851
|
-
let
|
|
894
|
+
let finalizationOutcome;
|
|
852
895
|
let wikiSlRefRepairResult = null;
|
|
853
896
|
let reconcileNotes = [];
|
|
854
897
|
let triageResult = null;
|
|
@@ -1491,55 +1534,187 @@ export class IngestBundleRunner {
|
|
|
1491
1534
|
artifactResolutionCount: stageIndex.artifactResolutions?.length ?? 0,
|
|
1492
1535
|
});
|
|
1493
1536
|
await stage4?.updateProgress(1.0, reconcileOutcome.skipped ? 'No reconciliation needed' : 'Reconciled');
|
|
1494
|
-
const
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1537
|
+
const preFinalizationSha = await sessionWorktree.git.revParseHead();
|
|
1538
|
+
const preFinalizationSourcesByConnection = await this.loadSourcesByConnection(sessionWorktree.workdir, slConnectionIds);
|
|
1539
|
+
let finalizationActions = [];
|
|
1540
|
+
let finalizationTouchedPaths = [];
|
|
1541
|
+
let finalizationTouchedSources = [];
|
|
1542
|
+
let finalizationChangedWikiPageKeys = [];
|
|
1543
|
+
let finalizationSha = null;
|
|
1544
|
+
activePhase = 'finalization';
|
|
1545
|
+
if (adapter.finalize) {
|
|
1546
|
+
const stageFinalization = ctx?.startPhase(0.04);
|
|
1547
|
+
emitStageProgress('finalization', 87, 'Running deterministic finalization');
|
|
1548
|
+
await stageFinalization?.updateProgress(0.0, 'Running deterministic finalization');
|
|
1549
|
+
await runTrace.event('debug', 'finalization', 'finalization_started', { sourceKey: job.sourceKey });
|
|
1550
|
+
const result = await adapter.finalize({
|
|
1551
|
+
connectionId: job.connectionId,
|
|
1552
|
+
sourceKey: job.sourceKey,
|
|
1553
|
+
syncId,
|
|
1554
|
+
jobId: job.jobId,
|
|
1555
|
+
runId: createdRunRow.id,
|
|
1556
|
+
stagedDir,
|
|
1557
|
+
workdir: sessionWorktree.workdir,
|
|
1558
|
+
...(overrideReport ? {} : { parseArtifacts }),
|
|
1559
|
+
stageIndex,
|
|
1560
|
+
workUnitOutcomes,
|
|
1561
|
+
reconciliationActions: reconcileActions,
|
|
1562
|
+
...(overrideReport
|
|
1563
|
+
? {
|
|
1564
|
+
overrideReplay: {
|
|
1565
|
+
priorJobId: overrideReport.jobId,
|
|
1566
|
+
priorRunId: overrideReport.runId,
|
|
1567
|
+
priorSyncId: overrideReport.body.syncId,
|
|
1568
|
+
evictionRawPaths: overrideReport.body.evictionInputs,
|
|
1569
|
+
},
|
|
1570
|
+
}
|
|
1571
|
+
: {}),
|
|
1572
|
+
});
|
|
1573
|
+
if (result.errors.length > 0) {
|
|
1574
|
+
finalizationOutcome = {
|
|
1511
1575
|
sourceKey: job.sourceKey,
|
|
1512
|
-
status:
|
|
1576
|
+
status: 'failed',
|
|
1577
|
+
commitSha: null,
|
|
1578
|
+
touchedPaths: [],
|
|
1579
|
+
declaredTouchedSources: result.touchedSources,
|
|
1580
|
+
derivedTouchedSources: [],
|
|
1581
|
+
declaredChangedWikiPageKeys: result.changedWikiPageKeys,
|
|
1582
|
+
derivedChangedWikiPageKeys: [],
|
|
1583
|
+
mismatches: [],
|
|
1513
1584
|
result: result.result,
|
|
1514
1585
|
errors: result.errors,
|
|
1515
1586
|
warnings: result.warnings,
|
|
1516
|
-
|
|
1587
|
+
actions: result.actions ?? [],
|
|
1588
|
+
provenanceExclusions: [],
|
|
1517
1589
|
};
|
|
1518
|
-
|
|
1519
|
-
await
|
|
1590
|
+
latestFinalizationOutcome = finalizationOutcome;
|
|
1591
|
+
await runTrace.event('error', 'finalization', 'finalization_failed', {
|
|
1592
|
+
sourceKey: job.sourceKey,
|
|
1593
|
+
errors: result.errors,
|
|
1594
|
+
warnings: result.warnings,
|
|
1595
|
+
});
|
|
1596
|
+
throw new Error(`deterministic finalization failed: ${result.errors.join('; ')}`);
|
|
1520
1597
|
}
|
|
1521
|
-
|
|
1522
|
-
|
|
1598
|
+
const changedBeforeFinalization = new Set([
|
|
1599
|
+
...projectionTouchedPaths,
|
|
1600
|
+
...workUnitOutcomes.flatMap((outcome) => outcome.patchTouchedPaths ?? []),
|
|
1601
|
+
...(preReconciliationSha && preFinalizationSha !== preReconciliationSha
|
|
1602
|
+
? (await sessionWorktree.git.diffNameStatus(preReconciliationSha, preFinalizationSha)).map((entry) => entry.path)
|
|
1603
|
+
: []),
|
|
1604
|
+
]);
|
|
1605
|
+
finalizationTouchedPaths = await sessionWorktree.git.changedPaths();
|
|
1606
|
+
const overlapping = finalizationTouchedPaths.filter((path) => changedBeforeFinalization.has(path));
|
|
1607
|
+
if (overlapping.length > 0) {
|
|
1608
|
+
await runTrace.event('error', 'finalization', 'finalization_failed', {
|
|
1609
|
+
sourceKey: job.sourceKey,
|
|
1610
|
+
reason: 'path_overlap',
|
|
1611
|
+
overlappingPaths: overlapping.sort(),
|
|
1612
|
+
});
|
|
1613
|
+
throw new Error(`finalization modified path(s) already changed earlier in this run: ${overlapping.sort().join(', ')}`);
|
|
1614
|
+
}
|
|
1615
|
+
const finalizationCommit = finalizationTouchedPaths.length > 0
|
|
1616
|
+
? await sessionWorktree.git.commitFiles(finalizationTouchedPaths, `ingest(${job.sourceKey}): deterministic finalization syncId=${syncId}`, this.deps.storage.systemGitAuthor.name, this.deps.storage.systemGitAuthor.email)
|
|
1617
|
+
: await sessionWorktree.git.commitStaged(`ingest(${job.sourceKey}): deterministic finalization syncId=${syncId}`, this.deps.storage.systemGitAuthor.name, this.deps.storage.systemGitAuthor.email);
|
|
1618
|
+
finalizationSha = finalizationCommit.created ? finalizationCommit.commitHash : null;
|
|
1619
|
+
const postFinalizationSha = await sessionWorktree.git.revParseHead();
|
|
1620
|
+
finalizationTouchedPaths =
|
|
1621
|
+
preFinalizationSha !== postFinalizationSha
|
|
1622
|
+
? (await sessionWorktree.git.diffNameStatus(preFinalizationSha, postFinalizationSha)).map((entry) => entry.path)
|
|
1623
|
+
: [];
|
|
1624
|
+
const changedConnectionIds = [
|
|
1625
|
+
...new Set([
|
|
1626
|
+
...slConnectionIds,
|
|
1627
|
+
...finalizationTouchedPaths
|
|
1628
|
+
.filter((path) => path.startsWith('semantic-layer/'))
|
|
1629
|
+
.map((path) => path.split('/')[1])
|
|
1630
|
+
.filter((connectionId) => Boolean(connectionId)),
|
|
1631
|
+
]),
|
|
1632
|
+
].sort();
|
|
1633
|
+
const postFinalizationSourcesByConnection = await this.loadSourcesByConnection(sessionWorktree.workdir, changedConnectionIds);
|
|
1634
|
+
const scope = await deriveFinalizationTouchedSources({
|
|
1635
|
+
changedPaths: finalizationTouchedPaths,
|
|
1636
|
+
beforeSourcesByConnection: preFinalizationSourcesByConnection,
|
|
1637
|
+
afterSourcesByConnection: postFinalizationSourcesByConnection,
|
|
1638
|
+
});
|
|
1639
|
+
if (scope.unresolvedPaths.length > 0) {
|
|
1640
|
+
await runTrace.event('error', 'finalization', 'finalization_failed', {
|
|
1641
|
+
sourceKey: job.sourceKey,
|
|
1642
|
+
reason: 'unresolved_semantic_layer_paths',
|
|
1643
|
+
unresolvedPaths: scope.unresolvedPaths,
|
|
1644
|
+
});
|
|
1645
|
+
throw new Error(`could not resolve finalization semantic-layer path(s): ${scope.unresolvedPaths.join(', ')}`);
|
|
1646
|
+
}
|
|
1647
|
+
finalizationTouchedSources = scope.touchedSources;
|
|
1648
|
+
finalizationChangedWikiPageKeys = deriveFinalizationWikiPageKeys(finalizationTouchedPaths);
|
|
1649
|
+
const mismatches = compareFinalizationDeclarations({
|
|
1650
|
+
declaredTouchedSources: result.touchedSources,
|
|
1651
|
+
derivedTouchedSources: finalizationTouchedSources,
|
|
1652
|
+
declaredChangedWikiPageKeys: result.changedWikiPageKeys,
|
|
1653
|
+
derivedChangedWikiPageKeys: finalizationChangedWikiPageKeys,
|
|
1654
|
+
});
|
|
1655
|
+
if (mismatches.length > 0) {
|
|
1656
|
+
finalizationOutcome = {
|
|
1523
1657
|
sourceKey: job.sourceKey,
|
|
1524
1658
|
status: 'failed',
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1659
|
+
commitSha: finalizationSha,
|
|
1660
|
+
touchedPaths: finalizationTouchedPaths,
|
|
1661
|
+
declaredTouchedSources: result.touchedSources,
|
|
1662
|
+
derivedTouchedSources: finalizationTouchedSources,
|
|
1663
|
+
declaredChangedWikiPageKeys: result.changedWikiPageKeys,
|
|
1664
|
+
derivedChangedWikiPageKeys: finalizationChangedWikiPageKeys,
|
|
1665
|
+
mismatches,
|
|
1666
|
+
result: result.result,
|
|
1667
|
+
errors: ['finalization touched artifact declaration mismatch'],
|
|
1668
|
+
warnings: result.warnings,
|
|
1669
|
+
actions: result.actions ?? [],
|
|
1670
|
+
provenanceExclusions: [],
|
|
1528
1671
|
};
|
|
1529
|
-
|
|
1530
|
-
|
|
1672
|
+
latestFinalizationOutcome = finalizationOutcome;
|
|
1673
|
+
await runTrace.event('error', 'finalization', 'finalization_failed', {
|
|
1674
|
+
sourceKey: job.sourceKey,
|
|
1675
|
+
reason: 'declaration_mismatch',
|
|
1676
|
+
mismatches,
|
|
1677
|
+
});
|
|
1678
|
+
throw new Error(`finalization touched artifact declaration mismatch: ${mismatches
|
|
1679
|
+
.map((mismatch) => `${mismatch.direction}:${mismatch.artifactKind}:${mismatch.key}`)
|
|
1680
|
+
.join(', ')}`);
|
|
1531
1681
|
}
|
|
1682
|
+
finalizationActions = result.actions ?? [];
|
|
1683
|
+
finalizationOutcome = {
|
|
1684
|
+
sourceKey: job.sourceKey,
|
|
1685
|
+
status: 'success',
|
|
1686
|
+
commitSha: finalizationSha,
|
|
1687
|
+
touchedPaths: finalizationTouchedPaths,
|
|
1688
|
+
declaredTouchedSources: result.touchedSources,
|
|
1689
|
+
derivedTouchedSources: finalizationTouchedSources,
|
|
1690
|
+
declaredChangedWikiPageKeys: result.changedWikiPageKeys,
|
|
1691
|
+
derivedChangedWikiPageKeys: finalizationChangedWikiPageKeys,
|
|
1692
|
+
mismatches,
|
|
1693
|
+
result: result.result,
|
|
1694
|
+
errors: [],
|
|
1695
|
+
warnings: result.warnings,
|
|
1696
|
+
actions: finalizationActions,
|
|
1697
|
+
provenanceExclusions: [],
|
|
1698
|
+
};
|
|
1699
|
+
latestFinalizationOutcome = finalizationOutcome;
|
|
1700
|
+
emitStageProgress('finalization', 88, 'Deterministic finalization complete');
|
|
1701
|
+
await stageFinalization?.updateProgress(1.0, 'Deterministic finalization complete');
|
|
1702
|
+
await runTrace.event('debug', 'finalization', 'finalization_committed', {
|
|
1703
|
+
sourceKey: job.sourceKey,
|
|
1704
|
+
commitSha: finalizationSha,
|
|
1705
|
+
touchedPaths: finalizationTouchedPaths,
|
|
1706
|
+
touchedSources: finalizationTouchedSources,
|
|
1707
|
+
changedWikiPageKeys: finalizationChangedWikiPageKeys,
|
|
1708
|
+
warnings: result.warnings,
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
else {
|
|
1712
|
+
await runTrace.event('debug', 'finalization', 'finalization_skipped', { sourceKey: job.sourceKey });
|
|
1532
1713
|
}
|
|
1533
|
-
await runTrace.event('debug', 'post_processor', 'post_processor_finished', {
|
|
1534
|
-
sourceKey: job.sourceKey,
|
|
1535
|
-
status: postProcessorOutcome?.status ?? 'skipped',
|
|
1536
|
-
touchedSources: postProcessorOutcome?.touchedSources ?? [],
|
|
1537
|
-
warnings: postProcessorOutcome?.warnings ?? [],
|
|
1538
|
-
});
|
|
1539
1714
|
const repairConnectionIds = [
|
|
1540
1715
|
...new Set([
|
|
1541
1716
|
...slConnectionIds,
|
|
1542
|
-
...
|
|
1717
|
+
...finalizationTouchedSources.map((source) => source.connectionId),
|
|
1543
1718
|
]),
|
|
1544
1719
|
].sort();
|
|
1545
1720
|
activePhase = 'wiki_sl_ref_repair';
|
|
@@ -1566,6 +1741,7 @@ export class IngestBundleRunner {
|
|
|
1566
1741
|
.flatMap((outcome) => outcome.patchTouchedPaths ?? [])
|
|
1567
1742
|
.flatMap((path) => this.wikiPageKeysFromPaths([path])),
|
|
1568
1743
|
...this.wikiPageKeysFromActions(reconcileActions),
|
|
1744
|
+
...finalizationChangedWikiPageKeys,
|
|
1569
1745
|
...postReconciliationPaths.flatMap((path) => this.wikiPageKeysFromPaths([path])),
|
|
1570
1746
|
...wikiSlRefRepairResult.repairs.filter((repair) => repair.scope === 'GLOBAL').map((repair) => repair.pageKey),
|
|
1571
1747
|
]);
|
|
@@ -1574,7 +1750,7 @@ export class IngestBundleRunner {
|
|
|
1574
1750
|
...workUnitOutcomes.flatMap((outcome) => outcome.touchedSlSources),
|
|
1575
1751
|
...this.touchedSlSourcesFromActions(reconcileActions, job.connectionId),
|
|
1576
1752
|
...this.touchedSlSourcesFromPaths(postReconciliationPaths),
|
|
1577
|
-
...
|
|
1753
|
+
...finalizationTouchedSources,
|
|
1578
1754
|
]);
|
|
1579
1755
|
const finalWikiGateScope = await this.wikiPageKeysForFinalGates({
|
|
1580
1756
|
wikiService: this.deps.wikiService.forWorktree(sessionWorktree.workdir),
|
|
@@ -1587,7 +1763,7 @@ export class IngestBundleRunner {
|
|
|
1587
1763
|
...projectionTouchedPaths,
|
|
1588
1764
|
...workUnitOutcomes.flatMap((outcome) => outcome.patchTouchedPaths ?? []),
|
|
1589
1765
|
...postReconciliationPaths,
|
|
1590
|
-
...
|
|
1766
|
+
...finalizationTouchedPaths,
|
|
1591
1767
|
];
|
|
1592
1768
|
const targetPolicyTraceData = {
|
|
1593
1769
|
allowedTargetConnectionIds: slConnectionIds,
|
|
@@ -1715,12 +1891,23 @@ export class IngestBundleRunner {
|
|
|
1715
1891
|
latestArtifactResolutions = stageIndex.artifactResolutions ?? [];
|
|
1716
1892
|
latestEvictionInputs = eviction?.deletedRawPaths ?? [];
|
|
1717
1893
|
latestUnresolvedCards = unresolvedCards ?? [];
|
|
1894
|
+
const finalizationProvenance = this.partitionFinalizationActionsForProvenance({
|
|
1895
|
+
actions: finalizationActions,
|
|
1896
|
+
currentRawPaths: new Set(currentHashes.keys()),
|
|
1897
|
+
currentEvictionRawPaths: new Set(stageIndex.evictionsApplied.map((entry) => entry.rawPath)),
|
|
1898
|
+
overrideEvictionRawPaths: new Set(overrideReport?.body.evictionInputs ?? []),
|
|
1899
|
+
});
|
|
1900
|
+
if (finalizationOutcome) {
|
|
1901
|
+
finalizationOutcome.provenanceExclusions = finalizationProvenance.exclusions;
|
|
1902
|
+
latestFinalizationOutcome = finalizationOutcome;
|
|
1903
|
+
}
|
|
1718
1904
|
const provenancePlan = this.buildProvenancePlan({
|
|
1719
1905
|
job,
|
|
1720
1906
|
syncId,
|
|
1721
1907
|
currentHashes,
|
|
1722
1908
|
stageIndex,
|
|
1723
1909
|
reconcileActions,
|
|
1910
|
+
finalizationActions: finalizationProvenance.actions,
|
|
1724
1911
|
});
|
|
1725
1912
|
const provenanceRows = provenancePlan.rows;
|
|
1726
1913
|
const currentRawPaths = new Set(currentHashes.keys());
|
|
@@ -1769,13 +1956,15 @@ export class IngestBundleRunner {
|
|
|
1769
1956
|
commitSha,
|
|
1770
1957
|
touchedPaths: mergeResult.touchedPaths,
|
|
1771
1958
|
});
|
|
1772
|
-
const memoryFlowSavedActions = stageIndex.workUnits
|
|
1773
|
-
|
|
1959
|
+
const memoryFlowSavedActions = stageIndex.workUnits
|
|
1960
|
+
.flatMap((wu) => wu.actions)
|
|
1961
|
+
.concat(reconcileActions)
|
|
1962
|
+
.concat(finalizationActions);
|
|
1774
1963
|
memoryFlow?.emit({
|
|
1775
1964
|
type: 'saved',
|
|
1776
1965
|
commitSha,
|
|
1777
|
-
wikiCount: countMemoryFlowActions(memoryFlowSavedActions, 'wiki')
|
|
1778
|
-
slCount: countMemoryFlowActions(memoryFlowSavedActions, 'sl')
|
|
1966
|
+
wikiCount: countMemoryFlowActions(memoryFlowSavedActions, 'wiki'),
|
|
1967
|
+
slCount: countMemoryFlowActions(memoryFlowSavedActions, 'sl'),
|
|
1779
1968
|
});
|
|
1780
1969
|
await stage6?.updateProgress(1.0, commitSha ? `Saved changes (${commitSha.slice(0, 8)})` : 'No changes to save');
|
|
1781
1970
|
// Sync the shared `knowledge` index from the squashed diff in a single
|
|
@@ -1792,7 +1981,7 @@ export class IngestBundleRunner {
|
|
|
1792
1981
|
...new Set(memoryFlowSavedActions
|
|
1793
1982
|
.filter((action) => action.target === 'sl')
|
|
1794
1983
|
.map((action) => actionTargetConnectionId(action, job.connectionId))
|
|
1795
|
-
.concat(
|
|
1984
|
+
.concat(finalizationTouchedSources.map((source) => source.connectionId))),
|
|
1796
1985
|
].sort();
|
|
1797
1986
|
for (const connectionId of touchedConnections) {
|
|
1798
1987
|
try {
|
|
@@ -1873,7 +2062,7 @@ export class IngestBundleRunner {
|
|
|
1873
2062
|
overrideOf: overrideReport?.jobId ?? null,
|
|
1874
2063
|
provenanceRows: reportProvenanceRows,
|
|
1875
2064
|
toolTranscripts: reportToolTranscripts,
|
|
1876
|
-
|
|
2065
|
+
finalization: finalizationOutcome,
|
|
1877
2066
|
wikiSlRefRepairs: wikiSlRefRepairResult.repairs,
|
|
1878
2067
|
wikiSlRefRepairWarnings: wikiSlRefRepairResult.warnings,
|
|
1879
2068
|
...(reportMemoryFlow ? { memoryFlow: reportMemoryFlow } : {}),
|
|
@@ -2031,6 +2220,7 @@ export class IngestBundleRunner {
|
|
|
2031
2220
|
artifactResolutions: latestArtifactResolutions,
|
|
2032
2221
|
evictionInputs: latestEvictionInputs,
|
|
2033
2222
|
reconciliationActions: latestReconciliationActions,
|
|
2223
|
+
finalization: latestFinalizationOutcome,
|
|
2034
2224
|
evictionDecisions: [],
|
|
2035
2225
|
unresolvedCards: latestUnresolvedCards,
|
|
2036
2226
|
supersededBy: null,
|