vouchington-tooling 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +219 -1
  2. package/dist/api-contract-discovery/index.d.mts +4 -1
  3. package/dist/api-contract-discovery/index.mjs +1 -0
  4. package/dist/api-contract-discovery/protocol-analysis-cache.d.mts +21 -0
  5. package/dist/api-contract-discovery/protocol-analysis-cache.mjs +7 -0
  6. package/dist/api-contract-discovery/protocol-callback-invocation.d.mts +2 -1
  7. package/dist/api-contract-discovery/protocol-callback-invocation.mjs +12 -4
  8. package/dist/api-contract-discovery/protocol-callback-values.d.mts +2 -1
  9. package/dist/api-contract-discovery/protocol-callback-values.mjs +3 -3
  10. package/dist/api-contract-discovery/protocol-execution-path.d.mts +2 -1
  11. package/dist/api-contract-discovery/protocol-execution-path.mjs +2 -2
  12. package/dist/api-contract-discovery/protocol-http-association.mjs +8 -0
  13. package/dist/api-contract-discovery/protocol-http-context-callers.d.mts +2 -1
  14. package/dist/api-contract-discovery/protocol-http-context-callers.mjs +13 -7
  15. package/dist/api-contract-discovery/protocol-http-context-escapes.d.mts +3 -0
  16. package/dist/api-contract-discovery/protocol-http-context-escapes.mjs +95 -0
  17. package/dist/api-contract-discovery/protocol-http-context.d.mts +1 -2
  18. package/dist/api-contract-discovery/protocol-http-context.mjs +6 -29
  19. package/dist/api-contract-discovery/protocol-http-method-access.d.mts +9 -0
  20. package/dist/api-contract-discovery/protocol-http-method-access.mjs +34 -0
  21. package/dist/api-contract-discovery/protocol-http-reflect.d.mts +5 -0
  22. package/dist/api-contract-discovery/protocol-http-reflect.mjs +45 -0
  23. package/dist/api-contract-discovery/protocol-http-response-mutations.d.mts +3 -0
  24. package/dist/api-contract-discovery/protocol-http-response-mutations.mjs +31 -0
  25. package/dist/api-contract-discovery/protocol-platform-callbacks.d.mts +2 -0
  26. package/dist/api-contract-discovery/protocol-platform-callbacks.mjs +4 -0
  27. package/dist/api-contract-discovery/protocol-sse-raw-writes.mjs +36 -7
  28. package/dist/api-contract-discovery/protocol-sse-write-helpers.d.mts +1 -0
  29. package/dist/api-contract-discovery/protocol-sse-write-helpers.mjs +21 -5
  30. package/dist/api-contract-discovery/protocol-sse-write-mutations.d.mts +1 -1
  31. package/dist/api-contract-discovery/protocol-sse-write-mutations.mjs +8 -2
  32. package/dist/api-contract-discovery/query-contract-extraction.mjs +20 -3
  33. package/dist/api-contract-discovery/query-contract-literals.d.mts +5 -0
  34. package/dist/api-contract-discovery/query-contract-literals.mjs +29 -0
  35. package/dist/api-contract-discovery/registered-route-binding-writes.d.mts +2 -1
  36. package/dist/api-contract-discovery/registered-route-binding-writes.mjs +20 -11
  37. package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +7 -1
  38. package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +27 -26
  39. package/dist/api-contract-discovery/registered-route-handler-helpers.d.mts +3 -0
  40. package/dist/api-contract-discovery/registered-route-handler-helpers.mjs +16 -0
  41. package/dist/api-contract-discovery/request-validation-callbacks.d.mts +9 -0
  42. package/dist/api-contract-discovery/request-validation-callbacks.mjs +27 -0
  43. package/dist/api-contract-discovery/request-validation-checker.d.mts +7 -0
  44. package/dist/api-contract-discovery/request-validation-checker.mjs +25 -0
  45. package/dist/api-contract-discovery/request-validation-conditional.d.mts +6 -0
  46. package/dist/api-contract-discovery/request-validation-conditional.mjs +31 -0
  47. package/dist/api-contract-discovery/request-validation-factory-calls.d.mts +11 -0
  48. package/dist/api-contract-discovery/request-validation-factory-calls.mjs +23 -0
  49. package/dist/api-contract-discovery/request-validation-facts.d.mts +16 -0
  50. package/dist/api-contract-discovery/request-validation-facts.mjs +64 -0
  51. package/dist/api-contract-discovery/request-validation-follow.d.mts +24 -0
  52. package/dist/api-contract-discovery/request-validation-follow.mjs +38 -0
  53. package/dist/api-contract-discovery/request-validation-handler-state.d.mts +8 -0
  54. package/dist/api-contract-discovery/request-validation-handler-state.mjs +35 -0
  55. package/dist/api-contract-discovery/request-validation-input.d.mts +12 -0
  56. package/dist/api-contract-discovery/request-validation-input.mjs +43 -0
  57. package/dist/api-contract-discovery/request-validation-keys.d.mts +25 -0
  58. package/dist/api-contract-discovery/request-validation-keys.mjs +119 -0
  59. package/dist/api-contract-discovery/request-validation-match.d.mts +11 -0
  60. package/dist/api-contract-discovery/request-validation-match.mjs +47 -0
  61. package/dist/api-contract-discovery/request-validation-mutation.d.mts +7 -0
  62. package/dist/api-contract-discovery/request-validation-mutation.mjs +80 -0
  63. package/dist/api-contract-discovery/request-validation-origin.d.mts +20 -0
  64. package/dist/api-contract-discovery/request-validation-origin.mjs +71 -0
  65. package/dist/api-contract-discovery/request-validation-reads.d.mts +13 -0
  66. package/dist/api-contract-discovery/request-validation-reads.mjs +88 -0
  67. package/dist/api-contract-discovery/request-validation-registration.d.mts +9 -0
  68. package/dist/api-contract-discovery/request-validation-registration.mjs +32 -0
  69. package/dist/api-contract-discovery/request-validation-report.d.mts +3 -0
  70. package/dist/api-contract-discovery/request-validation-report.mjs +21 -0
  71. package/dist/api-contract-discovery/request-validation-request-origin.d.mts +5 -0
  72. package/dist/api-contract-discovery/request-validation-request-origin.mjs +13 -0
  73. package/dist/api-contract-discovery/request-validation-select.d.mts +9 -0
  74. package/dist/api-contract-discovery/request-validation-select.mjs +36 -0
  75. package/dist/api-contract-discovery/request-validation-sites.d.mts +9 -0
  76. package/dist/api-contract-discovery/request-validation-sites.mjs +77 -0
  77. package/dist/api-contract-discovery/request-validation-state-key.d.mts +13 -0
  78. package/dist/api-contract-discovery/request-validation-state-key.mjs +30 -0
  79. package/dist/api-contract-discovery/request-validation-trace-helpers.d.mts +14 -0
  80. package/dist/api-contract-discovery/request-validation-trace-helpers.mjs +80 -0
  81. package/dist/api-contract-discovery/request-validation-trace-select.d.mts +23 -0
  82. package/dist/api-contract-discovery/request-validation-trace-select.mjs +45 -0
  83. package/dist/api-contract-discovery/request-validation-trace.d.mts +8 -0
  84. package/dist/api-contract-discovery/request-validation-trace.mjs +130 -0
  85. package/dist/api-contract-discovery/request-validation-types.d.mts +84 -0
  86. package/dist/api-contract-discovery/request-validation-types.mjs +1 -0
  87. package/dist/api-contract-discovery/request-validation-walker.d.mts +26 -0
  88. package/dist/api-contract-discovery/request-validation-walker.mjs +143 -0
  89. package/dist/api-contract-discovery/request-validation.test-helpers.d.mts +39 -0
  90. package/dist/api-contract-discovery/request-validation.test-helpers.mjs +108 -0
  91. package/dist/api-contract-discovery/response-contract-attribution-facts.d.mts +2 -0
  92. package/dist/api-contract-discovery/response-contract-attribution-facts.mjs +18 -0
  93. package/dist/api-contract-discovery/response-contract-attribution.d.mts +3 -0
  94. package/dist/api-contract-discovery/response-contract-attribution.mjs +23 -0
  95. package/dist/api-contract-discovery/response-contract-call-classification.d.mts +3 -0
  96. package/dist/api-contract-discovery/response-contract-call-classification.mjs +37 -0
  97. package/dist/api-contract-discovery/response-contract-error-branch.d.mts +1 -1
  98. package/dist/api-contract-discovery/response-contract-error-branch.mjs +109 -6
  99. package/dist/api-contract-discovery/response-contract-handler-bindings.d.mts +3 -0
  100. package/dist/api-contract-discovery/response-contract-handler-bindings.mjs +126 -0
  101. package/dist/api-contract-discovery/response-contract-handler-calls.d.mts +3 -0
  102. package/dist/api-contract-discovery/response-contract-handler-calls.mjs +81 -0
  103. package/dist/api-contract-discovery/response-contract-implicit-variants.d.mts +2 -0
  104. package/dist/api-contract-discovery/response-contract-implicit-variants.mjs +6 -0
  105. package/dist/api-contract-discovery/response-contract-implicit.d.mts +2 -1
  106. package/dist/api-contract-discovery/response-contract-implicit.mjs +9 -17
  107. package/dist/api-contract-discovery/response-contract-lenient.d.mts +18 -0
  108. package/dist/api-contract-discovery/response-contract-object-symbols.d.mts +3 -0
  109. package/dist/api-contract-discovery/response-contract-object-symbols.mjs +80 -0
  110. package/dist/api-contract-discovery/response-contract-property-mutations.d.mts +5 -0
  111. package/dist/api-contract-discovery/response-contract-property-mutations.mjs +84 -0
  112. package/dist/api-contract-discovery/response-contract-registration.d.mts +1 -1
  113. package/dist/api-contract-discovery/response-contract-registration.mjs +7 -3
  114. package/dist/api-contract-discovery/response-contract-registry.mjs +43 -8
  115. package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +3 -4
  116. package/dist/api-contract-discovery/response-contract-route-analysis.mjs +4 -101
  117. package/dist/api-contract-discovery/response-contract-route-syntax.d.mts +7 -0
  118. package/dist/api-contract-discovery/response-contract-route-syntax.mjs +41 -0
  119. package/dist/api-contract-discovery/response-contract-symbols.d.mts +5 -0
  120. package/dist/api-contract-discovery/response-contract-symbols.mjs +82 -0
  121. package/dist/basic-auth-doc-sync/index.d.mts +3 -0
  122. package/dist/basic-auth-doc-sync/index.mjs +2 -0
  123. package/dist/basic-auth-doc-sync/methods.d.mts +1 -0
  124. package/dist/basic-auth-doc-sync/methods.mjs +4 -0
  125. package/dist/basic-auth-doc-sync/runbook.d.mts +5 -0
  126. package/dist/basic-auth-doc-sync/runbook.mjs +59 -0
  127. package/dist/basic-auth-doc-sync/source.d.mts +2 -0
  128. package/dist/basic-auth-doc-sync/source.mjs +71 -0
  129. package/dist/compiler-build/index.d.mts +2 -0
  130. package/dist/compiler-build/index.mjs +1 -0
  131. package/dist/compiler-build/program-configuration.d.mts +9 -0
  132. package/dist/compiler-build/program-configuration.mjs +28 -0
  133. package/dist/compiler-build/program-types.d.mts +29 -0
  134. package/dist/compiler-build/program-types.mjs +1 -0
  135. package/dist/compiler-build/program.d.mts +7 -0
  136. package/dist/compiler-build/program.mjs +78 -0
  137. package/dist/contract-schema/index.d.mts +4 -0
  138. package/dist/contract-schema/index.mjs +2 -0
  139. package/dist/contract-schema/type-query-api.d.mts +44 -0
  140. package/dist/contract-schema/type-query-api.mjs +10 -0
  141. package/dist/contract-schema/type-query-call.d.mts +16 -0
  142. package/dist/contract-schema/type-query-call.mjs +52 -0
  143. package/dist/contract-schema/type-query-export.d.mts +10 -0
  144. package/dist/contract-schema/type-query-export.mjs +91 -0
  145. package/dist/contract-schema/type-query.d.mts +22 -0
  146. package/dist/contract-schema/type-query.mjs +39 -0
  147. package/dist/finite-enum-ripple/ast.d.mts +9 -0
  148. package/dist/finite-enum-ripple/ast.mjs +94 -0
  149. package/dist/finite-enum-ripple/check.d.mts +3 -0
  150. package/dist/finite-enum-ripple/check.mjs +62 -0
  151. package/dist/finite-enum-ripple/compare.d.mts +20 -0
  152. package/dist/finite-enum-ripple/compare.mjs +90 -0
  153. package/dist/finite-enum-ripple/component-paths.d.mts +8 -0
  154. package/dist/finite-enum-ripple/component-paths.mjs +30 -0
  155. package/dist/finite-enum-ripple/configured-object-mutations.d.mts +1 -0
  156. package/dist/finite-enum-ripple/configured-object-mutations.mjs +78 -0
  157. package/dist/finite-enum-ripple/create-page-jsx.d.mts +3 -0
  158. package/dist/finite-enum-ripple/create-page-jsx.mjs +28 -0
  159. package/dist/finite-enum-ripple/create-page-literals.d.mts +2 -0
  160. package/dist/finite-enum-ripple/create-page-literals.mjs +83 -0
  161. package/dist/finite-enum-ripple/create-page-object-visitor.d.mts +2 -0
  162. package/dist/finite-enum-ripple/create-page-object-visitor.mjs +57 -0
  163. package/dist/finite-enum-ripple/create-page-properties.d.mts +4 -0
  164. package/dist/finite-enum-ripple/create-page-properties.mjs +35 -0
  165. package/dist/finite-enum-ripple/index.d.mts +2 -0
  166. package/dist/finite-enum-ripple/index.mjs +1 -0
  167. package/dist/finite-enum-ripple/model.d.mts +74 -0
  168. package/dist/finite-enum-ripple/model.mjs +1 -0
  169. package/dist/finite-enum-ripple/parser-support.d.mts +11 -0
  170. package/dist/finite-enum-ripple/parser-support.mjs +86 -0
  171. package/dist/finite-enum-ripple/parsers.d.mts +32 -0
  172. package/dist/finite-enum-ripple/parsers.mjs +113 -0
  173. package/dist/finite-enum-ripple/pattern-captures.d.mts +2 -0
  174. package/dist/finite-enum-ripple/pattern-captures.mjs +45 -0
  175. package/dist/finite-enum-ripple/structured-route-check.d.mts +3 -0
  176. package/dist/finite-enum-ripple/structured-route-check.mjs +94 -0
  177. package/dist/finite-enum-ripple/types.d.mts +2 -0
  178. package/dist/finite-enum-ripple/types.mjs +60 -0
  179. package/dist/finite-enum-ripple/union-factory-check.d.mts +2 -0
  180. package/dist/finite-enum-ripple/union-factory-check.mjs +34 -0
  181. package/dist/finite-enum-ripple/union-types.d.mts +2 -0
  182. package/dist/finite-enum-ripple/union-types.mjs +129 -0
  183. package/dist/markdown/content-facts.d.mts +6 -0
  184. package/dist/markdown/content-facts.mjs +18 -0
  185. package/dist/markdown/html-blocks.d.mts +5 -0
  186. package/dist/markdown/html-blocks.mjs +51 -0
  187. package/dist/markdown/index.d.mts +4 -1
  188. package/dist/markdown/index.mjs +3 -0
  189. package/dist/markdown/tasks.d.mts +2 -0
  190. package/dist/markdown/tasks.mjs +10 -0
  191. package/dist/markdown/types.d.mts +27 -0
  192. package/dist/openapi-document/build-openapi-query.mjs +1 -1
  193. package/dist/openapi-document/openapi-types.d.mts +1 -1
  194. package/dist/openapi-document/query-types.d.mts +8 -2
  195. package/dist/post-publication-inventory/index.d.mts +5 -0
  196. package/dist/post-publication-inventory/index.mjs +2 -0
  197. package/dist/post-publication-inventory/readers/sql-executors.d.mts +3 -0
  198. package/dist/post-publication-inventory/readers/sql-executors.mjs +32 -0
  199. package/dist/post-publication-inventory/readers/sql-source.d.mts +7 -0
  200. package/dist/post-publication-inventory/readers/sql-source.mjs +87 -0
  201. package/dist/post-publication-inventory/readers/sql-template.d.mts +12 -0
  202. package/dist/post-publication-inventory/readers/sql-template.mjs +119 -0
  203. package/dist/post-publication-inventory/writers/source.d.mts +9 -0
  204. package/dist/post-publication-inventory/writers/source.mjs +128 -0
  205. package/dist/post-publication-inventory/writers/types.d.mts +19 -0
  206. package/dist/post-publication-inventory/writers/types.mjs +1 -0
  207. package/docs/basic-auth-doc-sync.md +37 -0
  208. package/package.json +24 -3
