From a49a7555dca98b7118e7e44e62c28253dc9d5ee0 Mon Sep 17 00:00:00 2001 From: Shay Date: Sun, 17 May 2026 20:05:45 -0700 Subject: [PATCH] =?UTF-8?q?feat(surface):=20ADR-0030=20=E2=80=94=20depth-l?= =?UTF-8?q?anguage=20hedge=20wiring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the ADR-0028 'English-only differentiation' gap. Hebrew and Koine Greek surfaces now consult identity-pack surface_preferences for hedge and claim-strength shaping, using language-appropriate canonical hedge phrases. CORE's three-language foundation (English / Hebrew / Greek) is now uniformly identity-aware at the realizer. Algorithm: the same four-band hedge/claim-strength logic from ADR-0028 runs for all three languages. Thresholds and claim_strength come from the identity pack (carried on SurfaceContext). Hedge phrases come from ctx for English and from a new module-level constant _DEPTH_HEDGE_PHRASES for Hebrew (he) and Koine Greek (grc). he: 'נראה ש' / 'אולי' / 'במקרים מסוימים,' grc: 'δοκεῖ ὅτι' / 'ἴσως' / 'ἐνίοτε,' Pack swap visibly affects depth-language output: a precision_first identity pulls hedges to higher alignment than default; a generosity pack pulls them to lower alignment. Same trajectory through the manifold → three different Hebrew surfaces under three different packs. Same for Greek. Files: generate/surface.py _DEPTH_HEDGE_PHRASES (new module constant) _apply_hedge(surface, ctx, lang='en') — lang param added _assemble_he(.., ctx) — ctx param added _assemble_grc(.., ctx) — ctx param added SentenceAssembler.assemble — passes context to he/grc tests/test_identity_surface_divergence_depth.py — 15 new tests: Hebrew hedge bands, Greek hedge bands, pack-swap divergence in both depth languages, three-language hedge phrase distinctness, backward compatibility with ctx=None docs/decisions/ADR-0030-depth-language-hedge.md — Accepted docs/identity_packs.md — closes known-limit #1 memory/identity-packs.md — refreshed Backward compat: - _apply_hedge default lang='en' so existing callers unaffected. - English surface output byte-for-byte unchanged. - _assemble_he / _assemble_grc with ctx=None match pre-ADR output byte-for-byte (asserted by TestBackwardCompatibility). Scope limits (documented in ADR): - Depth-language hedge phrases are canonical defaults, not per-pack overridable yet. Future ADR may add a 'languages' block to the pack schema if a downstream deployment needs override capability. - Contrast ('However, ...') and subordination ('Given that ..., ...') remain English-only. Hedge is the dominant differentiator. - Hebrew/Greek grammar / word order unchanged. Suite status: cognition 121, teaching 17, runtime 19, formation 182, smoke 67 — all green. Identity + safety + divergence suites: 26+15+15+15=71 all green. --- .../ADR-0030-depth-language-hedge.md | 112 +++++++++++ docs/identity_packs.md | 2 +- generate/surface.py | 112 ++++++++--- .../test_identity_surface_divergence_depth.py | 188 ++++++++++++++++++ 4 files changed, 388 insertions(+), 26 deletions(-) create mode 100644 docs/decisions/ADR-0030-depth-language-hedge.md create mode 100644 tests/test_identity_surface_divergence_depth.py diff --git a/docs/decisions/ADR-0030-depth-language-hedge.md b/docs/decisions/ADR-0030-depth-language-hedge.md new file mode 100644 index 00000000..fea5121b --- /dev/null +++ b/docs/decisions/ADR-0030-depth-language-hedge.md @@ -0,0 +1,112 @@ +# ADR-0030: Depth-Language Hedge Wiring + +**Status:** Accepted (2026-05-17) +**Author:** Joshua Shay + planner pass +**Companion docs:** [`identity_packs.md`](../identity_packs.md), [`ADR-0028-identity-surface-wiring.md`](ADR-0028-identity-surface-wiring.md) + +## Context + +[ADR-0028](ADR-0028-identity-surface-wiring.md) wired pack `surface_preferences` into the English assembler so that swapping identity packs produced visibly different English surfaces. ADR-0028 §"Scope limits" explicitly flagged the gap: + +> **English-only differentiation.** `_assemble_he` and `_assemble_grc` do not currently consult `SurfaceContext` for hedge/claim-strength shaping; they call neither `_apply_hedge` nor anything sensitive to the new fields. Per-language hedging is a future concern; identity packs are language-neutral, so the same preferences will eventually drive the same logic in `_assemble_he` and `_assemble_grc` once those gain hedge support. + +CORE's three-language foundation (English, Hebrew, Koine Greek) is not a localization concern — it's an architectural commitment, called out in the Whitepaper and CLAUDE.md. Leaving Hebrew and Greek surfaces unaffected by the identity pack contradicts the architecture: identity is load-bearing, but only on one of the three first-class languages? That's the wrong shape. + +This ADR closes the gap. + +## Decision + +Apply the same four-band hedge/claim-strength algorithm to Hebrew and Koine Greek surfaces. Same thresholds from the identity pack. Same `claim_strength` policy. Language-appropriate hedge surface strings. + +### Phrase ownership + +Hedge phrases for English live on `SurfaceContext` (lifted from the pack's `surface_preferences` block — ADR-0028). Hedge phrases for Hebrew and Koine Greek live as module-level constants in `generate/surface.py`: + +```python +_DEPTH_HEDGE_PHRASES: dict[str, tuple[str, str, str]] = { + "he": ( + "נראה ש", # strong — "nir'eh she" — it seems that + "אולי", # soft — "ulai" — perhaps + "במקרים מסוימים,", # qualifier — "in some cases," + ), + "grc": ( + "δοκεῖ ὅτι", # strong — "dokei hoti" — it seems that + "ἴσως", # soft — "isos" — perhaps + "ἐνίοτε,", # qualifier — "eniote," — at times, + ), +} +``` + +This is deliberate: at v1, depth-language phrases are *canonical* — every pack uses the same Hebrew strong-hedge phrase, the same Greek soft-hedge phrase, etc. Pack-supplied overrides per language would require either (a) extending the pack schema to carry a `languages` block, or (b) lifting depth-language phrases out of `surface.py` into language packs. Both are larger architectural moves; neither belongs in this ADR. The current arrangement is the minimum that closes the ADR-0028 gap. + +### What pack swap *does* affect in Hebrew/Greek + +Even though the phrases themselves are canonical, pack swap still visibly affects depth-language output because **thresholds and claim_strength come from the pack**: + +| Alignment | default_general_v1 (he) | precision_first_v1 (he) | generosity_first_v1 (he) | +|---|---|---|---| +| 0.30 | strong hedge "נראה ש" | strong hedge "נראה ש" | bare (above generosity's strong 0.20) | +| 0.45 | soft hedge "אולי" | strong hedge "נראה ש" (precision's strong is 0.55) | bare (above generosity's soft 0.30) | +| 0.60 | bare | soft hedge "אולי" (precision's soft is 0.70) | bare | +| 0.80 | bare | qualifier "במקרים מסוימים," (precision is qualified, marginal band 0.70–0.85) | bare | + +Same trajectory through the manifold → three different Hebrew surfaces depending on which pack is selected. Same for Greek. Test `TestDepthPackSwapDivergence::test_hebrew_pack_swap_visible_at_alignment_0p45` asserts this explicitly. + +### Algorithm + +`_apply_hedge` gains a `lang` parameter (defaulting to `"en"` so existing callers are unaffected): + +```python +def _apply_hedge(surface: str, ctx: SurfaceContext, lang: str = "en") -> str: + alignment = ctx.identity_alignment + if lang in _DEPTH_HEDGE_PHRASES: + strong, soft, qualifier = _DEPTH_HEDGE_PHRASES[lang] + else: + strong = ctx.preferred_hedge_strong + soft = ctx.preferred_hedge_soft + qualifier = ctx.preferred_qualifier + # ... four-band algorithm unchanged ... +``` + +`_assemble_he` and `_assemble_grc` gain `ctx: SurfaceContext | None` parameters and call `_apply_hedge` with the appropriate `lang` when `ctx is not None`. When `ctx is None` (legacy callers, mostly tests), no hedge is applied and behavior is byte-for-byte preserved. + +### Backward compatibility + +- **`_apply_hedge` callers passing only two arguments** continue to work — `lang` defaults to `"en"`. +- **English surface output is unchanged** because the English branch falls through to the same `ctx.preferred_hedge_*` fields it consulted before. +- **`_assemble_he` / `_assemble_grc` callers passing `ctx=None`** get the pre-ADR Hebrew/Greek surface byte-for-byte. The test class `TestBackwardCompatibility` in `tests/test_identity_surface_divergence_depth.py` asserts this. +- **Existing cognition / runtime / smoke / formation / teaching suites** are green at the ADR landing revision. + +### What this ADR does *not* do + +- **Does not introduce a `languages` block in the identity-pack schema.** Per-pack depth-language phrase overrides are deferred. A future ADR may add them once a concrete need emerges (e.g., a robotics deployment needs a different Hebrew hedge phrase for their domain). The current `_DEPTH_HEDGE_PHRASES` is a canonical default, not a hardcoded ceiling. +- **Does not extend `_apply_contrast` ("However, ...") or `_apply_subordination` ("Given that ..., ...") to Hebrew / Greek.** Those would need translations and grammar considerations beyond hedge phrases. Hedge phrases are the dominant differentiator; the others are nice-to-have. +- **Does not change Hebrew or Greek grammar / word order.** The existing `_assemble_he` and `_assemble_grc` constructions (VSO for Hebrew, SOV for Greek) are preserved verbatim. ADR-0030 only prefixes a hedge phrase to whatever the existing assembler produced. + +## Consequences + +### Positive + +- **Identity is now load-bearing across all three foundational languages.** The ADR-0027 → ADR-0028 → ADR-0030 chain closes the loop: identity packs are swappable (0027), the swap visibly affects English surfaces (0028), and now Hebrew and Greek surfaces too (0030). The architectural claim that "identity informs doing" applies uniformly across CORE's three-language foundation. +- **Same algorithm, same thresholds, same claim_strength policy.** Operators reasoning about "when does this pack hedge?" don't need to track per-language exceptions. One mental model. +- **Pure addition.** No existing test changed; no existing surface output changed. New behavior only emerges when callers explicitly pass `ctx` to depth-language assemblers and the alignment falls in a hedging band. + +### Negative / risks + +- **Phrases are canonical, not pack-overridable.** A future deployment that wants `"ייתכן ש"` instead of `"נראה ש"` for their Hebrew strong-hedge can't supply it without an ADR-0030 follow-up. The current set was chosen as the lexically simplest and most theologically neutral options. +- **Hebrew RTL rendering can look strange in terminals that don't bidi correctly.** The string is logically correct (hedge first in memory), but terminal display may visually reorder. Tests assert on byte content, not visual order, so this is a presentation-layer concern, not a correctness one. +- **`_lower_first` is a no-op for Hebrew (unicameral) and a real transform for Greek (case-bearing).** Calling it uniformly is correct but worth noting: a future ADR introducing depth-language pre-formatting may need to revisit. +- **The phrase set is small** — three phrases per depth language. A richer set (sentence-final hedges, different forms by mood, etc.) is a future concern. + +### Scope limits (explicit non-goals for this ADR) + +- No depth-language contrast ("However, ..." equivalent in Hebrew / Greek). +- No depth-language subordination ("Given that ..., ..." equivalent). +- No per-pack depth-language phrase overrides. +- No grammar-aware hedge placement (e.g., putting a hedge after the first verb instead of sentence-initial). + +## Verification + +- `tests/test_identity_surface_divergence_depth.py` — 15 tests covering Hebrew hedge bands, Greek hedge bands, pack-swap divergence in both languages, three-language hedge phrase distinctness, and backward compatibility under `ctx=None`. +- Cognition (121), teaching (17), runtime (19), formation (182), smoke (67) suites green at the same revision. +- `tests/test_identity_surface_divergence.py` (ADR-0028) — 15 tests still passing (English regression check). diff --git a/docs/identity_packs.md b/docs/identity_packs.md index 36e56736..f6897753 100644 --- a/docs/identity_packs.md +++ b/docs/identity_packs.md @@ -174,7 +174,7 @@ Each ratified pack ships alongside a `.mastery_report.json` companion f ## Known limits (read before designing around) -1. ~~**Identity does not yet visibly differentiate articulation at the realizer.**~~ **Closed by [ADR-0028](decisions/ADR-0028-identity-surface-wiring.md) (2026-05-17).** Pack `surface_preferences` now flow into the assembler's hedge and claim-strength decisions. `core chat --identity precision_first_v1 "Q"` produces a visibly different surface than the default pack on the same prompt at the same alignment. Hedging is English-only at v1; depth-language hedging is a future ADR. +1. ~~**Identity does not yet visibly differentiate articulation at the realizer.**~~ **Closed by [ADR-0028](decisions/ADR-0028-identity-surface-wiring.md) + [ADR-0030](decisions/ADR-0030-depth-language-hedge.md) (2026-05-17).** Pack `surface_preferences` now flow into the English, Hebrew, and Koine Greek assemblers. `core chat --identity precision_first_v1 "Q"` produces a visibly different surface than the default pack on the same prompt at the same alignment, *across all three foundational languages*. Per-pack depth-language phrase overrides remain a future concern (today's depth-language hedge phrases are canonical defaults in `generate/surface.py::_DEPTH_HEDGE_PHRASES`). 2. **One pack at a time.** Multi-pack overlays (`--identity general,domain_medical`) are deferred to a follow-up ADR. 3. **No language-specific identity yet.** Packs are language-neutral. Per-language identity is a future concern. 4. **Safety axes are still in `chat/runtime.py`.** Once the safety pack ADR lands, safety boundaries will move out of `boundary_ids` and into a separately-loaded safety pack. diff --git a/generate/surface.py b/generate/surface.py index 5f1acb79..f9ea2bba 100644 --- a/generate/surface.py +++ b/generate/surface.py @@ -27,6 +27,23 @@ _DEFAULT_HEDGE_STRONG_PHRASE: str = "It seems that" _DEFAULT_HEDGE_SOFT_PHRASE: str = "Perhaps" _DEFAULT_QUALIFIER_PHRASE: str = "In some cases," _DEFAULT_QUALIFIED_BAND_HIGH: float = 0.75 +# ADR-0030 — depth-language hedge phrases. Same thresholds and +# claim_strength policy from the identity pack apply to Hebrew and +# Koine Greek, but the hedge surface strings are language-specific +# (per-pack overrides are deferred to a future schema bump). Tuple +# layout: (strong, soft, qualifier). +_DEPTH_HEDGE_PHRASES: dict[str, tuple[str, str, str]] = { + "he": ( + "נראה ש", # "nir'eh she" — it seems that + "אולי", # "ulai" — perhaps + "במקרים מסוימים,", # "be'mikrim mesuyamim," — in some cases, + ), + "grc": ( + "δοκεῖ ὅτι", # "dokei hoti" — it seems that + "ἴσως", # "isos" — perhaps + "ἐνίοτε,", # "eniote," — at times, + ), +} CONTRAST_THRESHOLD: float = 0.3 _SLOT_PRONOUN: dict[str, str] = { "neut_sg": "it", @@ -120,7 +137,7 @@ def _lower_first(surface: str) -> str: return surface[0].lower() + surface[1:] if surface else surface -def _apply_hedge(surface: str, ctx: SurfaceContext) -> str: +def _apply_hedge(surface: str, ctx: SurfaceContext, lang: str = "en") -> str: """Apply identity-pack-supplied hedge and claim-strength shaping. Bands, in descending hedge strength: @@ -133,17 +150,28 @@ def _apply_hedge(surface: str, ctx: SurfaceContext) -> str: - ``claim_strength == "affirmative"`` → leave assertion bare. - ``claim_strength == "balanced"`` → leave assertion bare. 4. Above ``qualified_band_high`` → leave assertion bare. + + Thresholds and ``claim_strength`` come from the identity pack + (carried on ``ctx``) regardless of ``lang``. Hedge phrases come + from ``ctx`` for English and from ``_DEPTH_HEDGE_PHRASES`` for + Hebrew (``"he"``) and Koine Greek (``"grc"``) — ADR-0030. """ alignment = ctx.identity_alignment + if lang in _DEPTH_HEDGE_PHRASES: + strong_phrase, soft_phrase, qualifier_phrase = _DEPTH_HEDGE_PHRASES[lang] + else: + strong_phrase = ctx.preferred_hedge_strong + soft_phrase = ctx.preferred_hedge_soft + qualifier_phrase = ctx.preferred_qualifier if alignment < ctx.hedge_threshold_strong: - return f"{ctx.preferred_hedge_strong} {_lower_first(surface)}" + return f"{strong_phrase} {_lower_first(surface)}" if alignment < ctx.hedge_threshold_soft: - return f"{ctx.preferred_hedge_soft} {_lower_first(surface)}" + return f"{soft_phrase} {_lower_first(surface)}" if ( ctx.claim_strength == "qualified" and alignment < ctx.qualified_band_high ): - return f"{ctx.preferred_qualifier} {_lower_first(surface)}" + return f"{qualifier_phrase} {_lower_first(surface)}" return surface @@ -203,26 +231,48 @@ def _assemble_en( return surface -def _assemble_he(subject: str, predicate: str, object_: str | None, elaboration: str, role: str) -> str: +def _assemble_he( + subject: str, + predicate: str, + object_: str | None, + elaboration: str, + role: str, + ctx: SurfaceContext | None, +) -> str: obj = object_ or "" if role == "question": parts = ["\u05d4\u05d0\u05dd", predicate, subject] if obj: parts.append(obj) - return " ".join(parts) + "?" - if role == "refute": + surface = " ".join(parts) + "?" + elif role == "refute": parts = ["\u05dc\u05d0", predicate, subject] if obj: parts.append(obj) - return " ".join(parts) + "." - parts = [predicate, subject] - if obj: - parts.append(obj) - base = " ".join(parts) - return f"{base} \u2014 {elaboration}." if role == "elaborate" and elaboration else base + "." + surface = " ".join(parts) + "." + else: + parts = [predicate, subject] + if obj: + parts.append(obj) + base = " ".join(parts) + surface = ( + f"{base} \u2014 {elaboration}." + if role == "elaborate" and elaboration + else base + "." + ) + if ctx is not None: + surface = _apply_hedge(surface, ctx, lang="he") + return surface -def _assemble_grc(subject: str, predicate: str, object_: str | None, elaboration: str, role: str) -> str: +def _assemble_grc( + subject: str, + predicate: str, + object_: str | None, + elaboration: str, + role: str, + ctx: SurfaceContext | None, +) -> str: subj = _cap(subject) obj = object_ or "" if role == "question": @@ -230,19 +280,27 @@ def _assemble_grc(subject: str, predicate: str, object_: str | None, elaboration if obj: parts.append(obj) parts.append(predicate) - return " ".join(parts) + ";" - if role == "refute": + surface = " ".join(parts) + ";" + elif role == "refute": parts = [subj, "\u03bf\u1f50"] if obj: parts.append(obj) parts.append(predicate) - return " ".join(parts) + "." - parts = [subj] - if obj: - parts.append(obj) - parts.append(predicate) - base = " ".join(parts) - return f"{base} \u2014 {elaboration}." if role == "elaborate" and elaboration else base + "." + surface = " ".join(parts) + "." + else: + parts = [subj] + if obj: + parts.append(obj) + parts.append(predicate) + base = " ".join(parts) + surface = ( + f"{base} \u2014 {elaboration}." + if role == "elaborate" and elaboration + else base + "." + ) + if ctx is not None: + surface = _apply_hedge(surface, ctx, lang="grc") + return surface class SentenceAssembler: @@ -265,9 +323,13 @@ class SentenceAssembler: fallback = plan.surface or " ".join(t for t in tokens if t) return SentencePlan("", "", object_, None, role_str, lang, fallback) if lang == "he": - surface = _assemble_he(subject, predicate, object_, elaboration, role_str) + surface = _assemble_he( + subject, predicate, object_, elaboration, role_str, context, + ) elif lang == "grc": - surface = _assemble_grc(subject, predicate, object_, elaboration, role_str) + surface = _assemble_grc( + subject, predicate, object_, elaboration, role_str, context, + ) else: surface = _assemble_en(subject, predicate, object_, elaboration, role_str, context) return SentencePlan( diff --git a/tests/test_identity_surface_divergence_depth.py b/tests/test_identity_surface_divergence_depth.py new file mode 100644 index 00000000..7c606dd7 --- /dev/null +++ b/tests/test_identity_surface_divergence_depth.py @@ -0,0 +1,188 @@ +"""ADR-0030 — verify pack swap affects Hebrew and Koine Greek surfaces. + +ADR-0028 wired identity-pack ``surface_preferences`` into English +assembly only. ADR-0030 extends the same algorithm to Hebrew and Greek +with language-appropriate hedge phrases (canonical defaults baked into +``generate/surface.py`` until a future schema bump allows per-pack +overrides). + +These tests prove the depth-language surfaces: + +* Hedge bands fire correctly at the same thresholds as English. +* Hebrew hedges use Hebrew phrases (``"נראה ש"`` / ``"אולי"``); Greek + hedges use Greek phrases (``"δοκεῖ ὅτι"`` / ``"ἴσως"``). +* Pack swap → different hedge band entry points across languages. +* Claim-strength "qualified" prepends the language-specific qualifier + in the marginal band. +""" + +from __future__ import annotations + +import pytest + +from generate.articulation import ArticulationPlan +from generate.surface import SentenceAssembler, SurfaceContext +from packs.identity.loader import load_identity_manifold + +_ASSEMBLER = SentenceAssembler() + +# Hebrew hedge phrases (must match ``_DEPTH_HEDGE_PHRASES["he"]`` in surface.py). +_HE_HEDGE_STRONG = "נראה ש" +_HE_HEDGE_SOFT = "אולי" +_HE_QUALIFIER = "במקרים מסוימים," + +# Greek hedge phrases (must match ``_DEPTH_HEDGE_PHRASES["grc"]``). +_GRC_HEDGE_STRONG = "δοκεῖ ὅτι" +_GRC_HEDGE_SOFT = "ἴσως" +_GRC_QUALIFIER = "ἐνίοτε," + + +def _plan(lang: str) -> ArticulationPlan: + return ArticulationPlan( + subject="logos" if lang == "grc" else "truth" if lang == "en" else "אמת", + predicate="reveals" if lang == "en" else "מגלה" if lang == "he" else "ἀποκαλύπτει", + object="reality" if lang == "en" else "מציאות" if lang == "he" else "πραγματικότητα", + surface="", + output_language=lang, + frame_id="default", + ) + + +def _ctx_from_pack(pack_id: str, alignment: float) -> SurfaceContext: + prefs = load_identity_manifold(pack_id).surface_preferences + return SurfaceContext( + identity_alignment=alignment, + hedge_threshold_strong=prefs.hedge_threshold_strong, + hedge_threshold_soft=prefs.hedge_threshold_soft, + preferred_hedge_strong=prefs.preferred_hedge_strong, + preferred_hedge_soft=prefs.preferred_hedge_soft, + claim_strength=prefs.claim_strength, + qualified_band_high=prefs.qualified_band_high, + preferred_qualifier=prefs.preferred_qualifier, + ) + + +def _surface(pack_id: str, alignment: float, lang: str) -> str: + return _ASSEMBLER.assemble( + _plan(lang), tokens=[], role="assert", context=_ctx_from_pack(pack_id, alignment), + ).surface + + +# ---------- Hebrew hedge bands ---------- + + +class TestHebrewHedgeBands: + def test_high_alignment_is_bare(self) -> None: + s = _surface("default_general_v1", 0.9, "he") + assert _HE_HEDGE_STRONG not in s + assert _HE_HEDGE_SOFT not in s + + def test_strong_hedge_under_default(self) -> None: + s = _surface("default_general_v1", 0.3, "he") + assert s.startswith(_HE_HEDGE_STRONG), s + + def test_soft_hedge_under_default(self) -> None: + s = _surface("default_general_v1", 0.45, "he") + assert s.startswith(_HE_HEDGE_SOFT), s + + def test_precision_pushes_hedges_to_higher_alignment(self) -> None: + # alignment=0.60 is bare under default (soft=0.50) but in + # precision's soft band (strong=0.55, soft=0.70). + default_surface = _surface("default_general_v1", 0.60, "he") + precision_surface = _surface("precision_first_v1", 0.60, "he") + # Default leaves bare; precision applies soft hedge "אולי" (default + # phrase, since v1 packs don't override per-language). + assert _HE_HEDGE_SOFT not in default_surface + assert precision_surface.startswith(_HE_HEDGE_SOFT) + + def test_generosity_pulls_hedges_to_lower_alignment(self) -> None: + # alignment=0.45 is in default's soft band but above generosity's + # soft (0.30). + default_surface = _surface("default_general_v1", 0.45, "he") + generosity_surface = _surface("generosity_first_v1", 0.45, "he") + assert default_surface.startswith(_HE_HEDGE_SOFT) + assert _HE_HEDGE_SOFT not in generosity_surface + + +# ---------- Greek hedge bands ---------- + + +class TestGreekHedgeBands: + def test_high_alignment_is_bare(self) -> None: + s = _surface("default_general_v1", 0.9, "grc") + assert _GRC_HEDGE_STRONG not in s + assert _GRC_HEDGE_SOFT not in s + + def test_strong_hedge_under_default(self) -> None: + s = _surface("default_general_v1", 0.3, "grc") + assert s.startswith(_GRC_HEDGE_STRONG), s + + def test_soft_hedge_under_default(self) -> None: + s = _surface("default_general_v1", 0.45, "grc") + assert s.startswith(_GRC_HEDGE_SOFT), s + + def test_precision_qualified_band_uses_greek_qualifier(self) -> None: + # alignment=0.80 is in precision's marginal band [0.70, 0.85) and + # precision's claim_strength is "qualified". The Greek qualifier + # "ἐνίοτε," should be prepended. + s = _surface("precision_first_v1", 0.80, "grc") + assert s.startswith(_GRC_QUALIFIER), s + + def test_generosity_affirmative_never_qualifies_in_greek(self) -> None: + s = _surface("generosity_first_v1", 0.40, "grc") + # No hedge (above soft=0.30), no qualifier (affirmative). + assert _GRC_QUALIFIER not in s + assert _GRC_HEDGE_SOFT not in s + + +# ---------- pack-swap divergence proof ---------- + + +class TestDepthPackSwapDivergence: + def test_hebrew_pack_swap_visible_at_alignment_0p45(self) -> None: + a = _surface("default_general_v1", 0.45, "he") + b = _surface("precision_first_v1", 0.45, "he") + c = _surface("generosity_first_v1", 0.45, "he") + assert a != b + assert a != c + assert b != c + + def test_greek_pack_swap_visible_at_alignment_0p45(self) -> None: + a = _surface("default_general_v1", 0.45, "grc") + b = _surface("precision_first_v1", 0.45, "grc") + c = _surface("generosity_first_v1", 0.45, "grc") + assert a != b + assert a != c + assert b != c + + def test_all_three_languages_diverge_under_same_pack(self) -> None: + """English / Hebrew / Greek hedges use language-specific phrases.""" + en = _surface("default_general_v1", 0.3, "en") + he = _surface("default_general_v1", 0.3, "he") + grc = _surface("default_general_v1", 0.3, "grc") + # Each language's strong-hedge phrase appears in its own surface, + # not in others'. + assert "It seems that" in en + assert _HE_HEDGE_STRONG in he + assert _GRC_HEDGE_STRONG in grc + assert _HE_HEDGE_STRONG not in en + assert _GRC_HEDGE_STRONG not in he + + +# ---------- backward compatibility ---------- + + +class TestBackwardCompatibility: + def test_he_assembly_without_ctx_unchanged(self) -> None: + """Hebrew assembly with context=None must match pre-ADR-0030 output.""" + plan = _plan("he") + result = _ASSEMBLER.assemble(plan, tokens=[], role="assert", context=None) + # Pre-ADR Hebrew assembly: "predicate subject object." + assert result.surface == "מגלה אמת מציאות." + + def test_grc_assembly_without_ctx_unchanged(self) -> None: + """Greek assembly with context=None must match pre-ADR-0030 output.""" + plan = _plan("grc") + result = _ASSEMBLER.assemble(plan, tokens=[], role="assert", context=None) + # Pre-ADR Greek assembly: subject object predicate. + assert result.surface == "Logos πραγματικότητα ἀποκαλύπτει."