@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.69
4
+
5
+ - Corrected one-hop derived import identity: conventional relative default-class instances use target-side default-export evidence, package-derived members cannot leak into same-file exact-name resolution, and package-backed proxy aliases fail closed with an honest unsupported reason. Unresolved and ambiguous local/package symbol-call facts now remain visible as non-traversable trace edges.
6
+ - Contained invalid prepared-repository snapshots with typed, site-aware diagnostics and repository savepoints. Healthy repositories in the same index invocation publish normally; partial and total failures return bounded summaries and non-zero CLI status, while the final cross-repository invalidation/materialization phase remains atomic.
7
+ - Persisted template-literal `.emit()`/`.on()` topics as runtime-dependent event facts. Missing exact keys produce dynamic candidates and compact-safe missing-variable diagnostics; supplying every emit/subscription key traverses only an exact case-sensitive substituted event-name match.
8
+ - Recognized multi-line `cds.run(...)` structurally and recorded bounded `hasForUpdate: true` evidence on both wrapped and directly executed fluent CQL chains.
9
+ - Added the fail-closed `single_hop_helper_return` binding strategy for a direct connect-derived helper return inside a try block whose catch has no value return or connect. Branching, value-returning/connect-producing catch/finally blocks, and second-hop helpers remain unresolved.
10
+ - Kept `service-flow/compact-graph@1` while rejecting raw control characters before trimming, completing tuple-equivalent status/target elision for non-database-backed target kinds, exposing bounded tied implementation repositories and selector/lifecycle categories, and sourcing safe reason codes from parser warnings. Tuple columns, refs, redaction, and detailed JSON remain unchanged.
11
+ - Package/CLI is `0.1.69`, SQLite remains schema `13`, analyzer compatibility is `0.1.69-facts.1`, and the compact schema remains `service-flow/compact-graph@1`. Existing workspaces require `index --force` followed by `link --force`; no schema migration is required.
12
+
13
+ ## 0.1.68
14
+
15
+ - Replaced line-based call and binding ownership with deterministic UTF-16 full-span containment, exact subscription-registration identity, durable handler-reference status, and parser-carried lexical binding-site provenance. Unsupported or ambiguous ownership and reaching assignments now fail closed instead of selecting a convenient same-line row.
16
+ - Upgraded SQLite to schema 13 with binding-site span/owner columns, an exact-site uniqueness backstop, and a repository package-public-surface carrier. Legacy rows keep null provenance and `legacy_unknown`; upgrading requires `index --force` followed by `link --force`, while invalid prepared lexical proofs and failed lifecycle preflight preserve the last good facts and graph.
17
+ - Preserved typed ESM/CommonJS binding shape, imported versus local names, requested package/subpath, exact relative-module scope, public exposure completeness, and executable-body eligibility. Package resolution no longer guesses from repository-wide exported-name uniqueness; aliases and namespace members resolve only through a proven public entry, proxy target module ambiguity fails closed, and package identity changes stale dependency-only helper edges as well as matching symbol calls. Mutable, reassigned, or escaped public values and mutated CommonJS namespace objects fail closed instead of selecting a stale executable body.
18
+ - Strengthened current-fact lifecycle validation for link-consumed JSON, event origin/status/cardinality, exact owners, binding references, package provenance/state, and resolution matrices. Trace, doctor, and link return bounded `reindex_required` diagnostics before JSON-dependent work or graph replacement; doctor reports valid pre-link package rows as pending/relink work instead of terminal unresolved quality.
19
+ - Made `${...}` scanning balanced and non-evaluating, and made OData query, segment, quote, and invocation analysis treat accepted placeholders as opaque tokens. Optional chaining, calls, strings, regular expressions, and slashes inside a placeholder no longer become path syntax; exact complex `--var` keys can recover dynamic GET operation resolution.
20
+ - Kept `service-flow/compact-graph@1` while restoring complex missing-variable names and authoritative shown/omitted counts under the documented safe grammar. Compact edges omit only effective status/target summaries proven identical to their canonical tuple cells; differing persisted decisions and every bounded drill-down reference remain. This recovers the representative compact budgets of at most 15% of pretty and 20% of minified detailed JSON without weakening the detailed artifact or deleting references.
21
+ - Re-exported the public fact and trace type contracts from the package root. Package/CLI `0.1.68`, schema `13`, analyzer `0.1.68-facts.1`, and compact schema `service-flow/compact-graph@1` remain independent compatibility values.
22
+
3
23
  ## 0.1.67
