@saptools/service-flow 0.1.65 → 0.1.67

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 (42) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +67 -11
  3. package/TECHNICAL-NOTE.md +41 -1
  4. package/dist/{chunk-OONNRIDL.js → chunk-ZQABU7MR.js} +3764 -643
  5. package/dist/chunk-ZQABU7MR.js.map +1 -0
  6. package/dist/cli.js +158 -295
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +196 -1
  9. package/dist/index.js +7 -3
  10. package/dist/index.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/cli/002-doctor-lifecycle.ts +66 -0
  13. package/src/cli/doctor.ts +14 -27
  14. package/src/cli.ts +130 -102
  15. package/src/db/000-call-fact-repository.ts +537 -0
  16. package/src/db/001-fact-lifecycle.ts +111 -0
  17. package/src/db/migrations.ts +16 -1
  18. package/src/db/repositories.ts +1 -315
  19. package/src/db/schema.ts +4 -2
  20. package/src/index.ts +22 -0
  21. package/src/linker/004-event-subscription-handler-linker.ts +300 -0
  22. package/src/linker/cross-repo-linker.ts +23 -2
  23. package/src/output/001-compact-json-output.ts +6 -0
  24. package/src/parsers/imported-wrapper-parser.ts +4 -0
  25. package/src/parsers/outbound-call-parser.ts +11 -1
  26. package/src/parsers/symbol-parser.ts +7 -4
  27. package/src/trace/010-traversal-scope.ts +188 -0
  28. package/src/trace/011-event-subscriber-traversal.ts +366 -0
  29. package/src/trace/012-trace-graph-lookups.ts +74 -0
  30. package/src/trace/013-trace-root-scopes.ts +279 -0
  31. package/src/trace/014-compact-contract.ts +347 -0
  32. package/src/trace/015-trace-edge-recorder.ts +336 -0
  33. package/src/trace/016-compact-projector.ts +697 -0
  34. package/src/trace/017-trace-context.ts +378 -0
  35. package/src/trace/018-compact-trace.ts +87 -0
  36. package/src/trace/019-trace-edge-semantics.ts +328 -0
  37. package/src/trace/020-compact-field-projection.ts +387 -0
  38. package/src/trace/dynamic-branches.ts +35 -13
  39. package/src/trace/trace-engine.ts +271 -245
  40. package/src/types.ts +10 -0
  41. package/src/version.ts +1 -1
  42. package/dist/chunk-OONNRIDL.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.67
4
+
5
+ - Published the completed event-subscriber and compact-graph release as an output/package-only patch. SQLite remains schema 12 and `ANALYZER_VERSION` remains `0.1.66-facts.1`, proving package version changes no longer force an unnecessary fact reindex.
6
+
7
+ ## 0.1.66
8
+
9
+ - Made event-subscription handler identity durable with explicit `event_subscribe_handler` symbol-call roles, resolver-independent `factOrigin` provenance, and exact zero-based UTF-16 outer-call spans on outbound and symbol-call facts. Ordinary calls use `ordinary_call`; migrated facts remain fail-closed as `legacy_unknown` instead of receiving heuristic roles or spans.
10
+ - Upgraded SQLite to schema 12 with indexed call-site/role columns, stale-analyzer lifecycle checks, bounded read-only upgrade diagnostics, and a link preflight that refuses legacy or incomplete facts before replacing the existing graph. Upgrading from 0.1.65 requires `index --force` followed by `link --force`.
11
+ - Persisted one bounded `EVENT_SUBSCRIPTION_HANDLED_BY` edge per subscription after final symbol-call resolution, using the exact workspace/repository/file/full-span association and stable resolved, ambiguous, unresolved, or missing-association targets. Link never falls back to caller, line, start offset, labels, or case-folded event names.
12
+ - Continued async traces from an emitted event through every exact, case-sensitive workspace-name subscriber registration into each distinct resolved generic handler symbol and its downstream scope. Preserved registration fan-out and duplicate bridges, isolated subscriber context, enforced causal depth, convergence, and structural cycle safeguards, and kept ambiguous/unresolved/missing associations non-traversable. This is explicitly `static_name_only` inference, not proof of runtime broker/channel/tenant/payload delivery.
13
+ - Prevented event-handler-role and legacy symbol calls from leaking through ordinary synchronous local-symbol traversal when async traversal is disabled.
14
+ - Added versioned `compact-json` output to trace and graph. `service-flow/compact-graph@1` uses canonical semantic endpoints, sorted dictionaries, fixed-width tuples, dense output-local IDs, bounded allowlisted decision/diagnostic summaries, deterministic aggregation, and invocation-scoped `traceOrdinals` plus generation-scoped audit references.
15
+ - Kept existing pretty detailed JSON as the authoritative complete evidence/candidate artifact, along with existing table and Mermaid output. Compact JSON is intentionally lossy and omits raw evidence, candidate bodies and scores, payload/call expressions, helper chains, supplied runtime values, and arbitrary diagnostic/remediation text; it is deterministic only for identical database state and inputs.
16
+ - Made invalid trace/graph formats fail before database access with non-zero status, no stdout, and an explicit accepted-format list instead of silently falling back.
17
+ - Decoupled package/CLI version `0.1.66`, SQLite schema version `12`, and analyzer compatibility `0.1.66-facts.1`, allowing future output-only package patches to retain the analyzer without forcing a reindex.
18
+
3
19
  ## 0.1.65
