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,1495 @@
|
|
|
1
|
+
"""Actionable report helpers for the Frontmattering frontmatter kernel."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from finding_registry import guidance_for
|
|
11
|
+
from frontmattering.harness_errors import ConfigurationError, HarnessError, InternalBugError, ToolingError, ValidationFailure
|
|
12
|
+
from frontmattering.core import split_frontmatter
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
GUIDANCE: dict[str, tuple[str, str, str]] = {
|
|
16
|
+
"FRONTMATTERING-FM-FRONTMATTER-MISSING": (
|
|
17
|
+
"Markdown frontmatter is missing or unparseable",
|
|
18
|
+
"The kernel cannot build a canonical document model without a byte-zero YAML block.",
|
|
19
|
+
"Add exactly one YAML frontmatter block at the start of the file and make it parse.",
|
|
20
|
+
),
|
|
21
|
+
"FRONTMATTERING-FM-SCHEMA-VERSION-UNSUPPORTED": (
|
|
22
|
+
"Unsupported Frontmattering frontmatter schema version",
|
|
23
|
+
"This kernel is intentionally not backward-compatible; it validates only v0.1.5.",
|
|
24
|
+
"Rewrite or migrate this document to ai_document_contract.schema_version: frontmattering_frontmatter/v0_1_5.",
|
|
25
|
+
),
|
|
26
|
+
"FRONTMATTERING-FM-UNKNOWN-ROOT-BLOCK": (
|
|
27
|
+
"Unknown root frontmatter block",
|
|
28
|
+
"The closed root model forbids extra semantic blocks because they can launder authority.",
|
|
29
|
+
"Remove the block or model it through an approved v0.1.5 field/surface.",
|
|
30
|
+
),
|
|
31
|
+
"FRONTMATTERING-FM-UNKNOWN-FIELD": (
|
|
32
|
+
"Unknown frontmatter field",
|
|
33
|
+
"Every canonical field must be declared in the v0.1.5 field catalog.",
|
|
34
|
+
"Remove the field or amend the spec/kernel before using it.",
|
|
35
|
+
),
|
|
36
|
+
"FRONTMATTERING-FM-DOCUMENT-ID-DUPLICATE": (
|
|
37
|
+
"Duplicate document_id in one docset",
|
|
38
|
+
"Document identity must be unique for routing, relationships, and provenance.",
|
|
39
|
+
"Give each document a unique ai_document_contract.document_id.",
|
|
40
|
+
),
|
|
41
|
+
"FRONTMATTERING-FM-RELATIONSHIP-TARGET-UNRESOLVED": (
|
|
42
|
+
"Relationship target does not resolve in the same document set",
|
|
43
|
+
"A same_document_set relationship cannot be followed by agents or validators.",
|
|
44
|
+
"Add the target document to the validated docset, correct target_document_id, or change resolution_scope.",
|
|
45
|
+
),
|
|
46
|
+
"FRONTMATTERING-FM-RELATIONSHIP-TARGET-NOT-FOUND": (
|
|
47
|
+
"Required relationship target is missing",
|
|
48
|
+
"Required relationships are preconditions for safe document use.",
|
|
49
|
+
"Add the target document or make the relationship non-required only if the spec permits it.",
|
|
50
|
+
),
|
|
51
|
+
"FRONTMATTERING-FM-TARGET-SECTION-NOT-FOUND": (
|
|
52
|
+
"Relationship target section does not exist",
|
|
53
|
+
"Section-targeted edges must point to exact headings in the target document.",
|
|
54
|
+
"Correct target_sections or add the exact heading to the target document.",
|
|
55
|
+
),
|
|
56
|
+
"FRONTMATTERING-FM-LOAD-ORDER-INVERSION": (
|
|
57
|
+
"load_order contradicts an ordering dependency",
|
|
58
|
+
"A source document cannot load before a required ordering dependency it depends on.",
|
|
59
|
+
"Lower the target document load_order or raise the source document load_order.",
|
|
60
|
+
),
|
|
61
|
+
"FRONTMATTERING-FM-INTENTION-OWNER-MULTIPLE": (
|
|
62
|
+
"Multiple documents own the same intention",
|
|
63
|
+
"A deepest intention has exactly one canonical owner.",
|
|
64
|
+
"Keep one ownership: owns document and change the others to binds_to, realizes, or references.",
|
|
65
|
+
),
|
|
66
|
+
"FRONTMATTERING-FM-BINDING-DEPENDENCY-CYCLE": (
|
|
67
|
+
"Binding dependency cycle",
|
|
68
|
+
"Binding preconditions cannot be satisfied when required dependencies form a cycle.",
|
|
69
|
+
"Break the cycle by changing or removing one binding_precondition edge.",
|
|
70
|
+
),
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
FINDING_PRIORITY = [
|
|
75
|
+
"FRONTMATTERING-FM-FRONTMATTER-MISSING",
|
|
76
|
+
"FRONTMATTERING-FM-YAML-DUPLICATE-KEY",
|
|
77
|
+
"FRONTMATTERING-FM-YAML-FORBIDDEN-FEATURE",
|
|
78
|
+
"FRONTMATTERING-FM-SCHEMA-VERSION-UNSUPPORTED",
|
|
79
|
+
"FRONTMATTERING-FM-UNKNOWN-ROOT-BLOCK",
|
|
80
|
+
"FRONTMATTERING-FM-UNKNOWN-FIELD",
|
|
81
|
+
"FRONTMATTERING-FM-DOCUMENT-ID-DUPLICATE",
|
|
82
|
+
"FRONTMATTERING-FM-INTENTION-OWNER-MULTIPLE",
|
|
83
|
+
"FRONTMATTERING-FM-RELATIONSHIP-TARGET-UNRESOLVED",
|
|
84
|
+
"FRONTMATTERING-FM-RELATIONSHIP-TARGET-NOT-FOUND",
|
|
85
|
+
"FRONTMATTERING-FM-TARGET-SECTION-NOT-FOUND",
|
|
86
|
+
"FRONTMATTERING-FM-LOAD-ORDER-INVERSION",
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
SUMMARY_ITEM_LIMIT = 5
|
|
90
|
+
SUMMARY_READING_LIMIT = 12
|
|
91
|
+
|
|
92
|
+
BRIEF_SECTION_ALIASES = {
|
|
93
|
+
"summary": "summary",
|
|
94
|
+
"blocks": "blocks",
|
|
95
|
+
"traps": "traps",
|
|
96
|
+
"limits": "limits",
|
|
97
|
+
"do-not": "do_not",
|
|
98
|
+
"do_not": "do_not",
|
|
99
|
+
"reading-order": "reading_order",
|
|
100
|
+
"reading_order": "reading_order",
|
|
101
|
+
"defects": "defects_by_class",
|
|
102
|
+
"defects-by-class": "defects_by_class",
|
|
103
|
+
"defects_by_class": "defects_by_class",
|
|
104
|
+
"trust": "trust",
|
|
105
|
+
"claim-boundary": "claim_boundary",
|
|
106
|
+
"claim_boundary": "claim_boundary",
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _priority(error_dict: dict[str, Any]) -> tuple[int, str]:
|
|
111
|
+
eid = str(error_dict.get("error_id") or "")
|
|
112
|
+
try:
|
|
113
|
+
return (FINDING_PRIORITY.index(eid), eid)
|
|
114
|
+
except ValueError:
|
|
115
|
+
return (len(FINDING_PRIORITY), eid)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _default_guidance(error_id: str) -> tuple[str, str, str]:
|
|
119
|
+
return (
|
|
120
|
+
f"Frontmatter validation finding {error_id}",
|
|
121
|
+
"The document set violates a v0.1.5 kernel predicate.",
|
|
122
|
+
"Inspect the finding context, then repair the named field, relationship, evidence, or document-set invariant.",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _field_path_from_detail(detail: Any) -> str | None:
|
|
127
|
+
text = str(detail or "")
|
|
128
|
+
match = re.match(r"([A-Za-z0-9_.\[\]]+)(?:=| not |$)", text)
|
|
129
|
+
if match and "." in match.group(1):
|
|
130
|
+
return match.group(1)
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _line_hint(source_path: str | None, detail: Any) -> dict[str, Any]:
|
|
135
|
+
if not source_path:
|
|
136
|
+
return {}
|
|
137
|
+
path = Path(source_path)
|
|
138
|
+
if not path.exists():
|
|
139
|
+
return {}
|
|
140
|
+
try:
|
|
141
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
142
|
+
except OSError:
|
|
143
|
+
return {}
|
|
144
|
+
detail_text = str(detail or "")
|
|
145
|
+
candidates: list[str] = []
|
|
146
|
+
field_path = _field_path_from_detail(detail)
|
|
147
|
+
if field_path:
|
|
148
|
+
leaf = field_path.split(".")[-1].replace("[]", "")
|
|
149
|
+
candidates.append(f"{leaf}:")
|
|
150
|
+
containers = field_path.split(".")[:-1]
|
|
151
|
+
for container in reversed(containers):
|
|
152
|
+
key = container.replace("[]", "")
|
|
153
|
+
if key:
|
|
154
|
+
candidates.append(f"{key}:")
|
|
155
|
+
if "->" in detail_text:
|
|
156
|
+
target = detail_text.split("->", 1)[1].split("#", 1)[0].split("(", 1)[0]
|
|
157
|
+
candidates.append(f"target_document_id: {target}")
|
|
158
|
+
if "#" in detail_text:
|
|
159
|
+
candidates.append(detail_text.rsplit("#", 1)[-1])
|
|
160
|
+
candidates.append(detail_text)
|
|
161
|
+
for needle in [c for c in candidates if c]:
|
|
162
|
+
for idx, line in enumerate(lines, 1):
|
|
163
|
+
if needle in line:
|
|
164
|
+
return {
|
|
165
|
+
"line": idx,
|
|
166
|
+
"line_text": line.strip(),
|
|
167
|
+
"field_path": field_path,
|
|
168
|
+
}
|
|
169
|
+
return {"field_path": field_path} if field_path else {}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _document_id_for_source(result: dict[str, Any], source_path: str | None) -> str | None:
|
|
173
|
+
if not source_path:
|
|
174
|
+
return None
|
|
175
|
+
per_doc = (result.get("per_document") or {}).get(source_path) or {}
|
|
176
|
+
fm = per_doc.get("fm") or {}
|
|
177
|
+
adc = fm.get("ai_document_contract", {}) if isinstance(fm, dict) else {}
|
|
178
|
+
return adc.get("document_id")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def finding_error(
|
|
182
|
+
error_id: str,
|
|
183
|
+
detail: Any,
|
|
184
|
+
*,
|
|
185
|
+
source_path: str | None,
|
|
186
|
+
spec_path: str,
|
|
187
|
+
spec_sha256: str,
|
|
188
|
+
field_path: str | None = None,
|
|
189
|
+
line: int | None = None,
|
|
190
|
+
line_text: str | None = None,
|
|
191
|
+
document_id: str | None = None,
|
|
192
|
+
) -> ValidationFailure:
|
|
193
|
+
what, why, fix = guidance_for(error_id)
|
|
194
|
+
where = source_path or "docset"
|
|
195
|
+
if detail not in (None, ""):
|
|
196
|
+
where = f"{where}: {detail}"
|
|
197
|
+
source_hint = _line_hint(source_path, detail)
|
|
198
|
+
field_path = field_path or source_hint.get("field_path")
|
|
199
|
+
line = line or source_hint.get("line")
|
|
200
|
+
line_text = line_text or source_hint.get("line_text")
|
|
201
|
+
return ValidationFailure(
|
|
202
|
+
what=what,
|
|
203
|
+
where=where,
|
|
204
|
+
why=why,
|
|
205
|
+
fix=fix,
|
|
206
|
+
context={
|
|
207
|
+
"finding_id": error_id,
|
|
208
|
+
"detail": detail,
|
|
209
|
+
"source_path": source_path,
|
|
210
|
+
"document_id": document_id,
|
|
211
|
+
"field_path": field_path,
|
|
212
|
+
"line": line,
|
|
213
|
+
"line_text": line_text,
|
|
214
|
+
"spec_path": spec_path,
|
|
215
|
+
"spec_sha256": spec_sha256,
|
|
216
|
+
},
|
|
217
|
+
error_id=error_id,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def fixture_error(
|
|
222
|
+
*,
|
|
223
|
+
kind: str,
|
|
224
|
+
fixture_class: str,
|
|
225
|
+
status: str,
|
|
226
|
+
expected: str,
|
|
227
|
+
observed: list[str],
|
|
228
|
+
) -> HarnessError:
|
|
229
|
+
where = f"fixtures/{kind}/{fixture_class}"
|
|
230
|
+
context = {
|
|
231
|
+
"kind": kind,
|
|
232
|
+
"fixture_class": fixture_class,
|
|
233
|
+
"status": status,
|
|
234
|
+
"expected_finding": expected,
|
|
235
|
+
"observed_finding_ids": observed,
|
|
236
|
+
}
|
|
237
|
+
if status == "MISSING_FIXTURE":
|
|
238
|
+
return ConfigurationError(
|
|
239
|
+
what="Spec-declared fixture class is missing",
|
|
240
|
+
where=where,
|
|
241
|
+
why="Every spec-declared fixture class must have at least one Markdown fixture.",
|
|
242
|
+
fix="Create a fixture directory with one or more .md files for this class.",
|
|
243
|
+
context=context,
|
|
244
|
+
error_id="KERNEL-MISSING-FIXTURE",
|
|
245
|
+
)
|
|
246
|
+
if status == "WRONG_FINDING":
|
|
247
|
+
return ValidationFailure(
|
|
248
|
+
what="Invalid fixture produced the wrong finding",
|
|
249
|
+
where=where,
|
|
250
|
+
why="The fixture no longer proves the expected spec predicate.",
|
|
251
|
+
fix="Adjust the fixture mutation or validator so the expected finding is emitted for the right reason.",
|
|
252
|
+
context=context,
|
|
253
|
+
error_id="KERNEL-WRONG-FINDING",
|
|
254
|
+
)
|
|
255
|
+
if status == "NOT_REJECTED":
|
|
256
|
+
return ValidationFailure(
|
|
257
|
+
what="Invalid fixture was accepted",
|
|
258
|
+
where=where,
|
|
259
|
+
why="A declared invalid class must fail closed.",
|
|
260
|
+
fix="Add or repair the validator predicate for this class.",
|
|
261
|
+
context=context,
|
|
262
|
+
error_id="KERNEL-NOT-REJECTED",
|
|
263
|
+
)
|
|
264
|
+
if status == "REJECTED":
|
|
265
|
+
return ValidationFailure(
|
|
266
|
+
what="Valid fixture was rejected",
|
|
267
|
+
where=where,
|
|
268
|
+
why="A declared valid fixture must validate without findings.",
|
|
269
|
+
fix="Repair the fixture shape or correct an over-broad validator rule.",
|
|
270
|
+
context=context,
|
|
271
|
+
error_id="KERNEL-VALID-REJECTED",
|
|
272
|
+
)
|
|
273
|
+
return ToolingError(
|
|
274
|
+
what="Unknown kernel fixture status",
|
|
275
|
+
where=where,
|
|
276
|
+
why="The verifier produced a status outside the report vocabulary.",
|
|
277
|
+
fix="Fix verify_kernel.py status aggregation before trusting the report.",
|
|
278
|
+
context=context,
|
|
279
|
+
error_id="KERNEL-UNKNOWN-FIXTURE-STATUS",
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def source_for_finding(result: dict[str, Any], finding: tuple[str, Any]) -> str | None:
|
|
284
|
+
source_path = getattr(finding, "source_path", None)
|
|
285
|
+
if source_path:
|
|
286
|
+
return source_path
|
|
287
|
+
for path, per_doc in (result.get("per_document") or {}).items():
|
|
288
|
+
if finding in (per_doc.get("findings") or []):
|
|
289
|
+
return path
|
|
290
|
+
detail = str(finding[1])
|
|
291
|
+
if "->" in detail:
|
|
292
|
+
source_id = detail.split("->", 1)[0].split("(", 1)[0]
|
|
293
|
+
for path, per_doc in (result.get("per_document") or {}).items():
|
|
294
|
+
fm = per_doc.get("fm") or {}
|
|
295
|
+
adc = fm.get("ai_document_contract", {}) if isinstance(fm, dict) else {}
|
|
296
|
+
if adc.get("document_id") == source_id:
|
|
297
|
+
return path
|
|
298
|
+
return None
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def docset_mesh(result: dict[str, Any]) -> dict[str, Any]:
|
|
302
|
+
documents = []
|
|
303
|
+
relationships = []
|
|
304
|
+
unresolved_edges = []
|
|
305
|
+
task_catalog = _task_catalog(result.get("per_document") or {})
|
|
306
|
+
for path, per_doc in sorted((result.get("per_document") or {}).items()):
|
|
307
|
+
fm = per_doc.get("fm") or {}
|
|
308
|
+
if not isinstance(fm, dict):
|
|
309
|
+
continue
|
|
310
|
+
adc = fm.get("ai_document_contract", {})
|
|
311
|
+
routing = fm.get("routing", {})
|
|
312
|
+
intention = fm.get("deepest_intention", {})
|
|
313
|
+
doc_id = adc.get("document_id")
|
|
314
|
+
documents.append({
|
|
315
|
+
"path": path,
|
|
316
|
+
"document_id": doc_id,
|
|
317
|
+
"document_role": adc.get("document_role"),
|
|
318
|
+
"document_title": adc.get("document_title"),
|
|
319
|
+
"load_order": routing.get("load_order"),
|
|
320
|
+
"task_catalog": routing.get("task_catalog") or [],
|
|
321
|
+
"read_when_tags": routing.get("read_when_tags") or [],
|
|
322
|
+
"skip_when_tags": routing.get("skip_when_tags") or [],
|
|
323
|
+
"intention_id": intention.get("intention_id"),
|
|
324
|
+
"intention_ownership": intention.get("ownership"),
|
|
325
|
+
})
|
|
326
|
+
for index, rel in enumerate(fm.get("relationships") or []):
|
|
327
|
+
if not isinstance(rel, dict):
|
|
328
|
+
continue
|
|
329
|
+
edge = {
|
|
330
|
+
"source_document_id": doc_id,
|
|
331
|
+
"source_path": path,
|
|
332
|
+
"relationship_index": index,
|
|
333
|
+
"target_document_id": rel.get("target_document_id"),
|
|
334
|
+
"relationship_type": rel.get("relationship_type"),
|
|
335
|
+
"required": rel.get("required"),
|
|
336
|
+
"authority_effect": rel.get("authority_effect"),
|
|
337
|
+
"resolution_scope": rel.get("resolution_scope"),
|
|
338
|
+
"target_sections": rel.get("target_sections") or [],
|
|
339
|
+
}
|
|
340
|
+
relationships.append(edge)
|
|
341
|
+
ids = {d["document_id"] for d in documents}
|
|
342
|
+
for edge in relationships:
|
|
343
|
+
if edge["resolution_scope"] == "same_document_set" \
|
|
344
|
+
and edge["target_document_id"] not in ids:
|
|
345
|
+
unresolved_edges.append(edge)
|
|
346
|
+
return {
|
|
347
|
+
"document_count": len(documents),
|
|
348
|
+
"relationship_count": len(relationships),
|
|
349
|
+
"task_catalog": task_catalog,
|
|
350
|
+
"unresolved_same_set_edges": unresolved_edges,
|
|
351
|
+
"documents": documents,
|
|
352
|
+
"relationships": relationships,
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _doc_frontmatter(per_doc: dict[str, Any]) -> dict[str, Any]:
|
|
357
|
+
fm = per_doc.get("fm") or {}
|
|
358
|
+
return fm if isinstance(fm, dict) else {}
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _doc_identity(path: str, fm: dict[str, Any]) -> dict[str, Any]:
|
|
362
|
+
adc = fm.get("ai_document_contract", {})
|
|
363
|
+
routing = fm.get("routing", {})
|
|
364
|
+
return {
|
|
365
|
+
"document_id": adc.get("document_id"),
|
|
366
|
+
"document_role": adc.get("document_role"),
|
|
367
|
+
"document_title": adc.get("document_title"),
|
|
368
|
+
"load_order": routing.get("load_order"),
|
|
369
|
+
"task_catalog": routing.get("task_catalog") or [],
|
|
370
|
+
"read_when_tags": routing.get("read_when_tags") or [],
|
|
371
|
+
"skip_when_tags": routing.get("skip_when_tags") or [],
|
|
372
|
+
"path": path,
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _reading_edges(documents: dict[str, dict[str, Any]]) -> list[tuple[str, str]]:
|
|
377
|
+
edges: list[tuple[str, str]] = []
|
|
378
|
+
ids = set(documents)
|
|
379
|
+
for source_id, doc in documents.items():
|
|
380
|
+
for rel in doc.get("relationships") or []:
|
|
381
|
+
if not isinstance(rel, dict):
|
|
382
|
+
continue
|
|
383
|
+
target_id = rel.get("target_document_id")
|
|
384
|
+
if target_id not in ids:
|
|
385
|
+
continue
|
|
386
|
+
rel_type = rel.get("relationship_type")
|
|
387
|
+
if rel_type == "handover_to":
|
|
388
|
+
edges.append((source_id, target_id))
|
|
389
|
+
elif rel.get("required") or rel_type in {"depends_on", "validates", "projects_to"}:
|
|
390
|
+
edges.append((target_id, source_id))
|
|
391
|
+
return edges
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _topological_reading_order(documents: dict[str, dict[str, Any]]) -> list[str]:
|
|
395
|
+
order_key = lambda did: (
|
|
396
|
+
documents[did].get("routing", {}).get("load_order") or 999999,
|
|
397
|
+
documents[did].get("ai_document_contract", {}).get("document_id") or "",
|
|
398
|
+
)
|
|
399
|
+
outgoing = {did: [] for did in documents}
|
|
400
|
+
indegree = {did: 0 for did in documents}
|
|
401
|
+
for before, after in _reading_edges(documents):
|
|
402
|
+
if after not in outgoing[before]:
|
|
403
|
+
outgoing[before].append(after)
|
|
404
|
+
indegree[after] += 1
|
|
405
|
+
ready = sorted([did for did, count in indegree.items() if count == 0], key=order_key)
|
|
406
|
+
ordered: list[str] = []
|
|
407
|
+
while ready:
|
|
408
|
+
current = ready.pop(0)
|
|
409
|
+
ordered.append(current)
|
|
410
|
+
for nxt in sorted(outgoing[current], key=order_key):
|
|
411
|
+
indegree[nxt] -= 1
|
|
412
|
+
if indegree[nxt] == 0:
|
|
413
|
+
ready.append(nxt)
|
|
414
|
+
ready.sort(key=order_key)
|
|
415
|
+
remaining = [did for did in documents if did not in ordered]
|
|
416
|
+
return ordered + sorted(remaining, key=order_key)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _task_fit(fm: dict[str, Any], task: str | None) -> tuple[bool, str]:
|
|
420
|
+
if not task:
|
|
421
|
+
return True, "no task filter"
|
|
422
|
+
role = fm.get("ai_document_contract", {}).get("document_role")
|
|
423
|
+
if role in {"package_router", "frontmatter_canonical_model_spec"}:
|
|
424
|
+
return True, f"foundational role {role}"
|
|
425
|
+
routing = fm.get("routing", {})
|
|
426
|
+
read_tags = set(routing.get("read_when_tags") or [])
|
|
427
|
+
if task in read_tags:
|
|
428
|
+
return True, f"read_when_tags contains {task}"
|
|
429
|
+
return False, f"read_when_tags does not contain {task}"
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _finding_class(error_id: str) -> str:
|
|
433
|
+
if "RELATIONSHIP" in error_id or "TARGET" in error_id or "LOAD-ORDER" in error_id:
|
|
434
|
+
return "relationship_or_ordering"
|
|
435
|
+
if "SCHEMA" in error_id or "UNKNOWN" in error_id or "MISSING" in error_id:
|
|
436
|
+
return "shape_or_schema"
|
|
437
|
+
if "EVIDENCE" in error_id or "CLAIM" in error_id or "VERIF" in error_id:
|
|
438
|
+
return "claim_or_evidence"
|
|
439
|
+
if "INTENTION" in error_id:
|
|
440
|
+
return "intention"
|
|
441
|
+
return "other"
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _defects_by_class(findings: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
445
|
+
grouped: dict[tuple[str, str], dict[str, Any]] = {}
|
|
446
|
+
for item in findings:
|
|
447
|
+
error_id = str(item.get("error_id") or item.get("finding", {}).get("id") or "UNKNOWN")
|
|
448
|
+
key = (_finding_class(error_id), error_id)
|
|
449
|
+
bucket = grouped.setdefault(key, {
|
|
450
|
+
"class": key[0],
|
|
451
|
+
"finding_id": error_id,
|
|
452
|
+
"count": 0,
|
|
453
|
+
"documents": [],
|
|
454
|
+
"examples": [],
|
|
455
|
+
})
|
|
456
|
+
bucket["count"] += 1
|
|
457
|
+
source = item.get("source_path") or item.get("context", {}).get("source_path")
|
|
458
|
+
if source and source not in bucket["documents"]:
|
|
459
|
+
bucket["documents"].append(source)
|
|
460
|
+
if len(bucket["examples"]) < 3:
|
|
461
|
+
bucket["examples"].append({
|
|
462
|
+
"detail": item.get("finding", {}).get("detail") or item.get("context", {}).get("detail"),
|
|
463
|
+
"source_path": source,
|
|
464
|
+
"line": item.get("line"),
|
|
465
|
+
})
|
|
466
|
+
return sorted(grouped.values(), key=lambda row: (row["class"], row["finding_id"]))
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _available_tasks(per_document: dict[str, Any]) -> list[str]:
|
|
470
|
+
tasks: set[str] = set()
|
|
471
|
+
for per_doc in per_document.values():
|
|
472
|
+
fm = _doc_frontmatter(per_doc)
|
|
473
|
+
if not fm:
|
|
474
|
+
continue
|
|
475
|
+
for tag in fm.get("routing", {}).get("read_when_tags") or []:
|
|
476
|
+
if isinstance(tag, str) and tag:
|
|
477
|
+
tasks.add(tag)
|
|
478
|
+
return sorted(tasks)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def _task_catalog(per_document: dict[str, Any]) -> list[str]:
|
|
482
|
+
catalog: set[str] = set()
|
|
483
|
+
for per_doc in per_document.values():
|
|
484
|
+
fm = _doc_frontmatter(per_doc)
|
|
485
|
+
if not fm:
|
|
486
|
+
continue
|
|
487
|
+
role = fm.get("ai_document_contract", {}).get("document_role")
|
|
488
|
+
if role != "package_router":
|
|
489
|
+
continue
|
|
490
|
+
for tag in fm.get("routing", {}).get("task_catalog") or []:
|
|
491
|
+
if isinstance(tag, str) and tag:
|
|
492
|
+
catalog.add(tag)
|
|
493
|
+
return sorted(catalog)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def validate_brief_task(report: dict[str, Any], task: str | None) -> None:
|
|
497
|
+
if not task:
|
|
498
|
+
return
|
|
499
|
+
available_tasks = _available_tasks(report.get("_per_document") or {})
|
|
500
|
+
task_catalog = _task_catalog(report.get("_per_document") or {})
|
|
501
|
+
if task in available_tasks:
|
|
502
|
+
return
|
|
503
|
+
raise ToolingError(
|
|
504
|
+
"Unknown brief task filter",
|
|
505
|
+
f"--task {task}",
|
|
506
|
+
"The task filter must match a read_when_tags value declared by at least one target document.",
|
|
507
|
+
"Use one of the available_tasks values from the brief output, or add the task to read_when_tags in the intended document.",
|
|
508
|
+
{
|
|
509
|
+
"requested_task": task,
|
|
510
|
+
"available_tasks": available_tasks,
|
|
511
|
+
"task_catalog": task_catalog,
|
|
512
|
+
},
|
|
513
|
+
error_id="FRONTMATTERING-KERNEL-BRIEF-TASK-UNKNOWN",
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def _finding_source_path(item: dict[str, Any]) -> str | None:
|
|
518
|
+
return (
|
|
519
|
+
item.get("source_path")
|
|
520
|
+
or item.get("context", {}).get("source_path")
|
|
521
|
+
or item.get("finding", {}).get("source_path")
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _finding_document_id(item: dict[str, Any]) -> str | None:
|
|
526
|
+
return (
|
|
527
|
+
item.get("document_id")
|
|
528
|
+
or item.get("context", {}).get("document_id")
|
|
529
|
+
or item.get("finding", {}).get("document_id")
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def _scoped_findings(findings: list[dict[str, Any]], documents: dict[str, dict[str, Any]], task: str | None) -> list[dict[str, Any]]:
|
|
534
|
+
if not task:
|
|
535
|
+
return findings
|
|
536
|
+
selected_paths = {str(doc.get("path")) for doc in documents.values()}
|
|
537
|
+
selected_ids = set(documents)
|
|
538
|
+
scoped = []
|
|
539
|
+
for item in findings:
|
|
540
|
+
source_path = _finding_source_path(item)
|
|
541
|
+
document_id = _finding_document_id(item)
|
|
542
|
+
if source_path in selected_paths or document_id in selected_ids:
|
|
543
|
+
scoped.append(item)
|
|
544
|
+
return scoped
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def _trap_census(traps: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
548
|
+
grouped: dict[str, dict[str, Any]] = {}
|
|
549
|
+
for item in traps:
|
|
550
|
+
category = str(item.get("category") or "uncategorized")
|
|
551
|
+
bucket = grouped.setdefault(category, {"category": category, "count": 0, "documents": []})
|
|
552
|
+
bucket["count"] += 1
|
|
553
|
+
doc_id = item.get("document_id")
|
|
554
|
+
if doc_id and doc_id not in bucket["documents"]:
|
|
555
|
+
bucket["documents"].append(doc_id)
|
|
556
|
+
return sorted(grouped.values(), key=lambda row: (-row["count"], row["category"]))
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def _block_census(blocks: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
560
|
+
grouped: dict[tuple[str, str], dict[str, Any]] = {}
|
|
561
|
+
for item in blocks:
|
|
562
|
+
label = str(item.get("condition") or item.get("detail") or item.get("kind") or "block")
|
|
563
|
+
kind = str(item.get("kind") or "block")
|
|
564
|
+
key = (kind, label)
|
|
565
|
+
bucket = grouped.setdefault(key, {"kind": kind, "label": label, "count": 0, "documents": [], "recovery": None})
|
|
566
|
+
bucket["count"] += 1
|
|
567
|
+
doc_id = item.get("document_id") or item.get("source_document_id")
|
|
568
|
+
if doc_id and doc_id not in bucket["documents"]:
|
|
569
|
+
bucket["documents"].append(doc_id)
|
|
570
|
+
if bucket["recovery"] is None:
|
|
571
|
+
bucket["recovery"] = item.get("recovery_action") or item.get("fix")
|
|
572
|
+
return sorted(grouped.values(), key=lambda row: (-row["count"], row["kind"], row["label"]))
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _counter_census(items: list[dict[str, Any]], field: str, label: str) -> list[dict[str, Any]]:
|
|
576
|
+
grouped: dict[str, dict[str, Any]] = {}
|
|
577
|
+
for item in items:
|
|
578
|
+
value = str(item.get(field) or "unspecified")
|
|
579
|
+
bucket = grouped.setdefault(value, {label: value, "count": 0, "documents": []})
|
|
580
|
+
bucket["count"] += 1
|
|
581
|
+
doc_id = item.get("document_id")
|
|
582
|
+
if doc_id and doc_id not in bucket["documents"]:
|
|
583
|
+
bucket["documents"].append(doc_id)
|
|
584
|
+
return sorted(grouped.values(), key=lambda row: (-row["count"], row[label]))
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _trust_projection(doc: dict[str, Any]) -> dict[str, Any]:
|
|
588
|
+
identity = _doc_identity(doc["path"], doc)
|
|
589
|
+
adc = doc.get("ai_document_contract", {})
|
|
590
|
+
boundary = doc.get("claim_boundary_packet", {})
|
|
591
|
+
evidence_refs = doc.get("evidence_references") or []
|
|
592
|
+
digest_bound = [
|
|
593
|
+
item for item in evidence_refs
|
|
594
|
+
if isinstance(item, dict) and item.get("digest_sha256")
|
|
595
|
+
]
|
|
596
|
+
return {
|
|
597
|
+
**identity,
|
|
598
|
+
"lifecycle_state": adc.get("lifecycle_state"),
|
|
599
|
+
"authority": adc.get("authority"),
|
|
600
|
+
"claimed_state": boundary.get("claimed_state"),
|
|
601
|
+
"verifies_count": len(boundary.get("verifies") or []),
|
|
602
|
+
"does_not_verify_count": len(boundary.get("does_not_verify") or []),
|
|
603
|
+
"judgment_left_count": len(boundary.get("judgment_left_to_agent") or []),
|
|
604
|
+
"evidence_reference_count": len(evidence_refs),
|
|
605
|
+
"digest_bound_evidence_count": len(digest_bound),
|
|
606
|
+
"evidence_kinds": sorted({
|
|
607
|
+
str(item.get("evidence_kind"))
|
|
608
|
+
for item in evidence_refs
|
|
609
|
+
if isinstance(item, dict) and item.get("evidence_kind")
|
|
610
|
+
}),
|
|
611
|
+
"evidence_ids": [
|
|
612
|
+
item.get("evidence_id")
|
|
613
|
+
for item in evidence_refs
|
|
614
|
+
if isinstance(item, dict) and item.get("evidence_id")
|
|
615
|
+
],
|
|
616
|
+
"body_tether_freshness": (doc.get("_body_tether") or {}).get("freshness"),
|
|
617
|
+
"summary_trust": (doc.get("_body_tether") or {}).get("summary_trust"),
|
|
618
|
+
"body_tether_reasons": (doc.get("_body_tether") or {}).get("reasons") or [],
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def _trust_census(trust_items: list[dict[str, Any]]) -> dict[str, Any]:
|
|
623
|
+
evidence_kind_counts: dict[str, int] = {}
|
|
624
|
+
for item in trust_items:
|
|
625
|
+
kinds = item.get("evidence_kinds") or []
|
|
626
|
+
if not kinds:
|
|
627
|
+
evidence_kind_counts["none"] = evidence_kind_counts.get("none", 0) + 1
|
|
628
|
+
for kind in kinds:
|
|
629
|
+
evidence_kind_counts[str(kind)] = evidence_kind_counts.get(str(kind), 0) + 1
|
|
630
|
+
return {
|
|
631
|
+
"claimed_state": _counter_census(trust_items, "claimed_state", "claimed_state"),
|
|
632
|
+
"lifecycle_state": _counter_census(trust_items, "lifecycle_state", "lifecycle_state"),
|
|
633
|
+
"authority": _counter_census(trust_items, "authority", "authority"),
|
|
634
|
+
"evidence_kind_counts": dict(sorted(evidence_kind_counts.items(), key=lambda row: (-row[1], row[0]))),
|
|
635
|
+
"documents_without_evidence_references": [
|
|
636
|
+
item.get("document_id")
|
|
637
|
+
for item in trust_items
|
|
638
|
+
if not item.get("evidence_reference_count")
|
|
639
|
+
],
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def _freshness_census(reading_order: list[dict[str, Any]]) -> dict[str, Any]:
|
|
646
|
+
counts: dict[str, int] = {}
|
|
647
|
+
documents: dict[str, list[str]] = {}
|
|
648
|
+
for item in reading_order:
|
|
649
|
+
status = str(item.get("body_tether_freshness") or "UNTETHERED")
|
|
650
|
+
counts[status] = counts.get(status, 0) + 1
|
|
651
|
+
documents.setdefault(status, []).append(str(item.get("document_id")))
|
|
652
|
+
return {"counts": dict(sorted(counts.items())), "documents": documents}
|
|
653
|
+
|
|
654
|
+
def _section_ref(section: str) -> str:
|
|
655
|
+
return f"brief --section {section}"
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _format_more(total: int, shown: int, section: str) -> str | None:
|
|
659
|
+
remaining = total - shown
|
|
660
|
+
if remaining <= 0:
|
|
661
|
+
return None
|
|
662
|
+
return f"+{remaining} more -> {_section_ref(section)}"
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def _sha256_bytes(data: bytes) -> str:
|
|
666
|
+
return hashlib.sha256(data).hexdigest()
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def _display_path(path: str, selection: dict[str, Any]) -> str:
|
|
670
|
+
source = Path(path).resolve()
|
|
671
|
+
root_text = selection.get("project_root")
|
|
672
|
+
if root_text:
|
|
673
|
+
try:
|
|
674
|
+
return source.relative_to(Path(root_text).resolve()).as_posix()
|
|
675
|
+
except ValueError:
|
|
676
|
+
pass
|
|
677
|
+
return source.as_posix()
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def _source_binding(path: str, fm: dict[str, Any], selection: dict[str, Any]) -> dict[str, Any]:
|
|
681
|
+
source = Path(path)
|
|
682
|
+
raw = source.read_bytes() if source.exists() else b""
|
|
683
|
+
fm_text = None
|
|
684
|
+
try:
|
|
685
|
+
decoded = raw.decode("utf-8", errors="strict")
|
|
686
|
+
fm_text, _body, _findings = split_frontmatter(decoded)
|
|
687
|
+
except (UnicodeDecodeError, OSError):
|
|
688
|
+
pass
|
|
689
|
+
adc = fm.get("ai_document_contract") or {}
|
|
690
|
+
return {
|
|
691
|
+
"document_id": adc.get("document_id"),
|
|
692
|
+
"path": _display_path(path, selection),
|
|
693
|
+
"role": adc.get("document_role"),
|
|
694
|
+
"authority": adc.get("authority"),
|
|
695
|
+
"source_sha256": _sha256_bytes(raw),
|
|
696
|
+
"frontmatter_sha256": _sha256_bytes((fm_text or "").encode("utf-8")),
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def _inclusion_reason_codes(fm: dict[str, Any], task: str | None) -> list[str]:
|
|
701
|
+
role = (fm.get("ai_document_contract") or {}).get("document_role")
|
|
702
|
+
if task is None:
|
|
703
|
+
return ["FRONTMATTERING-BRIEF-WHOLE-CORPUS"]
|
|
704
|
+
if role in {"package_router", "frontmatter_canonical_model_spec"}:
|
|
705
|
+
return ["FRONTMATTERING-BRIEF-FOUNDATIONAL-ROLE"]
|
|
706
|
+
if task in set((fm.get("routing") or {}).get("read_when_tags") or []):
|
|
707
|
+
return ["FRONTMATTERING-BRIEF-TASK-TAG-MATCH"]
|
|
708
|
+
return ["FRONTMATTERING-BRIEF-INCLUDED-BY-VALIDATED-ROUTING"]
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def _orientation_projection(
|
|
712
|
+
report: dict[str, Any],
|
|
713
|
+
*,
|
|
714
|
+
task: str | None,
|
|
715
|
+
documents: dict[str, dict[str, Any]],
|
|
716
|
+
skipped: list[dict[str, Any]],
|
|
717
|
+
selected_order: list[str],
|
|
718
|
+
blocks: list[dict[str, Any]],
|
|
719
|
+
scoped_findings: list[dict[str, Any]],
|
|
720
|
+
) -> dict[str, Any]:
|
|
721
|
+
selection = report.get("selection") or {
|
|
722
|
+
"selected_paths_sha256": hashlib.sha256(b"").hexdigest(),
|
|
723
|
+
"selected_path_count": len(documents),
|
|
724
|
+
}
|
|
725
|
+
records: dict[str, dict[str, Any]] = {}
|
|
726
|
+
for doc_id, doc in documents.items():
|
|
727
|
+
binding = _source_binding(doc["path"], doc, selection)
|
|
728
|
+
records[doc_id] = {**binding, "inclusion_reason_codes": _inclusion_reason_codes(doc, task)}
|
|
729
|
+
|
|
730
|
+
superseded_ids: set[str] = set()
|
|
731
|
+
for source in documents.values():
|
|
732
|
+
for rel in source.get("relationships") or []:
|
|
733
|
+
if isinstance(rel, dict) and rel.get("relationship_type") == "supersedes":
|
|
734
|
+
target = rel.get("target_document_id")
|
|
735
|
+
if target in records:
|
|
736
|
+
superseded_ids.add(str(target))
|
|
737
|
+
|
|
738
|
+
authoritative = []
|
|
739
|
+
supporting = []
|
|
740
|
+
non_authoritative = {"advisory", "orientation_only"}
|
|
741
|
+
for doc_id in selected_order:
|
|
742
|
+
record = records[doc_id]
|
|
743
|
+
(supporting if record.get("authority") in non_authoritative else authoritative).append(record)
|
|
744
|
+
|
|
745
|
+
excluded = []
|
|
746
|
+
for item in skipped:
|
|
747
|
+
excluded.append({
|
|
748
|
+
"document_id": item.get("document_id"),
|
|
749
|
+
"path": _display_path(str(item.get("path")), selection),
|
|
750
|
+
"exclusion_reason_codes": ["FRONTMATTERING-BRIEF-TASK-TAG-NOT-MATCHED"],
|
|
751
|
+
})
|
|
752
|
+
|
|
753
|
+
ambiguities = []
|
|
754
|
+
# Authored stop_if clauses describe conditions that would block future work;
|
|
755
|
+
# their presence is not evidence that the condition is currently true.
|
|
756
|
+
# Orientation becomes incomplete only for observed structural blockers.
|
|
757
|
+
for block in blocks:
|
|
758
|
+
kind = block.get("kind")
|
|
759
|
+
if kind not in {"unresolved_required_edge", "binding_dependency_cycle"}:
|
|
760
|
+
continue
|
|
761
|
+
ambiguities.append({
|
|
762
|
+
"reason_code": "FRONTMATTERING-BRIEF-REQUIRED-RELATIONSHIP-UNRESOLVED" if kind == "unresolved_required_edge" else "FRONTMATTERING-BRIEF-BINDING-DEPENDENCY-CYCLE",
|
|
763
|
+
"document_id": block.get("document_id") or block.get("source_document_id"),
|
|
764
|
+
"detail": block.get("detail") or block.get("condition"),
|
|
765
|
+
"target_document_id": block.get("target_document_id"),
|
|
766
|
+
})
|
|
767
|
+
for finding in scoped_findings:
|
|
768
|
+
ambiguities.append({
|
|
769
|
+
"reason_code": "FRONTMATTERING-BRIEF-STRUCTURAL-FINDING",
|
|
770
|
+
"finding_id": finding.get("error_id"),
|
|
771
|
+
"document_id": finding.get("document_id"),
|
|
772
|
+
"path": finding.get("source_path"),
|
|
773
|
+
})
|
|
774
|
+
status = "READY" if report.get("terminal_state") == "GO" and not ambiguities else "CHECKPOINT_INCOMPLETE"
|
|
775
|
+
return {
|
|
776
|
+
"status": status,
|
|
777
|
+
"task": task,
|
|
778
|
+
"selection": {
|
|
779
|
+
"selected_paths_sha256": selection.get("selected_paths_sha256"),
|
|
780
|
+
"selected_path_count": selection.get("selected_path_count", len(documents)),
|
|
781
|
+
},
|
|
782
|
+
"authoritative_documents": authoritative,
|
|
783
|
+
"required_reading_order": [records[doc_id] for doc_id in selected_order],
|
|
784
|
+
"supporting_documents": supporting,
|
|
785
|
+
"superseded_documents": [records[doc_id] for doc_id in selected_order if doc_id in superseded_ids],
|
|
786
|
+
"excluded_documents": excluded,
|
|
787
|
+
"unresolved_ambiguities": ambiguities,
|
|
788
|
+
"source_bindings": [records[doc_id] for doc_id in selected_order],
|
|
789
|
+
"claim_boundary": {
|
|
790
|
+
"verifies": [
|
|
791
|
+
"Live source digests, validated metadata routing, and deterministic inclusion or exclusion reasons.",
|
|
792
|
+
],
|
|
793
|
+
"does_not_verify": [
|
|
794
|
+
"That excluded documents are semantically irrelevant.",
|
|
795
|
+
"Semantic adequacy, factual correctness, external adoption, or release readiness.",
|
|
796
|
+
],
|
|
797
|
+
},
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
def brief_projection(report: dict[str, Any], *, task: str | None = None) -> dict[str, Any]:
|
|
802
|
+
"""Project the already-validated document model into a cold-start hazard brief."""
|
|
803
|
+
per_document = report.get("_per_document") or {}
|
|
804
|
+
documents: dict[str, dict[str, Any]] = {}
|
|
805
|
+
skipped: list[dict[str, Any]] = []
|
|
806
|
+
available_tasks = _available_tasks(per_document)
|
|
807
|
+
task_catalog = _task_catalog(per_document)
|
|
808
|
+
for path, per_doc in sorted(per_document.items()):
|
|
809
|
+
fm = _doc_frontmatter(per_doc)
|
|
810
|
+
if not fm:
|
|
811
|
+
continue
|
|
812
|
+
doc_id = fm.get("ai_document_contract", {}).get("document_id")
|
|
813
|
+
if not doc_id:
|
|
814
|
+
continue
|
|
815
|
+
include, reason = _task_fit(fm, task)
|
|
816
|
+
if include:
|
|
817
|
+
documents[doc_id] = {"path": path, **fm, "_body_tether": per_doc.get("body_tether") or {}}
|
|
818
|
+
else:
|
|
819
|
+
skipped.append({**_doc_identity(path, fm), "skip_reason": reason, "body_tether": per_doc.get("body_tether") or {}})
|
|
820
|
+
|
|
821
|
+
blocks: list[dict[str, Any]] = []
|
|
822
|
+
traps: list[dict[str, Any]] = []
|
|
823
|
+
limits: list[dict[str, Any]] = []
|
|
824
|
+
do_not: list[dict[str, Any]] = []
|
|
825
|
+
trust: list[dict[str, Any]] = []
|
|
826
|
+
|
|
827
|
+
for doc_id in _topological_reading_order(documents):
|
|
828
|
+
doc = documents[doc_id]
|
|
829
|
+
path = doc["path"]
|
|
830
|
+
identity = _doc_identity(path, doc)
|
|
831
|
+
adc = doc.get("ai_document_contract", {})
|
|
832
|
+
trust.append(_trust_projection(doc))
|
|
833
|
+
for item in adc.get("stop_if") or []:
|
|
834
|
+
if isinstance(item, dict):
|
|
835
|
+
blocks.append({**identity, "kind": "stop_if", **item})
|
|
836
|
+
for item in adc.get("must_not_be_used_for") or []:
|
|
837
|
+
do_not.append({**identity, "text": item})
|
|
838
|
+
|
|
839
|
+
intention = doc.get("deepest_intention", {})
|
|
840
|
+
for item in intention.get("not_the_intention") or []:
|
|
841
|
+
if isinstance(item, dict):
|
|
842
|
+
traps.append({**identity, **item})
|
|
843
|
+
else:
|
|
844
|
+
traps.append({**identity, "statement": item})
|
|
845
|
+
|
|
846
|
+
boundary = doc.get("claim_boundary_packet", {})
|
|
847
|
+
for item in boundary.get("does_not_verify") or []:
|
|
848
|
+
limits.append({**identity, "kind": "does_not_verify", "text": item})
|
|
849
|
+
for item in boundary.get("judgment_left_to_agent") or []:
|
|
850
|
+
limits.append({**identity, "kind": "judgment_left_to_agent", "text": item})
|
|
851
|
+
|
|
852
|
+
mesh = report.get("docset_mesh") or {}
|
|
853
|
+
for edge in mesh.get("unresolved_same_set_edges") or []:
|
|
854
|
+
if not edge.get("required"):
|
|
855
|
+
continue
|
|
856
|
+
source_id = edge.get("source_document_id")
|
|
857
|
+
if source_id not in documents:
|
|
858
|
+
continue
|
|
859
|
+
blocks.append({
|
|
860
|
+
"kind": "unresolved_required_edge",
|
|
861
|
+
"document_id": source_id,
|
|
862
|
+
"source_document_id": source_id,
|
|
863
|
+
"target_document_id": edge.get("target_document_id"),
|
|
864
|
+
"detail": "required same-document-set relationship target is unresolved",
|
|
865
|
+
"recovery_action": "Add the target document, correct target_document_id, or change resolution_scope.",
|
|
866
|
+
})
|
|
867
|
+
for item in report.get("findings") or []:
|
|
868
|
+
if str(item.get("error_id") or "") != "FRONTMATTERING-FM-BINDING-DEPENDENCY-CYCLE":
|
|
869
|
+
continue
|
|
870
|
+
blocks.append({
|
|
871
|
+
"kind": "binding_dependency_cycle",
|
|
872
|
+
"detail": item.get("finding", {}).get("detail"),
|
|
873
|
+
"source_path": item.get("source_path"),
|
|
874
|
+
"line": item.get("line"),
|
|
875
|
+
"fix": item.get("fix"),
|
|
876
|
+
})
|
|
877
|
+
|
|
878
|
+
selected_order = _topological_reading_order(documents)
|
|
879
|
+
scoped_findings = _scoped_findings(report.get("findings") or [], documents, task)
|
|
880
|
+
reading_order = [
|
|
881
|
+
{
|
|
882
|
+
**_doc_identity(documents[doc_id]["path"], documents[doc_id]),
|
|
883
|
+
"agent_summary": documents[doc_id].get("routing", {}).get("agent_summary"),
|
|
884
|
+
"primary_action_expected": documents[doc_id].get("ai_document_contract", {}).get("primary_action_expected"),
|
|
885
|
+
"body_tether_freshness": (documents[doc_id].get("_body_tether") or {}).get("freshness"),
|
|
886
|
+
"summary_trust": (documents[doc_id].get("_body_tether") or {}).get("summary_trust"),
|
|
887
|
+
"body_tether_reasons": (documents[doc_id].get("_body_tether") or {}).get("reasons") or [],
|
|
888
|
+
}
|
|
889
|
+
for doc_id in selected_order
|
|
890
|
+
]
|
|
891
|
+
orientation = _orientation_projection(
|
|
892
|
+
report, task=task, documents=documents, skipped=skipped, selected_order=selected_order,
|
|
893
|
+
blocks=blocks, scoped_findings=scoped_findings,
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
return {
|
|
897
|
+
"schema_version": "frontmattering_brief/v0_3",
|
|
898
|
+
"orientation": orientation,
|
|
899
|
+
"selection": report.get("selection") or {},
|
|
900
|
+
"kernel_version": report.get("kernel_version"),
|
|
901
|
+
"terminal_state": report.get("terminal_state"),
|
|
902
|
+
"task": task,
|
|
903
|
+
"task_filter": {
|
|
904
|
+
"requested_task": task,
|
|
905
|
+
"available_tasks": available_tasks,
|
|
906
|
+
"task_catalog": task_catalog,
|
|
907
|
+
"scope": "selected_read_set" if task else "whole_corpus",
|
|
908
|
+
"selected_document_ids": selected_order,
|
|
909
|
+
"skipped_document_ids": [item.get("document_id") for item in skipped if item.get("document_id")],
|
|
910
|
+
"selected_document_count": len(documents),
|
|
911
|
+
"skipped_document_count": len(skipped),
|
|
912
|
+
},
|
|
913
|
+
"document_count": len(documents),
|
|
914
|
+
"skipped_count": len(skipped),
|
|
915
|
+
"blocks": blocks,
|
|
916
|
+
"block_census": _block_census(blocks),
|
|
917
|
+
"traps": traps,
|
|
918
|
+
"trap_census": _trap_census(traps),
|
|
919
|
+
"limits": limits,
|
|
920
|
+
"do_not": do_not,
|
|
921
|
+
"trust": trust,
|
|
922
|
+
"trust_census": _trust_census(trust),
|
|
923
|
+
"freshness_census": _freshness_census(reading_order),
|
|
924
|
+
"reading_order": reading_order,
|
|
925
|
+
"skipped_documents": skipped,
|
|
926
|
+
"available_tasks": available_tasks,
|
|
927
|
+
"task_catalog": task_catalog,
|
|
928
|
+
"defect_scope": {
|
|
929
|
+
"scope": "selected_read_set" if task else "whole_corpus",
|
|
930
|
+
"scoped_finding_count": len(scoped_findings),
|
|
931
|
+
"corpus_finding_count": len(report.get("findings") or []),
|
|
932
|
+
},
|
|
933
|
+
"defects_by_class": _defects_by_class(scoped_findings),
|
|
934
|
+
"section_hints": {
|
|
935
|
+
"blocks": _section_ref("blocks"),
|
|
936
|
+
"traps": _section_ref("traps"),
|
|
937
|
+
"limits": _section_ref("limits"),
|
|
938
|
+
"do_not": _section_ref("do-not"),
|
|
939
|
+
"trust": _section_ref("trust"),
|
|
940
|
+
"reading_order": _section_ref("reading-order"),
|
|
941
|
+
"defects_by_class": _section_ref("defects"),
|
|
942
|
+
},
|
|
943
|
+
"claim_boundary": {
|
|
944
|
+
"verifies": [
|
|
945
|
+
"This is a read-only projection of validated frontmatter and findings.",
|
|
946
|
+
],
|
|
947
|
+
"does_not_verify": [
|
|
948
|
+
"Semantic adequacy of document bodies.",
|
|
949
|
+
"That authored hazards are complete.",
|
|
950
|
+
"Implementation, adoption, release, or production readiness.",
|
|
951
|
+
],
|
|
952
|
+
},
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def _render_brief_summary(brief: dict[str, Any]) -> list[str]:
|
|
957
|
+
orientation = brief.get("orientation") or {}
|
|
958
|
+
selection = orientation.get("selection") or {}
|
|
959
|
+
required = orientation.get("required_reading_order") or []
|
|
960
|
+
chain = " -> ".join(
|
|
961
|
+
f"{item.get('document_id')}[{item.get('body_tether_freshness') or 'UNTETHERED'}]"
|
|
962
|
+
for item in required
|
|
963
|
+
) or "none"
|
|
964
|
+
lines = [
|
|
965
|
+
"# Frontmattering Cold-Start Brief",
|
|
966
|
+
"",
|
|
967
|
+
f"STATUS {orientation.get('status') or 'CHECKPOINT_INCOMPLETE'}",
|
|
968
|
+
f"TASK {orientation.get('task') or 'none'}",
|
|
969
|
+
f"SELECT {selection.get('selected_path_count', 0)} path(s) digest={selection.get('selected_paths_sha256')}",
|
|
970
|
+
f"READ {chain}",
|
|
971
|
+
f"BLOCKS {len(orientation.get('unresolved_ambiguities') or [])}",
|
|
972
|
+
"CLAIM metadata-derived orientation only; semantic adequacy is not verified",
|
|
973
|
+
"",
|
|
974
|
+
(
|
|
975
|
+
f"BRIEF - {brief.get('document_count')} docs - {brief.get('terminal_state')} - "
|
|
976
|
+
f"task: {brief.get('task') or 'none'}"
|
|
977
|
+
),
|
|
978
|
+
"",
|
|
979
|
+
]
|
|
980
|
+
|
|
981
|
+
block_census = brief.get("block_census") or []
|
|
982
|
+
if block_census:
|
|
983
|
+
block_total = sum(int(item.get("count") or 0) for item in block_census)
|
|
984
|
+
by_kind: dict[str, int] = {}
|
|
985
|
+
for item in block_census:
|
|
986
|
+
by_kind[str(item.get("kind") or "block")] = by_kind.get(str(item.get("kind") or "block"), 0) + int(item.get("count") or 0)
|
|
987
|
+
kind_text = " | ".join(f"{kind} x{count}" for kind, count in sorted(by_kind.items()))
|
|
988
|
+
lines.append(f"BLOCKS {block_total} item(s): {kind_text} -> {_section_ref('blocks')}")
|
|
989
|
+
repeated = [item for item in block_census if int(item.get("count") or 0) > 1]
|
|
990
|
+
if repeated:
|
|
991
|
+
item = repeated[0]
|
|
992
|
+
lines.append(f" top repeat: {item.get('label')} x{item.get('count')}")
|
|
993
|
+
else:
|
|
994
|
+
lines.append("BLOCKS none")
|
|
995
|
+
|
|
996
|
+
trap_census = brief.get("trap_census") or []
|
|
997
|
+
if trap_census:
|
|
998
|
+
rendered = [
|
|
999
|
+
f"{item.get('category')} x{item.get('count')}"
|
|
1000
|
+
for item in trap_census[:SUMMARY_ITEM_LIMIT]
|
|
1001
|
+
]
|
|
1002
|
+
lines.append("TRAPS " + " | ".join(rendered) + f" -> {_section_ref('traps')}")
|
|
1003
|
+
more = _format_more(len(trap_census), SUMMARY_ITEM_LIMIT, "traps")
|
|
1004
|
+
if more:
|
|
1005
|
+
lines.append(f" {more}")
|
|
1006
|
+
else:
|
|
1007
|
+
lines.append("TRAPS none")
|
|
1008
|
+
|
|
1009
|
+
limit_count = len(brief.get("limits") or [])
|
|
1010
|
+
if limit_count:
|
|
1011
|
+
lines.append(f"LIMITS {limit_count} item(s) across the selected READ set -> {_section_ref('limits')}")
|
|
1012
|
+
else:
|
|
1013
|
+
lines.append("LIMITS none")
|
|
1014
|
+
|
|
1015
|
+
do_not_count = len(brief.get("do_not") or [])
|
|
1016
|
+
if do_not_count:
|
|
1017
|
+
lines.append(f"DO NOT {do_not_count} item(s) across the selected READ set -> {_section_ref('do-not')}")
|
|
1018
|
+
else:
|
|
1019
|
+
lines.append("DO NOT none")
|
|
1020
|
+
|
|
1021
|
+
trust_census = brief.get("trust_census") or {}
|
|
1022
|
+
claimed = trust_census.get("claimed_state") or []
|
|
1023
|
+
if claimed:
|
|
1024
|
+
claimed_text = " | ".join(
|
|
1025
|
+
f"{item.get('claimed_state')} x{item.get('count')}"
|
|
1026
|
+
for item in claimed[:SUMMARY_ITEM_LIMIT]
|
|
1027
|
+
)
|
|
1028
|
+
evidence_counts = trust_census.get("evidence_kind_counts") or {}
|
|
1029
|
+
evidence_text = " | ".join(
|
|
1030
|
+
f"{kind} x{count}"
|
|
1031
|
+
for kind, count in list(evidence_counts.items())[:SUMMARY_ITEM_LIMIT]
|
|
1032
|
+
) or "none"
|
|
1033
|
+
lines.append(f"TRUST {claimed_text}; evidence: {evidence_text} -> {_section_ref('trust')}")
|
|
1034
|
+
else:
|
|
1035
|
+
lines.append("TRUST none")
|
|
1036
|
+
|
|
1037
|
+
freshness = brief.get("freshness_census") or {}
|
|
1038
|
+
freshness_counts = freshness.get("counts") or {}
|
|
1039
|
+
if freshness_counts:
|
|
1040
|
+
parts = [f"{status} x{count}" for status, count in freshness_counts.items()]
|
|
1041
|
+
trust_hint = "summary_trust=do_not_rely_without_reading_body" if freshness_counts.get("STALE") else "summary_trust=fresh_or_orientation"
|
|
1042
|
+
lines.append(f"FRESH {' | '.join(parts)}; {trust_hint} -> {_section_ref('reading-order')}")
|
|
1043
|
+
else:
|
|
1044
|
+
lines.append("FRESH none")
|
|
1045
|
+
|
|
1046
|
+
defects = brief.get("defects_by_class") or []
|
|
1047
|
+
if defects:
|
|
1048
|
+
defect_scope = (brief.get("defect_scope") or {}).get("scope") or "unknown_scope"
|
|
1049
|
+
lines.append(f"DEFECTS {len(defects)} class(es) [{defect_scope}]")
|
|
1050
|
+
for item in defects[:SUMMARY_ITEM_LIMIT]:
|
|
1051
|
+
lines.append(
|
|
1052
|
+
f" {item.get('class')} / {item.get('finding_id')}: "
|
|
1053
|
+
f"{item.get('count')} instance(s), {len(item.get('documents') or [])} document(s)"
|
|
1054
|
+
)
|
|
1055
|
+
more = _format_more(len(defects), SUMMARY_ITEM_LIMIT, "defects")
|
|
1056
|
+
if more:
|
|
1057
|
+
lines.append(f" {more}")
|
|
1058
|
+
else:
|
|
1059
|
+
lines.append("DEFECTS none")
|
|
1060
|
+
|
|
1061
|
+
reading_order = brief.get("reading_order") or []
|
|
1062
|
+
read_chain = " -> ".join(
|
|
1063
|
+
f"{doc.get('document_id')}[{doc.get('body_tether_freshness') or 'UNTETHERED'}]({doc.get('load_order')})"
|
|
1064
|
+
for doc in reading_order[:SUMMARY_READING_LIMIT]
|
|
1065
|
+
)
|
|
1066
|
+
if read_chain:
|
|
1067
|
+
lines.append(f"READ {read_chain}")
|
|
1068
|
+
more = _format_more(len(reading_order), SUMMARY_READING_LIMIT, "reading-order")
|
|
1069
|
+
if more:
|
|
1070
|
+
lines.append(f" {more}")
|
|
1071
|
+
if not reading_order:
|
|
1072
|
+
lines.append("READ none")
|
|
1073
|
+
|
|
1074
|
+
task_filter = brief.get("task_filter") or {}
|
|
1075
|
+
if brief.get("task"):
|
|
1076
|
+
lines.append(
|
|
1077
|
+
"SCOPE "
|
|
1078
|
+
f"--task {brief.get('task')} selected {task_filter.get('selected_document_count')} "
|
|
1079
|
+
f"doc(s), skipped {task_filter.get('skipped_document_count')} -> {_section_ref('reading-order')}"
|
|
1080
|
+
)
|
|
1081
|
+
else:
|
|
1082
|
+
tasks = brief.get("available_tasks") or []
|
|
1083
|
+
if tasks:
|
|
1084
|
+
lines.append("SCOPE no --task given; scope with: " + " | ".join(f"--task {task}" for task in tasks))
|
|
1085
|
+
else:
|
|
1086
|
+
lines.append("SCOPE no read_when_tags found in this document set")
|
|
1087
|
+
|
|
1088
|
+
boundary = brief.get("claim_boundary") or {}
|
|
1089
|
+
verifies = len(boundary.get("verifies") or [])
|
|
1090
|
+
does_not_verify = len(boundary.get("does_not_verify") or [])
|
|
1091
|
+
lines.append(f"CLAIM verifies x{verifies}; does_not_verify x{does_not_verify} -> {_section_ref('claim-boundary')}")
|
|
1092
|
+
return lines
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
def _render_brief_section(brief: dict[str, Any], section: str) -> list[str]:
|
|
1096
|
+
title = section.replace("_", " ").title()
|
|
1097
|
+
lines = [
|
|
1098
|
+
"# Frontmattering Cold-Start Brief",
|
|
1099
|
+
"",
|
|
1100
|
+
f"## {title}",
|
|
1101
|
+
"",
|
|
1102
|
+
]
|
|
1103
|
+
if section == "summary":
|
|
1104
|
+
return _render_brief_summary(brief)
|
|
1105
|
+
if section == "blocks":
|
|
1106
|
+
for item in brief.get("blocks") or []:
|
|
1107
|
+
label = item.get("condition_id") or item.get("target_document_id") or item.get("kind")
|
|
1108
|
+
detail = item.get("condition") or item.get("detail") or ""
|
|
1109
|
+
recovery = item.get("recovery_action") or item.get("fix")
|
|
1110
|
+
lines.append(f"- {label}: {detail} [{item.get('document_id') or item.get('source_path')}]")
|
|
1111
|
+
if recovery:
|
|
1112
|
+
lines.append(f" recovery: {recovery}")
|
|
1113
|
+
if not brief.get("blocks"):
|
|
1114
|
+
lines.append("- none")
|
|
1115
|
+
elif section == "traps":
|
|
1116
|
+
for item in brief.get("traps") or []:
|
|
1117
|
+
category = item.get("category") or "uncategorized"
|
|
1118
|
+
statement = item.get("statement") or item.get("text") or ""
|
|
1119
|
+
tether = item.get("confusion_tether")
|
|
1120
|
+
lines.append(f"- {category}: {statement} [{item.get('document_id')}]")
|
|
1121
|
+
if tether:
|
|
1122
|
+
lines.append(f" tether: {tether}")
|
|
1123
|
+
if not brief.get("traps"):
|
|
1124
|
+
lines.append("- none")
|
|
1125
|
+
elif section == "limits":
|
|
1126
|
+
for item in brief.get("limits") or []:
|
|
1127
|
+
lines.append(f"- {item.get('kind')}: {item.get('text')} [{item.get('document_id')}]")
|
|
1128
|
+
if not brief.get("limits"):
|
|
1129
|
+
lines.append("- none")
|
|
1130
|
+
elif section == "do_not":
|
|
1131
|
+
for item in brief.get("do_not") or []:
|
|
1132
|
+
lines.append(f"- {item.get('text')} [{item.get('document_id')}]")
|
|
1133
|
+
if not brief.get("do_not"):
|
|
1134
|
+
lines.append("- none")
|
|
1135
|
+
elif section == "trust":
|
|
1136
|
+
trust_census = brief.get("trust_census") or {}
|
|
1137
|
+
lines.append("### Claimed State")
|
|
1138
|
+
for item in trust_census.get("claimed_state") or []:
|
|
1139
|
+
lines.append(
|
|
1140
|
+
f"- {item.get('claimed_state')}: {item.get('count')} document(s) "
|
|
1141
|
+
f"{item.get('documents') or []}"
|
|
1142
|
+
)
|
|
1143
|
+
if not trust_census.get("claimed_state"):
|
|
1144
|
+
lines.append("- none")
|
|
1145
|
+
lines.append("")
|
|
1146
|
+
lines.append("### Evidence References")
|
|
1147
|
+
evidence_counts = trust_census.get("evidence_kind_counts") or {}
|
|
1148
|
+
if evidence_counts:
|
|
1149
|
+
for kind, count in evidence_counts.items():
|
|
1150
|
+
lines.append(f"- {kind}: {count} document(s)")
|
|
1151
|
+
else:
|
|
1152
|
+
lines.append("- none")
|
|
1153
|
+
missing = trust_census.get("documents_without_evidence_references") or []
|
|
1154
|
+
if missing:
|
|
1155
|
+
lines.append(f"- documents_without_evidence_references: {missing}")
|
|
1156
|
+
lines.append("")
|
|
1157
|
+
lines.append("### Documents")
|
|
1158
|
+
for item in brief.get("trust") or []:
|
|
1159
|
+
lines.append(
|
|
1160
|
+
f"- {item.get('document_id')}: claimed_state={item.get('claimed_state')}, "
|
|
1161
|
+
f"lifecycle={item.get('lifecycle_state')}, authority={item.get('authority')}, "
|
|
1162
|
+
f"verifies={item.get('verifies_count')}, does_not_verify={item.get('does_not_verify_count')}, "
|
|
1163
|
+
f"judgment_left={item.get('judgment_left_count')}, "
|
|
1164
|
+
f"evidence_refs={item.get('evidence_reference_count')}, "
|
|
1165
|
+
f"digest_bound={item.get('digest_bound_evidence_count')}"
|
|
1166
|
+
)
|
|
1167
|
+
if not brief.get("trust"):
|
|
1168
|
+
lines.append("- none")
|
|
1169
|
+
elif section == "reading_order":
|
|
1170
|
+
for index, doc in enumerate(brief.get("reading_order") or [], 1):
|
|
1171
|
+
lines.append(
|
|
1172
|
+
f"{index}. {doc.get('document_id')} | role={doc.get('document_role')} "
|
|
1173
|
+
f"| load_order={doc.get('load_order')} | path={doc.get('path')}"
|
|
1174
|
+
)
|
|
1175
|
+
lines.append(
|
|
1176
|
+
f" freshness={doc.get('body_tether_freshness') or 'UNTETHERED'} "
|
|
1177
|
+
f"summary_trust={doc.get('summary_trust') or 'orientation_only'}"
|
|
1178
|
+
)
|
|
1179
|
+
if doc.get("body_tether_reasons"):
|
|
1180
|
+
lines.append(f" freshness_reasons={doc.get('body_tether_reasons')}")
|
|
1181
|
+
if doc.get("agent_summary"):
|
|
1182
|
+
prefix = "summary_untrusted" if doc.get("body_tether_freshness") == "STALE" else "summary"
|
|
1183
|
+
lines.append(f" {prefix}: {doc.get('agent_summary')}")
|
|
1184
|
+
lines.append(
|
|
1185
|
+
f" read_when_tags={doc.get('read_when_tags') or []} "
|
|
1186
|
+
f"skip_when_tags={doc.get('skip_when_tags') or []}"
|
|
1187
|
+
)
|
|
1188
|
+
if not brief.get("reading_order"):
|
|
1189
|
+
lines.append("- none")
|
|
1190
|
+
elif section == "defects_by_class":
|
|
1191
|
+
for item in brief.get("defects_by_class") or []:
|
|
1192
|
+
lines.append(
|
|
1193
|
+
f"- {item.get('class')} / {item.get('finding_id')}: "
|
|
1194
|
+
f"{item.get('count')} instance(s), {len(item.get('documents') or [])} document(s)"
|
|
1195
|
+
)
|
|
1196
|
+
if not brief.get("defects_by_class"):
|
|
1197
|
+
lines.append("- none")
|
|
1198
|
+
elif section == "claim_boundary":
|
|
1199
|
+
boundary = brief.get("claim_boundary") or {}
|
|
1200
|
+
for item in boundary.get("verifies") or []:
|
|
1201
|
+
lines.append(f"- verifies: {item}")
|
|
1202
|
+
for item in boundary.get("does_not_verify") or []:
|
|
1203
|
+
lines.append(f"- does_not_verify: {item}")
|
|
1204
|
+
else:
|
|
1205
|
+
raise InternalBugError(
|
|
1206
|
+
"Unknown brief section",
|
|
1207
|
+
where="kernel_reporting._render_brief_section",
|
|
1208
|
+
context={"section": section},
|
|
1209
|
+
)
|
|
1210
|
+
return lines
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
def render_brief_markdown(brief: dict[str, Any], *, section: str = "summary") -> str:
|
|
1214
|
+
section_key = BRIEF_SECTION_ALIASES.get(section)
|
|
1215
|
+
if section_key is None:
|
|
1216
|
+
raise InternalBugError(
|
|
1217
|
+
"Unknown brief section",
|
|
1218
|
+
where="kernel_reporting.render_brief_markdown",
|
|
1219
|
+
context={"section": section, "allowed": sorted(BRIEF_SECTION_ALIASES)},
|
|
1220
|
+
)
|
|
1221
|
+
lines = _render_brief_section(brief, section_key)
|
|
1222
|
+
lines.append("")
|
|
1223
|
+
return "\n".join(lines)
|
|
1224
|
+
|
|
1225
|
+
|
|
1226
|
+
def render_brief_report(
|
|
1227
|
+
report: dict[str, Any],
|
|
1228
|
+
fmt: str,
|
|
1229
|
+
*,
|
|
1230
|
+
task: str | None = None,
|
|
1231
|
+
section: str = "summary",
|
|
1232
|
+
) -> str:
|
|
1233
|
+
validate_brief_task(report, task)
|
|
1234
|
+
brief = brief_projection(report, task=task)
|
|
1235
|
+
if fmt == "json":
|
|
1236
|
+
brief["selected_section"] = BRIEF_SECTION_ALIASES.get(section, section)
|
|
1237
|
+
return render_json(brief)
|
|
1238
|
+
if fmt == "markdown":
|
|
1239
|
+
return render_brief_markdown(brief, section=section)
|
|
1240
|
+
raise InternalBugError(
|
|
1241
|
+
"Unknown brief report format",
|
|
1242
|
+
where="kernel_reporting.render_brief_report",
|
|
1243
|
+
context={"format": fmt},
|
|
1244
|
+
)
|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
def validation_report(
|
|
1248
|
+
*,
|
|
1249
|
+
target_paths: list[Path],
|
|
1250
|
+
auth: Any,
|
|
1251
|
+
result: dict[str, Any],
|
|
1252
|
+
) -> dict[str, Any]:
|
|
1253
|
+
findings = list(result.get("findings") or [])
|
|
1254
|
+
errors = []
|
|
1255
|
+
for item in findings:
|
|
1256
|
+
fid, detail = item
|
|
1257
|
+
source_path = getattr(item, "source_path", None) or source_for_finding(result, item)
|
|
1258
|
+
document_id = getattr(item, "document_id", None) or _document_id_for_source(result, source_path)
|
|
1259
|
+
err = finding_error(
|
|
1260
|
+
fid,
|
|
1261
|
+
detail,
|
|
1262
|
+
source_path=source_path,
|
|
1263
|
+
spec_path=str(auth.spec_path),
|
|
1264
|
+
spec_sha256=auth.spec_sha256,
|
|
1265
|
+
field_path=getattr(item, "field_path", None),
|
|
1266
|
+
line=getattr(item, "line", None),
|
|
1267
|
+
line_text=getattr(item, "line_text", None),
|
|
1268
|
+
document_id=document_id,
|
|
1269
|
+
).to_dict()
|
|
1270
|
+
ctx = err.get("context", {})
|
|
1271
|
+
err["source_path"] = ctx.get("source_path")
|
|
1272
|
+
err["field_path"] = ctx.get("field_path")
|
|
1273
|
+
err["line"] = ctx.get("line")
|
|
1274
|
+
err["line_text"] = ctx.get("line_text")
|
|
1275
|
+
err["document_id"] = document_id
|
|
1276
|
+
err["finding"] = {
|
|
1277
|
+
"id": fid,
|
|
1278
|
+
"detail": detail,
|
|
1279
|
+
"source_path": ctx.get("source_path"),
|
|
1280
|
+
"field_path": ctx.get("field_path"),
|
|
1281
|
+
"line": ctx.get("line"),
|
|
1282
|
+
"document_id": err.get("document_id"),
|
|
1283
|
+
}
|
|
1284
|
+
errors.append(err)
|
|
1285
|
+
affected = sorted({
|
|
1286
|
+
str(e.get("context", {}).get("source_path"))
|
|
1287
|
+
for e in errors
|
|
1288
|
+
if e.get("context", {}).get("source_path")
|
|
1289
|
+
})
|
|
1290
|
+
terminal = "GO" if not errors else "VIOLATED"
|
|
1291
|
+
return {
|
|
1292
|
+
"kernel_version": "frontmattering_frontmatter_kernel/v0_1_5",
|
|
1293
|
+
"spec_path": str(auth.spec_path),
|
|
1294
|
+
"spec_sha256": auth.spec_sha256,
|
|
1295
|
+
"target_paths": [str(p) for p in target_paths],
|
|
1296
|
+
"terminal_state": terminal,
|
|
1297
|
+
"validation_terminal_state": terminal,
|
|
1298
|
+
"finding_count": len(errors),
|
|
1299
|
+
"findings": errors,
|
|
1300
|
+
"_per_document": result.get("per_document") or {},
|
|
1301
|
+
"docset_mesh": docset_mesh(result),
|
|
1302
|
+
"affected_documents": affected,
|
|
1303
|
+
"verifies": [
|
|
1304
|
+
"The kernel executed v0.1.5 frontmatter validation over the listed targets.",
|
|
1305
|
+
],
|
|
1306
|
+
"does_not_verify": [
|
|
1307
|
+
"Semantic adequacy of the document body.",
|
|
1308
|
+
"Adoption by an external authority.",
|
|
1309
|
+
"Production readiness.",
|
|
1310
|
+
"Compatibility with any non-v0.1.5 schema.",
|
|
1311
|
+
],
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
def _jsonable(value: Any) -> Any:
|
|
1316
|
+
if value is None or isinstance(value, (str, int, float, bool)):
|
|
1317
|
+
return value
|
|
1318
|
+
if isinstance(value, dict):
|
|
1319
|
+
return {str(key): _jsonable(inner) for key, inner in value.items()}
|
|
1320
|
+
if isinstance(value, (list, tuple)):
|
|
1321
|
+
return [_jsonable(item) for item in value]
|
|
1322
|
+
if isinstance(value, set):
|
|
1323
|
+
converted = [_jsonable(item) for item in value]
|
|
1324
|
+
return sorted(converted, key=lambda item: json.dumps(item, sort_keys=True, default=str))
|
|
1325
|
+
if isinstance(value, Path):
|
|
1326
|
+
return str(value)
|
|
1327
|
+
if hasattr(value, "to_dict"):
|
|
1328
|
+
return _jsonable(value.to_dict())
|
|
1329
|
+
if hasattr(value, "_asdict"):
|
|
1330
|
+
return _jsonable(value._asdict())
|
|
1331
|
+
if hasattr(value, "__dict__"):
|
|
1332
|
+
return _jsonable(vars(value))
|
|
1333
|
+
return str(value)
|
|
1334
|
+
|
|
1335
|
+
|
|
1336
|
+
def render_json(report: dict[str, Any]) -> str:
|
|
1337
|
+
return json.dumps(_jsonable(report), indent=2, sort_keys=True) + "\n"
|
|
1338
|
+
|
|
1339
|
+
|
|
1340
|
+
def render_markdown(report: dict[str, Any]) -> str:
|
|
1341
|
+
lines = [
|
|
1342
|
+
"# Frontmattering frontmatter validation report",
|
|
1343
|
+
"",
|
|
1344
|
+
"## Summary",
|
|
1345
|
+
"",
|
|
1346
|
+
f"- validation_terminal_state: {report.get('validation_terminal_state', report['terminal_state'])}",
|
|
1347
|
+
"- claim_ceiling: validation only - not adoption, release, or production readiness",
|
|
1348
|
+
f"- finding_count: {report['finding_count']}",
|
|
1349
|
+
f"- spec: {report['spec_path']}",
|
|
1350
|
+
f"- spec_sha256: {report['spec_sha256']}",
|
|
1351
|
+
"",
|
|
1352
|
+
]
|
|
1353
|
+
findings = list(report.get("findings") or [])
|
|
1354
|
+
if findings:
|
|
1355
|
+
lines += ["## Fix First", ""]
|
|
1356
|
+
for item in sorted(findings, key=_priority)[:5]:
|
|
1357
|
+
lines.append(f"- {item['error_id']}: {item['fix']}")
|
|
1358
|
+
lines.append("")
|
|
1359
|
+
lines += ["## Findings", ""]
|
|
1360
|
+
for i, item in enumerate(sorted(findings, key=_priority), 1):
|
|
1361
|
+
lines += [
|
|
1362
|
+
f"### {i}. {item['error_id']}",
|
|
1363
|
+
"",
|
|
1364
|
+
f"- what: {item['what']}",
|
|
1365
|
+
f"- where: {item['where']}",
|
|
1366
|
+
f"- source: {item.get('source_path') or 'docset'}",
|
|
1367
|
+
f"- line: {item.get('line') or 'unknown'}",
|
|
1368
|
+
f"- why: {item['why']}",
|
|
1369
|
+
f"- fix: {item['fix']}",
|
|
1370
|
+
f"- context: `{json.dumps(item.get('context', {}), sort_keys=True)}`",
|
|
1371
|
+
"",
|
|
1372
|
+
]
|
|
1373
|
+
else:
|
|
1374
|
+
lines += ["## Findings", "", "No findings.", ""]
|
|
1375
|
+
lines += ["## Affected Documents", ""]
|
|
1376
|
+
if report.get("affected_documents"):
|
|
1377
|
+
lines += [f"- {p}" for p in report["affected_documents"]]
|
|
1378
|
+
else:
|
|
1379
|
+
lines.append("- none")
|
|
1380
|
+
mesh = report.get("docset_mesh") or {}
|
|
1381
|
+
lines += ["", "## Document Mesh", ""]
|
|
1382
|
+
lines.append(f"- documents: {mesh.get('document_count', 0)}")
|
|
1383
|
+
lines.append(f"- relationships: {mesh.get('relationship_count', 0)}")
|
|
1384
|
+
unresolved = mesh.get("unresolved_same_set_edges") or []
|
|
1385
|
+
lines.append(f"- unresolved_same_set_edges: {len(unresolved)}")
|
|
1386
|
+
for edge in unresolved[:10]:
|
|
1387
|
+
lines.append(
|
|
1388
|
+
"- unresolved: "
|
|
1389
|
+
f"{edge.get('source_document_id')} -> {edge.get('target_document_id')} "
|
|
1390
|
+
f"({edge.get('relationship_type')})"
|
|
1391
|
+
)
|
|
1392
|
+
lines += ["", "## Raw Evidence", ""]
|
|
1393
|
+
for p in report.get("target_paths") or []:
|
|
1394
|
+
lines.append(f"- target: {p}")
|
|
1395
|
+
lines += ["", "## Claim Boundary", ""]
|
|
1396
|
+
lines += [f"- verifies: {v}" for v in report.get("verifies") or []]
|
|
1397
|
+
lines += [f"- does_not_verify: {v}" for v in report.get("does_not_verify") or []]
|
|
1398
|
+
lines += [
|
|
1399
|
+
"",
|
|
1400
|
+
"VERDICT: "
|
|
1401
|
+
f"validation_terminal_state={report.get('validation_terminal_state', report['terminal_state'])} "
|
|
1402
|
+
"(validation only; not adoption, release, or production readiness)",
|
|
1403
|
+
]
|
|
1404
|
+
return "\n".join(lines)
|
|
1405
|
+
|
|
1406
|
+
|
|
1407
|
+
def mesh_only_report(report: dict[str, Any]) -> dict[str, Any]:
|
|
1408
|
+
return {
|
|
1409
|
+
"kernel_version": report.get("kernel_version"),
|
|
1410
|
+
"terminal_state": report.get("terminal_state"),
|
|
1411
|
+
"validation_terminal_state": report.get("validation_terminal_state", report.get("terminal_state")),
|
|
1412
|
+
"finding_count": report.get("finding_count"),
|
|
1413
|
+
"spec_path": report.get("spec_path"),
|
|
1414
|
+
"spec_sha256": report.get("spec_sha256"),
|
|
1415
|
+
"docset_mesh": report.get("docset_mesh") or {},
|
|
1416
|
+
"selection": report.get("selection") or {},
|
|
1417
|
+
"does_not_verify": report.get("does_not_verify") or [],
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
def render_mesh_markdown(report: dict[str, Any]) -> str:
|
|
1422
|
+
mesh = report.get("docset_mesh") or {}
|
|
1423
|
+
lines = [
|
|
1424
|
+
"# Frontmattering frontmatter document mesh",
|
|
1425
|
+
"",
|
|
1426
|
+
"## Summary",
|
|
1427
|
+
"",
|
|
1428
|
+
f"- validation_terminal_state: {report.get('validation_terminal_state', report.get('terminal_state'))}",
|
|
1429
|
+
"- claim_ceiling: validation only - not adoption, release, or production readiness",
|
|
1430
|
+
f"- finding_count: {report.get('finding_count')}",
|
|
1431
|
+
f"- documents: {mesh.get('document_count', 0)}",
|
|
1432
|
+
f"- relationships: {mesh.get('relationship_count', 0)}",
|
|
1433
|
+
f"- unresolved_same_set_edges: {len(mesh.get('unresolved_same_set_edges') or [])}",
|
|
1434
|
+
"",
|
|
1435
|
+
"## Documents",
|
|
1436
|
+
"",
|
|
1437
|
+
]
|
|
1438
|
+
for doc in mesh.get("documents") or []:
|
|
1439
|
+
lines.append(
|
|
1440
|
+
f"- {doc.get('document_id')} | role={doc.get('document_role')} "
|
|
1441
|
+
f"| load_order={doc.get('load_order')} | path={doc.get('path')}"
|
|
1442
|
+
)
|
|
1443
|
+
lines.append(
|
|
1444
|
+
f" read_when_tags={doc.get('read_when_tags') or []} "
|
|
1445
|
+
f"skip_when_tags={doc.get('skip_when_tags') or []}"
|
|
1446
|
+
)
|
|
1447
|
+
if not mesh.get("documents"):
|
|
1448
|
+
lines.append("- none")
|
|
1449
|
+
lines += ["", "## Relationships", ""]
|
|
1450
|
+
for edge in mesh.get("relationships") or []:
|
|
1451
|
+
lines.append(
|
|
1452
|
+
f"- {edge.get('source_document_id')} -> {edge.get('target_document_id')} "
|
|
1453
|
+
f"| type={edge.get('relationship_type')} | required={edge.get('required')} "
|
|
1454
|
+
f"| scope={edge.get('resolution_scope')} | sections={edge.get('target_sections')}"
|
|
1455
|
+
)
|
|
1456
|
+
if not mesh.get("relationships"):
|
|
1457
|
+
lines.append("- none")
|
|
1458
|
+
lines += ["", "## Unresolved Same-Set Edges", ""]
|
|
1459
|
+
for edge in mesh.get("unresolved_same_set_edges") or []:
|
|
1460
|
+
lines.append(
|
|
1461
|
+
f"- {edge.get('source_document_id')} -> {edge.get('target_document_id')} "
|
|
1462
|
+
f"({edge.get('relationship_type')}) in {edge.get('source_path')}"
|
|
1463
|
+
)
|
|
1464
|
+
if not mesh.get("unresolved_same_set_edges"):
|
|
1465
|
+
lines.append("- none")
|
|
1466
|
+
lines += ["", "## Claim Boundary", ""]
|
|
1467
|
+
for item in report.get("does_not_verify") or []:
|
|
1468
|
+
lines.append(f"- does_not_verify: {item}")
|
|
1469
|
+
lines.append("")
|
|
1470
|
+
return "\n".join(lines)
|
|
1471
|
+
|
|
1472
|
+
|
|
1473
|
+
def render_mesh_report(report: dict[str, Any], fmt: str) -> str:
|
|
1474
|
+
mesh_report = mesh_only_report(report)
|
|
1475
|
+
if fmt == "json":
|
|
1476
|
+
return render_json(mesh_report)
|
|
1477
|
+
if fmt == "markdown":
|
|
1478
|
+
return render_mesh_markdown(mesh_report)
|
|
1479
|
+
raise InternalBugError(
|
|
1480
|
+
"Unknown mesh report format",
|
|
1481
|
+
where="kernel_reporting.render_mesh_report",
|
|
1482
|
+
context={"format": fmt},
|
|
1483
|
+
)
|
|
1484
|
+
|
|
1485
|
+
|
|
1486
|
+
def render_report(report: dict[str, Any], fmt: str) -> str:
|
|
1487
|
+
if fmt == "json":
|
|
1488
|
+
return render_json(report)
|
|
1489
|
+
if fmt == "markdown":
|
|
1490
|
+
return render_markdown(report)
|
|
1491
|
+
raise InternalBugError(
|
|
1492
|
+
"Unknown validation report format",
|
|
1493
|
+
where="kernel_reporting.render_report",
|
|
1494
|
+
context={"format": fmt},
|
|
1495
|
+
)
|