@terpjs/spec 0.14.0 → 0.15.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/response_model_not_table_model",
3
- "surface": "backend",
4
- "title": "A route's declared response type is a read DTO, never the persisted table model",
5
- "intent": "routes_declare_response_model proves a response type is declared; this rule proves it is not the persisted table itself. A declared response type set to a table model — directly or wrapped in a page envelope or collection — serializes the stored row, so a column such as a password hash leaks straight through the boundary. Return a read DTO listing exactly the safe fields instead.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_response_model_not_table_model"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.core",
16
- "ref": "_validate_router_response_models"
17
- }
18
- ],
19
- "reference": "response_model set to a table=True model, directly or wrapped in Page[...] / list[...] (the Page[User] footgun), is refused; return a *Read schema on terp.core.BaseSchema.",
20
- "opt_out": "# arch-allow-response-model-not-table-model: <reason>",
21
- "runtime": {
22
- "applicability": "required",
23
- "rationale": "Boot refuses a route whose response_model is (or wraps) a table=True ORM model (_validate_router_response_models -> BootError), with cross-package and nested-router fidelity a source scan cannot have."
24
- },
25
- "guide_topic": "module",
26
- "corpus": true
27
- }
1
+ {
2
+ "id": "backend/response_model_not_table_model",
3
+ "surface": "backend",
4
+ "title": "A route's declared response type is a read DTO, never the persisted table model",
5
+ "intent": "routes_declare_response_model proves a response type is declared; this rule proves it is not the persisted table itself. A declared response type set to a table model — directly or wrapped in a page envelope or collection — serializes the stored row, so a column such as a password hash leaks straight through the boundary. Return a read DTO listing exactly the safe fields instead.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_response_model_not_table_model"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "_validate_router_response_models"
17
+ }
18
+ ],
19
+ "reference": "response_model set to a table=True model, directly or wrapped in Page[...] / list[...] (the Page[User] footgun), is refused; return a *Read schema on terp.core.BaseSchema.",
20
+ "opt_out": "# arch-allow-response-model-not-table-model: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "Boot refuses a route whose response_model is (or wraps) a table=True ORM model (_validate_router_response_models -> BootError), with cross-package and nested-router fidelity a source scan cannot have."
24
+ },
25
+ "guide_topic": "module",
26
+ "corpus": true
27
+ }
@@ -1,27 +1,27 @@
1
- {
2
- "id": "backend/routes_declare_response_model",
3
- "surface": "backend",
4
- "title": "Every content route declares its response type (no bare data out)",
5
- "intent": "A route with neither a declared response type nor a no-body status code can serialize a bare stored object out of the boundary — whatever the handler happens to return, including columns that were never meant to leave the app. Declaring the response type makes the boundary shape explicit and reviewable; both decorator routes and imperative route registration are checked.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_routes_declare_response_model"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.core",
16
- "ref": "_validate_routes_declare_response_model"
17
- }
18
- ],
19
- "reference": "response_model= on @router.<verb>(...) decorators and router.add_api_route(...); a no-body status_code (204/205/304) is the accepted alternative.",
20
- "opt_out": "# arch-allow-routes-declare-response-model: <reason>",
21
- "runtime": {
22
- "applicability": "required",
23
- "rationale": "At boot, the route scan on the composition seam (the same seam as _validate_router_response_models) walks every composed route \u2014 decorator, imperative add_api_route, and nested included routers alike \u2014 and raises a BootError naming this rule and the route when a content route declares no response_model. No-body 204/205/304 statuses and Response-subclass-annotated non-content routes stay exempt, so the control refuses exactly the routes that would serialize a bare object out of the boundary."
24
- },
25
- "guide_topic": "module",
26
- "corpus": true
27
- }
1
+ {
2
+ "id": "backend/routes_declare_response_model",
3
+ "surface": "backend",
4
+ "title": "Every content route declares its response type (no bare data out)",
5
+ "intent": "A route with neither a declared response type nor a no-body status code can serialize a bare stored object out of the boundary — whatever the handler happens to return, including columns that were never meant to leave the app. Declaring the response type makes the boundary shape explicit and reviewable; both decorator routes and imperative route registration are checked.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_routes_declare_response_model"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "_validate_routes_declare_response_model"
17
+ }
18
+ ],
19
+ "reference": "response_model= on @router.<verb>(...) decorators and router.add_api_route(...); a no-body status_code (204/205/304) is the accepted alternative.",
20
+ "opt_out": "# arch-allow-routes-declare-response-model: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "At boot, the route scan on the composition seam (the same seam as _validate_router_response_models) walks every composed route \u2014 decorator, imperative add_api_route, and nested included routers alike \u2014 and raises a BootError naming this rule and the route when a content route declares no response_model. No-body 204/205/304 statuses and Response-subclass-annotated non-content routes stay exempt, so the control refuses exactly the routes that would serialize a bare object out of the boundary."
24
+ },
25
+ "guide_topic": "module",
26
+ "corpus": true
27
+ }
@@ -1,31 +1,31 @@
1
- {
2
- "id": "backend/safe_methods_are_read_only",
3
- "surface": "backend",
4
- "title": "A handler reachable via a safe HTTP method must not mutate",
5
- "intent": "The deny-by-default guard derives the required role tier from the HTTP method: a safe method (GET / HEAD / OPTIONS) is authorized against the policy's read requirement, a mutating one against the write requirement. So a handler reachable through a safe method that calls a mutating service method performs a write a read-tier caller cleared — a privilege-tier escape (a viewer triggering an editor/admin write via a read request). This holds for a mixed-method route too: the safe-method invocation runs at the read tier, so a handler that always mutates is flagged (split it, or branch on the method behind a mutating route). Both decorator and imperative route registration are checked. Put the write behind a mutating method so it is authorized at the write tier. The runtime half marks a safe-method request read-only, so the write chokepoint refuses the write.",
6
- "layer": "black-box",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_safe_methods_are_read_only"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.core",
16
- "ref": "build_read_only_request_binder"
17
- },
18
- {
19
- "kind": "black-box",
20
- "tool": "@terp/conformance",
21
- "ref": "standard: safe methods observably mutate nothing"
22
- }
23
- ],
24
- "opt_out": "# arch-allow-safe-methods-are-read-only: <reason>",
25
- "runtime": {
26
- "applicability": "required"
27
- },
28
- "reference": "Mutating BaseService calls (create/update/delete/_save/_remove) in safe-method handlers are flagged; create_app binds safe-method requests read-only (build_read_only_request_binder).",
29
- "guide_topic": "module",
30
- "corpus": true
31
- }
1
+ {
2
+ "id": "backend/safe_methods_are_read_only",
3
+ "surface": "backend",
4
+ "title": "A handler reachable via a safe HTTP method must not mutate",
5
+ "intent": "The deny-by-default guard derives the required role tier from the HTTP method: a safe method (GET / HEAD / OPTIONS) is authorized against the policy's read requirement, a mutating one against the write requirement. So a handler reachable through a safe method that calls a mutating service method performs a write a read-tier caller cleared — a privilege-tier escape (a viewer triggering an editor/admin write via a read request). This holds for a mixed-method route too: the safe-method invocation runs at the read tier, so a handler that always mutates is flagged (split it, or branch on the method behind a mutating route). Both decorator and imperative route registration are checked. Put the write behind a mutating method so it is authorized at the write tier. The runtime half marks a safe-method request read-only, so the write chokepoint refuses the write.",
6
+ "layer": "black-box",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_safe_methods_are_read_only"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "build_read_only_request_binder"
17
+ },
18
+ {
19
+ "kind": "black-box",
20
+ "tool": "@terp/conformance",
21
+ "ref": "standard: safe methods observably mutate nothing"
22
+ }
23
+ ],
24
+ "opt_out": "# arch-allow-safe-methods-are-read-only: <reason>",
25
+ "runtime": {
26
+ "applicability": "required"
27
+ },
28
+ "reference": "Mutating BaseService calls (create/update/delete/_save/_remove) in safe-method handlers are flagged; create_app binds safe-method requests read-only (build_read_only_request_binder).",
29
+ "guide_topic": "module",
30
+ "corpus": true
31
+ }
@@ -1,32 +1,32 @@
1
- {
2
- "id": "backend/schemas_exclude_sensitive_fields",
3
- "surface": "backend",
4
- "title": "A read / response DTO never exposes a credential-shaped field",
5
- "intent": "Routes serialize a response DTO out of the boundary, so a field whose name reads like a secret — a password, a password hash, or a *secret / *api_key / *token suffix — would leak the credential to every caller. A response DTO is a schema in the response role: a read/response base schema or any class wired as a route's declared response type (so an input DTO mistakenly reused as a response is caught), excluding the inputs that are only ever request bodies (a client supplies a password) and table models (a table may store the hash). Plain helper classes are not policed. This guards the gap response_model_not_table_model leaves: a hand-rolled read DTO that copies the stored hash. (Version counters are integers, not secrets.)",
6
- "layer": "black-box",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_schemas_exclude_sensitive_fields"
12
- },
13
- {
14
- "kind": "black-box",
15
- "tool": "@terp/conformance",
16
- "ref": "standard: responses never expose credential-shaped fields"
17
- },
18
- {
19
- "kind": "runtime",
20
- "tool": "terp.core",
21
- "ref": "_validate_schemas_exclude_sensitive_fields"
22
- }
23
- ],
24
- "reference": "BaseSchema / BaseUpdateSchema models and response_model= classes are scanned for password / hashed_password / *secret / *api_key / *token field names; request-body-only inputs and table=True models are excluded.",
25
- "opt_out": "# arch-allow-schemas-exclude-sensitive-fields: <reason>",
26
- "runtime": {
27
- "applicability": "required",
28
- "rationale": "At boot, the route scan on the composition seam (the same seam as _validate_router_response_models) walks every composed route \u2014 decorator, imperative add_api_route, and nested included routers alike \u2014 and raises a BootError naming this rule and the route when a declared response DTO carries a credential-shaped field (the same underscore-delimited word match, with the token_version / version / trailing-token exclusions, as the build-time rule; framework-vetted terp.* DTOs exempt). The black-box probe remains a conformance-suite observation on top of this in-app fail-closed control."
29
- },
30
- "guide_topic": "module",
31
- "corpus": true
32
- }
1
+ {
2
+ "id": "backend/schemas_exclude_sensitive_fields",
3
+ "surface": "backend",
4
+ "title": "A read / response DTO never exposes a credential-shaped field",
5
+ "intent": "Routes serialize a response DTO out of the boundary, so a field whose name reads like a secret — a password, a password hash, or a *secret / *api_key / *token suffix — would leak the credential to every caller. A response DTO is a schema in the response role: a read/response base schema or any class wired as a route's declared response type (so an input DTO mistakenly reused as a response is caught), excluding the inputs that are only ever request bodies (a client supplies a password) and table models (a table may store the hash). Plain helper classes are not policed. This guards the gap response_model_not_table_model leaves: a hand-rolled read DTO that copies the stored hash. (Version counters are integers, not secrets.)",
6
+ "layer": "black-box",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_schemas_exclude_sensitive_fields"
12
+ },
13
+ {
14
+ "kind": "black-box",
15
+ "tool": "@terp/conformance",
16
+ "ref": "standard: responses never expose credential-shaped fields"
17
+ },
18
+ {
19
+ "kind": "runtime",
20
+ "tool": "terp.core",
21
+ "ref": "_validate_schemas_exclude_sensitive_fields"
22
+ }
23
+ ],
24
+ "reference": "BaseSchema / BaseUpdateSchema models and response_model= classes are scanned for password / hashed_password / *secret / *api_key / *token field names; request-body-only inputs and table=True models are excluded.",
25
+ "opt_out": "# arch-allow-schemas-exclude-sensitive-fields: <reason>",
26
+ "runtime": {
27
+ "applicability": "required",
28
+ "rationale": "At boot, the route scan on the composition seam (the same seam as _validate_router_response_models) walks every composed route \u2014 decorator, imperative add_api_route, and nested included routers alike \u2014 and raises a BootError naming this rule and the route when a declared response DTO carries a credential-shaped field (the same underscore-delimited word match, with the token_version / version / trailing-token exclusions, as the build-time rule; framework-vetted terp.* DTOs exempt). The black-box probe remains a conformance-suite observation on top of this in-app fail-closed control."
29
+ },
30
+ "guide_topic": "module",
31
+ "corpus": true
32
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/session_imported_from_sqlmodel",
3
- "surface": "backend",
4
- "title": "The ORM session type is imported from the framework's canonical source, never the underlying library",
5
- "intent": "The framework standardises on one session type everywhere — the injected request session, the service layer, the write guard, and the migrations all speak the same type, re-exported from one canonical source. Importing the session type from the underlying ORM library instead quietly forks the app onto a second session type, so the rule names the one canonical import. (Constructing a session is separately banned by no_raw_session_construction — this only fixes the spelling.)",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_session_imported_from_sqlmodel"
12
- }
13
- ],
14
- "reference": "from sqlmodel import Session is canonical (SQLModel re-exports SQLAlchemy's); imports from sqlalchemy / sqlalchemy.orm are refused.",
15
- "opt_out": "# arch-allow-session-imported-from-sqlmodel: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "Which module a symbol was imported from is erased at runtime: sqlmodel's Session subclasses SQLAlchemy's, and the object in play is the framework-provided session either way. The typed exec/scoping benefits are carried by the session the framework hands out."
19
- },
20
- "guide_topic": "service",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/session_imported_from_sqlmodel",
3
+ "surface": "backend",
4
+ "title": "The ORM session type is imported from the framework's canonical source, never the underlying library",
5
+ "intent": "The framework standardises on one session type everywhere — the injected request session, the service layer, the write guard, and the migrations all speak the same type, re-exported from one canonical source. Importing the session type from the underlying ORM library instead quietly forks the app onto a second session type, so the rule names the one canonical import. (Constructing a session is separately banned by no_raw_session_construction — this only fixes the spelling.)",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_session_imported_from_sqlmodel"
12
+ }
13
+ ],
14
+ "reference": "from sqlmodel import Session is canonical (SQLModel re-exports SQLAlchemy's); imports from sqlalchemy / sqlalchemy.orm are refused.",
15
+ "opt_out": "# arch-allow-session-imported-from-sqlmodel: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "Which module a symbol was imported from is erased at runtime: sqlmodel's Session subclasses SQLAlchemy's, and the object in play is the framework-provided session either way. The typed exec/scoping benefits are carried by the session the framework hands out."
19
+ },
20
+ "guide_topic": "service",
21
+ "corpus": true
22
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/table_models_use_base_table",
3
- "surface": "backend",
4
- "title": "Every ORM table model inherits the platform base table (no bare tables)",
5
- "intent": "A table model that skips the platform base table bypasses the framework's managed identity, timestamps, and optimistic-concurrency version — a model living outside the control-plane contract, which every chokepoint (the service layer, audit, concurrency) presupposes.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_table_models_use_base_table"
12
- }
13
- ],
14
- "reference": "BaseTable supplies the UUID id, created_at/updated_at and the OCC version; a bare SQLModel table=True model is refused.",
15
- "opt_out": "# arch-allow-table-models-use-base-table: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "Inheritance is an authoring-shape fact, and Python generics are not runtime-enforced. The chokepoint contracts that matter at runtime (OCC version, managed columns, audit identity) presuppose BaseTable rather than police it -- a bare SQLModel table simply never fits the service seam."
19
- },
20
- "guide_topic": "module",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/table_models_use_base_table",
3
+ "surface": "backend",
4
+ "title": "Every ORM table model inherits the platform base table (no bare tables)",
5
+ "intent": "A table model that skips the platform base table bypasses the framework's managed identity, timestamps, and optimistic-concurrency version — a model living outside the control-plane contract, which every chokepoint (the service layer, audit, concurrency) presupposes.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_table_models_use_base_table"
12
+ }
13
+ ],
14
+ "reference": "BaseTable supplies the UUID id, created_at/updated_at and the OCC version; a bare SQLModel table=True model is refused.",
15
+ "opt_out": "# arch-allow-table-models-use-base-table: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "Inheritance is an authoring-shape fact, and Python generics are not runtime-enforced. The chokepoint contracts that matter at runtime (OCC version, managed columns, audit identity) presuppose BaseTable rather than police it -- a bare SQLModel table simply never fits the service seam."
19
+ },
20
+ "guide_topic": "module",
21
+ "corpus": true
22
+ }
@@ -1,27 +1,27 @@
1
- {
2
- "id": "backend/tables_have_migrations",
3
- "surface": "backend",
4
- "title": "Every app module that defines a table model ships a packaged migration history",
5
- "intent": "A deployed Terp app builds its schema from packaged migrations, never from dev-time schema auto-creation — so a module that declares a table model but ships no migration revision would deploy with that table missing, and the first request would fail on a nonexistent table. This rule fails the build first, and the running system's migration guard refuses the same violation at boot (the two halves of the migration control). Generate and commit the module's migration revision.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_tables_have_migrations"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.migrations",
16
- "ref": "assert_no_missing_histories"
17
- }
18
- ],
19
- "reference": "terp migrate make <name> generates the module's migrations/versions/ revision; assert_migrations_current is the production boot guard (it runs assert_no_missing_histories, so a table-owning package without any history refuses to boot), create_all the refused dev shortcut.",
20
- "opt_out": "# arch-allow-tables-have-migrations: <reason>",
21
- "runtime": {
22
- "applicability": "required",
23
- "rationale": "The boot guard refuses the standalone missing-history case this rule exists for: a declared package that defines table models but ships no migration history at all raises a MissingMigrationsError naming the packages -- fail closed at boot, before a request can hit a table that was never created. The pending-revisions half of the same guard keeps covering histories that exist but are behind head."
24
- },
25
- "guide_topic": "migrations",
26
- "corpus": true
27
- }
1
+ {
2
+ "id": "backend/tables_have_migrations",
3
+ "surface": "backend",
4
+ "title": "Every app module that defines a table model ships a packaged migration history",
5
+ "intent": "A deployed Terp app builds its schema from packaged migrations, never from dev-time schema auto-creation — so a module that declares a table model but ships no migration revision would deploy with that table missing, and the first request would fail on a nonexistent table. This rule fails the build first, and the running system's migration guard refuses the same violation at boot (the two halves of the migration control). Generate and commit the module's migration revision.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_tables_have_migrations"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.migrations",
16
+ "ref": "assert_no_missing_histories"
17
+ }
18
+ ],
19
+ "reference": "terp migrate make <name> generates the module's migrations/versions/ revision; assert_migrations_current is the production boot guard (it runs assert_no_missing_histories, so a table-owning package without any history refuses to boot), create_all the refused dev shortcut.",
20
+ "opt_out": "# arch-allow-tables-have-migrations: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "The boot guard refuses the standalone missing-history case this rule exists for: a declared package that defines table models but ships no migration history at all raises a MissingMigrationsError naming the packages -- fail closed at boot, before a request can hit a table that was never created. The pending-revisions half of the same guard keeps covering histories that exist but are behind head."
24
+ },
25
+ "guide_topic": "migrations",
26
+ "corpus": true
27
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/tenant_scoped_models_use_scoped_service",
3
- "surface": "backend",
4
- "title": "A tenant-scoped model's service extends the tenant-scoped service base",
5
- "intent": "This makes tenant isolation structural on the write side: reads of a tenant-scoped model are already filtered centrally by the registered tenant scope predicate, but the tenant-scoped service base is what stamps the tenant on create — so a plain service (which would insert an unstamped, never-visible row) is rejected at build time.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_tenant_scoped_models_use_scoped_service"
12
- }
13
- ],
14
- "reference": "A TenantScopedMixin model's service extends TenantScopedService (stamps tenant_id on create, ADR 0017); a plain BaseService is refused.",
15
- "opt_out": "# arch-allow-tenant-scoped-models-use-scoped-service: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "Service lineage is an authoring-shape fact. The leak-relevant runtime control is the registered row-scope predicate (apply_row_scope, declared under the scoping rules), which filters reads for the mixin regardless of which service touched the row; extending TenantScopedService is what makes writes stamp tenant_id so rows stay reachable in their tenant."
19
- },
20
- "guide_topic": "tenancy",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/tenant_scoped_models_use_scoped_service",
3
+ "surface": "backend",
4
+ "title": "A tenant-scoped model's service extends the tenant-scoped service base",
5
+ "intent": "This makes tenant isolation structural on the write side: reads of a tenant-scoped model are already filtered centrally by the registered tenant scope predicate, but the tenant-scoped service base is what stamps the tenant on create — so a plain service (which would insert an unstamped, never-visible row) is rejected at build time.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_tenant_scoped_models_use_scoped_service"
12
+ }
13
+ ],
14
+ "reference": "A TenantScopedMixin model's service extends TenantScopedService (stamps tenant_id on create, ADR 0017); a plain BaseService is refused.",
15
+ "opt_out": "# arch-allow-tenant-scoped-models-use-scoped-service: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "Service lineage is an authoring-shape fact. The leak-relevant runtime control is the registered row-scope predicate (apply_row_scope, declared under the scoping rules), which filters reads for the mixin regardless of which service touched the row; extending TenantScopedService is what makes writes stamp tenant_id so rows stay reachable in their tenant."
19
+ },
20
+ "guide_topic": "tenancy",
21
+ "corpus": true
22
+ }
@@ -1,21 +1,21 @@
1
- {
2
- "id": "backend/ungoverned_escape_hatch",
3
- "surface": "backend",
4
- "title": "The fail-closed ungoverned-opt-out condition, as structured violations",
5
- "intent": "An opt-out marker the governance contract does not honour is itself a violation, reported in-band as structured findings (one per marker line) rather than crashing the harness: a marker used with no escape-hatch budget governing it, and a marker whose missing justification means it never suppressed anything. Either way the fix is to govern the opt-out — add the budget and the reason — never to silently honour it.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "ungoverned_marker_violations"
12
- }
13
- ],
14
- "reference": "assert_app_clean raises on an ungoverned # arch-allow-* marker; ungoverned_marker_violations projects the budget-less condition and _apply_suppressions re-reports an unjustified marker under the same rule, for terp check --format json. The rule carries no opt_out: governance cannot be waived by the mechanism it governs.",
15
- "runtime": {
16
- "applicability": "not-applicable",
17
- "rationale": "A justification marker is a source comment; compiled bytecode retains neither the marker nor its reason. The escape-hatch contract governs authored source at the gate."
18
- },
19
- "guide_topic": "rules",
20
- "corpus": true
21
- }
1
+ {
2
+ "id": "backend/ungoverned_escape_hatch",
3
+ "surface": "backend",
4
+ "title": "The fail-closed ungoverned-opt-out condition, as structured violations",
5
+ "intent": "An opt-out marker the governance contract does not honour is itself a violation, reported in-band as structured findings (one per marker line) rather than crashing the harness: a marker used with no escape-hatch budget governing it, and a marker whose missing justification means it never suppressed anything. Either way the fix is to govern the opt-out — add the budget and the reason — never to silently honour it.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "ungoverned_marker_violations"
12
+ }
13
+ ],
14
+ "reference": "assert_app_clean raises on an ungoverned # arch-allow-* marker; ungoverned_marker_violations projects the budget-less condition and _apply_suppressions re-reports an unjustified marker under the same rule, for terp check --format json. The rule carries no opt_out: governance cannot be waived by the mechanism it governs.",
15
+ "runtime": {
16
+ "applicability": "not-applicable",
17
+ "rationale": "A justification marker is a source comment; compiled bytecode retains neither the marker nor its reason. The escape-hatch contract governs authored source at the gate."
18
+ },
19
+ "guide_topic": "rules",
20
+ "corpus": true
21
+ }
@@ -1,32 +1,32 @@
1
- {
2
- "id": "backend/update_schemas_inherit_base_update_schema",
3
- "surface": "backend",
4
- "title": "An update request contract must require the optimistic-concurrency token",
5
- "intent": "Optimistic concurrency only protects a row when the client echoes the version it loaded and the update path checks it before writing. That guarantee hinges on the update request contract demanding the token as a required field: an update contract that omits it lets a client send a blind write, so the check has nothing to compare and a concurrent edit is silently overwritten (a lost update). Every update request contract must inherit the shared concurrency-bearing base that makes the token a required field, rather than an ordinary contract that leaves it out. The token is not redeclared on the contract — it is a managed field that arrives by inheritance.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_update_schemas_inherit_base_update_schema"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.core",
16
- "ref": "BaseUpdateSchema"
17
- },
18
- {
19
- "kind": "runtime",
20
- "tool": "terp.core",
21
- "ref": "StaleDataError"
22
- }
23
- ],
24
- "runtime": {
25
- "applicability": "required",
26
- "rationale": "Two runtime halves back the source contract: BaseUpdateSchema makes the version token a required field, so a client that omits it is rejected at request validation (422); and BaseService.update matches the echoed token against the loaded row and raises a conflict (409) when they diverge. The build-time rule is the early-warning half — it ensures every update DTO actually inherits that base so it opts into both runtime checks, since a source scan is where an off-base update DTO is visible before it ever reaches a request."
27
- },
28
- "opt_out": "# arch-allow-update-schemas-inherit-base-update-schema: <reason>",
29
- "reference": "class NoteUpdate(BaseSchema) is refused; class NoteUpdate(BaseUpdateSchema) (directly or transitively) is compliant, as is a class wired as build_crud_router(update_schema=...). BaseUpdateSchema supplies the required version field; BaseService.update checks it.",
30
- "guide_topic": "module",
31
- "corpus": true
32
- }
1
+ {
2
+ "id": "backend/update_schemas_inherit_base_update_schema",
3
+ "surface": "backend",
4
+ "title": "An update request contract must require the optimistic-concurrency token",
5
+ "intent": "Optimistic concurrency only protects a row when the client echoes the version it loaded and the update path checks it before writing. That guarantee hinges on the update request contract demanding the token as a required field: an update contract that omits it lets a client send a blind write, so the check has nothing to compare and a concurrent edit is silently overwritten (a lost update). Every update request contract must inherit the shared concurrency-bearing base that makes the token a required field, rather than an ordinary contract that leaves it out. The token is not redeclared on the contract — it is a managed field that arrives by inheritance.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_update_schemas_inherit_base_update_schema"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "BaseUpdateSchema"
17
+ },
18
+ {
19
+ "kind": "runtime",
20
+ "tool": "terp.core",
21
+ "ref": "StaleDataError"
22
+ }
23
+ ],
24
+ "runtime": {
25
+ "applicability": "required",
26
+ "rationale": "Two runtime halves back the source contract: BaseUpdateSchema makes the version token a required field, so a client that omits it is rejected at request validation (422); and BaseService.update matches the echoed token against the loaded row and raises a conflict (409) when they diverge. The build-time rule is the early-warning half — it ensures every update DTO actually inherits that base so it opts into both runtime checks, since a source scan is where an off-base update DTO is visible before it ever reaches a request."
27
+ },
28
+ "opt_out": "# arch-allow-update-schemas-inherit-base-update-schema: <reason>",
29
+ "reference": "class NoteUpdate(BaseSchema) is refused; class NoteUpdate(BaseUpdateSchema) (directly or transitively) is compliant, as is a class wired as build_crud_router(update_schema=...). BaseUpdateSchema supplies the required version field; BaseService.update checks it.",
30
+ "guide_topic": "module",
31
+ "corpus": true
32
+ }
@@ -1,21 +1,21 @@
1
- {
2
- "id": "frontend/escape-hatch",
3
- "surface": "frontend",
4
- "title": "Every opt-out marker is justified and governed by the budget ratchet",
5
- "intent": "The one opt-out is a justified inline marker naming the rule; an unjustified marker is itself an error, and marker counts must exactly match the app's checked-in escape-hatch budget so opt-outs stay visible and can only shrink.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "@terp/eslint-boundaries",
11
- "ref": "terp/escape-hatch",
12
- "reported_as": "terp/escape-hatch"
13
- }
14
- ],
15
- "reference": "// terp-allow-<rule>: <reason> markers reconciled against the app's checked-in escape-hatch-budget.json (ADR 0059). The rule carries no opt_out: governance cannot be waived by the mechanism it governs.",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "Markers and budgets are source/repository artifacts; the built bundle retains neither. The controls the markers opt out of keep their own runtime halves regardless."
19
- },
20
- "corpus": true
21
- }
1
+ {
2
+ "id": "frontend/escape-hatch",
3
+ "surface": "frontend",
4
+ "title": "Every opt-out marker is justified and governed by the budget ratchet",
5
+ "intent": "The one opt-out is a justified inline marker naming the rule; an unjustified marker is itself an error, and marker counts must exactly match the app's checked-in escape-hatch budget so opt-outs stay visible and can only shrink.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "@terp/eslint-boundaries",
11
+ "ref": "terp/escape-hatch",
12
+ "reported_as": "terp/escape-hatch"
13
+ }
14
+ ],
15
+ "reference": "// terp-allow-<rule>: <reason> markers reconciled against the app's checked-in escape-hatch-budget.json (ADR 0059). The rule carries no opt_out: governance cannot be waived by the mechanism it governs.",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "Markers and budgets are source/repository artifacts; the built bundle retains neither. The controls the markers opt out of keep their own runtime halves regardless."
19
+ },
20
+ "corpus": true
21
+ }