4
20
 
5
21
  - Captured event-subscription handler references from `.on('Event', handlerRef)` as resolvable symbol-call facts using the existing import-binding-aware machinery for relative imports, package imports, namespace members, same-file symbols, and static accessors.
package/README.md CHANGED
@@ -25,7 +25,7 @@ Index independent Git repositories, persist CAP/CDS facts in SQLite, resolve cro
25
25
  - 🔗 **Service-to-service linking** — resolves `cds.connect.to(...)`, `remote.send(...)`, `cds.services.*` style calls, helper package imports, dynamic candidates, and unresolved evidence into graph edges
26
26
  - 🗄️ **SQLite-backed workspace cache** — stores deterministic facts under `.service-flow/service-flow.db` so large workspaces can be queried repeatedly without reparsing everything
27
27
  - 🧠 **Dynamic edge support** — preserves parameterized destinations and service paths such as `svc_${objectCode}_process`, then lets trace and graph commands apply runtime `--var key=value` values or explicitly explore/infer bounded dynamic target candidates
28
- - 📊 **Multiple output modes** — renders human-readable tables, JSON for automation, and Mermaid diagrams for architecture docs
28
+ - 📊 **Multiple output modes** — renders human-readable tables, authoritative detailed JSON, Mermaid diagrams, and versioned compact JSON for AI-oriented topology analysis
29
29
  - 🩺 **Diagnostics-first workflow** — records parse/index issues and exposes them through `service-flow doctor` instead of hiding partial analysis
30
30
  - 🧩 **CAP helper-aware binding evidence** — follows imported helpers exported directly or through named export lists and separates alias, destination, and service-path expressions for dynamic `cds.connect.to(alias, options)` calls
31
31
  - 🧭 **Nested workspace discovery** — scans nested repositories even when the selected root is itself a valid Git repository, while ignoring empty `.git` placeholders
@@ -60,7 +60,7 @@ npm install @saptools/service-flow
60
60
  - Dynamic target exploration is explicit. Default `--dynamic-mode strict` keeps every target with unresolved runtime variables fail-closed, but diagnostics can provide complete copyable `--var` sets. `--dynamic-mode candidates` renders only viable, capped, explicitly unselected branches while the route remains unresolved, never enters their handler bodies, and adds no exploratory branch once complete explicit values resolve the route. `--dynamic-mode infer` traverses only when the top viable, complete candidate scores at least `0.85` and exceeds the runner-up by more than `0.05`; exact ties, candidates exactly on the margin, conflicts, duplicate identities, incomplete leaders, and weaker scores stay unresolved.
61
61
  - Explicit variables are applied before candidate ranking. When a call has one selected binding, its original service-path, alias, and destination templates are matched against each concrete candidate before any repository-wide fallback; a package-level require is considered only when its alias matches that binding in the same caller repository. A conflict between a supplied value and a value derived from one of those validated signals rejects the candidate and yields `no_candidate_after_runtime_substitution` rather than a resolver-only target.
62
62
  - Identity fallback removes an npm scope, splits camel case, lowercases, folds non-alphanumeric separator runs to `_`, and trims edge separators. It requires the complete normalized concrete route-owner package basename or repository name to match one placeholder with literal prefix and suffix, a resolved implementation for that effective operation, and workspace-wide unique identity/value evidence. A helper-owned implementation is valid evidence for an inherited operation, but helper and base-model names are never used as route identity. It never uses substrings, duplicate names, or discovery order. Generated commands shell-quote expression keys and values when required.
63
- - `--max-dynamic-candidates` bounds candidate branches, viable and rejected suggestions, variable sets, nested derivation/conflict evidence, and projected persisted candidate arrays in table and JSON output. Link-time and doctor candidate-like arrays carry stable total/shown/omitted metadata; canonical SQLite facts, rather than a displayed prefix, remain the source for trace inference and implementation hints. `omittedCandidateCount` means omitted **viable** candidates; rejected candidates have separate total, shown, and omitted counts.
63
+ - `--max-dynamic-candidates` bounds candidate branches, viable and rejected suggestions, variable sets, nested derivation/conflict evidence, and projected persisted candidate arrays in table and detailed JSON output. Link-time and doctor candidate-like arrays carry stable total/shown/omitted metadata; canonical SQLite facts, rather than a displayed prefix, remain the source for trace inference and implementation hints. `omittedCandidateCount` means omitted **viable** candidates; rejected candidates have separate total, shown, and omitted counts.
64
64
  - Repository selectors on list, trace, graph, and inspect commands narrow scope by workspace and exact repository ID. Unknown selectors return empty machine-readable diagnostics instead of falling back to another workspace or the whole database.
65
65
  - Helper-package dependency edges prefer exact indexed package names. Duplicate package-name candidates are persisted as ambiguous evidence rather than silently selecting one repository.
66
66
  - Handler registration parsing is AST-based for common `createCombinedHandler({ handler: ... })` forms: direct arrays, arrays assembled with spreads, non-`handlers` array names, aliased class imports, default-imported arrays, named exported arrays, and safe relative re-exports. Class-level rows keep registration file/line and import evidence.
