fhir-scorecard 0.3.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 (314) hide show
  1. fhir_scorecard-0.3.0/.gitattributes +30 -0
  2. fhir_scorecard-0.3.0/.github/CODEOWNERS +2 -0
  3. fhir_scorecard-0.3.0/.github/ISSUE_TEMPLATE/add-endpoint.yml +74 -0
  4. fhir_scorecard-0.3.0/.github/ISSUE_TEMPLATE/bug_report.yml +53 -0
  5. fhir_scorecard-0.3.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
  6. fhir_scorecard-0.3.0/.github/ISSUE_TEMPLATE/feature_request.yml +35 -0
  7. fhir_scorecard-0.3.0/.github/ISSUE_TEMPLATE/remove-or-dispute.yml +34 -0
  8. fhir_scorecard-0.3.0/.github/allowed_signers +1 -0
  9. fhir_scorecard-0.3.0/.github/dependabot.yml +42 -0
  10. fhir_scorecard-0.3.0/.github/workflows/compliance-bundle.yml +182 -0
  11. fhir_scorecard-0.3.0/.github/workflows/live-integrity.yml +85 -0
  12. fhir_scorecard-0.3.0/.github/workflows/pages.yml +235 -0
  13. fhir_scorecard-0.3.0/.github/workflows/pypi-publish.yml +286 -0
  14. fhir_scorecard-0.3.0/.github/workflows/recheck.yml +81 -0
  15. fhir_scorecard-0.3.0/.github/workflows/release.yml +236 -0
  16. fhir_scorecard-0.3.0/.github/workflows/scorecard.yml +65 -0
  17. fhir_scorecard-0.3.0/.github/workflows/security.yml +106 -0
  18. fhir_scorecard-0.3.0/.github/workflows/verify.yml +43 -0
  19. fhir_scorecard-0.3.0/.gitignore +22 -0
  20. fhir_scorecard-0.3.0/.pre-commit-config.yaml +50 -0
  21. fhir_scorecard-0.3.0/.python-version +1 -0
  22. fhir_scorecard-0.3.0/CHANGELOG.md +1486 -0
  23. fhir_scorecard-0.3.0/CITATION.cff +25 -0
  24. fhir_scorecard-0.3.0/CODE_OF_CONDUCT.md +26 -0
  25. fhir_scorecard-0.3.0/CONTRIBUTING.md +124 -0
  26. fhir_scorecard-0.3.0/LICENSE +202 -0
  27. fhir_scorecard-0.3.0/Makefile +65 -0
  28. fhir_scorecard-0.3.0/PKG-INFO +678 -0
  29. fhir_scorecard-0.3.0/README.md +655 -0
  30. fhir_scorecard-0.3.0/ROADMAP.md +464 -0
  31. fhir_scorecard-0.3.0/SECURITY.md +50 -0
  32. fhir_scorecard-0.3.0/action/render_result.py +174 -0
  33. fhir_scorecard-0.3.0/action.yml +106 -0
  34. fhir_scorecard-0.3.0/corpus/SOURCES.json +40 -0
  35. fhir_scorecard-0.3.0/corpus/hl7/fhir-r4-capabilitystatement.html +2241 -0
  36. fhir_scorecard-0.3.0/corpus/hl7/fhir-r4-http.html +1961 -0
  37. fhir_scorecard-0.3.0/corpus/hl7/smart-app-launch-conformance.html +588 -0
  38. fhir_scorecard-0.3.0/corpus/hl7/us-core-index.html +4652 -0
  39. fhir_scorecard-0.3.0/data/CANDIDATES.md +618 -0
  40. fhir_scorecard-0.3.0/data/bundle/plan.json +22 -0
  41. fhir_scorecard-0.3.0/data/cohorts/alabama-marketplace.json +73 -0
  42. fhir_scorecard-0.3.0/data/cohorts/alabama-marketplace.roster.csv +5 -0
  43. fhir_scorecard-0.3.0/data/cohorts/alaska-marketplace.json +51 -0
  44. fhir_scorecard-0.3.0/data/cohorts/alaska-marketplace.roster.csv +3 -0
  45. fhir_scorecard-0.3.0/data/cohorts/arizona-marketplace.json +113 -0
  46. fhir_scorecard-0.3.0/data/cohorts/arizona-marketplace.roster.csv +8 -0
  47. fhir_scorecard-0.3.0/data/cohorts/california.json +376 -0
  48. fhir_scorecard-0.3.0/data/cohorts/delaware-marketplace.json +62 -0
  49. fhir_scorecard-0.3.0/data/cohorts/delaware-marketplace.roster.csv +4 -0
  50. fhir_scorecard-0.3.0/data/cohorts/florida-marketplace.json +234 -0
  51. fhir_scorecard-0.3.0/data/cohorts/florida-marketplace.roster.csv +16 -0
  52. fhir_scorecard-0.3.0/data/cohorts/indiana-marketplace.json +80 -0
  53. fhir_scorecard-0.3.0/data/cohorts/indiana-marketplace.roster.csv +6 -0
  54. fhir_scorecard-0.3.0/data/cohorts/iowa-marketplace.json +106 -0
  55. fhir_scorecard-0.3.0/data/cohorts/iowa-marketplace.roster.csv +7 -0
  56. fhir_scorecard-0.3.0/data/cohorts/kansas-marketplace.json +101 -0
  57. fhir_scorecard-0.3.0/data/cohorts/kansas-marketplace.roster.csv +7 -0
  58. fhir_scorecard-0.3.0/data/cohorts/louisiana-marketplace.json +95 -0
  59. fhir_scorecard-0.3.0/data/cohorts/louisiana-marketplace.roster.csv +7 -0
  60. fhir_scorecard-0.3.0/data/cohorts/michigan-marketplace.json +112 -0
  61. fhir_scorecard-0.3.0/data/cohorts/michigan-marketplace.roster.csv +8 -0
  62. fhir_scorecard-0.3.0/data/cohorts/mississippi-marketplace.json +90 -0
  63. fhir_scorecard-0.3.0/data/cohorts/mississippi-marketplace.roster.csv +6 -0
  64. fhir_scorecard-0.3.0/data/cohorts/missouri-marketplace.json +113 -0
  65. fhir_scorecard-0.3.0/data/cohorts/missouri-marketplace.roster.csv +8 -0
  66. fhir_scorecard-0.3.0/data/cohorts/nebraska-marketplace.json +89 -0
  67. fhir_scorecard-0.3.0/data/cohorts/nebraska-marketplace.roster.csv +6 -0
  68. fhir_scorecard-0.3.0/data/cohorts/north-carolina-marketplace.json +97 -0
  69. fhir_scorecard-0.3.0/data/cohorts/north-carolina-marketplace.roster.csv +7 -0
  70. fhir_scorecard-0.3.0/data/cohorts/ohio-marketplace.json +174 -0
  71. fhir_scorecard-0.3.0/data/cohorts/ohio-marketplace.roster.csv +12 -0
  72. fhir_scorecard-0.3.0/data/cohorts/oklahoma-marketplace.json +118 -0
  73. fhir_scorecard-0.3.0/data/cohorts/oklahoma-marketplace.roster.csv +8 -0
  74. fhir_scorecard-0.3.0/data/cohorts/texas-marketplace.json +249 -0
  75. fhir_scorecard-0.3.0/data/cohorts/texas-marketplace.roster.csv +16 -0
  76. fhir_scorecard-0.3.0/data/cohorts/west-virginia-marketplace.json +51 -0
  77. fhir_scorecard-0.3.0/data/cohorts/west-virginia-marketplace.roster.csv +3 -0
  78. fhir_scorecard-0.3.0/data/cohorts/wisconsin-marketplace.json +173 -0
  79. fhir_scorecard-0.3.0/data/cohorts/wisconsin-marketplace.roster.csv +13 -0
  80. fhir_scorecard-0.3.0/data/cohorts/wyoming-marketplace.json +50 -0
  81. fhir_scorecard-0.3.0/data/cohorts/wyoming-marketplace.roster.csv +3 -0
  82. fhir_scorecard-0.3.0/data/frames/qhp-landscape-py2026-individual-medical.csv +177 -0
  83. fhir_scorecard-0.3.0/data/history.json +500 -0
  84. fhir_scorecard-0.3.0/data/registry.json +1159 -0
  85. fhir_scorecard-0.3.0/data/rejected.json +131 -0
  86. fhir_scorecard-0.3.0/docs/I18N.md +26 -0
  87. fhir_scorecard-0.3.0/docs/PR-TRIAGE.md +503 -0
  88. fhir_scorecard-0.3.0/docs/RESPONSIBLE-TECH-AUDITS.md +148 -0
  89. fhir_scorecard-0.3.0/docs/SAMPLING-FRAME.md +197 -0
  90. fhir_scorecard-0.3.0/docs/adr/0000-record-architecture-decisions.md +30 -0
  91. fhir_scorecard-0.3.0/docs/adr/0001-release-versioning-na.md +50 -0
  92. fhir_scorecard-0.3.0/docs/adr/0002-release-versioning-applies-action-export.md +91 -0
  93. fhir_scorecard-0.3.0/docs/adr/0003-ai-narration-outside-the-graded-path.md +86 -0
  94. fhir_scorecard-0.3.0/docs/adr/0004-accessibility-and-weight-gates-without-a-browser.md +121 -0
  95. fhir_scorecard-0.3.0/docs/adr/0005-a-leaderboard-that-publishes-what-meets-the-floor.md +79 -0
  96. fhir_scorecard-0.3.0/docs/adr/0006-google-analytics-4.md +79 -0
  97. fhir_scorecard-0.3.0/docs/adr/0007-bundle-conversion-events.md +84 -0
  98. fhir_scorecard-0.3.0/docs/adr/0008-grade-history.md +112 -0
  99. fhir_scorecard-0.3.0/docs/adr/0009-readiness-report-regulatory-statements.md +57 -0
  100. fhir_scorecard-0.3.0/docs/ci-action.md +143 -0
  101. fhir_scorecard-0.3.0/docs/compliance-bundle-owner-steps.md +257 -0
  102. fhir_scorecard-0.3.0/docs/compliance-bundle-plan.md +188 -0
  103. fhir_scorecard-0.3.0/docs/findings/2026-08-15-anthem-multi-tenant-attribution.md +136 -0
  104. fhir_scorecard-0.3.0/docs/findings/2026-08-15-california-payer-cohort.json +173 -0
  105. fhir_scorecard-0.3.0/docs/findings/2026-08-15-california-payer-cohort.md +292 -0
  106. fhir_scorecard-0.3.0/docs/mcp-registry.md +94 -0
  107. fhir_scorecard-0.3.0/docs/payer-verifiability.md +138 -0
  108. fhir_scorecard-0.3.0/evals/ai/README.md +14 -0
  109. fhir_scorecard-0.3.0/evals/ai/results/2026-08-21-narration-bedrock-claude-sonnet-4-6.json +2018 -0
  110. fhir_scorecard-0.3.0/infra/compliance-bundle/.terraform.lock.hcl +46 -0
  111. fhir_scorecard-0.3.0/infra/compliance-bundle/backend.tf +14 -0
  112. fhir_scorecard-0.3.0/infra/compliance-bundle/common.py +527 -0
  113. fhir_scorecard-0.3.0/infra/compliance-bundle/main.tf +624 -0
  114. fhir_scorecard-0.3.0/infra/compliance-bundle/setup_handler.py +610 -0
  115. fhir_scorecard-0.3.0/infra/compliance-bundle/webhook_handler.py +188 -0
  116. fhir_scorecard-0.3.0/pyproject.toml +115 -0
  117. fhir_scorecard-0.3.0/scripts/build-lambda-package.sh +79 -0
  118. fhir_scorecard-0.3.0/scripts/stripe-setup.sh +138 -0
  119. fhir_scorecard-0.3.0/server.json +37 -0
  120. fhir_scorecard-0.3.0/src/fhir_scorecard/__init__.py +3 -0
  121. fhir_scorecard-0.3.0/src/fhir_scorecard/accessibility.py +341 -0
  122. fhir_scorecard-0.3.0/src/fhir_scorecard/ai/__init__.py +8 -0
  123. fhir_scorecard-0.3.0/src/fhir_scorecard/ai/corpus.py +278 -0
  124. fhir_scorecard-0.3.0/src/fhir_scorecard/ai/eval.py +207 -0
  125. fhir_scorecard-0.3.0/src/fhir_scorecard/ai/narrate.py +442 -0
  126. fhir_scorecard-0.3.0/src/fhir_scorecard/ai/provider.py +196 -0
  127. fhir_scorecard-0.3.0/src/fhir_scorecard/ai/retrieval.py +94 -0
  128. fhir_scorecard-0.3.0/src/fhir_scorecard/analytics.py +379 -0
  129. fhir_scorecard-0.3.0/src/fhir_scorecard/archive.py +589 -0
  130. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/bundle-setup.js +157 -0
  131. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/bundle.js +52 -0
  132. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/favicon.svg +6 -0
  133. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/site.css +445 -0
  134. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/social-card.png +0 -0
  135. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/VERSION.txt +13 -0
  136. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/css/uswds.min.css +4 -0
  137. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/merriweather/Latin-Merriweather-Bold.woff2 +0 -0
  138. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/merriweather/Latin-Merriweather-BoldItalic.woff2 +0 -0
  139. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/merriweather/Latin-Merriweather-Italic.woff2 +0 -0
  140. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/merriweather/Latin-Merriweather-Light.woff2 +0 -0
  141. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/merriweather/Latin-Merriweather-LightItalic.woff2 +0 -0
  142. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/merriweather/Latin-Merriweather-Regular.woff2 +0 -0
  143. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/roboto-mono/roboto-mono-v5-latin-300.woff2 +0 -0
  144. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/roboto-mono/roboto-mono-v5-latin-300italic.woff2 +0 -0
  145. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/roboto-mono/roboto-mono-v5-latin-700.woff2 +0 -0
  146. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/roboto-mono/roboto-mono-v5-latin-700italic.woff2 +0 -0
  147. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/roboto-mono/roboto-mono-v5-latin-italic.woff2 +0 -0
  148. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/roboto-mono/roboto-mono-v5-latin-regular.woff2 +0 -0
  149. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/source-sans-pro/sourcesanspro-bold-webfont.woff2 +0 -0
  150. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/source-sans-pro/sourcesanspro-bolditalic-webfont.woff2 +0 -0
  151. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/source-sans-pro/sourcesanspro-italic-webfont.woff2 +0 -0
  152. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/source-sans-pro/sourcesanspro-light-webfont.woff2 +0 -0
  153. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/source-sans-pro/sourcesanspro-lightitalic-webfont.woff2 +0 -0
  154. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/fonts/source-sans-pro/sourcesanspro-regular-webfont.woff2 +0 -0
  155. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/checkbox-indeterminate-alt.svg +1 -0
  156. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/checkbox-indeterminate.svg +1 -0
  157. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/correct8-alt.svg +1 -0
  158. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/correct8.svg +1 -0
  159. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/file-excel.svg +1 -0
  160. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/file-pdf.svg +1 -0
  161. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/file-video.svg +1 -0
  162. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/file-word.svg +1 -0
  163. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/file.svg +1 -0
  164. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/hero.jpg +0 -0
  165. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/loader.svg +1 -0
  166. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/sprite.svg +1 -0
  167. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/add.svg +1 -0
  168. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/arrow_back.svg +1 -0
  169. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/calendar_today.svg +1 -0
  170. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/check_circle.svg +1 -0
  171. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/close.svg +1 -0
  172. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/error.svg +1 -0
  173. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/expand_less.svg +1 -0
  174. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/expand_more.svg +1 -0
  175. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/info.svg +1 -0
  176. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/launch.svg +1 -0
  177. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/navigate_before.svg +1 -0
  178. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/navigate_far_before.svg +1 -0
  179. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/navigate_far_next.svg +1 -0
  180. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/navigate_next.svg +1 -0
  181. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/remove.svg +1 -0
  182. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/search.svg +1 -0
  183. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/unfold_more.svg +1 -0
  184. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons/warning.svg +1 -0
  185. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons-bg/check--blue-60v.svg +1 -0
  186. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons-bg/error--white.svg +1 -0
  187. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/img/usa-icons-bg/launch--white.svg +1 -0
  188. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/js/uswds-init.min.js +2 -0
  189. fhir_scorecard-0.3.0/src/fhir_scorecard/assets/uswds/js/uswds.min.js +84 -0
  190. fhir_scorecard-0.3.0/src/fhir_scorecard/audit.py +683 -0
  191. fhir_scorecard-0.3.0/src/fhir_scorecard/backend.py +284 -0
  192. fhir_scorecard-0.3.0/src/fhir_scorecard/bundle.py +541 -0
  193. fhir_scorecard-0.3.0/src/fhir_scorecard/bundle_order.py +194 -0
  194. fhir_scorecard-0.3.0/src/fhir_scorecard/bundle_report.py +332 -0
  195. fhir_scorecard-0.3.0/src/fhir_scorecard/capability.py +427 -0
  196. fhir_scorecard-0.3.0/src/fhir_scorecard/ci_report.py +319 -0
  197. fhir_scorecard-0.3.0/src/fhir_scorecard/cli.py +2083 -0
  198. fhir_scorecard-0.3.0/src/fhir_scorecard/cohort.py +313 -0
  199. fhir_scorecard-0.3.0/src/fhir_scorecard/cohort_matrix.py +263 -0
  200. fhir_scorecard-0.3.0/src/fhir_scorecard/concentration.py +439 -0
  201. fhir_scorecard-0.3.0/src/fhir_scorecard/conditions.py +160 -0
  202. fhir_scorecard-0.3.0/src/fhir_scorecard/coverage.py +542 -0
  203. fhir_scorecard-0.3.0/src/fhir_scorecard/dataset.py +371 -0
  204. fhir_scorecard-0.3.0/src/fhir_scorecard/deadline.py +227 -0
  205. fhir_scorecard-0.3.0/src/fhir_scorecard/diff.py +539 -0
  206. fhir_scorecard-0.3.0/src/fhir_scorecard/drift.py +662 -0
  207. fhir_scorecard-0.3.0/src/fhir_scorecard/entity_report.py +552 -0
  208. fhir_scorecard-0.3.0/src/fhir_scorecard/feeds.py +544 -0
  209. fhir_scorecard-0.3.0/src/fhir_scorecard/fetch.py +510 -0
  210. fhir_scorecard-0.3.0/src/fhir_scorecard/gate.py +61 -0
  211. fhir_scorecard-0.3.0/src/fhir_scorecard/grade_history.py +314 -0
  212. fhir_scorecard-0.3.0/src/fhir_scorecard/grading.py +845 -0
  213. fhir_scorecard-0.3.0/src/fhir_scorecard/intake.py +666 -0
  214. fhir_scorecard-0.3.0/src/fhir_scorecard/leaderboard.py +185 -0
  215. fhir_scorecard-0.3.0/src/fhir_scorecard/matrix.py +635 -0
  216. fhir_scorecard-0.3.0/src/fhir_scorecard/mcp.py +346 -0
  217. fhir_scorecard-0.3.0/src/fhir_scorecard/operator.py +127 -0
  218. fhir_scorecard-0.3.0/src/fhir_scorecard/over_time.py +412 -0
  219. fhir_scorecard-0.3.0/src/fhir_scorecard/published.py +735 -0
  220. fhir_scorecard-0.3.0/src/fhir_scorecard/py.typed +0 -0
  221. fhir_scorecard-0.3.0/src/fhir_scorecard/readiness.py +482 -0
  222. fhir_scorecard-0.3.0/src/fhir_scorecard/registry.py +207 -0
  223. fhir_scorecard-0.3.0/src/fhir_scorecard/report.py +48 -0
  224. fhir_scorecard-0.3.0/src/fhir_scorecard/reprobe.py +129 -0
  225. fhir_scorecard-0.3.0/src/fhir_scorecard/reverify.py +419 -0
  226. fhir_scorecard-0.3.0/src/fhir_scorecard/site.py +2432 -0
  227. fhir_scorecard-0.3.0/src/fhir_scorecard/snapshot.py +188 -0
  228. fhir_scorecard-0.3.0/src/fhir_scorecard/statistics.py +220 -0
  229. fhir_scorecard-0.3.0/src/fhir_scorecard/vantage.py +620 -0
  230. fhir_scorecard-0.3.0/src/fhir_scorecard/weight.py +128 -0
  231. fhir_scorecard-0.3.0/tests/conftest.py +80 -0
  232. fhir_scorecard-0.3.0/tests/fixtures/README.md +89 -0
  233. fhir_scorecard-0.3.0/tests/fixtures/ai/not-narratable.json +48 -0
  234. fhir_scorecard-0.3.0/tests/fixtures/ai/scorecards.json +1998 -0
  235. fhir_scorecard-0.3.0/tests/fixtures/aspirus-patient-access/refusal.json +11 -0
  236. fhir_scorecard-0.3.0/tests/fixtures/bcbs-arizona-patient-access/refusal.json +11 -0
  237. fhir_scorecard-0.3.0/tests/fixtures/cms-blue-button-2/metadata.json +253 -0
  238. fhir_scorecard-0.3.0/tests/fixtures/cms-blue-button-2/smart.json +36 -0
  239. fhir_scorecard-0.3.0/tests/fixtures/inferno-reference/metadata.json +1085 -0
  240. fhir_scorecard-0.3.0/tests/fixtures/inferno-reference/smart.json +513 -0
  241. fhir_scorecard-0.3.0/tests/fixtures/operator-registry.json +24 -0
  242. fhir_scorecard-0.3.0/tests/fixtures/oracle-health-open/metadata.json +1883 -0
  243. fhir_scorecard-0.3.0/tests/fixtures/registry.json +66 -0
  244. fhir_scorecard-0.3.0/tests/test_absence_cannot_raise_a_grade.py +326 -0
  245. fhir_scorecard-0.3.0/tests/test_accessibility_and_weight.py +423 -0
  246. fhir_scorecard-0.3.0/tests/test_ai_narration.py +570 -0
  247. fhir_scorecard-0.3.0/tests/test_alternation.py +357 -0
  248. fhir_scorecard-0.3.0/tests/test_analytics.py +721 -0
  249. fhir_scorecard-0.3.0/tests/test_app_to_server_wiring.py +218 -0
  250. fhir_scorecard-0.3.0/tests/test_archive.py +534 -0
  251. fhir_scorecard-0.3.0/tests/test_asked_and_not_served.py +411 -0
  252. fhir_scorecard-0.3.0/tests/test_audit_noindex.py +62 -0
  253. fhir_scorecard-0.3.0/tests/test_backend.py +240 -0
  254. fhir_scorecard-0.3.0/tests/test_bundle.py +545 -0
  255. fhir_scorecard-0.3.0/tests/test_bundle_order.py +251 -0
  256. fhir_scorecard-0.3.0/tests/test_bundle_report.py +240 -0
  257. fhir_scorecard-0.3.0/tests/test_bundle_scripts.py +193 -0
  258. fhir_scorecard-0.3.0/tests/test_capability.py +51 -0
  259. fhir_scorecard-0.3.0/tests/test_ci_action.py +517 -0
  260. fhir_scorecard-0.3.0/tests/test_ci_report.py +613 -0
  261. fhir_scorecard-0.3.0/tests/test_cli_and_report.py +839 -0
  262. fhir_scorecard-0.3.0/tests/test_cli_bundle_command.py +266 -0
  263. fhir_scorecard-0.3.0/tests/test_cohort.py +579 -0
  264. fhir_scorecard-0.3.0/tests/test_cohort_matrix.py +291 -0
  265. fhir_scorecard-0.3.0/tests/test_compliance_bundle_contract.py +177 -0
  266. fhir_scorecard-0.3.0/tests/test_compliance_bundle_infra.py +970 -0
  267. fhir_scorecard-0.3.0/tests/test_concentration.py +309 -0
  268. fhir_scorecard-0.3.0/tests/test_conditions.py +533 -0
  269. fhir_scorecard-0.3.0/tests/test_coverage.py +437 -0
  270. fhir_scorecard-0.3.0/tests/test_dataset.py +135 -0
  271. fhir_scorecard-0.3.0/tests/test_deadline.py +148 -0
  272. fhir_scorecard-0.3.0/tests/test_diff.py +584 -0
  273. fhir_scorecard-0.3.0/tests/test_drift.py +275 -0
  274. fhir_scorecard-0.3.0/tests/test_entity_report.py +707 -0
  275. fhir_scorecard-0.3.0/tests/test_failure_kinds.py +610 -0
  276. fhir_scorecard-0.3.0/tests/test_feeds.py +544 -0
  277. fhir_scorecard-0.3.0/tests/test_fetch_bytes.py +104 -0
  278. fhir_scorecard-0.3.0/tests/test_findings_evidence.py +325 -0
  279. fhir_scorecard-0.3.0/tests/test_grade_history.py +486 -0
  280. fhir_scorecard-0.3.0/tests/test_grading.py +572 -0
  281. fhir_scorecard-0.3.0/tests/test_headline_counts.py +399 -0
  282. fhir_scorecard-0.3.0/tests/test_intake.py +587 -0
  283. fhir_scorecard-0.3.0/tests/test_leaderboard.py +312 -0
  284. fhir_scorecard-0.3.0/tests/test_live_integrity_pending_publish.py +434 -0
  285. fhir_scorecard-0.3.0/tests/test_live_site_check.py +315 -0
  286. fhir_scorecard-0.3.0/tests/test_matrix.py +733 -0
  287. fhir_scorecard-0.3.0/tests/test_mcp.py +146 -0
  288. fhir_scorecard-0.3.0/tests/test_mcp_registry_manifest.py +115 -0
  289. fhir_scorecard-0.3.0/tests/test_not_observed.py +337 -0
  290. fhir_scorecard-0.3.0/tests/test_offline_mode.py +227 -0
  291. fhir_scorecard-0.3.0/tests/test_over_time.py +348 -0
  292. fhir_scorecard-0.3.0/tests/test_plan_evidence.py +391 -0
  293. fhir_scorecard-0.3.0/tests/test_probe_contract.py +537 -0
  294. fhir_scorecard-0.3.0/tests/test_profile_declarations.py +140 -0
  295. fhir_scorecard-0.3.0/tests/test_published_grades.py +560 -0
  296. fhir_scorecard-0.3.0/tests/test_published_states.py +382 -0
  297. fhir_scorecard-0.3.0/tests/test_readiness.py +397 -0
  298. fhir_scorecard-0.3.0/tests/test_registry_and_fetch.py +388 -0
  299. fhir_scorecard-0.3.0/tests/test_reprobe.py +153 -0
  300. fhir_scorecard-0.3.0/tests/test_reverify.py +468 -0
  301. fhir_scorecard-0.3.0/tests/test_shipped_code_is_gated.py +138 -0
  302. fhir_scorecard-0.3.0/tests/test_site.py +602 -0
  303. fhir_scorecard-0.3.0/tests/test_site_audit.py +799 -0
  304. fhir_scorecard-0.3.0/tests/test_site_bundle_pages.py +297 -0
  305. fhir_scorecard-0.3.0/tests/test_snapshot.py +240 -0
  306. fhir_scorecard-0.3.0/tests/test_stated_counts.py +324 -0
  307. fhir_scorecard-0.3.0/tests/test_statistics.py +214 -0
  308. fhir_scorecard-0.3.0/tests/test_status_claims.py +144 -0
  309. fhir_scorecard-0.3.0/tests/test_three_states_and_vantages.py +541 -0
  310. fhir_scorecard-0.3.0/tests/test_unreadable_capability.py +350 -0
  311. fhir_scorecard-0.3.0/tests/test_vantage.py +628 -0
  312. fhir_scorecard-0.3.0/tests/test_workflow_shell_safety.py +453 -0
  313. fhir_scorecard-0.3.0/tools/verify_live_site.py +945 -0
  314. fhir_scorecard-0.3.0/uv.lock +1456 -0
