BlueMagpie-TTS-Demo / glyph_ascii_v1.py
Codex
Align V5 expanded glyph grammar
86db2be
Raw History Blame Contribute Delete
17 kB
"""Strict, reversible ASCII identifier rendering for pre-certified glyph audio.
This module deliberately contains no runtime generation, audio, asset loading,
or release-evidence integration. It defines only the fail-closed
``glyph_ascii_v1`` grammar and the proof object that a later integration layer
can bind to a frozen asset manifest.
The accepted language is intentionally narrower than RFC email/URL syntax:
* every input byte must already be lowercase ASCII (there is no case folding);
* email local parts, DNS labels, and URL path segments contain letters plus a
small set of non-consecutive separators;
* URLs are exactly ``http`` or ``https``, have no authority decorations, and
have no query or fragment.
Every accepted byte maps bijectively to one of 32 expanded, unambiguous
production spoken tokens. Tokens in a complete rendering are separated by
exactly one ASCII space.
"""
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass
import hashlib
import json
import re
GLYPH_ASCII_V1_GRAMMAR_ID = "glyph_ascii_v1"
GLYPH_ASCII_V1_MAX_IDENTIFIER_BYTES = 512
GLYPH_ASCII_V1_MAX_ATOMS = 512
GLYPH_ASCII_V1_MAX_CANONICAL_CHARS = 8_192
_SHA256_RE = re.compile(r"[0-9a-f]{64}\Z", flags=re.ASCII)
_ALPHA = r"[a-z]+"
_LABEL = rf"{_ALPHA}(?:-{_ALPHA})*"
_DOMAIN = rf"{_LABEL}(?:\.{_LABEL})+"
_LOCAL = rf"{_ALPHA}(?:[._-]{_ALPHA})*"
_PATH_SEGMENT = rf"{_ALPHA}(?:[-_.]{_ALPHA})*"
_EMAIL_RE = re.compile(rf"{_LOCAL}@{_DOMAIN}\Z", flags=re.ASCII)
_URL_RE = re.compile(
rf"(?:http|https)://{_DOMAIN}(?:/{_PATH_SEGMENT}(?:/{_PATH_SEGMENT})*)?\Z",
flags=re.ASCII,
)
_ZH_ORDINALS = (
"一",
"二",
"三",
"四",
"五",
"六",
"七",
"八",
"九",
"十",
"十一",
"十二",
"十三",
"十四",
"十五",
"十六",
"十七",
"十八",
"十九",
"二十",
"二十一",
"二十二",
"二十三",
"二十四",
"二十五",
"二十六",
)
class GlyphAsciiV1Error(ValueError):
"""Raised when an identifier, manifest binding, or proof is invalid."""
@dataclass(frozen=True)
class GlyphAsciiV1Symbol:
"""One member of the frozen 32-byte canonical alphabet."""
raw_byte: int
canonical_token: str
asset_id: str
_LETTER_SYMBOLS = tuple(
GlyphAsciiV1Symbol(
raw_byte=ord(character),
canonical_token=f"第{ordinal}個英文字母",
asset_id=f"letter-{character}",
)
for character, ordinal in zip("abcdefghijklmnopqrstuvwxyz", _ZH_ORDINALS)
)
_PUNCTUATION_SYMBOLS = (
GlyphAsciiV1Symbol(ord("@"), "網路符號小老鼠", "symbol-at"),
GlyphAsciiV1Symbol(ord("."), "網路符號點", "symbol-dot"),
GlyphAsciiV1Symbol(ord("-"), "這個符號叫做橫線", "symbol-hyphen"),
GlyphAsciiV1Symbol(ord("_"), "這個符號叫做底線", "symbol-underscore"),
GlyphAsciiV1Symbol(ord(":"), "這個符號叫做冒號", "symbol-colon"),
GlyphAsciiV1Symbol(ord("/"), "網路符號斜線", "symbol-slash"),
)
GLYPH_ASCII_V1_SYMBOLS = _LETTER_SYMBOLS + _PUNCTUATION_SYMBOLS
_SYMBOL_BY_RAW_BYTE = {symbol.raw_byte: symbol for symbol in GLYPH_ASCII_V1_SYMBOLS}
_SYMBOL_BY_CANONICAL_TOKEN = {
symbol.canonical_token: symbol for symbol in GLYPH_ASCII_V1_SYMBOLS
}
_EXPECTED_ASSET_IDS = tuple(symbol.asset_id for symbol in GLYPH_ASCII_V1_SYMBOLS)
if (
len(GLYPH_ASCII_V1_SYMBOLS) != 32
or len(_SYMBOL_BY_RAW_BYTE) != 32
or len(_SYMBOL_BY_CANONICAL_TOKEN) != 32
or len(set(_EXPECTED_ASSET_IDS)) != 32
):
raise RuntimeError("glyph_ascii_v1 alphabet is not bijective")
@dataclass(frozen=True)
class GlyphAsciiV1AtomProof:
"""Range- and manifest-bound proof for one exact source byte."""
ordinal: int
absolute_raw_start: int
absolute_raw_end: int
identifier_raw_start: int
identifier_raw_end: int
canonical_start: int
canonical_end: int
raw_byte: int
canonical_token: str
asset_id: str
manifest_entry_sha256: str
@dataclass(frozen=True)
class GlyphAsciiV1Proof:
"""Complete canonical rendering proof for one accepted identifier."""
grammar_id: str
identifier_kind: str
absolute_raw_start: int
absolute_raw_end: int
canonical_start: int
canonical_end: int
raw_sha256: str
canonical_sha256: str
asset_manifest_sha256: str
atoms: tuple[GlyphAsciiV1AtomProof, ...]
def _require_plain_nonnegative_int(value: object, *, field: str) -> int:
if type(value) is not int or value < 0:
raise GlyphAsciiV1Error(f"{field} must be a non-negative integer")
return value
def _require_sha256(value: object, *, field: str) -> str:
if type(value) is not str or _SHA256_RE.fullmatch(value) is None:
raise GlyphAsciiV1Error(f"{field} must be a lowercase SHA-256 digest")
return value
def _validated_manifest(
asset_entry_sha256_by_asset_id: Mapping[str, str],
) -> tuple[tuple[str, str], ...]:
if not isinstance(asset_entry_sha256_by_asset_id, Mapping):
raise GlyphAsciiV1Error("asset manifest entries must be a mapping")
if set(asset_entry_sha256_by_asset_id) != set(_EXPECTED_ASSET_IDS):
raise GlyphAsciiV1Error(
"asset manifest must contain exactly the 32 glyph_ascii_v1 asset ids"
)
entries: list[tuple[str, str]] = []
for asset_id in _EXPECTED_ASSET_IDS:
if type(asset_id) is not str:
raise GlyphAsciiV1Error("asset id has an invalid type")
digest = _require_sha256(
asset_entry_sha256_by_asset_id[asset_id],
field=f"asset manifest entry {asset_id!r}",
)
entries.append((asset_id, digest))
return tuple(entries)
def _asset_manifest_sha256_from_entries(
entries: tuple[tuple[str, str], ...],
) -> str:
payload = json.dumps(
{
"grammar_id": GLYPH_ASCII_V1_GRAMMAR_ID,
"entries": [
{"asset_id": asset_id, "sha256": digest}
for asset_id, digest in entries
],
},
ensure_ascii=True,
separators=(",", ":"),
sort_keys=True,
).encode("ascii")
return hashlib.sha256(payload).hexdigest()
def glyph_ascii_v1_asset_manifest_sha256(
asset_entry_sha256_by_asset_id: Mapping[str, str],
) -> str:
"""Return the deterministic logical-manifest digest for all 32 assets."""
return _asset_manifest_sha256_from_entries(
_validated_manifest(asset_entry_sha256_by_asset_id)
)
def _strict_raw_bytes(raw_identifier: str) -> bytes:
if type(raw_identifier) is not str:
raise GlyphAsciiV1Error("raw identifier must be a string")
# This is intentionally lower(), never casefold(). Eligibility requires
# the source to arrive in its canonical lowercase form.
if raw_identifier != raw_identifier.lower():
raise GlyphAsciiV1Error("raw identifier is not native lowercase")
try:
raw_bytes = raw_identifier.encode("ascii", errors="strict")
except UnicodeEncodeError as error:
raise GlyphAsciiV1Error("raw identifier is not strict ASCII") from error
if not raw_bytes:
raise GlyphAsciiV1Error("raw identifier is empty")
if len(raw_bytes) > GLYPH_ASCII_V1_MAX_IDENTIFIER_BYTES:
raise GlyphAsciiV1Error("raw identifier exceeds the byte cap")
if len(raw_bytes) > GLYPH_ASCII_V1_MAX_ATOMS:
raise GlyphAsciiV1Error("raw identifier exceeds the atom cap")
return raw_bytes
def glyph_ascii_v1_identifier_kind(raw_identifier: str) -> str:
"""Parse the exact v1 byte grammar and return ``email`` or ``url``."""
raw_bytes = _strict_raw_bytes(raw_identifier)
# Match the decoded ASCII only after the byte-level eligibility checks.
raw = raw_bytes.decode("ascii")
matches = tuple(
kind
for kind, pattern in (("email", _EMAIL_RE), ("url", _URL_RE))
if pattern.fullmatch(raw) is not None
)
if len(matches) != 1:
raise GlyphAsciiV1Error("raw identifier is outside the unambiguous v1 grammar")
return matches[0]
def render_glyph_ascii_v1(
raw_identifier: str,
*,
asset_entry_sha256_by_asset_id: Mapping[str, str],
absolute_raw_start: int = 0,
canonical_start: int = 0,
) -> tuple[str, GlyphAsciiV1Proof]:
"""Render one identifier and bind every byte to its frozen asset entry."""
absolute_raw_start = _require_plain_nonnegative_int(
absolute_raw_start,
field="absolute_raw_start",
)
canonical_start = _require_plain_nonnegative_int(
canonical_start,
field="canonical_start",
)
identifier_kind = glyph_ascii_v1_identifier_kind(raw_identifier)
raw_bytes = raw_identifier.encode("ascii")
manifest_entries = _validated_manifest(asset_entry_sha256_by_asset_id)
entry_sha_by_asset_id = dict(manifest_entries)
manifest_sha256 = _asset_manifest_sha256_from_entries(manifest_entries)
tokens: list[str] = []
atoms: list[GlyphAsciiV1AtomProof] = []
canonical_cursor = canonical_start
for ordinal, raw_byte in enumerate(raw_bytes):
symbol = _SYMBOL_BY_RAW_BYTE.get(raw_byte)
if symbol is None:
# The grammar and alphabet are intentionally independent checks.
raise GlyphAsciiV1Error("accepted grammar byte lacks a canonical atom")
if ordinal:
canonical_cursor += 1 # exactly one ASCII space between atoms
token_start = canonical_cursor
token_end = token_start + len(symbol.canonical_token)
tokens.append(symbol.canonical_token)
atoms.append(
GlyphAsciiV1AtomProof(
ordinal=ordinal,
absolute_raw_start=absolute_raw_start + ordinal,
absolute_raw_end=absolute_raw_start + ordinal + 1,
identifier_raw_start=ordinal,
identifier_raw_end=ordinal + 1,
canonical_start=token_start,
canonical_end=token_end,
raw_byte=raw_byte,
canonical_token=symbol.canonical_token,
asset_id=symbol.asset_id,
manifest_entry_sha256=entry_sha_by_asset_id[symbol.asset_id],
)
)
canonical_cursor = token_end
canonical = " ".join(tokens)
if len(canonical) > GLYPH_ASCII_V1_MAX_CANONICAL_CHARS:
raise GlyphAsciiV1Error("canonical rendering exceeds the character cap")
proof = GlyphAsciiV1Proof(
grammar_id=GLYPH_ASCII_V1_GRAMMAR_ID,
identifier_kind=identifier_kind,
absolute_raw_start=absolute_raw_start,
absolute_raw_end=absolute_raw_start + len(raw_bytes),
canonical_start=canonical_start,
canonical_end=canonical_start + len(canonical),
raw_sha256=hashlib.sha256(raw_bytes).hexdigest(),
canonical_sha256=hashlib.sha256(canonical.encode("utf-8")).hexdigest(),
asset_manifest_sha256=manifest_sha256,
atoms=tuple(atoms),
)
return canonical, proof
def inverse_glyph_ascii_v1(
proof: GlyphAsciiV1Proof,
*,
asset_entry_sha256_by_asset_id: Mapping[str, str],
) -> str:
"""Validate a proof from first principles and reconstruct its exact bytes."""
if type(proof) is not GlyphAsciiV1Proof:
raise GlyphAsciiV1Error("glyph proof has an invalid type")
manifest_entries = _validated_manifest(asset_entry_sha256_by_asset_id)
entry_sha_by_asset_id = dict(manifest_entries)
manifest_sha256 = _asset_manifest_sha256_from_entries(manifest_entries)
if (
type(proof.grammar_id) is not str
or proof.grammar_id != GLYPH_ASCII_V1_GRAMMAR_ID
or type(proof.identifier_kind) is not str
or proof.identifier_kind not in {"email", "url"}
or _require_plain_nonnegative_int(
proof.absolute_raw_start,
field="proof.absolute_raw_start",
)
!= proof.absolute_raw_start
or _require_plain_nonnegative_int(
proof.absolute_raw_end,
field="proof.absolute_raw_end",
)
!= proof.absolute_raw_end
or _require_plain_nonnegative_int(
proof.canonical_start,
field="proof.canonical_start",
)
!= proof.canonical_start
or _require_plain_nonnegative_int(
proof.canonical_end,
field="proof.canonical_end",
)
!= proof.canonical_end
or proof.absolute_raw_end <= proof.absolute_raw_start
or proof.canonical_end <= proof.canonical_start
or _require_sha256(proof.raw_sha256, field="proof.raw_sha256")
!= proof.raw_sha256
or _require_sha256(proof.canonical_sha256, field="proof.canonical_sha256")
!= proof.canonical_sha256
or _require_sha256(
proof.asset_manifest_sha256,
field="proof.asset_manifest_sha256",
)
!= proof.asset_manifest_sha256
or proof.asset_manifest_sha256 != manifest_sha256
or type(proof.atoms) is not tuple
or not proof.atoms
or len(proof.atoms) > GLYPH_ASCII_V1_MAX_ATOMS
):
raise GlyphAsciiV1Error("glyph proof header is inconsistent")
raw_bytes = bytearray()
canonical_tokens: list[str] = []
raw_absolute_cursor = proof.absolute_raw_start
raw_identifier_cursor = 0
canonical_cursor = proof.canonical_start
for ordinal, atom in enumerate(proof.atoms):
if type(atom) is not GlyphAsciiV1AtomProof:
raise GlyphAsciiV1Error("glyph atom proof has an invalid type")
if (
type(atom.canonical_token) is not str
or type(atom.asset_id) is not str
):
raise GlyphAsciiV1Error("glyph atom text field has an invalid type")
integer_fields = (
atom.ordinal,
atom.absolute_raw_start,
atom.absolute_raw_end,
atom.identifier_raw_start,
atom.identifier_raw_end,
atom.canonical_start,
atom.canonical_end,
atom.raw_byte,
)
if any(type(value) is not int for value in integer_fields):
raise GlyphAsciiV1Error("glyph atom integer field has an invalid type")
entry_sha256 = _require_sha256(
atom.manifest_entry_sha256,
field="atom.manifest_entry_sha256",
)
symbol = _SYMBOL_BY_CANONICAL_TOKEN.get(atom.canonical_token)
expected_canonical_start = canonical_cursor + (1 if ordinal else 0)
if (
atom.ordinal != ordinal
or atom.absolute_raw_start != raw_absolute_cursor
or atom.absolute_raw_end != raw_absolute_cursor + 1
or atom.identifier_raw_start != raw_identifier_cursor
or atom.identifier_raw_end != raw_identifier_cursor + 1
or atom.canonical_start != expected_canonical_start
or atom.canonical_end
!= expected_canonical_start + len(atom.canonical_token)
or symbol is None
or symbol.raw_byte != atom.raw_byte
or symbol.asset_id != atom.asset_id
or entry_sha256 != atom.manifest_entry_sha256
or entry_sha_by_asset_id.get(atom.asset_id)
!= atom.manifest_entry_sha256
):
raise GlyphAsciiV1Error("glyph atom proof is inconsistent")
raw_bytes.append(atom.raw_byte)
canonical_tokens.append(atom.canonical_token)
raw_absolute_cursor = atom.absolute_raw_end
raw_identifier_cursor = atom.identifier_raw_end
canonical_cursor = atom.canonical_end
canonical = " ".join(canonical_tokens)
try:
raw = bytes(raw_bytes).decode("ascii", errors="strict")
except UnicodeDecodeError as error:
raise GlyphAsciiV1Error("glyph proof does not reconstruct ASCII") from error
if (
raw_absolute_cursor != proof.absolute_raw_end
or raw_identifier_cursor != len(raw_bytes)
or canonical_cursor != proof.canonical_end
or proof.absolute_raw_end
!= proof.absolute_raw_start + len(raw_bytes)
or proof.canonical_end != proof.canonical_start + len(canonical)
or hashlib.sha256(bytes(raw_bytes)).hexdigest() != proof.raw_sha256
or hashlib.sha256(canonical.encode("utf-8")).hexdigest()
!= proof.canonical_sha256
):
raise GlyphAsciiV1Error("glyph proof does not cover its declared ranges")
# Re-parse the reconstructed bytes, then rebuild the only valid proof.
kind = glyph_ascii_v1_identifier_kind(raw)
expected_canonical, expected_proof = render_glyph_ascii_v1(
raw,
asset_entry_sha256_by_asset_id=entry_sha_by_asset_id,
absolute_raw_start=proof.absolute_raw_start,
canonical_start=proof.canonical_start,
)
if (
kind != proof.identifier_kind
or expected_canonical != canonical
or type(expected_proof) is not type(proof)
or expected_proof != proof
):
raise GlyphAsciiV1Error("glyph proof is not the canonical expected proof")
return raw