@saptools/service-flow 0.1.67 → 0.1.69

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 (101) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +27 -9
  3. package/TECHNICAL-NOTE.md +36 -0
  4. package/dist/chunk-3N3B5KHV.js +19596 -0
  5. package/dist/chunk-3N3B5KHV.js.map +1 -0
  6. package/dist/cli.js +2645 -521
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +67 -2
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/001-index-summary.ts +22 -0
  12. package/src/cli/003-doctor-package-resolution.ts +68 -0
  13. package/src/cli/doctor.ts +25 -14
  14. package/src/cli.ts +151 -87
  15. package/src/db/000-call-fact-repository.ts +499 -340
  16. package/src/db/001-fact-lifecycle.ts +60 -30
  17. package/src/db/002-fact-json-inventory.ts +169 -0
  18. package/src/db/003-current-fact-semantics.ts +699 -0
  19. package/src/db/004-package-target-invalidation.ts +183 -0
  20. package/src/db/005-schema-structure.ts +201 -0
  21. package/src/db/006-relative-symbol-resolution.ts +464 -0
  22. package/src/db/007-package-fact-semantics.ts +573 -0
  23. package/src/db/008-relative-fact-semantics.ts +210 -0
  24. package/src/db/009-binding-fact-semantics.ts +352 -0
  25. package/src/db/010-package-symbol-surface-semantics.ts +320 -0
  26. package/src/db/011-symbol-call-semantics.ts +144 -0
  27. package/src/db/012-binding-reference-proof.ts +268 -0
  28. package/src/db/013-index-publication-failure.ts +91 -0
  29. package/src/db/014-binding-helper-provenance.ts +17 -0
  30. package/src/db/migrations.ts +16 -3
  31. package/src/db/repositories.ts +130 -6
  32. package/src/db/schema.ts +4 -2
  33. package/src/index.ts +12 -0
  34. package/src/indexer/cds-extension-resolver.ts +27 -3
  35. package/src/indexer/repository-indexer.ts +135 -13
  36. package/src/indexer/workspace-indexer.ts +237 -34
  37. package/src/linker/003-package-import-symbol-resolver.ts +363 -131
  38. package/src/linker/004-event-subscription-handler-linker.ts +34 -11
  39. package/src/linker/005-odata-path-structure.ts +371 -0
  40. package/src/linker/006-event-template-link.ts +72 -0
  41. package/src/linker/007-call-edge-insertion.ts +568 -0
  42. package/src/linker/cross-repo-linker.ts +4 -166
  43. package/src/linker/odata-path-normalizer.ts +273 -180
  44. package/src/linker/service-resolver.ts +197 -77
  45. package/src/parsers/000-direct-query-execution.ts +11 -0
  46. package/src/parsers/002-symbol-import-bindings.ts +516 -0
  47. package/src/parsers/003-package-public-surface.ts +661 -0
  48. package/src/parsers/004-fact-identity.ts +108 -0
  49. package/src/parsers/005-event-subscription-facts.ts +281 -0
  50. package/src/parsers/006-binding-identity.ts +348 -0
  51. package/src/parsers/007-source-fact-reconciliation.ts +105 -0
  52. package/src/parsers/008-package-surface-publication.ts +82 -0
  53. package/src/parsers/009-symbol-call-facts.ts +528 -0
  54. package/src/parsers/010-package-public-surface-analysis.ts +352 -0
  55. package/src/parsers/011-binding-lexical-scope.ts +583 -0
  56. package/src/parsers/012-package-fact-contract.ts +306 -0
  57. package/src/parsers/013-executable-body-eligibility.ts +35 -0
  58. package/src/parsers/014-service-binding-helper-flow.ts +306 -0
  59. package/src/parsers/015-service-binding-collector.ts +693 -0
  60. package/src/parsers/016-local-symbol-reference.ts +261 -0
  61. package/src/parsers/017-symbol-derived-contexts.ts +268 -0
  62. package/src/parsers/018-package-commonjs-syntax.ts +142 -0
  63. package/src/parsers/019-binding-assignment-targets.ts +76 -0
  64. package/src/parsers/020-stable-local-value.ts +217 -0
  65. package/src/parsers/021-binding-visibility.ts +168 -0
  66. package/src/parsers/022-outbound-expression-analysis.ts +700 -0
  67. package/src/parsers/023-outbound-call-classifier.ts +692 -0
  68. package/src/parsers/operation-path-analysis.ts +6 -1
  69. package/src/parsers/outbound-call-parser.ts +162 -512
  70. package/src/parsers/package-json-parser.ts +45 -3
  71. package/src/parsers/service-binding-parser-helpers.ts +86 -15
  72. package/src/parsers/service-binding-parser.ts +147 -597
  73. package/src/parsers/symbol-parser.ts +513 -352
  74. package/src/trace/002-trace-diagnostics.ts +36 -1
  75. package/src/trace/007-implementation-start-diagnostic.ts +1 -0
  76. package/src/trace/011-event-subscriber-traversal.ts +100 -8
  77. package/src/trace/013-trace-root-scopes.ts +2 -3
  78. package/src/trace/014-compact-contract.ts +6 -0
  79. package/src/trace/015-trace-edge-recorder.ts +61 -4
  80. package/src/trace/016-compact-projector.ts +15 -17
  81. package/src/trace/019-trace-edge-semantics.ts +6 -10
  82. package/src/trace/020-compact-field-projection.ts +122 -38
  83. package/src/trace/021-compact-decision-normalization.ts +171 -0
  84. package/src/trace/022-trace-fact-preflight.ts +21 -0
  85. package/src/trace/023-nested-event-scopes.ts +23 -0
  86. package/src/trace/024-compact-observation-decision.ts +81 -0
  87. package/src/trace/025-trace-implementation-scope.ts +123 -0
  88. package/src/trace/026-trace-start-scope.ts +336 -0
  89. package/src/trace/027-trace-scope-execution.ts +566 -0
  90. package/src/trace/028-trace-operation-execution.ts +336 -0
  91. package/src/trace/029-trace-start-implementation.ts +172 -0
  92. package/src/trace/030-event-runtime-resolution.ts +151 -0
  93. package/src/trace/031-local-call-expansion.ts +37 -0
  94. package/src/trace/implementation-hints.ts +9 -6
  95. package/src/trace/selectors.ts +1 -0
  96. package/src/trace/trace-engine.ts +122 -624
  97. package/src/types.ts +57 -0
  98. package/src/utils/001-placeholders.ts +188 -10
  99. package/src/version.ts +1 -1
  100. package/dist/chunk-ZQABU7MR.js +0 -12151
  101. package/dist/chunk-ZQABU7MR.js.map +0 -1
