@terpjs/spec 0.14.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 -0
  2. package/README.md +417 -0
  3. package/VERSION +1 -0
  4. package/app-check-report.schema.json +122 -0
  5. package/assurance-profile.schema.json +70 -0
  6. package/catalog/backend/alembic_downgrades_not_empty.json +22 -0
  7. package/catalog/backend/base_query_not_overridden.json +27 -0
  8. package/catalog/backend/canonical_module_shape.json +22 -0
  9. package/catalog/backend/datetime_columns_are_timezone_aware.json +23 -0
  10. package/catalog/backend/escape_hatch_budget.json +21 -0
  11. package/catalog/backend/events_reference_catalog.json +27 -0
  12. package/catalog/backend/input_schemas_exclude_managed_columns.json +27 -0
  13. package/catalog/backend/input_str_fields_have_max_length.json +22 -0
  14. package/catalog/backend/jobs_reference_catalog.json +27 -0
  15. package/catalog/backend/list_routes_paginate.json +32 -0
  16. package/catalog/backend/modules_declare_policy.json +26 -0
  17. package/catalog/backend/mutations_emit_audit.json +26 -0
  18. package/catalog/backend/mutations_require_write_role.json +32 -0
  19. package/catalog/backend/no_adhoc_background_runtime.json +22 -0
  20. package/catalog/backend/no_adhoc_config_decrypt.json +27 -0
  21. package/catalog/backend/no_adhoc_logging_config.json +22 -0
  22. package/catalog/backend/no_adhoc_middleware.json +27 -0
  23. package/catalog/backend/no_adhoc_permission_literals.json +27 -0
  24. package/catalog/backend/no_app_instantiation.json +22 -0
  25. package/catalog/backend/no_blocking_sleep.json +22 -0
  26. package/catalog/backend/no_cross_module_imports.json +21 -0
  27. package/catalog/backend/no_dependency_overrides.json +27 -0
  28. package/catalog/backend/no_destructive_migrations.json +22 -0
  29. package/catalog/backend/no_dynamic_sql.json +22 -0
  30. package/catalog/backend/no_empty_tests.json +22 -0
  31. package/catalog/backend/no_eval_or_exec.json +22 -0
  32. package/catalog/backend/no_hardcoded_credentials.json +21 -0
  33. package/catalog/backend/no_internal_imports.json +22 -0
  34. package/catalog/backend/no_manual_actor_stamping.json +27 -0
  35. package/catalog/backend/no_manual_ownership_checks.json +32 -0
  36. package/catalog/backend/no_manual_scope_filtering.json +27 -0
  37. package/catalog/backend/no_manual_table_schema.json +22 -0
  38. package/catalog/backend/no_manual_version_assignment.json +22 -0
  39. package/catalog/backend/no_mutable_default_args.json +22 -0
  40. package/catalog/backend/no_naive_datetime.json +22 -0
  41. package/catalog/backend/no_oversized_python_files.json +22 -0
  42. package/catalog/backend/no_print.json +22 -0
  43. package/catalog/backend/no_raw_app_routes.json +26 -0
  44. package/catalog/backend/no_raw_connection_access.json +27 -0
  45. package/catalog/backend/no_raw_file_references.json +27 -0
  46. package/catalog/backend/no_raw_outbound_http.json +22 -0
  47. package/catalog/backend/no_raw_session_construction.json +22 -0
  48. package/catalog/backend/no_star_imports.json +22 -0
  49. package/catalog/backend/no_todo_fixme.json +22 -0
  50. package/catalog/backend/no_unique_columns_on_soft_delete_models.json +22 -0
  51. package/catalog/backend/offset_queries_declare_ordering.json +22 -0
  52. package/catalog/backend/path_id_params_are_uuid.json +22 -0
  53. package/catalog/backend/policy_refs_resolve.json +27 -0
  54. package/catalog/backend/public_modules_are_read_only.json +26 -0
  55. package/catalog/backend/reads_use_base_query.json +27 -0
  56. package/catalog/backend/response_model_not_table_model.json +27 -0
  57. package/catalog/backend/routes_declare_response_model.json +27 -0
  58. package/catalog/backend/safe_methods_are_read_only.json +31 -0
  59. package/catalog/backend/schemas_exclude_sensitive_fields.json +32 -0
  60. package/catalog/backend/session_imported_from_sqlmodel.json +22 -0
  61. package/catalog/backend/table_models_use_base_table.json +22 -0
  62. package/catalog/backend/tables_have_migrations.json +27 -0
  63. package/catalog/backend/tenant_scoped_models_use_scoped_service.json +22 -0
  64. package/catalog/backend/ungoverned_escape_hatch.json +21 -0
  65. package/catalog/backend/update_schemas_inherit_base_update_schema.json +32 -0
  66. package/catalog/frontend/escape-hatch.json +21 -0
  67. package/catalog/frontend/generated-client-only.json +26 -0
  68. package/catalog/frontend/layout-contract.json +26 -0
  69. package/catalog/frontend/no-cross-module-imports.json +21 -0
  70. package/catalog/frontend/no-deep-imports.json +25 -0
  71. package/catalog/frontend/no-dom-html-injection.json +22 -0
  72. package/catalog/frontend/no-eval.json +21 -0
  73. package/catalog/frontend/no-inline-styling.json +25 -0
  74. package/catalog/frontend/no-style-imports.json +24 -0
  75. package/catalog/frontend/no-unsafe-href.json +21 -0
  76. package/catalog/frontend/no-unsafe-target-blank.json +21 -0
  77. package/catalog/frontend/router-links.json +22 -0
  78. package/catalog/frontend/token-styled-elements.json +25 -0
  79. package/catalog/schema.json +95 -0
  80. package/corpus/PENDING.json +4 -0
  81. package/corpus/RESIDUALS.json +21 -0
  82. package/corpus/backend/alembic_downgrades_not_empty/compliant-01/modules/notes/migrations/versions/0001_change.py +6 -0
  83. package/corpus/backend/alembic_downgrades_not_empty/violation-01/modules/notes/migrations/versions/0001_change.py +6 -0
  84. package/corpus/backend/base_query_not_overridden/compliant-01/modules/notes/service.py +8 -0
  85. package/corpus/backend/base_query_not_overridden/compliant-02/modules/notes/service.py +8 -0
  86. package/corpus/backend/base_query_not_overridden/violation-01/modules/notes/service.py +8 -0
  87. package/corpus/backend/base_query_not_overridden/violation-02/expected-findings.json +7 -0
  88. package/corpus/backend/base_query_not_overridden/violation-02/modules/notes/service.py +8 -0
  89. package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/models.py +6 -0
  90. package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/module.py +7 -0
  91. package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/router.py +3 -0
  92. package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/schemas.py +10 -0
  93. package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/service.py +5 -0
  94. package/corpus/backend/canonical_module_shape/violation-01/modules/notes/router.py +3 -0
  95. package/corpus/backend/datetime_columns_are_timezone_aware/compliant-01/modules/notes/models.py +12 -0
  96. package/corpus/backend/datetime_columns_are_timezone_aware/violation-01/modules/notes/models.py +12 -0
  97. package/corpus/backend/datetime_columns_are_timezone_aware/violation-02/modules/notes/models.py +13 -0
  98. package/corpus/backend/datetime_columns_are_timezone_aware/violation-03/modules/notes/models.py +15 -0
  99. package/corpus/backend/escape_hatch_budget/compliant-01/escape-hatch-budget.json +3 -0
  100. package/corpus/backend/escape_hatch_budget/compliant-01/modules/notes/service.py +2 -0
  101. package/corpus/backend/escape_hatch_budget/compliant-02/escape-hatch-budget.json +1 -0
  102. package/corpus/backend/escape_hatch_budget/compliant-02/modules/notes/service.py +4 -0
  103. package/corpus/backend/escape_hatch_budget/violation-01/escape-hatch-budget.json +1 -0
  104. package/corpus/backend/escape_hatch_budget/violation-01/modules/notes/service.py +2 -0
  105. package/corpus/backend/escape_hatch_budget/violation-02/escape-hatch-budget.json +3 -0
  106. package/corpus/backend/escape_hatch_budget/violation-02/modules/notes/service.py +4 -0
  107. package/corpus/backend/events_reference_catalog/compliant-01/modules/notes/module.py +9 -0
  108. package/corpus/backend/events_reference_catalog/violation-01/modules/notes/module.py +8 -0
  109. package/corpus/backend/input_schemas_exclude_managed_columns/compliant-01/modules/notes/schemas.py +6 -0
  110. package/corpus/backend/input_schemas_exclude_managed_columns/violation-01/expected-findings.json +7 -0
  111. package/corpus/backend/input_schemas_exclude_managed_columns/violation-01/modules/notes/schemas.py +9 -0
  112. package/corpus/backend/input_schemas_exclude_managed_columns/violation-02/expected-findings.json +12 -0
  113. package/corpus/backend/input_schemas_exclude_managed_columns/violation-02/modules/notes/router.py +14 -0
  114. package/corpus/backend/input_str_fields_have_max_length/compliant-01/modules/notes/schemas.py +4 -0
  115. package/corpus/backend/input_str_fields_have_max_length/violation-01/modules/notes/schemas.py +2 -0
  116. package/corpus/backend/jobs_reference_catalog/compliant-01/modules/notes/service.py +5 -0
  117. package/corpus/backend/jobs_reference_catalog/violation-01/modules/notes/service.py +2 -0
  118. package/corpus/backend/list_routes_paginate/compliant-01/modules/notes/router.py +6 -0
  119. package/corpus/backend/list_routes_paginate/violation-01/modules/notes/router.py +3 -0
  120. package/corpus/backend/modules_declare_policy/compliant-01/modules/notes/module.py +7 -0
  121. package/corpus/backend/modules_declare_policy/violation-01/modules/notes/module.py +6 -0
  122. package/corpus/backend/mutations_emit_audit/compliant-01/modules/notes/service.py +9 -0
  123. package/corpus/backend/mutations_emit_audit/violation-01/modules/notes/service.py +4 -0
  124. package/corpus/backend/mutations_require_write_role/compliant-01/modules/notes/module.py +7 -0
  125. package/corpus/backend/mutations_require_write_role/compliant-01/modules/notes/router.py +3 -0
  126. package/corpus/backend/mutations_require_write_role/compliant-02/modules/notes/module.py +7 -0
  127. package/corpus/backend/mutations_require_write_role/compliant-02/modules/notes/router.py +3 -0
  128. package/corpus/backend/mutations_require_write_role/violation-01/expected-findings.json +7 -0
  129. package/corpus/backend/mutations_require_write_role/violation-01/modules/notes/module.py +7 -0
  130. package/corpus/backend/mutations_require_write_role/violation-01/modules/notes/router.py +3 -0
  131. package/corpus/backend/mutations_require_write_role/violation-02/expected-findings.json +7 -0
  132. package/corpus/backend/mutations_require_write_role/violation-02/modules/notes/module.py +7 -0
  133. package/corpus/backend/mutations_require_write_role/violation-02/modules/notes/router.py +5 -0
  134. package/corpus/backend/mutations_require_write_role/violation-03/expected-findings.json +7 -0
  135. package/corpus/backend/mutations_require_write_role/violation-03/modules/notes/module.py +10 -0
  136. package/corpus/backend/mutations_require_write_role/violation-03/modules/notes/router.py +3 -0
  137. package/corpus/backend/no_adhoc_background_runtime/compliant-01/modules/notes/service.py +9 -0
  138. package/corpus/backend/no_adhoc_background_runtime/violation-01/modules/notes/service.py +5 -0
  139. package/corpus/backend/no_adhoc_background_runtime/violation-02/modules/notes/service.py +3 -0
  140. package/corpus/backend/no_adhoc_config_decrypt/compliant-01/modules/billing/service.py +2 -0
  141. package/corpus/backend/no_adhoc_config_decrypt/violation-01/modules/billing/service.py +2 -0
  142. package/corpus/backend/no_adhoc_logging_config/compliant-01/modules/notes/service.py +3 -0
  143. package/corpus/backend/no_adhoc_logging_config/violation-01/modules/notes/service.py +3 -0
  144. package/corpus/backend/no_adhoc_middleware/compliant-01/main.py +3 -0
  145. package/corpus/backend/no_adhoc_middleware/violation-01/main.py +4 -0
  146. package/corpus/backend/no_adhoc_middleware/violation-02/main.py +5 -0
  147. package/corpus/backend/no_adhoc_permission_literals/compliant-01/modules/notes/module.py +3 -0
  148. package/corpus/backend/no_adhoc_permission_literals/compliant-02/modules/notes/router.py +5 -0
  149. package/corpus/backend/no_adhoc_permission_literals/violation-01/expected-findings.json +7 -0
  150. package/corpus/backend/no_adhoc_permission_literals/violation-01/modules/notes/module.py +1 -0
  151. package/corpus/backend/no_adhoc_permission_literals/violation-02/expected-findings.json +7 -0
  152. package/corpus/backend/no_adhoc_permission_literals/violation-02/modules/notes/router.py +2 -0
  153. package/corpus/backend/no_adhoc_permission_literals/violation-03/expected-findings.json +12 -0
  154. package/corpus/backend/no_adhoc_permission_literals/violation-03/modules/notes/module.py +10 -0
  155. package/corpus/backend/no_app_instantiation/compliant-01/main.py +3 -0
  156. package/corpus/backend/no_app_instantiation/violation-01/main.py +3 -0
  157. package/corpus/backend/no_blocking_sleep/compliant-01/modules/notes/service.py +2 -0
  158. package/corpus/backend/no_blocking_sleep/violation-01/modules/notes/service.py +5 -0
  159. package/corpus/backend/no_cross_module_imports/compliant-01/modules/a/service.py +1 -0
  160. package/corpus/backend/no_cross_module_imports/violation-01/modules/a/service.py +1 -0
  161. package/corpus/backend/no_cross_module_imports/violation-02/modules/a/service.py +1 -0
  162. package/corpus/backend/no_dependency_overrides/compliant-01/main.py +12 -0
  163. package/corpus/backend/no_dependency_overrides/violation-01/main.py +11 -0
  164. package/corpus/backend/no_destructive_migrations/compliant-01/modules/notes/migrations/versions/0001_change.py +3 -0
  165. package/corpus/backend/no_destructive_migrations/compliant-02/modules/notes/migrations/versions/0001_change.py +8 -0
  166. package/corpus/backend/no_destructive_migrations/violation-01/expected-findings.json +7 -0
  167. package/corpus/backend/no_destructive_migrations/violation-01/modules/notes/migrations/versions/0001_change.py +2 -0
  168. package/corpus/backend/no_destructive_migrations/violation-02/expected-findings.json +7 -0
  169. package/corpus/backend/no_destructive_migrations/violation-02/modules/notes/migrations/versions/0001_change.py +2 -0
  170. package/corpus/backend/no_destructive_migrations/violation-03/expected-findings.json +7 -0
  171. package/corpus/backend/no_destructive_migrations/violation-03/modules/notes/migrations/versions/0001_change.py +3 -0
  172. package/corpus/backend/no_destructive_migrations/violation-04/expected-findings.json +12 -0
  173. package/corpus/backend/no_destructive_migrations/violation-04/modules/notes/migrations/versions/0001_change.py +6 -0
  174. package/corpus/backend/no_destructive_migrations/violation-05/expected-findings.json +12 -0
  175. package/corpus/backend/no_destructive_migrations/violation-05/modules/notes/migrations/versions/0001_change.py +3 -0
  176. package/corpus/backend/no_dynamic_sql/compliant-01/modules/notes/service.py +1 -0
  177. package/corpus/backend/no_dynamic_sql/compliant-02/modules/notes/service.py +20 -0
  178. package/corpus/backend/no_dynamic_sql/violation-01/modules/notes/service.py +2 -0
  179. package/corpus/backend/no_dynamic_sql/violation-02/modules/notes/service.py +2 -0
  180. package/corpus/backend/no_dynamic_sql/violation-03/modules/notes/service.py +7 -0
  181. package/corpus/backend/no_dynamic_sql/violation-04/modules/notes/service.py +16 -0
  182. package/corpus/backend/no_empty_tests/compliant-01/tests/test_notes.py +2 -0
  183. package/corpus/backend/no_empty_tests/violation-01/tests/test_notes.py +2 -0
  184. package/corpus/backend/no_eval_or_exec/compliant-01/modules/notes/service.py +2 -0
  185. package/corpus/backend/no_eval_or_exec/violation-01/modules/notes/service.py +2 -0
  186. package/corpus/backend/no_hardcoded_credentials/compliant-01/modules/billing/service.py +3 -0
  187. package/corpus/backend/no_hardcoded_credentials/compliant-02/modules/billing/service.py +18 -0
  188. package/corpus/backend/no_hardcoded_credentials/violation-01/expected-findings.json +7 -0
  189. package/corpus/backend/no_hardcoded_credentials/violation-01/modules/billing/service.py +2 -0
  190. package/corpus/backend/no_hardcoded_credentials/violation-02/expected-findings.json +7 -0
  191. package/corpus/backend/no_hardcoded_credentials/violation-02/modules/billing/service.py +1 -0
  192. package/corpus/backend/no_hardcoded_credentials/violation-03/expected-findings.json +22 -0
  193. package/corpus/backend/no_hardcoded_credentials/violation-03/modules/billing/service.py +11 -0
  194. package/corpus/backend/no_hardcoded_credentials/violation-04/expected-findings.json +12 -0
  195. package/corpus/backend/no_hardcoded_credentials/violation-04/modules/billing/service.py +8 -0
  196. package/corpus/backend/no_hardcoded_credentials/violation-05/expected-findings.json +12 -0
  197. package/corpus/backend/no_hardcoded_credentials/violation-05/modules/billing/service.py +4 -0
  198. package/corpus/backend/no_internal_imports/compliant-01/modules/notes/service.py +1 -0
  199. package/corpus/backend/no_internal_imports/violation-01/modules/notes/service.py +1 -0
  200. package/corpus/backend/no_manual_actor_stamping/compliant-01/modules/notes/service.py +5 -0
  201. package/corpus/backend/no_manual_actor_stamping/compliant-02/modules/notes/schemas.py +12 -0
  202. package/corpus/backend/no_manual_actor_stamping/violation-01/modules/notes/service.py +3 -0
  203. package/corpus/backend/no_manual_actor_stamping/violation-02/expected-findings.json +7 -0
  204. package/corpus/backend/no_manual_actor_stamping/violation-02/modules/notes/service.py +4 -0
  205. package/corpus/backend/no_manual_ownership_checks/compliant-01/modules/journals/service.py +3 -0
  206. package/corpus/backend/no_manual_ownership_checks/compliant-02/modules/journals/schemas.py +11 -0
  207. package/corpus/backend/no_manual_ownership_checks/violation-01/modules/journals/service.py +3 -0
  208. package/corpus/backend/no_manual_ownership_checks/violation-02/expected-findings.json +7 -0
  209. package/corpus/backend/no_manual_ownership_checks/violation-02/modules/journals/service.py +9 -0
  210. package/corpus/backend/no_manual_ownership_checks/violation-03/expected-findings.json +7 -0
  211. package/corpus/backend/no_manual_ownership_checks/violation-03/modules/notes/jobs.py +1 -0
  212. package/corpus/backend/no_manual_ownership_checks/violation-03/modules/notes/models.py +7 -0
  213. package/corpus/backend/no_manual_ownership_checks/violation-03/modules/notes/module.py +11 -0
  214. package/corpus/backend/no_manual_ownership_checks/violation-03/modules/notes/service.py +7 -0
  215. package/corpus/backend/no_manual_scope_filtering/compliant-01/modules/notes/service.py +8 -0
  216. package/corpus/backend/no_manual_scope_filtering/compliant-02/modules/notes/schemas.py +12 -0
  217. package/corpus/backend/no_manual_scope_filtering/violation-01/modules/notes/service.py +2 -0
  218. package/corpus/backend/no_manual_scope_filtering/violation-02/modules/notes/service.py +2 -0
  219. package/corpus/backend/no_manual_scope_filtering/violation-03/expected-findings.json +7 -0
  220. package/corpus/backend/no_manual_scope_filtering/violation-03/modules/notes/service.py +7 -0
  221. package/corpus/backend/no_manual_table_schema/compliant-01/modules/notes/models.py +6 -0
  222. package/corpus/backend/no_manual_table_schema/violation-01/modules/notes/models.py +8 -0
  223. package/corpus/backend/no_manual_version_assignment/compliant-01/modules/notes/service.py +5 -0
  224. package/corpus/backend/no_manual_version_assignment/violation-01/modules/notes/service.py +3 -0
  225. package/corpus/backend/no_manual_version_assignment/violation-02/modules/notes/service.py +3 -0
  226. package/corpus/backend/no_manual_version_assignment/violation-03/modules/notes/service.py +3 -0
  227. package/corpus/backend/no_mutable_default_args/compliant-01/modules/notes/service.py +3 -0
  228. package/corpus/backend/no_mutable_default_args/violation-01/modules/notes/service.py +2 -0
  229. package/corpus/backend/no_naive_datetime/compliant-01/modules/notes/service.py +2 -0
  230. package/corpus/backend/no_naive_datetime/violation-01/modules/notes/service.py +2 -0
  231. package/corpus/backend/no_naive_datetime/violation-02/modules/notes/service.py +2 -0
  232. package/corpus/backend/no_oversized_python_files/compliant-01/modules/notes/service.py +9 -0
  233. package/corpus/backend/no_oversized_python_files/violation-01/modules/notes/service.py +530 -0
  234. package/corpus/backend/no_print/compliant-01/modules/notes/service.py +7 -0
  235. package/corpus/backend/no_print/violation-01/modules/notes/service.py +2 -0
  236. package/corpus/backend/no_raw_app_routes/compliant-01/main.py +12 -0
  237. package/corpus/backend/no_raw_app_routes/violation-01/main.py +10 -0
  238. package/corpus/backend/no_raw_app_routes/violation-02/main.py +16 -0
  239. package/corpus/backend/no_raw_app_routes/violation-03/main.py +12 -0
  240. package/corpus/backend/no_raw_app_routes/violation-04/main.py +10 -0
  241. package/corpus/backend/no_raw_app_routes/violation-05/main.py +12 -0
  242. package/corpus/backend/no_raw_app_routes/violation-06/main.py +10 -0
  243. package/corpus/backend/no_raw_connection_access/compliant-01/modules/notes/service.py +5 -0
  244. package/corpus/backend/no_raw_connection_access/violation-01/modules/notes/service.py +2 -0
  245. package/corpus/backend/no_raw_connection_access/violation-02/modules/notes/service.py +2 -0
  246. package/corpus/backend/no_raw_file_references/compliant-01/modules/notes/models.py +10 -0
  247. package/corpus/backend/no_raw_file_references/violation-01/modules/notes/models.py +9 -0
  248. package/corpus/backend/no_raw_outbound_http/compliant-01/modules/notes/service.py +2 -0
  249. package/corpus/backend/no_raw_outbound_http/compliant-03/modules/notes/service.py +12 -0
  250. package/corpus/backend/no_raw_outbound_http/violation-01/expected-findings.json +7 -0
  251. package/corpus/backend/no_raw_outbound_http/violation-01/modules/notes/service.py +1 -0
  252. package/corpus/backend/no_raw_outbound_http/violation-02/expected-findings.json +7 -0
  253. package/corpus/backend/no_raw_outbound_http/violation-02/modules/notes/service.py +1 -0
  254. package/corpus/backend/no_raw_outbound_http/violation-03/expected-findings.json +7 -0
  255. package/corpus/backend/no_raw_outbound_http/violation-03/modules/notes/service.py +1 -0
  256. package/corpus/backend/no_raw_outbound_http/violation-04/expected-findings.json +12 -0
  257. package/corpus/backend/no_raw_outbound_http/violation-04/modules/notes/service.py +7 -0
  258. package/corpus/backend/no_raw_outbound_http/violation-05/expected-findings.json +12 -0
  259. package/corpus/backend/no_raw_outbound_http/violation-05/modules/notes/service.py +5 -0
  260. package/corpus/backend/no_raw_outbound_http/violation-06/expected-findings.json +12 -0
  261. package/corpus/backend/no_raw_outbound_http/violation-06/modules/notes/service.py +2 -0
  262. package/corpus/backend/no_raw_session_construction/compliant-01/modules/notes/service.py +3 -0
  263. package/corpus/backend/no_raw_session_construction/violation-01/modules/notes/service.py +3 -0
  264. package/corpus/backend/no_star_imports/compliant-01/modules/notes/service.py +1 -0
  265. package/corpus/backend/no_star_imports/violation-01/modules/notes/service.py +1 -0
  266. package/corpus/backend/no_todo_fixme/compliant-01/modules/notes/service.py +3 -0
  267. package/corpus/backend/no_todo_fixme/violation-01/modules/notes/service.py +3 -0
  268. package/corpus/backend/no_unique_columns_on_soft_delete_models/compliant-01/modules/notes/models.py +17 -0
  269. package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-01/expected-findings.json +7 -0
  270. package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-01/modules/notes/models.py +6 -0
  271. package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-02/expected-findings.json +7 -0
  272. package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-02/modules/notes/models.py +13 -0
  273. package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-03/expected-findings.json +7 -0
  274. package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-03/modules/notes/models.py +16 -0
  275. package/corpus/backend/offset_queries_declare_ordering/compliant-01/modules/notes/service.py +4 -0
  276. package/corpus/backend/offset_queries_declare_ordering/violation-01/modules/notes/service.py +2 -0
  277. package/corpus/backend/path_id_params_are_uuid/compliant-01/modules/notes/router.py +6 -0
  278. package/corpus/backend/path_id_params_are_uuid/violation-01/modules/notes/router.py +6 -0
  279. package/corpus/backend/policy_refs_resolve/compliant-01/modules/notes/module.py +7 -0
  280. package/corpus/backend/policy_refs_resolve/violation-01/modules/notes/module.py +8 -0
  281. package/corpus/backend/public_modules_are_read_only/compliant-01/modules/notes/module.py +7 -0
  282. package/corpus/backend/public_modules_are_read_only/compliant-01/modules/notes/router.py +3 -0
  283. package/corpus/backend/public_modules_are_read_only/violation-01/expected-findings.json +7 -0
  284. package/corpus/backend/public_modules_are_read_only/violation-01/modules/notes/module.py +7 -0
  285. package/corpus/backend/public_modules_are_read_only/violation-01/modules/notes/router.py +3 -0
  286. package/corpus/backend/public_modules_are_read_only/violation-02/expected-findings.json +7 -0
  287. package/corpus/backend/public_modules_are_read_only/violation-02/modules/notes/module.py +7 -0
  288. package/corpus/backend/public_modules_are_read_only/violation-02/modules/notes/router.py +5 -0
  289. package/corpus/backend/reads_use_base_query/compliant-01/modules/notes/models.py +6 -0
  290. package/corpus/backend/reads_use_base_query/compliant-01/modules/notes/service.py +8 -0
  291. package/corpus/backend/reads_use_base_query/compliant-02/modules/notes/models.py +9 -0
  292. package/corpus/backend/reads_use_base_query/compliant-02/modules/notes/service.py +9 -0
  293. package/corpus/backend/reads_use_base_query/violation-01/modules/notes/models.py +6 -0
  294. package/corpus/backend/reads_use_base_query/violation-01/modules/notes/service.py +9 -0
  295. package/corpus/backend/reads_use_base_query/violation-02/expected-findings.json +7 -0
  296. package/corpus/backend/reads_use_base_query/violation-02/modules/notes/models.py +6 -0
  297. package/corpus/backend/reads_use_base_query/violation-02/modules/notes/service.py +8 -0
  298. package/corpus/backend/response_model_not_table_model/compliant-01/modules/notes/models.py +6 -0
  299. package/corpus/backend/response_model_not_table_model/compliant-01/modules/notes/router.py +3 -0
  300. package/corpus/backend/response_model_not_table_model/compliant-01/modules/notes/schemas.py +5 -0
  301. package/corpus/backend/response_model_not_table_model/violation-01/modules/notes/models.py +6 -0
  302. package/corpus/backend/response_model_not_table_model/violation-01/modules/notes/router.py +3 -0
  303. package/corpus/backend/routes_declare_response_model/compliant-01/modules/notes/router.py +8 -0
  304. package/corpus/backend/routes_declare_response_model/violation-01/modules/notes/router.py +3 -0
  305. package/corpus/backend/safe_methods_are_read_only/compliant-01/modules/notes/router.py +6 -0
  306. package/corpus/backend/safe_methods_are_read_only/compliant-02/modules/notes/router.py +5 -0
  307. package/corpus/backend/safe_methods_are_read_only/violation-01/expected-findings.json +7 -0
  308. package/corpus/backend/safe_methods_are_read_only/violation-01/modules/notes/router.py +3 -0
  309. package/corpus/backend/safe_methods_are_read_only/violation-02/expected-findings.json +7 -0
  310. package/corpus/backend/safe_methods_are_read_only/violation-02/modules/notes/router.py +5 -0
  311. package/corpus/backend/safe_methods_are_read_only/violation-03/expected-findings.json +7 -0
  312. package/corpus/backend/safe_methods_are_read_only/violation-03/modules/notes/router.py +3 -0
  313. package/corpus/backend/schemas_exclude_sensitive_fields/compliant-01/modules/users/schemas.py +5 -0
  314. package/corpus/backend/schemas_exclude_sensitive_fields/compliant-02/modules/accounts/schemas.py +12 -0
  315. package/corpus/backend/schemas_exclude_sensitive_fields/violation-01/expected-findings.json +7 -0
  316. package/corpus/backend/schemas_exclude_sensitive_fields/violation-01/modules/users/schemas.py +3 -0
  317. package/corpus/backend/schemas_exclude_sensitive_fields/violation-02/expected-findings.json +7 -0
  318. package/corpus/backend/schemas_exclude_sensitive_fields/violation-02/modules/connectors/router.py +8 -0
  319. package/corpus/backend/schemas_exclude_sensitive_fields/violation-03/expected-findings.json +17 -0
  320. package/corpus/backend/schemas_exclude_sensitive_fields/violation-03/modules/integrations/schemas.py +7 -0
  321. package/corpus/backend/session_imported_from_sqlmodel/compliant-01/modules/notes/service.py +5 -0
  322. package/corpus/backend/session_imported_from_sqlmodel/violation-01/modules/notes/service.py +5 -0
  323. package/corpus/backend/table_models_use_base_table/compliant-01/modules/notes/models.py +6 -0
  324. package/corpus/backend/table_models_use_base_table/violation-01/modules/notes/models.py +5 -0
  325. package/corpus/backend/tables_have_migrations/compliant-01/modules/notes/migrations/versions/0a1b2c3d4e5f_create_notes_tables.py +22 -0
  326. package/corpus/backend/tables_have_migrations/compliant-01/modules/notes/models.py +6 -0
  327. package/corpus/backend/tables_have_migrations/compliant-02/capabilities/ledger/models.py +6 -0
  328. package/corpus/backend/tables_have_migrations/violation-01/expected-findings.json +7 -0
  329. package/corpus/backend/tables_have_migrations/violation-01/modules/notes/models.py +6 -0
  330. package/corpus/backend/tables_have_migrations/violation-02/expected-findings.json +7 -0
  331. package/corpus/backend/tables_have_migrations/violation-02/modules/notes/models.py +10 -0
  332. package/corpus/backend/tenant_scoped_models_use_scoped_service/compliant-01/modules/projects/models.py +6 -0
  333. package/corpus/backend/tenant_scoped_models_use_scoped_service/compliant-01/modules/projects/service.py +5 -0
  334. package/corpus/backend/tenant_scoped_models_use_scoped_service/compliant-02/modules/projects/models.py +8 -0
  335. package/corpus/backend/tenant_scoped_models_use_scoped_service/compliant-02/modules/projects/service.py +5 -0
  336. package/corpus/backend/tenant_scoped_models_use_scoped_service/violation-01/modules/projects/models.py +6 -0
  337. package/corpus/backend/tenant_scoped_models_use_scoped_service/violation-01/modules/projects/service.py +5 -0
  338. package/corpus/backend/ungoverned_escape_hatch/compliant-01/modules/notes/service.py +5 -0
  339. package/corpus/backend/ungoverned_escape_hatch/compliant-02/modules/notes/service.py +4 -0
  340. package/corpus/backend/ungoverned_escape_hatch/violation-01/modules/notes/service.py +2 -0
  341. package/corpus/backend/update_schemas_inherit_base_update_schema/compliant-01/modules/notes/schemas.py +2 -0
  342. package/corpus/backend/update_schemas_inherit_base_update_schema/violation-01/modules/notes/schemas.py +2 -0
  343. package/corpus/backend/update_schemas_inherit_base_update_schema/violation-02/modules/notes/schemas.py +5 -0
  344. package/corpus/frontend/escape-hatch/compliant-01/src/modules/widgets/Widget.tsx +4 -0
  345. package/corpus/frontend/escape-hatch/violation-01/src/modules/widgets/Widget.tsx +4 -0
  346. package/corpus/frontend/escape-hatch/violation-02/src/modules/widgets/Widget.tsx +7 -0
  347. package/corpus/frontend/escape-hatch/violation-03/src/modules/widgets/Widget.tsx +7 -0
  348. package/corpus/frontend/generated-client-only/compliant-01/src/modules/widgets/Widget.tsx +6 -0
  349. package/corpus/frontend/generated-client-only/compliant-02/src/modules/widgets/Widget.tsx +21 -0
  350. package/corpus/frontend/generated-client-only/compliant-03/src/modules/widgets/Widget.tsx +7 -0
  351. package/corpus/frontend/generated-client-only/compliant-04/src/modules/widgets/Widget.tsx +17 -0
  352. package/corpus/frontend/generated-client-only/violation-01/src/modules/widgets/Widget.tsx +3 -0
  353. package/corpus/frontend/generated-client-only/violation-02/src/modules/widgets/Widget.tsx +9 -0
  354. package/corpus/frontend/generated-client-only/violation-03/src/modules/widgets/Widget.tsx +14 -0
  355. package/corpus/frontend/generated-client-only/violation-04/src/modules/widgets/Widget.tsx +6 -0
  356. package/corpus/frontend/layout-contract/compliant-01/layout-contract.json +3 -0
  357. package/corpus/frontend/layout-contract/compliant-01/src/modules/widgets/Widget.tsx +8 -0
  358. package/corpus/frontend/layout-contract/violation-01/layout-contract.json +3 -0
  359. package/corpus/frontend/layout-contract/violation-01/src/modules/widgets/Widget.tsx +8 -0
  360. package/corpus/frontend/no-cross-module-imports/compliant-01/src/modules/widgets/Widget.tsx +4 -0
  361. package/corpus/frontend/no-cross-module-imports/violation-01/src/modules/widgets/Widget.tsx +3 -0
  362. package/corpus/frontend/no-deep-imports/compliant-01/src/modules/widgets/Widget.tsx +4 -0
  363. package/corpus/frontend/no-deep-imports/violation-01/src/modules/widgets/Widget.tsx +2 -0
  364. package/corpus/frontend/no-dom-html-injection/compliant-01/src/modules/widgets/Widget.tsx +4 -0
  365. package/corpus/frontend/no-dom-html-injection/compliant-02/src/modules/widgets/Widget.tsx +11 -0
  366. package/corpus/frontend/no-dom-html-injection/violation-01/src/modules/widgets/Widget.tsx +3 -0
  367. package/corpus/frontend/no-dom-html-injection/violation-02/src/modules/widgets/Widget.tsx +7 -0
  368. package/corpus/frontend/no-dom-html-injection/violation-03/src/modules/widgets/Widget.tsx +12 -0
  369. package/corpus/frontend/no-eval/compliant-01/src/modules/widgets/Widget.tsx +4 -0
  370. package/corpus/frontend/no-eval/compliant-02/src/modules/widgets/Widget.tsx +11 -0
  371. package/corpus/frontend/no-eval/violation-01/src/modules/widgets/Widget.tsx +3 -0
  372. package/corpus/frontend/no-eval/violation-02/src/modules/widgets/Widget.tsx +7 -0
  373. package/corpus/frontend/no-eval/violation-03/src/modules/widgets/Widget.tsx +10 -0
  374. package/corpus/frontend/no-eval/violation-04/src/modules/widgets/Widget.tsx +6 -0
  375. package/corpus/frontend/no-inline-styling/compliant-01/src/modules/widgets/Widget.tsx +4 -0
  376. package/corpus/frontend/no-inline-styling/violation-01/src/modules/widgets/Widget.tsx +3 -0
  377. package/corpus/frontend/no-inline-styling/violation-02/src/modules/widgets/Widget.tsx +6 -0
  378. package/corpus/frontend/no-style-imports/compliant-01/src/modules/widgets/Widget.tsx +4 -0
  379. package/corpus/frontend/no-style-imports/violation-01/src/modules/widgets/Widget.tsx +2 -0
  380. package/corpus/frontend/no-unsafe-href/compliant-01/src/modules/widgets/Widget.tsx +3 -0
  381. package/corpus/frontend/no-unsafe-href/violation-01/src/modules/widgets/Widget.tsx +3 -0
  382. package/corpus/frontend/no-unsafe-target-blank/compliant-01/src/modules/widgets/Widget.tsx +3 -0
  383. package/corpus/frontend/no-unsafe-target-blank/violation-01/src/modules/widgets/Widget.tsx +3 -0
  384. package/corpus/frontend/router-links/compliant-01/src/modules/widgets/Widget.tsx +4 -0
  385. package/corpus/frontend/router-links/violation-01/src/modules/widgets/Widget.tsx +3 -0
  386. package/corpus/frontend/token-styled-elements/compliant-01/src/modules/widgets/Widget.tsx +4 -0
  387. package/corpus/frontend/token-styled-elements/compliant-02/src/modules/widgets/Widget.tsx +4 -0
  388. package/corpus/frontend/token-styled-elements/violation-01/src/modules/widgets/Widget.tsx +3 -0
  389. package/findings.schema.json +40 -0
  390. package/package.json +23 -0
  391. package/restricted-surface.json +10 -0
  392. package/scorecard.schema.json +52 -0