4
24
 
5
25
  - 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.
package/README.md CHANGED
@@ -54,7 +54,7 @@ npm install @saptools/service-flow
54
54
 
55
55
  ### Correctness notes
56
56
 
57
- - Runtime `--var` values are considered only for dynamic, ambiguous, or unresolved **remote** graph edges whose alias, destination, service path, or operation path expressions contain supplied placeholders. Placeholder keys are the full trimmed expression inside `${...}`, so keys such as `domainInfo.serviceName`, `domainInfo.shortName?.toLowerCase()`, and `items[0].service` can be supplied literally without JavaScript evaluation. Local database, external HTTP, event, and already resolved static edges keep their persisted status, target, reason, and confidence. Partial substitutions remain dynamic and report the missing placeholder names.
57
+ - Runtime `--var` values are considered only for dynamic, ambiguous, or unresolved **remote** graph edges whose alias, destination, service path, or operation path expressions contain supplied placeholders. Placeholder keys are the full trimmed expression inside a balanced `${...}` region, so keys such as `domainInfo.serviceName`, `tenantInfo.region?.toLowerCase()`, and `items?.[0].service` can be supplied literally without JavaScript evaluation. Quote complex keys in the shell, for example `--var 'tenantInfo.region?.toLowerCase()=lookup'`. Local database, external HTTP, event, and already resolved static edges keep their persisted status, target, reason, and confidence. Partial substitutions remain dynamic and report the missing placeholder names.
58
58
  - `trace` and `graph` both accept repeatable `--var key=value` options. Effective substitutions are rendered in trace evidence without mutating the persisted graph. Confidence values are bounded to `[0, 1]`.
59
59
  - Contextual binding attempts retain a labelled `contextualPreSubstitutionState` for auditability. After compatible `--var` substitution, a dynamic edge, `effectiveResolution`, and `linker.reason` use the same sorted current missing-key message. Structural contextual blockers remain separately visible as `contextualBlocker`; they cannot be inferred away.
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.
@@ -71,18 +71,26 @@ npm install @saptools/service-flow
71
71
  - Repository fingerprints include source content, package name/version, dependencies and devDependencies, scripts, normalized `cds.requires` (including nested credentials), package file content, and the analyzer version. Metadata-only changes therefore trigger reindexing.
72
72
  - Index publication is designed around the last-good snapshot: failed parse or persistence attempts are recorded as diagnostics and must not be mixed with older graph facts. After indexing changes, relink before relying on graph/trace output; doctor reports stale or inconsistent stores where detectable.
73
73
  - Source discovery, file reads, hashing, parsing, and publication are all inside the repository-level protected indexing flow. A failed read keeps the previous fingerprint and facts, marks the repository failed, and records a `source_read_failed` diagnostic; a later successful index clears superseded read-failure diagnostics during fact publication.
74
- - Index preparation remains deterministic and sequential. Each source file and `package.json` is read once into a repository-scoped immutable snapshot, and all TypeScript parser entrypoints share one lazy AST for that file. Source snapshots are released after repository preparation; prepared facts still publish together in the existing workspace transaction.
74
+ - Index preparation remains deterministic and sequential. Each source file and `package.json` is read once into a repository-scoped immutable snapshot, and all TypeScript parser entrypoints share one lazy AST for that file. Publication uses one savepoint per repository inside the workspace consistency transaction: an invalid prepared snapshot rolls back and diagnoses only that repository, while the final package-invalidation/materialization pass remains workspace-atomic.
75
75
  - Index writers are coordinated per SQLite database with a short atomic claim. A second writer fails with `index_writer_active`; a claim that remains SQLite-locked past the bounded wait reports `index_writer_coordination_failed`. A dead owner or sufficiently old legacy ownerless row is reconciled before a new claim. `clean`, including `--db-only`, uses the same claim and removes only exact SQLite sidecar files. Normal failures finalize their run row, while read-only list, trace, inspect, and doctor commands remain usable whenever the short publication transaction is not active.
76
76
  - Normal successful database commands on supported Node 24 runtimes suppress the known `node:sqlite` experimental warning so JSON stdout remains parseable and stderr-clean. Real service-flow errors still use stderr and non-zero exit codes.