@@ -78,9 +78,11 @@ npm install @saptools/service-flow
78
78
  - Fresh databases include foreign keys for key graph, run, and diagnostic tables. Migrated legacy stores that still lack that metadata are reported by doctor with `legacy_schema_weaker_foreign_keys`; rebuild into a fresh database if strict structural parity is required.
79
79
  - Parser warnings describe analysis completeness, while routing status describes graph behavior. A terminal DB edge can remain terminal while still exposing parser warning evidence about an unknown entity.
80
80
  - Persisted graph rows take precedence during `trace` and `graph`: resolved call edges keep their `graph_edges.id`, `outbound_calls.id`, call-site file/line, outbound parser evidence, linker status/reason, and selected target evidence. Contextual runtime resolution can enrich evidence but does not replace an already resolved persisted target. Terminal start diagnostics such as ambiguous operations or rejected implementations return zero nodes and zero edges by default; candidates remain in structured diagnostics for automation.
81
+ - Event-subscription handler facts have the durable role `event_subscribe_handler`, retain `factOrigin: event_subscribe_handler_reference`, and keep resolver-owned `candidateStrategy` separate. Bare identifiers, `Class.member` references, and one single-argument wrapper level are supported; inline callbacks, recursive or multi-argument wrappers, dynamic event names, `.once(...)`, and other unsupported shapes remain fail-closed.
82
+ - A subscription and its handler reference associate only by workspace, repository, normalized source file, and the complete non-null outer `.on(...)` call span, with matching line/caller invariants. Link never falls back to caller, line, start offset, label, or case-folded name heuristics. Resolved, ambiguous, unresolved, and missing associations each persist one bounded `EVENT_SUBSCRIPTION_HANDLED_BY` row per registration.
81
83
  - OData entity paths are conservative terminal remote entity edges. Reads, mutations, deletes, navigation paths, media-stream paths such as `/Documents(ID)/content`, and uppercase unknown entity-set candidates do not inflate unresolved operation counts. Lowercase action/function-style paths remain eligible for indexed operation resolution.
82
84
  - External HTTP destinations are static only when a safe literal or local const literal proves the value. Identifier, property-read, function-call, and arbitrary destination expressions are dynamic with stable `destination:dynamic:<hash>` ids and neutral labels; conditional literal branches expose only safe candidate names.
83
- - Schema version 11 preserves the earlier reindex-required policy for legacy graph evidence and adds nullable writer-owner metadata without dropping prior run history. `doctor --strict` reports schema and analyzer drift when relink alone cannot make an upgraded database equivalent to a fresh index.
85
+ - Schema version 12 stores the exact UTF-16, half-open outer-call span on outbound and symbol-call facts plus an explicit symbol-call role. Migrated rows retain null spans and `legacy_unknown` rather than receiving guessed provenance, so upgrading from 0.1.65 requires a forced index followed by a forced link. `doctor --strict` reports schema and analyzer drift when relink alone cannot make an upgraded database equivalent to a fresh index.
84
86
 
85
87
 
86
88
  ## 🚀 Quick Start
@@ -107,6 +109,16 @@ service-flow doctor --workspace /path/to/workspace
107
109
 
108
110
  After `init`, the workspace configuration and SQLite database live below the selected workspace by default. Run `index` whenever source changes; unchanged repositories are skipped unless `--force` is supplied. Then run `link` to rebuild the graph edges used by `trace` and `graph`.
109
111
 
112
+ > [!IMPORTANT]
113
+ > Version 0.1.66 upgrades the database to schema 12 and changes the fact analyzer to `0.1.66-facts.1`. A migrated 0.1.65 database deliberately leaves old symbol-call roles as `legacy_unknown` and call-site spans null. Refresh facts and graph edges before tracing:
114
+ >
115
+ > ```bash
116
+ > service-flow index --workspace /path/to/workspace --force
117
+ > service-flow link --workspace /path/to/workspace --force
118
+ > ```
119
+ >
120
+ > Link refuses stale or incompletely reindexed facts before deleting the previous graph. Package/CLI version, SQLite schema version, and analyzer compatibility version are independent contracts; a later output-only package patch can keep the same analyzer version without forcing another reindex.
121
+
110
122
  ---
111
123
 
112
124
  ## 🧰 CLI
@@ -144,7 +156,7 @@ service-flow index --workspace /path/to/workspace --repo identity-service --forc
144
156
 
145
157
  ### 🔗 `service-flow link`
146
158
 
147
- Resolve indexed outbound calls after repositories have been indexed. This rebuilds the `graph_edges` table for the workspace. The summary separates remote operation calls resolved, local operation calls resolved, unresolved operation calls, ambiguous operation calls, dynamic operation calls, and terminal call edges so local CAP service resolutions are not labeled as remote.
159
+ Resolve indexed outbound calls after repositories have been indexed. This rebuilds the `graph_edges` table for the workspace. The summary separates remote operation calls resolved, local operation calls resolved, unresolved operation calls, ambiguous operation calls, dynamic operation calls, terminal call edges, and resolved/ambiguous/unresolved/missing event-subscription handler associations. Each subscription creates one `EVENT_SUBSCRIPTION_HANDLED_BY` edge; emit sites do not multiply that persisted cardinality.
148
160
 
