:PROPERTIES: :CREATED: [2026-06-04 Thu] :END: #+title: Cardinality Policies — Singular, Dual, and Plural Facts #+filetags: :passepartout:architecture: Classical logic requires consistency. A contradiction implies everything (=ex contradictione quodlibet=). Screamer, as a constraint solver, also requires consistency — a contradictory constraint set has no solutions. But the symbolic engine operates across domains where the meaning of contradiction is fundamentally different. The correct question is not "is this consistent?" but "what cardinality of truth does this domain support?" Time is not a policy. It is a universal dimension that applies equally to every fact, regardless of cardinality. All facts carry =:timestamp= and =:parent-id= fields. Every fact has a version history. Every fact lives in a Merkle chain that captures how it changed. The cardinality policy only governs what happens at a given logical moment when two values coexist for the same =entity= and =relation=. *** Policy :singular — One Active Value, One Version Chain The active set contains exactly one value for =(:entity :relation)= at a time. When a new value asserts for the same pair, the old value is not rejected. It is superseded — moved into the version history, linked to the new leaf by =:parent-id=, and retained permanently. The active value is the leaf of the Merkle chain. "I used to think =rm -rf /= was safe. Now I know it is catastrophic." Both facts exist. Both are true — the first at =2024-06-01=, the second at =2025-03-15=. The chain captures the evolution. The =:singular= policy means there is one truth /now/, not that there was only ever one truth. Use for: security classifications, file system state, gate rules, code correctness, deterministic safety constraints — domains that converge on one answer, evolving over time. *** Policy :dual — Exactly Two Values, in Explicit Tension The active set contains exactly two values for =(:entity :relation)=. Both are simultaneously true. Both carry independent version histories. A third value is rejected — the domain is binary by nature. Some contradictions are productive precisely /because/ they are binary. Thesis and antithesis. Love and resentment. Wave and particle. A poem's two incompatible readings. The symbolic index holds both, cross-referenced as complementary rather than conflicting. The user is not asked to resolve the tension. The tension is the fact. The system can reason about cardinality transitions: a =:dual= fact that has one interpretation superseded should collapse to =:singular=. A =:dual= that has a third interpretation asserted should prompt the user: "Promote to =:plural= or demote one interpretation?" Use for: productive binary tensions, complementary opposites, dialectical pairs, any domain where two answers are both true and their tension is meaningful. *** Policy :plural — N Active Values, Open Set The active set contains any number of values for =(:entity :relation)=. Each value has independent provenance and its own version history. Queries return all active values with provenance display. Contradictions are flagged as cross-references between values — information, not error. A =:plural= fact where all but one value are superseded should collapse to =:singular=. A =:plural= fact where the set reduces to two active values — and the remaining two are complementary — should collapse to =:dual=. Use for: literary interpretation, scientific hypotheses, personal beliefs held at different times (when tension is multi-faceted rather than binary), multi-source factual disagreement, open-ended exploration. *** Policy Assignment The policy is assigned when a category is defined. New categories default to =:plural= (safe — never loses information). Core security categories are explicitly =:singular=. The gate stack's bootstrapped facts are =:singular= because they describe the actual filesystem, which is physically singular. Categories for dialectical or complementary domains are explicitly =:dual=. The Screamer admission gate applies the cardinality policy at the active set: - =:singular= + same value, later timestamp → supersede old, chain new as leaf. - =:singular= + different value, same timestamp → reject (contradiction). Human resolves. - =:singular= + different value, later timestamp → supersede old, chain new as leaf. History preserved. - =:dual= + first value → admit. + second value → admit, cross-reference as complementary. + third value → prompt. - =:plural= + any value → admit. Active count transitions trigger collapse checks. *** Why This Matters for the Broader Memex In the coding domain, contradiction is rare, resolvable, and usually temporal (a rule changed). In the broader memex, contradiction is the product, not the error. Your poetry analysis contradicts your last diary entry. Your reading of /Pale Fire/ changed between 2023 and 2025. Wikidata says Mount Everest is 8848m; DBpedia says 8849m. You love this person AND you resent them. The symbolic engine's job is not to decide which is right. It is to surface the tension with provenance — "these three sources disagree; here is the chain for each" for plural facts, or "you hold these two positions in tension" for dual facts, or "you believed X until Tuesday, then Y" for singular facts that evolved. The cardinality policy names the /structure/ of the tension. The Merkle chain provides the /history/ of each position.