openaca 0.1.0__tar.gz

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 (406) hide show
  1. openaca-0.1.0/.claude/settings.json +35 -0
  2. openaca-0.1.0/.github/ISSUE_TEMPLATE/beta-feedback.md +68 -0
  3. openaca-0.1.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
  4. openaca-0.1.0/.github/workflows/autofix.yml +324 -0
  5. openaca-0.1.0/.github/workflows/ci.yml +135 -0
  6. openaca-0.1.0/.github/workflows/claude.yml +155 -0
  7. openaca-0.1.0/.github/workflows/notify-openaca-site.yml +27 -0
  8. openaca-0.1.0/.github/workflows/publish-pypi.yml +161 -0
  9. openaca-0.1.0/.github/workflows/publish.yml +87 -0
  10. openaca-0.1.0/.github/workflows/self-scan.yml +58 -0
  11. openaca-0.1.0/.gitignore +238 -0
  12. openaca-0.1.0/.gitleaks.toml +10 -0
  13. openaca-0.1.0/.openaca-seed-state-npm.json +6 -0
  14. openaca-0.1.0/.openaca-seed-state-pypi.json +6 -0
  15. openaca-0.1.0/.python-version +1 -0
  16. openaca-0.1.0/AGENTS.md +1 -0
  17. openaca-0.1.0/CLAUDE.md +200 -0
  18. openaca-0.1.0/CONTRIBUTING.md +262 -0
  19. openaca-0.1.0/LICENSE +201 -0
  20. openaca-0.1.0/PKG-INFO +276 -0
  21. openaca-0.1.0/README.md +256 -0
  22. openaca-0.1.0/SECURITY.md +32 -0
  23. openaca-0.1.0/action.yml +75 -0
  24. openaca-0.1.0/deploy/remote/intune-macos.sh +84 -0
  25. openaca-0.1.0/deploy/remote/jamf.sh +94 -0
  26. openaca-0.1.0/deploy/remote/kandji.sh +84 -0
  27. openaca-0.1.0/docs/README.md +40 -0
  28. openaca-0.1.0/docs/adrs/0001-licenses.md +49 -0
  29. openaca-0.1.0/docs/adrs/0002-schema-extension-key.md +47 -0
  30. openaca-0.1.0/docs/adrs/0003-single-namespace-architecture.md +56 -0
  31. openaca-0.1.0/docs/adrs/0004-advisory-id-year.md +66 -0
  32. openaca-0.1.0/docs/adrs/0005-manifest-parsers-posix-only.md +123 -0
  33. openaca-0.1.0/docs/adrs/0006-openaca-scan-subcommands-and-attribution.md +122 -0
  34. openaca-0.1.0/docs/adrs/0007-component-inventory-and-host-adapters.md +183 -0
  35. openaca-0.1.0/docs/adrs/0008-lockfile-dispatch-and-osv-federation.md +125 -0
  36. openaca-0.1.0/docs/adrs/0009-overlay-only-v0.md +64 -0
  37. openaca-0.1.0/docs/adrs/0010-overlay-taxonomies-and-seeding.md +86 -0
  38. openaca-0.1.0/docs/adrs/0011-llm-assisted-seed-annotation.md +78 -0
  39. openaca-0.1.0/docs/adrs/0012-minimal-overlay-schema.md +116 -0
  40. openaca-0.1.0/docs/adrs/0013-non-package-component-identities.md +82 -0
  41. openaca-0.1.0/docs/adrs/0014-rename-extension-key-to-openaca.md +64 -0
  42. openaca-0.1.0/docs/adrs/0015-overlay-data-cc-by-inlined-in-readme.md +69 -0
  43. openaca-0.1.0/docs/adrs/0016-agent-component-identity-and-scan-output.md +126 -0
  44. openaca-0.1.0/docs/adrs/0017-endpoint-scan-cwd-project-default.md +139 -0
  45. openaca-0.1.0/docs/adrs/0018-generic-skill-ecosystem-and-agent-bom-fields.md +107 -0
  46. openaca-0.1.0/docs/adrs/0019-source-ecosystems-and-agent-component-types.md +131 -0
  47. openaca-0.1.0/docs/adrs/0020-remote-mcp-server-inventory.md +178 -0
  48. openaca-0.1.0/docs/adrs/0021-skills-lock-source-provenance.md +71 -0
  49. openaca-0.1.0/docs/adrs/0022-agent-bom-open-cyclonedx.md +101 -0
  50. openaca-0.1.0/docs/adrs/0023-claude-code-plugin-distribution.md +74 -0
  51. openaca-0.1.0/docs/adrs/0024-opt-in-fleet-cli.md +137 -0
  52. openaca-0.1.0/docs/adrs/0025-curl-pipe-sh-install-via-uv.md +156 -0
  53. openaca-0.1.0/docs/adrs/0026-direct-uvx-for-one-shot-scans.md +86 -0
  54. openaca-0.1.0/docs/adrs/0027-osv-query-semantics.md +77 -0
  55. openaca-0.1.0/docs/adrs/0028-openaca-core-consumption-facade.md +113 -0
  56. openaca-0.1.0/docs/adrs/0029-agent-graph-occurrence-identity.md +146 -0
  57. openaca-0.1.0/docs/adrs/0030-centralize-identity-semantics.md +79 -0
  58. openaca-0.1.0/docs/adrs/0031-match-coordinates.md +114 -0
  59. openaca-0.1.0/docs/adrs/0032-remote-cli-namespace.md +68 -0
  60. openaca-0.1.0/docs/adrs/HOOK-PROMPT.md +1 -0
  61. openaca-0.1.0/docs/adrs/INDEX.md +59 -0
  62. openaca-0.1.0/docs/adrs/TEMPLATE.md +42 -0
  63. openaca-0.1.0/docs/concepts/identities.md +68 -0
  64. openaca-0.1.0/docs/concepts/scan-modes.md +66 -0
  65. openaca-0.1.0/docs/deploy.md +84 -0
  66. openaca-0.1.0/docs/frameworks/README.md +24 -0
  67. openaca-0.1.0/docs/frameworks/mitre-atlas.md +212 -0
  68. openaca-0.1.0/docs/frameworks/owasp-agentic-ai-top-10-2026.md +363 -0
  69. openaca-0.1.0/docs/frameworks/owasp-agentic-skills-top-10-2026.md +337 -0
  70. openaca-0.1.0/docs/frameworks/owasp-llm-top-10-2025.md +350 -0
  71. openaca-0.1.0/docs/frameworks/owasp-mcp-top-10-2025.md +358 -0
  72. openaca-0.1.0/docs/getting-started.md +97 -0
  73. openaca-0.1.0/docs/openaca-bom-schema.md +147 -0
  74. openaca-0.1.0/docs/plans/001-schema-and-tooling.md +1321 -0
  75. openaca-0.1.0/docs/plans/002-first-advisories.md +745 -0
  76. openaca-0.1.0/docs/plans/003-manifest-parsers.md +830 -0
  77. openaca-0.1.0/docs/plans/004-static-export.md +646 -0
  78. openaca-0.1.0/docs/plans/005-reference-action.md +668 -0
  79. openaca-0.1.0/docs/plans/006-disclosure-policy.md +413 -0
  80. openaca-0.1.0/docs/plans/007-fs-mode-cli-and-attribution.md +1153 -0
  81. openaca-0.1.0/docs/plans/008-component-inventory.md +416 -0
  82. openaca-0.1.0/docs/plans/009-plugin-internal-deps.md +2804 -0
  83. openaca-0.1.0/docs/plans/010-seed-overlay-pipeline.md +135 -0
  84. openaca-0.1.0/docs/plans/011-minimal-overlay-schema.md +114 -0
  85. openaca-0.1.0/docs/plans/012-candidate-annotation-surface-lock.md +905 -0
  86. openaca-0.1.0/docs/plans/013-rename-asve-to-openaca.md +73 -0
  87. openaca-0.1.0/docs/plans/014-posture-findings.md +1007 -0
  88. openaca-0.1.0/docs/plans/015-agent-component-identity-and-scan-output.md +561 -0
  89. openaca-0.1.0/docs/plans/016-claude-code-parser-coverage.md +92 -0
  90. openaca-0.1.0/docs/plans/017-generic-skill-ecosystem.md +35 -0
  91. openaca-0.1.0/docs/plans/018-source-ecosystem-component-type.md +29 -0
  92. openaca-0.1.0/docs/plans/019-remote-mcp-inventory.md +58 -0
  93. openaca-0.1.0/docs/plans/020-skill-lock-source-provenance.md +31 -0
  94. openaca-0.1.0/docs/plans/021-agent-bom.md +31 -0
  95. openaca-0.1.0/docs/plans/022-first-run-inventory-output.md +196 -0
  96. openaca-0.1.0/docs/plans/023-risk-attribution.md +136 -0
  97. openaca-0.1.0/docs/plans/024-bun-lock-parsing.md +486 -0
  98. openaca-0.1.0/docs/plans/025-osv-query-semantics.md +62 -0
  99. openaca-0.1.0/docs/plans/026-openaca-core-facade.md +128 -0
  100. openaca-0.1.0/docs/plans/027-fleet-deploy-script-tests.md +19 -0
  101. openaca-0.1.0/docs/plans/029-agent-graph-identity.md +43 -0
  102. openaca-0.1.0/docs/plans/030-identity-hardening.md +41 -0
  103. openaca-0.1.0/docs/plans/README.md +82 -0
  104. openaca-0.1.0/docs/posture/README.md +53 -0
  105. openaca-0.1.0/docs/posture/openaca-posture-api-endpoint-override.md +47 -0
  106. openaca-0.1.0/docs/posture/openaca-posture-insecure-transport.md +55 -0
  107. openaca-0.1.0/docs/posture/openaca-posture-mcp-auto-approve.md +49 -0
  108. openaca-0.1.0/docs/posture/openaca-posture-mutable-install-reference.md +75 -0
  109. openaca-0.1.0/docs/pypi-publishing.md +73 -0
  110. openaca-0.1.0/docs/reference/cli.md +142 -0
  111. openaca-0.1.0/docs/reference/coverage.md +66 -0
  112. openaca-0.1.0/docs/reference/overlays.md +68 -0
  113. openaca-0.1.0/docs/releases/v0.1.0.md +59 -0
  114. openaca-0.1.0/docs/releases/v0.1.0b1.md +17 -0
  115. openaca-0.1.0/docs/releases/v0.1.0b2.md +16 -0
  116. openaca-0.1.0/docs/releases/v0.1.0b3.md +18 -0
  117. openaca-0.1.0/docs/releases/v0.1.0b4.md +20 -0
  118. openaca-0.1.0/docs/releases/v0.1.0b5.md +17 -0
  119. openaca-0.1.0/docs/releases/v0.1.0b6.md +20 -0
  120. openaca-0.1.0/docs/releases/v0.1.0b7.md +52 -0
  121. openaca-0.1.0/docs/releases/v0.1.0b8.md +41 -0
  122. openaca-0.1.0/docs/remote-deployment.md +62 -0
  123. openaca-0.1.0/docs/sarif-conventions.md +53 -0
  124. openaca-0.1.0/docs/seed-review-rules.md +121 -0
  125. openaca-0.1.0/docs/specs/openaca-thesis.md +251 -0
  126. openaca-0.1.0/examples/skills/claude/openaca-candidate-review/SKILL.md +155 -0
  127. openaca-0.1.0/openaca/__init__.py +2 -0
  128. openaca-0.1.0/openaca/core/__init__.py +42 -0
  129. openaca-0.1.0/openaca/core/bom.py +15 -0
  130. openaca-0.1.0/openaca/core/component_ref.py +5 -0
  131. openaca-0.1.0/openaca/core/identity.py +5 -0
  132. openaca-0.1.0/openaca/core/matching.py +5 -0
  133. openaca-0.1.0/openaca/core/osv_queries.py +9 -0
  134. openaca-0.1.0/openaca/core/severity.py +5 -0
  135. openaca-0.1.0/openaca/py.typed +0 -0
  136. openaca-0.1.0/overlays/CVE-2026-20205.yaml +9 -0
  137. openaca-0.1.0/overlays/GHSA-3ch2-jxxc-v4xf.yaml +12 -0
  138. openaca-0.1.0/overlays/GHSA-3q26-f695-pp76.yaml +12 -0
  139. openaca-0.1.0/overlays/GHSA-6xpm-ggf7-wc3p.yaml +12 -0
  140. openaca-0.1.0/overlays/GHSA-m4qw-j7mx-qv6h.yaml +12 -0
  141. openaca-0.1.0/overlays/GHSA-rwc2-f344-q6w6.yaml +12 -0
  142. openaca-0.1.0/overlays/MAL-2024-8094.yaml +16 -0
  143. openaca-0.1.0/overlays/MAL-2024-9212.yaml +14 -0
  144. openaca-0.1.0/overlays/MAL-2024-9263.yaml +14 -0
  145. openaca-0.1.0/overlays/MAL-2025-15093.yaml +17 -0
  146. openaca-0.1.0/overlays/MAL-2025-190848.yaml +19 -0
  147. openaca-0.1.0/overlays/MAL-2025-190867.yaml +16 -0
  148. openaca-0.1.0/overlays/MAL-2025-190868.yaml +16 -0
  149. openaca-0.1.0/overlays/MAL-2025-190869.yaml +16 -0
  150. openaca-0.1.0/overlays/MAL-2025-190902.yaml +16 -0
  151. openaca-0.1.0/overlays/MAL-2025-190908.yaml +16 -0
  152. openaca-0.1.0/overlays/MAL-2025-190909.yaml +17 -0
  153. openaca-0.1.0/overlays/MAL-2025-190918.yaml +17 -0
  154. openaca-0.1.0/overlays/MAL-2025-190922.yaml +16 -0
  155. openaca-0.1.0/overlays/MAL-2025-190923.yaml +16 -0
  156. openaca-0.1.0/overlays/MAL-2025-190943.yaml +19 -0
  157. openaca-0.1.0/overlays/MAL-2025-191051.yaml +19 -0
  158. openaca-0.1.0/overlays/MAL-2025-191052.yaml +19 -0
  159. openaca-0.1.0/overlays/MAL-2025-191053.yaml +19 -0
  160. openaca-0.1.0/overlays/MAL-2025-191107.yaml +19 -0
  161. openaca-0.1.0/overlays/MAL-2025-191195.yaml +18 -0
  162. openaca-0.1.0/overlays/MAL-2025-191196.yaml +17 -0
  163. openaca-0.1.0/overlays/MAL-2025-191332.yaml +14 -0
  164. openaca-0.1.0/overlays/MAL-2025-191465.yaml +19 -0
  165. openaca-0.1.0/overlays/MAL-2025-191527.yaml +16 -0
  166. openaca-0.1.0/overlays/MAL-2025-191647.yaml +17 -0
  167. openaca-0.1.0/overlays/MAL-2025-191648.yaml +17 -0
  168. openaca-0.1.0/overlays/MAL-2025-191788.yaml +17 -0
  169. openaca-0.1.0/overlays/MAL-2025-191789.yaml +19 -0
  170. openaca-0.1.0/overlays/MAL-2025-191924.yaml +19 -0
  171. openaca-0.1.0/overlays/MAL-2025-191925.yaml +19 -0
  172. openaca-0.1.0/overlays/MAL-2025-191926.yaml +19 -0
  173. openaca-0.1.0/overlays/MAL-2025-191927.yaml +19 -0
  174. openaca-0.1.0/overlays/MAL-2025-191929.yaml +19 -0
  175. openaca-0.1.0/overlays/MAL-2025-191930.yaml +19 -0
  176. openaca-0.1.0/overlays/MAL-2025-191931.yaml +19 -0
  177. openaca-0.1.0/overlays/MAL-2025-192601.yaml +19 -0
  178. openaca-0.1.0/overlays/MAL-2025-192746.yaml +18 -0
  179. openaca-0.1.0/overlays/MAL-2025-192747.yaml +18 -0
  180. openaca-0.1.0/overlays/MAL-2025-2298.yaml +16 -0
  181. openaca-0.1.0/overlays/MAL-2025-26053.yaml +16 -0
  182. openaca-0.1.0/overlays/MAL-2025-26054.yaml +17 -0
  183. openaca-0.1.0/overlays/MAL-2025-3471.yaml +16 -0
  184. openaca-0.1.0/overlays/MAL-2025-4045.yaml +14 -0
  185. openaca-0.1.0/overlays/MAL-2025-41387.yaml +14 -0
  186. openaca-0.1.0/overlays/MAL-2025-41934.yaml +14 -0
  187. openaca-0.1.0/overlays/MAL-2025-41939.yaml +17 -0
  188. openaca-0.1.0/overlays/MAL-2025-41940.yaml +17 -0
  189. openaca-0.1.0/overlays/MAL-2025-42116.yaml +17 -0
  190. openaca-0.1.0/overlays/MAL-2025-42192.yaml +15 -0
  191. openaca-0.1.0/overlays/MAL-2025-4223.yaml +16 -0
  192. openaca-0.1.0/overlays/MAL-2025-42690.yaml +15 -0
  193. openaca-0.1.0/overlays/MAL-2025-4635.yaml +18 -0
  194. openaca-0.1.0/overlays/MAL-2025-46986.yaml +19 -0
  195. openaca-0.1.0/overlays/MAL-2025-47076.yaml +18 -0
  196. openaca-0.1.0/overlays/MAL-2025-47098.yaml +18 -0
  197. openaca-0.1.0/overlays/MAL-2025-47326.yaml +19 -0
  198. openaca-0.1.0/overlays/MAL-2025-47327.yaml +19 -0
  199. openaca-0.1.0/overlays/MAL-2025-47604.yaml +20 -0
  200. openaca-0.1.0/overlays/MAL-2025-47838.yaml +19 -0
  201. openaca-0.1.0/overlays/MAL-2025-47907.yaml +14 -0
  202. openaca-0.1.0/overlays/MAL-2025-47929.yaml +20 -0
  203. openaca-0.1.0/overlays/MAL-2025-48030.yaml +16 -0
  204. openaca-0.1.0/overlays/MAL-2025-48558.yaml +17 -0
  205. openaca-0.1.0/overlays/MAL-2025-48761.yaml +19 -0
  206. openaca-0.1.0/overlays/MAL-2025-48786.yaml +17 -0
  207. openaca-0.1.0/overlays/MAL-2025-49378.yaml +16 -0
  208. openaca-0.1.0/overlays/MAL-2025-49379.yaml +18 -0
  209. openaca-0.1.0/overlays/MAL-2025-49383.yaml +20 -0
  210. openaca-0.1.0/overlays/MAL-2025-5286.yaml +18 -0
  211. openaca-0.1.0/overlays/MAL-2025-5813.yaml +19 -0
  212. openaca-0.1.0/overlays/MAL-2025-6007.yaml +16 -0
  213. openaca-0.1.0/overlays/MAL-2025-6115.yaml +16 -0
  214. openaca-0.1.0/overlays/MAL-2025-6139.yaml +19 -0
  215. openaca-0.1.0/overlays/MAL-2025-635.yaml +17 -0
  216. openaca-0.1.0/overlays/MAL-2026-1151.yaml +16 -0
  217. openaca-0.1.0/overlays/MAL-2026-1321.yaml +16 -0
  218. openaca-0.1.0/overlays/MAL-2026-1380.yaml +16 -0
  219. openaca-0.1.0/overlays/MAL-2026-1642.yaml +17 -0
  220. openaca-0.1.0/overlays/MAL-2026-1930.yaml +19 -0
  221. openaca-0.1.0/overlays/MAL-2026-1990.yaml +16 -0
  222. openaca-0.1.0/overlays/MAL-2026-2004.yaml +16 -0
  223. openaca-0.1.0/overlays/MAL-2026-2005.yaml +16 -0
  224. openaca-0.1.0/overlays/MAL-2026-2006.yaml +16 -0
  225. openaca-0.1.0/overlays/MAL-2026-2144.yaml +18 -0
  226. openaca-0.1.0/overlays/MAL-2026-227.yaml +16 -0
  227. openaca-0.1.0/overlays/MAL-2026-229.yaml +16 -0
  228. openaca-0.1.0/overlays/MAL-2026-2315.yaml +20 -0
  229. openaca-0.1.0/overlays/MAL-2026-2328.yaml +19 -0
  230. openaca-0.1.0/overlays/MAL-2026-248.yaml +14 -0
  231. openaca-0.1.0/overlays/MAL-2026-2669.yaml +16 -0
  232. openaca-0.1.0/overlays/MAL-2026-2974.yaml +19 -0
  233. openaca-0.1.0/overlays/MAL-2026-3280.yaml +19 -0
  234. openaca-0.1.0/overlays/MAL-2026-3448.yaml +19 -0
  235. openaca-0.1.0/overlays/MAL-2026-3588.yaml +19 -0
  236. openaca-0.1.0/overlays/MAL-2026-3589.yaml +19 -0
  237. openaca-0.1.0/overlays/MAL-2026-3811.yaml +14 -0
  238. openaca-0.1.0/overlays/MAL-2026-405.yaml +16 -0
  239. openaca-0.1.0/overlays/MAL-2026-603.yaml +16 -0
  240. openaca-0.1.0/overlays/MAL-2026-844.yaml +15 -0
  241. openaca-0.1.0/overlays/MAL-2026-862.yaml +16 -0
  242. openaca-0.1.0/overlays/MAL-2026-904.yaml +14 -0
  243. openaca-0.1.0/pyproject.toml +52 -0
  244. openaca-0.1.0/schema/openaca-bom.schema.json +89 -0
  245. openaca-0.1.0/schema/openaca.schema.json +155 -0
  246. openaca-0.1.0/scripts/git-hooks/pre-push +102 -0
  247. openaca-0.1.0/scripts/install-hooks.sh +36 -0
  248. openaca-0.1.0/scripts/install.sh +81 -0
  249. openaca-0.1.0/scripts/seed-osv-overlays.sh +45 -0
  250. openaca-0.1.0/tests/__init__.py +0 -0
  251. openaca-0.1.0/tests/conftest.py +72 -0
  252. openaca-0.1.0/tests/fixtures/candidates/flowise-corrected-good.yaml +26 -0
  253. openaca-0.1.0/tests/fixtures/candidates/flowise-nano-bad.yaml +30 -0
  254. openaca-0.1.0/tests/fixtures/installs/minimal/plugins/cache/test-marketplace/sample-plugin/1.2.0/.claude-plugin/plugin.json +5 -0
  255. openaca-0.1.0/tests/fixtures/installs/minimal/plugins/installed_plugins.json +15 -0
  256. openaca-0.1.0/tests/fixtures/installs/minimal/settings.json +5 -0
  257. openaca-0.1.0/tests/fixtures/invalid/bad-cvss.yaml +24 -0
  258. openaca-0.1.0/tests/fixtures/invalid/bad-datetime.yaml +24 -0
  259. openaca-0.1.0/tests/fixtures/invalid/config-not-allowed.yaml +10 -0
  260. openaca-0.1.0/tests/fixtures/invalid/exposure-not-allowed.yaml +10 -0
  261. openaca-0.1.0/tests/fixtures/osv/ghsa-3ch2-jxxc-v4xf.json +19 -0
  262. openaca-0.1.0/tests/fixtures/osv/ghsa-3q26-f695-pp76.json +30 -0
  263. openaca-0.1.0/tests/fixtures/osv/ghsa-6xpm-ggf7-wc3p.json +18 -0
  264. openaca-0.1.0/tests/fixtures/osv/ghsa-m4qw-j7mx-qv6h.json +18 -0
  265. openaca-0.1.0/tests/fixtures/osv/ghsa-rwc2-f344-q6w6.json +19 -0
  266. openaca-0.1.0/tests/fixtures/reports/card-clean.txt +23 -0
  267. openaca-0.1.0/tests/fixtures/reports/card-endpoint.txt +30 -0
  268. openaca-0.1.0/tests/fixtures/reports/legacy-clean.txt +2 -0
  269. openaca-0.1.0/tests/fixtures/reports/legacy-findings-posture.txt +16 -0
  270. openaca-0.1.0/tests/fixtures/reports/legacy-findings.txt +9 -0
  271. openaca-0.1.0/tests/fixtures/repos/bun-plugin/.claude-plugin/plugin.json +1 -0
  272. openaca-0.1.0/tests/fixtures/repos/bun-plugin/bun.lock +23 -0
  273. openaca-0.1.0/tests/fixtures/repos/declared-components/.claude/agents/reviewer.md +4 -0
  274. openaca-0.1.0/tests/fixtures/repos/declared-components/.claude/commands/deploy.md +1 -0
  275. openaca-0.1.0/tests/fixtures/repos/declared-components/.claude/skills/bootstrap/SKILL.md +7 -0
  276. openaca-0.1.0/tests/fixtures/repos/exposed-mcp/.claude/settings.json +5 -0
  277. openaca-0.1.0/tests/fixtures/repos/exposed-mcp/.claude-plugin/plugin.json +5 -0
  278. openaca-0.1.0/tests/fixtures/repos/exposed-mcp/package.json +7 -0
  279. openaca-0.1.0/tests/fixtures/repos/sample-claude-desktop/claude_desktop_config.json +12 -0
  280. openaca-0.1.0/tests/fixtures/repos/sample-lockfile-npm/package-lock.json +9 -0
  281. openaca-0.1.0/tests/fixtures/repos/sample-lockfile-uv/uv.lock +5 -0
  282. openaca-0.1.0/tests/fixtures/repos/sample-mcp/mcp.json +20 -0
  283. openaca-0.1.0/tests/fixtures/repos/sample-npm/package.json +11 -0
  284. openaca-0.1.0/tests/fixtures/repos/sample-plugin/.claude-plugin/plugin.json +19 -0
  285. openaca-0.1.0/tests/fixtures/repos/sample-plugin-string-mcp/.claude-plugin/plugin.json +6 -0
  286. openaca-0.1.0/tests/fixtures/repos/sample-plugin-string-mcp/.mcp.json +8 -0
  287. openaca-0.1.0/tests/fixtures/repos/sample-pyproject/pyproject.toml +22 -0
  288. openaca-0.1.0/tests/fixtures/repos/sample-settings/.claude/settings.json +7 -0
  289. openaca-0.1.0/tests/fixtures/valid/cve-2026-0001.yaml +35 -0
  290. openaca-0.1.0/tests/remote/test_cli.py +401 -0
  291. openaca-0.1.0/tests/remote/test_client.py +244 -0
  292. openaca-0.1.0/tests/remote/test_collect.py +1435 -0
  293. openaca-0.1.0/tests/remote/test_config.py +58 -0
  294. openaca-0.1.0/tests/remote/test_deploy_scripts.py +268 -0
  295. openaca-0.1.0/tests/remote/test_redact_payload.py +414 -0
  296. openaca-0.1.0/tests/remote/test_upload_contract.py +577 -0
  297. openaca-0.1.0/tests/test_bom.py +621 -0
  298. openaca-0.1.0/tests/test_bom_cli.py +300 -0
  299. openaca-0.1.0/tests/test_bom_lint.py +148 -0
  300. openaca-0.1.0/tests/test_component_ref.py +229 -0
  301. openaca-0.1.0/tests/test_core_facade.py +31 -0
  302. openaca-0.1.0/tests/test_cvss.py +161 -0
  303. openaca-0.1.0/tests/test_e2e.py +808 -0
  304. openaca-0.1.0/tests/test_export.py +188 -0
  305. openaca-0.1.0/tests/test_finding_output.py +102 -0
  306. openaca-0.1.0/tests/test_identity.py +166 -0
  307. openaca-0.1.0/tests/test_lint.py +318 -0
  308. openaca-0.1.0/tests/test_matcher.py +776 -0
  309. openaca-0.1.0/tests/test_osv_federation.py +578 -0
  310. openaca-0.1.0/tests/test_overlays.py +47 -0
  311. openaca-0.1.0/tests/test_parsers/__init__.py +0 -0
  312. openaca-0.1.0/tests/test_parsers/test_bun_lock.py +169 -0
  313. openaca-0.1.0/tests/test_parsers/test_claude_command_agent.py +269 -0
  314. openaca-0.1.0/tests/test_parsers/test_claude_desktop_config.py +25 -0
  315. openaca-0.1.0/tests/test_parsers/test_claude_install.py +1410 -0
  316. openaca-0.1.0/tests/test_parsers/test_claude_plugin.py +565 -0
  317. openaca-0.1.0/tests/test_parsers/test_claude_settings.py +109 -0
  318. openaca-0.1.0/tests/test_parsers/test_claude_skill.py +149 -0
  319. openaca-0.1.0/tests/test_parsers/test_gitignore.py +197 -0
  320. openaca-0.1.0/tests/test_parsers/test_hooks_json.py +256 -0
  321. openaca-0.1.0/tests/test_parsers/test_mcp_json.py +935 -0
  322. openaca-0.1.0/tests/test_parsers/test_package_json.py +34 -0
  323. openaca-0.1.0/tests/test_parsers/test_package_lock_json.py +135 -0
  324. openaca-0.1.0/tests/test_parsers/test_pyproject_toml.py +140 -0
  325. openaca-0.1.0/tests/test_parsers/test_registry.py +62 -0
  326. openaca-0.1.0/tests/test_parsers/test_repo_mode_components.py +127 -0
  327. openaca-0.1.0/tests/test_parsers/test_repo_mode_lockfiles.py +28 -0
  328. openaca-0.1.0/tests/test_parsers/test_settings_layers.py +217 -0
  329. openaca-0.1.0/tests/test_parsers/test_skill_lock.py +103 -0
  330. openaca-0.1.0/tests/test_parsers/test_uv_lock.py +90 -0
  331. openaca-0.1.0/tests/test_posture_api_endpoint_override.py +104 -0
  332. openaca-0.1.0/tests/test_posture_finding.py +37 -0
  333. openaca-0.1.0/tests/test_posture_immutability.py +109 -0
  334. openaca-0.1.0/tests/test_posture_insecure_transport.py +105 -0
  335. openaca-0.1.0/tests/test_posture_integration.py +460 -0
  336. openaca-0.1.0/tests/test_posture_mcp_auto_approve.py +78 -0
  337. openaca-0.1.0/tests/test_posture_mutable_install.py +150 -0
  338. openaca-0.1.0/tests/test_promote.py +166 -0
  339. openaca-0.1.0/tests/test_render.py +1853 -0
  340. openaca-0.1.0/tests/test_sarif.py +304 -0
  341. openaca-0.1.0/tests/test_scan.py +1690 -0
  342. openaca-0.1.0/tests/test_schema.py +146 -0
  343. openaca-0.1.0/tests/test_seed_cli.py +1185 -0
  344. openaca-0.1.0/tests/test_seed_llm.py +316 -0
  345. openaca-0.1.0/tests/test_seed_validator.py +174 -0
  346. openaca-0.1.0/tests/test_seed_workflow_script.py +181 -0
  347. openaca-0.1.0/tests/test_severity.py +145 -0
  348. openaca-0.1.0/tools/__init__.py +1 -0
  349. openaca-0.1.0/tools/bom.py +413 -0
  350. openaca-0.1.0/tools/bom_cli.py +124 -0
  351. openaca-0.1.0/tools/bom_lint.py +136 -0
  352. openaca-0.1.0/tools/cli.py +40 -0
  353. openaca-0.1.0/tools/component_ref.py +72 -0
  354. openaca-0.1.0/tools/cvss.py +154 -0
  355. openaca-0.1.0/tools/export.py +161 -0
  356. openaca-0.1.0/tools/finding_output.py +150 -0
  357. openaca-0.1.0/tools/identity.py +347 -0
  358. openaca-0.1.0/tools/lint.py +215 -0
  359. openaca-0.1.0/tools/matcher.py +335 -0
  360. openaca-0.1.0/tools/osv_federation.py +294 -0
  361. openaca-0.1.0/tools/overlays.py +83 -0
  362. openaca-0.1.0/tools/parsers/__init__.py +276 -0
  363. openaca-0.1.0/tools/parsers/bun_lock.py +152 -0
  364. openaca-0.1.0/tools/parsers/claude_command_agent.py +167 -0
  365. openaca-0.1.0/tools/parsers/claude_install.py +666 -0
  366. openaca-0.1.0/tools/parsers/claude_plugin.py +100 -0
  367. openaca-0.1.0/tools/parsers/claude_plugin_root.py +310 -0
  368. openaca-0.1.0/tools/parsers/claude_settings.py +69 -0
  369. openaca-0.1.0/tools/parsers/claude_skill.py +91 -0
  370. openaca-0.1.0/tools/parsers/gitignore.py +102 -0
  371. openaca-0.1.0/tools/parsers/hooks_json.py +145 -0
  372. openaca-0.1.0/tools/parsers/mcp_json.py +786 -0
  373. openaca-0.1.0/tools/parsers/package_json.py +32 -0
  374. openaca-0.1.0/tools/parsers/package_lock_json.py +66 -0
  375. openaca-0.1.0/tools/parsers/pyproject_toml.py +99 -0
  376. openaca-0.1.0/tools/parsers/settings_layers.py +155 -0
  377. openaca-0.1.0/tools/parsers/skill_lock.py +161 -0
  378. openaca-0.1.0/tools/parsers/uv_lock.py +51 -0
  379. openaca-0.1.0/tools/posture/__init__.py +287 -0
  380. openaca-0.1.0/tools/posture/finding.py +65 -0
  381. openaca-0.1.0/tools/posture/immutability.py +138 -0
  382. openaca-0.1.0/tools/posture/rules/__init__.py +1 -0
  383. openaca-0.1.0/tools/posture/rules/api_endpoint_override.py +110 -0
  384. openaca-0.1.0/tools/posture/rules/insecure_transport.py +86 -0
  385. openaca-0.1.0/tools/posture/rules/mcp_auto_approve.py +86 -0
  386. openaca-0.1.0/tools/posture/rules/mutable_install.py +166 -0
  387. openaca-0.1.0/tools/promote.py +108 -0
  388. openaca-0.1.0/tools/py.typed +0 -0
  389. openaca-0.1.0/tools/remote/__init__.py +1 -0
  390. openaca-0.1.0/tools/remote/cli.py +163 -0
  391. openaca-0.1.0/tools/remote/client.py +255 -0
  392. openaca-0.1.0/tools/remote/collector.py +646 -0
  393. openaca-0.1.0/tools/remote/config.py +72 -0
  394. openaca-0.1.0/tools/remote/upload_contract.py +215 -0
  395. openaca-0.1.0/tools/render.py +1494 -0
  396. openaca-0.1.0/tools/sarif.py +207 -0
  397. openaca-0.1.0/tools/scan.py +958 -0
  398. openaca-0.1.0/tools/seed/__init__.py +1 -0
  399. openaca-0.1.0/tools/seed/__main__.py +683 -0
  400. openaca-0.1.0/tools/seed/llm.py +425 -0
  401. openaca-0.1.0/tools/seed/validator.py +79 -0
  402. openaca-0.1.0/tools/severity.py +109 -0
  403. openaca-0.1.0/tools/templates/advisory.html.j2 +117 -0
  404. openaca-0.1.0/tools/templates/index.html.j2 +68 -0
  405. openaca-0.1.0/tools/templates/style.css +118 -0
  406. openaca-0.1.0/uv.lock +872 -0
