Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions loopx/capabilities/periodic_report/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import hashlib
import json
import re
from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass
from datetime import datetime, timezone
Expand All @@ -18,6 +17,7 @@
from ...control_plane.content_digest import ENVELOPED_SHA256_PATTERN
from ...control_plane.digest_envelope import sha256_envelope
from ...public_safe_text import PUBLIC_SAFE_SLUG_PATTERN
from .field_shapes import REPORT_LANGUAGE_PATTERN, SUPPORTING_CONTENT_KINDS


SOURCE_RESULT_SCHEMA = "periodic_report_source_result_v0"
Expand All @@ -39,9 +39,7 @@
"runtime",
}
_ITEM_VISIBILITIES = {"primary", "supporting"}
_SUPPORTING_CONTENT_KINDS = {"coverage", "delivery_receipt", "runtime"}
_HIGHLIGHT_TONES = {"attention", "neutral", "positive"}
_LANGUAGE_RE = re.compile(r"^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$")
SourceCollector = Callable[[Mapping[str, Any]], Mapping[str, Any]]
ArtifactRenderer = Callable[[Mapping[str, Any]], Mapping[str, Any]]
ArtifactSink = Callable[[Mapping[str, Any], Mapping[str, Any]], Mapping[str, Any]]
Expand Down Expand Up @@ -226,7 +224,7 @@ def _normalize_item(raw: object, *, label: str) -> dict[str, Any]:
if visibility not in _ITEM_VISIBILITIES:
raise ValueError(f"{label}.visibility is invalid")
normalized["visibility"] = visibility
if content_kind in _SUPPORTING_CONTENT_KINDS and visibility != "supporting":
if content_kind in SUPPORTING_CONTENT_KINDS and visibility != "supporting":
raise ValueError(
f"{label}.visibility must be supporting for {content_kind} content"
)
Expand Down Expand Up @@ -268,7 +266,7 @@ def _normalize_editorial(raw: object) -> dict[str, Any] | None:
editorial.get("language"), "editorial.language", maximum=64
)
if language:
if not _LANGUAGE_RE.fullmatch(language):
if not REPORT_LANGUAGE_PATTERN.fullmatch(language):
raise ValueError("editorial.language must be a BCP-47-like language tag")
normalized["language"] = language

Expand Down
8 changes: 4 additions & 4 deletions loopx/capabilities/periodic_report/cadence_journal.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pathlib import Path
from typing import Any

from .field_shapes import REPORT_IDENTITY_PATTERN
from ...file_lock import LockAcquisitionPolicy, exclusive_file_lock
from ...registry import atomic_write_json
from .cadence import report_cadence_next_due_at, report_cadence_window
Expand All @@ -20,7 +21,6 @@

CADENCE_WINDOW_SCHEMA = "periodic_report_cadence_window_v0"
JOURNAL_SCHEMA = "periodic_report_cadence_journal_v0"
_ID = re.compile(r"^[a-z][a-z0-9_.:-]{2,127}$")


def _digest(value: object) -> str:
Expand All @@ -30,7 +30,7 @@ def _digest(value: object) -> str:


def cadence_journal_path(root: Path, goal_id: str) -> Path:
if not _ID.fullmatch(goal_id):
if not REPORT_IDENTITY_PATTERN.fullmatch(goal_id):
raise ValueError("cadence Goal identity is invalid")
return root / "goals" / goal_id / "periodic_reports" / "cadence.json"

