From 96d87b1b2b365a1050a1740dce1dd5b0a6a068b9 Mon Sep 17 00:00:00 2001 From: lll-hhh <169072815+lll-hhh@users.noreply.github.com> Date: Thu, 29 Oct 2026 22:51:02 +0800 Subject: [PATCH] refactor(public-safety): declare the compact label and identity shapes once Five contracts compiled the 120-character-minus-one label body under their own names and three compiled the identity-token body, so a bound change had eight places to land. Move both grammars to the shared identifier owner, keep each consumer's own name, field text and bound, and extend the shape census so a new local restatement fails. Signed-off-by: lll-hhh <169072815+lll-hhh@users.noreply.github.com> --- .../context_providers/service_ownership.py | 3 +- .../issue_fix/repository_context.py | 2 +- .../issue_fix/repository_memory.py | 2 +- .../issue_fix/repository_memory_provider.py | 2 +- .../issue_fix/reviewer_recommendation.py | 2 +- .../reliability_diagnostics/envelope.py | 2 +- loopx/control_plane/runtime/public_safety.py | 4 +- .../history_export.py | 2 +- loopx/public_safe_text.py | 15 ++++ .../test_compact_identifier_shape_owner.py | 70 +++++++++++++++++++ 10 files changed, 93 insertions(+), 11 deletions(-) diff --git a/loopx/capabilities/context_providers/service_ownership.py b/loopx/capabilities/context_providers/service_ownership.py index c4369f5a38..20c51f000e 100644 --- a/loopx/capabilities/context_providers/service_ownership.py +++ b/loopx/capabilities/context_providers/service_ownership.py @@ -2,12 +2,12 @@ import hashlib import json -import re from dataclasses import dataclass from pathlib import Path from typing import Mapping from ...file_lock import process_is_alive +from ...public_safe_text import COMPACT_LABEL_PATTERN as _LABEL CONTEXT_PROVIDER_SERVICE_RECEIPT_SCHEMA_VERSION = ( @@ -22,7 +22,6 @@ "pid", "observed_at", } -_LABEL = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,119}$") def _opaque_ref(kind: str, value: str) -> str: diff --git a/loopx/capabilities/issue_fix/repository_context.py b/loopx/capabilities/issue_fix/repository_context.py index 2730b08e37..eeff1c8d0e 100644 --- a/loopx/capabilities/issue_fix/repository_context.py +++ b/loopx/capabilities/issue_fix/repository_context.py @@ -9,6 +9,7 @@ from urllib.parse import urlsplit from ...control_plane.runtime.public_safety import public_safe_compact_text +from ...public_safe_text import COMPACT_LABEL_PATTERN as _SOURCE_ID_PATTERN from .repository_memory import ( SUPPORT_ASPECTS, build_issue_fix_repository_memory_hook, @@ -36,7 +37,6 @@ REQUIRED_FIX_ASPECTS = ("change_scope", "reproduction", "validation") MAX_SOURCES = 16 -_SOURCE_ID_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,119}$") _INPUT_FIELDS = {"schema_version", "repository_revision", "sources"} _SOURCE_FIELDS = { "source_id", diff --git a/loopx/capabilities/issue_fix/repository_memory.py b/loopx/capabilities/issue_fix/repository_memory.py index 1684f8c69b..a76a6f7419 100644 --- a/loopx/capabilities/issue_fix/repository_memory.py +++ b/loopx/capabilities/issue_fix/repository_memory.py @@ -7,6 +7,7 @@ from typing import Any from ...control_plane.runtime.public_safety import public_safe_compact_text +from ...public_safe_text import COMPACT_LABEL_PATTERN as _LABEL_PATTERN ISSUE_FIX_REPOSITORY_MEMORY_READ_RESULT_SCHEMA_VERSION = ( @@ -31,7 +32,6 @@ } MAX_MEMORY_RESULTS = 6 -_LABEL_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,119}$") _INPUT_FIELDS = { "schema_version", "provider", diff --git a/loopx/capabilities/issue_fix/repository_memory_provider.py b/loopx/capabilities/issue_fix/repository_memory_provider.py index c402461626..a139d1f5dd 100644 --- a/loopx/capabilities/issue_fix/repository_memory_provider.py +++ b/loopx/capabilities/issue_fix/repository_memory_provider.py @@ -24,6 +24,7 @@ ) from ...repository_identity import resolve_project_identity from ...control_plane.runtime.public_safety import public_safe_compact_text +from ...public_safe_text import COMPACT_LABEL_PATTERN as _LABEL from .repository_memory import ( ISSUE_FIX_REPOSITORY_MEMORY_READ_RESULT_SCHEMA_VERSION, MAX_MEMORY_RESULTS, @@ -66,7 +67,6 @@ "active_repository_revision", "active_scope_ref", } -_LABEL = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,119}$") _FORBIDDEN_WRITEBACK_FIELDS = { "credential", "credentials", diff --git a/loopx/capabilities/issue_fix/reviewer_recommendation.py b/loopx/capabilities/issue_fix/reviewer_recommendation.py index e99addf966..55b151db37 100644 --- a/loopx/capabilities/issue_fix/reviewer_recommendation.py +++ b/loopx/capabilities/issue_fix/reviewer_recommendation.py @@ -12,6 +12,7 @@ from urllib.parse import urlsplit from ...control_plane.runtime.public_safety import public_safe_compact_text +from ...public_safe_text import COMPACT_LABEL_PATTERN as REVIEWER_SOURCE_ID_PATTERN ISSUE_FIX_REVIEWER_RECOMMENDATION_SCHEMA_VERSION = ( @@ -43,7 +44,6 @@ "path_glob", "repository_fallback", } -REVIEWER_SOURCE_ID_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,119}$") REVIEWER_SOURCE_INPUT_FIELDS = {"schema_version", "sources"} REVIEWER_SOURCE_FIELDS = { "source_id", diff --git a/loopx/capabilities/reliability_diagnostics/envelope.py b/loopx/capabilities/reliability_diagnostics/envelope.py index 842617642f..955813aff2 100644 --- a/loopx/capabilities/reliability_diagnostics/envelope.py +++ b/loopx/capabilities/reliability_diagnostics/envelope.py @@ -23,6 +23,7 @@ normalize_public_safe_field_name, validate_public_safe_value, ) +from ...public_safe_text import IDENTITY_TOKEN_PATTERN from ...session_runtime import SOURCE_ID_KEYS CAPABILITY_ID = "reliability-diagnostics" @@ -30,7 +31,6 @@ OBSERVER_ENVELOPE_SCHEMA_VERSION = "reliability_observer_envelope_v0" OBSERVER_STATS_SCHEMA_VERSION = "reliability_observer_stats_v0" -IDENTITY_TOKEN_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,120}$") SUMMARY_TOKEN_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_./:-]{0,79}$") MAX_SEQUENCE = 2**53 diff --git a/loopx/control_plane/runtime/public_safety.py b/loopx/control_plane/runtime/public_safety.py index 8526679241..68ecdd44ec 100644 --- a/loopx/control_plane/runtime/public_safety.py +++ b/loopx/control_plane/runtime/public_safety.py @@ -11,6 +11,7 @@ # --no-implicit-reexport), so the existing importers of these names from this # module are unchanged. from ...public_safe_text import ( + IDENTITY_TOKEN_PATTERN as LOOPX_COMMAND_RECORD_GOAL_ID_PATTERN, LOCAL_PATH_SURFACE_PATTERN as LOCAL_PATH_SURFACE_PATTERN, PUBLIC_SAFE_LOCAL_PATH_PATTERNS as PUBLIC_SAFE_LOCAL_PATH_PATTERNS, REMOTE_LOCATION_SURFACE_PATTERN as REMOTE_LOCATION_SURFACE_PATTERN, @@ -54,9 +55,6 @@ "diagnose", } LOOPX_COMMAND_RECORD_TODO_ID_PATTERN = re.compile(r"^todo_[A-Za-z0-9_-]{6,80}$") -LOOPX_COMMAND_RECORD_GOAL_ID_PATTERN = re.compile( - r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,120}$" -) def compact_text(text: str, *, limit: int) -> str: diff --git a/loopx/extensions/openviking_semantic_preference/history_export.py b/loopx/extensions/openviking_semantic_preference/history_export.py index 7f19c40181..2d3721dc36 100644 --- a/loopx/extensions/openviking_semantic_preference/history_export.py +++ b/loopx/extensions/openviking_semantic_preference/history_export.py @@ -33,6 +33,7 @@ from ...control_plane.runtime.public_safety import public_safe_compact_text from ...entrypoint import pin_utf8_stdio from ...history import collect_history, validate_goal_id_path_segment +from ...public_safe_text import IDENTITY_TOKEN_PATTERN as _PUBLIC_GOAL_ID_RE HISTORY_EXPORT_SCHEMA_VERSION = "loopx_history_conclusion_export_v0" @@ -44,7 +45,6 @@ _SLUG_RE = re.compile(r"[^A-Za-z0-9._-]+") # Bounded ISO-8601-ish timestamp: digits, T/space, colon, dot, +/- and Z only. _TIMESTAMP_RE = re.compile(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9:.+\-]{1,20}Z?$") -_PUBLIC_GOAL_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,120}$") _SECRET_SHAPED_GOAL_ID_RE = re.compile( r"(?i)^(?:" r"gh[pousr]_[A-Za-z0-9]{20,}|" diff --git a/loopx/public_safe_text.py b/loopx/public_safe_text.py index f5156cb49e..7096a36625 100644 --- a/loopx/public_safe_text.py +++ b/loopx/public_safe_text.py @@ -370,6 +370,21 @@ # a value this shape accepts is not yet a claim that the destination may publish # it. OPAQUE_ID_PATTERN = re.compile(r"^[A-Za-z0-9._:-]{1,200}$") +# One definition for "a compact label naming a provider, service or source". Five +# contracts each compiled this body under their own name -- the service-ownership +# projection, the Issue-Fix repository context and repository memory, the memory +# provider that publishes them, and the reviewer-recommendation route -- and each +# keeps its own field names and error text. The bound is the one those sites +# already enforced: widening it to the 120-character identity shape below would +# accept labels they reject today, so the two stay separate on purpose and +# tests/control_plane/test_compact_identifier_shape_owner.py census both. +COMPACT_LABEL_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,119}$") +# One definition for "an identity token in a public-safe envelope", the shape +# `loopx/capabilities/reliability_diagnostics/README.md` already documents for +# `goal_id` and `session_id`. Three contracts compiled it locally -- the observer +# envelope, the command-record projector and the OpenViking history export -- and +# the envelope module remains the public name its own package imports. +IDENTITY_TOKEN_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,120}$") MODULE_QUALIFIED_SURFACE_PATTERN = re.compile( r"^[a-z][a-z0-9_-]*(?:\.[a-z][a-z0-9_-]*)+$" ) diff --git a/tests/control_plane/test_compact_identifier_shape_owner.py b/tests/control_plane/test_compact_identifier_shape_owner.py index 1ebfdfce46..6bdf6c0892 100644 --- a/tests/control_plane/test_compact_identifier_shape_owner.py +++ b/tests/control_plane/test_compact_identifier_shape_owner.py @@ -8,8 +8,16 @@ from loopx import chat_action_normalization, chat_actions, chat_action_store, domain_state from loopx.capabilities.benchmark_toolkit import integrity +from loopx.capabilities.context_providers import service_ownership from loopx.capabilities.decision_context import packets, profile, sources +from loopx.capabilities.issue_fix import ( + repository_context, + repository_memory, + repository_memory_provider, + reviewer_recommendation, +) from loopx.capabilities.material_lifecycle import _validation +from loopx.capabilities.reliability_diagnostics import envelope from loopx.capabilities.reward_memory import ( application, candidate_review, @@ -20,6 +28,7 @@ registry, ) from loopx.capabilities.semantic_preference import contract +from loopx.control_plane.runtime import public_safety from loopx.domain_packs import ml_experiment from loopx.control_plane.goals import ( botmux_runtime, @@ -27,8 +36,11 @@ goal_ref_validation, ) from loopx.extensions import presentation +from loopx.extensions.openviking_semantic_preference import history_export from loopx.public_safe_text import ( + COMPACT_LABEL_PATTERN, COMPACT_TOKEN_PATTERN, + IDENTITY_TOKEN_PATTERN, LETTER_LED_COMPACT_TOKEN_PATTERN, OPAQUE_ID_PATTERN as PUBLIC_OPAQUE_ID, MODULE_QUALIFIED_SURFACE_PATTERN, @@ -40,6 +52,8 @@ REFERENCE_SHAPE = "^[A-Za-z0-9][A-Za-z0-9._:/#-]{0,199}$" COMPACT_SHAPE = "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$" LETTER_LED_SHAPE = "^[A-Za-z][A-Za-z0-9_.:-]{0,79}$" +LABEL_SHAPE = "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,119}$" +IDENTITY_SHAPE = "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,120}$" SURFACE_SHAPE = "^[a-z][a-z0-9_-]*(?:\\.[a-z][a-z0-9_-]*)+$" OPAQUE_SHAPE = "^[A-Za-z0-9._:-]{1,200}$" @@ -49,6 +63,8 @@ OPEN_OPAQUE_SITES: dict[str, int] = {} OPEN_COMPACT_SITES: dict[str, int] = {} OPEN_LETTER_LED_SITES: dict[str, int] = {} +OPEN_LABEL_SITES: dict[str, int] = {} +OPEN_IDENTITY_SITES: dict[str, int] = {} SHAPE_CONSUMERS = { @@ -70,6 +86,18 @@ "loopx/capabilities/material_lifecycle/_validation.py", "loopx/extensions/presentation.py", ), + LABEL_SHAPE: ( + "loopx/capabilities/context_providers/service_ownership.py", + "loopx/capabilities/issue_fix/repository_context.py", + "loopx/capabilities/issue_fix/repository_memory.py", + "loopx/capabilities/issue_fix/repository_memory_provider.py", + "loopx/capabilities/issue_fix/reviewer_recommendation.py", + ), + IDENTITY_SHAPE: ( + "loopx/capabilities/reliability_diagnostics/envelope.py", + "loopx/control_plane/runtime/public_safety.py", + "loopx/extensions/openviking_semantic_preference/history_export.py", + ), SURFACE_SHAPE: ( "loopx/capabilities/reward_memory/application.py", "loopx/capabilities/reward_memory/candidate_review.py", @@ -144,6 +172,48 @@ def test_domain_state_and_ml_experiment_share_the_letter_led_token_shape() -> No assert ml_experiment._TOKEN_RE is LETTER_LED_COMPACT_TOKEN_PATTERN +def test_the_label_body_is_compiled_only_by_the_owner_and_declared_sites() -> None: + found: dict[str, int] = {} + for path in sorted(REPOSITORY_ROOT.glob("loopx/**/*.py")): + relative = path.relative_to(REPOSITORY_ROOT).as_posix() + count = path.read_text(encoding="utf-8").count(LABEL_SHAPE) + if count: + found[relative] = count + assert found == {OWNER_MODULE: 1, **OPEN_LABEL_SITES} + + +def test_the_identity_body_is_compiled_only_by_the_owner_and_declared_sites() -> None: + found: dict[str, int] = {} + for path in sorted(REPOSITORY_ROOT.glob("loopx/**/*.py")): + relative = path.relative_to(REPOSITORY_ROOT).as_posix() + count = path.read_text(encoding="utf-8").count(IDENTITY_SHAPE) + if count: + found[relative] = count + assert found == {OWNER_MODULE: 1, **OPEN_IDENTITY_SITES} + + +def test_labels_and_identities_import_the_owners_object() -> None: + assert service_ownership._LABEL is COMPACT_LABEL_PATTERN + assert repository_context._SOURCE_ID_PATTERN is COMPACT_LABEL_PATTERN + assert repository_memory._LABEL_PATTERN is COMPACT_LABEL_PATTERN + assert repository_memory_provider._LABEL is COMPACT_LABEL_PATTERN + assert reviewer_recommendation.REVIEWER_SOURCE_ID_PATTERN is COMPACT_LABEL_PATTERN + assert envelope.IDENTITY_TOKEN_PATTERN is IDENTITY_TOKEN_PATTERN + assert public_safety.LOOPX_COMMAND_RECORD_GOAL_ID_PATTERN is IDENTITY_TOKEN_PATTERN + assert history_export._PUBLIC_GOAL_ID_RE is IDENTITY_TOKEN_PATTERN + + +def test_the_label_and_identity_bounds_stay_one_character_apart() -> None: + # These two shapes share their grammar and differ only in the bound each + # consumer already enforced, so a fold that merges them would silently widen + # one side. The pair of values that separates them is pinned here. + longest_label = "a" + "b" * 119 + one_character_longer = "a" + "b" * 120 + assert COMPACT_LABEL_PATTERN.fullmatch(longest_label) + assert COMPACT_LABEL_PATTERN.fullmatch(one_character_longer) is None + assert IDENTITY_TOKEN_PATTERN.fullmatch(one_character_longer) + + def test_each_contract_holds_the_owners_object_and_reports_its_own_field() -> None: assert chat_actions._OPAQUE_ID is PUBLIC_OPAQUE_ID assert chat_action_store._OPAQUE_ID is PUBLIC_OPAQUE_ID