package/README.md CHANGED
@@ -8,11 +8,16 @@ GitHub Actions policy checks and compiler host freshness with bounded build sett
8
8
  [`config-inventory`](../../docs/config-inventory.md) collects environment, dynamic-configuration,
9
9
  and package-manager evidence using caller-owned file roles and policies.
10
10
 
11
+ [`finite-enum-ripple`](../../docs/finite-enum-ripple.md) compares finite TypeScript declarations,
12
+ route configuration, routed pages, and factory calls. Consumers supply every tracked-file role,
13
+ declaration name, route label, factory pattern, and exception.
14
+
11
15
  ```bash
12
16
  npm install vouchington-tooling
13
17
  # optional, only if you import vouchington-tooling/sql-ast
14
18
  npm install @libpg-query/parser
15
- # optional, only if you import vouchington-tooling/contract-schema
19
+ # optional, if you import vouchington-tooling/contract-schema, finite-enum-ripple,
20
+ # or post-publication-inventory
16
21
  # (classic compiler API; typescript@7's package root is version-only)
17
22
  npm install @typescript/typescript6
18
23
  # optional, only if you use the Vitest reporter export
@@ -366,6 +371,31 @@ summaries and quoted attributes; it is not a general HTML validator or sanitizer
366
371
  the input or infers semantic relevance, cost, schema conformance, or failure causes. The existing
367
372
  `markdownSectionBetweenHeadings` API retains its original boundary behavior.
368
373
 
374
+ `hasUncheckedMarkdownTask` reports parsed unchecked GFM task items, ignoring code and non-task text.
375
+ `markdownLiteralSpans` returns source-backed `{ kind, position, value }` facts for fenced or indented
376
+ code, inline code, and raw HTML. `markdownHtmlFacts` distinguishes inline HTML from block HTML and
377
+ labels CommonMark block types 1–7; `markdownHtmlBlocks` selects block types 1, 6, and 7, retaining
378
+ the parser's source bounds, including type 1 blocks through their matching closer or end of input.
379
+ Positions use 1-indexed line/column coordinates and JavaScript string offsets, including CRLF and
380
+ Unicode source text. These APIs return facts rather than mdast nodes so callers can apply their own
381
+ content policy without binding it to parser internals.
382
+
383
+ ```ts
384
+ import {
385
+ hasUncheckedMarkdownTask,
386
+ markdownHtmlBlocks,
387
+ markdownLiteralSpans,
388
+ } from 'vouchington-tooling/markdown'
389
+
390
+ const hasOpenTask = hasUncheckedMarkdownTask(body)
391
+ const literalSpans = markdownLiteralSpans(body)
392
+ const blockHtml = markdownHtmlBlocks(body)
393
+ ```
394
+
395
+ `markdownHtmlBlocks` reports CommonMark types 1, 6, and 7; inline tags do not appear in that result.
396
+ Use `markdownHtmlFacts` when inline/block classification and other CommonMark HTML block types are
397
+ also needed.
398
+
369
399
  Machine-wide agent configuration now belongs to [vouchington-machines](https://github.com/vouchington/vouchington-machines/blob/main/docs/agent-config.md).
370
400
  Use its `configure-agents.sh` and `diagnose-agents.sh`; project hooks and MCP configuration remain in their checkouts.
371
401
 
@@ -449,6 +479,138 @@ aliases; `options.onRouteError` enables lenient response and request extraction.
449
479
  does not cache programs or depend on the caller's repository layout. OpenAPI document assembly
450
480
  remains a separate `openapi-document` call.
451
481
 
482
+ `options.onAmbiguousAttribution` optionally emits facts for response calls lexically inside handler
483
+ functions registered to multiple routes, plus inline response calls within those handlers. Facts
484
+ are delivered after discovery succeeds, ordered by normalized source location; each callback gets
485
+ its own copy of the sorted route list. It recognizes direct property handlers on `const`
486
+ object-literal receivers, shorthand properties, immutable aliases, and inline handlers. Mutable
487
+ receivers whose properties may be reassigned, variable receivers without object-literal initializers,
488
+ and non-identifier receiver expressions fail closed for attribution.
489
+ Direct identifier assignment, identifier writes through array/object destructuring or loop targets,
490
+ identifier updates, and dot-property assignment or updates make that binding ineligible for attribution.
491
+ Computed receiver-property writes, `delete`, runtime reflection, and other indirect mutation are not
492
+ analyzed. Named helper calls are not followed transitively: for example, an
493
+ emission inside `emit(ctx)` is not attributed when a registered `shared(ctx)` handler calls
494
+ `emit(ctx)`. The callback does not change the discovered contract result.
495
+
496
+ Query contracts (`apiQuery`) carry requiredness. A top-level parameter descriptor type may include
497
+ `required: true` (inline or through an intersection such as `{ readonly required: true }`); the
498
+ extracted descriptor then has `required: true` and the OpenAPI parameter emits `required: true`,
499
+ otherwise `required: false`. `required` must be the literal `true`; `false`, `boolean`, optional, or
500
+ non-literal values fail as a malformed query parameter, as do `required` together with `default` and
501
+ `required` on `csv-array` `items`.
502
+
503
+ `discoverRequestValidationFacts({ program, sourceFiles, validators, factories, executedCallbacks })`
504
+ reports, per registered route (`METHOD:/template`), the route `kind` and `source`, every configured
505
+ validator call its handlers reach (`validatorSites`), every configured handler-factory call
506
+ (`factorySites`), and the raw request carriers the handlers read (`carrierReads`). It reports
507
+ facts only; which routes must validate and what counts as covered stay with the caller.
508
+
509
+ ```ts
510
+ import { discoverRequestValidationFacts } from 'vouchington-tooling/api-contract-discovery'
511
+
512
+ const facts = discoverRequestValidationFacts({
513
+ program,
514
+ sourceFiles,
515
+ validators: [
516
+ {
517
+ module: 'lib/validation.ts', // resolved declaration path suffix, or a package specifier
518
+ exportName: 'validateInput',
519
+ operationArgument: 1,
520
+ carriers: { kind: 'input-object', argument: 2 }, // or { kind: 'fixed', carriers, optionCarriers }
521
+ },
522
+ ],
523
+ factories: [
524
+ {
525
+ module: 'lib/factory.ts',
526
+ exportName: 'createThingHandler',
527
+ optionsArgument: 0,
528
+ operationProperty: 'operation',
529
+ carriers: ['path', 'body'],
530
+ },
531
+ ],
532
+ executedCallbacks: [
533
+ {
534
+ module: 'lib/admit.ts',
535
+ exportName: 'admitWork',
536
+ argument: 0,
537
+ properties: ['beforeCapacity'],
538
+ },
539
+ ],
540
+ })
541
+ ```
542
+
543
+ A call matches only when its callee resolves, through aliases, re-exports and path mappings, to the
544
+ configured export of a declaration file matching `module` (`.d.ts`, `.d.mts` and `.d.cts` suffixes
545
+ are ignored when comparing, and the configured `module` may be written with or without a `.ts`,
546
+ `.mts`, `.cts`, `.js`, `.mjs` or `.cjs` extension, so `lib/validation.js` matches `lib/validation.ts`
547
+ and `lib/validation.d.ts`); import specifier text and same-named local functions never match.
548
+ Operation keys resolve through literals, `const`s (including imported ones), `as const`,
549
+ `satisfies`, helper parameters bound to static arguments, and, for factories, the option property
550
+ including spread `const` objects; anything else, including a name with no symbol, gives
551
+ `operation: null` with `unresolvedReason`. Each validated carrier reports `origins`, the request
552
+ carriers (`path`, `query`, `body`, `header`) its value derives from, traced through calls, followed
553
+ helper returns, branches, logical operands, spreads and earlier assignments; `unresolved` is set
554
+ when part of the value depends on an unbound parameter. An unconditional reassignment of the
555
+ identifier itself earlier in the same function body (not inside a branch, loop or `try`) replaces
556
+ the earlier origins, conditional reassignments merge, and property writes such as `x.limit = n`
557
+ always add; an assignment on a statically dead path (`if (false)`) is ignored. A destructured
558
+ local traces only the property or element it binds (`const { chosen } = pair`), resolved through
559
+ object and array literals, `const`s and followed helper returns (also awaited), and falls back to the
560
+ whole initializer when it cannot be selected (rest, computed key, spread, unknown source).
561
+ A destructured handler context parameter (`({ query, params, request }) => ...`) binds each member
562
+ to its carrier. A helper parameter that is reassigned is not trusted as the bound operation key:
563
+ only a lone unconditional reassignment is used, anything else gives `operation: null`.
564
+ Generator functions are not followed, because calling one does not run its body.
565
+ Fixed-carrier validators report the carrier itself as their origin.
566
+
567
+ An input-object validator's input is resolved through `const` objects and spreads with
568
+ last-write-wins semantics, like the factory option resolver. When it cannot be fully resolved (a
569
+ non-literal input, a spread of something that is not a resolvable `const` object, a cyclic spread,
570
+ or a computed property name), `carriers` lists what was found and the site sets
571
+ `unresolvedCarriers` to the reason, so a consumer must not treat `carriers` as complete. A `const`
572
+ object (input or factory options) with a property write before the use (`o.p = x`, `o[k] = x`,
573
+ `delete o.p`, `o.n++`, `Object.assign(o, ...)`) is unresolvable too, and a factory then reports
574
+ `operation: null`.
575
+
576
+ `conditional` is true under `if`/ternary branches, the right of `&&`/`||`/`??`, `switch` cases,
577
+ loop bodies, `catch` and `finally`. Handlers are walked into function declarations and `const`
578
+ function expressions in `sourceFiles`, and only through bindings that are never reassigned (`let`
579
+ and reassigned bindings are not trusted as handlers). A callback passed to a followed helper is
580
+ followed only when the helper runs it, and is conditional unless it is invoked directly from the
581
+ helper's own body outside any condition. Known limit: the invocations of an inline callback are
582
+ found only in the callback's own source file, so a callback passed to a helper declared in another
583
+ file (`withTx(ctx, async () => { validateInput(...) })`) is not walked. Inline callback properties of an `executedCallbacks` host,
584
+ written as identifiers or string literals, are walked as executed and take only the condition of
585
+ the call to the configured host, never how its implementation invokes them. Configured validator
586
+ and factory implementations are never entered, but their callee and arguments are still walked for
587
+ nested validator sites and reads; only reads that build a validator's input are left out, including
588
+ reads inside a followed helper that only prepares that input. Validators evaluated while a handler is
589
+ built (registration time) are not sites: only the walk of the handler reports validators. A handler a
590
+ helper returns under a runtime branch (`if (flag) return ctx => ...`) reports `conditional: true`. Factory
591
+ sites are reported for factory calls whose returned handler is a registration-time value (a route
592
+ registration argument, a module `const` used as one, or a helper-returned handler); a factory call
593
+ inside a handler body, such as a discarded `createThingHandler(options);`, is not a factory site.
594
+ A factory site is also found through a module `const` initialized by a factory call that a handler
595
+ calls (`const handler = createThingHandler({...})`, then `handler(ctx)`). Like validator sites,
596
+ `FactorySite.conditional` is true when the site is reached only under a condition, and a site
597
+ reached both ways is reported unconditional. A template-literal operation key folds to a string
598
+ when every substitution resolves (literals, `const`s, bound parameters); otherwise it is `null`.
599
+
600
+ Pass the `program` you already built: the function uses it as given and never builds its own.
601
+ Helpers are followed only within `sourceFiles`, so pass every file that declares a followed helper,
602
+ not just the route files; a helper declared elsewhere is not entered, which silently yields empty
603
+ `origins` rather than an error. An omitted optional or defaulted helper argument contributes no
604
+ origin and is not `unresolved`; an omitted required argument is `unresolved`. Per-call caches make
605
+ repeated helpers cheap, so a 700-route program completes in seconds once the program exists.
606
+
607
+ Each `carrierReads` entry has `access`: `key` for a static key (`ctx.query.limit`, destructured
608
+ names, a resolvable `ctx.query[name]`), `computed` for a non-static key (`ctx.query[name]`, a
609
+ computed destructuring key; `key: null`), and `whole` for the carrier used as a value
610
+ (`consume(ctx.query)`, `{ ...ctx.params }`, `return ctx.headers`, a rest element) or a body read
611
+ such as `ctx.request.json()` (`key: null`). A `const` alias of a carrier is not itself a read; its
612
+ uses are.
613
+
452
614
  Version one recognizes literal-key `apiResponse`, `apiNoContent`, `apiOpenApiRawResponse`,
453
615
  `apiRequest`, `apiRequestContract`, `apiNoRequestBody`, `apiQuery`, and `apiHeaders` markers inside
454
616
  `app.route(...).get/post/put/patch/delete(...)` handlers. It also inspects unmarked `ctx.json`,
@@ -504,13 +666,19 @@ Caller-owned `apiOpenApiHttpResponse(key, response)` markers bind a constant opa
504
666
  carrying optional readonly `apiHttpResponseVariants` metadata. Each concrete union variant has
505
667
  `status` and `bodyKind`; content variants also have `mediaType` and `body`. Discovery associates
506
668
  the marked variable's status and body emissions without reading or replacing response bytes.
669
+ Feasible assignments, deletions, or updates to its body or status, including cast and constant
670
+ aliases, invalidate the selected contracts because their metadata no longer proves the dispatch.
507
671
  A status setter must dominate each body emission, including separate branches; constant context
508
672
  aliases are followed, assigned mutable context aliases and object-literal context wrappers fail closed,
509
673
  and helper parameters must resolve to the registered caller's unchanged response context.
510
674
  Helper-owned contracts also account for response emissions in their proven caller scopes.
675
+ Opaque callees receiving the canonical HTTP context or its response object invalidate its contract;
676
+ implemented helpers and consumed callbacks retain their proven dispatch behavior.
511
677
  Feasible assignments or deletions of response methods through canonical context aliases invalidate
512
678
  that dispatch evidence; dead, ignored, or unrelated method mutations preserve the contract.
513
679
  Indirect response `call`, `apply`, and `bind` forms fail closed, including borrowed context receivers.
680
+ Compiler-resolved standard `Reflect.apply` uses its target and actual receiver to reject unrepresented
681
+ HTTP emissions; project implementations, unrelated contexts, and non-executed calls are preserved.
514
682
  SSE frames and branded content variants reject body-forbidden 1xx, 204, 205, and 304 statuses.
515
683
  Literal bracket response methods have the same emission checks as property methods. Unrelated raw emissions
516
684
  remain unavailable. Separate branded response branches are checked together, and the nearest
@@ -548,6 +716,29 @@ the dedicated process group to exit after the direct child closes so descendants
548
716
  `isProcessGroupAlive` and `waitForProcessGroupExit` are also available when callers need the same
549
717
  process-group probe and bounded-drain semantics outside a browser session.
550
718
 
719
+ ### Contract schema type queries
720
+
721
+ `getExportedTypeFacts` and `getCallRowTypeFacts` use an existing TypeScript `Program` from the
722
+ `contract-schema` subpath. Every request must also pass `typescript`, the same TypeScript compiler
723
+ API instance that created that `Program`. The source-file recognition check is a compatibility
724
+ guard, and callers remain responsible for pairing the same compiler instance; the query uses the
725
+ supplied API for compiler operations. Pass the exact `SourceFile.fileName`, an exported name or exact call
726
+ expression text, and only the property names and exported comparison types the consumer needs.
727
+ Facts include the displayed type, whether it is `any`, requested property displays, and requested
728
+ assignability booleans. `defaultTypeParameterIndex` selects an exported generic default;
729
+ direct defaults and direct references to preceding defaults are supported. Dependent composite
730
+ defaults and uninstantiated generic type declarations used as assignability targets fail with an
731
+ instruction to export and select a named instantiated type. Generic function values remain valid
732
+ compiler-backed comparison targets. Default lookup uses an outer class, interface, or type-alias
733
+ parameter list when present; otherwise it uses the first checker-visible call or construct
734
+ signature, never a hidden implementation or a later overload.
735
+ `rowSource: 'typeArgument'` selects a call's explicit type argument, while
736
+ `rowSource: 'awaitedRows'` selects `Awaited<ReturnType>.rows[number]`.
737
+ Call results are source ordered and include one-based line and column. An absent call returns an
738
+ empty array so the consumer can enforce its own cardinality; a selected call missing its requested
739
+ row type fails with a descriptive error. The package does not choose source files, expected
740
+ properties, or contract policy.
741
+
551
742
  ## Workflow skills outside plugins
552
743
 
553
744
  The package ships a flat union of canonical workflow, testing, and database skills at
@@ -679,3 +870,30 @@ block, escaped and length-bounded like CI failures, and other entries are ignore
679
870
  `Status: none observed` or `Status: events observed`, each followed by
680
871
  `(journal entries only; no friction log observed)`; a truncated scan reports
681
872
  `unavailable (journal scan incomplete)` with the entries read so far.
873
+
874
+ ## Basic Auth exemption facts
875
+
876
+ `vouchington-tooling/basic-auth-doc-sync` extracts literal exemption collections from
877
+ TypeScript and route rows from Markdown. Callers configure declaration names and
878
+ runbook headings, then compare the returned data using their own route policy.
879
+ See the [API contract](docs/basic-auth-doc-sync.md).
880
+
881
+ ## Publication writer inventory facts
882
+
883
+ `vouchington-tooling/post-publication-inventory` exports
884
+ `analyzePostPublicationWriterSource(source, fileName, options)`. It returns five booleans for an
885
+ approved capture-helper call, a capture opt-out, writes through a configured entity table,
886
+ generated relation writes, and eligibility-table writes. Consumers supply every module, symbol,
887
+ property, receiver, and table name through `PostPublicationWriterSourceOptions` and retain their
888
+ inventory schema, tracked files, exceptions, and diagnostics.
889
+
890
+ This preserves source-inventory heuristics, rather than proving runtime execution or SQL safety.
891
+ Capture imports/calls are name-based and order-independent; relation-variable appends are
892
+ order-sensitive. DML recognition examines literal/template text. Static table matching lowercases
893
+ the extracted name; dynamic literal table matching preserves case. Malformed TypeScript uses the
894
+ compiler's recovery tree. An argument-free append returns no relation fact.
895
+ Pass TypeScript source (`.ts` or `.mts`); the parser uses TypeScript mode even if the file name ends
896
+ in `.tsx`. Template expressions join their static fragments for DML matching, while DML prefixes
897
+ for generated-table facts come from templates rather than ordinary quoted strings. Dynamic
898
+ eligibility-table matching expects an unquoted literal immediately after the DML prefix and does
899
+ not recognize `DELETE FROM ONLY`.
@@ -27,5 +27,8 @@ export type { BackendRequestContract } from './request-contract-types.mts';
27
27
  export type { BackendQueryContract, BackendQueryContractRegistry } from './query-contract-types.mts';
28
28
  export type { HeaderContract, HeaderContractRegistry } from './header-contract-types.mts';
29
29
  export type { RegisteredRoute } from './registered-route-catalog.mts';
30
- export type { RouteDiscoveryError, DiscoverApiResponseContractsOptions, } from './response-contract-lenient.mts';
30
+ export type { AmbiguousAttributionFact, RouteDiscoveryError, DiscoverApiResponseContractsOptions, } from './response-contract-lenient.mts';
31
31
  export type { DiscoverApiRequestContractsOptions } from './request-contract-lenient.mts';
32
+ export { discoverRequestValidationFacts } from './request-validation-facts.mts';
33
+ export type { RequestValidationFactsInput } from './request-validation-facts.mts';
34
+ export type { Carrier, CarrierRead, ExecutedCallbackConfig, FactoryConfig, FactorySite, RouteValidationFacts, ValidatorConfig, ValidatorSite, ValidatorSiteCarrier, } from './request-validation-types.mts';
@@ -24,3 +24,4 @@ export { discoverApiQueryContracts } from './query-contract-registry.mjs';
24
24
  export { discoverApiHeaderContracts } from './header-contract-registry.mjs';
25
25
  export { discoverRegisteredRoutes, routeShape } from './registered-route-catalog.mjs';
26
26
  export { responseStatusCodesForContract } from './response-contract-status.mjs';
27
+ export { discoverRequestValidationFacts } from './request-validation-facts.mjs';
@@ -0,0 +1,21 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ export type Assignment = {
3
+ node: ts.BinaryExpression;
4
+ direct: boolean;
5
+ start: number;
6
+ };
7
+ /**
8
+ * Memoized protocol-proof results for one analysis run. Nodes are immutable, so every entry stays
9
+ * valid for the run; callers create one per run and drop it afterwards.
10
+ */
11
+ export type ProtocolCache = {
12
+ /** Plain assignments per owning function (or source file), grouped by the symbol they write. */
13
+ assignments: Map<ts.Node, Map<ts.Symbol, Assignment[]>>;
14
+ /** Symbols written (assigned, updated, or looped over) anywhere in a source file. */
15
+ writtenSymbols: Map<ts.SourceFile, Set<ts.Symbol | undefined>>;
16
+ /** Whether a function is a supported protocol callback. */
17
+ supported: Map<ts.Node, boolean>;
18
+ /** Executable call expressions of a source file. */
19
+ calls: Map<ts.SourceFile, ts.CallExpression[]>;
20
+ };
21
+ export declare const createProtocolCache: () => ProtocolCache;
@@ -0,0 +1,7 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ export const createProtocolCache = () => ({
3
+ assignments: new Map(),
4
+ writtenSymbols: new Map(),
5
+ supported: new Map(),
6
+ calls: new Map(),
7
+ });
@@ -1,5 +1,6 @@
1
1
  import ts from '../contract-schema/typescript-api.mts';
2
+ import type { ProtocolCache } from './protocol-analysis-cache.mts';
2
3
  type FunctionNode = ts.FunctionLikeDeclaration;
3
4
  /** Proves callback consumption through concrete helper bodies, without executing nested closures. */
4
- export declare function isSupportedProtocolCallback(fn: FunctionNode, checker: ts.TypeChecker): boolean;
5
+ export declare function isSupportedProtocolCallback(fn: FunctionNode, checker: ts.TypeChecker, cache?: ProtocolCache): boolean;
5
6
  export {};
@@ -7,8 +7,16 @@ import { callbackBindingReplaced, callbackOptionsEscape } from './protocol-callb
7
7
  import { enclosingFunction } from './protocol-marker-analysis.mjs';
8
8
  import { createProtocolCallbackValueResolver, isProtocolCallbackFunction as functionNode, protocolCallbackSourceCalls, protocolCallbackHasWrittenBindings, } from './protocol-callback-values.mjs';
9
9
  /** Proves callback consumption through concrete helper bodies, without executing nested closures. */
10
- export function isSupportedProtocolCallback(fn, checker) {
11
- if (!fn.body || fn.asteriskToken || protocolCallbackHasWrittenBindings(fn, checker))
10
+ export function isSupportedProtocolCallback(fn, checker, cache) {
11
+ const known = cache?.supported.get(fn);
12
+ if (known !== undefined)
13
+ return known;
14
+ const supported = proveProtocolCallback(fn, checker, cache);
15
+ cache?.supported.set(fn, supported);
16
+ return supported;
17
+ }
18
+ function proveProtocolCallback(fn, checker, cache) {
19
+ if (!fn.body || fn.asteriskToken || protocolCallbackHasWrittenBindings(fn, checker, cache))
12
20
  return false;
13
21
  const resolver = createProtocolCallbackValueResolver(checker);
14
22
  const { resolve, symbol } = resolver;
@@ -20,7 +28,7 @@ export function isSupportedProtocolCallback(fn, checker) {
20
28
  invalidated ||= callbackOptionsEscape(call, env, fn, resolver);
21
29
  return false;
22
30
  }
23
- if (protocolCallbackHasWrittenBindings(callee.node, checker)) {
31
+ if (protocolCallbackHasWrittenBindings(callee.node, checker, cache)) {
24
32
  invalidated = true;
25
33
  return false;
26
34
  }
@@ -86,7 +94,7 @@ export function isSupportedProtocolCallback(fn, checker) {
86
94
  const call = argument.parent;
87
95
  if (!ts.isCallExpression(call) && !ts.isNewExpression(call)) {
88
96
  const owner = enclosingFunction(fn);
89
- if (owner && isSupportedProtocolCallback(owner, checker))
97
+ if (owner && isSupportedProtocolCallback(owner, checker, cache))
90
98
  return body(owner.body, new Map(), new Set([owner]), false);
91
99
  return protocolCallbackSourceCalls(fn).some((candidate) => potentiallyExecuted(candidate) &&
92
100
  ((registeredHandler(candidate) &&
@@ -1,4 +1,5 @@
1
1
  import ts from '../contract-schema/typescript-api.mts';
2
+ import type { ProtocolCache } from './protocol-analysis-cache.mts';
2
3
  type FunctionNode = ts.FunctionLikeDeclaration;
3
4
  export type CallbackValue = {
4
5
  node: ts.Node;
@@ -13,5 +14,5 @@ export declare function createProtocolCallbackValueResolver(checker: ts.TypeChec
13
14
  symbol: (node: ts.Node) => ts.Symbol | undefined;
14
15
  property: (value: CallbackValue | undefined, name: string, seen: Set<ts.Node>) => CallbackValue | undefined;
15
16
  };
16
- export declare function protocolCallbackHasWrittenBindings(fn: FunctionNode, checker: ts.TypeChecker): boolean;
17
+ export declare function protocolCallbackHasWrittenBindings(fn: FunctionNode, checker: ts.TypeChecker, cache?: ProtocolCache): boolean;
17
18
  export {};
@@ -108,7 +108,7 @@ export function createProtocolCallbackValueResolver(checker) {
108
108
  }
109
109
  return { resolve, symbol, property };
110
110
  }
111
- export function protocolCallbackHasWrittenBindings(fn, checker) {
112
- return ((ts.isFunctionDeclaration(fn) && hasBindingWrite(fn, checker)) ||
113
- runtimeParameters(fn).some((parameter) => hasBindingWrite(parameter, checker)));
111
+ export function protocolCallbackHasWrittenBindings(fn, checker, cache) {
112
+ return ((ts.isFunctionDeclaration(fn) && hasBindingWrite(fn, checker, cache)) ||
113
+ runtimeParameters(fn).some((parameter) => hasBindingWrite(parameter, checker, cache)));
114
114
  }
@@ -1,3 +1,4 @@
1
1
  import ts from '../contract-schema/typescript-api.mts';
2
+ import type { ProtocolCache } from './protocol-analysis-cache.mts';
2
3
  /** Proves execution up to an optional handler whose registered binding is already established. */
3
- export declare function executableProtocolPath(node: ts.Node, checker: ts.TypeChecker, boundHandler?: ts.Node): boolean;
4
+ export declare function executableProtocolPath(node: ts.Node, checker: ts.TypeChecker, boundHandler?: ts.Node, cache?: ProtocolCache): boolean;
@@ -3,12 +3,12 @@ import { isSupportedProtocolCallback } from './protocol-callback-invocation.mjs'
3
3
  import { potentiallyExecuted } from './protocol-executable-path.mjs';
4
4
  import { enclosingFunction } from './protocol-marker-analysis.mjs';
5
5
  /** Proves execution up to an optional handler whose registered binding is already established. */
6
- export function executableProtocolPath(node, checker, boundHandler) {
6
+ export function executableProtocolPath(node, checker, boundHandler, cache) {
7
7
  if (!potentiallyExecuted(node))
8
8
  return false;
9
9
  let fn = enclosingFunction(node);
10
10
  while (fn && fn !== boundHandler) {
11
- if (!potentiallyExecuted(fn) || !isSupportedProtocolCallback(fn, checker))
11
+ if (!potentiallyExecuted(fn) || !isSupportedProtocolCallback(fn, checker, cache))
12
12
  return false;
13
13
  fn = enclosingFunction(fn);
14
14
  }
@@ -10,6 +10,8 @@ import { visit } from './response-contract-route-analysis.mjs';
10
10
  import { isSupportedProtocolCallback } from './protocol-callback-invocation.mjs';
11
11
  import { mutatesHttpResponseMethod } from './protocol-http-method-mutations.mjs';
12
12
  import { opaqueProtocolCallbackPath } from './protocol-opaque-callback.mjs';
13
+ import { opaqueHttpContextArgument } from './protocol-http-context-escapes.mjs';
14
+ import { mutatedHttpResponse } from './protocol-http-response-mutations.mjs';
13
15
  const responseMethods = new Set([
14
16
  'setStatus',
15
17
  'pipeline',
@@ -27,6 +29,10 @@ export function associateHttpResponse(call, response, checker, siblings) {
27
29
  const emissions = new Map();
28
30
  for (const [scope, context] of httpContextScopes(handler, checker))
29
31
  visit(scope, (node) => {
32
+ if (mutatedHttpResponse(node, response, checker))
33
+ throw new Error('HTTP branded response body or status is mutated');
34
+ if (ts.isCallExpression(node) && opaqueHttpContextArgument(node, checker, context))
35
+ throw new Error('HTTP response context escapes through an opaque argument');
30
36
  if (mutatesHttpResponseMethod(node, context, checker))
31
37
  throw new Error('HTTP response context method is mutated');
32
38
  if (ts.isCallExpression(node) &&
@@ -68,6 +74,8 @@ export function associateHttpResponse(call, response, checker, siblings) {
68
74
  }
69
75
  /** Opaque callbacks may emit through an independently proven handler's context. */
70
76
  export function opaqueHttpResponse(call, checker, context) {
77
+ if (opaqueHttpContextArgument(call, checker, context))
78
+ return true;
71
79
  if (context)
72
80
  return (responseMethods.has(contextResponseMethod(call.expression, context, checker, true) ??
73
81
  indirectHttpResponseMethod(call, context, checker) ??
@@ -1,6 +1,7 @@
1
1
  import ts from '../contract-schema/typescript-api.mts';
2
+ import type { ProtocolCache } from './protocol-analysis-cache.mts';
2
3
  /** Use the same actual invocation sites for context admission and caller emission coverage. */
3
- export declare function httpContextInvocations(fn: ts.FunctionLikeDeclaration, checker: ts.TypeChecker): {
4
+ export declare function httpContextInvocations(fn: ts.FunctionLikeDeclaration, checker: ts.TypeChecker, cache?: ProtocolCache): {
4
5
  registered: boolean;
5
6
  callers: ts.CallExpression[];
6
7
  };
@@ -5,15 +5,21 @@ import { registeredHandler } from './protocol-callback-registration.mjs';
5
5
  import { executableProtocolPath } from './protocol-execution-path.mjs';
6
6
  import { enclosingFunction } from './protocol-marker-analysis.mjs';
7
7
  /** Use the same actual invocation sites for context admission and caller emission coverage. */
8
- export function httpContextInvocations(fn, checker) {
8
+ export function httpContextInvocations(fn, checker, cache) {
9
9
  const resolver = createProtocolCallbackValueResolver(checker);
10
- const calls = [];
11
- function visit(node) {
12
- if (ts.isCallExpression(node) && executableProtocolPath(node, checker))
13
- calls.push(node);
14
- ts.forEachChild(node, visit);
10
+ const sourceFile = fn.getSourceFile();
11
+ let calls = cache?.calls.get(sourceFile);
12
+ if (!calls) {
13
+ const found = [];
14
+ const visit = (node) => {
15
+ if (ts.isCallExpression(node) && executableProtocolPath(node, checker, undefined, cache))
16
+ found.push(node);
17
+ ts.forEachChild(node, visit);
18
+ };
19
+ visit(sourceFile);
20
+ calls = found;
21
+ cache?.calls.set(sourceFile, calls);
15
22
  }
16
- visit(fn.getSourceFile());
17
23
  const registered = calls.some((call) => registeredHandler(call) &&
18
24
  call.arguments.some((argument) => resolver.resolve(argument, new Map())?.node === fn));
19
25
  const callers = calls.filter((call) => {
@@ -0,0 +1,3 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ /** Opaque consumers of the canonical context may emit undocumented status or bodies. */
3
+ export declare function opaqueHttpContextArgument(call: ts.CallExpression, checker: ts.TypeChecker, context?: ts.Symbol, active?: Set<ts.FunctionLikeDeclaration>): boolean;
@@ -0,0 +1,95 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { expressionReceiver } from './protocol-write-receiver.mjs';
3
+ import { httpHandlerContext, contextResponseMethod } from './protocol-http-context.mjs';
4
+ import { httpContextInvocations } from './protocol-http-context-callers.mjs';
5
+ import { callbackArgumentBindings } from './protocol-callback-argument-bindings.mjs';
6
+ import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
7
+ import { enclosingFunction, unwrapExpression } from './protocol-marker-analysis.mjs';
8
+ import { executableProtocolPath } from './protocol-execution-path.mjs';
9
+ import { opaqueProtocolCallbackPath } from './protocol-opaque-callback.mjs';
10
+ import { visit } from './response-contract-route-analysis.mjs';
11
+ import { standardReflectApply } from './protocol-http-reflect.mjs';
12
+ import { createProtocolCallbackValueResolver, isProtocolCallbackFunction, } from './protocol-callback-values.mjs';
13
+ /** Opaque consumers of the canonical context may emit undocumented status or bodies. */
14
+ export function opaqueHttpContextArgument(call, checker, context, active = new Set()) {
15
+ const matches = (symbol) => call.arguments.some((argument) => contextArgument(argument, symbol, checker));
16
+ if (context && !matches(context))
17
+ return false;
18
+ if (!(executableProtocolPath(call, checker) || opaqueProtocolCallbackPath(call, checker)))
19
+ return false;
20
+ if (context)
21
+ return !implementedCallee(call, context, checker, active);
22
+ for (let owner = enclosingFunction(call); owner; owner = enclosingFunction(owner)) {
23
+ const name = runtimeParameters(owner)[0]?.name;
24
+ const symbol = name && ts.isIdentifier(name) ? checker.getSymbolAtLocation(name) : undefined;
25
+ if (symbol && matches(symbol) && httpHandlerContext(owner, checker) === symbol)
26
+ return !implementedCallee(call, symbol, checker, active);
27
+ }
28
+ return false;
29
+ }
30
+ function contextArgument(expression, context, checker) {
31
+ const receiver = expressionReceiver(expression, checker);
32
+ return ((receiver?.root === context &&
33
+ (receiver.path.length === 0 ||
34
+ (receiver.path.length === 1 && receiver.path[0] === 'response'))) ||
35
+ contextResponseMethod(unwrapExpression(expression), context, checker, true) === 'response');
36
+ }
37
+ /** Actual caller bindings retain the concrete implementations of consumed callback parameters. */
38
+ function implementedCallee(call, context, checker, active) {
39
+ const resolver = createProtocolCallbackValueResolver(checker);
40
+ if (standardReflectApply(call, checker)) {
41
+ const target = call.arguments[0] && resolver.resolve(call.arguments[0], new Map());
42
+ return !!(target &&
43
+ isProtocolCallbackFunction(target.node) &&
44
+ target.node.body &&
45
+ ts.isBlock(target.node.body) &&
46
+ target.node.body.statements.length === 0);
47
+ }
48
+ const implemented = (env) => {
49
+ const target = resolver.resolve(call.expression, env);
50
+ return !!(target &&
51
+ isProtocolCallbackFunction(target.node) &&
52
+ target.node.body &&
53
+ !calleeEscapes(target.node, call, context, checker, active));
54
+ };
55
+ if (implemented(new Map()))
56
+ return true;
57
+ const expression = unwrapExpression(call.expression);
58
+ const declaration = ts.isIdentifier(expression)
59
+ ? checker.getSymbolAtLocation(expression)?.valueDeclaration
60
+ : undefined;
61
+ const owner = declaration && ts.isParameter(declaration) ? enclosingFunction(declaration) : undefined;
62
+ if (!owner || !runtimeParameters(owner).includes(declaration))
63
+ return false;
64
+ const callers = httpContextInvocations(owner, checker).callers;
65
+ return (callers.length > 0 &&
66
+ callers.every((invocation) => {
67
+ const target = resolver.resolve(invocation.expression, new Map());
68
+ if (target?.node !== owner)
69
+ return false;
70
+ const env = callbackArgumentBindings(owner, invocation, new Map(), target.env, checker, resolver);
71
+ return !!env && implemented(env);
72
+ }));
73
+ }
74
+ /** The current proven call binds canonical arguments; other foreign callers do not hide escapes. */
75
+ function calleeEscapes(fn, call, context, checker, active) {
76
+ if (active.has(fn))
77
+ return true;
78
+ const next = new Set(active).add(fn);
79
+ const contexts = [context];
80
+ for (const [index, parameter] of runtimeParameters(fn).entries()) {
81
+ const argument = call.arguments[index];
82
+ if (!argument || !contextArgument(argument, context, checker))
83
+ continue;
84
+ if (!ts.isIdentifier(parameter.name))
85
+ return true;
86
+ contexts.push(checker.getSymbolAtLocation(parameter.name));
87
+ }
88
+ let escaped = false;
89
+ visit(fn.body, (node) => {
90
+ if (ts.isCallExpression(node) &&
91
+ contexts.some((symbol) => opaqueHttpContextArgument(node, checker, symbol, next)))
92
+ escaped = true;
93
+ });
94
+ return escaped;
95
+ }
@@ -1,8 +1,7 @@
1
1
  import ts from '../contract-schema/typescript-api.mts';
2
+ export { contextResponseMethod } from './protocol-http-method-access.mts';
2
3
  /** A helper parameter is a response context only when every executable caller passes one. */
3
4
  export declare function httpHandlerContext(fn: ts.FunctionLikeDeclaration, checker: ts.TypeChecker, active?: Set<ts.FunctionLikeDeclaration>): ts.Symbol | undefined;
4
- /** Literal bracket access is equivalent to property access for response methods. */
5
- export declare function contextResponseMethod(expression: ts.Expression, context: ts.Symbol, checker: ts.TypeChecker, allowMutable?: boolean): string | undefined;
6
5
  /** Follow only caller contexts already admitted by the actual-argument proof. */
7
6
  export declare function httpContextScopes(fn: ts.FunctionLikeDeclaration, checker: ts.TypeChecker): Map<ts.FunctionLikeDeclaration, ts.Symbol>;
8
7
  /** Indirect methods cannot prove the declared status or body, including borrowed this receivers. */