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.
- frontmattering/FRONTMATTER_INDEX_PROVENANCE.json +13 -0
- frontmattering/__init__.py +1 -0
- frontmattering/__main__.py +1263 -0
- frontmattering/ai_instruction_risk.py +51 -0
- frontmattering/authoring.py +320 -0
- frontmattering/body_tether.py +176 -0
- frontmattering/common/__init__.py +3 -0
- frontmattering/common/errors.py +1074 -0
- frontmattering/core.py +147 -0
- frontmattering/corpus_selection.py +436 -0
- frontmattering/frontmatter_index.py +340 -0
- frontmattering/harness_errors.py +555 -0
- frontmattering/inventory.py +83 -0
- frontmattering/markdown/MARKDOWN_PARSE_RESULT_schema.json +870 -0
- frontmattering/markdown/MARKDOWN_PROVENANCE.json +8 -0
- frontmattering/markdown/USER_MANUAL.md +263 -0
- frontmattering/markdown/__init__.py +0 -0
- frontmattering/markdown/api.py +553 -0
- frontmattering/markdown/cli/__init__.py +1 -0
- frontmattering/markdown/cli/__main__.py +10 -0
- frontmattering/markdown/cli/dump_sections.py +100 -0
- frontmattering/markdown/collectors_phase8/__init__.py +15 -0
- frontmattering/markdown/collectors_phase8/blockquotes.py +117 -0
- frontmattering/markdown/collectors_phase8/codeblocks.py +46 -0
- frontmattering/markdown/collectors_phase8/footnotes.py +152 -0
- frontmattering/markdown/collectors_phase8/frontmatter.py +80 -0
- frontmattering/markdown/collectors_phase8/headings.py +85 -0
- frontmattering/markdown/collectors_phase8/html.py +29 -0
- frontmattering/markdown/collectors_phase8/html_collector.py +89 -0
- frontmattering/markdown/collectors_phase8/images.py +68 -0
- frontmattering/markdown/collectors_phase8/inline_code.py +61 -0
- frontmattering/markdown/collectors_phase8/links.py +104 -0
- frontmattering/markdown/collectors_phase8/lists.py +66 -0
- frontmattering/markdown/collectors_phase8/math.py +28 -0
- frontmattering/markdown/collectors_phase8/paragraphs.py +41 -0
- frontmattering/markdown/collectors_phase8/tables.py +65 -0
- frontmattering/markdown/collectors_phase8/tasklists.py +59 -0
- frontmattering/markdown/fetchers/__init__.py +8 -0
- frontmattering/markdown/fetchers/preview.py +123 -0
- frontmattering/markdown/fetchers/url_fetcher.py +123 -0
- frontmattering/markdown/security/__init__.py +6 -0
- frontmattering/markdown/security/scanner.py +436 -0
- frontmattering/markdown/utils/__init__.py +0 -0
- frontmattering/markdown/utils/profiling.py +137 -0
- frontmattering/markdown/utils/section.py +98 -0
- frontmattering/markdown/utils/text_normalization.py +91 -0
- frontmattering/markdown/utils/timeout.py +165 -0
- frontmattering/markdown/utils/token_warehouse.py +1300 -0
- frontmattering/markdown/utils/url_utils.py +179 -0
- frontmattering/promotion.py +170 -0
- frontmattering/resources/AI_INDEX.json +691 -0
- frontmattering/resources/AI_START_HERE.md +378 -0
- frontmattering/resources/DAILY_USE_GUIDE.md +87 -0
- frontmattering/resources/FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md +3203 -0
- frontmattering/resources/KERNEL_ACCEPTANCE_REPORT.md +180 -0
- frontmattering/resources/__init__.py +0 -0
- frontmattering/resources/examples/no_frontmatter_demo/00_AI_START_HERE.md +43 -0
- frontmattering/resources/examples/no_frontmatter_demo/10_NORMATIVE_SPEC.md +28 -0
- frontmattering/resources/examples/no_frontmatter_demo/20_IMPLEMENTATION_CONTRACT.md +33 -0
- frontmattering/resources/examples/no_frontmatter_demo/30_TEST_SUFFICIENCY.md +34 -0
- frontmattering/resources/examples/no_frontmatter_demo/40_EVIDENCE_RECORD.md +33 -0
- frontmattering/resources/examples/no_frontmatter_demo/50_RELEASE_DECISION.md +29 -0
- frontmattering/resources/examples/power_demo/00_AI_START_HERE.md +142 -0
- frontmattering/resources/examples/power_demo/10_NORMATIVE_SPEC.md +117 -0
- frontmattering/resources/examples/power_demo/20_IMPLEMENTATION_CONTRACT.md +130 -0
- frontmattering/resources/examples/power_demo/30_TEST_SUFFICIENCY.md +132 -0
- frontmattering/resources/examples/power_demo/40_EVIDENCE_RECORD.md +128 -0
- frontmattering/resources/examples/power_demo/50_RELEASE_DECISION.md +124 -0
- frontmattering/resources/fixtures/invalid/adoption_digest_mismatch_rejected/doc.md +80 -0
- frontmattering/resources/fixtures/invalid/adoption_record_on_wrong_role_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/adoption_record_shape_rejected/doc.md +58 -0
- frontmattering/resources/fixtures/invalid/adoption_without_bootstrap_receipt_rejected/doc.md +78 -0
- frontmattering/resources/fixtures/invalid/authored_go_used_as_evidence_rejected/doc.md +57 -0
- frontmattering/resources/fixtures/invalid/body_tether_body_digest_mismatch_rejected/doc.md +65 -0
- frontmattering/resources/fixtures/invalid/body_tether_fields_mismatch_rejected/doc.md +63 -0
- frontmattering/resources/fixtures/invalid/body_tether_malformed_rejected/doc.md +58 -0
- frontmattering/resources/fixtures/invalid/body_tether_missing_under_trust_rejected/.frontmattering/project.yaml +11 -0
- frontmattering/resources/fixtures/invalid/body_tether_missing_under_trust_rejected/doc.md +57 -0
- frontmattering/resources/fixtures/invalid/body_tether_scope_selector_rejected/doc.md +68 -0
- frontmattering/resources/fixtures/invalid/body_tether_unknown_canonicalization_rejected/doc.md +63 -0
- frontmattering/resources/fixtures/invalid/bootstrap_receipt_shape_rejected/doc.md +80 -0
- frontmattering/resources/fixtures/invalid/bootstrap_without_validator_blocks_adoption/doc.md +71 -0
- frontmattering/resources/fixtures/invalid/claim_boundary_missing_does_not_verify_rejected/doc.md +55 -0
- frontmattering/resources/fixtures/invalid/claimed_state_overreach_rejected/doc.md +57 -0
- frontmattering/resources/fixtures/invalid/clause_enforcement_drift_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/container_cardinality_rejected/doc.md +57 -0
- frontmattering/resources/fixtures/invalid/deepest_intention_missing_rejected/doc.md +56 -0
- frontmattering/resources/fixtures/invalid/downstream_phase_without_upstream_evidence_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/duplicate_document_id_rejected/a.md +57 -0
- frontmattering/resources/fixtures/invalid/duplicate_document_id_rejected/b.md +57 -0
- frontmattering/resources/fixtures/invalid/duplicate_yaml_key_rejected/doc.md +58 -0
- frontmattering/resources/fixtures/invalid/evidence_edge_digest_missing_rejected/doc.md +65 -0
- frontmattering/resources/fixtures/invalid/evidence_reference_used_as_truth_rejected/doc.md +66 -0
- frontmattering/resources/fixtures/invalid/external_edge_locator_missing_rejected/doc.md +64 -0
- frontmattering/resources/fixtures/invalid/external_edge_set_id_missing_rejected/doc.md +64 -0
- frontmattering/resources/fixtures/invalid/extracted_catalog_drift_rejected/doc.md +60 -0
- frontmattering/resources/fixtures/invalid/fixture_reverse_reachability_drift_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/frontmatter_only_view_incomplete_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/frontmatter_registry_conflict_blocks/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/frontmatter_view_body_truth_overclaim_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/generated_state_in_frontmatter_rejected/doc.md +58 -0
- frontmattering/resources/fixtures/invalid/green_checks_conflation_rejected/doc.md +76 -0
- frontmattering/resources/fixtures/invalid/historical_digest_used_as_current_evidence_rejected/doc.md +66 -0
- frontmattering/resources/fixtures/invalid/implementation_start_gate_bypass_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/intention_positive_negative_conflation_rejected/doc.md +76 -0
- frontmattering/resources/fixtures/invalid/legacy_string_machine_surface_requires_mapping/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/load_order_inversion_flagged/a.md +63 -0
- frontmattering/resources/fixtures/invalid/load_order_inversion_flagged/b.md +57 -0
- frontmattering/resources/fixtures/invalid/machine_surface_identity_incomplete_rejected/doc.md +62 -0
- frontmattering/resources/fixtures/invalid/migration_field_drop_rejected/doc.md +60 -0
- frontmattering/resources/fixtures/invalid/migration_silent_coercion_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/missing_frontmatter_rejected/doc.md +1 -0
- frontmattering/resources/fixtures/invalid/non_root_exception_or_silent_fallback_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/nonowner_redefines_intention_rejected/doc.md +58 -0
- frontmattering/resources/fixtures/invalid/output_format_conflation_rejected/doc.md +76 -0
- frontmattering/resources/fixtures/invalid/parser_byte_ranges_missing_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/parser_partial_model_on_error_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/parser_source_locator_missing_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/projected_field_without_origin_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/projection_rebuild_nondeterminism_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/projection_row_without_source_locator_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/relationship_resolution_scope_missing_rejected/doc.md +62 -0
- frontmattering/resources/fixtures/invalid/relationship_resolution_scope_unknown_rejected/doc.md +63 -0
- frontmattering/resources/fixtures/invalid/relationship_semantics_rejected/doc.md +63 -0
- frontmattering/resources/fixtures/invalid/relationship_target_unresolved_rejected/doc.md +63 -0
- frontmattering/resources/fixtures/invalid/release_surface_digest_missing_rejected/doc.md +63 -0
- frontmattering/resources/fixtures/invalid/required_dependency_cycle_rejected/a.md +63 -0
- frontmattering/resources/fixtures/invalid/required_dependency_cycle_rejected/b.md +63 -0
- frontmattering/resources/fixtures/invalid/routing_tag_not_in_task_catalog_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/routing_tag_router_not_in_scope_rejected/doc.md +55 -0
- frontmattering/resources/fixtures/invalid/scope_on_nonsupersedes_rejected/doc.md +65 -0
- frontmattering/resources/fixtures/invalid/stale_bootstrap_receipt_rejected/doc.md +80 -0
- frontmattering/resources/fixtures/invalid/stale_validation_result_rejected/doc.md +60 -0
- frontmattering/resources/fixtures/invalid/supersedes_without_scope_rejected/doc.md +63 -0
- frontmattering/resources/fixtures/invalid/target_section_not_found_rejected/a.md +65 -0
- frontmattering/resources/fixtures/invalid/target_section_not_found_rejected/b.md +57 -0
- frontmattering/resources/fixtures/invalid/target_sections_on_external_edge_rejected/doc.md +67 -0
- frontmattering/resources/fixtures/invalid/task_scoped_defects_filtering/router.md +59 -0
- frontmattering/resources/fixtures/invalid/task_scoped_defects_filtering/selected.md +56 -0
- frontmattering/resources/fixtures/invalid/task_scoped_defects_filtering/skipped.md +56 -0
- frontmattering/resources/fixtures/invalid/template_claims_normative_authority_rejected/doc.md +57 -0
- frontmattering/resources/fixtures/invalid/two_documents_own_same_intention_rejected/a.md +76 -0
- frontmattering/resources/fixtures/invalid/two_documents_own_same_intention_rejected/b.md +76 -0
- frontmattering/resources/fixtures/invalid/undefined_conditional_token_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/undefined_type_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/unknown_authority_rejected/doc.md +57 -0
- frontmattering/resources/fixtures/invalid/unknown_document_role_rejected/doc.md +57 -0
- frontmattering/resources/fixtures/invalid/unknown_field_rejected/doc.md +58 -0
- frontmattering/resources/fixtures/invalid/unknown_root_block_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/unresolved_reference_on_required_edge_rejected/doc.md +63 -0
- frontmattering/resources/fixtures/invalid/unsupported_schema_version_rejected/doc.md +57 -0
- frontmattering/resources/fixtures/invalid/untemplated_frontmatter_bypass_rejected/doc.md +59 -0
- frontmattering/resources/fixtures/invalid/validator_passes_conflation_rejected/doc.md +76 -0
- frontmattering/resources/fixtures/invalid/yaml_1_1_boolean_words_not_coerced/doc.md +57 -0
- frontmattering/resources/fixtures/pilot_security/blockquote.md +3 -0
- frontmattering/resources/fixtures/pilot_security/code_fence.md +5 -0
- frontmattering/resources/fixtures/pilot_security/dangerous_html.md +3 -0
- frontmattering/resources/fixtures/pilot_security/dangerous_uri.md +3 -0
- frontmattering/resources/fixtures/pilot_security/direct_instruction.md +3 -0
- frontmattering/resources/fixtures/pilot_security/generated_resource_limits.yaml +16 -0
- frontmattering/resources/fixtures/pilot_security/indirect_instruction.md +4 -0
- frontmattering/resources/fixtures/pilot_security/legitimate_security_discussion.md +3 -0
- frontmattering/resources/fixtures/pilot_security/link.md +3 -0
- frontmattering/resources/fixtures/pilot_security/table.md +5 -0
- frontmattering/resources/fixtures/valid/adoption_locator_trigger_word_is_data_valid/doc.md +80 -0
- frontmattering/resources/fixtures/valid/body_tether_fresh_valid/doc.md +63 -0
- frontmattering/resources/fixtures/valid/bootstrap_validation_receipt_valid/doc.md +66 -0
- frontmattering/resources/fixtures/valid/claim_prose_substrings_are_not_verdicts_valid/doc.md +57 -0
- frontmattering/resources/fixtures/valid/companion_and_appendix_edges_valid/a.md +69 -0
- frontmattering/resources/fixtures/valid/companion_and_appendix_edges_valid/b.md +57 -0
- frontmattering/resources/fixtures/valid/document_binding_to_external_intention_valid/doc.md +57 -0
- frontmattering/resources/fixtures/valid/document_realizing_external_intention_valid/doc.md +57 -0
- frontmattering/resources/fixtures/valid/document_referencing_external_intention_valid/doc.md +57 -0
- frontmattering/resources/fixtures/valid/evidence_record_valid/doc.md +57 -0
- frontmattering/resources/fixtures/valid/evidence_support_substrings_are_not_proof_valid/doc.md +66 -0
- frontmattering/resources/fixtures/valid/external_document_set_edge_valid/doc.md +65 -0
- frontmattering/resources/fixtures/valid/frontmatter_adoption_record_valid/doc.md +80 -0
- frontmattering/resources/fixtures/valid/frontmatter_projection_mapping_valid/doc.md +57 -0
- frontmattering/resources/fixtures/valid/frontmatter_spec_bootstrap_valid/doc.md +76 -0
- frontmattering/resources/fixtures/valid/implementation_contract_valid/doc.md +57 -0
- frontmattering/resources/fixtures/valid/normative_spec_valid/doc.md +57 -0
- frontmattering/resources/fixtures/valid/orientation_router_valid/doc.md +59 -0
- frontmattering/resources/fixtures/valid/release_metadata_valid/doc.md +57 -0
- frontmattering/resources/fixtures/valid/target_sections_resolved_valid/a.md +65 -0
- frontmattering/resources/fixtures/valid/target_sections_resolved_valid/b.md +57 -0
- frontmattering/resources/fixtures/valid/template_valid/doc.md +57 -0
- frontmattering/resources/fixtures/valid/unresolved_reference_optional_edge_valid/doc.md +63 -0
- frontmattering/resources/fixtures/valid/untemplated_document_valid/doc.md +57 -0
- frontmattering/resources/fixtures/valid/yaml_1_2_plain_words_valid/doc.md +60 -0
- frontmattering/resources/generated/FRONTMATTERING_FRONTMATTER_SCHEMA.schema.json +2422 -0
- frontmattering/resources/tools/__init__.py +0 -0
- frontmattering/resources/tools/check_frontmatter_sqlite_index.py +40 -0
- frontmattering/resources/tools/check_kernel_cleanliness.py +103 -0
- frontmattering/resources/tools/check_kernel_exception_taxonomy.py +145 -0
- frontmattering/resources/tools/check_markdown_parser_adoption.py +35 -0
- frontmattering/resources/tools/check_pilot_readiness.py +545 -0
- frontmattering/resources/tools/check_projection_consistency.py +35 -0
- frontmattering/resources/tools/check_python_syntax.py +29 -0
- frontmattering/resources/tools/check_report_quality.py +478 -0
- frontmattering/resources/tools/distribution_receipt.py +54 -0
- frontmattering/resources/tools/finding_registry.py +625 -0
- frontmattering/resources/tools/frontmattering_cli.py +18 -0
- frontmattering/resources/tools/generate_ai_index.py +123 -0
- frontmattering/resources/tools/generate_frontmatter_schema.py +70 -0
- frontmattering/resources/tools/kernel_reporting.py +1495 -0
- frontmattering/resources/tools/make_fixtures.py +507 -0
- frontmattering/resources/tools/pack_kernel_distro.py +430 -0
- frontmattering/resources/tools/test_verify_kernel.py +177 -0
- frontmattering/resources/tools/validate_frontmatters.py +100 -0
- frontmattering/resources/tools/verify_kernel.py +197 -0
- frontmattering/resources/workflow-docs-manifest.yaml +199 -0
- frontmattering/schema_projection.py +288 -0
- frontmattering/speccompile.py +81 -0
- frontmattering/validator.py +957 -0
- frontmattering-0.4.2.dist-info/METADATA +91 -0
- frontmattering-0.4.2.dist-info/RECORD +224 -0
- frontmattering-0.4.2.dist-info/WHEEL +5 -0
- frontmattering-0.4.2.dist-info/entry_points.txt +2 -0
- frontmattering-0.4.2.dist-info/licenses/COMMERCIAL_LICENSE_REQUIRED.md +14 -0
- frontmattering-0.4.2.dist-info/licenses/LICENSE.md +39 -0
- frontmattering-0.4.2.dist-info/licenses/NOTICE +10 -0
- frontmattering-0.4.2.dist-info/licenses/THIRD_PARTY_NOTICES.md +12 -0
- frontmattering-0.4.2.dist-info/licenses/TRADEMARKS.md +7 -0
- 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
|
+
}
|