@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.
- package/LICENSE +201 -0
- package/README.md +417 -0
- package/VERSION +1 -0
- package/app-check-report.schema.json +122 -0
- package/assurance-profile.schema.json +70 -0
- package/catalog/backend/alembic_downgrades_not_empty.json +22 -0
- package/catalog/backend/base_query_not_overridden.json +27 -0
- package/catalog/backend/canonical_module_shape.json +22 -0
- package/catalog/backend/datetime_columns_are_timezone_aware.json +23 -0
- package/catalog/backend/escape_hatch_budget.json +21 -0
- package/catalog/backend/events_reference_catalog.json +27 -0
- package/catalog/backend/input_schemas_exclude_managed_columns.json +27 -0
- package/catalog/backend/input_str_fields_have_max_length.json +22 -0
- package/catalog/backend/jobs_reference_catalog.json +27 -0
- package/catalog/backend/list_routes_paginate.json +32 -0
- package/catalog/backend/modules_declare_policy.json +26 -0
- package/catalog/backend/mutations_emit_audit.json +26 -0
- package/catalog/backend/mutations_require_write_role.json +32 -0
- package/catalog/backend/no_adhoc_background_runtime.json +22 -0
- package/catalog/backend/no_adhoc_config_decrypt.json +27 -0
- package/catalog/backend/no_adhoc_logging_config.json +22 -0
- package/catalog/backend/no_adhoc_middleware.json +27 -0
- package/catalog/backend/no_adhoc_permission_literals.json +27 -0
- package/catalog/backend/no_app_instantiation.json +22 -0
- package/catalog/backend/no_blocking_sleep.json +22 -0
- package/catalog/backend/no_cross_module_imports.json +21 -0
- package/catalog/backend/no_dependency_overrides.json +27 -0
- package/catalog/backend/no_destructive_migrations.json +22 -0
- package/catalog/backend/no_dynamic_sql.json +22 -0
- package/catalog/backend/no_empty_tests.json +22 -0
- package/catalog/backend/no_eval_or_exec.json +22 -0
- package/catalog/backend/no_hardcoded_credentials.json +21 -0
- package/catalog/backend/no_internal_imports.json +22 -0
- package/catalog/backend/no_manual_actor_stamping.json +27 -0
- package/catalog/backend/no_manual_ownership_checks.json +32 -0
- package/catalog/backend/no_manual_scope_filtering.json +27 -0
- package/catalog/backend/no_manual_table_schema.json +22 -0
- package/catalog/backend/no_manual_version_assignment.json +22 -0
- package/catalog/backend/no_mutable_default_args.json +22 -0
- package/catalog/backend/no_naive_datetime.json +22 -0
- package/catalog/backend/no_oversized_python_files.json +22 -0
- package/catalog/backend/no_print.json +22 -0
- package/catalog/backend/no_raw_app_routes.json +26 -0
- package/catalog/backend/no_raw_connection_access.json +27 -0
- package/catalog/backend/no_raw_file_references.json +27 -0
- package/catalog/backend/no_raw_outbound_http.json +22 -0
- package/catalog/backend/no_raw_session_construction.json +22 -0
- package/catalog/backend/no_star_imports.json +22 -0
- package/catalog/backend/no_todo_fixme.json +22 -0
- package/catalog/backend/no_unique_columns_on_soft_delete_models.json +22 -0
- package/catalog/backend/offset_queries_declare_ordering.json +22 -0
- package/catalog/backend/path_id_params_are_uuid.json +22 -0
- package/catalog/backend/policy_refs_resolve.json +27 -0
- package/catalog/backend/public_modules_are_read_only.json +26 -0
- package/catalog/backend/reads_use_base_query.json +27 -0
- package/catalog/backend/response_model_not_table_model.json +27 -0
- package/catalog/backend/routes_declare_response_model.json +27 -0
- package/catalog/backend/safe_methods_are_read_only.json +31 -0
- package/catalog/backend/schemas_exclude_sensitive_fields.json +32 -0
- package/catalog/backend/session_imported_from_sqlmodel.json +22 -0
- package/catalog/backend/table_models_use_base_table.json +22 -0
- package/catalog/backend/tables_have_migrations.json +27 -0
- package/catalog/backend/tenant_scoped_models_use_scoped_service.json +22 -0
- package/catalog/backend/ungoverned_escape_hatch.json +21 -0
- package/catalog/backend/update_schemas_inherit_base_update_schema.json +32 -0
- package/catalog/frontend/escape-hatch.json +21 -0
- package/catalog/frontend/generated-client-only.json +26 -0
- package/catalog/frontend/layout-contract.json +26 -0
- package/catalog/frontend/no-cross-module-imports.json +21 -0
- package/catalog/frontend/no-deep-imports.json +25 -0
- package/catalog/frontend/no-dom-html-injection.json +22 -0
- package/catalog/frontend/no-eval.json +21 -0
- package/catalog/frontend/no-inline-styling.json +25 -0
- package/catalog/frontend/no-style-imports.json +24 -0
- package/catalog/frontend/no-unsafe-href.json +21 -0
- package/catalog/frontend/no-unsafe-target-blank.json +21 -0
- package/catalog/frontend/router-links.json +22 -0
- package/catalog/frontend/token-styled-elements.json +25 -0
- package/catalog/schema.json +95 -0
- package/corpus/PENDING.json +4 -0
- package/corpus/RESIDUALS.json +21 -0
- package/corpus/backend/alembic_downgrades_not_empty/compliant-01/modules/notes/migrations/versions/0001_change.py +6 -0
- package/corpus/backend/alembic_downgrades_not_empty/violation-01/modules/notes/migrations/versions/0001_change.py +6 -0
- package/corpus/backend/base_query_not_overridden/compliant-01/modules/notes/service.py +8 -0
- package/corpus/backend/base_query_not_overridden/compliant-02/modules/notes/service.py +8 -0
- package/corpus/backend/base_query_not_overridden/violation-01/modules/notes/service.py +8 -0
- package/corpus/backend/base_query_not_overridden/violation-02/expected-findings.json +7 -0
- package/corpus/backend/base_query_not_overridden/violation-02/modules/notes/service.py +8 -0
- package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/models.py +6 -0
- package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/module.py +7 -0
- package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/router.py +3 -0
- package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/schemas.py +10 -0
- package/corpus/backend/canonical_module_shape/compliant-01/modules/notes/service.py +5 -0
- package/corpus/backend/canonical_module_shape/violation-01/modules/notes/router.py +3 -0
- package/corpus/backend/datetime_columns_are_timezone_aware/compliant-01/modules/notes/models.py +12 -0
- package/corpus/backend/datetime_columns_are_timezone_aware/violation-01/modules/notes/models.py +12 -0
- package/corpus/backend/datetime_columns_are_timezone_aware/violation-02/modules/notes/models.py +13 -0
- package/corpus/backend/datetime_columns_are_timezone_aware/violation-03/modules/notes/models.py +15 -0
- package/corpus/backend/escape_hatch_budget/compliant-01/escape-hatch-budget.json +3 -0
- package/corpus/backend/escape_hatch_budget/compliant-01/modules/notes/service.py +2 -0
- package/corpus/backend/escape_hatch_budget/compliant-02/escape-hatch-budget.json +1 -0
- package/corpus/backend/escape_hatch_budget/compliant-02/modules/notes/service.py +4 -0
- package/corpus/backend/escape_hatch_budget/violation-01/escape-hatch-budget.json +1 -0
- package/corpus/backend/escape_hatch_budget/violation-01/modules/notes/service.py +2 -0
- package/corpus/backend/escape_hatch_budget/violation-02/escape-hatch-budget.json +3 -0
- package/corpus/backend/escape_hatch_budget/violation-02/modules/notes/service.py +4 -0
- package/corpus/backend/events_reference_catalog/compliant-01/modules/notes/module.py +9 -0
- package/corpus/backend/events_reference_catalog/violation-01/modules/notes/module.py +8 -0
- package/corpus/backend/input_schemas_exclude_managed_columns/compliant-01/modules/notes/schemas.py +6 -0
- package/corpus/backend/input_schemas_exclude_managed_columns/violation-01/expected-findings.json +7 -0
- package/corpus/backend/input_schemas_exclude_managed_columns/violation-01/modules/notes/schemas.py +9 -0
- package/corpus/backend/input_schemas_exclude_managed_columns/violation-02/expected-findings.json +12 -0
- package/corpus/backend/input_schemas_exclude_managed_columns/violation-02/modules/notes/router.py +14 -0
- package/corpus/backend/input_str_fields_have_max_length/compliant-01/modules/notes/schemas.py +4 -0
- package/corpus/backend/input_str_fields_have_max_length/violation-01/modules/notes/schemas.py +2 -0
- package/corpus/backend/jobs_reference_catalog/compliant-01/modules/notes/service.py +5 -0
- package/corpus/backend/jobs_reference_catalog/violation-01/modules/notes/service.py +2 -0
- package/corpus/backend/list_routes_paginate/compliant-01/modules/notes/router.py +6 -0
- package/corpus/backend/list_routes_paginate/violation-01/modules/notes/router.py +3 -0
- package/corpus/backend/modules_declare_policy/compliant-01/modules/notes/module.py +7 -0
- package/corpus/backend/modules_declare_policy/violation-01/modules/notes/module.py +6 -0
- package/corpus/backend/mutations_emit_audit/compliant-01/modules/notes/service.py +9 -0
- package/corpus/backend/mutations_emit_audit/violation-01/modules/notes/service.py +4 -0
- package/corpus/backend/mutations_require_write_role/compliant-01/modules/notes/module.py +7 -0
- package/corpus/backend/mutations_require_write_role/compliant-01/modules/notes/router.py +3 -0
- package/corpus/backend/mutations_require_write_role/compliant-02/modules/notes/module.py +7 -0
- package/corpus/backend/mutations_require_write_role/compliant-02/modules/notes/router.py +3 -0
- package/corpus/backend/mutations_require_write_role/violation-01/expected-findings.json +7 -0
- package/corpus/backend/mutations_require_write_role/violation-01/modules/notes/module.py +7 -0
- package/corpus/backend/mutations_require_write_role/violation-01/modules/notes/router.py +3 -0
- package/corpus/backend/mutations_require_write_role/violation-02/expected-findings.json +7 -0
- package/corpus/backend/mutations_require_write_role/violation-02/modules/notes/module.py +7 -0
- package/corpus/backend/mutations_require_write_role/violation-02/modules/notes/router.py +5 -0
- package/corpus/backend/mutations_require_write_role/violation-03/expected-findings.json +7 -0
- package/corpus/backend/mutations_require_write_role/violation-03/modules/notes/module.py +10 -0
- package/corpus/backend/mutations_require_write_role/violation-03/modules/notes/router.py +3 -0
- package/corpus/backend/no_adhoc_background_runtime/compliant-01/modules/notes/service.py +9 -0
- package/corpus/backend/no_adhoc_background_runtime/violation-01/modules/notes/service.py +5 -0
- package/corpus/backend/no_adhoc_background_runtime/violation-02/modules/notes/service.py +3 -0
- package/corpus/backend/no_adhoc_config_decrypt/compliant-01/modules/billing/service.py +2 -0
- package/corpus/backend/no_adhoc_config_decrypt/violation-01/modules/billing/service.py +2 -0
- package/corpus/backend/no_adhoc_logging_config/compliant-01/modules/notes/service.py +3 -0
- package/corpus/backend/no_adhoc_logging_config/violation-01/modules/notes/service.py +3 -0
- package/corpus/backend/no_adhoc_middleware/compliant-01/main.py +3 -0
- package/corpus/backend/no_adhoc_middleware/violation-01/main.py +4 -0
- package/corpus/backend/no_adhoc_middleware/violation-02/main.py +5 -0
- package/corpus/backend/no_adhoc_permission_literals/compliant-01/modules/notes/module.py +3 -0
- package/corpus/backend/no_adhoc_permission_literals/compliant-02/modules/notes/router.py +5 -0
- package/corpus/backend/no_adhoc_permission_literals/violation-01/expected-findings.json +7 -0
- package/corpus/backend/no_adhoc_permission_literals/violation-01/modules/notes/module.py +1 -0
- package/corpus/backend/no_adhoc_permission_literals/violation-02/expected-findings.json +7 -0
- package/corpus/backend/no_adhoc_permission_literals/violation-02/modules/notes/router.py +2 -0
- package/corpus/backend/no_adhoc_permission_literals/violation-03/expected-findings.json +12 -0
- package/corpus/backend/no_adhoc_permission_literals/violation-03/modules/notes/module.py +10 -0
- package/corpus/backend/no_app_instantiation/compliant-01/main.py +3 -0
- package/corpus/backend/no_app_instantiation/violation-01/main.py +3 -0
- package/corpus/backend/no_blocking_sleep/compliant-01/modules/notes/service.py +2 -0
- package/corpus/backend/no_blocking_sleep/violation-01/modules/notes/service.py +5 -0
- package/corpus/backend/no_cross_module_imports/compliant-01/modules/a/service.py +1 -0
- package/corpus/backend/no_cross_module_imports/violation-01/modules/a/service.py +1 -0
- package/corpus/backend/no_cross_module_imports/violation-02/modules/a/service.py +1 -0
- package/corpus/backend/no_dependency_overrides/compliant-01/main.py +12 -0
- package/corpus/backend/no_dependency_overrides/violation-01/main.py +11 -0
- package/corpus/backend/no_destructive_migrations/compliant-01/modules/notes/migrations/versions/0001_change.py +3 -0
- package/corpus/backend/no_destructive_migrations/compliant-02/modules/notes/migrations/versions/0001_change.py +8 -0
- package/corpus/backend/no_destructive_migrations/violation-01/expected-findings.json +7 -0
- package/corpus/backend/no_destructive_migrations/violation-01/modules/notes/migrations/versions/0001_change.py +2 -0
- package/corpus/backend/no_destructive_migrations/violation-02/expected-findings.json +7 -0
- package/corpus/backend/no_destructive_migrations/violation-02/modules/notes/migrations/versions/0001_change.py +2 -0
- package/corpus/backend/no_destructive_migrations/violation-03/expected-findings.json +7 -0
- package/corpus/backend/no_destructive_migrations/violation-03/modules/notes/migrations/versions/0001_change.py +3 -0
- package/corpus/backend/no_destructive_migrations/violation-04/expected-findings.json +12 -0
- package/corpus/backend/no_destructive_migrations/violation-04/modules/notes/migrations/versions/0001_change.py +6 -0
- package/corpus/backend/no_destructive_migrations/violation-05/expected-findings.json +12 -0
- package/corpus/backend/no_destructive_migrations/violation-05/modules/notes/migrations/versions/0001_change.py +3 -0
- package/corpus/backend/no_dynamic_sql/compliant-01/modules/notes/service.py +1 -0
- package/corpus/backend/no_dynamic_sql/compliant-02/modules/notes/service.py +20 -0
- package/corpus/backend/no_dynamic_sql/violation-01/modules/notes/service.py +2 -0
- package/corpus/backend/no_dynamic_sql/violation-02/modules/notes/service.py +2 -0
- package/corpus/backend/no_dynamic_sql/violation-03/modules/notes/service.py +7 -0
- package/corpus/backend/no_dynamic_sql/violation-04/modules/notes/service.py +16 -0
- package/corpus/backend/no_empty_tests/compliant-01/tests/test_notes.py +2 -0
- package/corpus/backend/no_empty_tests/violation-01/tests/test_notes.py +2 -0
- package/corpus/backend/no_eval_or_exec/compliant-01/modules/notes/service.py +2 -0
- package/corpus/backend/no_eval_or_exec/violation-01/modules/notes/service.py +2 -0
- package/corpus/backend/no_hardcoded_credentials/compliant-01/modules/billing/service.py +3 -0
- package/corpus/backend/no_hardcoded_credentials/compliant-02/modules/billing/service.py +18 -0
- package/corpus/backend/no_hardcoded_credentials/violation-01/expected-findings.json +7 -0
- package/corpus/backend/no_hardcoded_credentials/violation-01/modules/billing/service.py +2 -0
- package/corpus/backend/no_hardcoded_credentials/violation-02/expected-findings.json +7 -0
- package/corpus/backend/no_hardcoded_credentials/violation-02/modules/billing/service.py +1 -0
- package/corpus/backend/no_hardcoded_credentials/violation-03/expected-findings.json +22 -0
- package/corpus/backend/no_hardcoded_credentials/violation-03/modules/billing/service.py +11 -0
- package/corpus/backend/no_hardcoded_credentials/violation-04/expected-findings.json +12 -0
- package/corpus/backend/no_hardcoded_credentials/violation-04/modules/billing/service.py +8 -0
- package/corpus/backend/no_hardcoded_credentials/violation-05/expected-findings.json +12 -0
- package/corpus/backend/no_hardcoded_credentials/violation-05/modules/billing/service.py +4 -0
- package/corpus/backend/no_internal_imports/compliant-01/modules/notes/service.py +1 -0
- package/corpus/backend/no_internal_imports/violation-01/modules/notes/service.py +1 -0
- package/corpus/backend/no_manual_actor_stamping/compliant-01/modules/notes/service.py +5 -0
- package/corpus/backend/no_manual_actor_stamping/compliant-02/modules/notes/schemas.py +12 -0
- package/corpus/backend/no_manual_actor_stamping/violation-01/modules/notes/service.py +3 -0
- package/corpus/backend/no_manual_actor_stamping/violation-02/expected-findings.json +7 -0
- package/corpus/backend/no_manual_actor_stamping/violation-02/modules/notes/service.py +4 -0
- package/corpus/backend/no_manual_ownership_checks/compliant-01/modules/journals/service.py +3 -0
- package/corpus/backend/no_manual_ownership_checks/compliant-02/modules/journals/schemas.py +11 -0
- package/corpus/backend/no_manual_ownership_checks/violation-01/modules/journals/service.py +3 -0
- package/corpus/backend/no_manual_ownership_checks/violation-02/expected-findings.json +7 -0
- package/corpus/backend/no_manual_ownership_checks/violation-02/modules/journals/service.py +9 -0
- package/corpus/backend/no_manual_ownership_checks/violation-03/expected-findings.json +7 -0
- package/corpus/backend/no_manual_ownership_checks/violation-03/modules/notes/jobs.py +1 -0
- package/corpus/backend/no_manual_ownership_checks/violation-03/modules/notes/models.py +7 -0
- package/corpus/backend/no_manual_ownership_checks/violation-03/modules/notes/module.py +11 -0
- package/corpus/backend/no_manual_ownership_checks/violation-03/modules/notes/service.py +7 -0
- package/corpus/backend/no_manual_scope_filtering/compliant-01/modules/notes/service.py +8 -0
- package/corpus/backend/no_manual_scope_filtering/compliant-02/modules/notes/schemas.py +12 -0
- package/corpus/backend/no_manual_scope_filtering/violation-01/modules/notes/service.py +2 -0
- package/corpus/backend/no_manual_scope_filtering/violation-02/modules/notes/service.py +2 -0
- package/corpus/backend/no_manual_scope_filtering/violation-03/expected-findings.json +7 -0
- package/corpus/backend/no_manual_scope_filtering/violation-03/modules/notes/service.py +7 -0
- package/corpus/backend/no_manual_table_schema/compliant-01/modules/notes/models.py +6 -0
- package/corpus/backend/no_manual_table_schema/violation-01/modules/notes/models.py +8 -0
- package/corpus/backend/no_manual_version_assignment/compliant-01/modules/notes/service.py +5 -0
- package/corpus/backend/no_manual_version_assignment/violation-01/modules/notes/service.py +3 -0
- package/corpus/backend/no_manual_version_assignment/violation-02/modules/notes/service.py +3 -0
- package/corpus/backend/no_manual_version_assignment/violation-03/modules/notes/service.py +3 -0
- package/corpus/backend/no_mutable_default_args/compliant-01/modules/notes/service.py +3 -0
- package/corpus/backend/no_mutable_default_args/violation-01/modules/notes/service.py +2 -0
- package/corpus/backend/no_naive_datetime/compliant-01/modules/notes/service.py +2 -0
- package/corpus/backend/no_naive_datetime/violation-01/modules/notes/service.py +2 -0
- package/corpus/backend/no_naive_datetime/violation-02/modules/notes/service.py +2 -0
- package/corpus/backend/no_oversized_python_files/compliant-01/modules/notes/service.py +9 -0
- package/corpus/backend/no_oversized_python_files/violation-01/modules/notes/service.py +530 -0
- package/corpus/backend/no_print/compliant-01/modules/notes/service.py +7 -0
- package/corpus/backend/no_print/violation-01/modules/notes/service.py +2 -0
- package/corpus/backend/no_raw_app_routes/compliant-01/main.py +12 -0
- package/corpus/backend/no_raw_app_routes/violation-01/main.py +10 -0
- package/corpus/backend/no_raw_app_routes/violation-02/main.py +16 -0
- package/corpus/backend/no_raw_app_routes/violation-03/main.py +12 -0
- package/corpus/backend/no_raw_app_routes/violation-04/main.py +10 -0
- package/corpus/backend/no_raw_app_routes/violation-05/main.py +12 -0
- package/corpus/backend/no_raw_app_routes/violation-06/main.py +10 -0
- package/corpus/backend/no_raw_connection_access/compliant-01/modules/notes/service.py +5 -0
- package/corpus/backend/no_raw_connection_access/violation-01/modules/notes/service.py +2 -0
- package/corpus/backend/no_raw_connection_access/violation-02/modules/notes/service.py +2 -0
- package/corpus/backend/no_raw_file_references/compliant-01/modules/notes/models.py +10 -0
- package/corpus/backend/no_raw_file_references/violation-01/modules/notes/models.py +9 -0
- package/corpus/backend/no_raw_outbound_http/compliant-01/modules/notes/service.py +2 -0
- package/corpus/backend/no_raw_outbound_http/compliant-03/modules/notes/service.py +12 -0
- package/corpus/backend/no_raw_outbound_http/violation-01/expected-findings.json +7 -0
- package/corpus/backend/no_raw_outbound_http/violation-01/modules/notes/service.py +1 -0
- package/corpus/backend/no_raw_outbound_http/violation-02/expected-findings.json +7 -0
- package/corpus/backend/no_raw_outbound_http/violation-02/modules/notes/service.py +1 -0
- package/corpus/backend/no_raw_outbound_http/violation-03/expected-findings.json +7 -0
- package/corpus/backend/no_raw_outbound_http/violation-03/modules/notes/service.py +1 -0
- package/corpus/backend/no_raw_outbound_http/violation-04/expected-findings.json +12 -0
- package/corpus/backend/no_raw_outbound_http/violation-04/modules/notes/service.py +7 -0
- package/corpus/backend/no_raw_outbound_http/violation-05/expected-findings.json +12 -0
- package/corpus/backend/no_raw_outbound_http/violation-05/modules/notes/service.py +5 -0
- package/corpus/backend/no_raw_outbound_http/violation-06/expected-findings.json +12 -0
- package/corpus/backend/no_raw_outbound_http/violation-06/modules/notes/service.py +2 -0
- package/corpus/backend/no_raw_session_construction/compliant-01/modules/notes/service.py +3 -0
- package/corpus/backend/no_raw_session_construction/violation-01/modules/notes/service.py +3 -0
- package/corpus/backend/no_star_imports/compliant-01/modules/notes/service.py +1 -0
- package/corpus/backend/no_star_imports/violation-01/modules/notes/service.py +1 -0
- package/corpus/backend/no_todo_fixme/compliant-01/modules/notes/service.py +3 -0
- package/corpus/backend/no_todo_fixme/violation-01/modules/notes/service.py +3 -0
- package/corpus/backend/no_unique_columns_on_soft_delete_models/compliant-01/modules/notes/models.py +17 -0
- package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-01/expected-findings.json +7 -0
- package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-01/modules/notes/models.py +6 -0
- package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-02/expected-findings.json +7 -0
- package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-02/modules/notes/models.py +13 -0
- package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-03/expected-findings.json +7 -0
- package/corpus/backend/no_unique_columns_on_soft_delete_models/violation-03/modules/notes/models.py +16 -0
- package/corpus/backend/offset_queries_declare_ordering/compliant-01/modules/notes/service.py +4 -0
- package/corpus/backend/offset_queries_declare_ordering/violation-01/modules/notes/service.py +2 -0
- package/corpus/backend/path_id_params_are_uuid/compliant-01/modules/notes/router.py +6 -0
- package/corpus/backend/path_id_params_are_uuid/violation-01/modules/notes/router.py +6 -0
- package/corpus/backend/policy_refs_resolve/compliant-01/modules/notes/module.py +7 -0
- package/corpus/backend/policy_refs_resolve/violation-01/modules/notes/module.py +8 -0
- package/corpus/backend/public_modules_are_read_only/compliant-01/modules/notes/module.py +7 -0
- package/corpus/backend/public_modules_are_read_only/compliant-01/modules/notes/router.py +3 -0
- package/corpus/backend/public_modules_are_read_only/violation-01/expected-findings.json +7 -0
- package/corpus/backend/public_modules_are_read_only/violation-01/modules/notes/module.py +7 -0
- package/corpus/backend/public_modules_are_read_only/violation-01/modules/notes/router.py +3 -0
- package/corpus/backend/public_modules_are_read_only/violation-02/expected-findings.json +7 -0
- package/corpus/backend/public_modules_are_read_only/violation-02/modules/notes/module.py +7 -0
- package/corpus/backend/public_modules_are_read_only/violation-02/modules/notes/router.py +5 -0
- package/corpus/backend/reads_use_base_query/compliant-01/modules/notes/models.py +6 -0
- package/corpus/backend/reads_use_base_query/compliant-01/modules/notes/service.py +8 -0
- package/corpus/backend/reads_use_base_query/compliant-02/modules/notes/models.py +9 -0
- package/corpus/backend/reads_use_base_query/compliant-02/modules/notes/service.py +9 -0
- package/corpus/backend/reads_use_base_query/violation-01/modules/notes/models.py +6 -0
- package/corpus/backend/reads_use_base_query/violation-01/modules/notes/service.py +9 -0
- package/corpus/backend/reads_use_base_query/violation-02/expected-findings.json +7 -0
- package/corpus/backend/reads_use_base_query/violation-02/modules/notes/models.py +6 -0
- package/corpus/backend/reads_use_base_query/violation-02/modules/notes/service.py +8 -0
- package/corpus/backend/response_model_not_table_model/compliant-01/modules/notes/models.py +6 -0
- package/corpus/backend/response_model_not_table_model/compliant-01/modules/notes/router.py +3 -0
- package/corpus/backend/response_model_not_table_model/compliant-01/modules/notes/schemas.py +5 -0
- package/corpus/backend/response_model_not_table_model/violation-01/modules/notes/models.py +6 -0
- package/corpus/backend/response_model_not_table_model/violation-01/modules/notes/router.py +3 -0
- package/corpus/backend/routes_declare_response_model/compliant-01/modules/notes/router.py +8 -0
- package/corpus/backend/routes_declare_response_model/violation-01/modules/notes/router.py +3 -0
- package/corpus/backend/safe_methods_are_read_only/compliant-01/modules/notes/router.py +6 -0
- package/corpus/backend/safe_methods_are_read_only/compliant-02/modules/notes/router.py +5 -0
- package/corpus/backend/safe_methods_are_read_only/violation-01/expected-findings.json +7 -0
- package/corpus/backend/safe_methods_are_read_only/violation-01/modules/notes/router.py +3 -0
- package/corpus/backend/safe_methods_are_read_only/violation-02/expected-findings.json +7 -0
- package/corpus/backend/safe_methods_are_read_only/violation-02/modules/notes/router.py +5 -0
- package/corpus/backend/safe_methods_are_read_only/violation-03/expected-findings.json +7 -0
- package/corpus/backend/safe_methods_are_read_only/violation-03/modules/notes/router.py +3 -0
- package/corpus/backend/schemas_exclude_sensitive_fields/compliant-01/modules/users/schemas.py +5 -0
- package/corpus/backend/schemas_exclude_sensitive_fields/compliant-02/modules/accounts/schemas.py +12 -0
- package/corpus/backend/schemas_exclude_sensitive_fields/violation-01/expected-findings.json +7 -0
- package/corpus/backend/schemas_exclude_sensitive_fields/violation-01/modules/users/schemas.py +3 -0
- package/corpus/backend/schemas_exclude_sensitive_fields/violation-02/expected-findings.json +7 -0
- package/corpus/backend/schemas_exclude_sensitive_fields/violation-02/modules/connectors/router.py +8 -0
- package/corpus/backend/schemas_exclude_sensitive_fields/violation-03/expected-findings.json +17 -0
- package/corpus/backend/schemas_exclude_sensitive_fields/violation-03/modules/integrations/schemas.py +7 -0
- package/corpus/backend/session_imported_from_sqlmodel/compliant-01/modules/notes/service.py +5 -0
- package/corpus/backend/session_imported_from_sqlmodel/violation-01/modules/notes/service.py +5 -0
- package/corpus/backend/table_models_use_base_table/compliant-01/modules/notes/models.py +6 -0
- package/corpus/backend/table_models_use_base_table/violation-01/modules/notes/models.py +5 -0
- package/corpus/backend/tables_have_migrations/compliant-01/modules/notes/migrations/versions/0a1b2c3d4e5f_create_notes_tables.py +22 -0
- package/corpus/backend/tables_have_migrations/compliant-01/modules/notes/models.py +6 -0
- package/corpus/backend/tables_have_migrations/compliant-02/capabilities/ledger/models.py +6 -0
- package/corpus/backend/tables_have_migrations/violation-01/expected-findings.json +7 -0
- package/corpus/backend/tables_have_migrations/violation-01/modules/notes/models.py +6 -0
- package/corpus/backend/tables_have_migrations/violation-02/expected-findings.json +7 -0
- package/corpus/backend/tables_have_migrations/violation-02/modules/notes/models.py +10 -0
- package/corpus/backend/tenant_scoped_models_use_scoped_service/compliant-01/modules/projects/models.py +6 -0
- package/corpus/backend/tenant_scoped_models_use_scoped_service/compliant-01/modules/projects/service.py +5 -0
- package/corpus/backend/tenant_scoped_models_use_scoped_service/compliant-02/modules/projects/models.py +8 -0
- package/corpus/backend/tenant_scoped_models_use_scoped_service/compliant-02/modules/projects/service.py +5 -0
- package/corpus/backend/tenant_scoped_models_use_scoped_service/violation-01/modules/projects/models.py +6 -0
- package/corpus/backend/tenant_scoped_models_use_scoped_service/violation-01/modules/projects/service.py +5 -0
- package/corpus/backend/ungoverned_escape_hatch/compliant-01/modules/notes/service.py +5 -0
- package/corpus/backend/ungoverned_escape_hatch/compliant-02/modules/notes/service.py +4 -0
- package/corpus/backend/ungoverned_escape_hatch/violation-01/modules/notes/service.py +2 -0
- package/corpus/backend/update_schemas_inherit_base_update_schema/compliant-01/modules/notes/schemas.py +2 -0
- package/corpus/backend/update_schemas_inherit_base_update_schema/violation-01/modules/notes/schemas.py +2 -0
- package/corpus/backend/update_schemas_inherit_base_update_schema/violation-02/modules/notes/schemas.py +5 -0
- package/corpus/frontend/escape-hatch/compliant-01/src/modules/widgets/Widget.tsx +4 -0
- package/corpus/frontend/escape-hatch/violation-01/src/modules/widgets/Widget.tsx +4 -0
- package/corpus/frontend/escape-hatch/violation-02/src/modules/widgets/Widget.tsx +7 -0
- package/corpus/frontend/escape-hatch/violation-03/src/modules/widgets/Widget.tsx +7 -0
- package/corpus/frontend/generated-client-only/compliant-01/src/modules/widgets/Widget.tsx +6 -0
- package/corpus/frontend/generated-client-only/compliant-02/src/modules/widgets/Widget.tsx +21 -0
- package/corpus/frontend/generated-client-only/compliant-03/src/modules/widgets/Widget.tsx +7 -0
- package/corpus/frontend/generated-client-only/compliant-04/src/modules/widgets/Widget.tsx +17 -0
- package/corpus/frontend/generated-client-only/violation-01/src/modules/widgets/Widget.tsx +3 -0
- package/corpus/frontend/generated-client-only/violation-02/src/modules/widgets/Widget.tsx +9 -0
- package/corpus/frontend/generated-client-only/violation-03/src/modules/widgets/Widget.tsx +14 -0
- package/corpus/frontend/generated-client-only/violation-04/src/modules/widgets/Widget.tsx +6 -0
- package/corpus/frontend/layout-contract/compliant-01/layout-contract.json +3 -0
- package/corpus/frontend/layout-contract/compliant-01/src/modules/widgets/Widget.tsx +8 -0
- package/corpus/frontend/layout-contract/violation-01/layout-contract.json +3 -0
- package/corpus/frontend/layout-contract/violation-01/src/modules/widgets/Widget.tsx +8 -0
- package/corpus/frontend/no-cross-module-imports/compliant-01/src/modules/widgets/Widget.tsx +4 -0
- package/corpus/frontend/no-cross-module-imports/violation-01/src/modules/widgets/Widget.tsx +3 -0
- package/corpus/frontend/no-deep-imports/compliant-01/src/modules/widgets/Widget.tsx +4 -0
- package/corpus/frontend/no-deep-imports/violation-01/src/modules/widgets/Widget.tsx +2 -0
- package/corpus/frontend/no-dom-html-injection/compliant-01/src/modules/widgets/Widget.tsx +4 -0
- package/corpus/frontend/no-dom-html-injection/compliant-02/src/modules/widgets/Widget.tsx +11 -0
- package/corpus/frontend/no-dom-html-injection/violation-01/src/modules/widgets/Widget.tsx +3 -0
- package/corpus/frontend/no-dom-html-injection/violation-02/src/modules/widgets/Widget.tsx +7 -0
- package/corpus/frontend/no-dom-html-injection/violation-03/src/modules/widgets/Widget.tsx +12 -0
- package/corpus/frontend/no-eval/compliant-01/src/modules/widgets/Widget.tsx +4 -0
- package/corpus/frontend/no-eval/compliant-02/src/modules/widgets/Widget.tsx +11 -0
- package/corpus/frontend/no-eval/violation-01/src/modules/widgets/Widget.tsx +3 -0
- package/corpus/frontend/no-eval/violation-02/src/modules/widgets/Widget.tsx +7 -0
- package/corpus/frontend/no-eval/violation-03/src/modules/widgets/Widget.tsx +10 -0
- package/corpus/frontend/no-eval/violation-04/src/modules/widgets/Widget.tsx +6 -0
- package/corpus/frontend/no-inline-styling/compliant-01/src/modules/widgets/Widget.tsx +4 -0
- package/corpus/frontend/no-inline-styling/violation-01/src/modules/widgets/Widget.tsx +3 -0
- package/corpus/frontend/no-inline-styling/violation-02/src/modules/widgets/Widget.tsx +6 -0
- package/corpus/frontend/no-style-imports/compliant-01/src/modules/widgets/Widget.tsx +4 -0
- package/corpus/frontend/no-style-imports/violation-01/src/modules/widgets/Widget.tsx +2 -0
- package/corpus/frontend/no-unsafe-href/compliant-01/src/modules/widgets/Widget.tsx +3 -0
- package/corpus/frontend/no-unsafe-href/violation-01/src/modules/widgets/Widget.tsx +3 -0
- package/corpus/frontend/no-unsafe-target-blank/compliant-01/src/modules/widgets/Widget.tsx +3 -0
- package/corpus/frontend/no-unsafe-target-blank/violation-01/src/modules/widgets/Widget.tsx +3 -0
- package/corpus/frontend/router-links/compliant-01/src/modules/widgets/Widget.tsx +4 -0
- package/corpus/frontend/router-links/violation-01/src/modules/widgets/Widget.tsx +3 -0
- package/corpus/frontend/token-styled-elements/compliant-01/src/modules/widgets/Widget.tsx +4 -0
- package/corpus/frontend/token-styled-elements/compliant-02/src/modules/widgets/Widget.tsx +4 -0
- package/corpus/frontend/token-styled-elements/violation-01/src/modules/widgets/Widget.tsx +3 -0
- package/findings.schema.json +40 -0
- package/package.json +23 -0
- package/restricted-surface.json +10 -0
- package/scorecard.schema.json +52 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://terp.dev/spec/app-check-report.schema.json",
|
|
4
|
+
"title": "Terp Standard application check report",
|
|
5
|
+
"description": "The complete, self-describing result of ONE checker invocation over ONE application tree. Findings alone cannot support per-rule verdicts: a rule with zero findings is only passing if the run actually evaluated it, and a consumer must never supply that knowledge itself — its own catalog copy can be newer or older than the checked app's pinned toolchain. So the report carries its own evaluated-rule inventory, the spec version its rule ids resolve against, and the checker's identity: everything a driving tool needs to join verdicts to the catalog fail-closed. A rule absent from `rules` and `not_applicable` was not evaluated and can never render as passing. A multi-surface toolchain emits one report per checker run; a consumer merges reports through their inventories.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["terp_check_report", "spec_version", "checker", "ok", "rules", "findings"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"terp_check_report": {
|
|
11
|
+
"type": "integer",
|
|
12
|
+
"enum": [1],
|
|
13
|
+
"description": "Format marker and major version of this envelope. A consumer refuses an unknown marker value rather than guessing the shape."
|
|
14
|
+
},
|
|
15
|
+
"spec_version": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "The spec semver (VERSION) whose catalog the rule ids in this report resolve against — the checked app's own pinned standard, which may differ from any consumer-side catalog copy.",
|
|
18
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
|
19
|
+
},
|
|
20
|
+
"checker": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"description": "The checker's identity: tool name and its own version — the same identity the checker's certification scorecard (scorecard.schema.json) carries, so a consumer can tie a report to a verifiable certification claim.",
|
|
23
|
+
"required": ["tool", "version"],
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"properties": {
|
|
26
|
+
"tool": { "type": "string", "minLength": 1 },
|
|
27
|
+
"version": { "type": "string", "minLength": 1 }
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"ok": {
|
|
31
|
+
"type": "boolean",
|
|
32
|
+
"description": "The run's overall verdict: true only when the checker completed and attributed no findings. Overall only — per-rule verdicts are always derived from `rules` + `findings`, never from this flag."
|
|
33
|
+
},
|
|
34
|
+
"error": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"minLength": 1,
|
|
37
|
+
"description": "Present when the run failed to complete (a crash, a timeout, an unreadable tree): what went wrong, for humans. An erroring report is never ok, and it must not claim rules it did not fully evaluate — fail visible, never a silent pass."
|
|
38
|
+
},
|
|
39
|
+
"rules": {
|
|
40
|
+
"type": "array",
|
|
41
|
+
"description": "The evaluated-rule inventory: every catalog rule id this run actually held the tree to. Zero findings for a listed rule means pass; an unlisted rule has NO verdict from this run (a consumer renders it unknown, never green). A finding's rule is normally listed here; a consumer treats an attributed finding as a failure regardless.",
|
|
42
|
+
"items": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"pattern": "^(backend/[a-z0-9_]+|frontend/[a-z0-9-]+)$"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"not_applicable": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"description": "Opt-in rules the checked tree has not enabled (for example a slot-typed layout contract without its checked-in configuration): published as their own state so a consumer renders them as not applicable — never as passing, never as unknown.",
|
|
50
|
+
"items": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"pattern": "^(backend/[a-z0-9_]+|frontend/[a-z0-9-]+)$"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"findings": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"description": "The findings this run attributed to catalog rule ids. Each item is exactly the finding format's shape (findings.schema.json items — the spec suite holds the two identical), so a findings consumer and a report consumer read one shape.",
|
|
58
|
+
"items": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"required": ["rule", "path"],
|
|
61
|
+
"additionalProperties": false,
|
|
62
|
+
"properties": {
|
|
63
|
+
"rule": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"description": "The Terp Standard catalog id the finding realises (catalog/<surface>/<rule>.json).",
|
|
66
|
+
"pattern": "^(backend/[a-z0-9_]+|frontend/[a-z0-9-]+)$"
|
|
67
|
+
},
|
|
68
|
+
"path": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"description": "File path relative to the checked tree's root (the corpus case root, or the app root), with forward slashes."
|
|
71
|
+
},
|
|
72
|
+
"line": {
|
|
73
|
+
"type": "integer",
|
|
74
|
+
"description": "1-based line of the violation, when the checker can locate it.",
|
|
75
|
+
"minimum": 1
|
|
76
|
+
},
|
|
77
|
+
"message": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "Human-readable explanation; ideally directive (states the compliant fix)."
|
|
80
|
+
},
|
|
81
|
+
"fix_hint": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"description": "Agent-oriented remediation metadata: the compliant construct that replaces the violating one, sourced from the catalog entry's non-normative `reference` field (or the checker's own knowledge of the sanctioned pattern). Optional — a consumer must not require it."
|
|
84
|
+
},
|
|
85
|
+
"fingerprint": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"description": "A stable, checker-chosen identifier for THIS violation instance (e.g. a hash over rule + path + violating source construct), so consumers can track a finding across line-shifting edits. Optional; the fingerprint scheme is checker-internal — only stability across reformatting is expected."
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"unattributed": {
|
|
93
|
+
"type": "array",
|
|
94
|
+
"description": "Messages the run produced OUTSIDE the standard's rules (another configured tool's diagnostics sharing the run, a parse error): surfaced, never dropped — and never attributable to a catalog id, which the finding format's rule pattern refuses by construction.",
|
|
95
|
+
"items": {
|
|
96
|
+
"type": "object",
|
|
97
|
+
"required": ["path", "message"],
|
|
98
|
+
"additionalProperties": false,
|
|
99
|
+
"properties": {
|
|
100
|
+
"path": {
|
|
101
|
+
"type": "string",
|
|
102
|
+
"description": "File path relative to the checked tree's root, with forward slashes."
|
|
103
|
+
},
|
|
104
|
+
"line": {
|
|
105
|
+
"type": "integer",
|
|
106
|
+
"description": "1-based line of the message, when the producer located one.",
|
|
107
|
+
"minimum": 1
|
|
108
|
+
},
|
|
109
|
+
"message": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"description": "The diagnostic text, verbatim."
|
|
112
|
+
},
|
|
113
|
+
"reported_as": {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"minLength": 1,
|
|
116
|
+
"description": "The tool-internal rule id the message surfaced as, when one exists — omitted (never null) otherwise."
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://terp.dev/spec/assurance-profile.schema.json",
|
|
4
|
+
"title": "Terp assurance profile",
|
|
5
|
+
"description": "The machine-readable release-assurance claim a Terp toolchain emits after running its release verification profile: one document composing the named evidence lanes of the standard. The lane vocabulary and each lane's requirement level are normative and fixed by the spec (see the README's assurance-profile section): terp-standard, appsec-baseline and dependency-audit are REQUIRED - the claim ('ok') is true only when every required lane passed; a11y and blackbox-conformance are RECOMMENDED - they inform the reader but never carry the claim. Requirement levels are deliberately NOT a field of the document: an emitter cannot demote a required lane, and a consumer joins the document to the spec (via spec_version) for the canonical mapping. Every lane of the vocabulary must appear exactly once - omitting a lane would hide it. A lane's verdict is composed from the named verification checks ('checks', the check ids of the toolchain's release profile); a lane the toolchain does not realise yet is reported 'not-run' with no checks, never silently dropped and never counted as passed.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["terp_assurance", "spec_version", "toolchain", "profile", "ok", "lanes"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"terp_assurance": {
|
|
11
|
+
"description": "The assurance-profile document format version.",
|
|
12
|
+
"enum": [1]
|
|
13
|
+
},
|
|
14
|
+
"spec_version": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "The spec semver (VERSION) whose lane vocabulary and requirement mapping this document claims against.",
|
|
17
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
|
18
|
+
},
|
|
19
|
+
"toolchain": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"description": "The emitting toolchain's identity: tool name and its own version.",
|
|
22
|
+
"required": ["tool", "version"],
|
|
23
|
+
"additionalProperties": false,
|
|
24
|
+
"properties": {
|
|
25
|
+
"tool": { "type": "string", "minLength": 1 },
|
|
26
|
+
"version": { "type": "string", "minLength": 1 }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"profile": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "The verification profile the toolchain realised the lanes from (the reference toolchain: 'release').",
|
|
32
|
+
"minLength": 1
|
|
33
|
+
},
|
|
34
|
+
"ok": {
|
|
35
|
+
"type": "boolean",
|
|
36
|
+
"description": "The claim: true exactly when every REQUIRED lane has status 'passed'. Recommended lanes never affect it."
|
|
37
|
+
},
|
|
38
|
+
"lanes": {
|
|
39
|
+
"type": "array",
|
|
40
|
+
"description": "One entry per lane of the spec's vocabulary, each exactly once.",
|
|
41
|
+
"minItems": 1,
|
|
42
|
+
"items": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"required": ["id", "status", "checks"],
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"properties": {
|
|
47
|
+
"id": {
|
|
48
|
+
"description": "The lane, from the spec's fixed vocabulary.",
|
|
49
|
+
"enum": [
|
|
50
|
+
"terp-standard",
|
|
51
|
+
"appsec-baseline",
|
|
52
|
+
"dependency-audit",
|
|
53
|
+
"a11y",
|
|
54
|
+
"blackbox-conformance"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"status": {
|
|
58
|
+
"description": "'passed' (every composing check green), 'failed' (any composing check red), or 'not-run' (the toolchain does not realise the lane, or its precondition was unmet). 'not-run' is never a pass.",
|
|
59
|
+
"enum": ["passed", "failed", "not-run"]
|
|
60
|
+
},
|
|
61
|
+
"checks": {
|
|
62
|
+
"type": "array",
|
|
63
|
+
"description": "The verification-check ids whose verdicts compose this lane (evidence pointers into the toolchain's verification envelope). Non-empty for 'passed'/'failed'; empty for 'not-run' - a verdict without evidence is not a verdict.",
|
|
64
|
+
"items": { "type": "string", "minLength": 1 }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/alembic_downgrades_not_empty",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "A migration's downgrade must reverse the change, not be an empty stub",
|
|
5
|
+
"intent": "A migration that leaves its downgrade step empty (a bare no-op with no operations) cannot be rolled back: reversing the revision silently leaves the schema mismatched instead of restoring the previous state. Each migration's downgrade must either perform the reverse operations or, for a deliberately irreversible step, carry a comment explaining why the no-op is intentional.",
|
|
6
|
+
"layer": "static-portable",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_alembic_downgrades_not_empty"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"reference": "In each revision file under migrations/versions, a downgrade function whose body (after any docstring) is a lone pass / ellipsis or is empty, with no explanatory comment, is flagged; a reverse operation or an in-body '#' comment clears it.",
|
|
15
|
+
"opt_out": "# arch-allow-alembic-downgrades-not-empty: <reason>",
|
|
16
|
+
"runtime": {
|
|
17
|
+
"applicability": "not-applicable",
|
|
18
|
+
"rationale": "An empty downgrade is a property of the authored revision file; a forward deploy never executes downgrade, so the emptiness is invisible at runtime until a rollback is attempted, and the reversibility guarantee exists only in source."
|
|
19
|
+
},
|
|
20
|
+
"guide_topic": "migrations",
|
|
21
|
+
"corpus": true
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/base_query_not_overridden",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "A service never overrides the scoped base read query — read filters compose through the declared filter seam",
|
|
5
|
+
"intent": "The base read query composes the non-droppable row scope (soft-delete plus every registered capability predicate, e.g. tenancy) with the service's declared business filters. Overriding that composition point — the old, footgun-y seam — can silently drop soft-delete or tenant scoping the moment the override forgets to delegate up the chain, leaking soft-deleted or cross-tenant rows. Add static read conditions through the declared filter seam (it returns conditions, not a query, so scope cannot be dropped); a per-call filter belongs in a bespoke read built on the scoped base query.",
|
|
6
|
+
"layer": "static-bespoke",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_base_query_not_overridden"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"kind": "runtime",
|
|
15
|
+
"tool": "terp.core",
|
|
16
|
+
"ref": "apply_row_scope"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"reference": "BaseService.base_query composes the scope; add filters via business_filters(), or build bespoke reads on base_query().where(...) (ADR 0017).",
|
|
20
|
+
"opt_out": "# arch-allow-base-query-not-overridden: <reason>",
|
|
21
|
+
"runtime": {
|
|
22
|
+
"applicability": "required",
|
|
23
|
+
"rationale": "The request session re-scopes every user-facing ORM read idempotently (apply_row_scope in the write-guarded session), so even a query built by an override that dropped super().base_query() is filtered again before execution."
|
|
24
|
+
},
|
|
25
|
+
"guide_topic": "service",
|
|
26
|
+
"corpus": true
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/canonical_module_shape",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "Every wired module directory carries the canonical parts: models, schemas, service, router, manifest",
|
|
5
|
+
"intent": "Terp modules are uniform on purpose: the table lives in models, the DTOs in schemas, the logic in service, the routes in router, and the manifest declares the module — so the shape is predictable to discover and the other rules (response models, input caps, audited writes, the declared policy) have the surface they scan. A directory is treated as a module once it ships a manifest or a mounted router; it must then carry all of the canonical parts, and the rule names each missing one. Requiring the manifest is deliberate: a directory that ships a router with no manifest would otherwise be invisible to this rule and to modules_declare_policy (which only scans manifests), so it could mount a router with no declared policy unnoticed. A directory with neither signal (a partial or a shared-asset / helper directory) is left alone.",
|
|
6
|
+
"layer": "static-bespoke",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_canonical_module_shape"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"reference": "modules/<name>/ carries models.py, schemas.py, service.py, router.py and module.py (the ModuleSpec manifest).",
|
|
15
|
+
"opt_out": "# arch-allow-canonical-module-shape: <reason>",
|
|
16
|
+
"runtime": {
|
|
17
|
+
"applicability": "not-applicable",
|
|
18
|
+
"rationale": "Repository-layout governance: the module directory's file shape exists only in the source tree. Composition consumes the ModuleSpec object, which carries no memory of which files declared it, so no runtime seam can observe the layout."
|
|
19
|
+
},
|
|
20
|
+
"guide_topic": "module",
|
|
21
|
+
"corpus": true
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/datetime_columns_are_timezone_aware",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "Stored timestamp columns must keep their timezone",
|
|
5
|
+
"intent": "A timestamp column declared without an explicit timezone maps to a naive database type, which discards the zone of even a correctly-built aware value on the way in — so the moment the row records is ambiguous, and ordering or comparison across zones is silently wrong on read. This is the storage half of the hole whose in-memory half the naive-timestamp rule closes: capturing an aware value is worth nothing if the column cannot keep it. Every persisted timestamp column must declare timezone-aware storage.",
|
|
6
|
+
"layer": "static-bespoke",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_datetime_columns_are_timezone_aware"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"reference": "On a table model, a datetime (or datetime | None) field is refused unless its declaration pins a timezone-aware column type — Field(sa_type=DateTime(timezone=True)) or Field(sa_column=Column(DateTime(timezone=True), ...)); the TIMESTAMP spelling and a qualified name (sa.DateTime) count equally. A bare annotation, a Field() that never names the column type, and an explicit timezone=False are all naive storage. Fields a table inherits from a mixin declared in the same file are in scope, since the column lands on the table either way. Classes no table inherits (DTOs, unrelated payloads) declare no columns and are out of scope.",
|
|
15
|
+
"opt_out": "# arch-allow-datetime-columns-are-timezone-aware: <reason>",
|
|
16
|
+
"runtime": {
|
|
17
|
+
"applicability": "deferred",
|
|
18
|
+
"rationale": "Unlike the in-memory naive-timestamp rule — where the zone is already lost at the point the value is built, so no seam downstream can recover it — this invariant survives into the running system: the mapped column type is inspectable on the ORM metadata the framework already collects at boot, and a naive timestamp column is decidable there without ambiguity. A fail-closed boot check over the collected table metadata would therefore add independent fidelity, catching a column whose naive type arrives from a path the source scan cannot follow: a dynamically built column, a mixin imported from another package, or a table contributed by an installed capability. The framework owns that seam (its create_app boot validations) but has not yet shipped this control.",
|
|
19
|
+
"tracking": "terp-framework reference implementation: the create_app boot-validation seam in terp.core.app (alongside the existing _validate_* boot checks) — to walk the collected table metadata and refuse a timestamp column whose mapped type is not timezone-aware."
|
|
20
|
+
},
|
|
21
|
+
"guide_topic": "module",
|
|
22
|
+
"corpus": true
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/escape_hatch_budget",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "Opt-out marker counts must exactly match the checked-in budget (a ratchet)",
|
|
5
|
+
"intent": "The budget is a checked-in per-app object mapping each opt-out marker to its count. Actual usage must equal it exactly: a marker that rose needs a justified budget bump in the same change; one that dropped must be lowered to lock in the win; an unbudgeted marker must be added with a justified count. This keeps every secure-by-default opt-out visible, greppable, and governed.",
|
|
6
|
+
"layer": "static-bespoke",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_escape_hatch_budget"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"reference": "# arch-allow-<rule>: <reason> markers reconciled against the app's checked-in escape-hatch budget JSON (design §8). The rule carries no opt_out: governance cannot be waived by the mechanism it governs.",
|
|
15
|
+
"runtime": {
|
|
16
|
+
"applicability": "not-applicable",
|
|
17
|
+
"rationale": "The budget ratchet reconciles a checked-in governance file against source markers -- both sides are repository artifacts with no runtime counterpart. The controls the markers opt out of keep their own runtime halves regardless."
|
|
18
|
+
},
|
|
19
|
+
"guide_topic": "rules",
|
|
20
|
+
"corpus": true
|
|
21
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/events_reference_catalog",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "Emitted / subscribed events are typed catalog constants, never bare strings",
|
|
5
|
+
"intent": "The event bus carries the same no-drift guarantee as the permission model: every event a module emits or subscribes to is a typed definition from the control-plane catalog. The rule forbids a bare string (or an inline, ad hoc definition) wherever an event is named — the event argument of an emit call, the argument of a subscribe decorator, the emits / subscribes lists of the module manifest, and the lifecycle event map — so an event name can never drift in outside the catalog.",
|
|
6
|
+
"layer": "static-bespoke",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_events_reference_catalog"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"kind": "runtime",
|
|
15
|
+
"tool": "terp.core",
|
|
16
|
+
"ref": "emit"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"reference": "EventDefinition constants from the control-plane catalog, cited in emit(...) / subscribe(...) / ModuleSpec(emits=..., subscribes=...) / LifecycleEventMap(...); the runtime half is terp.core.emit.",
|
|
20
|
+
"opt_out": "# arch-allow-events-reference-catalog: <reason>",
|
|
21
|
+
"runtime": {
|
|
22
|
+
"applicability": "required",
|
|
23
|
+
"rationale": "emit() is the single producer chokepoint and fails closed on an event that is unknown to -- or shadows -- the registered EventCatalog entry; boot additionally refuses a ModuleSpec whose declared emits/subscribes miss the catalog (ControlPlane.validation_errors)."
|
|
24
|
+
},
|
|
25
|
+
"guide_topic": "events",
|
|
26
|
+
"corpus": true
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/input_schemas_exclude_managed_columns",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "No input schema declares a framework-managed column",
|
|
5
|
+
"intent": "An input schema is a *Create / *Update or any class used as a request body (a route handler's body parameter, or a generated CRUD router's create/update schema) — the same role-based definition the input-cap rule uses, so an off-convention DTO (UserProvision, LoginRequest) is covered too. The write chokepoint copies a schema's fields onto the model, so a client-settable id / version / tenant_id / created_by_id is an over-posting (mass-assignment) hole — a client could forge the primary key, defeat optimistic concurrency, or cross a tenant boundary. The framework assigns every managed column centrally; an input schema must never expose one. (The write chokepoint also strips the same set at runtime — this rule is the build-time half of that two-layer control.)",
|
|
6
|
+
"layer": "static-bespoke",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_input_schemas_exclude_managed_columns"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"kind": "runtime",
|
|
15
|
+
"tool": "terp.core",
|
|
16
|
+
"ref": "_without_managed_columns"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"reference": "BaseService.create/update strip the managed set via _without_managed_columns; build_crud_router create/update schemas are scanned too.",
|
|
20
|
+
"opt_out": "# arch-allow-input-schemas-exclude-managed-columns: <reason>",
|
|
21
|
+
"runtime": {
|
|
22
|
+
"applicability": "required",
|
|
23
|
+
"rationale": "BaseService.create/update strip every framework-managed column from the inbound payload at the write chokepoint (_without_managed_columns), so even an over-wide schema that ships cannot mass-assign id / version / scope / actor columns."
|
|
24
|
+
},
|
|
25
|
+
"guide_topic": "module",
|
|
26
|
+
"corpus": true
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/input_str_fields_have_max_length",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "Every string a client can supply caps its length",
|
|
5
|
+
"intent": "A field is client-supplied when it lives on a table model, on a *Create / *Update schema, or on any class used as a request body (a route handler's body parameter, or a generated CRUD router's create/update schema) — so an input DTO named off-convention (LoginRequest, UserProvision) is capped too, not only the *Create / *Update ones. Plain strings, optional strings, and sequence containers of strings all count; an uncapped one is an unbounded-input (DoS / abuse) hole.",
|
|
6
|
+
"layer": "static-bespoke",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_input_str_fields_have_max_length"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"reference": "str / str | None / list[str] fields declare max_length; build_crud_router create/update schemas are scanned too.",
|
|
15
|
+
"opt_out": "# arch-allow-input-str-fields-have-max-length: <reason>",
|
|
16
|
+
"runtime": {
|
|
17
|
+
"applicability": "not-applicable",
|
|
18
|
+
"rationale": "The rule's job is to force the cap into the authored schema -- and a declared cap is then enforced at request time by validation itself, so compliance creates its own runtime control. No shipped seam can supply a missing per-field cap; the per-module request-size middleware bounds the payload wholesale as a distinct Tier-A control."
|
|
19
|
+
},
|
|
20
|
+
"guide_topic": "module",
|
|
21
|
+
"corpus": true
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/jobs_reference_catalog",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "Enqueued / declared jobs are typed catalog constants, never bare strings",
|
|
5
|
+
"intent": "Background work carries the same no-drift guarantee as the event bus: every job a module enqueues or declares is a typed definition from the control-plane catalog. The rule forbids a bare string (or an inline, ad hoc definition) wherever a job is named — the job argument of an enqueue call and the jobs list of the module manifest — so a job name can never drift in outside the catalog. The runtime half is the enqueue chokepoint, which rejects a job not registered in the active catalog.",
|
|
6
|
+
"layer": "static-bespoke",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_jobs_reference_catalog"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"kind": "runtime",
|
|
15
|
+
"tool": "terp.core",
|
|
16
|
+
"ref": "enqueue"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"reference": "JobDefinition constants from the control-plane catalog, cited in enqueue(job=...) / ModuleSpec(jobs=[...]); the runtime half is terp.core.enqueue.",
|
|
20
|
+
"opt_out": "# arch-allow-jobs-reference-catalog: <reason>",
|
|
21
|
+
"runtime": {
|
|
22
|
+
"applicability": "required",
|
|
23
|
+
"rationale": "enqueue is the single producer chokepoint and fails closed on a job the active JobCatalog does not register (the job analogue of emit); execution re-checks (run_job raises JobError on a stale envelope), and boot refuses a ModuleSpec whose declared jobs miss the catalog (ControlPlane.validation_errors)."
|
|
24
|
+
},
|
|
25
|
+
"guide_topic": "jobs",
|
|
26
|
+
"corpus": true
|
|
27
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/list_routes_paginate",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "A list route returns a capped page envelope, never a bare unbounded collection",
|
|
5
|
+
"intent": "Pagination is a mandatory cross-cutting control: a route whose declared response type is a bare collection serializes an unbounded result — a resource-exhaustion and over-exposure footgun on a large table, and a page-envelope guarantee that was previously only a convention. Wrap the read DTO in the framework's capped page envelope so every list is bounded and uniformly shaped. A single-object response is unaffected; both decorator routes and imperative route registration are checked.",
|
|
6
|
+
"layer": "black-box",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_list_routes_paginate"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"kind": "black-box",
|
|
15
|
+
"tool": "@terp/conformance",
|
|
16
|
+
"ref": "standard: list routes return a capped Page envelope"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"kind": "runtime",
|
|
20
|
+
"tool": "terp.core",
|
|
21
|
+
"ref": "_validate_list_routes_paginate"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"reference": "response_model=Page[ReadDTO] returned via Page.of(...) with PaginationDep (ADR 0006, Tier A); bare list[...] / Sequence[...] response models are refused.",
|
|
25
|
+
"opt_out": "# arch-allow-list-routes-paginate: <reason>",
|
|
26
|
+
"runtime": {
|
|
27
|
+
"applicability": "required",
|
|
28
|
+
"rationale": "At boot, the route scan on the composition seam (the same seam as _validate_router_response_models) walks every composed route \u2014 decorator, imperative add_api_route, and nested included routers alike \u2014 and raises a BootError naming this rule and the route when a route declares a bare list[...] / Sequence[...] response_model instead of Page[T]. PaginationDep's fail-closed skip/limit caps and the @terp/conformance Page-envelope probe remain in place on top of this in-app refusal."
|
|
29
|
+
},
|
|
30
|
+
"guide_topic": "service",
|
|
31
|
+
"corpus": true
|
|
32
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/modules_declare_policy",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "Every module manifest declares an access policy",
|
|
5
|
+
"intent": "The deny-by-default posture hangs on the declaration: composition mounts a module's routes behind the guard its declared policy configures, so a module without one has no stated authority model at all — there is nothing to authorize against, and the framework refuses to guess. Requiring the declaration in the manifest keeps every module's access decision explicit, reviewable, and in one predictable place.",
|
|
6
|
+
"layer": "static-bespoke",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_modules_declare_policy"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"kind": "runtime",
|
|
15
|
+
"tool": "terp.core",
|
|
16
|
+
"ref": "build_guard"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"runtime": {
|
|
20
|
+
"applicability": "required"
|
|
21
|
+
},
|
|
22
|
+
"reference": "modules/<name>/module.py declares ModuleSpec(policy=Policy(...)); create_app mounts the router behind the guard built from it (build_guard).",
|
|
23
|
+
"opt_out": "# arch-allow-modules-declare-policy: <reason>",
|
|
24
|
+
"guide_topic": "policy",
|
|
25
|
+
"corpus": true
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/mutations_emit_audit",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "Modules never write to the session directly; mutations go through the audited chokepoint",
|
|
5
|
+
"intent": "Audit is auto-emitted from the single audited write chokepoint of the model's service, inside the write's transaction. A module that calls the session's persistence methods itself — or smuggles a write through a session-executed data-modification statement — bypasses that chokepoint and would persist a mutation with no audit trail. The receiver is recognised by the conventional session names and by any parameter annotated with the session type (so renaming the variable does not evade the rule). Routing every write through the service keeps the trail structural — a method call on the model's service is fine; a raw session write is not.",
|
|
6
|
+
"layer": "static-bespoke",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_mutations_emit_audit"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"kind": "runtime",
|
|
15
|
+
"tool": "terp.core",
|
|
16
|
+
"ref": "WriteGuardedSession"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"reference": "BaseService.create/update/delete (_save/_remove) is the audited chokepoint; session.add/delete/merge/commit/flush, bulk_* helpers, and DML via session.execute/exec are refused, on conventional names and Session/SessionDep-annotated parameters alike.",
|
|
20
|
+
"opt_out": "# arch-allow-mutations-emit-audit: <reason>",
|
|
21
|
+
"runtime": {
|
|
22
|
+
"applicability": "required"
|
|
23
|
+
},
|
|
24
|
+
"guide_topic": "service",
|
|
25
|
+
"corpus": true
|
|
26
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/mutations_require_write_role",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "A module with a mutating route must not gate writes below its read tier",
|
|
5
|
+
"intent": "A module that exposes a mutating route is a write surface, so its declared policy must gate writes at or above the read tier — otherwise anyone who can read can also mutate (privilege inversion). Two shapes are caught statically: the write tier set to the read floor, and a default-ladder inversion where the write rank is below the read rank (including a raised read tier whose write tier is left at the lower default). The framework's default policy is the safe shape. A custom role ladder's ranks are not knowable from a source scan, so those are enforced by the boot-time check — this rule is the early-warning build-time half. A public module is governed by public_modules_are_read_only instead. The check is tied to the policy bound in the module's manifest.",
|
|
6
|
+
"layer": "static-bespoke",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_mutations_require_write_role"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"kind": "runtime",
|
|
15
|
+
"tool": "terp.core",
|
|
16
|
+
"ref": "build_guard"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"kind": "runtime",
|
|
20
|
+
"tool": "terp.core",
|
|
21
|
+
"ref": "_validate_policy_write_tiers"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"runtime": {
|
|
25
|
+
"applicability": "required",
|
|
26
|
+
"rationale": "Two runtime halves: the request guard authorizes every mutating method at the policy's write tier (build_guard), and boot refuses a Policy whose write rank sits below its read rank for any role ladder, including custom ranks a source scan cannot know (_validate_policy_write_tiers)."
|
|
27
|
+
},
|
|
28
|
+
"opt_out": "# arch-allow-mutations-require-write-role: <reason>",
|
|
29
|
+
"reference": "Policy(write=Roles.VIEWER), Policy.tiers(write=...) at the read floor, and default-ladder inversions like Policy(read=Roles.ADMIN, write=Roles.EDITOR) are refused; Policy.default() is the safe shape, and create_app -> _validate_policy_write_tiers covers custom ladders at boot.",
|
|
30
|
+
"guide_topic": "policy",
|
|
31
|
+
"corpus": true
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/no_adhoc_background_runtime",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "App modules don't import a background engine / runtime directly \u2014 only adapter caps do",
|
|
5
|
+
"intent": "Background work (a scheduled sync, an export, a webhook) goes through the typed enqueue chokepoint and the context-binding kernel runner, so the engine that actually runs it stays a composition-root choice wired into an opt-in adapter capability, never an import baked into domain code. The rule forbids importing broker / scheduler engines (and a raw thread- or process-execution construct, or a bare import that can reach one) anywhere in an app module; an explicit synchronization primitive is a correctness tool, not background execution, and stays allowed. The constructive counterpart is the jobs seam itself: every job runs through the enqueue chokepoint and the active queue, so an adapter swap never touches a call site. An adapter capability legitimately imports its engine under a budgeted opt-out marker.",
|
|
6
|
+
"layer": "static-portable",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_no_adhoc_background_runtime"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"reference": "terp.core.enqueue + the active JobQueue are the sanctioned seam; Celery / Azure Service Bus / Redis / APScheduler imports and threading/multiprocessing execution constructs (Thread, Process, pools) are refused, while primitives like RLock stay allowed.",
|
|
15
|
+
"opt_out": "# arch-allow-no-adhoc-background-runtime: <reason>",
|
|
16
|
+
"runtime": {
|
|
17
|
+
"applicability": "not-applicable",
|
|
18
|
+
"rationale": "Import-form rule: which package imported an engine is a source fact, and at runtime the sanctioned adapter capability legitimately holds the same engine in the same process, so engine presence cannot be attributed to an app module. The constructive counterpart is the jobs seam itself (enqueue + the active JobQueue) -- it makes ad hoc runtimes unnecessary, but nothing can refuse a hand-spawned thread fail-closed."
|
|
19
|
+
},
|
|
20
|
+
"guide_topic": "jobs",
|
|
21
|
+
"corpus": true
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/no_adhoc_config_decrypt",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "Sealed config is never decrypted ad hoc; one budgeted call site only",
|
|
5
|
+
"intent": "A sealed configuration value stays opaque in app code: a module renders the masked form and never unseals a value itself. The single sanctioned decrypt site carries a justified opt-out marker counted against the app's escape-hatch budget. The runtime half is the decrypt chokepoint itself, which fails closed unless called from the one registered call site.",
|
|
6
|
+
"layer": "static-portable",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_no_adhoc_config_decrypt"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"kind": "runtime",
|
|
15
|
+
"tool": "terp.core",
|
|
16
|
+
"ref": "decrypt_config"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"reference": "mask_config renders sealed values (enc:v1:...); terp.core.secrets.decrypt_config fails closed unless called from the site registered via register_decrypt_call_site (design §5.4).",
|
|
20
|
+
"opt_out": "# arch-allow-no-adhoc-config-decrypt: <reason>",
|
|
21
|
+
"runtime": {
|
|
22
|
+
"applicability": "required",
|
|
23
|
+
"rationale": "decrypt_config itself fails closed on every path unless invoked from the single call site registered via register_decrypt_call_site -- an ad hoc decrypt raises SecretsError at runtime; the budgeted marker governs which site that is."
|
|
24
|
+
},
|
|
25
|
+
"guide_topic": "capability",
|
|
26
|
+
"corpus": true
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/no_adhoc_logging_config",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "App code never configures logging globally; redaction is centralized",
|
|
5
|
+
"intent": "Structured logging plus secret/PII redaction is installed once, centrally, at composition. A module that re-points the global logging configuration itself can silently bypass the central redaction filter.",
|
|
6
|
+
"layer": "static-portable",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "terp.arch",
|
|
11
|
+
"ref": "check_no_adhoc_logging_config"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"reference": "configure_logging (called by create_app) installs the redacting handlers; logging.basicConfig / dictConfig / fileConfig calls in app modules are refused.",
|
|
15
|
+
"opt_out": "# arch-allow-no-adhoc-logging-config: <reason>",
|
|
16
|
+
"runtime": {
|
|
17
|
+
"applicability": "not-applicable",
|
|
18
|
+
"rationale": "Global logging mutation is ambient interpreter state with no framework chokepoint to refuse it. The paired protection is constructive rather than preventive: the framework installs redaction on every handler it configures, but cannot intercept a module calling logging.basicConfig at import time."
|
|
19
|
+
},
|
|
20
|
+
"guide_topic": "capability",
|
|
21
|
+
"corpus": true
|
|
22
|
+
}
|