@stsepelin/checktrail 0.1.0-alpha.1

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 (385) hide show
  1. package/CONTRIBUTING.md +42 -0
  2. package/LICENSE +21 -0
  3. package/README.md +355 -0
  4. package/SECURITY.md +46 -0
  5. package/dist/src/actionlint-evidence.d.ts +2 -0
  6. package/dist/src/actionlint-evidence.js +166 -0
  7. package/dist/src/actionlint-inputs.d.ts +5 -0
  8. package/dist/src/actionlint-inputs.js +123 -0
  9. package/dist/src/actionlint-runner.d.ts +1 -0
  10. package/dist/src/actionlint-runner.js +122 -0
  11. package/dist/src/actionlint.d.ts +19 -0
  12. package/dist/src/actionlint.js +80 -0
  13. package/dist/src/adapters.d.ts +49 -0
  14. package/dist/src/adapters.js +430 -0
  15. package/dist/src/architecture.d.ts +107 -0
  16. package/dist/src/architecture.js +230 -0
  17. package/dist/src/clang-evidence.d.ts +2 -0
  18. package/dist/src/clang-evidence.js +194 -0
  19. package/dist/src/clang-protocol.d.ts +3 -0
  20. package/dist/src/clang-protocol.js +25 -0
  21. package/dist/src/clang-runner.d.ts +1 -0
  22. package/dist/src/clang-runner.js +159 -0
  23. package/dist/src/clang.d.ts +44 -0
  24. package/dist/src/clang.js +197 -0
  25. package/dist/src/cli.d.ts +2 -0
  26. package/dist/src/cli.js +338 -0
  27. package/dist/src/config.d.ts +33 -0
  28. package/dist/src/config.js +140 -0
  29. package/dist/src/contract-schema.d.ts +85 -0
  30. package/dist/src/contract-schema.js +56 -0
  31. package/dist/src/contract-worker.d.ts +1 -0
  32. package/dist/src/contract-worker.js +123 -0
  33. package/dist/src/contracts.d.ts +22 -0
  34. package/dist/src/contracts.js +123 -0
  35. package/dist/src/django-evidence.d.ts +2 -0
  36. package/dist/src/django-evidence.js +100 -0
  37. package/dist/src/django-runner.d.ts +1 -0
  38. package/dist/src/django-runner.js +80 -0
  39. package/dist/src/django.d.ts +9 -0
  40. package/dist/src/django.js +50 -0
  41. package/dist/src/dotnet-compiler.d.ts +1 -0
  42. package/dist/src/dotnet-compiler.js +87 -0
  43. package/dist/src/dotnet-evidence.d.ts +2 -0
  44. package/dist/src/dotnet-evidence.js +127 -0
  45. package/dist/src/dotnet-runner.d.ts +1 -0
  46. package/dist/src/dotnet-runner.js +167 -0
  47. package/dist/src/dotnet.d.ts +65 -0
  48. package/dist/src/dotnet.js +120 -0
  49. package/dist/src/engine.d.ts +19 -0
  50. package/dist/src/engine.js +239 -0
  51. package/dist/src/environment.d.ts +6 -0
  52. package/dist/src/environment.js +36 -0
  53. package/dist/src/eslint-evidence.d.ts +2 -0
  54. package/dist/src/eslint-evidence.js +98 -0
  55. package/dist/src/eslint-runner.d.ts +1 -0
  56. package/dist/src/eslint-runner.js +61 -0
  57. package/dist/src/eslint.d.ts +2 -0
  58. package/dist/src/eslint.js +40 -0
  59. package/dist/src/evidence.d.ts +2 -0
  60. package/dist/src/evidence.js +313 -0
  61. package/dist/src/external-adapter.d.ts +164 -0
  62. package/dist/src/external-adapter.js +265 -0
  63. package/dist/src/external-evidence.d.ts +2 -0
  64. package/dist/src/external-evidence.js +127 -0
  65. package/dist/src/external-runner.d.ts +1 -0
  66. package/dist/src/external-runner.js +122 -0
  67. package/dist/src/fastapi-evidence.d.ts +2 -0
  68. package/dist/src/fastapi-evidence.js +126 -0
  69. package/dist/src/fastapi-runner.d.ts +1 -0
  70. package/dist/src/fastapi-runner.js +84 -0
  71. package/dist/src/fastapi.d.ts +10 -0
  72. package/dist/src/fastapi.js +52 -0
  73. package/dist/src/fetch-pack.d.ts +16 -0
  74. package/dist/src/fetch-pack.js +154 -0
  75. package/dist/src/finding-policy-schema.d.ts +146 -0
  76. package/dist/src/finding-policy-schema.js +70 -0
  77. package/dist/src/finding-policy.d.ts +62 -0
  78. package/dist/src/finding-policy.js +206 -0
  79. package/dist/src/git-selection.d.ts +3 -0
  80. package/dist/src/git-selection.js +229 -0
  81. package/dist/src/go-directives.d.ts +1 -0
  82. package/dist/src/go-directives.js +32 -0
  83. package/dist/src/go-scope.d.ts +11 -0
  84. package/dist/src/go-scope.js +122 -0
  85. package/dist/src/golangci-evidence.d.ts +2 -0
  86. package/dist/src/golangci-evidence.js +92 -0
  87. package/dist/src/golangci-runner.d.ts +1 -0
  88. package/dist/src/golangci-runner.js +124 -0
  89. package/dist/src/golangci.d.ts +2 -0
  90. package/dist/src/golangci.js +35 -0
  91. package/dist/src/guidance.d.ts +98 -0
  92. package/dist/src/guidance.js +247 -0
  93. package/dist/src/index.d.ts +31 -0
  94. package/dist/src/index.js +16 -0
  95. package/dist/src/inventory.d.ts +4 -0
  96. package/dist/src/inventory.js +94 -0
  97. package/dist/src/java-compiler.d.ts +1 -0
  98. package/dist/src/java-compiler.js +110 -0
  99. package/dist/src/java-evidence.d.ts +2 -0
  100. package/dist/src/java-evidence.js +114 -0
  101. package/dist/src/java-runner.d.ts +1 -0
  102. package/dist/src/java-runner.js +79 -0
  103. package/dist/src/java.d.ts +25 -0
  104. package/dist/src/java.js +102 -0
  105. package/dist/src/jest-evidence.d.ts +2 -0
  106. package/dist/src/jest-evidence.js +117 -0
  107. package/dist/src/jest-runner.d.ts +1 -0
  108. package/dist/src/jest-runner.js +46 -0
  109. package/dist/src/jest.d.ts +2 -0
  110. package/dist/src/jest.js +36 -0
  111. package/dist/src/junit.d.ts +18 -0
  112. package/dist/src/junit.js +157 -0
  113. package/dist/src/laravel-evidence.d.ts +2 -0
  114. package/dist/src/laravel-evidence.js +175 -0
  115. package/dist/src/laravel-runner.d.ts +1 -0
  116. package/dist/src/laravel-runner.js +168 -0
  117. package/dist/src/laravel.d.ts +8 -0
  118. package/dist/src/laravel.js +50 -0
  119. package/dist/src/local-tool.d.ts +1 -0
  120. package/dist/src/local-tool.js +19 -0
  121. package/dist/src/mcp.d.ts +14 -0
  122. package/dist/src/mcp.js +362 -0
  123. package/dist/src/mutation.d.ts +118 -0
  124. package/dist/src/mutation.js +370 -0
  125. package/dist/src/mypy-evidence.d.ts +2 -0
  126. package/dist/src/mypy-evidence.js +63 -0
  127. package/dist/src/mypy.d.ts +2 -0
  128. package/dist/src/mypy.js +59 -0
  129. package/dist/src/node-reporter.d.ts +1 -0
  130. package/dist/src/node-reporter.js +28 -0
  131. package/dist/src/nuxt-evidence.d.ts +2 -0
  132. package/dist/src/nuxt-evidence.js +160 -0
  133. package/dist/src/nuxt-protocol.d.ts +52 -0
  134. package/dist/src/nuxt-protocol.js +29 -0
  135. package/dist/src/nuxt-runner.d.ts +1 -0
  136. package/dist/src/nuxt-runner.js +165 -0
  137. package/dist/src/nuxt.d.ts +24 -0
  138. package/dist/src/nuxt.js +105 -0
  139. package/dist/src/output.d.ts +3 -0
  140. package/dist/src/output.js +48 -0
  141. package/dist/src/phpstan-evidence.d.ts +2 -0
  142. package/dist/src/phpstan-evidence.js +81 -0
  143. package/dist/src/phpstan.d.ts +2 -0
  144. package/dist/src/phpstan.js +51 -0
  145. package/dist/src/phpunit-evidence.d.ts +2 -0
  146. package/dist/src/phpunit-evidence.js +53 -0
  147. package/dist/src/phpunit.d.ts +2 -0
  148. package/dist/src/phpunit.js +58 -0
  149. package/dist/src/pint-evidence.d.ts +2 -0
  150. package/dist/src/pint-evidence.js +79 -0
  151. package/dist/src/pint-runner.d.ts +1 -0
  152. package/dist/src/pint-runner.js +53 -0
  153. package/dist/src/pint.d.ts +2 -0
  154. package/dist/src/pint.js +37 -0
  155. package/dist/src/playwright-evidence.d.ts +2 -0
  156. package/dist/src/playwright-evidence.js +127 -0
  157. package/dist/src/playwright-reporter.d.ts +10 -0
  158. package/dist/src/playwright-reporter.js +37 -0
  159. package/dist/src/playwright-runner.d.ts +1 -0
  160. package/dist/src/playwright-runner.js +61 -0
  161. package/dist/src/playwright.d.ts +2 -0
  162. package/dist/src/playwright.js +35 -0
  163. package/dist/src/policy-pack.d.ts +21 -0
  164. package/dist/src/policy-pack.js +41 -0
  165. package/dist/src/pytest-evidence.d.ts +2 -0
  166. package/dist/src/pytest-evidence.js +116 -0
  167. package/dist/src/pytest-runner.d.ts +1 -0
  168. package/dist/src/pytest-runner.js +48 -0
  169. package/dist/src/pytest.d.ts +2 -0
  170. package/dist/src/pytest.js +25 -0
  171. package/dist/src/report-validation.d.ts +129 -0
  172. package/dist/src/report-validation.js +27 -0
  173. package/dist/src/review.d.ts +270 -0
  174. package/dist/src/review.js +349 -0
  175. package/dist/src/ruby.d.ts +2 -0
  176. package/dist/src/ruby.js +25 -0
  177. package/dist/src/ruff-evidence.d.ts +2 -0
  178. package/dist/src/ruff-evidence.js +99 -0
  179. package/dist/src/ruff.d.ts +2 -0
  180. package/dist/src/ruff.js +40 -0
  181. package/dist/src/runner.d.ts +8 -0
  182. package/dist/src/runner.js +117 -0
  183. package/dist/src/runtime-inventory.d.ts +112 -0
  184. package/dist/src/runtime-inventory.js +200 -0
  185. package/dist/src/rust-dep-info.d.ts +1 -0
  186. package/dist/src/rust-dep-info.js +46 -0
  187. package/dist/src/rust-evidence.d.ts +2 -0
  188. package/dist/src/rust-evidence.js +157 -0
  189. package/dist/src/rust-runner.d.ts +1 -0
  190. package/dist/src/rust-runner.js +161 -0
  191. package/dist/src/rust.d.ts +7 -0
  192. package/dist/src/rust.js +39 -0
  193. package/dist/src/sarif.d.ts +86 -0
  194. package/dist/src/sarif.js +113 -0
  195. package/dist/src/schemas.d.ts +393 -0
  196. package/dist/src/schemas.js +222 -0
  197. package/dist/src/staticcheck-evidence.d.ts +2 -0
  198. package/dist/src/staticcheck-evidence.js +75 -0
  199. package/dist/src/swift.d.ts +2 -0
  200. package/dist/src/swift.js +20 -0
  201. package/dist/src/task-store.d.ts +37 -0
  202. package/dist/src/task-store.js +349 -0
  203. package/dist/src/tool-versions.d.ts +3 -0
  204. package/dist/src/tool-versions.js +260 -0
  205. package/dist/src/types.d.ts +157 -0
  206. package/dist/src/types.js +37 -0
  207. package/dist/src/typescript-build-evidence.d.ts +2 -0
  208. package/dist/src/typescript-build-evidence.js +86 -0
  209. package/dist/src/typescript-build-runner.d.ts +1 -0
  210. package/dist/src/typescript-build-runner.js +132 -0
  211. package/dist/src/typescript-build.d.ts +2 -0
  212. package/dist/src/typescript-build.js +41 -0
  213. package/dist/src/typescript.d.ts +2 -0
  214. package/dist/src/typescript.js +54 -0
  215. package/dist/src/validation-task-worker.d.ts +1 -0
  216. package/dist/src/validation-task-worker.js +205 -0
  217. package/dist/src/validation-tasks-protocol.d.ts +69 -0
  218. package/dist/src/validation-tasks-protocol.js +57 -0
  219. package/dist/src/validation-tasks.d.ts +11 -0
  220. package/dist/src/validation-tasks.js +144 -0
  221. package/dist/src/vitest-evidence.d.ts +2 -0
  222. package/dist/src/vitest-evidence.js +112 -0
  223. package/dist/src/vitest-runner.d.ts +1 -0
  224. package/dist/src/vitest-runner.js +48 -0
  225. package/dist/src/vitest.d.ts +2 -0
  226. package/dist/src/vitest.js +36 -0
  227. package/dist/src/vue-router-capture.d.ts +42 -0
  228. package/dist/src/vue-router-capture.js +87 -0
  229. package/dist/src/vue-router-evidence.d.ts +2 -0
  230. package/dist/src/vue-router-evidence.js +126 -0
  231. package/dist/src/vue-router-protocol.d.ts +63 -0
  232. package/dist/src/vue-router-protocol.js +44 -0
  233. package/dist/src/vue-router-runner.d.ts +1 -0
  234. package/dist/src/vue-router-runner.js +88 -0
  235. package/dist/src/vue-router.d.ts +23 -0
  236. package/dist/src/vue-router.js +108 -0
  237. package/dist/src/vue-tsc-runner.d.ts +1 -0
  238. package/dist/src/vue-tsc-runner.js +24 -0
  239. package/dist/src/workspace.d.ts +7 -0
  240. package/dist/src/workspace.js +53 -0
  241. package/docs/ACCEPTANCE.md +56 -0
  242. package/docs/ACTIONLINT.md +112 -0
  243. package/docs/ARCHITECTURE-POLICY.md +82 -0
  244. package/docs/ARCHITECTURE.md +122 -0
  245. package/docs/CLANG.md +113 -0
  246. package/docs/CLIENTS.md +109 -0
  247. package/docs/CONTRACTS.md +73 -0
  248. package/docs/DEPENDENCIES.md +42 -0
  249. package/docs/DJANGO.md +70 -0
  250. package/docs/DOTNET.md +114 -0
  251. package/docs/ENVIRONMENTS.md +52 -0
  252. package/docs/ESLINT.md +68 -0
  253. package/docs/EVALUATION.md +124 -0
  254. package/docs/EXECUTION.md +250 -0
  255. package/docs/EXTERNAL-ADAPTERS.md +170 -0
  256. package/docs/EXTERNAL-EVALUATION.md +155 -0
  257. package/docs/EXTERNAL-RUFF-EVALUATION.md +85 -0
  258. package/docs/FASTAPI.md +73 -0
  259. package/docs/FINDING-POLICY.md +67 -0
  260. package/docs/GO-RACE.md +19 -0
  261. package/docs/GO-SCOPE.md +63 -0
  262. package/docs/GOLANGCI-LINT.md +64 -0
  263. package/docs/GUIDANCE.md +46 -0
  264. package/docs/IMPACT-MEASUREMENT.md +97 -0
  265. package/docs/INSTALLATION.md +102 -0
  266. package/docs/JAVA.md +79 -0
  267. package/docs/JEST.md +36 -0
  268. package/docs/JUNIT.md +31 -0
  269. package/docs/LANGUAGES.md +113 -0
  270. package/docs/LARAVEL.md +111 -0
  271. package/docs/MCP-COMPATIBILITY.md +68 -0
  272. package/docs/MUTATIONS.md +93 -0
  273. package/docs/MYPY.md +37 -0
  274. package/docs/NATIVE-CI.md +96 -0
  275. package/docs/NUXT.md +123 -0
  276. package/docs/PACK-DISTRIBUTION.md +98 -0
  277. package/docs/PERFORMANCE.md +71 -0
  278. package/docs/PEST.md +36 -0
  279. package/docs/PHPSTAN.md +48 -0
  280. package/docs/PHPUNIT.md +32 -0
  281. package/docs/PINT.md +41 -0
  282. package/docs/PLAN.md +265 -0
  283. package/docs/PLAYWRIGHT.md +73 -0
  284. package/docs/POLICY-PACKS.md +106 -0
  285. package/docs/PRIOR-WORKFLOW-EVALUATION.md +116 -0
  286. package/docs/PUBLICATION.md +28 -0
  287. package/docs/PYTEST.md +51 -0
  288. package/docs/RELEASE.md +115 -0
  289. package/docs/RENAMING.md +55 -0
  290. package/docs/REVIEW-EXCHANGE.md +136 -0
  291. package/docs/RUBY.md +50 -0
  292. package/docs/RUFF.md +39 -0
  293. package/docs/RUNTIME-INVENTORY.md +56 -0
  294. package/docs/RUST.md +74 -0
  295. package/docs/SARIF.md +64 -0
  296. package/docs/STATUS.md +530 -0
  297. package/docs/SWIFT.md +48 -0
  298. package/docs/TASK-STORAGE.md +141 -0
  299. package/docs/TOOL-IDENTITY.md +30 -0
  300. package/docs/TYPESCRIPT-BUILD.md +55 -0
  301. package/docs/VALIDATION-TASKS.md +119 -0
  302. package/docs/VITEST.md +36 -0
  303. package/docs/VUE-ROUTER.md +130 -0
  304. package/docs/VUE-TSC.md +26 -0
  305. package/docs/WORKSPACES.md +81 -0
  306. package/docs/licenses/nodable-entities-3.0.0.txt +21 -0
  307. package/docs/measurements/client-claude-before-schema-fix-darwin-arm64-node26.json +60 -0
  308. package/docs/measurements/client-claude-darwin-arm64-node26.json +55 -0
  309. package/docs/measurements/client-codex-darwin-arm64-node26.json +175 -0
  310. package/docs/measurements/evaluation-corpus-repo-verifier.json +332 -0
  311. package/docs/measurements/evaluation-darwin-arm64-node26.json +1236 -0
  312. package/docs/measurements/evaluation-linux-arm64-node22.json +1236 -0
  313. package/docs/measurements/external-eslint-darwin-arm64-node26.json +11866 -0
  314. package/docs/measurements/external-eslint-linux-arm64-node22.json +11866 -0
  315. package/docs/measurements/external-eslint-preservation.json +1527 -0
  316. package/docs/measurements/external-ruff-darwin-arm64-node26.json +1031 -0
  317. package/docs/measurements/external-ruff-linux-arm64-node22.json +1031 -0
  318. package/docs/measurements/hosted-ci-52ba415.json +1005 -0
  319. package/docs/measurements/impact-darwin-arm64-node26.json +6685 -0
  320. package/docs/measurements/impact-linux-arm64-node22.json +6690 -0
  321. package/docs/measurements/performance-darwin-arm64-node26.json +845 -0
  322. package/docs/measurements/performance-linux-arm64-node22.json +845 -0
  323. package/docs/measurements/required-native-profiles.json +500 -0
  324. package/package.json +72 -0
  325. package/packs/actionlint.json +8 -0
  326. package/packs/cpp.json +8 -0
  327. package/packs/dotnet.json +8 -0
  328. package/packs/go.json +7 -0
  329. package/packs/java.json +8 -0
  330. package/packs/javascript-node.json +7 -0
  331. package/packs/nuxt.json +7 -0
  332. package/packs/php-pest.json +7 -0
  333. package/packs/php-phpunit.json +7 -0
  334. package/packs/python-pytest.json +7 -0
  335. package/packs/ruby.json +7 -0
  336. package/packs/rust.json +7 -0
  337. package/packs/swift.json +7 -0
  338. package/packs/vue-router.json +7 -0
  339. package/packs/vue.json +11 -0
  340. package/schemas/actionlint-config.schema.json +19 -0
  341. package/schemas/architecture-policy.schema.json +51 -0
  342. package/schemas/architecture-report.schema.json +123 -0
  343. package/schemas/architecture-summary.schema.json +82 -0
  344. package/schemas/clang-database.schema.json +23 -0
  345. package/schemas/config.schema.json +69 -0
  346. package/schemas/contract-bundle.schema.json +78 -0
  347. package/schemas/contract-report.schema.json +114 -0
  348. package/schemas/contract-summary.schema.json +72 -0
  349. package/schemas/dependency-graph.schema.json +65 -0
  350. package/schemas/django-config.schema.json +16 -0
  351. package/schemas/dotnet-config.schema.json +60 -0
  352. package/schemas/external-manifest.schema.json +97 -0
  353. package/schemas/external-reference.schema.json +10 -0
  354. package/schemas/external-request.schema.json +56 -0
  355. package/schemas/external-result.schema.json +111 -0
  356. package/schemas/fastapi-config.schema.json +27 -0
  357. package/schemas/finding-baseline.schema.json +92 -0
  358. package/schemas/finding-comparison-summary.schema.json +95 -0
  359. package/schemas/finding-comparison.schema.json +121 -0
  360. package/schemas/guidance-context.schema.json +34 -0
  361. package/schemas/guidance-report.schema.json +176 -0
  362. package/schemas/guidance-summary.schema.json +68 -0
  363. package/schemas/java-config.schema.json +24 -0
  364. package/schemas/junit.schema.json +69 -0
  365. package/schemas/laravel-config.schema.json +11 -0
  366. package/schemas/mutation-recipe.schema.json +31 -0
  367. package/schemas/mutation-report.schema.json +238 -0
  368. package/schemas/mutation-summary.schema.json +98 -0
  369. package/schemas/nuxt-config.schema.json +51 -0
  370. package/schemas/plan-summary.schema.json +63 -0
  371. package/schemas/plan.schema.json +244 -0
  372. package/schemas/policy-pack.schema.json +41 -0
  373. package/schemas/report-summary.schema.json +100 -0
  374. package/schemas/report.schema.json +463 -0
  375. package/schemas/review-assessment.schema.json +133 -0
  376. package/schemas/review-context-summary.schema.json +31 -0
  377. package/schemas/review-context.schema.json +254 -0
  378. package/schemas/review-receipt-summary.schema.json +104 -0
  379. package/schemas/review-receipt.schema.json +271 -0
  380. package/schemas/review-selection.schema.json +30 -0
  381. package/schemas/runtime-comparison-summary.schema.json +68 -0
  382. package/schemas/runtime-comparison.schema.json +127 -0
  383. package/schemas/runtime-inventory.schema.json +99 -0
  384. package/schemas/vue-router-config.schema.json +67 -0
  385. package/server.json +47 -0
