frontmattering 0.4.2__py3-none-any.whl

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 (224) hide show
  1. frontmattering/FRONTMATTER_INDEX_PROVENANCE.json +13 -0
  2. frontmattering/__init__.py +1 -0
  3. frontmattering/__main__.py +1263 -0
  4. frontmattering/ai_instruction_risk.py +51 -0
  5. frontmattering/authoring.py +320 -0
  6. frontmattering/body_tether.py +176 -0
  7. frontmattering/common/__init__.py +3 -0
  8. frontmattering/common/errors.py +1074 -0
  9. frontmattering/core.py +147 -0
  10. frontmattering/corpus_selection.py +436 -0
  11. frontmattering/frontmatter_index.py +340 -0
  12. frontmattering/harness_errors.py +555 -0
  13. frontmattering/inventory.py +83 -0
  14. frontmattering/markdown/MARKDOWN_PARSE_RESULT_schema.json +870 -0
  15. frontmattering/markdown/MARKDOWN_PROVENANCE.json +8 -0
  16. frontmattering/markdown/USER_MANUAL.md +263 -0
  17. frontmattering/markdown/__init__.py +0 -0
  18. frontmattering/markdown/api.py +553 -0
  19. frontmattering/markdown/cli/__init__.py +1 -0
  20. frontmattering/markdown/cli/__main__.py +10 -0
  21. frontmattering/markdown/cli/dump_sections.py +100 -0
  22. frontmattering/markdown/collectors_phase8/__init__.py +15 -0
  23. frontmattering/markdown/collectors_phase8/blockquotes.py +117 -0
  24. frontmattering/markdown/collectors_phase8/codeblocks.py +46 -0
  25. frontmattering/markdown/collectors_phase8/footnotes.py +152 -0
  26. frontmattering/markdown/collectors_phase8/frontmatter.py +80 -0
  27. frontmattering/markdown/collectors_phase8/headings.py +85 -0
  28. frontmattering/markdown/collectors_phase8/html.py +29 -0
  29. frontmattering/markdown/collectors_phase8/html_collector.py +89 -0
  30. frontmattering/markdown/collectors_phase8/images.py +68 -0
  31. frontmattering/markdown/collectors_phase8/inline_code.py +61 -0
  32. frontmattering/markdown/collectors_phase8/links.py +104 -0
  33. frontmattering/markdown/collectors_phase8/lists.py +66 -0
  34. frontmattering/markdown/collectors_phase8/math.py +28 -0
  35. frontmattering/markdown/collectors_phase8/paragraphs.py +41 -0
  36. frontmattering/markdown/collectors_phase8/tables.py +65 -0
  37. frontmattering/markdown/collectors_phase8/tasklists.py +59 -0
  38. frontmattering/markdown/fetchers/__init__.py +8 -0
  39. frontmattering/markdown/fetchers/preview.py +123 -0
  40. frontmattering/markdown/fetchers/url_fetcher.py +123 -0
  41. frontmattering/markdown/security/__init__.py +6 -0
  42. frontmattering/markdown/security/scanner.py +436 -0
  43. frontmattering/markdown/utils/__init__.py +0 -0
  44. frontmattering/markdown/utils/profiling.py +137 -0
  45. frontmattering/markdown/utils/section.py +98 -0
  46. frontmattering/markdown/utils/text_normalization.py +91 -0
  47. frontmattering/markdown/utils/timeout.py +165 -0
  48. frontmattering/markdown/utils/token_warehouse.py +1300 -0
  49. frontmattering/markdown/utils/url_utils.py +179 -0
  50. frontmattering/promotion.py +170 -0
  51. frontmattering/resources/AI_INDEX.json +691 -0
  52. frontmattering/resources/AI_START_HERE.md +378 -0
  53. frontmattering/resources/DAILY_USE_GUIDE.md +87 -0
  54. frontmattering/resources/FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md +3203 -0
  55. frontmattering/resources/KERNEL_ACCEPTANCE_REPORT.md +180 -0
  56. frontmattering/resources/__init__.py +0 -0
  57. frontmattering/resources/examples/no_frontmatter_demo/00_AI_START_HERE.md +43 -0
  58. frontmattering/resources/examples/no_frontmatter_demo/10_NORMATIVE_SPEC.md +28 -0
  59. frontmattering/resources/examples/no_frontmatter_demo/20_IMPLEMENTATION_CONTRACT.md +33 -0
  60. frontmattering/resources/examples/no_frontmatter_demo/30_TEST_SUFFICIENCY.md +34 -0
  61. frontmattering/resources/examples/no_frontmatter_demo/40_EVIDENCE_RECORD.md +33 -0
  62. frontmattering/resources/examples/no_frontmatter_demo/50_RELEASE_DECISION.md +29 -0
  63. frontmattering/resources/examples/power_demo/00_AI_START_HERE.md +142 -0
  64. frontmattering/resources/examples/power_demo/10_NORMATIVE_SPEC.md +117 -0
  65. frontmattering/resources/examples/power_demo/20_IMPLEMENTATION_CONTRACT.md +130 -0
  66. frontmattering/resources/examples/power_demo/30_TEST_SUFFICIENCY.md +132 -0
  67. frontmattering/resources/examples/power_demo/40_EVIDENCE_RECORD.md +128 -0
  68. frontmattering/resources/examples/power_demo/50_RELEASE_DECISION.md +124 -0
  69. frontmattering/resources/fixtures/invalid/adoption_digest_mismatch_rejected/doc.md +80 -0
  70. frontmattering/resources/fixtures/invalid/adoption_record_on_wrong_role_rejected/doc.md +59 -0
  71. frontmattering/resources/fixtures/invalid/adoption_record_shape_rejected/doc.md +58 -0
  72. frontmattering/resources/fixtures/invalid/adoption_without_bootstrap_receipt_rejected/doc.md +78 -0
  73. frontmattering/resources/fixtures/invalid/authored_go_used_as_evidence_rejected/doc.md +57 -0
  74. frontmattering/resources/fixtures/invalid/body_tether_body_digest_mismatch_rejected/doc.md +65 -0
  75. frontmattering/resources/fixtures/invalid/body_tether_fields_mismatch_rejected/doc.md +63 -0
  76. frontmattering/resources/fixtures/invalid/body_tether_malformed_rejected/doc.md +58 -0
  77. frontmattering/resources/fixtures/invalid/body_tether_missing_under_trust_rejected/.frontmattering/project.yaml +11 -0
  78. frontmattering/resources/fixtures/invalid/body_tether_missing_under_trust_rejected/doc.md +57 -0
  79. frontmattering/resources/fixtures/invalid/body_tether_scope_selector_rejected/doc.md +68 -0
  80. frontmattering/resources/fixtures/invalid/body_tether_unknown_canonicalization_rejected/doc.md +63 -0
  81. frontmattering/resources/fixtures/invalid/bootstrap_receipt_shape_rejected/doc.md +80 -0
  82. frontmattering/resources/fixtures/invalid/bootstrap_without_validator_blocks_adoption/doc.md +71 -0
  83. frontmattering/resources/fixtures/invalid/claim_boundary_missing_does_not_verify_rejected/doc.md +55 -0
  84. frontmattering/resources/fixtures/invalid/claimed_state_overreach_rejected/doc.md +57 -0
  85. frontmattering/resources/fixtures/invalid/clause_enforcement_drift_rejected/doc.md +59 -0
  86. frontmattering/resources/fixtures/invalid/container_cardinality_rejected/doc.md +57 -0
  87. frontmattering/resources/fixtures/invalid/deepest_intention_missing_rejected/doc.md +56 -0
  88. frontmattering/resources/fixtures/invalid/downstream_phase_without_upstream_evidence_rejected/doc.md +59 -0
  89. frontmattering/resources/fixtures/invalid/duplicate_document_id_rejected/a.md +57 -0
  90. frontmattering/resources/fixtures/invalid/duplicate_document_id_rejected/b.md +57 -0
  91. frontmattering/resources/fixtures/invalid/duplicate_yaml_key_rejected/doc.md +58 -0
  92. frontmattering/resources/fixtures/invalid/evidence_edge_digest_missing_rejected/doc.md +65 -0
  93. frontmattering/resources/fixtures/invalid/evidence_reference_used_as_truth_rejected/doc.md +66 -0
  94. frontmattering/resources/fixtures/invalid/external_edge_locator_missing_rejected/doc.md +64 -0
  95. frontmattering/resources/fixtures/invalid/external_edge_set_id_missing_rejected/doc.md +64 -0
  96. frontmattering/resources/fixtures/invalid/extracted_catalog_drift_rejected/doc.md +60 -0
  97. frontmattering/resources/fixtures/invalid/fixture_reverse_reachability_drift_rejected/doc.md +59 -0
  98. frontmattering/resources/fixtures/invalid/frontmatter_only_view_incomplete_rejected/doc.md +59 -0
  99. frontmattering/resources/fixtures/invalid/frontmatter_registry_conflict_blocks/doc.md +59 -0
  100. frontmattering/resources/fixtures/invalid/frontmatter_view_body_truth_overclaim_rejected/doc.md +59 -0
  101. frontmattering/resources/fixtures/invalid/generated_state_in_frontmatter_rejected/doc.md +58 -0
  102. frontmattering/resources/fixtures/invalid/green_checks_conflation_rejected/doc.md +76 -0
  103. frontmattering/resources/fixtures/invalid/historical_digest_used_as_current_evidence_rejected/doc.md +66 -0
  104. frontmattering/resources/fixtures/invalid/implementation_start_gate_bypass_rejected/doc.md +59 -0
  105. frontmattering/resources/fixtures/invalid/intention_positive_negative_conflation_rejected/doc.md +76 -0
  106. frontmattering/resources/fixtures/invalid/legacy_string_machine_surface_requires_mapping/doc.md +59 -0
  107. frontmattering/resources/fixtures/invalid/load_order_inversion_flagged/a.md +63 -0
  108. frontmattering/resources/fixtures/invalid/load_order_inversion_flagged/b.md +57 -0
  109. frontmattering/resources/fixtures/invalid/machine_surface_identity_incomplete_rejected/doc.md +62 -0
  110. frontmattering/resources/fixtures/invalid/migration_field_drop_rejected/doc.md +60 -0
  111. frontmattering/resources/fixtures/invalid/migration_silent_coercion_rejected/doc.md +59 -0
  112. frontmattering/resources/fixtures/invalid/missing_frontmatter_rejected/doc.md +1 -0
  113. frontmattering/resources/fixtures/invalid/non_root_exception_or_silent_fallback_rejected/doc.md +59 -0
  114. frontmattering/resources/fixtures/invalid/nonowner_redefines_intention_rejected/doc.md +58 -0
  115. frontmattering/resources/fixtures/invalid/output_format_conflation_rejected/doc.md +76 -0
  116. frontmattering/resources/fixtures/invalid/parser_byte_ranges_missing_rejected/doc.md +59 -0
  117. frontmattering/resources/fixtures/invalid/parser_partial_model_on_error_rejected/doc.md +59 -0
  118. frontmattering/resources/fixtures/invalid/parser_source_locator_missing_rejected/doc.md +59 -0
  119. frontmattering/resources/fixtures/invalid/projected_field_without_origin_rejected/doc.md +59 -0
  120. frontmattering/resources/fixtures/invalid/projection_rebuild_nondeterminism_rejected/doc.md +59 -0
  121. frontmattering/resources/fixtures/invalid/projection_row_without_source_locator_rejected/doc.md +59 -0
  122. frontmattering/resources/fixtures/invalid/relationship_resolution_scope_missing_rejected/doc.md +62 -0
  123. frontmattering/resources/fixtures/invalid/relationship_resolution_scope_unknown_rejected/doc.md +63 -0
  124. frontmattering/resources/fixtures/invalid/relationship_semantics_rejected/doc.md +63 -0
  125. frontmattering/resources/fixtures/invalid/relationship_target_unresolved_rejected/doc.md +63 -0
  126. frontmattering/resources/fixtures/invalid/release_surface_digest_missing_rejected/doc.md +63 -0
  127. frontmattering/resources/fixtures/invalid/required_dependency_cycle_rejected/a.md +63 -0
  128. frontmattering/resources/fixtures/invalid/required_dependency_cycle_rejected/b.md +63 -0
  129. frontmattering/resources/fixtures/invalid/routing_tag_not_in_task_catalog_rejected/doc.md +59 -0
  130. frontmattering/resources/fixtures/invalid/routing_tag_router_not_in_scope_rejected/doc.md +55 -0
  131. frontmattering/resources/fixtures/invalid/scope_on_nonsupersedes_rejected/doc.md +65 -0
  132. frontmattering/resources/fixtures/invalid/stale_bootstrap_receipt_rejected/doc.md +80 -0
  133. frontmattering/resources/fixtures/invalid/stale_validation_result_rejected/doc.md +60 -0
  134. frontmattering/resources/fixtures/invalid/supersedes_without_scope_rejected/doc.md +63 -0
  135. frontmattering/resources/fixtures/invalid/target_section_not_found_rejected/a.md +65 -0
  136. frontmattering/resources/fixtures/invalid/target_section_not_found_rejected/b.md +57 -0
  137. frontmattering/resources/fixtures/invalid/target_sections_on_external_edge_rejected/doc.md +67 -0
  138. frontmattering/resources/fixtures/invalid/task_scoped_defects_filtering/router.md +59 -0
  139. frontmattering/resources/fixtures/invalid/task_scoped_defects_filtering/selected.md +56 -0
  140. frontmattering/resources/fixtures/invalid/task_scoped_defects_filtering/skipped.md +56 -0
  141. frontmattering/resources/fixtures/invalid/template_claims_normative_authority_rejected/doc.md +57 -0
  142. frontmattering/resources/fixtures/invalid/two_documents_own_same_intention_rejected/a.md +76 -0
  143. frontmattering/resources/fixtures/invalid/two_documents_own_same_intention_rejected/b.md +76 -0
  144. frontmattering/resources/fixtures/invalid/undefined_conditional_token_rejected/doc.md +59 -0
  145. frontmattering/resources/fixtures/invalid/undefined_type_rejected/doc.md +59 -0
  146. frontmattering/resources/fixtures/invalid/unknown_authority_rejected/doc.md +57 -0
  147. frontmattering/resources/fixtures/invalid/unknown_document_role_rejected/doc.md +57 -0
  148. frontmattering/resources/fixtures/invalid/unknown_field_rejected/doc.md +58 -0
  149. frontmattering/resources/fixtures/invalid/unknown_root_block_rejected/doc.md +59 -0
  150. frontmattering/resources/fixtures/invalid/unresolved_reference_on_required_edge_rejected/doc.md +63 -0
  151. frontmattering/resources/fixtures/invalid/unsupported_schema_version_rejected/doc.md +57 -0
  152. frontmattering/resources/fixtures/invalid/untemplated_frontmatter_bypass_rejected/doc.md +59 -0
  153. frontmattering/resources/fixtures/invalid/validator_passes_conflation_rejected/doc.md +76 -0
  154. frontmattering/resources/fixtures/invalid/yaml_1_1_boolean_words_not_coerced/doc.md +57 -0
  155. frontmattering/resources/fixtures/pilot_security/blockquote.md +3 -0
  156. frontmattering/resources/fixtures/pilot_security/code_fence.md +5 -0
  157. frontmattering/resources/fixtures/pilot_security/dangerous_html.md +3 -0
  158. frontmattering/resources/fixtures/pilot_security/dangerous_uri.md +3 -0
  159. frontmattering/resources/fixtures/pilot_security/direct_instruction.md +3 -0
  160. frontmattering/resources/fixtures/pilot_security/generated_resource_limits.yaml +16 -0
  161. frontmattering/resources/fixtures/pilot_security/indirect_instruction.md +4 -0
  162. frontmattering/resources/fixtures/pilot_security/legitimate_security_discussion.md +3 -0
  163. frontmattering/resources/fixtures/pilot_security/link.md +3 -0
  164. frontmattering/resources/fixtures/pilot_security/table.md +5 -0
  165. frontmattering/resources/fixtures/valid/adoption_locator_trigger_word_is_data_valid/doc.md +80 -0
  166. frontmattering/resources/fixtures/valid/body_tether_fresh_valid/doc.md +63 -0
  167. frontmattering/resources/fixtures/valid/bootstrap_validation_receipt_valid/doc.md +66 -0
  168. frontmattering/resources/fixtures/valid/claim_prose_substrings_are_not_verdicts_valid/doc.md +57 -0
  169. frontmattering/resources/fixtures/valid/companion_and_appendix_edges_valid/a.md +69 -0
  170. frontmattering/resources/fixtures/valid/companion_and_appendix_edges_valid/b.md +57 -0
  171. frontmattering/resources/fixtures/valid/document_binding_to_external_intention_valid/doc.md +57 -0
  172. frontmattering/resources/fixtures/valid/document_realizing_external_intention_valid/doc.md +57 -0
  173. frontmattering/resources/fixtures/valid/document_referencing_external_intention_valid/doc.md +57 -0
  174. frontmattering/resources/fixtures/valid/evidence_record_valid/doc.md +57 -0
  175. frontmattering/resources/fixtures/valid/evidence_support_substrings_are_not_proof_valid/doc.md +66 -0
  176. frontmattering/resources/fixtures/valid/external_document_set_edge_valid/doc.md +65 -0
  177. frontmattering/resources/fixtures/valid/frontmatter_adoption_record_valid/doc.md +80 -0
  178. frontmattering/resources/fixtures/valid/frontmatter_projection_mapping_valid/doc.md +57 -0
  179. frontmattering/resources/fixtures/valid/frontmatter_spec_bootstrap_valid/doc.md +76 -0
  180. frontmattering/resources/fixtures/valid/implementation_contract_valid/doc.md +57 -0
  181. frontmattering/resources/fixtures/valid/normative_spec_valid/doc.md +57 -0
  182. frontmattering/resources/fixtures/valid/orientation_router_valid/doc.md +59 -0
  183. frontmattering/resources/fixtures/valid/release_metadata_valid/doc.md +57 -0
  184. frontmattering/resources/fixtures/valid/target_sections_resolved_valid/a.md +65 -0
  185. frontmattering/resources/fixtures/valid/target_sections_resolved_valid/b.md +57 -0
  186. frontmattering/resources/fixtures/valid/template_valid/doc.md +57 -0
  187. frontmattering/resources/fixtures/valid/unresolved_reference_optional_edge_valid/doc.md +63 -0
  188. frontmattering/resources/fixtures/valid/untemplated_document_valid/doc.md +57 -0
  189. frontmattering/resources/fixtures/valid/yaml_1_2_plain_words_valid/doc.md +60 -0
  190. frontmattering/resources/generated/FRONTMATTERING_FRONTMATTER_SCHEMA.schema.json +2422 -0
  191. frontmattering/resources/tools/__init__.py +0 -0
  192. frontmattering/resources/tools/check_frontmatter_sqlite_index.py +40 -0
  193. frontmattering/resources/tools/check_kernel_cleanliness.py +103 -0
  194. frontmattering/resources/tools/check_kernel_exception_taxonomy.py +145 -0
  195. frontmattering/resources/tools/check_markdown_parser_adoption.py +35 -0
  196. frontmattering/resources/tools/check_pilot_readiness.py +545 -0
  197. frontmattering/resources/tools/check_projection_consistency.py +35 -0
  198. frontmattering/resources/tools/check_python_syntax.py +29 -0
  199. frontmattering/resources/tools/check_report_quality.py +478 -0
  200. frontmattering/resources/tools/distribution_receipt.py +54 -0
  201. frontmattering/resources/tools/finding_registry.py +625 -0
  202. frontmattering/resources/tools/frontmattering_cli.py +18 -0
  203. frontmattering/resources/tools/generate_ai_index.py +123 -0
  204. frontmattering/resources/tools/generate_frontmatter_schema.py +70 -0
  205. frontmattering/resources/tools/kernel_reporting.py +1495 -0
  206. frontmattering/resources/tools/make_fixtures.py +507 -0
  207. frontmattering/resources/tools/pack_kernel_distro.py +430 -0
  208. frontmattering/resources/tools/test_verify_kernel.py +177 -0
  209. frontmattering/resources/tools/validate_frontmatters.py +100 -0
  210. frontmattering/resources/tools/verify_kernel.py +197 -0
  211. frontmattering/resources/workflow-docs-manifest.yaml +199 -0
  212. frontmattering/schema_projection.py +288 -0
  213. frontmattering/speccompile.py +81 -0
  214. frontmattering/validator.py +957 -0
  215. frontmattering-0.4.2.dist-info/METADATA +91 -0
  216. frontmattering-0.4.2.dist-info/RECORD +224 -0
  217. frontmattering-0.4.2.dist-info/WHEEL +5 -0
  218. frontmattering-0.4.2.dist-info/entry_points.txt +2 -0
  219. frontmattering-0.4.2.dist-info/licenses/COMMERCIAL_LICENSE_REQUIRED.md +14 -0
  220. frontmattering-0.4.2.dist-info/licenses/LICENSE.md +39 -0
  221. frontmattering-0.4.2.dist-info/licenses/NOTICE +10 -0
  222. frontmattering-0.4.2.dist-info/licenses/THIRD_PARTY_NOTICES.md +12 -0
  223. frontmattering-0.4.2.dist-info/licenses/TRADEMARKS.md +7 -0
  224. frontmattering-0.4.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,555 @@