Expand All @@ -44,7 +44,7 @@ def validate_cadence_window(raw: object) -> dict[str, Any]:
raise ValueError("cadence window contract is invalid")
value = dict(raw)
if value["schema_version"] != CADENCE_WINDOW_SCHEMA or any(
not isinstance(value[key], str) or not _ID.fullmatch(value[key])
not isinstance(value[key], str) or not REPORT_IDENTITY_PATTERN.fullmatch(value[key])
for key in ("goal_id", "agent_id")
):
raise ValueError("cadence window identity is invalid")
Expand Down Expand Up @@ -150,7 +150,7 @@ def admit_cadence_window(
A configuration change may supersede an unprepared interval under the same
lock used by consumption, preserving its predecessor as immutable history.
"""
if not _ID.fullmatch(agent_id):
if not REPORT_IDENTITY_PATTERN.fullmatch(agent_id):
raise ValueError("cadence Agent identity is invalid")
path = cadence_journal_path(runtime_root, goal_id)
if subscription_resolver is None and (
Expand Down
28 changes: 28 additions & 0 deletions loopx/capabilities/periodic_report/field_shapes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""One owner for the field rules a periodic report is validated against. Refs #4447 Track A.

Three decisions were each stated twice on the base revision. ``adapters.py`` and
``project_progress.py`` both compiled the language-tag rule, and both listed the
content kinds that are supporting by nature -- the two set literals even spelled
their members in a different order, which a value-keyed duplicate scan reads as two
different values. The identity rule that gates ``goal_id``, ``agent_id`` and ``failure_code`` was
compiled three times under two different binding names -- ``pending_intent.py`` and
``request_action.py`` called it ``_IDENTITY_RE``, ``cadence_journal.py`` called it
``_ID`` -- which is exactly what a name-keyed duplicate scan cannot see.

Every one of those sites only asks membership or ``fullmatch``, so importing the
owner here is behaviour-preserving, while a widening in one copy no longer leaves
the other behind silently.

``incremental.py`` keeps its own ``_IDENTITY_RE``. It is a *different* rule --
case-insensitive and up to 160 characters versus lowercase-led 3..128 -- so it is
bounded-context reuse of one name, not a copy, and it is pinned as distinct here
rather than merged away by renaming.
"""

from __future__ import annotations

import re

SUPPORTING_CONTENT_KINDS = frozenset({"coverage", "delivery_receipt", "runtime"})
REPORT_IDENTITY_PATTERN = re.compile(r"^[a-z][a-z0-9_.:-]{2,127}$")
REPORT_LANGUAGE_PATTERN = re.compile(r"^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$")
4 changes: 2 additions & 2 deletions loopx/capabilities/periodic_report/pending_intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from ...control_plane.effect_runtime import effect_runtime_result
from ...control_plane.digest_envelope import sha256_envelope
from ...history import load_registry
from .field_shapes import REPORT_IDENTITY_PATTERN
from .todo_source import read_report_todo_source
from ...registry import (
atomic_write_json,
Expand Down Expand Up @@ -56,7 +57,6 @@
HOOK_ID = "periodic_report.pending_intent"
CAPABILITY_ID = "periodic-report"
_DISPATCH_RE = re.compile(r"^pwh_[0-9a-f]{64}\.json$")
_IDENTITY_RE = re.compile(r"^[a-z][a-z0-9_.:-]{2,127}$")
_CHINESE_RE = re.compile(r"[\u3400-\u4dbf\u4e00-\u9fff]")
_ANALYSIS_SECTION_CONTRACT = (
("overview", "全景判断"),
Expand Down Expand Up @@ -333,7 +333,7 @@ def pending_periodic_report_intents(
) -> list[dict[str, Any]]:
"""Read only exact, eligible, unconsumed intents for one Goal/Agent."""

if not _IDENTITY_RE.fullmatch(goal_id) or not _IDENTITY_RE.fullmatch(agent_id):
if not REPORT_IDENTITY_PATTERN.fullmatch(goal_id) or not REPORT_IDENTITY_PATTERN.fullmatch(agent_id):
return []
pending: list[dict[str, Any]] = []
for intent in periodic_report_request_intents(
Expand Down
8 changes: 3 additions & 5 deletions loopx/capabilities/periodic_report/project_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import hashlib
import json
import re
from collections.abc import Mapping, Sequence
from typing import Any

from .field_shapes import REPORT_LANGUAGE_PATTERN, SUPPORTING_CONTENT_KINDS
from .adapters import (
PeriodicReportSourceAdapter,
build_periodic_report_source_result,
Expand Down Expand Up @@ -44,8 +44,6 @@
"supporting_evidence": "支撑证据",
},
}
_LANGUAGE_RE = re.compile(r"^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$")
_SUPPORTING_CONTENT_KINDS = {"coverage", "runtime", "delivery_receipt"}
_MAX_PRIMARY_ITEMS = 8
_MAX_SUPPORTING_ITEMS = 16

Expand Down Expand Up @@ -80,7 +78,7 @@ def _snapshot_ref(projection: Mapping[str, Any], *, language: str) -> str:

def _language(value: object) -> str:
language = str(value or "en").strip()
if not _LANGUAGE_RE.fullmatch(language):
if not REPORT_LANGUAGE_PATTERN.fullmatch(language):
raise ValueError(
"project_progress.language must be a BCP-47-like language tag"
)
Expand Down Expand Up @@ -126,7 +124,7 @@ def build_project_progress_periodic_report_source(
if section_spec is None:
raise ValueError(f"{label}.content_kind is invalid")
visibility = str(item.get("visibility") or "primary").strip().lower()
if content_kind in _SUPPORTING_CONTENT_KINDS:
if content_kind in SUPPORTING_CONTENT_KINDS:
supporting_count += 1
elif visibility == "supporting":
supporting_count += 1
Expand Down
6 changes: 3 additions & 3 deletions loopx/capabilities/periodic_report/request_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from ...file_lock import exclusive_file_lock
from ...history import load_registry
from ...registry import atomic_write_json, find_registry_goal
from .field_shapes import REPORT_IDENTITY_PATTERN
from .machine_defaults import resolve_goal_periodic_report_subscription
from .machine_store import read_periodic_report_machine_defaults
from .presets import build_periodic_report_preset_activation
Expand All @@ -32,7 +33,6 @@
REQUEST_SETTLE_PORT = "periodic_report.request.settle_source"
REQUEST_ADAPTER_PHASE = "capability_action"
_REQUEST_FILE_RE = re.compile(r"^prq_[0-9a-f]{64}\.json$")
_IDENTITY_RE = re.compile(r"^[a-z][a-z0-9_.:-]{2,127}$")

SourceBinder = Callable[..., Mapping[str, Any]]
SourceSettler = Callable[..., Mapping[str, Any]]
Expand Down Expand Up @@ -414,7 +414,7 @@ def record_periodic_report_request(
source_adapter_id: str | None,
execute: bool,
) -> dict[str, Any]:
if not _IDENTITY_RE.fullmatch(goal_id) or not _IDENTITY_RE.fullmatch(agent_id):
if not REPORT_IDENTITY_PATTERN.fullmatch(goal_id) or not REPORT_IDENTITY_PATTERN.fullmatch(agent_id):
raise ValueError("periodic-report request Goal/Agent identity is invalid")
opaque_ref = _source_ref(source_ref)
selected_adapter_id = str(source_adapter_id or "").strip()
Expand Down Expand Up @@ -674,7 +674,7 @@ def settle_periodic_report_request(
or result.get("raw_content_returned") is not False
or (
result.get("status") == SOURCE_SETTLEMENT_TERMINAL_STATUS
and not _IDENTITY_RE.fullmatch(str(result.get("failure_code") or ""))
and not REPORT_IDENTITY_PATTERN.fullmatch(str(result.get("failure_code") or ""))
)
):
raise ValueError("periodic-report source settlement receipt is invalid")
Expand Down
Loading
Loading