vouchington-tooling 0.29.0 → 0.30.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 (141) hide show
  1. package/README.md +64 -16
  2. package/dist/api-contract-discovery/header-contract-registry.mjs +2 -0
  3. package/dist/api-contract-discovery/protocol-body-status.d.mts +2 -0
  4. package/dist/api-contract-discovery/protocol-body-status.mjs +4 -0
  5. package/dist/api-contract-discovery/protocol-callback-argument-bindings.d.mts +7 -0
  6. package/dist/api-contract-discovery/protocol-callback-argument-bindings.mjs +33 -0
  7. package/dist/api-contract-discovery/protocol-callback-invocation.d.mts +5 -0
  8. package/dist/api-contract-discovery/protocol-callback-invocation.mjs +103 -0
  9. package/dist/api-contract-discovery/protocol-callback-mutations.d.mts +5 -0
  10. package/dist/api-contract-discovery/protocol-callback-mutations.mjs +42 -0
  11. package/dist/api-contract-discovery/protocol-callback-registration.d.mts +3 -0
  12. package/dist/api-contract-discovery/protocol-callback-registration.mjs +27 -0
  13. package/dist/api-contract-discovery/protocol-callback-values.d.mts +17 -0
  14. package/dist/api-contract-discovery/protocol-callback-values.mjs +114 -0
  15. package/dist/api-contract-discovery/protocol-context-alias.d.mts +3 -0
  16. package/dist/api-contract-discovery/protocol-context-alias.mjs +20 -0
  17. package/dist/api-contract-discovery/protocol-contract-extraction.d.mts +9 -0
  18. package/dist/api-contract-discovery/protocol-contract-extraction.mjs +87 -0
  19. package/dist/api-contract-discovery/protocol-contract-keys.d.mts +1 -0
  20. package/dist/api-contract-discovery/protocol-contract-keys.mjs +8 -0
  21. package/dist/api-contract-discovery/protocol-contract-registry.d.mts +5 -0
  22. package/dist/api-contract-discovery/protocol-contract-registry.mjs +133 -0
  23. package/dist/api-contract-discovery/protocol-executable-path.d.mts +3 -0
  24. package/dist/api-contract-discovery/protocol-executable-path.mjs +64 -0
  25. package/dist/api-contract-discovery/protocol-execution-path.d.mts +3 -0
  26. package/dist/api-contract-discovery/protocol-execution-path.mjs +16 -0
  27. package/dist/api-contract-discovery/protocol-http-association.d.mts +7 -0
  28. package/dist/api-contract-discovery/protocol-http-association.mjs +97 -0
  29. package/dist/api-contract-discovery/protocol-http-body-kinds.d.mts +3 -0
  30. package/dist/api-contract-discovery/protocol-http-body-kinds.mjs +19 -0
  31. package/dist/api-contract-discovery/protocol-http-context-callers.d.mts +6 -0
  32. package/dist/api-contract-discovery/protocol-http-context-callers.mjs +34 -0
  33. package/dist/api-contract-discovery/protocol-http-context.d.mts +9 -0
  34. package/dist/api-contract-discovery/protocol-http-context.mjs +108 -0
  35. package/dist/api-contract-discovery/protocol-http-emission.d.mts +3 -0
  36. package/dist/api-contract-discovery/protocol-http-emission.mjs +76 -0
  37. package/dist/api-contract-discovery/protocol-http-implicit-taint.d.mts +7 -0
  38. package/dist/api-contract-discovery/protocol-http-implicit-taint.mjs +26 -0
  39. package/dist/api-contract-discovery/protocol-http-registration.d.mts +12 -0
  40. package/dist/api-contract-discovery/protocol-http-registration.mjs +53 -0
  41. package/dist/api-contract-discovery/protocol-marker-analysis.d.mts +7 -0
  42. package/dist/api-contract-discovery/protocol-marker-analysis.mjs +50 -0
  43. package/dist/api-contract-discovery/protocol-opaque-callback.d.mts +3 -0
  44. package/dist/api-contract-discovery/protocol-opaque-callback.mjs +107 -0
  45. package/dist/api-contract-discovery/protocol-platform-callbacks.d.mts +5 -0
  46. package/dist/api-contract-discovery/protocol-platform-callbacks.mjs +72 -0
  47. package/dist/api-contract-discovery/protocol-platform-mutation-targets.d.mts +5 -0
  48. package/dist/api-contract-discovery/protocol-platform-mutation-targets.mjs +89 -0
  49. package/dist/api-contract-discovery/protocol-platform-writes.d.mts +3 -0
  50. package/dist/api-contract-discovery/protocol-platform-writes.mjs +92 -0
  51. package/dist/api-contract-discovery/protocol-requested-keys.d.mts +5 -0
  52. package/dist/api-contract-discovery/protocol-requested-keys.mjs +45 -0
  53. package/dist/api-contract-discovery/protocol-sse-callers.d.mts +7 -0
  54. package/dist/api-contract-discovery/protocol-sse-callers.mjs +67 -0
  55. package/dist/api-contract-discovery/protocol-sse-emission.d.mts +11 -0
  56. package/dist/api-contract-discovery/protocol-sse-emission.mjs +61 -0
  57. package/dist/api-contract-discovery/protocol-sse-feasible-status.d.mts +3 -0
  58. package/dist/api-contract-discovery/protocol-sse-feasible-status.mjs +24 -0
  59. package/dist/api-contract-discovery/protocol-sse-raw-writes.d.mts +9 -0
  60. package/dist/api-contract-discovery/protocol-sse-raw-writes.mjs +70 -0
  61. package/dist/api-contract-discovery/protocol-sse-status.d.mts +3 -0
  62. package/dist/api-contract-discovery/protocol-sse-status.mjs +84 -0
  63. package/dist/api-contract-discovery/protocol-sse-write-access.d.mts +9 -0
  64. package/dist/api-contract-discovery/protocol-sse-write-access.mjs +42 -0
  65. package/dist/api-contract-discovery/protocol-sse-write-helpers.d.mts +6 -0
  66. package/dist/api-contract-discovery/protocol-sse-write-helpers.mjs +66 -0
  67. package/dist/api-contract-discovery/protocol-sse-write-mutations.d.mts +9 -0
  68. package/dist/api-contract-discovery/protocol-sse-write-mutations.mjs +36 -0
  69. package/dist/api-contract-discovery/protocol-sse-write-resolution.d.mts +14 -0
  70. package/dist/api-contract-discovery/protocol-sse-write-resolution.mjs +79 -0
  71. package/dist/api-contract-discovery/protocol-status-dominance.d.mts +5 -0
  72. package/dist/api-contract-discovery/protocol-status-dominance.mjs +35 -0
  73. package/dist/api-contract-discovery/protocol-write-receiver.d.mts +9 -0
  74. package/dist/api-contract-discovery/protocol-write-receiver.mjs +79 -0
  75. package/dist/api-contract-discovery/query-contract-registry.mjs +2 -0
  76. package/dist/api-contract-discovery/registered-route-binding-writes.d.mts +3 -0
  77. package/dist/api-contract-discovery/registered-route-binding-writes.mjs +30 -0
  78. package/dist/api-contract-discovery/registered-route-catalog.mjs +11 -7
  79. package/dist/api-contract-discovery/registered-route-factory-returns.d.mts +3 -0
  80. package/dist/api-contract-discovery/registered-route-factory-returns.mjs +29 -0
  81. package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +5 -1
  82. package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +79 -35
  83. package/dist/api-contract-discovery/registered-route-runtime-parameters.d.mts +3 -0
  84. package/dist/api-contract-discovery/registered-route-runtime-parameters.mjs +5 -0
  85. package/dist/api-contract-discovery/registered-route-terminal-error.d.mts +3 -0
  86. package/dist/api-contract-discovery/registered-route-terminal-error.mjs +98 -0
  87. package/dist/api-contract-discovery/request-contract-registry.mjs +2 -0
  88. package/dist/api-contract-discovery/response-contract-implicit.mjs +28 -10
  89. package/dist/api-contract-discovery/response-contract-registry.mjs +14 -1
  90. package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +1 -1
  91. package/dist/api-contract-discovery/response-contract-route-analysis.mjs +22 -4
  92. package/dist/api-contract-discovery/response-contract-status.d.mts +1 -1
  93. package/dist/api-contract-discovery/response-contract-status.mjs +10 -8
  94. package/dist/api-contract-discovery/response-contract-types.d.mts +7 -0
  95. package/dist/cli/index.mjs +0 -3
  96. package/dist/cli/parse-forwarded-commands.d.mts +0 -3
  97. package/dist/cli/parse-forwarded-commands.mjs +0 -2
  98. package/dist/cli/parse.d.mts +0 -3
  99. package/dist/cli/usage.d.mts +1 -1
  100. package/dist/cli/usage.mjs +0 -5
  101. package/dist/contract-schema/contract-schema-bounded-array.d.mts +1 -3
  102. package/dist/contract-schema/contract-schema-bounded-array.mjs +5 -0
  103. package/dist/contract-schema/contract-schema-object-tuple.mjs +7 -5
  104. package/dist/contract-schema/contract-schema-type-extractor.mjs +13 -0
  105. package/dist/contract-schema/contract-schema-validator.mjs +2 -2
  106. package/dist/index.d.mts +0 -2
  107. package/dist/index.mjs +0 -1
  108. package/dist/openapi-document/build-openapi-catalog-sse.d.mts +4 -0
  109. package/dist/openapi-document/build-openapi-catalog-sse.mjs +27 -0
  110. package/dist/openapi-document/build-openapi-document.mjs +5 -2
  111. package/dist/openapi-document/build-openapi-response.d.mts +2 -2
  112. package/dist/openapi-document/build-openapi-response.mjs +33 -3
  113. package/dist/openapi-document/build-openapi-sse-events.d.mts +11 -0
  114. package/dist/openapi-document/build-openapi-sse-events.mjs +46 -0
  115. package/dist/openapi-document/contract-schema-intersection-merge.mjs +11 -11
  116. package/dist/openapi-document/contract-schema-to-openapi.mjs +3 -3
  117. package/dist/openapi-document/openapi-route-helpers.mjs +1 -3
  118. package/dist/openapi-document/openapi-types.d.mts +3 -0
  119. package/dist/openapi-document/operation-types.d.mts +7 -0
  120. package/package.json +3 -8
  121. package/dist/agent-harness-config/apply.d.mts +0 -4
  122. package/dist/agent-harness-config/apply.mjs +0 -72
  123. package/dist/agent-harness-config/cli.d.mts +0 -10
  124. package/dist/agent-harness-config/cli.mjs +0 -100
  125. package/dist/agent-harness-config/index.d.mts +0 -4
  126. package/dist/agent-harness-config/index.mjs +0 -3
  127. package/dist/agent-harness-config/merge-json.d.mts +0 -5
  128. package/dist/agent-harness-config/merge-json.mjs +0 -62
  129. package/dist/agent-harness-config/merge-toml.d.mts +0 -6
  130. package/dist/agent-harness-config/merge-toml.mjs +0 -132
  131. package/dist/agent-harness-config/paths.d.mts +0 -3
  132. package/dist/agent-harness-config/paths.mjs +0 -117
  133. package/dist/agent-harness-config/policy.d.mts +0 -14
  134. package/dist/agent-harness-config/policy.mjs +0 -51
  135. package/dist/agent-harness-config/prerequisites.d.mts +0 -2
  136. package/dist/agent-harness-config/prerequisites.mjs +0 -67
  137. package/dist/agent-harness-config/toml-value.d.mts +0 -6
  138. package/dist/agent-harness-config/toml-value.mjs +0 -69
  139. package/dist/agent-harness-config/types.d.mts +0 -90
  140. package/dist/agent-harness-config/types.mjs +0 -2
  141. package/docs/agent-harness-config.md +0 -134
