@terpjs/spec 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +417 -417
  3. package/VERSION +1 -1
  4. package/app-check-report.schema.json +122 -122
  5. package/assurance-profile.schema.json +70 -70
  6. package/catalog/backend/alembic_downgrades_not_empty.json +22 -22
  7. package/catalog/backend/base_query_not_overridden.json +27 -27
  8. package/catalog/backend/canonical_module_shape.json +22 -22
  9. package/catalog/backend/datetime_columns_are_timezone_aware.json +23 -23
  10. package/catalog/backend/escape_hatch_budget.json +21 -21
  11. package/catalog/backend/events_reference_catalog.json +27 -27
  12. package/catalog/backend/input_schemas_exclude_managed_columns.json +27 -27
  13. package/catalog/backend/input_str_fields_have_max_length.json +22 -22
  14. package/catalog/backend/jobs_reference_catalog.json +27 -27
  15. package/catalog/backend/list_routes_paginate.json +32 -32
  16. package/catalog/backend/modules_declare_policy.json +26 -26
  17. package/catalog/backend/mutations_emit_audit.json +26 -26
  18. package/catalog/backend/mutations_require_write_role.json +32 -32
  19. package/catalog/backend/no_adhoc_background_runtime.json +22 -22
  20. package/catalog/backend/no_adhoc_config_decrypt.json +27 -27
  21. package/catalog/backend/no_adhoc_logging_config.json +22 -22
  22. package/catalog/backend/no_adhoc_middleware.json +27 -27
  23. package/catalog/backend/no_adhoc_permission_literals.json +27 -27
  24. package/catalog/backend/no_app_instantiation.json +22 -22
  25. package/catalog/backend/no_blocking_sleep.json +22 -22
  26. package/catalog/backend/no_cross_module_imports.json +21 -21
  27. package/catalog/backend/no_dependency_overrides.json +27 -27
  28. package/catalog/backend/no_destructive_migrations.json +22 -22
  29. package/catalog/backend/no_dynamic_sql.json +22 -22
  30. package/catalog/backend/no_empty_tests.json +22 -22
  31. package/catalog/backend/no_eval_or_exec.json +22 -22
  32. package/catalog/backend/no_hardcoded_credentials.json +21 -21
  33. package/catalog/backend/no_internal_imports.json +22 -22
  34. package/catalog/backend/no_manual_actor_stamping.json +27 -27
  35. package/catalog/backend/no_manual_ownership_checks.json +32 -32
  36. package/catalog/backend/no_manual_scope_filtering.json +27 -27
  37. package/catalog/backend/no_manual_table_schema.json +22 -22
  38. package/catalog/backend/no_manual_version_assignment.json +22 -22
  39. package/catalog/backend/no_mutable_default_args.json +22 -22
  40. package/catalog/backend/no_naive_datetime.json +22 -22
  41. package/catalog/backend/no_oversized_python_files.json +22 -22
  42. package/catalog/backend/no_print.json +22 -22
  43. package/catalog/backend/no_raw_app_routes.json +26 -26
  44. package/catalog/backend/no_raw_connection_access.json +27 -27
  45. package/catalog/backend/no_raw_file_references.json +27 -27
  46. package/catalog/backend/no_raw_outbound_http.json +22 -22
  47. package/catalog/backend/no_raw_session_construction.json +22 -22
  48. package/catalog/backend/no_star_imports.json +22 -22
  49. package/catalog/backend/no_todo_fixme.json +22 -22
  50. package/catalog/backend/no_unique_columns_on_soft_delete_models.json +22 -22
  51. package/catalog/backend/offset_queries_declare_ordering.json +22 -22
  52. package/catalog/backend/path_id_params_are_uuid.json +22 -22
  53. package/catalog/backend/policy_refs_resolve.json +27 -27
  54. package/catalog/backend/public_modules_are_read_only.json +26 -26
  55. package/catalog/backend/reads_use_base_query.json +27 -27
  56. package/catalog/backend/response_model_not_table_model.json +27 -27
  57. package/catalog/backend/routes_declare_response_model.json +27 -27
  58. package/catalog/backend/safe_methods_are_read_only.json +31 -31
  59. package/catalog/backend/schemas_exclude_sensitive_fields.json +32 -32
  60. package/catalog/backend/session_imported_from_sqlmodel.json +22 -22
  61. package/catalog/backend/table_models_use_base_table.json +22 -22
  62. package/catalog/backend/tables_have_migrations.json +27 -27
  63. package/catalog/backend/tenant_scoped_models_use_scoped_service.json +22 -22
  64. package/catalog/backend/ungoverned_escape_hatch.json +21 -21
  65. package/catalog/backend/update_schemas_inherit_base_update_schema.json +32 -32
  66. package/catalog/frontend/escape-hatch.json +21 -21
  67. package/catalog/frontend/generated-client-only.json +26 -26
  68. package/catalog/frontend/layout-contract.json +26 -26
  69. package/catalog/frontend/no-cross-module-imports.json +21 -21
  70. package/catalog/frontend/no-deep-imports.json +25 -25
  71. package/catalog/frontend/no-dom-html-injection.json +22 -22
  72. package/catalog/frontend/no-eval.json +21 -21
  73. package/catalog/frontend/no-inline-styling.json +25 -25
  74. package/catalog/frontend/no-style-imports.json +24 -24
  75. package/catalog/frontend/no-unsafe-href.json +21 -21
  76. package/catalog/frontend/no-unsafe-target-blank.json +21 -21
  77. package/catalog/frontend/router-links.json +22 -22
  78. package/catalog/frontend/token-styled-elements.json +25 -25
  79. package/catalog/schema.json +95 -95
  80. package/corpus/PENDING.json +4 -4
  81. package/corpus/RESIDUALS.json +21 -21
  82. package/corpus/backend/alembic_downgrades_not_empty/compliant-01/modules/notes/migrations/versions/0001_change.py +6 -6
  83. package/corpus/backend/alembic_downgrades_not_empty/violation-01/modules/notes/migrations/versions/0001_change.py +6 -6
  84. package/corpus/backend/base_query_not_overridden/compliant-01/modules/notes/service.py +8 -8
  85. package/corpus/backend/base_query_not_overridden/compliant-02/modules/notes/service.py +8 -8
  86. package/corpus/backend/base_query_not_overridden/violation-01/modules/notes/service.py +8 -8
  87. package/corpus/backend/base_query_not_overridden/violation-02/expected-findings.json +7 -7
  88. package/corpus/backend/base_query_not_overridden/violation-02/modules/notes/service.py +8 -8
  89. package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/models.py +6 -6
  90. package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/module.py +7 -7
  91. package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/router.py +3 -3
  92. package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/schemas.py +10 -10
  93. package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/service.py +5 -5
  94. package/corpus/backend/canonical_module_shape/violation-01/modules/notes/router.py +3 -3
  95. package/corpus/backend/datetime_columns_are_timezone_aware/compliant-01/modules/notes/models.py +12 -12
  96. package/corpus/backend/datetime_columns_are_timezone_aware/violation-01/modules/notes/models.py +12 -12
  97. package/corpus/backend/datetime_columns_are_timezone_aware/violation-02/modules/notes/models.py +13 -13
  98. package/corpus/backend/datetime_columns_are_timezone_aware/violation-03/modules/notes/models.py +15 -15
  99. package/corpus/backend/escape_hatch_budget/compliant-01/escape-hatch-budget.json +3 -3
  100. package/corpus/backend/escape_hatch_budget/compliant-01/modules/notes/service.py +2 -2
  101. package/corpus/backend/escape_hatch_budget/compliant-02/escape-hatch-budget.json +1 -1
  102. package/corpus/backend/escape_hatch_budget/compliant-02/modules/notes/service.py +4 -4
  103. package/corpus/backend/escape_hatch_budget/violation-01/escape-hatch-budget.json +1 -1
  104. package/corpus/backend/escape_hatch_budget/violation-01/modules/notes/service.py +2 -2
  105. package/corpus/backend/escape_hatch_budget/violation-02/escape-hatch-budget.json +3 -3
  106. package/corpus/backend/escape_hatch_budget/violation-02/modules/notes/service.py +4 -4
  107. package/corpus/backend/events_reference_catalog/compliant-01/modules/notes/module.py +9 -9
  108. package/corpus/backend/events_reference_catalog/violation-01/modules/notes/module.py +8 -8
  109. package/corpus/backend/input_schemas_exclude_managed_columns/compliant-01/modules/notes/schemas.py +6 -6
  110. package/corpus/backend/input_schemas_exclude_managed_columns/violation-01/expected-findings.json +7 -7
  111. package/corpus/backend/input_schemas_exclude_managed_columns/violation-01/modules/notes/schemas.py +9 -9
  112. package/corpus/backend/input_schemas_exclude_managed_columns/violation-02/expected-findings.json +12 -12
  113. package/corpus/backend/input_schemas_exclude_managed_columns/violation-02/modules/notes/router.py +14 -14
  114. package/corpus/backend/input_str_fields_have_max_length/compliant-01/modules/notes/schemas.py +4 -4
  115. package/corpus/backend/input_str_fields_have_max_length/violation-01/modules/notes/schemas.py +2 -2
  116. package/corpus/backend/jobs_reference_catalog/compliant-01/modules/notes/service.py +5 -5
  117. package/corpus/backend/jobs_reference_catalog/violation-01/modules/notes/service.py +2 -2
  118. package/corpus/backend/list_routes_paginate/compliant-01/modules/notes/router.py +6 -6
  119. package/corpus/backend/list_routes_paginate/violation-01/modules/notes/router.py +3 -3
  120. package/corpus/backend/modules_declare_policy/compliant-01/modules/notes/module.py +7 -7
  121. package/corpus/backend/modules_declare_policy/violation-01/modules/notes/module.py +6 -6
  122. package/corpus/backend/mutations_emit_audit/compliant-01/modules/notes/service.py +9 -9
  123. package/corpus/backend/mutations_emit_audit/violation-01/modules/notes/service.py +4 -4
  124. package/corpus/backend/mutations_require_write_role/compliant-01/modules/notes/module.py +7 -7
  125. package/corpus/backend/mutations_require_write_role/compliant-01/modules/notes/router.py +3 -3
  126. package/corpus/backend/mutations_require_write_role/compliant-02/modules/notes/module.py +7 -7
  127. package/corpus/backend/mutations_require_write_role/compliant-02/modules/notes/router.py +3 -3
  128. package/corpus/backend/mutations_require_write_role/violation-01/expected-findings.json +7 -7
  129. package/corpus/backend/mutations_require_write_role/violation-01/modules/notes/module.py +7 -7
  130. package/corpus/backend/mutations_require_write_role/violation-01/modules/notes/router.py +3 -3
  131. package/corpus/backend/mutations_require_write_role/violation-02/expected-findings.json +7 -7
  132. package/corpus/backend/mutations_require_write_role/violation-02/modules/notes/module.py +7 -7
  133. package/corpus/backend/mutations_require_write_role/violation-02/modules/notes/router.py +5 -5
  134. package/corpus/backend/mutations_require_write_role/violation-03/expected-findings.json +7 -7
  135. package/corpus/backend/mutations_require_write_role/violation-03/modules/notes/module.py +10 -10
  136. package/corpus/backend/mutations_require_write_role/violation-03/modules/notes/router.py +3 -3
  137. package/corpus/backend/no_adhoc_background_runtime/compliant-01/modules/notes/service.py +9 -9
  138. package/corpus/backend/no_adhoc_background_runtime/violation-01/modules/notes/service.py +5 -5
  139. package/corpus/backend/no_adhoc_background_runtime/violation-02/modules/notes/service.py +3 -3
  140. package/corpus/backend/no_adhoc_config_decrypt/compliant-01/modules/billing/service.py +2 -2
  141. package/corpus/backend/no_adhoc_config_decrypt/violation-01/modules/billing/service.py +2 -2
  142. package/corpus/backend/no_adhoc_logging_config/compliant-01/modules/notes/service.py +3 -3
  143. package/corpus/backend/no_adhoc_logging_config/violation-01/modules/notes/service.py +3 -3
  144. package/corpus/backend/no_adhoc_middleware/compliant-01/main.py +3 -3
  145. package/corpus/backend/no_adhoc_middleware/violation-01/main.py +4 -4
  146. package/corpus/backend/no_adhoc_middleware/violation-02/main.py +5 -5
  147. package/corpus/backend/no_adhoc_permission_literals/compliant-01/modules/notes/module.py +3 -3
  148. package/corpus/backend/no_adhoc_permission_literals/compliant-02/modules/notes/router.py +5 -5
  149. package/corpus/backend/no_adhoc_permission_literals/violation-01/expected-findings.json +7 -7
  150. package/corpus/backend/no_adhoc_permission_literals/violation-01/modules/notes/module.py +1 -1
  151. package/corpus/backend/no_adhoc_permission_literals/violation-02/expected-findings.json +7 -7
  152. package/corpus/backend/no_adhoc_permission_literals/violation-02/modules/notes/router.py +2 -2
  153. package/corpus/backend/no_adhoc_permission_literals/violation-03/expected-findings.json +12 -12
  154. package/corpus/backend/no_adhoc_permission_literals/violation-03/modules/notes/module.py +10 -10
  155. package/corpus/backend/no_app_instantiation/compliant-01/main.py +3 -3
  156. package/corpus/backend/no_app_instantiation/violation-01/main.py +3 -3
  157. package/corpus/backend/no_blocking_sleep/compliant-01/modules/notes/service.py +2 -2
  158. package/corpus/backend/no_blocking_sleep/violation-01/modules/notes/service.py +5 -5
  159. package/corpus/backend/no_cross_module_imports/compliant-01/modules/a/service.py +1 -1
  160. package/corpus/backend/no_cross_module_imports/violation-01/modules/a/service.py +1 -1
  161. package/corpus/backend/no_cross_module_imports/violation-02/modules/a/service.py +1 -1
  162. package/corpus/backend/no_dependency_overrides/compliant-01/main.py +12 -12
  163. package/corpus/backend/no_dependency_overrides/violation-01/main.py +11 -11
  164. package/corpus/backend/no_destructive_migrations/compliant-01/modules/notes/migrations/versions/0001_change.py +3 -3
  165. package/corpus/backend/no_destructive_migrations/compliant-02/modules/notes/migrations/versions/0001_change.py +8 -8
  166. package/corpus/backend/no_destructive_migrations/violation-01/expected-findings.json +7 -7
  167. package/corpus/backend/no_destructive_migrations/violation-01/modules/notes/migrations/versions/0001_change.py +2 -2
  168. package/corpus/backend/no_destructive_migrations/violation-02/expected-findings.json +7 -7
  169. package/corpus/backend/no_destructive_migrations/violation-02/modules/notes/migrations/versions/0001_change.py +2 -2
  170. package/corpus/backend/no_destructive_migrations/violation-03/expected-findings.json +7 -7
  171. package/corpus/backend/no_destructive_migrations/violation-03/modules/notes/migrations/versions/0001_change.py +3 -3
  172. package/corpus/backend/no_destructive_migrations/violation-04/expected-findings.json +12 -12
  173. package/corpus/backend/no_destructive_migrations/violation-04/modules/notes/migrations/versions/0001_change.py +6 -6
  174. package/corpus/backend/no_destructive_migrations/violation-05/expected-findings.json +12 -12
  175. package/corpus/backend/no_destructive_migrations/violation-05/modules/notes/migrations/versions/0001_change.py +3 -3
  176. package/corpus/backend/no_dynamic_sql/compliant-01/modules/notes/service.py +1 -1
  177. package/corpus/backend/no_dynamic_sql/compliant-02/modules/notes/service.py +20 -20
  178. package/corpus/backend/no_dynamic_sql/violation-01/modules/notes/service.py +2 -2
  179. package/corpus/backend/no_dynamic_sql/violation-02/modules/notes/service.py +2 -2
  180. package/corpus/backend/no_dynamic_sql/violation-03/modules/notes/service.py +7 -7
  181. package/corpus/backend/no_dynamic_sql/violation-04/modules/notes/service.py +16 -16
  182. package/corpus/backend/no_empty_tests/compliant-01/tests/test_notes.py +2 -2
  183. package/corpus/backend/no_empty_tests/violation-01/tests/test_notes.py +2 -2
  184. package/corpus/backend/no_eval_or_exec/compliant-01/modules/notes/service.py +2 -2
  185. package/corpus/backend/no_eval_or_exec/violation-01/modules/notes/service.py +2 -2
  186. package/corpus/backend/no_hardcoded_credentials/compliant-01/modules/billing/service.py +3 -3
  187. package/corpus/backend/no_hardcoded_credentials/compliant-02/modules/billing/service.py +18 -18
  188. package/corpus/backend/no_hardcoded_credentials/violation-01/expected-findings.json +7 -7
  189. package/corpus/backend/no_hardcoded_credentials/violation-01/modules/billing/service.py +2 -2
  190. package/corpus/backend/no_hardcoded_credentials/violation-02/expected-findings.json +7 -7
  191. package/corpus/backend/no_hardcoded_credentials/violation-02/modules/billing/service.py +1 -1
  192. package/corpus/backend/no_hardcoded_credentials/violation-03/expected-findings.json +22 -22
  193. package/corpus/backend/no_hardcoded_credentials/violation-03/modules/billing/service.py +11 -11
  194. package/corpus/backend/no_hardcoded_credentials/violation-04/expected-findings.json +12 -12
  195. package/corpus/backend/no_hardcoded_credentials/violation-04/modules/billing/service.py +8 -8
  196. package/corpus/backend/no_hardcoded_credentials/violation-05/expected-findings.json +12 -12
  197. package/corpus/backend/no_hardcoded_credentials/violation-05/modules/billing/service.py +4 -4
  198. package/corpus/backend/no_internal_imports/compliant-01/modules/notes/service.py +1 -1
  199. package/corpus/backend/no_internal_imports/violation-01/modules/notes/service.py +1 -1
  200. package/corpus/backend/no_manual_actor_stamping/compliant-01/modules/notes/service.py +5 -5
  201. package/corpus/backend/no_manual_actor_stamping/compliant-02/modules/notes/schemas.py +12 -12
  202. package/corpus/backend/no_manual_actor_stamping/violation-01/modules/notes/service.py +3 -3
  203. package/corpus/backend/no_manual_actor_stamping/violation-02/expected-findings.json +7 -7
  204. package/corpus/backend/no_manual_actor_stamping/violation-02/modules/notes/service.py +4 -4
  205. package/corpus/backend/no_manual_ownership_checks/compliant-01/modules/journals/service.py +3 -3
  206. package/corpus/backend/no_manual_ownership_checks/compliant-02/modules/journals/schemas.py +11 -11
  207. package/corpus/backend/no_manual_ownership_checks/violation-01/modules/journals/service.py +3 -3
  208. package/corpus/backend/no_manual_ownership_checks/violation-02/expected-findings.json +7 -7
  209. package/corpus/backend/no_manual_ownership_checks/violation-02/modules/journals/service.py +9 -9
  210. package/corpus/backend/no_manual_ownership_checks/violation-03/expected-findings.json +7 -7
  211. package/corpus/backend/no_manual_ownership_checks/violation-03/modules/notes/jobs.py +1 -1
  212. package/corpus/backend/no_manual_ownership_checks/violation-03/modules/notes/models.py +7 -7
  213. package/corpus/backend/no_manual_ownership_checks/violation-03/modules/notes/module.py +11 -11
  214. package/corpus/backend/no_manual_ownership_checks/violation-03/modules/notes/service.py +7 -7
  215. package/corpus/backend/no_manual_scope_filtering/compliant-01/modules/notes/service.py +8 -8
  216. package/corpus/backend/no_manual_scope_filtering/compliant-02/modules/notes/schemas.py +12 -12
  217. package/corpus/backend/no_manual_scope_filtering/violation-01/modules/notes/service.py +2 -2
  218. package/corpus/backend/no_manual_scope_filtering/violation-02/modules/notes/service.py +2 -2
  219. package/corpus/backend/no_manual_scope_filtering/violation-03/expected-findings.json +7 -7
  220. package/corpus/backend/no_manual_scope_filtering/violation-03/modules/notes/service.py +7 -7
  221. package/corpus/backend/no_manual_table_schema/compliant-01/modules/notes/models.py +6 -6
  222. package/corpus/backend/no_manual_table_schema/violation-01/modules/notes/models.py +8 -8
  223. package/corpus/backend/no_manual_version_assignment/compliant-01/modules/notes/service.py +5 -5
  224. package/corpus/backend/no_manual_version_assignment/violation-01/modules/notes/service.py +3 -3
  225. package/corpus/backend/no_manual_version_assignment/violation-02/modules/notes/service.py +3 -3
  226. package/corpus/backend/no_manual_version_assignment/violation-03/modules/notes/service.py +3 -3
  227. package/corpus/backend/no_mutable_default_args/compliant-01/modules/notes/service.py +3 -3
  228. package/corpus/backend/no_mutable_default_args/violation-01/modules/notes/service.py +2 -2
  229. package/corpus/backend/no_naive_datetime/compliant-01/modules/notes/service.py +2 -2
  230. package/corpus/backend/no_naive_datetime/violation-01/modules/notes/service.py +2 -2
  231. package/corpus/backend/no_naive_datetime/violation-02/modules/notes/service.py +2 -2
  232. package/corpus/backend/no_oversized_python_files/compliant-01/modules/notes/service.py +9 -9
  233. package/corpus/backend/no_oversized_python_files/violation-01/modules/notes/service.py +530 -530
  234. package/corpus/backend/no_print/compliant-01/modules/notes/service.py +7 -7
  235. package/corpus/backend/no_print/violation-01/modules/notes/service.py +2 -2
  236. package/corpus/backend/no_raw_app_routes/compliant-01/main.py +12 -12
  237. package/corpus/backend/no_raw_app_routes/violation-01/main.py +10 -10
  238. package/corpus/backend/no_raw_app_routes/violation-02/main.py +16 -16
  239. package/corpus/backend/no_raw_app_routes/violation-03/main.py +12 -12
  240. package/corpus/backend/no_raw_app_routes/violation-04/main.py +10 -10
  241. package/corpus/backend/no_raw_app_routes/violation-05/main.py +12 -12
  242. package/corpus/backend/no_raw_app_routes/violation-06/main.py +10 -10
  243. package/corpus/backend/no_raw_connection_access/compliant-01/modules/notes/service.py +5 -5
  244. package/corpus/backend/no_raw_connection_access/violation-01/modules/notes/service.py +2 -2
  245. package/corpus/backend/no_raw_connection_access/violation-02/modules/notes/service.py +2 -2
  246. package/corpus/backend/no_raw_file_references/compliant-01/modules/notes/models.py +10 -10
  247. package/corpus/backend/no_raw_file_references/violation-01/modules/notes/models.py +9 -9
  248. package/corpus/backend/no_raw_outbound_http/compliant-01/modules/notes/service.py +2 -2
  249. package/corpus/backend/no_raw_outbound_http/compliant-03/modules/notes/service.py +12 -12
  250. package/corpus/backend/no_raw_outbound_http/violation-01/expected-findings.json +7 -7
  251. package/corpus/backend/no_raw_outbound_http/violation-01/modules/notes/service.py +1 -1
  252. package/corpus/backend/no_raw_outbound_http/violation-02/expected-findings.json +7 -7
  253. package/corpus/backend/no_raw_outbound_http/violation-02/modules/notes/service.py +1 -1
  254. package/corpus/backend/no_raw_outbound_http/violation-03/expected-findings.json +7 -7
  255. package/corpus/backend/no_raw_outbound_http/violation-03/modules/notes/service.py +1 -1
  256. package/corpus/backend/no_raw_outbound_http/violation-04/expected-findings.json +12 -12
  257. package/corpus/backend/no_raw_outbound_http/violation-04/modules/notes/service.py +7 -7
  258. package/corpus/backend/no_raw_outbound_http/violation-05/expected-findings.json +12 -12
  259. package/corpus/backend/no_raw_outbound_http/violation-05/modules/notes/service.py +5 -5
  260. package/corpus/backend/no_raw_outbound_http/violation-06/expected-findings.json +12 -12
  261. package/corpus/backend/no_raw_outbound_http/violation-06/modules/notes/service.py +2 -2
  262. package/corpus/backend/no_raw_session_construction/compliant-01/modules/notes/service.py +3 -3
  263. package/corpus/backend/no_raw_session_construction/violation-01/modules/notes/service.py +3 -3
  264. package/corpus/backend/no_star_imports/compliant-01/modules/notes/service.py +1 -1
  265. package/corpus/backend/no_star_imports/violation-01/modules/notes/service.py +1 -1
  266. package/corpus/backend/no_todo_fixme/compliant-01/modules/notes/service.py +3 -3
  267. package/corpus/backend/no_todo_fixme/violation-01/modules/notes/service.py +3 -3
  268. package/corpus/backend/no_unique_columns_on_soft_delete_models/compliant-01/modules/notes/models.py +17 -17
  269. package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-01/expected-findings.json +7 -7
  270. package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-01/modules/notes/models.py +6 -6
  271. package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-02/expected-findings.json +7 -7
  272. package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-02/modules/notes/models.py +13 -13
  273. package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-03/expected-findings.json +7 -7
  274. package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-03/modules/notes/models.py +16 -16
  275. package/corpus/backend/offset_queries_declare_ordering/compliant-01/modules/notes/service.py +4 -4
  276. package/corpus/backend/offset_queries_declare_ordering/violation-01/modules/notes/service.py +2 -2
  277. package/corpus/backend/path_id_params_are_uuid/compliant-01/modules/notes/router.py +6 -6
  278. package/corpus/backend/path_id_params_are_uuid/violation-01/modules/notes/router.py +6 -6
  279. package/corpus/backend/policy_refs_resolve/compliant-01/modules/notes/module.py +7 -7
  280. package/corpus/backend/policy_refs_resolve/violation-01/modules/notes/module.py +8 -8
  281. package/corpus/backend/public_modules_are_read_only/compliant-01/modules/notes/module.py +7 -7
  282. package/corpus/backend/public_modules_are_read_only/compliant-01/modules/notes/router.py +3 -3
  283. package/corpus/backend/public_modules_are_read_only/violation-01/expected-findings.json +7 -7
  284. package/corpus/backend/public_modules_are_read_only/violation-01/modules/notes/module.py +7 -7
  285. package/corpus/backend/public_modules_are_read_only/violation-01/modules/notes/router.py +3 -3
  286. package/corpus/backend/public_modules_are_read_only/violation-02/expected-findings.json +7 -7
  287. package/corpus/backend/public_modules_are_read_only/violation-02/modules/notes/module.py +7 -7
  288. package/corpus/backend/public_modules_are_read_only/violation-02/modules/notes/router.py +5 -5
  289. package/corpus/backend/reads_use_base_query/compliant-01/modules/notes/models.py +6 -6
  290. package/corpus/backend/reads_use_base_query/compliant-01/modules/notes/service.py +8 -8
  291. package/corpus/backend/reads_use_base_query/compliant-02/modules/notes/models.py +9 -9
  292. package/corpus/backend/reads_use_base_query/compliant-02/modules/notes/service.py +9 -9
  293. package/corpus/backend/reads_use_base_query/violation-01/modules/notes/models.py +6 -6
  294. package/corpus/backend/reads_use_base_query/violation-01/modules/notes/service.py +9 -9
  295. package/corpus/backend/reads_use_base_query/violation-02/expected-findings.json +7 -7
  296. package/corpus/backend/reads_use_base_query/violation-02/modules/notes/models.py +6 -6
  297. package/corpus/backend/reads_use_base_query/violation-02/modules/notes/service.py +8 -8
  298. package/corpus/backend/response_model_not_table_model/compliant-01/modules/notes/models.py +6 -6
  299. package/corpus/backend/response_model_not_table_model/compliant-01/modules/notes/router.py +3 -3
  300. package/corpus/backend/response_model_not_table_model/compliant-01/modules/notes/schemas.py +5 -5
  301. package/corpus/backend/response_model_not_table_model/violation-01/modules/notes/models.py +6 -6
  302. package/corpus/backend/response_model_not_table_model/violation-01/modules/notes/router.py +3 -3
  303. package/corpus/backend/routes_declare_response_model/compliant-01/modules/notes/router.py +8 -8
  304. package/corpus/backend/routes_declare_response_model/violation-01/modules/notes/router.py +3 -3
  305. package/corpus/backend/safe_methods_are_read_only/compliant-01/modules/notes/router.py +6 -6
  306. package/corpus/backend/safe_methods_are_read_only/compliant-02/modules/notes/router.py +5 -5
  307. package/corpus/backend/safe_methods_are_read_only/violation-01/expected-findings.json +7 -7
  308. package/corpus/backend/safe_methods_are_read_only/violation-01/modules/notes/router.py +3 -3
  309. package/corpus/backend/safe_methods_are_read_only/violation-02/expected-findings.json +7 -7
  310. package/corpus/backend/safe_methods_are_read_only/violation-02/modules/notes/router.py +5 -5
  311. package/corpus/backend/safe_methods_are_read_only/violation-03/expected-findings.json +7 -7
  312. package/corpus/backend/safe_methods_are_read_only/violation-03/modules/notes/router.py +3 -3
  313. package/corpus/backend/schemas_exclude_sensitive_fields/compliant-01/modules/users/schemas.py +5 -5
  314. package/corpus/backend/schemas_exclude_sensitive_fields/compliant-02/modules/accounts/schemas.py +12 -12
  315. package/corpus/backend/schemas_exclude_sensitive_fields/violation-01/expected-findings.json +7 -7
  316. package/corpus/backend/schemas_exclude_sensitive_fields/violation-01/modules/users/schemas.py +3 -3
  317. package/corpus/backend/schemas_exclude_sensitive_fields/violation-02/expected-findings.json +7 -7
  318. package/corpus/backend/schemas_exclude_sensitive_fields/violation-02/modules/connectors/router.py +8 -8
  319. package/corpus/backend/schemas_exclude_sensitive_fields/violation-03/expected-findings.json +17 -17
  320. package/corpus/backend/schemas_exclude_sensitive_fields/violation-03/modules/integrations/schemas.py +7 -7
  321. package/corpus/backend/session_imported_from_sqlmodel/compliant-01/modules/notes/service.py +5 -5
  322. package/corpus/backend/session_imported_from_sqlmodel/violation-01/modules/notes/service.py +5 -5
  323. package/corpus/backend/table_models_use_base_table/compliant-01/modules/notes/models.py +6 -6
  324. package/corpus/backend/table_models_use_base_table/violation-01/modules/notes/models.py +5 -5
  325. package/corpus/backend/tables_have_migrations/compliant-01/modules/notes/migrations/versions/0a1b2c3d4e5f_create_notes_tables.py +22 -22
  326. package/corpus/backend/tables_have_migrations/compliant-01/modules/notes/models.py +6 -6
  327. package/corpus/backend/tables_have_migrations/compliant-02/capabilities/ledger/models.py +6 -6
  328. package/corpus/backend/tables_have_migrations/violation-01/expected-findings.json +7 -7
  329. package/corpus/backend/tables_have_migrations/violation-01/modules/notes/models.py +6 -6
  330. package/corpus/backend/tables_have_migrations/violation-02/expected-findings.json +7 -7
  331. package/corpus/backend/tables_have_migrations/violation-02/modules/notes/models.py +10 -10
  332. package/corpus/backend/tenant_scoped_models_use_scoped_service/compliant-01/modules/projects/models.py +6 -6
  333. package/corpus/backend/tenant_scoped_models_use_scoped_service/compliant-01/modules/projects/service.py +5 -5
  334. package/corpus/backend/tenant_scoped_models_use_scoped_service/compliant-02/modules/projects/models.py +8 -8
  335. package/corpus/backend/tenant_scoped_models_use_scoped_service/compliant-02/modules/projects/service.py +5 -5
  336. package/corpus/backend/tenant_scoped_models_use_scoped_service/violation-01/modules/projects/models.py +6 -6
  337. package/corpus/backend/tenant_scoped_models_use_scoped_service/violation-01/modules/projects/service.py +5 -5
  338. package/corpus/backend/ungoverned_escape_hatch/compliant-01/modules/notes/service.py +5 -5
  339. package/corpus/backend/ungoverned_escape_hatch/compliant-02/modules/notes/service.py +4 -4
  340. package/corpus/backend/ungoverned_escape_hatch/violation-01/modules/notes/service.py +2 -2
  341. package/corpus/backend/update_schemas_inherit_base_update_schema/compliant-01/modules/notes/schemas.py +2 -2
  342. package/corpus/backend/update_schemas_inherit_base_update_schema/violation-01/modules/notes/schemas.py +2 -2
  343. package/corpus/backend/update_schemas_inherit_base_update_schema/violation-02/modules/notes/schemas.py +5 -5
  344. package/corpus/frontend/escape-hatch/compliant-01/src/modules/widgets/Widget.tsx +4 -4
  345. package/corpus/frontend/escape-hatch/violation-01/src/modules/widgets/Widget.tsx +4 -4
  346. package/corpus/frontend/escape-hatch/violation-02/src/modules/widgets/Widget.tsx +7 -7
  347. package/corpus/frontend/escape-hatch/violation-03/src/modules/widgets/Widget.tsx +7 -7
  348. package/corpus/frontend/generated-client-only/compliant-01/src/modules/widgets/Widget.tsx +6 -6
  349. package/corpus/frontend/generated-client-only/compliant-02/src/modules/widgets/Widget.tsx +21 -21
  350. package/corpus/frontend/generated-client-only/compliant-03/src/modules/widgets/Widget.tsx +7 -7
  351. package/corpus/frontend/generated-client-only/compliant-04/src/modules/widgets/Widget.tsx +17 -17
  352. package/corpus/frontend/generated-client-only/violation-01/src/modules/widgets/Widget.tsx +3 -3
  353. package/corpus/frontend/generated-client-only/violation-02/src/modules/widgets/Widget.tsx +9 -9
  354. package/corpus/frontend/generated-client-only/violation-03/src/modules/widgets/Widget.tsx +14 -14
  355. package/corpus/frontend/generated-client-only/violation-04/src/modules/widgets/Widget.tsx +6 -6
  356. package/corpus/frontend/layout-contract/compliant-01/layout-contract.json +3 -3
  357. package/corpus/frontend/layout-contract/compliant-01/src/modules/widgets/Widget.tsx +8 -8
  358. package/corpus/frontend/layout-contract/violation-01/layout-contract.json +3 -3
  359. package/corpus/frontend/layout-contract/violation-01/src/modules/widgets/Widget.tsx +8 -8
  360. package/corpus/frontend/no-cross-module-imports/compliant-01/src/modules/widgets/Widget.tsx +4 -4
  361. package/corpus/frontend/no-cross-module-imports/violation-01/src/modules/widgets/Widget.tsx +3 -3
  362. package/corpus/frontend/no-deep-imports/compliant-01/src/modules/widgets/Widget.tsx +4 -4
  363. package/corpus/frontend/no-deep-imports/violation-01/src/modules/widgets/Widget.tsx +2 -2
  364. package/corpus/frontend/no-dom-html-injection/compliant-01/src/modules/widgets/Widget.tsx +4 -4
  365. package/corpus/frontend/no-dom-html-injection/compliant-02/src/modules/widgets/Widget.tsx +11 -11
  366. package/corpus/frontend/no-dom-html-injection/violation-01/src/modules/widgets/Widget.tsx +3 -3
  367. package/corpus/frontend/no-dom-html-injection/violation-02/src/modules/widgets/Widget.tsx +7 -7
  368. package/corpus/frontend/no-dom-html-injection/violation-03/src/modules/widgets/Widget.tsx +12 -12
  369. package/corpus/frontend/no-eval/compliant-01/src/modules/widgets/Widget.tsx +4 -4
  370. package/corpus/frontend/no-eval/compliant-02/src/modules/widgets/Widget.tsx +11 -11
  371. package/corpus/frontend/no-eval/violation-01/src/modules/widgets/Widget.tsx +3 -3
  372. package/corpus/frontend/no-eval/violation-02/src/modules/widgets/Widget.tsx +7 -7
  373. package/corpus/frontend/no-eval/violation-03/src/modules/widgets/Widget.tsx +10 -10
  374. package/corpus/frontend/no-eval/violation-04/src/modules/widgets/Widget.tsx +6 -6
  375. package/corpus/frontend/no-inline-styling/compliant-01/src/modules/widgets/Widget.tsx +4 -4
  376. package/corpus/frontend/no-inline-styling/violation-01/src/modules/widgets/Widget.tsx +3 -3
  377. package/corpus/frontend/no-inline-styling/violation-02/src/modules/widgets/Widget.tsx +6 -6
  378. package/corpus/frontend/no-style-imports/compliant-01/src/modules/widgets/Widget.tsx +4 -4
  379. package/corpus/frontend/no-style-imports/violation-01/src/modules/widgets/Widget.tsx +2 -2
  380. package/corpus/frontend/no-unsafe-href/compliant-01/src/modules/widgets/Widget.tsx +3 -3
  381. package/corpus/frontend/no-unsafe-href/violation-01/src/modules/widgets/Widget.tsx +3 -3
  382. package/corpus/frontend/no-unsafe-target-blank/compliant-01/src/modules/widgets/Widget.tsx +3 -3
  383. package/corpus/frontend/no-unsafe-target-blank/violation-01/src/modules/widgets/Widget.tsx +3 -3
  384. package/corpus/frontend/router-links/compliant-01/src/modules/widgets/Widget.tsx +4 -4
  385. package/corpus/frontend/router-links/violation-01/src/modules/widgets/Widget.tsx +3 -3
  386. package/corpus/frontend/token-styled-elements/compliant-01/src/modules/widgets/Widget.tsx +4 -4
  387. package/corpus/frontend/token-styled-elements/compliant-02/src/modules/widgets/Widget.tsx +4 -4
  388. package/corpus/frontend/token-styled-elements/violation-01/src/modules/widgets/Widget.tsx +3 -3
  389. package/findings.schema.json +40 -40
  390. package/package.json +23 -23
  391. package/restricted-surface.json +10 -10
  392. package/scorecard.schema.json +52 -52
