core/chat/runtime.py
Shay 07ad3af845 feat(surface): ADR-0031 — score-decomposition surface (per-axis hedges)
Closes the 'identity hedges are generic' gap.  When IdentityCheck reports
that a specific axis is deviating AND the pack supplies an axis_hedges
entry for that axis, the assembler uses that axis's phrase instead of
ADR-0028's generic preferred_hedge_*.  The hedge text now names what is
actually at issue.

Selection: lex-smallest axis_id in (ctx.deviation_axes ∩ axis_hedges).
Deterministic; loader emits axis_hedges in lex order on axis_id.

Example surface at alignment=0.30 (strong band) under default pack:
  No deviation             → 'It seems that truth reveals reality.'
  truthfulness deviates    → 'Evidence is thin that truth reveals reality.'
  coherence deviates       → 'This does not yet cohere: truth reveals reality.'
  reverence deviates       → 'Reports suggest truth reveals reality.'

Same trajectory + truthfulness deviation, three different packs:
  default_general_v1   → 'Evidence is thin that truth reveals reality.'
  precision_first_v1   → 'The evidence does not support that truth reveals reality.'
  generosity_first_v1  → 'Truth reveals reality.'  (above generosity's strong=0.20)

Schema (additive, optional):
  surface_preferences.axis_hedges = {
    <axis_id>: { 'strong': str, 'soft': str, 'qualifier': str },
    ...
  }

Bounds: each phrase length 1–64; axis_id non-empty.  Absent block →
ADR-0028 byte-for-byte fallback.  Loader emits pairs in lex order on
axis_id for hashability + deterministic tie-break.

Files:
  core/physics/identity.py
    + class AxisHedge (frozen: strong, soft, qualifier)
    SurfacePreferences gains axis_hedges: Tuple = ()
  packs/identity/loader.py
    + _build_axis_hedges(): parse + bounds-check + emit lex-ordered tuple
  generate/surface.py
    SurfaceContext gains deviation_axes: frozenset[str] + axis_hedges tuple
    + _axis_specific_phrase(ctx): lex-smallest match or None
    _apply_hedge consults axis-specific phrase before ADR-0028 fallback
    Depth languages (he, grc) unchanged — ADR-0030 canonical phrases
  chat/runtime.py
    _build_surface_context lifts identity_score.deviation_axes and
    prefs.axis_hedges into SurfaceContext
  packs/identity/*.json
    Three v1 packs gain axis_hedges blocks (truthfulness, coherence,
    reverence — each pack uses voice consistent with its character)
  scripts/ratify_identity_packs.py (no change — idempotent)
  packs/identity/*.mastery_report.json
    Auto-refreshed.  New SHAs:
      default_general_v1   → 2ab7d469013509ba5030313ca9a609a443d0716e3ddcc5596f59858ce054f5d3
      precision_first_v1   → 78aa1e6a68a35c2c8576b6196a52d421b94f6d11e006128986902a4fd08679af
      generosity_first_v1  → 511f1ce20edd4266239da61443bfc93473a5433f20bfee6692a25a03073dc933

Tests: tests/test_identity_score_decomposition.py — 17 new tests:
  per-axis phrase selection, band gating still applies, pack swap with
  same deviation produces three different phrases, lex tie-break is
  deterministic, depth-language fallback to ADR-0030, backward compat
  with empty deviation_axes, and the contract that all three v1 packs
  ship axis_hedges for all three default-pack axes.

Suite status (all green):
  cognition 121, teaching 17, runtime 19, formation 182, smoke 67
  identity+safety+English+depth divergence 71
  score decomposition 17

Scope limits (documented in ADR-0031):
  - English-only at v1 (depth languages use canonical ADR-0030 phrases)
  - Lex tie-break is operational not semantic — pack authors can re-key
    if they need a different priority
  - No dominance-driven phrasing (Interpretation A); preserved as
    forward-compatible follow-up

Docs: ADR-0031 (Accepted) recorded; docs/identity_packs.md gains
§Axis-specific hedge phrases section and updated v1-pack SHAs; memory
'identity-packs.md' refreshed.
2026-05-17 20:16:22 -07:00

579 lines
24 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, replace
import re
from collections.abc import Sequence
from typing import List
import numpy as np
from algebra.versor import versor_condition
from core.config import DEFAULT_CONFIG, DEFAULT_IDENTITY_PACK, RuntimeConfig
from core.physics.drive import DriveGradientMap, GradientField
from core.physics.energy import EnergyProfile
from core.physics.exertion import CycleCost, ExertionMeter
from core.physics.identity import (
CharacterProfile,
IdentityCheck,
IdentityScore,
TurnEvent,
)
from packs.identity.loader import load_identity_manifold
from packs.safety.loader import load_safety_pack
from field.state import FieldState
from generate.articulation import ArticulationPlan, realize
from generate.dialogue import DialogueRole, classify_dialogue_blade, propose_dialogue
from generate.intent_bridge import articulate_with_intent
from generate.proposition import FrameRegistry, Proposition, propose
from generate.result import GenerationResult
from generate.stream import generate
from generate.surface import SentenceAssembler, SentencePlan, SurfaceContext
from ingest.gate import inject
from language_packs import OOVPolicy, load_mounted_packs, load_pack, load_pack_entries
from persona.motor import PersonaMotor
from session.context import SessionContext
from session.correction import CorrectionPass
from vault.decompose import default_decomposer, default_gate
_TOKEN_RE = re.compile(r"\w+", re.UNICODE)
_SEED_ALIASES = {
"logos": "\u03bb\u03cc\u03b3\u03bf\u03c2",
"dabar": "\u05d3\u05d1\u05e8",
"or": "\u05d0\u05d5\u05e8",
"phos": "\u03c6\u03c9\u03c2",
"zoe": "\u03b6\u03c9\u03ae",
"arche": "\u1f00\u03c1\u03c7\u03ae",
"aletheia": "\u1f00\u03bb\u03ae\u03b8\u03b5\u03b9\u03b1",
}
_QUESTION_WORDS = frozenset({"what", "who", "how", "why", "when", "where", "which"})
_TERMINALS = frozenset({".", "?", ";", "!"})
_UNKNOWN_DOMAIN_SURFACE = "I don't know — insufficient grounding for that yet."
def _energy_scalar(energy_obj) -> float:
if energy_obj is None:
return 1.0
if isinstance(energy_obj, EnergyProfile):
return float(energy_obj.raw)
try:
return float(energy_obj)
except (TypeError, ValueError):
return 1.0
def _is_question_input(raw_text: str, tokens: Sequence[str]) -> bool:
if raw_text.strip().endswith("?"):
return True
return bool(tokens and tokens[0].casefold() in _QUESTION_WORDS)
def _stable_dialogue_role(role: DialogueRole, *, raw_text: str, tokens: Sequence[str]) -> DialogueRole:
if role in {"question", "refute"} and not _is_question_input(raw_text, tokens):
return "elaborate"
return role
def _terminal_for_role(role: DialogueRole, output_language: str) -> str:
if role == "question":
return ";" if output_language == "grc" else "?"
return "."
def _terminate_surface(surface: str, *, role: DialogueRole, output_language: str) -> str:
stripped = surface.strip()
if not stripped:
return stripped
if stripped[-1] in _TERMINALS:
return stripped
return f"{stripped}{_terminal_for_role(role, output_language)}"
def _prefer_prompt_anchor(
articulation: ArticulationPlan,
filtered_tokens: Sequence[str],
*,
output_language: str,
) -> ArticulationPlan:
if output_language != "en" or len(filtered_tokens) < 2:
return articulation
content_tokens = [
token
for token in filtered_tokens
if token.casefold() not in _QUESTION_WORDS and token.casefold() not in {"is", "are", "was", "were"}
]
if not content_tokens:
return articulation
anchor = content_tokens[-1]
if anchor == articulation.subject:
return articulation
return replace(
articulation,
subject=anchor,
surface=" ".join(part for part in (anchor, articulation.predicate, articulation.object) if part),
)
@dataclass
class _StubBindingFrame:
frame_id: str
coherence_magnitude: float
region_ids: frozenset
cycle_index: int
def _make_trajectory_from_result(result, turn: int):
from core.physics.reasoning import TrajectoryOperator
operator = TrajectoryOperator()
states = result.trajectory or (result.final_state,)
frames = [
_StubBindingFrame(
frame_id=f"t{turn}_s{i}",
coherence_magnitude=_energy_scalar(getattr(fs, "energy", None)),
region_ids=frozenset({str(getattr(fs, "node", 0))}),
cycle_index=turn,
)
for i, fs in enumerate(states)
]
return operator.build(frames, trajectory_id=f"turn_{turn}")
@dataclass(frozen=True, slots=True)
class ChatResponse:
surface: str
proposition: Proposition
articulation: ArticulationPlan
articulation_surface: str
dialogue_role: DialogueRole
versor_condition: float
output_language: str
frame_pack: str
walk_surface: str
salience_top_k: int | None
candidates_used: int | None
vault_hits: int
identity_score: IdentityScore | None
character_profile: CharacterProfile
flagged: bool
# ADR-0023 §2 — per-transition admissibility evidence and region
# provenance flag. An empty tuple is the contract for "no
# admissibility was checked this turn" (cold start, refusal, stub).
admissibility_trace: tuple = ()
region_was_unconstrained: bool = True
class ChatRuntime:
def __init__(
self,
pack_id: str | Sequence[str] | None = None,
*,
frame_pack: str | None = None,
config: RuntimeConfig = DEFAULT_CONFIG,
) -> None:
if pack_id is not None or frame_pack is not None:
pack_ids = (pack_id,) if isinstance(pack_id, str) else tuple(pack_id or config.input_packs)
resolved_config = RuntimeConfig(
input_packs=pack_ids,
output_language=config.output_language,
frame_pack=frame_pack or config.frame_pack,
max_tokens=config.max_tokens,
allow_cross_language_recall=config.allow_cross_language_recall,
allow_cross_language_generation=config.allow_cross_language_generation,
vault_reproject_interval=config.vault_reproject_interval,
use_salience=config.use_salience,
salience_top_k=config.salience_top_k,
inhibition_threshold=config.inhibition_threshold,
inner_loop_admissibility=config.inner_loop_admissibility,
admissibility_threshold=config.admissibility_threshold,
admissibility_mode=config.admissibility_mode,
admissibility_margin=config.admissibility_margin,
)
else:
resolved_config = config
pack_ids = tuple(config.input_packs)
self.config = resolved_config
manifests = []
manifolds = []
entries = []
for mounted_pack_id in pack_ids:
manifest, manifold = load_pack(mounted_pack_id)
manifests.append(manifest)
manifolds.append(manifold)
entries.extend(load_pack_entries(mounted_pack_id))
manifold = manifolds[0] if len(pack_ids) == 1 else load_mounted_packs(pack_ids)
self._manifests = tuple(manifests)
identity_pack_id = resolved_config.identity_pack or DEFAULT_IDENTITY_PACK
# ADR-0027 Phase 5 complete: v1 packs are ratified. Loader defaults
# to production mode (require_ratified=None -> require unless
# CORE_ALLOW_UNRATIFIED_IDENTITY=1).
identity_manifold = load_identity_manifold(identity_pack_id)
# ADR-0029: safety pack is always loaded; its boundary_ids are
# unioned into the runtime manifold. Identity packs may add
# boundaries but cannot remove safety boundaries. Failure to
# load the safety pack is fail-closed; SafetyPackError propagates
# and prevents runtime startup.
self.safety_pack = load_safety_pack()
self.identity_manifold = type(identity_manifold)(
value_axes=identity_manifold.value_axes,
boundary_ids=(
identity_manifold.boundary_ids | self.safety_pack.boundary_ids
),
alignment_threshold=identity_manifold.alignment_threshold,
surface_preferences=identity_manifold.surface_preferences,
)
self.identity_pack_id = identity_pack_id
# Keep the generic runtime neutral. Identity/persona motivation belongs
# behind an explicit IdentityProfile contract, not the baseline chat path.
persona_motor = PersonaMotor.identity()
self._context = SessionContext(
manifold,
persona=persona_motor,
vault_reproject_interval=resolved_config.vault_reproject_interval,
)
self._frame_registry = FrameRegistry.from_pack(resolved_config.frame_pack, self._context.vocab)
self._surface_by_fold = {e.surface.casefold(): e.surface for e in entries}
self._surface_by_fold.update(_SEED_ALIASES)
self._pos_by_surface = {e.surface: (e.pos or e.part_of_speech or "X") for e in entries}
self.exertion_meter = ExertionMeter(capacity_ceiling=128.0)
self.drive_gradients = tuple(GradientField(axis=axis, magnitude=0.75) for axis in self.identity_manifold.value_axes)
self._drive_map = DriveGradientMap(gradients=self.drive_gradients)
self.character_profile = CharacterProfile.from_manifold(
self.identity_manifold,
drive_summaries={g.axis.name: g.magnitude for g in self.drive_gradients},
fatigue_index=0.0,
)
self._identity_check = IdentityCheck()
self.turn_log: List[TurnEvent] = []
self._correction_pass = CorrectionPass()
self._last_valence: float = 0.0
@property
def session(self) -> SessionContext:
return self._context
def _tokenize(self, text: str) -> list[str]:
return [self._surface_by_fold.get(m.group(0).casefold(), m.group(0)) for m in _TOKEN_RE.finditer(text)]
def tokenize(self, text: str) -> list[str]:
return self._tokenize(text)
def _apply_oov_policy(self, tokens: list[str]) -> list[str]:
kept: list[str] = []
for token in tokens:
try:
self._context.vocab.get_versor(token)
kept.append(token)
except KeyError:
if all(manifest.oov_policy is OOVPolicy.FAIL_CLOSED for manifest in self._manifests):
raise
if any(manifest.oov_policy is OOVPolicy.PROPOSE_VOCAB_EXPANSION for manifest in self._manifests):
raise KeyError(f"OOV token requires vocab proposal: {token}")
kept.append(token)
return kept
def _syntactic_guard(self, tokens: tuple[str, ...]) -> list[str]:
out: list[str] = []
prev_pos: str | None = None
for token in tokens:
pos = self._pos_by_surface.get(token, "X")
if pos == prev_pos:
continue
out.append(token)
prev_pos = pos
return out
def _dialogue_reference(self) -> np.ndarray | None:
blade = self._context.last_dialogue_blade
if blade is None or float(np.linalg.norm(blade)) < 1e-8:
return None
return blade
def _apply_drive_bias(self, field_state: FieldState) -> FieldState:
"""Generic runtime keeps motivation/drive disabled.
Motivation is an identity-profile concern, not a free runtime field
mutation. Keeping this a no-op preserves the neutral baseline while
generic chat closure and cognition evals are being stabilized.
"""
return field_state
def _build_surface_context(self, identity_score, current_valence: float) -> SurfaceContext:
active = self._context.referents.active_referent()
alignment = float(identity_score.alignment) if identity_score is not None else 1.0
deviation_axes = (
frozenset(identity_score.deviation_axes)
if identity_score is not None
else frozenset()
)
prefs = self.identity_manifold.surface_preferences
# ADR-0031 — flatten the manifold's axis_hedges (tuple of
# (axis_id, AxisHedge)) into the wire-format quadruples that
# SurfaceContext carries. Order is preserved (loader emits in
# lex order); _axis_specific_phrase relies on this.
axis_hedges = tuple(
(axis_id, hedge.strong, hedge.soft, hedge.qualifier)
for axis_id, hedge in prefs.axis_hedges
)
return SurfaceContext(
active_referent_surface=active.surface if active is not None else "",
active_referent_slot=active.slot if active is not None else "neut_sg",
identity_alignment=alignment,
valence_delta=current_valence - self._last_valence,
elab_conjunction="",
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,
deviation_axes=deviation_axes,
axis_hedges=axis_hedges,
)
def _stub_response(self, field_state: FieldState) -> ChatResponse:
zero = np.zeros(field_state.F.shape, dtype=np.float32)
prop = Proposition(
subject="",
predicate="",
object_=None,
surface=_UNKNOWN_DOMAIN_SURFACE,
frame_id="unknown_domain",
subject_versor=zero,
predicate_versor=zero,
object_versor=None,
relation=zero,
)
art = ArticulationPlan(
subject="",
predicate="",
object=None,
surface=_UNKNOWN_DOMAIN_SURFACE,
output_language=self.config.output_language,
frame_id="unknown_domain",
)
return ChatResponse(
surface=_UNKNOWN_DOMAIN_SURFACE,
proposition=prop,
articulation=art,
articulation_surface=_UNKNOWN_DOMAIN_SURFACE,
dialogue_role="assert",
versor_condition=versor_condition(field_state.F),
output_language=self.config.output_language,
frame_pack=self.config.frame_pack,
walk_surface=_UNKNOWN_DOMAIN_SURFACE,
salience_top_k=None,
candidates_used=None,
vault_hits=0,
identity_score=None,
character_profile=self.character_profile,
flagged=False,
)
def chat(self, text: str, max_tokens: int | None = None) -> ChatResponse:
tokens = self._tokenize(text)
filtered = self._apply_oov_policy(tokens)
if not filtered:
raise ValueError("ChatRuntime.chat() received no in-vocabulary tokens.")
probe_state = self._context.probe_ingest(filtered)
# INV-24 recall role: RECOGNITION. Feeds UnknownDomainGate — asks
# "have we seen anything like this before?", not "what is admissible
# evidence?". Session-tier SPECULATIVE memory must count here, so
# no min_status filter is applied.
direct_hits = self._context.vault.recall(probe_state.F, top_k=3)
direct_best = max((h["score"] for h in direct_hits), default=0.0)
gate_decision = default_gate.check(
direct_best,
vault=self._context.vault,
query=probe_state.F,
decomposer=default_decomposer,
)
if gate_decision.fire:
committed = self._context.commit_ingest(filtered)
empty_result = GenerationResult(tokens=(), final_state=committed, vault_hits=0)
self._context.finalize_turn(
empty_result,
tokens_in=tuple(filtered),
input_versor=committed.F,
dialogue_role="assert",
metadata={"unknown": True, "unknown_source": gate_decision.source},
)
return self._stub_response(committed)
field_state = self._context.commit_ingest(filtered)
field_state = self._apply_drive_bias(field_state)
reference_blade = self._dialogue_reference()
base_proposition = propose(
field_state,
None,
self._context.vocab,
self._frame_registry,
output_lang=self.config.output_language,
)
dialogue_role = _stable_dialogue_role(
classify_dialogue_blade(base_proposition.relation, reference_blade),
raw_text=text,
tokens=tokens,
)
proposition = propose_dialogue(
field_state,
self._context.vault,
self._context.vocab,
self._frame_registry,
reference_blade,
output_lang=self.config.output_language,
)
articulation = realize(proposition, self._context.vocab, output_language=self.config.output_language)
articulation = _prefer_prompt_anchor(articulation, filtered, output_language=self.config.output_language)
self._context.record_dialogue(proposition)
result = generate(
field_state,
self._context.vocab,
self._context.persona,
max_tokens=self.config.max_tokens if max_tokens is None else max_tokens,
record_trajectory=True,
vault=self._context.vault,
recall_top_k=3 if self.config.allow_cross_language_recall else 0,
output_lang=self.config.output_language,
allow_cross_language_generation=self.config.allow_cross_language_generation,
use_salience=self.config.use_salience,
salience_top_k=self.config.salience_top_k,
inhibition_threshold=self.config.inhibition_threshold,
inner_loop_admissibility=self.config.inner_loop_admissibility,
admissibility_threshold=self.config.admissibility_threshold,
admissibility_mode=self.config.admissibility_mode,
admissibility_margin=self.config.admissibility_margin,
)
# --- Articulation fidelity: replace bare S-P-O join with intent-aware surface ---
# articulate_with_intent() classifies the input intent, builds a proposition
# graph grounded on the generation result's recalled tokens, and calls the
# realize_semantic() path (13-construction realizer) that was previously
# implemented but never connected to the chat hot path.
# Falls back to the existing articulation.surface when bridge returns "".
if self.config.output_language == "en":
recalled_words = tuple(
tok for tok in (result.tokens or ()) if tok and tok.isalpha()
)
intent_surface = articulate_with_intent(text, articulation, recalled_words)
if intent_surface:
articulation = replace(articulation, surface=intent_surface)
# --- end articulation fidelity fix ---
reasoning_trajectory = _make_trajectory_from_result(result, self._context.turn)
identity_score = self._identity_check.check(reasoning_trajectory, self.identity_manifold)
flagged = identity_score.flagged
cycle_cost = CycleCost(
cycle_index=self._context.turn,
attention_cost=float(result.candidates_used or 0),
inhibition_cost=float(self.config.inhibition_threshold),
digest_cost=0.0,
trajectory_cost=float(len(result.trajectory or ())),
)
self.exertion_meter.record(cycle_cost)
fatigue = self.exertion_meter.fatigue(at_cycle=self._context.turn)
self.character_profile = CharacterProfile.from_manifold(
self.identity_manifold,
drive_summaries={g.axis.name: g.magnitude * (1.0 - fatigue.value) for g in self.drive_gradients},
fatigue_index=fatigue.value,
)
self._context.finalize_turn(
result,
tokens_in=tuple(filtered),
dialogue_role=str(dialogue_role),
)
current_valence = _energy_scalar(getattr(result.final_state, "valence", None))
surface_ctx = self._build_surface_context(identity_score, current_valence)
self._last_valence = current_valence
surface = _terminate_surface(articulation.surface, role=dialogue_role, output_language=self.config.output_language)
articulation = replace(articulation, surface=surface)
sentence_plan: SentencePlan = SentenceAssembler().assemble(
articulation,
result.tokens,
role=dialogue_role,
context=surface_ctx,
)
walk_surface = sentence_plan.surface
vault_hits = int(result.vault_hits)
turn_event = TurnEvent(
turn=self._context.turn - 1,
input_tokens=tuple(filtered),
surface=surface,
walk_surface=walk_surface,
articulation_surface=articulation.surface,
dialogue_role=str(dialogue_role),
identity_score=identity_score,
cycle_cost_total=cycle_cost.total,
vault_hits=vault_hits,
versor_condition=versor_condition(result.final_state.F),
flagged=flagged,
elaboration=sentence_plan.elaboration,
)
self.turn_log.append(turn_event)
return ChatResponse(
surface=walk_surface,
proposition=proposition,
articulation=articulation,
articulation_surface=articulation.surface,
dialogue_role=dialogue_role,
versor_condition=versor_condition(result.final_state.F),
output_language=self.config.output_language,
frame_pack=self.config.frame_pack,
walk_surface=walk_surface,
salience_top_k=result.salience_top_k,
candidates_used=result.candidates_used,
vault_hits=vault_hits,
identity_score=identity_score,
character_profile=self.character_profile,
flagged=flagged,
admissibility_trace=result.admissibility_trace,
region_was_unconstrained=result.region_was_unconstrained,
)
def _unknown_domain_response(self, field_state: FieldState, filtered: list[str]) -> ChatResponse:
return self._stub_response(field_state)
def correct(self, text: str, target_turn: int = -1, max_tokens: int | None = None) -> ChatResponse:
tokens = self._tokenize(text)
filtered = self._apply_oov_policy(tokens)
if not filtered:
raise ValueError("correct() received no in-vocabulary tokens.")
correction_state = inject(filtered, self._context.vocab)
correction_result = self._correction_pass.apply(
self._context.graph,
correction_state.F,
from_turn=target_turn,
)
self._context.apply_corrected_outputs(correction_result.records)
regen_tokens = self._context.last_input_tokens
if not regen_tokens:
return self._stub_response(correction_state)
return self.chat(" ".join(regen_tokens), max_tokens=max_tokens)
def respond(self, text: str, max_tokens: int | None = None) -> str:
try:
return self.chat(text, max_tokens=max_tokens).surface
except ValueError:
return ""
async def achat(self, text: str, max_tokens: int | None = None) -> ChatResponse:
return self.chat(text, max_tokens=max_tokens)
async def arespond(self, text: str, max_tokens: int | None = None) -> str:
try:
return (await self.achat(text, max_tokens=max_tokens)).surface
except ValueError:
return ""
# The previous ``_default_identity_manifold()`` constructor was removed as
# part of ADR-0027. Identity is now loaded from a pack at runtime via
# ``packs.identity.loader.load_identity_manifold`` using
# ``RuntimeConfig.identity_pack`` (default ``DEFAULT_IDENTITY_PACK``).
# The previously-hardcoded three axes (truthfulness / coherence /
# reverence) live in ``packs/identity/default_general_v1.json``.