149
161
  ```bash
150
162
  service-flow link --workspace /path/to/workspace
@@ -158,14 +170,18 @@ service-flow link --workspace /path/to/workspace --force
158
170
 
159
171
  ### Pipeline-safe output
160
172
 
161
- Normal command output is safe to pipe to a Unix consumer that intentionally stops reading early. A closed stdout pipe stops further output without an unhandled `EPIPE` stack trace; unrelated stdout failures still use the normal stderr diagnostic and non-zero exit outcome. Complete JSON, table, and Mermaid output bytes are unchanged.
173
+ Normal command output is safe to pipe to a Unix consumer that intentionally stops reading early. A closed stdout pipe stops further output without an unhandled `EPIPE` stack trace; unrelated stdout failures still use the normal stderr diagnostic and non-zero exit outcome. Complete detailed JSON, compact JSON, table, and Mermaid output bytes follow the same stdout policy.
174
+
175
+ Trace and graph validate `--format` before opening the database. An unknown value writes a clear error to stderr, emits no stdout, and exits non-zero instead of silently falling back to a different format.
162
176
 
163
177
  ### 🧵 `service-flow trace`
164
178
 
165
- Trace one starting point and render table, JSON, or Mermaid output. Trace now
179
+ Trace one starting point and render table, detailed JSON, compact JSON, or Mermaid output. Trace
166
180
  walks linked `graph_edges`, so a resolved remote operation is followed into the
167
181
  target handler up to `--depth` instead of showing only calls in the first file.
168
182
 
183
+ With `--include-async`, an emitted event is matched by its exact, case-sensitive raw name to every current-generation subscription in the same indexed workspace. Each registration produces a bridge to its resolved handler, and each distinct handler scope is expanded at most once for the same evaluation context. Duplicate registrations therefore remain visible without duplicating the handler body. A bridge is still rendered at the depth boundary, but its handler body is not expanded; structural self-cycles and mutual event cycles are rendered and stopped deterministically. Subscriber evaluation begins with an empty binding/payload context. This is a workspace-wide static name inference, not proof that a runtime broker, channel, tenant, ordering rule, payload, or deployed application will deliver an event. A subscribe call never reverse-triggers emitters, and `includeAsync=false` neither follows event bridges nor treats event-handler references as ordinary synchronous calls.
184
+
169
185
  ### Symbol-scoped helper traversal
170
186
 
171
187
  `service-flow trace` starts from the selected handler method symbol, renders outbound calls owned by that symbol, and follows conservative local helper-call facts. Handler helper properties such as `helper = async () => { ... }` and `helper = function () { ... }` are indexed as `ClassName.helper`; top-level CAP lifecycle, route, and event callbacks receive synthetic `module:<file>#callback:<line>` owners only when their body contains a supported outbound call or event subscription. Supported helper edges include same-file functions, `this.method()` calls, and exactly mapped relative imports/exports that resolve to an indexed executable symbol. Proxy-member calls keep factory/import evidence and avoid resolving by repository-wide member name alone when the target is ambiguous. Calls from unrelated functions in the same source file are not included merely because the file path matches.
@@ -173,12 +189,13 @@ target handler up to `--depth` instead of showing only calls in the first file.
173
189
  Local CAP calls through `cds.services.<Service>.<operation>()`, bracket service lookups, and simple aliases are indexed as local operation calls. Linking first stays within the same repository and matches the target operation by exact qualified CDS service name, exact simple service name, exact service path, or an unambiguous service-path suffix. If no same-repository service exists, the linker can use implementation-context evidence to resolve model-package operations for helper packages: a resolved/ambiguous implementation candidate, registration package, or dependency/import edge must tie the caller repository to the model operation. Name-only global matches are preserved as unresolved candidate evidence rather than guessed links. Entity accessors such as `cds.services.db.entities(...)` are treated as entity metadata access, not operation calls.
174
190
 
175
191
  Conservative local symbol traversal intentionally excludes decorators, built-ins such as `JSON.parse`, collection methods, third-party APIs, and arbitrary property chains unless the callee can plausibly resolve to an indexed local symbol. Named export lists such as `export { loadTemplate as publicLoadTemplate }` are indexed with the public exported name so relative imports can resolve. One-level object-literal helpers are indexed as symbols named like `cacheHelper.getConfiguration`; nested object literals are not yet expanded beyond the first helper level. `parseGeneratedConstants` remains a public low-level parser export for callers that need it, but generated constants are not persisted as graph facts in this patch; linking uses the deterministic decorator normalizer described above.
176
- JSON output includes typed nodes for calls, operations, database entities,
192
+ Detailed JSON output includes typed nodes for calls, operations, database entities,
177
193
  external destinations, and unresolved/dynamic candidates when edges exist. Chained CAP DB queries inside `cds.run(...)` and direct supported builders are parsed with TypeScript AST evidence for `SELECT`, `INSERT`, `UPSERT`, `UPDATE`, and `DELETE` forms. A direct builder needs both a recognized CAP root and a proven execution context: direct `await`, return from an `async` or syntactically guaranteed-Promise callable, or a static element of awaited `Promise.all([...])`. Plain query factories and unrelated methods named `from`, `where`, or `set` are not promoted to database facts. When the query target is genuinely dynamic, graph status remains terminal and JSON retains `parserWarning` evidence, while table and Mermaid render the target as `Entity: unknown` rather than a numeric call id.