77
77
  - Doctor treats a `running` index run as abandoned only after 60 minutes and includes the run id/start time. Active short-lived concurrent runs are not default warnings.
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
+ - Every AST-backed call owner is selected from executable symbols by complete zero-based, half-open UTF-16 span containment. Event subscriptions and their handler references prefer the exact same-span synthetic `event_registration`; otherwise the narrowest containing scope and a fixed binary kind/name order apply. Physical line remains display evidence and is never an identity fallback.
82
+ - Event-subscription handler facts have the durable role `event_subscribe_handler`, retain `factOrigin: event_subscribe_handler_reference`, and keep resolver-owned `candidateStrategy` separate. Every supported subscription records one closed `handlerReferenceStatus`; named/member/namespace and supported single-wrapper references require one same-span role row, while explicitly unsupported inline/wrapper/reference shapes and a missing argument require none.
83
+ - Event names supplied as template literals are persisted as opaque, non-evaluated expressions instead of being dropped. Strict traces keep them dynamic until every exact emit- and subscribe-side placeholder key is supplied with `--var`; only equal, case-sensitive substituted names traverse the subscriber boundary.
84
+ - 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 validation. Link never falls back to caller, line, start offset, label, or case-folded name heuristics. Resolved, ambiguous, unresolved, and explicitly unsupported associations remain distinguishable.
85
+ - Service bindings carry their own declaration/assignment span and `owned_exact` or `ownerless_file_scope` status. Supported calls carry the exact visible binding site and a complete outer-to-inner lexical-scope proof capped at 16 scopes; persistence joins by repository, file, variable, and full site span. A deeper proof fails closed instead of being truncated. Shadowed, future, branch-dependent, hoisted, ambiguous, or otherwise unsupported flows remain unselected rather than using same-line/source-order proximity.
86
+ - Package symbol calls preserve module origin, ESM/CommonJS binding shape, local and imported names, requested package, and requested public subpath. Resolution requires a unique package repository plus a complete public-entry/name exposure proof and an executable body. An explicit `exports` map is authoritative; unsupported conditional/wildcard maps, unsupported or ambiguous entrypoints/barrels, unexposed internals, wrong subpaths, declaration-only targets, duplicate repositories/targets, incomplete retained evidence, mutable/reassigned/escaped public values, and mutated CommonJS namespace objects fail closed rather than selecting a stale body. Incomplete exact-scope evidence uses `public_surface_evidence_incomplete`; this conservative compatibility cost is intentional for package shapes the static analyzer cannot prove.
87
+ - One-hop derived import calls retain the import and target-declaration identities separately. Relative default-class instances resolve only when the exact requested module contains the matching declared class method and marks that class as the default export; package-derived members never fall through to a coincidental same-file name. A relative object shorthand that points onward to a package stays visibly unsupported, and every unresolved or ambiguous symbol-call fact is rendered as a non-traversable trace edge.
88
+ - Structural `cds.run` recognition is insensitive to source-line formatting. Query evidence records `hasForUpdate: true` when the proven fluent CQL chain contains `.forUpdate(...)`, without inspecting or retaining the lock argument.
89
+ - A local deterministic helper may contribute a service binding through one direct return of `cds.connect.to(...)` or `cds.connect.messaging(...)`, including a try block whose catch only logs and swallows the error. The evidence strategy is `single_hop_helper_return`; branching, a returning/connect-producing catch or finally block, and second-hop helpers remain fail-closed.
90
+ - Repository public-surface evidence uses the versioned `service-flow/package-public-surface@1` carrier and retains at most 256 public exposure records with truthful total/shown/omitted metadata. A displayed prefix never proves uniqueness or absence; an omitted requested scope stays unresolved unless an authoritative exact-name count proves the decision.
83
91
  - 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.
84
92
  - 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.
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.
93
+ - Schema version 13 adds exact binding-site spans and ownership plus the repository public-surface carrier to the schema-12 call span/role facts. A writer-only v12→v13 migration leaves legacy binding spans/public surfaces null and ownership `legacy_unknown`; it never reconstructs provenance from line, names, or old relationships. Package `0.1.69` keeps schema 13 and uses analyzer `0.1.69-facts.1`; the new strategy remains JSON evidence rather than a SQL-enumerated column. Upgrading requires a forced index followed by a forced link. Read-only commands report bounded schema/reindex diagnostics and link preserves the last good graph until both passes succeed.
86
94
 
