@saptools/service-flow 0.1.68 → 0.1.70

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.
Files changed (124) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +11 -5
  3. package/TECHNICAL-NOTE.md +13 -0
  4. package/dist/{chunk-AEM4JY22.js → chunk-GSLFY6J2.js} +6986 -5503
  5. package/dist/chunk-GSLFY6J2.js.map +1 -0
  6. package/dist/cli.js +334 -122
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +7 -1
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/{002-doctor-lifecycle.ts → doctor-lifecycle.ts} +1 -1
  12. package/src/cli/{001-doctor-projection.ts → doctor-projection.ts} +1 -1
  13. package/src/cli/doctor.ts +5 -5
  14. package/src/cli/index-summary.ts +22 -0
  15. package/src/cli.ts +155 -91
  16. package/src/db/{009-binding-fact-semantics.ts → binding-fact-semantics.ts} +6 -1
  17. package/src/db/binding-helper-provenance.ts +17 -0
  18. package/src/db/{012-binding-reference-proof.ts → binding-reference-proof.ts} +4 -0
  19. package/src/db/{000-call-fact-repository.ts → call-fact-repository.ts} +49 -23
  20. package/src/db/{003-current-fact-semantics.ts → current-fact-semantics.ts} +5 -4
  21. package/src/db/{001-fact-lifecycle.ts → fact-lifecycle.ts} +3 -3
  22. package/src/db/index-publication-failure.ts +91 -0
  23. package/src/db/{007-package-fact-semantics.ts → package-fact-semantics.ts} +6 -6
  24. package/src/db/{010-package-symbol-surface-semantics.ts → package-symbol-surface-semantics.ts} +2 -2
  25. package/src/db/{004-package-target-invalidation.ts → package-target-invalidation.ts} +11 -1
  26. package/src/db/{008-relative-fact-semantics.ts → relative-fact-semantics.ts} +9 -6
  27. package/src/db/{006-relative-symbol-resolution.ts → relative-symbol-resolution.ts} +28 -7
  28. package/src/db/repositories.ts +24 -7
  29. package/src/db/{011-symbol-call-semantics.ts → symbol-call-semantics.ts} +1 -1
  30. package/src/index.ts +3 -3
  31. package/src/indexer/cds-extension-resolver.ts +27 -3
  32. package/src/indexer/repository-indexer.ts +70 -9
  33. package/src/indexer/workspace-indexer.ts +142 -30
  34. package/src/linker/call-edge-insertion.ts +568 -0
  35. package/src/linker/{002-call-evidence.ts → call-evidence.ts} +2 -2
  36. package/src/linker/cross-repo-linker.ts +6 -169
  37. package/src/linker/dynamic-edge-resolver.ts +1 -1
  38. package/src/linker/{004-event-subscription-handler-linker.ts → event-subscription-handler-linker.ts} +32 -11
  39. package/src/linker/event-template-link.ts +72 -0
  40. package/src/linker/external-http-target.ts +1 -1
  41. package/src/linker/helper-package-linker.ts +1 -1
  42. package/src/linker/{000-implementation-candidates.ts → implementation-candidates.ts} +1 -1
  43. package/src/linker/{001-implementation-evidence-projection.ts → implementation-evidence-projection.ts} +1 -1
  44. package/src/linker/odata-path-normalizer.ts +1 -1
  45. package/src/linker/{005-odata-path-structure.ts → odata-path-structure.ts} +1 -1
  46. package/src/linker/{003-package-import-symbol-resolver.ts → package-import-symbol-resolver.ts} +3 -3
  47. package/src/linker/service-resolver.ts +2 -2
  48. package/src/output/{001-compact-json-output.ts → compact-json-output.ts} +1 -1
  49. package/src/parsers/{006-binding-identity.ts → binding-identity.ts} +9 -4
  50. package/src/parsers/{011-binding-lexical-scope.ts → binding-lexical-scope.ts} +1 -1
  51. package/src/parsers/{021-binding-visibility.ts → binding-visibility.ts} +18 -2
  52. package/src/parsers/{000-direct-query-execution.ts → direct-query-execution.ts} +11 -0
  53. package/src/parsers/{005-event-subscription-facts.ts → event-subscription-facts.ts} +2 -2
  54. package/src/parsers/{016-local-symbol-reference.ts → local-symbol-reference.ts} +2 -2
  55. package/src/parsers/operation-path-analysis.ts +1 -1
  56. package/src/parsers/outbound-call-classifier.ts +692 -0
  57. package/src/parsers/outbound-call-parser.ts +146 -509
  58. package/src/parsers/outbound-expression-analysis.ts +700 -0
  59. package/src/parsers/{018-package-commonjs-syntax.ts → package-commonjs-syntax.ts} +1 -1
  60. package/src/parsers/{012-package-fact-contract.ts → package-fact-contract.ts} +3 -3
  61. package/src/parsers/{010-package-public-surface-analysis.ts → package-public-surface-analysis.ts} +2 -2
  62. package/src/parsers/{003-package-public-surface.ts → package-public-surface.ts} +3 -3
  63. package/src/parsers/{008-package-surface-publication.ts → package-surface-publication.ts} +1 -1
  64. package/src/parsers/{001-query-entity-resolution.ts → query-entity-resolution.ts} +1 -1
  65. package/src/parsers/{015-service-binding-collector.ts → service-binding-collector.ts} +3 -3
  66. package/src/parsers/{014-service-binding-helper-flow.ts → service-binding-helper-flow.ts} +72 -6
  67. package/src/parsers/service-binding-parser-helpers.ts +1 -1
  68. package/src/parsers/service-binding-parser.ts +2 -2
  69. package/src/parsers/{007-source-fact-reconciliation.ts → source-fact-reconciliation.ts} +3 -3
  70. package/src/parsers/{020-stable-local-value.ts → stable-local-value.ts} +2 -2
  71. package/src/parsers/{009-symbol-call-facts.ts → symbol-call-facts.ts} +3 -3
  72. package/src/parsers/{017-symbol-derived-contexts.ts → symbol-derived-contexts.ts} +2 -2
  73. package/src/parsers/symbol-parser.ts +43 -11
  74. package/src/trace/{014-compact-contract.ts → compact-contract.ts} +6 -0
  75. package/src/trace/{021-compact-decision-normalization.ts → compact-decision-normalization.ts} +77 -11
  76. package/src/trace/{020-compact-field-projection.ts → compact-field-projection.ts} +77 -4
  77. package/src/trace/{024-compact-observation-decision.ts → compact-observation-decision.ts} +10 -5
  78. package/src/trace/{016-compact-projector.ts → compact-projector.ts} +6 -7
  79. package/src/trace/{018-compact-trace.ts → compact-trace.ts} +3 -3
  80. package/src/trace/{006-contextual-projection.ts → contextual-projection.ts} +1 -1
  81. package/src/trace/{008-contextual-runtime-state.ts → contextual-runtime-state.ts} +1 -1
  82. package/src/trace/{001-dynamic-identity.ts → dynamic-identity.ts} +2 -2
  83. package/src/trace/{003-dynamic-references.ts → dynamic-references.ts} +2 -2
  84. package/src/trace/dynamic-targets.ts +6 -6
  85. package/src/trace/event-runtime-resolution.ts +151 -0
  86. package/src/trace/{011-event-subscriber-traversal.ts → event-subscriber-traversal.ts} +101 -9
  87. package/src/trace/evidence.ts +2 -2
  88. package/src/trace/implementation-hints.ts +10 -7
  89. package/src/trace/{005-implementation-selection.ts → implementation-selection.ts} +2 -2
  90. package/src/trace/{007-implementation-start-diagnostic.ts → implementation-start-diagnostic.ts} +1 -0
  91. package/src/trace/local-call-expansion.ts +37 -0
  92. package/src/trace/{009-selected-handler-provenance.ts → selected-handler-provenance.ts} +1 -1
  93. package/src/trace/selectors.ts +2 -1
  94. package/src/trace/{017-trace-context.ts → trace-context.ts} +2 -2
  95. package/src/trace/{015-trace-edge-recorder.ts → trace-edge-recorder.ts} +62 -5
  96. package/src/trace/{019-trace-edge-semantics.ts → trace-edge-semantics.ts} +9 -13
  97. package/src/trace/trace-engine.ts +10 -10
  98. package/src/trace/{022-trace-fact-preflight.ts → trace-fact-preflight.ts} +2 -2
  99. package/src/trace/{025-trace-implementation-scope.ts → trace-implementation-scope.ts} +1 -1
  100. package/src/trace/{028-trace-operation-execution.ts → trace-operation-execution.ts} +10 -10
  101. package/src/trace/{013-trace-root-scopes.ts → trace-root-scopes.ts} +5 -6
  102. package/src/trace/{027-trace-scope-execution.ts → trace-scope-execution.ts} +44 -44
  103. package/src/trace/{029-trace-start-implementation.ts → trace-start-implementation.ts} +7 -7
  104. package/src/trace/{026-trace-start-scope.ts → trace-start-scope.ts} +6 -5
  105. package/src/trace/{010-traversal-scope.ts → traversal-scope.ts} +1 -1
  106. package/src/types.ts +2 -1
  107. package/src/version.ts +1 -1
  108. package/dist/chunk-AEM4JY22.js.map +0 -1
  109. /package/src/cli/{000-clean.ts → clean.ts} +0 -0
  110. /package/src/cli/{003-doctor-package-resolution.ts → doctor-package-resolution.ts} +0 -0
  111. /package/src/db/{002-fact-json-inventory.ts → fact-json-inventory.ts} +0 -0
  112. /package/src/db/{005-schema-structure.ts → schema-structure.ts} +0 -0
  113. /package/src/output/{000-stdout-policy.ts → stdout-policy.ts} +0 -0
  114. /package/src/parsers/{019-binding-assignment-targets.ts → binding-assignment-targets.ts} +0 -0
  115. /package/src/parsers/{013-executable-body-eligibility.ts → executable-body-eligibility.ts} +0 -0
  116. /package/src/parsers/{004-fact-identity.ts → fact-identity.ts} +0 -0
  117. /package/src/parsers/{002-symbol-import-bindings.ts → symbol-import-bindings.ts} +0 -0
  118. /package/src/trace/{004-dynamic-candidate-sources.ts → dynamic-candidate-sources.ts} +0 -0
  119. /package/src/trace/{000-dynamic-target-types.ts → dynamic-target-types.ts} +0 -0
  120. /package/src/trace/{023-nested-event-scopes.ts → nested-event-scopes.ts} +0 -0
  121. /package/src/trace/{002-trace-diagnostics.ts → trace-diagnostics.ts} +0 -0
  122. /package/src/trace/{012-trace-graph-lookups.ts → trace-graph-lookups.ts} +0 -0
  123. /package/src/utils/{000-bounded-projection.ts → bounded-projection.ts} +0 -0
  124. /package/src/utils/{001-placeholders.ts → placeholders.ts} +0 -0