178
194
 
179
195
  ```bash
180
196
  service-flow trace --workspace /path/to/workspace --repo facade-service --operation doWork
181
197
  service-flow trace --workspace /path/to/workspace --service /FacadeService --path /doWork --format json
198
+ service-flow trace --workspace /path/to/workspace --service /FacadeService --path /doWork --include-async --format compact-json
182
199
  service-flow trace --workspace /path/to/workspace --handler EntryHandler --depth 1 --format json
183
200
  service-flow trace --workspace /path/to/workspace --service /FacadeService --path /doWork --depth 2
184
201
  service-flow trace --workspace /path/to/workspace --repo facade-service --operation doWork --var objectCode=xx --var objectType=Thing
@@ -195,7 +212,7 @@ service-flow trace --workspace /path/to/workspace --service /FacadeService --pat
195
212
  | `--path <operationPath>` | Start from an operation path such as `/doWork` |
196
213
  | `--handler <name>` | Start from a handler class or handler-like selector |
197
214
  | `--depth <n>` | Maximum executable/service scope depth; defaults to `25`. Implementation hops are rendered at the current scope depth, while downstream handler bodies consume the next depth. The `step` field never exceeds the requested depth. |
198
- | `--format <format>` | `table`, `json`, or `mermaid`; defaults to `table` |
215
+ | `--format <format>` | Exactly `table`, `json`, `mermaid`, or `compact-json`; defaults to `table` |
199
216
  | `--include-external` | Include external HTTP/destination edges in traversal output |
200
217
  | `--include-db` | Include local DB query edges in traversal output |
201
218
  | `--include-async` | Include async publish/subscribe edges in traversal output |
@@ -212,6 +229,7 @@ Render a deeper architecture graph from the same selector model used by `trace`.
212
229
  ```bash
213
230
  service-flow graph --workspace /path/to/workspace --service /FacadeService --path /doWork
214
231
  service-flow graph --workspace /path/to/workspace --repo facade-service --operation doWork --format json
232
+ service-flow graph --workspace /path/to/workspace --repo facade-service --operation doWork --format compact-json
215
233
  ```
216
234
 
217
235
  | Flag | Description |
@@ -221,7 +239,7 @@ service-flow graph --workspace /path/to/workspace --repo facade-service --operat
221
239
  | `--operation <name>` | Filter/start by operation name |
222
240
  | `--service <path>` | Filter/start by service path |
223
241
  | `--path <operationPath>` | Filter/start by operation path |
224
- | `--format <format>` | `mermaid` or `json`; defaults to `mermaid` |
242
+ | `--format <format>` | Exactly `mermaid`, `json`, or `compact-json`; defaults to `mermaid` |
225
243
  | `--implementation-repo <name>` | Select one implementation repository for every ambiguous hop |
226
244
  | `--implementation-hint <scope>` | Apply a repeatable scoped implementation selection |
227
245
  | `--var <key=value>` | Apply repeatable runtime substitutions |
@@ -369,7 +387,7 @@ service-flow trace --workspace /path/to/workspace --repo facade-service --operat
369
387
 
370
388
  When a concrete target exists after variable substitution, the trace shows both the parameterized evidence and the resolved match. When it does not, `service-flow` keeps the edge as a dynamic candidate or unresolved edge so the missing link remains visible.
371
389
 
372
- When runtime values are missing, strict trace output includes the missing variable names plus bounded candidate guidance when indexed operations and routing metadata can derive values. JSON includes `dynamicTargetExploration`, `dynamicTargetCandidates`, `dynamicTargetCandidateSuggestions`, `suggestedVarSets`, and `dynamicTargetInference`; table output shows compact candidate counts and copyable `--var` examples. Use `--dynamic-mode candidates` to render capped exploratory branches, or `--dynamic-mode infer` to continue only through a unique fully-derived candidate.
390
+ When runtime values are missing, strict trace output includes the missing variable names plus bounded candidate guidance when indexed operations and routing metadata can derive values. Detailed JSON includes `dynamicTargetExploration`, `dynamicTargetCandidates`, `dynamicTargetCandidateSuggestions`, `suggestedVarSets`, and `dynamicTargetInference`; compact JSON retains only allowlisted counts and missing variable names, while table output shows compact candidate counts and copyable `--var` examples. Use `--dynamic-mode candidates` to render capped exploratory branches, or `--dynamic-mode infer` to continue only through a unique fully-derived candidate.
373
391
 
374
392
  Direct sends and same-file or imported wrappers share one path-candidate analysis. Literals and immutable aliases resolve; conditional or branch-assigned static alternatives remain ambiguous with bounded raw and normalized candidate projections plus counts; dynamic reassignments remain dynamic with the exact runtime identifier. Wrapper definition sends are treated as templates when a concrete caller-site edge can be indexed.
375
393
 
@@ -409,6 +427,7 @@ service-flow init /path/to/workspace --db /custom/path/service-flow.db
409
427
  - It does **not** execute CAP services, load `.env` files, call SAP BTP, or connect to remote systems.
410
428
  - Persisted summaries and CLI output redact keys that look like credentials, including `authorization`, `cookie`, `token`, `secret`, `password`, `key`, and `credential`.
411
429
  - Payload bodies are summarized for traceability; runtime payload values are not required for indexing.
430
+ - Compact JSON is projected through a field-by-field allowlist. It omits raw parser/outbound evidence, candidate and score bodies, payloads, call arguments, helper-chain bodies, supplied variable values, and arbitrary diagnostic/remediation text. Query metadata contains supplied variable names only; `runtimeValuesOmitted` is always `true`, so exact replay requires the original supplied values retained by the caller.
412
431
 
413
432
  ---
414
433
 
@@ -424,7 +443,9 @@ Step Type From To
424
443
  2 remote_action facade-service:srv/functions/Entry /IdentityService/resolveAccess srv/functions/EntryHandler.ts:10
425
444
  ```