@@ -1,27 +1,27 @@
1
- {
2
- "id": "backend/no_raw_connection_access",
3
- "surface": "backend",
4
- "title": "Modules never reach the raw DB connection / engine behind the session",
5
- "intent": "The runtime write guard covers the request session's own persistence methods, but the bound engine / connection the session exposes can issue data-modification statements directly, bypassing the audited chokepoint. A module must never reach for the session's underlying bind or connection; persist through the model's service so every write is audited. The escape is caught at the reach itself, and raw session / engine construction is separately banned by no_raw_session_construction — so an unrelated connect call on a domain object (a websocket / cache / search client) is deliberately not flagged.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_raw_connection_access"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.core",
16
- "ref": "WriteGuardedSession"
17
- }
18
- ],
19
- "reference": "session.get_bind() / session.connection() calls are refused (ADR 0015, F3); BaseService is the audited write path, and WriteGuardedSession gates session.connection() at runtime.",
20
- "opt_out": "# arch-allow-no-raw-connection-access: <reason>",
21
- "runtime": {
22
- "applicability": "required",
23
- "rationale": "The write-guarded request session gates session.connection() itself (WriteGuardedSession), closing the primary in-request escape to unaudited DML; the get_bind()/engine remainder has no construction hook, which is exactly the residual the build-time half covers."
24
- },
25
- "guide_topic": "service",
26
- "corpus": true
27
- }
1
+ {
2
+ "id": "backend/no_raw_connection_access",
3
+ "surface": "backend",
4
+ "title": "Modules never reach the raw DB connection / engine behind the session",
5
+ "intent": "The runtime write guard covers the request session's own persistence methods, but the bound engine / connection the session exposes can issue data-modification statements directly, bypassing the audited chokepoint. A module must never reach for the session's underlying bind or connection; persist through the model's service so every write is audited. The escape is caught at the reach itself, and raw session / engine construction is separately banned by no_raw_session_construction — so an unrelated connect call on a domain object (a websocket / cache / search client) is deliberately not flagged.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_raw_connection_access"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "WriteGuardedSession"
17
+ }
18
+ ],
19
+ "reference": "session.get_bind() / session.connection() calls are refused (ADR 0015, F3); BaseService is the audited write path, and WriteGuardedSession gates session.connection() at runtime.",
20
+ "opt_out": "# arch-allow-no-raw-connection-access: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "The write-guarded request session gates session.connection() itself (WriteGuardedSession), closing the primary in-request escape to unaudited DML; the get_bind()/engine remainder has no construction hook, which is exactly the residual the build-time half covers."
24
+ },
25
+ "guide_topic": "service",
26
+ "corpus": true
27
+ }
@@ -1,27 +1,27 @@
1
- {
2
- "id": "backend/no_raw_file_references",
3
- "surface": "backend",
4
- "title": "A table model's file-pointer column is a declared file reference, never a bare id column",
5
- "intent": "A stored pointer to a file object carries authorization semantics: the files capability serves delegated reads only through a declared reference, failing closed on an undeclared column. A bare id-typed file-pointer column on a table model is an undeclared reference: nothing ties the file's access to the referencing row, which is the classic object-level (BOLA) drift. Declare the column with the files capability's reference type so the pointer is greppable, verified at runtime, and served through the module's own already-authorized row — never hand-rolled. A non-table schema (a read DTO exposing the pointer) is fine and not policed; only the persisted column is.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_raw_file_references"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.capabilities.files",
16
- "ref": "load_for"
17
- }
18
- ],
19
- "reference": "FileRef(...) from terp-cap-files declares the column; FileService.load_for fail-closes on an undeclared *file_id column (ADR 0057).",
20
- "opt_out": "# arch-allow-no-raw-file-references: <reason>",
21
- "runtime": {
22
- "applicability": "required",
23
- "rationale": "Delegated reads fail closed on an undeclared reference column: FileService.load_for serves a file only through a FileRef-declared column (UndeclaredFileReferenceError otherwise, ADR 0057), so a bare uuid column never acquires read-delegation semantics."
24
- },
25
- "guide_topic": "files",
26
- "corpus": true
27
- }
1
+ {
2
+ "id": "backend/no_raw_file_references",
3
+ "surface": "backend",
4
+ "title": "A table model's file-pointer column is a declared file reference, never a bare id column",
5
+ "intent": "A stored pointer to a file object carries authorization semantics: the files capability serves delegated reads only through a declared reference, failing closed on an undeclared column. A bare id-typed file-pointer column on a table model is an undeclared reference: nothing ties the file's access to the referencing row, which is the classic object-level (BOLA) drift. Declare the column with the files capability's reference type so the pointer is greppable, verified at runtime, and served through the module's own already-authorized row — never hand-rolled. A non-table schema (a read DTO exposing the pointer) is fine and not policed; only the persisted column is.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_raw_file_references"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.capabilities.files",
16
+ "ref": "load_for"
17
+ }
18
+ ],
19
+ "reference": "FileRef(...) from terp-cap-files declares the column; FileService.load_for fail-closes on an undeclared *file_id column (ADR 0057).",
20
+ "opt_out": "# arch-allow-no-raw-file-references: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "Delegated reads fail closed on an undeclared reference column: FileService.load_for serves a file only through a FileRef-declared column (UndeclaredFileReferenceError otherwise, ADR 0057), so a bare uuid column never acquires read-delegation semantics."
24
+ },
25
+ "guide_topic": "files",
26
+ "corpus": true
27
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/no_raw_outbound_http",
3
- "surface": "backend",
4
- "title": "App modules do not import raw HTTP clients; outbound calls use a capability",
5
- "intent": "Directly importing an HTTP client library — or the lower-level socket / protocol primitives that reach the same network — makes SSRF protection, allowlists, egress auditing, and timeout policy a per-call-site choice. Outbound traffic belongs behind a declared capability that centralizes those controls. As a security rule this also scans test and migration files inside a module — they are importable code, so they are application surface too.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_raw_outbound_http"
12
- }
13
- ],
14
- "reference": "httpx / requests / urllib.request / urllib3 / aiohttp imports and the socket / http.client escape routes are refused in app modules; outbound calls go through a declared egress capability.",
15
- "opt_out": "# arch-allow-no-raw-outbound-http: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "Import-form rule: the sanctioned egress capability itself drives an HTTP client at runtime, so client presence and traffic cannot be attributed to an app module from inside the process. Network-level egress policy is deployment configuration, not a per-module framework seam."
19
- },
20
- "guide_topic": "capability",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/no_raw_outbound_http",
3
+ "surface": "backend",
4
+ "title": "App modules do not import raw HTTP clients; outbound calls use a capability",
5
+ "intent": "Directly importing an HTTP client library — or the lower-level socket / protocol primitives that reach the same network — makes SSRF protection, allowlists, egress auditing, and timeout policy a per-call-site choice. Outbound traffic belongs behind a declared capability that centralizes those controls. As a security rule this also scans test and migration files inside a module — they are importable code, so they are application surface too.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_raw_outbound_http"
12
+ }
13
+ ],
14
+ "reference": "httpx / requests / urllib.request / urllib3 / aiohttp imports and the socket / http.client escape routes are refused in app modules; outbound calls go through a declared egress capability.",
15
+ "opt_out": "# arch-allow-no-raw-outbound-http: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "Import-form rule: the sanctioned egress capability itself drives an HTTP client at runtime, so client presence and traffic cannot be attributed to an app module from inside the process. Network-level egress policy is deployment configuration, not a per-module framework seam."
19
+ },
20
+ "guide_topic": "capability",
21
+ "corpus": true
22
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/no_raw_session_construction",
3
- "surface": "backend",
4
- "title": "App code never constructs a database session or engine; it uses the injected request session",
5
- "intent": "The injected request session is where the framework's runtime controls live — the write guard, row scoping, and the audit hooks all ride on the session the framework hands out. A hand-constructed session or engine sits outside every one of those chokepoints by definition: its reads are unscoped, its writes are unaudited and unguarded, and it silently forks the app onto a second connection lifecycle.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_raw_session_construction"
12
- }
13
- ],
14
- "reference": "SessionDep injects the guarded request session; Session(...) / create_engine / sessionmaker construction in app code is refused.",
15
- "opt_out": "# arch-allow-no-raw-session-construction: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "Nothing in Python lets the framework refuse construction of a third-party class: a hand-built Session or engine never passes through any framework seam -- which is exactly why it must be caught in source. The guarded request session (SessionDep) is the surface the framework does control; an app-constructed one sits outside every chokepoint by definition."
19
- },
20
- "guide_topic": "service",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/no_raw_session_construction",
3
+ "surface": "backend",
4
+ "title": "App code never constructs a database session or engine; it uses the injected request session",
5
+ "intent": "The injected request session is where the framework's runtime controls live — the write guard, row scoping, and the audit hooks all ride on the session the framework hands out. A hand-constructed session or engine sits outside every one of those chokepoints by definition: its reads are unscoped, its writes are unaudited and unguarded, and it silently forks the app onto a second connection lifecycle.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_raw_session_construction"
12
+ }
13
+ ],
14
+ "reference": "SessionDep injects the guarded request session; Session(...) / create_engine / sessionmaker construction in app code is refused.",
15
+ "opt_out": "# arch-allow-no-raw-session-construction: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "Nothing in Python lets the framework refuse construction of a third-party class: a hand-built Session or engine never passes through any framework seam -- which is exactly why it must be caught in source. The guarded request session (SessionDep) is the surface the framework does control; an app-constructed one sits outside every chokepoint by definition."
19
+ },
20
+ "guide_topic": "service",
21
+ "corpus": true
22
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/no_star_imports",
3
- "surface": "backend",
4
- "title": "Import names explicitly, never with a wildcard",
5
- "intent": "A wildcard import pulls an unknown, changeable set of names into a namespace, so the module's real dependency surface is invisible and one upstream rename can silently shadow a local name. Naming each import keeps the dependency graph legible to readers and to the boundary checks that police it.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_star_imports"
12
- }
13
- ],
14
- "reference": "A 'from <module> import *' statement is refused; list the specific names the module uses instead.",
15
- "opt_out": "# arch-allow-no-star-imports: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "The invariant is a property of the authored import statement. Once the module is loaded the wildcard has already been expanded into concrete bindings, indistinguishable from explicit imports, so no runtime seam can reconstruct which spelling was written — the only faithful check reads the source."
19
- },
20
- "guide_topic": "module",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/no_star_imports",
3
+ "surface": "backend",
4
+ "title": "Import names explicitly, never with a wildcard",
5
+ "intent": "A wildcard import pulls an unknown, changeable set of names into a namespace, so the module's real dependency surface is invisible and one upstream rename can silently shadow a local name. Naming each import keeps the dependency graph legible to readers and to the boundary checks that police it.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_star_imports"
12
+ }
13
+ ],
14
+ "reference": "A 'from <module> import *' statement is refused; list the specific names the module uses instead.",
15
+ "opt_out": "# arch-allow-no-star-imports: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The invariant is a property of the authored import statement. Once the module is loaded the wildcard has already been expanded into concrete bindings, indistinguishable from explicit imports, so no runtime seam can reconstruct which spelling was written — the only faithful check reads the source."
19
+ },
20
+ "guide_topic": "module",
21
+ "corpus": true
22
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/no_todo_fixme",
3
- "surface": "backend",
4
- "title": "No placeholder comments for deferred work",
5
- "intent": "A TODO, FIXME, HACK, or XXX comment marks unfinished work that ships anyway and is almost never revisited, so the gap it names silently becomes permanent. Finish the behaviour or delete the dead branch — do not leave a note promising a fix that will not come.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_todo_fixme"
12
- }
13
- ],
14
- "reference": "The markers TODO, FIXME, HACK, and XXX are refused when they appear in a real comment token; matching ignores identical text inside strings or docstrings.",
15
- "opt_out": "# arch-allow-no-todo-fixme: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "The invariant is a property of the authored comments, which the interpreter discards entirely — there is nothing at runtime to observe. Only a source-form check can see the placeholder."
19
- },
20
- "guide_topic": "module",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/no_todo_fixme",
3
+ "surface": "backend",
4
+ "title": "No placeholder comments for deferred work",
5
+ "intent": "A TODO, FIXME, HACK, or XXX comment marks unfinished work that ships anyway and is almost never revisited, so the gap it names silently becomes permanent. Finish the behaviour or delete the dead branch — do not leave a note promising a fix that will not come.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_todo_fixme"
12
+ }
13
+ ],
14
+ "reference": "The markers TODO, FIXME, HACK, and XXX are refused when they appear in a real comment token; matching ignores identical text inside strings or docstrings.",
15
+ "opt_out": "# arch-allow-no-todo-fixme: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The invariant is a property of the authored comments, which the interpreter discards entirely — there is nothing at runtime to observe. Only a source-form check can see the placeholder."
19
+ },
20
+ "guide_topic": "module",
21
+ "corpus": true
22
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/no_unique_columns_on_soft_delete_models",
3
- "surface": "backend",
4
- "title": "Soft-delete models never declare a full-table unique constraint (dead rows block reuse)",
5
- "intent": "A soft-deleted row stays in the table, so it keeps occupying every full-table unique index: the \"deleted\" value (an email, a slug, a code) can never be used again, surfacing as an inexplicable conflict long after the delete. Scope uniqueness to the live rows with a partial unique index that excludes soft-deleted rows — which this rule accepts — or deactivate instead of deleting.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_unique_columns_on_soft_delete_models"
12
- }
13
- ],
14
- "reference": "A partial unique index in __table_args__ (unique=True with postgresql_where / sqlite_where on deleted_at IS NULL) is the accepted shape; the identity user table keeps email unique by deactivating instead of deleting.",
15
- "opt_out": "# arch-allow-no-unique-columns-on-soft-delete-models: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "The constraint is baked into migration-produced DDL; at runtime the schema already exists, and the failure mode (a dead row blocking reuse) surfaces as the database's own constraint error, mapped to the uniform 409 envelope -- handled, but only preventable when the model is authored."
19
- },
20
- "guide_topic": "module",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/no_unique_columns_on_soft_delete_models",
3
+ "surface": "backend",
4
+ "title": "Soft-delete models never declare a full-table unique constraint (dead rows block reuse)",
5
+ "intent": "A soft-deleted row stays in the table, so it keeps occupying every full-table unique index: the \"deleted\" value (an email, a slug, a code) can never be used again, surfacing as an inexplicable conflict long after the delete. Scope uniqueness to the live rows with a partial unique index that excludes soft-deleted rows — which this rule accepts — or deactivate instead of deleting.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_unique_columns_on_soft_delete_models"
12
+ }
13
+ ],
14
+ "reference": "A partial unique index in __table_args__ (unique=True with postgresql_where / sqlite_where on deleted_at IS NULL) is the accepted shape; the identity user table keeps email unique by deactivating instead of deleting.",
15
+ "opt_out": "# arch-allow-no-unique-columns-on-soft-delete-models: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The constraint is baked into migration-produced DDL; at runtime the schema already exists, and the failure mode (a dead row blocking reuse) surfaces as the database's own constraint error, mapped to the uniform 409 envelope -- handled, but only preventable when the model is authored."
19
+ },
20
+ "guide_topic": "module",
21
+ "corpus": true
22
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/offset_queries_declare_ordering",
3
- "surface": "backend",
4
- "title": "An offset-paginated query must declare an explicit ordering",
5
- "intent": "Row order without an explicit ordering clause is undefined, so paging a query by a numeric offset over an unordered result can silently skip or repeat rows between pages. A query that skips a number of rows must also declare a deterministic ordering so the page sequence is stable and every row is seen exactly once.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_offset_queries_declare_ordering"
12
- }
13
- ],
14
- "reference": "A function that calls .offset(...) but no .order_by(...) is flagged at the offset call; add an explicit ordering, or page through the framework's ordered pagination helper.",
15
- "opt_out": "# arch-allow-offset-queries-declare-ordering: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "The unordered-offset defect is a property of the authored query expression; the database executes an unordered offset without error, so there is no fail-closed runtime signal, and the ordering guarantee exists only by constraining the source."
19
- },
20
- "guide_topic": "service",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/offset_queries_declare_ordering",
3
+ "surface": "backend",
4
+ "title": "An offset-paginated query must declare an explicit ordering",
5
+ "intent": "Row order without an explicit ordering clause is undefined, so paging a query by a numeric offset over an unordered result can silently skip or repeat rows between pages. A query that skips a number of rows must also declare a deterministic ordering so the page sequence is stable and every row is seen exactly once.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_offset_queries_declare_ordering"
12
+ }
13
+ ],
14
+ "reference": "A function that calls .offset(...) but no .order_by(...) is flagged at the offset call; add an explicit ordering, or page through the framework's ordered pagination helper.",
15
+ "opt_out": "# arch-allow-offset-queries-declare-ordering: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The unordered-offset defect is a property of the authored query expression; the database executes an unordered offset without error, so there is no fail-closed runtime signal, and the ordering guarantee exists only by constraining the source."
19
+ },
20
+ "guide_topic": "service",
21
+ "corpus": true
22
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/path_id_params_are_uuid",
3
- "surface": "backend",
4
- "title": "A route path parameter naming a resource id must be typed as a UUID",
5
- "intent": "A URL path segment that names a resource id (spelled id or ending in _id) identifies a specific row, and the platform issues UUID identifiers. Typing that path parameter as a UUID rejects a malformed identifier at the request boundary, before it reaches the data layer, instead of letting an untyped or wrongly-typed value through. Only path parameters are in scope; query and body parameters are unaffected.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_path_id_params_are_uuid"
12
- }
13
- ],
14
- "reference": "A handler parameter that also appears in the route decorator's URL template and is named id or ends in _id must be annotated uuid.UUID (a uuid.UUID attribute or a bare UUID name); a missing or non-UUID annotation is flagged at the handler.",
15
- "opt_out": "# arch-allow-path-id-params-are-uuid: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "The rule constrains the authored route signature (the declared parameter type), a source-form property of the handler rather than an observable runtime event; the framework does not re-derive intended id-parameter types at mount time, so a mis-typed parameter merely validates against the wrong type with no separate signal that a UUID was intended."
19
- },
20
- "guide_topic": "module",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/path_id_params_are_uuid",
3
+ "surface": "backend",
4
+ "title": "A route path parameter naming a resource id must be typed as a UUID",
5
+ "intent": "A URL path segment that names a resource id (spelled id or ending in _id) identifies a specific row, and the platform issues UUID identifiers. Typing that path parameter as a UUID rejects a malformed identifier at the request boundary, before it reaches the data layer, instead of letting an untyped or wrongly-typed value through. Only path parameters are in scope; query and body parameters are unaffected.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_path_id_params_are_uuid"
12
+ }
13
+ ],
14
+ "reference": "A handler parameter that also appears in the route decorator's URL template and is named id or ends in _id must be annotated uuid.UUID (a uuid.UUID attribute or a bare UUID name); a missing or non-UUID annotation is flagged at the handler.",
15
+ "opt_out": "# arch-allow-path-id-params-are-uuid: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The rule constrains the authored route signature (the declared parameter type), a source-form property of the handler rather than an observable runtime event; the framework does not re-derive intended id-parameter types at mount time, so a mis-typed parameter merely validates against the wrong type with no separate signal that a UUID was intended."
19
+ },
20
+ "guide_topic": "module",
21
+ "corpus": true
22
+ }
@@ -1,27 +1,27 @@
1
- {
2
- "id": "backend/policy_refs_resolve",
3
- "surface": "backend",
4
- "title": "Every typed authority a policy cites resolves in the app's authority registry",
5
- "intent": "The build-time half of control-plane registry resolution: boot validation already refuses an undeclared authority at runtime; this rule catches the same drift at the gate, before the app ever boots. Any reference that traces to the app's authority registry — via a module alias or a name imported from the registry — must name something the registry actually declares. References the scan cannot trace to the registry (kernel default roles, locally built objects) are left to the runtime check, so the rule stays precise, never heuristic.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_policy_refs_resolve"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.core",
16
- "ref": "validation_errors"
17
- }
18
- ],
19
- "reference": "control_plane/permissions.py is the registry; aliased references like perms.BILLING_READ must resolve there, kernel defaults like Roles.EDITOR are left to ControlPlane.validation_errors at boot.",
20
- "opt_out": "# arch-allow-policy-refs-resolve: <reason>",
21
- "runtime": {
22
- "applicability": "required",
23
- "rationale": "Boot validation refuses an undeclared authority before the app serves (ControlPlane.validation_errors -> BootError); the build-time rule catches the same drift earlier, at the gate."
24
- },
25
- "guide_topic": "policy",
26
- "corpus": true
27
- }
1
+ {
2
+ "id": "backend/policy_refs_resolve",
3
+ "surface": "backend",
4
+ "title": "Every typed authority a policy cites resolves in the app's authority registry",
5
+ "intent": "The build-time half of control-plane registry resolution: boot validation already refuses an undeclared authority at runtime; this rule catches the same drift at the gate, before the app ever boots. Any reference that traces to the app's authority registry — via a module alias or a name imported from the registry — must name something the registry actually declares. References the scan cannot trace to the registry (kernel default roles, locally built objects) are left to the runtime check, so the rule stays precise, never heuristic.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_policy_refs_resolve"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "validation_errors"
17
+ }
18
+ ],
19
+ "reference": "control_plane/permissions.py is the registry; aliased references like perms.BILLING_READ must resolve there, kernel defaults like Roles.EDITOR are left to ControlPlane.validation_errors at boot.",
20
+ "opt_out": "# arch-allow-policy-refs-resolve: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "Boot validation refuses an undeclared authority before the app serves (ControlPlane.validation_errors -> BootError); the build-time rule catches the same drift earlier, at the gate."
24
+ },
25
+ "guide_topic": "policy",
26
+ "corpus": true
27
+ }
@@ -1,26 +1,26 @@
1
- {
2
- "id": "backend/public_modules_are_read_only",
3
- "surface": "backend",
4
- "title": "A public (unauthenticated) module must not expose a mutating route",
5
- "intent": "A public policy drops authentication for the whole module, so a mutating route under it is an unauthenticated write — almost always an accident, and the broken-access-control footgun the deny-by-default posture exists to prevent. A genuinely public write (a sign-up / contact form / webhook receiver) is rare and deliberate, so it stays available through the governed escape hatch: a justified opt-out marker ratcheted by the escape-hatch budget, making the unauthenticated write visible and budgeted rather than silent. Gate the writes behind a policy with a write role, or justify the public write explicitly. The runtime half is the boot refusal of a public module that exposes a mutating route unless its policy opts in explicitly with a reason — each layer carries its own justified opt-out.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_public_modules_are_read_only"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.core",
16
- "ref": "_validate_public_modules_read_only"
17
- }
18
- ],
19
- "runtime": {
20
- "applicability": "required"
21
- },
22
- "reference": "Policy.public(reason=...) declares the public module; create_app -> _validate_public_modules_read_only refuses public writes unless the policy opts in via Policy.public_write(reason=...).",
23
- "opt_out": "# arch-allow-public-modules-are-read-only: <reason>",
24
- "guide_topic": "policy",
25
- "corpus": true
26
- }
1
+ {
2
+ "id": "backend/public_modules_are_read_only",
3
+ "surface": "backend",
4
+ "title": "A public (unauthenticated) module must not expose a mutating route",
5
+ "intent": "A public policy drops authentication for the whole module, so a mutating route under it is an unauthenticated write — almost always an accident, and the broken-access-control footgun the deny-by-default posture exists to prevent. A genuinely public write (a sign-up / contact form / webhook receiver) is rare and deliberate, so it stays available through the governed escape hatch: a justified opt-out marker ratcheted by the escape-hatch budget, making the unauthenticated write visible and budgeted rather than silent. Gate the writes behind a policy with a write role, or justify the public write explicitly. The runtime half is the boot refusal of a public module that exposes a mutating route unless its policy opts in explicitly with a reason — each layer carries its own justified opt-out.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_public_modules_are_read_only"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "_validate_public_modules_read_only"
17
+ }
18
+ ],
19
+ "runtime": {
20
+ "applicability": "required"
21
+ },
22
+ "reference": "Policy.public(reason=...) declares the public module; create_app -> _validate_public_modules_read_only refuses public writes unless the policy opts in via Policy.public_write(reason=...).",
23
+ "opt_out": "# arch-allow-public-modules-are-read-only: <reason>",
24
+ "guide_topic": "policy",
25
+ "corpus": true
26
+ }
@@ -1,27 +1,27 @@
1
- {
2
- "id": "backend/reads_use_base_query",
3
- "surface": "backend",
4
- "title": "A scope-trait model is read through the composed scoped query, never a raw query built from scratch",
5
- "intent": "A model that mixes a soft-delete or tenant-scope trait carries row scope. A bespoke read that queries the model directly — instead of building on the composed scoped query — drops that scope, leaking soft-deleted or cross-tenant rows (closing the composition point to overrides did not close a new read method that never calls it). Build reads on the scoped query and the declared filter seam; the request session re-applies the scope to single-entity reads as the runtime backstop, and this rule is the build-time early warning. The one sanctioned raw query — the scoped composition point itself — lives in the framework, not a module, so it is never scanned here.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_reads_use_base_query"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.core",
16
- "ref": "apply_row_scope"
17
- }
18
- ],
19
- "reference": "select(Model) on a SoftDeleteMixin / TenantScopedMixin model is refused in modules (ADR 0017, F1); build on base_query() / business_filters(), with apply_row_scope as the runtime backstop.",
20
- "opt_out": "# arch-allow-reads-use-base-query: <reason>",
21
- "runtime": {
22
- "applicability": "required",
23
- "rationale": "The request session re-scopes user-facing ORM reads idempotently (apply_row_scope): a bespoke select(Model) that skipped base_query() is filtered for soft-delete / registered predicates again at execution, so the leak the rule warns about is closed even when the source pattern slips through."
24
- },
25
- "guide_topic": "service",
26
- "corpus": true
27
- }
1
+ {
2
+ "id": "backend/reads_use_base_query",
3
+ "surface": "backend",
4
+ "title": "A scope-trait model is read through the composed scoped query, never a raw query built from scratch",
5
+ "intent": "A model that mixes a soft-delete or tenant-scope trait carries row scope. A bespoke read that queries the model directly — instead of building on the composed scoped query — drops that scope, leaking soft-deleted or cross-tenant rows (closing the composition point to overrides did not close a new read method that never calls it). Build reads on the scoped query and the declared filter seam; the request session re-applies the scope to single-entity reads as the runtime backstop, and this rule is the build-time early warning. The one sanctioned raw query — the scoped composition point itself — lives in the framework, not a module, so it is never scanned here.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_reads_use_base_query"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "apply_row_scope"
17
+ }
18
+ ],
19
+ "reference": "select(Model) on a SoftDeleteMixin / TenantScopedMixin model is refused in modules (ADR 0017, F1); build on base_query() / business_filters(), with apply_row_scope as the runtime backstop.",
20
+ "opt_out": "# arch-allow-reads-use-base-query: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "The request session re-scopes user-facing ORM reads idempotently (apply_row_scope): a bespoke select(Model) that skipped base_query() is filtered for soft-delete / registered predicates again at execution, so the leak the rule warns about is closed even when the source pattern slips through."
24
+ },
25
+ "guide_topic": "service",
26
+ "corpus": true
27
+ }