@@ -1,5 +1,18 @@
1
1
  import type { Db } from './connection.js';
2
2
  import { CURRENT_SCHEMA_VERSION, schemaVersion } from './migrations.js';
3
+ import {
4
+ invalidFactJsonCategories,
5
+ type FactJsonCategoryCount,
6
+ } from './002-fact-json-inventory.js';
7
+ import {
8
+ invalidFactSemanticCategories,
9
+ type FactSemanticCategoryCount,
10
+ type PackageFactPhase,
11
+ } from './003-current-fact-semantics.js';
12
+ import {
13
+ invalidSchemaStructureCategories,
14
+ type SchemaStructureCategoryCount,
15
+ } from './005-schema-structure.js';
3
16
  import { ANALYZER_VERSION } from '../version.js';
4
17
 
5
18
  export type FactLifecycleCode =
@@ -18,6 +31,7 @@ const remediation = [
18
31
  'service-flow index --workspace /workspace --force',
19
32
  'service-flow link --workspace /workspace --force',
20
33
  ].join('\n');
34
+ const CATEGORY_LIMIT = 24;
21
35
 
22
36
  function count(db: Db, sql: string, ...params: unknown[]): number {
23
37
  const row = db.prepare(sql).get(...params);
@@ -32,35 +46,13 @@ function oldAnalyzerCount(db: Db, workspaceId?: number): number {
32
46
  workspaceId, workspaceId, ANALYZER_VERSION);
33
47
  }
34
48
 