@@ -27,14 +27,14 @@ import {
27
27
  loadPackageJsonSnapshot,
28
28
  } from '../parsers/package-json-parser.js';
29
29
  import { parseServiceBindings } from '../parsers/service-binding-parser.js';
30
- import { reconcileSourceFacts } from '../parsers/007-source-fact-reconciliation.js';
30
+ import { reconcileSourceFacts } from '../parsers/source-fact-reconciliation.js';
31
31
  import {
32
32
  analyzeRepositoryPackageSurface,
33
33
  mergePackageSymbolEvidence,
34
- } from '../parsers/008-package-surface-publication.js';
34
+ } from '../parsers/package-surface-publication.js';
35
35
  import type {
36
36
  PackagePublicSurfaceFact,
37
- } from '../parsers/003-package-public-surface.js';
37
+ } from '../parsers/package-public-surface.js';
38
38
  import { normalizePath } from '../utils/path-utils.js';
39
39
  import { errorMessage } from '../utils/diagnostics.js';
40
40
  import { sha256Text } from '../utils/hashing.js';
@@ -44,7 +44,11 @@ import {
44
44
  finalizePackageTargetInvalidations,
45
45
  invalidatePackageTargetFacts,
46
46
  type PackageInvalidationBatch,
47
- } from '../db/004-package-target-invalidation.js';
47
+ } from '../db/package-target-invalidation.js';
48
+ import {
49
+ isPreparedRepositorySnapshotError,
50
+ recordPreparedSnapshotFailure,
51
+ } from '../db/index-publication-failure.js';
48
52
  import {
49
53
  loadRepositorySourceContext,
50
54
  type RepositorySourceContext,
@@ -82,12 +86,21 @@ export async function indexRepository(
82
86
  ): Promise<IndexRepoResult> {
83
87
  try {
84
88
  const prepared = await prepareRepositoryIndex(repo, force, instrumentation);
85
- if (!prepared.skipped) db.transaction(() => {
89
+ if (prepared.skipped)
90
+ return { fileCount: 0, diagnosticCount: 0, skipped: true };
91
+ const outcome = db.transaction(() => {
86
92
  const batch = createPackageInvalidationBatch([prepared.repo.id]);
87
- publishPreparedRepositoryIndex(db, prepared, batch);
88
- finalizePackageTargetInvalidations(db, batch);
93
+ const published = publishOneRepository(db, prepared, batch);
94
+ if (published.ok) finalizePackageTargetInvalidations(db, batch);
95
+ return published;
89
96
  });
90
- return { fileCount: prepared.fileCount, diagnosticCount: prepared.diagnosticCount, skipped: prepared.skipped };
97
+ return outcome.ok
98
+ ? {
99
+ fileCount: prepared.fileCount,
100
+ diagnosticCount: prepared.diagnosticCount,
101
+ skipped: false,
102
+ }
103
+ : { fileCount: 0, diagnosticCount: 1, skipped: false };
91
104
  } catch (error) {
92
105
  recordIndexFailure(db, repo.id, error);
93
106
  return { fileCount: 0, diagnosticCount: 1, skipped: false };
@@ -172,10 +185,58 @@ export function publishPreparedRepositoryIndex(
172
185
  insertCalls(db, repoId, prepared.parsed.calls);
173
186
  db.prepare("UPDATE repositories SET last_indexed_at=?, index_status='indexed', error_count=0, fingerprint=?, fact_generation=COALESCE(fact_generation,0)+1, graph_stale_reason='facts_changed', graph_stale_at=?, fact_analyzer_version=? WHERE id=?").run(now, prepared.fingerprint, now, ANALYZER_VERSION, repoId);
174
187
  }
188
+
189
+ export type RepositoryPublicationOutcome =
190
+ | { ok: true }
191
+ | { ok: false; error: unknown };
192
+
193
+ export function publishOneRepository(
194
+ db: Db,
195
+ prepared: PreparedRepositoryIndex,
196
+ invalidations: PackageInvalidationBatch,
197
+ ): RepositoryPublicationOutcome {
198
+ try {
199
+ db.transaction(() => withPublicationSavepoint(
200
+ db,
201
+ prepared.repo.id,
202
+ () => publishPreparedRepositoryIndex(db, prepared, invalidations),
203
+ ));
204
+ return { ok: true };
205
+ } catch (error) {
206
+ recordIndexFailure(db, prepared.repo.id, error);
207
+ return { ok: false, error };
208
+ }
209
+ }
210
+
211
+ function withPublicationSavepoint<T>(
212
+ db: Db,
213
+ repoId: number,
214
+ publish: () => T,
215
+ ): T {
216
+ const name = `service_flow_repository_${repoId}`;
217
+ db.exec(`SAVEPOINT ${name}`);
218
+ try {
219
+ const result = publish();
220
+ db.exec(`RELEASE SAVEPOINT ${name}`);
221
+ return result;
222
+ } catch (error) {
223
+ db.exec(`ROLLBACK TO SAVEPOINT ${name}`);
224
+ db.exec(`RELEASE SAVEPOINT ${name}`);
225
+ throw error;
226
+ }
227
+ }
228
+
175
229
  export function recordIndexFailure(db: Db, repoId: number, error: unknown): void {
230
+ if (isPreparedRepositorySnapshotError(error)) {
231
+ recordPreparedSnapshotFailure(db, repoId, error);
232
+ return;
233
+ }
176
234
  const message = errorMessage(error);
177
235
  db.prepare("UPDATE repositories SET index_status='failed', error_count=1 WHERE id=?").run(repoId);
178
- db.prepare("DELETE FROM diagnostics WHERE repo_id=? AND code IN ('index_failed_snapshot_preserved','source_read_failed')").run(repoId);
236
+ db.prepare(`DELETE FROM diagnostics WHERE repo_id=? AND (
237
+ code IN ('index_failed_snapshot_preserved','source_read_failed')
238
+ OR code GLOB 'invalid_prepared_repository_snapshot:*'
239
+ )`).run(repoId);
179
240
  db.prepare('INSERT INTO diagnostics(repo_id,severity,code,message) VALUES(?,?,?,?)').run(repoId, 'error', 'source_read_failed', `Index failed before publication; previous facts and fingerprint were preserved. ${message}`);
180
241
  }
181
242
  async function parseAllSourceFacts(
@@ -1,12 +1,23 @@
1
1
  import type { Db } from '../db/connection.js';
2
2
  import { listRepositories, reposByName } from '../db/repositories.js';
3
3
  import { errorMessage } from '../utils/diagnostics.js';
4
- import { prepareRepositoryIndex, publishPreparedRepositoryIndex, recordIndexFailure, type PreparedRepositoryIndex } from './repository-indexer.js';
4
+ import {
5
+ prepareRepositoryIndex,
6
+ publishOneRepository,
7
+ recordIndexFailure,
8
+ type PreparedRepositoryIndex,
9
+ } from './repository-indexer.js';
5
10
  import { materializeCdsExtensionOperations } from './cds-extension-resolver.js';
6
11
  import {
7
12
  createPackageInvalidationBatch,
8
13
  finalizePackageTargetInvalidations,
9
- } from '../db/004-package-target-invalidation.js';
14
+ mergePackageInvalidationEffects,
15
+ type PackageInvalidationBatch,
16
+ } from '../db/package-target-invalidation.js';
17
+ import {
18
+ isPreparedRepositorySnapshotError,
19
+ } from '../db/index-publication-failure.js';
20
+ import { binaryCompare } from '../parsers/fact-identity.js';
10
21
  // Ownerless rows predate PID coordination; this matches doctor's stale-run threshold without taking over a recent legacy writer.
11
22
  const LEGACY_OWNER_RECOVERY_MS = 60 * 60 * 1_000;
12
23
  type RunningIndexRow = Record<string, unknown>;
@@ -80,11 +91,21 @@ export function claimIndexRun(
80
91
  throw error;
81
92
  }
82
93
  }
94
+ export interface IndexWorkspaceSummary {
95
+ repoCount: number;
96
+ indexedCount: number;
97
+ skippedCount: number;
98
+ failedCount: number;
99
+ failedRepos: Array<{ name: string; code: string }>;
100
+ fileCount: number;
101
+ diagnosticCount: number;
102
+ }
103
+
83
104
  export async function indexWorkspace(
84
105
  db: Db,
85
106
  workspaceId: number,
86
107
  options: { repo?: string; force: boolean; injectDerivedMaterializationFailure?: boolean },
87
- ): Promise<{ repoCount: number; indexedCount: number; skippedCount: number; fileCount: number; diagnosticCount: number }> {
108
+ ): Promise<IndexWorkspaceSummary> {
88
109
  const repos = selectedRepositories(db, workspaceId, options.repo);
89
110
  const runId = claimIndexRun(db, workspaceId, repos.length);
90
111
  const state: PreparationState = {
@@ -92,8 +113,9 @@ export async function indexWorkspace(
92
113
  };
93
114
  try {
94
115
  await prepareRepositories(repos, options.force, state);
95
- publishPreparedRows(db, workspaceId, options, runId, state);
96
- return indexSummary(repos.length, state);
116
+ return publishPreparedWorkspaceRows(
117
+ db, workspaceId, runId, state.rows, options,
118
+ );
97
119
  } catch (error) {
98
120
  finishFailedRun(db, runId, state, error);
99
121
  if (state.activeRepoId && state.rows.length < repos.length)
@@ -111,6 +133,18 @@ interface PreparationState {
111
133
  activeRepoId?: number;
112
134
  }
113
135
 
136
+ interface PublicationState {
137
+ fileCount: number;
138
+ diagnosticCount: number;
139
+ skippedCount: number;
140
+ indexedCount: number;
141
+ publicationFailureCount: number;
142
+ failedRepoIds: Set<number>;
143
+ failedRepos: Array<{ name: string; code: string }>;
144
+ rows: readonly PreparedRepositoryIndex[];
145
+ activeRepoId?: number;
146
+ }
147
+
114
148
  function selectedRepositories(
115
149
  db: Db,
116
150
  workspaceId: number,
@@ -145,38 +179,118 @@ async function prepareRepositories(
145
179
  }
146
180
  }
147
181
 
148
- function publishPreparedRows(
182
+ export function publishPreparedWorkspaceRows(
149
183
  db: Db,
150
184
  workspaceId: number,
151
- options: { injectDerivedMaterializationFailure?: boolean },
152
185
  runId: number,
153
- state: PreparationState,
154
- ): void {
186
+ rows: readonly PreparedRepositoryIndex[],
187
+ options: { injectDerivedMaterializationFailure?: boolean } = {},
188
+ ): IndexWorkspaceSummary {
189
+ const state = publicationState(rows);
155
190
  db.transaction(() => {
156
- const invalidations = createPackageInvalidationBatch(
157
- state.rows.filter((row) => !row.skipped).map((row) => row.repo.id),
158
- );
191
+ const effects = createPackageInvalidationBatch([]);
192
+ const publishedRepoIds: number[] = [];
159
193
  for (const row of state.rows) {
160
194
  state.activeRepoId = row.repo.id;
161
- publishPreparedRepositoryIndex(db, row, invalidations);
195
+ if (row.skipped) continue;
196
+ const result = publishPreparedRow(db, row);
197
+ if (result.status === 'failed') {
198
+ recordPublicationFailure(db, state, row, result.error);
199
+ continue;
200
+ }
201
+ state.indexedCount += 1;
202
+ publishedRepoIds.push(row.repo.id);
203
+ mergePackageInvalidationEffects(effects, result.effects);
162
204
  }
163
205
  if (options.injectDerivedMaterializationFailure)
164
206
  throw new Error('Injected derived materialization failure');
165
- materializeCdsExtensionOperations(db, workspaceId);
207
+ materializeCdsExtensionOperations(
208
+ db, workspaceId, state.failedRepoIds,
209
+ );
210
+ const invalidations = createPackageInvalidationBatch(publishedRepoIds);
211
+ mergePackageInvalidationEffects(invalidations, effects);
166
212
  finalizePackageTargetInvalidations(db, invalidations);
167
- finishSuccessfulRun(db, runId, state);
213
+ finishCompletedRun(db, runId, state);
214
+ });
215
+ return indexSummary(rows.length, state);
216
+ }
217
+
218
+ function publicationState(
219
+ rows: readonly PreparedRepositoryIndex[],
220
+ ): PublicationState {
221
+ return {
222
+ rows,
223
+ fileCount: rows.reduce((total, row) => total + row.fileCount, 0),
224
+ diagnosticCount: rows.reduce(
225
+ (total, row) => total + row.diagnosticCount, 0,
226
+ ),
227
+ skippedCount: rows.filter((row) => row.skipped).length,
228
+ indexedCount: 0,
229
+ publicationFailureCount: 0,
230
+ failedRepoIds: new Set(),
231
+ failedRepos: [],
232
+ };
233
+ }
234
+
235
+ type PublicationResult =
236
+ | { status: 'published'; effects: PackageInvalidationBatch }
237
+ | { status: 'failed'; error: unknown };
238
+
239
+ function publishPreparedRow(
240
+ db: Db,
241
+ row: PreparedRepositoryIndex,
242
+ ): PublicationResult {
243
+ const effects = createPackageInvalidationBatch([row.repo.id]);
244
+ const outcome = publishOneRepository(db, row, effects);
245
+ if (!outcome.ok && !isPreparedRepositorySnapshotError(outcome.error))
246
+ throw outcome.error;
247
+ return outcome.ok
248
+ ? { status: 'published', effects }
249
+ : { status: 'failed', error: outcome.error };
250
+ }
251
+
252
+ function recordPublicationFailure(
253
+ db: Db,
254
+ state: PublicationState,
255
+ row: PreparedRepositoryIndex,
256
+ error: unknown,
257
+ ): void {
258
+ state.failedRepoIds.add(row.repo.id);
259
+ state.failedRepos.push({
260
+ name: row.repo.name,
261
+ code: isPreparedRepositorySnapshotError(error)
262
+ ? error.message : 'source_read_failed',
168
263
  });
264
+ state.publicationFailureCount += 1;
169
265
  }
170
266
 
171
- function finishSuccessfulRun(
267
+ function finishCompletedRun(
172
268
  db: Db,
173
269
  runId: number,
174
- state: PreparationState,
270
+ state: PublicationState,
175
271
  ): void {
176
- db.prepare(`UPDATE index_runs SET finished_at=?,status='success',
177
- file_count=?,diagnostic_count=? WHERE id=?`).run(
178
- new Date().toISOString(), state.fileCount, state.diagnosticCount, runId,
272
+ const status = completedRunStatus(
273
+ state.rows.length, state.publicationFailureCount,
179
274
  );
275
+ const error = status === 'success' ? null
276
+ : `${state.publicationFailureCount} repositories failed index publication.`;
277
+ db.prepare(`UPDATE index_runs SET finished_at=?,status=?,
278
+ file_count=?,diagnostic_count=?,error_message=? WHERE id=?`).run(
279
+ new Date().toISOString(), status, state.fileCount,
280
+ completedDiagnosticCount(state), error, runId,
281
+ );
282
+ }
283
+
284
+ function completedRunStatus(
285
+ repoCount: number,
286
+ failedCount: number,
287
+ ): 'success' | 'partial_failure' | 'failed' {
288
+ if (failedCount === 0) return 'success';
289
+ return failedCount === repoCount ? 'failed' : 'partial_failure';
290
+ }
291
+
292
+ function completedDiagnosticCount(state: PublicationState): number {
293
+ return state.diagnosticCount + state.publicationFailureCount;
180
294
  }
181
295
 
182
296
  function finishFailedRun(
@@ -194,19 +308,17 @@ function finishFailedRun(
194
308
 
195
309
  function indexSummary(
196
310
  repoCount: number,
197
- state: PreparationState,
198
- ): {
199
- repoCount: number;
200
- indexedCount: number;
201
- skippedCount: number;
202
- fileCount: number;
203
- diagnosticCount: number;
204
- } {
311
+ state: PublicationState,
312
+ ): IndexWorkspaceSummary {
205
313
  return {
206
314
  repoCount,
207
- indexedCount: repoCount - state.skippedCount,
315
+ indexedCount: state.indexedCount,
208
316
  skippedCount: state.skippedCount,
317
+ failedCount: state.publicationFailureCount,
318
+ failedRepos: [...state.failedRepos].sort((left, right) =>
319
+ binaryCompare(left.name, right.name)
320
+ || binaryCompare(left.code, right.code)),
209
321
  fileCount: state.fileCount,
210
- diagnosticCount: state.diagnosticCount,
322
+ diagnosticCount: completedDiagnosticCount(state),
211
323
  };
212
324
  }