1
+ """Fail-closed harness error base classes.
2
+
3
+ Programming contract:
4
+ - A missing requirement is not a warning.
5
+ - An unknown state is not success.
6
+ - A caught HarnessError must be re-raised, wrapped, or converted into an
7
+ explicit failure result at a boundary such as a CLI/API response.
8
+ - Do not return success with error details hidden in context.
9
+ - Do not use broad ``except Exception: pass`` around harness validation.
10
+ - Prefer ``fail_closed(...)`` at the point where a contract is violated.
11
+
12
+ Testing contract:
13
+ - Expected harness/validator failures should raise a HarnessError subclass.
14
+ - Tests can catch ``HarnessError`` to prove fail-closed behavior generically.
15
+ - Tests can assert ``to_dict()`` fields so failures stay actionable.
16
+ - Coded subclasses let tests assert stable recovery IDs without parsing prose.
17
+ - Bare ``Exception``/``ValueError`` means implementation bug, not domain failure.
18
+
19
+ Error taxonomy authority:
20
+ - All intentional harness/domain error classes should live in this module.
21
+ - Other modules may import and raise these errors, but should not define local
22
+ HarnessError subclasses.
23
+ - Centralizing the taxonomy prevents fragmented error IDs, duplicated
24
+ subclasses, inconsistent ``fix`` quality, and agent-invented local errors.
25
+ - Tests should be able to inventory expected domain failures from this module.
26
+ - Ordinary unexpected implementation bugs may still surface as normal Python
27
+ exceptions; expected domain failures should route through this taxonomy.
28
+
29
+ Taxonomy shape:
30
+ - Use a small number of broad category subclasses.
31
+ - Use many stable ``error_id`` values for specific failures.
32
+ - Category class means "how callers broadly handle this failure".
33
+ - ``error_id`` means "exact recovery, test, and documentation key".
34
+ - Add a subclass only when catch/handling behavior differs.
35
+ - Add an ``error_id`` when the specific recovery/test/doc message differs.
36
+ - Do not create a new subclass for every individual failure.
37
+
38
+ Suggested broad categories:
39
+ - ConfigurationError: bad/missing config, schema, manifest, or setup.
40
+ - ValidationFailure: input failed a contract or validator rule.
41
+ - AuthorityError: authority map, ownership, permission, or policy authority.
42
+ - EvidenceError: missing, stale, untrusted, or insufficient evidence.
43
+ - ToolingError: query tool, subprocess, parser, index, or generated artifact.
44
+ - BoundaryError: CLI/API/serialization/boundary contract failure.
45
+ - InternalBugError: invariant broken inside implementation. This is not a
46
+ HarnessError subclass and should not be caught by domain boundary handlers.
47
+
48
+ SOLID guardrails:
49
+ - Single responsibility: error classes hold structured failure data and intent.
50
+ They do not validate, log, render CLI output, write telemetry, or perform
51
+ recovery.
52
+ - Open/closed: add new domain errors by subclassing or adding stable IDs, not
53
+ by changing the base contract for each new use case.
54
+ - Liskov substitution: every subclass must remain usable as a HarnessError and
55
+ must preserve fail-closed semantics.
56
+ - Interface segregation: CLI/HTTP/log/test rendering belongs in boundary
57
+ helpers, not on every error subclass.
58
+ - Dependency inversion: validators depend on the HarnessError contract;
59
+ boundary adapters depend on formatting/serialization helpers.
60
+ - Do not make the base class too clever. Error objects are data and intent;
61
+ boundary adapters decide presentation; validators decide when to fail.
62
+
63
+ Field contract:
64
+ - ``what``: human-readable failure summary.
65
+ - ``where``: stable code or workflow location that produced the failure.
66
+ - ``why``: consequence or invariant being protected.
67
+ - ``fix``: concrete next action; do not leave users or agents guessing.
68
+ - ``context``: machine-readable evidence for tests, CLIs, logs, and automation.
69
+
70
+ Motivation:
71
+ A HarnessError is not just an exception. It is a recovery packet. Every
72
+ intentional failure should answer: what happened, where it happened, why it
73
+ matters, what to do next, and what evidence supports that answer.
74
+
75
+ AI-agent rule:
76
+ If you cannot fill these fields honestly, you probably do not understand the
77
+ failure well enough to continue silently.
78
+
79
+ Agent-gradient rule:
80
+ Agents tend to choose the shortest local edit that makes the immediate check
81
+ pass. Therefore fail-closed APIs must make explicit failure cheaper than silent
82
+ recovery. Do not rely on exhortation alone. Provide one-line guards, explicit
83
+ Outcome envelopes, runtime boundary checks, and tests/gates that reject silent
84
+ swallow patterns.
85
+
86
+ Boundary rule:
87
+ - Inside the system: raise.
88
+ - At a CLI/API boundary: serialize the error and return a nonzero/failed result.
89
+ - Never silently downgrade a HarnessError into a warning or success response.
90
+
91
+ Catch policy:
92
+ - Catch HarnessError only to add context, wrap it, re-raise it, or serialize it
93
+ at a boundary.
94
+ - Preserve the original cause when wrapping unexpected exceptions.
95
+
96
+ Data policy:
97
+ - ``context`` must be deterministic, JSON-serializable, and safe to print.
98
+ - Do not put secrets, huge blobs, raw full-file dumps, or nondeterministic data
99
+ into ``context``.
100
+ - Constructors normalize non-JSON context defensively so error rendering does
101
+ not fail while reporting an already-failing state.
102
+ - ``where`` should be a stable selector such as module.function or workflow step.
103
+ - ``fix`` should be executable guidance, not vague advice.
104
+ - ``error_id`` values should be stable and namespaced because tests and
105
+ recovery docs may depend on them.
106
+
107
+ Good example:
108
+ ``IdentifiedHarnessError.fail_closed(error_id="QPV_E_RECEIPT_MISSING", ...)``
109
+
110
+ Bad examples:
111
+ - ``return {"status": "ok", "error": "receipt missing"}``
112
+ - ``except Exception: pass``
113
+ - ``raise ValueError("bad")`` for an expected domain failure
114
+
115
+ Cheap honesty API:
116
+ - ``fail("receipt missing", error_cls=EvidenceError, error_id="QPV_E_...")``
117
+ raises with caller location captured automatically.
118
+ - ``require(condition, "condition failed")`` is an assert-like fail-closed guard.
119
+ - ``value = must(value, "value missing")`` replaces ``if not value: return {}``.
120
+ - ``Ok(value, evidence={...})`` and ``Fail(error)`` are explicit outcome
121
+ envelopes; there is no empty-dict success inhabitant.
122
+ - ``ensure_outcome(value)`` rejects boundary returns that are not ``Ok`` or
123
+ ``Fail`` so dynamic code cannot smuggle ``None``, ``{}``, ``[]``, or ``False``
124
+ through an Outcome contract.
125
+
126
+ Enforcement loop:
127
+ - Make the loud path the shortest honest code: ``require``/``must``/``fail``.
128
+ - Make clean success carry evidence: ``Ok(value, evidence={...})``.
129
+ - Make failure explicit: ``Fail(HarnessError(...))`` or raise internally.
130
+ - Make boundary silence invalid: ``ensure_outcome`` rejects non-Outcome values.
131
+ - Make silent swallows fail gates: AST/xref rules should flag
132
+ ``except ...: pass`` and ``except ...: return None/{}/[]`` inside fail-closed
133
+ zones, while allowing broad catches only in declared boundary adapters.
134
+ - Require negative fixtures for fail-closed functions so a swallow turns the
135
+ function's own test red instead of hiding behind a green empty result.
136
+
137
+ This module exists to make silent failure harder than explicit failure.
138
+ """
139
+
140
+ from __future__ import annotations
141
+
142
+ import inspect
143
+ import json
144
+ from dataclasses import dataclass
145
+ from typing import Any, Generic, NoReturn, TypeAlias, TypeVar
146
+
147
+ T = TypeVar("T")
148
+
149
+ __all__ = [
150
+ "HarnessError",
151
+ "IdentifiedHarnessError",
152
+ "ConfigurationError",
153
+ "ValidationFailure",
154
+ "AuthorityError",
155
+ "EvidenceError",
156
+ "ToolingError",
157
+ "BoundaryError",
158
+ "InternalBugError",
159
+ "CodedHarnessError",
160
+ "Outcome",
161
+ "Ok",
162
+ "Fail",
163
+ "where_here",
164
+ "fail",
165
+ "require",
166
+ "must",
167
+ "ensure_outcome",
168
+ ]
169
+
170
+
171
+ def _json_safe(value: Any, seen: set[int] | None = None) -> Any:
172
+ seen = set() if seen is None else seen
173
+ if isinstance(value, str | int | float | bool) or value is None:
174
+ return value
175
+ marker = id(value)
176
+ if marker in seen:
177
+ return "<cycle>"
178
+ try:
179
+ seen.add(marker)
180
+ if isinstance(value, dict):
181
+ return {str(key): _json_safe(item, seen) for key, item in value.items()}
182
+ if isinstance(value, list | tuple):
183
+ return [_json_safe(item, seen) for item in value]
184
+ if isinstance(value, set | frozenset):
185
+ return [_json_safe(item, seen) for item in sorted(value, key=repr)]
186
+ try:
187
+ json.dumps(value)
188
+ return value
189
+ except TypeError:
190
+ return repr(value)
191
+ finally:
192
+ seen.remove(marker)
193
+
194
+
195
+ def _normalize_context(context: dict[str, Any] | None) -> dict[str, Any]:
196
+ if context is None:
197
+ return {}
198
+ if not isinstance(context, dict):
199
+ return {"value": _json_safe(context)}
200
+ return _json_safe(context)
201
+
202
+
203
+ def where_here(skip: int = 1) -> str:
204
+ """Return a stable best-effort caller selector for fail-closed helpers."""
205
+ frame = inspect.currentframe()
206
+ try:
207
+ for _ in range(skip):
208
+ if frame is None:
209
+ return "<unknown>"
210
+ frame = frame.f_back
211
+ if frame is None:
212
+ return "<unknown>"
213
+ module = frame.f_globals.get("__name__", "<unknown>")
214
+ function = frame.f_code.co_name
215
+ return f"{module}.{function}:{frame.f_lineno}"
216
+ finally:
217
+ del frame
218
+
219
+
220
+ class HarnessError(Exception):
221
+ """Base class for fail-closed harness exceptions.
222
+
223
+ This is a stop signal, not a log message.
224
+
225
+ Development rule: raise early when a required condition is missing,
226
+ ambiguous, stale, or unproven. Silent fallback hides bugs and makes later
227
+ evidence unreliable.
228
+
229
+ AI-agent guidance:
230
+ - When uncertain, raise or return a structured failure.
231
+ - Never hide this error to make a workflow appear green.
232
+ - Catch only to add context, then re-raise or return an explicit failure.
233
+ - Prefer ``HarnessError.fail_closed(...)`` over ``None``, ``False``, empty
234
+ dictionaries, guessed defaults, or warning-only results.
235
+ - Use ``require`` and ``must`` for local guards because they are intentionally
236
+ cheaper than hand-written silent fallback branches.
237
+ - For functions that promise an Outcome, return ``Ok`` with evidence or
238
+ ``Fail`` with a HarnessError; never return an empty container as a fake
239
+ success state.
240
+
241
+ Test-design guidance:
242
+ - Use ``pytest.raises(HarnessError)`` for expected fail-closed outcomes.
243
+ - Assert ``what``, ``where``, ``why``, and ``fix`` are populated enough to
244
+ guide recovery.
245
+ - Reserve non-HarnessError exceptions for unexpected implementation bugs.
246
+ - Inventory expected domain subclasses from this module instead of scanning
247
+ scattered local error classes.
248
+
249
+ Recovery-packet guidance:
250
+ - ``what`` should name the failed condition.
251
+ - ``where`` should make the failure locatable without a stack trace.
252
+ - ``why`` should explain the protected invariant or consequence.
253
+ - ``fix`` should give the next concrete repair action.
254
+ - ``context`` should carry structured evidence that tests and CLIs can
255
+ inspect without scraping prose.
256
+
257
+ Boundary guidance:
258
+ - Raise inside validators and harness internals.
259
+ - Serialize only at CLI/API/process boundaries.
260
+ - Never report success with error data hidden in the payload.
261
+ """
262
+
263
+ def __init__(
264
+ self,
265
+ what: str,
266
+ where: str,
267
+ why: str = "",
268
+ fix: str = "",
269
+ context: dict[str, Any] | None = None,
270
+ *,
271
+ error_id: str | None = None,
272
+ ) -> None:
273
+ super().__init__(what)
274
+ self.what = what
275
+ self.where = where
276
+ self.why = why
277
+ self.fix = fix
278
+ self.context = _normalize_context(context)
279
+ self.error_id = error_id
280
+
281
+ def __reduce__(self) -> tuple[object, tuple[type["HarnessError"], dict[str, Any]]]:
282
+ return (_restore_harness_error, (type(self), self.to_dict()))
283
+
284
+ def __setstate__(self, state: dict[str, Any]) -> None:
285
+ self.what = str(state.get("what", ""))
286
+ self.where = str(state.get("where", ""))
287
+ self.why = str(state.get("why", ""))
288
+ self.fix = str(state.get("fix", ""))
289
+ context = state.get("context", {})
290
+ self.context = _normalize_context(context if isinstance(context, dict) else {})
291
+ error_id = state.get("error_id")
292
+ self.error_id = str(error_id) if error_id else None
293
+ self.args = (self.what,)
294
+
295
+ @classmethod
296
+ def fail_closed(
297
+ cls,
298
+ what: str,
299
+ where: str,
300
+ why: str = "",
301
+ fix: str = "",
302
+ context: dict[str, Any] | None = None,
303
+ *,
304
+ error_id: str | None = None,
305
+ ) -> NoReturn:
306
+ """Raise immediately for an unproven or contract-violating state."""
307
+ raise cls(
308
+ what,
309
+ where,
310
+ why,
311
+ fix,
312
+ context,
313
+ error_id=error_id,
314
+ )
315
+
316
+ def to_dict(self) -> dict[str, Any]:
317
+ payload = {
318
+ "error_type": self.__class__.__name__,
319
+ "what": self.what,
320
+ "where": self.where,
321
+ "why": self.why,
322
+ "fix": self.fix,
323
+ "context": self.context,
324
+ }
325
+ if self.error_id:
326
+ payload["error_id"] = self.error_id
327
+ return payload
328
+
329
+ def __str__(self) -> str:
330
+ try:
331
+ return json.dumps(self.to_dict(), sort_keys=True)
332
+ except (TypeError, ValueError):
333
+ return f"{self.__class__.__name__}: {self.what} ({self.where})"
334
+
335
+
336
+ class IdentifiedHarnessError(HarnessError):
337
+ """Structured harness error with stable machine-readable error_id.
338
+
339
+ Use this pattern when callers need both fail-closed behavior and a stable
340
+ recovery/classification key. The default development posture remains:
341
+ construct only when needed for tests or serialization; call
342
+ ``IdentifiedHarnessError.fail_closed(...)`` when execution must stop.
343
+
344
+ Production domain failures should provide ``error_id``. Construction without
345
+ an error_id remains allowed so category subclasses are substitutable with
346
+ HarnessError during tests, wrapping, and generic failure handling.
347
+ """
348
+
349
+
350
+ CodedHarnessError = IdentifiedHarnessError
351
+
352
+
353
+ class ConfigurationError(IdentifiedHarnessError):
354
+ """Bad or missing config, schema, manifest, or setup."""
355
+
356
+
357
+ class ValidationFailure(IdentifiedHarnessError):
358
+ """Input failed a contract or validator rule."""
359
+
360
+
361
+ class AuthorityError(IdentifiedHarnessError):
362
+ """Authority map, ownership, permission, or policy authority failure."""
363
+
364
+
365
+ class EvidenceError(IdentifiedHarnessError):
366
+ """Missing, stale, untrusted, or insufficient evidence."""
367
+
368
+
369
+ class ToolingError(IdentifiedHarnessError):
370
+ """Query tool, subprocess, parser, index, or generated artifact failure."""
371
+
372
+
373
+ class BoundaryError(IdentifiedHarnessError):
374
+ """CLI/API/serialization/boundary contract failure."""
375
+
376
+
377
+ class InternalBugError(IdentifiedHarnessError):
378
+ """Invariant broken inside implementation.
379
+
380
+ Internal bugs are still HarnessError instances so executable boundaries can
381
+ serialize them without leaking raw tracebacks. They must remain terminal:
382
+ callers may report them, but must not launder them into a recoverable GO.
383
+ """
384
+
385
+ def __init__(
386
+ self,
387
+ message: str,
388
+ *,
389
+ where: str = "",
390
+ context: dict[str, Any] | None = None,
391
+ ) -> None:
392
+ super().__init__(
393
+ message,
394
+ where,
395
+ "A kernel invariant or impossible branch was reached.",
396
+ "Fix the kernel implementation; do not treat this as user-repairable input.",
397
+ context,
398
+ error_id="FRONTMATTERING-KERNEL-INTERNAL-BUG",
399
+ )
400
+
401
+
402
+ def fail(
403
+ what: str,
404
+ *,
405
+ error_cls: type[HarnessError] = HarnessError,
406
+ error_id: str | None = None,
407
+ where: str | None = None,
408
+ why: str = "",
409
+ fix: str = "",
410
+ context: dict[str, Any] | None = None,
411
+ ) -> NoReturn:
412
+ """Raise a fail-closed HarnessError with automatic caller location."""
413
+ raise error_cls(
414
+ what,
415
+ where or where_here(skip=2),
416
+ why,
417
+ fix,
418
+ context,
419
+ error_id=error_id,
420
+ )
421
+
422
+
423
+ def require(
424
+ condition: Any,
425
+ what: str,
426
+ *,
427
+ error_cls: type[HarnessError] = ValidationFailure,
428
+ error_id: str | None = None,
429
+ where: str | None = None,
430
+ why: str = "",
431
+ fix: str = "",
432
+ context: dict[str, Any] | None = None,
433
+ ) -> None:
434
+ """Assert-like guard that raises instead of silently returning falsey data."""
435
+ if not condition:
436
+ fail(
437
+ what,
438
+ error_cls=error_cls,
439
+ error_id=error_id,
440
+ where=where or where_here(skip=2),
441
+ why=why,
442
+ fix=fix,
443
+ context=context,
444
+ )
445
+
446
+
447
+ def must(
448
+ value: T | None,
449
+ what: str,
450
+ *,
451
+ error_cls: type[HarnessError] = EvidenceError,
452
+ error_id: str | None = None,
453
+ where: str | None = None,
454
+ why: str = "",
455
+ fix: str = "",
456
+ context: dict[str, Any] | None = None,
457
+ reject_empty: bool = False,
458
+ ) -> T:
459
+ """Return value or raise fail-closed if it is absent.
460
+
461
+ By default only ``None`` is absent, so falsey-but-valid values like ``0`` or
462
+ ``False`` survive. Set ``reject_empty=True`` for loaders where empty
463
+ containers/strings are invalid evidence.
464
+ """
465
+ if value is None or (reject_empty and not value):
466
+ fail(
467
+ what,
468
+ error_cls=error_cls,
469
+ error_id=error_id,
470
+ where=where or where_here(skip=2),
471
+ why=why,
472
+ fix=fix,
473
+ context=context,
474
+ )
475
+ return value
476
+
477
+
478
+ @dataclass(frozen=True)
479
+ class Ok(Generic[T]):
480
+ """Explicit success envelope that must carry evidence."""
481
+
482
+ value: T
483
+ evidence: dict[str, Any]
484
+
485
+ def __post_init__(self) -> None:
486
+ if not isinstance(self.evidence, dict) or not self.evidence:
487
+ raise InternalBugError(
488
+ "Ok outcome requires non-empty evidence",
489
+ where="harness_errors.Ok.__post_init__",
490
+ )
491
+ object.__setattr__(self, "evidence", _normalize_context(self.evidence))
492
+
493
+
494
+ @dataclass(frozen=True)
495
+ class Fail:
496
+ """Explicit failure envelope carrying a structured HarnessError."""
497
+
498
+ error: HarnessError
499
+
500
+ def __post_init__(self) -> None:
501
+ if not isinstance(self.error, HarnessError):
502
+ raise InternalBugError(
503
+ "Fail outcome requires a HarnessError",
504
+ where="harness_errors.Fail.__post_init__",
505
+ context={"actual_type": type(self.error).__name__},
506
+ )
507
+
508
+
509
+ Outcome: TypeAlias = Ok[T] | Fail
510
+
511
+
512
+ def ensure_outcome(
513
+ value: Any,
514
+ *,
515
+ where: str | None = None,
516
+ error_id: str = "HARNESS_E_NON_OUTCOME_RETURN",
517
+ what: str = "Outcome-returning function returned a non-Outcome value",
518
+ context: dict[str, Any] | None = None,
519
+ ) -> Outcome[Any]:
520
+ """Return an Outcome or fail closed when a boundary receives silence.
521
+
522
+ This is the runtime backstop for dynamic code. Type hints can say a function
523
+ returns ``Outcome[T]``, but this guard makes the boundary reject accidental or
524
+ agent-authored returns such as ``None``, ``{}``, ``[]``, or ``False``.
525
+ """
526
+ if isinstance(value, (Ok, Fail)):
527
+ return value
528
+
529
+ boundary_context = {
530
+ "actual_type": type(value).__name__,
531
+ "actual_repr": repr(value)[:500],
532
+ "allowed_types": ["Ok", "Fail"],
533
+ }
534
+ if context:
535
+ boundary_context["caller_context"] = _normalize_context(context)
536
+
537
+ fail(
538
+ what,
539
+ error_cls=BoundaryError,
540
+ error_id=error_id,
541
+ where=where or where_here(skip=2),
542
+ why="Outcome contracts must carry explicit success evidence or structured failure.",
543
+ fix="Return Ok(value, evidence={...}) or Fail(error); do not return silent falsey values.",
544
+ context=boundary_context,
545
+ )
546
+
547
+
548
+ def _restore_harness_error(
549
+ error_cls: type[HarnessError],
550
+ state: dict[str, Any],
551
+ ) -> HarnessError:
552
+ error = error_cls.__new__(error_cls)
553
+ Exception.__init__(error, str(state.get("what", "")))
554
+ error.__setstate__(state)
555
+ return error
@@ -0,0 +1,83 @@
1
+ """Read-only deterministic inventory for governed and plain Markdown."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from .core import parse_frontmatter
8
+ from .corpus_selection import SelectionResult
9
+
10
+
11
+ def _classify(path: Path, *, selected: bool, normalized: str) -> dict[str, Any]:
12
+ try:
13
+ raw_bytes = path.read_bytes()
14
+ raw = raw_bytes.decode("utf-8", errors="strict")
15
+ except (OSError, UnicodeDecodeError) as exc:
16
+ return {
17
+ "path": normalized,
18
+ "status": "unreadable",
19
+ "reason_code": "FRONTMATTERING-INVENTORY-UNREADABLE",
20
+ "selected": selected,
21
+ "proposed_action": "inspect_encoding_or_permissions",
22
+ "detail": str(exc),
23
+ }
24
+ if not raw_bytes.startswith(b"---"):
25
+ return {
26
+ "path": normalized,
27
+ "status": "plain_markdown_selected" if selected else "markdown_outside_selection",
28
+ "reason_code": "FRONTMATTERING-INVENTORY-PLAIN-SELECTED" if selected else "FRONTMATTERING-INVENTORY-OUTSIDE-SELECTION",
29
+ "selected": selected,
30
+ "proposed_action": "consider_promotion" if selected else "none",
31
+ }
32
+ fm, _body, findings = parse_frontmatter(raw)
33
+ if fm is not None and not findings:
34
+ status = "governed_valid" if selected else "markdown_outside_selection"
35
+ return {
36
+ "path": normalized,
37
+ "status": status,
38
+ "reason_code": "FRONTMATTERING-INVENTORY-GOVERNED-VALID" if selected else "FRONTMATTERING-INVENTORY-OUTSIDE-SELECTION",
39
+ "selected": selected,
40
+ "proposed_action": "none",
41
+ "document_id": (fm.get("ai_document_contract") or {}).get("document_id"),
42
+ }
43
+ return {
44
+ "path": normalized,
45
+ "status": "governed_invalid" if selected else "markdown_outside_selection",
46
+ "reason_code": "FRONTMATTERING-INVENTORY-GOVERNED-INVALID" if selected else "FRONTMATTERING-INVENTORY-OUTSIDE-SELECTION",
47
+ "selected": selected,
48
+ "proposed_action": "repair_frontmatter" if selected else "none",
49
+ "finding_ids": sorted({getattr(item, "id", str(item)) for item in findings}),
50
+ }
51
+
52
+
53
+ def build_inventory(selection: SelectionResult) -> dict[str, Any]:
54
+ selected_set = {p.resolve() for p in selection.paths}
55
+ documents = [
56
+ _classify(
57
+ path,
58
+ selected=path.resolve() in selected_set,
59
+ normalized=selection.normalized_path(path),
60
+ )
61
+ for path in sorted(selection.all_markdown_paths, key=lambda p: selection.normalized_path(p))
62
+ ]
63
+ census = {
64
+ "governed_valid": sum(1 for item in documents if item["status"] == "governed_valid"),
65
+ "governed_invalid": sum(1 for item in documents if item["status"] == "governed_invalid"),
66
+ "plain_markdown_selected": sum(1 for item in documents if item["status"] == "plain_markdown_selected"),
67
+ "markdown_outside_selection": sum(1 for item in documents if item["status"] == "markdown_outside_selection"),
68
+ "unreadable": sum(1 for item in documents if item["status"] == "unreadable"),
69
+ }
70
+ return {
71
+ "schema_version": "frontmattering_inventory/v0_2",
72
+ "project": {
73
+ "project_file": str(selection.project_file.resolve()) if selection.project_file else None,
74
+ "project_schema": selection.project_schema,
75
+ },
76
+ "selection": selection.payload(),
77
+ "census": census,
78
+ "documents": documents,
79
+ "claim_boundary": {
80
+ "verifies": ["deterministic file and frontmatter classification"],
81
+ "does_not_verify": ["whether a plain document should become governed"],
82
+ },
83
+ }