@@ -0,0 +1,30 @@
1
+
2
+ # CI is the authoritative writer of the drift/availability history: it runs daily from a
3
+ # stable vantage and commits what it observed. Local grade runs overwrite it, which produced a
4
+ # rebase conflict every time. Union-merge keeps both sides rather than failing the rebase; CI
5
+ # rewrites the file wholesale on its next run regardless.
6
+ data/history.json merge=union
7
+
8
+ # GitHub Actions downloads a source archive for every `uses:` invocation. Keep that archive to
9
+ # the Action runtime instead of shipping the curated registry, the cohort files, the captured
10
+ # fixtures, and the test suite to every consumer. The published dataset lives on the site; a
11
+ # consumer gating their own endpoint has no use for a copy of it.
12
+ /* export-ignore
13
+ /action -export-ignore
14
+ /action.yml -export-ignore
15
+ /CODE_OF_CONDUCT.md -export-ignore
16
+ /LICENSE -export-ignore
17
+ /pyproject.toml -export-ignore
18
+ /README.md -export-ignore
19
+ /SECURITY.md -export-ignore
20
+ /src -export-ignore
21
+ # The vendored design system (stylesheets, fonts, icons) exists to build the site. The Action
22
+ # runtime a consumer downloads runs `check` and never builds a site, so the 50-odd asset files
23
+ # stay out of the archive the same way the dataset does. A checkout has them; the archive is
24
+ # the Action runtime, bounded on purpose.
25
+ /src/fhir_scorecard/assets export-ignore
26
+
27
+ # Retained specification pages are evidence: keep their bytes exactly as fetched so the
28
+ # SHA-256 values in corpus/SOURCES.json stay true on every checkout. They serve the optional
29
+ # narration layer, not the Action runtime, so they stay out of the archive with the dataset.
30
+ /corpus/** -text
@@ -0,0 +1,2 @@
1
+ # Default owner for everything in the repository.
2
+ * @ChelseaKR
@@ -0,0 +1,74 @@
1
+ name: Add or correct an endpoint
2
+ description: Tell us about a public FHIR endpoint we are missing or have wrong
3
+ title: "[endpoint] "
4
+ labels: ["registry"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Thank you. If we recorded your endpoint as unreachable and it is not, we would rather be
10
+ corrected than counted right. This has already happened once: a live payer endpoint was
11
+ logged as dead because a middlebox on the probing network intercepted TLS.
12
+
13
+ Only public, unauthenticated surfaces are graded. We never register for API access,
14
+ never authenticate, and never request patient data.
15
+ - type: input
16
+ id: organization
17
+ attributes:
18
+ label: Organization
19
+ placeholder: Example Health Plan
20
+ validations:
21
+ required: true
22
+ - type: input
23
+ id: base_url
24
+ attributes:
25
+ label: FHIR base URL
26
+ description: "The base, not the metadata path. We append /metadata ourselves."
27
+ placeholder: https://fhir.example.com/r4
28
+ validations:
29
+ required: true
30
+ - type: dropdown
31
+ id: kind
32
+ attributes:
33
+ label: Category
34
+ description: Grades are only comparable within a category.
35
+ options:
36
+ - Payer Patient Access API
37
+ - Payer Provider Directory API
38
+ - Provider or health system API
39
+ - EHR vendor sandbox
40
+ - Reference or test server
41
+ validations:
42
+ required: true
43
+ - type: dropdown
44
+ id: fhir_release
45
+ attributes:
46
+ label: FHIR release
47
+ description: Endpoints are graded against the release they intend to serve.
48
+ options: ["R4", "R5", "STU3"]
49
+ validations:
50
+ required: true
51
+ - type: input
52
+ id: documentation
53
+ attributes:
54
+ label: Public documentation URL
55
+ description: >
56
+ Where this base URL is published. This is how we confirm the publisher is who the entry
57
+ claims, which is what "verified" means here. Entries without it are not added.
58
+ placeholder: https://example.com/developers
59
+ validations:
60
+ required: true
61
+ - type: textarea
62
+ id: context
63
+ attributes:
64
+ label: Anything else
65
+ description: Corrections to an existing entry, or context on what we got wrong.
66
+ - type: checkboxes
67
+ id: confirm
68
+ attributes:
69
+ label: Please confirm
70
+ options:
71
+ - label: This base URL is intended to be publicly reachable without authentication
72
+ required: true
73
+ - label: I am not asking you to authenticate, register, or request patient data
74
+ required: true
@@ -0,0 +1,53 @@
1
+ name: Bug report
2
+ description: Something behaves differently than documented
3
+ labels: ["bug"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Thanks for taking the time. The most useful reports say what you ran,
9
+ what happened, and what you expected instead.
10
+
11
+ If this is a security vulnerability, please do not file it here. See
12
+ SECURITY.md for how to report it privately.
13
+ - type: textarea
14
+ id: what-happened
15
+ attributes:
16
+ label: What happened
17
+ description: >-
18
+ What you ran and what came back. Paste the output rather than describing it.
19
+ validations:
20
+ required: true
21
+ - type: textarea
22
+ id: expected
23
+ attributes:
24
+ label: What you expected instead
25
+ description: >-
26
+ If a rule or spec section says otherwise, quoting it helps a lot.
27
+ validations:
28
+ required: true
29
+ - type: textarea
30
+ id: input
31
+ attributes:
32
+ label: The endpoint
33
+ description: >-
34
+ The endpoint URL as it appears in the registry, if it is public.
35
+ render: text
36
+ validations:
37
+ required: false
38
+ - type: input
39
+ id: version
40
+ attributes:
41
+ label: Version
42
+ description: >-
43
+ The scorecard page or commit you saw this on
44
+ validations:
45
+ required: true
46
+ - type: textarea
47
+ id: anything-else
48
+ attributes:
49
+ label: Anything else
50
+ description: >-
51
+ Environment, workarounds you tried, related issues.
52
+ validations:
53
+ required: false
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: How grades are calculated
4
+ url: https://fhir.chelseakr.com/how-we-grade/
5
+ about: Every finding code, what it checks, and the spec clause it cites
6
+ - name: Why some payers cannot be checked publicly
7
+ url: https://github.com/ChelseaKR/fhir-scorecard/blob/main/docs/payer-verifiability.md
8
+ about: The write-up, including the measurement errors this project made and corrected
@@ -0,0 +1,35 @@
1
+ name: Idea or feature request
2
+ description: Suggest something this should do that it does not
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Ideas are welcome, including ones that turn out to be a bad fit. If you
9
+ are unsure whether something is a bug or intended behavior, open a
10
+ discussion instead and we can work it out there.
11
+ - type: textarea
12
+ id: problem
13
+ attributes:
14
+ label: The problem
15
+ description: >-
16
+ What are you trying to do that is hard or impossible right now?
17
+ validations:
18
+ required: true
19
+ - type: textarea
20
+ id: proposal
21
+ attributes:
22
+ label: What you have in mind
23
+ description: >-
24
+ A rough shape is fine. Alternatives you considered are useful too.
25
+ validations:
26
+ required: false
27
+ - type: checkboxes
28
+ id: willing
29
+ attributes:
30
+ label: Would you want to build it?
31
+ description: >-
32
+ Entirely optional, and a no is a perfectly good answer.
33
+ options:
34
+ - label: I would be interested in opening a PR for this
35
+ required: false
@@ -0,0 +1,34 @@
1
+ name: Dispute or remove an entry
2
+ description: Ask us to correct or take down something about your organization
3
+ title: "[dispute] "
4
+ labels: ["registry", "dispute"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ This project measures public surfaces and takes a non-adversarial posture. If an entry is
10
+ wrong, or you would rather not be listed, say so and we will correct or remove it.
11
+ We do not require you to prove anything first.
12
+ - type: input
13
+ id: endpoint
14
+ attributes:
15
+ label: Which entry
16
+ placeholder: "endpoint id or organization name"
17
+ validations:
18
+ required: true
19
+ - type: dropdown
20
+ id: request
21
+ attributes:
22
+ label: What are you asking for
23
+ options:
24
+ - Correct a fact
25
+ - Remove the entry
26
+ - Something else
27
+ validations:
28
+ required: true
29
+ - type: textarea
30
+ id: detail
31
+ attributes:
32
+ label: Details
33
+ validations:
34
+ required: true
@@ -0,0 +1 @@
1
+ 3114598+ChelseaKR@users.noreply.github.com namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIOjn/aI0d9jElCoi7qT4+6j9UmgTH7vLGAqJXiKMoCE
@@ -0,0 +1,42 @@
1
+ # Dependency scanning and update PRs (SECURITY-AND-SUPPLY-CHAIN-STANDARD).
2
+ # Runtime dependencies are stdlib-only; this watches the dev toolchain and the
3
+ # SHA-pinned GitHub Actions.
4
+ version: 2
5
+ updates:
6
+ # `uv`, not `pip`. This project's dependency surface is a PEP 735 `[dependency-groups]`
7
+ # block resolved into `uv.lock`; `[project] dependencies` is empty. The `pip` ecosystem
8
+ # does not read `[dependency-groups]`, and a bump that did not also update `uv.lock`
9
+ # would fail `make verify`, which opens with `uv lock --check`.
10
+ #
11
+ # The symptom was silence, not red PRs. Every dependabot pull request ever opened on this
12
+ # repository - twelve of them, 2026-08-07 to 2026-08-27, on a weekly schedule - is
13
+ # `github-actions`. Zero are `pip`, across a window in which pytest, mypy, ruff, pip-audit
14
+ # and hypothesis all released. The dev toolchain the README calls "the whole dependency
15
+ # surface" was unwatched, and nothing said so.
16
+ #
17
+ # `uv` is a documented `package-ecosystem` value: GitHub's Dependabot options reference
18
+ # lists it, and version updates went generally available for it on 2025-03-13, security
19
+ # updates on 2025-12-16. `uv.lock` is committed, which is what the ecosystem reads.
20
+ - package-ecosystem: "uv"
21
+ directory: "/"
22
+ schedule:
23
+ interval: "weekly"
24
+ - package-ecosystem: "github-actions"
25
+ directory: "/"
26
+ schedule:
27
+ interval: "weekly"
28
+ groups:
29
+ # `security.yml` pins github/codeql-action/init and github/codeql-action/analyze to
30
+ # the same commit, and CodeQL refuses to run when the two disagree: "Loaded a
31
+ # configuration file for version X, but running version Y". Dependabot treats the two
32
+ # actions as separate dependencies, so ungrouped it opens one PR per action and each
33
+ # one, in isolation, makes the versions disagree. Both PRs are then born red through
34
+ # no fault of their own, and whichever merges first leaves `main` red until the other
35
+ # follows. That happened on the 4.37.6 to 4.37.7 bump (#43 merged before #41) and
36
+ # again on 4.37.7 to 4.37.8 (#51, #52).
37
+ #
38
+ # Grouping them means one PR moves both pins together, which is the only state CodeQL
39
+ # accepts.
40
+ codeql-action:
41
+ patterns:
42
+ - "github/codeql-action*"
@@ -0,0 +1,182 @@
1
+ name: Compliance report bundle
2
+
3
+ # The compliance-bundle tier's fulfillment step (docs/compliance-bundle-plan.md): render one
4
+ # buyer's branded evidence reports for a cohort of endpoints, zip them with a manifest, put the
5
+ # archive behind a 30-day capability link, and email the link. GitHub Actions is the on-demand
6
+ # compute, so the tier needs no always-on server.
7
+ #
8
+ # Dispatched by infra/compliance-bundle's post-checkout setup Lambda. It can also be dispatched
9
+ # by hand, on main, to re-run an order: the repair for an undelivered one is a re-run with the
10
+ # same order_ref. Payment is settled before dispatch; this workflow never talks to Stripe.
11
+ #
12
+ # NOTHING ABOUT THE BUYER IS AN INPUT. This repository is public, and GitHub prints each step's
13
+ # environment -- including any value that came from a workflow_dispatch input -- in a run log
14
+ # anyone can read. An earlier draft took the buyer's email address, organization name, endpoint
15
+ # list, and the bundle id (the download capability) as inputs, and would have published every
16
+ # one of them for every order. The only input now is `order_ref`: a random 128-bit name for an
17
+ # object the setup Lambda wrote to the private artifacts bucket. On its own it grants nothing;
18
+ # reading the object needs this workflow's AWS role, which trusts runs on main only. Every value
19
+ # read from the object is registered with ::add-mask:: before any later step can print it, so an
20
+ # error message that happens to quote an address is masked too.
21
+ #
22
+ # Renders from the already-published dataset, never re-probes. A bundle is packaging for the
23
+ # free site's own numbers, not a second measurement, so this fetches
24
+ # https://fhir.chelseakr.com/scorecards.json -- the same file a free visitor reads -- rather than
25
+ # re-grading, which would re-probe every payer's endpoint on every purchase and risk a report
26
+ # that disagrees with the free page for the same endpoint on the same day.
27
+
28
+ on:
29
+ workflow_dispatch:
30
+ inputs:
31
+ order_ref:
32
+ description: "32 lowercase hex characters naming a stored order; carries no buyer data"
33
+ required: true
34
+ type: string
35
+
36
+ permissions:
37
+ contents: read
38
+
39
+ # Scoped per order rather than global: GitHub keeps only one pending run per concurrency group,
40
+ # so a shared group would let a third arrival evict a queued, paid order. Scoped to one order, an
41
+ # eviction can only ever drop a duplicate run of the same build. order_ref is safe to name here
42
+ # because it carries nothing.
43
+ concurrency:
44
+ group: compliance-bundle-${{ inputs.order_ref }}
45
+ cancel-in-progress: false
46
+
47
+ jobs:
48
+ bundle:
49
+ runs-on: ubuntu-latest
50
+ timeout-minutes: 20
51
+ permissions:
52
+ contents: read
53
+ id-token: write # read the order and write the archive through AWS OIDC
54
+ steps:
55
+ # A paid order has nowhere to go without these three, so their absence fails the run
56
+ # (and the run's failure is what tells the owner) rather than building an archive nobody
57
+ # can reach.
58
+ - name: Refuse to start without the delivery configuration
59
+ shell: bash
60
+ env:
61
+ ARTIFACTS_BUCKET: ${{ vars.ARTIFACTS_BUCKET }}
62
+ BUNDLE_API_BASE: ${{ vars.BUNDLE_API_BASE }}
63
+ SES_FROM: ${{ vars.SES_FROM }}
64
+ ORDER_REF: ${{ inputs.order_ref }}
65
+ run: |
66
+ set -euo pipefail
67
+ if [ -z "${ARTIFACTS_BUCKET}" ] || [ -z "${BUNDLE_API_BASE}" ] || [ -z "${SES_FROM}" ]; then
68
+ echo "::error::ARTIFACTS_BUCKET, BUNDLE_API_BASE and SES_FROM must all be set as repository variables before an order can be fulfilled. See docs/compliance-bundle-owner-steps.md."
69
+ exit 1
70
+ fi
71
+ if ! [[ "${ORDER_REF}" =~ ^[a-f0-9]{32}$ ]]; then
72
+ echo "::error::order_ref must be 32 lowercase hex characters."
73
+ exit 1
74
+ fi
75
+
76
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
77
+ with:
78
+ persist-credentials: false
79
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
80
+ with:
81
+ python-version: "3.12"
82
+ - name: Install the locked environment
83
+ shell: bash
84
+ run: |
85
+ set -euo pipefail
86
+ uv sync --locked
87
+
88
+ - name: Authenticate to AWS
89
+ uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0
90
+ with:
91
+ role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
92
+ aws-region: ${{ vars.AWS_REGION || 'us-west-2' }}
93
+
94
+ # The object is read into request.json and every buyer-supplied value in it is masked
95
+ # before anything else runs. The masking happens inside Python reading the file, so no
96
+ # value ever appears in a step's rendered script or environment block.
97
+ - name: Collect the order and mask what it holds
98
+ shell: bash
99
+ env:
100
+ ARTIFACTS_BUCKET: ${{ vars.ARTIFACTS_BUCKET }}
101
+ ORDER_REF: ${{ inputs.order_ref }}
102
+ run: |
103
+ set -euo pipefail
104
+ aws s3 cp "s3://${ARTIFACTS_BUCKET}/compliance-requests/${ORDER_REF}.json" request.json --only-show-errors
105
+ uv run python -m fhir_scorecard.bundle_order mask request.json
106
+
107
+ # The published dataset, fetched fresh rather than trusted from the checkout: the live
108
+ # site is rebuilt daily on a schedule independent of commits (pages.yml), and a bundle
109
+ # must render the numbers a buyer could see on fhir.chelseakr.com today. `curl -f` fails
110
+ # the run on anything but a 2xx: a dataset that could not be read must never render as
111
+ # "no scorecard is published" for an endpoint that has one.
112
+ - name: Fetch the published dataset
113
+ shell: bash
114
+ run: |
115
+ set -euo pipefail
116
+ curl -fsSL --retry 3 --retry-delay 5 -o scorecards.json https://fhir.chelseakr.com/scorecards.json
117
+
118
+ - name: Render the bundle
119
+ shell: bash
120
+ run: |
121
+ set -euo pipefail
122
+ max_endpoints="$(uv run python -m fhir_scorecard.bundle_order cap request.json)"
123
+ uv run fhir-scorecard bundle --request request.json \
124
+ --registry data/registry.json --scorecards scorecards.json \
125
+ --max-endpoints "${max_endpoints}" \
126
+ --out bundle.zip --plan-out manifest.json
127
+ included="$(jq '.included' manifest.json)"
128
+ requested="$(jq '.requested' manifest.json)"
129
+ {
130
+ echo "## Compliance report bundle"
131
+ echo
132
+ echo "${included} of ${requested} requested endpoints included; the manifest says"
133
+ echo "what happened to the rest. Nothing identifying the order is shown on this page."
134
+ } >> "$GITHUB_STEP_SUMMARY"
135
+
136
+ # compliance-bundles/ expires after 30 days (main.tf's lifecycle rule). The only way to it
137
+ # is the download route in infra/compliance-bundle, which presigns per click.
138
+ - name: Upload the archive behind its capability key
139
+ shell: bash
140
+ env:
141
+ ARTIFACTS_BUCKET: ${{ vars.ARTIFACTS_BUCKET }}
142
+ run: |
143
+ set -euo pipefail
144
+ key="$(uv run python -m fhir_scorecard.bundle_order archive-key request.json)"
145
+ aws s3 cp bundle.zip "s3://${ARTIFACTS_BUCKET}/${key}" \
146
+ --content-type application/zip --only-show-errors
147
+
148
+ - name: Email the download link
149
+ shell: bash
150
+ env:
151
+ BUNDLE_API_BASE: ${{ vars.BUNDLE_API_BASE }}
152
+ SES_FROM: ${{ vars.SES_FROM }}
153
+ run: |
154
+ set -euo pipefail
155
+ uv run --with boto3 python -m fhir_scorecard.bundle_order email request.json manifest.json
156
+
157
+ # What this may NOT do is name the order: the bundle id is the download credential, and
158
+ # annotations and run summaries are public on a public repository. This says only that an
159
+ # order failed and how to repair it.
160
+ - name: Say that a paid order failed
161
+ if: ${{ failure() }}
162
+ shell: bash
163
+ env:
164
+ ORDER_REF: ${{ inputs.order_ref }}
165
+ run: |
166
+ set -euo pipefail
167
+ echo "::error::A paid compliance report bundle did not complete. The buyer was told the build had started and nothing else will tell them otherwise. Re-running this workflow on main with the same order_ref is the repair."
168
+ {
169
+ echo
170
+ echo "## A paid order was not delivered"
171
+ echo
172
+ echo "- order_ref: \`${ORDER_REF}\` (names the stored order; carries no buyer data)"
173
+ echo
174
+ echo "Re-running this workflow with the same order_ref is safe: the archive and the"
175
+ echo "download link are both keyed on the bundle id inside the stored order, so a"
176
+ echo "re-run fills the object the buyer's existing link already points at."
177
+ } >> "$GITHUB_STEP_SUMMARY"
178
+
179
+ # Deliberately no upload-artifact step. A run artifact of this repository is published:
180
+ # every signed-in GitHub account can download it for its whole retention window. bundle.zip
181
+ # is the paid deliverable and manifest.json and request.json carry the download capability
182
+ # and the buyer's address.
@@ -0,0 +1,85 @@
1
+ # Is the site still the site this repository publishes, and is it still being published?
2
+ #
3
+ # grade-and-publish runs `audit-site` on the local site/ directory before the upload,
4
+ # and the deploy job returns. After that nothing has ever looked at the bytes a reader
5
+ # receives. A publish that failed, an upload that dropped an asset, or a Pages setting
6
+ # that stopped serving would leave every gate green while the live site was stale,
7
+ # broken, or gone, and nothing in this repository could tell.
8
+ #
9
+ # This check is narrower than its siblings on purpose. Most of this site is a daily
10
+ # rescore of every live third-party endpoint in the registry, every page footer carries a minute-
11
+ # resolution Generated stamp, and the availability and history pages derive from
12
+ # data/history.json, which CI restores from the capability-history branch rather than
13
+ # from main. Rebuilding that here would mean re-probing every registry endpoint to produce
14
+ # numbers that legitimately differ from the published ones, which is a moving target
15
+ # and would produce noise rather than signal.
16
+ #
17
+ # So it checks what IS a pure function of committed inputs, exactly: all 58 files under
18
+ # src/fhir_scorecard/assets, which write_assets copies verbatim; robots.txt and
19
+ # dataset.schema.json, which are pure functions of the origin string; and the identity
20
+ # half of api/index.json and dataset.csv, meaning the endpoint set and every field that
21
+ # comes from data/registry.json rather than from a probe.
22
+ #
23
+ # One rendered page is read, for four tags. The home page's og:image and twitter:image
24
+ # are site.social_card_url(origin) and nothing else, and og:title, og:description and
25
+ # og:image:alt have to be there. They carry no grade, latency or timestamp, so they are
26
+ # as pure as the two documents above -- and a head naming a card the deployment does not
27
+ # serve is invisible from inside a checkout and blank everywhere the page is shared.
28
+ #
29
+ # Plus the one thing no rebuild would catch and no audit does: the site has to be
30
+ # recent. A site frozen a year ago still audits clean, so api/index.json's generated_at
31
+ # must parse, must not be in the future, and must be inside 48 hours of the run.
32
+ #
33
+ # It reads the network, deliberately, which is why it holds only `contents: read`, is
34
+ # its own workflow, and is not a required check: it grades the deployment, and a
35
+ # deployment that has not happened yet is not a reason to block a merge.
36
+ #
37
+ # Runs daily and on demand (Actions > live integrity sentinel > Run workflow). The cron
38
+ # sits well after grade-and-publish's 14:17 so it reads a settled deployment -- but it
39
+ # also sits five hours and fifty minutes before the next one, and a change merged in that
40
+ # window is on main and not yet on the site. That is 24% of the clock, and the working
41
+ # part of it. It used to be reported as a failure: 1 of the 1 reds this check had by
42
+ # 2026-09-12 was exactly that, #120 merged at 16:24 after the 14:27 publish. The tool now
43
+ # dates the newest commit touching a checked input and calls a site that predates it a
44
+ # pending publish rather than a fault, which is why the checkout below is not shallow.
45
+ name: live integrity sentinel
46
+
47
+ on:
48
+ schedule:
49
+ - cron: "7 20 * * *"
50
+ workflow_dispatch:
51
+
52
+ permissions:
53
+ contents: read
54
+
55
+ concurrency:
56
+ group: live-integrity
57
+ cancel-in-progress: false
58
+
59
+ jobs:
60
+ verify-live:
61
+ name: the live site still matches its committed inputs
62
+ runs-on: ubuntu-latest
63
+ timeout-minutes: 10
64
+ steps:
65
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
66
+ with:
67
+ ref: main
68
+ persist-credentials: false
69
+ # Full history, because the check asks `git log` when a checked input last
70
+ # changed and the default depth of 1 cannot answer. A shallow clone grafts its
71
+ # oldest fetched commit into a parentless root, so at depth 1 every file in the
72
+ # tree reads as having been added at HEAD: the inputs would date to the newest
73
+ # commit on main whatever it touched, and any difference at all would be excused
74
+ # as a publish that has not run yet. verify_live_site.py refuses a parentless
75
+ # answer for that reason, so a shallow checkout does not weaken the check -- it
76
+ # loses the distinction entirely and every merge-window difference goes back to
77
+ # being a spurious red. tests/test_live_integrity_pending_publish.py asserts this.
78
+ fetch-depth: 0
79
+
80
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
81
+ with:
82
+ python-version: "3.12"
83
+
84
+ - name: Compare the live site with this checkout's committed inputs
85
+ run: python3 tools/verify_live_site.py