35
- function invalidCurrentFactCount(db: Db, workspaceId?: number): number {
36
- const outbound = count(db, `SELECT COUNT(*) count
37
- FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
38
- WHERE (? IS NULL OR r.workspace_id=?) AND r.fact_analyzer_version=?
39
- AND (typeof(c.call_site_start_offset)<>'integer'
40
- OR typeof(c.call_site_end_offset)<>'integer'
41
- OR c.call_site_start_offset<0
42
- OR c.call_site_end_offset<=c.call_site_start_offset)`,
43
- workspaceId, workspaceId, ANALYZER_VERSION);
44
- const symbols = count(db, `SELECT COUNT(*) count
45
- FROM symbol_calls c JOIN repositories r ON r.id=c.repo_id
46
- WHERE (? IS NULL OR r.workspace_id=?)
47
- AND (c.call_role='legacy_unknown'
48
- OR (r.fact_analyzer_version=? AND (
49
- typeof(c.call_site_start_offset)<>'integer'
50
- OR typeof(c.call_site_end_offset)<>'integer'
51
- OR c.call_site_start_offset<0
52
- OR c.call_site_end_offset<=c.call_site_start_offset
53
- OR c.call_role NOT IN ('ordinary_call','event_subscribe_handler'))))`,
54
- workspaceId, workspaceId, ANALYZER_VERSION);
55
- return outbound + symbols;
56
- }
57
-
58
49
  export function factLifecycleDiagnostic(
59
50
  db: Db,
60
51
  workspaceId?: number,
52
+ phase: PackageFactPhase = 'pre_package',
61
53
  ): FactLifecycleDiagnostic | undefined {
62
54
  return schemaLifecycleDiagnostic(db)
63
- ?? currentFactLifecycleDiagnostic(db, workspaceId);
55
+ ?? currentFactLifecycleDiagnostic(db, workspaceId, phase);
64
56
  }
65
57
 
66
58
  export function schemaLifecycleDiagnostic(
@@ -83,29 +75,67 @@ export function schemaLifecycleDiagnostic(
83
75
  currentSchemaVersion: currentSchema,
84
76
  requiredSchemaVersion: CURRENT_SCHEMA_VERSION,
85
77
  };
78
+ const structureCategories = invalidSchemaStructureCategories(db);
79
+ if (structureCategories.length > 0)
80
+ return reindexDiagnostic(0, structureCategories);
86
81
  return undefined;
87
82
  }
88
83
 
89
84
  export function currentFactLifecycleDiagnostic(
90
85
  db: Db,
91
86
  workspaceId?: number,
87
+ phase: PackageFactPhase = 'pre_package',
92
88
  ): FactLifecycleDiagnostic | undefined {
93
89
  const staleRepositories = oldAnalyzerCount(db, workspaceId);
94
- const invalidFacts = invalidCurrentFactCount(db, workspaceId);
95
- if (staleRepositories === 0 && invalidFacts === 0) return undefined;
90
+ if (staleRepositories > 0)
91
+ return reindexDiagnostic(staleRepositories, []);
92
+ const jsonCategories = invalidFactJsonCategories(db, workspaceId);
93
+ if (jsonCategories.length > 0)
94
+ return reindexDiagnostic(0, jsonCategories);
95
+ const semanticCategories = invalidFactSemanticCategories(
96
+ db, workspaceId, phase,
97
+ );
98
+ if (semanticCategories.length === 0) return undefined;
99
+ return reindexDiagnostic(0, semanticCategories);
100
+ }
101
+
102
+ type InvalidFactCategory =
103
+ | FactJsonCategoryCount
104
+ | FactSemanticCategoryCount
105
+ | SchemaStructureCategoryCount;
106
+
107
+ function reindexDiagnostic(
108
+ staleRepositories: number,
109
+ categories: InvalidFactCategory[],
110
+ ): FactLifecycleDiagnostic {
111
+ const invalidFacts = categories.reduce((sum, item) => sum + item.count, 0);
112
+ const shown = categories.slice(0, CATEGORY_LIMIT);
96
113
  return {
97
114
  severity: 'error',
98
115
  code: 'reindex_required',
99
- message: 'Call-site facts are stale or lack typed roles and exact spans; force index and link before tracing or rebuilding graph edges.',
116
+ message: 'Current facts are stale or fail bounded semantic integrity checks; force index and link before tracing or rebuilding graph edges.',
100
117
  remediation,
101
118
  staleRepositoryCount: staleRepositories,
102
119
  invalidCallFactCount: invalidFacts,
120
+ invalidFactCategories: shown,
121
+ invalidFactCategoryCount: categories.length,
122
+ shownInvalidFactCategoryCount: shown.length,
123
+ omittedInvalidFactCategoryCount: categories.length - shown.length,
103
124
  requiredAnalyzerVersion: ANALYZER_VERSION,
104
125
  };
105
126
  }
