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,1263 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import contextlib
|
|
5
|
+
import hashlib
|
|
6
|
+
import importlib.util
|
|
7
|
+
import zipfile
|
|
8
|
+
import io
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
import shutil
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
sys.dont_write_bytecode = True
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
PACKAGE_ROOT = Path(__file__).resolve().parent
|
|
21
|
+
_SOURCE_KERNEL_ROOT = PACKAGE_ROOT.parent
|
|
22
|
+
KERNEL_ROOT = _SOURCE_KERNEL_ROOT if (_SOURCE_KERNEL_ROOT / "FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md").exists() else PACKAGE_ROOT / "resources"
|
|
23
|
+
TOOLS_DIR = KERNEL_ROOT / "tools"
|
|
24
|
+
SPEC_PATH = KERNEL_ROOT / "FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md"
|
|
25
|
+
|
|
26
|
+
if str(TOOLS_DIR) not in sys.path:
|
|
27
|
+
sys.path.insert(0, str(TOOLS_DIR))
|
|
28
|
+
|
|
29
|
+
from .speccompile import load_spec # noqa: E402
|
|
30
|
+
from .validator import validate_docset # noqa: E402
|
|
31
|
+
from .schema_projection import render_schema # noqa: E402
|
|
32
|
+
from .core import parse_frontmatter, split_frontmatter # noqa: E402
|
|
33
|
+
from .markdown.api import parse_markdown_text # noqa: E402
|
|
34
|
+
from .ai_instruction_risk import scan_ai_instruction_risk # noqa: E402
|
|
35
|
+
from .frontmatter_index import build_index, ensure_index_fresh, query_index, DEFAULT_INDEX # noqa: E402
|
|
36
|
+
from .authoring import init_project, new_document, PROFILES # noqa: E402
|
|
37
|
+
from .corpus_selection import SelectionResult, resolve_corpus # noqa: E402
|
|
38
|
+
from .inventory import build_inventory # noqa: E402
|
|
39
|
+
from .promotion import promote_document # noqa: E402
|
|
40
|
+
from .body_tether import update_tether # noqa: E402
|
|
41
|
+
from .harness_errors import HarnessError, InternalBugError, ToolingError # noqa: E402
|
|
42
|
+
from finding_registry import declared_finding_ids, finding_contract, guidance_for, retired_finding_info # noqa: E402
|
|
43
|
+
from kernel_reporting import ( # noqa: E402
|
|
44
|
+
render_mesh_report,
|
|
45
|
+
render_brief_report,
|
|
46
|
+
render_report,
|
|
47
|
+
validation_report,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
EXIT_GO = 0
|
|
52
|
+
EXIT_FINDING = 1
|
|
53
|
+
EXIT_USAGE = 2
|
|
54
|
+
EXIT_SELF_CHECK = 3
|
|
55
|
+
EXIT_INTERNAL = 4
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class StrictArgumentParser(argparse.ArgumentParser):
|
|
59
|
+
"""Argument parser with exact option names and no legacy prefix matching."""
|
|
60
|
+
|
|
61
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
62
|
+
kwargs.setdefault("allow_abbrev", False)
|
|
63
|
+
super().__init__(*args, **kwargs)
|
|
64
|
+
BRIEF_DEV_DIR_NAMES = {"archive", "distro_smoke", "fixtures", "mesh", "usecase"}
|
|
65
|
+
ROUTING_TASK_FIELDS = {"routing.read_when_tags[]", "routing.skip_when_tags[]"}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def collect_markdown_paths(inputs: list[str]) -> list[Path]:
|
|
69
|
+
"""Resolve Markdown paths through the single Frontmattering v0.3 selection authority."""
|
|
70
|
+
return list(resolve_corpus(inputs).paths)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def build_report(inputs: list[str]) -> dict[str, Any]:
|
|
74
|
+
selection = resolve_corpus(inputs)
|
|
75
|
+
paths = list(selection.paths)
|
|
76
|
+
auth = load_spec(SPEC_PATH)
|
|
77
|
+
result = validate_docset(paths, auth)
|
|
78
|
+
report = validation_report(target_paths=paths, auth=auth, result=result)
|
|
79
|
+
report["selection"] = selection.payload()
|
|
80
|
+
report["project"] = {
|
|
81
|
+
"project_file": str(selection.project_file.resolve()) if selection.project_file else None,
|
|
82
|
+
"project_schema": selection.project_schema,
|
|
83
|
+
"validation_profile": selection.validation_profile,
|
|
84
|
+
}
|
|
85
|
+
return report
|
|
86
|
+
|
|
87
|
+
def brief_paths(paths: list[Path], *, include_dev: bool, inputs: list[str]) -> list[Path]:
|
|
88
|
+
if include_dev:
|
|
89
|
+
return paths
|
|
90
|
+
explicit_dev_roots = {
|
|
91
|
+
Path(raw).resolve()
|
|
92
|
+
for raw in inputs
|
|
93
|
+
if Path(raw).name in BRIEF_DEV_DIR_NAMES
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def is_explicit_dev_target(path: Path) -> bool:
|
|
97
|
+
resolved = path.resolve()
|
|
98
|
+
return any(resolved == root or resolved.is_relative_to(root) for root in explicit_dev_roots)
|
|
99
|
+
|
|
100
|
+
filtered = [
|
|
101
|
+
path for path in paths
|
|
102
|
+
if is_explicit_dev_target(path) or not any(part in BRIEF_DEV_DIR_NAMES for part in path.parts)
|
|
103
|
+
]
|
|
104
|
+
if not filtered:
|
|
105
|
+
raise ToolingError(
|
|
106
|
+
"Cold-start brief has no non-dev Markdown targets",
|
|
107
|
+
"brief target filtering",
|
|
108
|
+
"The brief command hides fixture/archive/dev-only directories by default.",
|
|
109
|
+
"Pass --include-dev or point brief at the intended authored document files.",
|
|
110
|
+
{"filtered_directories": sorted(BRIEF_DEV_DIR_NAMES)},
|
|
111
|
+
error_id="FRONTMATTERING-KERNEL-BRIEF-NO-TARGETS",
|
|
112
|
+
)
|
|
113
|
+
return filtered
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def build_brief_report(inputs: list[str], *, include_dev: bool) -> dict[str, Any]:
|
|
117
|
+
selection = resolve_corpus(inputs)
|
|
118
|
+
paths = brief_paths(list(selection.paths), include_dev=include_dev, inputs=inputs)
|
|
119
|
+
final_selection = selection.restrict(paths)
|
|
120
|
+
auth = load_spec(SPEC_PATH)
|
|
121
|
+
result = validate_docset(paths, auth)
|
|
122
|
+
report = validation_report(target_paths=paths, auth=auth, result=result)
|
|
123
|
+
report["selection"] = final_selection.payload()
|
|
124
|
+
report["project"] = {
|
|
125
|
+
"project_file": str(selection.project_file.resolve()) if selection.project_file else None,
|
|
126
|
+
"project_schema": selection.project_schema,
|
|
127
|
+
"validation_profile": selection.validation_profile,
|
|
128
|
+
}
|
|
129
|
+
return report
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def exit_for_report(report: dict[str, Any]) -> int:
|
|
133
|
+
return EXIT_GO if report.get("terminal_state") == "GO" else EXIT_FINDING
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def selected_format(args: argparse.Namespace) -> str:
|
|
137
|
+
return args.format
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def command_validate(args: argparse.Namespace) -> int:
|
|
141
|
+
try:
|
|
142
|
+
report = build_report(args.paths)
|
|
143
|
+
except ToolingError as exc:
|
|
144
|
+
print(exc, file=sys.stderr)
|
|
145
|
+
return EXIT_USAGE
|
|
146
|
+
print(render_report(report, selected_format(args)))
|
|
147
|
+
return exit_for_report(report)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def command_mesh(args: argparse.Namespace) -> int:
|
|
151
|
+
try:
|
|
152
|
+
report = build_report(args.paths)
|
|
153
|
+
except ToolingError as exc:
|
|
154
|
+
print(exc, file=sys.stderr)
|
|
155
|
+
return EXIT_USAGE
|
|
156
|
+
print(render_mesh_report(report, selected_format(args)))
|
|
157
|
+
return exit_for_report(report)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def command_brief(args: argparse.Namespace) -> int:
|
|
161
|
+
try:
|
|
162
|
+
report = build_brief_report(args.paths, include_dev=args.include_dev)
|
|
163
|
+
rendered = render_brief_report(report, selected_format(args), task=args.task, section=args.section)
|
|
164
|
+
except ToolingError as exc:
|
|
165
|
+
print(exc, file=sys.stderr)
|
|
166
|
+
return EXIT_USAGE
|
|
167
|
+
print(rendered)
|
|
168
|
+
return EXIT_GO
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def command_explain(args: argparse.Namespace) -> int:
|
|
172
|
+
auth = load_spec(SPEC_PATH)
|
|
173
|
+
declared = args.finding_id in declared_finding_ids(auth)
|
|
174
|
+
retired = retired_finding_info(args.finding_id)
|
|
175
|
+
payload = finding_contract(args.finding_id, auth=auth)
|
|
176
|
+
if retired is not None:
|
|
177
|
+
payload.update({
|
|
178
|
+
"finding_status": "retired",
|
|
179
|
+
"retired_in": retired["retired_in"],
|
|
180
|
+
"last_declared_in": retired["last_declared_in"],
|
|
181
|
+
"superseded_by": retired["superseded_by"],
|
|
182
|
+
"retirement_reason": retired["reason"],
|
|
183
|
+
})
|
|
184
|
+
elif not declared:
|
|
185
|
+
payload["finding_status"] = "unknown"
|
|
186
|
+
if args.format == "json":
|
|
187
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
188
|
+
else:
|
|
189
|
+
print(f"# {args.finding_id}")
|
|
190
|
+
print()
|
|
191
|
+
for key in ("finding_status", "title", "class", "severity", "automation_class", "why"):
|
|
192
|
+
print(f"- {key}: {payload.get(key)}")
|
|
193
|
+
print("- deterministic_repairs:")
|
|
194
|
+
for item in payload.get("deterministic_repairs") or []:
|
|
195
|
+
print(f" - {item}")
|
|
196
|
+
print("- judgment_required:")
|
|
197
|
+
for item in payload.get("judgment_required") or []:
|
|
198
|
+
print(f" - {item}")
|
|
199
|
+
print()
|
|
200
|
+
print("Claim boundary:")
|
|
201
|
+
for item in payload.get("does_not_verify") or []:
|
|
202
|
+
print(f"- does_not_verify: {item}")
|
|
203
|
+
return EXIT_GO if declared else EXIT_FINDING
|
|
204
|
+
|
|
205
|
+
def _tether_status_rows(paths: list[Path]) -> list[dict[str, Any]]:
|
|
206
|
+
auth = load_spec(SPEC_PATH)
|
|
207
|
+
result = validate_docset(paths, auth)
|
|
208
|
+
rows = []
|
|
209
|
+
for path, per_doc in sorted((result.get("per_document") or {}).items()):
|
|
210
|
+
fm = per_doc.get("fm") or {}
|
|
211
|
+
adc = fm.get("ai_document_contract", {}) if isinstance(fm, dict) else {}
|
|
212
|
+
status = per_doc.get("body_tether") or {}
|
|
213
|
+
rows.append({
|
|
214
|
+
"path": path,
|
|
215
|
+
"document_id": adc.get("document_id"),
|
|
216
|
+
"freshness": status.get("freshness") or "UNTETHERED",
|
|
217
|
+
"summary_trust": status.get("summary_trust") or "orientation_only",
|
|
218
|
+
"reasons": status.get("reasons") or [],
|
|
219
|
+
"body_sha256_current": status.get("body_sha256_current"),
|
|
220
|
+
"body_sha256_declared": status.get("body_sha256_declared"),
|
|
221
|
+
"tethered_fields_sha256_current": status.get("tethered_fields_sha256_current"),
|
|
222
|
+
"tethered_fields_sha256_declared": status.get("tethered_fields_sha256_declared"),
|
|
223
|
+
})
|
|
224
|
+
return rows
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def command_tether_check(args: argparse.Namespace) -> int:
|
|
228
|
+
try:
|
|
229
|
+
paths = collect_markdown_paths(args.paths)
|
|
230
|
+
except ToolingError as exc:
|
|
231
|
+
print(exc, file=sys.stderr)
|
|
232
|
+
return EXIT_USAGE
|
|
233
|
+
rows = _tether_status_rows(paths)
|
|
234
|
+
payload = {
|
|
235
|
+
"schema_version": "frontmattering_tether_check/v0_1",
|
|
236
|
+
"document_count": len(rows),
|
|
237
|
+
"stale_count": sum(1 for row in rows if row["freshness"] == "STALE"),
|
|
238
|
+
"untethered_count": sum(1 for row in rows if row["freshness"] == "UNTETHERED"),
|
|
239
|
+
"fresh_count": sum(1 for row in rows if row["freshness"] == "FRESH"),
|
|
240
|
+
"rows": rows,
|
|
241
|
+
"claim_boundary": {
|
|
242
|
+
"verifies": ["Body/frontmatter tether freshness under the current validator semantics."],
|
|
243
|
+
"does_not_verify": ["Semantic correctness of summaries or external approval."],
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
if args.format == "json":
|
|
247
|
+
print(json.dumps(payload, indent=2, sort_keys=True, default=str))
|
|
248
|
+
else:
|
|
249
|
+
print("# Frontmattering tether check")
|
|
250
|
+
print(f"fresh={payload['fresh_count']} stale={payload['stale_count']} untethered={payload['untethered_count']}")
|
|
251
|
+
for row in rows:
|
|
252
|
+
reasons = ";".join(row.get("reasons") or []) or "none"
|
|
253
|
+
print(f"- {row['freshness']} {row.get('document_id') or '?'} {row['path']} summary_trust={row['summary_trust']} reasons={reasons}")
|
|
254
|
+
return EXIT_FINDING if payload["stale_count"] else EXIT_GO
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def command_tether_update(args: argparse.Namespace) -> int:
|
|
258
|
+
try:
|
|
259
|
+
paths = collect_markdown_paths(args.paths)
|
|
260
|
+
except ToolingError as exc:
|
|
261
|
+
print(exc, file=sys.stderr)
|
|
262
|
+
return EXIT_USAGE
|
|
263
|
+
updated = []
|
|
264
|
+
skipped = []
|
|
265
|
+
failed = []
|
|
266
|
+
for path in paths:
|
|
267
|
+
try:
|
|
268
|
+
if update_tether(path, add=args.add, canonical_fields=load_spec(SPEC_PATH).body_tether_canonical_trust_fields):
|
|
269
|
+
updated.append(str(path))
|
|
270
|
+
else:
|
|
271
|
+
skipped.append(str(path))
|
|
272
|
+
except (OSError, ValueError, yaml.YAMLError) as exc:
|
|
273
|
+
failed.append({"path": str(path), "error": str(exc)})
|
|
274
|
+
payload = {
|
|
275
|
+
"schema_version": "frontmattering_tether_update/v0_1",
|
|
276
|
+
"updated_count": len(updated),
|
|
277
|
+
"skipped_count": len(skipped),
|
|
278
|
+
"failed_count": len(failed),
|
|
279
|
+
"updated_paths": updated,
|
|
280
|
+
"skipped_paths": skipped,
|
|
281
|
+
"failures": failed,
|
|
282
|
+
"claim_boundary": {
|
|
283
|
+
"verifies": ["body_tether digest fields were recomputed for reported updated files."],
|
|
284
|
+
"does_not_verify": ["Semantic correctness of summaries or external approval."],
|
|
285
|
+
},
|
|
286
|
+
}
|
|
287
|
+
if args.format == "json":
|
|
288
|
+
print(json.dumps(payload, indent=2, sort_keys=True, default=str))
|
|
289
|
+
else:
|
|
290
|
+
print("# Frontmattering tether update")
|
|
291
|
+
print(f"updated={len(updated)} skipped={len(skipped)} failed={len(failed)}")
|
|
292
|
+
for path in updated:
|
|
293
|
+
print(f"- updated {path}")
|
|
294
|
+
for path in skipped:
|
|
295
|
+
print(f"- skipped {path}")
|
|
296
|
+
for item in failed:
|
|
297
|
+
print(f"- failed {item['path']}: {item['error']}")
|
|
298
|
+
return EXIT_FINDING if failed else EXIT_GO
|
|
299
|
+
|
|
300
|
+
def run_doctor_step(name: str, argv: list[str], *, cwd: Path) -> dict[str, Any]:
|
|
301
|
+
"""Execute a shipped Python check in-process with isolated argv and output.
|
|
302
|
+
|
|
303
|
+
Doctor used to launch one subprocess per check. In constrained agent and CI
|
|
304
|
+
environments, repeated child creation could stall after several successful
|
|
305
|
+
checks. Loading each shipped script from its own path preserves that script's
|
|
306
|
+
ROOT semantics while giving doctor one deterministic process boundary.
|
|
307
|
+
"""
|
|
308
|
+
if len(argv) < 2:
|
|
309
|
+
return {
|
|
310
|
+
"name": name,
|
|
311
|
+
"argv": argv,
|
|
312
|
+
"exit_code": 2,
|
|
313
|
+
"status": "FAIL",
|
|
314
|
+
"output": "Doctor step is missing a Python script path.",
|
|
315
|
+
"verifies": [],
|
|
316
|
+
"does_not_verify": ["The malformed doctor step was not executed."],
|
|
317
|
+
}
|
|
318
|
+
script = Path(argv[1])
|
|
319
|
+
if not script.is_absolute():
|
|
320
|
+
script = cwd / script
|
|
321
|
+
module_name = "_frontmattering_doctor_" + hashlib.sha256(
|
|
322
|
+
f"{name}:{script}".encode("utf-8")
|
|
323
|
+
).hexdigest()[:16]
|
|
324
|
+
stdout = io.StringIO()
|
|
325
|
+
stderr = io.StringIO()
|
|
326
|
+
old_argv = list(sys.argv)
|
|
327
|
+
old_cwd = Path.cwd()
|
|
328
|
+
exit_code = 0
|
|
329
|
+
try:
|
|
330
|
+
os.chdir(cwd)
|
|
331
|
+
sys.argv = [str(script), *argv[2:]]
|
|
332
|
+
spec = importlib.util.spec_from_file_location(module_name, script)
|
|
333
|
+
if spec is None or spec.loader is None:
|
|
334
|
+
raise InternalBugError(
|
|
335
|
+
"Cannot load doctor check",
|
|
336
|
+
where="frontmattering.__main__.run_doctor_step",
|
|
337
|
+
context={"name": name, "script": str(script)},
|
|
338
|
+
)
|
|
339
|
+
module = importlib.util.module_from_spec(spec)
|
|
340
|
+
with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
|
|
341
|
+
try:
|
|
342
|
+
spec.loader.exec_module(module)
|
|
343
|
+
main_fn = getattr(module, "main", None)
|
|
344
|
+
if callable(main_fn):
|
|
345
|
+
result = main_fn()
|
|
346
|
+
exit_code = 0 if result is None else int(result)
|
|
347
|
+
except SystemExit as exc:
|
|
348
|
+
value = exc.code
|
|
349
|
+
exit_code = value if isinstance(value, int) else (0 if value is None else 1)
|
|
350
|
+
except (HarnessError, OSError, ValueError, TypeError, AssertionError, ImportError, SyntaxError) as exc:
|
|
351
|
+
exit_code = 1
|
|
352
|
+
print(f"{type(exc).__name__}: {exc}", file=stderr)
|
|
353
|
+
finally:
|
|
354
|
+
sys.argv = old_argv
|
|
355
|
+
os.chdir(old_cwd)
|
|
356
|
+
output = "\n".join(part for part in [stdout.getvalue().strip(), stderr.getvalue().strip()] if part)
|
|
357
|
+
return {
|
|
358
|
+
"name": name,
|
|
359
|
+
"argv": argv,
|
|
360
|
+
"exit_code": exit_code,
|
|
361
|
+
"status": "PASS" if exit_code == 0 else "FAIL",
|
|
362
|
+
"output": output,
|
|
363
|
+
"verifies": [f"The named deterministic check {name} executed with the recorded exit code."],
|
|
364
|
+
"does_not_verify": ["Semantic adequacy, external adoption, release approval, or production readiness."],
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def command_doctor(args: argparse.Namespace) -> int:
|
|
369
|
+
python = sys.executable
|
|
370
|
+
root = KERNEL_ROOT.parents[1]
|
|
371
|
+
rel = lambda path: str(path.relative_to(root))
|
|
372
|
+
steps = [
|
|
373
|
+
run_doctor_step(
|
|
374
|
+
"python_syntax",
|
|
375
|
+
[python, rel(KERNEL_ROOT / "tools" / "check_python_syntax.py")],
|
|
376
|
+
cwd=root,
|
|
377
|
+
),
|
|
378
|
+
run_doctor_step(
|
|
379
|
+
"report_quality",
|
|
380
|
+
[python, rel(KERNEL_ROOT / "tools" / "check_report_quality.py")],
|
|
381
|
+
cwd=root,
|
|
382
|
+
),
|
|
383
|
+
run_doctor_step(
|
|
384
|
+
"exception_taxonomy",
|
|
385
|
+
[python, rel(KERNEL_ROOT / "tools" / "check_kernel_exception_taxonomy.py")],
|
|
386
|
+
cwd=root,
|
|
387
|
+
),
|
|
388
|
+
run_doctor_step(
|
|
389
|
+
"ai_index_current",
|
|
390
|
+
[python, rel(KERNEL_ROOT / "tools" / "generate_ai_index.py"), "--check"],
|
|
391
|
+
cwd=root,
|
|
392
|
+
),
|
|
393
|
+
run_doctor_step(
|
|
394
|
+
"frontmatter_schema_current",
|
|
395
|
+
[python, rel(KERNEL_ROOT / "tools" / "generate_frontmatter_schema.py"), "--check"],
|
|
396
|
+
cwd=root,
|
|
397
|
+
),
|
|
398
|
+
run_doctor_step(
|
|
399
|
+
"projection_consistency",
|
|
400
|
+
[python, rel(KERNEL_ROOT / "tools" / "check_projection_consistency.py")],
|
|
401
|
+
cwd=root,
|
|
402
|
+
),
|
|
403
|
+
run_doctor_step(
|
|
404
|
+
"markdown_parser_adoption",
|
|
405
|
+
[python, rel(KERNEL_ROOT / "tools" / "check_markdown_parser_adoption.py")],
|
|
406
|
+
cwd=root,
|
|
407
|
+
),
|
|
408
|
+
run_doctor_step(
|
|
409
|
+
"frontmatter_sqlite_index",
|
|
410
|
+
[python, rel(KERNEL_ROOT / "tools" / "check_frontmatter_sqlite_index.py")],
|
|
411
|
+
cwd=root,
|
|
412
|
+
),
|
|
413
|
+
run_doctor_step(
|
|
414
|
+
"kernel_verifier",
|
|
415
|
+
[python, rel(KERNEL_ROOT / "tools" / "verify_kernel.py")],
|
|
416
|
+
cwd=root,
|
|
417
|
+
),
|
|
418
|
+
run_doctor_step(
|
|
419
|
+
"brief_smoke",
|
|
420
|
+
[
|
|
421
|
+
python,
|
|
422
|
+
rel(KERNEL_ROOT / "tools" / "frontmattering_cli.py"),
|
|
423
|
+
"brief",
|
|
424
|
+
rel(KERNEL_ROOT / "AI_START_HERE.md"),
|
|
425
|
+
rel(KERNEL_ROOT / "FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md"),
|
|
426
|
+
"--task",
|
|
427
|
+
"frontmatter_authoring",
|
|
428
|
+
"--format",
|
|
429
|
+
"json",
|
|
430
|
+
],
|
|
431
|
+
cwd=root,
|
|
432
|
+
),
|
|
433
|
+
run_doctor_step(
|
|
434
|
+
"cleanliness",
|
|
435
|
+
[python, rel(KERNEL_ROOT / "tools" / "check_kernel_cleanliness.py")],
|
|
436
|
+
cwd=root,
|
|
437
|
+
),
|
|
438
|
+
]
|
|
439
|
+
overall = "PASS" if all(step["exit_code"] == 0 for step in steps) else "FAIL"
|
|
440
|
+
if args.format == "json":
|
|
441
|
+
print(json.dumps({"schema_version": "frontmattering_doctor/v0_3", "doctor_verdict": overall, "steps": steps, "claim_boundary": {"verifies": ["Named local checks executed."], "does_not_verify": ["Semantic adequacy, external adoption, release approval, or production readiness."]}}, indent=2, sort_keys=True))
|
|
442
|
+
else:
|
|
443
|
+
print(f"# Frontmattering kernel doctor\n\n- doctor_verdict: {overall}")
|
|
444
|
+
for step in steps:
|
|
445
|
+
print()
|
|
446
|
+
print(f"## {step['name']}")
|
|
447
|
+
print(f"- status: {step['status']}")
|
|
448
|
+
print(f"- exit_code: {step['exit_code']}")
|
|
449
|
+
if step["output"]:
|
|
450
|
+
print()
|
|
451
|
+
print("```text")
|
|
452
|
+
print(step["output"])
|
|
453
|
+
print("```")
|
|
454
|
+
return EXIT_GO if overall == "PASS" else EXIT_SELF_CHECK
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def catalog_payload(auth: Any, field: str | None) -> dict[str, Any]:
|
|
458
|
+
if field is not None:
|
|
459
|
+
entry = auth.fields.get(field)
|
|
460
|
+
if entry is None:
|
|
461
|
+
raise ToolingError(
|
|
462
|
+
"Unknown frontmatter field",
|
|
463
|
+
field,
|
|
464
|
+
"The field is not in the closed field catalog of this specification.",
|
|
465
|
+
"Run `frontmattering catalog` with no --field to list the closed vocabularies.",
|
|
466
|
+
{"field": field},
|
|
467
|
+
error_id="FRONTMATTERING-KERNEL-CATALOG-FIELD-UNKNOWN",
|
|
468
|
+
)
|
|
469
|
+
payload = {
|
|
470
|
+
"field": field,
|
|
471
|
+
"type": entry.get("type"),
|
|
472
|
+
"required_when": entry.get("required_when"),
|
|
473
|
+
"allowed_values": entry.get("allowed_values"),
|
|
474
|
+
}
|
|
475
|
+
if field in ROUTING_TASK_FIELDS:
|
|
476
|
+
built_ins = entry.get("allowed_values") or []
|
|
477
|
+
payload["allowed_values"] = None
|
|
478
|
+
payload["value_domain"] = {
|
|
479
|
+
"built_in_values": built_ins,
|
|
480
|
+
"corpus_declared_values": "Any task_tag value declared in routing.task_catalog[] by an in-scope package_router document.",
|
|
481
|
+
"router_field": "routing.task_catalog[]",
|
|
482
|
+
"scope_note": "Single-file validation of a document with corpus-local tags requires the package_router document in the same validation target.",
|
|
483
|
+
}
|
|
484
|
+
return payload
|
|
485
|
+
return {
|
|
486
|
+
"closed_root_blocks": sorted(auth.closed_root),
|
|
487
|
+
"closed_vocabularies": {
|
|
488
|
+
name: entry.get("allowed_values")
|
|
489
|
+
for name, entry in sorted(auth.fields.items())
|
|
490
|
+
if entry.get("allowed_values")
|
|
491
|
+
},
|
|
492
|
+
"role_compatibility": auth.role_compat,
|
|
493
|
+
"claim_state_compatibility": auth.claim_compat,
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def command_catalog(args: argparse.Namespace) -> int:
|
|
498
|
+
auth = load_spec(SPEC_PATH)
|
|
499
|
+
try:
|
|
500
|
+
payload = catalog_payload(auth, args.field)
|
|
501
|
+
except ToolingError as exc:
|
|
502
|
+
print(exc, file=sys.stderr)
|
|
503
|
+
return EXIT_USAGE
|
|
504
|
+
|
|
505
|
+
payload["spec_sha256"] = auth.spec_sha256
|
|
506
|
+
payload["claim_boundary"] = (
|
|
507
|
+
"Catalog projects the closed model of this specification. "
|
|
508
|
+
"It is not validation evidence and does not verify any document."
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
if args.format == "json":
|
|
512
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
513
|
+
return EXIT_GO
|
|
514
|
+
|
|
515
|
+
print("# Frontmattering closed model catalog")
|
|
516
|
+
print()
|
|
517
|
+
print(f"- spec_sha256: {auth.spec_sha256}")
|
|
518
|
+
print()
|
|
519
|
+
if args.field:
|
|
520
|
+
print(f"## {payload['field']}")
|
|
521
|
+
print()
|
|
522
|
+
print(f"- type: {payload['type']}")
|
|
523
|
+
print(f"- required_when: {payload['required_when']}")
|
|
524
|
+
values = payload["allowed_values"]
|
|
525
|
+
if values:
|
|
526
|
+
print("- allowed_values:")
|
|
527
|
+
for value in values:
|
|
528
|
+
print(f" - {value}")
|
|
529
|
+
else:
|
|
530
|
+
print("- allowed_values: (open - constrained by type, not by enum)")
|
|
531
|
+
if payload.get("value_domain"):
|
|
532
|
+
domain = payload["value_domain"]
|
|
533
|
+
print("- value_domain:")
|
|
534
|
+
print(" - built_in_values:")
|
|
535
|
+
for value in domain["built_in_values"]:
|
|
536
|
+
print(f" - {value}")
|
|
537
|
+
print(f" - corpus_declared_values: {domain['corpus_declared_values']}")
|
|
538
|
+
print(f" - router_field: {domain['router_field']}")
|
|
539
|
+
print(f" - scope_note: {domain['scope_note']}")
|
|
540
|
+
else:
|
|
541
|
+
print("## Closed root blocks")
|
|
542
|
+
print()
|
|
543
|
+
for block in payload["closed_root_blocks"]:
|
|
544
|
+
print(f"- {block}")
|
|
545
|
+
print()
|
|
546
|
+
print("## Closed vocabularies")
|
|
547
|
+
print()
|
|
548
|
+
for name, values in payload["closed_vocabularies"].items():
|
|
549
|
+
print(f"- {name}: {', '.join(str(v) for v in values)}")
|
|
550
|
+
print()
|
|
551
|
+
print("Claim boundary: the catalog projects the closed model only; "
|
|
552
|
+
"it is not validation evidence.")
|
|
553
|
+
return EXIT_GO
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def _task_tags_from_paths(inputs: list[str]) -> set[str]:
|
|
557
|
+
tags: set[str] = set()
|
|
558
|
+
for path in collect_markdown_paths(inputs):
|
|
559
|
+
fm, _body, findings = parse_frontmatter(path.read_text(encoding="utf-8"))
|
|
560
|
+
if fm is None or findings:
|
|
561
|
+
continue
|
|
562
|
+
routing = fm.get("routing") if isinstance(fm, dict) else None
|
|
563
|
+
if isinstance(routing, dict):
|
|
564
|
+
for tag in routing.get("task_catalog") or []:
|
|
565
|
+
if isinstance(tag, str) and tag:
|
|
566
|
+
tags.add(tag)
|
|
567
|
+
return tags
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def command_schema(args: argparse.Namespace) -> int:
|
|
571
|
+
selection = None
|
|
572
|
+
try:
|
|
573
|
+
if args.paths:
|
|
574
|
+
selection = resolve_corpus(args.paths)
|
|
575
|
+
tags = _task_tags_from_paths([str(path) for path in selection.paths])
|
|
576
|
+
else:
|
|
577
|
+
tags = set()
|
|
578
|
+
except ToolingError as exc:
|
|
579
|
+
print(exc, file=sys.stderr)
|
|
580
|
+
return EXIT_USAGE
|
|
581
|
+
schema_payload = json.loads(render_schema(load_spec(SPEC_PATH), additional_task_tags=tags))
|
|
582
|
+
if selection is not None:
|
|
583
|
+
selection_payload = selection.payload()
|
|
584
|
+
schema_payload["selection"] = selection_payload
|
|
585
|
+
schema_payload["x-frontmattering-selection"] = selection_payload
|
|
586
|
+
rendered = json.dumps(schema_payload, indent=2, sort_keys=True) + "\n"
|
|
587
|
+
if args.vscode:
|
|
588
|
+
schema_path = args.output or ".frontmattering/generated/FRONTMATTERING_FRONTMATTER_SCHEMA.schema.json"
|
|
589
|
+
payload = {"yaml.schemas": {schema_path: ["docs/**/*.md"]}}
|
|
590
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
591
|
+
return EXIT_GO
|
|
592
|
+
if args.check:
|
|
593
|
+
target = Path(args.check)
|
|
594
|
+
if not target.exists() or target.read_text(encoding="utf-8") != rendered:
|
|
595
|
+
print(ToolingError("Generated JSON Schema is stale", str(target), "Fresh corpus-aware schema emission differs from the written projection.", f"Run: frontmattering schema {' '.join(args.paths)} --output {target}", error_id="FRONTMATTERING-SCHEMA-STALE"), file=sys.stderr)
|
|
596
|
+
return EXIT_FINDING
|
|
597
|
+
print(f"schema_check=PASS path={target}")
|
|
598
|
+
return EXIT_GO
|
|
599
|
+
if args.output:
|
|
600
|
+
target = Path(args.output)
|
|
601
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
602
|
+
target.write_text(rendered, encoding="utf-8")
|
|
603
|
+
print(f"schema_written={target}")
|
|
604
|
+
return EXIT_GO
|
|
605
|
+
print(rendered, end="")
|
|
606
|
+
return EXIT_GO
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def command_init(args: argparse.Namespace) -> int:
|
|
611
|
+
try:
|
|
612
|
+
created = init_project(Path(args.directory), args.set_id, args.title, force=args.force)
|
|
613
|
+
except HarnessError as exc:
|
|
614
|
+
print(exc, file=sys.stderr)
|
|
615
|
+
return EXIT_USAGE
|
|
616
|
+
for path in created:
|
|
617
|
+
print(path)
|
|
618
|
+
root = Path(args.directory)
|
|
619
|
+
print("Next:")
|
|
620
|
+
print(f" frontmattering new {root / 'docs' / '10_GUIDE.md'} --profile guide --title \"First guide\"")
|
|
621
|
+
print(f" frontmattering validate {root / 'docs'}")
|
|
622
|
+
print(f" frontmattering brief {root / 'docs'}")
|
|
623
|
+
return EXIT_GO
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def command_new(args: argparse.Namespace) -> int:
|
|
627
|
+
try:
|
|
628
|
+
path = new_document(Path(args.path), args.profile, args.title, summary=args.summary, force=args.force)
|
|
629
|
+
except HarnessError as exc:
|
|
630
|
+
print(f"Frontmattering authoring error: {exc}", file=sys.stderr)
|
|
631
|
+
return EXIT_USAGE
|
|
632
|
+
print(path)
|
|
633
|
+
print("Next: frontmattering validate " + str(path.parent))
|
|
634
|
+
return EXIT_GO
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def command_inventory(args: argparse.Namespace) -> int:
|
|
638
|
+
try:
|
|
639
|
+
selection = resolve_corpus(args.paths)
|
|
640
|
+
payload = build_inventory(selection)
|
|
641
|
+
except ToolingError as exc:
|
|
642
|
+
print(exc, file=sys.stderr)
|
|
643
|
+
return EXIT_USAGE
|
|
644
|
+
if selected_format(args) == "json":
|
|
645
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
646
|
+
else:
|
|
647
|
+
print("# Frontmattering corpus inventory")
|
|
648
|
+
print()
|
|
649
|
+
print(f"- project_schema: {payload['project']['project_schema']}")
|
|
650
|
+
print(f"- selected_path_count: {payload['selection']['selected_path_count']}")
|
|
651
|
+
for key, value in payload["census"].items():
|
|
652
|
+
print(f"- {key}: {value}")
|
|
653
|
+
print()
|
|
654
|
+
print("## Documents")
|
|
655
|
+
print()
|
|
656
|
+
for item in payload["documents"]:
|
|
657
|
+
print(f"- {item['path']}: {item['status']} ({item['reason_code']})")
|
|
658
|
+
print()
|
|
659
|
+
print("Claim boundary: deterministic classification only; no semantic profile recommendation is made.")
|
|
660
|
+
return EXIT_GO
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def command_promote(args: argparse.Namespace) -> int:
|
|
664
|
+
if bool(args.output) == bool(args.in_place):
|
|
665
|
+
print(ToolingError("Promotion output mode is ambiguous", "frontmattering promote", "Exactly one of --output or --in-place is required.", "Choose an explicit destination or intentional in-place write.", error_id="FRONTMATTERING-PROMOTE-OUTPUT-MODE-REQUIRED"), file=sys.stderr)
|
|
666
|
+
return EXIT_USAGE
|
|
667
|
+
source = Path(args.input)
|
|
668
|
+
destination = source if args.in_place else Path(args.output)
|
|
669
|
+
try:
|
|
670
|
+
receipt = promote_document(
|
|
671
|
+
source, profile=args.profile, title=args.title, destination=destination, check_only=args.check
|
|
672
|
+
)
|
|
673
|
+
except ToolingError as exc:
|
|
674
|
+
print(exc, file=sys.stderr)
|
|
675
|
+
return EXIT_USAGE
|
|
676
|
+
if args.format == "json":
|
|
677
|
+
print(json.dumps(receipt, indent=2, sort_keys=True))
|
|
678
|
+
else:
|
|
679
|
+
print("# Frontmattering promotion receipt")
|
|
680
|
+
print()
|
|
681
|
+
for key in ("terminal_state", "input", "destination", "profile", "title", "title_source", "input_sha256", "output_sha256", "body_bytes_preserved"):
|
|
682
|
+
print(f"- {key}: {receipt[key]}")
|
|
683
|
+
print()
|
|
684
|
+
print("Claim boundary: frontmatter scaffolding and body-byte preservation only; semantic role appropriateness is not verified.")
|
|
685
|
+
return EXIT_GO
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def _json_safe(value: Any) -> Any:
|
|
689
|
+
if isinstance(value, dict):
|
|
690
|
+
return {str(k): _json_safe(v) for k, v in value.items()}
|
|
691
|
+
if isinstance(value, (list, tuple)):
|
|
692
|
+
return [_json_safe(v) for v in value]
|
|
693
|
+
if isinstance(value, (str, int, float, bool)) or value is None:
|
|
694
|
+
return value
|
|
695
|
+
if hasattr(value, "to_dict"):
|
|
696
|
+
return _json_safe(value.to_dict())
|
|
697
|
+
return str(value)
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def _parse_one_document(path: Path, args: argparse.Namespace) -> tuple[dict, int]:
|
|
701
|
+
if not path.exists() or not path.is_file() or path.suffix.lower() != ".md":
|
|
702
|
+
raise ToolingError(
|
|
703
|
+
"Markdown input is required", str(path),
|
|
704
|
+
"The parse command accepts an existing .md file or a directory with --batch.",
|
|
705
|
+
"Pass an existing Markdown file, or use --batch with a directory.",
|
|
706
|
+
{"path": str(path)}, error_id="FRONTMATTERING-PARSE-INPUT-INVALID",
|
|
707
|
+
)
|
|
708
|
+
raw = path.read_text(encoding="utf-8", errors="replace")
|
|
709
|
+
canonical_fm, body, fm_findings = parse_frontmatter(raw)
|
|
710
|
+
parsed = parse_markdown_text(
|
|
711
|
+
body,
|
|
712
|
+
config={"allows_html": args.allows_html, "one_pass": True, "surface_all_links": True},
|
|
713
|
+
security_profile=args.security_profile,
|
|
714
|
+
source_path=str(path),
|
|
715
|
+
)
|
|
716
|
+
content_security = parsed.metadata.get("security", {})
|
|
717
|
+
ai_risk = scan_ai_instruction_risk(body, parsed.structure)
|
|
718
|
+
metadata = dict(parsed.metadata)
|
|
719
|
+
metadata.pop("security", None)
|
|
720
|
+
payload = {
|
|
721
|
+
"schema_version": "frontmattering_document_ir/v0_3",
|
|
722
|
+
"distribution_version": "0.4.2",
|
|
723
|
+
"canonical_model_version": "0.1.5",
|
|
724
|
+
"source": {
|
|
725
|
+
"path": str(path),
|
|
726
|
+
"sha256": __import__("hashlib").sha256(raw.encode("utf-8")).hexdigest(),
|
|
727
|
+
"size_bytes": len(raw.encode("utf-8")),
|
|
728
|
+
},
|
|
729
|
+
"frontmatter": {
|
|
730
|
+
"authority": "canonical_frontmattering_strict_parser",
|
|
731
|
+
"value": canonical_fm,
|
|
732
|
+
"findings": [getattr(f, "id", str(f)) for f in fm_findings],
|
|
733
|
+
"generic_parser_projection": None,
|
|
734
|
+
"generic_projection_authoritative": False,
|
|
735
|
+
"body_parser_input": "body_after_strict_frontmattering_frontmatter_boundary",
|
|
736
|
+
},
|
|
737
|
+
"markdown": {
|
|
738
|
+
"implementation": "frontmattering_markdown_parser",
|
|
739
|
+
"source_lineage": "owned_internal_code",
|
|
740
|
+
"structure": parsed.structure,
|
|
741
|
+
"metadata": metadata,
|
|
742
|
+
},
|
|
743
|
+
"security": {
|
|
744
|
+
"content_security": content_security,
|
|
745
|
+
"ai_instruction_risk": ai_risk,
|
|
746
|
+
},
|
|
747
|
+
"claim_boundary": {
|
|
748
|
+
"establishes": ["token-derived Markdown structure", "content-security findings", "literal AI-instruction-risk advisory findings", "strict Frontmattering frontmatter parse state"],
|
|
749
|
+
"does_not_establish": ["body semantic correctness", "factual correctness", "semantic prompt-injection detection", "adoption", "release readiness", "production readiness"],
|
|
750
|
+
},
|
|
751
|
+
}
|
|
752
|
+
return payload, (EXIT_GO if not fm_findings else EXIT_FINDING)
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def command_parse(args: argparse.Namespace) -> int:
|
|
756
|
+
path = Path(args.path)
|
|
757
|
+
if args.batch:
|
|
758
|
+
if not path.is_dir():
|
|
759
|
+
raise ToolingError("Batch input must be a directory", str(path), "--batch compiles Markdown files under one directory.", "Pass an existing directory.", {"path": str(path)}, error_id="FRONTMATTERING-PARSE-BATCH-INVALID")
|
|
760
|
+
if not args.output:
|
|
761
|
+
raise ToolingError("Batch output directory is required", "--output", "Batch mode writes one IR per Markdown file plus a manifest.", "Pass --output DIRECTORY.", {}, error_id="FRONTMATTERING-PARSE-BATCH-OUTPUT-REQUIRED")
|
|
762
|
+
selection = resolve_corpus([str(path)])
|
|
763
|
+
out_root = Path(args.output)
|
|
764
|
+
out_root.mkdir(parents=True, exist_ok=True)
|
|
765
|
+
records = []
|
|
766
|
+
terminal = EXIT_GO
|
|
767
|
+
for src in selection.paths:
|
|
768
|
+
payload, code = _parse_one_document(src, args)
|
|
769
|
+
terminal = max(terminal, code)
|
|
770
|
+
rel = src.relative_to(path.resolve()).with_suffix(src.suffix + ".ir.json")
|
|
771
|
+
dest = out_root / rel
|
|
772
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
773
|
+
dest.write_text(json.dumps(_json_safe(payload), indent=2, sort_keys=True, ensure_ascii=False)+"\n", encoding="utf-8")
|
|
774
|
+
records.append({"source": rel.as_posix()[:-8], "ir": rel.as_posix(), "source_sha256": payload["source"]["sha256"]})
|
|
775
|
+
manifest = {"schema_version":"frontmattering_ir_batch_manifest/v0_3","distribution_version":"0.4.2-pypi-clear","source_root":str(path),"document_count":len(records),"records":records,"selection":selection.payload(),"claim_boundary":"Disposable deterministic projection; source Markdown remains authoritative."}
|
|
776
|
+
(out_root/"manifest.json").write_text(json.dumps(manifest,indent=2,sort_keys=True)+"\n",encoding="utf-8")
|
|
777
|
+
print(f"document_ir_batch_written={out_root} documents={len(records)}")
|
|
778
|
+
return terminal
|
|
779
|
+
payload, code = _parse_one_document(path, args)
|
|
780
|
+
output = json.dumps(_json_safe(payload), indent=2, sort_keys=True, ensure_ascii=False) + "\n"
|
|
781
|
+
if args.output:
|
|
782
|
+
out = Path(args.output)
|
|
783
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
784
|
+
out.write_text(output, encoding="utf-8")
|
|
785
|
+
print(f"document_ir_written={out}")
|
|
786
|
+
else:
|
|
787
|
+
print(output, end="")
|
|
788
|
+
return code
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def command_index(args: argparse.Namespace) -> int:
|
|
792
|
+
index_path = Path(getattr(args, "output", None) or getattr(args, "index", None) or DEFAULT_INDEX)
|
|
793
|
+
if args.index_action in {"build", "rebuild", "update"}:
|
|
794
|
+
selection = resolve_corpus([args.corpus])
|
|
795
|
+
result = build_index(
|
|
796
|
+
selection.project_root,
|
|
797
|
+
index_path,
|
|
798
|
+
source_paths=selection.paths,
|
|
799
|
+
selection=selection.replay_payload(),
|
|
800
|
+
)
|
|
801
|
+
print(json.dumps(result, indent=2, sort_keys=True))
|
|
802
|
+
return EXIT_GO
|
|
803
|
+
if args.index_action in {"check", "status"}:
|
|
804
|
+
proof = ensure_index_fresh(index_path)
|
|
805
|
+
print(json.dumps({"schema_version":"frontmattering_frontmatter_index_status/v0_1","terminal_state":"CURRENT","freshness_proof":proof.__dict__}, indent=2, sort_keys=True))
|
|
806
|
+
return EXIT_GO
|
|
807
|
+
if args.index_action == "delete":
|
|
808
|
+
if index_path.exists():
|
|
809
|
+
index_path.unlink()
|
|
810
|
+
print(json.dumps({"terminal_state":"DELETED","index_path":str(index_path)}, indent=2, sort_keys=True))
|
|
811
|
+
return EXIT_GO
|
|
812
|
+
raise ToolingError("Unknown index action", str(args.index_action), "The requested frontmatter-index operation is unsupported.", "Use build, rebuild, update, check, status, or delete.", {}, error_id="FRONTMATTERING-INDEX-ACTION-INVALID")
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
def command_query(args: argparse.Namespace) -> int:
|
|
816
|
+
result = query_index(Path(args.index or DEFAULT_INDEX), task=args.task, authority=args.authority, role=args.role, target=args.target)
|
|
817
|
+
print(json.dumps(result, indent=2, sort_keys=True, ensure_ascii=False))
|
|
818
|
+
return EXIT_GO
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
RUNTIME_WRAPPER_TEMPLATE = r"""#!/usr/bin/env python3
|
|
822
|
+
from __future__ import annotations
|
|
823
|
+
|
|
824
|
+
import dataclasses
|
|
825
|
+
import hashlib
|
|
826
|
+
import json
|
|
827
|
+
import sys
|
|
828
|
+
import tempfile
|
|
829
|
+
from pathlib import Path
|
|
830
|
+
from typing import Any
|
|
831
|
+
|
|
832
|
+
MODEL_VERSION = "0.1.5"
|
|
833
|
+
KERNEL_VERSION = "__KERNEL_VERSION__"
|
|
834
|
+
RUNTIME_SCHEMA = "frontmattering_runtime_validate_only/v0_2"
|
|
835
|
+
SPEC_RESOURCE = "resources/FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md"
|
|
836
|
+
VALIDATOR_RESOURCE = "validator.py"
|
|
837
|
+
RUNTIME_BUILD_MANIFEST = __RUNTIME_BUILD_MANIFEST__
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
def _package_bytes(relative_path: str) -> bytes:
|
|
841
|
+
import frontmattering
|
|
842
|
+
package_dir = Path(frontmattering.__file__).parent
|
|
843
|
+
resource_path = (package_dir / relative_path).as_posix()
|
|
844
|
+
loader = getattr(frontmattering, "__loader__", None) or getattr(frontmattering.__spec__, "loader", None)
|
|
845
|
+
if loader is not None and hasattr(loader, "get_data"):
|
|
846
|
+
return loader.get_data(resource_path)
|
|
847
|
+
return Path(resource_path).read_bytes()
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
def _sha256(data: bytes) -> str:
|
|
851
|
+
return hashlib.sha256(data).hexdigest()
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
def _load_authority():
|
|
855
|
+
from frontmattering.speccompile import load_spec
|
|
856
|
+
spec_bytes = _package_bytes(SPEC_RESOURCE)
|
|
857
|
+
# load_spec is intentionally reused unchanged: the runtime supplies the
|
|
858
|
+
# bundled spec bytes through a temporary read-only file instead of parsing a
|
|
859
|
+
# second spec representation.
|
|
860
|
+
with tempfile.NamedTemporaryFile("wb", suffix="_FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md") as handle:
|
|
861
|
+
handle.write(spec_bytes)
|
|
862
|
+
handle.flush()
|
|
863
|
+
return load_spec(Path(handle.name))
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
def _runtime_identity() -> dict[str, Any]:
|
|
867
|
+
spec_bytes = _package_bytes(SPEC_RESOURCE)
|
|
868
|
+
validator_bytes = _package_bytes(VALIDATOR_RESOURCE)
|
|
869
|
+
return {
|
|
870
|
+
"runtime_schema": RUNTIME_SCHEMA,
|
|
871
|
+
"runtime_surface": "validate_only",
|
|
872
|
+
"runtime_packaging": "zipapp_embedded_frontmattering_validator",
|
|
873
|
+
"canonical_model_version": MODEL_VERSION,
|
|
874
|
+
"kernel_version": KERNEL_VERSION,
|
|
875
|
+
"spec_resource": SPEC_RESOURCE,
|
|
876
|
+
"spec_sha256": _sha256(spec_bytes),
|
|
877
|
+
"validator_resource": VALIDATOR_RESOURCE,
|
|
878
|
+
"validator_sha256": _sha256(validator_bytes),
|
|
879
|
+
**RUNTIME_BUILD_MANIFEST,
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
def _markdown_paths(targets: list[str]) -> list[Path]:
|
|
884
|
+
paths: list[Path] = []
|
|
885
|
+
for raw in targets or ["."]:
|
|
886
|
+
target = Path(raw)
|
|
887
|
+
if target.is_file() and target.suffix.lower() == ".md":
|
|
888
|
+
paths.append(target)
|
|
889
|
+
elif target.is_dir():
|
|
890
|
+
paths.extend(p for p in target.rglob("*.md") if p.is_file())
|
|
891
|
+
return sorted({p.resolve() for p in paths}, key=lambda item: item.as_posix())
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
def _finding_payload(finding: Any) -> dict[str, Any]:
|
|
895
|
+
if dataclasses.is_dataclass(finding):
|
|
896
|
+
return dataclasses.asdict(finding)
|
|
897
|
+
payload = {
|
|
898
|
+
"id": getattr(finding, "id", str(finding)),
|
|
899
|
+
"detail": getattr(finding, "detail", ""),
|
|
900
|
+
"source_path": getattr(finding, "source_path", None),
|
|
901
|
+
"document_id": getattr(finding, "document_id", None),
|
|
902
|
+
"field_path": getattr(finding, "field_path", None),
|
|
903
|
+
"line": getattr(finding, "line", None),
|
|
904
|
+
"line_text": getattr(finding, "line_text", None),
|
|
905
|
+
"context": getattr(finding, "context", {}),
|
|
906
|
+
}
|
|
907
|
+
return payload
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def validate(targets: list[str]) -> dict[str, Any]:
|
|
911
|
+
from frontmattering.validator import validate_docset
|
|
912
|
+
docs = _markdown_paths(targets)
|
|
913
|
+
auth = _load_authority()
|
|
914
|
+
result = validate_docset(docs, auth)
|
|
915
|
+
findings = [_finding_payload(f) for f in result.get("findings", [])]
|
|
916
|
+
terminal = "GO" if not findings else "VIOLATED"
|
|
917
|
+
return {
|
|
918
|
+
"schema_version": RUNTIME_SCHEMA,
|
|
919
|
+
"runtime_identity": _runtime_identity(),
|
|
920
|
+
"terminal_state": terminal,
|
|
921
|
+
"validation_terminal_state": terminal,
|
|
922
|
+
"finding_count": len(findings),
|
|
923
|
+
"findings": findings,
|
|
924
|
+
"validated_path_count": len(docs),
|
|
925
|
+
"validated_paths": [str(p) for p in docs],
|
|
926
|
+
"claim_boundary": {
|
|
927
|
+
"verifies": [
|
|
928
|
+
"The packed artifact executed the bundled frontmattering.validate_docset code path.",
|
|
929
|
+
"The verdict identity binds the bundled Frontmattering frontmatter spec bytes.",
|
|
930
|
+
],
|
|
931
|
+
"does_not_verify": [
|
|
932
|
+
"Authoring, mutation, indexing, promotion, initialization, or release approval surfaces.",
|
|
933
|
+
"External adoption, semantic adequacy, or production readiness.",
|
|
934
|
+
],
|
|
935
|
+
},
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
def _parse_args(argv: list[str]) -> tuple[str, list[str], str]:
|
|
940
|
+
if len(argv) < 2 or argv[1] != "validate":
|
|
941
|
+
return "usage", [], "json"
|
|
942
|
+
paths: list[str] = []
|
|
943
|
+
fmt = "json"
|
|
944
|
+
i = 2
|
|
945
|
+
while i < len(argv):
|
|
946
|
+
if argv[i] == "--format":
|
|
947
|
+
if i + 1 >= len(argv) or argv[i + 1] not in {"json"}:
|
|
948
|
+
return "bad_format", [], fmt
|
|
949
|
+
fmt = argv[i + 1]
|
|
950
|
+
i += 2
|
|
951
|
+
else:
|
|
952
|
+
paths.append(argv[i])
|
|
953
|
+
i += 1
|
|
954
|
+
return "validate", paths, fmt
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def main(argv: list[str] | None = None) -> int:
|
|
958
|
+
argv = list(sys.argv if argv is None else argv)
|
|
959
|
+
action, paths, _fmt = _parse_args(argv)
|
|
960
|
+
if action == "usage":
|
|
961
|
+
print("usage: frontmattering-runtime validate <path> [<path> ...] [--format json]", file=sys.stderr)
|
|
962
|
+
return 2
|
|
963
|
+
if action == "bad_format":
|
|
964
|
+
print("frontmattering-runtime only supports --format json", file=sys.stderr)
|
|
965
|
+
return 2
|
|
966
|
+
report = validate(paths)
|
|
967
|
+
print(json.dumps(report, indent=2, sort_keys=True, default=str))
|
|
968
|
+
return 0 if report["terminal_state"] == "GO" else 1
|
|
969
|
+
|
|
970
|
+
|
|
971
|
+
if __name__ == "__main__":
|
|
972
|
+
raise SystemExit(main(sys.argv))
|
|
973
|
+
"""
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def _runtime_payload_files() -> list[Path]:
|
|
977
|
+
"""Return the minimal validate-only runtime payload.
|
|
978
|
+
|
|
979
|
+
The runtime must carry the actual validation modules and the spec resource,
|
|
980
|
+
but it intentionally omits fixtures, authoring tools, generated reports, and
|
|
981
|
+
mutable/diagnostic surfaces. Keeping the payload narrow prevents a slow or
|
|
982
|
+
misleading "whole distribution" runtime while preserving kernel validation
|
|
983
|
+
semantics.
|
|
984
|
+
"""
|
|
985
|
+
relative_files = [
|
|
986
|
+
"__init__.py",
|
|
987
|
+
"harness_errors.py",
|
|
988
|
+
"core.py",
|
|
989
|
+
"speccompile.py",
|
|
990
|
+
"body_tether.py",
|
|
991
|
+
"corpus_selection.py",
|
|
992
|
+
"validator.py",
|
|
993
|
+
"resources/FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md",
|
|
994
|
+
]
|
|
995
|
+
files = [PACKAGE_ROOT / rel for rel in relative_files]
|
|
996
|
+
missing = [str(path.relative_to(PACKAGE_ROOT)) for path in files if not path.is_file()]
|
|
997
|
+
if missing:
|
|
998
|
+
raise ToolingError("Runtime payload assembly failed", "frontmattering runtime pack", f"Required runtime payload files are missing: {missing}", "Restore the validate-only runtime files before packing.")
|
|
999
|
+
return files
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
def _tree_sha256(files: list[Path]) -> str:
|
|
1003
|
+
h = hashlib.sha256()
|
|
1004
|
+
for path in files:
|
|
1005
|
+
rel = path.relative_to(PACKAGE_ROOT).as_posix().encode("utf-8")
|
|
1006
|
+
data = path.read_bytes()
|
|
1007
|
+
h.update(len(rel).to_bytes(8, "big"))
|
|
1008
|
+
h.update(rel)
|
|
1009
|
+
h.update(len(data).to_bytes(8, "big"))
|
|
1010
|
+
h.update(data)
|
|
1011
|
+
return h.hexdigest()
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
def _runtime_wrapper() -> str:
|
|
1015
|
+
files = _runtime_payload_files()
|
|
1016
|
+
manifest = {
|
|
1017
|
+
"frontmattering_tree_sha256": _tree_sha256(files),
|
|
1018
|
+
"packed_file_count": len(files),
|
|
1019
|
+
"runtime_builder": "frontmattering.__main__.command_runtime_pack",
|
|
1020
|
+
}
|
|
1021
|
+
return RUNTIME_WRAPPER_TEMPLATE.replace(
|
|
1022
|
+
"__KERNEL_VERSION__", "0.4.2"
|
|
1023
|
+
).replace(
|
|
1024
|
+
"__RUNTIME_BUILD_MANIFEST__", json.dumps(manifest, sort_keys=True)
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
def _write_runtime_zipapp(out: Path, wrapper: str) -> None:
|
|
1029
|
+
if out.exists() and out.is_dir():
|
|
1030
|
+
shutil.rmtree(out)
|
|
1031
|
+
elif out.exists():
|
|
1032
|
+
out.unlink()
|
|
1033
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
1034
|
+
files = _runtime_payload_files()
|
|
1035
|
+
with zipfile.ZipFile(out, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
|
1036
|
+
main_info = zipfile.ZipInfo("__main__.py")
|
|
1037
|
+
main_info.date_time = (1980, 1, 1, 0, 0, 0)
|
|
1038
|
+
main_info.external_attr = 0o100755 << 16
|
|
1039
|
+
zf.writestr(main_info, wrapper.encode("utf-8"))
|
|
1040
|
+
for src in files:
|
|
1041
|
+
rel = "frontmattering/" + src.relative_to(PACKAGE_ROOT).as_posix()
|
|
1042
|
+
info = zipfile.ZipInfo(rel)
|
|
1043
|
+
info.date_time = (1980, 1, 1, 0, 0, 0)
|
|
1044
|
+
mode = 0o100755 if os.access(src, os.X_OK) else 0o100644
|
|
1045
|
+
info.external_attr = mode << 16
|
|
1046
|
+
zf.writestr(info, src.read_bytes())
|
|
1047
|
+
try:
|
|
1048
|
+
out.chmod(out.stat().st_mode | 0o111)
|
|
1049
|
+
except OSError as exc:
|
|
1050
|
+
raise ToolingError(
|
|
1051
|
+
"Runtime artifact permission update failed",
|
|
1052
|
+
str(out),
|
|
1053
|
+
"The packed runtime must not silently hide incomplete filesystem finalization.",
|
|
1054
|
+
"Check filesystem permissions or choose a writable output path.",
|
|
1055
|
+
{"error": str(exc)},
|
|
1056
|
+
error_id="FRONTMATTERING-RUNTIME-PACK-CHMOD-FAILED",
|
|
1057
|
+
) from exc
|
|
1058
|
+
|
|
1059
|
+
def command_runtime_pack(args: argparse.Namespace) -> int:
|
|
1060
|
+
out = Path(args.out)
|
|
1061
|
+
wrapper = _runtime_wrapper()
|
|
1062
|
+
_write_runtime_zipapp(out, wrapper)
|
|
1063
|
+
artifact_bytes = out.read_bytes()
|
|
1064
|
+
files = _runtime_payload_files()
|
|
1065
|
+
payload = {
|
|
1066
|
+
"schema_version": "frontmattering_runtime_pack_report/v0_2",
|
|
1067
|
+
"runtime_artifact": str(out),
|
|
1068
|
+
"canonical_model_version": "0.1.5",
|
|
1069
|
+
"kernel_version": "0.4.2",
|
|
1070
|
+
"artifact_format": "python_zipapp",
|
|
1071
|
+
"surface": "validate_only",
|
|
1072
|
+
"artifact_sha256": hashlib.sha256(artifact_bytes).hexdigest(),
|
|
1073
|
+
"spec_sha256": hashlib.sha256(SPEC_PATH.read_bytes()).hexdigest(),
|
|
1074
|
+
"validator_sha256": hashlib.sha256((PACKAGE_ROOT / "validator.py").read_bytes()).hexdigest(),
|
|
1075
|
+
"frontmattering_tree_sha256": _tree_sha256(files),
|
|
1076
|
+
"packed_file_count": len(files),
|
|
1077
|
+
"claim_boundary": {
|
|
1078
|
+
"verifies": [
|
|
1079
|
+
"The runtime artifact carries frontmattering validation modules and the Frontmattering spec resource.",
|
|
1080
|
+
"The runtime surface is validate-only.",
|
|
1081
|
+
],
|
|
1082
|
+
"does_not_verify": [
|
|
1083
|
+
"Authoring, mutation, release approval, external adoption, or production readiness.",
|
|
1084
|
+
],
|
|
1085
|
+
},
|
|
1086
|
+
}
|
|
1087
|
+
if args.format == "json":
|
|
1088
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
1089
|
+
else:
|
|
1090
|
+
print(
|
|
1091
|
+
"runtime_pack=PASS "
|
|
1092
|
+
f"artifact={payload['runtime_artifact']} "
|
|
1093
|
+
f"format={payload['artifact_format']} "
|
|
1094
|
+
f"canonical_model_version={payload['canonical_model_version']} "
|
|
1095
|
+
f"kernel_version={payload['kernel_version']} "
|
|
1096
|
+
f"sha256={payload['artifact_sha256']} "
|
|
1097
|
+
f"spec_sha256={payload['spec_sha256']} "
|
|
1098
|
+
f"validator_sha256={payload['validator_sha256']}"
|
|
1099
|
+
)
|
|
1100
|
+
return EXIT_GO
|
|
1101
|
+
|
|
1102
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
1103
|
+
parser = StrictArgumentParser(
|
|
1104
|
+
prog="frontmattering",
|
|
1105
|
+
description="Govern and orient load-bearing Markdown with Frontmattering canonical model v0.1.5 frontmatter.",
|
|
1106
|
+
)
|
|
1107
|
+
parser.add_argument("--version", action="version", version="frontmattering distribution v0.4.2; canonical model v0.1.5")
|
|
1108
|
+
sub = parser.add_subparsers(dest="command", required=True, parser_class=StrictArgumentParser)
|
|
1109
|
+
|
|
1110
|
+
init = sub.add_parser("init", help="Initialize a small Frontmattering document set and package router.")
|
|
1111
|
+
init.add_argument("directory")
|
|
1112
|
+
init.add_argument("--set-id", required=True, help="Stable document-set identifier.")
|
|
1113
|
+
init.add_argument("--title", required=True, help="Human-readable document-set title.")
|
|
1114
|
+
init.add_argument("--force", action="store_true", help="Overwrite generated project files.")
|
|
1115
|
+
init.set_defaults(func=command_init)
|
|
1116
|
+
|
|
1117
|
+
new = sub.add_parser("new", help="Create a canonical document from a practical starter profile.")
|
|
1118
|
+
new.add_argument("path", help="Output Markdown path inside an initialized Frontmattering v0.3 project.")
|
|
1119
|
+
new.add_argument("--profile", choices=sorted(PROFILES), default="guide")
|
|
1120
|
+
new.add_argument("--title", required=True)
|
|
1121
|
+
new.add_argument("--summary")
|
|
1122
|
+
new.add_argument("--force", action="store_true")
|
|
1123
|
+
new.set_defaults(func=command_new)
|
|
1124
|
+
|
|
1125
|
+
inventory = sub.add_parser("inventory", help="Classify selected governed and plain Markdown without writing files.")
|
|
1126
|
+
inventory.add_argument("paths", nargs="*", default=["."])
|
|
1127
|
+
inventory.add_argument("--format", choices=["markdown", "json"], default="markdown")
|
|
1128
|
+
inventory.set_defaults(func=command_inventory)
|
|
1129
|
+
|
|
1130
|
+
promote = sub.add_parser("promote", help="Add a shipped starter-profile frontmatter block to existing plain Markdown.")
|
|
1131
|
+
promote.add_argument("input")
|
|
1132
|
+
promote.add_argument("--profile", choices=sorted(PROFILES), required=True)
|
|
1133
|
+
promote.add_argument("--title")
|
|
1134
|
+
promote.add_argument("--output")
|
|
1135
|
+
promote.add_argument("--in-place", action="store_true")
|
|
1136
|
+
promote.add_argument("--check", action="store_true", help="Emit the proposed receipt without writing.")
|
|
1137
|
+
promote.add_argument("--format", choices=["markdown", "json"], default="markdown")
|
|
1138
|
+
promote.set_defaults(func=command_promote)
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
parse_cmd = sub.add_parser("parse", help="Emit security-aware JSON document IR using the Frontmattering token-based Markdown parser.")
|
|
1142
|
+
parse_cmd.add_argument("path", help="Markdown file to parse.")
|
|
1143
|
+
parse_cmd.add_argument("--output", help="Write JSON IR to this path, or batch output directory with --batch.")
|
|
1144
|
+
parse_cmd.add_argument("--batch", action="store_true", help="Compile every .md file below PATH into a disposable IR directory.")
|
|
1145
|
+
parse_cmd.add_argument("--security-profile", choices=["permissive", "moderate", "strict"], default="moderate")
|
|
1146
|
+
parse_cmd.add_argument("--allows-html", action="store_true", help="Tokenize raw HTML so it is represented and security-scanned.")
|
|
1147
|
+
parse_cmd.set_defaults(func=command_parse)
|
|
1148
|
+
|
|
1149
|
+
index_cmd = sub.add_parser("index", help="Build and verify the disposable SQLite canonical-frontmatter index.")
|
|
1150
|
+
index_sub = index_cmd.add_subparsers(dest="index_action", required=True, parser_class=StrictArgumentParser)
|
|
1151
|
+
for action in ("build", "rebuild", "update"):
|
|
1152
|
+
sp = index_sub.add_parser(action, help=f"{action.capitalize()} the digest-bound frontmatter index.")
|
|
1153
|
+
sp.add_argument("corpus", nargs="?", default=".")
|
|
1154
|
+
sp.add_argument("--output", default=str(DEFAULT_INDEX))
|
|
1155
|
+
sp.set_defaults(func=command_index)
|
|
1156
|
+
for action in ("check", "status", "delete"):
|
|
1157
|
+
sp = index_sub.add_parser(action, help=f"{action.capitalize()} the frontmatter index.")
|
|
1158
|
+
sp.add_argument("--index", default=str(DEFAULT_INDEX))
|
|
1159
|
+
sp.set_defaults(func=command_index)
|
|
1160
|
+
|
|
1161
|
+
query_cmd = sub.add_parser("query", help="Query only freshness-proven canonical frontmatter projections.")
|
|
1162
|
+
query_cmd.add_argument("--index", default=str(DEFAULT_INDEX))
|
|
1163
|
+
query_cmd.add_argument("--task")
|
|
1164
|
+
query_cmd.add_argument("--authority")
|
|
1165
|
+
query_cmd.add_argument("--role")
|
|
1166
|
+
query_cmd.add_argument("--target", help="Return documents whose relationship target matches this document ID.")
|
|
1167
|
+
query_cmd.set_defaults(func=command_query)
|
|
1168
|
+
|
|
1169
|
+
validate = sub.add_parser("validate", help="Validate one Markdown file, many files, or a directory.")
|
|
1170
|
+
validate.add_argument("paths", nargs="*", default=["."])
|
|
1171
|
+
validate.add_argument("--format", choices=["markdown", "json"], default="markdown")
|
|
1172
|
+
validate.set_defaults(func=command_validate)
|
|
1173
|
+
|
|
1174
|
+
mesh = sub.add_parser("mesh", help="Render the frontmatter-only document mesh.")
|
|
1175
|
+
mesh.add_argument("paths", nargs="*", default=["."])
|
|
1176
|
+
mesh.add_argument("--format", choices=["markdown", "json"], default="markdown")
|
|
1177
|
+
mesh.set_defaults(func=command_mesh)
|
|
1178
|
+
|
|
1179
|
+
brief = sub.add_parser("brief", help="Render the cold-start hazard and reading-order brief.")
|
|
1180
|
+
brief.add_argument("paths", nargs="*", default=["."])
|
|
1181
|
+
brief.add_argument("--task", help="Optional read_when_tags task filter.")
|
|
1182
|
+
brief.add_argument(
|
|
1183
|
+
"--section",
|
|
1184
|
+
choices=["summary", "blocks", "traps", "limits", "do-not", "trust", "reading-order", "defects", "claim-boundary"],
|
|
1185
|
+
default="summary",
|
|
1186
|
+
help="Render the bounded summary or expand one hazard section.",
|
|
1187
|
+
)
|
|
1188
|
+
brief.add_argument("--include-dev", action="store_true",
|
|
1189
|
+
help="Include fixtures, archive, mesh, usecase, and distro smoke docs.")
|
|
1190
|
+
brief.add_argument("--format", choices=["markdown", "json"], default="markdown")
|
|
1191
|
+
brief.set_defaults(func=command_brief)
|
|
1192
|
+
|
|
1193
|
+
explain = sub.add_parser("explain", help="Explain one FRONTMATTERING-FM finding identifier.")
|
|
1194
|
+
explain.add_argument("finding_id")
|
|
1195
|
+
explain.add_argument("--format", choices=["markdown", "json"], default="markdown")
|
|
1196
|
+
explain.set_defaults(func=command_explain)
|
|
1197
|
+
|
|
1198
|
+
doctor = sub.add_parser("doctor", help="Run the kernel's targeted self-checks.")
|
|
1199
|
+
doctor.add_argument("--format", choices=["markdown", "json"], default="markdown")
|
|
1200
|
+
doctor.set_defaults(func=command_doctor)
|
|
1201
|
+
|
|
1202
|
+
catalog = sub.add_parser(
|
|
1203
|
+
"catalog",
|
|
1204
|
+
help="Print the closed model: root blocks and controlled vocabularies.",
|
|
1205
|
+
)
|
|
1206
|
+
catalog.add_argument("--field", help="Show one field's type, obligation, and allowed values.")
|
|
1207
|
+
catalog.add_argument("--format", choices=["markdown", "json"], default="markdown")
|
|
1208
|
+
catalog.set_defaults(func=command_catalog)
|
|
1209
|
+
|
|
1210
|
+
schema = sub.add_parser(
|
|
1211
|
+
"schema",
|
|
1212
|
+
help="Emit a corpus-aware JSON Schema projection for editor and CI tooling.",
|
|
1213
|
+
)
|
|
1214
|
+
schema.add_argument("paths", nargs="*", help="Optional corpus paths whose router task_catalog widens the task-tag enum.")
|
|
1215
|
+
schema.add_argument("--output", help="Write the schema projection to this path.")
|
|
1216
|
+
schema.add_argument("--check", metavar="PATH", help="Fail if PATH differs from fresh emission.")
|
|
1217
|
+
schema.add_argument("--vscode", action="store_true", help="Print a VS Code YAML schema settings fragment.")
|
|
1218
|
+
schema.set_defaults(func=command_schema)
|
|
1219
|
+
|
|
1220
|
+
tether = sub.add_parser("tether", help="Check or refresh body/frontmatter freshness tethers.")
|
|
1221
|
+
tether_sub = tether.add_subparsers(dest="tether_action", required=True, parser_class=StrictArgumentParser)
|
|
1222
|
+
tether_check = tether_sub.add_parser("check", help="Report FRESH / STALE / UNTETHERED status for governed Markdown.")
|
|
1223
|
+
tether_check.add_argument("paths", nargs="*", default=["."])
|
|
1224
|
+
tether_check.add_argument("--format", choices=["text", "json"], default="text")
|
|
1225
|
+
tether_check.set_defaults(func=command_tether_check)
|
|
1226
|
+
tether_update = tether_sub.add_parser("update", help="Refresh existing body_tether digests; pass --add to create missing tethers.")
|
|
1227
|
+
tether_update.add_argument("paths", nargs="*", default=["."])
|
|
1228
|
+
tether_update.add_argument("--add", action="store_true", help="Add body_tether blocks to documents that do not already carry one.")
|
|
1229
|
+
tether_update.add_argument("--format", choices=["text", "json"], default="text")
|
|
1230
|
+
tether_update.set_defaults(func=command_tether_update)
|
|
1231
|
+
|
|
1232
|
+
runtime = sub.add_parser("runtime", help="Produce or inspect validate-only Frontmattering runtime artifacts.")
|
|
1233
|
+
runtime_sub = runtime.add_subparsers(dest="runtime_action", required=True, parser_class=StrictArgumentParser)
|
|
1234
|
+
runtime_pack = runtime_sub.add_parser("pack", help="Write a self-contained validate-only runtime artifact.")
|
|
1235
|
+
runtime_pack.add_argument("--out", required=True, help="Output path for the runtime artifact.")
|
|
1236
|
+
runtime_pack.add_argument("--format", choices=["text", "json"], default="text")
|
|
1237
|
+
runtime_pack.set_defaults(func=command_runtime_pack)
|
|
1238
|
+
|
|
1239
|
+
return parser
|
|
1240
|
+
|
|
1241
|
+
|
|
1242
|
+
def main(argv: list[str] | None = None) -> int:
|
|
1243
|
+
parser = build_parser()
|
|
1244
|
+
args = parser.parse_args(argv)
|
|
1245
|
+
try:
|
|
1246
|
+
return args.func(args)
|
|
1247
|
+
except BrokenPipeError:
|
|
1248
|
+
return EXIT_GO
|
|
1249
|
+
except HarnessError as exc:
|
|
1250
|
+
print(exc, file=sys.stderr)
|
|
1251
|
+
return EXIT_INTERNAL
|
|
1252
|
+
except Exception as exc:
|
|
1253
|
+
wrapped = InternalBugError(
|
|
1254
|
+
f"Unexpected kernel exception: {type(exc).__name__}",
|
|
1255
|
+
where="frontmattering.__main__.main",
|
|
1256
|
+
context={"original_error": str(exc), "original_type": type(exc).__name__},
|
|
1257
|
+
)
|
|
1258
|
+
print(wrapped, file=sys.stderr)
|
|
1259
|
+
return EXIT_INTERNAL
|
|
1260
|
+
|
|
1261
|
+
|
|
1262
|
+
if __name__ == "__main__":
|
|
1263
|
+
sys.exit(main())
|