@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,21 @@
1
+ {
2
+ "id": "frontend/no-cross-module-imports",
3
+ "surface": "frontend",
4
+ "title": "An app module never imports a sibling module",
5
+ "intent": "Modules couple only through the platform packages and their own files. A sibling import — static, dynamic import(), relative or via the app alias — creates hidden coupling that breaks module isolation and independent evolution.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "@terp/eslint-boundaries",
11
+ "ref": "terp/no-cross-module-imports",
12
+ "reported_as": "terp/no-cross-module-imports"
13
+ }
14
+ ],
15
+ "opt_out": "// terp-allow-no-cross-module-imports: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "Module independence is an import-graph property of the source tree; the bundler merges every module into one dependency graph, after which a cross-module edge is indistinguishable from composition."
19
+ },
20
+ "corpus": true
21
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "frontend/no-deep-imports",
3
+ "surface": "frontend",
4
+ "title": "App modules import platform packages from the root only",
5
+ "intent": "A deep import into a platform package's internals couples the app to files that are free to move; the package root is the published, stable surface. The refused path segments are declared in restricted-surface.json (deepImportPathSegments).",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "@terp/eslint-boundaries",
11
+ "ref": "BOUNDARY_SPEC.internalImportPatterns",
12
+ "reported_as": "no-restricted-imports"
13
+ }
14
+ ],
15
+ "reference": "@terp/*/src/* and @terp/*/dist/* imports are refused; import from the @terp/* package root.",
16
+ "opt_out": "// terp-allow-no-deep-imports: <reason>",
17
+ "runtime": {
18
+ "applicability": "not-applicable",
19
+ "rationale": "Package-boundary form: the bundler resolves and inlines whatever was imported, and the built artifact retains no distinction between a root import and a deep one."
20
+ },
21
+ "restricted_surface": [
22
+ "deepImportPathSegments"
23
+ ],
24
+ "corpus": true
25
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "id": "frontend/no-dom-html-injection",
3
+ "surface": "frontend",
4
+ "title": "DOM HTML-injection sinks are refused in app modules",
5
+ "intent": "dangerouslySetInnerHTML, innerHTML/outerHTML assignment, insertAdjacentHTML and document.write inject markup that bypasses the framework's output escaping — the canonical XSS sink. Rich text renders through the stack's sanctioned rich-text component instead.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "@terp/eslint-boundaries",
11
+ "ref": "terp/no-dom-html-injection",
12
+ "reported_as": "terp/no-dom-html-injection"
13
+ }
14
+ ],
15
+ "reference": "The Markdown component from @terp/react-core for rich text.",
16
+ "opt_out": "// terp-allow-no-dom-html-injection: <reason>",
17
+ "runtime": {
18
+ "applicability": "not-applicable",
19
+ "rationale": "The sinks are platform primitives on every element; React's default output escaping is the ambient constructive protection and the sanctioned Markdown component sanitises rich text. A runtime hook over every element's HTML mutation is not a seam react-core owns or could make sound -- vendored code legitimately uses the same primitives."
20
+ },
21
+ "corpus": true
22
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "frontend/no-eval",
3
+ "surface": "frontend",
4
+ "title": "eval() and new Function() are refused in app modules",
5
+ "intent": "Runtime code evaluation defeats every static guarantee the boundary provides and is a classic injection primitive. There is no sanctioned dynamic-code path in an app module.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "@terp/eslint-boundaries",
11
+ "ref": "terp/no-eval",
12
+ "reported_as": "terp/no-eval"
13
+ }
14
+ ],
15
+ "opt_out": "// terp-allow-no-eval: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The prohibition is on authoring dynamic-code primitives in app modules \u2014 a source-form fact. In the running bundle the primitive itself is a platform global the framework cannot remove, and legitimately vendored code may reference it, so a runtime hook cannot attribute authorship to an app module. Deployment-level CSP can harden the served app, but it is deployment configuration, not a control this framework owns per rule."
19
+ },
20
+ "corpus": true
21
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "frontend/no-inline-styling",
3
+ "surface": "frontend",
4
+ "title": "style and className attributes are refused in app modules",
5
+ "intent": "A style or className attribute is a side channel into hand-authored CSS that drifts per screen; styling flows from the app's design tokens and sanctioned layout primitives only. The refused attributes are declared in restricted-surface.json (restrictedAttributes).",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "@terp/eslint-boundaries",
11
+ "ref": "BOUNDARY_SPEC.restrictedAttributes",
12
+ "reported_as": "no-restricted-syntax"
13
+ }
14
+ ],
15
+ "reference": "Layout via Stack, DetailList and the page archetypes from @terp/react-core; theming via the app's token source.",
16
+ "opt_out": "// terp-allow-no-inline-styling: <reason>",
17
+ "runtime": {
18
+ "applicability": "not-applicable",
19
+ "rationale": "style/className are authoring-surface attributes: the token pipeline itself emits inline styles and class names into the built DOM, so their presence at runtime is legitimate and unattributable to app authorship."
20
+ },
21
+ "restricted_surface": [
22
+ "restrictedAttributes"
23
+ ],
24
+ "corpus": true
25
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "id": "frontend/no-style-imports",
3
+ "surface": "frontend",
4
+ "title": "Module-authored stylesheets are refused",
5
+ "intent": "Importing a stylesheet from an app module reintroduces per-screen CSS; theming flows from the app's token source only. The refused stylesheet extensions are declared in restricted-surface.json (styleImportExtensions).",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "@terp/eslint-boundaries",
11
+ "ref": "BOUNDARY_SPEC.styleImportPatterns",
12
+ "reported_as": "no-restricted-imports"
13
+ }
14
+ ],
15
+ "opt_out": "// terp-allow-no-style-imports: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "A stylesheet import is a build-graph edge; the bundler compiles it into the emitted CSS, after which module authorship does not exist."
19
+ },
20
+ "restricted_surface": [
21
+ "styleImportExtensions"
22
+ ],
23
+ "corpus": true
24
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "frontend/no-unsafe-href",
3
+ "surface": "frontend",
4
+ "title": "javascript: URLs are refused in app modules",
5
+ "intent": "A javascript: href executes script on click \u2014 an XSS vector that survives the framework's attribute escaping. Links carry http(s), mailto, tel or in-app router paths only.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "@terp/eslint-boundaries",
11
+ "ref": "terp/no-unsafe-href",
12
+ "reported_as": "terp/no-unsafe-href"
13
+ }
14
+ ],
15
+ "opt_out": "// terp-allow-no-unsafe-href: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "The refusal is of authoring javascript: URLs in source. Data-driven links are the sanctioned components' own concern (the Markdown component strips unsafe protocols at render -- its control, not this rule's), and raw anchors are refused wholesale in app modules anyway."
19
+ },
20
+ "corpus": true
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "frontend/no-unsafe-target-blank",
3
+ "surface": "frontend",
4
+ "title": "A static target=\"_blank\" link declares rel=\"noopener\"",
5
+ "intent": "Without rel=noopener the opened page holds a window.opener reference to the app \u2014 the reverse-tabnabbing primitive. The rule enforces the safe default on every statically-authored external link.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "@terp/eslint-boundaries",
11
+ "ref": "terp/no-unsafe-target-blank",
12
+ "reported_as": "terp/no-unsafe-target-blank"
13
+ }
14
+ ],
15
+ "opt_out": "// terp-allow-no-unsafe-target-blank: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "An authored static target=_blank without rel is a source form; the residual risk it addresses is legacy-engine reverse-tabnabbing, fixable only where the anchor is written. Modern engines default to noopener behaviour, and raw anchors are refused wholesale in app modules regardless."
19
+ },
20
+ "corpus": true
21
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "id": "frontend/router-links",
3
+ "surface": "frontend",
4
+ "title": "In-app links go through the router, never a raw anchor",
5
+ "intent": "A raw <a href=\"/...\"> forces a full reload and skips the role-aware route guard; in-app navigation uses the stack's router-integrated link. External https:// anchors stay allowed.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "@terp/eslint-boundaries",
11
+ "ref": "BOUNDARY_SPEC.restrictInAppAnchors",
12
+ "reported_as": "no-restricted-syntax"
13
+ }
14
+ ],
15
+ "reference": "Link from @terp/react-core (router-integrated, role-aware).",
16
+ "opt_out": "// terp-allow-router-links: <reason>",
17
+ "runtime": {
18
+ "applicability": "not-applicable",
19
+ "rationale": "In the rendered DOM the router's own Link IS an anchor -- raw-anchor authorship is only visible in source, so no DOM check can distinguish a sanctioned link from a hand-written one."
20
+ },
21
+ "corpus": true
22
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "frontend/token-styled-elements",
3
+ "surface": "frontend",
4
+ "title": "Raw interactive/structural HTML elements are refused in app modules",
5
+ "intent": "A raw <button>/<input>/<select>/<textarea>/<table>/<dialog>/<form> bypasses the app's sanctioned, token-styled primitive surface (accessible and theme-consistent by construction). The refused elements are declared in restricted-surface.json (restrictedElements); each has a sanctioned replacement in the stack's component surface, and the violation message names it.",
6
+ "layer": "static-portable",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "@terp/eslint-boundaries",
11
+ "ref": "BOUNDARY_SPEC.restrictedElements",
12
+ "reported_as": "no-restricted-syntax"
13
+ }
14
+ ],
15
+ "reference": "Button, Input, Select, Textarea, DataView, ConfirmDialog and Stack as=\"form\" from @terp/react-core (BOUNDARY_SPEC.restrictedElements maps each element to its replacement).",
16
+ "opt_out": "// terp-allow-token-styled-elements: <reason>",
17
+ "runtime": {
18
+ "applicability": "not-applicable",
19
+ "rationale": "The sanctioned components themselves render the raw elements into the built DOM -- every button on screen is legitimate output of Button. Compilation erases which JSX authored a node, and third-party components legitimately render the same primitives, so a DOM scan cannot soundly attribute a raw element to app authorship (unlike the layout contract's slot check, which verifies the identity of sanctioned components at one owned seam)."
20
+ },
21
+ "restricted_surface": [
22
+ "restrictedElements"
23
+ ],
24
+ "corpus": true
25
+ }
@@ -0,0 +1,95 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://terp.dev/spec/catalog/schema.json",
4
+ "title": "Terp Standard catalog entry",
5
+ "description": "One enforced Terp rule as data (ADR 0080). The file lives at catalog/<surface>/<rule>.json and its id must equal '<surface>/<rule>'. The spec version an entry belongs to is the checked-in spec/VERSION.",
6
+ "type": "object",
7
+ "required": ["id", "surface", "title", "intent", "layer", "enforcement", "runtime", "corpus"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "id": {
11
+ "type": "string",
12
+ "description": "Stack-neutral rule id: backend/<snake_case> or frontend/<kebab-case>. Findings are attributed to this id (see ../findings.schema.json).",
13
+ "pattern": "^(backend/[a-z0-9_]+|frontend/[a-z0-9-]+)$"
14
+ },
15
+ "surface": { "enum": ["backend", "frontend"] },
16
+ "title": { "type": "string", "minLength": 1 },
17
+ "intent": { "type": "string", "minLength": 1 },
18
+ "layer": {
19
+ "description": "Cheapest faithful verification for a NEW stack: black-box (probe a running app), static-portable (generic source patterns), static-bespoke (needs deep framework knowledge).",
20
+ "enum": ["black-box", "static-portable", "static-bespoke"]
21
+ },
22
+ "enforcement": {
23
+ "description": "How the reference implementation enforces the rule today. At least one build-time entry; a 'runtime' entry records the fail-closed runtime half of the two-layer discipline, and a 'black-box' entry names the conformance-suite probe.",
24
+ "type": "array",
25
+ "minItems": 1,
26
+ "items": {
27
+ "type": "object",
28
+ "required": ["kind", "tool", "ref"],
29
+ "additionalProperties": false,
30
+ "properties": {
31
+ "kind": { "enum": ["build-time", "runtime", "black-box"] },
32
+ "tool": { "type": "string", "minLength": 1 },
33
+ "ref": { "type": "string", "minLength": 1 },
34
+ "reported_as": {
35
+ "type": "string",
36
+ "description": "Frontend build-time only: the ESLint rule id violations surface as. Several catalog rules share a core rule id, so conformance findings are attributed via the adapter's catalogRuleId mapping, never this field."
37
+ }
38
+ }
39
+ }
40
+ },
41
+ "runtime": {
42
+ "description": "The rule's runtime-applicability classification: whether the two-layer discipline pairs this build-time rule with a fail-closed runtime control. 'required' means the reference implementation owns such a control and the entry declares it (a kind 'runtime' enforcement entry whose ref must resolve — the parity test fails otherwise). 'not-applicable' means the invariant is a property of the authored artifact (source form, imports, markers, checked-in files) that is erased or already materialised by the time the app runs, so no runtime seam can enforce it — the rationale states why. 'deferred' means a runtime control would add independent fidelity on a seam the framework owns but has not shipped yet — an explicit, reviewed gap, never a silent one. Coherence is held by the spec suite: 'required' iff a kind 'runtime' enforcement entry exists; an exemption always carries a rationale.",
43
+ "type": "object",
44
+ "required": ["applicability"],
45
+ "additionalProperties": false,
46
+ "properties": {
47
+ "applicability": { "enum": ["required", "not-applicable", "deferred"] },
48
+ "rationale": {
49
+ "type": "string",
50
+ "description": "Why the rule is exempt from the runtime half (mandatory for 'not-applicable' and 'deferred'; optional for 'required', e.g. to record which vector the control covers).",
51
+ "minLength": 1
52
+ },
53
+ "tracking": {
54
+ "type": "string",
55
+ "description": "Where the deferral is tracked (mandatory for 'deferred', held by the spec suite): an issue URL, or the reference implementation's tracker document plus the named seam — so a deferred runtime control has a lifecycle instead of being an open-ended gap.",
56
+ "minLength": 1
57
+ }
58
+ }
59
+ },
60
+ "restricted_surface": {
61
+ "type": "array",
62
+ "description": "The restricted-surface.json keys this rule realises (frontend prohibition rules only). The structural half of the citation: the spec suite resolves each key against the refused surface and requires every key to be claimed by some rule, so the linkage never depends on parsing prose.",
63
+ "minItems": 1,
64
+ "items": {
65
+ "enum": [
66
+ "restrictedElements",
67
+ "restrictedAttributes",
68
+ "restrictedGlobals",
69
+ "restrictedMemberCalls",
70
+ "styleImportExtensions",
71
+ "deepImportPathSegments"
72
+ ]
73
+ }
74
+ },
75
+ "reference": {
76
+ "type": "string",
77
+ "description": "Reference-implementation metadata: the compliant realisation the reference stack offers (component / helper names). Not normative — the title and intent state the stack-neutral invariant; another stack ships its own realisation.",
78
+ "minLength": 1
79
+ },
80
+ "opt_out": {
81
+ "type": "string",
82
+ "description": "The reference realisation of the abstract escape-hatch contract: a justified inline marker naming the CATALOG RULE NAME (the <rule> half of the id — never a tool-internal rule id, mirroring findings attribution), counted against the app's checked-in budget ratchet. Another stack implements the same contract with its own comment syntax; only the semantics are normative. Omitted exactly by the escape-hatch governance rules themselves (the budget ratchet, the ungoverned-marker condition) — governance cannot be waived by the mechanism it governs. The spec suite holds both the derivation and the omissions.",
83
+ "minLength": 1
84
+ },
85
+ "guide_topic": {
86
+ "type": "string",
87
+ "description": "Reference-implementation metadata (backend only): the `terp guide` topic teaching the compliant pattern. Not normative for other stacks.",
88
+ "minLength": 1
89
+ },
90
+ "corpus": {
91
+ "type": "boolean",
92
+ "description": "Whether corpus/<id>/ cases exist (held to the directory truth by the parity test; a covered rule needs at least one violation-* AND one compliant-* case)."
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "_comment": "The corpus-coverage ratchet: every catalog rule still WITHOUT corpus cases, exactly. Seeding a rule's corpus removes it here (the list only shrinks); a new rule without corpus must be added here explicitly, so uncovered rules stay visible and reviewed. Held to the catalog truth by tests/architecture/test_spec_catalog.py.",
3
+ "pending": []
4
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "_comment": "The detector-residual ratchet: the statically-erased or renamed forms DELIBERATELY outside the corpus contract, per rule — known limits of precise, low-false-positive detection, recorded as data so a second implementation neither over-fits nor under-claims. Governed like corpus/PENDING.json: the list only shrinks — closing a residual means seeding the corpus case that contracts it and deleting the entry here, never silently. Every key must be a catalog rule id; held to the catalog by tests/test_standard.py.",
3
+ "residuals": {
4
+ "backend/no_dynamic_sql": [
5
+ "an alias-renamed symbol import (`from sqlalchemy import text as sql_text`) is not required to be resolved to `text`"
6
+ ],
7
+ "backend/no_raw_outbound_http": [
8
+ "dynamic import (`importlib.import_module(\"httpx\")`) is not required to be seen as an import"
9
+ ],
10
+ "frontend/generated-client-only": [
11
+ "egress through receivers other than `window`/`globalThis` (`self.fetch`) is not required"
12
+ ],
13
+ "frontend/no-dom-html-injection": [
14
+ "a computed sink (`el[\"innerHTML\"] = ...`) is not required to be recognised"
15
+ ],
16
+ "frontend/no-eval": [
17
+ "`Function(\"...\")` called without `new` is not required",
18
+ "a computed global (`window[\"eval\"]`) is not required to be recognised"
19
+ ]
20
+ }
21
+ }
@@ -0,0 +1,6 @@
1
+ def upgrade():
2
+ op.add_column("notes", column)
3
+
4
+
5
+ def downgrade():
6
+ op.drop_column("notes", "title")
@@ -0,0 +1,6 @@
1
+ def upgrade():
2
+ op.add_column("notes", column)
3
+
4
+
5
+ def downgrade():
6
+ pass
@@ -0,0 +1,8 @@
1
+ from terp.core import BaseService
2
+
3
+
4
+ class NoteService(BaseService):
5
+ model = Note
6
+
7
+ def business_filters(self):
8
+ return [Note.archived == False]
@@ -0,0 +1,8 @@
1
+ from terp.core import BaseService
2
+
3
+
4
+ class NoteService(BaseService):
5
+ model = Note
6
+
7
+ def business_filters(self):
8
+ return [Note.archived == False]
@@ -0,0 +1,8 @@
1
+ from terp.core import BaseService
2
+
3
+
4
+ class NoteService(BaseService):
5
+ model = Note
6
+
7
+ def base_query(self):
8
+ return super().base_query().where(Note.archived == False)
@@ -0,0 +1,7 @@
1
+ [
2
+ {
3
+ "rule": "backend/base_query_not_overridden",
4
+ "path": "modules/notes/service.py",
5
+ "line": 7
6
+ }
7
+ ]
@@ -0,0 +1,8 @@
1
+ from terp.core import BaseService
2
+
3
+
4
+ class NoteService(BaseService):
5
+ model = Note
6
+
7
+ async def base_query(self):
8
+ return (await super().base_query()).where(Note.archived == False)
@@ -0,0 +1,6 @@
1
+ from sqlmodel import Field
2
+ from terp.core import BaseTable
3
+
4
+
5
+ class Note(BaseTable, table=True):
6
+ title: str = Field(max_length=200)
@@ -0,0 +1,7 @@
1
+ from terp.core import ModuleSpec, Policy, Roles
2
+
3
+ spec = ModuleSpec(
4
+ name="notes",
5
+ router=router,
6
+ policy=Policy(read=Roles.VIEWER, write=Roles.EDITOR),
7
+ )
@@ -0,0 +1,3 @@
1
+ @router.get('/', response_model=Page[NoteRead])
2
+ def list_notes() -> Page[NoteRead]:
3
+ return Page()
@@ -0,0 +1,10 @@
1
+ from sqlmodel import Field
2
+ from terp.core import BaseSchema
3
+
4
+
5
+ class NoteCreate(BaseSchema):
6
+ title: str = Field(max_length=200)
7
+
8
+
9
+ class NoteRead(BaseSchema):
10
+ title: str
@@ -0,0 +1,5 @@
1
+ from terp.core import BaseService
2
+
3
+
4
+ class NoteService(BaseService):
5
+ model = Note
@@ -0,0 +1,3 @@
1
+ @router.get('/', response_model=Page[NoteRead])
2
+ def list_notes() -> Page[NoteRead]:
3
+ return Page()
@@ -0,0 +1,12 @@
1
+ from datetime import datetime
2
+
3
+ from sqlalchemy import DateTime
4
+ from sqlmodel import Field
5
+
6
+ from terp.core import BaseTable
7
+
8
+
9
+ class Note(BaseTable, table=True):
10
+ title: str = Field(max_length=200)
11
+ # The column type pins the zone, so the stored moment stays unambiguous.
12
+ due_at: datetime | None = Field(default=None, sa_type=DateTime(timezone=True))
@@ -0,0 +1,12 @@
1
+ from datetime import datetime
2
+
3
+ from sqlmodel import Field
4
+
5
+ from terp.core import BaseTable
6
+
7
+
8
+ class Note(BaseTable, table=True):
9
+ title: str = Field(max_length=200)
10
+ # No column type is named, so this maps to naive storage: the database
11
+ # drops the zone of whatever aware value the service hands it.
12
+ due_at: datetime | None = Field(default=None, index=True)
@@ -0,0 +1,13 @@
1
+ from datetime import datetime
2
+
3
+ from sqlalchemy import DateTime
4
+ from sqlmodel import Field
5
+
6
+ from terp.core import BaseTable
7
+
8
+
9
+ class Note(BaseTable, table=True):
10
+ title: str = Field(max_length=200)
11
+ # An explicit column type that is declared without a timezone is naive
12
+ # storage just as thoroughly as naming no type at all.
13
+ due_at: datetime = Field(sa_type=DateTime(timezone=False))
@@ -0,0 +1,15 @@
1
+ from datetime import datetime
2
+
3
+ from sqlmodel import Field
4
+
5
+ from terp.core import BaseTable
6
+
7
+
8
+ class ArchivedMixin:
9
+ # Declaring the column one class up does not change where it lands: the
10
+ # table still gets a naive timestamp column.
11
+ archived_at: datetime | None = Field(default=None)
12
+
13
+
14
+ class Note(ArchivedMixin, BaseTable, table=True):
15
+ title: str = Field(max_length=200)
@@ -0,0 +1,3 @@
1
+ {
2
+ "arch-allow-no-dynamic-sql": 1
3
+ }
@@ -0,0 +1,2 @@
1
+ def legacy_report(session, sql):
2
+ return session.execute(text(sql)) # arch-allow-no-dynamic-sql: legacy reporting query, parameterised upstream
@@ -0,0 +1,4 @@
1
+ def render_help():
2
+ # Marker-shaped text inside a string is not counted by the budget ratchet:
3
+ # with an empty budget this app is exactly in balance.
4
+ return "suppress with '# arch-allow-no-dynamic-sql: <reason>' after review"
@@ -0,0 +1,2 @@
1
+ def legacy_report(session, sql):
2
+ return session.execute(text(sql)) # arch-allow-no-dynamic-sql: legacy reporting query, parameterised upstream
@@ -0,0 +1,3 @@
1
+ {
2
+ "arch-allow-made-up-rule": 1
3
+ }
@@ -0,0 +1,4 @@
1
+ def legacy_report():
2
+ # A marker (or budget key) that names no rule with a governed opt-out is
3
+ # refused outright - it can never be budgeted into legitimacy.
4
+ return None # arch-allow-made-up-rule: stale name
@@ -0,0 +1,9 @@
1
+ from control_plane.events import NOTE_CREATED
2
+ from terp.core import ModuleSpec, Policy, Roles
3
+
4
+ spec = ModuleSpec(
5
+ name="notes",
6
+ router=router,
7
+ policy=Policy(read=Roles.VIEWER, write=Roles.EDITOR),
8
+ emits=[NOTE_CREATED],
9
+ )
@@ -0,0 +1,8 @@
1
+ from terp.core import ModuleSpec, Policy, Roles
2
+
3
+ spec = ModuleSpec(
4
+ name="notes",
5
+ router=router,
6
+ policy=Policy(read=Roles.VIEWER, write=Roles.EDITOR),
7
+ emits=["notes.created"],
8
+ )
@@ -0,0 +1,6 @@
1
+ from sqlmodel import Field
2
+ from terp.core import BaseSchema
3
+
4
+
5
+ class NoteCreate(BaseSchema):
6
+ title: str = Field(max_length=200)
@@ -0,0 +1,7 @@
1
+ [
2
+ {
3
+ "rule": "backend/input_schemas_exclude_managed_columns",
4
+ "path": "modules/notes/schemas.py",
5
+ "line": 8
6
+ }
7
+ ]
@@ -0,0 +1,9 @@
1
+ import uuid
2
+
3
+ from sqlmodel import Field
4
+ from terp.core import BaseSchema
5
+
6
+
7
+ class NoteCreate(BaseSchema):
8
+ id: uuid.UUID
9
+ title: str = Field(max_length=200)