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.
- package/README.md +219 -1
- package/dist/api-contract-discovery/index.d.mts +4 -1
- package/dist/api-contract-discovery/index.mjs +1 -0
- package/dist/api-contract-discovery/protocol-analysis-cache.d.mts +21 -0
- package/dist/api-contract-discovery/protocol-analysis-cache.mjs +7 -0
- package/dist/api-contract-discovery/protocol-callback-invocation.d.mts +2 -1
- package/dist/api-contract-discovery/protocol-callback-invocation.mjs +12 -4
- package/dist/api-contract-discovery/protocol-callback-values.d.mts +2 -1
- package/dist/api-contract-discovery/protocol-callback-values.mjs +3 -3
- package/dist/api-contract-discovery/protocol-execution-path.d.mts +2 -1
- package/dist/api-contract-discovery/protocol-execution-path.mjs +2 -2
- package/dist/api-contract-discovery/protocol-http-association.mjs +8 -0
- package/dist/api-contract-discovery/protocol-http-context-callers.d.mts +2 -1
- package/dist/api-contract-discovery/protocol-http-context-callers.mjs +13 -7
- package/dist/api-contract-discovery/protocol-http-context-escapes.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-http-context-escapes.mjs +95 -0
- package/dist/api-contract-discovery/protocol-http-context.d.mts +1 -2
- package/dist/api-contract-discovery/protocol-http-context.mjs +6 -29
- package/dist/api-contract-discovery/protocol-http-method-access.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-http-method-access.mjs +34 -0
- package/dist/api-contract-discovery/protocol-http-reflect.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-http-reflect.mjs +45 -0
- package/dist/api-contract-discovery/protocol-http-response-mutations.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-http-response-mutations.mjs +31 -0
- package/dist/api-contract-discovery/protocol-platform-callbacks.d.mts +2 -0
- package/dist/api-contract-discovery/protocol-platform-callbacks.mjs +4 -0
- package/dist/api-contract-discovery/protocol-sse-raw-writes.mjs +36 -7
- package/dist/api-contract-discovery/protocol-sse-write-helpers.d.mts +1 -0
- package/dist/api-contract-discovery/protocol-sse-write-helpers.mjs +21 -5
- package/dist/api-contract-discovery/protocol-sse-write-mutations.d.mts +1 -1
- package/dist/api-contract-discovery/protocol-sse-write-mutations.mjs +8 -2
- package/dist/api-contract-discovery/query-contract-extraction.mjs +20 -3
- package/dist/api-contract-discovery/query-contract-literals.d.mts +5 -0
- package/dist/api-contract-discovery/query-contract-literals.mjs +29 -0
- package/dist/api-contract-discovery/registered-route-binding-writes.d.mts +2 -1
- package/dist/api-contract-discovery/registered-route-binding-writes.mjs +20 -11
- package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +7 -1
- package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +27 -26
- package/dist/api-contract-discovery/registered-route-handler-helpers.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-handler-helpers.mjs +16 -0
- package/dist/api-contract-discovery/request-validation-callbacks.d.mts +9 -0
- package/dist/api-contract-discovery/request-validation-callbacks.mjs +27 -0
- package/dist/api-contract-discovery/request-validation-checker.d.mts +7 -0
- package/dist/api-contract-discovery/request-validation-checker.mjs +25 -0
- package/dist/api-contract-discovery/request-validation-conditional.d.mts +6 -0
- package/dist/api-contract-discovery/request-validation-conditional.mjs +31 -0
- package/dist/api-contract-discovery/request-validation-factory-calls.d.mts +11 -0
- package/dist/api-contract-discovery/request-validation-factory-calls.mjs +23 -0
- package/dist/api-contract-discovery/request-validation-facts.d.mts +16 -0
- package/dist/api-contract-discovery/request-validation-facts.mjs +64 -0
- package/dist/api-contract-discovery/request-validation-follow.d.mts +24 -0
- package/dist/api-contract-discovery/request-validation-follow.mjs +38 -0
- package/dist/api-contract-discovery/request-validation-handler-state.d.mts +8 -0
- package/dist/api-contract-discovery/request-validation-handler-state.mjs +35 -0
- package/dist/api-contract-discovery/request-validation-input.d.mts +12 -0
- package/dist/api-contract-discovery/request-validation-input.mjs +43 -0
- package/dist/api-contract-discovery/request-validation-keys.d.mts +25 -0
- package/dist/api-contract-discovery/request-validation-keys.mjs +119 -0
- package/dist/api-contract-discovery/request-validation-match.d.mts +11 -0
- package/dist/api-contract-discovery/request-validation-match.mjs +47 -0
- package/dist/api-contract-discovery/request-validation-mutation.d.mts +7 -0
- package/dist/api-contract-discovery/request-validation-mutation.mjs +80 -0
- package/dist/api-contract-discovery/request-validation-origin.d.mts +20 -0
- package/dist/api-contract-discovery/request-validation-origin.mjs +71 -0
- package/dist/api-contract-discovery/request-validation-reads.d.mts +13 -0
- package/dist/api-contract-discovery/request-validation-reads.mjs +88 -0
- package/dist/api-contract-discovery/request-validation-registration.d.mts +9 -0
- package/dist/api-contract-discovery/request-validation-registration.mjs +32 -0
- package/dist/api-contract-discovery/request-validation-report.d.mts +3 -0
- package/dist/api-contract-discovery/request-validation-report.mjs +21 -0
- package/dist/api-contract-discovery/request-validation-request-origin.d.mts +5 -0
- package/dist/api-contract-discovery/request-validation-request-origin.mjs +13 -0
- package/dist/api-contract-discovery/request-validation-select.d.mts +9 -0
- package/dist/api-contract-discovery/request-validation-select.mjs +36 -0
- package/dist/api-contract-discovery/request-validation-sites.d.mts +9 -0
- package/dist/api-contract-discovery/request-validation-sites.mjs +77 -0
- package/dist/api-contract-discovery/request-validation-state-key.d.mts +13 -0
- package/dist/api-contract-discovery/request-validation-state-key.mjs +30 -0
- package/dist/api-contract-discovery/request-validation-trace-helpers.d.mts +14 -0
- package/dist/api-contract-discovery/request-validation-trace-helpers.mjs +80 -0
- package/dist/api-contract-discovery/request-validation-trace-select.d.mts +23 -0
- package/dist/api-contract-discovery/request-validation-trace-select.mjs +45 -0
- package/dist/api-contract-discovery/request-validation-trace.d.mts +8 -0
- package/dist/api-contract-discovery/request-validation-trace.mjs +130 -0
- package/dist/api-contract-discovery/request-validation-types.d.mts +84 -0
- package/dist/api-contract-discovery/request-validation-types.mjs +1 -0
- package/dist/api-contract-discovery/request-validation-walker.d.mts +26 -0
- package/dist/api-contract-discovery/request-validation-walker.mjs +143 -0
- package/dist/api-contract-discovery/request-validation.test-helpers.d.mts +39 -0
- package/dist/api-contract-discovery/request-validation.test-helpers.mjs +108 -0
- package/dist/api-contract-discovery/response-contract-attribution-facts.d.mts +2 -0
- package/dist/api-contract-discovery/response-contract-attribution-facts.mjs +18 -0
- package/dist/api-contract-discovery/response-contract-attribution.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-attribution.mjs +23 -0
- package/dist/api-contract-discovery/response-contract-call-classification.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-call-classification.mjs +37 -0
- package/dist/api-contract-discovery/response-contract-error-branch.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-error-branch.mjs +109 -6
- package/dist/api-contract-discovery/response-contract-handler-bindings.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-handler-bindings.mjs +126 -0
- package/dist/api-contract-discovery/response-contract-handler-calls.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-handler-calls.mjs +81 -0
- package/dist/api-contract-discovery/response-contract-implicit-variants.d.mts +2 -0
- package/dist/api-contract-discovery/response-contract-implicit-variants.mjs +6 -0
- package/dist/api-contract-discovery/response-contract-implicit.d.mts +2 -1
- package/dist/api-contract-discovery/response-contract-implicit.mjs +9 -17
- package/dist/api-contract-discovery/response-contract-lenient.d.mts +18 -0
- package/dist/api-contract-discovery/response-contract-object-symbols.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-object-symbols.mjs +80 -0
- package/dist/api-contract-discovery/response-contract-property-mutations.d.mts +5 -0
- package/dist/api-contract-discovery/response-contract-property-mutations.mjs +84 -0
- package/dist/api-contract-discovery/response-contract-registration.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-registration.mjs +7 -3
- package/dist/api-contract-discovery/response-contract-registry.mjs +43 -8
- package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +3 -4
- package/dist/api-contract-discovery/response-contract-route-analysis.mjs +4 -101
- package/dist/api-contract-discovery/response-contract-route-syntax.d.mts +7 -0
- package/dist/api-contract-discovery/response-contract-route-syntax.mjs +41 -0
- package/dist/api-contract-discovery/response-contract-symbols.d.mts +5 -0
- package/dist/api-contract-discovery/response-contract-symbols.mjs +82 -0
- package/dist/basic-auth-doc-sync/index.d.mts +3 -0
- package/dist/basic-auth-doc-sync/index.mjs +2 -0
- package/dist/basic-auth-doc-sync/methods.d.mts +1 -0
- package/dist/basic-auth-doc-sync/methods.mjs +4 -0
- package/dist/basic-auth-doc-sync/runbook.d.mts +5 -0
- package/dist/basic-auth-doc-sync/runbook.mjs +59 -0
- package/dist/basic-auth-doc-sync/source.d.mts +2 -0
- package/dist/basic-auth-doc-sync/source.mjs +71 -0
- package/dist/compiler-build/index.d.mts +2 -0
- package/dist/compiler-build/index.mjs +1 -0
- package/dist/compiler-build/program-configuration.d.mts +9 -0
- package/dist/compiler-build/program-configuration.mjs +28 -0
- package/dist/compiler-build/program-types.d.mts +29 -0
- package/dist/compiler-build/program-types.mjs +1 -0
- package/dist/compiler-build/program.d.mts +7 -0
- package/dist/compiler-build/program.mjs +78 -0
- package/dist/contract-schema/index.d.mts +4 -0
- package/dist/contract-schema/index.mjs +2 -0
- package/dist/contract-schema/type-query-api.d.mts +44 -0
- package/dist/contract-schema/type-query-api.mjs +10 -0
- package/dist/contract-schema/type-query-call.d.mts +16 -0
- package/dist/contract-schema/type-query-call.mjs +52 -0
- package/dist/contract-schema/type-query-export.d.mts +10 -0
- package/dist/contract-schema/type-query-export.mjs +91 -0
- package/dist/contract-schema/type-query.d.mts +22 -0
- package/dist/contract-schema/type-query.mjs +39 -0
- package/dist/finite-enum-ripple/ast.d.mts +9 -0
- package/dist/finite-enum-ripple/ast.mjs +94 -0
- package/dist/finite-enum-ripple/check.d.mts +3 -0
- package/dist/finite-enum-ripple/check.mjs +62 -0
- package/dist/finite-enum-ripple/compare.d.mts +20 -0
- package/dist/finite-enum-ripple/compare.mjs +90 -0
- package/dist/finite-enum-ripple/component-paths.d.mts +8 -0
- package/dist/finite-enum-ripple/component-paths.mjs +30 -0
- package/dist/finite-enum-ripple/configured-object-mutations.d.mts +1 -0
- package/dist/finite-enum-ripple/configured-object-mutations.mjs +78 -0
- package/dist/finite-enum-ripple/create-page-jsx.d.mts +3 -0
- package/dist/finite-enum-ripple/create-page-jsx.mjs +28 -0
- package/dist/finite-enum-ripple/create-page-literals.d.mts +2 -0
- package/dist/finite-enum-ripple/create-page-literals.mjs +83 -0
- package/dist/finite-enum-ripple/create-page-object-visitor.d.mts +2 -0
- package/dist/finite-enum-ripple/create-page-object-visitor.mjs +57 -0
- package/dist/finite-enum-ripple/create-page-properties.d.mts +4 -0
- package/dist/finite-enum-ripple/create-page-properties.mjs +35 -0
- package/dist/finite-enum-ripple/index.d.mts +2 -0
- package/dist/finite-enum-ripple/index.mjs +1 -0
- package/dist/finite-enum-ripple/model.d.mts +74 -0
- package/dist/finite-enum-ripple/model.mjs +1 -0
- package/dist/finite-enum-ripple/parser-support.d.mts +11 -0
- package/dist/finite-enum-ripple/parser-support.mjs +86 -0
- package/dist/finite-enum-ripple/parsers.d.mts +32 -0
- package/dist/finite-enum-ripple/parsers.mjs +113 -0
- package/dist/finite-enum-ripple/pattern-captures.d.mts +2 -0
- package/dist/finite-enum-ripple/pattern-captures.mjs +45 -0
- package/dist/finite-enum-ripple/structured-route-check.d.mts +3 -0
- package/dist/finite-enum-ripple/structured-route-check.mjs +94 -0
- package/dist/finite-enum-ripple/types.d.mts +2 -0
- package/dist/finite-enum-ripple/types.mjs +60 -0
- package/dist/finite-enum-ripple/union-factory-check.d.mts +2 -0
- package/dist/finite-enum-ripple/union-factory-check.mjs +34 -0
- package/dist/finite-enum-ripple/union-types.d.mts +2 -0
- package/dist/finite-enum-ripple/union-types.mjs +129 -0
- package/dist/markdown/content-facts.d.mts +6 -0
- package/dist/markdown/content-facts.mjs +18 -0
- package/dist/markdown/html-blocks.d.mts +5 -0
- package/dist/markdown/html-blocks.mjs +51 -0
- package/dist/markdown/index.d.mts +4 -1
- package/dist/markdown/index.mjs +3 -0
- package/dist/markdown/tasks.d.mts +2 -0
- package/dist/markdown/tasks.mjs +10 -0
- package/dist/markdown/types.d.mts +27 -0
- package/dist/openapi-document/build-openapi-query.mjs +1 -1
- package/dist/openapi-document/openapi-types.d.mts +1 -1
- package/dist/openapi-document/query-types.d.mts +8 -2
- package/dist/post-publication-inventory/index.d.mts +5 -0
- package/dist/post-publication-inventory/index.mjs +2 -0
- package/dist/post-publication-inventory/readers/sql-executors.d.mts +3 -0
- package/dist/post-publication-inventory/readers/sql-executors.mjs +32 -0
- package/dist/post-publication-inventory/readers/sql-source.d.mts +7 -0
- package/dist/post-publication-inventory/readers/sql-source.mjs +87 -0
- package/dist/post-publication-inventory/readers/sql-template.d.mts +12 -0
- package/dist/post-publication-inventory/readers/sql-template.mjs +119 -0
- package/dist/post-publication-inventory/writers/source.d.mts +9 -0
- package/dist/post-publication-inventory/writers/source.mjs +128 -0
- package/dist/post-publication-inventory/writers/types.d.mts +19 -0
- package/dist/post-publication-inventory/writers/types.mjs +1 -0
- package/docs/basic-auth-doc-sync.md +37 -0
- 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,
|
|
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;
|
|
@@ -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
|
-
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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. */
|