@@ -0,0 +1,84 @@
1
+ export const fastapiRunner = String.raw `
2
+ import asyncio, contextlib, importlib, json, sys
3
+ from datetime import datetime, timezone
4
+ from importlib.metadata import version, PackageNotFoundError
5
+
6
+ def identity(value):
7
+ module = getattr(value, '__module__', None)
8
+ name = getattr(value, '__qualname__', None)
9
+ if not isinstance(module, str) or not isinstance(name, str):
10
+ raise ValueError('Unsupported callable identity')
11
+ return module + '.' + name
12
+
13
+ async def capture(config):
14
+ from fastapi import FastAPI
15
+ from fastapi.routing import APIRoute, APIWebSocketRoute
16
+ from starlette.routing import Route, WebSocketRoute
17
+ app = getattr(importlib.import_module(config['module']), config['attribute'])
18
+ if not isinstance(app, FastAPI):
19
+ raise ValueError('Configured object is not a FastAPI application')
20
+ async with app.router.lifespan_context(app):
21
+ entries = []
22
+ indices = []
23
+ supported = 0
24
+ application = 0
25
+ for index, route in enumerate(app.routes):
26
+ if type(route) not in (APIRoute, APIWebSocketRoute, Route, WebSocketRoute):
27
+ continue
28
+ methods = sorted(route.methods) if isinstance(route, Route) else ['WEBSOCKET']
29
+ protocol = 'http' if isinstance(route, Route) else 'websocket'
30
+ if not methods:
31
+ continue
32
+ dependencies = []
33
+ if isinstance(route, (APIRoute, APIWebSocketRoute)):
34
+ application += 1
35
+ pending = list(route.dependant.dependencies)
36
+ visited = 0
37
+ while pending:
38
+ dependency = pending.pop(0)
39
+ visited += 1
40
+ if visited > 1000:
41
+ raise ValueError('Dependency inventory exceeds limit')
42
+ dependencies.append(identity(dependency.call))
43
+ pending[0:0] = dependency.dependencies
44
+ for method in methods:
45
+ indices.append(index)
46
+ key = 'HTTP ' + method + ' ' + route.path if protocol == 'http' else 'WEBSOCKET ' + route.path
47
+ entries.append({'key': key, 'attributes': {
48
+ 'protocol': protocol, 'method': method, 'path': route.path, 'handler': identity(route.endpoint),
49
+ 'name': route.name, 'dependencies': dependencies,
50
+ 'includeInSchema': bool(getattr(route, 'include_in_schema', False))
51
+ }})
52
+ supported += 1
53
+ if len(entries) > 20000:
54
+ raise ValueError('Route inventory exceeds limit')
55
+ return entries, indices, len(app.routes), supported, application
56
+
57
+ try:
58
+ try:
59
+ versions = {'fastapi': version('fastapi'), 'starlette': version('starlette')}
60
+ except PackageNotFoundError:
61
+ json.dump({'unavailable': 'fastapi-runtime', 'reason': 'missing-package'}, sys.stdout)
62
+ sys.exit(3)
63
+ if versions != {'fastapi': '0.141.1', 'starlette': '1.6.0'}:
64
+ json.dump({'unavailable': 'fastapi-runtime', 'reason': 'unsupported-version'}, sys.stdout)
65
+ sys.exit(3)
66
+ config = json.loads(sys.argv[1])
67
+ with contextlib.redirect_stdout(sys.stderr):
68
+ entries, indices, total, supported, application = asyncio.run(capture(config))
69
+ json.dump({
70
+ 'version': 1, 'versions': versions, 'totalRoutes': total, 'supportedRoutes': supported,
71
+ 'applicationRoutes': application,
72
+ 'entryRouteIndices': indices,
73
+ 'runtime': {
74
+ 'schemaVersion': 1, 'format': 'runtime-inventory',
75
+ 'producer': {'name': 'checktrail.fastapi-routes', 'version': '1.0.0'},
76
+ 'assembly': {'name': config['assembly'], 'environment': config['environment']},
77
+ 'sourceFingerprint': sys.argv[2], 'capturedAt': datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z'),
78
+ 'collections': [{'kind': 'routes', 'complete': supported == total, 'ordered': True, 'entries': entries}]
79
+ }
80
+ }, sys.stdout)
81
+ except Exception as error:
82
+ print(type(error).__name__ + ': ' + str(error), file=sys.stderr)
83
+ sys.exit(2)
84
+ `;
@@ -0,0 +1,10 @@
1
+ import { z } from "zod";
2
+ import type { Check, Inventory, Project } from "./types.js";
3
+ export declare const fastapiConfigSchema: z.ZodObject<{
4
+ schemaVersion: z.ZodLiteral<1>;
5
+ module: z.ZodString;
6
+ attribute: z.ZodString;
7
+ assembly: z.ZodString;
8
+ environment: z.ZodString;
9
+ }, z.core.$strict>;
10
+ export declare function fastapiCheck(source: Inventory, project: Project): Promise<Check>;
@@ -0,0 +1,52 @@
1
+ import path from "node:path";
2
+ import { z } from "zod";
3
+ import { readProjectFile } from "./inventory.js";
4
+ import { fastapiRunner } from "./fastapi-runner.js";
5
+ export const fastapiConfigSchema = z.strictObject({
6
+ schemaVersion: z.literal(1),
7
+ module: z
8
+ .string()
9
+ .regex(/^[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*$/)
10
+ .max(256),
11
+ attribute: z
12
+ .string()
13
+ .regex(/^[A-Za-z_]\w*$/)
14
+ .max(128),
15
+ assembly: z.string().min(1).max(256),
16
+ environment: z.string().min(1).max(256),
17
+ });
18
+ export async function fastapiCheck(source, project) {
19
+ const check = {
20
+ id: "python.fastapi-routes",
21
+ adapter: project.adapter,
22
+ project: project.path,
23
+ kind: "analysis",
24
+ parser: "fastapi-json",
25
+ scope: [],
26
+ commands: [],
27
+ reason: "Capture the configured FastAPI application's flat native route table after lifespan startup and reject exact duplicate HTTP/WebSocket routes.",
28
+ };
29
+ if (!project.files.includes("checktrail.fastapi.json")) {
30
+ check.unavailableReason =
31
+ "FastAPI route validation requires an explicit checktrail.fastapi.json application profile.";
32
+ return check;
33
+ }
34
+ const config = fastapiConfigSchema.parse(JSON.parse(await readProjectFile(source.root, path.posix.join(project.path, "checktrail.fastapi.json"))));
35
+ const modulePath = config.module.replaceAll(".", "/");
36
+ const candidates = [`${modulePath}.py`, `${modulePath}/__init__.py`].filter((file) => project.files.includes(file));
37
+ if (candidates.length !== 1) {
38
+ check.unavailableReason =
39
+ "FastAPI import entry must resolve to one inventoried local Python module or package.";
40
+ return check;
41
+ }
42
+ check.scope = candidates;
43
+ check.commands = [
44
+ {
45
+ executable: "python3",
46
+ args: ["-c", fastapiRunner, JSON.stringify(config), source.fingerprint],
47
+ cwd: project.path,
48
+ env: { PYTHONDONTWRITEBYTECODE: "1" },
49
+ },
50
+ ];
51
+ return check;
52
+ }
@@ -0,0 +1,16 @@
1
+ import { type PackReference } from "./policy-pack.js";
2
+ export interface FetchPackOptions {
3
+ url: string;
4
+ sha256: string;
5
+ output: string;
6
+ timeoutMs?: number;
7
+ signal?: AbortSignal;
8
+ }
9
+ export interface FetchedPack {
10
+ schemaVersion: 1;
11
+ id: string;
12
+ version: string;
13
+ bytes: number;
14
+ reference: PackReference;
15
+ }
16
+ export declare function fetchPolicyPack(root: string, input: FetchPackOptions): Promise<FetchedPack>;
@@ -0,0 +1,154 @@
1
+ import { createHash } from "node:crypto";
2
+ import { link, lstat, mkdtemp, open, realpath, rm } from "node:fs/promises";
3
+ import { request } from "node:https";
4
+ import path from "node:path";
5
+ import { z } from "zod";
6
+ import { withinRoot } from "./inventory.js";
7
+ import { parsePolicyPack } from "./policy-pack.js";
8
+ const optionsSchema = z.strictObject({
9
+ url: z.string().min(1).max(8192),
10
+ sha256: z.string().regex(/^[a-f0-9]{64}$/),
11
+ output: z.string().min(1).max(4096),
12
+ timeoutMs: z.number().int().min(1).max(120_000).default(30_000),
13
+ });
14
+ async function download(url, timeoutMs, signal) {
15
+ if (signal?.aborted)
16
+ throw new Error("Policy pack download cancelled");
17
+ return new Promise((resolve, reject) => {
18
+ const chunks = [];
19
+ let bytes = 0;
20
+ let settled = false;
21
+ const finish = (error) => {
22
+ if (settled)
23
+ return;
24
+ settled = true;
25
+ clearTimeout(timer);
26
+ signal?.removeEventListener("abort", cancel);
27
+ if (error) {
28
+ req.destroy();
29
+ reject(error);
30
+ }
31
+ else
32
+ resolve(Buffer.concat(chunks));
33
+ };
34
+ const cancel = () => finish(new Error("Policy pack download cancelled"));
35
+ const req = request(url, {
36
+ method: "GET",
37
+ agent: false,
38
+ rejectUnauthorized: true,
39
+ maxHeaderSize: 16 * 1024,
40
+ headers: { Accept: "application/json", "Accept-Encoding": "identity" },
41
+ }, (response) => {
42
+ response.on("error", () => finish(new Error("Policy pack transfer failed")));
43
+ if (response.statusCode !== 200) {
44
+ finish(new Error("Policy pack endpoint must return HTTP 200; redirects are not followed"));
45
+ return;
46
+ }
47
+ const length = response.headers["content-length"];
48
+ if ((length && (!/^\d+$/.test(length) || Number(length) > 64 * 1024)) ||
49
+ (response.headers["content-encoding"] &&
50
+ response.headers["content-encoding"] !== "identity")) {
51
+ finish(new Error("Policy pack response exceeds limits or uses content encoding"));
52
+ return;
53
+ }
54
+ response.on("data", (chunk) => {
55
+ bytes += chunk.length;
56
+ if (bytes > 64 * 1024)
57
+ finish(new Error("Policy pack exceeds 64 KiB limit"));
58
+ else
59
+ chunks.push(chunk);
60
+ });
61
+ response.on("end", () => {
62
+ if (!response.complete)
63
+ finish(new Error("Policy pack transfer is incomplete"));
64
+ else
65
+ finish();
66
+ });
67
+ });
68
+ const timer = setTimeout(() => finish(new Error("Policy pack download timed out")), timeoutMs);
69
+ req.on("error", () => finish(new Error("Policy pack HTTPS connection failed")));
70
+ signal?.addEventListener("abort", cancel, { once: true });
71
+ if (signal?.aborted)
72
+ cancel();
73
+ else
74
+ req.end();
75
+ });
76
+ }
77
+ export async function fetchPolicyPack(root, input) {
78
+ const { signal, ...provided } = input;
79
+ const parsed = optionsSchema.safeParse(provided);
80
+ if (!parsed.success)
81
+ throw new Error("Invalid policy pack download options");
82
+ const options = parsed.data;
83
+ let url;
84
+ try {
85
+ url = new URL(options.url);
86
+ }
87
+ catch {
88
+ throw new Error("Policy pack endpoint must be an absolute HTTPS URL");
89
+ }
90
+ if (url.protocol !== "https:" || url.username || url.password || url.hash)
91
+ throw new Error("Policy pack endpoint requires HTTPS without user information or a fragment");
92
+ const output = options.output;
93
+ if (path.isAbsolute(output) ||
94
+ path.posix.normalize(output) !== output ||
95
+ output.startsWith("../") ||
96
+ output === ".." ||
97
+ output === "." ||
98
+ !output.endsWith(".json") ||
99
+ [...output].some((character) => character === "\\" ||
100
+ character.charCodeAt(0) < 32 ||
101
+ character.charCodeAt(0) === 127))
102
+ throw new Error("Policy pack output must be a normalized relative JSON path");
103
+ const canonicalRoot = await realpath(root);
104
+ const parent = await withinRoot(canonicalRoot, path.dirname(output));
105
+ if (parent !== path.resolve(canonicalRoot, path.dirname(output)))
106
+ throw new Error("Policy pack output directories must not traverse symbolic links");
107
+ const destination = path.join(parent, path.basename(output));
108
+ try {
109
+ await lstat(destination);
110
+ throw new Error("Policy pack output already exists");
111
+ }
112
+ catch (error) {
113
+ if (error.code !== "ENOENT")
114
+ throw error;
115
+ }
116
+ const bytes = await download(url, options.timeoutMs, signal);
117
+ if (createHash("sha256").update(bytes).digest("hex") !== options.sha256)
118
+ throw new Error("Policy pack integrity mismatch");
119
+ let pack;
120
+ try {
121
+ pack = parsePolicyPack(new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes));
122
+ }
123
+ catch {
124
+ throw new Error("Downloaded policy pack is not valid UTF-8 policy JSON");
125
+ }
126
+ if (signal?.aborted)
127
+ throw new Error("Policy pack download cancelled");
128
+ const temporary = await mkdtemp(path.join(parent, ".checktrail-pack-"));
129
+ try {
130
+ const staged = path.join(temporary, "pack.json");
131
+ const handle = await open(staged, "wx", 0o600);
132
+ try {
133
+ await handle.writeFile(bytes);
134
+ await handle.sync();
135
+ }
136
+ finally {
137
+ await handle.close();
138
+ }
139
+ if (signal?.aborted)
140
+ throw new Error("Policy pack download cancelled");
141
+ // A hard link publishes complete bytes without replacing a concurrently created destination.
142
+ await link(staged, destination);
143
+ }
144
+ finally {
145
+ await rm(temporary, { recursive: true, force: true });
146
+ }
147
+ return {
148
+ schemaVersion: 1,
149
+ id: pack.id,
150
+ version: pack.version,
151
+ bytes: bytes.length,
152
+ reference: { path: output, sha256: options.sha256 },
153
+ };
154
+ }
@@ -0,0 +1,146 @@
1
+ import { z } from "zod";
2
+ export declare const findingKeySchema: z.ZodObject<{
3
+ checkId: z.ZodString;
4
+ project: z.ZodUnion<readonly [z.ZodLiteral<".">, z.ZodString]>;
5
+ ruleId: z.ZodString;
6
+ file: z.ZodString;
7
+ line: z.ZodNumber;
8
+ level: z.ZodEnum<{
9
+ error: "error";
10
+ warning: "warning";
11
+ note: "note";
12
+ }>;
13
+ messageHash: z.ZodString;
14
+ }, z.core.$strict>;
15
+ export declare const findingBaselineEntrySchema: z.ZodObject<{
16
+ checkId: z.ZodString;
17
+ project: z.ZodUnion<readonly [z.ZodLiteral<".">, z.ZodString]>;
18
+ ruleId: z.ZodString;
19
+ file: z.ZodString;
20
+ line: z.ZodNumber;
21
+ level: z.ZodEnum<{
22
+ error: "error";
23
+ warning: "warning";
24
+ note: "note";
25
+ }>;
26
+ messageHash: z.ZodString;
27
+ id: z.ZodString;
28
+ occurrences: z.ZodNumber;
29
+ kind: z.ZodEnum<{
30
+ baseline: "baseline";
31
+ exception: "exception";
32
+ }>;
33
+ owner: z.ZodString;
34
+ reason: z.ZodString;
35
+ expiresAt: z.ZodOptional<z.ZodISODateTime>;
36
+ }, z.core.$strict>;
37
+ export declare const findingBaselineSchema: z.ZodObject<{
38
+ schemaVersion: z.ZodLiteral<1>;
39
+ createdFrom: z.ZodObject<{
40
+ runId: z.ZodString;
41
+ sourceFingerprint: z.ZodString;
42
+ policyFingerprint: z.ZodString;
43
+ }, z.core.$strict>;
44
+ limits: z.ZodObject<{
45
+ maxEntries: z.ZodNumber;
46
+ maxExceptions: z.ZodNumber;
47
+ }, z.core.$strict>;
48
+ entries: z.ZodArray<z.ZodObject<{
49
+ checkId: z.ZodString;
50
+ project: z.ZodUnion<readonly [z.ZodLiteral<".">, z.ZodString]>;
51
+ ruleId: z.ZodString;
52
+ file: z.ZodString;
53
+ line: z.ZodNumber;
54
+ level: z.ZodEnum<{
55
+ error: "error";
56
+ warning: "warning";
57
+ note: "note";
58
+ }>;
59
+ messageHash: z.ZodString;
60
+ id: z.ZodString;
61
+ occurrences: z.ZodNumber;
62
+ kind: z.ZodEnum<{
63
+ baseline: "baseline";
64
+ exception: "exception";
65
+ }>;
66
+ owner: z.ZodString;
67
+ reason: z.ZodString;
68
+ expiresAt: z.ZodOptional<z.ZodISODateTime>;
69
+ }, z.core.$strict>>;
70
+ }, z.core.$strict>;
71
+ export type FindingBaseline = z.infer<typeof findingBaselineSchema>;
72
+ export declare const findingComparisonSchema: z.ZodObject<{
73
+ schemaVersion: z.ZodLiteral<1>;
74
+ engineVersion: z.ZodString;
75
+ provenance: z.ZodLiteral<"finding-comparison">;
76
+ outcome: z.ZodEnum<{
77
+ passed: "passed";
78
+ failed: "failed";
79
+ incomplete: "incomplete";
80
+ }>;
81
+ validationOutcome: z.ZodEnum<{
82
+ passed: "passed";
83
+ failed: "failed";
84
+ incomplete: "incomplete";
85
+ }>;
86
+ reason: z.ZodString;
87
+ sourceVerified: z.ZodBoolean;
88
+ baselineFingerprint: z.ZodString;
89
+ counts: z.ZodObject<{
90
+ current: z.ZodNumber;
91
+ new: z.ZodNumber;
92
+ matched: z.ZodNumber;
93
+ stale: z.ZodNumber;
94
+ expired: z.ZodNumber;
95
+ changed: z.ZodNumber;
96
+ unverified: z.ZodNumber;
97
+ expanded: z.ZodNumber;
98
+ modified: z.ZodNumber;
99
+ }, z.core.$strict>;
100
+ limitExceeded: z.ZodBoolean;
101
+ unaccountedFailures: z.ZodNumber;
102
+ entries: z.ZodArray<z.ZodObject<{
103
+ id: z.ZodString;
104
+ status: z.ZodEnum<{
105
+ unverified: "unverified";
106
+ changed: "changed";
107
+ matched: "matched";
108
+ stale: "stale";
109
+ expired: "expired";
110
+ }>;
111
+ occurrences: z.ZodNumber;
112
+ }, z.core.$strict>>;
113
+ newFindingIds: z.ZodArray<z.ZodString>;
114
+ }, z.core.$strict>;
115
+ export type FindingComparison = z.infer<typeof findingComparisonSchema>;
116
+ export declare const findingComparisonSummarySchema: z.ZodObject<{
117
+ schemaVersion: z.ZodLiteral<1>;
118
+ engineVersion: z.ZodString;
119
+ provenance: z.ZodLiteral<"finding-comparison">;
120
+ outcome: z.ZodEnum<{
121
+ passed: "passed";
122
+ failed: "failed";
123
+ incomplete: "incomplete";
124
+ }>;
125
+ reason: z.ZodString;
126
+ counts: z.ZodObject<{
127
+ current: z.ZodNumber;
128
+ new: z.ZodNumber;
129
+ matched: z.ZodNumber;
130
+ stale: z.ZodNumber;
131
+ expired: z.ZodNumber;
132
+ changed: z.ZodNumber;
133
+ unverified: z.ZodNumber;
134
+ expanded: z.ZodNumber;
135
+ modified: z.ZodNumber;
136
+ }, z.core.$strict>;
137
+ validationOutcome: z.ZodEnum<{
138
+ passed: "passed";
139
+ failed: "failed";
140
+ incomplete: "incomplete";
141
+ }>;
142
+ sourceVerified: z.ZodBoolean;
143
+ baselineFingerprint: z.ZodString;
144
+ limitExceeded: z.ZodBoolean;
145
+ unaccountedFailures: z.ZodNumber;
146
+ }, z.core.$strict>;
@@ -0,0 +1,70 @@
1
+ import { z } from "zod";
2
+ const hash = z.string().regex(/^[a-f0-9]{64}$/);
3
+ const relative = z
4
+ .string()
5
+ .min(1)
6
+ .max(4096)
7
+ .regex(/^(?![A-Za-z]:)(?:(?!\.{1,2}\/)[^/\\\0]+\/)*(?!\.{1,2}$)[^/\\\0]+$/, "Expected a normalized relative file path");
8
+ export const findingKeySchema = z.strictObject({
9
+ checkId: z.string().min(1).max(256),
10
+ project: z.union([z.literal("."), relative]),
11
+ ruleId: z.string().min(1).max(256),
12
+ file: relative,
13
+ line: z.number().int().positive(),
14
+ level: z.enum(["error", "warning", "note"]),
15
+ messageHash: hash,
16
+ });
17
+ export const findingBaselineEntrySchema = findingKeySchema.extend({
18
+ id: hash,
19
+ occurrences: z.number().int().positive().max(100_000),
20
+ kind: z.enum(["baseline", "exception"]),
21
+ owner: z.string().trim().min(1).max(256),
22
+ reason: z.string().trim().min(1).max(4096),
23
+ expiresAt: z.iso.datetime().optional(),
24
+ });
25
+ export const findingBaselineSchema = z.strictObject({
26
+ schemaVersion: z.literal(1),
27
+ createdFrom: z.strictObject({
28
+ runId: z.string(),
29
+ sourceFingerprint: z.string(),
30
+ policyFingerprint: z.string(),
31
+ }),
32
+ limits: z.strictObject({
33
+ maxEntries: z.number().int().nonnegative().max(10_000),
34
+ maxExceptions: z.number().int().nonnegative().max(10_000),
35
+ }),
36
+ entries: z.array(findingBaselineEntrySchema).max(10_000),
37
+ });
38
+ export const findingComparisonSchema = z.strictObject({
39
+ schemaVersion: z.literal(1),
40
+ engineVersion: z.string(),
41
+ provenance: z.literal("finding-comparison"),
42
+ outcome: z.enum(["passed", "failed", "incomplete"]),
43
+ validationOutcome: z.enum(["passed", "failed", "incomplete"]),
44
+ reason: z.string(),
45
+ sourceVerified: z.boolean(),
46
+ baselineFingerprint: hash,
47
+ counts: z.strictObject({
48
+ current: z.number().int().nonnegative(),
49
+ new: z.number().int().nonnegative(),
50
+ matched: z.number().int().nonnegative(),
51
+ stale: z.number().int().nonnegative(),
52
+ expired: z.number().int().nonnegative(),
53
+ changed: z.number().int().nonnegative(),
54
+ unverified: z.number().int().nonnegative(),
55
+ expanded: z.number().int().nonnegative(),
56
+ modified: z.number().int().nonnegative(),
57
+ }),
58
+ limitExceeded: z.boolean(),
59
+ unaccountedFailures: z.number().int().nonnegative(),
60
+ entries: z.array(z.strictObject({
61
+ id: hash,
62
+ status: z.enum(["matched", "stale", "expired", "changed", "unverified"]),
63
+ occurrences: z.number().int().nonnegative(),
64
+ })),
65
+ newFindingIds: z.array(hash),
66
+ });
67
+ export const findingComparisonSummarySchema = findingComparisonSchema.omit({
68
+ entries: true,
69
+ newFindingIds: true,
70
+ });
@@ -0,0 +1,62 @@
1
+ import { type FindingBaseline, type FindingComparison } from "./finding-policy-schema.js";
2
+ export declare function projectFindingComparison(result: FindingComparison, detailed: boolean): {
3
+ schemaVersion: 1;
4
+ engineVersion: string;
5
+ provenance: "finding-comparison";
6
+ outcome: "passed" | "failed" | "incomplete";
7
+ validationOutcome: "passed" | "failed" | "incomplete";
8
+ reason: string;
9
+ sourceVerified: boolean;
10
+ baselineFingerprint: string;
11
+ counts: {
12
+ current: number;
13
+ new: number;
14
+ matched: number;
15
+ stale: number;
16
+ expired: number;
17
+ changed: number;
18
+ unverified: number;
19
+ expanded: number;
20
+ modified: number;
21
+ };
22
+ limitExceeded: boolean;
23
+ unaccountedFailures: number;
24
+ entries: {
25
+ id: string;
26
+ status: "unverified" | "changed" | "matched" | "stale" | "expired";
27
+ occurrences: number;
28
+ }[];
29
+ newFindingIds: string[];
30
+ } | {
31
+ schemaVersion: 1;
32
+ engineVersion: string;
33
+ provenance: "finding-comparison";
34
+ outcome: "passed" | "failed" | "incomplete";
35
+ validationOutcome: "passed" | "failed" | "incomplete";
36
+ reason: string;
37
+ sourceVerified: boolean;
38
+ baselineFingerprint: string;
39
+ counts: {
40
+ current: number;
41
+ new: number;
42
+ matched: number;
43
+ stale: number;
44
+ expired: number;
45
+ changed: number;
46
+ unverified: number;
47
+ expanded: number;
48
+ modified: number;
49
+ };
50
+ limitExceeded: boolean;
51
+ unaccountedFailures: number;
52
+ };
53
+ export declare function createFindingBaseline(input: unknown, options: {
54
+ owner: string;
55
+ reason: string;
56
+ kind?: "baseline" | "exception";
57
+ expiresAt?: string;
58
+ }): FindingBaseline;
59
+ export declare function compareFindings(input: unknown, baselineInput: unknown, options?: {
60
+ now?: string;
61
+ previousBaseline?: unknown;
62
+ }): FindingComparison;