@@ -0,0 +1,27 @@
1
+ {
2
+ "id": "backend/no_adhoc_middleware",
3
+ "surface": "backend",
4
+ "title": "App code never wires HTTP middleware itself; security is centralized",
5
+ "intent": "Cross-cutting HTTP security (headers, CORS, rate-limit, body-size, request-id) is declared once as central security configuration and installed at composition. A module that registers middleware itself — by call, decorator, or subclassing — is assembling a security posture outside that single control plane.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_adhoc_middleware"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "_freeze_app_middleware_registration"
17
+ }
18
+ ],
19
+ "reference": "SecurityConfig declared once and installed by create_app; add_middleware(...), the @app.middleware(\"http\") decorator, and BaseHTTPMiddleware subclasses are refused in app modules. On the composed app both registration spellings raise BootError at runtime; create_app's middleware parameter is the one sanctioned composition seam.",
20
+ "opt_out": "# arch-allow-no-adhoc-middleware: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "The composition freeze covers the middleware seam: on the composed app, add_middleware(...) and the @app.middleware(...) decorator are replaced by refusers that raise a BootError naming this rule and the sanctioned alternative (create_app's middleware parameter) -- so middleware registered between composition and serving is refused fail-closed, not just flagged at build time."
24
+ },
25
+ "guide_topic": "capability",
26
+ "corpus": true
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "id": "backend/no_adhoc_permission_literals",
3
+ "surface": "backend",
4
+ "title": "Modules reference typed authority objects, never bare permission strings",
5
+ "intent": "An authority named as a bare string is invisible to the control plane: it cannot be resolved against the app's declared registry, so a typo or a stale name silently grants nothing (or the wrong thing) instead of failing the build. Citing typed authority objects keeps every permission reference greppable and verifiable against the registry, and lets the runtime normalization chokepoint refuse anything unregistered.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_adhoc_permission_literals"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "requirement_from"
17
+ }
18
+ ],
19
+ "reference": "Role / Permission objects from the control-plane registry; requirement_from raises TypeError on a bare string.",
20
+ "opt_out": "# arch-allow-no-adhoc-permission-literals: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "The typed normalization chokepoint refuses a bare string outright (requirement_from raises TypeError for anything but Role / Permission / the legacy enum), and boot refuses a Policy citing an authority the control plane does not declare."
24
+ },
25
+ "guide_topic": "policy",
26
+ "corpus": true
27
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "id": "backend/no_app_instantiation",
3
+ "surface": "backend",
4
+ "title": "App code never constructs the web application object directly",
5
+ "intent": "The framework's composer owns app composition (deny-by-default guards, the control plane, the error envelope). A hand-built application object is an application assembled outside the framework — it acquires none of those controls.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_app_instantiation"
12
+ }
13
+ ],
14
+ "reference": "terp.core.create_app composes the app; a bare FastAPI() constructor call is refused.",
15
+ "opt_out": "# arch-allow-no-app-instantiation: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "A raw FastAPI() is an ordinary object the framework never sees: it acquires no guard, middleware, or error envelope -- and offers no seam through which terp.core could refuse its construction. Only composition through create_app passes framework-owned surface; the rule keeps authorship inside it."
19
+ },
20
+ "guide_topic": "capability",
21
+ "corpus": true
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "id": "backend/no_blocking_sleep",
3
+ "surface": "backend",
4
+ "title": "Never block the thread with a synchronous sleep",
5
+ "intent": "A synchronous sleep parks the thread that is serving the request or running the job, so the whole worker sits idle and the pool starves under load. Waiting must yield the thread — poll with an awaitable, schedule a delayed job, or let the runtime back off — never freeze it.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_blocking_sleep"
12
+ }
13
+ ],
14
+ "reference": "A call to the standard library's time.sleep() is refused; use a non-blocking wait or a scheduled job.",
15
+ "opt_out": "# arch-allow-no-blocking-sleep: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The invariant is a property of the authored call. At execution time a blocking sleep is a normal function call on the stack, and by the time it runs the thread is already parked — no seam upstream can turn a blocking wait into a yielding one, so the only faithful check refuses the construct in source."
19
+ },
20
+ "guide_topic": "jobs",
21
+ "corpus": true
22
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "backend/no_cross_module_imports",
3
+ "surface": "backend",
4
+ "title": "A module never imports a sibling module (leaf domains stay independent)",
5
+ "intent": "Modules couple only through the platform surface and their own files — a sibling import creates hidden coupling that breaks module isolation and independent evolution. Both absolute and relative sibling imports are caught: a relative import is resolved to its absolute module first, so renaming the import style does not re-couple two leaf modules.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_cross_module_imports"
12
+ }
13
+ ],
14
+ "opt_out": "# arch-allow-no-cross-module-imports: <reason>",
15
+ "runtime": {
16
+ "applicability": "not-applicable",
17
+ "rationale": "Module independence is an import-graph property of the source tree. At runtime the composed app legitimately holds every module in one process, so a cross-module reference is indistinguishable from composition itself."
18
+ },
19
+ "guide_topic": "module",
20
+ "corpus": true
21
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "id": "backend/no_dependency_overrides",
3
+ "surface": "backend",
4
+ "title": "App code never rebinds the composed app's dependency injections",
5
+ "intent": "Composition binds the authentication and session seams once. Rebinding the app's dependency-override map in app code (e.g. replacing the principal provider) silently disables authentication or swaps the database session outside every guard. Overrides are a TEST-ONLY seam; application code has no legitimate use.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_dependency_overrides"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "_freeze_dependency_overrides"
17
+ }
18
+ ],
19
+ "reference": "app.dependency_overrides on the create_app-composed app is the refused surface; composition binds the seams (create_app(principal_provider=...)) before the freeze.",
20
+ "opt_out": "# arch-allow-no-dependency-overrides: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "Outside the local environment the composed app's dependency-override map is replaced by a refusing mapping: every mutating spelling raises a BootError naming this rule while per-request override reads keep working. The local environment deliberately keeps the map writable -- overrides are the sanctioned test-only seam (a local test suite rebinding the session against a scratch engine), which is exactly the legitimate use the rule's intent carves out; a deployed app can never rebind its auth or session seams post-composition."
24
+ },
25
+ "guide_topic": "capability",
26
+ "corpus": true
27
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "id": "backend/no_destructive_migrations",
3
+ "surface": "backend",
4
+ "title": "Destructive migration operations require a reason-bearing marker",
5
+ "intent": "Dropping a table or column, changing a column's type, and executing raw destructive statements in an upgrade can destroy data or make rollback unsafe — whether spelled directly, on a batch block, through an alias, or smuggled into a raw statement. Each destructive operation is a violation; a reviewed one is justified through the standard governed escape hatch — a justified marker on (or immediately above) the operation, counted against the app's budget ratchet — so every accepted risk is explicit, reviewable, greppable, and ratcheted like any other opt-out.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_destructive_migrations"
12
+ }
13
+ ],
14
+ "reference": "drop_table / drop_column / type-changing alter_column and execute(...) of DROP TABLE / DROP COLUMN / TRUNCATE / DELETE FROM / ALTER TABLE ... DROP in upgrade() are flagged per operation; the standard # arch-allow-no-destructive-migrations marker (budgeted) justifies a reviewed one. The pre-0.6.0 file-level terp-allow-destructive-migration waiver is retired.",
15
+ "opt_out": "# arch-allow-no-destructive-migrations: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The invariant is reviewability of the authored revision: a reason-bearing marker is a comment, invisible to Alembic when the DDL executes. What could be checked at apply time is a different (op-classification) control; the justified-marker contract itself only exists in source."
19
+ },
20
+ "guide_topic": "migrations",
21
+ "corpus": true
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "id": "backend/no_dynamic_sql",
3
+ "surface": "backend",
4
+ "title": "Raw SQL text in app modules must be a static literal, never dynamically built",
5
+ "intent": "A raw-SQL construct built dynamically — through interpolation, concatenation, format calls, or a variable — is not statically reviewable and is easy to turn into SQL injection. Keep SQL as a literal and pass data through bound parameters / the query builder's typed expressions instead. As a security rule this also scans test and migration files inside a module — they are importable code, so they are application surface too.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_dynamic_sql"
12
+ }
13
+ ],
14
+ "reference": "text(...) / sqlalchemy.text(...) with an f-string, concatenation, .format, % formatting, or a variable is refused; SQLAlchemy bound parameters / ORM expressions are the compliant path.",
15
+ "opt_out": "# arch-allow-no-dynamic-sql: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The invariant is a property of the authored source: whether SQL text was written as a static, reviewable literal. At execution time every statement is just a string \u2014 how it was built is erased, so no runtime seam can distinguish a vetted literal from concatenated input. The adjacent runtime protections are different controls: the write guard refuses smuggled DML (mutations_emit_audit) and parameter binding is the compliant data path."
19
+ },
20
+ "guide_topic": "service",
21
+ "corpus": true
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "id": "backend/no_empty_tests",
3
+ "surface": "backend",
4
+ "title": "Every test must assert a real outcome",
5
+ "intent": "A test whose body is empty, is only a docstring or a bare pass, or asserts a constant that can never fail, passes unconditionally — it exercises nothing yet reports green, giving false confidence that the behaviour it names is covered. A test must drive real behaviour and assert a real result.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_empty_tests"
12
+ }
13
+ ],
14
+ "reference": "A test_* function with an empty body, a lone pass, or a single assertion of a constant truthy value is refused. The scan covers every test_*.py file — the test tree the other source rules deliberately skip.",
15
+ "opt_out": "# arch-allow-no-empty-tests: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The invariant is a property of the authored test source, evaluated at build time in the test suite itself; there is no production runtime seam that observes whether a test asserts anything. Only a source-form check can see the empty body."
19
+ },
20
+ "guide_topic": "rules",
21
+ "corpus": true
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "id": "backend/no_eval_or_exec",
3
+ "surface": "backend",
4
+ "title": "Never execute a string as code",
5
+ "intent": "Turning a runtime string into executed code is a code-injection hole: any value an attacker can influence on its way into the evaluator becomes arbitrary code the process runs. There is no safe in-app use — parse the data into a structure, dispatch on a lookup table, or import a real module instead of evaluating text.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_eval_or_exec"
12
+ }
13
+ ],
14
+ "reference": "Calls to the eval() and exec() builtins are refused. The scan covers the whole importable tree — including tests and migrations — because any Python that runs is in scope.",
15
+ "opt_out": "# arch-allow-no-eval-or-exec: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The invariant is a property of the authored source: whether a dynamic-execution builtin was written. By the time the process runs, an evaluated string is indistinguishable from ordinary control flow, and no runtime seam upstream can tell a vetted call from an injected one — the only faithful check is refusing the construct in source."
19
+ },
20
+ "guide_topic": "capability",
21
+ "corpus": true
22
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "backend/no_hardcoded_credentials",
3
+ "surface": "backend",
4
+ "title": "App modules do not hard-code credentials or recognizable secret tokens",
5
+ "intent": "A credential-shaped assignment to a non-empty string literal is almost always a secret that should come from sealed config / environment wiring, not source. The rule also rejects common high-confidence secret literal formats anywhere in a module so leaked keys are caught even when assigned to a bland variable name. As a security rule this also scans test and migration files inside a module — a real secret is a leak wherever it is committed.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_hardcoded_credentials"
12
+ }
13
+ ],
14
+ "opt_out": "# arch-allow-no-hardcoded-credentials: <reason>",
15
+ "runtime": {
16
+ "applicability": "not-applicable",
17
+ "rationale": "At runtime a credential is a string wherever it came from: a source literal and an injected environment value are indistinguishable once loaded. 'It was committed to the repository' is a source-tree fact only a source scan can state."
18
+ },
19
+ "guide_topic": "capability",
20
+ "corpus": true
21
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "id": "backend/no_raw_connection_access",
3
+ "surface": "backend",
4
+ "title": "Modules never reach the raw DB connection / engine behind the session",
5
+ "intent": "The runtime write guard covers the request session's own persistence methods, but the bound engine / connection the session exposes can issue data-modification statements directly, bypassing the audited chokepoint. A module must never reach for the session's underlying bind or connection; persist through the model's service so every write is audited. The escape is caught at the reach itself, and raw session / engine construction is separately banned by no_raw_session_construction — so an unrelated connect call on a domain object (a websocket / cache / search client) is deliberately not flagged.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_raw_connection_access"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.core",
16
+ "ref": "WriteGuardedSession"
17
+ }
18
+ ],
19
+ "reference": "session.get_bind() / session.connection() calls are refused (ADR 0015, F3); BaseService is the audited write path, and WriteGuardedSession gates session.connection() at runtime.",
20
+ "opt_out": "# arch-allow-no-raw-connection-access: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "The write-guarded request session gates session.connection() itself (WriteGuardedSession), closing the primary in-request escape to unaudited DML; the get_bind()/engine remainder has no construction hook, which is exactly the residual the build-time half covers."
24
+ },
25
+ "guide_topic": "service",
26
+ "corpus": true
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "id": "backend/no_raw_file_references",
3
+ "surface": "backend",
4
+ "title": "A table model's file-pointer column is a declared file reference, never a bare id column",
5
+ "intent": "A stored pointer to a file object carries authorization semantics: the files capability serves delegated reads only through a declared reference, failing closed on an undeclared column. A bare id-typed file-pointer column on a table model is an undeclared reference: nothing ties the file's access to the referencing row, which is the classic object-level (BOLA) drift. Declare the column with the files capability's reference type so the pointer is greppable, verified at runtime, and served through the module's own already-authorized row — never hand-rolled. A non-table schema (a read DTO exposing the pointer) is fine and not policed; only the persisted column is.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_no_raw_file_references"
12
+ },
13
+ {
14
+ "kind": "runtime",
15
+ "tool": "terp.capabilities.files",
16
+ "ref": "load_for"
17
+ }
18
+ ],
19
+ "reference": "FileRef(...) from terp-cap-files declares the column; FileService.load_for fail-closes on an undeclared *file_id column (ADR 0057).",
20
+ "opt_out": "# arch-allow-no-raw-file-references: <reason>",
21
+ "runtime": {
22
+ "applicability": "required",
23
+ "rationale": "Delegated reads fail closed on an undeclared reference column: FileService.load_for serves a file only through a FileRef-declared column (UndeclaredFileReferenceError otherwise, ADR 0057), so a bare uuid column never acquires read-delegation semantics."
24
+ },
25
+ "guide_topic": "files",
26
+ "corpus": true
27
+ }