87
95
 
88
96
  ## 🚀 Quick Start
@@ -110,7 +118,7 @@ service-flow doctor --workspace /path/to/workspace
110
118
  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`.
111
119
 
112
120
  > [!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:
121
+ > Version 0.1.69 keeps database schema 13 and changes the fact analyzer to `0.1.69-facts.1`. Derived-import identity, dynamic event topics, structural CQL dispatch, and helper-return bindings therefore require fresh facts even though no SQL migration is needed. Refresh facts and graph edges before tracing:
114
122
  >
115
123
  > ```bash
116
124
  > service-flow index --workspace /path/to/workspace --force
@@ -280,8 +288,11 @@ External HTTP facts use semantic terminal nodes instead of outbound-call row ids
280
288
  - If a remote edge is unresolved, run `service-flow list calls --operation <name>`
281
289
  and `service-flow inspect operation <name>` to compare the captured call path
282
290
  with indexed CDS operations. Operation-path-only matches are shown as ambiguous/unresolved with candidate counts instead of high-confidence cross-repo links.
283
- - Service bindings are matched to outbound calls by repository, source file, and
284
- variable name to avoid false cross-file matches. If a helper-returned client is
291
+ - Service bindings are matched to outbound calls only by the parser-carried
292
+ repository, source file, variable name, and complete binding-site `[start,end)`
293
+ identity, then validated against the complete lexical-scope proof. Line,
294
+ nearest declaration, shared owner, and source-order proximity are never
295
+ selection fallbacks. If a helper-returned client is
285
296
  not linked, export the helper from a relative import target and ensure it returns
286
297
  `cds.connect.to(...)` directly or returns an object property backed by a local
287
298
  connected-client variable. Supported helper shapes include function declarations,
@@ -465,13 +476,19 @@ Step Type From To
465
476
  `--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
477
 
467
478
  ```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":[]}
