From 1ed5c9d847d0cf287f1bd4b0b8b30209d349584c Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 21 Aug 2026 22:14:09 +0000 Subject: [PATCH 1/6] Implement character-to-word gonol candidate Align EDCM live surfaces with skill-lib gonol-build@fb3b53a: characters -> words is load-bearing, morphology is not a required stage. Add edcm.character_word as an implemented candidate that admits Unicode scalars, closes whitespace-delimited word gonols, consumes current UCNS Public Gonol geometry, and does not select canon or activate measurement. --- .agents/skills/README.md | 2 +- .agents/skills/gonol-build/SKILL.md | 305 +++--------------- AGENTS.md | 7 +- CANON.md | 6 +- CLAUDE.md | 4 +- README.md | 4 +- docs/ENGLISH_LEXICAL_FLOOR.md | 2 +- docs/GONOL_LANGUAGE_BOUNDARY.md | 23 +- docs/ucns-adapter.md | 2 +- edcm/character_word.py | 409 +++++++++++++++++++++++++ tests/test_character_word_gonol.py | 139 +++++++++ tests/test_gonol_language_authority.py | 5 + 12 files changed, 633 insertions(+), 275 deletions(-) create mode 100644 edcm/character_word.py create mode 100644 tests/test_character_word_gonol.py diff --git a/.agents/skills/README.md b/.agents/skills/README.md index ad18891..ee1e800 100644 --- a/.agents/skills/README.md +++ b/.agents/skills/README.md @@ -3,7 +3,7 @@ This directory contains repo-local copies of canonical skills from `The-Interdependency/skill-lib`. -Source commit: `a1c6a7124af537ee9937b6fc6084940091982fe5` +Source commit: `fb3b53a7629f7f03ecf255167d52c13abef1a979` Repo-local copies are not the source of truth. Edit `skill-lib` first, then propagate from the canonical source. diff --git a/.agents/skills/gonol-build/SKILL.md b/.agents/skills/gonol-build/SKILL.md index 3679dc3..edc8b7c 100644 --- a/.agents/skills/gonol-build/SKILL.md +++ b/.agents/skills/gonol-build/SKILL.md @@ -1,303 +1,90 @@ --- name: gonol-build -description: Cross-repository construction, review, replay, and continuation discipline for gonols. Load this when realizing gonol or affixiation geometry in UCNS; when constructing character, word, morphology, definition, punctuation-function, or recursive gonols in EDCM; when deciding whether a relation belongs inside a gonol; or when promoting a completed gonol to atomic participation at another scale. Do not load for unrelated geometry, ordinary prose editing, or EDCM measurement that consumes already-closed gonols without changing their construction. Resolve METAPAT semantic authority, UCNS geometric authority, and EDCM text-domain authority separately; never restore historical gonal-morphology, omega/phi/psi, bone/flesh, or carrier-LCM language rules. +description: Construction, closure, and replay contract for gonols across UCNS and EDCM. Load this when building or reviewing UCNS geometry used by gonols, or building EDCM character, word, definition, or recursive-relation gonols. UCNS owns geometry; EDCM owns text construction. The required EDCM order is characters -> words -> definitions -> recursive gonol relations. Pronunciation is not required unless an explicitly declared later experiment makes it part of the construction. Do not load for unrelated geometry, ordinary prose editing, or measurement over already-closed gonols. --- -# gonol-build — construct, close, promote, recurse +# gonol-build -Use this procedural skill to preserve gonol construction semantics without -collapsing repository authority boundaries. +Use this skill to keep gonol construction on the declared architecture and nothing else. -The governing split is: +## Authority ```text -METAPAT -> conceptual meaning of affixiation and relational integration -UCNS -> gonol geometry, native Möbius/Public Gonol carrier, geometric operations -EDCM -> text-domain admission and linguistic/semantic gonol construction -skill-lib -> construction, evidence, replay, and continuation discipline +UCNS = geometry +EDCM = text-domain gonol construction +skill-lib = construction/replay discipline ``` -No repository gains another repository's authority merely because a construction -crosses the boundary. +Resolve the current UCNS and EDCM authorities before building. Do not move text semantics into UCNS or invent geometry in EDCM. -## Workflow: authority gate - -Before acting: - -1. Resolve the current commits or PR heads of every repository whose authority - the construction uses. -2. Read the governing repository instructions and the directly applicable - canon/contract source before choosing a construction rule. -3. For affixiation, resolve current METAPAT semantics before implementing its - geometry or text-domain application. -4. For carrier, topology, orientation, closure geometry, or Public Gonol - operations, resolve current UCNS authority. -5. For character admission, morphology, words, definitions, source corpora, and - recursive textual relations, resolve current EDCM authority. -6. Distinguish declared architecture, implemented construction, evidence status, - proposed continuation, and `hmmm`. -7. If governing sources disagree, stop at the conflict. Do not choose the most - convenient repository or reconstruct authority from historical artifacts. - -Treat `hmmm` and an unresolved constructor as a ban on unqualified promotion, -not as a ban on declared candidate construction. An unresolved constructor is -permission to construct a named, bounded, falsifiable candidate under the -contracts that are already fixed; it is not permission to invent the missing -contract. `hmmm` blocks unqualified certainty and silent canonization. It does -not block declared experimentation, implementation, comparison, or candidate -construction. - -## Domain rule: every character is a gonol - -For active EDCM text construction: +## EDCM construction contract ```text -every admitted character is a gonol +characters -> words -> definitions -> recursive gonol relations ``` -This is an **EDCM text-domain rule**, not a UCNS linguistic rule and not a -METAPAT axiom. +This order is load-bearing. -EDCM owns what counts as an admitted character for a declared source/profile. -If the source boundary has not selected code point, grapheme, exact Public Gonol -glyph inventory, or another character unit, preserve that question as `hmmm`. -UCNS may geometrically realize an admitted character on its carrier; UCNS does -not silently decide EDCM's character-admission semantics. +- Every admitted character is a gonol. +- Ordered character gonols close into a word gonol. +- A closed word gonol is atomic at the consuming scale while its constituent identities, order, multiplicity, source positions, and provenance remain recoverable. +- Definition gonols are constructed from the applicable closed word gonols and exact source definition evidence. +- Recursive relations are constructed from already-closed gonols without reopening or erasing their internal structure. -Character occurrences remain separately addressable even when repeated. Their -source identity, order, multiplicity, and provenance are preserved according to -the active EDCM construction profile. +Do not insert another required stage into this sequence unless the governing contract is explicitly changed. -## Construction invariant +## Pronunciation boundary -```text -eligible gonol participants - -> explicit relation or geometrically authorized function application - -> relation enters the construction - -> closure with source identity and receipt - -> completed gonol - -> atomic participant at another declared scale -``` - -`atomic` means indivisible for participation at the consuming scale. It does -not mean internally structureless, provenance-free, irreversible, or forbidden -from inspection. +Pronunciation is not required for this construction. Pronunciation, phonetic spelling, IPA, audio, or other sound representations must not alter gonol identity, closure, ordering, or relations unless a later explicitly declared experiment makes phonology part of its construction. -When a relationship helps constitute the result, keep it intrinsic to the -gonol construction. Sidecars may carry indexes, caches, projections, -provenance, and receipts; an external edge table does not become the gonol's -authoritative relational content merely because it is easier to query. +Source pronunciation data may remain source metadata. It is not a dependency of the current build. -## Active EDCM text order +## Construction invariant -Unless current EDCM authority explicitly changes it, preserve: +At every scale: ```text -characters -> morphology -> definitions -> recursive gonol relations +ordered eligible gonols +-> authorized UCNS geometric relation/application +-> closure +-> deterministic identity + provenance receipt +-> atomic participation at the next declared scale ``` -This is an order of construction dependencies, not permission to flatten every -higher-scale gonol back into characters. Close each completed construction and -reuse its atomic identity at the consuming scale. - -Words and other intermediate closures may exist where the declared construction -requires them; do not manufacture a mandatory scale merely because a conventional -NLP pipeline usually has one. - -## Affixiation boundary - -METAPAT defines affixiation conceptually: already-bounded participants remain -individually addressable, their identity and provenance are preserved, the -declared relation may integrate as a higher-scale object-whole, and the -integrated whole may later participate recursively without erasing its -constituents. - -UCNS owns any exact geometric realization of that operation. Do not invent an -alternative carrier, topology, scale increment, arity rule, containment rule, -or coupling law merely to complete an implementation. Implement the native -carrier under the fixed METAPAT invariants and let unresolved geometric law -remain `hmmm` until construction exposes it. - -EDCM applies affixiation to text-domain gonols. Linguistic prefixes and suffixes -are one instance of affixiation; they do not define affixiation. - -The complete English root, stem, affix, irregular-transformation, and family law -remains unresolved unless current EDCM source/evidence establishes otherwise. -Never invent a decomposition to complete a pipeline. Do not assume every word is -`root + affix`, that a lemma is a final root, or that one stemmer supplies -semantic authority. - -## Closure rules - -- Preserve ordered occurrence identity, multiplicity, exact source evidence, - source offsets, and provenance required by the governing EDCM profile. -- Apply no normalization, folding, trimming, deduplication, inferred sorting, - or silent collapse unless the current profile explicitly authorizes it. -- Keep completed gonol closure intact inside larger constructions. Do not reopen - a closed word or definition merely because a larger relation contains it. -- A repeated completed gonol may reuse atomic identity when the governing - construction says it is the same gonol; repeated occurrences remain - separately addressable. -- Larger and smaller scales may couple directly when the governing construction - permits it. Do not invent mandatory adjacent-scale traversal. -- Do not materialize an all-pairs relationship graph as semantic authority. -- Do not substitute conventional NLP token IDs, subword IDs, opaque embedding - vectors, or whole-string hashes for gonol identity. - -## Public Gonol function application - -UCNS owns the geometric operation of Public Gonol function positions. EDCM may -apply only operations geometrically authorized by the current UCNS source and -evidence. - -- An unresolved geometric operation remains `hmmm`; Unicode names, dictionary - definitions, neighboring glyphs, or conventional punctuation grammar do not - fill it. -- Require occurrence-addressed application context when the governing EDCM - construction requires context. -- Never infer adjacency, precedence, grammatical role, or application scope from - glyph shape alone. -- Bind function identity, result identity, ordered participants, occurrence - addresses, carrier/construction identity, and source profile in the receipt. -- Replay against the same geometric authority and explicit construction plan. -- Do not promote structural survival into parsing, grammar, semantic utility, or - universal function law. +Preserve exact source identity, occurrence order, multiplicity, and provenance. Do not normalize, deduplicate, infer relations, or substitute tokens, embeddings, hashes, or another representation for gonol identity unless the active contract explicitly authorizes it. -## Candidate construction +If required UCNS geometry is unresolved, preserve that boundary as `hmmm`; do not fill it with an invented rule. -`Constructor unresolved` means no constructor is yet canonical. It does not mean -construction cannot begin. +## Candidate boundary -- Inventing hidden canon is prohibited. -- Constructing a declared, falsifiable candidate is required progress once the - governing contracts name the participants and boundary sufficiently to build. -- Freeze selectable implementation choices before inspecting the candidate's - evidentiary outcome when those choices could change the claimed result. -- A candidate must name its identity, standing, frozen choices, nonclaims, and - selection effect before a completion receipt is minted. -- Replay of a candidate establishes only that candidate's reproducibility. It - does not select it or transfer authority across repositories. +An unresolved constructor is permission to construct a named, bounded candidate; it does not block declared experimentation. It blocks promotion beyond the evidence, not construction or testing. -## Evidence and replay +## Completion and replay -For a construction that claims completion: +Before a full run, preflight the resources required to finish it. Once a healthy admitted run begins, let it reach its natural terminal condition unless a genuine safety/resource boundary or preregistered load-bearing stop condition fires. Do not add arbitrary wall-clock limits. -1. Bind exact METAPAT, UCNS, EDCM, source-artifact, profile, code, and option - identities that materially determine the construction. -2. Admit the complete declared source, not an inspected prefix presented as a - finished run. -3. Preflight required compute and storage before starting. Once an admitted - healthy run begins, let it reach its natural terminal condition unless a - genuine safety boundary or preregistered load-bearing stop condition fires. -4. Emit deterministic construction/semantic receipts separately from resource - observations. -5. Independently reconstruct or replay the complete declared scope. -6. Compare byte-for-byte where the protocol requires exact identity. -7. Propagate failures and unresolved prerequisites without changing criteria - after outcome inspection. +A completion claim requires: -Passing fixtures proves only the fixtures. A historical receipt remains -historical evidence after its constructor or authority split is superseded; it -is not a receipt for the replacement construction. +1. exact UCNS, EDCM, source/profile, and constructor identities; +2. the complete declared source scope; +3. deterministic construction receipts; and +4. independent complete replay where replay is required by the governing protocol. -## Required output - -Report: - -```text -authority identities: -source identity: -construction identity: -participants and scales: -intrinsic relations/functions: -closure boundary: -atomic participation boundary: -receipt and replay status: -claims supported: -claims not supported: -hmmm: -next dependency-complete action: -``` - -Use `SURVIVED`, `FALSIFIED`, `BLOCKED`, `UNRESOLVED`, and `DEPRECATED` only under -the governing protocol. Do not translate `SURVIVED` into proved or canonical. +Replay establishes reproducibility of that construction only. It does not by itself establish semantic quality, measurement validity, cognition claims, or canon outside the declared scope. ## Usage guidance -When building text gonols, start in EDCM and consume UCNS geometry rather than -moving text semantics into UCNS. When building or repairing carrier geometry, -start in UCNS and use the METAPAT contract only for the relational invariants it -authorizes. When affixiation semantics themselves are in question, repair or -resolve them in METAPAT before downstream construction. - -A minimal routing example is: +For text construction, start in EDCM and consume current UCNS geometry. ```text -METAPAT: affixiation preserves bounded participant identity/provenance -UCNS: realize that relation on the native Möbius/Public Gonol geometry -EDCM: admit characters as gonols and construct text through affixiation +UCNS: geometry +EDCM: characters -> words -> definitions -> recursive gonol relations ``` -## Anti-patterns: refuse these - -- Assigning character, morphology, definition, or lexical-source authority to - UCNS merely because UCNS provides their geometry. -- Assigning affixiation semantics to EDCM or UCNS merely because they implement - or apply it. -- Reopening closed gonols merely because a larger construction contains them. -- Inferring function context, syntax, precedence, or operation from adjacency, - Unicode names, dictionary glosses, or glyph shape. -- Treating a source word list as a semantic dictionary. -- Treating `hmmm` or an unresolved constructor as permission to refuse a - declared candidate construction, complete run, or replay. -- Inventing morphology, a lexical family map, geometry, measurement authority, - alternate carrier, scale rule, or coupling law to fill an absent constructor. -- Promoting a sidecar relationship graph into intrinsic gonol semantics. -- Treating a partial corpus run as complete evidence. -- Adding arbitrary wall-clock limits to a healthy admitted computation. -- Restoring the deprecated omega/phi/psi cores, bone/flesh categories, fixed - morphology weights, or `multiplyFuel`/carrier-LCM as the universal language - law. -- Transferring UCNS construction survival into EDCM measurement validity, - PTCNA efficacy, cognition claims, or selected universal canon. - -## Minimal examples - -Character admission: - -```text -EDCM source/profile admits "a" -> that character occurrence is a gonol -UCNS supplies its authorized carrier geometry -EDCM owns the text-domain construction that consumes it -``` - -Recursive closure: - -```text -bounded gonols + declared relation - -> affixiate under METAPAT invariants - -> UCNS geometric realization - -> closed EDCM text-domain gonol - -> atomic participation in a later declared relation -``` - -Honest incomplete continuation: - -```text -geometric coupling law unresolved - -> construct the smallest declared UCNS candidate allowed by fixed contracts - -> preserve candidate standing - -> hmmm blocks promotion, not construction -``` +When a word closes, use that word gonol atomically at the next scale. Ignore pronunciation unless a future explicit construction says otherwise. ## hmmm -`hmmm` blocks unqualified promotion. It does not block experimentation or -construction of a declared candidate. - -- which exact EDCM character-admission unit applies where not already source-pinned; -- the source-supported complete English morphology law; -- the exact UCNS Möbius-carrier affixiation/coupling law exposed by implementation; -- executable geometry for direct coupling across distant recursive scales; -- quantitative compression and reconstruction behavior after recursive gonols exist; -- which recursive-gonol candidate, if any, later becomes selected canon. +- exact UCNS geometric operations that remain unresolved in current implementation; +- any future construction that explicitly adds phonology or another stage; +- any recursive relation whose governing source or geometry is not yet established. diff --git a/AGENTS.md b/AGENTS.md index 9b64cda..6260057 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ description: | 1. `CANON.md` 2. `docs/GONOL_LANGUAGE_BOUNDARY.md` -3. `.agents/skills/gonol-build/SKILL.md` for gonol, character, morphology, definition, recursive-relation, or affixiation work +3. `.agents/skills/gonol-build/SKILL.md` for gonol, character, word, definition, recursive-relation, or affixiation work 4. `README.md` 5. `docs/UCNS_EDCM_EXPERIMENT_PROGRAM.md` 6. `CLAUDE.md` @@ -37,7 +37,7 @@ METAPAT affixiation semantics: The-Interdependency/metapat at the current govern UCNS gonol geometry: The-Interdependency/ucns at the current governing or experiment-pinned commit EDCM text construction: The-Interdependency/edcm current canon/profile EDCM baseline measurement: The-Interdependency/edcm:edcm/measurement -organization skills: The-Interdependency/skill-lib@a1c6a7124af537ee9937b6fc6084940091982fe5 +organization skills: The-Interdependency/skill-lib@fb3b53a7629f7f03ecf255167d52c13abef1a979 experiment evidence: exact report, corpus, candidate, and workflow identities ``` @@ -56,6 +56,7 @@ EDCM owns the character-admission profile and linguistic/semantic construction. - `edcm/measurement/` is the frozen maintained baseline candidate. - `edcm.ucns_edcm_experiments` is the first joint experiment runner. - EDCM owns current text-domain gonol admission and construction; measurement remains separately frozen and evaluated. +- `edcm.character_word` is the implemented candidate for `characters -> words`. It is not selected canon. - UCNS structural policies, product-character candidates, faithful-breadth candidates, and unresolved Möbius coupling laws remain noncanonical unless current UCNS authority says otherwise. - EDCM axes, thresholds, marker lists, and circuit parameters remain candidates unless an explicit canon decision says otherwise. - A passing hypothesis is experiment-supported evidence, not canon. @@ -103,7 +104,7 @@ python -m twine check dist/* ## Usage guidance -For new text-gonol construction, begin in EDCM. Resolve the exact EDCM source/admission profile, import METAPAT affixiation invariants without redefining them, consume current UCNS geometry, build the declared construction, and replay it independently before claiming completion. Freeze any EDCM measurement only after construction and keep its evidence status separate. +For new text-gonol construction, begin in EDCM. Resolve the exact EDCM source/admission profile, import METAPAT affixiation invariants without redefining them, consume current UCNS geometry, and construct `characters -> words` with `edcm.character_word`. Replay independently before claiming completion. Freeze any EDCM measurement only after construction and keep its evidence status separate. For historical experiments, reproduce the exact historical producer epoch and names. Historical UCNS-owned lexical artifacts remain evidence; they do not restore current language authority to UCNS. diff --git a/CANON.md b/CANON.md index c1d7c51..5b8b826 100644 --- a/CANON.md +++ b/CANON.md @@ -61,14 +61,14 @@ The current EDCM construction dependency is: ```text characters ↓ -morphology +words ↓ definitions ↓ recursive gonol relations ``` -This is a dependency order, not permission to flatten completed gonols or impose conventional NLP intermediate scales. A completed gonol is atomic for participation at its consuming scale while constituent identity and provenance remain recoverable. +This order is load-bearing. Ordered character gonols close into word gonols. A closed word gonol is atomic at the consuming scale while constituent identities, order, multiplicity, source positions, and provenance remain recoverable. Morphology is not a required stage. Historical morphology evidence remains historical; the complete English morphology law remains `hmmm`. Affixiation is defined by METAPAT, not by EDCM and not by UCNS. EDCM applies affixiation to text-domain gonols. UCNS owns any exact geometric realization on the native Möbius/Public Gonol carrier. An unresolved UCNS coupling law remains `hmmm`; neither EDCM nor UCNS may fill it by importing an invented carrier, topology, scale increment, arity rule, containment rule, or linguistic convention. @@ -245,7 +245,7 @@ This phase may implement: - positive unresolved-reference and alternative-bundle evidence; - reproducible reports and policy-preservation findings; - CI execution against exact UCNS commits where experiments require them; -- source-bound EDCM character, morphology, definition, and recursive-gonol candidate constructors under the current authority split; +- source-bound EDCM character, word, definition, and recursive-gonol candidate constructors under the current authority split; - candidate UCNS Möbius-carrier realizations of affixiation under fixed METAPAT invariants; and - preregistered EDCM measurements of completed text-gonol constructions. diff --git a/CLAUDE.md b/CLAUDE.md index efede29..b3d4d96 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -104,14 +104,14 @@ The active construction dependency is: ```text characters ↓ -morphology +words ↓ definitions ↓ recursive gonol relations ``` -Relationships that constitute a gonol enter the construction. Completed gonols are atomic for participation at the consuming scale without becoming structureless or provenance-free. Do not reopen a completed gonol or impose a conventional NLP intermediate scale merely because doing so simplifies later processing. +This order is load-bearing. `edcm.character_word` is the implemented candidate for character admission and word closure. Morphology is not a required stage. Do not reopen a completed word gonol or insert another required scale merely because a conventional NLP pipeline contains one. Do not insert a conventional NLP tokenizer, token IDs, subword pieces, opaque external vector lookup, or whole-string hash as a substitute for gonol identity. Source prose, dictionaries, corpora, labels, and annotations may remain evidence and provenance; they become gonol semantics only through a source-bound EDCM construction whose admission, relation, closure, and replay boundaries are explicit. diff --git a/README.md b/README.md index dd36030..0936654 100644 --- a/README.md +++ b/README.md @@ -67,13 +67,15 @@ The current EDCM construction dependency is: ```text characters ↓ -morphology +words ↓ definitions ↓ recursive gonol relations ``` +`edcm.character_word` is the implemented candidate for the first two stages. It admits Unicode scalars as character gonols and closes non-whitespace runs into word gonols. It is not selected canon and does not activate measurement. + Affixiation is defined by METAPAT. UCNS owns any exact geometric realization. EDCM applies affixiation to text-domain gonols. Completed gonols are atomic for participation at the consuming scale while constituent identity and provenance remain recoverable. Do not insert conventional NLP token IDs, subword pieces, opaque external embedding vectors, or whole-string hashes as substitutes for gonol identity. Source prose, dictionaries, corpora, labels, and annotations may remain evidence and provenance; they become gonol semantics only through explicit EDCM construction. diff --git a/docs/ENGLISH_LEXICAL_FLOOR.md b/docs/ENGLISH_LEXICAL_FLOOR.md index 2451909..40aa172 100644 --- a/docs/ENGLISH_LEXICAL_FLOOR.md +++ b/docs/ENGLISH_LEXICAL_FLOOR.md @@ -55,7 +55,7 @@ For active EDCM text construction: ```text every admitted character is a gonol ↓ -morphology +words ↓ definitions ↓ diff --git a/docs/GONOL_LANGUAGE_BOUNDARY.md b/docs/GONOL_LANGUAGE_BOUNDARY.md index fec0ab1..5a4f199 100644 --- a/docs/GONOL_LANGUAGE_BOUNDARY.md +++ b/docs/GONOL_LANGUAGE_BOUNDARY.md @@ -51,16 +51,18 @@ The current EDCM construction dependency is: ```text characters ↓ -morphology +words ↓ definitions ↓ recursive gonol relations ``` -This is a dependency order, not a demand to reopen every completed object at every later scale. A completed gonol is atomic for participation at its consuming scale while retaining recoverable constituent identity and provenance. +This order is load-bearing. Every admitted character is a gonol. Ordered character gonols close into a word gonol. A closed word gonol is atomic at the consuming scale while constituent identities, order, multiplicity, source positions, and provenance remain recoverable. Definition gonols are constructed from already-closed word gonols. Recursive relations are constructed from already-closed gonols. -Words and other intermediate closures may be used where an explicit EDCM construction requires them. EDCM must not manufacture a mandatory intermediate scale merely because a conventional NLP pipeline normally contains one. +Do not insert another required stage into this sequence unless the governing contract is explicitly changed. Morphology is not a required stage. Historical morphology evidence remains historical; the complete English morphology law remains `hmmm`. + +`edcm.character_word` is the implemented candidate for character admission and word closure. It is not selected canon. Relationships that constitute a gonol enter the construction. They are not merely external semantic edges. Sidecars may index, cache, project, or record provenance; they do not replace intrinsic relational content. @@ -124,13 +126,26 @@ Preflight real resource requirements before a complete run. Once a healthy admit ## Usage guidance +Copy-pasteable first-two-stages candidate: + +```python +from edcm.character_word import construct_character_word_gonols, replay_character_word_gonols + +first = construct_character_word_gonols("don't cut.", source_id="example") +second = replay_character_word_gonols("don't cut.", source_id="example") +assert first.receipt_digest == second.receipt_digest +assert ["".join(word.kind_id) for word in first.words] == ["don't", "cut."] +``` + +Set `UCNS_SOURCE_ROOT` to a current UCNS checkout if `ucns.public_gonol` is not importable. + For new text-gonol work: 1. start in EDCM; 2. resolve the exact EDCM source/admission profile; 3. import current METAPAT affixiation invariants rather than redefining them; 4. consume current UCNS geometry rather than moving text semantics into UCNS; -5. construct characters, morphology, definitions, and recursive relations under explicit EDCM receipts; +5. construct characters, words, definitions, and recursive relations under explicit EDCM receipts; use `edcm.character_word` for the first two stages; 6. keep unresolved UCNS geometric operations as `hmmm`; 7. replay the complete declared construction before claiming completion; and 8. freeze any later EDCM measurement separately. diff --git a/docs/ucns-adapter.md b/docs/ucns-adapter.md index 3f0c6c7..e3f2f46 100644 --- a/docs/ucns-adapter.md +++ b/docs/ucns-adapter.md @@ -188,7 +188,7 @@ New text-gonol work starts in EDCM and follows the active contract: - METAPAT supplies affixiation semantics without being redefined downstream; - UCNS supplies the current gonol/Möbius/Public Gonol geometry; - unresolved UCNS operations remain `hmmm` rather than being inferred from Unicode names, dictionary definitions, adjacency, glyph shape, or conventional grammar; -- EDCM constructs morphology, definitions, and recursive gonol relations while preserving identity, occurrence, order, multiplicity, relation identity, scale, source, and provenance; +- EDCM constructs words, then definitions, then recursive gonol relations while preserving identity, occurrence, order, multiplicity, relation identity, scale, source, and provenance; `edcm.character_word` is the implemented candidate for the first two stages; - completion claims require a deterministic receipt and independent complete replay; and - EDCM measurement remains a separate projection with explicit information loss and falsifiers. diff --git a/edcm/character_word.py b/edcm/character_word.py new file mode 100644 index 0000000..89fde68 --- /dev/null +++ b/edcm/character_word.py @@ -0,0 +1,409 @@ +"""EDCM character-to-word gonol candidate constructor. + +Usage guidance +-------------- +This is an implemented candidate, not selected canon. It owns EDCM text-domain +admission and word closure only. Import current UCNS Public Gonol geometry; +do not copy the carrier into EDCM. + + from edcm.character_word import construct_character_word_gonols, replay_character_word_gonols + + first = construct_character_word_gonols("don't cut.", source_id="example") + second = replay_character_word_gonols("don't cut.", source_id="example") + assert first.receipt_digest == second.receipt_digest + +Frozen choices for ``edcm.character_word_gonol/v1``: + +- admission unit: one Unicode scalar (code point), not a grapheme cluster; +- word closure: Python ``str.isspace()`` (Unicode White_Space) is the declared + EDCM delimiter relation, not a UCNS function inferred from SPACE; +- Public Gonol position is attached when the scalar is on the current carrier; + otherwise geometry remains unresolved; +- pronunciation is ignored; +- no case folding, NFC, trimming, or deduplication. + +UCNS geometry is consumed through ``ucns.public_gonol``. Set +``UCNS_SOURCE_ROOT`` to a current UCNS checkout if the package is not +importable. The constructor fails closed on a Public Gonol digest mismatch. +""" + +# === MODULE_BUILD === +# id: edcm_character_word_gonol +# module_name: character_word +# module_kind: engine +# summary: named EDCM candidate that admits Unicode-scalar character gonols and closes non-whitespace runs into word gonols while consuming current UCNS Public Gonol geometry without selecting canon or activating measurement +# owner: Erin Spencer +# public_surface: CONSTRUCTOR_ID, CONSTRUCTOR_VERSION, PINNED_PUBLIC_GONOL_SHA256, CharacterGonol, WordGonol, CharacterWordReceipt, CharacterWordError, construct_character_word_gonols, replay_character_word_gonols, canonical_receipt_bytes +# internal_surface: _load_public_gonol, _character_record, _close_words, _receipt_payload, _digest +# auth_boundary: current UCNS Public Gonol digest must match the pinned geometry identity +# storage_boundary: none; receipts remain caller-owned in-memory objects +# network_boundary: none +# user_data_boundary: caller-supplied source text remains in memory and is not transmitted +# admin_only: false +# tests: tests.test_character_word_gonol +# rollout: explicit candidate constructor; no measurement, definition, or recursive-relation activation +# rollback: remove this module; historical lexical-floor and word-gonol observation adapters remain unchanged +# requires: ucns.public_gonol +# since: 2026-08-21 +# unresolved: UCNS geometric operation of each Public Gonol function position; Möbius-carrier affixiation/coupling law; definition and recursive gonol constructors; complete English morphology law +# === END MODULE_BUILD === + +# === CONTRACTS === +# id: character_admission_is_unicode_scalar +# given: a source string is admitted +# then: every Unicode scalar is one character gonol with occurrence index, exact scalar, and optional Public Gonol position +# class: construction +# since: 2026-08-21 +# +# id: word_closure_uses_declared_whitespace +# given: admitted character gonols include Unicode White_Space +# then: maximal non-whitespace runs close into word gonols and whitespace remains separately addressable boundary gonols +# class: construction +# since: 2026-08-21 +# +# id: closed_words_preserve_constituents +# given: a word gonol is closed +# then: constituent character identities, order, multiplicity, and source offsets remain recoverable and the word is atomic for later participation +# class: construction +# since: 2026-08-21 +# +# id: geometry_mismatch_fails_closed +# given: imported Public Gonol digest differs from the pinned identity +# then: construction raises rather than copying or inventing a carrier +# class: safety +# since: 2026-08-21 +# +# id: candidate_does_not_select_canon +# given: a receipt is minted +# then: standing is implemented-candidate, selection_effect is none, and measurement/definition/recursive claims remain nonclaims +# class: doctrine +# since: 2026-08-21 +# === END CONTRACTS === + +from __future__ import annotations + +from dataclasses import dataclass +from hashlib import sha256 +import importlib +import json +import os +from pathlib import Path +import sys +from types import ModuleType +from typing import Any, Callable, Mapping + + +CONSTRUCTOR_ID = "edcm.character_word_gonol" +CONSTRUCTOR_VERSION = "v1" +PINNED_PUBLIC_GONOL_SHA256 = ( + "55d10c84529a4d7bc7714786357e977b68d9df2ac3f73d20e229580b552c2ef5" +) +STANDING = "implemented-candidate" +SELECTION_EFFECT = "none" + +FROZEN_CHOICES: tuple[tuple[str, str], ...] = ( + ("admission_unit", "unicode-scalar"), + ("word_delimiter", "python-str-isspace-unicode-white-space"), + ("carrier", "ucns.public_gonol"), + ("pronunciation", "ignored"), + ("normalization", "none"), +) + +NONCLAIMS: tuple[str, ...] = ( + "not selected canon", + "not EDCM measurement validity", + "not a definition gonol constructor", + "not a recursive-relation constructor", + "not complete English morphology law", + "not a UCNS geometric function operation", + "not METAPAT canon promotion", +) + +HMMM: tuple[str, ...] = ( + "exact UCNS geometric operation of each Public Gonol function position", + "UCNS Möbius-carrier affixiation/coupling law", + "definition gonol constructor", + "recursive gonol-relation constructor", + "source-supported complete English morphology law", +) + + +class CharacterWordError(RuntimeError): + """Fail-closed constructor error.""" + + +@dataclass(frozen=True, slots=True) +class CharacterGonol: + """One admitted Unicode-scalar character gonol.""" + + occurrence: int + scalar: str + codepoint: int + carrier_index: int | None + role: str + + @property + def kind_id(self) -> str: + return self.scalar + + +@dataclass(frozen=True, slots=True) +class WordGonol: + """Ordered non-whitespace character gonols closed as one word.""" + + occurrence: int + source_start: int + source_end: int + characters: tuple[CharacterGonol, ...] + + @property + def kind_id(self) -> tuple[str, ...]: + return tuple(character.scalar for character in self.characters) + + +@dataclass(frozen=True, slots=True) +class CharacterWordReceipt: + """Deterministic construction receipt. Digest is replay identity, not gonol identity.""" + + constructor_id: str + constructor_version: str + standing: str + selection_effect: str + source_id: str + source_length: int + carrier_digest: str + frozen_choices: tuple[tuple[str, str], ...] + characters: tuple[CharacterGonol, ...] + words: tuple[WordGonol, ...] + boundaries: tuple[CharacterGonol, ...] + nonclaims: tuple[str, ...] + hmmm: tuple[str, ...] + receipt_digest: str + + +def _candidate_ucns_roots() -> tuple[Path, ...]: + roots: list[Path] = [] + configured = os.environ.get("UCNS_SOURCE_ROOT") + if configured: + roots.append(Path(configured) / "src") + sibling = Path(__file__).resolve().parents[2] / "ucns" / "src" + if sibling.is_dir(): + roots.append(sibling) + return tuple(roots) + + +def _load_public_gonol() -> ModuleType: + try: + module = importlib.import_module("ucns.public_gonol") + except ImportError: + module = None + last_error: Exception | None = None + for root in _candidate_ucns_roots(): + if str(root) not in sys.path: + sys.path.insert(0, str(root)) + try: + module = importlib.import_module("ucns.public_gonol") + break + except ImportError as exc: + last_error = exc + if module is None: + raise CharacterWordError( + "current UCNS public_gonol geometry is required; " + "install ucns or set UCNS_SOURCE_ROOT" + ) from last_error + digest = str(getattr(module, "PUBLIC_GONOL_SHA256", "")) + if digest != PINNED_PUBLIC_GONOL_SHA256: + raise CharacterWordError( + "UCNS Public Gonol digest mismatch: " + f"constructor pins {PINNED_PUBLIC_GONOL_SHA256}, " + f"imported {digest or 'missing'}" + ) + return module + + +def _character_record( + occurrence: int, + scalar: str, + position_of: Callable[[str], int | None], + role: str, +) -> CharacterGonol: + if not isinstance(scalar, str) or len(scalar) != 1: + raise CharacterWordError("admission unit is exactly one Unicode scalar") + codepoint = ord(scalar) + if 0xD800 <= codepoint <= 0xDFFF: + raise CharacterWordError("surrogate code points are not Unicode scalars") + return CharacterGonol( + occurrence=occurrence, + scalar=scalar, + codepoint=codepoint, + carrier_index=position_of(scalar), + role=role, + ) + + +def _close_words( + characters: tuple[CharacterGonol, ...], +) -> tuple[tuple[WordGonol, ...], tuple[CharacterGonol, ...]]: + words: list[WordGonol] = [] + boundaries: list[CharacterGonol] = [] + current: list[CharacterGonol] = [] + for character in characters: + if character.scalar.isspace(): + if current: + start = current[0].occurrence + end = current[-1].occurrence + 1 + words.append( + WordGonol( + occurrence=len(words), + source_start=start, + source_end=end, + characters=tuple(current), + ) + ) + current = [] + boundaries.append( + CharacterGonol( + occurrence=character.occurrence, + scalar=character.scalar, + codepoint=character.codepoint, + carrier_index=character.carrier_index, + role="boundary", + ) + ) + continue + current.append( + CharacterGonol( + occurrence=character.occurrence, + scalar=character.scalar, + codepoint=character.codepoint, + carrier_index=character.carrier_index, + role="word-member", + ) + ) + if current: + words.append( + WordGonol( + occurrence=len(words), + source_start=current[0].occurrence, + source_end=current[-1].occurrence + 1, + characters=tuple(current), + ) + ) + return tuple(words), tuple(boundaries) + + +def _character_payload(character: CharacterGonol) -> dict[str, Any]: + return { + "occurrence": character.occurrence, + "scalar": character.scalar, + "codepoint": character.codepoint, + "carrier_index": character.carrier_index, + "role": character.role, + "kind_id": character.kind_id, + } + + +def _receipt_payload( + *, + source_id: str, + source_length: int, + carrier_digest: str, + characters: tuple[CharacterGonol, ...], + words: tuple[WordGonol, ...], + boundaries: tuple[CharacterGonol, ...], +) -> dict[str, Any]: + return { + "constructor_id": CONSTRUCTOR_ID, + "constructor_version": CONSTRUCTOR_VERSION, + "standing": STANDING, + "selection_effect": SELECTION_EFFECT, + "source_id": source_id, + "source_length": source_length, + "carrier_digest": carrier_digest, + "frozen_choices": [list(item) for item in FROZEN_CHOICES], + "characters": [_character_payload(item) for item in characters], + "words": [ + { + "occurrence": word.occurrence, + "source_start": word.source_start, + "source_end": word.source_end, + "kind_id": list(word.kind_id), + "characters": [_character_payload(item) for item in word.characters], + } + for word in words + ], + "boundaries": [_character_payload(item) for item in boundaries], + "nonclaims": list(NONCLAIMS), + "hmmm": list(HMMM), + } + + +def canonical_receipt_bytes(payload: Mapping[str, Any]) -> bytes: + return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode( + "utf-8" + ) + + +def _digest(payload: Mapping[str, Any]) -> str: + return sha256(canonical_receipt_bytes(payload)).hexdigest() + + +def construct_character_word_gonols(source: str, *, source_id: str) -> CharacterWordReceipt: + """Admit every Unicode scalar and close whitespace-delimited word gonols.""" + + if not isinstance(source, str): + raise CharacterWordError("source must be a Unicode string") + if not isinstance(source_id, str) or not source_id: + raise CharacterWordError("source_id must be a non-empty string") + + geometry = _load_public_gonol() + position_of = geometry.public_gonol_position + admitted: list[CharacterGonol] = [] + for occurrence, scalar in enumerate(source): + admitted.append(_character_record(occurrence, scalar, position_of, "admitted")) + characters = tuple(admitted) + words, boundaries = _close_words(characters) + payload = _receipt_payload( + source_id=source_id, + source_length=len(source), + carrier_digest=str(geometry.PUBLIC_GONOL_SHA256), + characters=characters, + words=words, + boundaries=boundaries, + ) + return CharacterWordReceipt( + constructor_id=CONSTRUCTOR_ID, + constructor_version=CONSTRUCTOR_VERSION, + standing=STANDING, + selection_effect=SELECTION_EFFECT, + source_id=source_id, + source_length=len(source), + carrier_digest=str(geometry.PUBLIC_GONOL_SHA256), + frozen_choices=FROZEN_CHOICES, + characters=characters, + words=words, + boundaries=boundaries, + nonclaims=NONCLAIMS, + hmmm=HMMM, + receipt_digest=_digest(payload), + ) + + +def replay_character_word_gonols(source: str, *, source_id: str) -> CharacterWordReceipt: + """Independently reconstruct the same declared source.""" + + return construct_character_word_gonols(source, source_id=source_id) + + +__all__ = [ + "CONSTRUCTOR_ID", + "CONSTRUCTOR_VERSION", + "PINNED_PUBLIC_GONOL_SHA256", + "CharacterGonol", + "CharacterWordError", + "CharacterWordReceipt", + "WordGonol", + "canonical_receipt_bytes", + "construct_character_word_gonols", + "replay_character_word_gonols", +] diff --git a/tests/test_character_word_gonol.py b/tests/test_character_word_gonol.py new file mode 100644 index 0000000..76e56fb --- /dev/null +++ b/tests/test_character_word_gonol.py @@ -0,0 +1,139 @@ +# === CHECKS === +# id: character_admission_is_unicode_scalar_check +# proves: character_admission_is_unicode_scalar +# call: self::test_every_unicode_scalar_is_a_character_gonol +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: word_closure_uses_declared_whitespace_check +# proves: word_closure_uses_declared_whitespace +# call: self::test_whitespace_closes_words_and_keeps_boundaries +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: closed_words_preserve_constituents_check +# proves: closed_words_preserve_constituents +# call: self::test_closed_word_keeps_apostrophe_and_constituents +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: geometry_mismatch_fails_closed_check +# proves: geometry_mismatch_fails_closed +# call: self::test_digest_mismatch_fails_closed +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: candidate_does_not_select_canon_check +# proves: candidate_does_not_select_canon +# call: self::test_receipt_remains_unimplemented_canon +# timeout: 30 +# mutates: none +# cleanup: none +# === END CHECKS === + +from __future__ import annotations + +from types import SimpleNamespace +import unittest +from unittest.mock import patch + +from edcm.character_word import ( + CONSTRUCTOR_ID, + CONSTRUCTOR_VERSION, + PINNED_PUBLIC_GONOL_SHA256, + CharacterWordError, + construct_character_word_gonols, + replay_character_word_gonols, +) + + +class CharacterWordGonolTest(unittest.TestCase): + def test_every_unicode_scalar_is_a_character_gonol(self) -> None: + receipt = construct_character_word_gonols("Aé ", source_id="fixture-scalars") + self.assertEqual([item.scalar for item in receipt.characters], ["A", "é", " "]) + self.assertEqual([item.occurrence for item in receipt.characters], [0, 1, 2]) + self.assertEqual(receipt.characters[0].carrier_index, 1) + self.assertIsNone(receipt.characters[1].carrier_index) + self.assertEqual(receipt.characters[2].carrier_index, 0) + self.assertEqual(receipt.carrier_digest, PINNED_PUBLIC_GONOL_SHA256) + + def test_whitespace_closes_words_and_keeps_boundaries(self) -> None: + receipt = construct_character_word_gonols(" word gonol\n", source_id="fixture-space") + self.assertEqual(["".join(word.kind_id) for word in receipt.words], ["word", "gonol"]) + self.assertEqual([item.scalar for item in receipt.boundaries], [" ", " ", " ", " ", "\n"]) + self.assertEqual(receipt.words[0].source_start, 2) + self.assertEqual(receipt.words[0].source_end, 6) + self.assertEqual(receipt.words[1].source_start, 8) + self.assertEqual(receipt.words[1].source_end, 13) + + def test_closed_word_keeps_apostrophe_and_constituents(self) -> None: + receipt = construct_character_word_gonols("don't cut.", source_id="fixture-dont") + self.assertEqual(len(receipt.words), 2) + dont = receipt.words[0] + self.assertEqual(dont.kind_id, ("d", "o", "n", "'", "t")) + self.assertEqual([item.role for item in dont.characters], ["word-member"] * 5) + self.assertEqual(dont.characters[3].scalar, "'") + self.assertIsNotNone(dont.characters[3].carrier_index) + cut = receipt.words[1] + self.assertEqual(cut.kind_id, ("c", "u", "t", ".")) + self.assertEqual(receipt.boundaries[0].scalar, " ") + + def test_empty_source_is_complete_and_empty(self) -> None: + receipt = construct_character_word_gonols("", source_id="fixture-empty") + self.assertEqual(receipt.source_length, 0) + self.assertEqual(receipt.characters, ()) + self.assertEqual(receipt.words, ()) + self.assertEqual(receipt.boundaries, ()) + + def test_replay_matches_byte_identity(self) -> None: + source = "don't cut." + first = construct_character_word_gonols(source, source_id="fixture-replay") + second = replay_character_word_gonols(source, source_id="fixture-replay") + self.assertEqual(first.receipt_digest, second.receipt_digest) + self.assertEqual( + [item.scalar for item in first.characters], + [item.scalar for item in second.characters], + ) + self.assertEqual( + [word.kind_id for word in first.words], + [word.kind_id for word in second.words], + ) + + def test_receipt_remains_unimplemented_canon(self) -> None: + receipt = construct_character_word_gonols("a", source_id="fixture-standing") + self.assertEqual(receipt.constructor_id, CONSTRUCTOR_ID) + self.assertEqual(receipt.constructor_version, CONSTRUCTOR_VERSION) + self.assertEqual(receipt.standing, "implemented-candidate") + self.assertEqual(receipt.selection_effect, "none") + self.assertIn("not selected canon", receipt.nonclaims) + self.assertIn("not EDCM measurement validity", receipt.nonclaims) + self.assertIn("definition gonol constructor", receipt.hmmm) + + def test_identity_is_structure_not_hash(self) -> None: + receipt = construct_character_word_gonols("aa", source_id="fixture-identity") + self.assertEqual(receipt.characters[0].kind_id, "a") + self.assertEqual(receipt.characters[1].kind_id, "a") + self.assertNotEqual(receipt.characters[0].occurrence, receipt.characters[1].occurrence) + self.assertEqual(receipt.words[0].kind_id, ("a", "a")) + self.assertNotEqual(receipt.receipt_digest, receipt.words[0].kind_id) + + def test_digest_mismatch_fails_closed(self) -> None: + fake = SimpleNamespace( + PUBLIC_GONOL_SHA256="0" * 64, + public_gonol_position=lambda _glyph: 0, + ) + with patch("edcm.character_word.importlib.import_module", return_value=fake): + with self.assertRaisesRegex(CharacterWordError, "digest mismatch"): + construct_character_word_gonols("a", source_id="fixture-mismatch") + + def test_surrogate_fails_closed(self) -> None: + with self.assertRaisesRegex(CharacterWordError, "surrogate"): + construct_character_word_gonols("\ud800", source_id="fixture-surrogate") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_gonol_language_authority.py b/tests/test_gonol_language_authority.py index d8b1acd..f88bb16 100644 --- a/tests/test_gonol_language_authority.py +++ b/tests/test_gonol_language_authority.py @@ -31,6 +31,11 @@ def test_live_surfaces_route_gonol_authority_consistently() -> None: assert "METAPAT defines affixiation" in boundary assert "UCNS owns any exact geometric realization" in boundary assert "EDCM applies affixiation to text-domain gonols" in boundary + for text in (boundary, canon, agents): + assert "characters" in text and "words" in text + assert "This order is load-bearing" in boundary + assert "edcm.character_word" in boundary + assert "Morphology is not a required stage" in boundary def test_live_surfaces_do_not_restore_ucns_language_ownership() -> None: From 6fed0d9b53cde6e83c38a8f244cec8b430059cf8 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 21 Aug 2026 22:20:49 +0000 Subject: [PATCH 2/6] Implement definition-gonol candidate from closed words Add edcm.definition_gonol as an implemented candidate for the third construction stage. It affixiates already-closed word gonols with exact caller-supplied definition evidence, does not load a dictionary or invent a gloss, and does not select canon or activate measurement. --- AGENTS.md | 4 +- CLAUDE.md | 2 +- README.md | 2 +- docs/GONOL_LANGUAGE_BOUNDARY.md | 23 +- docs/ucns-adapter.md | 2 +- edcm/character_word.py | 2 +- edcm/definition_gonol.py | 303 +++++++++++++++++++++++++ tests/test_definition_gonol.py | 164 +++++++++++++ tests/test_gonol_language_authority.py | 1 + 9 files changed, 495 insertions(+), 8 deletions(-) create mode 100644 edcm/definition_gonol.py create mode 100644 tests/test_definition_gonol.py diff --git a/AGENTS.md b/AGENTS.md index 6260057..d4143da 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,7 +56,7 @@ EDCM owns the character-admission profile and linguistic/semantic construction. - `edcm/measurement/` is the frozen maintained baseline candidate. - `edcm.ucns_edcm_experiments` is the first joint experiment runner. - EDCM owns current text-domain gonol admission and construction; measurement remains separately frozen and evaluated. -- `edcm.character_word` is the implemented candidate for `characters -> words`. It is not selected canon. +- `edcm.character_word` is the implemented candidate for `characters -> words`. `edcm.definition_gonol` is the implemented candidate for definition gonols from exact source definition evidence. Neither is selected canon. - UCNS structural policies, product-character candidates, faithful-breadth candidates, and unresolved Möbius coupling laws remain noncanonical unless current UCNS authority says otherwise. - EDCM axes, thresholds, marker lists, and circuit parameters remain candidates unless an explicit canon decision says otherwise. - A passing hypothesis is experiment-supported evidence, not canon. @@ -104,7 +104,7 @@ python -m twine check dist/* ## Usage guidance -For new text-gonol construction, begin in EDCM. Resolve the exact EDCM source/admission profile, import METAPAT affixiation invariants without redefining them, consume current UCNS geometry, and construct `characters -> words` with `edcm.character_word`. Replay independently before claiming completion. Freeze any EDCM measurement only after construction and keep its evidence status separate. +For new text-gonol construction, begin in EDCM. Resolve the exact EDCM source/admission profile, import METAPAT affixiation invariants without redefining them, consume current UCNS geometry, construct `characters -> words` with `edcm.character_word`, then construct definition gonols with `edcm.definition_gonol` from exact source definition evidence. Replay independently before claiming completion. Freeze any EDCM measurement only after construction and keep its evidence status separate. For historical experiments, reproduce the exact historical producer epoch and names. Historical UCNS-owned lexical artifacts remain evidence; they do not restore current language authority to UCNS. diff --git a/CLAUDE.md b/CLAUDE.md index b3d4d96..f43f52a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -111,7 +111,7 @@ definitions recursive gonol relations ``` -This order is load-bearing. `edcm.character_word` is the implemented candidate for character admission and word closure. Morphology is not a required stage. Do not reopen a completed word gonol or insert another required scale merely because a conventional NLP pipeline contains one. +This order is load-bearing. `edcm.character_word` is the implemented candidate for character admission and word closure. `edcm.definition_gonol` is the implemented candidate for definition gonols from exact source definition evidence. Morphology is not a required stage. Do not reopen a completed word gonol or insert another required scale merely because a conventional NLP pipeline contains one. Do not insert a conventional NLP tokenizer, token IDs, subword pieces, opaque external vector lookup, or whole-string hash as a substitute for gonol identity. Source prose, dictionaries, corpora, labels, and annotations may remain evidence and provenance; they become gonol semantics only through a source-bound EDCM construction whose admission, relation, closure, and replay boundaries are explicit. diff --git a/README.md b/README.md index 0936654..a620180 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ definitions recursive gonol relations ``` -`edcm.character_word` is the implemented candidate for the first two stages. It admits Unicode scalars as character gonols and closes non-whitespace runs into word gonols. It is not selected canon and does not activate measurement. +`edcm.character_word` is the implemented candidate for the first two stages. `edcm.definition_gonol` is the implemented candidate for definition gonols from already-closed word gonols and exact caller-supplied definition evidence. Neither is selected canon, and neither activates measurement. Affixiation is defined by METAPAT. UCNS owns any exact geometric realization. EDCM applies affixiation to text-domain gonols. Completed gonols are atomic for participation at the consuming scale while constituent identity and provenance remain recoverable. diff --git a/docs/GONOL_LANGUAGE_BOUNDARY.md b/docs/GONOL_LANGUAGE_BOUNDARY.md index 5a4f199..64197ca 100644 --- a/docs/GONOL_LANGUAGE_BOUNDARY.md +++ b/docs/GONOL_LANGUAGE_BOUNDARY.md @@ -62,7 +62,7 @@ This order is load-bearing. Every admitted character is a gonol. Ordered charact Do not insert another required stage into this sequence unless the governing contract is explicitly changed. Morphology is not a required stage. Historical morphology evidence remains historical; the complete English morphology law remains `hmmm`. -`edcm.character_word` is the implemented candidate for character admission and word closure. It is not selected canon. +`edcm.character_word` is the implemented candidate for character admission and word closure. `edcm.definition_gonol` is the implemented candidate for definition gonols from already-closed word gonols and exact caller-supplied definition evidence. Neither is selected canon. Relationships that constitute a gonol enter the construction. They are not merely external semantic edges. Sidecars may index, cache, project, or record provenance; they do not replace intrinsic relational content. @@ -137,6 +137,25 @@ assert first.receipt_digest == second.receipt_digest assert ["".join(word.kind_id) for word in first.words] == ["don't", "cut."] ``` +Copy-pasteable definition candidate: + +```python +from edcm.definition_gonol import construct_definition_gonol, replay_definition_gonol + +first = construct_definition_gonol( + headword="cut", + definition="to divide with a sharp edge", + source_id="example:cut#1", +) +second = replay_definition_gonol( + headword="cut", + definition="to divide with a sharp edge", + source_id="example:cut#1", +) +assert first.receipt_digest == second.receipt_digest +assert first.definition.relation == "definition-of" +``` + Set `UCNS_SOURCE_ROOT` to a current UCNS checkout if `ucns.public_gonol` is not importable. For new text-gonol work: @@ -145,7 +164,7 @@ For new text-gonol work: 2. resolve the exact EDCM source/admission profile; 3. import current METAPAT affixiation invariants rather than redefining them; 4. consume current UCNS geometry rather than moving text semantics into UCNS; -5. construct characters, words, definitions, and recursive relations under explicit EDCM receipts; use `edcm.character_word` for the first two stages; +5. construct characters, words, definitions, and recursive relations under explicit EDCM receipts; use `edcm.character_word` then `edcm.definition_gonol`; 6. keep unresolved UCNS geometric operations as `hmmm`; 7. replay the complete declared construction before claiming completion; and 8. freeze any later EDCM measurement separately. diff --git a/docs/ucns-adapter.md b/docs/ucns-adapter.md index e3f2f46..ac08a0c 100644 --- a/docs/ucns-adapter.md +++ b/docs/ucns-adapter.md @@ -188,7 +188,7 @@ New text-gonol work starts in EDCM and follows the active contract: - METAPAT supplies affixiation semantics without being redefined downstream; - UCNS supplies the current gonol/Möbius/Public Gonol geometry; - unresolved UCNS operations remain `hmmm` rather than being inferred from Unicode names, dictionary definitions, adjacency, glyph shape, or conventional grammar; -- EDCM constructs words, then definitions, then recursive gonol relations while preserving identity, occurrence, order, multiplicity, relation identity, scale, source, and provenance; `edcm.character_word` is the implemented candidate for the first two stages; +- EDCM constructs words, then definitions, then recursive gonol relations while preserving identity, occurrence, order, multiplicity, relation identity, scale, source, and provenance; `edcm.character_word` and `edcm.definition_gonol` are the implemented candidates for those stages; - completion claims require a deterministic receipt and independent complete replay; and - EDCM measurement remains a separate projection with explicit information loss and falsifiers. diff --git a/edcm/character_word.py b/edcm/character_word.py index 89fde68..43d6401 100644 --- a/edcm/character_word.py +++ b/edcm/character_word.py @@ -45,7 +45,7 @@ # rollback: remove this module; historical lexical-floor and word-gonol observation adapters remain unchanged # requires: ucns.public_gonol # since: 2026-08-21 -# unresolved: UCNS geometric operation of each Public Gonol function position; Möbius-carrier affixiation/coupling law; definition and recursive gonol constructors; complete English morphology law +# unresolved: UCNS geometric operation of each Public Gonol function position; Möbius-carrier affixiation/coupling law; recursive gonol constructor; complete English morphology law # === END MODULE_BUILD === # === CONTRACTS === diff --git a/edcm/definition_gonol.py b/edcm/definition_gonol.py new file mode 100644 index 0000000..30d62ac --- /dev/null +++ b/edcm/definition_gonol.py @@ -0,0 +1,303 @@ +"""EDCM definition-gonol candidate constructor. + +Usage guidance +-------------- +This is an implemented candidate, not selected canon. It constructs one +definition gonol from already-closed word gonols plus exact caller-supplied +definition evidence. It does not load a dictionary, invent a gloss, or reopen +closed words. + + from edcm.definition_gonol import ( + construct_definition_gonol, + replay_definition_gonol, + ) + + first = construct_definition_gonol( + headword="cut", + definition="to divide with a sharp edge", + source_id="example:cut#1", + ) + second = replay_definition_gonol( + headword="cut", + definition="to divide with a sharp edge", + source_id="example:cut#1", + ) + assert first.receipt_digest == second.receipt_digest + +Frozen choices for ``edcm.definition_gonol/v1``: + +- definition evidence is the exact ``headword`` and ``definition`` strings plus + ``source_id`` supplied by the caller; +- both strings are constructed through ``edcm.character_word``; +- closed word gonols participate atomically and are not reopened; +- the intrinsic relation is ``definition-of``: ordered headword words plus + ordered definition-body words; +- pronunciation is ignored and must not be passed as a substitute gloss; +- no lemma, stem, morphology, or sense-inventory is inferred; +- each call constructs one definition gonol; multiple senses remain multiple + gonols. + +Historical OEWN and lexical-floor artifacts are not this constructor. Pass +their exact sense text as ``definition`` only when reproducing that source +identity. +""" + +# === MODULE_BUILD === +# id: edcm_definition_gonol +# module_name: definition_gonol +# module_kind: engine +# summary: named EDCM candidate that affixiates already-closed word gonols with exact caller-supplied definition evidence into one definition gonol without selecting canon, inventing glosses, or activating measurement +# owner: Erin Spencer +# public_surface: CONSTRUCTOR_ID, CONSTRUCTOR_VERSION, RELATION, DefinitionGonol, DefinitionGonolError, DefinitionGonolReceipt, construct_definition_gonol, replay_definition_gonol +# internal_surface: _require_words, _word_payload, _receipt_payload, _digest +# auth_boundary: consumes edcm.character_word and therefore the pinned UCNS Public Gonol digest +# storage_boundary: none; receipts remain caller-owned in-memory objects +# network_boundary: none +# user_data_boundary: caller-supplied headword and definition text remain in memory and are not transmitted +# admin_only: false +# tests: tests.test_definition_gonol +# rollout: explicit candidate constructor; no measurement or recursive-relation activation +# rollback: remove this module; character_word and historical lexical-floor evidence remain unchanged +# requires: edcm_character_word_gonol +# since: 2026-08-21 +# unresolved: UCNS geometric operation of each Public Gonol function position; Möbius-carrier affixiation/coupling law; recursive gonol constructor; complete English morphology law; selection among competing definition sources +# === END MODULE_BUILD === + +# === CONTRACTS === +# id: definition_uses_closed_word_gonols +# given: headword and definition strings are admitted +# then: both are constructed through character_word and the definition gonol participants are those closed word gonols without reopening their characters as a free stream +# class: construction +# since: 2026-08-21 +# +# id: definition_requires_exact_source_evidence +# given: definition evidence is missing, empty of words, or not a string +# then: construction fails closed rather than inventing a gloss or loading a dictionary +# class: safety +# since: 2026-08-21 +# +# id: definition_relation_is_intrinsic +# given: a definition gonol is closed +# then: the relation definition-of enters the construction as ordered headword words plus ordered body words with recoverable nested receipts +# class: construction +# since: 2026-08-21 +# +# id: definition_candidate_does_not_select_canon +# given: a receipt is minted +# then: standing is implemented-candidate, selection_effect is none, and measurement, recursion, morphology, and dictionary-authority remain nonclaims +# class: doctrine +# since: 2026-08-21 +# === END CONTRACTS === + +from __future__ import annotations + +from dataclasses import dataclass +from hashlib import sha256 +import json +from typing import Any, Mapping + +from .character_word import CharacterWordReceipt, WordGonol, construct_character_word_gonols + + +CONSTRUCTOR_ID = "edcm.definition_gonol" +CONSTRUCTOR_VERSION = "v1" +RELATION = "definition-of" +STANDING = "implemented-candidate" +SELECTION_EFFECT = "none" + +FROZEN_CHOICES: tuple[tuple[str, str], ...] = ( + ("word_constructor", "edcm.character_word_gonol/v1"), + ("definition_evidence", "caller-supplied-exact-strings"), + ("relation", RELATION), + ("pronunciation", "ignored"), + ("morphology", "not-a-stage"), + ("sense_inventory", "one-definition-per-call"), +) + +NONCLAIMS: tuple[str, ...] = ( + "not selected canon", + "not EDCM measurement validity", + "not a recursive-relation constructor", + "not complete English morphology law", + "not a dictionary or sense inventory", + "not a UCNS geometric function operation", + "not METAPAT canon promotion", + "not OEWN or lexical-floor revival", +) + +HMMM: tuple[str, ...] = ( + "exact UCNS geometric operation of each Public Gonol function position", + "UCNS Möbius-carrier affixiation/coupling law", + "recursive gonol-relation constructor", + "source-supported complete English morphology law", + "which definition source, if any, is later selected", +) + + +class DefinitionGonolError(RuntimeError): + """Fail-closed definition constructor error.""" + + +@dataclass(frozen=True, slots=True) +class DefinitionGonol: + """Closed definition gonol whose participants are already-closed words.""" + + occurrence: int + relation: str + headword_words: tuple[WordGonol, ...] + body_words: tuple[WordGonol, ...] + + @property + def kind_id(self) -> tuple[tuple[tuple[str, ...], ...], tuple[tuple[str, ...], ...]]: + return ( + tuple(word.kind_id for word in self.headword_words), + tuple(word.kind_id for word in self.body_words), + ) + + +@dataclass(frozen=True, slots=True) +class DefinitionGonolReceipt: + """Deterministic definition receipt. Digest is replay identity, not gonol identity.""" + + constructor_id: str + constructor_version: str + standing: str + selection_effect: str + source_id: str + frozen_choices: tuple[tuple[str, str], ...] + headword_receipt: CharacterWordReceipt + definition_receipt: CharacterWordReceipt + definition: DefinitionGonol + nonclaims: tuple[str, ...] + hmmm: tuple[str, ...] + receipt_digest: str + + +def _require_words(receipt: CharacterWordReceipt, *, field: str) -> tuple[WordGonol, ...]: + if not receipt.words: + raise DefinitionGonolError( + f"{field} produced no closed word gonols; exact source evidence is required" + ) + return receipt.words + + +def _word_payload(word: WordGonol) -> dict[str, Any]: + return { + "occurrence": word.occurrence, + "source_start": word.source_start, + "source_end": word.source_end, + "kind_id": list(word.kind_id), + "character_occurrences": [item.occurrence for item in word.characters], + } + + +def _receipt_payload( + *, + source_id: str, + headword_receipt: CharacterWordReceipt, + definition_receipt: CharacterWordReceipt, + definition: DefinitionGonol, +) -> dict[str, Any]: + return { + "constructor_id": CONSTRUCTOR_ID, + "constructor_version": CONSTRUCTOR_VERSION, + "standing": STANDING, + "selection_effect": SELECTION_EFFECT, + "source_id": source_id, + "frozen_choices": [list(item) for item in FROZEN_CHOICES], + "relation": RELATION, + "headword_receipt_digest": headword_receipt.receipt_digest, + "definition_receipt_digest": definition_receipt.receipt_digest, + "headword_words": [_word_payload(word) for word in definition.headword_words], + "body_words": [_word_payload(word) for word in definition.body_words], + "kind_id": [ + [list(word_kind) for word_kind in definition.kind_id[0]], + [list(word_kind) for word_kind in definition.kind_id[1]], + ], + "nonclaims": list(NONCLAIMS), + "hmmm": list(HMMM), + } + + +def _digest(payload: Mapping[str, Any]) -> str: + return sha256( + json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") + ).hexdigest() + + +def construct_definition_gonol( + *, + headword: str, + definition: str, + source_id: str, +) -> DefinitionGonolReceipt: + """Close one definition gonol from exact headword and definition source text.""" + + if not isinstance(headword, str): + raise DefinitionGonolError("headword must be an exact Unicode string") + if not isinstance(definition, str): + raise DefinitionGonolError("definition must be an exact Unicode string") + if not isinstance(source_id, str) or not source_id: + raise DefinitionGonolError("source_id must be a non-empty string") + + headword_receipt = construct_character_word_gonols( + headword, + source_id=f"{source_id}#headword", + ) + definition_receipt = construct_character_word_gonols( + definition, + source_id=f"{source_id}#definition", + ) + definition_gonol = DefinitionGonol( + occurrence=0, + relation=RELATION, + headword_words=_require_words(headword_receipt, field="headword"), + body_words=_require_words(definition_receipt, field="definition"), + ) + payload = _receipt_payload( + source_id=source_id, + headword_receipt=headword_receipt, + definition_receipt=definition_receipt, + definition=definition_gonol, + ) + return DefinitionGonolReceipt( + constructor_id=CONSTRUCTOR_ID, + constructor_version=CONSTRUCTOR_VERSION, + standing=STANDING, + selection_effect=SELECTION_EFFECT, + source_id=source_id, + frozen_choices=FROZEN_CHOICES, + headword_receipt=headword_receipt, + definition_receipt=definition_receipt, + definition=definition_gonol, + nonclaims=NONCLAIMS, + hmmm=HMMM, + receipt_digest=_digest(payload), + ) + + +def replay_definition_gonol( + *, + headword: str, + definition: str, + source_id: str, +) -> DefinitionGonolReceipt: + """Independently reconstruct the same declared definition source.""" + + return construct_definition_gonol( + headword=headword, + definition=definition, + source_id=source_id, + ) + + +__all__ = [ + "CONSTRUCTOR_ID", + "CONSTRUCTOR_VERSION", + "RELATION", + "DefinitionGonol", + "DefinitionGonolError", + "DefinitionGonolReceipt", + "construct_definition_gonol", + "replay_definition_gonol", +] diff --git a/tests/test_definition_gonol.py b/tests/test_definition_gonol.py new file mode 100644 index 0000000..937fbdc --- /dev/null +++ b/tests/test_definition_gonol.py @@ -0,0 +1,164 @@ +# === CHECKS === +# id: definition_uses_closed_word_gonols_check +# proves: definition_uses_closed_word_gonols +# call: self::test_definition_uses_closed_word_gonols_without_reopening +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: definition_requires_exact_source_evidence_check +# proves: definition_requires_exact_source_evidence +# call: self::test_empty_definition_fails_closed +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: definition_relation_is_intrinsic_check +# proves: definition_relation_is_intrinsic +# call: self::test_definition_relation_binds_headword_and_body_words +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: definition_candidate_does_not_select_canon_check +# proves: definition_candidate_does_not_select_canon +# call: self::test_receipt_remains_unimplemented_canon +# timeout: 30 +# mutates: none +# cleanup: none +# === END CHECKS === + +from __future__ import annotations + +import unittest + +from edcm.character_word import construct_character_word_gonols +from edcm.definition_gonol import ( + CONSTRUCTOR_ID, + CONSTRUCTOR_VERSION, + RELATION, + DefinitionGonolError, + construct_definition_gonol, + replay_definition_gonol, +) + + +class DefinitionGonolTest(unittest.TestCase): + def test_definition_uses_closed_word_gonols_without_reopening(self) -> None: + receipt = construct_definition_gonol( + headword="don't", + definition="do not", + source_id="fixture:dont#1", + ) + independent = construct_character_word_gonols("don't", source_id="fixture:dont#1#headword") + self.assertEqual(len(receipt.definition.headword_words), 1) + self.assertEqual(receipt.definition.headword_words[0].kind_id, independent.words[0].kind_id) + self.assertEqual( + receipt.definition.headword_words[0].characters, + independent.words[0].characters, + ) + self.assertEqual(receipt.headword_receipt.receipt_digest, independent.receipt_digest) + self.assertEqual( + ["".join(word.kind_id) for word in receipt.definition.body_words], + ["do", "not"], + ) + + def test_definition_relation_binds_headword_and_body_words(self) -> None: + receipt = construct_definition_gonol( + headword="ice cream", + definition="a frozen dessert", + source_id="fixture:ice-cream#1", + ) + self.assertEqual(receipt.definition.relation, RELATION) + self.assertEqual( + ["".join(word.kind_id) for word in receipt.definition.headword_words], + ["ice", "cream"], + ) + self.assertEqual( + ["".join(word.kind_id) for word in receipt.definition.body_words], + ["a", "frozen", "dessert"], + ) + self.assertEqual( + receipt.definition.kind_id, + ( + (("i", "c", "e"), ("c", "r", "e", "a", "m")), + ( + ("a",), + ("f", "r", "o", "z", "e", "n"), + ("d", "e", "s", "s", "e", "r", "t"), + ), + ), + ) + + def test_empty_definition_fails_closed(self) -> None: + with self.assertRaisesRegex(DefinitionGonolError, "definition produced no closed word"): + construct_definition_gonol( + headword="cut", + definition=" ", + source_id="fixture:cut#empty", + ) + + def test_empty_headword_fails_closed(self) -> None: + with self.assertRaisesRegex(DefinitionGonolError, "headword produced no closed word"): + construct_definition_gonol( + headword="\n", + definition="to divide", + source_id="fixture:cut#no-head", + ) + + def test_missing_source_id_fails_closed(self) -> None: + with self.assertRaisesRegex(DefinitionGonolError, "source_id"): + construct_definition_gonol(headword="cut", definition="to divide", source_id="") + + def test_senses_remain_separate_gonols(self) -> None: + first = construct_definition_gonol( + headword="cut", + definition="to divide with a sharp edge", + source_id="fixture:cut#1", + ) + second = construct_definition_gonol( + headword="cut", + definition="to reduce", + source_id="fixture:cut#2", + ) + self.assertEqual(first.definition.headword_words[0].kind_id, second.definition.headword_words[0].kind_id) + self.assertNotEqual(first.definition.kind_id, second.definition.kind_id) + self.assertNotEqual(first.receipt_digest, second.receipt_digest) + + def test_replay_matches_byte_identity(self) -> None: + kwargs = { + "headword": "cut", + "definition": "to divide with a sharp edge", + "source_id": "fixture:cut#replay", + } + first = construct_definition_gonol(**kwargs) + second = replay_definition_gonol(**kwargs) + self.assertEqual(first.receipt_digest, second.receipt_digest) + self.assertEqual(first.definition.kind_id, second.definition.kind_id) + + def test_receipt_remains_unimplemented_canon(self) -> None: + receipt = construct_definition_gonol( + headword="cut", + definition="to divide", + source_id="fixture:cut#standing", + ) + self.assertEqual(receipt.constructor_id, CONSTRUCTOR_ID) + self.assertEqual(receipt.constructor_version, CONSTRUCTOR_VERSION) + self.assertEqual(receipt.standing, "implemented-candidate") + self.assertEqual(receipt.selection_effect, "none") + self.assertIn("not a dictionary or sense inventory", receipt.nonclaims) + self.assertIn("not OEWN or lexical-floor revival", receipt.nonclaims) + self.assertIn("recursive gonol-relation constructor", receipt.hmmm) + + def test_identity_is_structure_not_hash(self) -> None: + receipt = construct_definition_gonol( + headword="a", + definition="b", + source_id="fixture:id", + ) + self.assertEqual(receipt.definition.kind_id, ((("a",),), (("b",),))) + self.assertNotEqual(receipt.receipt_digest, receipt.definition.kind_id) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_gonol_language_authority.py b/tests/test_gonol_language_authority.py index f88bb16..c9a5be2 100644 --- a/tests/test_gonol_language_authority.py +++ b/tests/test_gonol_language_authority.py @@ -35,6 +35,7 @@ def test_live_surfaces_route_gonol_authority_consistently() -> None: assert "characters" in text and "words" in text assert "This order is load-bearing" in boundary assert "edcm.character_word" in boundary + assert "edcm.definition_gonol" in boundary assert "Morphology is not a required stage" in boundary From 6808e9d9d0679f97311db32ab24aa568e478ca6b Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 22 Aug 2026 01:35:00 +0000 Subject: [PATCH 3/6] Implement recursive-gonol candidate from closed gonols Add edcm.recursive_gonol as an implemented candidate for the fourth construction stage. It closes recursive relations over already-closed word, definition, or recursive gonols with an exact caller-supplied relation identity, does not infer adjacency or invent UCNS coupling geometry, and does not select canon. --- AGENTS.md | 4 +- CLAUDE.md | 2 +- README.md | 2 +- docs/GONOL_LANGUAGE_BOUNDARY.md | 25 ++- docs/ucns-adapter.md | 2 +- edcm/character_word.py | 6 +- edcm/definition_gonol.py | 4 +- edcm/recursive_gonol.py | 278 +++++++++++++++++++++++++ tests/test_character_word_gonol.py | 2 +- tests/test_definition_gonol.py | 2 +- tests/test_gonol_language_authority.py | 1 + tests/test_recursive_gonol.py | 170 +++++++++++++++ 12 files changed, 484 insertions(+), 14 deletions(-) create mode 100644 edcm/recursive_gonol.py create mode 100644 tests/test_recursive_gonol.py diff --git a/AGENTS.md b/AGENTS.md index d4143da..cdd79f1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,7 +56,7 @@ EDCM owns the character-admission profile and linguistic/semantic construction. - `edcm/measurement/` is the frozen maintained baseline candidate. - `edcm.ucns_edcm_experiments` is the first joint experiment runner. - EDCM owns current text-domain gonol admission and construction; measurement remains separately frozen and evaluated. -- `edcm.character_word` is the implemented candidate for `characters -> words`. `edcm.definition_gonol` is the implemented candidate for definition gonols from exact source definition evidence. Neither is selected canon. +- `edcm.character_word`, `edcm.definition_gonol`, and `edcm.recursive_gonol` are the implemented candidates for `characters -> words -> definitions -> recursive gonol relations`. None is selected canon. - UCNS structural policies, product-character candidates, faithful-breadth candidates, and unresolved Möbius coupling laws remain noncanonical unless current UCNS authority says otherwise. - EDCM axes, thresholds, marker lists, and circuit parameters remain candidates unless an explicit canon decision says otherwise. - A passing hypothesis is experiment-supported evidence, not canon. @@ -104,7 +104,7 @@ python -m twine check dist/* ## Usage guidance -For new text-gonol construction, begin in EDCM. Resolve the exact EDCM source/admission profile, import METAPAT affixiation invariants without redefining them, consume current UCNS geometry, construct `characters -> words` with `edcm.character_word`, then construct definition gonols with `edcm.definition_gonol` from exact source definition evidence. Replay independently before claiming completion. Freeze any EDCM measurement only after construction and keep its evidence status separate. +For new text-gonol construction, begin in EDCM. Resolve the exact EDCM source/admission profile, import METAPAT affixiation invariants without redefining them, consume current UCNS geometry, construct `characters -> words` with `edcm.character_word`, definition gonols with `edcm.definition_gonol`, and recursive relations with `edcm.recursive_gonol`. Replay independently before claiming completion. Freeze any EDCM measurement only after construction and keep its evidence status separate. For historical experiments, reproduce the exact historical producer epoch and names. Historical UCNS-owned lexical artifacts remain evidence; they do not restore current language authority to UCNS. diff --git a/CLAUDE.md b/CLAUDE.md index f43f52a..5afbc0a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -111,7 +111,7 @@ definitions recursive gonol relations ``` -This order is load-bearing. `edcm.character_word` is the implemented candidate for character admission and word closure. `edcm.definition_gonol` is the implemented candidate for definition gonols from exact source definition evidence. Morphology is not a required stage. Do not reopen a completed word gonol or insert another required scale merely because a conventional NLP pipeline contains one. +This order is load-bearing. `edcm.character_word`, `edcm.definition_gonol`, and `edcm.recursive_gonol` are the implemented candidates for that sequence. Morphology is not a required stage. Do not reopen a completed gonol or invent UCNS coupling geometry. Do not insert a conventional NLP tokenizer, token IDs, subword pieces, opaque external vector lookup, or whole-string hash as a substitute for gonol identity. Source prose, dictionaries, corpora, labels, and annotations may remain evidence and provenance; they become gonol semantics only through a source-bound EDCM construction whose admission, relation, closure, and replay boundaries are explicit. diff --git a/README.md b/README.md index a620180..69c18c3 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ definitions recursive gonol relations ``` -`edcm.character_word` is the implemented candidate for the first two stages. `edcm.definition_gonol` is the implemented candidate for definition gonols from already-closed word gonols and exact caller-supplied definition evidence. Neither is selected canon, and neither activates measurement. +`edcm.character_word`, `edcm.definition_gonol`, and `edcm.recursive_gonol` are the implemented candidates for `characters -> words -> definitions -> recursive gonol relations`. None is selected canon, and none activates measurement. UCNS coupling geometry remains `hmmm`. Affixiation is defined by METAPAT. UCNS owns any exact geometric realization. EDCM applies affixiation to text-domain gonols. Completed gonols are atomic for participation at the consuming scale while constituent identity and provenance remain recoverable. diff --git a/docs/GONOL_LANGUAGE_BOUNDARY.md b/docs/GONOL_LANGUAGE_BOUNDARY.md index 64197ca..f18277d 100644 --- a/docs/GONOL_LANGUAGE_BOUNDARY.md +++ b/docs/GONOL_LANGUAGE_BOUNDARY.md @@ -62,7 +62,7 @@ This order is load-bearing. Every admitted character is a gonol. Ordered charact Do not insert another required stage into this sequence unless the governing contract is explicitly changed. Morphology is not a required stage. Historical morphology evidence remains historical; the complete English morphology law remains `hmmm`. -`edcm.character_word` is the implemented candidate for character admission and word closure. `edcm.definition_gonol` is the implemented candidate for definition gonols from already-closed word gonols and exact caller-supplied definition evidence. Neither is selected canon. +`edcm.character_word` is the implemented candidate for character admission and word closure. `edcm.definition_gonol` is the implemented candidate for definition gonols from already-closed word gonols and exact caller-supplied definition evidence. `edcm.recursive_gonol` is the implemented candidate for recursive relations over already-closed gonols. None is selected canon. UCNS coupling geometry remains `hmmm`. Relationships that constitute a gonol enter the construction. They are not merely external semantic edges. Sidecars may index, cache, project, or record provenance; they do not replace intrinsic relational content. @@ -156,6 +156,26 @@ assert first.receipt_digest == second.receipt_digest assert first.definition.relation == "definition-of" ``` +Copy-pasteable recursive candidate: + +```python +from edcm.character_word import construct_character_word_gonols +from edcm.recursive_gonol import construct_recursive_gonol, replay_recursive_gonol + +words = construct_character_word_gonols("cut divide", source_id="example") +first = construct_recursive_gonol( + relation="example:ordered-pair", + participants=words.words, + source_id="example:pair#1", +) +second = replay_recursive_gonol( + relation="example:ordered-pair", + participants=words.words, + source_id="example:pair#1", +) +assert first.receipt_digest == second.receipt_digest +``` + Set `UCNS_SOURCE_ROOT` to a current UCNS checkout if `ucns.public_gonol` is not importable. For new text-gonol work: @@ -164,7 +184,7 @@ For new text-gonol work: 2. resolve the exact EDCM source/admission profile; 3. import current METAPAT affixiation invariants rather than redefining them; 4. consume current UCNS geometry rather than moving text semantics into UCNS; -5. construct characters, words, definitions, and recursive relations under explicit EDCM receipts; use `edcm.character_word` then `edcm.definition_gonol`; +5. construct characters, words, definitions, and recursive relations under explicit EDCM receipts; use `edcm.character_word`, `edcm.definition_gonol`, then `edcm.recursive_gonol`; 6. keep unresolved UCNS geometric operations as `hmmm`; 7. replay the complete declared construction before claiming completion; and 8. freeze any later EDCM measurement separately. @@ -176,5 +196,6 @@ For UCNS geometry work, work in UCNS. For changes to the meaning of affixiation, - the exact EDCM character-admission unit for any source/profile that has not yet selected one; - the source-supported complete English morphology law; - the exact UCNS Möbius-carrier affixiation/coupling law exposed by implementation; +- which recursive relations, if any, are later selected, and the exact UCNS geometry for those relations; - executable direct coupling across distant recursive scales; - the EDCM projection, information-loss accounting, metric, benchmark, and falsifier for recursive text-gonol evaluation. diff --git a/docs/ucns-adapter.md b/docs/ucns-adapter.md index ac08a0c..f8358d3 100644 --- a/docs/ucns-adapter.md +++ b/docs/ucns-adapter.md @@ -188,7 +188,7 @@ New text-gonol work starts in EDCM and follows the active contract: - METAPAT supplies affixiation semantics without being redefined downstream; - UCNS supplies the current gonol/Möbius/Public Gonol geometry; - unresolved UCNS operations remain `hmmm` rather than being inferred from Unicode names, dictionary definitions, adjacency, glyph shape, or conventional grammar; -- EDCM constructs words, then definitions, then recursive gonol relations while preserving identity, occurrence, order, multiplicity, relation identity, scale, source, and provenance; `edcm.character_word` and `edcm.definition_gonol` are the implemented candidates for those stages; +- EDCM constructs words, then definitions, then recursive gonol relations while preserving identity, occurrence, order, multiplicity, relation identity, scale, source, and provenance; `edcm.character_word`, `edcm.definition_gonol`, and `edcm.recursive_gonol` are the implemented candidates for those stages; - completion claims require a deterministic receipt and independent complete replay; and - EDCM measurement remains a separate projection with explicit information loss and falsifiers. diff --git a/edcm/character_word.py b/edcm/character_word.py index 43d6401..ec20424 100644 --- a/edcm/character_word.py +++ b/edcm/character_word.py @@ -45,7 +45,7 @@ # rollback: remove this module; historical lexical-floor and word-gonol observation adapters remain unchanged # requires: ucns.public_gonol # since: 2026-08-21 -# unresolved: UCNS geometric operation of each Public Gonol function position; Möbius-carrier affixiation/coupling law; recursive gonol constructor; complete English morphology law +# unresolved: UCNS geometric operation of each Public Gonol function position; Möbius-carrier affixiation/coupling law; which recursive relations are later selected; complete English morphology law # === END MODULE_BUILD === # === CONTRACTS === @@ -122,8 +122,8 @@ HMMM: tuple[str, ...] = ( "exact UCNS geometric operation of each Public Gonol function position", "UCNS Möbius-carrier affixiation/coupling law", - "definition gonol constructor", - "recursive gonol-relation constructor", + "which definition source, if any, is later selected", + "which recursive relations, if any, are later selected", "source-supported complete English morphology law", ) diff --git a/edcm/definition_gonol.py b/edcm/definition_gonol.py index 30d62ac..cd46929 100644 --- a/edcm/definition_gonol.py +++ b/edcm/definition_gonol.py @@ -60,7 +60,7 @@ # rollback: remove this module; character_word and historical lexical-floor evidence remain unchanged # requires: edcm_character_word_gonol # since: 2026-08-21 -# unresolved: UCNS geometric operation of each Public Gonol function position; Möbius-carrier affixiation/coupling law; recursive gonol constructor; complete English morphology law; selection among competing definition sources +# unresolved: UCNS geometric operation of each Public Gonol function position; Möbius-carrier affixiation/coupling law; which recursive relations are later selected; complete English morphology law; selection among competing definition sources # === END MODULE_BUILD === # === CONTRACTS === @@ -128,7 +128,7 @@ HMMM: tuple[str, ...] = ( "exact UCNS geometric operation of each Public Gonol function position", "UCNS Möbius-carrier affixiation/coupling law", - "recursive gonol-relation constructor", + "which recursive relations, if any, are later selected", "source-supported complete English morphology law", "which definition source, if any, is later selected", ) diff --git a/edcm/recursive_gonol.py b/edcm/recursive_gonol.py new file mode 100644 index 0000000..6b9e097 --- /dev/null +++ b/edcm/recursive_gonol.py @@ -0,0 +1,278 @@ +"""EDCM recursive-gonol candidate constructor. + +Usage guidance +-------------- +This is an implemented candidate, not selected canon. It closes one recursive +gonol from already-closed word, definition, or recursive gonols plus an exact +caller-supplied relation identity. It does not infer the relation from +adjacency, reopen participants, or invent UCNS coupling geometry. + + from edcm.character_word import construct_character_word_gonols + from edcm.recursive_gonol import construct_recursive_gonol, replay_recursive_gonol + + words = construct_character_word_gonols("cut divide", source_id="example") + first = construct_recursive_gonol( + relation="example:ordered-pair", + participants=words.words, + source_id="example:pair#1", + ) + second = replay_recursive_gonol( + relation="example:ordered-pair", + participants=words.words, + source_id="example:pair#1", + ) + assert first.receipt_digest == second.receipt_digest + +Frozen choices for ``edcm.recursive_gonol/v1``: + +- participants are already-closed ``WordGonol``, ``DefinitionGonol``, or + ``RecursiveGonol`` values; +- at least two participants are required; +- the relation identity is the exact caller-supplied string; +- participants remain atomic and recoverable; +- the closed recursive gonol may itself participate in a later relation; +- UCNS geometric coupling remains unresolved and is not filled; +- pronunciation, morphology, and adjacency grammar are not used. + +Do not pass character gonols. Close words first. Do not treat a WordNet edge +table or other sidecar graph as this constructor. +""" + +# === MODULE_BUILD === +# id: edcm_recursive_gonol +# module_name: recursive_gonol +# module_kind: engine +# summary: named EDCM candidate that closes recursive gonols from already-closed word, definition, or recursive gonols and an exact caller-supplied relation without inventing UCNS geometry or selecting canon +# owner: Erin Spencer +# public_surface: CONSTRUCTOR_ID, CONSTRUCTOR_VERSION, RecursiveGonol, RecursiveGonolError, RecursiveGonolReceipt, construct_recursive_gonol, replay_recursive_gonol +# internal_surface: _scale_of, _kind_payload, _participant_payload, _receipt_payload, _digest +# auth_boundary: consumes closed EDCM gonols; does not invent UCNS coupling geometry +# storage_boundary: none; receipts remain caller-owned in-memory objects +# network_boundary: none +# user_data_boundary: caller-supplied relation identity and participant gonols remain in memory and are not transmitted +# admin_only: false +# tests: tests.test_recursive_gonol +# rollout: explicit candidate constructor; no measurement activation and no UCNS coupling law +# rollback: remove this module; character_word and definition_gonol remain unchanged +# requires: edcm_character_word_gonol, edcm_definition_gonol +# since: 2026-08-21 +# unresolved: UCNS geometric operation of each Public Gonol function position; Möbius-carrier affixiation/coupling law; which recursive relations are later selected; complete English morphology law +# === END MODULE_BUILD === + +# === CONTRACTS === +# id: recursive_uses_closed_gonols +# given: ordered already-closed word, definition, or recursive gonols and an exact relation identity +# then: the recursive gonol participants are those gonols without reopening their internal character streams +# class: construction +# since: 2026-08-21 +# +# id: recursive_relation_is_caller_supplied +# given: the relation identity is missing, empty, or inferred from adjacency +# then: construction fails closed rather than inventing a linguistic or geometric relation +# class: safety +# since: 2026-08-21 +# +# id: recursive_result_may_participate +# given: a recursive gonol is closed +# then: that gonol may be supplied as a participant in a later recursive construction while remaining atomic +# class: construction +# since: 2026-08-21 +# +# id: recursive_candidate_does_not_select_canon +# given: a receipt is minted +# then: standing is implemented-candidate, selection_effect is none, and measurement, UCNS coupling, and relation-inventory claims remain nonclaims +# class: doctrine +# since: 2026-08-21 +# === END CONTRACTS === + +from __future__ import annotations + +from dataclasses import dataclass +from hashlib import sha256 +import json +from typing import Any, Mapping, Sequence, Union + +from .character_word import CharacterGonol, WordGonol +from .definition_gonol import DefinitionGonol + + +CONSTRUCTOR_ID = "edcm.recursive_gonol" +CONSTRUCTOR_VERSION = "v1" +STANDING = "implemented-candidate" +SELECTION_EFFECT = "none" + +FROZEN_CHOICES: tuple[tuple[str, str], ...] = ( + ("participants", "closed-word-definition-or-recursive-gonols"), + ("minimum_participants", "2"), + ("relation", "caller-supplied-exact-identity"), + ("ucns_coupling", "hmmm"), + ("pronunciation", "ignored"), + ("morphology", "not-a-stage"), +) + +NONCLAIMS: tuple[str, ...] = ( + "not selected canon", + "not EDCM measurement validity", + "not a UCNS geometric coupling law", + "not a dictionary, WordNet, or sense-inventory graph", + "not complete English morphology law", + "not adjacency or punctuation grammar", + "not METAPAT canon promotion", +) + +HMMM: tuple[str, ...] = ( + "exact UCNS geometric operation of each Public Gonol function position", + "UCNS Möbius-carrier affixiation/coupling law", + "which recursive relations, if any, are later selected", + "source-supported complete English morphology law", +) + +ClosedGonol = Union[WordGonol, DefinitionGonol, "RecursiveGonol"] + + +class RecursiveGonolError(RuntimeError): + """Fail-closed recursive constructor error.""" + + +@dataclass(frozen=True, slots=True) +class RecursiveGonol: + """Closed recursive gonol whose participants are already-closed gonols.""" + + occurrence: int + relation: str + participants: tuple[ClosedGonol, ...] + + @property + def kind_id(self) -> tuple[str, tuple[tuple[str, Any], ...]]: + return ( + self.relation, + tuple((_scale_of(item), item.kind_id) for item in self.participants), + ) + + +@dataclass(frozen=True, slots=True) +class RecursiveGonolReceipt: + """Deterministic recursive receipt. Digest is replay identity, not gonol identity.""" + + constructor_id: str + constructor_version: str + standing: str + selection_effect: str + source_id: str + frozen_choices: tuple[tuple[str, str], ...] + recursive: RecursiveGonol + nonclaims: tuple[str, ...] + hmmm: tuple[str, ...] + receipt_digest: str + + +def _scale_of(item: ClosedGonol) -> str: + if isinstance(item, WordGonol): + return "word" + if isinstance(item, DefinitionGonol): + return "definition" + if isinstance(item, RecursiveGonol): + return "recursive" + raise RecursiveGonolError("participants must be closed word, definition, or recursive gonols") + + +def _kind_payload(kind_id: Any) -> Any: + if isinstance(kind_id, tuple): + return [_kind_payload(item) for item in kind_id] + if isinstance(kind_id, list): + return [_kind_payload(item) for item in kind_id] + return kind_id + + +def _participant_payload(item: ClosedGonol) -> dict[str, Any]: + return { + "scale": _scale_of(item), + "occurrence": item.occurrence, + "kind_id": _kind_payload(item.kind_id), + } + + +def _receipt_payload(*, source_id: str, recursive: RecursiveGonol) -> dict[str, Any]: + return { + "constructor_id": CONSTRUCTOR_ID, + "constructor_version": CONSTRUCTOR_VERSION, + "standing": STANDING, + "selection_effect": SELECTION_EFFECT, + "source_id": source_id, + "frozen_choices": [list(item) for item in FROZEN_CHOICES], + "relation": recursive.relation, + "participants": [_participant_payload(item) for item in recursive.participants], + "kind_id": _kind_payload(recursive.kind_id), + "nonclaims": list(NONCLAIMS), + "hmmm": list(HMMM), + } + + +def _digest(payload: Mapping[str, Any]) -> str: + return sha256( + json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") + ).hexdigest() + + +def construct_recursive_gonol( + *, + relation: str, + participants: Sequence[ClosedGonol], + source_id: str, +) -> RecursiveGonolReceipt: + """Close one recursive gonol from already-closed gonols and an exact relation.""" + + if not isinstance(relation, str) or not relation or relation.isspace(): + raise RecursiveGonolError("relation must be an exact non-empty caller-supplied identity") + if not isinstance(source_id, str) or not source_id: + raise RecursiveGonolError("source_id must be a non-empty string") + if not isinstance(participants, Sequence) or isinstance(participants, (str, bytes)): + raise RecursiveGonolError("participants must be an ordered sequence of closed gonols") + closed = tuple(participants) + if len(closed) < 2: + raise RecursiveGonolError("recursive construction requires at least two closed gonols") + for item in closed: + if isinstance(item, CharacterGonol): + raise RecursiveGonolError("character gonols are not recursive participants; close words first") + _scale_of(item) + + recursive = RecursiveGonol(occurrence=0, relation=relation, participants=closed) + payload = _receipt_payload(source_id=source_id, recursive=recursive) + return RecursiveGonolReceipt( + constructor_id=CONSTRUCTOR_ID, + constructor_version=CONSTRUCTOR_VERSION, + standing=STANDING, + selection_effect=SELECTION_EFFECT, + source_id=source_id, + frozen_choices=FROZEN_CHOICES, + recursive=recursive, + nonclaims=NONCLAIMS, + hmmm=HMMM, + receipt_digest=_digest(payload), + ) + + +def replay_recursive_gonol( + *, + relation: str, + participants: Sequence[ClosedGonol], + source_id: str, +) -> RecursiveGonolReceipt: + """Independently reconstruct the same declared recursive relation.""" + + return construct_recursive_gonol( + relation=relation, + participants=participants, + source_id=source_id, + ) + + +__all__ = [ + "CONSTRUCTOR_ID", + "CONSTRUCTOR_VERSION", + "RecursiveGonol", + "RecursiveGonolError", + "RecursiveGonolReceipt", + "construct_recursive_gonol", + "replay_recursive_gonol", +] diff --git a/tests/test_character_word_gonol.py b/tests/test_character_word_gonol.py index 76e56fb..af07d4f 100644 --- a/tests/test_character_word_gonol.py +++ b/tests/test_character_word_gonol.py @@ -111,7 +111,7 @@ def test_receipt_remains_unimplemented_canon(self) -> None: self.assertEqual(receipt.selection_effect, "none") self.assertIn("not selected canon", receipt.nonclaims) self.assertIn("not EDCM measurement validity", receipt.nonclaims) - self.assertIn("definition gonol constructor", receipt.hmmm) + self.assertIn("which definition source, if any, is later selected", receipt.hmmm) def test_identity_is_structure_not_hash(self) -> None: receipt = construct_character_word_gonols("aa", source_id="fixture-identity") diff --git a/tests/test_definition_gonol.py b/tests/test_definition_gonol.py index 937fbdc..796bb25 100644 --- a/tests/test_definition_gonol.py +++ b/tests/test_definition_gonol.py @@ -148,7 +148,7 @@ def test_receipt_remains_unimplemented_canon(self) -> None: self.assertEqual(receipt.selection_effect, "none") self.assertIn("not a dictionary or sense inventory", receipt.nonclaims) self.assertIn("not OEWN or lexical-floor revival", receipt.nonclaims) - self.assertIn("recursive gonol-relation constructor", receipt.hmmm) + self.assertIn("which recursive relations, if any, are later selected", receipt.hmmm) def test_identity_is_structure_not_hash(self) -> None: receipt = construct_definition_gonol( diff --git a/tests/test_gonol_language_authority.py b/tests/test_gonol_language_authority.py index c9a5be2..e3aba19 100644 --- a/tests/test_gonol_language_authority.py +++ b/tests/test_gonol_language_authority.py @@ -36,6 +36,7 @@ def test_live_surfaces_route_gonol_authority_consistently() -> None: assert "This order is load-bearing" in boundary assert "edcm.character_word" in boundary assert "edcm.definition_gonol" in boundary + assert "edcm.recursive_gonol" in boundary assert "Morphology is not a required stage" in boundary diff --git a/tests/test_recursive_gonol.py b/tests/test_recursive_gonol.py new file mode 100644 index 0000000..b843eb9 --- /dev/null +++ b/tests/test_recursive_gonol.py @@ -0,0 +1,170 @@ +# === CHECKS === +# id: recursive_uses_closed_gonols_check +# proves: recursive_uses_closed_gonols +# call: self::test_recursive_uses_closed_words_without_reopening +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: recursive_relation_is_caller_supplied_check +# proves: recursive_relation_is_caller_supplied +# call: self::test_missing_relation_fails_closed +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: recursive_result_may_participate_check +# proves: recursive_result_may_participate +# call: self::test_closed_recursive_gonol_may_participate_again +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: recursive_candidate_does_not_select_canon_check +# proves: recursive_candidate_does_not_select_canon +# call: self::test_receipt_remains_unimplemented_canon +# timeout: 30 +# mutates: none +# cleanup: none +# === END CHECKS === + +from __future__ import annotations + +import unittest + +from edcm.character_word import construct_character_word_gonols +from edcm.definition_gonol import construct_definition_gonol +from edcm.recursive_gonol import ( + CONSTRUCTOR_ID, + CONSTRUCTOR_VERSION, + RecursiveGonolError, + construct_recursive_gonol, + replay_recursive_gonol, +) + + +class RecursiveGonolTest(unittest.TestCase): + def test_recursive_uses_closed_words_without_reopening(self) -> None: + words = construct_character_word_gonols("cut divide", source_id="fixture:words") + receipt = construct_recursive_gonol( + relation="fixture:ordered-pair", + participants=words.words, + source_id="fixture:pair#1", + ) + self.assertEqual(receipt.recursive.participants, words.words) + self.assertEqual(receipt.recursive.participants[0].characters, words.words[0].characters) + self.assertEqual( + ["".join(word.kind_id) for word in receipt.recursive.participants], + ["cut", "divide"], + ) + + def test_relation_is_not_inferred_from_adjacency(self) -> None: + words = construct_character_word_gonols("cut divide", source_id="fixture:words") + first = construct_recursive_gonol( + relation="fixture:ordered-pair", + participants=words.words, + source_id="fixture:pair#1", + ) + second = construct_recursive_gonol( + relation="fixture:contrast", + participants=words.words, + source_id="fixture:pair#2", + ) + self.assertEqual(first.recursive.participants, second.recursive.participants) + self.assertNotEqual(first.recursive.kind_id, second.recursive.kind_id) + self.assertNotEqual(first.receipt_digest, second.receipt_digest) + + def test_definition_gonol_may_participate(self) -> None: + definition = construct_definition_gonol( + headword="cut", + definition="to divide", + source_id="fixture:cut#1", + ) + other = construct_character_word_gonols("edge", source_id="fixture:edge").words[0] + receipt = construct_recursive_gonol( + relation="fixture:uses", + participants=(definition.definition, other), + source_id="fixture:uses#1", + ) + self.assertEqual(receipt.recursive.participants[0], definition.definition) + self.assertEqual(receipt.recursive.participants[1], other) + + def test_closed_recursive_gonol_may_participate_again(self) -> None: + words = construct_character_word_gonols("cut divide edge", source_id="fixture:three") + inner = construct_recursive_gonol( + relation="fixture:ordered-pair", + participants=words.words[:2], + source_id="fixture:inner", + ) + outer = construct_recursive_gonol( + relation="fixture:attach", + participants=(inner.recursive, words.words[2]), + source_id="fixture:outer", + ) + self.assertEqual(outer.recursive.participants[0], inner.recursive) + self.assertEqual(outer.recursive.participants[1], words.words[2]) + self.assertEqual(outer.recursive.kind_id[0], "fixture:attach") + + def test_missing_relation_fails_closed(self) -> None: + words = construct_character_word_gonols("cut divide", source_id="fixture:words") + with self.assertRaisesRegex(RecursiveGonolError, "relation must be an exact"): + construct_recursive_gonol(relation=" ", participants=words.words, source_id="fixture:bad") + + def test_single_participant_fails_closed(self) -> None: + words = construct_character_word_gonols("cut", source_id="fixture:one") + with self.assertRaisesRegex(RecursiveGonolError, "at least two closed gonols"): + construct_recursive_gonol( + relation="fixture:loop", + participants=words.words, + source_id="fixture:loop", + ) + + def test_character_gonol_fails_closed(self) -> None: + words = construct_character_word_gonols("cut", source_id="fixture:char") + with self.assertRaisesRegex(RecursiveGonolError, "character gonols are not recursive"): + construct_recursive_gonol( + relation="fixture:bad", + participants=(words.characters[0], words.words[0]), + source_id="fixture:bad-char", + ) + + def test_replay_matches_byte_identity(self) -> None: + words = construct_character_word_gonols("cut divide", source_id="fixture:replay") + kwargs = { + "relation": "fixture:ordered-pair", + "participants": words.words, + "source_id": "fixture:replay#1", + } + first = construct_recursive_gonol(**kwargs) + second = replay_recursive_gonol(**kwargs) + self.assertEqual(first.receipt_digest, second.receipt_digest) + self.assertEqual(first.recursive.kind_id, second.recursive.kind_id) + + def test_receipt_remains_unimplemented_canon(self) -> None: + words = construct_character_word_gonols("cut divide", source_id="fixture:stand") + receipt = construct_recursive_gonol( + relation="fixture:ordered-pair", + participants=words.words, + source_id="fixture:stand#1", + ) + self.assertEqual(receipt.constructor_id, CONSTRUCTOR_ID) + self.assertEqual(receipt.constructor_version, CONSTRUCTOR_VERSION) + self.assertEqual(receipt.standing, "implemented-candidate") + self.assertEqual(receipt.selection_effect, "none") + self.assertIn("not a UCNS geometric coupling law", receipt.nonclaims) + self.assertIn("UCNS Möbius-carrier affixiation/coupling law", receipt.hmmm) + + def test_identity_is_structure_not_hash(self) -> None: + words = construct_character_word_gonols("a b", source_id="fixture:id") + receipt = construct_recursive_gonol( + relation="r", + participants=words.words, + source_id="fixture:id#1", + ) + self.assertEqual(receipt.recursive.kind_id[0], "r") + self.assertEqual(receipt.recursive.kind_id[1][0][0], "word") + self.assertNotEqual(receipt.receipt_digest, receipt.recursive.kind_id) + + +if __name__ == "__main__": + unittest.main() From 7bc138b01d0ae58e4a3977f64aca8cb054692080 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 22 Aug 2026 02:43:12 +0000 Subject: [PATCH 4/6] Unify gonol candidate constructor --- .agents/skills/README.md | 2 +- .agents/skills/gonol-build/SKILL.md | 305 ++++++++++++--- AGENTS.md | 6 +- CANON.md | 16 +- CLAUDE.md | 14 +- README.md | 16 +- docs/ENGLISH_LEXICAL_FLOOR.md | 14 +- docs/GONOL_LANGUAGE_BOUNDARY.md | 87 ++--- docs/ucns-adapter.md | 2 +- edcm/character_word.py | 409 ------------------- edcm/definition_gonol.py | 303 -------------- edcm/gonol.py | 520 +++++++++++++++++++++++++ edcm/recursive_gonol.py | 278 ------------- edcm_msdmd.ts | 223 +++++++++++ tests/test_character_word_gonol.py | 139 ------- tests/test_definition_gonol.py | 164 -------- tests/test_gonol_constructor.py | 173 ++++++++ tests/test_gonol_language_authority.py | 12 +- tests/test_recursive_gonol.py | 170 -------- 19 files changed, 1220 insertions(+), 1633 deletions(-) delete mode 100644 edcm/character_word.py delete mode 100644 edcm/definition_gonol.py create mode 100644 edcm/gonol.py delete mode 100644 edcm/recursive_gonol.py delete mode 100644 tests/test_character_word_gonol.py delete mode 100644 tests/test_definition_gonol.py create mode 100644 tests/test_gonol_constructor.py delete mode 100644 tests/test_recursive_gonol.py diff --git a/.agents/skills/README.md b/.agents/skills/README.md index ee1e800..ad18891 100644 --- a/.agents/skills/README.md +++ b/.agents/skills/README.md @@ -3,7 +3,7 @@ This directory contains repo-local copies of canonical skills from `The-Interdependency/skill-lib`. -Source commit: `fb3b53a7629f7f03ecf255167d52c13abef1a979` +Source commit: `a1c6a7124af537ee9937b6fc6084940091982fe5` Repo-local copies are not the source of truth. Edit `skill-lib` first, then propagate from the canonical source. diff --git a/.agents/skills/gonol-build/SKILL.md b/.agents/skills/gonol-build/SKILL.md index edc8b7c..3679dc3 100644 --- a/.agents/skills/gonol-build/SKILL.md +++ b/.agents/skills/gonol-build/SKILL.md @@ -1,90 +1,303 @@ --- name: gonol-build -description: Construction, closure, and replay contract for gonols across UCNS and EDCM. Load this when building or reviewing UCNS geometry used by gonols, or building EDCM character, word, definition, or recursive-relation gonols. UCNS owns geometry; EDCM owns text construction. The required EDCM order is characters -> words -> definitions -> recursive gonol relations. Pronunciation is not required unless an explicitly declared later experiment makes it part of the construction. Do not load for unrelated geometry, ordinary prose editing, or measurement over already-closed gonols. +description: Cross-repository construction, review, replay, and continuation discipline for gonols. Load this when realizing gonol or affixiation geometry in UCNS; when constructing character, word, morphology, definition, punctuation-function, or recursive gonols in EDCM; when deciding whether a relation belongs inside a gonol; or when promoting a completed gonol to atomic participation at another scale. Do not load for unrelated geometry, ordinary prose editing, or EDCM measurement that consumes already-closed gonols without changing their construction. Resolve METAPAT semantic authority, UCNS geometric authority, and EDCM text-domain authority separately; never restore historical gonal-morphology, omega/phi/psi, bone/flesh, or carrier-LCM language rules. --- -# gonol-build +# gonol-build — construct, close, promote, recurse -Use this skill to keep gonol construction on the declared architecture and nothing else. +Use this procedural skill to preserve gonol construction semantics without +collapsing repository authority boundaries. -## Authority +The governing split is: ```text -UCNS = geometry -EDCM = text-domain gonol construction -skill-lib = construction/replay discipline +METAPAT -> conceptual meaning of affixiation and relational integration +UCNS -> gonol geometry, native Möbius/Public Gonol carrier, geometric operations +EDCM -> text-domain admission and linguistic/semantic gonol construction +skill-lib -> construction, evidence, replay, and continuation discipline ``` -Resolve the current UCNS and EDCM authorities before building. Do not move text semantics into UCNS or invent geometry in EDCM. +No repository gains another repository's authority merely because a construction +crosses the boundary. -## EDCM construction contract +## Workflow: authority gate + +Before acting: + +1. Resolve the current commits or PR heads of every repository whose authority + the construction uses. +2. Read the governing repository instructions and the directly applicable + canon/contract source before choosing a construction rule. +3. For affixiation, resolve current METAPAT semantics before implementing its + geometry or text-domain application. +4. For carrier, topology, orientation, closure geometry, or Public Gonol + operations, resolve current UCNS authority. +5. For character admission, morphology, words, definitions, source corpora, and + recursive textual relations, resolve current EDCM authority. +6. Distinguish declared architecture, implemented construction, evidence status, + proposed continuation, and `hmmm`. +7. If governing sources disagree, stop at the conflict. Do not choose the most + convenient repository or reconstruct authority from historical artifacts. + +Treat `hmmm` and an unresolved constructor as a ban on unqualified promotion, +not as a ban on declared candidate construction. An unresolved constructor is +permission to construct a named, bounded, falsifiable candidate under the +contracts that are already fixed; it is not permission to invent the missing +contract. `hmmm` blocks unqualified certainty and silent canonization. It does +not block declared experimentation, implementation, comparison, or candidate +construction. + +## Domain rule: every character is a gonol + +For active EDCM text construction: ```text -characters -> words -> definitions -> recursive gonol relations +every admitted character is a gonol ``` -This order is load-bearing. +This is an **EDCM text-domain rule**, not a UCNS linguistic rule and not a +METAPAT axiom. -- Every admitted character is a gonol. -- Ordered character gonols close into a word gonol. -- A closed word gonol is atomic at the consuming scale while its constituent identities, order, multiplicity, source positions, and provenance remain recoverable. -- Definition gonols are constructed from the applicable closed word gonols and exact source definition evidence. -- Recursive relations are constructed from already-closed gonols without reopening or erasing their internal structure. +EDCM owns what counts as an admitted character for a declared source/profile. +If the source boundary has not selected code point, grapheme, exact Public Gonol +glyph inventory, or another character unit, preserve that question as `hmmm`. +UCNS may geometrically realize an admitted character on its carrier; UCNS does +not silently decide EDCM's character-admission semantics. -Do not insert another required stage into this sequence unless the governing contract is explicitly changed. +Character occurrences remain separately addressable even when repeated. Their +source identity, order, multiplicity, and provenance are preserved according to +the active EDCM construction profile. -## Pronunciation boundary +## Construction invariant -Pronunciation is not required for this construction. Pronunciation, phonetic spelling, IPA, audio, or other sound representations must not alter gonol identity, closure, ordering, or relations unless a later explicitly declared experiment makes phonology part of its construction. +```text +eligible gonol participants + -> explicit relation or geometrically authorized function application + -> relation enters the construction + -> closure with source identity and receipt + -> completed gonol + -> atomic participant at another declared scale +``` -Source pronunciation data may remain source metadata. It is not a dependency of the current build. +`atomic` means indivisible for participation at the consuming scale. It does +not mean internally structureless, provenance-free, irreversible, or forbidden +from inspection. -## Construction invariant +When a relationship helps constitute the result, keep it intrinsic to the +gonol construction. Sidecars may carry indexes, caches, projections, +provenance, and receipts; an external edge table does not become the gonol's +authoritative relational content merely because it is easier to query. -At every scale: +## Active EDCM text order + +Unless current EDCM authority explicitly changes it, preserve: ```text -ordered eligible gonols --> authorized UCNS geometric relation/application --> closure --> deterministic identity + provenance receipt --> atomic participation at the next declared scale +characters -> morphology -> definitions -> recursive gonol relations ``` -Preserve exact source identity, occurrence order, multiplicity, and provenance. Do not normalize, deduplicate, infer relations, or substitute tokens, embeddings, hashes, or another representation for gonol identity unless the active contract explicitly authorizes it. +This is an order of construction dependencies, not permission to flatten every +higher-scale gonol back into characters. Close each completed construction and +reuse its atomic identity at the consuming scale. + +Words and other intermediate closures may exist where the declared construction +requires them; do not manufacture a mandatory scale merely because a conventional +NLP pipeline usually has one. + +## Affixiation boundary + +METAPAT defines affixiation conceptually: already-bounded participants remain +individually addressable, their identity and provenance are preserved, the +declared relation may integrate as a higher-scale object-whole, and the +integrated whole may later participate recursively without erasing its +constituents. + +UCNS owns any exact geometric realization of that operation. Do not invent an +alternative carrier, topology, scale increment, arity rule, containment rule, +or coupling law merely to complete an implementation. Implement the native +carrier under the fixed METAPAT invariants and let unresolved geometric law +remain `hmmm` until construction exposes it. + +EDCM applies affixiation to text-domain gonols. Linguistic prefixes and suffixes +are one instance of affixiation; they do not define affixiation. + +The complete English root, stem, affix, irregular-transformation, and family law +remains unresolved unless current EDCM source/evidence establishes otherwise. +Never invent a decomposition to complete a pipeline. Do not assume every word is +`root + affix`, that a lemma is a final root, or that one stemmer supplies +semantic authority. + +## Closure rules + +- Preserve ordered occurrence identity, multiplicity, exact source evidence, + source offsets, and provenance required by the governing EDCM profile. +- Apply no normalization, folding, trimming, deduplication, inferred sorting, + or silent collapse unless the current profile explicitly authorizes it. +- Keep completed gonol closure intact inside larger constructions. Do not reopen + a closed word or definition merely because a larger relation contains it. +- A repeated completed gonol may reuse atomic identity when the governing + construction says it is the same gonol; repeated occurrences remain + separately addressable. +- Larger and smaller scales may couple directly when the governing construction + permits it. Do not invent mandatory adjacent-scale traversal. +- Do not materialize an all-pairs relationship graph as semantic authority. +- Do not substitute conventional NLP token IDs, subword IDs, opaque embedding + vectors, or whole-string hashes for gonol identity. + +## Public Gonol function application + +UCNS owns the geometric operation of Public Gonol function positions. EDCM may +apply only operations geometrically authorized by the current UCNS source and +evidence. + +- An unresolved geometric operation remains `hmmm`; Unicode names, dictionary + definitions, neighboring glyphs, or conventional punctuation grammar do not + fill it. +- Require occurrence-addressed application context when the governing EDCM + construction requires context. +- Never infer adjacency, precedence, grammatical role, or application scope from + glyph shape alone. +- Bind function identity, result identity, ordered participants, occurrence + addresses, carrier/construction identity, and source profile in the receipt. +- Replay against the same geometric authority and explicit construction plan. +- Do not promote structural survival into parsing, grammar, semantic utility, or + universal function law. -If required UCNS geometry is unresolved, preserve that boundary as `hmmm`; do not fill it with an invented rule. +## Candidate construction -## Candidate boundary +`Constructor unresolved` means no constructor is yet canonical. It does not mean +construction cannot begin. -An unresolved constructor is permission to construct a named, bounded candidate; it does not block declared experimentation. It blocks promotion beyond the evidence, not construction or testing. +- Inventing hidden canon is prohibited. +- Constructing a declared, falsifiable candidate is required progress once the + governing contracts name the participants and boundary sufficiently to build. +- Freeze selectable implementation choices before inspecting the candidate's + evidentiary outcome when those choices could change the claimed result. +- A candidate must name its identity, standing, frozen choices, nonclaims, and + selection effect before a completion receipt is minted. +- Replay of a candidate establishes only that candidate's reproducibility. It + does not select it or transfer authority across repositories. -## Completion and replay +## Evidence and replay -Before a full run, preflight the resources required to finish it. Once a healthy admitted run begins, let it reach its natural terminal condition unless a genuine safety/resource boundary or preregistered load-bearing stop condition fires. Do not add arbitrary wall-clock limits. +For a construction that claims completion: -A completion claim requires: +1. Bind exact METAPAT, UCNS, EDCM, source-artifact, profile, code, and option + identities that materially determine the construction. +2. Admit the complete declared source, not an inspected prefix presented as a + finished run. +3. Preflight required compute and storage before starting. Once an admitted + healthy run begins, let it reach its natural terminal condition unless a + genuine safety boundary or preregistered load-bearing stop condition fires. +4. Emit deterministic construction/semantic receipts separately from resource + observations. +5. Independently reconstruct or replay the complete declared scope. +6. Compare byte-for-byte where the protocol requires exact identity. +7. Propagate failures and unresolved prerequisites without changing criteria + after outcome inspection. -1. exact UCNS, EDCM, source/profile, and constructor identities; -2. the complete declared source scope; -3. deterministic construction receipts; and -4. independent complete replay where replay is required by the governing protocol. +Passing fixtures proves only the fixtures. A historical receipt remains +historical evidence after its constructor or authority split is superseded; it +is not a receipt for the replacement construction. -Replay establishes reproducibility of that construction only. It does not by itself establish semantic quality, measurement validity, cognition claims, or canon outside the declared scope. +## Required output + +Report: + +```text +authority identities: +source identity: +construction identity: +participants and scales: +intrinsic relations/functions: +closure boundary: +atomic participation boundary: +receipt and replay status: +claims supported: +claims not supported: +hmmm: +next dependency-complete action: +``` + +Use `SURVIVED`, `FALSIFIED`, `BLOCKED`, `UNRESOLVED`, and `DEPRECATED` only under +the governing protocol. Do not translate `SURVIVED` into proved or canonical. ## Usage guidance -For text construction, start in EDCM and consume current UCNS geometry. +When building text gonols, start in EDCM and consume UCNS geometry rather than +moving text semantics into UCNS. When building or repairing carrier geometry, +start in UCNS and use the METAPAT contract only for the relational invariants it +authorizes. When affixiation semantics themselves are in question, repair or +resolve them in METAPAT before downstream construction. + +A minimal routing example is: ```text -UCNS: geometry -EDCM: characters -> words -> definitions -> recursive gonol relations +METAPAT: affixiation preserves bounded participant identity/provenance +UCNS: realize that relation on the native Möbius/Public Gonol geometry +EDCM: admit characters as gonols and construct text through affixiation ``` -When a word closes, use that word gonol atomically at the next scale. Ignore pronunciation unless a future explicit construction says otherwise. +## Anti-patterns: refuse these + +- Assigning character, morphology, definition, or lexical-source authority to + UCNS merely because UCNS provides their geometry. +- Assigning affixiation semantics to EDCM or UCNS merely because they implement + or apply it. +- Reopening closed gonols merely because a larger construction contains them. +- Inferring function context, syntax, precedence, or operation from adjacency, + Unicode names, dictionary glosses, or glyph shape. +- Treating a source word list as a semantic dictionary. +- Treating `hmmm` or an unresolved constructor as permission to refuse a + declared candidate construction, complete run, or replay. +- Inventing morphology, a lexical family map, geometry, measurement authority, + alternate carrier, scale rule, or coupling law to fill an absent constructor. +- Promoting a sidecar relationship graph into intrinsic gonol semantics. +- Treating a partial corpus run as complete evidence. +- Adding arbitrary wall-clock limits to a healthy admitted computation. +- Restoring the deprecated omega/phi/psi cores, bone/flesh categories, fixed + morphology weights, or `multiplyFuel`/carrier-LCM as the universal language + law. +- Transferring UCNS construction survival into EDCM measurement validity, + PTCNA efficacy, cognition claims, or selected universal canon. + +## Minimal examples + +Character admission: + +```text +EDCM source/profile admits "a" -> that character occurrence is a gonol +UCNS supplies its authorized carrier geometry +EDCM owns the text-domain construction that consumes it +``` + +Recursive closure: + +```text +bounded gonols + declared relation + -> affixiate under METAPAT invariants + -> UCNS geometric realization + -> closed EDCM text-domain gonol + -> atomic participation in a later declared relation +``` + +Honest incomplete continuation: + +```text +geometric coupling law unresolved + -> construct the smallest declared UCNS candidate allowed by fixed contracts + -> preserve candidate standing + -> hmmm blocks promotion, not construction +``` ## hmmm -- exact UCNS geometric operations that remain unresolved in current implementation; -- any future construction that explicitly adds phonology or another stage; -- any recursive relation whose governing source or geometry is not yet established. +`hmmm` blocks unqualified promotion. It does not block experimentation or +construction of a declared candidate. + +- which exact EDCM character-admission unit applies where not already source-pinned; +- the source-supported complete English morphology law; +- the exact UCNS Möbius-carrier affixiation/coupling law exposed by implementation; +- executable geometry for direct coupling across distant recursive scales; +- quantitative compression and reconstruction behavior after recursive gonols exist; +- which recursive-gonol candidate, if any, later becomes selected canon. diff --git a/AGENTS.md b/AGENTS.md index cdd79f1..733f317 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,7 +37,7 @@ METAPAT affixiation semantics: The-Interdependency/metapat at the current govern UCNS gonol geometry: The-Interdependency/ucns at the current governing or experiment-pinned commit EDCM text construction: The-Interdependency/edcm current canon/profile EDCM baseline measurement: The-Interdependency/edcm:edcm/measurement -organization skills: The-Interdependency/skill-lib@fb3b53a7629f7f03ecf255167d52c13abef1a979 +organization skills: The-Interdependency/skill-lib@a1c6a7124af537ee9937b6fc6084940091982fe5 experiment evidence: exact report, corpus, candidate, and workflow identities ``` @@ -56,7 +56,7 @@ EDCM owns the character-admission profile and linguistic/semantic construction. - `edcm/measurement/` is the frozen maintained baseline candidate. - `edcm.ucns_edcm_experiments` is the first joint experiment runner. - EDCM owns current text-domain gonol admission and construction; measurement remains separately frozen and evaluated. -- `edcm.character_word`, `edcm.definition_gonol`, and `edcm.recursive_gonol` are the implemented candidates for `characters -> words -> definitions -> recursive gonol relations`. None is selected canon. +- `edcm.gonol` is the implemented candidate constructor for closing gonols through declared scale option sets. None is selected canon. - UCNS structural policies, product-character candidates, faithful-breadth candidates, and unresolved Möbius coupling laws remain noncanonical unless current UCNS authority says otherwise. - EDCM axes, thresholds, marker lists, and circuit parameters remain candidates unless an explicit canon decision says otherwise. - A passing hypothesis is experiment-supported evidence, not canon. @@ -104,7 +104,7 @@ python -m twine check dist/* ## Usage guidance -For new text-gonol construction, begin in EDCM. Resolve the exact EDCM source/admission profile, import METAPAT affixiation invariants without redefining them, consume current UCNS geometry, construct `characters -> words` with `edcm.character_word`, definition gonols with `edcm.definition_gonol`, and recursive relations with `edcm.recursive_gonol`. Replay independently before claiming completion. Freeze any EDCM measurement only after construction and keep its evidence status separate. +For new text-gonol construction, begin in EDCM. Resolve the exact EDCM source/admission profile, import METAPAT affixiation invariants without redefining them, consume current UCNS geometry where normally available, and close gonols with `edcm.gonol` scale option sets. Once closed, a gonol is atomic at any scale; admissible larger-scale construction may consume closed gonols directly without reopening. Replay independently before claiming completion. Freeze any EDCM measurement only after construction and keep its evidence status separate. For historical experiments, reproduce the exact historical producer epoch and names. Historical UCNS-owned lexical artifacts remain evidence; they do not restore current language authority to UCNS. diff --git a/CANON.md b/CANON.md index 5b8b826..faaf001 100644 --- a/CANON.md +++ b/CANON.md @@ -56,19 +56,9 @@ every admitted character is a gonol This is an EDCM text-domain rule. EDCM owns the declared source/profile that determines what is admitted as a character. If a construction has not yet selected its exact unit—Unicode code point, grapheme, Public Gonol glyph inventory, or another explicit unit—that admission boundary remains `hmmm`. UCNS may realize admitted characters geometrically; UCNS does not own the linguistic admission rule merely because it owns the geometry. -The current EDCM construction dependency is: +Current EDCM text construction uses declared scale option sets rather than a mandatory adjacent-scale ladder. `edcm.gonol` is the implemented candidate constructor for closing one gonol through a declared scale option set. Admissible scales may include character, word, definition, recursive relation, morphology, punctuation-function, source-specific, or later named scales when the governing construction declares their options. -```text -characters - ↓ -words - ↓ -definitions - ↓ -recursive gonol relations -``` - -This order is load-bearing. Ordered character gonols close into word gonols. A closed word gonol is atomic at the consuming scale while constituent identities, order, multiplicity, source positions, and provenance remain recoverable. Morphology is not a required stage. Historical morphology evidence remains historical; the complete English morphology law remains `hmmm`. +Once closed, a gonol is atomic at any scale. Closed gonols may participate directly at any admissible scale without reopening, while identity, order, multiplicity, source positions, relation identity, and provenance remain recoverable. Historical morphology evidence remains historical; the complete English morphology law remains `hmmm`. Affixiation is defined by METAPAT, not by EDCM and not by UCNS. EDCM applies affixiation to text-domain gonols. UCNS owns any exact geometric realization on the native Möbius/Public Gonol carrier. An unresolved UCNS coupling law remains `hmmm`; neither EDCM nor UCNS may fill it by importing an invented carrier, topology, scale increment, arity rule, containment rule, or linguistic convention. @@ -245,7 +235,7 @@ This phase may implement: - positive unresolved-reference and alternative-bundle evidence; - reproducible reports and policy-preservation findings; - CI execution against exact UCNS commits where experiments require them; -- source-bound EDCM character, word, definition, and recursive-gonol candidate constructors under the current authority split; +- source-bound EDCM gonol candidate construction through declared scale option sets under the current authority split; - candidate UCNS Möbius-carrier realizations of affixiation under fixed METAPAT invariants; and - preregistered EDCM measurements of completed text-gonol constructions. diff --git a/CLAUDE.md b/CLAUDE.md index 5afbc0a..06b1388 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -99,19 +99,7 @@ every admitted character is a gonol This is an EDCM text-domain rule. EDCM owns the source/profile that decides what is admitted as a character. If a profile has not selected Unicode code point, grapheme, exact Public Gonol glyph inventory, or another explicit unit, that admission boundary remains `hmmm`; UCNS does not silently decide it merely because UCNS supplies the geometry. -The active construction dependency is: - -```text -characters - ↓ -words - ↓ -definitions - ↓ -recursive gonol relations -``` - -This order is load-bearing. `edcm.character_word`, `edcm.definition_gonol`, and `edcm.recursive_gonol` are the implemented candidates for that sequence. Morphology is not a required stage. Do not reopen a completed gonol or invent UCNS coupling geometry. +Active construction uses declared scale option sets rather than a mandatory adjacent-scale ladder. `edcm.gonol` is the implemented candidate constructor for closing one gonol at a declared scale. Once closed, a gonol is atomic at any scale; do not reopen a completed gonol or invent UCNS coupling geometry. Do not insert a conventional NLP tokenizer, token IDs, subword pieces, opaque external vector lookup, or whole-string hash as a substitute for gonol identity. Source prose, dictionaries, corpora, labels, and annotations may remain evidence and provenance; they become gonol semantics only through a source-bound EDCM construction whose admission, relation, closure, and replay boundaries are explicit. diff --git a/README.md b/README.md index 69c18c3..b2d0141 100644 --- a/README.md +++ b/README.md @@ -62,21 +62,9 @@ every admitted character is a gonol EDCM owns the source/profile that determines what is admitted as a character. If a profile has not selected Unicode code point, grapheme, exact Public Gonol glyph inventory, or another explicit unit, that admission boundary remains `hmmm`; UCNS does not silently choose it merely because UCNS owns the geometry. -The current EDCM construction dependency is: +Current EDCM text construction uses declared scale option sets rather than a mandatory adjacent-scale ladder. `edcm.gonol` is the implemented candidate constructor for closing one gonol at an admissible scale. None of its scale options is selected canon, and construction does not activate measurement. UCNS coupling geometry remains `hmmm`. -```text -characters - ↓ -words - ↓ -definitions - ↓ -recursive gonol relations -``` - -`edcm.character_word`, `edcm.definition_gonol`, and `edcm.recursive_gonol` are the implemented candidates for `characters -> words -> definitions -> recursive gonol relations`. None is selected canon, and none activates measurement. UCNS coupling geometry remains `hmmm`. - -Affixiation is defined by METAPAT. UCNS owns any exact geometric realization. EDCM applies affixiation to text-domain gonols. Completed gonols are atomic for participation at the consuming scale while constituent identity and provenance remain recoverable. +Affixiation is defined by METAPAT. UCNS owns any exact geometric realization. EDCM applies affixiation to text-domain gonols. Once closed, a gonol is atomic at any scale. Closed gonols may participate directly at any admissible scale without reopening, while constituent identity and provenance remain recoverable. Do not insert conventional NLP token IDs, subword pieces, opaque external embedding vectors, or whole-string hashes as substitutes for gonol identity. Source prose, dictionaries, corpora, labels, and annotations may remain evidence and provenance; they become gonol semantics only through explicit EDCM construction. diff --git a/docs/ENGLISH_LEXICAL_FLOOR.md b/docs/ENGLISH_LEXICAL_FLOOR.md index 40aa172..d3ceaae 100644 --- a/docs/ENGLISH_LEXICAL_FLOOR.md +++ b/docs/ENGLISH_LEXICAL_FLOOR.md @@ -50,19 +50,9 @@ Intrinsic `*.ucns.json` files contain only dense integer occurrence addresses, o This sealed run does **not** define the current text basis. -For active EDCM text construction: - -```text -every admitted character is a gonol - ↓ -words - ↓ -definitions - ↓ -recursive gonol relations -``` +For active EDCM text construction, every admitted character is a gonol, and later closures use declared scale option sets rather than a mandatory adjacent-scale ladder. -This construction is EDCM-owned. METAPAT supplies the conceptual affixiation contract. UCNS supplies the gonol/Möbius/Public Gonol geometry and only those function operations that geometry establishes. The precise EDCM source/admission profile and unresolved UCNS coupling geometry remain explicit boundaries rather than being inferred from this historical OEWN experiment. +This construction is EDCM-owned. METAPAT supplies the conceptual affixiation contract. UCNS supplies the gonol/Möbius/Public Gonol geometry and only those function operations that geometry establishes. Once closed, a gonol is atomic at any scale. The precise EDCM source/admission profile and unresolved UCNS coupling geometry remain explicit boundaries rather than being inferred from this historical OEWN experiment. The former rule requiring definitions to close over a fixed NGSL word set is deprecated. OEWN definition strings loaded by this historical EDCM run remain source prose and provenance evidence; they are not automatically promoted into current EDCM gonol semantics. diff --git a/docs/GONOL_LANGUAGE_BOUNDARY.md b/docs/GONOL_LANGUAGE_BOUNDARY.md index f18277d..769b74b 100644 --- a/docs/GONOL_LANGUAGE_BOUNDARY.md +++ b/docs/GONOL_LANGUAGE_BOUNDARY.md @@ -46,23 +46,11 @@ Every admitted occurrence remains separately addressable. Repeated characters ma ## Active text construction -The current EDCM construction dependency is: +Current EDCM text construction uses declared scale option sets rather than a mandatory adjacent-scale ladder. Every admitted character is a gonol, but EDCM must not encode `character -> word -> definition -> recursive` as the only lawful construction path. -```text -characters - ↓ -words - ↓ -definitions - ↓ -recursive gonol relations -``` - -This order is load-bearing. Every admitted character is a gonol. Ordered character gonols close into a word gonol. A closed word gonol is atomic at the consuming scale while constituent identities, order, multiplicity, source positions, and provenance remain recoverable. Definition gonols are constructed from already-closed word gonols. Recursive relations are constructed from already-closed gonols. +Once closed, a gonol is atomic at any scale. Closed gonols may participate directly at any admissible scale without reopening, while identity, order, multiplicity, source positions, relation identity, and provenance remain recoverable. -Do not insert another required stage into this sequence unless the governing contract is explicitly changed. Morphology is not a required stage. Historical morphology evidence remains historical; the complete English morphology law remains `hmmm`. - -`edcm.character_word` is the implemented candidate for character admission and word closure. `edcm.definition_gonol` is the implemented candidate for definition gonols from already-closed word gonols and exact caller-supplied definition evidence. `edcm.recursive_gonol` is the implemented candidate for recursive relations over already-closed gonols. None is selected canon. UCNS coupling geometry remains `hmmm`. +`edcm.gonol` is the implemented candidate constructor for closing one gonol through a declared scale option set. Its current option sets include character, word, definition, and recursive relation scales. None is selected canon. UCNS coupling geometry remains `hmmm`, and absent `ucns.public_gonol` does not prevent candidate construction. Relationships that constitute a gonol enter the construction. They are not merely external semantic edges. Sidecars may index, cache, project, or record provenance; they do not replace intrinsic relational content. @@ -126,57 +114,34 @@ Preflight real resource requirements before a complete run. Once a healthy admit ## Usage guidance -Copy-pasteable first-two-stages candidate: - -```python -from edcm.character_word import construct_character_word_gonols, replay_character_word_gonols - -first = construct_character_word_gonols("don't cut.", source_id="example") -second = replay_character_word_gonols("don't cut.", source_id="example") -assert first.receipt_digest == second.receipt_digest -assert ["".join(word.kind_id) for word in first.words] == ["don't", "cut."] -``` - -Copy-pasteable definition candidate: +Copy-pasteable unified candidate: ```python -from edcm.definition_gonol import construct_definition_gonol, replay_definition_gonol - -first = construct_definition_gonol( - headword="cut", - definition="to divide with a sharp edge", - source_id="example:cut#1", -) -second = replay_definition_gonol( - headword="cut", - definition="to divide with a sharp edge", +from edcm.gonol import construct_gonol, replay_gonol + +word = construct_gonol(scale="word", source="cut", source_id="example:cut") +definition = construct_gonol( + scale="definition", + relation="example:definition-evidence", + source="to divide with a sharp edge", + participants=(word.gonol,), source_id="example:cut#1", ) -assert first.receipt_digest == second.receipt_digest -assert first.definition.relation == "definition-of" -``` - -Copy-pasteable recursive candidate: - -```python -from edcm.character_word import construct_character_word_gonols -from edcm.recursive_gonol import construct_recursive_gonol, replay_recursive_gonol - -words = construct_character_word_gonols("cut divide", source_id="example") -first = construct_recursive_gonol( - relation="example:ordered-pair", - participants=words.words, - source_id="example:pair#1", -) -second = replay_recursive_gonol( - relation="example:ordered-pair", - participants=words.words, - source_id="example:pair#1", +recursive = construct_gonol( + scale="recursive", + relation="example:mentions", + participants=(word.gonol, definition.gonol), + source_id="example:relation#1", ) -assert first.receipt_digest == second.receipt_digest +assert recursive.receipt_digest == replay_gonol( + scale="recursive", + relation="example:mentions", + participants=(word.gonol, definition.gonol), + source_id="example:relation#1", +).receipt_digest ``` -Set `UCNS_SOURCE_ROOT` to a current UCNS checkout if `ucns.public_gonol` is not importable. +If `ucns.public_gonol` is normally importable and its digest matches the pinned Public Gonol identity, `edcm.gonol` observes source-unit positions. If it is absent, construction records geometry as `hmmm` and still closes the candidate. It does not mutate `sys.path`. For new text-gonol work: @@ -184,7 +149,7 @@ For new text-gonol work: 2. resolve the exact EDCM source/admission profile; 3. import current METAPAT affixiation invariants rather than redefining them; 4. consume current UCNS geometry rather than moving text semantics into UCNS; -5. construct characters, words, definitions, and recursive relations under explicit EDCM receipts; use `edcm.character_word`, `edcm.definition_gonol`, then `edcm.recursive_gonol`; +5. close gonols through declared scale option sets under explicit EDCM receipts; use `edcm.gonol`; 6. keep unresolved UCNS geometric operations as `hmmm`; 7. replay the complete declared construction before claiming completion; and 8. freeze any later EDCM measurement separately. @@ -196,6 +161,6 @@ For UCNS geometry work, work in UCNS. For changes to the meaning of affixiation, - the exact EDCM character-admission unit for any source/profile that has not yet selected one; - the source-supported complete English morphology law; - the exact UCNS Möbius-carrier affixiation/coupling law exposed by implementation; -- which recursive relations, if any, are later selected, and the exact UCNS geometry for those relations; +- which scale option sets and recursive relations, if any, are later selected, and the exact UCNS geometry for those relations; - executable direct coupling across distant recursive scales; - the EDCM projection, information-loss accounting, metric, benchmark, and falsifier for recursive text-gonol evaluation. diff --git a/docs/ucns-adapter.md b/docs/ucns-adapter.md index f8358d3..6b75530 100644 --- a/docs/ucns-adapter.md +++ b/docs/ucns-adapter.md @@ -188,7 +188,7 @@ New text-gonol work starts in EDCM and follows the active contract: - METAPAT supplies affixiation semantics without being redefined downstream; - UCNS supplies the current gonol/Möbius/Public Gonol geometry; - unresolved UCNS operations remain `hmmm` rather than being inferred from Unicode names, dictionary definitions, adjacency, glyph shape, or conventional grammar; -- EDCM constructs words, then definitions, then recursive gonol relations while preserving identity, occurrence, order, multiplicity, relation identity, scale, source, and provenance; `edcm.character_word`, `edcm.definition_gonol`, and `edcm.recursive_gonol` are the implemented candidates for those stages; +- EDCM closes gonols through declared scale option sets while preserving identity, occurrence, order, multiplicity, relation identity, scale, source, and provenance; `edcm.gonol` is the implemented candidate constructor; - completion claims require a deterministic receipt and independent complete replay; and - EDCM measurement remains a separate projection with explicit information loss and falsifiers. diff --git a/edcm/character_word.py b/edcm/character_word.py deleted file mode 100644 index ec20424..0000000 --- a/edcm/character_word.py +++ /dev/null @@ -1,409 +0,0 @@ -"""EDCM character-to-word gonol candidate constructor. - -Usage guidance --------------- -This is an implemented candidate, not selected canon. It owns EDCM text-domain -admission and word closure only. Import current UCNS Public Gonol geometry; -do not copy the carrier into EDCM. - - from edcm.character_word import construct_character_word_gonols, replay_character_word_gonols - - first = construct_character_word_gonols("don't cut.", source_id="example") - second = replay_character_word_gonols("don't cut.", source_id="example") - assert first.receipt_digest == second.receipt_digest - -Frozen choices for ``edcm.character_word_gonol/v1``: - -- admission unit: one Unicode scalar (code point), not a grapheme cluster; -- word closure: Python ``str.isspace()`` (Unicode White_Space) is the declared - EDCM delimiter relation, not a UCNS function inferred from SPACE; -- Public Gonol position is attached when the scalar is on the current carrier; - otherwise geometry remains unresolved; -- pronunciation is ignored; -- no case folding, NFC, trimming, or deduplication. - -UCNS geometry is consumed through ``ucns.public_gonol``. Set -``UCNS_SOURCE_ROOT`` to a current UCNS checkout if the package is not -importable. The constructor fails closed on a Public Gonol digest mismatch. -""" - -# === MODULE_BUILD === -# id: edcm_character_word_gonol -# module_name: character_word -# module_kind: engine -# summary: named EDCM candidate that admits Unicode-scalar character gonols and closes non-whitespace runs into word gonols while consuming current UCNS Public Gonol geometry without selecting canon or activating measurement -# owner: Erin Spencer -# public_surface: CONSTRUCTOR_ID, CONSTRUCTOR_VERSION, PINNED_PUBLIC_GONOL_SHA256, CharacterGonol, WordGonol, CharacterWordReceipt, CharacterWordError, construct_character_word_gonols, replay_character_word_gonols, canonical_receipt_bytes -# internal_surface: _load_public_gonol, _character_record, _close_words, _receipt_payload, _digest -# auth_boundary: current UCNS Public Gonol digest must match the pinned geometry identity -# storage_boundary: none; receipts remain caller-owned in-memory objects -# network_boundary: none -# user_data_boundary: caller-supplied source text remains in memory and is not transmitted -# admin_only: false -# tests: tests.test_character_word_gonol -# rollout: explicit candidate constructor; no measurement, definition, or recursive-relation activation -# rollback: remove this module; historical lexical-floor and word-gonol observation adapters remain unchanged -# requires: ucns.public_gonol -# since: 2026-08-21 -# unresolved: UCNS geometric operation of each Public Gonol function position; Möbius-carrier affixiation/coupling law; which recursive relations are later selected; complete English morphology law -# === END MODULE_BUILD === - -# === CONTRACTS === -# id: character_admission_is_unicode_scalar -# given: a source string is admitted -# then: every Unicode scalar is one character gonol with occurrence index, exact scalar, and optional Public Gonol position -# class: construction -# since: 2026-08-21 -# -# id: word_closure_uses_declared_whitespace -# given: admitted character gonols include Unicode White_Space -# then: maximal non-whitespace runs close into word gonols and whitespace remains separately addressable boundary gonols -# class: construction -# since: 2026-08-21 -# -# id: closed_words_preserve_constituents -# given: a word gonol is closed -# then: constituent character identities, order, multiplicity, and source offsets remain recoverable and the word is atomic for later participation -# class: construction -# since: 2026-08-21 -# -# id: geometry_mismatch_fails_closed -# given: imported Public Gonol digest differs from the pinned identity -# then: construction raises rather than copying or inventing a carrier -# class: safety -# since: 2026-08-21 -# -# id: candidate_does_not_select_canon -# given: a receipt is minted -# then: standing is implemented-candidate, selection_effect is none, and measurement/definition/recursive claims remain nonclaims -# class: doctrine -# since: 2026-08-21 -# === END CONTRACTS === - -from __future__ import annotations - -from dataclasses import dataclass -from hashlib import sha256 -import importlib -import json -import os -from pathlib import Path -import sys -from types import ModuleType -from typing import Any, Callable, Mapping - - -CONSTRUCTOR_ID = "edcm.character_word_gonol" -CONSTRUCTOR_VERSION = "v1" -PINNED_PUBLIC_GONOL_SHA256 = ( - "55d10c84529a4d7bc7714786357e977b68d9df2ac3f73d20e229580b552c2ef5" -) -STANDING = "implemented-candidate" -SELECTION_EFFECT = "none" - -FROZEN_CHOICES: tuple[tuple[str, str], ...] = ( - ("admission_unit", "unicode-scalar"), - ("word_delimiter", "python-str-isspace-unicode-white-space"), - ("carrier", "ucns.public_gonol"), - ("pronunciation", "ignored"), - ("normalization", "none"), -) - -NONCLAIMS: tuple[str, ...] = ( - "not selected canon", - "not EDCM measurement validity", - "not a definition gonol constructor", - "not a recursive-relation constructor", - "not complete English morphology law", - "not a UCNS geometric function operation", - "not METAPAT canon promotion", -) - -HMMM: tuple[str, ...] = ( - "exact UCNS geometric operation of each Public Gonol function position", - "UCNS Möbius-carrier affixiation/coupling law", - "which definition source, if any, is later selected", - "which recursive relations, if any, are later selected", - "source-supported complete English morphology law", -) - - -class CharacterWordError(RuntimeError): - """Fail-closed constructor error.""" - - -@dataclass(frozen=True, slots=True) -class CharacterGonol: - """One admitted Unicode-scalar character gonol.""" - - occurrence: int - scalar: str - codepoint: int - carrier_index: int | None - role: str - - @property - def kind_id(self) -> str: - return self.scalar - - -@dataclass(frozen=True, slots=True) -class WordGonol: - """Ordered non-whitespace character gonols closed as one word.""" - - occurrence: int - source_start: int - source_end: int - characters: tuple[CharacterGonol, ...] - - @property - def kind_id(self) -> tuple[str, ...]: - return tuple(character.scalar for character in self.characters) - - -@dataclass(frozen=True, slots=True) -class CharacterWordReceipt: - """Deterministic construction receipt. Digest is replay identity, not gonol identity.""" - - constructor_id: str - constructor_version: str - standing: str - selection_effect: str - source_id: str - source_length: int - carrier_digest: str - frozen_choices: tuple[tuple[str, str], ...] - characters: tuple[CharacterGonol, ...] - words: tuple[WordGonol, ...] - boundaries: tuple[CharacterGonol, ...] - nonclaims: tuple[str, ...] - hmmm: tuple[str, ...] - receipt_digest: str - - -def _candidate_ucns_roots() -> tuple[Path, ...]: - roots: list[Path] = [] - configured = os.environ.get("UCNS_SOURCE_ROOT") - if configured: - roots.append(Path(configured) / "src") - sibling = Path(__file__).resolve().parents[2] / "ucns" / "src" - if sibling.is_dir(): - roots.append(sibling) - return tuple(roots) - - -def _load_public_gonol() -> ModuleType: - try: - module = importlib.import_module("ucns.public_gonol") - except ImportError: - module = None - last_error: Exception | None = None - for root in _candidate_ucns_roots(): - if str(root) not in sys.path: - sys.path.insert(0, str(root)) - try: - module = importlib.import_module("ucns.public_gonol") - break - except ImportError as exc: - last_error = exc - if module is None: - raise CharacterWordError( - "current UCNS public_gonol geometry is required; " - "install ucns or set UCNS_SOURCE_ROOT" - ) from last_error - digest = str(getattr(module, "PUBLIC_GONOL_SHA256", "")) - if digest != PINNED_PUBLIC_GONOL_SHA256: - raise CharacterWordError( - "UCNS Public Gonol digest mismatch: " - f"constructor pins {PINNED_PUBLIC_GONOL_SHA256}, " - f"imported {digest or 'missing'}" - ) - return module - - -def _character_record( - occurrence: int, - scalar: str, - position_of: Callable[[str], int | None], - role: str, -) -> CharacterGonol: - if not isinstance(scalar, str) or len(scalar) != 1: - raise CharacterWordError("admission unit is exactly one Unicode scalar") - codepoint = ord(scalar) - if 0xD800 <= codepoint <= 0xDFFF: - raise CharacterWordError("surrogate code points are not Unicode scalars") - return CharacterGonol( - occurrence=occurrence, - scalar=scalar, - codepoint=codepoint, - carrier_index=position_of(scalar), - role=role, - ) - - -def _close_words( - characters: tuple[CharacterGonol, ...], -) -> tuple[tuple[WordGonol, ...], tuple[CharacterGonol, ...]]: - words: list[WordGonol] = [] - boundaries: list[CharacterGonol] = [] - current: list[CharacterGonol] = [] - for character in characters: - if character.scalar.isspace(): - if current: - start = current[0].occurrence - end = current[-1].occurrence + 1 - words.append( - WordGonol( - occurrence=len(words), - source_start=start, - source_end=end, - characters=tuple(current), - ) - ) - current = [] - boundaries.append( - CharacterGonol( - occurrence=character.occurrence, - scalar=character.scalar, - codepoint=character.codepoint, - carrier_index=character.carrier_index, - role="boundary", - ) - ) - continue - current.append( - CharacterGonol( - occurrence=character.occurrence, - scalar=character.scalar, - codepoint=character.codepoint, - carrier_index=character.carrier_index, - role="word-member", - ) - ) - if current: - words.append( - WordGonol( - occurrence=len(words), - source_start=current[0].occurrence, - source_end=current[-1].occurrence + 1, - characters=tuple(current), - ) - ) - return tuple(words), tuple(boundaries) - - -def _character_payload(character: CharacterGonol) -> dict[str, Any]: - return { - "occurrence": character.occurrence, - "scalar": character.scalar, - "codepoint": character.codepoint, - "carrier_index": character.carrier_index, - "role": character.role, - "kind_id": character.kind_id, - } - - -def _receipt_payload( - *, - source_id: str, - source_length: int, - carrier_digest: str, - characters: tuple[CharacterGonol, ...], - words: tuple[WordGonol, ...], - boundaries: tuple[CharacterGonol, ...], -) -> dict[str, Any]: - return { - "constructor_id": CONSTRUCTOR_ID, - "constructor_version": CONSTRUCTOR_VERSION, - "standing": STANDING, - "selection_effect": SELECTION_EFFECT, - "source_id": source_id, - "source_length": source_length, - "carrier_digest": carrier_digest, - "frozen_choices": [list(item) for item in FROZEN_CHOICES], - "characters": [_character_payload(item) for item in characters], - "words": [ - { - "occurrence": word.occurrence, - "source_start": word.source_start, - "source_end": word.source_end, - "kind_id": list(word.kind_id), - "characters": [_character_payload(item) for item in word.characters], - } - for word in words - ], - "boundaries": [_character_payload(item) for item in boundaries], - "nonclaims": list(NONCLAIMS), - "hmmm": list(HMMM), - } - - -def canonical_receipt_bytes(payload: Mapping[str, Any]) -> bytes: - return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode( - "utf-8" - ) - - -def _digest(payload: Mapping[str, Any]) -> str: - return sha256(canonical_receipt_bytes(payload)).hexdigest() - - -def construct_character_word_gonols(source: str, *, source_id: str) -> CharacterWordReceipt: - """Admit every Unicode scalar and close whitespace-delimited word gonols.""" - - if not isinstance(source, str): - raise CharacterWordError("source must be a Unicode string") - if not isinstance(source_id, str) or not source_id: - raise CharacterWordError("source_id must be a non-empty string") - - geometry = _load_public_gonol() - position_of = geometry.public_gonol_position - admitted: list[CharacterGonol] = [] - for occurrence, scalar in enumerate(source): - admitted.append(_character_record(occurrence, scalar, position_of, "admitted")) - characters = tuple(admitted) - words, boundaries = _close_words(characters) - payload = _receipt_payload( - source_id=source_id, - source_length=len(source), - carrier_digest=str(geometry.PUBLIC_GONOL_SHA256), - characters=characters, - words=words, - boundaries=boundaries, - ) - return CharacterWordReceipt( - constructor_id=CONSTRUCTOR_ID, - constructor_version=CONSTRUCTOR_VERSION, - standing=STANDING, - selection_effect=SELECTION_EFFECT, - source_id=source_id, - source_length=len(source), - carrier_digest=str(geometry.PUBLIC_GONOL_SHA256), - frozen_choices=FROZEN_CHOICES, - characters=characters, - words=words, - boundaries=boundaries, - nonclaims=NONCLAIMS, - hmmm=HMMM, - receipt_digest=_digest(payload), - ) - - -def replay_character_word_gonols(source: str, *, source_id: str) -> CharacterWordReceipt: - """Independently reconstruct the same declared source.""" - - return construct_character_word_gonols(source, source_id=source_id) - - -__all__ = [ - "CONSTRUCTOR_ID", - "CONSTRUCTOR_VERSION", - "PINNED_PUBLIC_GONOL_SHA256", - "CharacterGonol", - "CharacterWordError", - "CharacterWordReceipt", - "WordGonol", - "canonical_receipt_bytes", - "construct_character_word_gonols", - "replay_character_word_gonols", -] diff --git a/edcm/definition_gonol.py b/edcm/definition_gonol.py deleted file mode 100644 index cd46929..0000000 --- a/edcm/definition_gonol.py +++ /dev/null @@ -1,303 +0,0 @@ -"""EDCM definition-gonol candidate constructor. - -Usage guidance --------------- -This is an implemented candidate, not selected canon. It constructs one -definition gonol from already-closed word gonols plus exact caller-supplied -definition evidence. It does not load a dictionary, invent a gloss, or reopen -closed words. - - from edcm.definition_gonol import ( - construct_definition_gonol, - replay_definition_gonol, - ) - - first = construct_definition_gonol( - headword="cut", - definition="to divide with a sharp edge", - source_id="example:cut#1", - ) - second = replay_definition_gonol( - headword="cut", - definition="to divide with a sharp edge", - source_id="example:cut#1", - ) - assert first.receipt_digest == second.receipt_digest - -Frozen choices for ``edcm.definition_gonol/v1``: - -- definition evidence is the exact ``headword`` and ``definition`` strings plus - ``source_id`` supplied by the caller; -- both strings are constructed through ``edcm.character_word``; -- closed word gonols participate atomically and are not reopened; -- the intrinsic relation is ``definition-of``: ordered headword words plus - ordered definition-body words; -- pronunciation is ignored and must not be passed as a substitute gloss; -- no lemma, stem, morphology, or sense-inventory is inferred; -- each call constructs one definition gonol; multiple senses remain multiple - gonols. - -Historical OEWN and lexical-floor artifacts are not this constructor. Pass -their exact sense text as ``definition`` only when reproducing that source -identity. -""" - -# === MODULE_BUILD === -# id: edcm_definition_gonol -# module_name: definition_gonol -# module_kind: engine -# summary: named EDCM candidate that affixiates already-closed word gonols with exact caller-supplied definition evidence into one definition gonol without selecting canon, inventing glosses, or activating measurement -# owner: Erin Spencer -# public_surface: CONSTRUCTOR_ID, CONSTRUCTOR_VERSION, RELATION, DefinitionGonol, DefinitionGonolError, DefinitionGonolReceipt, construct_definition_gonol, replay_definition_gonol -# internal_surface: _require_words, _word_payload, _receipt_payload, _digest -# auth_boundary: consumes edcm.character_word and therefore the pinned UCNS Public Gonol digest -# storage_boundary: none; receipts remain caller-owned in-memory objects -# network_boundary: none -# user_data_boundary: caller-supplied headword and definition text remain in memory and are not transmitted -# admin_only: false -# tests: tests.test_definition_gonol -# rollout: explicit candidate constructor; no measurement or recursive-relation activation -# rollback: remove this module; character_word and historical lexical-floor evidence remain unchanged -# requires: edcm_character_word_gonol -# since: 2026-08-21 -# unresolved: UCNS geometric operation of each Public Gonol function position; Möbius-carrier affixiation/coupling law; which recursive relations are later selected; complete English morphology law; selection among competing definition sources -# === END MODULE_BUILD === - -# === CONTRACTS === -# id: definition_uses_closed_word_gonols -# given: headword and definition strings are admitted -# then: both are constructed through character_word and the definition gonol participants are those closed word gonols without reopening their characters as a free stream -# class: construction -# since: 2026-08-21 -# -# id: definition_requires_exact_source_evidence -# given: definition evidence is missing, empty of words, or not a string -# then: construction fails closed rather than inventing a gloss or loading a dictionary -# class: safety -# since: 2026-08-21 -# -# id: definition_relation_is_intrinsic -# given: a definition gonol is closed -# then: the relation definition-of enters the construction as ordered headword words plus ordered body words with recoverable nested receipts -# class: construction -# since: 2026-08-21 -# -# id: definition_candidate_does_not_select_canon -# given: a receipt is minted -# then: standing is implemented-candidate, selection_effect is none, and measurement, recursion, morphology, and dictionary-authority remain nonclaims -# class: doctrine -# since: 2026-08-21 -# === END CONTRACTS === - -from __future__ import annotations - -from dataclasses import dataclass -from hashlib import sha256 -import json -from typing import Any, Mapping - -from .character_word import CharacterWordReceipt, WordGonol, construct_character_word_gonols - - -CONSTRUCTOR_ID = "edcm.definition_gonol" -CONSTRUCTOR_VERSION = "v1" -RELATION = "definition-of" -STANDING = "implemented-candidate" -SELECTION_EFFECT = "none" - -FROZEN_CHOICES: tuple[tuple[str, str], ...] = ( - ("word_constructor", "edcm.character_word_gonol/v1"), - ("definition_evidence", "caller-supplied-exact-strings"), - ("relation", RELATION), - ("pronunciation", "ignored"), - ("morphology", "not-a-stage"), - ("sense_inventory", "one-definition-per-call"), -) - -NONCLAIMS: tuple[str, ...] = ( - "not selected canon", - "not EDCM measurement validity", - "not a recursive-relation constructor", - "not complete English morphology law", - "not a dictionary or sense inventory", - "not a UCNS geometric function operation", - "not METAPAT canon promotion", - "not OEWN or lexical-floor revival", -) - -HMMM: tuple[str, ...] = ( - "exact UCNS geometric operation of each Public Gonol function position", - "UCNS Möbius-carrier affixiation/coupling law", - "which recursive relations, if any, are later selected", - "source-supported complete English morphology law", - "which definition source, if any, is later selected", -) - - -class DefinitionGonolError(RuntimeError): - """Fail-closed definition constructor error.""" - - -@dataclass(frozen=True, slots=True) -class DefinitionGonol: - """Closed definition gonol whose participants are already-closed words.""" - - occurrence: int - relation: str - headword_words: tuple[WordGonol, ...] - body_words: tuple[WordGonol, ...] - - @property - def kind_id(self) -> tuple[tuple[tuple[str, ...], ...], tuple[tuple[str, ...], ...]]: - return ( - tuple(word.kind_id for word in self.headword_words), - tuple(word.kind_id for word in self.body_words), - ) - - -@dataclass(frozen=True, slots=True) -class DefinitionGonolReceipt: - """Deterministic definition receipt. Digest is replay identity, not gonol identity.""" - - constructor_id: str - constructor_version: str - standing: str - selection_effect: str - source_id: str - frozen_choices: tuple[tuple[str, str], ...] - headword_receipt: CharacterWordReceipt - definition_receipt: CharacterWordReceipt - definition: DefinitionGonol - nonclaims: tuple[str, ...] - hmmm: tuple[str, ...] - receipt_digest: str - - -def _require_words(receipt: CharacterWordReceipt, *, field: str) -> tuple[WordGonol, ...]: - if not receipt.words: - raise DefinitionGonolError( - f"{field} produced no closed word gonols; exact source evidence is required" - ) - return receipt.words - - -def _word_payload(word: WordGonol) -> dict[str, Any]: - return { - "occurrence": word.occurrence, - "source_start": word.source_start, - "source_end": word.source_end, - "kind_id": list(word.kind_id), - "character_occurrences": [item.occurrence for item in word.characters], - } - - -def _receipt_payload( - *, - source_id: str, - headword_receipt: CharacterWordReceipt, - definition_receipt: CharacterWordReceipt, - definition: DefinitionGonol, -) -> dict[str, Any]: - return { - "constructor_id": CONSTRUCTOR_ID, - "constructor_version": CONSTRUCTOR_VERSION, - "standing": STANDING, - "selection_effect": SELECTION_EFFECT, - "source_id": source_id, - "frozen_choices": [list(item) for item in FROZEN_CHOICES], - "relation": RELATION, - "headword_receipt_digest": headword_receipt.receipt_digest, - "definition_receipt_digest": definition_receipt.receipt_digest, - "headword_words": [_word_payload(word) for word in definition.headword_words], - "body_words": [_word_payload(word) for word in definition.body_words], - "kind_id": [ - [list(word_kind) for word_kind in definition.kind_id[0]], - [list(word_kind) for word_kind in definition.kind_id[1]], - ], - "nonclaims": list(NONCLAIMS), - "hmmm": list(HMMM), - } - - -def _digest(payload: Mapping[str, Any]) -> str: - return sha256( - json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") - ).hexdigest() - - -def construct_definition_gonol( - *, - headword: str, - definition: str, - source_id: str, -) -> DefinitionGonolReceipt: - """Close one definition gonol from exact headword and definition source text.""" - - if not isinstance(headword, str): - raise DefinitionGonolError("headword must be an exact Unicode string") - if not isinstance(definition, str): - raise DefinitionGonolError("definition must be an exact Unicode string") - if not isinstance(source_id, str) or not source_id: - raise DefinitionGonolError("source_id must be a non-empty string") - - headword_receipt = construct_character_word_gonols( - headword, - source_id=f"{source_id}#headword", - ) - definition_receipt = construct_character_word_gonols( - definition, - source_id=f"{source_id}#definition", - ) - definition_gonol = DefinitionGonol( - occurrence=0, - relation=RELATION, - headword_words=_require_words(headword_receipt, field="headword"), - body_words=_require_words(definition_receipt, field="definition"), - ) - payload = _receipt_payload( - source_id=source_id, - headword_receipt=headword_receipt, - definition_receipt=definition_receipt, - definition=definition_gonol, - ) - return DefinitionGonolReceipt( - constructor_id=CONSTRUCTOR_ID, - constructor_version=CONSTRUCTOR_VERSION, - standing=STANDING, - selection_effect=SELECTION_EFFECT, - source_id=source_id, - frozen_choices=FROZEN_CHOICES, - headword_receipt=headword_receipt, - definition_receipt=definition_receipt, - definition=definition_gonol, - nonclaims=NONCLAIMS, - hmmm=HMMM, - receipt_digest=_digest(payload), - ) - - -def replay_definition_gonol( - *, - headword: str, - definition: str, - source_id: str, -) -> DefinitionGonolReceipt: - """Independently reconstruct the same declared definition source.""" - - return construct_definition_gonol( - headword=headword, - definition=definition, - source_id=source_id, - ) - - -__all__ = [ - "CONSTRUCTOR_ID", - "CONSTRUCTOR_VERSION", - "RELATION", - "DefinitionGonol", - "DefinitionGonolError", - "DefinitionGonolReceipt", - "construct_definition_gonol", - "replay_definition_gonol", -] diff --git a/edcm/gonol.py b/edcm/gonol.py new file mode 100644 index 0000000..3de586e --- /dev/null +++ b/edcm/gonol.py @@ -0,0 +1,520 @@ +"""Unified EDCM gonol candidate constructor. + +Usage guidance +-------------- +This is an implemented candidate, not selected canon. It closes one gonol at a +declared scale using the scale's option set. It does not encode a mandatory +``character -> word -> definition -> recursive`` ladder. + + from edcm.gonol import construct_gonol, replay_gonol + + word = construct_gonol(scale="word", source="cut", source_id="example:cut") + rel = construct_gonol( + scale="recursive", + relation="example:pair", + participants=(word.gonol, word.gonol), + source_id="example:pair#1", + ) + assert rel.receipt_digest == replay_gonol( + scale="recursive", + relation="example:pair", + participants=(word.gonol, word.gonol), + source_id="example:pair#1", + ).receipt_digest + +Frozen choices for ``edcm.gonol/v1``: + +- construction is one constructor selected by a scale option set; +- once closed, a gonol is atomic at any scale; +- closed gonols may participate directly at any admissible scale without + reopening; +- source strings are exact Unicode scalar sequences: no normalization, case + folding, trimming, deduplication, or token substitution; +- relation identity is exact caller-supplied text where the option set requires + it; +- UCNS Public Gonol geometry is consumed only when normally importable, and + absence remains ``hmmm`` rather than a base-package failure; +- no UCNS function operation or Mobius coupling law is invented. +""" + +# === MODULE_BUILD === +# id: edcm_gonol +# module_name: gonol +# module_kind: engine +# summary: unified EDCM candidate constructor that closes gonols through declared scale option sets while preserving closed-gonol atomicity, deterministic replay, and UCNS/METAPAT authority boundaries +# owner: Erin Spencer +# public_surface: CONSTRUCTOR_ID, CONSTRUCTOR_VERSION, PINNED_PUBLIC_GONOL_SHA256, ScaleOptionSet, ClosedGonol, GonolReceipt, GonolConstructionError, SCALE_OPTION_SETS, construct_gonol, replay_gonol, canonical_receipt_bytes +# internal_surface: _option_set, _require_text, _source_units, _load_optional_public_gonol, _geometry_observation, _participant_payload, _atomic_payload, _receipt_payload, _digest +# auth_boundary: EDCM owns text-domain closure; UCNS Public Gonol geometry is optional observation unless normally importable with matching digest; METAPAT affixiation semantics are consumed, not redefined +# storage_boundary: none; receipts remain caller-owned in-memory objects +# network_boundary: none +# user_data_boundary: caller-supplied source, relation, participants, and source_id remain in memory and are not transmitted +# admin_only: false +# tests: tests.test_gonol_constructor +# rollout: explicit candidate constructor; no canon selection, measurement activation, UCNS function operation, or Mobius coupling promotion +# rollback: remove this module; historical lexical-floor and UCNS observation adapters remain unchanged +# requires: none +# since: 2026-08-22 +# unresolved: exact UCNS geometric operation of Public Gonol function positions; Mobius-carrier affixiation/coupling law; which scales and relations are later selected; complete English morphology law +# === END MODULE_BUILD === + +# === CONTRACTS === +# id: single_constructor_uses_scale_option_sets +# given: a caller closes source evidence or closed gonol participants +# then: edcm.gonol uses the declared scale option set rather than dispatching through specialized ladder constructors +# class: construction +# since: 2026-08-22 +# +# id: closed_gonol_atomic_at_any_scale +# given: a closed gonol participates in another construction +# then: the participant is consumed by atomic identity while recoverable provenance and nested structure remain available +# class: construction +# since: 2026-08-22 +# +# id: construction_survives_absent_ucns_geometry +# given: ucns.public_gonol is not normally importable +# then: construction records geometry as hmmm and does not mutate sys.path or fail base-package CI +# class: safety +# since: 2026-08-22 +# +# id: geometry_mismatch_fails_closed +# given: importable UCNS Public Gonol geometry has a digest different from the pinned identity +# then: construction raises rather than consuming or copying mismatched geometry +# class: safety +# since: 2026-08-22 +# +# id: unified_candidate_does_not_select_canon +# given: a receipt is minted +# then: standing is implemented-candidate, selection_effect is none, and measurement, UCNS operation, and METAPAT promotion remain nonclaims +# class: doctrine +# since: 2026-08-22 +# === END CONTRACTS === + +from __future__ import annotations + +from dataclasses import dataclass +from hashlib import sha256 +import importlib +import json +from types import ModuleType +from typing import Any, Mapping, Sequence + + +CONSTRUCTOR_ID = "edcm.gonol" +CONSTRUCTOR_VERSION = "v1" +PINNED_PUBLIC_GONOL_SHA256 = ( + "55d10c84529a4d7bc7714786357e977b68d9df2ac3f73d20e229580b552c2ef5" +) +STANDING = "implemented-candidate" +SELECTION_EFFECT = "none" + +NONCLAIMS: tuple[str, ...] = ( + "not selected canon", + "not EDCM measurement validity", + "not a mandatory character-word-definition-recursive ladder", + "not complete English morphology law", + "not a UCNS geometric function operation", + "not a UCNS Mobius coupling law", + "not METAPAT canon promotion", +) + +HMMM: tuple[str, ...] = ( + "exact UCNS geometric operation of each Public Gonol function position", + "UCNS Mobius-carrier affixiation/coupling law", + "which scales and relations, if any, are later selected", + "source-supported complete English morphology law", +) + + +class GonolConstructionError(RuntimeError): + """Fail-closed constructor error.""" + + +@dataclass(frozen=True, slots=True) +class ScaleOptionSet: + """Frozen options for one admissible gonol scale.""" + + scale: str + option_set_id: str + source_policy: str + participant_policy: str + relation_policy: str + default_relation: str | None + closure_policy: str + geometry_policy: str + + +SCALE_OPTION_SETS: Mapping[str, ScaleOptionSet] = { + "character": ScaleOptionSet( + scale="character", + option_set_id="edcm.gonol.scale.character/v1", + source_policy="exactly-one-unicode-scalar", + participant_policy="none", + relation_policy="declared-default", + default_relation="admitted-character", + closure_policy="close-one-source-unit", + geometry_policy="observe-public-gonol-position-if-importable", + ), + "word": ScaleOptionSet( + scale="word", + option_set_id="edcm.gonol.scale.word/v1", + source_policy="exact-source-string-or-closed-participants", + participant_policy="any-closed-gonols-without-reopening", + relation_policy="caller-supplied-or-declared-default", + default_relation="word-closure", + closure_policy="close-declared-word-scale-object", + geometry_policy="observe-public-gonol-positions-if-importable", + ), + "definition": ScaleOptionSet( + scale="definition", + option_set_id="edcm.gonol.scale.definition/v1", + source_policy="exact-source-string-or-closed-participants", + participant_policy="any-closed-gonols-without-reopening", + relation_policy="caller-supplied-or-declared-default", + default_relation="definition-evidence", + closure_policy="close-declared-definition-scale-object", + geometry_policy="observe-public-gonol-positions-if-importable", + ), + "recursive": ScaleOptionSet( + scale="recursive", + option_set_id="edcm.gonol.scale.recursive/v1", + source_policy="optional-exact-source-evidence", + participant_policy="any-closed-gonols-without-reopening", + relation_policy="caller-supplied-required", + default_relation=None, + closure_policy="close-relation-over-atomic-participants", + geometry_policy="observe-public-gonol-positions-if-importable", + ), +} + + +@dataclass(frozen=True, slots=True) +class ClosedGonol: + """Closed gonol value. Atomic identity participates; internals remain recoverable.""" + + occurrence: int + scale: str + option_set_id: str + relation: str + source_id: str + source_units: tuple[str, ...] + participants: tuple["ClosedGonol", ...] + atomic_id: str + provenance: tuple[tuple[str, str], ...] + + @property + def kind_id(self) -> tuple[str, str]: + return (self.scale, self.atomic_id) + + +@dataclass(frozen=True, slots=True) +class GonolReceipt: + """Deterministic construction receipt. Digest is replay identity.""" + + constructor_id: str + constructor_version: str + standing: str + selection_effect: str + source_id: str + option_set: ScaleOptionSet + gonol: ClosedGonol + geometry: Mapping[str, Any] + nonclaims: tuple[str, ...] + hmmm: tuple[str, ...] + receipt_digest: str + + +def _option_set(scale: str) -> ScaleOptionSet: + if not isinstance(scale, str) or not scale: + raise GonolConstructionError("scale must be a non-empty string") + try: + return SCALE_OPTION_SETS[scale] + except KeyError as exc: + allowed = ", ".join(sorted(SCALE_OPTION_SETS)) + raise GonolConstructionError(f"scale must be one of: {allowed}") from exc + + +def _require_text(value: str, *, field: str, allow_empty: bool = False) -> str: + if not isinstance(value, str): + raise GonolConstructionError(f"{field} must be an exact Unicode string") + if not allow_empty and not value: + raise GonolConstructionError(f"{field} must be a non-empty string") + return value + + +def _source_units(source: str | None, *, options: ScaleOptionSet) -> tuple[str, ...]: + if source is None: + return () + text = _require_text(source, field="source", allow_empty=False) + units = tuple(text) + for unit in units: + codepoint = ord(unit) + if 0xD800 <= codepoint <= 0xDFFF: + raise GonolConstructionError("surrogate code points are not Unicode scalars") + if options.scale == "character" and len(units) != 1: + raise GonolConstructionError("character scale closes exactly one Unicode scalar") + if options.scale == "word" and any(unit.isspace() for unit in units): + raise GonolConstructionError("word scale source must be one closed source unit, not whitespace-delimited text") + return units + + +def _closed_participants(participants: Sequence[ClosedGonol] | None) -> tuple[ClosedGonol, ...]: + if participants is None: + return () + if not isinstance(participants, Sequence) or isinstance(participants, (str, bytes)): + raise GonolConstructionError("participants must be an ordered sequence of closed gonols") + closed = tuple(participants) + for item in closed: + if not isinstance(item, ClosedGonol): + raise GonolConstructionError("participants must already be closed gonols") + return closed + + +def _relation_value(relation: str | None, *, options: ScaleOptionSet) -> str: + if relation is None: + if options.default_relation is None: + raise GonolConstructionError("relation must be exact caller-supplied text for this scale") + return options.default_relation + if not isinstance(relation, str) or not relation or relation.isspace(): + raise GonolConstructionError("relation must be exact non-empty caller-supplied text") + return relation + + +def _load_optional_public_gonol() -> ModuleType | None: + try: + return importlib.import_module("ucns.public_gonol") + except ImportError: + return None + + +def _geometry_observation(source_units: tuple[str, ...]) -> dict[str, Any]: + module = _load_optional_public_gonol() + if module is None: + return { + "state": "hmmm", + "authority": "ucns.public_gonol", + "reason": "ucns.public_gonol not normally importable", + "positions": [], + } + digest = str(getattr(module, "PUBLIC_GONOL_SHA256", "")) + if digest != PINNED_PUBLIC_GONOL_SHA256: + raise GonolConstructionError( + "UCNS Public Gonol digest mismatch: " + f"constructor pins {PINNED_PUBLIC_GONOL_SHA256}, " + f"imported {digest or 'missing'}" + ) + position_of = getattr(module, "public_gonol_position", None) + if not callable(position_of): + raise GonolConstructionError("UCNS public_gonol is missing public_gonol_position") + return { + "state": "observed", + "authority": "ucns.public_gonol", + "carrier_digest": digest, + "positions": [position_of(unit) for unit in source_units], + } + + +def _kind_payload(value: Any) -> Any: + if isinstance(value, tuple): + return [_kind_payload(item) for item in value] + if isinstance(value, list): + return [_kind_payload(item) for item in value] + return value + + +def _option_payload(options: ScaleOptionSet) -> dict[str, str | None]: + return { + "scale": options.scale, + "option_set_id": options.option_set_id, + "source_policy": options.source_policy, + "participant_policy": options.participant_policy, + "relation_policy": options.relation_policy, + "default_relation": options.default_relation, + "closure_policy": options.closure_policy, + "geometry_policy": options.geometry_policy, + } + + +def _participant_payload(item: ClosedGonol) -> dict[str, Any]: + return { + "scale": item.scale, + "occurrence": item.occurrence, + "relation": item.relation, + "source_id": item.source_id, + "kind_id": _kind_payload(item.kind_id), + "atomic_id": item.atomic_id, + "option_set_id": item.option_set_id, + "provenance": [list(pair) for pair in item.provenance], + } + + +def _atomic_payload( + *, + occurrence: int, + source_id: str, + options: ScaleOptionSet, + relation: str, + source_units: tuple[str, ...], + participants: tuple[ClosedGonol, ...], +) -> dict[str, Any]: + return { + "constructor_id": CONSTRUCTOR_ID, + "constructor_version": CONSTRUCTOR_VERSION, + "standing": STANDING, + "selection_effect": SELECTION_EFFECT, + "occurrence": occurrence, + "source_id": source_id, + "option_set": _option_payload(options), + "relation": relation, + "source_units": list(source_units), + "participants": [_participant_payload(item) for item in participants], + "closure_invariant": "once closed, a gonol is atomic at any scale", + } + + +def _receipt_payload( + *, + source_id: str, + options: ScaleOptionSet, + gonol: ClosedGonol, + geometry: Mapping[str, Any], +) -> dict[str, Any]: + return { + "constructor_id": CONSTRUCTOR_ID, + "constructor_version": CONSTRUCTOR_VERSION, + "standing": STANDING, + "selection_effect": SELECTION_EFFECT, + "source_id": source_id, + "option_set": _option_payload(options), + "gonol": _atomic_payload( + occurrence=gonol.occurrence, + source_id=gonol.source_id, + options=options, + relation=gonol.relation, + source_units=gonol.source_units, + participants=gonol.participants, + ), + "atomic_id": gonol.atomic_id, + "geometry": dict(geometry), + "nonclaims": list(NONCLAIMS), + "hmmm": list(HMMM), + } + + +def canonical_receipt_bytes(payload: Mapping[str, Any]) -> bytes: + """Return stable JSON bytes for construction receipts.""" + + return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode( + "utf-8" + ) + + +def _digest(payload: Mapping[str, Any]) -> str: + return sha256(canonical_receipt_bytes(payload)).hexdigest() + + +def construct_gonol( + *, + scale: str, + source_id: str, + source: str | None = None, + participants: Sequence[ClosedGonol] | None = None, + relation: str | None = None, + occurrence: int = 0, +) -> GonolReceipt: + """Close one gonol at a declared scale using its option set.""" + + options = _option_set(scale) + source_id = _require_text(source_id, field="source_id") + if isinstance(occurrence, bool) or not isinstance(occurrence, int) or occurrence < 0: + raise GonolConstructionError("occurrence must be a non-negative integer") + units = _source_units(source, options=options) + closed = _closed_participants(participants) + if options.scale == "character" and closed: + raise GonolConstructionError("character scale does not accept closed participants") + if not units and not closed: + raise GonolConstructionError("construction requires source evidence or closed participants") + if options.scale == "recursive" and len(closed) < 2: + raise GonolConstructionError("recursive scale requires at least two closed participants") + relation_value = _relation_value(relation, options=options) + geometry = _geometry_observation(units) + atomic_payload = _atomic_payload( + occurrence=occurrence, + source_id=source_id, + options=options, + relation=relation_value, + source_units=units, + participants=closed, + ) + atomic_id = _digest(atomic_payload) + provenance = ( + ("constructor", f"{CONSTRUCTOR_ID}/{CONSTRUCTOR_VERSION}"), + ("source_id", source_id), + ("option_set", options.option_set_id), + ) + gonol = ClosedGonol( + occurrence=occurrence, + scale=options.scale, + option_set_id=options.option_set_id, + relation=relation_value, + source_id=source_id, + source_units=units, + participants=closed, + atomic_id=atomic_id, + provenance=provenance, + ) + payload = _receipt_payload( + source_id=source_id, + options=options, + gonol=gonol, + geometry=geometry, + ) + return GonolReceipt( + constructor_id=CONSTRUCTOR_ID, + constructor_version=CONSTRUCTOR_VERSION, + standing=STANDING, + selection_effect=SELECTION_EFFECT, + source_id=source_id, + option_set=options, + gonol=gonol, + geometry=geometry, + nonclaims=NONCLAIMS, + hmmm=HMMM, + receipt_digest=_digest(payload), + ) + + +def replay_gonol( + *, + scale: str, + source_id: str, + source: str | None = None, + participants: Sequence[ClosedGonol] | None = None, + relation: str | None = None, + occurrence: int = 0, +) -> GonolReceipt: + """Independently reconstruct the same declared gonol closure.""" + + return construct_gonol( + scale=scale, + source_id=source_id, + source=source, + participants=participants, + relation=relation, + occurrence=occurrence, + ) + + +__all__ = [ + "CONSTRUCTOR_ID", + "CONSTRUCTOR_VERSION", + "PINNED_PUBLIC_GONOL_SHA256", + "SCALE_OPTION_SETS", + "ScaleOptionSet", + "ClosedGonol", + "GonolConstructionError", + "GonolReceipt", + "canonical_receipt_bytes", + "construct_gonol", + "replay_gonol", +] diff --git a/edcm/recursive_gonol.py b/edcm/recursive_gonol.py deleted file mode 100644 index 6b9e097..0000000 --- a/edcm/recursive_gonol.py +++ /dev/null @@ -1,278 +0,0 @@ -"""EDCM recursive-gonol candidate constructor. - -Usage guidance --------------- -This is an implemented candidate, not selected canon. It closes one recursive -gonol from already-closed word, definition, or recursive gonols plus an exact -caller-supplied relation identity. It does not infer the relation from -adjacency, reopen participants, or invent UCNS coupling geometry. - - from edcm.character_word import construct_character_word_gonols - from edcm.recursive_gonol import construct_recursive_gonol, replay_recursive_gonol - - words = construct_character_word_gonols("cut divide", source_id="example") - first = construct_recursive_gonol( - relation="example:ordered-pair", - participants=words.words, - source_id="example:pair#1", - ) - second = replay_recursive_gonol( - relation="example:ordered-pair", - participants=words.words, - source_id="example:pair#1", - ) - assert first.receipt_digest == second.receipt_digest - -Frozen choices for ``edcm.recursive_gonol/v1``: - -- participants are already-closed ``WordGonol``, ``DefinitionGonol``, or - ``RecursiveGonol`` values; -- at least two participants are required; -- the relation identity is the exact caller-supplied string; -- participants remain atomic and recoverable; -- the closed recursive gonol may itself participate in a later relation; -- UCNS geometric coupling remains unresolved and is not filled; -- pronunciation, morphology, and adjacency grammar are not used. - -Do not pass character gonols. Close words first. Do not treat a WordNet edge -table or other sidecar graph as this constructor. -""" - -# === MODULE_BUILD === -# id: edcm_recursive_gonol -# module_name: recursive_gonol -# module_kind: engine -# summary: named EDCM candidate that closes recursive gonols from already-closed word, definition, or recursive gonols and an exact caller-supplied relation without inventing UCNS geometry or selecting canon -# owner: Erin Spencer -# public_surface: CONSTRUCTOR_ID, CONSTRUCTOR_VERSION, RecursiveGonol, RecursiveGonolError, RecursiveGonolReceipt, construct_recursive_gonol, replay_recursive_gonol -# internal_surface: _scale_of, _kind_payload, _participant_payload, _receipt_payload, _digest -# auth_boundary: consumes closed EDCM gonols; does not invent UCNS coupling geometry -# storage_boundary: none; receipts remain caller-owned in-memory objects -# network_boundary: none -# user_data_boundary: caller-supplied relation identity and participant gonols remain in memory and are not transmitted -# admin_only: false -# tests: tests.test_recursive_gonol -# rollout: explicit candidate constructor; no measurement activation and no UCNS coupling law -# rollback: remove this module; character_word and definition_gonol remain unchanged -# requires: edcm_character_word_gonol, edcm_definition_gonol -# since: 2026-08-21 -# unresolved: UCNS geometric operation of each Public Gonol function position; Möbius-carrier affixiation/coupling law; which recursive relations are later selected; complete English morphology law -# === END MODULE_BUILD === - -# === CONTRACTS === -# id: recursive_uses_closed_gonols -# given: ordered already-closed word, definition, or recursive gonols and an exact relation identity -# then: the recursive gonol participants are those gonols without reopening their internal character streams -# class: construction -# since: 2026-08-21 -# -# id: recursive_relation_is_caller_supplied -# given: the relation identity is missing, empty, or inferred from adjacency -# then: construction fails closed rather than inventing a linguistic or geometric relation -# class: safety -# since: 2026-08-21 -# -# id: recursive_result_may_participate -# given: a recursive gonol is closed -# then: that gonol may be supplied as a participant in a later recursive construction while remaining atomic -# class: construction -# since: 2026-08-21 -# -# id: recursive_candidate_does_not_select_canon -# given: a receipt is minted -# then: standing is implemented-candidate, selection_effect is none, and measurement, UCNS coupling, and relation-inventory claims remain nonclaims -# class: doctrine -# since: 2026-08-21 -# === END CONTRACTS === - -from __future__ import annotations - -from dataclasses import dataclass -from hashlib import sha256 -import json -from typing import Any, Mapping, Sequence, Union - -from .character_word import CharacterGonol, WordGonol -from .definition_gonol import DefinitionGonol - - -CONSTRUCTOR_ID = "edcm.recursive_gonol" -CONSTRUCTOR_VERSION = "v1" -STANDING = "implemented-candidate" -SELECTION_EFFECT = "none" - -FROZEN_CHOICES: tuple[tuple[str, str], ...] = ( - ("participants", "closed-word-definition-or-recursive-gonols"), - ("minimum_participants", "2"), - ("relation", "caller-supplied-exact-identity"), - ("ucns_coupling", "hmmm"), - ("pronunciation", "ignored"), - ("morphology", "not-a-stage"), -) - -NONCLAIMS: tuple[str, ...] = ( - "not selected canon", - "not EDCM measurement validity", - "not a UCNS geometric coupling law", - "not a dictionary, WordNet, or sense-inventory graph", - "not complete English morphology law", - "not adjacency or punctuation grammar", - "not METAPAT canon promotion", -) - -HMMM: tuple[str, ...] = ( - "exact UCNS geometric operation of each Public Gonol function position", - "UCNS Möbius-carrier affixiation/coupling law", - "which recursive relations, if any, are later selected", - "source-supported complete English morphology law", -) - -ClosedGonol = Union[WordGonol, DefinitionGonol, "RecursiveGonol"] - - -class RecursiveGonolError(RuntimeError): - """Fail-closed recursive constructor error.""" - - -@dataclass(frozen=True, slots=True) -class RecursiveGonol: - """Closed recursive gonol whose participants are already-closed gonols.""" - - occurrence: int - relation: str - participants: tuple[ClosedGonol, ...] - - @property - def kind_id(self) -> tuple[str, tuple[tuple[str, Any], ...]]: - return ( - self.relation, - tuple((_scale_of(item), item.kind_id) for item in self.participants), - ) - - -@dataclass(frozen=True, slots=True) -class RecursiveGonolReceipt: - """Deterministic recursive receipt. Digest is replay identity, not gonol identity.""" - - constructor_id: str - constructor_version: str - standing: str - selection_effect: str - source_id: str - frozen_choices: tuple[tuple[str, str], ...] - recursive: RecursiveGonol - nonclaims: tuple[str, ...] - hmmm: tuple[str, ...] - receipt_digest: str - - -def _scale_of(item: ClosedGonol) -> str: - if isinstance(item, WordGonol): - return "word" - if isinstance(item, DefinitionGonol): - return "definition" - if isinstance(item, RecursiveGonol): - return "recursive" - raise RecursiveGonolError("participants must be closed word, definition, or recursive gonols") - - -def _kind_payload(kind_id: Any) -> Any: - if isinstance(kind_id, tuple): - return [_kind_payload(item) for item in kind_id] - if isinstance(kind_id, list): - return [_kind_payload(item) for item in kind_id] - return kind_id - - -def _participant_payload(item: ClosedGonol) -> dict[str, Any]: - return { - "scale": _scale_of(item), - "occurrence": item.occurrence, - "kind_id": _kind_payload(item.kind_id), - } - - -def _receipt_payload(*, source_id: str, recursive: RecursiveGonol) -> dict[str, Any]: - return { - "constructor_id": CONSTRUCTOR_ID, - "constructor_version": CONSTRUCTOR_VERSION, - "standing": STANDING, - "selection_effect": SELECTION_EFFECT, - "source_id": source_id, - "frozen_choices": [list(item) for item in FROZEN_CHOICES], - "relation": recursive.relation, - "participants": [_participant_payload(item) for item in recursive.participants], - "kind_id": _kind_payload(recursive.kind_id), - "nonclaims": list(NONCLAIMS), - "hmmm": list(HMMM), - } - - -def _digest(payload: Mapping[str, Any]) -> str: - return sha256( - json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") - ).hexdigest() - - -def construct_recursive_gonol( - *, - relation: str, - participants: Sequence[ClosedGonol], - source_id: str, -) -> RecursiveGonolReceipt: - """Close one recursive gonol from already-closed gonols and an exact relation.""" - - if not isinstance(relation, str) or not relation or relation.isspace(): - raise RecursiveGonolError("relation must be an exact non-empty caller-supplied identity") - if not isinstance(source_id, str) or not source_id: - raise RecursiveGonolError("source_id must be a non-empty string") - if not isinstance(participants, Sequence) or isinstance(participants, (str, bytes)): - raise RecursiveGonolError("participants must be an ordered sequence of closed gonols") - closed = tuple(participants) - if len(closed) < 2: - raise RecursiveGonolError("recursive construction requires at least two closed gonols") - for item in closed: - if isinstance(item, CharacterGonol): - raise RecursiveGonolError("character gonols are not recursive participants; close words first") - _scale_of(item) - - recursive = RecursiveGonol(occurrence=0, relation=relation, participants=closed) - payload = _receipt_payload(source_id=source_id, recursive=recursive) - return RecursiveGonolReceipt( - constructor_id=CONSTRUCTOR_ID, - constructor_version=CONSTRUCTOR_VERSION, - standing=STANDING, - selection_effect=SELECTION_EFFECT, - source_id=source_id, - frozen_choices=FROZEN_CHOICES, - recursive=recursive, - nonclaims=NONCLAIMS, - hmmm=HMMM, - receipt_digest=_digest(payload), - ) - - -def replay_recursive_gonol( - *, - relation: str, - participants: Sequence[ClosedGonol], - source_id: str, -) -> RecursiveGonolReceipt: - """Independently reconstruct the same declared recursive relation.""" - - return construct_recursive_gonol( - relation=relation, - participants=participants, - source_id=source_id, - ) - - -__all__ = [ - "CONSTRUCTOR_ID", - "CONSTRUCTOR_VERSION", - "RecursiveGonol", - "RecursiveGonolError", - "RecursiveGonolReceipt", - "construct_recursive_gonol", - "replay_recursive_gonol", -] diff --git a/edcm_msdmd.ts b/edcm_msdmd.ts index 6b0761f..37bd9c2 100644 --- a/edcm_msdmd.ts +++ b/edcm_msdmd.ts @@ -702,6 +702,85 @@ export default defineMsdmdCollection({ "file": "edcm/goal_vector_experiment.py", "id": "edcm_goal_vector_experiment" }, + { + "block": "CONTRACTS", + "fields": { + "class": "construction", + "given": "a closed gonol participates in another construction", + "since": "2026-08-22", + "then": "the participant is consumed by atomic identity while recoverable provenance and nested structure remain available" + }, + "file": "edcm/gonol.py", + "id": "closed_gonol_atomic_at_any_scale" + }, + { + "block": "CONTRACTS", + "fields": { + "class": "safety", + "given": "ucns.public_gonol is not normally importable", + "since": "2026-08-22", + "then": "construction records geometry as hmmm and does not mutate sys.path or fail base-package CI" + }, + "file": "edcm/gonol.py", + "id": "construction_survives_absent_ucns_geometry" + }, + { + "block": "CONTRACTS", + "fields": { + "class": "safety", + "given": "importable UCNS Public Gonol geometry has a digest different from the pinned identity", + "since": "2026-08-22", + "then": "construction raises rather than consuming or copying mismatched geometry" + }, + "file": "edcm/gonol.py", + "id": "geometry_mismatch_fails_closed" + }, + { + "block": "CONTRACTS", + "fields": { + "class": "construction", + "given": "a caller closes source evidence or closed gonol participants", + "since": "2026-08-22", + "then": "edcm.gonol uses the declared scale option set rather than dispatching through specialized ladder constructors" + }, + "file": "edcm/gonol.py", + "id": "single_constructor_uses_scale_option_sets" + }, + { + "block": "CONTRACTS", + "fields": { + "class": "doctrine", + "given": "a receipt is minted", + "since": "2026-08-22", + "then": "standing is implemented-candidate, selection_effect is none, and measurement, UCNS operation, and METAPAT promotion remain nonclaims" + }, + "file": "edcm/gonol.py", + "id": "unified_candidate_does_not_select_canon" + }, + { + "block": "MODULE_BUILD", + "fields": { + "admin_only": "false", + "auth_boundary": "EDCM owns text-domain closure; UCNS Public Gonol geometry is optional observation unless normally importable with matching digest; METAPAT affixiation semantics are consumed, not redefined", + "internal_surface": "_option_set, _require_text, _source_units, _load_optional_public_gonol, _geometry_observation, _participant_payload, _atomic_payload, _receipt_payload, _digest", + "module_kind": "engine", + "module_name": "gonol", + "network_boundary": "none", + "owner": "Erin Spencer", + "public_surface": "CONSTRUCTOR_ID, CONSTRUCTOR_VERSION, PINNED_PUBLIC_GONOL_SHA256, ScaleOptionSet, ClosedGonol, GonolReceipt, GonolConstructionError, SCALE_OPTION_SETS, construct_gonol, replay_gonol, canonical_receipt_bytes", + "requires": "none", + "rollback": "remove this module; historical lexical-floor and UCNS observation adapters remain unchanged", + "rollout": "explicit candidate constructor; no canon selection, measurement activation, UCNS function operation, or Mobius coupling promotion", + "since": "2026-08-22", + "storage_boundary": "none; receipts remain caller-owned in-memory objects", + "summary": "unified EDCM candidate constructor that closes gonols through declared scale option sets while preserving closed-gonol atomicity, deterministic replay, and UCNS/METAPAT authority boundaries", + "tests": "tests.test_gonol_constructor", + "unresolved": "exact UCNS geometric operation of Public Gonol function positions; Mobius-carrier affixiation/coupling law; which scales and relations are later selected; complete English morphology law", + "user_data_boundary": "caller-supplied source, relation, participants, and source_id remain in memory and are not transmitted" + }, + "file": "edcm/gonol.py", + "id": "edcm_gonol" + }, { "block": "MODULE_BUILD", "fields": { @@ -1852,6 +1931,66 @@ export default defineMsdmdCollection({ "file": "tests/test_goal_vector_experiment.py", "id": "check_goal_vector_sealed_evidence" }, + { + "block": "CHECKS", + "fields": { + "call": "self::test_closed_gonols_participate_directly_without_ladder", + "cleanup": "none", + "mutates": "none", + "proves": "closed_gonol_atomic_at_any_scale", + "timeout": "30" + }, + "file": "tests/test_gonol_constructor.py", + "id": "closed_gonol_atomic_at_any_scale_check" + }, + { + "block": "CHECKS", + "fields": { + "call": "self::test_base_construction_survives_absent_ucns_without_sys_path_mutation", + "cleanup": "none", + "mutates": "none", + "proves": "construction_survives_absent_ucns_geometry", + "timeout": "30" + }, + "file": "tests/test_gonol_constructor.py", + "id": "construction_survives_absent_ucns_geometry_check" + }, + { + "block": "CHECKS", + "fields": { + "call": "self::test_digest_mismatch_fails_closed", + "cleanup": "none", + "mutates": "none", + "proves": "geometry_mismatch_fails_closed", + "timeout": "30" + }, + "file": "tests/test_gonol_constructor.py", + "id": "geometry_mismatch_fails_closed_check" + }, + { + "block": "CHECKS", + "fields": { + "call": "self::test_constructor_uses_declared_scale_option_set", + "cleanup": "none", + "mutates": "none", + "proves": "single_constructor_uses_scale_option_sets", + "timeout": "30" + }, + "file": "tests/test_gonol_constructor.py", + "id": "single_constructor_uses_scale_option_sets_check" + }, + { + "block": "CHECKS", + "fields": { + "call": "self::test_receipt_remains_candidate", + "cleanup": "none", + "mutates": "none", + "proves": "unified_candidate_does_not_select_canon", + "timeout": "30" + }, + "file": "tests/test_gonol_constructor.py", + "id": "unified_candidate_does_not_select_canon_check" + }, { "block": "CHECKS", "fields": { @@ -4663,6 +4802,48 @@ export default defineMsdmdCollection({ "source_id": "check_ucns_edcm_v4_resolvers", "to": "python3" }, + { + "from": "closed_gonol_atomic_at_any_scale_check", + "kind": "calls", + "source_block": "CHECKS", + "source_id": "closed_gonol_atomic_at_any_scale_check", + "to": "self::test_closed_gonols_participate_directly_without_ladder" + }, + { + "from": "closed_gonol_atomic_at_any_scale_check", + "kind": "claims_proves", + "source_block": "CHECKS", + "source_id": "closed_gonol_atomic_at_any_scale_check", + "to": "closed_gonol_atomic_at_any_scale" + }, + { + "from": "construction_survives_absent_ucns_geometry_check", + "kind": "calls", + "source_block": "CHECKS", + "source_id": "construction_survives_absent_ucns_geometry_check", + "to": "self::test_base_construction_survives_absent_ucns_without_sys_path_mutation" + }, + { + "from": "construction_survives_absent_ucns_geometry_check", + "kind": "claims_proves", + "source_block": "CHECKS", + "source_id": "construction_survives_absent_ucns_geometry_check", + "to": "construction_survives_absent_ucns_geometry" + }, + { + "from": "geometry_mismatch_fails_closed_check", + "kind": "calls", + "source_block": "CHECKS", + "source_id": "geometry_mismatch_fails_closed_check", + "to": "self::test_digest_mismatch_fails_closed" + }, + { + "from": "geometry_mismatch_fails_closed_check", + "kind": "claims_proves", + "source_block": "CHECKS", + "source_id": "geometry_mismatch_fails_closed_check", + "to": "geometry_mismatch_fails_closed" + }, { "from": "language_relational_branch_check", "kind": "calls", @@ -4747,6 +4928,34 @@ export default defineMsdmdCollection({ "source_id": "oewn_builder_order_check", "to": "oewn_source_is_exact_pinned_and_resumable" }, + { + "from": "single_constructor_uses_scale_option_sets_check", + "kind": "calls", + "source_block": "CHECKS", + "source_id": "single_constructor_uses_scale_option_sets_check", + "to": "self::test_constructor_uses_declared_scale_option_set" + }, + { + "from": "single_constructor_uses_scale_option_sets_check", + "kind": "claims_proves", + "source_block": "CHECKS", + "source_id": "single_constructor_uses_scale_option_sets_check", + "to": "single_constructor_uses_scale_option_sets" + }, + { + "from": "unified_candidate_does_not_select_canon_check", + "kind": "calls", + "source_block": "CHECKS", + "source_id": "unified_candidate_does_not_select_canon_check", + "to": "self::test_receipt_remains_candidate" + }, + { + "from": "unified_candidate_does_not_select_canon_check", + "kind": "claims_proves", + "source_block": "CHECKS", + "source_id": "unified_candidate_does_not_select_canon_check", + "to": "unified_candidate_does_not_select_canon" + }, { "from": "edcm_ucns_fork_lint_docs", "kind": "covers", @@ -4831,6 +5040,20 @@ export default defineMsdmdCollection({ "source_id": "edcm_goal_vector_experiment", "to": "edcm_ucns_adapter" }, + { + "from": "edcm_gonol", + "kind": "owns", + "source_block": "MODULE_BUILD", + "source_id": "edcm_gonol", + "to": "Erin Spencer" + }, + { + "from": "edcm_gonol", + "kind": "requires", + "source_block": "MODULE_BUILD", + "source_id": "edcm_gonol", + "to": "none" + }, { "from": "edcm_integrity", "kind": "owns", diff --git a/tests/test_character_word_gonol.py b/tests/test_character_word_gonol.py deleted file mode 100644 index af07d4f..0000000 --- a/tests/test_character_word_gonol.py +++ /dev/null @@ -1,139 +0,0 @@ -# === CHECKS === -# id: character_admission_is_unicode_scalar_check -# proves: character_admission_is_unicode_scalar -# call: self::test_every_unicode_scalar_is_a_character_gonol -# timeout: 30 -# mutates: none -# cleanup: none -# -# id: word_closure_uses_declared_whitespace_check -# proves: word_closure_uses_declared_whitespace -# call: self::test_whitespace_closes_words_and_keeps_boundaries -# timeout: 30 -# mutates: none -# cleanup: none -# -# id: closed_words_preserve_constituents_check -# proves: closed_words_preserve_constituents -# call: self::test_closed_word_keeps_apostrophe_and_constituents -# timeout: 30 -# mutates: none -# cleanup: none -# -# id: geometry_mismatch_fails_closed_check -# proves: geometry_mismatch_fails_closed -# call: self::test_digest_mismatch_fails_closed -# timeout: 30 -# mutates: none -# cleanup: none -# -# id: candidate_does_not_select_canon_check -# proves: candidate_does_not_select_canon -# call: self::test_receipt_remains_unimplemented_canon -# timeout: 30 -# mutates: none -# cleanup: none -# === END CHECKS === - -from __future__ import annotations - -from types import SimpleNamespace -import unittest -from unittest.mock import patch - -from edcm.character_word import ( - CONSTRUCTOR_ID, - CONSTRUCTOR_VERSION, - PINNED_PUBLIC_GONOL_SHA256, - CharacterWordError, - construct_character_word_gonols, - replay_character_word_gonols, -) - - -class CharacterWordGonolTest(unittest.TestCase): - def test_every_unicode_scalar_is_a_character_gonol(self) -> None: - receipt = construct_character_word_gonols("Aé ", source_id="fixture-scalars") - self.assertEqual([item.scalar for item in receipt.characters], ["A", "é", " "]) - self.assertEqual([item.occurrence for item in receipt.characters], [0, 1, 2]) - self.assertEqual(receipt.characters[0].carrier_index, 1) - self.assertIsNone(receipt.characters[1].carrier_index) - self.assertEqual(receipt.characters[2].carrier_index, 0) - self.assertEqual(receipt.carrier_digest, PINNED_PUBLIC_GONOL_SHA256) - - def test_whitespace_closes_words_and_keeps_boundaries(self) -> None: - receipt = construct_character_word_gonols(" word gonol\n", source_id="fixture-space") - self.assertEqual(["".join(word.kind_id) for word in receipt.words], ["word", "gonol"]) - self.assertEqual([item.scalar for item in receipt.boundaries], [" ", " ", " ", " ", "\n"]) - self.assertEqual(receipt.words[0].source_start, 2) - self.assertEqual(receipt.words[0].source_end, 6) - self.assertEqual(receipt.words[1].source_start, 8) - self.assertEqual(receipt.words[1].source_end, 13) - - def test_closed_word_keeps_apostrophe_and_constituents(self) -> None: - receipt = construct_character_word_gonols("don't cut.", source_id="fixture-dont") - self.assertEqual(len(receipt.words), 2) - dont = receipt.words[0] - self.assertEqual(dont.kind_id, ("d", "o", "n", "'", "t")) - self.assertEqual([item.role for item in dont.characters], ["word-member"] * 5) - self.assertEqual(dont.characters[3].scalar, "'") - self.assertIsNotNone(dont.characters[3].carrier_index) - cut = receipt.words[1] - self.assertEqual(cut.kind_id, ("c", "u", "t", ".")) - self.assertEqual(receipt.boundaries[0].scalar, " ") - - def test_empty_source_is_complete_and_empty(self) -> None: - receipt = construct_character_word_gonols("", source_id="fixture-empty") - self.assertEqual(receipt.source_length, 0) - self.assertEqual(receipt.characters, ()) - self.assertEqual(receipt.words, ()) - self.assertEqual(receipt.boundaries, ()) - - def test_replay_matches_byte_identity(self) -> None: - source = "don't cut." - first = construct_character_word_gonols(source, source_id="fixture-replay") - second = replay_character_word_gonols(source, source_id="fixture-replay") - self.assertEqual(first.receipt_digest, second.receipt_digest) - self.assertEqual( - [item.scalar for item in first.characters], - [item.scalar for item in second.characters], - ) - self.assertEqual( - [word.kind_id for word in first.words], - [word.kind_id for word in second.words], - ) - - def test_receipt_remains_unimplemented_canon(self) -> None: - receipt = construct_character_word_gonols("a", source_id="fixture-standing") - self.assertEqual(receipt.constructor_id, CONSTRUCTOR_ID) - self.assertEqual(receipt.constructor_version, CONSTRUCTOR_VERSION) - self.assertEqual(receipt.standing, "implemented-candidate") - self.assertEqual(receipt.selection_effect, "none") - self.assertIn("not selected canon", receipt.nonclaims) - self.assertIn("not EDCM measurement validity", receipt.nonclaims) - self.assertIn("which definition source, if any, is later selected", receipt.hmmm) - - def test_identity_is_structure_not_hash(self) -> None: - receipt = construct_character_word_gonols("aa", source_id="fixture-identity") - self.assertEqual(receipt.characters[0].kind_id, "a") - self.assertEqual(receipt.characters[1].kind_id, "a") - self.assertNotEqual(receipt.characters[0].occurrence, receipt.characters[1].occurrence) - self.assertEqual(receipt.words[0].kind_id, ("a", "a")) - self.assertNotEqual(receipt.receipt_digest, receipt.words[0].kind_id) - - def test_digest_mismatch_fails_closed(self) -> None: - fake = SimpleNamespace( - PUBLIC_GONOL_SHA256="0" * 64, - public_gonol_position=lambda _glyph: 0, - ) - with patch("edcm.character_word.importlib.import_module", return_value=fake): - with self.assertRaisesRegex(CharacterWordError, "digest mismatch"): - construct_character_word_gonols("a", source_id="fixture-mismatch") - - def test_surrogate_fails_closed(self) -> None: - with self.assertRaisesRegex(CharacterWordError, "surrogate"): - construct_character_word_gonols("\ud800", source_id="fixture-surrogate") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_definition_gonol.py b/tests/test_definition_gonol.py deleted file mode 100644 index 796bb25..0000000 --- a/tests/test_definition_gonol.py +++ /dev/null @@ -1,164 +0,0 @@ -# === CHECKS === -# id: definition_uses_closed_word_gonols_check -# proves: definition_uses_closed_word_gonols -# call: self::test_definition_uses_closed_word_gonols_without_reopening -# timeout: 30 -# mutates: none -# cleanup: none -# -# id: definition_requires_exact_source_evidence_check -# proves: definition_requires_exact_source_evidence -# call: self::test_empty_definition_fails_closed -# timeout: 30 -# mutates: none -# cleanup: none -# -# id: definition_relation_is_intrinsic_check -# proves: definition_relation_is_intrinsic -# call: self::test_definition_relation_binds_headword_and_body_words -# timeout: 30 -# mutates: none -# cleanup: none -# -# id: definition_candidate_does_not_select_canon_check -# proves: definition_candidate_does_not_select_canon -# call: self::test_receipt_remains_unimplemented_canon -# timeout: 30 -# mutates: none -# cleanup: none -# === END CHECKS === - -from __future__ import annotations - -import unittest - -from edcm.character_word import construct_character_word_gonols -from edcm.definition_gonol import ( - CONSTRUCTOR_ID, - CONSTRUCTOR_VERSION, - RELATION, - DefinitionGonolError, - construct_definition_gonol, - replay_definition_gonol, -) - - -class DefinitionGonolTest(unittest.TestCase): - def test_definition_uses_closed_word_gonols_without_reopening(self) -> None: - receipt = construct_definition_gonol( - headword="don't", - definition="do not", - source_id="fixture:dont#1", - ) - independent = construct_character_word_gonols("don't", source_id="fixture:dont#1#headword") - self.assertEqual(len(receipt.definition.headword_words), 1) - self.assertEqual(receipt.definition.headword_words[0].kind_id, independent.words[0].kind_id) - self.assertEqual( - receipt.definition.headword_words[0].characters, - independent.words[0].characters, - ) - self.assertEqual(receipt.headword_receipt.receipt_digest, independent.receipt_digest) - self.assertEqual( - ["".join(word.kind_id) for word in receipt.definition.body_words], - ["do", "not"], - ) - - def test_definition_relation_binds_headword_and_body_words(self) -> None: - receipt = construct_definition_gonol( - headword="ice cream", - definition="a frozen dessert", - source_id="fixture:ice-cream#1", - ) - self.assertEqual(receipt.definition.relation, RELATION) - self.assertEqual( - ["".join(word.kind_id) for word in receipt.definition.headword_words], - ["ice", "cream"], - ) - self.assertEqual( - ["".join(word.kind_id) for word in receipt.definition.body_words], - ["a", "frozen", "dessert"], - ) - self.assertEqual( - receipt.definition.kind_id, - ( - (("i", "c", "e"), ("c", "r", "e", "a", "m")), - ( - ("a",), - ("f", "r", "o", "z", "e", "n"), - ("d", "e", "s", "s", "e", "r", "t"), - ), - ), - ) - - def test_empty_definition_fails_closed(self) -> None: - with self.assertRaisesRegex(DefinitionGonolError, "definition produced no closed word"): - construct_definition_gonol( - headword="cut", - definition=" ", - source_id="fixture:cut#empty", - ) - - def test_empty_headword_fails_closed(self) -> None: - with self.assertRaisesRegex(DefinitionGonolError, "headword produced no closed word"): - construct_definition_gonol( - headword="\n", - definition="to divide", - source_id="fixture:cut#no-head", - ) - - def test_missing_source_id_fails_closed(self) -> None: - with self.assertRaisesRegex(DefinitionGonolError, "source_id"): - construct_definition_gonol(headword="cut", definition="to divide", source_id="") - - def test_senses_remain_separate_gonols(self) -> None: - first = construct_definition_gonol( - headword="cut", - definition="to divide with a sharp edge", - source_id="fixture:cut#1", - ) - second = construct_definition_gonol( - headword="cut", - definition="to reduce", - source_id="fixture:cut#2", - ) - self.assertEqual(first.definition.headword_words[0].kind_id, second.definition.headword_words[0].kind_id) - self.assertNotEqual(first.definition.kind_id, second.definition.kind_id) - self.assertNotEqual(first.receipt_digest, second.receipt_digest) - - def test_replay_matches_byte_identity(self) -> None: - kwargs = { - "headword": "cut", - "definition": "to divide with a sharp edge", - "source_id": "fixture:cut#replay", - } - first = construct_definition_gonol(**kwargs) - second = replay_definition_gonol(**kwargs) - self.assertEqual(first.receipt_digest, second.receipt_digest) - self.assertEqual(first.definition.kind_id, second.definition.kind_id) - - def test_receipt_remains_unimplemented_canon(self) -> None: - receipt = construct_definition_gonol( - headword="cut", - definition="to divide", - source_id="fixture:cut#standing", - ) - self.assertEqual(receipt.constructor_id, CONSTRUCTOR_ID) - self.assertEqual(receipt.constructor_version, CONSTRUCTOR_VERSION) - self.assertEqual(receipt.standing, "implemented-candidate") - self.assertEqual(receipt.selection_effect, "none") - self.assertIn("not a dictionary or sense inventory", receipt.nonclaims) - self.assertIn("not OEWN or lexical-floor revival", receipt.nonclaims) - self.assertIn("which recursive relations, if any, are later selected", receipt.hmmm) - - def test_identity_is_structure_not_hash(self) -> None: - receipt = construct_definition_gonol( - headword="a", - definition="b", - source_id="fixture:id", - ) - self.assertEqual(receipt.definition.kind_id, ((("a",),), (("b",),))) - self.assertNotEqual(receipt.receipt_digest, receipt.definition.kind_id) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_gonol_constructor.py b/tests/test_gonol_constructor.py new file mode 100644 index 0000000..f50e7bd --- /dev/null +++ b/tests/test_gonol_constructor.py @@ -0,0 +1,173 @@ +# === CHECKS === +# id: single_constructor_uses_scale_option_sets_check +# proves: single_constructor_uses_scale_option_sets +# call: self::test_constructor_uses_declared_scale_option_set +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: closed_gonol_atomic_at_any_scale_check +# proves: closed_gonol_atomic_at_any_scale +# call: self::test_closed_gonols_participate_directly_without_ladder +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: construction_survives_absent_ucns_geometry_check +# proves: construction_survives_absent_ucns_geometry +# call: self::test_base_construction_survives_absent_ucns_without_sys_path_mutation +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: geometry_mismatch_fails_closed_check +# proves: geometry_mismatch_fails_closed +# call: self::test_digest_mismatch_fails_closed +# timeout: 30 +# mutates: none +# cleanup: none +# +# id: unified_candidate_does_not_select_canon_check +# proves: unified_candidate_does_not_select_canon +# call: self::test_receipt_remains_candidate +# timeout: 30 +# mutates: none +# cleanup: none +# === END CHECKS === + +from __future__ import annotations + +import sys +from types import SimpleNamespace +import unittest +from unittest.mock import patch + +from edcm.gonol import ( + CONSTRUCTOR_ID, + CONSTRUCTOR_VERSION, + PINNED_PUBLIC_GONOL_SHA256, + SCALE_OPTION_SETS, + GonolConstructionError, + construct_gonol, + replay_gonol, +) + + +class GonolConstructorTest(unittest.TestCase): + def test_constructor_uses_declared_scale_option_set(self) -> None: + receipt = construct_gonol(scale="word", source="cut", source_id="fixture:cut") + self.assertEqual(receipt.constructor_id, CONSTRUCTOR_ID) + self.assertEqual(receipt.constructor_version, CONSTRUCTOR_VERSION) + self.assertEqual(receipt.option_set, SCALE_OPTION_SETS["word"]) + self.assertEqual(receipt.gonol.scale, "word") + self.assertEqual(receipt.gonol.source_units, ("c", "u", "t")) + self.assertEqual(receipt.gonol.relation, "word-closure") + + def test_base_construction_survives_absent_ucns_without_sys_path_mutation(self) -> None: + before = tuple(sys.path) + with patch("edcm.gonol.importlib.import_module", side_effect=ImportError("no ucns")): + receipt = construct_gonol(scale="word", source="cut", source_id="fixture:no-ucns") + self.assertEqual(tuple(sys.path), before) + self.assertEqual(receipt.geometry["state"], "hmmm") + self.assertEqual(receipt.geometry["reason"], "ucns.public_gonol not normally importable") + self.assertEqual(receipt.gonol.kind_id[0], "word") + + def test_importable_geometry_is_observed_without_operation_claim(self) -> None: + fake = SimpleNamespace( + PUBLIC_GONOL_SHA256=PINNED_PUBLIC_GONOL_SHA256, + public_gonol_position=lambda glyph: {" ": 0, "A": 1}.get(glyph), + ) + with patch("edcm.gonol.importlib.import_module", return_value=fake): + receipt = construct_gonol(scale="character", source="A", source_id="fixture:A") + self.assertEqual(receipt.geometry["state"], "observed") + self.assertEqual(receipt.geometry["positions"], [1]) + self.assertIn("not a UCNS geometric function operation", receipt.nonclaims) + self.assertIn("exact UCNS geometric operation", receipt.hmmm[0]) + + def test_digest_mismatch_fails_closed(self) -> None: + fake = SimpleNamespace( + PUBLIC_GONOL_SHA256="0" * 64, + public_gonol_position=lambda _glyph: 0, + ) + with patch("edcm.gonol.importlib.import_module", return_value=fake): + with self.assertRaisesRegex(GonolConstructionError, "digest mismatch"): + construct_gonol(scale="character", source="A", source_id="fixture:mismatch") + + def test_closed_gonols_participate_directly_without_ladder(self) -> None: + character = construct_gonol(scale="character", source="c", source_id="fixture:c") + word = construct_gonol(scale="word", source="cut", source_id="fixture:cut") + definition = construct_gonol( + scale="definition", + relation="fixture:defines-directly", + participants=(character.gonol, word.gonol), + source="direct cross-scale evidence", + source_id="fixture:def", + ) + self.assertEqual(definition.gonol.participants, (character.gonol, word.gonol)) + self.assertEqual([item.scale for item in definition.gonol.participants], ["character", "word"]) + self.assertNotIn("mandatory character-word-definition-recursive ladder", definition.receipt_digest) + self.assertIn("not a mandatory character-word-definition-recursive ladder", definition.nonclaims) + + def test_recursive_requires_relation_and_two_closed_participants(self) -> None: + word = construct_gonol(scale="word", source="cut", source_id="fixture:one") + with self.assertRaisesRegex(GonolConstructionError, "relation must be exact"): + construct_gonol(scale="recursive", participants=(word.gonol, word.gonol), source_id="bad") + with self.assertRaisesRegex(GonolConstructionError, "at least two closed participants"): + construct_gonol( + scale="recursive", + relation="fixture:loop", + participants=(word.gonol,), + source_id="bad-one", + ) + + def test_order_and_multiplicity_are_preserved_in_atomic_identity(self) -> None: + a = construct_gonol(scale="character", source="a", source_id="fixture:a") + b = construct_gonol(scale="character", source="b", source_id="fixture:b") + first = construct_gonol( + scale="recursive", + relation="fixture:sequence", + participants=(a.gonol, a.gonol, b.gonol), + source_id="fixture:seq-aab", + ) + second = construct_gonol( + scale="recursive", + relation="fixture:sequence", + participants=(a.gonol, b.gonol, a.gonol), + source_id="fixture:seq-aba", + ) + self.assertNotEqual(first.gonol.atomic_id, second.gonol.atomic_id) + self.assertEqual(first.gonol.participants[0], first.gonol.participants[1]) + self.assertEqual([item.source_id for item in first.gonol.participants], ["fixture:a", "fixture:a", "fixture:b"]) + + def test_replay_matches_byte_identity(self) -> None: + word = construct_gonol(scale="word", source="cut", source_id="fixture:replay-word") + kwargs = { + "scale": "recursive", + "relation": "fixture:pair", + "participants": (word.gonol, word.gonol), + "source_id": "fixture:replay", + } + first = construct_gonol(**kwargs) + second = replay_gonol(**kwargs) + self.assertEqual(first.receipt_digest, second.receipt_digest) + self.assertEqual(first.gonol.atomic_id, second.gonol.atomic_id) + + def test_character_and_word_validation_fail_closed(self) -> None: + with self.assertRaisesRegex(GonolConstructionError, "exactly one Unicode scalar"): + construct_gonol(scale="character", source="ab", source_id="fixture:bad-char") + with self.assertRaisesRegex(GonolConstructionError, "surrogate"): + construct_gonol(scale="character", source="\ud800", source_id="fixture:bad-surrogate") + with self.assertRaisesRegex(GonolConstructionError, "whitespace-delimited"): + construct_gonol(scale="word", source="two words", source_id="fixture:bad-word") + + def test_receipt_remains_candidate(self) -> None: + receipt = construct_gonol(scale="word", source="cut", source_id="fixture:standing") + self.assertEqual(receipt.standing, "implemented-candidate") + self.assertEqual(receipt.selection_effect, "none") + self.assertIn("not selected canon", receipt.nonclaims) + self.assertIn("not EDCM measurement validity", receipt.nonclaims) + self.assertIn("which scales and relations, if any, are later selected", receipt.hmmm) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_gonol_language_authority.py b/tests/test_gonol_language_authority.py index e3aba19..e412d2c 100644 --- a/tests/test_gonol_language_authority.py +++ b/tests/test_gonol_language_authority.py @@ -32,12 +32,12 @@ def test_live_surfaces_route_gonol_authority_consistently() -> None: assert "UCNS owns any exact geometric realization" in boundary assert "EDCM applies affixiation to text-domain gonols" in boundary for text in (boundary, canon, agents): - assert "characters" in text and "words" in text - assert "This order is load-bearing" in boundary - assert "edcm.character_word" in boundary - assert "edcm.definition_gonol" in boundary - assert "edcm.recursive_gonol" in boundary - assert "Morphology is not a required stage" in boundary + assert "scale option sets" in text + assert "edcm.gonol" in text + assert "Once closed, a gonol is atomic at any scale" in text + assert "mandatory adjacent-scale ladder" in boundary + assert "Closed gonols may participate directly at any admissible scale" in boundary + assert "does not mutate `sys.path`" in boundary def test_live_surfaces_do_not_restore_ucns_language_ownership() -> None: diff --git a/tests/test_recursive_gonol.py b/tests/test_recursive_gonol.py deleted file mode 100644 index b843eb9..0000000 --- a/tests/test_recursive_gonol.py +++ /dev/null @@ -1,170 +0,0 @@ -# === CHECKS === -# id: recursive_uses_closed_gonols_check -# proves: recursive_uses_closed_gonols -# call: self::test_recursive_uses_closed_words_without_reopening -# timeout: 30 -# mutates: none -# cleanup: none -# -# id: recursive_relation_is_caller_supplied_check -# proves: recursive_relation_is_caller_supplied -# call: self::test_missing_relation_fails_closed -# timeout: 30 -# mutates: none -# cleanup: none -# -# id: recursive_result_may_participate_check -# proves: recursive_result_may_participate -# call: self::test_closed_recursive_gonol_may_participate_again -# timeout: 30 -# mutates: none -# cleanup: none -# -# id: recursive_candidate_does_not_select_canon_check -# proves: recursive_candidate_does_not_select_canon -# call: self::test_receipt_remains_unimplemented_canon -# timeout: 30 -# mutates: none -# cleanup: none -# === END CHECKS === - -from __future__ import annotations - -import unittest - -from edcm.character_word import construct_character_word_gonols -from edcm.definition_gonol import construct_definition_gonol -from edcm.recursive_gonol import ( - CONSTRUCTOR_ID, - CONSTRUCTOR_VERSION, - RecursiveGonolError, - construct_recursive_gonol, - replay_recursive_gonol, -) - - -class RecursiveGonolTest(unittest.TestCase): - def test_recursive_uses_closed_words_without_reopening(self) -> None: - words = construct_character_word_gonols("cut divide", source_id="fixture:words") - receipt = construct_recursive_gonol( - relation="fixture:ordered-pair", - participants=words.words, - source_id="fixture:pair#1", - ) - self.assertEqual(receipt.recursive.participants, words.words) - self.assertEqual(receipt.recursive.participants[0].characters, words.words[0].characters) - self.assertEqual( - ["".join(word.kind_id) for word in receipt.recursive.participants], - ["cut", "divide"], - ) - - def test_relation_is_not_inferred_from_adjacency(self) -> None: - words = construct_character_word_gonols("cut divide", source_id="fixture:words") - first = construct_recursive_gonol( - relation="fixture:ordered-pair", - participants=words.words, - source_id="fixture:pair#1", - ) - second = construct_recursive_gonol( - relation="fixture:contrast", - participants=words.words, - source_id="fixture:pair#2", - ) - self.assertEqual(first.recursive.participants, second.recursive.participants) - self.assertNotEqual(first.recursive.kind_id, second.recursive.kind_id) - self.assertNotEqual(first.receipt_digest, second.receipt_digest) - - def test_definition_gonol_may_participate(self) -> None: - definition = construct_definition_gonol( - headword="cut", - definition="to divide", - source_id="fixture:cut#1", - ) - other = construct_character_word_gonols("edge", source_id="fixture:edge").words[0] - receipt = construct_recursive_gonol( - relation="fixture:uses", - participants=(definition.definition, other), - source_id="fixture:uses#1", - ) - self.assertEqual(receipt.recursive.participants[0], definition.definition) - self.assertEqual(receipt.recursive.participants[1], other) - - def test_closed_recursive_gonol_may_participate_again(self) -> None: - words = construct_character_word_gonols("cut divide edge", source_id="fixture:three") - inner = construct_recursive_gonol( - relation="fixture:ordered-pair", - participants=words.words[:2], - source_id="fixture:inner", - ) - outer = construct_recursive_gonol( - relation="fixture:attach", - participants=(inner.recursive, words.words[2]), - source_id="fixture:outer", - ) - self.assertEqual(outer.recursive.participants[0], inner.recursive) - self.assertEqual(outer.recursive.participants[1], words.words[2]) - self.assertEqual(outer.recursive.kind_id[0], "fixture:attach") - - def test_missing_relation_fails_closed(self) -> None: - words = construct_character_word_gonols("cut divide", source_id="fixture:words") - with self.assertRaisesRegex(RecursiveGonolError, "relation must be an exact"): - construct_recursive_gonol(relation=" ", participants=words.words, source_id="fixture:bad") - - def test_single_participant_fails_closed(self) -> None: - words = construct_character_word_gonols("cut", source_id="fixture:one") - with self.assertRaisesRegex(RecursiveGonolError, "at least two closed gonols"): - construct_recursive_gonol( - relation="fixture:loop", - participants=words.words, - source_id="fixture:loop", - ) - - def test_character_gonol_fails_closed(self) -> None: - words = construct_character_word_gonols("cut", source_id="fixture:char") - with self.assertRaisesRegex(RecursiveGonolError, "character gonols are not recursive"): - construct_recursive_gonol( - relation="fixture:bad", - participants=(words.characters[0], words.words[0]), - source_id="fixture:bad-char", - ) - - def test_replay_matches_byte_identity(self) -> None: - words = construct_character_word_gonols("cut divide", source_id="fixture:replay") - kwargs = { - "relation": "fixture:ordered-pair", - "participants": words.words, - "source_id": "fixture:replay#1", - } - first = construct_recursive_gonol(**kwargs) - second = replay_recursive_gonol(**kwargs) - self.assertEqual(first.receipt_digest, second.receipt_digest) - self.assertEqual(first.recursive.kind_id, second.recursive.kind_id) - - def test_receipt_remains_unimplemented_canon(self) -> None: - words = construct_character_word_gonols("cut divide", source_id="fixture:stand") - receipt = construct_recursive_gonol( - relation="fixture:ordered-pair", - participants=words.words, - source_id="fixture:stand#1", - ) - self.assertEqual(receipt.constructor_id, CONSTRUCTOR_ID) - self.assertEqual(receipt.constructor_version, CONSTRUCTOR_VERSION) - self.assertEqual(receipt.standing, "implemented-candidate") - self.assertEqual(receipt.selection_effect, "none") - self.assertIn("not a UCNS geometric coupling law", receipt.nonclaims) - self.assertIn("UCNS Möbius-carrier affixiation/coupling law", receipt.hmmm) - - def test_identity_is_structure_not_hash(self) -> None: - words = construct_character_word_gonols("a b", source_id="fixture:id") - receipt = construct_recursive_gonol( - relation="r", - participants=words.words, - source_id="fixture:id#1", - ) - self.assertEqual(receipt.recursive.kind_id[0], "r") - self.assertEqual(receipt.recursive.kind_id[1][0][0], "word") - self.assertNotEqual(receipt.receipt_digest, receipt.recursive.kind_id) - - -if __name__ == "__main__": - unittest.main() From 6a0f64fe16e7bb95e2a27ef3671092fb367af756 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 22 Aug 2026 03:55:53 +0000 Subject: [PATCH 5/6] Carry suffix coupling options on closed suffix gonols --- CANON.md | 4 +- README.md | 2 +- docs/GONOL_LANGUAGE_BOUNDARY.md | 22 +++++- docs/integrity-gates.md | 4 +- edcm/gonol.py | 114 ++++++++++++++++++++++++++++---- edcm_msdmd.ts | 41 +++++++++++- tests/test_gonol_constructor.py | 83 +++++++++++++++++++++++ 7 files changed, 247 insertions(+), 23 deletions(-) diff --git a/CANON.md b/CANON.md index faaf001..f1ceba1 100644 --- a/CANON.md +++ b/CANON.md @@ -56,9 +56,9 @@ every admitted character is a gonol This is an EDCM text-domain rule. EDCM owns the declared source/profile that determines what is admitted as a character. If a construction has not yet selected its exact unit—Unicode code point, grapheme, Public Gonol glyph inventory, or another explicit unit—that admission boundary remains `hmmm`. UCNS may realize admitted characters geometrically; UCNS does not own the linguistic admission rule merely because it owns the geometry. -Current EDCM text construction uses declared scale option sets rather than a mandatory adjacent-scale ladder. `edcm.gonol` is the implemented candidate constructor for closing one gonol through a declared scale option set. Admissible scales may include character, word, definition, recursive relation, morphology, punctuation-function, source-specific, or later named scales when the governing construction declares their options. +Current EDCM text construction uses declared scale option sets rather than a mandatory adjacent-scale ladder. `edcm.gonol` is the implemented candidate constructor for closing one gonol through a declared scale option set. Admissible scales may include character, word, suffix, suffix-coupling, definition, recursive relation, morphology, punctuation-function, source-specific, or later named scales when the governing construction declares their options. -Once closed, a gonol is atomic at any scale. Closed gonols may participate directly at any admissible scale without reopening, while identity, order, multiplicity, source positions, relation identity, and provenance remain recoverable. Historical morphology evidence remains historical; the complete English morphology law remains `hmmm`. +Once closed, a gonol is atomic at any scale. Closed gonols may participate directly at any admissible scale without reopening, while identity, order, multiplicity, source positions, relation identity, and provenance remain recoverable. Suffix-coupling exceptions are carried by the closed suffix gonol, such as `ing` carrying `suffix-coupling.final-y-after-consonant = preserve-y`, rather than by a global morphology law. Historical morphology evidence remains historical; the complete English morphology law remains `hmmm`. Affixiation is defined by METAPAT, not by EDCM and not by UCNS. EDCM applies affixiation to text-domain gonols. UCNS owns any exact geometric realization on the native Möbius/Public Gonol carrier. An unresolved UCNS coupling law remains `hmmm`; neither EDCM nor UCNS may fill it by importing an invented carrier, topology, scale increment, arity rule, containment rule, or linguistic convention. diff --git a/README.md b/README.md index b2d0141..75b3b0e 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ every admitted character is a gonol EDCM owns the source/profile that determines what is admitted as a character. If a profile has not selected Unicode code point, grapheme, exact Public Gonol glyph inventory, or another explicit unit, that admission boundary remains `hmmm`; UCNS does not silently choose it merely because UCNS owns the geometry. -Current EDCM text construction uses declared scale option sets rather than a mandatory adjacent-scale ladder. `edcm.gonol` is the implemented candidate constructor for closing one gonol at an admissible scale. None of its scale options is selected canon, and construction does not activate measurement. UCNS coupling geometry remains `hmmm`. +Current EDCM text construction uses declared scale option sets rather than a mandatory adjacent-scale ladder. `edcm.gonol` is the implemented candidate constructor for closing one gonol at an admissible scale. None of its scale options is selected canon, and construction does not activate measurement. Suffix-coupling exceptions are carried by the closed suffix gonol, such as `ing` carrying `suffix-coupling.final-y-after-consonant = preserve-y`, rather than by a global morphology law. UCNS coupling geometry remains `hmmm`. Affixiation is defined by METAPAT. UCNS owns any exact geometric realization. EDCM applies affixiation to text-domain gonols. Once closed, a gonol is atomic at any scale. Closed gonols may participate directly at any admissible scale without reopening, while constituent identity and provenance remain recoverable. diff --git a/docs/GONOL_LANGUAGE_BOUNDARY.md b/docs/GONOL_LANGUAGE_BOUNDARY.md index 769b74b..76d9f57 100644 --- a/docs/GONOL_LANGUAGE_BOUNDARY.md +++ b/docs/GONOL_LANGUAGE_BOUNDARY.md @@ -50,7 +50,7 @@ Current EDCM text construction uses declared scale option sets rather than a man Once closed, a gonol is atomic at any scale. Closed gonols may participate directly at any admissible scale without reopening, while identity, order, multiplicity, source positions, relation identity, and provenance remain recoverable. -`edcm.gonol` is the implemented candidate constructor for closing one gonol through a declared scale option set. Its current option sets include character, word, definition, and recursive relation scales. None is selected canon. UCNS coupling geometry remains `hmmm`, and absent `ucns.public_gonol` does not prevent candidate construction. +`edcm.gonol` is the implemented candidate constructor for closing one gonol through a declared scale option set. Its current option sets include character, word, suffix, suffix-coupling, definition, and recursive relation scales. None is selected canon. UCNS coupling geometry remains `hmmm`, and absent `ucns.public_gonol` does not prevent candidate construction. Relationships that constitute a gonol enter the construction. They are not merely external semantic edges. Sidecars may index, cache, project, or record provenance; they do not replace intrinsic relational content. @@ -64,6 +64,8 @@ UCNS owns any exact geometric realization of that operation. The native Möbius/ EDCM applies affixiation to text-domain gonols. Linguistic prefixes and suffixes are one instance of affixiation; they do not define affixiation. +Suffix-coupling options that affect a suffix relation are carried by the relevant closed suffix gonol. For example, a closed `ing` suffix gonol may carry `suffix-coupling.final-y-after-consonant = preserve-y`; the suffix-coupling construction preserves that option through the suffix participant instead of storing an `ing` exception in a global morphology law or reopening the suffix. + The complete English root, stem, affix, irregular-transformation, and family law remains unresolved unless source-backed EDCM evidence establishes it. EDCM must not invent decomposition to complete a pipeline. ## Public Gonol function positions @@ -119,7 +121,13 @@ Copy-pasteable unified candidate: ```python from edcm.gonol import construct_gonol, replay_gonol -word = construct_gonol(scale="word", source="cut", source_id="example:cut") +word = construct_gonol(scale="word", source="try", source_id="example:try") +ing = construct_gonol( + scale="suffix", + source="ing", + source_id="example:ing", + carried_options=(("suffix-coupling.final-y-after-consonant", "preserve-y"),), +) definition = construct_gonol( scale="definition", relation="example:definition-evidence", @@ -133,12 +141,22 @@ recursive = construct_gonol( participants=(word.gonol, definition.gonol), source_id="example:relation#1", ) +suffix_coupling = construct_gonol( + scale="suffix-coupling", + participants=(word.gonol, ing.gonol), + source_id="example:trying#1", +) assert recursive.receipt_digest == replay_gonol( scale="recursive", relation="example:mentions", participants=(word.gonol, definition.gonol), source_id="example:relation#1", ).receipt_digest +assert suffix_coupling.receipt_digest == replay_gonol( + scale="suffix-coupling", + participants=(word.gonol, ing.gonol), + source_id="example:trying#1", +).receipt_digest ``` If `ucns.public_gonol` is normally importable and its digest matches the pinned Public Gonol identity, `edcm.gonol` observes source-unit positions. If it is absent, construction records geometry as `hmmm` and still closes the candidate. It does not mutate `sys.path`. diff --git a/docs/integrity-gates.md b/docs/integrity-gates.md index 7d65c9f..4993f26 100644 --- a/docs/integrity-gates.md +++ b/docs/integrity-gates.md @@ -76,7 +76,7 @@ This prevents a false green where source tests pass while packaged resources or ## Skill-lib and msdmd EDCM vendors a bounded build/evidence subset from -`The-Interdependency/skill-lib@2b24be24947223b86440f59f1bd9766130f9cc11`. +`The-Interdependency/skill-lib@a1c6a7124af537ee9937b6fc6084940091982fe5`. The skill-compliance workflow checks those files byte-for-byte, generates the canonical `edcm_msdmd.ts` collection, compares it with the tracked collection, and runs the EDCM-native metadata validator. @@ -86,7 +86,7 @@ Local validation: ```bash python /path/to/skill-lib/tools/check_consumer_drift.py . \ --canon-root /path/to/skill-lib \ - --sha 2b24be24947223b86440f59f1bd9766130f9cc11 \ + --sha a1c6a7124af537ee9937b6fc6084940091982fe5 \ --strict-sha --require-vendored PYTHONPATH=/path/to/skill-lib python /path/to/skill-lib/msdmd/collect.py \ diff --git a/edcm/gonol.py b/edcm/gonol.py index 3de586e..666c919 100644 --- a/edcm/gonol.py +++ b/edcm/gonol.py @@ -8,18 +8,22 @@ from edcm.gonol import construct_gonol, replay_gonol - word = construct_gonol(scale="word", source="cut", source_id="example:cut") + word = construct_gonol(scale="word", source="try", source_id="example:try") + ing = construct_gonol( + scale="suffix", + source="ing", + source_id="example:ing", + carried_options=(("suffix-coupling.final-y-after-consonant", "preserve-y"),), + ) rel = construct_gonol( - scale="recursive", - relation="example:pair", - participants=(word.gonol, word.gonol), - source_id="example:pair#1", + scale="suffix-coupling", + participants=(word.gonol, ing.gonol), + source_id="example:trying#1", ) assert rel.receipt_digest == replay_gonol( - scale="recursive", - relation="example:pair", - participants=(word.gonol, word.gonol), - source_id="example:pair#1", + scale="suffix-coupling", + participants=(word.gonol, ing.gonol), + source_id="example:trying#1", ).receipt_digest Frozen choices for ``edcm.gonol/v1``: @@ -32,6 +36,8 @@ folding, trimming, deduplication, or token substitution; - relation identity is exact caller-supplied text where the option set requires it; +- suffix-coupling exceptions are carried by the closed suffix gonol, not by a + global morphology law or by reopening the suffix during coupling; - UCNS Public Gonol geometry is consumed only when normally importable, and absence remains ``hmmm`` rather than a base-package failure; - no UCNS function operation or Mobius coupling law is invented. @@ -41,10 +47,10 @@ # id: edcm_gonol # module_name: gonol # module_kind: engine -# summary: unified EDCM candidate constructor that closes gonols through declared scale option sets while preserving closed-gonol atomicity, deterministic replay, and UCNS/METAPAT authority boundaries +# summary: unified EDCM candidate constructor that closes gonols through declared scale option sets while preserving closed-gonol atomicity, carried suffix options, deterministic replay, and UCNS/METAPAT authority boundaries # owner: Erin Spencer # public_surface: CONSTRUCTOR_ID, CONSTRUCTOR_VERSION, PINNED_PUBLIC_GONOL_SHA256, ScaleOptionSet, ClosedGonol, GonolReceipt, GonolConstructionError, SCALE_OPTION_SETS, construct_gonol, replay_gonol, canonical_receipt_bytes -# internal_surface: _option_set, _require_text, _source_units, _load_optional_public_gonol, _geometry_observation, _participant_payload, _atomic_payload, _receipt_payload, _digest +# internal_surface: _option_set, _require_text, _source_units, _closed_participants, _carried_option_pairs, _relation_value, _load_optional_public_gonol, _geometry_observation, _participant_payload, _atomic_payload, _receipt_payload, _digest # auth_boundary: EDCM owns text-domain closure; UCNS Public Gonol geometry is optional observation unless normally importable with matching digest; METAPAT affixiation semantics are consumed, not redefined # storage_boundary: none; receipts remain caller-owned in-memory objects # network_boundary: none @@ -71,6 +77,12 @@ # class: construction # since: 2026-08-22 # +# id: suffix_exception_carried_by_suffix_gonol +# given: suffix coupling has a final-y exception such as ing preserving y after a consonant +# then: the exception is stored on the closed suffix gonol participant and replayed through participant provenance rather than global morphology law +# class: construction +# since: 2026-08-22 +# # id: construction_survives_absent_ucns_geometry # given: ucns.public_gonol is not normally importable # then: construction records geometry as hmmm and does not mutate sys.path or fail base-package CI @@ -142,6 +154,7 @@ class ScaleOptionSet: default_relation: str | None closure_policy: str geometry_policy: str + carried_option_policy: str SCALE_OPTION_SETS: Mapping[str, ScaleOptionSet] = { @@ -154,6 +167,7 @@ class ScaleOptionSet: default_relation="admitted-character", closure_policy="close-one-source-unit", geometry_policy="observe-public-gonol-position-if-importable", + carried_option_policy="none", ), "word": ScaleOptionSet( scale="word", @@ -164,6 +178,29 @@ class ScaleOptionSet: default_relation="word-closure", closure_policy="close-declared-word-scale-object", geometry_policy="observe-public-gonol-positions-if-importable", + carried_option_policy="declared-on-closed-gonol", + ), + "suffix": ScaleOptionSet( + scale="suffix", + option_set_id="edcm.gonol.scale.suffix/v1", + source_policy="exact-source-string", + participant_policy="none", + relation_policy="declared-default", + default_relation="suffix-form", + closure_policy="close-declared-suffix-scale-object", + geometry_policy="observe-public-gonol-positions-if-importable", + carried_option_policy="declared-on-closed-suffix-gonol", + ), + "suffix-coupling": ScaleOptionSet( + scale="suffix-coupling", + option_set_id="edcm.gonol.scale.suffix-coupling/v1", + source_policy="optional-exact-source-evidence", + participant_policy="closed-base-and-closed-suffix-without-reopening", + relation_policy="caller-supplied-or-declared-default", + default_relation="suffix-coupling", + closure_policy="close-relation-over-atomic-base-and-suffix", + geometry_policy="observe-public-gonol-positions-if-importable", + carried_option_policy="consume-carried-options-from-suffix-participant", ), "definition": ScaleOptionSet( scale="definition", @@ -174,6 +211,7 @@ class ScaleOptionSet: default_relation="definition-evidence", closure_policy="close-declared-definition-scale-object", geometry_policy="observe-public-gonol-positions-if-importable", + carried_option_policy="declared-on-closed-gonol", ), "recursive": ScaleOptionSet( scale="recursive", @@ -184,6 +222,7 @@ class ScaleOptionSet: default_relation=None, closure_policy="close-relation-over-atomic-participants", geometry_policy="observe-public-gonol-positions-if-importable", + carried_option_policy="declared-on-closed-gonol", ), } @@ -199,6 +238,7 @@ class ClosedGonol: source_id: str source_units: tuple[str, ...] participants: tuple["ClosedGonol", ...] + carried_options: tuple[tuple[str, str], ...] atomic_id: str provenance: tuple[tuple[str, str], ...] @@ -253,8 +293,10 @@ def _source_units(source: str | None, *, options: ScaleOptionSet) -> tuple[str, raise GonolConstructionError("surrogate code points are not Unicode scalars") if options.scale == "character" and len(units) != 1: raise GonolConstructionError("character scale closes exactly one Unicode scalar") - if options.scale == "word" and any(unit.isspace() for unit in units): - raise GonolConstructionError("word scale source must be one closed source unit, not whitespace-delimited text") + if options.scale in {"word", "suffix"} and any(unit.isspace() for unit in units): + raise GonolConstructionError( + f"{options.scale} scale source must be one closed source unit, not whitespace-delimited text" + ) return units @@ -270,6 +312,26 @@ def _closed_participants(participants: Sequence[ClosedGonol] | None) -> tuple[Cl return closed +def _carried_option_pairs( + carried_options: Sequence[Sequence[str]] | None, +) -> tuple[tuple[str, str], ...]: + if carried_options is None: + return () + if not isinstance(carried_options, Sequence) or isinstance(carried_options, (str, bytes)): + raise GonolConstructionError("carried_options must be an ordered sequence of exact text pairs") + pairs: list[tuple[str, str]] = [] + for pair in carried_options: + if not isinstance(pair, Sequence) or isinstance(pair, (str, bytes)) or len(pair) != 2: + raise GonolConstructionError("each carried option must be an exact text pair") + key, value = pair + if not isinstance(key, str) or not key or key.isspace(): + raise GonolConstructionError("carried option key must be exact non-empty text") + if not isinstance(value, str) or not value or value.isspace(): + raise GonolConstructionError("carried option value must be exact non-empty text") + pairs.append((key, value)) + return tuple(pairs) + + def _relation_value(relation: str | None, *, options: ScaleOptionSet) -> str: if relation is None: if options.default_relation is None: @@ -332,6 +394,7 @@ def _option_payload(options: ScaleOptionSet) -> dict[str, str | None]: "default_relation": options.default_relation, "closure_policy": options.closure_policy, "geometry_policy": options.geometry_policy, + "carried_option_policy": options.carried_option_policy, } @@ -344,6 +407,7 @@ def _participant_payload(item: ClosedGonol) -> dict[str, Any]: "kind_id": _kind_payload(item.kind_id), "atomic_id": item.atomic_id, "option_set_id": item.option_set_id, + "carried_options": [list(pair) for pair in item.carried_options], "provenance": [list(pair) for pair in item.provenance], } @@ -356,6 +420,7 @@ def _atomic_payload( relation: str, source_units: tuple[str, ...], participants: tuple[ClosedGonol, ...], + carried_options: tuple[tuple[str, str], ...], ) -> dict[str, Any]: return { "constructor_id": CONSTRUCTOR_ID, @@ -368,6 +433,7 @@ def _atomic_payload( "relation": relation, "source_units": list(source_units), "participants": [_participant_payload(item) for item in participants], + "carried_options": [list(pair) for pair in carried_options], "closure_invariant": "once closed, a gonol is atomic at any scale", } @@ -393,6 +459,7 @@ def _receipt_payload( relation=gonol.relation, source_units=gonol.source_units, participants=gonol.participants, + carried_options=gonol.carried_options, ), "atomic_id": gonol.atomic_id, "geometry": dict(geometry), @@ -420,6 +487,7 @@ def construct_gonol( source: str | None = None, participants: Sequence[ClosedGonol] | None = None, relation: str | None = None, + carried_options: Sequence[Sequence[str]] | None = None, occurrence: int = 0, ) -> GonolReceipt: """Close one gonol at a declared scale using its option set.""" @@ -430,8 +498,22 @@ def construct_gonol( raise GonolConstructionError("occurrence must be a non-negative integer") units = _source_units(source, options=options) closed = _closed_participants(participants) + carried = _carried_option_pairs(carried_options) if options.scale == "character" and closed: raise GonolConstructionError("character scale does not accept closed participants") + if options.scale == "character" and carried: + raise GonolConstructionError("character scale does not carry declared options") + if options.scale == "suffix" and closed: + raise GonolConstructionError("suffix scale does not accept closed participants") + if options.scale == "suffix-coupling": + if len(closed) != 2 or closed[1].scale != "suffix": + raise GonolConstructionError( + "suffix-coupling scale requires ordered closed base and closed suffix participants" + ) + if carried: + raise GonolConstructionError( + "suffix-coupling options must be carried by the closed suffix participant" + ) if not units and not closed: raise GonolConstructionError("construction requires source evidence or closed participants") if options.scale == "recursive" and len(closed) < 2: @@ -445,13 +527,14 @@ def construct_gonol( relation=relation_value, source_units=units, participants=closed, + carried_options=carried, ) atomic_id = _digest(atomic_payload) provenance = ( ("constructor", f"{CONSTRUCTOR_ID}/{CONSTRUCTOR_VERSION}"), ("source_id", source_id), ("option_set", options.option_set_id), - ) + ) + tuple(("carried_option", f"{key}={value}") for key, value in carried) gonol = ClosedGonol( occurrence=occurrence, scale=options.scale, @@ -460,6 +543,7 @@ def construct_gonol( source_id=source_id, source_units=units, participants=closed, + carried_options=carried, atomic_id=atomic_id, provenance=provenance, ) @@ -491,6 +575,7 @@ def replay_gonol( source: str | None = None, participants: Sequence[ClosedGonol] | None = None, relation: str | None = None, + carried_options: Sequence[Sequence[str]] | None = None, occurrence: int = 0, ) -> GonolReceipt: """Independently reconstruct the same declared gonol closure.""" @@ -501,6 +586,7 @@ def replay_gonol( source=source, participants=participants, relation=relation, + carried_options=carried_options, occurrence=occurrence, ) diff --git a/edcm_msdmd.ts b/edcm_msdmd.ts index 37bd9c2..1c7baf5 100644 --- a/edcm_msdmd.ts +++ b/edcm_msdmd.ts @@ -746,6 +746,17 @@ export default defineMsdmdCollection({ "file": "edcm/gonol.py", "id": "single_constructor_uses_scale_option_sets" }, + { + "block": "CONTRACTS", + "fields": { + "class": "construction", + "given": "suffix coupling has a final-y exception such as ing preserving y after a consonant", + "since": "2026-08-22", + "then": "the exception is stored on the closed suffix gonol participant and replayed through participant provenance rather than global morphology law" + }, + "file": "edcm/gonol.py", + "id": "suffix_exception_carried_by_suffix_gonol" + }, { "block": "CONTRACTS", "fields": { @@ -762,7 +773,7 @@ export default defineMsdmdCollection({ "fields": { "admin_only": "false", "auth_boundary": "EDCM owns text-domain closure; UCNS Public Gonol geometry is optional observation unless normally importable with matching digest; METAPAT affixiation semantics are consumed, not redefined", - "internal_surface": "_option_set, _require_text, _source_units, _load_optional_public_gonol, _geometry_observation, _participant_payload, _atomic_payload, _receipt_payload, _digest", + "internal_surface": "_option_set, _require_text, _source_units, _closed_participants, _carried_option_pairs, _relation_value, _load_optional_public_gonol, _geometry_observation, _participant_payload, _atomic_payload, _receipt_payload, _digest", "module_kind": "engine", "module_name": "gonol", "network_boundary": "none", @@ -773,7 +784,7 @@ export default defineMsdmdCollection({ "rollout": "explicit candidate constructor; no canon selection, measurement activation, UCNS function operation, or Mobius coupling promotion", "since": "2026-08-22", "storage_boundary": "none; receipts remain caller-owned in-memory objects", - "summary": "unified EDCM candidate constructor that closes gonols through declared scale option sets while preserving closed-gonol atomicity, deterministic replay, and UCNS/METAPAT authority boundaries", + "summary": "unified EDCM candidate constructor that closes gonols through declared scale option sets while preserving closed-gonol atomicity, carried suffix options, deterministic replay, and UCNS/METAPAT authority boundaries", "tests": "tests.test_gonol_constructor", "unresolved": "exact UCNS geometric operation of Public Gonol function positions; Mobius-carrier affixiation/coupling law; which scales and relations are later selected; complete English morphology law", "user_data_boundary": "caller-supplied source, relation, participants, and source_id remain in memory and are not transmitted" @@ -1979,6 +1990,18 @@ export default defineMsdmdCollection({ "file": "tests/test_gonol_constructor.py", "id": "single_constructor_uses_scale_option_sets_check" }, + { + "block": "CHECKS", + "fields": { + "call": "self::test_suffix_coupling_exception_is_carried_by_closed_suffix", + "cleanup": "none", + "mutates": "none", + "proves": "suffix_exception_carried_by_suffix_gonol", + "timeout": "30" + }, + "file": "tests/test_gonol_constructor.py", + "id": "suffix_exception_carried_by_suffix_gonol_check" + }, { "block": "CHECKS", "fields": { @@ -4942,6 +4965,20 @@ export default defineMsdmdCollection({ "source_id": "single_constructor_uses_scale_option_sets_check", "to": "single_constructor_uses_scale_option_sets" }, + { + "from": "suffix_exception_carried_by_suffix_gonol_check", + "kind": "calls", + "source_block": "CHECKS", + "source_id": "suffix_exception_carried_by_suffix_gonol_check", + "to": "self::test_suffix_coupling_exception_is_carried_by_closed_suffix" + }, + { + "from": "suffix_exception_carried_by_suffix_gonol_check", + "kind": "claims_proves", + "source_block": "CHECKS", + "source_id": "suffix_exception_carried_by_suffix_gonol_check", + "to": "suffix_exception_carried_by_suffix_gonol" + }, { "from": "unified_candidate_does_not_select_canon_check", "kind": "calls", diff --git a/tests/test_gonol_constructor.py b/tests/test_gonol_constructor.py index f50e7bd..b3bb9cd 100644 --- a/tests/test_gonol_constructor.py +++ b/tests/test_gonol_constructor.py @@ -13,6 +13,13 @@ # mutates: none # cleanup: none # +# id: suffix_exception_carried_by_suffix_gonol_check +# proves: suffix_exception_carried_by_suffix_gonol +# call: self::test_suffix_coupling_exception_is_carried_by_closed_suffix +# timeout: 30 +# mutates: none +# cleanup: none +# # id: construction_survives_absent_ucns_geometry_check # proves: construction_survives_absent_ucns_geometry # call: self::test_base_construction_survives_absent_ucns_without_sys_path_mutation @@ -108,6 +115,82 @@ def test_closed_gonols_participate_directly_without_ladder(self) -> None: self.assertNotIn("mandatory character-word-definition-recursive ladder", definition.receipt_digest) self.assertIn("not a mandatory character-word-definition-recursive ladder", definition.nonclaims) + def test_suffix_coupling_exception_is_carried_by_closed_suffix(self) -> None: + base = construct_gonol(scale="word", source="try", source_id="fixture:try") + ing = construct_gonol( + scale="suffix", + source="ing", + source_id="fixture:ing", + carried_options=(("suffix-coupling.final-y-after-consonant", "preserve-y"),), + ) + coupling = construct_gonol( + scale="suffix-coupling", + participants=(base.gonol, ing.gonol), + source_id="fixture:trying", + ) + + self.assertEqual(coupling.option_set, SCALE_OPTION_SETS["suffix-coupling"]) + self.assertEqual(coupling.gonol.relation, "suffix-coupling") + self.assertEqual(coupling.gonol.participants, (base.gonol, ing.gonol)) + self.assertEqual( + coupling.gonol.participants[1].carried_options, + (("suffix-coupling.final-y-after-consonant", "preserve-y"),), + ) + self.assertEqual(coupling.gonol.carried_options, ()) + self.assertIn(("carried_option", "suffix-coupling.final-y-after-consonant=preserve-y"), ing.gonol.provenance) + self.assertNotIn("final-y-after-consonant", repr(SCALE_OPTION_SETS["suffix-coupling"])) + self.assertNotIn("preserve-y", repr(SCALE_OPTION_SETS["suffix-coupling"])) + + replay = replay_gonol( + scale="suffix-coupling", + participants=(base.gonol, ing.gonol), + source_id="fixture:trying", + ) + self.assertEqual(coupling.receipt_digest, replay.receipt_digest) + + def test_suffix_carried_options_are_part_of_identity_and_fail_closed(self) -> None: + base = construct_gonol(scale="word", source="try", source_id="fixture:try") + ing_preserve = construct_gonol( + scale="suffix", + source="ing", + source_id="fixture:ing", + carried_options=(("suffix-coupling.final-y-after-consonant", "preserve-y"),), + ) + ing_change = construct_gonol( + scale="suffix", + source="ing", + source_id="fixture:ing", + carried_options=(("suffix-coupling.final-y-after-consonant", "change-y-to-i"),), + ) + self.assertNotEqual(ing_preserve.gonol.atomic_id, ing_change.gonol.atomic_id) + + preserved = construct_gonol( + scale="suffix-coupling", + participants=(base.gonol, ing_preserve.gonol), + source_id="fixture:trying", + ) + changed = construct_gonol( + scale="suffix-coupling", + participants=(base.gonol, ing_change.gonol), + source_id="fixture:trying", + ) + self.assertNotEqual(preserved.gonol.atomic_id, changed.gonol.atomic_id) + + with self.assertRaisesRegex(GonolConstructionError, "carried by the closed suffix participant"): + construct_gonol( + scale="suffix-coupling", + participants=(base.gonol, ing_preserve.gonol), + carried_options=(("suffix-coupling.final-y-after-consonant", "preserve-y"),), + source_id="fixture:bad-carrier", + ) + + with self.assertRaisesRegex(GonolConstructionError, "closed base and closed suffix"): + construct_gonol( + scale="suffix-coupling", + participants=(ing_preserve.gonol, base.gonol), + source_id="fixture:bad-order", + ) + def test_recursive_requires_relation_and_two_closed_participants(self) -> None: word = construct_gonol(scale="word", source="cut", source_id="fixture:one") with self.assertRaisesRegex(GonolConstructionError, "relation must be exact"): From 552544b08989c41cf902aea6714c7bafbb046d43 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 22 Aug 2026 04:11:23 +0000 Subject: [PATCH 6/6] Require suffix coupling options on suffix gonols --- edcm/gonol.py | 8 +++++++- edcm_msdmd.ts | 2 +- tests/test_gonol_constructor.py | 10 +++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/edcm/gonol.py b/edcm/gonol.py index 666c919..1534a7c 100644 --- a/edcm/gonol.py +++ b/edcm/gonol.py @@ -50,7 +50,7 @@ # summary: unified EDCM candidate constructor that closes gonols through declared scale option sets while preserving closed-gonol atomicity, carried suffix options, deterministic replay, and UCNS/METAPAT authority boundaries # owner: Erin Spencer # public_surface: CONSTRUCTOR_ID, CONSTRUCTOR_VERSION, PINNED_PUBLIC_GONOL_SHA256, ScaleOptionSet, ClosedGonol, GonolReceipt, GonolConstructionError, SCALE_OPTION_SETS, construct_gonol, replay_gonol, canonical_receipt_bytes -# internal_surface: _option_set, _require_text, _source_units, _closed_participants, _carried_option_pairs, _relation_value, _load_optional_public_gonol, _geometry_observation, _participant_payload, _atomic_payload, _receipt_payload, _digest +# internal_surface: _option_set, _require_text, _source_units, _closed_participants, _carried_option_pairs, _has_suffix_coupling_options, _relation_value, _load_optional_public_gonol, _geometry_observation, _participant_payload, _atomic_payload, _receipt_payload, _digest # auth_boundary: EDCM owns text-domain closure; UCNS Public Gonol geometry is optional observation unless normally importable with matching digest; METAPAT affixiation semantics are consumed, not redefined # storage_boundary: none; receipts remain caller-owned in-memory objects # network_boundary: none @@ -332,6 +332,10 @@ def _carried_option_pairs( return tuple(pairs) +def _has_suffix_coupling_options(carried_options: tuple[tuple[str, str], ...]) -> bool: + return any(key.startswith("suffix-coupling.") for key, _value in carried_options) + + def _relation_value(relation: str | None, *, options: ScaleOptionSet) -> str: if relation is None: if options.default_relation is None: @@ -503,6 +507,8 @@ def construct_gonol( raise GonolConstructionError("character scale does not accept closed participants") if options.scale == "character" and carried: raise GonolConstructionError("character scale does not carry declared options") + if options.scale != "suffix" and _has_suffix_coupling_options(carried): + raise GonolConstructionError("suffix-coupling options must be carried by a closed suffix gonol") if options.scale == "suffix" and closed: raise GonolConstructionError("suffix scale does not accept closed participants") if options.scale == "suffix-coupling": diff --git a/edcm_msdmd.ts b/edcm_msdmd.ts index 1c7baf5..1d3b327 100644 --- a/edcm_msdmd.ts +++ b/edcm_msdmd.ts @@ -773,7 +773,7 @@ export default defineMsdmdCollection({ "fields": { "admin_only": "false", "auth_boundary": "EDCM owns text-domain closure; UCNS Public Gonol geometry is optional observation unless normally importable with matching digest; METAPAT affixiation semantics are consumed, not redefined", - "internal_surface": "_option_set, _require_text, _source_units, _closed_participants, _carried_option_pairs, _relation_value, _load_optional_public_gonol, _geometry_observation, _participant_payload, _atomic_payload, _receipt_payload, _digest", + "internal_surface": "_option_set, _require_text, _source_units, _closed_participants, _carried_option_pairs, _has_suffix_coupling_options, _relation_value, _load_optional_public_gonol, _geometry_observation, _participant_payload, _atomic_payload, _receipt_payload, _digest", "module_kind": "engine", "module_name": "gonol", "network_boundary": "none", diff --git a/tests/test_gonol_constructor.py b/tests/test_gonol_constructor.py index b3bb9cd..17130f1 100644 --- a/tests/test_gonol_constructor.py +++ b/tests/test_gonol_constructor.py @@ -176,7 +176,7 @@ def test_suffix_carried_options_are_part_of_identity_and_fail_closed(self) -> No ) self.assertNotEqual(preserved.gonol.atomic_id, changed.gonol.atomic_id) - with self.assertRaisesRegex(GonolConstructionError, "carried by the closed suffix participant"): + with self.assertRaisesRegex(GonolConstructionError, "carried by a closed suffix gonol"): construct_gonol( scale="suffix-coupling", participants=(base.gonol, ing_preserve.gonol), @@ -184,6 +184,14 @@ def test_suffix_carried_options_are_part_of_identity_and_fail_closed(self) -> No source_id="fixture:bad-carrier", ) + with self.assertRaisesRegex(GonolConstructionError, "carried by a closed suffix gonol"): + construct_gonol( + scale="word", + source="try", + source_id="fixture:bad-word-carrier", + carried_options=(("suffix-coupling.final-y-after-consonant", "preserve-y"),), + ) + with self.assertRaisesRegex(GonolConstructionError, "closed base and closed suffix"): construct_gonol( scale="suffix-coupling",