426
445
 
427
- ### JSON
446
+ ### Detailed JSON
447
+
448
+ `--format json` remains the complete, pretty-printed `TraceResult` and the authoritative audit artifact for raw evidence, candidate inspection, locations, and effective versus persisted decisions. It is intentionally large.
428
449
 
429
450
  ```json
430
451
  {
@@ -439,6 +460,40 @@ Step Type From To
439
460
  }
440
461
  ```
441
462
 
463
+ ### Compact JSON
464
+
465
+ `--format compact-json` projects the same traversal into the minified, newline-terminated `service-flow/compact-graph@1` contract. It is a lossy AI-oriented semantic topology and bounded decision summary; use the detailed JSON companion whenever exact evidence is required.
466
+
467
+ ```json
468
+ {"schema":"service-flow/compact-graph@1","start":{"repo":"facade-service","servicePath":"/FacadeService","operation":"doWork","operationPath":null,"handler":null},"query":{"depth":25,"includeAsync":true,"includeDb":true,"includeExternal":true,"dynamicMode":"strict","maxDynamicCandidates":5,"suppliedVariableNames":[],"runtimeValuesOmitted":true,"implementationRepo":null,"implementationHints":[]},"source":{"schemaVersion":12,"analyzerVersion":"0.1.66-facts.1","graphGeneration":7},"summary":{"completeness":"complete","fullTraceNodes":2,"fullTraceEdges":1,"fullTraceDiagnostics":0,"nodes":2,"edges":1,"collapsedEdges":0,"statusCounts":{"resolved":0,"terminal":1,"inferred":0,"dynamic":0,"ambiguous":0,"unresolved":0,"cycle":0},"projection":{"evidence":"summary-only","syntheticEndpoints":0,"omittedUnreferencedFullNodes":0}},"repos":["facade-service"],"files":["srv/EntryHandler.ts"],"nodeColumns":["id","kind","label","repo","file","line"],"nodes":[["n0","symbol","EntryHandler.doWork",0,0,8],["n1","db_entity","Template",null,null,null]],"edgeColumns":["id","traceOrdinals","step","type","from","to","status","confidence","count","details"],"edges":[["e0",[0],1,"local_db_query","n0","n1","terminal",0.95,1,null]],"diagnosticColumns":["fullDiagnosticIndex","severity","code","message","file","line","details"],"diagnostics":[]}
469
+ ```
470
+
471
+ The `nodeColumns`, `edgeColumns`, and `diagnosticColumns` arrays define fixed-width tuples; absent cells are explicit `null`. Any breaking change to those columns or to the declared v1 top-level/query/source/summary/status/aggregation/diagnostic semantics requires a new `@N` schema. Repository and file dictionaries are sorted, and dense `n0...`/`e0...` IDs are assigned after canonical sorting. Those dense IDs are output-local and are not stable database identifiers.
472
+
473
+ Each compact edge's `traceOrdinals` contains the zero-based detailed edge index or indexes from the exact corresponding trace invocation. Aggregated equivalent observations retain their multiplicity in `count` and their complete sorted ordinal list. These ordinals are valid only for the same database generation, selector, traversal options, implementation hints, and runtime inputs. Bounded `details.refs` can also carry graph, call, operation, symbol, or handler IDs for exact drill-down in the declared `source.graphGeneration`; those database references are generation-scoped rather than long-lived IDs.
474
+
475
+ `source.analyzerVersion` is the one persisted analyzer value for the selected scope. It is `none` when no repositories exist, `mixed` when multiple analyzer versions are present, and `legacy_unknown` when the persisted value is absent. Reindex before relying on topology whenever the source reports a sentinel rather than the current analyzer.
476
+
477
+ For identical database state and inputs, canonical dictionaries, semantic endpoints, aggregation, and ordering make compact output byte-deterministic. Database rebuilds can legitimately change graph generation, trace ordinals, and generation-scoped references, so byte identity is not promised across arbitrary rebuilds. Representative large traces are regression-tested against both pretty and minified detailed JSON size budgets; compact output does not achieve this by weakening detailed JSON.
478
+
479
+ Write the compact graph and its detailed audit companion through ordinary stdout redirection:
480
+
481
+ ```bash
482
+ service-flow graph \
483
+ --workspace /path/to/workspace \
484
+ --repo facade-service \
485
+ --service /FacadeService \
486
+ --operation doWork \
487
+ --format compact-json > order-flow.graph.json
488
+
489
+ service-flow graph \
490
+ --workspace /path/to/workspace \
491
+ --repo facade-service \
492
+ --service /FacadeService \
493
+ --operation doWork \
494
+ --format json > order-flow.trace.json
495
+ ```
496
+
442
497
  ### Mermaid
