@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,22 +1,22 @@
1
- {
2
- "id": "backend/no_internal_imports",
3
- "surface": "backend",
4
- "title": "Modules import only the platform's public surface, never its internal namespaces",
5
- "intent": "The public surface is the platform's contract: it is documented, versioned, and stable. An internal namespace is free to move or change shape at any release, and it exposes seams (raw session plumbing, guard internals) whose direct use bypasses the framework's controls — so a module that reaches into it is coupled to undocumented internals and can silently sidestep the secure defaults.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_internal_imports"
12
- }
13
- ],
14
- "reference": "Import from the terp.core public surface; terp.core._internal imports are refused.",
15
- "opt_out": "# arch-allow-no-internal-imports: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "Python has no enforced import visibility: at runtime _internal objects are ordinary attributes, reachable through the public modules that legitimately use them. The underscore contract is source-form; the runtime controls live in what _internal implements, not in refusing its import."
19
- },
20
- "guide_topic": "module",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/no_internal_imports",
3
+ "surface": "backend",
4
+ "title": "Modules import only the platform's public surface, never its internal namespaces",
5
+ "intent": "The public surface is the platform's contract: it is documented, versioned, and stable. An internal namespace is free to move or change shape at any release, and it exposes seams (raw session plumbing, guard internals) whose direct use bypasses the framework's controls — so a module that reaches into it is coupled to undocumented internals and can silently sidestep the secure defaults.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_internal_imports"
12
+ }
13
+ ],
14
+ "reference": "Import from the terp.core public surface; terp.core._internal imports are refused.",
15
+ "opt_out": "# arch-allow-no-internal-imports: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "Python has no enforced import visibility: at runtime _internal objects are ordinary attributes, reachable through the public modules that legitimately use them. The underscore contract is source-form; the runtime controls live in what _internal implements, not in refusing its import."
19
+ },
20
+ "guide_topic": "module",
21
+ "corpus": true
22
+ }
@@ -1,27 +1,27 @@
1
- {
2
- "id": "backend/no_manual_actor_stamping",
3
- "surface": "backend",
4
- "title": "Modules never set the framework-managed actor-stamp columns by hand",
5
- "intent": "Who created and last modified a row is provenance, applied centrally: the audited write chokepoint fills created_by_id (on insert) and modified_by_id (on every write) from the request actor. A module that assigns those columns is forging or clobbering that trail — the actor must come from the authenticated request, never from caller-supplied data. As with the scope columns, a read DTO may still expose the column (an annotation is fine); only attribute access (set / compare) is policed.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_manual_actor_stamping"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.core",
16
- "ref": "_save"
17
- }
18
- ],
19
- "reference": "ActorStampedMixin columns are stamped by BaseService._save (ADR 0012); assignments to created_by_id / modified_by_id in module code are refused.",
20
- "opt_out": "# arch-allow-no-manual-actor-stamping: <reason>",
21
- "runtime": {
22
- "applicability": "required",
23
- "rationale": "The audited write chokepoint (_save) stamps created_by_id on insert and modified_by_id on every save from the request actor, unconditionally for a stamped model -- a hand-written stamp never survives the write, and inbound payloads have the stamp columns stripped (_without_managed_columns)."
24
- },
25
- "guide_topic": "service",
26
- "corpus": true
27
- }
1
+ {
2
+ "id": "backend/no_manual_actor_stamping",
3
+ "surface": "backend",
4
+ "title": "Modules never set the framework-managed actor-stamp columns by hand",
5
+ "intent": "Who created and last modified a row is provenance, applied centrally: the audited write chokepoint fills created_by_id (on insert) and modified_by_id (on every write) from the request actor. A module that assigns those columns is forging or clobbering that trail — the actor must come from the authenticated request, never from caller-supplied data. As with the scope columns, a read DTO may still expose the column (an annotation is fine); only attribute access (set / compare) is policed.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_manual_actor_stamping"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "_save"
17
+ }
18
+ ],
19
+ "reference": "ActorStampedMixin columns are stamped by BaseService._save (ADR 0012); assignments to created_by_id / modified_by_id in module code are refused.",
20
+ "opt_out": "# arch-allow-no-manual-actor-stamping: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "The audited write chokepoint (_save) stamps created_by_id on insert and modified_by_id on every save from the request actor, unconditionally for a stamped model -- a hand-written stamp never survives the write, and inbound payloads have the stamp columns stripped (_without_managed_columns)."
24
+ },
25
+ "guide_topic": "service",
26
+ "corpus": true
27
+ }
@@ -1,32 +1,32 @@
1
- {
2
- "id": "backend/no_manual_ownership_checks",
3
- "surface": "backend",
4
- "title": "Modules preserve structural row ownership and never reimplement it by hand",
5
- "intent": "Object-level authorization belongs to the platform's write boundary: user-owned rows retain their ownership declaration across request and background workflows, and a worker identity never becomes a blanket cross-owner authority. Application code must not compare, filter or assign the managed owner column, nor remove ownership merely to let unattended maintenance mutate every user's rows. Cross-owner maintenance requires a separately reviewed authority mechanism whose scope is explicit; ordinary background work remains subject to the same row ownership as interactive writes.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_manual_ownership_checks"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.core",
16
- "ref": "apply_object_authz"
17
- },
18
- {
19
- "kind": "runtime",
20
- "tool": "terp.core",
21
- "ref": "_validate_background_jobs_preserve_ownership"
22
- }
23
- ],
24
- "reference": "OwnedMixin declares ownership; BaseService stamps owner_id and apply_object_authz gates writes. create_app refuses a job-bearing ModuleSpec whose declared service model omits OwnedMixin. A custom object-authz predicate can be registered.",
25
- "opt_out": "# arch-allow-no-manual-ownership-checks: <reason>",
26
- "runtime": {
27
- "applicability": "required",
28
- "rationale": "Every update/delete of an owned row is authorized per-row at the write chokepoint and owner identity is stamped centrally on create. Composition also rejects job-bearing modules that bind an unowned service model, so an application cannot remove the ownership trait to grant background work blanket cross-owner authority."
29
- },
30
- "guide_topic": "ownership",
31
- "corpus": true
32
- }
1
+ {
2
+ "id": "backend/no_manual_ownership_checks",
3
+ "surface": "backend",
4
+ "title": "Modules preserve structural row ownership and never reimplement it by hand",
5
+ "intent": "Object-level authorization belongs to the platform's write boundary: user-owned rows retain their ownership declaration across request and background workflows, and a worker identity never becomes a blanket cross-owner authority. Application code must not compare, filter or assign the managed owner column, nor remove ownership merely to let unattended maintenance mutate every user's rows. Cross-owner maintenance requires a separately reviewed authority mechanism whose scope is explicit; ordinary background work remains subject to the same row ownership as interactive writes.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_manual_ownership_checks"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "apply_object_authz"
17
+ },
18
+ {
19
+ "kind": "runtime",
20
+ "tool": "terp.core",
21
+ "ref": "_validate_background_jobs_preserve_ownership"
22
+ }
23
+ ],
24
+ "reference": "OwnedMixin declares ownership; BaseService stamps owner_id and apply_object_authz gates writes. create_app refuses a job-bearing ModuleSpec whose declared service model omits OwnedMixin. A custom object-authz predicate can be registered.",
25
+ "opt_out": "# arch-allow-no-manual-ownership-checks: <reason>",
26
+ "runtime": {
27
+ "applicability": "required",
28
+ "rationale": "Every update/delete of an owned row is authorized per-row at the write chokepoint and owner identity is stamped centrally on create. Composition also rejects job-bearing modules that bind an unowned service model, so an application cannot remove the ownership trait to grant background work blanket cross-owner authority."
29
+ },
30
+ "guide_topic": "ownership",
31
+ "corpus": true
32
+ }
@@ -1,27 +1,27 @@
1
- {
2
- "id": "backend/no_manual_scope_filtering",
3
- "surface": "backend",
4
- "title": "Modules never touch the framework-managed scope columns",
5
- "intent": "Soft-delete and tenant scoping are applied centrally: the composed base read query filters out soft-deleted rows and applies every registered row predicate (e.g. the tenant filter), and the audited delete chokepoint stamps the deletion. A module that references deleted_at / tenant_id — to filter, set, or compare — is re-implementing that scope predicate by hand, which can leak or destroy scoped rows. The composed scoped read is the only path; expose the column in a read DTO if you must surface it, but never filter or assign it in module code.",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_manual_scope_filtering"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.core",
16
- "ref": "apply_row_scope"
17
- }
18
- ],
19
- "reference": "BaseService.base_query composes the scope (deleted_at IS NULL + registered predicates); deleted_at / tenant_id attribute access in module code is refused.",
20
- "opt_out": "# arch-allow-no-manual-scope-filtering: <reason>",
21
- "runtime": {
22
- "applicability": "required",
23
- "rationale": "Row scope is applied centrally and non-droppably: apply_row_scope composes the soft-delete filter and every registered predicate into each read (including raw selects, re-scoped by the request session), the delete chokepoint stamps deleted_at, and the managed scope columns are stripped from inbound payloads."
24
- },
25
- "guide_topic": "tenancy",
26
- "corpus": true
27
- }
1
+ {
2
+ "id": "backend/no_manual_scope_filtering",
3
+ "surface": "backend",
4
+ "title": "Modules never touch the framework-managed scope columns",
5
+ "intent": "Soft-delete and tenant scoping are applied centrally: the composed base read query filters out soft-deleted rows and applies every registered row predicate (e.g. the tenant filter), and the audited delete chokepoint stamps the deletion. A module that references deleted_at / tenant_id — to filter, set, or compare — is re-implementing that scope predicate by hand, which can leak or destroy scoped rows. The composed scoped read is the only path; expose the column in a read DTO if you must surface it, but never filter or assign it in module code.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_manual_scope_filtering"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "apply_row_scope"
17
+ }
18
+ ],
19
+ "reference": "BaseService.base_query composes the scope (deleted_at IS NULL + registered predicates); deleted_at / tenant_id attribute access in module code is refused.",
20
+ "opt_out": "# arch-allow-no-manual-scope-filtering: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "Row scope is applied centrally and non-droppably: apply_row_scope composes the soft-delete filter and every registered predicate into each read (including raw selects, re-scoped by the request session), the delete chokepoint stamps deleted_at, and the managed scope columns are stripped from inbound payloads."
24
+ },
25
+ "guide_topic": "tenancy",
26
+ "corpus": true
27
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/no_manual_table_schema",
3
- "surface": "backend",
4
- "title": "Table models never hand-write a physical schema placement (the layout is managed)",
5
- "intent": "The physical schema layout is a deployment decision: under the flat layout every table lives in the default schema, and under the per-module layout the migration runtime routes each package's tables into its own database schema — in both layouts the model metadata stays schema-free. A hand-written schema placement pins one table to a fixed schema, silently escaping the managed layout (and breaking lightweight dev/test databases that parse a schema prefix differently).",
6
- "layer": "static-bespoke",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_manual_table_schema"
12
- }
13
- ],
14
- "reference": "DB_SCHEMA_LAYOUT (flat / per-module via search_path, ADR 0070) owns placement; __table_args__ = {\"schema\": ...} is refused.",
15
- "opt_out": "# arch-allow-no-manual-table-schema: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "schema= placement is materialised into DDL by the packaged migrations; by the time the app serves, tables live where the migrations put them. The managed-layout guarantee is enforced where the DDL is authored, not against an already-created schema."
19
- },
20
- "guide_topic": "migrations",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/no_manual_table_schema",
3
+ "surface": "backend",
4
+ "title": "Table models never hand-write a physical schema placement (the layout is managed)",
5
+ "intent": "The physical schema layout is a deployment decision: under the flat layout every table lives in the default schema, and under the per-module layout the migration runtime routes each package's tables into its own database schema — in both layouts the model metadata stays schema-free. A hand-written schema placement pins one table to a fixed schema, silently escaping the managed layout (and breaking lightweight dev/test databases that parse a schema prefix differently).",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_manual_table_schema"
12
+ }
13
+ ],
14
+ "reference": "DB_SCHEMA_LAYOUT (flat / per-module via search_path, ADR 0070) owns placement; __table_args__ = {\"schema\": ...} is refused.",
15
+ "opt_out": "# arch-allow-no-manual-table-schema: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "schema= placement is materialised into DDL by the packaged migrations; by the time the app serves, tables live where the migrations put them. The managed-layout guarantee is enforced where the DDL is authored, not against an already-created schema."
19
+ },
20
+ "guide_topic": "migrations",
21
+ "corpus": true
22
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/no_manual_version_assignment",
3
- "surface": "backend",
4
- "title": "The optimistic-concurrency token is never assigned by hand",
5
- "intent": "Every row carries an integer concurrency token that the persistence layer increments on each update and matches against the value the caller loaded, so two writers racing on the same row cannot silently clobber each other. Writing that token by hand does not fail — it overwrites the loaded value with the caller's own, so the concurrency check ends up comparing the row against itself and a lost update slips through undetected. Application code must never assign the token; the persistence layer owns it end to end.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_manual_version_assignment"
12
- }
13
- ],
14
- "reference": "db_obj.version = data.version, row.version += 1, and setattr(db_obj, \"version\", ...) are refused; the update seam bumps and checks the token, so application code leaves it untouched.",
15
- "opt_out": "# arch-allow-no-manual-version-assignment: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "The concurrency control itself is a live runtime seam — the persistence layer appends the loaded token to every update's WHERE clause and raises a conflict when zero rows match. But this rule guards the inverse breach, and that breach is not runtime-observable: a hand-written token assignment is an ordinary attribute write that succeeds, then quietly defeats the check by making the loaded and compared values identical. Nothing downstream can distinguish a caller-supplied token from a genuinely loaded one, so the only place to catch it is the authored source that performs the assignment."
19
- },
20
- "guide_topic": "service",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/no_manual_version_assignment",
3
+ "surface": "backend",
4
+ "title": "The optimistic-concurrency token is never assigned by hand",
5
+ "intent": "Every row carries an integer concurrency token that the persistence layer increments on each update and matches against the value the caller loaded, so two writers racing on the same row cannot silently clobber each other. Writing that token by hand does not fail — it overwrites the loaded value with the caller's own, so the concurrency check ends up comparing the row against itself and a lost update slips through undetected. Application code must never assign the token; the persistence layer owns it end to end.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_manual_version_assignment"
12
+ }
13
+ ],
14
+ "reference": "db_obj.version = data.version, row.version += 1, and setattr(db_obj, \"version\", ...) are refused; the update seam bumps and checks the token, so application code leaves it untouched.",
15
+ "opt_out": "# arch-allow-no-manual-version-assignment: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The concurrency control itself is a live runtime seam — the persistence layer appends the loaded token to every update's WHERE clause and raises a conflict when zero rows match. But this rule guards the inverse breach, and that breach is not runtime-observable: a hand-written token assignment is an ordinary attribute write that succeeds, then quietly defeats the check by making the loaded and compared values identical. Nothing downstream can distinguish a caller-supplied token from a genuinely loaded one, so the only place to catch it is the authored source that performs the assignment."
19
+ },
20
+ "guide_topic": "service",
21
+ "corpus": true
22
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/no_mutable_default_args",
3
- "surface": "backend",
4
- "title": "No mutable default argument values",
5
- "intent": "A default argument value is evaluated once when the function is defined and then shared by every call that omits it, so a mutable default (a list, dict, or set) accumulates state across calls — a classic aliasing bug that leaks data between otherwise independent invocations. Default to a sentinel and build the container inside the body.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_mutable_default_args"
12
- }
13
- ],
14
- "reference": "A list, dict, or set literal used as a parameter default (positional or keyword-only) is refused; default to None and construct the container in the body when the argument is omitted.",
15
- "opt_out": "# arch-allow-no-mutable-default-args: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "The invariant is a property of the authored signature. The shared default object is created at definition time, before any request runs, so by execution the aliasing is already baked in and no runtime seam can un-share it — the only faithful check reads the signature in source."
19
- },
20
- "guide_topic": "service",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/no_mutable_default_args",
3
+ "surface": "backend",
4
+ "title": "No mutable default argument values",
5
+ "intent": "A default argument value is evaluated once when the function is defined and then shared by every call that omits it, so a mutable default (a list, dict, or set) accumulates state across calls — a classic aliasing bug that leaks data between otherwise independent invocations. Default to a sentinel and build the container inside the body.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_mutable_default_args"
12
+ }
13
+ ],
14
+ "reference": "A list, dict, or set literal used as a parameter default (positional or keyword-only) is refused; default to None and construct the container in the body when the argument is omitted.",
15
+ "opt_out": "# arch-allow-no-mutable-default-args: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The invariant is a property of the authored signature. The shared default object is created at definition time, before any request runs, so by execution the aliasing is already baked in and no runtime seam can un-share it — the only faithful check reads the signature in source."
19
+ },
20
+ "guide_topic": "service",
21
+ "corpus": true
22
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/no_naive_datetime",
3
- "surface": "backend",
4
- "title": "Timestamps must be timezone-aware, never naive",
5
- "intent": "A timestamp captured without a timezone silently assumes the process's local zone, so it cannot be stored, compared, or ordered correctly once more than one zone is involved — a classic source of off-by-hours bugs. Every timestamp the app produces must carry an explicit timezone (UTC), so the moment it names is unambiguous.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_naive_datetime"
12
- }
13
- ],
14
- "reference": "datetime.utcnow() (deprecated, naive) and a bare datetime.now() (no tz) are refused; datetime.now(UTC) is the compliant, timezone-aware path.",
15
- "opt_out": "# arch-allow-no-naive-datetime: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "The invariant is a property of the authored source: which constructor was written. At execution time a naive and an aware timestamp are both just datetime values, and a naive one has already lost its zone at the point it was built — no runtime seam upstream of that call can reconstruct the intended zone or distinguish a vetted aware call from a naive one. The compliant path is the explicit-zone constructor itself."
19
- },
20
- "guide_topic": "service",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/no_naive_datetime",
3
+ "surface": "backend",
4
+ "title": "Timestamps must be timezone-aware, never naive",
5
+ "intent": "A timestamp captured without a timezone silently assumes the process's local zone, so it cannot be stored, compared, or ordered correctly once more than one zone is involved — a classic source of off-by-hours bugs. Every timestamp the app produces must carry an explicit timezone (UTC), so the moment it names is unambiguous.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_naive_datetime"
12
+ }
13
+ ],
14
+ "reference": "datetime.utcnow() (deprecated, naive) and a bare datetime.now() (no tz) are refused; datetime.now(UTC) is the compliant, timezone-aware path.",
15
+ "opt_out": "# arch-allow-no-naive-datetime: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The invariant is a property of the authored source: which constructor was written. At execution time a naive and an aware timestamp are both just datetime values, and a naive one has already lost its zone at the point it was built — no runtime seam upstream of that call can reconstruct the intended zone or distinguish a vetted aware call from a naive one. The compliant path is the explicit-zone constructor itself."
19
+ },
20
+ "guide_topic": "service",
21
+ "corpus": true
22
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/no_oversized_python_files",
3
- "surface": "backend",
4
- "title": "No source file grows past the line-count cap",
5
- "intent": "A file that keeps growing stops being reviewable: it hides more than one responsibility, is harder to reason about in one sitting, and is exactly what an automated author tends to produce when it appends to an existing file instead of factoring the work into a new one. Capping the line count of every hand-authored source file forces the cohesive-file discipline the rest of the layout assumes — a responsibility that outgrows its file is split into its own file, not piled onto the current one. Generated and machine-owned trees (dependency caches, database migration history, the test suite) are out of scope: their size is not an authoring decision the cap should second-guess.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_oversized_python_files"
12
- }
13
- ],
14
- "reference": "Every scanned *.py file must stay at or under 500 physical lines; generated/vendored caches, the migration history and the test tree are excluded from the scan. Split a file that grows past the cap into smaller, cohesive modules (extract helpers or sub-services into their own files).",
15
- "opt_out": "# arch-allow-no-oversized-python-files: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "The invariant is a property of the authored source tree — how many physical lines a file has. By the time the app runs, each module has been imported as a code object that carries no memory of its file's length, so no runtime seam can observe or enforce the cap. The build-time line count is the only faithful control."
19
- },
20
- "guide_topic": "module",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/no_oversized_python_files",
3
+ "surface": "backend",
4
+ "title": "No source file grows past the line-count cap",
5
+ "intent": "A file that keeps growing stops being reviewable: it hides more than one responsibility, is harder to reason about in one sitting, and is exactly what an automated author tends to produce when it appends to an existing file instead of factoring the work into a new one. Capping the line count of every hand-authored source file forces the cohesive-file discipline the rest of the layout assumes — a responsibility that outgrows its file is split into its own file, not piled onto the current one. Generated and machine-owned trees (dependency caches, database migration history, the test suite) are out of scope: their size is not an authoring decision the cap should second-guess.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_oversized_python_files"
12
+ }
13
+ ],
14
+ "reference": "Every scanned *.py file must stay at or under 500 physical lines; generated/vendored caches, the migration history and the test tree are excluded from the scan. Split a file that grows past the cap into smaller, cohesive modules (extract helpers or sub-services into their own files).",
15
+ "opt_out": "# arch-allow-no-oversized-python-files: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The invariant is a property of the authored source tree — how many physical lines a file has. By the time the app runs, each module has been imported as a code object that carries no memory of its file's length, so no runtime seam can observe or enforce the cap. The build-time line count is the only faithful control."
19
+ },
20
+ "guide_topic": "module",
21
+ "corpus": true
22
+ }
@@ -1,22 +1,22 @@
1
- {
2
- "id": "backend/no_print",
3
- "surface": "backend",
4
- "title": "Emit diagnostics through the logger, never a bare print",
5
- "intent": "Printing straight to standard output bypasses log levels, structure, and routing, so the message escapes the platform's logging pipeline and cannot be filtered, correlated, or shipped to a sink. Every diagnostic must go through the structured logger so it carries a level and is captured.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_print"
12
- }
13
- ],
14
- "reference": "A call to the print() builtin is refused; obtain a logger and call it at the appropriate level.",
15
- "opt_out": "# arch-allow-no-print: <reason>",
16
- "runtime": {
17
- "applicability": "not-applicable",
18
- "rationale": "The invariant is a property of the authored call: which output function was written. Once the app runs, a print and a log call are both just function invocations, and the output has already left the structured pipeline — no runtime seam can retro-route it, so the only faithful check reads the source."
19
- },
20
- "guide_topic": "service",
21
- "corpus": true
22
- }
1
+ {
2
+ "id": "backend/no_print",
3
+ "surface": "backend",
4
+ "title": "Emit diagnostics through the logger, never a bare print",
5
+ "intent": "Printing straight to standard output bypasses log levels, structure, and routing, so the message escapes the platform's logging pipeline and cannot be filtered, correlated, or shipped to a sink. Every diagnostic must go through the structured logger so it carries a level and is captured.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_print"
12
+ }
13
+ ],
14
+ "reference": "A call to the print() builtin is refused; obtain a logger and call it at the appropriate level.",
15
+ "opt_out": "# arch-allow-no-print: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The invariant is a property of the authored call: which output function was written. Once the app runs, a print and a log call are both just function invocations, and the output has already left the structured pipeline — no runtime seam can retro-route it, so the only faithful check reads the source."
19
+ },
20
+ "guide_topic": "service",
21
+ "corpus": true
22
+ }
@@ -1,26 +1,26 @@
1
- {
2
- "id": "backend/no_raw_app_routes",
3
- "surface": "backend",
4
- "title": "App code never registers HTTP surface on the composed app object",
5
- "intent": "Composition mounts every module router behind the deny-by-default policy guard. Surface registered on the composed app itself — a mount, an included router, a directly added route, a verb decorator on the composed app, or a lifecycle hook — is served or executed WITHOUT that guard and is invisible to the module permission model. Modules declare one flat router in their manifest; composition mounts it.",
6
- "layer": "static-portable",
7
- "enforcement": [
8
- {
9
- "kind": "build-time",
10
- "tool": "terp.arch",
11
- "ref": "check_no_raw_app_routes"
12
- },
13
- {
14
- "kind": "runtime",
15
- "tool": "terp.core",
16
- "ref": "_freeze_app_route_registration"
17
- }
18
- ],
19
- "reference": "app.mount / app.include_router / app.add_route / verb decorators / app.on_event / app.add_event_handler on the create_app-produced app are refused; modules declare one router on their ModuleSpec.",
20
- "opt_out": "# arch-allow-no-raw-app-routes: <reason>",
21
- "runtime": {
22
- "applicability": "required"
23
- },
24
- "guide_topic": "capability",
25
- "corpus": true
26
- }
1
+ {
2
+ "id": "backend/no_raw_app_routes",
3
+ "surface": "backend",
4
+ "title": "App code never registers HTTP surface on the composed app object",
5
+ "intent": "Composition mounts every module router behind the deny-by-default policy guard. Surface registered on the composed app itself — a mount, an included router, a directly added route, a verb decorator on the composed app, or a lifecycle hook — is served or executed WITHOUT that guard and is invisible to the module permission model. Modules declare one flat router in their manifest; composition mounts it.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_raw_app_routes"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "_freeze_app_route_registration"
17
+ }
18
+ ],
19
+ "reference": "app.mount / app.include_router / app.add_route / verb decorators / app.on_event / app.add_event_handler on the create_app-produced app are refused; modules declare one router on their ModuleSpec.",
20
+ "opt_out": "# arch-allow-no-raw-app-routes: <reason>",
21
+ "runtime": {
22
+ "applicability": "required"
23
+ },
24
+ "guide_topic": "capability",
25
+ "corpus": true
26
+ }