diff --git a/packs/register/assertive_v1.json b/packs/register/assertive_v1.json index 7c36418e..543c6997 100644 --- a/packs/register/assertive_v1.json +++ b/packs/register/assertive_v1.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Assertive register — declarative markers, no hedging.", "schema_version": "1.0.0", - "mastery_report_sha256": "44750505a1fce4eaadf47a4fe5a22c8d3d53d41957f5d161797a539dc9258844", + "mastery_report_sha256": "785f079aad5e2492ed7d054716ed2ecf897622230bd2cc96d419cf0ebe9cc9f5", "display_name": "Assertive", "depth_preference": "standard", "realizer_overrides": { @@ -19,8 +19,7 @@ ], "transitions": [], "closings": [ - "", - "." + "" ] } } diff --git a/packs/register/assertive_v1.mastery_report.json b/packs/register/assertive_v1.mastery_report.json index a2980108..4eb21264 100644 --- a/packs/register/assertive_v1.mastery_report.json +++ b/packs/register/assertive_v1.mastery_report.json @@ -3,7 +3,7 @@ "depth_preference": "standard", "discourse_markers_empty": false, "marker_bucket_sizes": { - "closings": 2, + "closings": 1, "openings": 4, "transitions": 0 }, @@ -16,10 +16,10 @@ }, "failure_reasons": [], "issued_at": "2026-05-20T00:00:00Z", - "pack_source_sha256": "a6d1c49aee2ef5222c7e4481ddb968b40be4eb13cae6eaa14ad140a1a60ec128", + "pack_source_sha256": "f542dae9062ee981018846517da65c6480200138e029df7254669b5b0a9dfc44", "ratification_method": "seeded_variation_replay_equivalence", "ratified": true, "register_id": "assertive_v1", - "report_sha256": "44750505a1fce4eaadf47a4fe5a22c8d3d53d41957f5d161797a539dc9258844", + "report_sha256": "785f079aad5e2492ed7d054716ed2ecf897622230bd2cc96d419cf0ebe9cc9f5", "schema_version": "1.0.0" } diff --git a/packs/register/blunt_v1.json b/packs/register/blunt_v1.json index 06fb7a3f..5b815b1e 100644 --- a/packs/register/blunt_v1.json +++ b/packs/register/blunt_v1.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Blunt register — short, sharp, no qualification.", "schema_version": "1.0.0", - "mastery_report_sha256": "2925ce07bd132a235296098d135ad46f171efc46f2cead69b06f52d309661d33", + "mastery_report_sha256": "0c435910a295c7cade443cccfd175c90f0302cc90f0c5a6c613ef62c190462ed", "display_name": "Blunt", "depth_preference": "terse", "realizer_overrides": { @@ -21,8 +21,7 @@ ], "transitions": [], "closings": [ - "", - "." + "" ] } } diff --git a/packs/register/blunt_v1.mastery_report.json b/packs/register/blunt_v1.mastery_report.json index 6a242821..7f388130 100644 --- a/packs/register/blunt_v1.mastery_report.json +++ b/packs/register/blunt_v1.mastery_report.json @@ -3,7 +3,7 @@ "depth_preference": "terse", "discourse_markers_empty": false, "marker_bucket_sizes": { - "closings": 2, + "closings": 1, "openings": 4, "transitions": 0 }, @@ -18,10 +18,10 @@ }, "failure_reasons": [], "issued_at": "2026-05-20T00:00:00Z", - "pack_source_sha256": "71a9bc92336de59c4014777e5d3e36bdece30c6519e21f7fd8df984f7847309f", + "pack_source_sha256": "6f148b6f90971ff4be82d8fa22001d890e2f8c98ac8f6caa4f453328ba1eabcd", "ratification_method": "seeded_variation_replay_equivalence", "ratified": true, "register_id": "blunt_v1", - "report_sha256": "2925ce07bd132a235296098d135ad46f171efc46f2cead69b06f52d309661d33", + "report_sha256": "0c435910a295c7cade443cccfd175c90f0302cc90f0c5a6c613ef62c190462ed", "schema_version": "1.0.0" } diff --git a/packs/register/dry_v1.json b/packs/register/dry_v1.json index 17bcc9a4..fa8c297c 100644 --- a/packs/register/dry_v1.json +++ b/packs/register/dry_v1.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Dry register — understated, factual.", "schema_version": "1.0.0", - "mastery_report_sha256": "1f3232b4eb99d6df156de3f34c5e8fe24f8453ac04a59bb3bd41553ffc5b169a", + "mastery_report_sha256": "6f413313c0cc42bae47dfb3f1273d429006c0b4405a48764ad6aac8d69da3ebc", "display_name": "Dry", "depth_preference": "standard", "realizer_overrides": { @@ -19,7 +19,6 @@ "transitions": [], "closings": [ "", - ".", " — that's it." ] } diff --git a/packs/register/dry_v1.mastery_report.json b/packs/register/dry_v1.mastery_report.json index c6feae1f..dd7b1b2d 100644 --- a/packs/register/dry_v1.mastery_report.json +++ b/packs/register/dry_v1.mastery_report.json @@ -3,7 +3,7 @@ "depth_preference": "standard", "discourse_markers_empty": false, "marker_bucket_sizes": { - "closings": 3, + "closings": 2, "openings": 3, "transitions": 0 }, @@ -16,10 +16,10 @@ }, "failure_reasons": [], "issued_at": "2026-05-20T00:00:00Z", - "pack_source_sha256": "eccd21f4b00102526fc22d28bac38e091dbda30a408848ac6f42bbfc16716bfb", + "pack_source_sha256": "0db49fb251edc60106c04ca75b3422b4661a26e8e1ac17cd9ef2c7973f6617c8", "ratification_method": "seeded_variation_replay_equivalence", "ratified": true, "register_id": "dry_v1", - "report_sha256": "1f3232b4eb99d6df156de3f34c5e8fe24f8453ac04a59bb3bd41553ffc5b169a", + "report_sha256": "6f413313c0cc42bae47dfb3f1273d429006c0b4405a48764ad6aac8d69da3ebc", "schema_version": "1.0.0" } diff --git a/packs/register/mathematical_v1.json b/packs/register/mathematical_v1.json index 435d6a38..39b1e328 100644 --- a/packs/register/mathematical_v1.json +++ b/packs/register/mathematical_v1.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Mathematical register — proof-cadence.", "schema_version": "1.0.0", - "mastery_report_sha256": "04e9a09cce4e599781b2d4994383cebf4f6529391f58005a84e69363494508c2", + "mastery_report_sha256": "b85e2505fe647aa474b599cfa447c3677ad92c7c305d34ae63c97c8b5ba82fb6", "display_name": "Mathematical", "depth_preference": "standard", "realizer_overrides": { @@ -21,7 +21,6 @@ "transitions": [], "closings": [ "", - ".", " — qed." ] } diff --git a/packs/register/mathematical_v1.mastery_report.json b/packs/register/mathematical_v1.mastery_report.json index dc6baa12..2b30d05c 100644 --- a/packs/register/mathematical_v1.mastery_report.json +++ b/packs/register/mathematical_v1.mastery_report.json @@ -3,7 +3,7 @@ "depth_preference": "standard", "discourse_markers_empty": false, "marker_bucket_sizes": { - "closings": 3, + "closings": 2, "openings": 4, "transitions": 0 }, @@ -17,10 +17,10 @@ }, "failure_reasons": [], "issued_at": "2026-05-20T00:00:00Z", - "pack_source_sha256": "f7885f683ad806730ede0ce1a0a4f3d2503ead583719f055949e2a1ccdad281e", + "pack_source_sha256": "59be9a46ca10a612aa201fcb3736d1791505b9fea5add9b40ac5e57e21f84028", "ratification_method": "seeded_variation_replay_equivalence", "ratified": true, "register_id": "mathematical_v1", - "report_sha256": "04e9a09cce4e599781b2d4994383cebf4f6529391f58005a84e69363494508c2", + "report_sha256": "b85e2505fe647aa474b599cfa447c3677ad92c7c305d34ae63c97c8b5ba82fb6", "schema_version": "1.0.0" } diff --git a/scripts/ratify_register_packs.py b/scripts/ratify_register_packs.py index e3b01d14..3145c9c3 100644 --- a/scripts/ratify_register_packs.py +++ b/scripts/ratify_register_packs.py @@ -266,6 +266,64 @@ def _validate_per_intent_block( ) +_BARE_SENTENCE_FINAL_PUNCT: frozenset[str] = frozenset(".!?;:") +"""Closing entries beginning with one of these characters concatenate +directly onto a surface that already ends in ``.`` (CORE's deterministic +realizer always emits a trailing period) and produce double-punctuation +artefacts like ``true..``. Ellipsis (``...`` / ``…``) is exempt — that +is a legitimate stylistic ending in its own right. +""" + + +def _validate_discourse_markers_shape(pack: dict, register_id: str) -> None: + """Reject discourse-marker shapes that would produce surface + artefacts when concatenated to a sentence-final surface. + + Catches the class of authoring bug discovered during the 100-pack + widened tour: a bare ``"."`` closing yields ``..`` when seeded + variation picks it. Defense in depth — pack authors should not + have to remember the realizer's trailing-period convention. + + Rules (closings only — openings always lead the surface with a + space separator inserted by the composer): + * ``""`` is always legal (no-op closing) + * Entries starting with bare ``.``, ``!``, ``?``, ``;``, ``:`` + are refused — they double-punctuate. Ellipsis (``"..."`` / + ``"…"``) is exempt and remains legal. + * Entries starting with an alphanumeric character are refused — + they would join with no separator, e.g. ``"true.holds."``. + """ + markers = pack.get("discourse_markers", {}) + if not isinstance(markers, dict): + return # shape-only — schema validator owns dict-vs-not + closings = markers.get("closings", []) + if not isinstance(closings, list): + return + for entry in closings: + if not isinstance(entry, str) or entry == "": + continue + first = entry[0] + if entry.startswith(("...", "…")): + continue + if first in _BARE_SENTENCE_FINAL_PUNCT: + raise SystemExit( + f"R4 gate refuses {register_id!r}: discourse_markers." + f"closings[{entry!r}] starts with bare sentence-final " + f"punctuation. CORE's realizer always emits a trailing " + f"'.', so {entry!r} concatenates to '..'-style " + f"artefacts. Either drop the entry or prefix it with a " + f"separator (e.g. ' — note.')." + ) + if first.isalnum(): + raise SystemExit( + f"R4 gate refuses {register_id!r}: discourse_markers." + f"closings[{entry!r}] starts with an alphanumeric " + f"character. Closings concatenate directly to the " + f"surface — prefix with a space or punctuation " + f"separator (e.g. ' note.' or ' — note.')." + ) + + def _markers_have_content(pack: dict) -> bool: """True iff openings or closings has at least one entry (R4 needs one of these populated for a marker-using ratification). @@ -301,6 +359,7 @@ def _ratify_one(pack_path: Path, register_id: str) -> tuple[dict, dict[str, Any] # must have at least one of openings/closings populated. Null # registers pass trivially. _validate_overrides_known_keys(pack, register_id) + _validate_discourse_markers_shape(pack, register_id) null_register = _is_null_register(pack) overrides = pack.get("realizer_overrides", {}) or {}