443
498
 
444
499
  ```mermaid
@@ -451,6 +506,7 @@ flowchart TD
451
506
  ## ⚠️ Limitations
452
507
 
453
508
  - Static analysis cannot know every runtime branch, feature flag, or environment-specific destination.
509
+ - Exact event-name matching is a workspace-scoped static inference. It does not establish broker, destination, channel, tenant, payload compatibility, ordering, deployment, or runtime delivery.
454
510
  - Dynamic service names and paths may need `--var key=value` values to resolve concrete targets.
455
511
  - Highly customized frameworks can still appear as unresolved edges until parser support is added.
456
512
  - Parse failures are stored as diagnostics and reported by `service-flow doctor`.
package/TECHNICAL-NOTE.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Service Flow Resolution Notes
2
2
 
3
+ ## 0.1.66 event-subscriber and compact graph notes
4
+
5
+ ### Fact identity, migration, and link lifecycle
6
+
7
+ - Schema 12 adds nullable `call_site_start_offset`/`call_site_end_offset` columns to outbound and symbol calls and a required symbol-call role. Current parsers record zero-based, half-open TypeScript source spans in UTF-16 code units. An ordinary symbol call owns its invocation span; an event handler reference owns the outer subscription `.on(...)` span, so the outbound registration and handler-reference fact share an exact AST call-site identity.
8
+ - Current facts use `ordinary_call` or `event_subscribe_handler`. The migration default `legacy_unknown` is deliberately fail-closed: v11/0.1.65 rows receive neither a guessed role nor reconstructed spans from caller, line, JSON, or labels. Migrating marks repositories stale with `schema_v12_call_sites_require_reindex`; link refuses stale analyzer facts, `legacy_unknown`, or invalid/missing current-analyzer spans before replacing graph edges.
9
+ - Event-handler provenance is durable and orthogonal to resolution. Parser evidence retains `factOrigin: event_subscribe_handler_reference`, while `candidateStrategy` remains resolver-owned and can change during index-time or package-import resolution without erasing the origin, role, or span.
10
+ - Subscription association requires the same workspace, repository, normalized file, complete non-null outer-call span, and `event_subscribe_handler` role, then verifies source-line and available caller-symbol equality. There is no fallback to caller-only, file/line-only, start-only, callee labels, nearest symbols, or case-folded names. Missing or inconsistent identity yields one stable unresolved association; multiple role-site facts yield one ambiguous association.
11
+ - Linking runs subscription association after package-import symbol resolution and persists exactly one `EVENT_SUBSCRIPTION_HANDLED_BY` edge per `async_subscribe` registration. A resolved handler targets its generic executable symbol; ambiguous or unresolved references target a stable symbol reference; a missing or invalid association targets a stable subscription-handler reference. This persisted relation proves that a subscription declaration names a handler, not that a particular emitter reaches it at runtime.
12
+ - Package/CLI `VERSION`, SQLite `CURRENT_SCHEMA_VERSION`, and fact `ANALYZER_VERSION` are independent compatibility contracts. This release uses package 0.1.66, schema 12, and analyzer `0.1.66-facts.1`; an output-only package patch can retain the analyzer value. Compact `source.analyzerVersion` reports a unique persisted value, or the sentinel `none`, `mixed`, or `legacy_unknown` when that guarantee is unavailable.
13
+
14
+ Upgrade a 0.1.65 workspace explicitly:
15
+
16
+ ```bash
17
+ service-flow index --workspace /workspace --force
18
+ service-flow link --workspace /workspace --force
19
+ ```
20
+
21
+ Read-only commands do not migrate or mutate facts. They return bounded `schema_upgrade_required` or `reindex_required` diagnostics for unsupported state rather than leaking missing-column SQL errors. A migrated-but-not-reindexed schema 12 store never treats `legacy_unknown` as an ordinary call, and a failed link preflight leaves the previous graph and stale reason intact.
22
+
23
+ ### Async traversal semantics
24
+
25
+ - `includeAsync=false` excludes both event dispatch and event-handler-role rows from ordinary local-symbol traversal. With async enabled, only `HANDLER_EMITS_EVENT` initiates dispatch. `EVENT_CONSUMED_BY_HANDLER` remains an observed subscription call and never reverse-triggers emitters or other subscriptions.
26
+ - An emit loads current-generation `EVENT_SUBSCRIPTION_HANDLED_BY` rows from the same workspace with exact binary, case-sensitive equality on the raw event name. Each registration renders one `event_name_matches_subscription_handler` bridge with `matchStrategy=workspace_exact_event_name` and `dispatchCertainty=static_name_only`. Multiple exact registrations are fan-out; duplicate registrations remain separate bridge observations even when they resolve to one handler.
27
+ - A resolved bridge queues the generic handler symbol at the next causal depth with an empty binding/payload context. The bridge remains visible at the maximum depth but the body is not expanded. Unresolved, ambiguous, missing, or stale associations render bounded reasons and stop. An emitted event with no exact indexed subscriber remains terminal without a warning because an external consumer is valid.
28
+ - Structural ancestry and evaluation scheduling use separate canonical keys. Structural identity stops self- and mutual-event cycles even if contextual values differ; the evaluation key includes a deterministic context fingerprint so legitimately different call contexts can still be evaluated. Convergent duplicate registrations preserve every bridge, expand one already-scheduled target body once, and are not mislabeled as cycles.
29
+ - Exact workspace name equality is intentionally a static inference. It does not prove broker/channel compatibility, destination, tenant, payload transfer, deployment, ordering, delivery, or runtime reachability. Case-only near matches stay disconnected.
30
+
31
+ ### Compact semantic graph contract
32
+
33
+ - Existing table, Mermaid, and pretty detailed JSON retain their established shapes. Detailed JSON remains the complete audit artifact for raw evidence, candidates, source locations, and effective/persisted decisions. `compact-json` is a separate lossy projection built by an optional observer during the same trace execution; it neither changes `TraceResult` nor runs an independent SQL graph algorithm.
34
+ - The compact schema is `service-flow/compact-graph@1`. `start`, `query`, `source`, `summary`, sorted repository/file dictionaries, tuple-column declarations, nodes, edges, and diagnostics are always present. Tuple rows have fixed widths and explicit `null` cells. Breaking a declared column, field, status, aggregation, detail, diagnostic, or correlation meaning requires a new `@N` version.
35
+ - Semantic endpoints are captured from structured IDs at detailed-edge creation: operation to implementation handler, caller symbol/scope to callee symbol or target, event to subscriber symbol/reference, and target/scope to cycle. Outbound calls without a source symbol use an exact call-site endpoint. A genuinely unavailable side receives a side-specific synthetic endpoint; display labels are never merged into identity.
36
+ - Repositories, files, and canonical nodes use binary code-point sorting. Dense `n0...` and `e0...` IDs are assigned only after sorting and are output-local. Edges aggregate only when step, type, source, target, canonical status, confidence, and bounded decision details match. Multiplicity remains in `count`; all member detailed-edge indexes remain sorted in `traceOrdinals`.
37
+ - `traceOrdinals` is an invocation-scoped coordinate into the exact companion detailed trace. Bounded `details.refs` carries unioned graph/call/operation/symbol/handler IDs for same-generation drill-down. Neither ordinal nor database reference is a durable cross-rebuild ID; `source.schemaVersion`, `source.analyzerVersion`, and `source.graphGeneration` declare their compatibility scope.
38
+ - Canonical statuses are `resolved`, `terminal`, `inferred`, `dynamic`, `ambiguous`, `unresolved`, and `cycle`. A resolved subscription-handler reference reached through workspace event-name equality is `inferred`, never runtime-proven `resolved`; a true ancestry cycle takes precedence. Summary status counts and per-edge counts describe original detailed observations, not just aggregated rows, and ordinals partition the detailed edge indexes exactly.
39
+ - Decision and diagnostic projection is an explicit bounded allowlist. It can retain safe resolution summaries, missing variable names and authoritative counts, dynamic/implementation strategy, stable reason codes, event-association metadata, one templated remediation hint, and bounded audit references. It never copies arbitrary evidence, diagnostic messages, remediation text, candidate bodies/scores, supplied values, payloads, AST/call expressions, helper chains, or mirrored evidence objects. Redaction remains a final layer.
40
+ - Compact stdout is one minified JSON line plus one newline. Identical database state and inputs produce byte-identical output; a rebuild may legitimately change graph generation, trace ordinals, and database references. Large dynamic and async/DB regression fixtures enforce compact size against both pretty and minified detailed JSON without weakening the detailed artifact.
41
+ - Trace accepts exactly `table|json|mermaid|compact-json`; graph accepts exactly `mermaid|json|compact-json`. Invalid formats fail before database access, write no stdout, and identify the accepted values on stderr. Compact output uses the existing shared stdout/EPIPE behavior and ordinary shell redirection rather than a new file-writing subsystem.
42
+
3
43
  ## 0.1.56 direct query execution-context notes
4
44
 
5
45
  - A structural direct CAP builder root now produces one `local_db_query` fact in four proven contexts: direct `await`, a return from the nearest `async` callable, a return from a callable with an AST-guaranteed `Promise` or `PromiseLike` contract, and a static direct element of awaited `Promise.all([...])`. Evidence adds `queryExecutionContext` as `await`, `async_return`, `promise_return`, or `promise_aggregate`, while retaining the existing direct dispatch and bounded source offsets.
@@ -167,7 +207,7 @@ Schema version 6 adds queryable external target metadata columns to `outbound_ca
167
207
  - Operation candidate scores are clamped into `[0, 1]` before graph or trace rendering.
168
208
  - Helper package linking uses exact `repositories.package_name` matches before normalized directory-name fallback. Ambiguous package names are represented as ambiguous graph edges with bounded candidate projections and count metadata.
169
209
  - Fingerprints hash normalized package facts and package bytes in addition to source file paths/content and analyzer version.
170
- - The CLI version imports package metadata, so package metadata, `service-flow --version`, changelog, and analyzer/fingerprint version share one release source.
210
+ - The CLI version imports package metadata, while the analyzer/fingerprint compatibility value is declared independently. Releases that change generated facts advance the analyzer value; output-only package patches can leave it unchanged and avoid unnecessary reindex churn.
171
211
  - Supported runtime is Node.js 24+ with `node:sqlite` validation; older runtimes should fail with a compatibility message instead of a late `DatabaseSync` error.
172
212
 
173
213