106
127
 
107
- export function assertWorkspaceLinkable(db: Db, workspaceId: number): void {
108
- const diagnostic = factLifecycleDiagnostic(db, workspaceId);
128
+ export function assertWorkspaceLinkable(
129
+ db: Db,
130
+ workspaceId: number,
131
+ phase: PackageFactPhase = 'pre_package',
132
+ ): void {
133
+ const diagnostic = factLifecycleDiagnostic(db, workspaceId, phase);
109
134
  if (!diagnostic) return;
110
- throw new Error(`${diagnostic.code}: ${diagnostic.message}\n${diagnostic.remediation}`);
135
+ const categories = Array.isArray(diagnostic.invalidFactCategories)
136
+ ? ` categories=${JSON.stringify(diagnostic.invalidFactCategories)}`
137
+ : '';
138
+ throw new Error(
139
+ `${diagnostic.code}: ${diagnostic.message}${categories}\n${diagnostic.remediation}`,
140
+ );
111
141
  }
@@ -0,0 +1,169 @@
1
+ import type { Db } from './connection.js';
2
+ import { ANALYZER_VERSION } from '../version.js';
3
+
4
+ export type FactJsonShape = 'object' | 'array';
5
+
6
+ export interface FactJsonInventoryItem {
7
+ table: string;
8
+ column: string;
9
+ nullable: boolean;
10
+ shape: FactJsonShape;
11
+ consumers: readonly string[];
12
+ repositoryJoin: string;
13
+ }
14
+
15
+ export interface FactJsonCategoryCount {
16
+ category: string;
17
+ count: number;
18
+ }
19
+
20
+ const directRepositoryJoin = 'JOIN repositories lifecycle_repo ON lifecycle_repo.id=fact.repo_id';
21
+
22
+ export const LINK_FACT_JSON_INVENTORY: readonly FactJsonInventoryItem[] = [
23
+ {
24
+ table: 'repositories',
25
+ column: 'dependencies_json',
26
+ nullable: false,
27
+ shape: 'object',
28
+ consumers: ['helper-package-linker'],
29
+ repositoryJoin: '',
30
+ },
31
+ {
32
+ table: 'repositories',
33
+ column: 'package_public_surface_json',
34
+ nullable: false,
35
+ shape: 'object',
36
+ consumers: ['package-import-symbol-resolver'],
37
+ repositoryJoin: '',
38
+ },
39
+ {
40
+ table: 'handler_methods',
41
+ column: 'decorator_resolution_json',
42
+ nullable: false,
43
+ shape: 'object',
44
+ consumers: ['implementation-linker', 'trace-selector'],
45
+ repositoryJoin:
46
+ 'JOIN handler_classes owner ON owner.id=fact.handler_class_id JOIN repositories lifecycle_repo ON lifecycle_repo.id=owner.repo_id',
47
+ },
48
+ {
49
+ table: 'symbols',
50
+ column: 'evidence_json',
51
+ nullable: true,
52
+ shape: 'object',
53
+ consumers: ['symbol-call-resolver', 'implementation-linker'],
54
+ repositoryJoin: directRepositoryJoin,
55
+ },
56
+ {
57
+ table: 'symbol_calls',
58
+ column: 'evidence_json',
59
+ nullable: false,
60
+ shape: 'object',
61
+ consumers: ['package-import-symbol-resolver', 'event-subscription-linker'],
62
+ repositoryJoin: directRepositoryJoin,
63
+ },
64
+ {
65
+ table: 'outbound_calls',
66
+ column: 'evidence_json',
67
+ nullable: false,
68
+ shape: 'object',
69
+ consumers: ['cross-repository-linker', 'trace-engine'],
70
+ repositoryJoin: directRepositoryJoin,
71
+ },
72
+ {
73
+ table: 'outbound_calls',
74
+ column: 'alias_chain_json',
75
+ nullable: true,
76
+ shape: 'array',
77
+ consumers: ['cross-repository-linker', 'trace-engine'],
78
+ repositoryJoin: directRepositoryJoin,
79
+ },
80
+ {
81
+ table: 'service_bindings',
82
+ column: 'placeholders_json',
83
+ nullable: false,
84
+ shape: 'array',
85
+ consumers: ['cross-repository-linker', 'trace-engine'],
86
+ repositoryJoin: directRepositoryJoin,
87
+ },
88
+ {
89
+ table: 'service_bindings',
90
+ column: 'helper_chain_json',
91
+ nullable: true,
92
+ shape: 'array',
93
+ consumers: ['cross-repository-linker', 'trace-engine'],
94
+ repositoryJoin: directRepositoryJoin,
95
+ },
96
+ {
97
+ table: 'cds_requires',
98
+ column: 'raw_json',
99
+ nullable: false,
100
+ shape: 'object',
101
+ consumers: ['service-resolution'],
102
+ repositoryJoin: directRepositoryJoin,
103
+ },
104
+ ] as const;
105
+
106
+ function repositoryPredicate(item: FactJsonInventoryItem): string {
107
+ if (item.table === 'repositories') {
108
+ return `fact.fact_analyzer_version=?
109
+ AND (? IS NULL OR fact.workspace_id=?)`;
110
+ }
111
+ return `lifecycle_repo.fact_analyzer_version=?
112
+ AND (? IS NULL OR lifecycle_repo.workspace_id=?)`;
113
+ }
114
+
115
+ function invalidJsonCount(
116
+ db: Db,
117
+ item: FactJsonInventoryItem,
118
+ workspaceId: number | undefined,
119
+ ): number {
120
+ const nullClause = item.nullable
121
+ ? `fact.${item.column} IS NOT NULL AND`
122
+ : `fact.${item.column} IS NULL OR`;
123
+ const row = db.prepare(`SELECT COUNT(*) count FROM ${item.table} fact
124
+ ${item.repositoryJoin}
125
+ WHERE ${repositoryPredicate(item)}
126
+ AND (${nullClause} json_valid(fact.${item.column})=0)`).get(
127
+ ANALYZER_VERSION, workspaceId, workspaceId,
128
+ );
129
+ return Number(row?.count ?? 0);
130
+ }
131
+
132
+ function wrongShapeCount(
133
+ db: Db,
134
+ item: FactJsonInventoryItem,
135
+ workspaceId: number | undefined,
136
+ ): number {
137
+ const row = db.prepare(`SELECT COUNT(*) count FROM ${item.table} fact
138
+ ${item.repositoryJoin}
139
+ WHERE ${repositoryPredicate(item)}
140
+ AND fact.${item.column} IS NOT NULL
141
+ AND json_type(fact.${item.column})<>?`).get(
142
+ ANALYZER_VERSION, workspaceId, workspaceId, item.shape,
143
+ );
144
+ return Number(row?.count ?? 0);
145
+ }
146
+
147
+ function category(
148
+ item: FactJsonInventoryItem,
149
+ suffix: string,
150
+ count: number,
151
+ ): FactJsonCategoryCount {
152
+ return {
153
+ category: `json_${item.table}_${item.column}_${suffix}`,
154
+ count,
155
+ };
156
+ }
157
+
158
+ export function invalidFactJsonCategories(
159
+ db: Db,
160
+ workspaceId?: number,
161
+ ): FactJsonCategoryCount[] {
162
+ const invalid = LINK_FACT_JSON_INVENTORY.map((item) =>
163
+ category(item, 'invalid', invalidJsonCount(db, item, workspaceId)))
164
+ .filter((item) => item.count > 0);
165
+ if (invalid.length > 0) return invalid;
166
+ return LINK_FACT_JSON_INVENTORY.map((item) =>
167
+ category(item, 'wrong_shape', wrongShapeCount(db, item, workspaceId)))
168
+ .filter((item) => item.count > 0);
169
+ }