package/README.md CHANGED
@@ -34,9 +34,6 @@ vouchington runner-port-policy
34
34
  vouchington runner-port-policy --file ./policy.json
35
35
  vouchington runner-port-policy --reserved 2200
36
36
  vouchington with-host-lock --name expensive-build --timeout-seconds 60 -- make build
37
- vouchington agent-harness-config dump
38
- vouchington agent-harness-config check --global
39
- vouchington agent-harness-config apply --global --repo /path/to/checkout
40
37
  vouchington gha-runtime-audit --pr-workflow CI --push-workflow '/^Main CI \\(.+\\)$/'
41
38
  vouchington require-up-to-date --remote origin --branch main
42
39
  vouchington gitleaks-directory-scan --config .gitleaks.toml
@@ -199,11 +196,6 @@ import {
199
196
  import { initSqlAst, extractCreateTableMetadata } from 'vouchington-tooling/sql-ast'
200
197
  import { splitSqlStatements, stripSqlComments } from 'vouchington-tooling/sql-scanner'
201
198
  import { auditCiJobRuntime } from 'vouchington-tooling/gha-runtime-audit'
202
- import {
203
- applyHarnessConfig,
204
- checkHarnessConfig,
205
- dumpHarnessPolicy,
206
- } from 'vouchington-tooling/agent-harness-config'
207
199
  import {
208
200
  inspectHarnessEnvironment,
209
201
  selectHarnessSession,
@@ -374,14 +366,8 @@ summaries and quoted attributes; it is not a general HTML validator or sanitizer
374
366
  the input or infers semantic relevance, cost, schema conformance, or failure causes. The existing
375
367
  `markdownSectionBetweenHeadings` API retains its original boundary behavior.
376
368
 
377
- `agent-harness-config` merges classifier-auto and sandbox keys into Claude, Codex, Grok, and Cursor
378
- config files. See [docs/agent-harness-config.md](./docs/agent-harness-config.md). `--global` updates
379
- home-directory configs; `--repo` updates a checkout. It does not copy allowlists, hooks, or plugins.
380
- Grok uses `permission_mode = "auto"` plus `auto_mode.enabled` and `default_auto_mode` so the
381
- classifier stays available in plan mode; its defined sandbox profile still requires
382
- `--sandbox workspace-write` at launch. Cursor uses global `approvalMode = "auto-review"`;
383
- `unrestricted` disables the sandbox. Repo checks surface user-level trust or mode prerequisites
384
- instead of claiming that repo files alone activate them.
369
+ Machine-wide agent configuration now belongs to [vouchington-machines](https://github.com/vouchington/vouchington-machines/blob/main/docs/agent-config.md).
370
+ Use its `configure-agents.sh` and `diagnose-agents.sh`; project hooks and MCP configuration remain in their checkouts.
385
371
 
386
372
  `agent-harness-identity` inspects the four harness environment signals without choosing a winner.
387
373
  Callers supply their own precedence to `selectHarnessSession`, keeping product-specific identity
@@ -470,6 +456,68 @@ Version one recognizes literal-key `apiResponse`, `apiNoContent`, `apiOpenApiRaw
470
456
  `ctx.setStatus`, and static `Content-Type` setters. The input source files and TypeScript checker
471
457
  are the source representation; the adapter does not parse or discover a repository on its own.
472
458
 
459
+ Error-only 405 routes require a terminal throw on the handler's own context. Named handlers and
460
+ imported wrappers are followed through compiler declarations; conditional, caught, recursive,
461
+ fallthrough, and callback-only throws do not prove an error-only route. Empty `never[]` contracts
462
+ render as arrays with no items, and `Record<string, never>` renders as a closed empty object.
463
+ Standalone `never` and `any` remain unsupported contracts.
464
+
465
+ Caller-owned `apiSseFrame(key, { event, data })` markers expose literal event names and concrete
466
+ payloads from the frames actually written by a route, including callbacks passed to a handler
467
+ factory. SSE responses keep a string body schema and describe each payload under the media
468
+ type's `x-sse-events` map. Callback arguments require a concrete helper invocation path; ignored,
469
+ declaration-only, deferred, or rewritten callbacks fail closed. Callback options passed to a callee
470
+ without a concrete implementation also fail closed. Opaque callback escape checks include array and
471
+ tuple containers, nested object properties, and literal array spreads. Factory callbacks must execute
472
+ from the returned request handler; registration-time invocations do not prove a request emission. Compiler-resolved platform timers
473
+ and Promise executors supply invocation paths using the caller Program's actual standard-library
474
+ identities; shadowed, custom, or reassigned platform APIs do not. Mutation checks recognize Node's
475
+ compiler-declared `global` as well as `globalThis`. Fluent route registrations retain their method. Stable named
476
+ handlers and called local helpers are followed within their declaring source file. Ordinary response,
477
+ request, query, and header markers also require callback invocation before route attribution.
478
+ Broad names, unknown root payloads, executable unmarked frames through stream aliases, and
479
+ conditional or mutable-context status changes fail closed. Called SSE helpers inherit the proven
480
+ request context and its status at the invocation; mutually exclusive complete status branches retain
481
+ their separate responses. Explicit SSE statuses must be integers from 100 through 599.
482
+ Unconditional blocks preserve the last proven status setter; conditional or foreign overwrites fail closed.
483
+ Indirect `setStatus.call`, `apply`, and `bind` forms fail closed instead of assuming status 200.
484
+ Ambiguous caller contexts fail closed. A raw stream failure invalidates all
485
+ selected SSE rows, including when only one protocol variant was requested. Object-literal wrapper
486
+ receivers fail closed for marked frames because their properties can mutate; raw wrapper writes
487
+ are matched to the original stream, including proven helper-parameter forwarding, overloaded helper
488
+ implementations, and literal bracket
489
+ methods. Compiler-resolved `Reflect.apply` calls use their actual receiver and payload.
490
+ Unsupported computed methods on that receiver fail closed. Byte-bearing
491
+ stream `end` calls and streams used as pipe destinations are raw emissions; empty terminal cleanup
492
+ is preserved. Replacing a selected stream's `write` implementation invalidates its frame evidence.
493
+ Binding a selected stream's `write` or `end` method fails closed because the bound
494
+ method can escape the proven emission path. A callback that captures
495
+ the route's stream or HTTP context and escapes to a callee without a concrete implementation can
496
+ write undocumented bytes, so the affected response contracts fail closed. Uncalled local callbacks,
497
+ dead branches, and implemented helpers that ignore their callback preserve the proven contracts.
498
+ Cataloged SSE routes with only extracted non-SSE variants retain an unavailable event-stream
499
+ possibility alongside those responses until the extracted variants describe the stream.
500
+
501
+ Caller-owned `apiOpenApiHttpResponse(key, response)` markers bind a constant opaque response
502
+ carrying optional readonly `apiHttpResponseVariants` metadata. Each concrete union variant has
503
+ `status` and `bodyKind`; content variants also have `mediaType` and `body`. Discovery associates
504
+ the marked variable's status and body emissions without reading or replacing response bytes.
505
+ A status setter must dominate each body emission, including separate branches; constant context
506
+ aliases are followed, assigned mutable context aliases and object-literal context wrappers fail closed,
507
+ and helper parameters must resolve to the registered caller's unchanged response context.
508
+ Helper-owned contracts also account for response emissions in their proven caller scopes.
509
+ Indirect response `call`, `apply`, and `bind` forms fail closed, including borrowed context receivers.
510
+ SSE frames and branded content variants reject body-forbidden 1xx, 204, 205, and 304 statuses.
511
+ Literal bracket response methods have the same emission checks as property methods. Unrelated raw emissions
512
+ remain unavailable. Separate branded response branches are checked together, and the nearest
513
+ proven status setter for each body must belong to that response. Observed content and bodyless
514
+ emissions must match the complete metadata union, including an empty response in the `else` branch
515
+ of a positive body check. Exact requested protocol rows are selected before
516
+ other payloads, media types, or statuses are validated. In lenient mode, incomplete marker evidence
517
+ keeps every selected sibling unavailable. Explicit 400 content variants also include the framework's
518
+ JSON error schema independently of their own media type. Explicit bodyless 400 metadata also
519
+ retains that framework JSON possibility and fails closed on the conflicting body kinds.
520
+
473
521
  Fork diagnostics use a caller-selected `recordDirectory` in both the worker setup file and the
474
522
  main-process reporter. Call `registerForkExitSentinel({ recordDirectory })` once per fork, then
475
523
  update `setCurrentForkExitModule` and `setCurrentForkExitProject` around tests. Construct
@@ -1,8 +1,10 @@
1
1
  import ts from '../contract-schema/typescript-api.mjs';
2
+ import { registerPlatformCompilerLibraries } from './protocol-platform-callbacks.mjs';
2
3
  import { contractError } from './response-contract-registration.mjs';
3
4
  import { parseHeaderContract } from './header-contract-parser.mjs';
4
5
  import { collectHandlerBindings, enclosingRouteBinding, visit, } from './response-contract-route-analysis.mjs';
5
6
  export function discoverApiHeaderContracts(program, sourceFiles, knownResponseRoutes) {
7
+ registerPlatformCompilerLibraries(program);
6
8
  const checker = program.getTypeChecker();
7
9
  const bindings = collectHandlerBindings(sourceFiles, checker);
8
10
  const contracts = new Map();
@@ -0,0 +1,2 @@
1
+ /** RFC 9110 sections 6.4.1 and 15.3.6 prohibit content at these response statuses. */
2
+ export declare function statusForbidsBody(status: number): boolean;
@@ -0,0 +1,4 @@
1
+ /** RFC 9110 sections 6.4.1 and 15.3.6 prohibit content at these response statuses. */
2
+ export function statusForbidsBody(status) {
3
+ return (status >= 100 && status < 200) || status === 204 || status === 205 || status === 304;
4
+ }
@@ -0,0 +1,7 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ import type { CallbackBindings, createProtocolCallbackValueResolver } from './protocol-callback-values.mts';
3
+ /** Binds literal callback parameters, declining unsupported destructuring shapes. */
4
+ export declare function callbackArgumentBindings(fn: ts.FunctionLikeDeclaration, call: ts.CallExpression | ts.NewExpression, env: CallbackBindings, captured: CallbackBindings, checker: ts.TypeChecker, resolver: ReturnType<typeof createProtocolCallbackValueResolver>): Map<ts.Symbol, {
5
+ node: ts.Node;
6
+ env: CallbackBindings;
7
+ }> | undefined;
@@ -0,0 +1,33 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
3
+ /** Binds literal callback parameters, declining unsupported destructuring shapes. */
4
+ export function callbackArgumentBindings(fn, call, env, captured, checker, resolver) {
5
+ const next = new Map(captured);
6
+ for (const [index, parameter] of runtimeParameters(fn).entries()) {
7
+ const argument = call.arguments?.[index];
8
+ if (!argument)
9
+ continue;
10
+ if (ts.isSpreadElement(argument))
11
+ return undefined;
12
+ if (ts.isIdentifier(parameter.name)) {
13
+ next.set(checker.getSymbolAtLocation(parameter.name), { node: argument, env });
14
+ }
15
+ else {
16
+ if (!ts.isObjectBindingPattern(parameter.name))
17
+ return undefined;
18
+ const value = resolver.resolve(argument, env);
19
+ for (const element of parameter.name.elements) {
20
+ const key = element.propertyName ?? element.name;
21
+ if (!ts.isIdentifier(element.name) ||
22
+ element.dotDotDotToken ||
23
+ element.initializer ||
24
+ !(ts.isIdentifier(key) || ts.isStringLiteral(key)))
25
+ return undefined;
26
+ const bound = resolver.property(value, key.text, new Set());
27
+ if (bound)
28
+ next.set(checker.getSymbolAtLocation(element.name), bound);
29
+ }
30
+ }
31
+ }
32
+ return next;
33
+ }
@@ -0,0 +1,5 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ type FunctionNode = ts.FunctionLikeDeclaration;
3
+ /** Proves callback consumption through concrete helper bodies, without executing nested closures. */
4
+ export declare function isSupportedProtocolCallback(fn: FunctionNode, checker: ts.TypeChecker): boolean;
5
+ export {};
@@ -0,0 +1,103 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
3
+ import { invokedResult, registeredHandler } from './protocol-callback-registration.mjs';
4
+ import { platformCallbackArgument } from './protocol-platform-callbacks.mjs';
5
+ import { potentiallyExecuted } from './protocol-executable-path.mjs';
6
+ import { callbackBindingReplaced, callbackOptionsEscape } from './protocol-callback-mutations.mjs';
7
+ import { enclosingFunction } from './protocol-marker-analysis.mjs';
8
+ import { createProtocolCallbackValueResolver, isProtocolCallbackFunction as functionNode, protocolCallbackSourceCalls, protocolCallbackHasWrittenBindings, } from './protocol-callback-values.mjs';
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))
12
+ return false;
13
+ const resolver = createProtocolCallbackValueResolver(checker);
14
+ const { resolve, symbol } = resolver;
15
+ let invalidated = false;
16
+ function consumed(call, env, active, returned = false) {
17
+ const platform = platformCallbackArgument(call, checker);
18
+ const callee = resolve(platform ?? call.expression, env);
19
+ if (!callee || !functionNode(callee.node) || !callee.node.body || callee.node.asteriskToken) {
20
+ invalidated ||= callbackOptionsEscape(call, env, fn, resolver);
21
+ return false;
22
+ }
23
+ if (protocolCallbackHasWrittenBindings(callee.node, checker)) {
24
+ invalidated = true;
25
+ return false;
26
+ }
27
+ if (callee.node === fn)
28
+ return !returned;
29
+ if (active.has(callee.node))
30
+ return false;
31
+ const chain = new Set([...active, callee.node]);
32
+ if (platform)
33
+ return body(callee.node.body, callee.env, chain, false);
34
+ const next = new Map(callee.env);
35
+ runtimeParameters(callee.node).forEach((parameter, index) => {
36
+ const argument = call.arguments[index];
37
+ const target = symbol(parameter.name);
38
+ if (argument && !ts.isSpreadElement(argument))
39
+ next.set(target, { node: argument, env });
40
+ });
41
+ return body(callee.node.body, next, chain, returned || invokedResult(call));
42
+ }
43
+ function body(content, env, active, returned) {
44
+ let found = false;
45
+ let replaced = false;
46
+ function visit(node) {
47
+ if (found || replaced || !potentiallyExecuted(node))
48
+ return;
49
+ if (functionNode(node))
50
+ return;
51
+ if (callbackBindingReplaced(node, env, fn, resolver)) {
52
+ replaced = true;
53
+ invalidated = true;
54
+ return;
55
+ }
56
+ if ((ts.isCallExpression(node) || ts.isNewExpression(node)) &&
57
+ consumed(node, env, active, returned && ts.isReturnStatement(node.parent)) &&
58
+ (!returned || ts.isReturnStatement(node.parent))) {
59
+ found = true;
60
+ return;
61
+ }
62
+ if (returned && ts.isReturnStatement(node) && node.expression) {
63
+ const value = resolve(node.expression, env);
64
+ if (value &&
65
+ functionNode(value.node) &&
66
+ value.node.body &&
67
+ !value.node.asteriskToken &&
68
+ !active.has(value.node) &&
69
+ body(value.node.body, value.env, new Set([...active, value.node]), false)) {
70
+ found = true;
71
+ return;
72
+ }
73
+ }
74
+ ts.forEachChild(node, visit);
75
+ }
76
+ visit(content);
77
+ return found && !invalidated;
78
+ }
79
+ let argument = fn;
80
+ while (ts.isPropertyAssignment(argument.parent) ||
81
+ ts.isObjectLiteralExpression(argument.parent) ||
82
+ ts.isParenthesizedExpression(argument.parent) ||
83
+ ts.isAsExpression(argument.parent) ||
84
+ ts.isNonNullExpression(argument.parent))
85
+ argument = argument.parent;
86
+ const call = argument.parent;
87
+ if (!ts.isCallExpression(call) && !ts.isNewExpression(call)) {
88
+ const owner = enclosingFunction(fn);
89
+ if (owner && isSupportedProtocolCallback(owner, checker))
90
+ return body(owner.body, new Map(), new Set([owner]), false);
91
+ return protocolCallbackSourceCalls(fn).some((candidate) => potentiallyExecuted(candidate) &&
92
+ ((registeredHandler(candidate) &&
93
+ candidate.arguments.some((value) => resolve(value, new Map())?.node === fn)) ||
94
+ consumed(candidate, new Map(), new Set())));
95
+ }
96
+ if (!potentiallyExecuted(call))
97
+ return false;
98
+ if (ts.isCallExpression(call) && call.expression === argument)
99
+ return true;
100
+ if (argument === fn && ts.isCallExpression(call) && registeredHandler(call))
101
+ return true;
102
+ return (!!call.arguments?.includes(argument) && consumed(call, new Map(), new Set()));
103
+ }
@@ -0,0 +1,5 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ import type { CallbackBindings, createProtocolCallbackValueResolver } from './protocol-callback-values.mts';
3
+ export declare function callbackBindingReplaced(node: ts.Node, env: CallbackBindings, fn: ts.FunctionLikeDeclaration, resolver: ReturnType<typeof createProtocolCallbackValueResolver>): boolean;
4
+ /** An opaque callee may replace a callback stored in a forwarded options object. */
5
+ export declare function callbackOptionsEscape(node: ts.CallExpression | ts.NewExpression, env: CallbackBindings, fn: ts.FunctionLikeDeclaration, resolver: ReturnType<typeof createProtocolCallbackValueResolver>): boolean;
@@ -0,0 +1,42 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { unwrapExpression } from './protocol-marker-analysis.mjs';
3
+ export function callbackBindingReplaced(node, env, fn, resolver) {
4
+ const { resolve, symbol } = resolver;
5
+ const write = ts.isDeleteExpression(node)
6
+ ? node.expression
7
+ : ts.isBinaryExpression(node) &&
8
+ node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
9
+ node.operatorToken.kind <= ts.SyntaxKind.LastAssignment
10
+ ? node.left
11
+ : undefined;
12
+ if (!write)
13
+ return false;
14
+ let root = unwrapExpression(write);
15
+ while (ts.isPropertyAccessExpression(root) || ts.isElementAccessExpression(root))
16
+ root = unwrapExpression(root.expression);
17
+ return (ts.isIdentifier(root) && env.has(symbol(root))) || resolve(write, env)?.node === fn;
18
+ }
19
+ /** An opaque callee may replace a callback stored in a forwarded options object. */
20
+ export function callbackOptionsEscape(node, env, fn, resolver) {
21
+ function contains(value, bindings, seen = new Set()) {
22
+ const resolved = resolver.resolve(value, bindings);
23
+ if (!resolved || seen.has(resolved.node))
24
+ return false;
25
+ seen.add(resolved.node);
26
+ if (resolved.node === fn)
27
+ return true;
28
+ if (ts.isArrayLiteralExpression(resolved.node))
29
+ return resolved.node.elements.some((element) => contains(ts.isSpreadElement(element) ? element.expression : element, resolved.env, seen));
30
+ if (!ts.isObjectLiteralExpression(resolved.node))
31
+ return false;
32
+ return resolved.node.properties.some((member) => member === fn ||
33
+ (ts.isPropertyAssignment(member)
34
+ ? contains(member.initializer, resolved.env, seen)
35
+ : ts.isShorthandPropertyAssignment(member) &&
36
+ (() => {
37
+ const target = resolver.property(resolved, member.name.text, new Set());
38
+ return !!target && contains(target.node, target.env, seen);
39
+ })()));
40
+ }
41
+ return !!node.arguments?.some((argument) => contains(argument, env));
42
+ }
@@ -0,0 +1,3 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ export declare function registeredHandler(call: ts.CallExpression): boolean;
3
+ export declare function invokedResult(call: ts.CallExpression | ts.NewExpression): boolean;
@@ -0,0 +1,27 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { unwrapExpression } from './protocol-marker-analysis.mjs';
3
+ export function registeredHandler(call) {
4
+ const method = unwrapExpression(call.expression);
5
+ if (!ts.isPropertyAccessExpression(method) ||
6
+ !['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].includes(method.name.text))
7
+ return false;
8
+ let receiver = method.expression;
9
+ while (ts.isCallExpression(receiver) && ts.isPropertyAccessExpression(receiver.expression)) {
10
+ if (receiver.expression.name.text === 'route')
11
+ return true;
12
+ receiver = receiver.expression.expression;
13
+ }
14
+ return false;
15
+ }
16
+ export function invokedResult(call) {
17
+ let node = call;
18
+ while (ts.isParenthesizedExpression(node.parent) ||
19
+ ts.isAsExpression(node.parent) ||
20
+ ts.isNonNullExpression(node.parent) ||
21
+ ts.isAwaitExpression(node.parent))
22
+ node = node.parent;
23
+ const parent = node.parent;
24
+ return (ts.isCallExpression(parent) &&
25
+ (parent.expression === node ||
26
+ (registeredHandler(parent) && parent.arguments.includes(node))));
27
+ }
@@ -0,0 +1,17 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ type FunctionNode = ts.FunctionLikeDeclaration;
3
+ export type CallbackValue = {
4
+ node: ts.Node;
5
+ env: CallbackBindings;
6
+ };
7
+ export type CallbackBindings = ReadonlyMap<ts.Symbol, CallbackValue>;
8
+ export declare function isProtocolCallbackFunction(node: ts.Node): node is FunctionNode;
9
+ /** Resolves stable named uses in outer statements and directly registered inline handlers. */
10
+ export declare function protocolCallbackSourceCalls(fn: FunctionNode): ts.CallExpression[];
11
+ export declare function createProtocolCallbackValueResolver(checker: ts.TypeChecker): {
12
+ resolve: (node: ts.Node, env: CallbackBindings, seen?: Set<ts.Node>) => CallbackValue | undefined;
13
+ symbol: (node: ts.Node) => ts.Symbol | undefined;
14
+ property: (value: CallbackValue | undefined, name: string, seen: Set<ts.Node>) => CallbackValue | undefined;
15
+ };
16
+ export declare function protocolCallbackHasWrittenBindings(fn: FunctionNode, checker: ts.TypeChecker): boolean;
17
+ export {};
@@ -0,0 +1,114 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { registeredHandler } from './protocol-callback-registration.mjs';
3
+ import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
4
+ import { hasBindingWrite } from './registered-route-binding-writes.mjs';
5
+ import { unwrapExpression } from './protocol-marker-analysis.mjs';
6
+ export function isProtocolCallbackFunction(node) {
7
+ return ts.isFunctionLike(node) && 'body' in node;
8
+ }
9
+ /** Resolves stable named uses in outer statements and directly registered inline handlers. */
10
+ export function protocolCallbackSourceCalls(fn) {
11
+ const declaration = fn.parent;
12
+ if (!ts.isFunctionDeclaration(fn) &&
13
+ (!ts.isVariableDeclaration(declaration) ||
14
+ !ts.isIdentifier(declaration.name) ||
15
+ !ts.isVariableDeclarationList(declaration.parent) ||
16
+ !(declaration.parent.flags & ts.NodeFlags.Const)))
17
+ return [];
18
+ const calls = [];
19
+ function visit(node) {
20
+ if (isProtocolCallbackFunction(node) &&
21
+ !(ts.isCallExpression(node.parent) &&
22
+ registeredHandler(node.parent) &&
23
+ node.parent.arguments.includes(node)))
24
+ return;
25
+ if (ts.isCallExpression(node))
26
+ calls.push(node);
27
+ ts.forEachChild(node, visit);
28
+ }
29
+ visit(fn.getSourceFile());
30
+ return calls;
31
+ }
32
+ function literalName(node) {
33
+ return ts.isIdentifier(node) || ts.isStringLiteral(node) ? node.text : undefined;
34
+ }
35
+ export function createProtocolCallbackValueResolver(checker) {
36
+ function symbol(node) {
37
+ const found = checker.getSymbolAtLocation(node);
38
+ return found?.flags && found.flags & ts.SymbolFlags.Alias
39
+ ? checker.getAliasedSymbol(found)
40
+ : found;
41
+ }
42
+ function property(value, name, seen) {
43
+ if (!value ||
44
+ !ts.isObjectLiteralExpression(value.node) ||
45
+ value.node.properties.some(ts.isSpreadAssignment))
46
+ return undefined;
47
+ const member = value.node.properties.find((item) => item.name && literalName(item.name) === name);
48
+ if (member && ts.isPropertyAssignment(member))
49
+ return resolve(member.initializer, value.env, seen);
50
+ if (member && ts.isShorthandPropertyAssignment(member)) {
51
+ const target = checker.getShorthandAssignmentValueSymbol(member);
52
+ return target ? declaration(target, value.env, seen) : undefined;
53
+ }
54
+ return member && isProtocolCallbackFunction(member)
55
+ ? { node: member, env: value.env }
56
+ : undefined;
57
+ }
58
+ function declaration(target, env, seen) {
59
+ const bound = env.get(target);
60
+ if (bound)
61
+ return resolve(bound.node, bound.env, seen);
62
+ const implemented = target.declarations?.find((item) => isProtocolCallbackFunction(item) && item.body);
63
+ if (implemented)
64
+ return { node: implemented, env };
65
+ const node = target.valueDeclaration;
66
+ if (!node || seen.has(node))
67
+ return undefined;
68
+ seen.add(node);
69
+ if (ts.isVariableDeclaration(node) &&
70
+ node.initializer &&
71
+ ts.isVariableDeclarationList(node.parent) &&
72
+ node.parent.flags & ts.NodeFlags.Const)
73
+ return resolve(node.initializer, env, seen);
74
+ if (ts.isBindingElement(node) && ts.isObjectBindingPattern(node.parent)) {
75
+ const owner = node.parent.parent;
76
+ const name = literalName(node.propertyName ?? node.name);
77
+ if (name &&
78
+ ts.isVariableDeclaration(owner) &&
79
+ owner.initializer &&
80
+ ts.isVariableDeclarationList(owner.parent) &&
81
+ owner.parent.flags & ts.NodeFlags.Const)
82
+ return property(resolve(owner.initializer, env, seen), name, seen);
83
+ }
84
+ return undefined;
85
+ }
86
+ function resolve(node, env, seen = new Set()) {
87
+ if (ts.isExpression(node))
88
+ node = unwrapExpression(node);
89
+ if (seen.has(node))
90
+ return undefined;
91
+ seen.add(node);
92
+ if (isProtocolCallbackFunction(node) ||
93
+ ts.isObjectLiteralExpression(node) ||
94
+ ts.isArrayLiteralExpression(node))
95
+ return { node, env };
96
+ if (ts.isIdentifier(node)) {
97
+ const target = symbol(node);
98
+ return target ? declaration(target, env, seen) : undefined;
99
+ }
100
+ if (ts.isPropertyAccessExpression(node)) {
101
+ const member = property(resolve(node.expression, env, seen), node.name.text, seen);
102
+ if (member)
103
+ return member;
104
+ const target = symbol(node.name);
105
+ return target ? declaration(target, env, seen) : undefined;
106
+ }
107
+ return undefined;
108
+ }
109
+ return { resolve, symbol, property };
110
+ }
111
+ export function protocolCallbackHasWrittenBindings(fn, checker) {
112
+ return ((ts.isFunctionDeclaration(fn) && hasBindingWrite(fn, checker)) ||
113
+ runtimeParameters(fn).some((parameter) => hasBindingWrite(parameter, checker)));
114
+ }
@@ -0,0 +1,3 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ export declare function unsupportedContextAlias(node: ts.VariableDeclaration, context: ts.Symbol, checker: ts.TypeChecker): boolean;
3
+ export declare function unsupportedContextAssignment(node: ts.Node, context: ts.Symbol, checker: ts.TypeChecker): boolean;
@@ -0,0 +1,20 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { expressionReceiver } from './protocol-write-receiver.mjs';
3
+ export function unsupportedContextAlias(node, context, checker) {
4
+ if (!node.initializer)
5
+ return false;
6
+ const receiver = expressionReceiver(node.initializer, checker);
7
+ return (receiver?.root === context &&
8
+ (receiver.path.length === 0 ||
9
+ (receiver.path.length === 1 && receiver.path[0] === 'response')) &&
10
+ (!ts.isIdentifier(node.name) ||
11
+ !ts.isVariableDeclarationList(node.parent) ||
12
+ !(node.parent.flags & ts.NodeFlags.Const)));
13
+ }
14
+ export function unsupportedContextAssignment(node, context, checker) {
15
+ if (!ts.isBinaryExpression(node) || node.operatorToken.kind !== ts.SyntaxKind.EqualsToken)
16
+ return false;
17
+ const receiver = expressionReceiver(node.right, checker);
18
+ return (receiver?.root === context &&
19
+ (receiver.path.length === 0 || (receiver.path.length === 1 && receiver.path[0] === 'response')));
20
+ }
@@ -0,0 +1,9 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ import { type ExtractedResponseContract } from '../contract-schema/index.mts';
3
+ import type { DiscoverApiResponseContractsOptions } from './response-contract-lenient.mts';
4
+ import type { BackendResponseContract } from './response-contract-types.mts';
5
+ export declare function extractSseEvents(type: ts.Type, checker: ts.TypeChecker, source: string, options: DiscoverApiResponseContractsOptions | undefined): {
6
+ eventName: string;
7
+ contract: ExtractedResponseContract;
8
+ }[];
9
+ export declare function extractHttpVariants(type: ts.Type, checker: ts.TypeChecker, source: string, options: DiscoverApiResponseContractsOptions | undefined, selectRow: (index: number) => boolean): Omit<BackendResponseContract, 'method' | 'routeTemplate'>[];
@@ -0,0 +1,87 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { extractContractSchema } from '../contract-schema/index.mjs';
3
+ import { propertyType, stringLiterals, typeVariants } from './protocol-marker-analysis.mjs';
4
+ import { noContentContract } from './response-contract-registration.mjs';
5
+ import { statusForbidsBody } from './protocol-body-status.mjs';
6
+ export function extractSseEvents(type, checker, source, options) {
7
+ return typeVariants(type).flatMap((variant) => {
8
+ const names = stringLiterals(propertyType(variant, 'event', checker));
9
+ const contract = concretePayload(propertyType(variant, 'data', checker), checker, source, options);
10
+ return names.map((eventName) => ({ eventName, contract }));
11
+ });
12
+ }
13
+ export function extractHttpVariants(type, checker, source, options, selectRow) {
14
+ const carrier = propertyType(type, 'apiHttpResponseVariants', checker);
15
+ const variants = typeVariants(carrier).filter((variant) => !(variant.flags & ts.TypeFlags.Undefined));
16
+ if (variants.length === 0)
17
+ throw new Error('HTTP response requires concrete variants');
18
+ let row = 0;
19
+ const selections = variants.map((variant) => {
20
+ const rawMedia = rawPropertyType(variant, 'mediaType', checker);
21
+ const rawKind = rawPropertyType(variant, 'bodyKind', checker);
22
+ const count = rawKind?.isStringLiteral() && rawKind.value === 'none'
23
+ ? 1
24
+ : rawMedia?.isUnion()
25
+ ? rawMedia.types.length
26
+ : 1;
27
+ const selected = Array.from({ length: count }, () => selectRow(row++));
28
+ return selected;
29
+ });
30
+ return variants.flatMap((variant, index) => {
31
+ const selected = selections[index];
32
+ if (!selected.some(Boolean))
33
+ return [];
34
+ const status = propertyType(variant, 'status', checker);
35
+ if (!(status.flags & ts.TypeFlags.NumberLiteral))
36
+ throw new Error('HTTP response requires a literal status');
37
+ const value = status.value;
38
+ if (!Number.isInteger(value) || value < 100 || value > 599)
39
+ throw new Error('HTTP response status is invalid');
40
+ const kinds = stringLiterals(propertyType(variant, 'bodyKind', checker));
41
+ if (kinds.length !== 1)
42
+ throw new Error('HTTP response requires one concrete body kind per variant');
43
+ const statusCodes = [value];
44
+ if (kinds[0] === 'none')
45
+ return [
46
+ {
47
+ ...noContentContract(source),
48
+ statusCodes,
49
+ statusKnowledge: 'explicit',
50
+ bodyKind: 'none',
51
+ mediaTypeKnowledge: 'none',
52
+ ...(value === 400 ? { includeDefaultError: true } : {}),
53
+ },
54
+ ];
55
+ if (kinds[0] !== 'content')
56
+ throw new Error('HTTP response body kind is invalid');
57
+ if (statusForbidsBody(value))
58
+ throw new Error(`HTTP status ${value} forbids response content`);
59
+ const contract = concretePayload(propertyType(variant, 'body', checker), checker, source, options);
60
+ return stringLiterals(propertyType(variant, 'mediaType', checker)).flatMap((mediaType, index) => selected[index]
61
+ ? [
62
+ {
63
+ ...contract,
64
+ statusCodes,
65
+ statusKnowledge: 'explicit',
66
+ bodyKind: 'content',
67
+ mediaType,
68
+ mediaTypeKnowledge: 'known',
69
+ ...(value === 400 ? { includeDefaultError: true } : {}),
70
+ },
71
+ ]
72
+ : []);
73
+ });
74
+ }
75
+ function concretePayload(type, checker, source, options) {
76
+ const contract = extractContractSchema(type, checker, source, options);
77
+ if (contract.schema.root.type === 'unknown')
78
+ throw new Error('Protocol payload must have a concrete root');
79
+ return contract;
80
+ }
81
+ function rawPropertyType(type, name, checker) {
82
+ const property = checker.getPropertyOfType(type, name);
83
+ const declaration = property?.valueDeclaration ?? property?.declarations?.[0];
84
+ return property && declaration
85
+ ? checker.getTypeOfSymbolAtLocation(property, declaration)
86
+ : undefined;
87
+ }
@@ -0,0 +1 @@
1
+ export declare function nextProtocolKey(contracts: ReadonlyMap<string, unknown>, key: string): string;
@@ -0,0 +1,8 @@
1
+ export function nextProtocolKey(contracts, key) {
2
+ if (!contracts.has(key))
3
+ return key;
4
+ let index = 2;
5
+ while (contracts.has(`${key}#protocol-${index}`))
6
+ index++;
7
+ return `${key}#protocol-${index}`;
8
+ }
@@ -0,0 +1,5 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ import { type DiscoverApiResponseContractsOptions } from './response-contract-lenient.mts';
3
+ import { type HandlerBindings } from './response-contract-route-analysis.mts';
4
+ import type { BackendResponseContract } from './response-contract-types.mts';
5
+ export declare function discoverProtocolContracts(sourceFiles: readonly ts.SourceFile[], checker: ts.TypeChecker, bindings: HandlerBindings, contracts: Map<string, BackendResponseContract>, options: DiscoverApiResponseContractsOptions | undefined, requestedKeys?: ReadonlySet<string>): Set<ts.CallExpression>;