479
+ {"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":13,"analyzerVersion":"0.1.69-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","database_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
480
  ```
470
481
 
471
482
  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
483
 
473
484
  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
485
 
486
+ The edge tuple's `status` and `to` cells are the canonical effective decision. Optional `details.decision.effectiveResolutionStatus` and `effectiveTarget` are omitted only when they were derived from those exact same canonical values; label equality is insufficient. Differing persisted status/target decisions, counts, reason codes, and references remain. Each reference group retains at most 5 values with `total`, `shown`, and `omitted`; missing-variable projection retains at most 8 safe names, each at most 160 UTF-16 code units.
487
+
488
+ An ambiguous implementation decision with more than one candidate may include `tiedCandidateRepos` only when more than one uniquely attributable repository exists. Compact start diagnostics may likewise include bounded `selectorSuggestions`, a short `selectorKind`, or bounded `invalidFactCategories`. Each of these optional reference groups uses the same 5-value cap and truthful `total`/`shown`/`omitted` counts. They contain only allowlisted codes or safe identifiers; detailed JSON remains authoritative for full candidate and lifecycle evidence. Implementation-hint remediation lists the accepted `service`, `operation`, `package`, `repository`, `family`, and required `repo` keys.
489
+
490
+ Detailed/runtime artifacts retain the arbitrary full trimmed placeholder key. Compact output accepts an ASCII identifier (`[A-Za-z_$][A-Za-z0-9_$]*`), followed by zero or more direct/optional ASCII identifier members or non-negative decimal numeric element accesses, and optionally one final zero-argument `toLowerCase`, `toUpperCase`, or `trim` transform. Raw control characters are rejected before surrounding-space normalization, so a leading or trailing control cannot be trimmed into a displayable name. Unsafe or overlong keys remain counted in `missingVariableCount` and `omittedMissingVariableCount` and are available through the correlated detailed edge/diagnostic; compact never executes or partially evaluates them.
491
+
475
492
  `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
493
 
477
494
  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.
@@ -508,6 +525,7 @@ flowchart TD
508
525
  - Static analysis cannot know every runtime branch, feature flag, or environment-specific destination.
509
526
  - 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.
510
527
  - Dynamic service names and paths may need `--var key=value` values to resolve concrete targets.
528
+ - OData path punctuation is structural only outside balanced opaque placeholders and quoted values. A top-level query after a placeholder remains a query, while `?`, `/`, or parentheses inside its expression do not classify the path. A placeholder overlapping the first classification head remains runtime-dependent until exact substitution and indexed evidence prove the target.
511
529
  - Highly customized frameworks can still appear as unresolved edges until parser support is added.
512
530
  - Parse failures are stored as diagnostics and reported by `service-flow doctor`.
513
531
  - The resolver prefers source evidence and confidence scores over speculative matches.
package/TECHNICAL-NOTE.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Service Flow Resolution Notes
2
2
 
3
+ ## 0.1.69 containment, dynamic event, and compact actionability notes
4
+
5
+ - Package/CLI `0.1.69`, SQLite schema `13`, analyzer `0.1.69-facts.1`, and compact contract `service-flow/compact-graph@1` remain independent. No SQL migration is needed because `single_hop_helper_return` is carried in validated JSON evidence; all repositories still require `index --force` and `link --force` because generated facts changed.
6
+ - Prepared-snapshot failures use a closed typed error and repository site. Workspace indexing contains those failures with one repository savepoint, commits healthy siblings, writes one bounded diagnostic for each failed repository, and finishes the run as `success`, `partial_failure`, or `failed`. Unexpected cross-repository materialization/invalidation failures still roll back the outer workspace transaction.
7
+ - Relative default-class method resolution compares the caller's default binding to target-side default-export evidence, while current-fact proof uses the caller's local identifier. Package-derived members have no same-file fallback, package-backed relative proxy aliases carry an explicit unsupported reason, and unresolved/ambiguous symbol calls remain visible but non-traversable in traces.
8
+ - Event template expressions are persisted without evaluation. Missing exact keys create dynamic event candidates; a trace traverses only after emit and subscription templates are both fully substituted and their concrete names match with binary case sensitivity.
9
+ - CQL dispatch recognizes `cds.run` structurally across whitespace and records only the bounded `hasForUpdate: true` fact for a proven lock modifier. One direct helper-return hop may preserve connect-derived service bindings through a non-value-producing catch, with explicit helper-chain provenance and lifecycle validation.
10
+ - Compact v1 rejects controls before trimming missing names, uses intra-edge canonical identity to remove complete tuple-equivalent persisted/effective decisions for every safe target kind, and retains genuinely different decisions. Rare ambiguous/start/lifecycle diagnostics gain bounded repository, selector, and invalid-category reference groups. A parser warning contributes its allowlisted `code`; the generic `parser_warning` sentinel instead uses an allowlisted `message` code, while arbitrary free text remains omitted.
11
+
12
+ ## 0.1.68 exact provenance and structural runtime notes
13
+
14
+ ### Fact identity and schema lifecycle
15
+
16
+ - Package/CLI `0.1.68`, SQLite schema `13`, analyzer `0.1.68-facts.1`, and compact contract `service-flow/compact-graph@1` are independent values. Parser/link semantics changed, so a writer migrates v12 to v13 and the workspace then requires `index --force` followed by `link --force`; read-only trace/doctor never migrate.
17
+ - The migration adds nullable binding-site offsets, non-null `owner_resolution` with legacy default `legacy_unknown`, an exact-site lookup/partial uniqueness index, and nullable `repositories.package_public_surface_json`. It leaves every legacy span and public surface null, marks affected facts stale with `schema_v13_fact_provenance_requires_reindex`, and preserves the previous graph.
18
+ - Current AST-backed calls and bindings use zero-based, half-open TypeScript UTF-16 spans. Owner selection considers only `event_registration`, `callback`, `method`, `object_method`, and `function`; an event registration with the exact outer subscription span wins, then width, fixed kind order, start/end, and binary qualified name decide. Equal identity remains ambiguous, and neither line nor row ID breaks the tie.
19
+ - Every supported subscription is derived from the same outbound classification that creates its exact registration. Its closed `handlerReferenceStatus` distinguishes a required role from explicit inline/wrapper/reference/missing-argument exclusions. Required roles retain `factOrigin: event_subscribe_handler_reference`; resolver strategy remains independently mutable.
20
+ - Bindings carry their local declaration/reaching-assignment span and exact owner resolution. Each supported call carries a complete outer-to-inner lexical proof capped at 16 scopes plus the exact visible binding site. Publication validates that proof and owner compatibility before replacing prior facts, then lifecycle revalidates its persisted form; shadowing, a later declaration, branch-dependent flow, unsupported `var`, a deeper unrepresentable scope chain, or duplicate facts fails closed rather than truncating correctness evidence.
21
+ - Lifecycle preflight inventories every link-consumed JSON carrier, validates JSON and top-level shape before extraction, then validates exact owners, event association, binding references, package surface/state, and symbol-call cardinalities. Diagnostics contain only bounded aggregate categories and fixed remediation. Preflight runs before selector/detail queries and before link allocates a generation or deletes the last good graph.
22
+
23
+ ### Typed modules and package public surface
24
+
25
+ - Import origin and binding/reference shape are independent typed fields: relative/package, ESM named/default/namespace or supported CommonJS destructured/namespace, local/imported name, referenced member, raw specifier, package name, and requested public subpath. Relative resolution applies its normalized module set as a hard eligibility constraint to every strategy.
26
+ - Each current repository persists `service-flow/package-public-surface@1`, including zero-symbol, type-only, unsupported, and not-applicable packages. An explicit `exports` map is authoritative. Supported literal root/subpath entries, direct exports, named/renamed relative re-exports, and bounded `export *` traversal can establish exposure. Conditional/wildcard/ambiguous maps, cycles, unsupported entrypoint/barrel shapes, compiled-output mappings, mutable/reassigned/escaped public values, and mutated CommonJS namespace objects fail closed rather than selecting a stale executable body; incomplete exact-scope proof uses `public_surface_evidence_incomplete`. This intentionally trades compatibility for avoiding a false public target.
27
+ - Public exposure and executable body eligibility are separate. Declaration files, ambient declarations, abstract/bodyless methods, and overload signatures without an implementation are not traversable; one concrete overload body is the eligible target.
28
+ - The public-exposure retention cap is 256 records. Complete evidence carries truthful total/shown/omitted metadata and exact-scope counts; a retained prefix alone never proves uniqueness or absence. Package resolution intersects a unique repository, requested entry/subpath, public name, compatible binding shape, exact exposed target, and body eligibility.
29
+ - Package rows publish in the exact `package_import_pending` matrix. Before graph replacement, the workspace package pass reevaluates every typed package row to a terminal resolved/ambiguous/unresolved matrix and reruns lifecycle validation. Doctor labels this valid interim state as pending/relink work and excludes it from terminal unresolved quality. Reindexing or renaming a sibling package resets matching current callers to pending before target deletion, increments affected caller generations once, and marks the workspace graph stale; an identity change also invalidates dependency-only helper edges even when no package symbol call exists.
30
+
31
+ ### Placeholder/OData and compact v1 normalization
32
+
33
+ - One balanced non-evaluating scanner owns placeholder spans and exact trimmed keys. Strings, nested object/array braces, template interpolation, comments, regular-expression literals, and division are lexed structurally; malformed input returns one stable fail-closed status and no truncated prefix.
34
+ - OData analysis consumes accepted placeholder spans as opaque runtime tokens. Query delimiters, path separators, quotes, and key/invocation parentheses are recognized only outside them. If a placeholder overlaps the first-segment classification head, the head stays runtime-dependent; placeholders wholly inside a valid outer key or invocation argument preserve the static head and outer shape.
35
+ - A GET path such as `/${tenantInfo.region?.toLowerCase()}` therefore remains a dynamic operation candidate with the exact missing key. Supplying `--var 'tenantInfo.region?.toLowerCase()=lookup'` can resolve it only through indexed service/operation evidence; wrong or partial names remain unresolved.
36
+ - Compact v1 displays at most 8 unique binary-sorted safe missing names, each at most 160 UTF-16 code units. The grammar is one ASCII identifier (`[A-Za-z_$][A-Za-z0-9_$]*`), then zero or more direct/optional ASCII identifier members or non-negative decimal numeric indexes, and optionally one final zero-argument `toLowerCase`, `toUpperCase`, or `trim`; unsafe keys remain in authoritative totals/omissions and detailed JSON.
37
+ - The edge tuple `status` and `to` cells are canonical. Optional effective status/target decision fields are removed before aggregation only when proven identical to those exact canonical cells; labels are never compared as identity. Persisted differences and every reference group remain, with at most 5 shown values and truthful total/shown/omitted counts.
38
+
3
39
  ## 0.1.66 event-subscriber and compact graph notes
4
40
 
5
41
  ### Fact identity, migration, and link lifecycle