@@ -0,0 +1,35 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
+ "hooks": {
4
+ "SessionStart": [
5
+ {
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "if [ -f docs/adrs/INDEX.md ]; then printf '=== ARCHITECTURE DECISIONS (docs/adrs/) ===\\n\\nRead a full ADR before changing logic in the area it covers.\\n\\n'; cat docs/adrs/INDEX.md; fi"
10
+ }
11
+ ]
12
+ }
13
+ ],
14
+ "PreCompact": [
15
+ {
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "if [ -f docs/adrs/HOOK-PROMPT.md ]; then printf '=== ADR CHECK BEFORE COMPACTION ===\\n\\n'; cat docs/adrs/HOOK-PROMPT.md; fi"
20
+ }
21
+ ]
22
+ }
23
+ ],
24
+ "SessionEnd": [
25
+ {
26
+ "hooks": [
27
+ {
28
+ "type": "command",
29
+ "command": "if [ -f docs/adrs/HOOK-PROMPT.md ]; then printf '=== ADR CHECK BEFORE SESSION END ===\\n\\n'; cat docs/adrs/HOOK-PROMPT.md; fi"
30
+ }
31
+ ]
32
+ }
33
+ ]
34
+ }
35
+ }
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: Beta feedback
3
+ about: Report scanner ergonomics, coverage gaps, or workflow fit during the 0.1.0bN beta.
4
+ title: '[beta] '
5
+ labels: ['beta']
6
+ ---
7
+
8
+ <!-- Thanks for testing OpenACA. Fields marked (required) are the ones I look at first. -->
9
+
10
+ ### Feedback type (required)
11
+
12
+ <!-- Pick the closest fit — helps me triage. -->
13
+
14
+ - [ ] Scanner ergonomics — install / first scan / output legibility / CLI friction
15
+ - [ ] Coverage gap — something the scanner inventoried or missed that surprised me
16
+ - [ ] Workflow fit — where OpenACA does or doesn't fit in my security tooling
17
+
18
+ ### Command run (required)
19
+
20
+ ```bash
21
+ # Paste the full command you ran, e.g.:
22
+ # openaca scan repo --target ./my-mcp-server --include-posture
23
+ ```
24
+
25
+ ### OpenACA version (required)
26
+
27
+ ```
28
+ # Output of `openaca --version`
29
+ ```
30
+
31
+ ### Expected vs actual (required)
32
+
33
+ **Expected:**
34
+
35
+ **Actual:**
36
+
37
+ ### Output (redacted as needed)
38
+
39
+ <!--
40
+ Paste scanner output. If it contains internal names, paths, or component IDs
41
+ you don't want public, redact freely — replace with <redacted> or generic
42
+ placeholders. The shape of the output is more useful than the literal
43
+ contents. SARIF is sometimes easier to redact than text.
44
+ -->
45
+
46
+ ```
47
+ ```
48
+
49
+ ### Missing or incorrect inventory
50
+
51
+ <!--
52
+ If the scanner missed something it should have inventoried, or inventoried
53
+ something incorrectly, describe what + where. Format hint:
54
+ - Manifest: path/to/file
55
+ - Should have shown: <name@version>
56
+ - Actually showed: <empty | wrong name | wrong version>
57
+ Leave blank if this isn't an inventory issue.
58
+ -->
59
+
60
+ ### Environment
61
+
62
+ - OS:
63
+ - Python version (`python --version`):
64
+ - Install path (`pip install ...` / `uv sync` / other):
65
+
66
+ ### Anything else?
67
+
68
+ <!-- Workflow context, what you wish it did, why you tried it, etc. -->
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security disclosure
4
+ url: https://github.com/open-agent-security/openaca/security/advisories/new
5
+ about: Private security advisories for OpenACA-the-scanner bugs. See SECURITY.md.
6
+ - name: Beta tester guide
7
+ url: https://github.com/open-agent-security/openaca-demo/blob/main/BETA-TESTER-GUIDE.md
8
+ about: Beta-tester guide (hosted in the public openaca-demo repo since openaca is private during the closed beta).
@@ -0,0 +1,324 @@
1
+ name: Autofix
2
+
3
+ # Two-direction comment-bounce that closes the Codex/Claude review loop
4
+ # without human intervention:
5
+ #
6
+ # 1. Codex bot reviews a PR -> this workflow posts `@claude please
7
+ # address...` -> claude.yml fires, claude reads the review, fixes
8
+ # it, pushes commits, posts a "Claude finished" summary comment.
9
+ #
10
+ # 2. Any new commits land on a PR head (`pull_request: synchronize`) ->
11
+ # this workflow posts `@codex review` -> Codex re-reviews HEAD. ONE
12
+ # job covers every source of new commits — the claude bot's fixes, a
13
+ # human / local-agent push, AND the auto-rebase force-push below —
14
+ # because `synchronize` fires regardless of who pushed. (Codex
15
+ # auto-reviews on PR open / draft->ready natively, but NOT on
16
+ # subsequent pushes; without this, later commits land unreviewed.)
17
+ #
18
+ # The two bounces together produce: codex-flag -> claude-fix ->
19
+ # codex-rereview -> ... bounded by the claude-side iteration cap in
20
+ # bounce 1 (the cap counts claude runs, so push-driven re-reviews add
21
+ # entry points, not an unbounded cycle). Codex is silent when clean
22
+ # (thumbs reaction), so an unnecessary @codex review on a no-op SHA costs
23
+ # one cheap call with no PR noise.
24
+ #
25
+ # Authentication: comments are posted (and PR branches are rebased)
26
+ # using AUTOFIX_TRIGGER_PAT, NOT secrets.GITHUB_TOKEN. GitHub explicitly
27
+ # suppresses workflow runs triggered by events created with
28
+ # GITHUB_TOKEN (recursion guard) — so a comment posted under
29
+ # github-actions[bot] would be silently dropped before claude.yml ever
30
+ # fired, and a rebase pushed under GITHUB_TOKEN would not re-trigger
31
+ # CI / Codex on the rebased branch. The PAT is fine-grained, owned by
32
+ # a repo OWNER/MEMBER, with BOTH:
33
+ # - `Pull requests: Read and write` — for `gh pr comment` (bounces 1
34
+ # and 2) and `gh pr list` (bounce 0).
35
+ # - `Contents: Read and write` — for `actions/checkout` to fetch and
36
+ # `git push` from the rebase-stale-prs job. WITHOUT this, the job
37
+ # fails at the `Checkout` step with `403: Write access to
38
+ # repository not granted` — actions/checkout asks GitHub to verify
39
+ # write permission upfront because the same token will push later.
40
+ # The resulting comments/pushes are authored by that user, which DOES
41
+ # trigger downstream workflows AND naturally passes claude.yml's
42
+ # OWNER/MEMBER/COLLABORATOR trust gate. The same PAT serves all three
43
+ # bounces — Codex's webhook listener watches @-mentions in PR comments
44
+ # regardless of author.
45
+ #
46
+ # If AUTOFIX_TRIGGER_PAT is unset, both `gh pr comment` paths fail
47
+ # with an auth error and the chain breaks; rebase-stale-prs detects
48
+ # the missing secret and skips with a workflow warning. Manual
49
+ # @claude triggers are unaffected.
50
+ #
51
+ # Workflow-injection safety: every user/bot-controlled input (PR number,
52
+ # comment body, review id) is read via env: and referenced as a shell
53
+ # variable. Comment bodies posted to the PR are hardcoded literals — no
54
+ # interpolation of untrusted input into shell.
55
+
56
+ on:
57
+ pull_request_review:
58
+ types: [submitted]
59
+ pull_request:
60
+ # Bounce 2: new commits on a PR head -> request a Codex re-review.
61
+ # `synchronize` fires for pushes from ANY author — claude bot fixes,
62
+ # human / local-agent pushes, AND the auto-rebase force-push from
63
+ # rebase-stale-prs. App-authored pushes (claude[bot]) are NOT the
64
+ # workflow's GITHUB_TOKEN, so they are not recursion-suppressed and DO
65
+ # fire this event (verified: CI runs on claude's pushed commits).
66
+ types: [synchronize]
67
+ push:
68
+ # Bounce 0: main moved -> rebase open PRs that are now stale. Catches
69
+ # both PR merges AND direct pushes to main (manual hotfixes, manual
70
+ # main pushes). pull_request:closed+merged would miss the latter.
71
+ branches: [main]
72
+
73
+ jobs:
74
+ trigger-claude-via-comment:
75
+ # Bounce 1: Codex review submitted -> @claude addresses it.
76
+ if: github.event_name == 'pull_request_review' && github.event.review.user.login == 'chatgpt-codex-connector[bot]' && github.event.review.state == 'commented'
77
+ runs-on: ubuntu-latest
78
+ permissions:
79
+ pull-requests: write
80
+ env:
81
+ GH_TOKEN: ${{ secrets.AUTOFIX_TRIGGER_PAT }}
82
+ REPO: ${{ github.repository }}
83
+ PR_NUM: ${{ github.event.pull_request.number }}
84
+ REVIEW_ID: ${{ github.event.review.id }}
85
+ RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
86
+ steps:
87
+ # Cap the auto-address loop at 7 claude runs per PR. If claude has
88
+ # already run 7+ times and Codex is still finding issues, the loop
89
+ # isn't converging — force human attention rather than burn cycles.
90
+ #
91
+ # Default was 3 originally; bumped to 7 because legitimate Codex
92
+ # reviews on substantive PRs routinely surface 5-6 rounds of real
93
+ # findings (defensive type guards across sibling files, CLI-flag
94
+ # table extensions, OSV event-window edge cases). 3 was capping
95
+ # real review work, not just runaway loops; 7 keeps the safety
96
+ # net while accommodating multi-round legitimate review. Adjust
97
+ # MAX below if your project's review patterns differ.
98
+ #
99
+ # Count claude-code-action runs by counting top-level PR comments
100
+ # authored by the bot (the action posts ONE in-progress comment
101
+ # per run and edits it in place to the "Claude finished..." summary
102
+ # — 1 comment == 1 run). More accurate than counting bot commits:
103
+ # a run that lands 0 commits (no-op fix or pre-push failure) and a
104
+ # run that lands N commits both equal one iteration of the loop.
105
+ #
106
+ # Login string is "claude" — NOT "claude[bot]" — because
107
+ # `gh pr view --json comments` uses GraphQL, and GraphQL's
108
+ # `author.login` returns the bare bot name without the `[bot]`
109
+ # suffix. The `[bot]` suffix only appears in REST surfaces (e.g.
110
+ # `github.event.comment.user.login` at bounce-2 below). Same bot,
111
+ # two string representations depending on which API you read.
112
+ - name: Cap auto-address iterations (max 7)
113
+ run: |
114
+ MAX=7
115
+ CLAUDE_RUNS=$(gh pr view "$PR_NUM" -R "$REPO" --json comments \
116
+ --jq '[.comments[] | select(.author.login == "claude")] | length')
117
+ if [ "$CLAUDE_RUNS" -ge "$MAX" ]; then
118
+ gh pr comment "$PR_NUM" -R "$REPO" --body "🛑 Auto-address loop limit hit — claude has already run $CLAUDE_RUNS times on this PR (max $MAX). Codex review id $REVIEW_ID was NOT auto-addressed. Manual review needed; re-tag claude explicitly after addressing the underlying issue if you want to continue. ([workflow run]($RUN_URL))"
119
+ echo "::error::Auto-address loop limit hit ($CLAUDE_RUNS/$MAX) — see PR comment."
120
+ exit 1
121
+ fi
122
+ echo "Auto-address iteration $((CLAUDE_RUNS + 1)) of $MAX"
123
+
124
+ # Post the @claude trigger. The body intentionally references "the
125
+ # Codex review above" — claude-code-action's default prompt
126
+ # construction will include the full review thread so the bot has
127
+ # the actual findings + line context to act on.
128
+ #
129
+ # No HEAD-SHA marker is needed here: bounce 2 is now driven by
130
+ # `pull_request: synchronize` (a real push), so "claude finished
131
+ # without pushing" simply produces no synchronize and no re-review —
132
+ # the old comment-driven no-push detection it required is gone.
133
+ - name: Post @claude trigger comment
134
+ run: |
135
+ gh pr comment "$PR_NUM" -R "$REPO" --body "@claude please address the Codex review feedback above. CRITICAL: before applying any fix that depends on infrastructure or invariants you have not verified (DLQ, schema, contracts, ADRs in docs/adrs/), grep the repo for the prerequisite. If it is not present, push back via a PR comment explaining the missing context rather than implementing on a false premise."
136
+
137
+ request-codex-rereview-on-push:
138
+ # Bounce 2: new commits pushed to a PR head -> @codex re-reviews HEAD.
139
+ # `pull_request: synchronize` fires on EVERY push to the PR head
140
+ # regardless of author — the claude bot's fixes, a human / local-agent
141
+ # push, AND the auto-rebase force-push from rebase-stale-prs — so this
142
+ # single job covers all of them. Replaces the old issue_comment /
143
+ # "Claude finished" trigger: claude-code-action commits via the Claude
144
+ # GitHub App (author claude[bot]), and App-authored pushes fire
145
+ # pull_request events (they are not the workflow's GITHUB_TOKEN, so not
146
+ # recursion-suppressed). "Claude finished without pushing" needs no
147
+ # special case now — no commits means no synchronize means no review.
148
+ #
149
+ # Same-repo only: GitHub withholds secrets from pull_request runs on
150
+ # fork PRs, so AUTOFIX_TRIGGER_PAT would be empty and `gh pr comment`
151
+ # would fail. Excluding forks here keeps fork PRs from producing red
152
+ # runs (and claude.yml refuses fork PRs downstream anyway).
153
+ if: >
154
+ github.event_name == 'pull_request'
155
+ && github.event.pull_request.head.repo.full_name == github.repository
156
+ runs-on: ubuntu-latest
157
+ permissions:
158
+ pull-requests: write
159
+ concurrency:
160
+ # Coalesce rapid pushes to one review of the final SHA. Without this,
161
+ # 5 quick commits = 5 separate @codex reviews (each distinct SHA
162
+ # dodges the per-SHA marker). cancel-in-progress keeps only the
163
+ # latest push's job, which then reads the current HEAD.
164
+ group: codex-rereview-${{ github.event.pull_request.number }}
165
+ cancel-in-progress: true
166
+ env:
167
+ GH_TOKEN: ${{ secrets.AUTOFIX_TRIGGER_PAT }}
168
+ REPO: ${{ github.repository }}
169
+ PR_NUM: ${{ github.event.pull_request.number }}
170
+ steps:
171
+ # Per-SHA idempotency: skip if we already requested review for the
172
+ # current HEAD. Guards a double-synchronize on the same SHA and any
173
+ # overlap with a manual @codex review. Read live HEAD (not the event
174
+ # SHA) so a coalesced burst reviews the latest commit.
175
+ - name: Post @codex re-review trigger (idempotent per SHA)
176
+ run: |
177
+ HEAD_SHA=$(gh pr view "$PR_NUM" -R "$REPO" --json headRefOid --jq '.headRefOid')
178
+ MARKER="<!-- autofix-rereview-sha:$HEAD_SHA -->"
179
+ ALREADY=$(gh pr view "$PR_NUM" -R "$REPO" --json comments \
180
+ --jq "[.comments[] | select(.body | contains(\"$MARKER\"))] | length")
181
+ if [ "$ALREADY" -gt 0 ]; then
182
+ echo "Already requested @codex review for $HEAD_SHA — skipping."
183
+ exit 0
184
+ fi
185
+ gh pr comment "$PR_NUM" -R "$REPO" --body "$MARKER
186
+ @codex review the latest commits pushed to this PR."
187
+
188
+ rebase-stale-prs:
189
+ # Bounce 0: main moved -> for each open PR (same-repo only, fork PRs
190
+ # excluded), check mergeStateStatus and either rebase cleanly or ask
191
+ # @claude to resolve conflicts. Catches both PR-merge pushes and
192
+ # direct pushes to main.
193
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
194
+ runs-on: ubuntu-latest
195
+ permissions:
196
+ contents: write
197
+ pull-requests: write
198
+ concurrency:
199
+ # Coalesce: if main moves twice while we're still working on the
200
+ # first push, cancel and re-run with the latest SHA. Avoids racing
201
+ # two rebase runs against the same PR.
202
+ group: rebase-stale-prs
203
+ cancel-in-progress: true
204
+ env:
205
+ # Use the PAT for both gh API calls AND the git push. Pushing to a
206
+ # PR branch under GITHUB_TOKEN works but invalidates downstream
207
+ # workflow triggers (recursion guard) — the PAT keeps the @claude
208
+ # comment path working if this rebase later races with a Codex
209
+ # review on the same PR.
210
+ GH_TOKEN: ${{ secrets.AUTOFIX_TRIGGER_PAT }}
211
+ REPO: ${{ github.repository }}
212
+ MAIN_SHA: ${{ github.sha }}
213
+ steps:
214
+ # Graceful skip when the PAT secret isn't configured yet (initial
215
+ # repo setup). Without this guard, actions/checkout fails with
216
+ # "Input required and not supplied: token" on every push to main,
217
+ # turning every merge into a red workflow run during the window
218
+ # between "workflows scaffolded" and "secrets configured."
219
+ - id: have_pat
220
+ name: Check AUTOFIX_TRIGGER_PAT presence
221
+ env:
222
+ PAT: ${{ secrets.AUTOFIX_TRIGGER_PAT }}
223
+ run: |
224
+ if [ -z "$PAT" ]; then
225
+ echo "::warning::AUTOFIX_TRIGGER_PAT not set; skipping rebase. Add the secret per setup-autofix instructions to enable."
226
+ echo "skip=true" >> "$GITHUB_OUTPUT"
227
+ else
228
+ echo "skip=false" >> "$GITHUB_OUTPUT"
229
+ fi
230
+
231
+ - name: Checkout (full history for rebase)
232
+ if: steps.have_pat.outputs.skip != 'true'
233
+ uses: actions/checkout@v4
234
+ with:
235
+ fetch-depth: 0
236
+ token: ${{ secrets.AUTOFIX_TRIGGER_PAT }}
237
+
238
+ - name: Configure git author
239
+ if: steps.have_pat.outputs.skip != 'true'
240
+ run: |
241
+ git config user.email "actions@users.noreply.github.com"
242
+ git config user.name "github-actions[bot]"
243
+
244
+ - name: Rebase BEHIND PRs / flag DIRTY ones
245
+ if: steps.have_pat.outputs.skip != 'true'
246
+ run: |
247
+ set -euo pipefail
248
+
249
+ # mergeStateStatus values that matter:
250
+ # CLEAN - up to date with main (or ahead). skip.
251
+ # BEHIND - mergeable, base moved. clean rebase needed.
252
+ # DIRTY - conflicts. ask @claude.
253
+ # BLOCKED - branch protection or required checks failing. skip.
254
+ # UNSTABLE - mergeable but failing checks. skip — pre-existing.
255
+ # UNKNOWN - GitHub still computing. skip; next push retries.
256
+ #
257
+ # We exclude fork PRs: pushing to a fork branch from this workflow
258
+ # 403s, and posting @claude on a fork is blocked downstream by
259
+ # claude.yml's fork-refusal step anyway.
260
+ PRS_JSON=$(gh pr list --state open -R "$REPO" \
261
+ --json number,headRefName,mergeStateStatus,baseRefName,headRepository \
262
+ --jq "[.[] | select(.baseRefName == \"main\") | select(.headRepository.nameWithOwner == \"$REPO\")]")
263
+
264
+ echo "Open same-repo PRs targeting main:"
265
+ echo "$PRS_JSON" | jq -r '.[] | " #\(.number) [\(.mergeStateStatus)] \(.headRefName)"'
266
+
267
+ # Process each PR. Loop over JSON array via index so we can read
268
+ # only env-style fields and never interpolate untrusted data.
269
+ COUNT=$(echo "$PRS_JSON" | jq 'length')
270
+ for i in $(seq 0 $((COUNT - 1))); do
271
+ PR_NUM=$(echo "$PRS_JSON" | jq -r ".[$i].number")
272
+ BRANCH=$(echo "$PRS_JSON" | jq -r ".[$i].headRefName")
273
+ STATUS=$(echo "$PRS_JSON" | jq -r ".[$i].mergeStateStatus")
274
+
275
+ # Defensive: branch names are user-controlled. Refuse anything
276
+ # that isn't a sane ref. (`refs/heads/<name>` syntax: letters,
277
+ # digits, slashes, dashes, underscores, dots.)
278
+ if ! printf '%s' "$BRANCH" | grep -qE '^[A-Za-z0-9._/-]+$'; then
279
+ echo "::warning::PR #$PR_NUM has unsafe branch name; skipping"
280
+ continue
281
+ fi
282
+
283
+ case "$STATUS" in
284
+ BEHIND)
285
+ echo "PR #$PR_NUM ($BRANCH): clean rebase needed"
286
+ git fetch origin "$BRANCH":"refs/remotes/origin/$BRANCH" --force
287
+ git checkout -B "$BRANCH" "origin/$BRANCH"
288
+ if git rebase origin/main; then
289
+ if git push --force-with-lease origin "$BRANCH"; then
290
+ gh pr comment "$PR_NUM" -R "$REPO" \
291
+ --body "🔁 Auto-rebased on \`main\` (no conflicts) after $MAIN_SHA."
292
+ else
293
+ echo "::warning::Force-push of #$PR_NUM failed (lease lost?). Will retry on next push."
294
+ fi
295
+ else
296
+ # Race: GitHub said BEHIND but the rebase produced
297
+ # conflicts. The next push will see DIRTY and route to
298
+ # the @claude path.
299
+ git rebase --abort
300
+ echo "::warning::Rebase of #$PR_NUM failed despite BEHIND status (likely race with another push)"
301
+ fi
302
+ git checkout main
303
+ ;;
304
+ DIRTY)
305
+ echo "PR #$PR_NUM ($BRANCH): conflicts — asking @claude to rebase"
306
+ # Idempotency: don't re-ask claude for the same main SHA.
307
+ MARKER="<!-- autofix-rebase-claude:$MAIN_SHA -->"
308
+ ALREADY=$(gh pr view "$PR_NUM" -R "$REPO" --json comments \
309
+ --jq "[.comments[] | select(.body | contains(\"$MARKER\"))] | length")
310
+ if [ "$ALREADY" -gt 0 ]; then
311
+ echo " already asked @claude for $MAIN_SHA — skipping"
312
+ continue
313
+ fi
314
+ gh pr comment "$PR_NUM" -R "$REPO" --body "$MARKER
315
+ @claude main has moved and this PR has merge conflicts. Please rebase \`$BRANCH\` on \`origin/main\` and resolve any conflicts. Run the local pre-push gate before force-pushing. If the conflicts indicate the underlying approach is invalidated by the new main, push back via a comment instead of force-fitting a resolution."
316
+ ;;
317
+ CLEAN)
318
+ echo "PR #$PR_NUM ($BRANCH): already current — skipping"
319
+ ;;
320
+ *)
321
+ echo "PR #$PR_NUM ($BRANCH): status $STATUS — leaving alone"
322
+ ;;
323
+ esac
324
+ done
@@ -0,0 +1,135 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint-and-test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ # Skip the gates on chore/setup PRs that don't have Python sources yet.
15
+ # Without this, `uv sync --frozen` fails on a clean repo because there's
16
+ # no pyproject.toml/uv.lock to install from. Once the first pyproject.toml
17
+ # lands, the rest of the workflow runs.
18
+ - id: project_check
19
+ name: Detect Python project files
20
+ run: |
21
+ if [ -f pyproject.toml ] && [ -f uv.lock ]; then
22
+ echo "have_project=true" >> "$GITHUB_OUTPUT"
23
+ else
24
+ echo "have_project=false" >> "$GITHUB_OUTPUT"
25
+ echo "::notice::No pyproject.toml + uv.lock yet — skipping lint/test gates."
26
+ fi
27
+
28
+ - name: Install uv
29
+ if: steps.project_check.outputs.have_project == 'true'
30
+ uses: astral-sh/setup-uv@v6
31
+ with:
32
+ enable-cache: true
33
+ # Match pyproject.toml `requires-python`. Pinning here in CI catches
34
+ # any newer-syntax regressions that would break the lower bound.
35
+ # `python-version` here both installs that interpreter AND sets
36
+ # UV_PYTHON so every subsequent `uv sync`/`uv run` uses it.
37
+ python-version: "3.11"
38
+
39
+ - name: Sync deps
40
+ if: steps.project_check.outputs.have_project == 'true'
41
+ # `--frozen` forbids re-resolution: CI installs exactly what's in the
42
+ # committed uv.lock instead of silently picking up newer transitive
43
+ # versions. If uv.lock is missing or stale vs. pyproject.toml, this
44
+ # fails fast — exactly what we want in CI.
45
+ run: uv sync --frozen
46
+
47
+ - name: Ruff check
48
+ if: steps.project_check.outputs.have_project == 'true'
49
+ run: uv run ruff check .
50
+
51
+ - name: Ruff format check
52
+ if: steps.project_check.outputs.have_project == 'true'
53
+ run: uv run ruff format --check .
54
+
55
+ - name: Pyright
56
+ if: steps.project_check.outputs.have_project == 'true'
57
+ run: uv run pyright
58
+
59
+ - name: Pytest
60
+ if: steps.project_check.outputs.have_project == 'true'
61
+ run: uv run pytest --cov=tools --cov-report=term-missing
62
+
63
+ - name: Lint overlay corpus
64
+ if: steps.project_check.outputs.have_project == 'true'
65
+ run: |
66
+ if [ -d overlays ] && [ "$(find overlays -name '*.yaml' -print -quit)" ]; then
67
+ uv run openaca lint overlays/
68
+ else
69
+ echo "::notice::no overlays present yet; skipping openaca lint"
70
+ fi
71
+
72
+ # lint-and-test installs from the committed uv.lock (--frozen) — the *dev*
73
+ # env. End users run `uv tool install` from PyPI, which ignores uv.lock and
74
+ # re-resolves deps from this package's metadata (pyproject constraints). That
75
+ # path once shipped a dep pre-release that broke the CLI at runtime. This job
76
+ # exercises it: build the wheel, install with deps resolved fresh (mirroring
77
+ # scripts/install.sh's --prerelease=explicit), then run the user-facing
78
+ # commands — so a too-loose constraint fails CI here, not a user's shell.
79
+ # No network calls: the scan fixture has no OSV-queryable components, and
80
+ # the remote sync targets an unreachable port (per linter discipline,
81
+ # external APIs must not gate PRs).
82
+ smoke-install:
83
+ runs-on: ubuntu-latest
84
+ steps:
85
+ - uses: actions/checkout@v4
86
+ - name: Install uv
87
+ uses: astral-sh/setup-uv@v6
88
+ with:
89
+ python-version: "3.11"
90
+ - name: Build wheel
91
+ run: uv build --wheel
92
+ - name: Install the wheel with deps resolved fresh (no lock)
93
+ run: |
94
+ uv venv /tmp/smoke
95
+ uv pip install --python /tmp/smoke/bin/python --prerelease=explicit dist/*.whl
96
+ - name: Smoke — user-facing commands against freshly-resolved deps
97
+ run: |
98
+ set -eu
99
+ openaca=/tmp/smoke/bin/openaca
100
+
101
+ "$openaca" --version
102
+
103
+ # scan repo: parsers + matcher + renderer wiring. The remote-MCP
104
+ # fixture yields a real component but no OSV-queryable PURL.
105
+ mkdir -p /tmp/smoke-target
106
+ cat > /tmp/smoke-target/mcp.json <<'FIXTURE'
107
+ {"mcpServers": {"remote": {"type": "http", "url": "https://api.example.test/mcp"}}}
108
+ FIXTURE
109
+ "$openaca" scan repo --target /tmp/smoke-target --include-posture --fail-on none
110
+
111
+ # bom repo: BOM build + serialization wiring.
112
+ "$openaca" bom repo --target /tmp/smoke-target --output /tmp/smoke-bom.json
113
+ test -s /tmp/smoke-bom.json
114
+
115
+ # remote configure + sync: config, endpoint collection, and the
116
+ # HTTP client stack. The API is an unreachable port, so a healthy
117
+ # CLI exits 2 with its network-failure message; a dependency
118
+ # breakage surfaces as a crash (exit 1 / traceback) and fails here.
119
+ export HOME=/tmp/smoke-home
120
+ mkdir -p "$HOME"
121
+ OPENACA_REMOTE_TOKEN=ot_SMOKE "$openaca" remote configure --api-url http://127.0.0.1:9
122
+ set +e
123
+ out=$("$openaca" remote sync endpoint 2>&1)
124
+ code=$?
125
+ set -e
126
+ printf '%s\n' "$out"
127
+ if [ "$code" -ne 2 ]; then
128
+ echo "expected exit 2 (clean network failure), got $code" >&2
129
+ exit 1
130
+ fi
131
+ if printf '%s' "$out" | grep -q "Traceback"; then
132
+ echo "remote sync crashed instead of failing cleanly" >&2
133
+ exit 1
134
+ fi
135
+ echo "smoke ok: CLI surfaces work against freshly-resolved deps"