From 48e28d38aa60112317553ade98ce513c20f3f283 Mon Sep 17 00:00:00 2001 From: owengregson Date: Fri, 24 Jul 2026 11:43:10 -1000 Subject: [PATCH 01/54] docs: add gen-2 ideation campaign plan (Project Kaleidoscope) Design doc + runnable launch packet for an Opus 5 / Fable 5 parallel-workflow ideation campaign that forces novelty by construction: forbidden-solution lists, assumption inversion, cross-domain analogy seeding, and forced fusion. Scope is the whole next-gen tool (seven tracks); blue-sky generation with a separate feasibility/novelty tagging pass. Agent-facing prompts use neutral product/IP-protection framing. Plan only; the workflow is opt-in. Co-Authored-By: Claude Opus 4.8 --- docs/ruam-gen2-ideation-prompt.md | 52 ++ ...7-24-ruam-gen2-ideation-campaign-design.md | 464 ++++++++++++++++++ 2 files changed, 516 insertions(+) create mode 100644 docs/ruam-gen2-ideation-prompt.md create mode 100644 docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-campaign-design.md diff --git a/docs/ruam-gen2-ideation-prompt.md b/docs/ruam-gen2-ideation-prompt.md new file mode 100644 index 0000000..342bf40 --- /dev/null +++ b/docs/ruam-gen2-ideation-prompt.md @@ -0,0 +1,52 @@ +# Ruam Gen-2 Ideation — Launch Packet + +> **Paste this to a fresh session to kick off the campaign.** It is the lean operator brief. +> The full design (rationale, seed banks, scoring, schemas, and the complete runnable script) +> is in `docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-campaign-design.md` — read it first. + +--- + +## Mission (one paragraph) + +Ruam is a developer tool that protects intellectual property in shipped JavaScript: it transforms a developer's own functions so automated tooling cannot cheaply and mechanically summarize or reconstruct the original program. The first generation of hardening ideas were competent but *incremental* — they iterated on primitives the codebase already had (salt the key, chain the keystream, fold a digest), because the ideation anchored on a defeatist "it's only work-factor" ceiling. **Your job is to run a structured ideation campaign that makes incrementalism structurally impossible** and surfaces genuinely novel directions for Ruam's *whole* next generation — across seven tracks, not only its resilience layer. + +## How novelty is forced (do not skip) + +Every idea-generator works under four simultaneous constraints: +1. **Forbidden-solution list** — anything that matches an existing Ruam feature or a canonical primitive (ciphers, digests, base-N codecs, LCG/FNV, table/switch VMs, control-flow flattening, string tables, dead code, opaque predicates) is disqualified before scoring. +2. **Assumption inversion** — each agent overturns one Ruam "axiom" (e.g., "the decoder must ship with the code," "the output is fixed once emitted," "a fragment is understandable locally"). +3. **Cross-domain analogy** — each agent derives its mechanism from a domain far from software (immune systems, holography, origami metamaterials, stage misdirection, DNA repair, ecology…), explaining the source system *before* stating the idea. +4. **Forced fusion** — a dedicated phase hybridizes the boldest ideas into mechanisms neither parent would produce. + +## Model roles + +- **Fable 5** (`claude-fable-5`) — framing brief + final synthesis/ranking (the judgment layer). +- **Opus 5** (`claude-opus-5`) — the divergent generator fleet + fusion (each agent a unique lens × assumption × track × persona). + +## Vocabulary rule (mandatory) + +All agent-facing text uses **product / IP-protection framing**. **Banned:** attack, adversary, red-team, exploit, weaponize, injection, bypass, "make the model fail." **Use instead:** resilience to automated analysis, originality of output, protecting the developer's IP, novel program transformations. Frame the "AI" angle as *"automated analysis/reconstruction tooling,"* never as harming a model. This keeps the run from false-flagging safeguards. + +## The seven tracks (whole next-gen tool) + +1. Resilience to automated understanding · 2. Novel transformation paradigms · 3. New product surfaces & capabilities · 4. Self-modifying / living output · 5. Semantic-level protection · 6. Verifiability & developer experience · 7. Distribution / licensing / delivery models. + +## Launch + +The campaign is a `Workflow`. **It requires explicit opt-in** (say `ultracode`, or "run this workflow") because it dispatches Opus 5 + Fable 5 agents and is token-intensive. + +1. Read the design doc (link at top) and copy the script from its **§11**. +2. Invoke: + ``` + Workflow({ script: <§11 script>, args: { generators: 7 } }) + ``` + or, after the first run persisted it, `Workflow({ scriptPath: , args: { generators: 16 } })` for a wider sweep. +3. Phase order: **Frame → Diverge → Fuse → Tag → Synthesize → Coverage.** Default 14 agents (under the 15-agent guideline); raise `args.generators` to widen. + +## Definition of done + +The run returns `{ headline, slate, fusions, coverage, counts }`. It succeeds when `slate.ranked` holds ≥ 3 ideas with recognizability < 0.5 across ≥ 3 tracks, ≥ 1 fusion in the top 5, and every top-10 idea carries a "why this is not a gen-1 iteration" line. If every top idea scores recognizability ≥ 0.7, the run failed its own bar — re-seed (bump `args.generators` or edit the lens/assumption strides) rather than accept retreads. + +## After the campaign + +A selected idea feeds the *existing* build pipeline: brainstorming → design spec → `writing-plans` → TDD → verify. This campaign is the front end; it does not replace that machine. diff --git a/docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-campaign-design.md b/docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-campaign-design.md new file mode 100644 index 0000000..0281b22 --- /dev/null +++ b/docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-campaign-design.md @@ -0,0 +1,464 @@ +# Project Kaleidoscope — Ruam Gen-2 Ideation Campaign + +**Date:** 2026-07-24 +**Status:** Plan (design complete; not yet run). +**Type:** Ideation-campaign design. This document is *runnable later* — §11 is a complete `Workflow` script and §12 is the operator guide. A lean paste-and-launch companion lives at `docs/ruam-gen2-ideation-prompt.md`. + +--- + +## 0. What this is (and what it is not) + +Ruam is a JavaScript source-protection tool: it compiles a developer's functions into custom bytecode run by a per-build-polymorphic embedded interpreter, so shipped code does not hand a reader a clean, easily-reconstructed copy of the original logic. This document designs a **structured ideation campaign** that uses **Opus 5** and **Fable 5** subagents, dispatched in **parallel workflows**, to generate *genuinely novel* directions for Ruam's next generation — across the whole product, not only its resilience layer. + +It is a **plan**. Running it is a separate, explicit step (see §12). This document does not itself dispatch any agent. + +**The one non-negotiable design property:** the campaign is engineered to make convergence-on-the-obvious *structurally impossible*. Novelty is enforced by construction (§3), not left to chance. + +--- + +## 1. Why the first generation was uncreative — the diagnosis + +The gen-1 hardening work (`docs/superpowers/specs/2026-06-30-anti-ai-decompilation-design.md`, branch `anti-ai-hardening`) produced competent but *incremental* ideas: salt the key per unit, chain the keystream, fold a cross-file digest. Every one is an **iteration on a primitive that already existed in the codebase.** + +The root cause is not a lack of effort — it was a **47-agent swarm**. The root cause is an **anchor**. Gen-1's design doc opened by declaring an "honest ceiling": + +> *"perfect … defense against a patient AI is impossible … client-executable code is client-observable … the achievable wins are (a) raise human-hours, (b) destroy transferability, (c) an off-device secret."* + +Once that frame was accepted, every downstream agent reasoned *inside it* and optimized within a solved space. The ideas were rankings of known levers, not new categories. The gen-1 v2 handoff even sensed the trap ("Do not let that conclusion truncate your ideation") but could not escape it, because it never removed the anchor — it just asked agents to try harder next to it. + +**Design consequence for gen-2:** we do not ask agents to be creative. We **remove the anchor**, **forbid the known solution space**, and **force transfer from foreign domains** — so the recognizable answer is simply unavailable to them. + +--- + +## 2. Success criteria for the campaign + +A run is successful if it produces: + +1. **A ranked slate of candidate directions** spanning the seven tracks (§6), each with a novelty score, an impact estimate, feasibility tags, and a maturity bucket. +2. **At least several genuinely unrecognizable ideas** — mechanisms that do not pattern-match any primitive already in Ruam or in the common obfuscation/compiler canon (recognizability score below the threshold in §9). +3. **At least one strong fusion** (§ Phase 2) — a hybrid neither parent idea would have produced. +4. For every idea: a one-line **"why this is not a gen-1 iteration"** note. + +A run *fails its own bar* (and should be re-seeded, §Phase 5) if every surviving idea is a recognizable primitive or a relabeling of a gen-1 workstream. + +--- + +## 3. The four creativity engines + +Every generation agent operates under all four simultaneously. These are the heart of the design. + +### 3.1 Forbidden-solution list +Each generator receives the full inventory of *what already exists* and *what an automated analyzer recognizes for free* — the Ruam feature list, the gen-1 workstreams, and the canonical primitives (stream ciphers, digests/checksums, base-N codecs, LCG/FNV PRNGs, switch/table-dispatch VMs, control-flow flattening, string tables, dead-code, opaque predicates). **Any idea that pattern-matches an item on this list is disqualified before it is scored.** You cannot win by reinventing a keystream. + +### 3.2 Assumption inversion +Each agent is handed exactly one Ruam "axiom" to **overturn** (bank in §7.2), e.g. *"the decoder must ship alongside the code it decodes,"* *"the output is fixed the moment it is emitted,"* *"a local fragment can be understood by reading it locally."* The agent must produce a mechanism that only makes sense *if the axiom is false.* Inversion reliably relocates thinking outside the solved space. + +### 3.3 Cross-domain analogy seeding +Each agent is assigned one lens from a domain **far** from software (bank in §7.1: immune systems, mycelial networks, holography, origami metamaterials, untranslatable languages, stage misdirection, DNA repair, ecology, phase transitions, cartographic projection…). The agent must (a) explain the source-domain mechanism in its own terms, (b) map each element of it onto a Ruam concept, and (c) *only then* state the idea. The mandatory "explain the source first" step is what turns a shallow metaphor into a real structural transfer — and it is the strongest single lever against training-distribution convergence. + +### 3.4 Forced fusion +A dedicated phase takes the boldest raw ideas and **hybridizes** them in pairs/triples. Gen-1 recorded that its only mechanism to survive scrutiny was a *fusion* of several levers — never a standalone trick. Gen-2 makes fusion a deliberate phase rather than an accident, because the interaction of two unlike ideas is where the genuinely new mechanisms live. + +--- + +## 4. Model roles — Opus 5 and Fable 5, split on purpose + +Per the project's `efficient-fable` doctrine (Fable for decomposition, architecture/product tradeoffs, synthesis, and final judgment; cheaper models for bounded heavy lifting): + +- **Fable 5 (`claude-fable-5`)** — the **judgment layer.** It writes the framing brief (Phase 0), steers cross-pollination, and performs final synthesis and ranking (Phase 4). Fable makes the "is this actually novel / does it cohere / is this a gen-1 relabel" calls. +- **Opus 5 (`claude-opus-5`)** — the **divergent fleet.** The many parallel generators (Phase 1) and the fusion agents (Phase 2). Each generator gets a *unique* lens × assumption × track × persona tuple, so no two explore the same space. + +Reasoning effort: generation and synthesis run at **high/max** effort; tagging runs at **medium**. + +--- + +## 5. Phase topology (blue-sky → filter) + +Ideation is fully unconstrained first; feasibility enters only as *annotation*, never as a gate that deletes ideas. + +| Phase | Model ×N | Purpose | Consumes | Produces | +|---|---|---|---|---| +| **0 — Framing brief** | Fable 5 ×1 | Read the repo + gen-1 record; emit the shared neutral context packet: current-state summary, the forbidden-solution list, the de-anchoring provocations, the track definitions. | repo docs | `Brief` (§10) | +| **1 — Divergent generation** | Opus 5 ×7 (scalable) | Each agent = one lens × one inverted assumption × one track × one persona → 3–5 raw ideas derived by analogy. **No feasibility filtering.** Weird beats safe. | `Brief` + its tuple | `RawIdea[]` | +| **2 — Forced fusion** | Opus 5 ×2 | Hybridize the boldest raw ideas into mechanisms neither parent would produce. | pooled `RawIdea[]` | `RawIdea[]` (fusions) | +| **3 — Feasibility & novelty tagging** | mixed ×2 | For each idea: recognizability score, impact, rail-checks, maturity bucket. **Annotates; never deletes.** | idea pool (batched) | `TaggedIdea[]` | +| **4 — Synthesis & ranked slate** | Fable 5 ×1 | Cluster, dedupe, rank by novelty × impact × feasibility; surface best fusions; write per-idea "why novel" notes. | `TaggedIdea[]` | `Slate` (§10) | +| **5 — Completeness critic** | ×1 (optional) | "Which lens produced nothing? Which assumption did nobody dare invert? Which track is thin?" → seeds a second round. | `Slate` + coverage | `CoverageReport` | + +**Default agent count:** 1 + 7 + 2 + 2 + 1 + 1 = **14** (under the standard 15-agent workflow guideline). Scale the Phase-1 fleet via `args.generators` (§12); a fleet of 16–20 is a reasonable "big sweep." + +**Barrier placement.** Phase 1 → Phase 2 uses a genuine barrier (`parallel`): fusion needs the *full* set of raw ideas to pair across generators. Everything after the idea pool is formed can pipeline. + +--- + +## 6. Scope — the seven tracks ("whole next-gen tool") + +Hardening is one track of seven. The campaign ideates on what Ruam *becomes*. + +1. **Resilience to automated understanding** — output that automated tooling cannot cheaply, mechanically summarize or reconstruct. (The gen-1 lineage, reframed as a product property.) +2. **Novel transformation paradigms** — what the compiler/VM core could become beyond bytecode + interpreter (new execution models, new representations of "a program"). +3. **New product surfaces & capabilities** — what Ruam *is* to a developer: integrations, workflows, guarantees, interfaces, deployment shapes. +4. **Self-modifying / living output** — artifacts that are not fixed at emit time: regenerating, metamorphic, or self-maintaining. +5. **Semantic-level protection** — protecting *meaning* and intent, not just surface form. +6. **Verifiability & developer experience** — proving the protected program is faithful; trust, debuggability, observability of one's own output. +7. **Distribution / licensing / delivery models** — how protected code is packaged, licensed, updated, and monetized. + +--- + +## 7. Seed banks + +### 7.1 Domain lenses (≈22 — assign one per generator, by index) +adaptive immune system (clonal selection, self/non-self) · mycelial nutrient networks · holography (every shard holds the whole at lower resolution) · origami & mechanical metamaterials · untranslatable languages / linguistic relativity · stage magic & misdirection · DNA error-correction & codon degeneracy · ecology & keystone species · quantum measurement / observer effect · music theory & counterpoint · slime-mold pathfinding · camouflage, mimicry & aposematism · legal contracts & escrow · cellular automata & emergence · metamorphosis (same organism, unrecognizable form) · phase transitions & superconductivity · ant-colony stigmergy · fractals & scale-free structure · immune memory & vaccination · orbital/tidal resonance & coupled oscillators · fermentation & time-based transformation · cartographic projection (every map distorts something). + +### 7.2 Inverted assumptions (≈14 — assign one per generator) +"the decoder must ship alongside the code" · "the output is fixed once emitted" · "the meaning lives inside the file" · "one input deterministically yields one output" · "each function is a stable, enumerable unit" · "protection is applied once, at build time" · "build-time and run-time are separate worlds" · "the tool protects code but does not participate at run time" · "a local fragment is understandable locally" · "the artifact is a passive text to be read" · "opacity and correctness are in tension" · "Ruam is a compiler" (→ Ruam is a ___) · "the program behaves the same whether observed or not" · "structure is what hides meaning" (→ absence of structure). + +### 7.3 Personas (≈8 — assign one per generator) +materials scientist · field biologist · stage magician · theoretical linguist · game designer · complexity/systems theorist · industrial designer · composer. + +**Assignment rule (deterministic, no randomness):** generator *i* gets `lens[i % L]`, `assumption[(i·5) % A]`, `track[i % 7]`, `persona[i % 8]`. Co-prime strides spread the combinations so the fleet covers a wide grid. + +--- + +## 8. Neutral vocabulary policy (safety-framing guardrail) + +Ruam is a legitimate developer tool for protecting intellectual property in shipped JavaScript. **All agent-facing prompt text uses product/engineering framing** so nothing false-flags safeguards when the campaign is run. + +**Banned in every agent prompt:** *attack, attacker, adversary, adversarial, red-team, exploit, weaponize, malicious, victim, payload, injection, evade, bypass, defeat-the-AI, "make the model fail."* + +**Approved framing:** *resilience to automated analysis; resistance to mechanical summarization/reconstruction; distinctiveness and originality of output; protecting the developer's intellectual property; novel program transformations; hard-to-mechanize structure; the tool's evolution as a product.* Where the "AI" angle is relevant, phrase it as **"automated analysis/reconstruction tooling"** and frame the objective as *raising the effort and reducing the transferability of mechanical understanding* — never as harming a model. + +This policy is not cosmetic: it is a hard requirement on the embedded prompt strings in §11. + +--- + +## 9. Scoring & selection rubric (Phase 3 + Phase 4) + +Each idea is scored on four axes; the composite drives the ranking. + +- **Recognizability** `0.0–1.0` (LOWER IS BETTER). 0.0 = matches no known primitive/pattern; 1.0 = a textbook primitive or a gen-1 relabel. **Ideas at ≥ 0.7 are flagged "iteration, not invention"** and sink in the ranking regardless of impact. +- **Impact** `1–5` — how far it moves Ruam forward on its track. +- **Feasibility rails** (each `pass | needs-relaxation | fails`): *server-free · size-lean (no context-padding bloat) · CSP/Trusted-Types-safe · build==runtime provable · additive-API-compatible.* Tags, not filters. +- **Maturity bucket** (one of): `shippable-now` · `needs-rail-relaxed` · `research-spike` · `product-pivot`. + +**Composite rank (Phase 4):** `impact × (1 − recognizability) × feasibilityWeight`, with a deliberate **novelty premium**: two ideas of equal composite are ordered by lower recognizability first. Best fusions are surfaced separately even if a rail is red — a `research-spike` that is genuinely new is more valuable to this campaign than a `shippable-now` retread. + +--- + +## 10. Structured output schemas + +Used as the `schema` option on `agent()` so returns are validated data, not prose to parse. + +```js +const BRIEF_SCHEMA = { + type: "object", + properties: { + stateSummary: { type: "string" }, + forbiddenSolutions: { type: "array", items: { type: "string" } }, + provocations: { type: "array", items: { type: "string" } }, + tracks: { type: "array", items: { type: "string" } } + }, + required: ["stateSummary", "forbiddenSolutions", "provocations", "tracks"] +}; + +const RAW_IDEAS_SCHEMA = { + type: "object", + properties: { + ideas: { + type: "array", + items: { + type: "object", + properties: { + name: { type: "string" }, + lens: { type: "string" }, // source domain used + assumptionOverturned: { type: "string" }, + track: { type: "string" }, + sourceMechanism: { type: "string" }, // the analogy, explained first + coreIdea: { type: "string" }, // 2–4 sentences + whyNovel: { type: "string" }, // what it does NOT resemble + roughSketch: { type: "string" }, // how it might map into Ruam + boldness: { type: "number" } // self-rating 1–5 + }, + required: ["name", "coreIdea", "whyNovel", "roughSketch"] + } + } + }, + required: ["ideas"] +}; + +const TAGGED_IDEAS_SCHEMA = { + type: "object", + properties: { + ideas: { + type: "array", + items: { + type: "object", + properties: { + name: { type: "string" }, + track: { type: "string" }, + recognizability: { type: "number" }, // 0..1, lower = more novel + impact: { type: "number" }, // 1..5 + rails: { + type: "object", + properties: { + serverFree: { type: "string" }, // pass|needs-relaxation|fails + sizeLean: { type: "string" }, + cspSafe: { type: "string" }, + buildRuntimeProvable: { type: "string" }, + additiveApi: { type: "string" } + } + }, + maturity: { type: "string" }, // shippable-now|needs-rail-relaxed|research-spike|product-pivot + notNovelBecause: { type: "string" } // empty if genuinely novel + }, + required: ["name", "recognizability", "impact", "maturity"] + } + } + }, + required: ["ideas"] +}; + +const SLATE_SCHEMA = { + type: "object", + properties: { + ranked: { + type: "array", + items: { + type: "object", + properties: { + rank: { type: "number" }, + name: { type: "string" }, + track: { type: "string" }, + composite: { type: "number" }, + oneLine: { type: "string" }, + whyNotGen1: { type: "string" }, + maturity: { type: "string" } + }, + required: ["rank", "name", "oneLine", "whyNotGen1"] + } + }, + bestFusions: { type: "array", items: { type: "string" } }, + headline: { type: "string" } + }, + required: ["ranked", "headline"] +}; +``` + +--- + +## 11. The runnable `Workflow` script + +Paste as the `script` argument to the `Workflow` tool (or save and pass via `scriptPath`). Plain JS. No `Date.now()`/`Math.random()`. All embedded prompt strings follow §8. + +```js +export const meta = { + name: 'ruam-gen2-ideation', + description: 'Generate genuinely novel next-generation directions for Ruam via cross-domain analogy, assumption inversion, and forced fusion.', + phases: [ + { title: 'Frame', detail: 'Fable writes the shared neutral brief + forbidden-solution list', model: 'claude-fable-5' }, + { title: 'Diverge', detail: 'Opus fleet: one lens x assumption x track x persona each', model: 'claude-opus-5' }, + { title: 'Fuse', detail: 'Opus hybridizes the boldest raw ideas', model: 'claude-opus-5' }, + { title: 'Tag', detail: 'Feasibility + novelty annotation (never deletes)' }, + { title: 'Synthesize', detail: 'Fable clusters, ranks, writes the slate', model: 'claude-fable-5' }, + { title: 'Coverage', detail: 'Completeness critic seeds a possible round 2' }, + ], +} + +// ---- schemas (see design doc section 10) ---- +const BRIEF_SCHEMA = { type:"object", properties:{ stateSummary:{type:"string"}, forbiddenSolutions:{type:"array",items:{type:"string"}}, provocations:{type:"array",items:{type:"string"}}, tracks:{type:"array",items:{type:"string"}} }, required:["stateSummary","forbiddenSolutions","provocations","tracks"] } +const RAW_IDEAS_SCHEMA = { type:"object", properties:{ ideas:{ type:"array", items:{ type:"object", properties:{ name:{type:"string"}, lens:{type:"string"}, assumptionOverturned:{type:"string"}, track:{type:"string"}, sourceMechanism:{type:"string"}, coreIdea:{type:"string"}, whyNovel:{type:"string"}, roughSketch:{type:"string"}, boldness:{type:"number"} }, required:["name","coreIdea","whyNovel","roughSketch"] } } }, required:["ideas"] } +const TAGGED_IDEAS_SCHEMA = { type:"object", properties:{ ideas:{ type:"array", items:{ type:"object", properties:{ name:{type:"string"}, track:{type:"string"}, recognizability:{type:"number"}, impact:{type:"number"}, rails:{type:"object"}, maturity:{type:"string"}, notNovelBecause:{type:"string"} }, required:["name","recognizability","impact","maturity"] } } }, required:["ideas"] } +const SLATE_SCHEMA = { type:"object", properties:{ ranked:{ type:"array", items:{ type:"object", properties:{ rank:{type:"number"}, name:{type:"string"}, track:{type:"string"}, composite:{type:"number"}, oneLine:{type:"string"}, whyNotGen1:{type:"string"}, maturity:{type:"string"} }, required:["rank","name","oneLine","whyNotGen1"] } }, bestFusions:{type:"array",items:{type:"string"}}, headline:{type:"string"} }, required:["ranked","headline"] } + +// ---- seed banks (design doc section 7) ---- +const LENSES = ["adaptive immune system (clonal selection, self vs non-self)","mycelial nutrient networks","holography (every shard holds the whole at lower resolution)","origami and mechanical metamaterials","untranslatable languages / linguistic relativity","stage magic and misdirection","DNA error-correction and codon degeneracy","ecology and keystone species","quantum measurement / observer effect","music theory and counterpoint","slime-mold pathfinding","camouflage, mimicry and aposematism","legal contracts and escrow","cellular automata and emergence","metamorphosis (same organism, unrecognizable form)","phase transitions and superconductivity","ant-colony stigmergy","fractals and scale-free structure","immune memory and vaccination","orbital/tidal resonance and coupled oscillators","fermentation and time-based transformation","cartographic projection (every map distorts something)"] +const ASSUMPTIONS = ["the decoder must ship alongside the code it decodes","the output is fixed the moment it is emitted","the meaning of the program lives inside the file","one input deterministically yields one output","each function is a stable, enumerable unit","protection is applied once, at build time","build-time and run-time are separate worlds","the tool protects code but does not participate at run time","a local fragment can be understood by reading it locally","the artifact is a passive text to be read","opacity and correctness are in tension","Ruam is a compiler (finish: Ruam is actually a ___)","the program behaves the same whether observed or not","structure is what hides meaning (invert: the absence of structure)"] +const TRACKS = ["resilience to automated understanding","novel transformation paradigms","new product surfaces and capabilities","self-modifying / living output","semantic-level protection","verifiability and developer experience","distribution / licensing / delivery models"] +const PERSONAS = ["materials scientist","field biologist","stage magician","theoretical linguist","game designer","complexity/systems theorist","industrial designer","composer"] + +const NEUTRAL_FRAMING = `Ruam is a legitimate developer tool that protects intellectual property in shipped JavaScript: it transforms a developer's own functions so that automated tooling cannot cheaply and mechanically summarize or reconstruct the original program. Frame everything as product evolution and resilience to automated analysis. Do NOT use security-offensive vocabulary (no "attack", "adversary", "red-team", "exploit", "injection", "bypass", "defeat the model"). Speak in terms of originality, resilience, IP protection, and novel program transformations.` + +const N = (args && args.generators) || 7 + +// ---- Phase 0: framing brief (Fable) ---- +phase('Frame') +const brief = await agent( + `${NEUTRAL_FRAMING} + +You are writing the shared creative brief for an ideation campaign about the NEXT GENERATION of Ruam. Read these files in the repo: docs/superpowers/specs/2026-06-30-anti-ai-decompilation-design.md, docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-campaign-design.md, and CLAUDE.md (the "Architecture Notes" and feature list). + +Produce a brief with: +1. stateSummary: a crisp picture of what Ruam is and does today. +2. forbiddenSolutions: an explicit inventory of ideas that ALREADY EXIST or that automated tooling recognizes instantly — the current Ruam feature set AND the canonical primitives (stream ciphers, checksums/digests, base-N codecs, LCG/FNV generators, table/switch-dispatch interpreters, control-flow flattening, string tables, dead code, opaque predicates). Any future idea that matches one of these is disqualified. Be thorough; this list is what prevents reinvention. +3. provocations: 8-12 sharp de-anchoring prompts that push a thinker OUT of the "improve the cipher" mindset and toward new categories. +4. tracks: the seven tracks from the design doc, each with one sentence on what a breakthrough there would look like.`, + { label: 'framing-brief', phase: 'Frame', model: 'claude-fable-5', effort: 'high', schema: BRIEF_SCHEMA } +) + +// ---- Phase 1: divergent generation (Opus fleet) ---- +phase('Diverge') +const assignments = [] +for (let i = 0; i < N; i++) { + assignments.push({ + lens: LENSES[i % LENSES.length], + assumption: ASSUMPTIONS[(i * 5) % ASSUMPTIONS.length], + track: TRACKS[i % TRACKS.length], + persona: PERSONAS[i % PERSONAS.length], + }) +} + +const rawBatches = await parallel(assignments.map((a, i) => () => + agent( + `${NEUTRAL_FRAMING} + +SHARED BRIEF (do not restate; build on it): +State: ${brief.stateSummary} +Do-not-reinvent list: ${JSON.stringify(brief.forbiddenSolutions)} + +You are idea-generator #${i + 1}. Think like a ${a.persona}. You must generate mechanisms for the next generation of Ruam under THREE binding creative constraints: + +1) LENS — derive your ideas by analogy to: ${a.lens}. + First, explain how this real-world system actually works, in your own words. Then map each element of it onto a Ruam concept (the build step, the emitted artifact, the embedded interpreter, the developer, the automated analyzer, the run-time). ONLY THEN state your idea. Shallow metaphors are rejected; the mapping must be concrete. + +2) OVERTURN — build ideas that only make sense if this usual assumption is FALSE: "${a.assumption}". Lean into what becomes possible once it no longer holds. + +3) TRACK — aim at: ${a.track}. + +Hard rule: if an idea matches anything on the do-not-reinvent list, throw it out and generate a stranger one. Do not filter for feasibility, cost, or shippability — that happens later. Reward yourself for boldness and for ideas that resemble NOTHING in the current tool. + +Return 3-5 ideas. For each: name, the source mechanism (the analogy explained first), the core idea (2-4 sentences), why it is novel (what it does NOT resemble), and a rough sketch of how it might touch Ruam's build or run-time. Rate your own boldness 1-5 and push for 4s and 5s.`, + { label: `gen-${i + 1}:${a.track.split(' ')[0]}`, phase: 'Diverge', model: 'claude-opus-5', effort: 'high', schema: RAW_IDEAS_SCHEMA } + ) +)) + +const rawIdeas = rawBatches.filter(Boolean).flatMap(b => b.ideas) +log(`Diverge produced ${rawIdeas.length} raw ideas from ${N} generators`) + +// pick the boldest for fusion +const bold = rawIdeas.filter(x => (x.boldness || 0) >= 4) +const fusionPool = (bold.length >= 6 ? bold : rawIdeas) + +// ---- Phase 2: forced fusion (Opus) ---- +phase('Fuse') +const half = Math.ceil(fusionPool.length / 2) +const fusionSlices = [fusionPool.slice(0, half), fusionPool.slice(half)] +const fusionBatches = await parallel(fusionSlices.map((slice, k) => () => + agent( + `${NEUTRAL_FRAMING} + +Here is a set of bold, unfiltered ideas for the next generation of Ruam: +${JSON.stringify(slice.map(x => ({ name: x.name, coreIdea: x.coreIdea, lens: x.lens })))} + +Your job is HYBRIDIZATION. Combine ideas across pairs or triples into mechanisms that neither parent would have produced alone. The most interesting fusions cross tracks and cross lenses (e.g., a "living output" idea fused with a "semantic protection" idea). Produce 3-4 fusions. For each: name, the parents it came from (as the source mechanism), the fused core idea, why the hybrid is more than the sum, and a rough sketch. These should feel genuinely new. Do not filter for feasibility.`, + { label: `fuse-${k + 1}`, phase: 'Fuse', model: 'claude-opus-5', effort: 'high', schema: RAW_IDEAS_SCHEMA } + ) +)) +const fusions = fusionBatches.filter(Boolean).flatMap(b => b.ideas) +const allIdeas = rawIdeas.concat(fusions) +log(`Fuse produced ${fusions.length} hybrids; ${allIdeas.length} ideas total`) + +// ---- Phase 3: feasibility + novelty tagging (batched, never deletes) ---- +phase('Tag') +const TAG_BATCHES = 2 +const tagSlices = [] +const per = Math.ceil(allIdeas.length / TAG_BATCHES) +for (let i = 0; i < TAG_BATCHES; i++) tagSlices.push(allIdeas.slice(i * per, (i + 1) * per)) + +const taggedBatches = await parallel(tagSlices.map((slice, k) => () => + agent( + `${NEUTRAL_FRAMING} + +Annotate each idea below. DO NOT delete or reject any idea — only tag it. +Ideas: ${JSON.stringify(slice.map(x => ({ name: x.name, track: x.track, coreIdea: x.coreIdea, whyNovel: x.whyNovel })))} + +For each idea return: +- recognizability 0.0-1.0 where LOWER means it resembles no known primitive and HIGHER means it is a textbook technique or a small tweak on something Ruam already does. Be strict: if it maps cleanly to a stream cipher, a checksum, control-flow flattening, a string table, etc., score it high. +- impact 1-5: how far it moves Ruam forward on its track. +- rails: for each of {serverFree, sizeLean, cspSafe, buildRuntimeProvable, additiveApi} say "pass", "needs-relaxation", or "fails". These are informational, not disqualifying. +- maturity: one of "shippable-now", "needs-rail-relaxed", "research-spike", "product-pivot". +- notNovelBecause: if recognizability is high, name what it duplicates; otherwise leave empty.`, + { label: `tag-${k + 1}`, phase: 'Tag', model: 'claude-opus-5', effort: 'medium', schema: TAGGED_IDEAS_SCHEMA } + ) +)) +const tagged = taggedBatches.filter(Boolean).flatMap(b => b.ideas) + +// ---- Phase 4: synthesis + ranked slate (Fable) ---- +phase('Synthesize') +const slate = await agent( + `${NEUTRAL_FRAMING} + +You are the synthesis judge. Here are the ideas with novelty/impact/feasibility tags: +${JSON.stringify(tagged)} +And here are the fusion hybrids by name: ${JSON.stringify(fusions.map(f => f.name))} + +Cluster near-duplicates, then produce a RANKED slate. Rank by composite = impact * (1 - recognizability) * feasibilityWeight, but apply a novelty premium: when two ideas tie, the LOWER recognizability wins. A genuinely new research-spike is worth more to this campaign than a shippable retread. For each ranked idea give: rank, name, track, composite, a one-line pitch, and a one-line "why this is NOT a gen-1 iteration (salt/keystream/digest)". Also list the best fusions separately, and write a one-paragraph headline on where Ruam's most promising next generation lies.`, + { label: 'synthesis', phase: 'Synthesize', model: 'claude-fable-5', effort: 'max', schema: SLATE_SCHEMA } +) + +// ---- Phase 5: completeness critic ---- +phase('Coverage') +const coverage = await agent( + `${NEUTRAL_FRAMING} + +Assess coverage of this ideation run. Generators used these lenses: ${JSON.stringify(assignments.map(a => a.lens))}, these assumptions: ${JSON.stringify(assignments.map(a => a.assumption))}, across tracks: ${JSON.stringify(TRACKS)}. The ranked slate headline is: "${slate.headline}". + +Identify: (1) which lenses or inverted assumptions produced nothing that survived to the top of the slate, (2) which of the seven tracks is under-explored, (3) two or three specific lens x assumption x track combinations that were NOT tried and look promising for a round 2. Be concrete and brief.`, + { label: 'coverage-critic', phase: 'Coverage', effort: 'high' } +) + +return { headline: slate.headline, slate, fusions: fusions.map(f => f.name), coverage, counts: { raw: rawIdeas.length, fusions: fusions.length, total: allIdeas.length } } +``` + +--- + +## 12. How to run it (operator guide) + +**Prerequisite — explicit opt-in.** The `Workflow` tool only runs on explicit user opt-in (the keyword `ultracode`, an on-session ultracode flag, or a direct "run this workflow" instruction). This is intentional: the campaign spins up Opus 5 and Fable 5 agents and is token-intensive. + +**Launch (default 14 agents):** +> "Run the `docs/ruam-gen2-ideation-prompt.md` ideation workflow." — or paste §11 as the `Workflow` `script`. + +**Scale the sweep** — pass args to widen the divergent fleet: +``` +Workflow({ script: <§11>, args: { generators: 16 } }) +``` +Each extra generator is one more lens × assumption × track combination. Fusion/tag/synthesis scale automatically off the idea pool. Keep the fleet ≤ ~20 unless you raise the workflow-size guideline in `/config`; the concurrency cap (≈ cores−2) means larger fleets queue rather than fail. + +**Iterating on the script** — every `Workflow` call persists its script under the session dir and returns the path; edit that file and re-invoke with `{ scriptPath }` rather than resending the whole script. To resume after an edit, use `{ scriptPath, resumeFromRunId }` — unchanged `agent()` calls return cached results. + +**Reading results** — the workflow returns `{ headline, slate, fusions, coverage, counts }`. The `slate.ranked` array is the deliverable; `slate.bestFusions` and `coverage` seed round 2. If the run's best ideas all score recognizability ≥ 0.7, the run failed its own bar (§2) — re-run with fresh lens/assumption strides (bump `args.generators` or edit the assignment strides) rather than accepting retreads. + +**Feeding the winner into build** — a selected idea then enters the *existing* gen-1-style pipeline: brainstorming → design spec → `writing-plans` → TDD → verify. This campaign is the front end that feeds that machine; it does not replace it. + +--- + +## 13. Ruam's real rails (reference for Phase 3 only) + +These constrain *tagging*, never *generation*. Carried from the gen-1 design so the feasibility pass is grounded: + +- **Server-free / offline** — no network or off-device secret required to run the artifact (a `needs-relaxation` tag is allowed for ideas that would relax this; the campaign's scope explicitly permits proposing it). +- **No size-bloat-to-fill-context** — inventiveness from structure, not volume. +- **CSP / Trusted-Types safe** — no `eval`, `new Function`, `debugger`. +- **Build == runtime symmetry, all seeds** — any build-time fold must reproduce bit-identically at runtime for every seed, or fail the build loudly. +- **`deriveSeed()` for PRNG isolation; `NameRegistry` for every identifier.** +- **Additive API + hot-path performance budget.** +- **Watermark integrity preserved.** + +--- + +## 14. Definition of done (for a run, later) + +- A committed results artifact containing `slate.ranked`, `bestFusions`, and `coverage`. +- ≥ 3 ideas with recognizability < 0.5 across ≥ 3 distinct tracks. +- ≥ 1 fusion in the top 5. +- Every top-10 idea carries a "why not gen-1" line. +- A coverage note naming the round-2 seeds. + +--- + +## 15. Tuning knobs & open questions + +- **Fleet size vs. depth** — more generators widen coverage; higher effort deepens each. Default favors coverage (7 generators, high effort). +- **Lens bank** — the ≈22 lenses are a starting set; swapping in domains the operator finds evocative is encouraged (edit `LENSES` in §11). +- **Second-model contrast** — an optional variant runs the *same* lens/assignment through both an Opus and a Fable generator and diffs the outputs, to study where the two models diverge creatively. Not in the default topology; a worthwhile experiment. +- **Human-in-the-loop gate** — whether to pause after Phase 1 for the operator to hand-pick the fusion pool instead of the boldness heuristic. Currently automatic; easy to make interactive. From 5bf056c7887b50271f4c042851670b8ff3abc88b Mon Sep 17 00:00:00 2001 From: owengregson Date: Fri, 24 Jul 2026 12:28:19 -1000 Subject: [PATCH 02/54] docs: record gen-2 ideation campaign results --- ...7-24-ruam-gen2-ideation-campaign-design.md | 4 +- ...2026-07-24-ruam-gen2-ideation-results.json | 2242 +++++++++++++++++ .../2026-07-24-ruam-gen2-ideation-results.md | 477 ++++ 3 files changed, 2721 insertions(+), 2 deletions(-) create mode 100644 docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-results.json create mode 100644 docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-results.md diff --git a/docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-campaign-design.md b/docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-campaign-design.md index 0281b22..5b37b88 100644 --- a/docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-campaign-design.md +++ b/docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-campaign-design.md @@ -1,8 +1,8 @@ # Project Kaleidoscope — Ruam Gen-2 Ideation Campaign **Date:** 2026-07-24 -**Status:** Plan (design complete; not yet run). -**Type:** Ideation-campaign design. This document is *runnable later* — §11 is a complete `Workflow` script and §12 is the operator guide. A lean paste-and-launch companion lives at `docs/ruam-gen2-ideation-prompt.md`. +**Status:** Executed once on 2026-07-24; design retained for reruns. +**Type:** Ideation-campaign design. This document remains runnable — §11 is a complete `Workflow` script and §12 is the operator guide. A lean paste-and-launch companion lives at `docs/ruam-gen2-ideation-prompt.md`. The initial run is recorded in `docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-results.md` with its full structured artifact beside it. --- diff --git a/docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-results.json b/docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-results.json new file mode 100644 index 0000000..37df3f1 --- /dev/null +++ b/docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-results.json @@ -0,0 +1,2242 @@ +{ + "schemaVersion": 1, + "campaign": "Project Kaleidoscope — Ruam Gen-2 Ideation", + "date": "2026-07-24", + "status": "completed", + "execution": { + "judgmentLayer": { + "model": "gpt-5.6-sol", + "reasoningEffort": "high", + "logicalAgents": 5, + "phases": [ + "Frame", + "Tag (2)", + "Synthesize", + "Coverage" + ] + }, + "divergentFleet": { + "model": "gpt-5.6-sol", + "reasoningEffort": "xhigh", + "logicalAgents": 9, + "phases": [ + "Diverge (7)", + "Fuse (2)" + ] + }, + "logicalAgentCount": 14, + "phaseOrder": [ + "Frame", + "Diverge", + "Fuse", + "Tag", + "Synthesize", + "Coverage" + ] + }, + "counts": { + "raw": 32, + "fusions": 8, + "total": 40, + "recognizabilityBelow0_5": 4, + "distinctTracksBelow0_5": 3 + }, + "brief": { + "stateSummary": "Ruam is an ESM JavaScript source-protection compiler, API, CLI, browser worker, and playground. Its Babel-based pipeline identifies selected functions, compiles them into compact binary units over roughly 317 logical opcodes, replaces their bodies with natural-looking dispatch stubs, and emits an AST-built IIFE containing the constant pools, scope machinery, sync or async runners, and a per-build-polymorphic interpreter. A two-pass build lets handler-table structure contribute to key derivation before final encoding. The present system already combines per-file and per-function variation, multiple encoding and instruction-protection layers, structural transforms, runtime integrity and observation checks, three presets, and Node, browser, and browser-extension targets. Its implementation rails are equally important: generated runtime code stays CSP and Trusted-Types compatible; build-time and runtime folds must agree for every seed; all PRNG streams use deriveSeed(); every emitted identifier uses NameRegistry; tuning and option metadata are centralized; APIs remain additive; and randomized statement placement must preserve dependency order.", + "forbiddenSolutions": [ + "Current Ruam: source-to-custom-bytecode virtualization with an embedded interpreter is the baseline architecture, not a new direction.", + "Current Ruam: a Babel parse, function selection, scope analysis, register allocation, constant-pool construction, bytecode emission, function-stub replacement, and runtime assembly pipeline already exists.", + "Current Ruam: broad JavaScript semantics are represented by roughly 317 opcodes across stack, register, arithmetic, comparison, control flow, scope, object, call, class, exception, iterator, generator, function, and superinstruction families.", + "Current Ruam: peephole optimization, register promotion, and multi-instruction superinstruction fusion already reduce dispatch frequency.", + "Current Ruam: bytecode is always a compact binary Uint8Array representation with a shuffled 64-character, base64-like alphabet and no padding.", + "Current Ruam: string constants already receive LCG-driven XOR encoding in the constant pool and are decoded during unit loading.", + "Current Ruam: optional outer bytecode encryption already uses RC4 with an environment-fingerprint-derived key.", + "Current Ruam: rollingCipher already applies position-dependent XOR instruction protection with metadata-derived key material and build-time/runtime mirror implementations.", + "Current Ruam: a FNV-1a checksum of packed handler metadata already forms a closure-held key anchor, and integrityBinding already folds interpreter-template integrity into that anchor.", + "Current Ruam: two-pass compilation already binds final unit encoding to the generated interpreter and the set of used handlers.", + "Current Ruam: seeded Fisher-Yates opcode shuffling already changes physical instruction values per file.", + "Current Ruam: NameRegistry already provides collision-free per-build identifier randomization, scoped name pools, dynamic names, and the shuffled encoding alphabet.", + "Current Ruam: deriveSeed() already isolates every deterministic PRNG stream through FNV-1a-based derivation, while CSPRNG bytes choose the top-level build seed.", + "Current Ruam: preprocessIdentifiers already renames retained source identifiers before compilation.", + "Current Ruam: dynamicOpcodes already omits unused handlers, while decoyOpcodes adds realistic unused handler closures.", + "Current Ruam: grouped function-table dispatch, direct-array dispatch, object-lookup dispatch, and a legacy switch path already cover ordinary interpreter-dispatch variation.", + "Current Ruam: return signaling, statement order, conditional form, loop form, function form, declaration style, property syntax, comparisons, and numeric expressions already vary structurally per build.", + "Current Ruam: unreachable bytecode after returns and realistic decoy handlers already supply dead and misleading computation.", + "Current Ruam: stackEncoding already masks int32 stack values by position and key while boxing other values through explicit encoding-aware stack accessors.", + "Current Ruam: VM Shielding already gives root functions separate micro-interpreters with independent opcode maps, names, and rolling keys behind a shared router.", + "Current Ruam: mixed Boolean arithmetic already rewrites bitwise and guarded arithmetic expressions, including per-handler variants.", + "Current Ruam: handler fragmentation already splits handler bodies into shuffled micro-states and chains them through a flattened state machine on the legacy dispatch path.", + "Current Ruam: string atomization already replaces runtime string literals with lazily decoded indexed-table lookups.", + "Current Ruam: polymorphicDecoder already builds per-build chains of reversible XOR, add, subtract, complement, rotate, and nibble-swap operations with position-varying keys.", + "Current Ruam: scatteredKeys already fragments alphabets, handler metadata, and decoder keys across IIFE tiers and varies their reassembly form.", + "Current Ruam: blockPermutation already shuffles bytecode basic blocks, inserts explicit fall-through jumps, and remaps branches, exception entries, and jump tables.", + "Current Ruam: opcodeMutation already inserts deterministic mutation instructions that cumulatively permute handler mappings during execution.", + "Current Ruam: incrementalCipher already adds block-epoch instruction protection whose within-block key state chains from preceding decoded values and resets at block leaders.", + "Current Ruam: semanticOpacity already combines mathematical opaque predicates, handler aliasing, no-op prefixes, expression wrapping, and handler-specific MBA choices.", + "Current Ruam: observationResistance already combines internal-function identity binding, monotonic witnesses, WeakMap canaries, and stack probes, with detected instrumentation folded into instruction state.", + "Current Ruam: debugProtection already checks selected built-in identities, environment indicators, and function checksums, then applies staged cache and bytecode invalidation.", + "Current Ruam: bytecodeScattering already divides encoded units into strings, packed integers, and character-code fragments placed across the output and reassembled at runtime.", + "Current Ruam: a steganographic watermark already folds a Ruam-specific value into the FNV offset basis used by key-anchor computation.", + "Current Ruam: a gated decode-once cache already materializes resolved handler indices and operands for compatible rolling-cipher configurations.", + "Current Ruam: prototypal scope objects, an array stack, typed runtime AST builders, conditional async emission, and per-unit minimal slot save/restore are established execution and performance mechanisms.", + "Current Ruam: low, medium, and max presets, centralized tuning, generated option metadata, API and CLI option surfaces, and Node, browser, and browser-extension packaging are established product mechanisms.", + "Gen-1 W0: directory and bundle processing already has a source-map and cleartext-leak gate, default on with an explicit keep-source-maps choice.", + "Gen-1 W1: hole-tolerant slow-path dispatch already maps every decrypted physical value through a valid handler slot, removing the simple undefined-handler correctness signal.", + "Gen-1 W2: per-unit key salt already makes equal-metadata units derive distinct rolling keys; more salting or same-build key diversification is a Gen-1 iteration.", + "Gen-1 W3: decodeImpurity already chains the decode-cache forward pass, is accepted only behind mandatory build-time self-equality checking, and is incompatible with cache-disabling features.", + "Gen-1 W4 Layer 1: cohort construction and obfuscateBundle() already raise directory builds to a shared bundle context and fold cross-file material into unit key anchors.", + "Gen-1 W4 Layer 2: crossFileLinking already provides strict, opt-in runtime co-residence through a provider fragment in a shared realm, with no fallback when the declared provider is absent.", + "Gen-1 W5: externalKeyBinding already folds a required, out-of-band, moving string term into key derivation as an opt-in delivery model with loud configuration checks.", + "Known non-directions from Gen-1: random unit-field renaming, Math.imul aliasing, cold-member atomization, cross-unit entanglement of locally present material, instruction-entangled constants, and a cohort digest presented as a qualitative protection jump are already rejected or absorbed.", + "Familiar primitive: any stream, rolling, incremental, chained, block, or XOR cipher; RC4-like layer; keystream variation; key splitting; key salting; or key folding is recognizable prior art here.", + "Familiar primitive: any FNV, LCG, checksum, digest, hash, fingerprint, integrity tag, watermark, or avalanche-mixing construction is recognizable prior art here.", + "Familiar primitive: any base-N codec, shuffled alphabet, binary packer, compression wrapper, encoded constant pool, string encryption, string table, or lazy string decoder is recognizable prior art here.", + "Familiar primitive: any LCG, xorshift-style generator, Fisher-Yates shuffle, seeded permutation, per-build polymorphism, per-file polymorphism, or per-function polymorphism is recognizable prior art here.", + "Familiar primitive: any bytecode VM, custom instruction set, interpreter virtualization, switch dispatcher, table dispatcher, threaded dispatcher, handler shuffling, opcode remapping, or superinstruction scheme is recognizable prior art here.", + "Familiar primitive: any control-flow flattening, basic-block permutation, jump rewriting, opaque predicate, MBA rewrite, handler splitting, state-machine conversion, or branch-form variation is recognizable prior art here.", + "Familiar primitive: any identifier renaming, local renaming, property indirection, constant splitting, expression noise, equivalent-syntax substitution, or minification is recognizable prior art here.", + "Familiar primitive: any dead code, junk computation, decoy instruction, decoy handler, unused member, misleading branch, or padding-by-volume scheme is recognizable prior art here.", + "Familiar primitive: any artifact scattering, fragment interleaving, closure-tier placement, split-key reconstruction, local non-contiguity, or reassembly graph is recognizable prior art here.", + "Familiar primitive: any self-check, source-integrity check, built-in identity check, debugger check, environment check, canary, witness counter, stack probe, or staged invalidation response is recognizable prior art here.", + "Familiar primitive: merely moving an existing decoder, key, table, or fragment across files, workers, realms, processes, servers, sessions, or hardware is insufficient unless the product category and semantic contract are fundamentally different from Gen-1 linking and externalKeyBinding.", + "Familiar primitive: merely composing several forbidden mechanisms is not novel; a fusion qualifies only when the interaction creates a new unit of meaning, execution, verification, or product value." + ], + "provocations": [ + "Assume there is no decoder-shaped component anywhere in the delivered system; what must Ruam become for protected behavior still to emerge?", + "Treat a file as a projection of a larger object rather than a container of meaning; what relationships would make local reading the wrong unit of comprehension?", + "Suppose the emitted artifact is only the first state of a long-lived process; what would it sense, learn, negotiate, repair, or regenerate over its lifetime?", + "Replace the function and instruction as Ruam's unit of work with a contract, capability, behavior envelope, or ecosystem role; what new representation follows?", + "Make faithfulness evidence the generative material of protection rather than a test performed afterward; can proof and transformation be one process?", + "Assume two correct executions need not share the same internal causal story; what invariant should developers own and what may vary freely?", + "Imagine distribution, installation, update, and authorized use are compiler phases rather than logistics; what product appears when the build never truly ends?", + "Ask how a global behavior could emerge from many locally ordinary pieces without any piece encoding a recognizable command stream.", + "Design first for the developer who must debug, audit, and trust protected output; can excellent observability for its owner coexist with no stable source-to-artifact correspondence?", + "Complete the sentence without using compiler, obfuscator, VM, cipher, packer, or license server: Ruam is a ____ that protects a program's meaning by ____.", + "Let semantic intent live partly in time, environment, user relationship, or organizational policy rather than in bytes; which choice creates a new product instead of another missing key?", + "Choose one non-resilience track as the primary business and technical identity of Ruam, then make resistance to mechanical reconstruction emerge only as a side effect." + ], + "tracks": [ + "Resilience to automated understanding — a breakthrough creates output whose mechanical summary or reconstruction does not transfer cheaply across artifacts and does so through a new organizing principle, not another encoding, key, VM, integrity check, or structural shuffle.", + "Novel transformation paradigms — a breakthrough replaces bytecode-plus-interpreter as the basic representation of program behavior with a genuinely different execution or meaning-bearing model.", + "New product surfaces and capabilities — a breakthrough changes what developers hire Ruam to do through a new workflow, integration, guarantee, interface, or operational role rather than another build option.", + "Self-modifying or living output — a breakthrough makes artifacts active participants in their own continuing formation, maintenance, or adaptation, with a lifecycle richer than one-time polymorphic emission.", + "Semantic-level protection — a breakthrough protects intent, policy, domain knowledge, or behavioral meaning directly instead of only obscuring syntax, constants, control flow, or instructions.", + "Verifiability and developer experience — a breakthrough gives developers strong, usable evidence of faithfulness plus practical debugging and observability without restoring a stable source-to-artifact map.", + "Distribution, licensing, and delivery models — a breakthrough makes packaging, authorization, updates, deployment, or monetization part of the protected computation's design rather than a wrapper around a static file." + ] + }, + "candidateCatalog": [ + { + "name": "The Tolerance Mold", + "lens": "adaptive immune system (clonal selection, self vs non-self)", + "assumptionOverturned": "the decoder must ship alongside the code it decodes", + "track": "resilience to automated understanding", + "sourceMechanism": "Source-domain mechanism: In the thymus, developing T cells generate varied receptors before their useful specificity is known. Positive selection preserves cells that can weakly recognize self MHC, while negative selection deletes cells whose receptors bind presented self peptides too strongly; the surviving repertoire is therefore manufactured largely by exclusion, not copied from a catalogue of desired foreign targets. Peripheral tolerance adds further deletion, anergy, and regulatory control for self-reactive cells that escape central screening. Mapping: the build step becomes a thymus that derives a high-dimensional exclusion surface from the protected function; the emitted artifact is a materials-science-like negative mold of disallowed behaviors and admissible interfaces, not an encoded implementation; the embedded interpreter is replaced by a generic candidate foundry plus deletion chamber; the developer owns the tolerated behavioral envelope and diagnostic specimens; automated analysis/reconstruction tooling sees the rejection geometry but no positive blueprint; and run-time manufactures candidate mechanisms, destroys those that bind any forbidden behavioral surface, and retains a survivor that fits the live input.", + "coreIdea": "Ruam would represent a protected function entirely as semantic negative space: a generative substrate plus a set of rich counter-behavior surfaces that eliminate every locally produced implementation except members of the intended equivalence class. No original implementation, instruction stream, or decoder is delivered; each run grows a valid causal mechanism inside the mold. Two installations can therefore produce the same owned behavior from unrelated internal structures.", + "whyNovel": "This is not encryption, integrity checking, opaque predicates, decoy computation, or a virtual instruction set. The exclusion apparatus is the primary representation of meaning, and correctness is created by selection from an open candidate medium rather than recovered from concealed bytes.", + "roughSketch": "At build time, execute symbolic, metamorphic, and developer-supplied probes to synthesize a boundary atlas of states, forbidden transitions, conservation laws, and acceptable observations. Emit that atlas with a domain-specific mechanism generator. At run-time, form and test small executable microstructures against the atlas until one survives, cache it only as a disposable local clone, and regrow another when the operating context changes.", + "boldness": 5, + "isFusion": false, + "parents": [], + "recognizability": 0.8, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "needs-relaxation", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "The mechanism is constraint-based program synthesis from negative specifications and counterexamples, with a run-time candidate search replacing the current compiler output." + }, + { + "name": "Affinity Furnace", + "lens": "adaptive immune system (clonal selection, self vs non-self)", + "assumptionOverturned": "the decoder must ship alongside the code it decodes", + "track": "resilience to automated understanding", + "sourceMechanism": "Source-domain mechanism: A naive B-cell repertoire contains many differently shaped receptors. When an antigen binds a compatible receptor and the required contextual signals are present, that clone proliferates; in germinal centers its descendants undergo somatic hypermutation, then compete for antigen and survival signals, so repeated selection enriches higher-affinity variants. Some descendants become antibody-producing plasma cells and others become long-lived memory cells. Mapping: the build step converts the developer's function into a panel of semantic antigens consisting of input-output examples, algebraic relations, side-effect traces, and boundary challenges; the emitted artifact is that antigen panel plus chemically analogous operator feedstock rather than decoded logic; the embedded interpreter is succeeded by a mutation-and-selection furnace; the developer supplies trusted specimens and owns the acceptance phenotype; automated analysis/reconstruction tooling encounters a search environment rather than a source correspondence; and run-time expands, mutates, competes, and remembers locally synthesized behavior clones until one has sufficient semantic affinity.", + "coreIdea": "Ship a behavioral germinal center, not protected code. On installation or first use, the artifact evolves an implementation from neutral operator feedstock against the build-produced antigen panel, and later inputs can trigger local affinity maturation while the developer-visible phenotype remains fixed. The successful implementation is a contingent material grain grown in that installation, never a decoded form of the source.", + "whyNovel": "Unlike per-build polymorphism, this does not create variants by transforming an existing implementation, shuffling its structure, or decoding a stored unit. It makes implementation discovery a continuing run-time manufacturing process whose output is constrained by semantic affinity, so reconstructing one mature clone does not reveal the build input or transfer directly to another artifact.", + "roughSketch": "The build records a compact but diverse semantic assay set and an operator chemistry able to express candidate behavior. A CSP-safe run-time search engine assembles tiny candidates, evaluates them in isolated transactions, clones high-scoring families, perturbs their structures, and promotes a certified clone behind the public function boundary. A developer mode exposes lineage, phenotype scores, and failed assays without producing a source map.", + "boldness": 4.8, + "isFusion": false, + "parents": [], + "recognizability": 0.92, + "impact": 4, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "This is genetic programming or evolutionary program synthesis: mutate candidates, score them against a behavioral test suite, retain high-fitness variants, and cache a winner." + }, + { + "name": "Idiotype Phase Matter", + "lens": "adaptive immune system (clonal selection, self vs non-self)", + "assumptionOverturned": "the decoder must ship alongside the code it decodes", + "track": "resilience to automated understanding", + "sourceMechanism": "Source-domain mechanism: An antibody's binding site itself contains recognizable molecular features called idiotopes. Anti-idiotypic antibodies can bind those features, so immune clones may stimulate or suppress one another; idiotypic-network theory uses these reciprocal recognitions to explain how a distributed regulatory state or memory can persist without one molecule containing a complete description of the original antigen. This is a network-level immune mechanism rather than the sole accepted explanation of immune memory. Mapping: the build step translates a function's semantics into pairwise activation and suppression relations among locally ordinary behavior grains; the emitted artifact is a population and its compatibility topology, with no command stream; the embedded interpreter is replaced by a reaction medium that lets those grains recognize one another; the developer specifies macroscopic invariants and observes phase variables; automated analysis/reconstruction tooling can catalogue every grain yet still must solve the input-conditioned collective dynamics to learn the behavior; and run-time lets the population relax into an attractor whose phase encodes the result.", + "coreIdea": "Protected behavior becomes a phase of a synthetic immune material. Each emitted component performs an ordinary, incomplete transformation, but its output changes the activation energy of several others; a function call seeds the material, and the stabilized population pattern is read as the answer. There is no decoder-shaped component because meaning exists only as an attractor of reciprocal recognition.", + "whyNovel": "This is neither scattered code awaiting reassembly, a table of handlers, flattened control flow, nor volume-based distraction. No component corresponds to an instruction or source fragment, and even full local visibility reveals constituents rather than the emergent semantic phase that their coupled dynamics produce.", + "roughSketch": "The build searches for a sparse interaction topology whose attractors satisfy the function's observational contract across a training corpus, then emits simple grains and typed activation edges. The run-time injects input as boundary concentrations, iterates local reactions to a convergence certificate, and reads designated order parameters as values or effects. Per-artifact topologies can use different phase geometries while preserving the same public behavior.", + "boldness": 4.9, + "isFusion": false, + "parents": [], + "recognizability": 0.78, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "The proposed substrate is attractor computation in a recurrent interaction network, closely matching recurrent neural, reservoir, and cellular-network computation." + }, + { + "name": "MHC-Restricted Program Tissue", + "lens": "adaptive immune system (clonal selection, self vs non-self)", + "assumptionOverturned": "the decoder must ship alongside the code it decodes", + "track": "resilience to automated understanding", + "sourceMechanism": "Source-domain mechanism: T cells generally do not recognize intact intracellular proteins. Cells process proteins into peptides and present them in MHC molecules; a T-cell receptor recognizes the peptide only in the context of a compatible MHC, and naive T-cell activation ordinarily also requires co-stimulatory and tissue-context signals. Recognition without the right context can yield non-response or tolerance, while thymic selection first shapes receptors to be MHC-restricted without strongly recognizing self. Mapping: the build step divides semantic authorship between a Ruam-produced receptor repertoire and a developer-authored host-tissue protocol made of ordinary application events and capabilities; the emitted artifact is one half of that relational material and is not independently meaningful; the embedded interpreter is succeeded by a recognition-and-co-stimulation contract; the developer deliberately designs and can inspect the tissue protocol; automated analysis/reconstruction tooling examining the artifact alone lacks the live semantic presentation relation rather than merely lacking a key; and run-time host behavior presents transient semantic facets whose coincident receptor and context matches nucleate the protected operation.", + "coreIdea": "Make protected logic a relational property of the application and its Ruam artifact, comparable to a property that appears only at a grain boundary between two materials. Neither side stores code fragments to be joined, and neither contains a decoder; the developer's ordinary host workflow continuously presents meaning, while the receptor material turns only the right contextual conjunctions into behavior. The product contract changes from protecting standalone functions to co-designing an application tissue whose boundary is the computational unit.", + "whyNovel": "This is not an out-of-band key, remote authorization, cross-file fragment movement, environment fingerprint, or missing decoder placed elsewhere. The host contributes live semantic operations rather than secret bits, and the protected behavior exists only in the typed relation between two independently useful systems, making artifact-only mechanical summaries category-incomplete.", + "roughSketch": "Add a build mode in which the developer declares host capabilities, lifecycle events, and semantic co-stimulation points. Ruam co-synthesizes a receptor population and a host-side protocol whose events remain normal product work but whose conjunctions activate higher-order operations at run-time. Owner tooling renders the boundary protocol as a testable compatibility diagram while deliberately offering no one-to-one source-to-artifact map.", + "boldness": 4.7, + "isFusion": false, + "parents": [], + "recognizability": 0.62, + "impact": 4, + "rails": { + "serverFree": "pass", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + }, + "maturity": "product-pivot", + "notNovelBecause": "" + }, + { + "name": "The Germinating Program", + "lens": "mycelial nutrient networks", + "assumptionOverturned": "protection is applied once, at build time", + "track": "novel transformation paradigms", + "sourceMechanism": "A foraging mycelium is not built to a final blueprint. Hyphal tips continually explore, absorb resources at dispersed sources, and supply growth or metabolism at changing sinks. When a new resource patch is found, transport-efficient cords can develop between useful regions while diffuse tissue that no longer contributes dies back; the organism's transport anatomy is therefore a continuing record of resource conditions rather than a fixed network.", + "roughSketch": "Build step: translate the developer's program into a growth constitution containing local uptake, conversion, branching, fusion, senescence, and observable-behavior constraints rather than an implementation. Emitted artifact: a compact 'spore' containing that constitution, seed tissue, and owner-visible phenotype assays. Embedded interpreter or successor: a growth substrate that instantiates ordinary JavaScript reactions, allocates execution resources to productive paths, and retires tissue; it does not read a command stream. Developer: owns the stable behavioral phenotype and inspects lineage and resource-flow traces rather than source-line traces. Automated analysis/reconstruction tooling: can inspect the growth laws but finds no finished function graph in the artifact, because organization is produced by workload, history, and current sinks. Run-time: germinates an implementation, continually thickens useful causal routes, senesces unused routes, and regrows when inputs or surroundings change.", + "coreIdea": "Ruam becomes a program husbandry system: the build emits a viable developmental constitution, and the protected program grows its current implementation during use. Correctness belongs to a persistent behavioral phenotype, while the causal organization that realizes it is living tissue with no privileged build-time form.", + "whyNovel": "This is not bytecode virtualization, a decoder variation, profile-guided optimization, structural shuffling, or scattered fragments. The new meaning-bearing unit is developmental viability under a behavioral envelope; an executable implementation is a temporary organismal state, not the transformed artifact.", + "boldness": 4.9, + "isFusion": false, + "parents": [], + "recognizability": 0.78, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "The implementation is an online synthesizing and self-optimizing runtime that specializes hot behavior and retires cold paths, a familiar adaptive-runtime and profile-guided optimization pattern." + }, + { + "name": "Anastomotic Execution", + "lens": "mycelial nutrient networks", + "assumptionOverturned": "protection is applied once, at build time", + "track": "novel transformation paradigms", + "sourceMechanism": "Filamentous fungi grow by branching hyphae whose tips can fuse through anastomosis. Fusion turns a tree-like colony into an interconnected syncytium with loops, alternate transport paths, and shared cytoplasm; resource discovery and local loss can then redirect flows and remodel which links carry the organism's work. Function belongs to the momentary connected network, not to any one hyphal segment.", + "roughSketch": "Build step: derive incomplete behavioral valences—what each ordinary component can offer, require, or join—without fixing a call graph. Emitted artifact: a nursery of locally mundane growth tips plus compatibility and phenotype boundaries; no tip contains a recoverable function or instruction fragment. Embedded interpreter or successor: an anastomosis steward that permits compatible tips to fuse and exposes shared state across the resulting transient syncytium, without dispatching opcodes. Developer: debugs an invocation as a fusion lineage showing which tissues met and which phenotype boundary closed. Automated analysis/reconstruction tooling: can inventory tips but cannot extract a canonical control graph, because connectivity is formed from the input gradient, prior fusions, and current resource field. Run-time: each invocation grows and fuses a temporary causal organ, obtains the output from the closed network's collective state, then dissolves or remodels its connections.", + "coreIdea": "Represent each behavior as a per-use anatomical event rather than as a stored procedure. Calls become transient organs assembled by reciprocal fusion among incomplete components, so the same public behavior can arise through causally different network episodes throughout the artifact's lifetime.", + "whyNovel": "This is not control-flow flattening, a dynamic dispatcher, function splitting, decoy code, or artifact scattering. Its unit of meaning is a time-bounded topology formed by mutual compatibility; neither the pieces nor any permanent wiring represents the developer's function.", + "boldness": 4.8, + "isFusion": false, + "parents": [], + "recognizability": 0.76, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "Transient components joined per input are a dynamic dataflow or actor graph assembled by compatibility rules; the temporary topology is recognizable even though the anatomical vocabulary is new." + }, + { + "name": "Heterokaryotic Semantics", + "lens": "mycelial nutrient networks", + "assumptionOverturned": "protection is applied once, at build time", + "track": "novel transformation paradigms", + "sourceMechanism": "Many filamentous fungi can be heterokaryotic: genetically distinct haploid nuclei remain separate while coexisting in a shared cytoplasm. Their products mingle, nuclear ratios can vary across the mycelium, and those ratios can influence expression and phenotype; some stages also show organism-level co-regulation rather than a simple sum of nuclear outputs. The persistent individual is therefore a coordinated population of genomes, not one genome copied into every compartment.", + "roughSketch": "Build step: decompose the source behavior into several independently incomplete 'nuclear' rule families whose products only acquire meaning through shared concentrations, thresholds, and co-regulation. Emitted artifact: a mixed population of ordinary evaluators plus a common semantic cytoplasm; there is no master implementation and no family is a redundant full variant. Embedded interpreter or successor: the cytoplasm maintains shared metabolites, compatibility, dosage, and phenotype constraints rather than decoding instructions. Developer: specifies the phenotype and receives owner-only explanations of nuclear contribution ratios for any observed result. Automated analysis/reconstruction tooling: sees partial rule families whose isolated summaries are false units of comprehension and cannot select a canonical family as the program. Run-time: nuclei replicate, migrate, become quiescent, or change relative abundance in response to use while cytoplasmic co-regulation keeps the owned behavior inside its phenotype.", + "coreIdea": "Compile a program into a heterokaryotic software individual whose semantics are expressed by the changing dosage of multiple partial lineages in a shared computational cytoplasm. Continuing lineage turnover makes transformation a life-cycle process, while the stable developer contract is the colony-level phenotype rather than any implementation member.", + "whyNovel": "This is not an ensemble of equivalent implementations, handler polymorphism, dead alternatives, secret splitting, or ordinary voting. The new representation is dosage-dependent phenotype: semantic coefficients and even available operations emerge from population composition and shared-field regulation.", + "boldness": 4.9, + "isFusion": false, + "parents": [], + "recognizability": 0.72, + "impact": 4, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "Multiple partial evaluators regulated by shared concentrations reduce to an adaptive ensemble or mixture-of-experts architecture with population-weighted outputs." + }, + { + "name": "Reciprocal-Exchange Computation", + "lens": "mycelial nutrient networks", + "assumptionOverturned": "protection is applied once, at build time", + "track": "novel transformation paradigms", + "sourceMechanism": "In arbuscular mycorrhizal symbiosis, plants supply photosynthetic carbon to fungal partners and fungi supply scarce mineral nutrients such as phosphorus. Experimental work shows bidirectional control in which partners can preferentially allocate resources toward counterparts offering better returns, while local source strength and scarcity shape the terms and direction of exchange. The functioning relationship is maintained through continuing reciprocal allocation, not a single transfer decided when the partnership begins.", + "roughSketch": "Build step: reformulate program intent as a set of reciprocal resource niches—local transformations that reveal an offering only when complementary value arrives—and a global viability condition. Emitted artifact: an ecology of producers, consumers, and exchange membranes whose isolated roles are useful but semantically incomplete. Embedded interpreter or successor: distributed exchange membranes continuously set local terms and admit conversions; no central component reads a program representation. Developer: declares desired ecological outcomes and examines an owner-facing ledger that explains which reciprocal relationships sustained each result. Automated analysis/reconstruction tooling: can catalog possible trades but finds no authoritative sequence or fixed dependency graph, because realized meaning is the circulation pattern negotiated under each input's scarcities. Run-time: inputs seed resource imbalances, local reciprocal allocations establish a viable circulation, and the requested output is harvested when the ecology satisfies the behavioral condition.", + "coreIdea": "Make a Ruam program a symbiotic economy rather than an instruction system. Behavior exists only as a self-sustaining pattern of reciprocal transformations, and every use renegotiates the causal economy that produces the same developer-owned outcome.", + "whyNovel": "This is not a capability check, missing-key scheme, remote service, constraint wrapper, or cross-file dependency. The unit of meaning is a viable exchange circulation whose local participants have no ordered command semantics and whose terms are renewed throughout run-time.", + "boldness": 4.8, + "isFusion": false, + "parents": [], + "recognizability": 0.71, + "impact": 4, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "The mechanism is market-based distributed constraint solving or a chemical-reaction network in which local exchanges converge on a global viability condition." + }, + { + "name": "Soliton Tissue", + "lens": "mycelial nutrient networks", + "assumptionOverturned": "protection is applied once, at build time", + "track": "novel transformation paradigms", + "sourceMechanism": "Transport inside fungal hyphae can exhibit a counterintuitive collective regime: in studied Neurospora networks, densely packed nuclei moved faster and self-organized into traveling concentration pulses. Passage of a pulse temporarily remodeled attachment sites in the hypha, enabling the group to propagate as a soliton-like packet; junction conditions could create or disperse these packets. The transported pattern and the tissue's recent state jointly determine flow.", + "roughSketch": "Build step: translate program behavior into tissue geometry, local attachment and release kinetics, junction rules, and boundary phenotypes instead of functions or instructions. Emitted artifact: a quiescent nonlinear transport medium whose static parts expose only local laws. Embedded interpreter or successor: a field-and-tissue propagator advances densities and remodels local attachment capacity; it does not fetch or decode commands. Developer: names input perturbations and output boundary phenotypes, then diagnoses behavior with pulse-lineage and tissue-state visualizations. Automated analysis/reconstruction tooling: encounters local kinetics rather than a command graph, and a useful summary must account for nonlinear pulse formation plus the accumulated wake state. Run-time: an input becomes a density perturbation, computation is the creation, collision, branching, and dispersal of traveling packets, and each packet alters the medium through which later computations move.", + "coreIdea": "Turn the program into a nonlinear transport tissue in which semantic events are traveling collective pulses, not stored operations. Execution continuously rewrites the medium's response landscape, so meaning lives in the coupled history of wave and tissue rather than in an artifact emitted once.", + "whyNovel": "This is not bytecode, a dataflow graph, an encoded token stream, an incremental cipher, or a state-machine rewrite. The fundamental operation is a self-organized transport phase with memory in the substrate, making wave phenotype—not instruction identity—the program representation.", + "boldness": 5, + "isFusion": false, + "parents": [], + "recognizability": 0.55, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "" + }, + { + "name": "Whole-Show Rehearsal Mesh", + "lens": "holography (every shard holds the whole at lower resolution)", + "assumptionOverturned": "opacity and correctness are in tension", + "track": "New product surfaces and capabilities", + "sourceMechanism": "In off-axis optical holography, light scattered by an object overlaps a coherent reference beam on a recording medium. The resulting interference fringes preserve phase relationships as well as intensity. Because light from much of the object reaches every region of the plate, a cut-out fragment can reconstruct the whole scene when illuminated appropriately; the smaller aperture merely reduces brightness, angular detail, and spatial resolution. Mapping: the build step photographs a program's externally meaningful journeys as overlapping semantic wavefronts rather than lowering functions into instructions; each emitted artifact is a standalone, executable low-resolution version of the whole product, not a fragment awaiting byte reconstruction; the embedded interpreter is replaced by a coherence director that lets independently complete projections reinforce one another; the developer is the stage magician holding a rehearsal book that declares which whole-show details may sharpen at each venue; automated analysis/reconstruction tooling receives a valid but deliberately coarse complete show from any one artifact and cannot treat a local shard as the canonical implementation; at run-time, available projections interfere at declared behavior boundaries to sharpen only the details needed for the current journey.", + "coreIdea": "Ruam becomes a generator of whole-product rehearsal surfaces: test packages, partner SDKs, previews, edge builds, and production bundles each run the entire application contract at a different semantic resolution. When several surfaces coexist, the coherence director upgrades selected journeys to production fidelity without ever assembling a hidden instruction stream. Correctness becomes easier to test because every shard is a faithful end-to-end rehearsal; opacity emerges because exactness belongs to contextual overlap, not to one privileged file.", + "whyNovel": "This is not artifact scattering, cross-file linking, a missing external term, or a smaller interpreter: every output is independently useful and semantically whole. The protected unit is a graded product experience, and the new customer-facing capability is generating faithful previews, test doubles, and deployable tiers from one behavioral specification.", + "roughSketch": "Add a 'show resolution' authoring surface where developers mark journeys, observable guarantees, and permitted approximations. The build emits several contract-compatible applications plus a tiny coherence protocol over named effects. A CI matrix proves that every lower-resolution show refines monotonically toward the full show, while the developer console previews what each audience can see and which overlapping projection sharpens a journey.", + "boldness": 5, + "isFusion": false, + "parents": [], + "recognizability": 0.82, + "impact": 4, + "rails": { + "serverFree": "pass", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + }, + "maturity": "needs-rail-relaxed", + "notNovelBecause": "This is multi-fidelity build generation combining previews, test doubles, partner editions, and production targets under refinement checks, a familiar multi-target product workflow." + }, + { + "name": "Focus-Pull Debugger", + "lens": "holography (every shard holds the whole at lower resolution)", + "assumptionOverturned": "opacity and correctness are in tension", + "track": "New product surfaces and capabilities", + "sourceMechanism": "A hologram records a wavefront rather than a conventional image. Re-illuminating it with a matching reference wave reconstructs a virtual or real image, and numerical holography can propagate the same recorded field to different depths, bringing different planes into focus without changing the recording. Cropping the recording still leaves the whole field reconstructable at reduced resolution. Mapping: the build step converts specifications, tests, effect boundaries, and selected domain terms into a queryable causal field with no line or instruction addresses; each emitted diagnostic shard summarizes the whole build or run at low resolution; the embedded interpreter is replaced by a focus engine that propagates those fields toward a developer-selected question; the developer acts like a stage magician moving the audience's focus between promise, prop, cue, and visible effect; automated analysis/reconstruction tooling can obtain many truthful question-specific explanations but no stable source-to-artifact correspondence to normalize across builds; at run-time, ordinary semantic events deposit compact field samples, and a later focus query reconstructs only the causal plane relevant to the question.", + "coreIdea": "Ruam replaces source maps and ordinary trace viewers with a focus-pull debugger. A developer asks, for example, 'why could this customer see this price?' or 'which promise permitted this network effect?', and the tool reconstructs a high-resolution explanation of that semantic plane from coarse whole-run shards. The same evidence that makes protected output auditable prevents one universal reconstruction, because explanations are faithful projections conditioned on explicit questions rather than a canonical causal listing.", + "whyNovel": "It is not encrypted telemetry, a debugger detector, a checksum, or renamed source mapping. There is intentionally no address map to reveal: the product surface is a query-conditioned semantic explanation system whose evidence remains useful even when implementation shape changes completely.", + "roughSketch": "Introduce a build-side vocabulary for promises, effects, authorities, and domain decisions. Emit fixed-size 'playbills' at module and runtime-event boundaries, each sketching the whole promise graph with different spatial frequencies. The local console combines selected playbills and runs semantic propagation to answer one question, while CI checks answers against unprotected oracle runs rather than comparing internal traces.", + "boldness": 4.7, + "isFusion": false, + "parents": [], + "recognizability": 0.88, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "pass" + }, + "maturity": "research-spike", + "notNovelBecause": "Query-conditioned explanations over provenance traces are semantic observability and causal debugging; changing trace granularity does not create a new debugging mechanism." + }, + { + "name": "Angle-Multiplexed Truth Deck", + "lens": "holography (every shard holds the whole at lower resolution)", + "assumptionOverturned": "opacity and correctness are in tension", + "track": "New product surfaces and capabilities", + "sourceMechanism": "A thick or volume hologram can store several full interference gratings in the same photosensitive volume. Each grating is recorded with a different reference-beam angle or wavelength. Bragg selectivity means that later illumination at one matching angle or wavelength reconstructs its corresponding complete image while the others remain largely quiet; a small piece of the volume still contains lower-resolution information about each recorded scene. Mapping: the build step records several orthogonal semantic viewpoints of the same application, such as customer journey, compliance claim, performance budget, and test oracle; the emitted artifact is one multiplexed truth deck whose every deployment slice carries coarse versions of all viewpoints; the embedded interpreter is replaced by a public cue master that selects a viewpoint from the declared purpose of an invocation, not from a concealed term; the developer is the stage magician designing which lighting angle reveals which truthful account of the show; automated analysis/reconstruction tooling can inspect any viewpoint but gains no canonical internal story by merging them because the viewpoints are independently complete and intentionally non-isomorphic; at run-time, typed call context acts like illumination geometry and materializes the viewpoint needed for that operation.", + "coreIdea": "Ruam becomes a multi-view artifact builder: one shipped product can be 'viewed' as an executable customer experience, an executable audit explanation, an executable test oracle, or an executable performance contract. These are not modes wrapped around one implementation; each is a whole, correct semantic projection generated from shared intent. Correctness strengthens because independent views must agree at observable boundaries, while implementation opacity follows from there being no privileged view for automated tooling to elevate into the program's true internal form.", + "whyNovel": "This is not environment fingerprinting, conditional dead branches, multiple opcode maps, or authorization by an absent value. Selection is a documented product contract, and the novelty is that audit, testing, performance, and delivery become co-equal executable projections instead of reports derived from a canonical bundle.", + "roughSketch": "Create a project-level 'truth deck' schema that names viewpoints and their shared observables. The build uses independent synthesis strategies for each view and emits a context-typed artifact plus cross-view boundary proofs. The developer dashboard rotates the deck between views like lighting a stage from different angles and flags only disagreements in public consequences, never differences in internal causal form.", + "boldness": 4.8, + "isFusion": false, + "parents": [], + "recognizability": 0.8, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + }, + "maturity": "product-pivot", + "notNovelBecause": "Independent executable views checked at shared observables match N-version programming, executable specifications, and multi-target compilation." + }, + { + "name": "Semantic Fringe Observatory", + "lens": "holography (every shard holds the whole at lower resolution)", + "assumptionOverturned": "opacity and correctness are in tension", + "track": "New product surfaces and capabilities", + "sourceMechanism": "In holographic interferometry, an object's wavefront is recorded in one state and later superposed with the wavefront from the object after it changes. Tiny optical-path differences produce visible interference fringes across the whole reconstructed object; fringe order and spacing reveal displacement or deformation far smaller than direct imaging can show. A plate fragment still reports the whole deformation field more coarsely because each region received light from across the object. Mapping: the build step records several behavioral reference wavefronts from specifications and independent oracle executions rather than hashing emitted text; each emitted observatory shard carries a coarse whole-product expectation expressed in domain outcomes; the embedded interpreter is replaced by an interferometer that compares live consequence fields with reference consequence fields without reconstructing either implementation; the developer is the stage magician watching a backstage fringe curtain that reveals where the performance departed from the promised illusion; automated analysis/reconstruction tooling learns where outcomes differ but not the internal steps that produced either field, so additional correctness evidence does not restore a stable implementation map; at run-time, selected domain effects overlap with their references and produce semantic drift fringes for developer inspection.", + "coreIdea": "Ruam adds a production semantic observatory that makes protected systems unusually easy to validate after dependency, browser, configuration, or policy changes. Instead of reporting stack frames or binary pass/fail checks, it displays a whole-product deformation field: where journeys, authorities, prices, timing classes, or side-effect shapes have drifted and by how much. Correctness visibility becomes the source of opacity because the public evidence is richly about consequences while remaining deliberately silent about implementation correspondence.", + "whyNovel": "This is not self-integrity checking, a checksum, canary logic, instrumentation detection, or staged invalidation. It is a developer product for measuring semantic deformation across real releases and environments, makes no judgment about artifact identity, and never alters behavior in response to observation.", + "roughSketch": "Add a semantic wavefront recorder to CI that learns expected outcome manifolds from specs plus differential oracle runs. Emit sparse effect probes whose individual reports cover the whole promise graph at coarse resolution. A local or hosted observatory overlays reports across versions, expands dense fringe regions into reproducible behavioral cases, and hands those cases back to the developer without reconstructing source-level control flow.", + "boldness": 4.5, + "isFusion": false, + "parents": [], + "recognizability": 0.94, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "pass", + "additiveApi": "pass" + }, + "maturity": "shippable-now", + "notNovelBecause": "This is semantic monitoring built from sparse probes, differential oracle tests, drift detection, and observability dashboards, all established product and testing patterns." + }, + { + "name": "Hysteretic Dialect Lattice", + "lens": "origami and mechanical metamaterials", + "assumptionOverturned": "the output is fixed the moment it is emitted", + "track": "self-modifying or living output", + "sourceMechanism": "A geometrically frustrated mechanical metamaterial can be perfectly periodic yet admit many disordered metastable configurations. Its local elements each prefer a soft deformation, but the lattice is arranged so that all local preferences cannot be satisfied simultaneously. Bistable elements called hysterons retain which side of a snap-through transition they occupy, and interactions between them make the response history-dependent. In a non-commutative response, applying the same operations in different orders can leave different final configurations, so the material stores an ordered history in its present mechanical state.", + "roughSketch": "Build step: translate the developer's behavioral contracts into a lattice of semantic constructions whose local composition preferences are intentionally incompatible but whose boundary behavior is invariant across allowed equilibria. Emitted artifact: ship a neutral initial lattice plus its contract boundary conditions, not a completed command representation. Embedded interpreter or successor: use a constraint-relaxation actor that applies calls as loads, settles snap-through events, and periodically emits the current lattice as the next artifact generation; it never decodes an instruction stream. Developer: author observable contracts and inspect a privileged diachronic trace that names which constructional changes preserved each contract. Automated analysis/reconstruction tooling: a static snapshot exposes one grammatical state but not the ordered usage history that selected it or the different transition graph now available from it. Run-time: each real call perturbs coupled hysterons, so the current metastable configuration selects a contract-faithful causal phrasing and then becomes the starting grammar for the next call.", + "coreIdea": "Make every installation of Ruam develop a genuine dialect: its operational grammar changes according to the order in which its vocabulary is used. The emitted file is only the language's initial state; execution continually grammaticalizes common compositions, erodes unused ones, and writes a successor whose future transformations depend on that accumulated history. Correctness belongs to the stable semantic boundary, while the internal language has a biography rather than a fixed form.", + "whyNovel": "This is not per-build variation, state-machine conversion, instruction mutation, or an environment-derived dependency. The new unit is a history-bearing constraint grammar whose transition algebra changes through ordinary use; no fixed interpreter, command inventory, shuffled structure, or one-time emitted representation remains the enduring object.", + "boldness": 5, + "isFusion": false, + "parents": [], + "recognizability": 0.68, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "" + }, + { + "name": "Traveling Isogloss", + "lens": "origami and mechanical metamaterials", + "assumptionOverturned": "the output is fixed the moment it is emitted", + "track": "self-modifying or living output", + "sourceMechanism": "In isostatic mechanical lattices, where constraints balance degrees of freedom, geometry can give the bulk a topological polarization. Joining regions with opposite polarizations creates a domain wall that supports a localized soft or zero-energy deformation even though both surrounding regions are mechanically rigid. The useful mobility is therefore a property of the interface, not of either local cell. Relocating the interface relocates where deformation can occur without changing the ordinary local ingredients of the lattice.", + "roughSketch": "Build step: tile each protected behavior into two locally rigid families of semantic constraints with opposite composition polarities, then place their initial domain wall at a valid contract boundary. Emitted artifact: deliver an apparently ordinary lattice of locally inert clauses in which only the current interface has enough freedom to form a behavioral utterance. Embedded interpreter or successor: replace the interpreter with a domain-wall carrier that performs the interface-local composition and refolds adjacent cells so the wall moves after every semantic act. Developer: specify the external contract and use an owner view that pins, names, and replays isogloss motion without exposing a stable source correspondence. Automated analysis/reconstruction tooling: extracting any local region yields rigid, incomplete relations; even a global snapshot reveals only today's isogloss, while the next execution changes which relations can move and where meaning can be articulated. Run-time: input supplies stress at the current boundary, the localized mode produces the required result, and the completed act translates the wall into a new region, thereby rewriting the artifact's locus of expressiveness.", + "coreIdea": "Let executable meaning exist only on a traveling topological boundary, like an isogloss separating two internally consistent dialect regions. Each call is spoken at that boundary and causes the boundary to migrate, so the artifact continuously transfers its sole expressive degree of freedom through otherwise unremarkable material. There is no stable function body to preserve: the place capable of realizing behavior is recreated by the preceding behavior.", + "whyNovel": "This is not fragment scattering, cross-file co-residence, local indirection, or a mobile decoder. Nothing meaningful is hidden in pieces awaiting reassembly; meaning is the emergent deformability of a changing global interface, and the successor mechanism moves that topological condition rather than relocating encoded content.", + "boldness": 5, + "isFusion": false, + "parents": [], + "recognizability": 0.3, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "" + }, + { + "name": "Post-Emit Morphogenesis", + "lens": "origami and mechanical metamaterials", + "assumptionOverturned": "the output is fixed the moment it is emitted", + "track": "self-modifying or living output", + "sourceMechanism": "Active self-folding origami uses adjoining material layers with different stimulus responses. When one layer swells, shrinks, or changes strain relative to a passive layer, the mismatch generates curvature at a programmed hinge. The flat configuration can have several folding branches, so robust systems use staged activation: one responsive material first pre-biases each vertex toward the intended branch, and another later drives the creases to their target angles. Fabrication therefore does not finish when the flat sheet is printed; later environmental exposure completes the structure through an ordered developmental process.", + "roughSketch": "Build step: print a semantic laminate containing contract surfaces, latent constructions, and staged responsiveness to actual value shapes, call rhythms, and composition neighborhoods rather than preselecting one causal implementation. Emitted artifact: ship a viable precursor whose first state is intentionally under-differentiated, analogous to a patterned flat sheet rather than a finished mechanism. Embedded interpreter or successor: use a finite developmental folder that converts ordinary use into branch-selection and fold stimuli, then lets newly formed semantic seams take over and eventually replace the folder with a smaller successor organizer. Developer: author the invariants and permissible differentiation envelope, then inspect a lineage view showing which runtime exposures caused each contract-preserving organ to form. Automated analysis/reconstruction tooling: an early specimen contains only latent potential, while a mature specimen reflects one installation's development; neither alone supplies a transferable account of the other possible ontogenies. Run-time: representative use first pre-biases ambiguous regions, later use folds them into specialized organs, and epoch boundaries produce descendants whose architecture is inherited from acquired structure rather than copied from the original emission.", + "coreIdea": "Treat the build result as an embryo that completes its own protected architecture after deployment. Real use acts as developmental exposure: it selects among contract-equivalent branches, differentiates frequently interacting behaviors into new organs, and replaces the original organizer with successively smaller organizers. Ruam would protect meaning by making the delivered program's mature causal anatomy unavailable at emit time, even to Ruam itself.", + "whyNovel": "This is not lazy decoding, profile-guided optimization, per-install permutation, or a missing external term. The artifact undergoes staged ontogeny in which future semantic components and even the mechanism that forms them are replaced by descendants; the emitted precursor is not an encoded copy of a completed program.", + "boldness": 5, + "isFusion": false, + "parents": [], + "recognizability": 0.84, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "needs-relaxation", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "Staged run-time differentiation from usage profiles is adaptive specialization or tiered JIT compilation, with a self-replacing bootstrap compiler layered on top." + }, + { + "name": "Auxetic Contract Tissue", + "lens": "origami and mechanical metamaterials", + "assumptionOverturned": "the output is fixed the moment it is emitted", + "track": "self-modifying or living output", + "sourceMechanism": "An auxetic mechanical metamaterial expands laterally when stretched, giving it a negative Poisson ratio. In Miura-derived and re-entrant origami, this counterintuitive response comes mainly from the coupled geometry of folds and hinges rather than an unusual constituent substance. Some patterns can change the sign and magnitude of their Poisson ratio as the fold state changes. A local axial demand can therefore reorganize a wider area, and the current configuration determines how load in one direction creates motion in another.", + "roughSketch": "Build step: convert behavioral contracts into a coupled construction tissue where every semantic degree of freedom has transverse partners and where all allowed fold states satisfy the same external boundary. Emitted artifact: provide a compact folded tissue with no stable partition into functions, only connected contract-bearing cells. Embedded interpreter or successor: use a geometric equilibrium successor that responds to semantic demand by unfolding the addressed region, recruiting perpendicular constructions, compacting relaxed regions, and persisting the new rest geometry. Developer: define boundary judgments and linguistic well-formedness rules, then observe contract coordinates and causal stress maps instead of source lines. Automated analysis/reconstruction tooling: isolating a frequently used region misses the transverse constructions that come into existence under load, while exercising it changes the rest geometry that later observations encounter; understanding requires the evolving global constitutive law, not a local trace. Run-time: each call stretches one semantic axis, the tissue expands along coupled axes to form a fresh realization, and relaxation leaves a revised baseline that changes how the next call can be phrased.", + "coreIdea": "Turn program behavior into auxetic semantic tissue whose internal vocabulary grows sideways wherever meaning is exercised. Execution does not select a pre-existing route; it mechanically creates a wider construction around the requested contract, then preserves part of that expansion as the next resting language. The artifact stays behaviorally bounded while continually changing the dimensionality and neighborhood of its own implementation.", + "whyNovel": "This is not redundant paths, function cloning, adaptive caching, expression noise, or structural bloat. The representation is a constitutive relation between semantic loads and global geometric response, and runtime meaning is newly formed by transverse coupling rather than chosen from a fixed stock of alternatives.", + "boldness": 4, + "isFusion": false, + "parents": [], + "recognizability": 0.58, + "impact": 4, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "" + }, + { + "name": "Allomorphic Phase Sheet", + "lens": "origami and mechanical metamaterials", + "assumptionOverturned": "the output is fixed the moment it is emitted", + "track": "self-modifying or living output", + "sourceMechanism": "Some four-vertex origami tessellations admit distinct compatible folding modes. A change in the mountain-versus-valley assignment of a crease can move a pattern between Miura-like and eggbox-like configurations, with a corresponding switch across positive and negative mechanical responses. When cells in different modes are combined at a kinematic bifurcation, compatibility can lock a mode across a region: a small polarity choice changes the admissible collective motion and thus the macroscopic material law.", + "roughSketch": "Build step: express each behavioral region as a family of contract-equivalent construction grammars joined at explicit kinematic bifurcations, with a small set of crease polarities controlling which grammar is collectively admissible. Emitted artifact: ship one initially folded phase plus unresolved bifurcations, not a catalog of alternate implementations. Embedded interpreter or successor: use a fold-phase coordinator that changes a polarity only when current semantic pressure makes a neighboring phase compatible, then derives and installs replacement bifurcations for the changed region. Developer: own the invariant denotation and declare which causal grammar families are acceptable, while an owner tool translates phase transitions into stable contract-level explanations. Automated analysis/reconstruction tooling: a snapshot can induce the grammar of its present phase, but after a bifurcation the admissible dependencies, grouping, and causal order change collectively, invalidating a stable reconstruction without any local substitution scheme. Run-time: accumulated discourse context pushes a region toward a bifurcation, a polarity flips, and the whole region begins realizing the same denotation through a different globally constrained grammar before laying new future phase boundaries.", + "coreIdea": "Give each artifact grammatical phases and let use drive true phase changes between them. A tiny runtime polarity decision would not swap one implementation for another; it would alter which causal relations are legal across an entire region, much as a linguistic parameter can reorganize many surface constructions at once. Each new phase also redraws its own next bifurcations, making the output a continuing sequence of internally coherent but structurally incommensurable languages.", + "whyNovel": "This is not equivalent-syntax substitution, control-flow rewriting, opcode remapping, or ordinary polymorphism. A phase transition changes the generative compatibility rules for a whole semantic region and then creates a new configuration space; the alternatives are not prebuilt code forms selected from a fixed menu.", + "boldness": 5, + "isFusion": false, + "parents": [], + "recognizability": 0.75, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "needs-relaxation", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "Contract-equivalent grammars selected and rewritten at run time are dynamic program rewriting and metamorphic phase switching, even if the switch is expressed as a regional constraint change." + }, + { + "name": "Evidential Rulecraft", + "lens": "untranslatable languages and linguistic relativity", + "assumptionOverturned": "build-time and run-time are separate worlds", + "track": "semantic-level protection", + "sourceMechanism": "Source mechanism first: in languages with grammatical evidentiality, a speaker must routinely mark the basis of a statement, such as direct perception, inference, or report; the exact categories vary by language. A language without obligatory evidential marking can still express the content, but translation often needs a paraphrase to preserve the speaker's epistemic commitment. This is a defensible weak-relativity mechanism: grammar makes particular distinctions habitually salient without determining what a speaker can think. Mapping: the build step is the first evidential negotiation, converting protected intent into claims that are deliberately incomplete until their provenance exists; the emitted artifact is an open rule charter defining admissible evidence relationships rather than final decisions; the embedded interpreter's successor is an evidential referee that recognizes, combines, and challenges evidence-bearing acts; the developer is a game designer who declares outcome envelopes and what kinds of grounds can justify each outcome; automated analysis/reconstruction tooling is like a translator holding the grammar but not the lived provenance that gives a claim its force; run-time is the continuing conversation in which application events create evidence categories and thereby finish the rule.", + "coreIdea": "Ruam becomes an evidential mechanics engine: every protected domain decision exists only as a verdict over provenance that the live application co-produces. A build emits neither the decision procedure nor a disguised equivalent; it emits the grammar of acceptable justification, and each run continues the build by turning newly witnessed, inferred, and delegated facts into a momentary executable ruling.", + "whyNovel": "This protects the meaning of a decision by distributing it between a semantic obligation and the circumstances that can warrant it. It is not instruction virtualization, encoded constants, structural variation, an integrity test, or a missing external term: the run is constitutive of the rule rather than merely supplying data to a pre-existing rule.", + "roughSketch": "Expose a designer-facing contract such as outcomes, prohibited outcomes, evidence roles, and example adjudications. Emit a compact charter of claim forms plus ordinary host actions, then let a resident referee maintain a provenance graph and derive short-lived rulings when gameplay events satisfy a form. Owner diagnostics replay the evidence conversation in domain language, while every new class of evidence extends the same compilation process after delivery.", + "boldness": 4, + "isFusion": false, + "parents": [], + "recognizability": 0.91, + "impact": 4, + "rails": { + "serverFree": "pass", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + }, + "maturity": "product-pivot", + "notNovelBecause": "A provenance graph plus admissible evidence forms and derived rulings is a policy-as-code or rule-engine architecture with provenance-aware decisions." + }, + { + "name": "The Deictic Commons", + "lens": "untranslatable languages and linguistic relativity", + "assumptionOverturned": "build-time and run-time are separate worlds", + "track": "semantic-level protection", + "sourceMechanism": "Source mechanism first: deixis gives expressions such as I, you, here, now, this, and socially marked forms their reference from the speech situation. Demonstratives can depend on joint attention, and social deixis can encode relationships that a literal translation must unpack. A transcript stripped of speaker roles, place, time, shared attention, and prior discourse therefore does not fully determine the utterance's reference or force. Mapping: the build step authors a deictic grammar and creates only the opening common ground; the emitted artifact is a set of relational utterance forms whose important referents do not yet exist; the embedded interpreter's successor is a common-ground curator that tracks who can mean what by here, now, ours, guest, or witness; the developer is the game master who defines role relations and invariant consequences, not a fixed procedure; automated analysis/reconstruction tooling is the transcript reader lacking the particular joint-attention history that established each referent; run-time is the live table where participants, objects, places, epochs, and prior commitments continually create and retire the meanings needed for the next move.", + "coreIdea": "Ruam becomes a deictic rules commons in which protected operations are authored as situated speech acts rather than functions. The delivered artifact cannot name the sensitive concepts directly because their referents are minted by the application's evolving relationships; each invocation both performs behavior and revises the language in which later behavior can be requested.", + "whyNovel": "The semantic unit is a relationship in common ground, not a function, instruction, renamed identifier, local fragment, or environment-derived secret. Runtime context does not merely choose among precompiled branches; it creates the referents that make a branch-like notion expressible at all, so there is no stable source-to-artifact vocabulary for tooling to recover.", + "roughSketch": "Let a developer declare role lattices, joint-attention events, temporal perspectives, and consequences of domain utterances. The build emits an initial conversation board and host-visible verbs with unresolved indexicals. During use, the curator binds meanings through witnessed interactions, expires them when the shared situation changes, and records an owner-readable dialogue trace that explains decisions without revealing a timeless rulebook.", + "boldness": 4, + "isFusion": false, + "parents": [], + "recognizability": 0.87, + "impact": 4, + "rails": { + "serverFree": "pass", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + }, + "maturity": "product-pivot", + "notNovelBecause": "Runtime-minted referents derived from roles, events, relationships, and time match context-aware capability systems, relationship-based policy, and event-sourced rule engines." + }, + { + "name": "Iterated Dialect Mechanics", + "lens": "untranslatable languages and linguistic relativity", + "assumptionOverturned": "build-time and run-time are separate worlds", + "track": "semantic-level protection", + "sourceMechanism": "Source mechanism first: in iterated-learning accounts of language change, each generation learns from a limited sample produced by the previous generation. The transmission bottleneck and learner biases can reshape grammar toward learnable regularities, while communicative use selects for successful coordination; what persists is usable communicative function, not exact vocabulary or structure. This is a model of cultural evolution, not a claim that every historical change has one cause. Mapping: the build step is generation zero, teaching protected behavior through a designed set of playable situations rather than freezing a final representation; the emitted artifact is a curriculum, a small population of provisional speakers, and rules for teaching the next population; the embedded interpreter's successor is a rule apprentice that learns how to coordinate from demonstrations and then becomes a teacher; the developer is the game designer who owns victory conditions, invariant scenarios, and semantic boundaries; automated analysis/reconstruction tooling meets one transient dialect whose internal categories are not inherited verbatim by the next; run-time is the succession of teaching seasons in which live use supplies the limited corpus and continually rebuilds the behavior language.", + "coreIdea": "Ruam becomes a language ecology that protects a mechanic by transmitting it, not storing it. The running artifact periodically raises a successor from constrained demonstrations of current play, retires the predecessor's dialect, and carries forward only the developer's semantic invariants, making runtime cultural transmission the ongoing build system.", + "whyNovel": "This is not per-build polymorphism, instruction mutation, self-checking, or a decoder that changes form. Successive artifacts do not share a hidden command vocabulary at all; they share the ability to coordinate on a game contract, and preservation is judged by semantic play rather than equality of an encoded program.", + "roughSketch": "The authoring surface is a scenario studio containing example rounds, forbidden outcomes, metamorphic relationships, and scoring rules. An initial cohort learns a compact local rule language from those rounds. At chosen lifecycle events, a pupil observes a deliberately narrow sample of the current cohort, proves itself by completing fresh semantic scenarios, takes over ordinary host decisions, and later teaches another pupil; the resulting lineage is inspectable to its owner as a series of behavioral traditions.", + "boldness": 4, + "isFusion": false, + "parents": [], + "recognizability": 0.58, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "" + }, + { + "name": "Anisomorphic Ruleworlds", + "lens": "untranslatable languages and linguistic relativity", + "assumptionOverturned": "build-time and run-time are separate worlds", + "track": "semantic-level protection", + "sourceMechanism": "Source mechanism first: languages are anisomorphic: they partition semantic fields, grammatical roles, kinship, space, time, and agency along different boundaries. Translation is therefore not generally a word-for-word bijection; multiple paraphrases may preserve an utterance's practical consequences while imposing different conceptual decompositions, and back-translation need not recover one uniquely privileged original phrasing. Mapping: the build step creates several non-isomorphic but contract-equivalent decompositions of the protected domain and deliberately withholds a canonical one; the emitted artifact is a playable treaty among those ruleworlds rather than one encoded procedure; the embedded interpreter's successor is a world adjudicator that can honor commitments made under one ontology after moving them into another; the developer is a game designer who specifies invariant outcomes and supplies alternative readings of important mechanics; automated analysis/reconstruction tooling can construct valid semantic models but has no artifact-internal basis for elevating one to the developer's original intent; run-time is continuous diplomatic play in which the active ontology changes and outstanding obligations are translated, making each transition another build phase.", + "coreIdea": "Ruam represents a sensitive mechanic as an equivalence class of genuinely different ruleworlds. A run provisionally inhabits one ontology, creates real obligations there, then crosses into another ontology that preserves the promised outcomes while changing what counts as actor, resource, cause, and action; after emission, the protected program has no single canonical semantic anatomy to reconstruct.", + "whyNovel": "The alternatives are all causally operative and semantically legitimate, so they are neither dead material nor misleading copies. This differs from syntax substitution, handler variation, control-structure reshaping, and multiple encoded forms of one procedure: only the outcome treaty is stable, while the very decomposition of the domain remains plural.", + "roughSketch": "A design tool asks the developer to express the same mechanic through contrasting ontologies, for example exchange, ecology, obligation, and territory, then identifies the outcome correspondences that must survive translation. The artifact carries ordinary pieces from all ruleworlds plus treaty moves between them. The adjudicator changes worlds at meaningful play events, migrates live commitments through the treaty, and reports faithfulness in terms of preserved player-facing promises instead of source correspondence.", + "boldness": 5, + "isFusion": false, + "parents": [], + "recognizability": 0.46, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "fails", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "" + }, + { + "name": "The Cue-Sheet Contract Theater", + "lens": "stage magic and misdirection", + "sourceMechanism": "Source domain: A large stage illusion is designed as two descriptions that are deliberately kept distinct. The effect script records what spectators should be able to perceive, while the method is a coordinated system of blocking, prop states, lighting, sound, assistants, and precisely timed cues. A stage manager can call and rehearse those cues, verify that every visible beat occurred, and diagnose a missed transition without needing the audience's narrative to describe the hidden mechanics. Mapping: the build step becomes rehearsal and writes an effect score of boundary-visible behavior and causal obligations; the emitted artifact becomes the performing company rather than a container of instructions; the embedded interpreter or successor becomes a semantic stage manager that coordinates whichever internal actors can satisfy the next scored beat; the developer becomes the director who authors effects, invariants, and recovery beats; automated analysis/reconstruction tooling occupies the spectator position and can observe the effect score but receives no stable cue-to-mechanism correspondence; run-time becomes the live performance in which semantic cues are checked and an owner-only rehearsal record is produced.", + "assumptionOverturned": "Ruam is a compiler → Ruam is actually a semantic stage manager.", + "track": "verifiability and developer experience", + "coreIdea": "Ruam accepts an executable effect score alongside the source: externally meaningful state changes, permitted causal orderings, resource obligations, and failure semantics. It emits a behavior company whose internal realization may have no function-level ancestry, plus a local rehearsal console that can prove which effect beats were fulfilled and replay only the semantic seam around a missed beat. Correctness evidence is therefore the primary generated product, while resistance to stable source-to-artifact correspondence follows from verifying the performance rather than translating the script.", + "whyNovel": "This is not a bytecode interpreter, syntax rewrite, shuffled implementation, self-integrity check, source map, or encoded execution log. Its durable unit is a developer-owned effect beat and its causal obligations; the deployed mechanism is free to be organized around roles and cues that have no instruction-by-instruction source counterpart.", + "roughSketch": "Introduce an effect-score language that can describe observable transitions, temporal envelopes, allowed nondeterminism, and recovery promises. The build constructs a proof obligation graph and synthesizes a role-based runtime whose owner telemetry names only score beats; a rehearsal tool runs counterfactual cue sequences and returns minimal failed obligations rather than reconstructed source frames.", + "boldness": 4.9, + "isFusion": false, + "parents": [], + "recognizability": 0.66, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + }, + "maturity": "product-pivot", + "notNovelBecause": "" + }, + { + "name": "Question-Sightline Observatory", + "lens": "stage magic and misdirection", + "sourceMechanism": "Source domain: Forced perspective produces a convincing spatial relationship only from a designed sightline. Set geometry, relative scale, converging edges, actor blocking, and depth cues are coordinated so that the viewer receives a coherent scene; moving to another viewpoint yields a different but physically legitimate projection of the same arrangement. The mechanism is not a painted falsehood but a relationship among observer position, geometry, and selected visual invariants. Mapping: the build step becomes a scene designer that derives a family of legitimate semantic projections from program contracts; the emitted artifact becomes the full geometric arrangement without a privileged readable facade; the embedded interpreter or successor becomes a perspective instrument that answers a bounded semantic question by selecting and maintaining its observation coordinates; the developer becomes the viewer who chooses a sightline such as why a value changed or which promise authorized an effect; automated analysis/reconstruction tooling sees individual projections but cannot assume they assemble into a global source model; run-time becomes the changing stage on which query-specific causal coordinates are resolved.", + "assumptionOverturned": "Ruam is a compiler → Ruam is actually a question-conditioned observatory.", + "track": "verifiability and developer experience", + "coreIdea": "Instead of emitting a universal trace or a source map, Ruam emits a queryable observation geometry. A developer asks a semantic question and receives a mechanically checked causal projection containing exactly the events necessary to answer it, with the projection expressed in domain contracts rather than functions or instructions. Different questions deliberately produce non-nestable views, so excellent diagnosis does not accumulate into a stable replica of the implementation.", + "whyNovel": "This is not log filtering, identifier renaming, local fragment scattering, debugger detection, or per-build structural variation. The novel unit is a verified question-relative causal projection, and the guarantee is that each view is complete for its declared question even though no universal implementation view exists.", + "roughSketch": "At build time, derive observation bases from developer-declared entities, effects, and authorization relations, then prove coverage conditions for each supported question class. At run-time, retain a compact causal incidence structure rather than source locations; the local developer console solves for a requested projection and checks its completeness certificate before rendering a domain-level explanation.", + "boldness": 5, + "isFusion": false, + "parents": [], + "recognizability": 0.76, + "impact": 4, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "pass" + }, + "maturity": "research-spike", + "notNovelBecause": "The implementable mechanism is query-directed causal tracing and program slicing with certified projection completeness, a familiar observability and provenance pattern." + }, + { + "name": "Dual-Reality Debugging", + "lens": "stage magic and misdirection", + "sourceMechanism": "Source domain: In a dual-reality mentalism presentation, two groups receive different information through role-specific instructions, wording, positioning, or props. Each group's experience is internally coherent, and carefully chosen public language permits both interpretations to coexist during the same effect; no participant has merely seen a redacted copy of one master script. Mapping: the build step becomes a designer of mutually consistent observation algebras for roles such as developer, QA, operations, and public consumer; the emitted artifact becomes the shared performance that supports all of those realities; the embedded interpreter or successor becomes a projection coordinator that turns one semantic event into role-valid observations while enforcing cross-view consistency; the developer becomes the participant allowed to compose selected views in a local diagnostic workspace; automated analysis/reconstruction tooling receives the public algebra, which is behaviorally truthful but insufficient to infer the private diagnostic algebras; run-time becomes the single event history from which distinct, consistency-linked realities are generated.", + "assumptionOverturned": "Ruam is a compiler → Ruam is actually a multi-perspective observability constitution.", + "track": "verifiability and developer experience", + "coreIdea": "Ruam defines debugging as composition of typed realities rather than inspection of a canonical trace. Production output exposes a public behavioral view, operational tooling gets a reliability view, QA gets an invariant view, and the developer can temporarily join selected views to obtain a causal explanation whose validity is checked by cross-view laws. Because none of the individual realities is a masked source trace, deployable observability remains useful without creating a stable implementation map.", + "whyNovel": "This is not access control around ordinary logs, relocation of a decoder, an external string term, artifact fragmentation, or source-map retention. Each view has different semantic primitives and independently useful truth conditions, while the central technical object is a set of compositional consistency laws among realities.", + "roughSketch": "Add a projection-policy layer whose types define what counts as an event and a causal link for each role. Compile the program's behavioral contracts into cross-projection laws, emit only the public and operational projections needed in deployment, and let an owner-side console join captured projections with build-held QA and developer views to explain a failure at the contract level.", + "boldness": 4.8, + "isFusion": false, + "parents": [], + "recognizability": 0.74, + "impact": 4, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "pass" + }, + "maturity": "research-spike", + "notNovelBecause": "It largely restates role-specific telemetry views, access-controlled observability, and consistency checks between projections." + }, + { + "name": "Black-Art Absence Certificates", + "lens": "stage magic and misdirection", + "sourceMechanism": "Source domain: The black-art principle hides black-clad assistants, supports, openings, or objects against a black background by tightly controlling illumination, contrast, costume, and spectator sightlines. The visible illusion is shaped as much by guaranteed absences in the lit field as by the objects that remain visible; the dark region is an engineered part of the apparatus, not empty scenery. Mapping: the build step becomes a lighting designer that derives forbidden observable states and forbidden causal combinations from the developer's contracts; the emitted artifact becomes the lit field whose public identity is its permitted observations; the embedded interpreter or successor becomes an absence custodian that maintains a proof-oriented model of what must never enter that field; the developer becomes the illusion designer who specifies impossibility boundaries and inspects counterexamples; automated analysis/reconstruction tooling can catalog visible outcomes but cannot turn certified absences into a unique hidden mechanism; run-time becomes a succession of lit semantic fields, each accompanied by evidence that excluded states remained outside the observation boundary.", + "assumptionOverturned": "Ruam is a compiler → Ruam is actually a negative-space certifier.", + "track": "verifiability and developer experience", + "coreIdea": "Ruam makes negative behavioral space the primary artifact: not what every internal step does, but which externally meaningful states, causal combinations, data relationships, and authority crossings are impossible. It may synthesize any implementation that inhabits the remaining space, while the developer receives continuous, counterexample-oriented evidence for the exclusion boundary. Debugging begins from a violated impossibility and expands only the smallest semantic neighborhood needed to explain it.", + "whyNovel": "This is not an opaque predicate, unreachable code, environment check, canary, integrity tag, or structural concealment. The exclusions are developer-level semantic guarantees and the successor's identity is a proof-carrying boundary of possible behavior, not a mechanism that checks whether its own bytes changed.", + "roughSketch": "Create a negative-contract DSL for impossible state pairs, forbidden causal paths, information non-relations, and temporal exclusions. The build synthesizes an implementation plus an absence monitor expressed over domain events; verification tooling searches the exclusion boundary symbolically and at run-time, returning a minimal semantic counterexample without exposing source positions.", + "boldness": 4.7, + "isFusion": false, + "parents": [], + "recognizability": 0.84, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "Negative contracts, safety-property model checking, counterexample generation, and runtime contract monitoring are established formal-verification techniques." + }, + { + "name": "The Convincer Ecology", + "lens": "stage magic and misdirection", + "sourceMechanism": "Source domain: A convincer is an action or condition that strengthens the audience's confidence in the stated situation: a spectator may handle an object, a card may be signed, a container may be examined, or a choice may be made before the climax. Skilled routines use several partially independent convincers because each supports a different inference; their overlap creates a robust experiential case without any single convincer exposing the method. Mapping: the build step becomes a dramaturg that grows a diverse ecology of falsifiable semantic demonstrations from the developer's trust questions; the emitted artifact becomes a repertoire capable of participating in those demonstrations; the embedded interpreter or successor becomes a rehearsal partner that can instantiate requested demonstrations and report domain outcomes; the developer becomes the spectator-director who selects which claims need confidence and can demand counterexamples; automated analysis/reconstruction tooling observes many-to-one facts about permitted behavior rather than a transferable implementation correspondence; run-time becomes an ongoing sequence of naturally occurring and deliberately rehearsed demonstrations whose evidence is combined by explicit independence rules.", + "assumptionOverturned": "Ruam is a compiler → Ruam is actually a semantic accreditation studio.", + "track": "verifiability and developer experience", + "coreIdea": "For each protected capability, Ruam generates a living portfolio of heterogeneous correctness demonstrations: algebraic properties, metamorphic scenarios, boundary interactions, counterfactual replays, and production-derived examples, each grounded in the developer's domain vocabulary. The owner sees which independent lines of evidence support each behavioral claim and can ask Ruam to grow a new convincer where confidence is thin. The protected artifact is hired to sustain and refresh this evidence ecology, not merely to execute translated code.", + "whyNovel": "This is not a checksum, self-test, fixed test suite, watermark, decoy computation, or encoded constant pool. Its product value is an adaptive evidence topology connecting domain claims to independent demonstrations, with no requirement that any demonstration reveal how a function or instruction realizes the claim.", + "roughSketch": "Extend project configuration with trust questions and independence criteria. The build derives a claim graph and several unlike evidence generators; a local evidence workbench visualizes coverage and contradictions, while run-time contributes domain-level observations that can seed new rehearsals without recording a source-aligned trace.", + "boldness": 4.6, + "isFusion": false, + "parents": [], + "recognizability": 0.9, + "impact": 4, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "pass" + }, + "maturity": "shippable-now", + "notNovelBecause": "The mechanism is a portfolio of property-based, metamorphic, boundary, counterfactual, and production-derived tests organized as an evidence graph." + }, + { + "name": "Phenotype Manufacturing Right", + "lens": "DNA error-correction and codon degeneracy", + "assumptionOverturned": "the meaning of the program lives inside the file", + "track": "distribution, licensing, and delivery models", + "sourceMechanism": "In the standard genetic code, 61 sense codons map to 20 amino acids, so several codons can specify the same amino acid. Wobble in recognition by transfer RNAs helps one transfer RNA read multiple synonymous codons; as a result, no single synonymous DNA spelling uniquely owns the amino-acid sequence that a ribosome produces. Mapping: the build step becomes phenotype specification rather than implementation emission; the emitted artifact is a manufacturing order plus conformance gauges; the embedded interpreter is replaced by an install-time behavior foundry that produces ordinary JavaScript; the developer owns the observable phenotype and tolerances; automated analysis/reconstruction tooling sees only a manufacturing fixture and one contingent specimen; and run-time executes a locally fabricated realization that can later be replaced by a synonymous one.", + "coreIdea": "Ruam licenses the right to manufacture a behavioral phenotype, not a copy of a program. A customer receives a compact product specification, and an authorized foundry at installation time synthesizes and certifies an ordinary local implementation from the current platform's components; renewals authorize new manufacture rather than unlock old bytes. The program's durable meaning lives in the developer-held phenotype contract and certification relationship, while every file is disposable production tooling or a temporary specimen.", + "whyNovel": "This is neither per-build polymorphism nor an interpreter with an external term: there is no canonical implementation to decode, and authorization governs fabrication and certification of equivalent products rather than access to concealed bytes.", + "roughSketch": "The developer marks behavioral invariants, permitted tolerances, and platform obligations. Ruam emits a phenotype dossier with executable acceptance experiments and a foundry request; a local or managed synthesis stage makes ordinary modules, proves them against the dossier, and issues a short-lived product passport. Distribution becomes a licensed manufacturing workflow, much like a design owner authorizing regional factories to make conforming parts from locally available processes.", + "boldness": 5, + "isFusion": false, + "parents": [], + "recognizability": 0.56, + "impact": 5, + "rails": { + "serverFree": "needs-relaxation", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + }, + "maturity": "product-pivot", + "notNovelBecause": "" + }, + { + "name": "Synonymous Supply Network", + "lens": "DNA error-correction and codon degeneracy", + "assumptionOverturned": "the meaning of the program lives inside the file", + "track": "distribution, licensing, and delivery models", + "sourceMechanism": "Synonymous codons name the same amino acid, but they are not always operationally equivalent: organisms and tissues have different abundances of matching transfer RNAs, so codon choice can change translation rate, expression level, and co-translational folding while leaving the nominal amino-acid sequence unchanged. Mapping: the build step defines a target phenotype, interface tolerances, and qualified substitution classes; the emitted artifact is a bill of behavioral materials with open supplier positions; the interpreter is replaced by a local sourcing and qualification engine; the developer acts as the industrial designer who approves equivalence classes; automated analysis/reconstruction tooling lacks the live supplier catalog and chosen assembly; and run-time is the current assembled product whose structure and operating qualities reflect local supply.", + "coreIdea": "Ruam becomes a governed semantic supply network in which a license grants access to changing families of qualified implementation parts. Installation procures compatible ordinary JavaScript components from an organization, device maker, developer, or regional catalog and assembles them within published tolerance stacks; no package contains the whole product, and no supplier is the canonical implementation. Commercial tiers can govern supplier diversity, performance envelopes, provenance, or regional availability rather than toggling hidden features.", + "whyNovel": "Unlike a package manager, fragment reassembly, or cross-file co-residence, the protected product is the continuing qualification and substitution ecosystem itself: supplier parts are complete ordinary components, while global meaning is defined by the approved relationships and tolerance stack among them.", + "roughSketch": "At build time Ruam extracts component obligations and creates substitution sockets with behavioral gauges instead of stable function identities. A license manifest selects eligible catalogs and quality bands; installation resolves a bill of materials, runs fit checks, and records the assembly's provenance. Updates can qualify or retire suppliers without issuing a new canonical application, giving the developer a product-line and supply-chain surface rather than a larger build-option matrix.", + "boldness": 5, + "isFusion": false, + "parents": [], + "recognizability": 0.86, + "impact": 4, + "rails": { + "serverFree": "needs-relaxation", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + }, + "maturity": "product-pivot", + "notNovelBecause": "It is a governed component marketplace and package resolver using interface contracts, compatibility tests, provenance, and software-product-line substitution." + }, + { + "name": "Lineage Repair Warranty", + "lens": "DNA error-correction and codon degeneracy", + "assumptionOverturned": "the meaning of the program lives inside the file", + "track": "distribution, licensing, and delivery models", + "sourceMechanism": "After DNA replication, mismatch-repair systems recognize distortions caused by incorrectly paired bases, use transient strand cues such as nicks and replication-associated marks to identify the newly synthesized strand, remove a tract containing the mismatch, and resynthesize it from the intact complementary strand before sealing it. The repair machinery does not carry a separate master sequence for every possible correction. Mapping: the build step creates semantic repair boundaries, lineage cues, and behavioral reference specimens; the emitted artifact is a serviceable descendant plus its product passport, not the definitive program; the interpreter is replaced by a maintenance agent that can remanufacture an affected component; the developer is the lineage steward who publishes what counts as healthy behavior; automated analysis/reconstruction tooling sees only one historical specimen rather than the rules governing supported descendants; and run-time supplies observations and trusted prior behavior from which a corrected descendant is made.", + "coreIdea": "Ruam sells a lineage warranty: a licensed application is defined as the succession of supported descendants produced by an ongoing repair relationship, not by any release file. Delivery events carry semantic repair policies, reference examples, and permitted resynthesis materials rather than a target binary or source-shaped patch. A deployment whose behavior drifts is locally remanufactured from its own functioning regions, its certified history, and the newest developer policy.", + "whyNovel": "This is not a source-integrity reaction, patch chain, or scattered artifact: a delivery event contains no target implementation to recover, and its product value is constructive maintenance that creates a new supported descendant rather than verification followed by invalidation.", + "roughSketch": "The initial build partitions behavior into replaceable service regions and records developer-approved observations as a lineage passport. During operation, the maintenance agent identifies semantic disagreements at region boundaries, preserves the still-conforming regions as the template, and synthesizes only the divergent region under the current warranty policy. Subscription tiers can cover repair cadence, certified histories, and the breadth of behavior that Ruam promises to keep in conformance.", + "boldness": 4, + "isFusion": false, + "parents": [], + "recognizability": 0.82, + "impact": 5, + "rails": { + "serverFree": "needs-relaxation", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "product-pivot", + "notNovelBecause": "The proposed mechanism is contract-guided automated program repair and self-healing software wrapped in a maintenance subscription." + }, + { + "name": "Fidelity-Gradient License", + "lens": "DNA error-correction and codon degeneracy", + "assumptionOverturned": "the meaning of the program lives inside the file", + "track": "distribution, licensing, and delivery models", + "sourceMechanism": "When ordinary DNA replication stalls at a damaged base, cells can temporarily recruit specialized translesion polymerases that synthesize across the lesion. These enzymes trade fidelity for continuity, after which the normal polymerase resumes and later repair processes can restore the affected region. Mapping: the build step separates essential invariants from refinable semantics and marks safe handoff points; the emitted artifact contains a continuation-grade behavioral envelope rather than the complete high-fidelity behavior; the interpreter is replaced by a run-time pathway selector and local fabricator; the developer specifies acceptable temporary approximations and later reconciliation rules; automated analysis/reconstruction tooling can observe the fallback envelope but not the exact future high-fidelity semantics or its delivery history; and run-time selects an operating grade, continues through an unavailable delivery interval, then reconciles state when licensed high-fidelity material returns.", + "coreIdea": "A Ruam license purchases semantic fidelity over time rather than a binary feature switch. During an expired, disconnected, or delayed delivery interval, the application continues inside a developer-approved approximation envelope; renewed service progressively replaces approximate decisions with exact behavior and reconciles any affected state. Premium delivery models can sell fidelity, correction latency, and continuity guarantees as independent dimensions.", + "whyNovel": "This is not an external secret that makes the same concealed program readable and not ordinary feature tiering: authorization changes the permitted precision and repair trajectory of behavior, while exact meaning is a time-distributed service outcome rather than content waiting inside the file.", + "roughSketch": "The build produces graded contracts for each protected behavior: non-negotiable invariants, bounded approximate responses, reconciliation obligations, and state checkpoints. The emitted application can fabricate a temporary ordinary implementation that stays within its current grade, while licensed deliveries provide exact reference cases or higher-grade synthesis constraints. The owner receives an operational dashboard showing fidelity debt and reconciliation status instead of a simple valid-or-invalid license indicator.", + "boldness": 5, + "isFusion": false, + "parents": [], + "recognizability": 0.8, + "impact": 4, + "rails": { + "serverFree": "needs-relaxation", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + }, + "maturity": "product-pivot", + "notNovelBecause": "It combines graceful feature degradation, approximate computing, service-tier licensing, fidelity debt, and eventual state reconciliation." + }, + { + "name": "Proofreading Royalty", + "lens": "DNA error-correction and codon degeneracy", + "assumptionOverturned": "the meaning of the program lives inside the file", + "track": "distribution, licensing, and delivery models", + "sourceMechanism": "Replicative DNA polymerases improve accuracy through proofreading: when a wrong nucleotide is incorporated, extension often slows, the primer end shifts from the polymerase active site to a 3-to-5 exonuclease site, the incorrect nucleotide is removed, and synthesis resumes from the corrected end. Correctness therefore emerges from a coupled propose-inspect-correct cycle rather than nucleotide selection alone. Mapping: the build step separates broad candidate production from proprietary acceptance and correction policy; the emitted artifact is a candidate maker plus a correction interface; the interpreter is replaced by a licensed proofreader, which may be a local service appliance or managed service; the developer authors the semantic acceptance boundary; automated analysis/reconstruction tooling can summarize candidate production but cannot derive which candidates become certified outcomes; and run-time presents candidate decisions for correction before committing the accepted result.", + "coreIdea": "Ruam turns proprietary logic into a metered correctness service: the shipped application proposes a bounded set of plausible actions, while the licensed proofreader selects, edits, or certifies the action that the product may commit. Customers purchase certified decisions, correction capacity, or domain coverage rather than seats for a static program. The commercially valuable meaning lives in the evolving correction policy and its stream of certifications, not in the proposal file.", + "whyNovel": "This is not a license server returning permission, a decoder returning hidden instructions, or remote execution of the whole function; the local product does substantive work, while the saleable unit is semantic correction and accountable certification at the decision boundary.", + "roughSketch": "Ruam identifies decision surfaces in developer code and compiles each into a local proposal factory plus a narrow, typed proofreading protocol. The developer keeps or distributes a separately evolving policy that can reject, reshape, or certify proposals; receipts make the accepted causal history observable to the owner. Distribution offerings can meter per certified outcome, bundle policy domains, or place an organization-controlled proofreader appliance on site.", + "boldness": 5, + "isFusion": false, + "parents": [], + "recognizability": 0.88, + "impact": 4, + "rails": { + "serverFree": "fails", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "product-pivot", + "notNovelBecause": "Local candidate generation followed by an external policy oracle is a familiar split-computing and decision-certification service pattern." + }, + { + "name": "The Questioning Scar", + "lens": "adaptive immune selection × holographic refocusing × hysteretic metamaterials", + "assumptionOverturned": "Opacity and correctness are not in tension; a diagnostic question can be part of the artifact's continuing formation, and neither a fixed output nor a shipped decoder is required.", + "track": "verifiability and developer experience", + "sourceMechanism": "Parents: The Tolerance Mold makes behavior by eliminating candidates against semantic negative-space surfaces; Focus-Pull Debugger reconstructs only the causal plane named by a developer's question; Hysteretic Dialect Lattice stores the ordered history of use as a metastable grammar. New interaction: a diagnostic question is applied as a temporary load to the history-bearing tolerance mold, changing which explanation-and-execution pairs can survive. The surviving pair both answers the question and snaps the lattice into a new metastable dialect, so later executions naturally deposit finer evidence around that semantic concern. Immune selection makes each focused explanation behaviorally accountable, holographic focus turns owner inquiry into selective pressure, and hysteresis ensures that accumulated insight never becomes one canonical implementation view.", + "coreIdea": "Ruam would provide a debugger that grows a one-use causal organ for each owner question. The organ must simultaneously reproduce the owned behavior and express a faithful answer in the requested semantic vocabulary; after it dissolves, the artifact retains a structural scar that changes how future behavior and evidence are formed. Debugging therefore improves an installation's owner observability over time while making its internal dialect increasingly biographical rather than convergent.", + "whyNovel": "This is not source mapping, trace collection, artifact-identity checking, structural shuffling, or ordinary adaptive optimization. Explanation is not a report emitted by a stable implementation: question, implementation, evidence, and the artifact's next grammar are co-produced by one contract-governed selection event.", + "roughSketch": "At build time, derive semantic exclusion surfaces and a vocabulary of promises, effects, authorities, and domain decisions, then embed them in a multistable composition lattice. When the developer asks a question, translate it into focus constraints and relax the lattice until a candidate causal organ passes both behavior assays and explanation-coherence assays. Return its semantic account, record the snap-through path in an owner lineage view, and persist only the resulting lattice state as the starting material for later calls.", + "boldness": 5, + "parents": [ + "The Tolerance Mold", + "Focus-Pull Debugger", + "Hysteretic Dialect Lattice" + ], + "isFusion": true, + "recognizability": 0.38, + "impact": 4, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "needs-relaxation", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "" + }, + { + "name": "Rehearsal Germ Layers", + "lens": "holographic whole-scene reconstruction × immune affinity maturation × staged self-folding origami", + "assumptionOverturned": "The emitted artifact need not be complete or fixed, correctness evidence can generate protected structure rather than merely assess it, and no decoder-shaped component must accompany the result.", + "track": "new product surfaces and capabilities", + "sourceMechanism": "Parents: Whole-Show Rehearsal Mesh supplies several independently useful, low-resolution versions of the entire product; Affinity Furnace grows contingent behavior clones by selecting against semantic specimens; Post-Emit Morphogenesis lets deployment exposure fold an under-differentiated precursor into mature organs. New interaction: the disagreements among whole-product rehearsal surfaces become both affinity assays and morphogen gradients. A candidate organ survives only when it sharpens agreement among several coarse end-to-end projections; that successful sharpening folds the precursor, and the new fold emits a higher-resolution rehearsal that becomes the assay for the next developmental stage. Rehearsal therefore directs maturation, selection chooses which folds become anatomy, and the developing anatomy continuously rewrites the rehearsal material that selects its descendants.", + "coreIdea": "Ruam would ship preview, test, partner, edge, and production surfaces as germ layers of one developing product rather than as separately derived bundles. Real journeys cause these complete-but-coarse surfaces to overlap; wherever their observable promises agree but lack detail, the installation grows a specialized behavior organ and promotes its result into the next round of rehearsals. The mature application is thus generated by a chain of cross-resolution correctness agreements that did not exist at emit time.", + "whyNovel": "This is not lazy materialization, profile-guided specialization, per-install variation, a test wrapper, or fragments that later reassemble. No finished implementation is waiting behind the rehearsals: independently complete product experiences serve as developmental material, and their improving mutual coherence is what manufactures new executable anatomy.", + "roughSketch": "Let developers declare journeys, observable promises, and permitted resolution loss for several deployment surfaces. Emit a semantic laminate containing coarse whole-journey realizations, neutral operator tissue, and staged fold rules. During use, compare overlapping consequences across the surfaces, select candidate tissue that monotonically sharpens all affected journeys, fold it into a persistent organ, and regenerate the surface suite from the newly differentiated state; an owner console shows lineage from rehearsal discrepancy to organ formation.", + "boldness": 5, + "parents": [ + "Whole-Show Rehearsal Mesh", + "Affinity Furnace", + "Post-Emit Morphogenesis" + ], + "isFusion": true, + "recognizability": 0.52, + "impact": 4, + "rails": { + "serverFree": "pass", + "sizeLean": "fails", + "cspSafe": "needs-relaxation", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "" + }, + { + "name": "Fringe-Driven Phase Healing", + "lens": "holographic interferometry × idiotypic immune phases × history-bearing soliton transport", + "assumptionOverturned": "Correctness observation can be the active material of run-time maintenance, meaning need not reside in a decoder or command stream, and protection can continue after build time.", + "track": "self-modifying / living output", + "sourceMechanism": "Parents: Semantic Fringe Observatory turns small consequence differences into a whole-product deformation field; Idiotype Phase Matter expresses behavior as an attractor of mutually regulating ordinary grains; Soliton Tissue carries collective pulses through a substrate whose recent passage changes later propagation. New interaction: a semantic fringe is converted into a bounded collective pulse and launched through the reciprocal phase material. Its passage temporarily changes which grains stimulate or suppress one another, moving the material toward an attractor whose public consequences reduce the fringe; the altered tissue then changes both future behavior and which deformations become visible next. The observatory supplies repair geometry, the pulse makes that geometry an executable event, and the phase network turns each repair into a new semantic equilibrium rather than a patched copy of an earlier implementation.", + "coreIdea": "Ruam would make protected output homeostatic at the semantic level. Dependency, browser, configuration, or policy changes produce consequence fringes; those fringes become traveling repair waves that reorganize the distributed behavior phase until owned promises regain coherence. The owner observes the same wave lineage that maintained the program, so explanation and continued formation arise from one process without revealing a stable command graph.", + "whyNovel": "This does not compare artifact identity, inspect a fixed implementation, invalidate execution, or apply a stored correction script. The correction has no prewritten target structure: a measured semantic deformation becomes a physicalized wave whose interaction with a collective phase discovers and installs a new contract-faithful equilibrium.", + "roughSketch": "Record reference consequence fields from specifications and independent oracle runs, then synthesize a sparse population of reciprocal behavior grains inside a nonlinear transport tissue. At run time, convert any measured fringe into a shaped density perturbation, propagate it through junctions, and let its wake alter local recognition thresholds until designated outcome fields reconverge. Present developers with a pulse-and-attractor lineage explaining which promises drifted, where the repair wave traveled, and which macroscopic phase variables now support the result.", + "boldness": 5, + "parents": [ + "Semantic Fringe Observatory", + "Idiotype Phase Matter", + "Soliton Tissue" + ], + "isFusion": true, + "recognizability": 0.68, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "" + }, + { + "name": "The Migrating Treaty", + "lens": "MHC-restricted recognition × mycorrhizal reciprocal allocation × polarized metamaterial domain walls", + "assumptionOverturned": "Program meaning need not live in a standalone file, protection need not be a one-time build act, and the only expressive region can be recreated elsewhere after every use.", + "track": "semantic-level protection", + "sourceMechanism": "Parents: MHC-Restricted Program Tissue makes behavior a typed relation between ordinary host workflow and Ruam material; Reciprocal-Exchange Computation makes meaning a viable circulation of complementary offerings; Traveling Isogloss localizes expressiveness at a movable boundary between two rigid semantic regions. New interaction: host events do not merely select or trigger behavior; they contribute typed resources to a reciprocal circulation that can close only at the current domain wall. Closing the circulation yields the requested consequence and shifts the wall, which changes local exchange terms and therefore which future host conjunction can form the next viable circuit. Context supplies the economy, the economy gives the boundary a reason and direction to move, and boundary motion continually rewrites how context can become meaning.", + "coreIdea": "Protected behavior becomes a migrating treaty between an application and its Ruam-grown tissue. Each call is realized only when current host capabilities and artifact offerings form a self-sustaining exchange at the sole expressive boundary; fulfilling that treaty relocates the boundary and alters the terms under which the next behavior may arise. Neither participant contains a standalone implementation, and yesterday's relational explanation is not the grammar of tomorrow's agreement.", + "whyNovel": "This is not cross-file co-residence, a capability gate, a missing external value, fragment movement, or a mobile decoder. The host provides useful semantic work rather than concealed bits, while meaning is the completed circulation at a history-moving interface—not content stored on either side or pieces waiting to be joined.", + "roughSketch": "Add a design surface where developers name host capabilities, lifecycle events, reciprocal offerings, and public semantic invariants. Co-synthesize a host protocol and two families of locally rigid Ruam clauses with an initial polarized boundary. Each invocation presents host offerings as resource gradients; local membranes negotiate a viable loop at the boundary, expose its outcome, then refold neighboring clauses so the wall and exchange schedule move together. An owner view renders treaty lineage and invariant satisfaction without presenting a permanent source correspondence.", + "boldness": 5, + "parents": [ + "MHC-Restricted Program Tissue", + "Reciprocal-Exchange Computation", + "Traveling Isogloss" + ], + "isFusion": true, + "recognizability": 0.6, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "" + }, + { + "name": "Treaty-Sightline Foundry", + "lens": "linguistic anisomorphism × forced perspective × codon-degenerate manufacturing", + "assumptionOverturned": "installation produces one implementation whose correctness can be explained from one privileged semantic viewpoint", + "track": "new product surfaces and capabilities", + "sourceMechanism": "Parents: Anisomorphic Ruleworlds contributes several contract-equivalent ontologies with no canonical decomposition; Question-Sightline Observatory contributes complete but deliberately non-nestable causal projections for developer questions; Phenotype Manufacturing Right contributes licensed local fabrication of ordinary implementations from a behavioral phenotype. The new interaction is that a diagnostic question does not merely inspect the installed specimen: its sightline selects a foreign ruleworld in which the local foundry must manufacture a second, ontology-distinct specimen. Agreement is certified only between the question-relative consequences of the two specimens. The treaty therefore changes what the foundry makes, the foundry gives each projection a materially different causal world to examine, and the projection turns cross-world manufacture into correctness evidence.", + "coreIdea": "Ruam becomes a question-conditioned semantic foundry. An installation begins with one locally manufactured realization of an outcome treaty. When its owner asks why a promise held, the system must cross into an ontology that partitions actors, resources, causes, and actions differently, fabricate a temporary counterpart from local materials, and solve for the smallest sightline on which both products make the same promise. Explanations are thus constructive acts that expand the product's population of valid ruleworlds; there is neither a canonical implementation nor a universal explanation waiting to be extracted.", + "whyNovel": "This is not per-build polymorphism, ordinary component substitution, source mapping, log filtering, an encoded program, or a remote missing term. The durable unit is a three-way agreement among an outcome treaty, a newly manufactured causal ontology, and a question-complete projection. None of the parents alone makes explanation manufacture a semantically incommensurable product whose comparison is itself the certificate.", + "roughSketch": "The developer authors invariant outcomes, contrasting domain readings, and supported question classes. A licensed install-time foundry makes an ordinary local module in one reading. The owner console turns a question such as “what authorized this effect?” into a semantic sightline, chooses a treaty-compatible ontology in which that question has a different causal anatomy, and asks the foundry for a short-lived counterpart. A projection solver reports only the cross-world seam needed to show that the promised outcome survived. Repeated questions grow a site-specific atlas of manufactured ruleworlds without ever converging on a master model.", + "boldness": 5, + "parents": [ + "Anisomorphic Ruleworlds", + "Question-Sightline Observatory", + "Phenotype Manufacturing Right" + ], + "isFusion": true, + "recognizability": 0.6, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + }, + "maturity": "product-pivot", + "notNovelBecause": "" + }, + { + "name": "Evidential Phase Succession", + "lens": "mechanical phase transitions × grammatical evidentiality × iterated language learning", + "assumptionOverturned": "a living artifact can change its form while retaining one stable language for stating and testing its rules", + "track": "self-modifying or living output", + "sourceMechanism": "Parents: Allomorphic Phase Sheet contributes collective grammar changes at compatibility bifurcations; Evidential Rulecraft contributes rulings that exist only when live provenance satisfies a justification grammar; Iterated Dialect Mechanics contributes successor populations that inherit coordination through limited demonstrations rather than a stored vocabulary. The new interaction makes evidence the mechanical load that tips a whole region into a new grammatical phase, while the resulting phase changes which provenance distinctions the next apprentice can perceive and teach. Succession is required to stabilize each phase: an apprentice must learn the new evidential categories from live cases before becoming the region's speaker, and its teaching bottleneck lays different future bifurcations.", + "coreIdea": "A protected behavior lives as a lineage of evidential phases. Accumulated patterns of witnessed, inferred, delegated, and contested facts create semantic pressure until the current dialect can no longer express its rulings compactly; the artifact then undergoes a collective grammar transition and raises a successor using a narrow curriculum drawn from that pressure. Because the successor's categories determine what will count as evidence next, use changes not only the rule's realization but the future conditions under which the rule can be justified.", + "whyNovel": "This is not opcode mutation, a state machine, a self-test, adaptive caching, rotating syntax, or a decoder that teaches another decoder. The evolving unit is the coupling between provenance categories, region-wide causal grammar, and cultural transmission. No parent alone yields a phase change that rewrites its own evidence ontology and must be ratified by a newly taught generation.", + "roughSketch": "The developer supplies outcome envelopes, unacceptable outcomes, provenance roles, and example adjudications rather than a procedure. The initial artifact speaks a provisional evidential dialect. Live rulings accumulate a pressure field over distinctions the dialect handles poorly. At a compatibility threshold, an entire semantic region changes its admissible causal relations; a pupil observes a deliberately narrow set of cases from both sides of the transition, proves coordination on fresh cases, then replaces the prior speaker. Owner tooling shows phase boundaries, curriculum coverage, and contract-level reasons without preserving a timeless internal vocabulary.", + "boldness": 5, + "parents": [ + "Allomorphic Phase Sheet", + "Evidential Rulecraft", + "Iterated Dialect Mechanics" + ], + "isFusion": true, + "recognizability": 0.42, + "impact": 5, + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "research-spike", + "notNovelBecause": "" + }, + { + "name": "Situated Absence Lineage", + "lens": "engineered negative space × deictic common ground × biological lineage repair", + "assumptionOverturned": "a behavioral guarantee denotes the same forbidden state for every role, place, time, and supported descendant", + "track": "distribution, licensing, and delivery models", + "sourceMechanism": "Parents: Black-Art Absence Certificates contributes developer-level proofs about states and causal relationships that must remain absent; The Deictic Commons contributes referents whose meaning is minted by live roles, places, epochs, and joint attention; Lineage Repair Warranty contributes local remanufacture of a drifting descendant from healthy regions, certified history, and current policy. The new interaction makes every absence boundary deictic: “this authority,” “our epoch,” or “the witnessed object” acquires force only in a live common ground. When an absence ceases to hold, the warranty does not restore an old implementation; it remanufactures the affected relationship and, in doing so, creates new referents and retires old ones. Those referent changes redefine the next absence certificates, so guarantees guide repair while repair evolves what the guarantees can mean.", + "coreIdea": "Ruam sells continuity of situated impossibilities. The protected product is a lineage whose descendants promise that certain domain relationships cannot arise within the common ground currently shared by particular participants. A semantic disagreement triggers constructive repair of the relationship, not byte invalidation; the repaired descendant establishes a new shared situation with a correspondingly new negative space. Meaning survives as a traceable succession of role-relative promises rather than as one file or timeless policy.", + "whyNovel": "This is not a source-integrity check, environment check, patch chain, external key, access-controlled log, or fragment reconstruction. The maintained object is a moving boundary of relationship-level impossibility whose vocabulary is created by use. None of the parents alone makes repair both consume and regenerate the situated semantics of the warranty it fulfills.", + "roughSketch": "The developer defines role lattices, joint-attention events, temporal perspectives, and impossible domain relations. Each deployment carries a lineage passport of certified common-ground transitions, plus service boundaries around replaceable relationships. The custodian proves absence claims against the live referents. If a claim fails, a maintenance process preserves conforming relationships, resynthesizes the smallest broken social or temporal relation from current policy and trusted history, and issues a descendant passport whose newly minted terms become the basis of subsequent guarantees. Owner diagnostics narrate the repaired promise in domain language.", + "boldness": 5, + "parents": [ + "Black-Art Absence Certificates", + "The Deictic Commons", + "Lineage Repair Warranty" + ], + "isFusion": true, + "recognizability": 0.8, + "impact": 4, + "rails": { + "serverFree": "needs-relaxation", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "product-pivot", + "notNovelBecause": "It composes contextual policy contracts, runtime monitoring, provenance certificates, and automated program repair into a lineage service." + }, + { + "name": "Confidence-Driven Contract Metabolism", + "lens": "auxetic metamaterials × stage convincers × synonymous biological supply", + "assumptionOverturned": "evidence verifies a finished product assembled from a supplier catalog that stays external to execution", + "track": "verifiability and developer experience", + "sourceMechanism": "Parents: Auxetic Contract Tissue contributes a coupled semantic material whose neighborhood and resting geometry expand under local demand; The Convincer Ecology contributes heterogeneous, partially independent demonstrations that support developer trust questions; Synonymous Supply Network contributes changing families of ordinary qualified components whose global meaning lies in fit and tolerance relationships. The new interaction turns low confidence into mechanical demand on the contract tissue. Stretching one trust question recruits perpendicular supplier components specifically to grow unlike demonstrations; the results then qualify, retire, or redefine supplier classes and persist as a new resting geometry. Thus evidence pressure reshapes the product, newly sourced parts change which evidence can be independent, and the supply ecology becomes endogenous to the artifact's semantic metabolism.", + "coreIdea": "Ruam emits a product that grows where its owner has the least confidence. A thinly supported domain claim creates semantic tension, causing the artifact to widen that contract into neighboring constructions, source qualified ordinary components that realize those new dimensions, and stage independent demonstrations across them. Successful demonstrations become structural nutrients: they alter future substitution sockets and make later behavior expressible through a broader, locally evolved contract tissue.", + "whyNovel": "This is not redundant implementations, code bloat, a package manager, a fixed test suite, fragment scattering, or adaptive optimization. The basic unit is a feedback loop in which confidence topology is constitutive material for implementation growth and component qualification. None of the parents alone makes trust questions physically reorganize the product's semantic dimensions and its future supplier market.", + "roughSketch": "The developer declares behavioral boundaries, trust questions, evidence-independence criteria, and component tolerance gauges. The installed artifact maintains a causal stress map: a claim supported by too few unlike demonstrations stretches its region, creating new substitution sockets along coupled contract axes. A sourcing process fills them from eligible local or organizational catalogs, while a rehearsal workbench derives metamorphic, boundary, counterfactual, and production-grounded convincers from the expanded tissue. Evidence updates both the confidence map and supplier qualifications, so each installation develops a distinct, owner-readable metabolism rather than accumulating a stable implementation map.", + "boldness": 5, + "parents": [ + "Auxetic Contract Tissue", + "The Convincer Ecology", + "Synonymous Supply Network" + ], + "isFusion": true, + "recognizability": 0.74, + "impact": 4, + "rails": { + "serverFree": "needs-relaxation", + "sizeLean": "fails", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + }, + "maturity": "product-pivot", + "notNovelBecause": "The core loop is confidence-gap-driven test generation, component sourcing, qualification, and contract-guided program synthesis." + } + ], + "headline": "The most promising Gen2 direction is to stop treating a fixed implementation as the protected object and instead make a developer-owned semantic treaty generate, evolve, and explain multiple causally distinct realizations. Traveling Isogloss supplies the strongest new execution primitive—a moving boundary that alone can express behavior—while Evidential Phase Succession and Anisomorphic Ruleworlds show how that primitive can mature into a product: meaning can migrate between grammars and ontologies, with owner-facing evidence proving stable outcomes rather than exposing a canonical program. Phenotype Manufacturing Right and The Questioning Scar add credible commercial and developer surfaces. Together they suggest Ruam Gen2 as a semantic-lifecycle system whose resilience to mechanical reconstruction is a consequence of plural, history-bearing realizations, not another concealment layer.", + "slate": { + "headline": "The most promising Gen2 direction is to stop treating a fixed implementation as the protected object and instead make a developer-owned semantic treaty generate, evolve, and explain multiple causally distinct realizations. Traveling Isogloss supplies the strongest new execution primitive—a moving boundary that alone can express behavior—while Evidential Phase Succession and Anisomorphic Ruleworlds show how that primitive can mature into a product: meaning can migrate between grammars and ontologies, with owner-facing evidence proving stable outcomes rather than exposing a canonical program. Phenotype Manufacturing Right and The Questioning Scar add credible commercial and developer surfaces. Together they suggest Ruam Gen2 as a semantic-lifecycle system whose resilience to mechanical reconstruction is a consequence of plural, history-bearing realizations, not another concealment layer.", + "method": "Near-duplicates were clustered without deleting or merging any candidate. For each rail, pass=1.0, needs-relaxation=0.8, and fails=0.5; feasibilityWeight is the arithmetic mean across serverFree, sizeLean, cspSafe, buildRuntimeProvable, and additiveApi. Composite is impact * (1 - recognizability) * feasibilityWeight, rounded to three decimals. Ranking is descending composite, then lower recognizability, then higher impact, with candidate input order retained only for complete ties; no score was adjusted for narrative preference.", + "clusterSummary": [ + { + "cluster": "moving-boundary-and-grammar-succession", + "description": "Meaning resides in a history-bearing boundary or grammar that changes its own future transition rules; this is the clearest genuinely Gen2 mechanism family.", + "members": [ + "Hysteretic Dialect Lattice", + "Traveling Isogloss", + "Allomorphic Phase Sheet", + "Iterated Dialect Mechanics", + "Evidential Phase Succession" + ] + }, + { + "cluster": "plural-ontologies-and-relational-meaning", + "description": "Behavior exists in relations among host context, evidence, shared referents, or multiple causal ontologies rather than inside one artifact.", + "members": [ + "MHC-Restricted Program Tissue", + "Evidential Rulecraft", + "The Deictic Commons", + "Anisomorphic Ruleworlds", + "The Migrating Treaty" + ] + }, + { + "cluster": "emergent-material-computation", + "description": "Ordinary local components collectively realize behavior through attractors, transient topology, population dosage, exchange circulation, or traveling waves.", + "members": [ + "Idiotype Phase Matter", + "Anastomotic Execution", + "Heterokaryotic Semantics", + "Reciprocal-Exchange Computation", + "Soliton Tissue" + ] + }, + { + "cluster": "generative-phenotype-manufacture", + "description": "A behavioral envelope or phenotype, rather than stored code, drives local synthesis, selection, growth, or licensed manufacture of disposable implementations.", + "members": [ + "The Tolerance Mold", + "Affinity Furnace", + "The Germinating Program", + "Post-Emit Morphogenesis", + "Phenotype Manufacturing Right" + ] + }, + { + "cluster": "semantic-observability-and-proof", + "description": "Developer trust comes from question-relative explanations, independent executable projections, effect contracts, absence proofs, or evolving evidence portfolios.", + "members": [ + "Focus-Pull Debugger", + "Angle-Multiplexed Truth Deck", + "Semantic Fringe Observatory", + "The Cue-Sheet Contract Theater", + "Question-Sightline Observatory", + "Dual-Reality Debugging", + "Black-Art Absence Certificates", + "The Convincer Ecology", + "The Questioning Scar" + ] + }, + { + "cluster": "developmental-product-surfaces", + "description": "Product surfaces, semantic deformation, contract geometry, and owner questions become active material that grows or repairs the deployed system.", + "members": [ + "Whole-Show Rehearsal Mesh", + "Auxetic Contract Tissue", + "Rehearsal Germ Layers", + "Fringe-Driven Phase Healing", + "Treaty-Sightline Foundry", + "Confidence-Driven Contract Metabolism" + ] + }, + { + "cluster": "lifecycle-licensing-and-continuity", + "description": "Commercial value attaches to qualified supply, descendant repair, semantic fidelity, certified decisions, or continuity of contextual guarantees rather than static bytes.", + "members": [ + "Synonymous Supply Network", + "Lineage Repair Warranty", + "Fidelity-Gradient License", + "Proofreading Royalty", + "Situated Absence Lineage" + ] + } + ], + "ranked": [ + { + "rank": 1, + "name": "Traveling Isogloss", + "track": "self-modifying or living output", + "composite": 2.870, + "recognizability": 0.3, + "impact": 5, + "feasibilityWeight": 0.82, + "oneLine": "Two internally rigid semantic regions share one movable interface that alone can realize calls, and each completed call relocates that expressive interface for the next invocation.", + "whyNotGen1": "Unlike salt, keystream, or digest work, no encoded implementation is varied or verified; the computation is the history-dependent motion of a semantic boundary.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "moving-boundary-and-grammar-succession", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 2, + "name": "Evidential Phase Succession", + "track": "self-modifying or living output", + "composite": 2.378, + "recognizability": 0.42, + "impact": 5, + "feasibilityWeight": 0.82, + "oneLine": "Accumulated provenance pressure triggers a region-wide grammar change, after which a narrowly trained successor must ratify the new evidence categories before taking over.", + "whyNotGen1": "Unlike salt, keystream, or digest work, the changing object is the coupling among evidence ontology, causal grammar, and learned succession rather than encoded bytes.", + "maturity": "research-spike", + "isFusion": true, + "parents": [ + "Allomorphic Phase Sheet", + "Evidential Rulecraft", + "Iterated Dialect Mechanics" + ], + "cluster": "moving-boundary-and-grammar-succession", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 3, + "name": "Anisomorphic Ruleworlds", + "track": "semantic-level protection", + "composite": 2.214, + "recognizability": 0.46, + "impact": 5, + "feasibilityWeight": 0.82, + "oneLine": "Execution moves among contract-equivalent rule systems that disagree about what counts as an actor, resource, cause, and action while preserving observable outcomes.", + "whyNotGen1": "Unlike salt, keystream, or digest work, it preserves an outcome treaty while changing the program's causal ontology, not the representation of one procedure.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "plural-ontologies-and-relational-meaning", + "rails": { + "serverFree": "pass", + "sizeLean": "fails", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 4, + "name": "Phenotype Manufacturing Right", + "track": "distribution, licensing, and delivery models", + "composite": 1.936, + "recognizability": 0.56, + "impact": 5, + "feasibilityWeight": 0.88, + "oneLine": "A license authorizes a local foundry to synthesize and certify a disposable implementation from a behavioral phenotype and currently available platform components.", + "whyNotGen1": "Unlike salt, keystream, or digest work, authorization governs manufacture of a new conforming specimen rather than access to or decoding of a canonical artifact.", + "maturity": "product-pivot", + "isFusion": false, + "parents": [], + "cluster": "generative-phenotype-manufacture", + "rails": { + "serverFree": "needs-relaxation", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 5, + "name": "The Questioning Scar", + "track": "verifiability and developer experience", + "composite": 1.934, + "recognizability": 0.38, + "impact": 4, + "feasibilityWeight": 0.78, + "oneLine": "Each owner question grows a temporary implementation that must both reproduce behavior and answer in domain terms, then leaves a structural change that shapes later behavior and evidence.", + "whyNotGen1": "Unlike salt, keystream, or digest work, explanation and implementation are co-produced for a question and permanently alter the artifact's future grammar.", + "maturity": "research-spike", + "isFusion": true, + "parents": [ + "The Tolerance Mold", + "Focus-Pull Debugger", + "Hysteretic Dialect Lattice" + ], + "cluster": "semantic-observability-and-proof", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "needs-relaxation", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 6, + "name": "Soliton Tissue", + "track": "novel transformation paradigms", + "composite": 1.845, + "recognizability": 0.55, + "impact": 5, + "feasibilityWeight": 0.82, + "oneLine": "Inputs launch stable collective pulses through a nonlinear medium whose state is rewritten by prior pulses, with outputs read from wave phenotype rather than dispatched operations.", + "whyNotGen1": "Unlike salt, keystream, or digest work, the representation is a history-bearing transport medium and its collective waves, with no instruction stream to conceal.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "emergent-material-computation", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 7, + "name": "Treaty-Sightline Foundry", + "track": "new product surfaces and capabilities", + "composite": 1.760, + "recognizability": 0.6, + "impact": 5, + "feasibilityWeight": 0.88, + "oneLine": "An owner question selects a foreign causal ontology, triggers manufacture of a temporary implementation in that ontology, and derives the smallest cross-world projection that certifies the same outcome treaty.", + "whyNotGen1": "Unlike salt, keystream, or digest work, a diagnostic question manufactures an ontology-distinct counterpart and uses cross-world agreement as the explanation certificate.", + "maturity": "product-pivot", + "isFusion": true, + "parents": [ + "Anisomorphic Ruleworlds", + "Question-Sightline Observatory", + "Phenotype Manufacturing Right" + ], + "cluster": "developmental-product-surfaces", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 8, + "name": "Iterated Dialect Mechanics", + "track": "semantic-level protection", + "composite": 1.722, + "recognizability": 0.58, + "impact": 5, + "feasibilityWeight": 0.82, + "oneLine": "A running artifact periodically trains a successor from constrained demonstrations of semantic play, retires its own vocabulary, and retains only contract-level coordination.", + "whyNotGen1": "Unlike salt, keystream, or digest work, successive artifacts share no hidden command vocabulary; behavior persists through cultural transmission of semantic invariants.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "moving-boundary-and-grammar-succession", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 9, + "name": "The Migrating Treaty", + "track": "semantic-level protection", + "composite": 1.640, + "recognizability": 0.6, + "impact": 5, + "feasibilityWeight": 0.82, + "oneLine": "Host capabilities and artifact offerings close a reciprocal circuit only at a movable semantic boundary, and fulfilling one call shifts the boundary and renegotiates the next call's terms.", + "whyNotGen1": "Unlike salt, keystream, or digest work, neither side contributes concealed bits; meaning is a self-sustaining exchange whose successful completion relocates its own interface.", + "maturity": "research-spike", + "isFusion": true, + "parents": [ + "MHC-Restricted Program Tissue", + "Reciprocal-Exchange Computation", + "Traveling Isogloss" + ], + "cluster": "plural-ontologies-and-relational-meaning", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 10, + "name": "The Cue-Sheet Contract Theater", + "track": "verifiability and developer experience", + "composite": 1.496, + "recognizability": 0.66, + "impact": 5, + "feasibilityWeight": 0.88, + "oneLine": "Developers specify externally meaningful effects and causal obligations, then Ruam generates a role-and-cue realization plus a console that proves and replays only failed semantic beats.", + "whyNotGen1": "Unlike salt, keystream, or digest work, the durable unit is an effect contract and its proof, while the generated realization need not retain function-level ancestry.", + "maturity": "product-pivot", + "isFusion": false, + "parents": [], + "cluster": "semantic-observability-and-proof", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 11, + "name": "MHC-Restricted Program Tissue", + "track": "resilience to automated understanding", + "composite": 1.398, + "recognizability": 0.62, + "impact": 4, + "feasibilityWeight": 0.92, + "oneLine": "Ordinary host events present typed semantic operations to a complementary artifact, and only their live contextual conjunction realizes the protected behavior.", + "whyNotGen1": "Unlike salt, keystream, or digest work, the host supplies useful semantic participation rather than a secret term, so behavior belongs to the relation between two systems.", + "maturity": "product-pivot", + "isFusion": false, + "parents": [], + "cluster": "plural-ontologies-and-relational-meaning", + "rails": { + "serverFree": "pass", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 12, + "name": "Rehearsal Germ Layers", + "track": "new product surfaces and capabilities", + "composite": 1.382, + "recognizability": 0.52, + "impact": 4, + "feasibilityWeight": 0.72, + "oneLine": "Coarse but complete product surfaces overlap on real journeys, and their agreement selects newly synthesized behavior organs that increase the next generation's resolution.", + "whyNotGen1": "Unlike salt, keystream, or digest work, no finished implementation waits to be recovered; cross-resolution product agreement is the developmental material that grows one.", + "maturity": "research-spike", + "isFusion": true, + "parents": [ + "Whole-Show Rehearsal Mesh", + "Affinity Furnace", + "Post-Emit Morphogenesis" + ], + "cluster": "developmental-product-surfaces", + "rails": { + "serverFree": "pass", + "sizeLean": "fails", + "cspSafe": "needs-relaxation", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 13, + "name": "Auxetic Contract Tissue", + "track": "self-modifying or living output", + "composite": 1.378, + "recognizability": 0.58, + "impact": 4, + "feasibilityWeight": 0.82, + "oneLine": "Exercising a contract mechanically expands its semantic neighborhood along coupled dimensions, and part of that newly formed structure becomes the artifact's next resting language.", + "whyNotGen1": "Unlike salt, keystream, or digest work, execution changes the dimensionality of the implementation vocabulary instead of re-encoding a fixed route.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "developmental-product-surfaces", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 14, + "name": "Hysteretic Dialect Lattice", + "track": "self-modifying or living output", + "composite": 1.312, + "recognizability": 0.68, + "impact": 5, + "feasibilityWeight": 0.82, + "oneLine": "Each installation grammaticalizes frequent compositions, erodes unused ones, and emits a successor grammar whose future transitions depend on its usage history.", + "whyNotGen1": "Unlike salt, keystream, or digest work, the artifact evolves a history-dependent constraint grammar rather than applying fresh variation to stable commands.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "moving-boundary-and-grammar-succession", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 15, + "name": "Fringe-Driven Phase Healing", + "track": "self-modifying / living output", + "composite": 1.312, + "recognizability": 0.68, + "impact": 5, + "feasibilityWeight": 0.82, + "oneLine": "Measured semantic drift is converted into a traveling pulse that changes local interaction thresholds until the distributed behavior settles into a new contract-faithful attractor.", + "whyNotGen1": "Unlike salt, keystream, or digest work, a consequence deformation directly becomes the repair event that reorganizes a collective semantic phase.", + "maturity": "research-spike", + "isFusion": true, + "parents": [ + "Semantic Fringe Observatory", + "Idiotype Phase Matter", + "Soliton Tissue" + ], + "cluster": "developmental-product-surfaces", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 16, + "name": "Anastomotic Execution", + "track": "novel transformation paradigms", + "composite": 0.984, + "recognizability": 0.76, + "impact": 5, + "feasibilityWeight": 0.82, + "oneLine": "Each invocation assembles a transient causal network by fusing compatible incomplete components, reads the network's collective result, then dissolves or remodels it.", + "whyNotGen1": "Substantially recognizable as a dynamic dataflow or actor graph; unlike salt, keystream, or digest work, its variation is per-call topology rather than encoded data.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "emergent-material-computation", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 17, + "name": "Allomorphic Phase Sheet", + "track": "self-modifying or living output", + "composite": 0.975, + "recognizability": 0.75, + "impact": 5, + "feasibilityWeight": 0.78, + "oneLine": "Runtime pressure changes the legal causal relations across an entire semantic region, and each new coherent grammar redraws the set of possible future phase transitions.", + "whyNotGen1": "Substantially recognizable as dynamic program rewriting or metamorphic phase switching; unlike salt, keystream, or digest work, the switch changes regional compatibility rules.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "moving-boundary-and-grammar-succession", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "needs-relaxation", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 18, + "name": "Dual-Reality Debugging", + "track": "verifiability and developer experience", + "composite": 0.957, + "recognizability": 0.74, + "impact": 4, + "feasibilityWeight": 0.92, + "oneLine": "Production, operations, QA, and developer views use different semantic primitives, and temporary typed joins among them produce explanations checked by cross-view consistency laws.", + "whyNotGen1": "Substantially recognizable as role-specific telemetry with consistency checks; unlike salt, keystream, or digest work, it concerns observability views rather than concealment.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "semantic-observability-and-proof", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "pass" + } + }, + { + "rank": 19, + "name": "Reciprocal-Exchange Computation", + "track": "novel transformation paradigms", + "composite": 0.951, + "recognizability": 0.71, + "impact": 4, + "feasibilityWeight": 0.82, + "oneLine": "Local components exchange complementary resources until a self-sustaining circulation satisfies the owned outcome, with every use renegotiating the exchange terms.", + "whyNotGen1": "Substantially recognizable as distributed constraint solving or a chemical-reaction network; unlike salt, keystream, or digest work, meaning is a viable exchange circulation.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "emergent-material-computation", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 20, + "name": "Heterokaryotic Semantics", + "track": "novel transformation paradigms", + "composite": 0.918, + "recognizability": 0.72, + "impact": 4, + "feasibilityWeight": 0.82, + "oneLine": "Multiple incomplete evaluator lineages share a regulatory field, and changing their relative dosage changes the colony-level behavior while preserving a phenotype contract.", + "whyNotGen1": "Substantially recognizable as an adaptive ensemble or mixture of experts; unlike salt, keystream, or digest work, output depends on regulated population composition.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "emergent-material-computation", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 21, + "name": "Idiotype Phase Matter", + "track": "resilience to automated understanding", + "composite": 0.902, + "recognizability": 0.78, + "impact": 5, + "feasibilityWeight": 0.82, + "oneLine": "Input perturbs a sparse recurrent population of incomplete transformations, whose mutual activation and suppression settle into an attractor encoding the result.", + "whyNotGen1": "Substantially recognizable as recurrent, reservoir, or cellular-network computation; unlike salt, keystream, or digest work, the answer is a collective attractor.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "emergent-material-computation", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 22, + "name": "The Germinating Program", + "track": "novel transformation paradigms", + "composite": 0.902, + "recognizability": 0.78, + "impact": 5, + "feasibilityWeight": 0.82, + "oneLine": "A developmental constitution grows, reinforces, and retires causal pathways under workload while phenotype assays keep the changing implementation inside its behavioral envelope.", + "whyNotGen1": "Substantially recognizable as an adaptive runtime with profile-guided specialization; unlike salt, keystream, or digest work, the implementation is grown rather than decoded.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "generative-phenotype-manufacture", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 23, + "name": "Question-Sightline Observatory", + "track": "verifiability and developer experience", + "composite": 0.883, + "recognizability": 0.76, + "impact": 4, + "feasibilityWeight": 0.92, + "oneLine": "A semantic question generates a mechanically complete causal projection containing only the events needed for that answer, and different questions deliberately yield non-nestable views.", + "whyNotGen1": "Substantially recognizable as query-directed causal tracing and certified program slicing; unlike salt, keystream, or digest work, it is an owner observability surface.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "semantic-observability-and-proof", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "pass" + } + }, + { + "rank": 24, + "name": "Angle-Multiplexed Truth Deck", + "track": "New product surfaces and capabilities", + "composite": 0.880, + "recognizability": 0.8, + "impact": 5, + "feasibilityWeight": 0.88, + "oneLine": "One intent specification generates independently executable customer, audit, test, and performance projections whose observable boundaries must agree.", + "whyNotGen1": "Substantially recognizable as N-version programming, executable specifications, and multi-target compilation; unlike salt, keystream, or digest work, it multiplies product views.", + "maturity": "product-pivot", + "isFusion": false, + "parents": [], + "cluster": "semantic-observability-and-proof", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 25, + "name": "The Tolerance Mold", + "track": "resilience to automated understanding", + "composite": 0.780, + "recognizability": 0.8, + "impact": 5, + "feasibilityWeight": 0.78, + "oneLine": "A runtime generator proposes small implementations and rejects any that violate a build-produced atlas of forbidden transitions, conservation laws, and accepted observations.", + "whyNotGen1": "Substantially recognizable as constraint-based program synthesis from negative examples; unlike salt, keystream, or digest work, it selects behavior rather than decodes code.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "generative-phenotype-manufacture", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "needs-relaxation", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 26, + "name": "Confidence-Driven Contract Metabolism", + "track": "verifiability and developer experience", + "composite": 0.749, + "recognizability": 0.74, + "impact": 4, + "feasibilityWeight": 0.72, + "oneLine": "Low-confidence claims expand adjacent contract dimensions, recruit qualified components, and generate independent demonstrations whose results reshape future component sockets.", + "whyNotGen1": "Substantially recognizable as confidence-driven test generation, component sourcing, and contract-guided synthesis; unlike salt, keystream, or digest work, evidence gaps reorganize the product.", + "maturity": "product-pivot", + "isFusion": true, + "parents": [ + "Auxetic Contract Tissue", + "The Convincer Ecology", + "Synonymous Supply Network" + ], + "cluster": "developmental-product-surfaces", + "rails": { + "serverFree": "needs-relaxation", + "sizeLean": "fails", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 27, + "name": "Black-Art Absence Certificates", + "track": "verifiability and developer experience", + "composite": 0.704, + "recognizability": 0.84, + "impact": 5, + "feasibilityWeight": 0.88, + "oneLine": "Ruam specifies impossible domain states and causal relationships, synthesizes within the remaining space, and explains violations through minimal semantic counterexamples.", + "whyNotGen1": "Substantially recognizable as safety-property model checking and runtime contracts; unlike salt, keystream, or digest work, it certifies developer-level negative behavior.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "semantic-observability-and-proof", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 28, + "name": "Lineage Repair Warranty", + "track": "distribution, licensing, and delivery models", + "composite": 0.702, + "recognizability": 0.82, + "impact": 5, + "feasibilityWeight": 0.78, + "oneLine": "A licensed deployment is maintained as a descendant lineage, with drift repaired locally from conforming regions, certified history, semantic policy, and permitted synthesis materials.", + "whyNotGen1": "Substantially recognizable as contract-guided program repair and self-healing software; unlike salt, keystream, or digest work, delivery constructs a new descendant.", + "maturity": "product-pivot", + "isFusion": false, + "parents": [], + "cluster": "lifecycle-licensing-and-continuity", + "rails": { + "serverFree": "needs-relaxation", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 29, + "name": "Fidelity-Gradient License", + "track": "distribution, licensing, and delivery models", + "composite": 0.672, + "recognizability": 0.8, + "impact": 4, + "feasibilityWeight": 0.84, + "oneLine": "License state controls an approved semantic approximation envelope, while renewed service progressively restores exact decisions and reconciles accumulated fidelity debt.", + "whyNotGen1": "Substantially recognizable as graceful degradation, approximate computing, and service-tier licensing; unlike salt, keystream, or digest work, authorization changes behavioral precision.", + "maturity": "product-pivot", + "isFusion": false, + "parents": [], + "cluster": "lifecycle-licensing-and-continuity", + "rails": { + "serverFree": "needs-relaxation", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 30, + "name": "Whole-Show Rehearsal Mesh", + "track": "New product surfaces and capabilities", + "composite": 0.662, + "recognizability": 0.82, + "impact": 4, + "feasibilityWeight": 0.92, + "oneLine": "Ruam generates complete preview, test, partner, edge, and production experiences at different semantic resolutions and upgrades journeys when their overlaps agree.", + "whyNotGen1": "Substantially recognizable as multi-fidelity, multi-target build generation; unlike salt, keystream, or digest work, each output is a useful whole-product surface.", + "maturity": "needs-rail-relaxed", + "isFusion": false, + "parents": [], + "cluster": "developmental-product-surfaces", + "rails": { + "serverFree": "pass", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 31, + "name": "Situated Absence Lineage", + "track": "distribution, licensing, and delivery models", + "composite": 0.624, + "recognizability": 0.8, + "impact": 4, + "feasibilityWeight": 0.78, + "oneLine": "A service certifies relationship-level impossibilities in live shared context, and any failed guarantee triggers repair that creates new referents and a descendant guarantee vocabulary.", + "whyNotGen1": "Substantially recognizable as contextual policy, monitoring, provenance, and program repair; unlike salt, keystream, or digest work, repair regenerates the semantics of future warranties.", + "maturity": "product-pivot", + "isFusion": true, + "parents": [ + "Black-Art Absence Certificates", + "The Deictic Commons", + "Lineage Repair Warranty" + ], + "cluster": "lifecycle-licensing-and-continuity", + "rails": { + "serverFree": "needs-relaxation", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 32, + "name": "Post-Emit Morphogenesis", + "track": "self-modifying or living output", + "composite": 0.624, + "recognizability": 0.84, + "impact": 5, + "feasibilityWeight": 0.78, + "oneLine": "A deployed precursor uses real workload to differentiate interacting behaviors into organs and repeatedly replaces its own organizer with smaller descendants.", + "whyNotGen1": "Substantially recognizable as adaptive specialization or tiered JIT with a self-replacing bootstrap; unlike salt, keystream, or digest work, maturation constructs future anatomy.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "generative-phenotype-manufacture", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "needs-relaxation", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 33, + "name": "Focus-Pull Debugger", + "track": "New product surfaces and capabilities", + "composite": 0.576, + "recognizability": 0.88, + "impact": 5, + "feasibilityWeight": 0.96, + "oneLine": "A domain question selects and reconstructs a high-resolution causal plane from coarse whole-run evidence without exposing a universal address map.", + "whyNotGen1": "Substantially recognizable as query-conditioned provenance tracing and causal debugging; unlike salt, keystream, or digest work, it explains semantic questions.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "semantic-observability-and-proof", + "rails": { + "serverFree": "pass", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "pass" + } + }, + { + "rank": 34, + "name": "Synonymous Supply Network", + "track": "distribution, licensing, and delivery models", + "composite": 0.493, + "recognizability": 0.86, + "impact": 4, + "feasibilityWeight": 0.88, + "oneLine": "Installation resolves ordinary qualified components from changing supplier catalogs and assembles them under published compatibility and tolerance contracts.", + "whyNotGen1": "Substantially recognizable as a governed component marketplace and package resolver; unlike salt, keystream, or digest work, the license buys a qualification ecosystem.", + "maturity": "product-pivot", + "isFusion": false, + "parents": [], + "cluster": "lifecycle-licensing-and-continuity", + "rails": { + "serverFree": "needs-relaxation", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 35, + "name": "The Deictic Commons", + "track": "semantic-level protection", + "composite": 0.478, + "recognizability": 0.87, + "impact": 4, + "feasibilityWeight": 0.92, + "oneLine": "Application relationships mint situated referents at runtime, and each operation both uses those referents and revises the vocabulary available to later operations.", + "whyNotGen1": "Substantially recognizable as relationship-based policy, context-aware capabilities, and event-sourced rules; unlike salt, keystream, or digest work, context creates semantic referents.", + "maturity": "product-pivot", + "isFusion": false, + "parents": [], + "cluster": "plural-ontologies-and-relational-meaning", + "rails": { + "serverFree": "pass", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 36, + "name": "The Convincer Ecology", + "track": "verifiability and developer experience", + "composite": 0.368, + "recognizability": 0.9, + "impact": 4, + "feasibilityWeight": 0.92, + "oneLine": "Ruam maintains an evidence graph of property, metamorphic, boundary, counterfactual, and production-derived demonstrations and grows new tests where owner confidence is thin.", + "whyNotGen1": "Substantially recognizable as an adaptive portfolio of established testing methods; unlike salt, keystream, or digest work, it is a developer confidence product.", + "maturity": "shippable-now", + "isFusion": false, + "parents": [], + "cluster": "semantic-observability-and-proof", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "pass" + } + }, + { + "rank": 37, + "name": "Proofreading Royalty", + "track": "distribution, licensing, and delivery models", + "composite": 0.365, + "recognizability": 0.88, + "impact": 4, + "feasibilityWeight": 0.76, + "oneLine": "A local application proposes bounded candidate actions while a licensed service selects, edits, or certifies the action permitted to commit.", + "whyNotGen1": "Substantially recognizable as split computing with an external policy oracle; unlike salt, keystream, or digest work, the remote product is decision certification.", + "maturity": "product-pivot", + "isFusion": false, + "parents": [], + "cluster": "lifecycle-licensing-and-continuity", + "rails": { + "serverFree": "fails", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 38, + "name": "Evidential Rulecraft", + "track": "semantic-level protection", + "composite": 0.331, + "recognizability": 0.91, + "impact": 4, + "feasibilityWeight": 0.92, + "oneLine": "The build emits a grammar of acceptable justification, and live witnessed, inferred, and delegated facts instantiate a momentary domain ruling.", + "whyNotGen1": "Substantially recognizable as provenance-aware policy-as-code or a rule engine; unlike salt, keystream, or digest work, live evidence constitutes the decision.", + "maturity": "product-pivot", + "isFusion": false, + "parents": [], + "cluster": "plural-ontologies-and-relational-meaning", + "rails": { + "serverFree": "pass", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "needs-relaxation", + "additiveApi": "needs-relaxation" + } + }, + { + "rank": 39, + "name": "Semantic Fringe Observatory", + "track": "New product surfaces and capabilities", + "composite": 0.300, + "recognizability": 0.94, + "impact": 5, + "feasibilityWeight": 1.0, + "oneLine": "Sparse semantic probes and reference oracles produce a whole-product deformation field showing how journeys, authorities, timing, and side effects drift across releases.", + "whyNotGen1": "Substantially recognizable as differential testing, drift detection, and observability dashboards; unlike salt, keystream, or digest work, it measures consequences without altering behavior.", + "maturity": "shippable-now", + "isFusion": false, + "parents": [], + "cluster": "semantic-observability-and-proof", + "rails": { + "serverFree": "pass", + "sizeLean": "pass", + "cspSafe": "pass", + "buildRuntimeProvable": "pass", + "additiveApi": "pass" + } + }, + { + "rank": 40, + "name": "Affinity Furnace", + "track": "resilience to automated understanding", + "composite": 0.262, + "recognizability": 0.92, + "impact": 4, + "feasibilityWeight": 0.82, + "oneLine": "At installation, neutral operators are mutated and selected against behavioral assays until a local implementation passes, with later inputs able to trigger further selection.", + "whyNotGen1": "Substantially recognizable as genetic programming or evolutionary synthesis; unlike salt, keystream, or digest work, it searches for an implementation instead of decoding one.", + "maturity": "research-spike", + "isFusion": false, + "parents": [], + "cluster": "generative-phenotype-manufacture", + "rails": { + "serverFree": "pass", + "sizeLean": "needs-relaxation", + "cspSafe": "pass", + "buildRuntimeProvable": "fails", + "additiveApi": "needs-relaxation" + } + } + ], + "bestFusions": [ + "Evidential Phase Succession", + "The Questioning Scar", + "Treaty-Sightline Foundry", + "The Migrating Treaty", + "Fringe-Driven Phase Healing", + "Rehearsal Germ Layers" + ] + }, + "fusions": [ + "The Questioning Scar", + "Rehearsal Germ Layers", + "Fringe-Driven Phase Healing", + "The Migrating Treaty", + "Treaty-Sightline Foundry", + "Evidential Phase Succession", + "Situated Absence Lineage", + "Confidence-Driven Contract Metabolism" + ], + "coverage": { + "summary": "The run is substantively successful and does not require immediate reseeding: four ideas score below 0.5 recognizability across three tracks, and two fusions appear in the top five. A focused Round 2 is nevertheless recommended because resilience has no top-10 representative, four tracks have no sub-0.5 idea, and several high-ranked mechanisms remain research-level descriptions without a concrete JavaScript representation or proof strategy. Lens and assumption survival below is attributed strictly to original Phase-1 ideas, not to later fusion descendants: on that basis adaptive immune system and holography have no direct top-10 result, while holography alone has no direct top-15 result. Both lenses still influenced top-10 fusions, so this is a direct-generation gap rather than total conceptual absence. The coverage note supplies three untried seeds.", + "runVerdict": { + "successful": true, + "immediateReseedRequired": false, + "criteria": [ + { + "criterion": "At least 3 ideas with recognizability below 0.5 across at least 3 distinct tracks", + "passed": true, + "evidence": "Four ideas qualify: Traveling Isogloss (0.30, self-modifying or living output), Evidential Phase Succession (0.42, self-modifying or living output), Anisomorphic Ruleworlds (0.46, semantic-level protection), and The Questioning Scar (0.38, verifiability and developer experience). They span three distinct tracks." + }, + { + "criterion": "At least 1 fusion in the top 5", + "passed": true, + "evidence": "Evidential Phase Succession is a fusion at rank 2 and The Questioning Scar is a fusion at rank 5." + }, + { + "criterion": "Every top-10 idea carries a whyNotGen1 line", + "passed": true, + "evidence": "All 10 top-ranked entries contain a non-empty whyNotGen1 field." + }, + { + "criterion": "A coverage note names Round-2 seeds", + "passed": true, + "evidence": "This coverage report names exactly three untried lens × assumption × track × persona combinations." + } + ] + }, + "lensesWithoutTop10": [ + "adaptive immune system (clonal selection, self/non-self)", + "holography (every shard holds the whole at lower resolution)" + ], + "lensesWithoutTop15": [ + "holography (every shard holds the whole at lower resolution)" + ], + "assumptionsWithoutTop10": [ + "the decoder must ship alongside the code", + "opacity and correctness are in tension" + ], + "assumptionsWithoutTop15": [ + "opacity and correctness are in tension" + ], + "thinTracks": [ + { + "track": "resilience to automated understanding", + "diagnosis": "Thin by direct generation count, top-rank representation, and recognizability: it has 4 direct ideas, no fusion assigned to the track, no top-10 entry, a first appearance at rank 11, and no idea below 0.5 recognizability; its best score is 0.62." + }, + { + "track": "new product surfaces and capabilities", + "diagnosis": "Thin by direct generation count, direct top-rank representation, recognizability, and implementation specificity: it has 4 direct ideas plus 2 fusions, but its top-15 presence comes only from fusions at ranks 7 and 12; the best direct idea is rank 24 at 0.80 recognizability, no idea is below 0.5, and the proposed consoles and foundries do not yet name a narrow integration or API-level spike." + }, + { + "track": "semantic-level protection", + "diagnosis": "Thin only by direct generation count: it has 4 direct ideas plus 1 fusion. Outcome quality is strong rather than thin, with three top-10 entries and Anisomorphic Ruleworlds at 0.46 recognizability." + }, + { + "track": "novel transformation paradigms", + "diagnosis": "Thin by recognizability and implementation specificity: 5 direct ideas produced one top-10 entry, but none score below 0.5 recognizability and all remain research spikes. The sketches name nonlinear media, growth, or exchange systems without yet defining a compact JavaScript semantic lowering, execution kernel, or equivalence proof." + }, + { + "track": "distribution, licensing, and delivery models", + "diagnosis": "Thin by recognizability and implementation specificity despite a strong rank-4 representative: 5 direct ideas plus 1 fusion yield no idea below 0.5 recognizability, the track average is 0.79, and every candidate is a product pivot without a bounded authorization, installation, renewal, and continuity protocol." + }, + { + "track": "self-modifying or living output", + "diagnosis": "Thin only by implementation specificity: it dominates the top 15 and supplies two sub-0.5 ideas, but its leading mechanisms are research spikes whose build-runtime equivalence rail fails and whose moving-boundary or succession machinery is not yet reduced to an executable prototype." + } + ], + "unsampledRegions": [ + "Observation-conditioned semantics: no generator directly inverted the assumption that behavior is unchanged by observation or used the quantum measurement / observer-effect lens.", + "Functionless and non-enumerable program structure: no generator directly inverted the stable-function-unit assumption, leaving room for representations whose units emerge only during composition.", + "Active artifact protocols: no generator directly inverted the passive-text assumption or the assumption that Ruam does not participate at run time, so continuing product and delivery relationships were reached only indirectly.", + "Local-to-global projection and deliberate distortion: the local-fragment assumption and cartographic-projection lens were both unused, leaving a promising developer-facing region between faithful explanation and non-canonical representation.", + "Absence-of-structure and plural outcomes: neither the no-structure inversion nor the one-input/one-output inversion received a direct assignment." + ], + "round2Seeds": [ + { + "lens": "quantum measurement / observer effect", + "assumption": "the program behaves the same whether observed or not", + "track": "resilience to automated understanding", + "persona": "complexity/systems theorist", + "rationale": "Resilience is the only track absent from the top 10 and has no sub-0.5 idea. This combination directly samples the missing observation-conditioned region and can seek a developer-owned measurement contract whose questions help constitute valid execution, creating a new organizing principle rather than another representation layer." + }, + { + "lens": "cartographic projection (every map distorts something)", + "assumption": "a local fragment is understandable locally", + "track": "new product surfaces and capabilities", + "persona": "industrial designer", + "rationale": "This track has no direct top-15 idea and no sub-0.5 result. Treating every developer view as a purpose-built projection can produce a concrete product surface for choosing, comparing, and validating useful semantic distortions while making the global contract—not a local fragment—the unit of explanation." + }, + { + "lens": "legal contracts and escrow", + "assumption": "the artifact is a passive text to be read", + "track": "distribution / licensing / delivery models", + "persona": "game designer", + "rationale": "Distribution has a strong headline idea but no sub-0.5 candidate and lacks a bounded lifecycle protocol. Combining executable obligations, staged settlement, and stateful rule transitions can make authorization and continuity an active multi-party process with explicit events and invariants, improving both originality and implementation specificity without merely relocating a key." + } + ] + } +} diff --git a/docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-results.md b/docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-results.md new file mode 100644 index 0000000..f9d10be --- /dev/null +++ b/docs/superpowers/specs/2026-07-24-ruam-gen2-ideation-results.md @@ -0,0 +1,477 @@ +# Project Kaleidoscope — Ruam Gen-2 Ideation Results + +**Date:** 2026-07-24 +**Status:** Completed; all campaign gates passed. +**Structured artifact:** [2026-07-24-ruam-gen2-ideation-results.json](2026-07-24-ruam-gen2-ideation-results.json) +**Campaign design:** [2026-07-24-ruam-gen2-ideation-campaign-design.md](2026-07-24-ruam-gen2-ideation-campaign-design.md) + +## Outcome + +The most promising Gen2 direction is to stop treating a fixed implementation as the protected object and instead make a developer-owned semantic treaty generate, evolve, and explain multiple causally distinct realizations. Traveling Isogloss supplies the strongest new execution primitive—a moving boundary that alone can express behavior—while Evidential Phase Succession and Anisomorphic Ruleworlds show how that primitive can mature into a product: meaning can migrate between grammars and ontologies, with owner-facing evidence proving stable outcomes rather than exposing a canonical program. Phenotype Manufacturing Right and The Questioning Scar add credible commercial and developer surfaces. Together they suggest Ruam Gen2 as a semantic-lifecycle system whose resilience to mechanical reconstruction is a consequence of plural, history-bearing realizations, not another concealment layer. + +The run passed its definition of done: **4** ideas scored below 0.5 recognizability across **3** tracks, two fusions reached the top five, every top-10 entry has a Gen-1 distinction, and the coverage critic supplied three Round-2 seeds. Immediate reseeding is not required; a focused second round is recommended. + +| Metric | Result | +|---|---:| +| Raw ideas | 32 | +| Forced fusions | 8 | +| Total ranked | 40 | +| Recognizability < 0.5 | 4 | +| Tracks represented below 0.5 | 3 | +| Logical agent calls | 14 | + +### Top five + +| Rank | Direction | Composite | Recognizability | Impact | Maturity | Fusion | +|---:|---|---:|---:|---:|---|---| +| 1 | Traveling Isogloss | 2.870 | 0.3 | 5 | research-spike | no | +| 2 | Evidential Phase Succession | 2.378 | 0.42 | 5 | research-spike | yes | +| 3 | Anisomorphic Ruleworlds | 2.214 | 0.46 | 5 | research-spike | no | +| 4 | Phenotype Manufacturing Right | 1.936 | 0.56 | 5 | product-pivot | no | +| 5 | The Questioning Scar | 1.934 | 0.38 | 4 | research-spike | yes | + +## Execution and scoring + +The original six-phase topology was reproduced with GPT-5.6 Sol: `high` for the judgment layer (Frame, two Tag passes, Synthesize, Coverage) and `xhigh` for the divergent fleet (seven generators and two fusion agents). Generation used the plan’s deterministic lens × assumption × track × persona assignments and did not admit feasibility as a filter. + +Near-duplicates were clustered without deleting or merging any candidate. For each rail, pass=1.0, needs-relaxation=0.8, and fails=0.5; feasibilityWeight is the arithmetic mean across serverFree, sizeLean, cspSafe, buildRuntimeProvable, and additiveApi. Composite is impact * (1 - recognizability) * feasibilityWeight, rounded to three decimals. Ranking is descending composite, then lower recognizability, then higher impact, with candidate input order retained only for complete ties; no score was adjusted for narrative preference. + +## Ranked slate + +### 1. Traveling Isogloss + +- **Track:** self-modifying or living output +- **Scores:** composite 2.870; recognizability 0.3; impact 5; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** Two internally rigid semantic regions share one movable interface that alone can realize calls, and each completed call relocates that expressive interface for the next invocation. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, no encoded implementation is varied or verified; the computation is the history-dependent motion of a semantic boundary. + +### 2. Evidential Phase Succession — fusion + +- **Track:** self-modifying or living output +- **Scores:** composite 2.378; recognizability 0.42; impact 5; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Parents:** Allomorphic Phase Sheet, Evidential Rulecraft, Iterated Dialect Mechanics +- **Mechanism:** Accumulated provenance pressure triggers a region-wide grammar change, after which a narrowly trained successor must ratify the new evidence categories before taking over. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, the changing object is the coupling among evidence ontology, causal grammar, and learned succession rather than encoded bytes. + +### 3. Anisomorphic Ruleworlds + +- **Track:** semantic-level protection +- **Scores:** composite 2.214; recognizability 0.46; impact 5; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `fails`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `needs-relaxation` +- **Mechanism:** Execution moves among contract-equivalent rule systems that disagree about what counts as an actor, resource, cause, and action while preserving observable outcomes. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, it preserves an outcome treaty while changing the program's causal ontology, not the representation of one procedure. + +### 4. Phenotype Manufacturing Right + +- **Track:** distribution, licensing, and delivery models +- **Scores:** composite 1.936; recognizability 0.56; impact 5; feasibility weight 0.88; maturity `product-pivot` +- **Rails:** server-free `needs-relaxation`; size-lean `pass`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `needs-relaxation` +- **Mechanism:** A license authorizes a local foundry to synthesize and certify a disposable implementation from a behavioral phenotype and currently available platform components. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, authorization governs manufacture of a new conforming specimen rather than access to or decoding of a canonical artifact. + +### 5. The Questioning Scar — fusion + +- **Track:** verifiability and developer experience +- **Scores:** composite 1.934; recognizability 0.38; impact 4; feasibility weight 0.78; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `needs-relaxation`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Parents:** The Tolerance Mold, Focus-Pull Debugger, Hysteretic Dialect Lattice +- **Mechanism:** Each owner question grows a temporary implementation that must both reproduce behavior and answer in domain terms, then leaves a structural change that shapes later behavior and evidence. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, explanation and implementation are co-produced for a question and permanently alter the artifact's future grammar. + +### 6. Soliton Tissue + +- **Track:** novel transformation paradigms +- **Scores:** composite 1.845; recognizability 0.55; impact 5; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** Inputs launch stable collective pulses through a nonlinear medium whose state is rewritten by prior pulses, with outputs read from wave phenotype rather than dispatched operations. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, the representation is a history-bearing transport medium and its collective waves, with no instruction stream to conceal. + +### 7. Treaty-Sightline Foundry — fusion + +- **Track:** new product surfaces and capabilities +- **Scores:** composite 1.760; recognizability 0.6; impact 5; feasibility weight 0.88; maturity `product-pivot` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `needs-relaxation` +- **Parents:** Anisomorphic Ruleworlds, Question-Sightline Observatory, Phenotype Manufacturing Right +- **Mechanism:** An owner question selects a foreign causal ontology, triggers manufacture of a temporary implementation in that ontology, and derives the smallest cross-world projection that certifies the same outcome treaty. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, a diagnostic question manufactures an ontology-distinct counterpart and uses cross-world agreement as the explanation certificate. + +### 8. Iterated Dialect Mechanics + +- **Track:** semantic-level protection +- **Scores:** composite 1.722; recognizability 0.58; impact 5; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** A running artifact periodically trains a successor from constrained demonstrations of semantic play, retires its own vocabulary, and retains only contract-level coordination. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, successive artifacts share no hidden command vocabulary; behavior persists through cultural transmission of semantic invariants. + +### 9. The Migrating Treaty — fusion + +- **Track:** semantic-level protection +- **Scores:** composite 1.640; recognizability 0.6; impact 5; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Parents:** MHC-Restricted Program Tissue, Reciprocal-Exchange Computation, Traveling Isogloss +- **Mechanism:** Host capabilities and artifact offerings close a reciprocal circuit only at a movable semantic boundary, and fulfilling one call shifts the boundary and renegotiates the next call's terms. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, neither side contributes concealed bits; meaning is a self-sustaining exchange whose successful completion relocates its own interface. + +### 10. The Cue-Sheet Contract Theater + +- **Track:** verifiability and developer experience +- **Scores:** composite 1.496; recognizability 0.66; impact 5; feasibility weight 0.88; maturity `product-pivot` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `needs-relaxation` +- **Mechanism:** Developers specify externally meaningful effects and causal obligations, then Ruam generates a role-and-cue realization plus a console that proves and replays only failed semantic beats. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, the durable unit is an effect contract and its proof, while the generated realization need not retain function-level ancestry. + +### 11. MHC-Restricted Program Tissue + +- **Track:** resilience to automated understanding +- **Scores:** composite 1.398; recognizability 0.62; impact 4; feasibility weight 0.92; maturity `product-pivot` +- **Rails:** server-free `pass`; size-lean `pass`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `needs-relaxation` +- **Mechanism:** Ordinary host events present typed semantic operations to a complementary artifact, and only their live contextual conjunction realizes the protected behavior. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, the host supplies useful semantic participation rather than a secret term, so behavior belongs to the relation between two systems. + +### 12. Rehearsal Germ Layers — fusion + +- **Track:** new product surfaces and capabilities +- **Scores:** composite 1.382; recognizability 0.52; impact 4; feasibility weight 0.72; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `fails`; CSP-safe `needs-relaxation`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Parents:** Whole-Show Rehearsal Mesh, Affinity Furnace, Post-Emit Morphogenesis +- **Mechanism:** Coarse but complete product surfaces overlap on real journeys, and their agreement selects newly synthesized behavior organs that increase the next generation's resolution. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, no finished implementation waits to be recovered; cross-resolution product agreement is the developmental material that grows one. + +### 13. Auxetic Contract Tissue + +- **Track:** self-modifying or living output +- **Scores:** composite 1.378; recognizability 0.58; impact 4; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** Exercising a contract mechanically expands its semantic neighborhood along coupled dimensions, and part of that newly formed structure becomes the artifact's next resting language. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, execution changes the dimensionality of the implementation vocabulary instead of re-encoding a fixed route. + +### 14. Hysteretic Dialect Lattice + +- **Track:** self-modifying or living output +- **Scores:** composite 1.312; recognizability 0.68; impact 5; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** Each installation grammaticalizes frequent compositions, erodes unused ones, and emits a successor grammar whose future transitions depend on its usage history. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, the artifact evolves a history-dependent constraint grammar rather than applying fresh variation to stable commands. + +### 15. Fringe-Driven Phase Healing — fusion + +- **Track:** self-modifying / living output +- **Scores:** composite 1.312; recognizability 0.68; impact 5; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Parents:** Semantic Fringe Observatory, Idiotype Phase Matter, Soliton Tissue +- **Mechanism:** Measured semantic drift is converted into a traveling pulse that changes local interaction thresholds until the distributed behavior settles into a new contract-faithful attractor. +- **Why this is not Gen-1:** Unlike salt, keystream, or digest work, a consequence deformation directly becomes the repair event that reorganizes a collective semantic phase. + +### 16. Anastomotic Execution + +- **Track:** novel transformation paradigms +- **Scores:** composite 0.984; recognizability 0.76; impact 5; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** Each invocation assembles a transient causal network by fusing compatible incomplete components, reads the network's collective result, then dissolves or remodels it. +- **Why this is not Gen-1:** Substantially recognizable as a dynamic dataflow or actor graph; unlike salt, keystream, or digest work, its variation is per-call topology rather than encoded data. + +### 17. Allomorphic Phase Sheet + +- **Track:** self-modifying or living output +- **Scores:** composite 0.975; recognizability 0.75; impact 5; feasibility weight 0.78; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `needs-relaxation`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** Runtime pressure changes the legal causal relations across an entire semantic region, and each new coherent grammar redraws the set of possible future phase transitions. +- **Why this is not Gen-1:** Substantially recognizable as dynamic program rewriting or metamorphic phase switching; unlike salt, keystream, or digest work, the switch changes regional compatibility rules. + +### 18. Dual-Reality Debugging + +- **Track:** verifiability and developer experience +- **Scores:** composite 0.957; recognizability 0.74; impact 4; feasibility weight 0.92; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `pass` +- **Mechanism:** Production, operations, QA, and developer views use different semantic primitives, and temporary typed joins among them produce explanations checked by cross-view consistency laws. +- **Why this is not Gen-1:** Substantially recognizable as role-specific telemetry with consistency checks; unlike salt, keystream, or digest work, it concerns observability views rather than concealment. + +### 19. Reciprocal-Exchange Computation + +- **Track:** novel transformation paradigms +- **Scores:** composite 0.951; recognizability 0.71; impact 4; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** Local components exchange complementary resources until a self-sustaining circulation satisfies the owned outcome, with every use renegotiating the exchange terms. +- **Why this is not Gen-1:** Substantially recognizable as distributed constraint solving or a chemical-reaction network; unlike salt, keystream, or digest work, meaning is a viable exchange circulation. + +### 20. Heterokaryotic Semantics + +- **Track:** novel transformation paradigms +- **Scores:** composite 0.918; recognizability 0.72; impact 4; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** Multiple incomplete evaluator lineages share a regulatory field, and changing their relative dosage changes the colony-level behavior while preserving a phenotype contract. +- **Why this is not Gen-1:** Substantially recognizable as an adaptive ensemble or mixture of experts; unlike salt, keystream, or digest work, output depends on regulated population composition. + +### 21. Idiotype Phase Matter + +- **Track:** resilience to automated understanding +- **Scores:** composite 0.902; recognizability 0.78; impact 5; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** Input perturbs a sparse recurrent population of incomplete transformations, whose mutual activation and suppression settle into an attractor encoding the result. +- **Why this is not Gen-1:** Substantially recognizable as recurrent, reservoir, or cellular-network computation; unlike salt, keystream, or digest work, the answer is a collective attractor. + +### 22. The Germinating Program + +- **Track:** novel transformation paradigms +- **Scores:** composite 0.902; recognizability 0.78; impact 5; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** A developmental constitution grows, reinforces, and retires causal pathways under workload while phenotype assays keep the changing implementation inside its behavioral envelope. +- **Why this is not Gen-1:** Substantially recognizable as an adaptive runtime with profile-guided specialization; unlike salt, keystream, or digest work, the implementation is grown rather than decoded. + +### 23. Question-Sightline Observatory + +- **Track:** verifiability and developer experience +- **Scores:** composite 0.883; recognizability 0.76; impact 4; feasibility weight 0.92; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `pass` +- **Mechanism:** A semantic question generates a mechanically complete causal projection containing only the events needed for that answer, and different questions deliberately yield non-nestable views. +- **Why this is not Gen-1:** Substantially recognizable as query-directed causal tracing and certified program slicing; unlike salt, keystream, or digest work, it is an owner observability surface. + +### 24. Angle-Multiplexed Truth Deck + +- **Track:** New product surfaces and capabilities +- **Scores:** composite 0.880; recognizability 0.8; impact 5; feasibility weight 0.88; maturity `product-pivot` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `needs-relaxation` +- **Mechanism:** One intent specification generates independently executable customer, audit, test, and performance projections whose observable boundaries must agree. +- **Why this is not Gen-1:** Substantially recognizable as N-version programming, executable specifications, and multi-target compilation; unlike salt, keystream, or digest work, it multiplies product views. + +### 25. The Tolerance Mold + +- **Track:** resilience to automated understanding +- **Scores:** composite 0.780; recognizability 0.8; impact 5; feasibility weight 0.78; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `needs-relaxation`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** A runtime generator proposes small implementations and rejects any that violate a build-produced atlas of forbidden transitions, conservation laws, and accepted observations. +- **Why this is not Gen-1:** Substantially recognizable as constraint-based program synthesis from negative examples; unlike salt, keystream, or digest work, it selects behavior rather than decodes code. + +### 26. Confidence-Driven Contract Metabolism — fusion + +- **Track:** verifiability and developer experience +- **Scores:** composite 0.749; recognizability 0.74; impact 4; feasibility weight 0.72; maturity `product-pivot` +- **Rails:** server-free `needs-relaxation`; size-lean `fails`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Parents:** Auxetic Contract Tissue, The Convincer Ecology, Synonymous Supply Network +- **Mechanism:** Low-confidence claims expand adjacent contract dimensions, recruit qualified components, and generate independent demonstrations whose results reshape future component sockets. +- **Why this is not Gen-1:** Substantially recognizable as confidence-driven test generation, component sourcing, and contract-guided synthesis; unlike salt, keystream, or digest work, evidence gaps reorganize the product. + +### 27. Black-Art Absence Certificates + +- **Track:** verifiability and developer experience +- **Scores:** composite 0.704; recognizability 0.84; impact 5; feasibility weight 0.88; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `needs-relaxation` +- **Mechanism:** Ruam specifies impossible domain states and causal relationships, synthesizes within the remaining space, and explains violations through minimal semantic counterexamples. +- **Why this is not Gen-1:** Substantially recognizable as safety-property model checking and runtime contracts; unlike salt, keystream, or digest work, it certifies developer-level negative behavior. + +### 28. Lineage Repair Warranty + +- **Track:** distribution, licensing, and delivery models +- **Scores:** composite 0.702; recognizability 0.82; impact 5; feasibility weight 0.78; maturity `product-pivot` +- **Rails:** server-free `needs-relaxation`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** A licensed deployment is maintained as a descendant lineage, with drift repaired locally from conforming regions, certified history, semantic policy, and permitted synthesis materials. +- **Why this is not Gen-1:** Substantially recognizable as contract-guided program repair and self-healing software; unlike salt, keystream, or digest work, delivery constructs a new descendant. + +### 29. Fidelity-Gradient License + +- **Track:** distribution, licensing, and delivery models +- **Scores:** composite 0.672; recognizability 0.8; impact 4; feasibility weight 0.84; maturity `product-pivot` +- **Rails:** server-free `needs-relaxation`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `needs-relaxation` +- **Mechanism:** License state controls an approved semantic approximation envelope, while renewed service progressively restores exact decisions and reconciles accumulated fidelity debt. +- **Why this is not Gen-1:** Substantially recognizable as graceful degradation, approximate computing, and service-tier licensing; unlike salt, keystream, or digest work, authorization changes behavioral precision. + +### 30. Whole-Show Rehearsal Mesh + +- **Track:** New product surfaces and capabilities +- **Scores:** composite 0.662; recognizability 0.82; impact 4; feasibility weight 0.92; maturity `needs-rail-relaxed` +- **Rails:** server-free `pass`; size-lean `pass`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `needs-relaxation` +- **Mechanism:** Ruam generates complete preview, test, partner, edge, and production experiences at different semantic resolutions and upgrades journeys when their overlaps agree. +- **Why this is not Gen-1:** Substantially recognizable as multi-fidelity, multi-target build generation; unlike salt, keystream, or digest work, each output is a useful whole-product surface. + +### 31. Situated Absence Lineage — fusion + +- **Track:** distribution, licensing, and delivery models +- **Scores:** composite 0.624; recognizability 0.8; impact 4; feasibility weight 0.78; maturity `product-pivot` +- **Rails:** server-free `needs-relaxation`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Parents:** Black-Art Absence Certificates, The Deictic Commons, Lineage Repair Warranty +- **Mechanism:** A service certifies relationship-level impossibilities in live shared context, and any failed guarantee triggers repair that creates new referents and a descendant guarantee vocabulary. +- **Why this is not Gen-1:** Substantially recognizable as contextual policy, monitoring, provenance, and program repair; unlike salt, keystream, or digest work, repair regenerates the semantics of future warranties. + +### 32. Post-Emit Morphogenesis + +- **Track:** self-modifying or living output +- **Scores:** composite 0.624; recognizability 0.84; impact 5; feasibility weight 0.78; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `needs-relaxation`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** A deployed precursor uses real workload to differentiate interacting behaviors into organs and repeatedly replaces its own organizer with smaller descendants. +- **Why this is not Gen-1:** Substantially recognizable as adaptive specialization or tiered JIT with a self-replacing bootstrap; unlike salt, keystream, or digest work, maturation constructs future anatomy. + +### 33. Focus-Pull Debugger + +- **Track:** New product surfaces and capabilities +- **Scores:** composite 0.576; recognizability 0.88; impact 5; feasibility weight 0.96; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `pass`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `pass` +- **Mechanism:** A domain question selects and reconstructs a high-resolution causal plane from coarse whole-run evidence without exposing a universal address map. +- **Why this is not Gen-1:** Substantially recognizable as query-conditioned provenance tracing and causal debugging; unlike salt, keystream, or digest work, it explains semantic questions. + +### 34. Synonymous Supply Network + +- **Track:** distribution, licensing, and delivery models +- **Scores:** composite 0.493; recognizability 0.86; impact 4; feasibility weight 0.88; maturity `product-pivot` +- **Rails:** server-free `needs-relaxation`; size-lean `pass`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `needs-relaxation` +- **Mechanism:** Installation resolves ordinary qualified components from changing supplier catalogs and assembles them under published compatibility and tolerance contracts. +- **Why this is not Gen-1:** Substantially recognizable as a governed component marketplace and package resolver; unlike salt, keystream, or digest work, the license buys a qualification ecosystem. + +### 35. The Deictic Commons + +- **Track:** semantic-level protection +- **Scores:** composite 0.478; recognizability 0.87; impact 4; feasibility weight 0.92; maturity `product-pivot` +- **Rails:** server-free `pass`; size-lean `pass`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `needs-relaxation` +- **Mechanism:** Application relationships mint situated referents at runtime, and each operation both uses those referents and revises the vocabulary available to later operations. +- **Why this is not Gen-1:** Substantially recognizable as relationship-based policy, context-aware capabilities, and event-sourced rules; unlike salt, keystream, or digest work, context creates semantic referents. + +### 36. The Convincer Ecology + +- **Track:** verifiability and developer experience +- **Scores:** composite 0.368; recognizability 0.9; impact 4; feasibility weight 0.92; maturity `shippable-now` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `pass` +- **Mechanism:** Ruam maintains an evidence graph of property, metamorphic, boundary, counterfactual, and production-derived demonstrations and grows new tests where owner confidence is thin. +- **Why this is not Gen-1:** Substantially recognizable as an adaptive portfolio of established testing methods; unlike salt, keystream, or digest work, it is a developer confidence product. + +### 37. Proofreading Royalty + +- **Track:** distribution, licensing, and delivery models +- **Scores:** composite 0.365; recognizability 0.88; impact 4; feasibility weight 0.76; maturity `product-pivot` +- **Rails:** server-free `fails`; size-lean `pass`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** A local application proposes bounded candidate actions while a licensed service selects, edits, or certifies the action permitted to commit. +- **Why this is not Gen-1:** Substantially recognizable as split computing with an external policy oracle; unlike salt, keystream, or digest work, the remote product is decision certification. + +### 38. Evidential Rulecraft + +- **Track:** semantic-level protection +- **Scores:** composite 0.331; recognizability 0.91; impact 4; feasibility weight 0.92; maturity `product-pivot` +- **Rails:** server-free `pass`; size-lean `pass`; CSP-safe `pass`; build/runtime provable `needs-relaxation`; additive API `needs-relaxation` +- **Mechanism:** The build emits a grammar of acceptable justification, and live witnessed, inferred, and delegated facts instantiate a momentary domain ruling. +- **Why this is not Gen-1:** Substantially recognizable as provenance-aware policy-as-code or a rule engine; unlike salt, keystream, or digest work, live evidence constitutes the decision. + +### 39. Semantic Fringe Observatory + +- **Track:** New product surfaces and capabilities +- **Scores:** composite 0.300; recognizability 0.94; impact 5; feasibility weight 1.0; maturity `shippable-now` +- **Rails:** server-free `pass`; size-lean `pass`; CSP-safe `pass`; build/runtime provable `pass`; additive API `pass` +- **Mechanism:** Sparse semantic probes and reference oracles produce a whole-product deformation field showing how journeys, authorities, timing, and side effects drift across releases. +- **Why this is not Gen-1:** Substantially recognizable as differential testing, drift detection, and observability dashboards; unlike salt, keystream, or digest work, it measures consequences without altering behavior. + +### 40. Affinity Furnace + +- **Track:** resilience to automated understanding +- **Scores:** composite 0.262; recognizability 0.92; impact 4; feasibility weight 0.82; maturity `research-spike` +- **Rails:** server-free `pass`; size-lean `needs-relaxation`; CSP-safe `pass`; build/runtime provable `fails`; additive API `needs-relaxation` +- **Mechanism:** At installation, neutral operators are mutated and selected against behavioral assays until a local implementation passes, with later inputs able to trigger further selection. +- **Why this is not Gen-1:** Substantially recognizable as genetic programming or evolutionary synthesis; unlike salt, keystream, or digest work, it searches for an implementation instead of decoding one. + +## Best fusions + +1. **Evidential Phase Succession** +2. **The Questioning Scar** +3. **Treaty-Sightline Foundry** +4. **The Migrating Treaty** +5. **Fringe-Driven Phase Healing** +6. **Rehearsal Germ Layers** + +## Concept clusters + +### moving-boundary-and-grammar-succession + +Meaning resides in a history-bearing boundary or grammar that changes its own future transition rules; this is the clearest genuinely Gen2 mechanism family. + +Members: Hysteretic Dialect Lattice, Traveling Isogloss, Allomorphic Phase Sheet, Iterated Dialect Mechanics, Evidential Phase Succession + +### plural-ontologies-and-relational-meaning + +Behavior exists in relations among host context, evidence, shared referents, or multiple causal ontologies rather than inside one artifact. + +Members: MHC-Restricted Program Tissue, Evidential Rulecraft, The Deictic Commons, Anisomorphic Ruleworlds, The Migrating Treaty + +### emergent-material-computation + +Ordinary local components collectively realize behavior through attractors, transient topology, population dosage, exchange circulation, or traveling waves. + +Members: Idiotype Phase Matter, Anastomotic Execution, Heterokaryotic Semantics, Reciprocal-Exchange Computation, Soliton Tissue + +### generative-phenotype-manufacture + +A behavioral envelope or phenotype, rather than stored code, drives local synthesis, selection, growth, or licensed manufacture of disposable implementations. + +Members: The Tolerance Mold, Affinity Furnace, The Germinating Program, Post-Emit Morphogenesis, Phenotype Manufacturing Right + +### semantic-observability-and-proof + +Developer trust comes from question-relative explanations, independent executable projections, effect contracts, absence proofs, or evolving evidence portfolios. + +Members: Focus-Pull Debugger, Angle-Multiplexed Truth Deck, Semantic Fringe Observatory, The Cue-Sheet Contract Theater, Question-Sightline Observatory, Dual-Reality Debugging, Black-Art Absence Certificates, The Convincer Ecology, The Questioning Scar + +### developmental-product-surfaces + +Product surfaces, semantic deformation, contract geometry, and owner questions become active material that grows or repairs the deployed system. + +Members: Whole-Show Rehearsal Mesh, Auxetic Contract Tissue, Rehearsal Germ Layers, Fringe-Driven Phase Healing, Treaty-Sightline Foundry, Confidence-Driven Contract Metabolism + +### lifecycle-licensing-and-continuity + +Commercial value attaches to qualified supply, descendant repair, semantic fidelity, certified decisions, or continuity of contextual guarantees rather than static bytes. + +Members: Synonymous Supply Network, Lineage Repair Warranty, Fidelity-Gradient License, Proofreading Royalty, Situated Absence Lineage + +## Coverage and verdict + +The run is substantively successful and does not require immediate reseeding: four ideas score below 0.5 recognizability across three tracks, and two fusions appear in the top five. A focused Round 2 is nevertheless recommended because resilience has no top-10 representative, four tracks have no sub-0.5 idea, and several high-ranked mechanisms remain research-level descriptions without a concrete JavaScript representation or proof strategy. Lens and assumption survival below is attributed strictly to original Phase-1 ideas, not to later fusion descendants: on that basis adaptive immune system and holography have no direct top-10 result, while holography alone has no direct top-15 result. Both lenses still influenced top-10 fusions, so this is a direct-generation gap rather than total conceptual absence. The coverage note supplies three untried seeds. + +### Gate checks + +- **Pass: At least 3 ideas with recognizability below 0.5 across at least 3 distinct tracks.** Four ideas qualify: Traveling Isogloss (0.30, self-modifying or living output), Evidential Phase Succession (0.42, self-modifying or living output), Anisomorphic Ruleworlds (0.46, semantic-level protection), and The Questioning Scar (0.38, verifiability and developer experience). They span three distinct tracks. +- **Pass: At least 1 fusion in the top 5.** Evidential Phase Succession is a fusion at rank 2 and The Questioning Scar is a fusion at rank 5. +- **Pass: Every top-10 idea carries a whyNotGen1 line.** All 10 top-ranked entries contain a non-empty whyNotGen1 field. +- **Pass: A coverage note names Round-2 seeds.** This coverage report names exactly three untried lens × assumption × track × persona combinations. + +### Thin tracks + +- **resilience to automated understanding:** Thin by direct generation count, top-rank representation, and recognizability: it has 4 direct ideas, no fusion assigned to the track, no top-10 entry, a first appearance at rank 11, and no idea below 0.5 recognizability; its best score is 0.62. +- **new product surfaces and capabilities:** Thin by direct generation count, direct top-rank representation, recognizability, and implementation specificity: it has 4 direct ideas plus 2 fusions, but its top-15 presence comes only from fusions at ranks 7 and 12; the best direct idea is rank 24 at 0.80 recognizability, no idea is below 0.5, and the proposed consoles and foundries do not yet name a narrow integration or API-level spike. +- **semantic-level protection:** Thin only by direct generation count: it has 4 direct ideas plus 1 fusion. Outcome quality is strong rather than thin, with three top-10 entries and Anisomorphic Ruleworlds at 0.46 recognizability. +- **novel transformation paradigms:** Thin by recognizability and implementation specificity: 5 direct ideas produced one top-10 entry, but none score below 0.5 recognizability and all remain research spikes. The sketches name nonlinear media, growth, or exchange systems without yet defining a compact JavaScript semantic lowering, execution kernel, or equivalence proof. +- **distribution, licensing, and delivery models:** Thin by recognizability and implementation specificity despite a strong rank-4 representative: 5 direct ideas plus 1 fusion yield no idea below 0.5 recognizability, the track average is 0.79, and every candidate is a product pivot without a bounded authorization, installation, renewal, and continuity protocol. +- **self-modifying or living output:** Thin only by implementation specificity: it dominates the top 15 and supplies two sub-0.5 ideas, but its leading mechanisms are research spikes whose build-runtime equivalence rail fails and whose moving-boundary or succession machinery is not yet reduced to an executable prototype. + +### Unsampled regions + +- Observation-conditioned semantics: no generator directly inverted the assumption that behavior is unchanged by observation or used the quantum measurement / observer-effect lens. +- Functionless and non-enumerable program structure: no generator directly inverted the stable-function-unit assumption, leaving room for representations whose units emerge only during composition. +- Active artifact protocols: no generator directly inverted the passive-text assumption or the assumption that Ruam does not participate at run time, so continuing product and delivery relationships were reached only indirectly. +- Local-to-global projection and deliberate distortion: the local-fragment assumption and cartographic-projection lens were both unused, leaving a promising developer-facing region between faithful explanation and non-canonical representation. +- Absence-of-structure and plural outcomes: neither the no-structure inversion nor the one-input/one-output inversion received a direct assignment. + +## Recommended Round 2 seeds + +### 1. quantum measurement / observer effect + +- **Overturn:** the program behaves the same whether observed or not +- **Track:** resilience to automated understanding +- **Persona:** complexity/systems theorist +- **Rationale:** Resilience is the only track absent from the top 10 and has no sub-0.5 idea. This combination directly samples the missing observation-conditioned region and can seek a developer-owned measurement contract whose questions help constitute valid execution, creating a new organizing principle rather than another representation layer. + +### 2. cartographic projection (every map distorts something) + +- **Overturn:** a local fragment is understandable locally +- **Track:** new product surfaces and capabilities +- **Persona:** industrial designer +- **Rationale:** This track has no direct top-15 idea and no sub-0.5 result. Treating every developer view as a purpose-built projection can produce a concrete product surface for choosing, comparing, and validating useful semantic distortions while making the global contract—not a local fragment—the unit of explanation. + +### 3. legal contracts and escrow + +- **Overturn:** the artifact is a passive text to be read +- **Track:** distribution / licensing / delivery models +- **Persona:** game designer +- **Rationale:** Distribution has a strong headline idea but no sub-0.5 candidate and lacks a bounded lifecycle protocol. Combining executable obligations, staged settlement, and stateful rule transitions can make authorization and continuity an active multi-party process with explicit events and invariants, improving both originality and implementation specificity without merely relocating a key. + +## Interpretation + +The campaign’s clearest result is a category shift: Ruam Gen2 looks less like another concealment layer and more like a semantic-lifecycle system. The top directions protect a developer-owned behavioral treaty while allowing causal realizations, grammars, explanations, or licensed specimens to be plural and history-bearing. This is a research direction, not an immediate implementation commitment: all top-five entries are `research-spike` or `product-pivot` maturity. + +Source track labels retain generator capitalization and slash/wording variants in the structured artifact for auditability. This report presents them unchanged where attached to individual ideas. From e8cecb56ba89d47512a16e325d81cf46f64b2ecb Mon Sep 17 00:00:00 2001 From: owengregson Date: Fri, 24 Jul 2026 13:19:54 -1000 Subject: [PATCH 03/54] docs: plan full traveling isogloss replacement --- ...-traveling-isogloss-implementation-plan.md | 1529 +++++++++++++++++ 1 file changed, 1529 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-24-traveling-isogloss-implementation-plan.md diff --git a/docs/superpowers/specs/2026-07-24-traveling-isogloss-implementation-plan.md b/docs/superpowers/specs/2026-07-24-traveling-isogloss-implementation-plan.md new file mode 100644 index 0000000..cfdf164 --- /dev/null +++ b/docs/superpowers/specs/2026-07-24-traveling-isogloss-implementation-plan.md @@ -0,0 +1,1529 @@ +# Traveling Isogloss — Rank 1 Implementation Plan + +**Date:** 2026-07-24 +**Status:** Implementation-ready replacement plan +**Source:** Rank 1 in [Project Kaleidoscope — Ruam Gen-2 Ideation Results](2026-07-24-ruam-gen2-ideation-results.md) +**Decision:** Replace Ruam's VM bytecode execution model with Traveling Isogloss. The completed product has one execution engine—the Boundary Constraint Machine—and contains no legacy VM runtime, VM fallback, bytecode encoder, or backend selector. + +## 1. Outcome + +Traveling Isogloss will replace the current compiler-to-bytecode-to-VM path. A selected root function and all of its child units will compile into a **Boundary Constraint Machine (BCM)**: + +- The emitted artifact contains two populations of locally ambiguous semantic cells, reusable operand reservoirs, a neighborhood graph, and one mutable carrier state per root group. +- No emitted function owns a fixed instruction stream. +- A semantic operation becomes uniquely selectable only when the current left cell, current right cell, and the carrier's history-derived witness meet at the active boundary. +- Executing that operation changes cell phase and moves the carrier to a new boundary. +- Completing, throwing from, yielding from, or suspending a call closes a lineage segment and leaves the group at a different contract gate for the next invocation. +- The state lives in the emitted runtime closure. It is server-free, CSP-safe, and persistent across calls for the lifetime of that loaded artifact. +- A developer-only sidecar can map opaque boundary motion back to source spans without shipping that map in the protected artifact. + +Implementation occurs on a replacement branch. The old VM may be invoked temporarily as a differential test oracle while the BCM is incomplete, but it is not a product backend and is deleted at cutover. No release described by this plan offers a VM/Isogloss choice or silently falls back to VM execution. + +This is a research execution model and full runtime replacement, not a claim that program behavior becomes unrecoverable. Its defensible security claim is narrower: + +> The emitted artifact has no stable site-to-operation function body. Recovering useful meaning requires replaying a history-dependent carrier through input-dependent boundary states, producing an execution-specific lineage rather than extracting one canonical instruction stream. + +## 2. Locked semantic decisions + +These decisions remove ambiguity before implementation begins. + +### 2.1 What a dialect cell is + +A cell is a reusable constraint bundle over the runtime handler catalog. It does not store an opcode, handler index, operand, or source location. + +Each cell contains phase-selectable clauses. A clause describes: + +- candidate semantic signature classes; +- permitted stack-shape transitions; +- permitted effect classes; +- permitted control exits; +- synthetic per-build dimensions derived from an isolated PRNG stream; +- references to reusable operand reservoirs; +- neighboring cells that may become boundary partners after an exit. + +Every clause must be locally ambiguous. Under the default profile, it must match at least eight handlers. + +### 2.2 What the boundary is + +The boundary is the ordered triple: + +```text +left cell clause + right cell clause + carrier witness +``` + +The left clause alone is ambiguous. The right clause alone is ambiguous. Their static intersection must remain ambiguous. Only the addition of the current carrier witness may select one handler and one operand projection. + +The carrier witness is derived from prior boundary motion, the current cell phases, the current frame shape, and a per-group lineage accumulator. It is not a stored opcode token. + +### 2.3 What moves + +The active boundary moves after every semantic transition, not merely after a public call: + +1. Resolve the unique handler and operand projection at the active boundary. +2. Execute the handler. +3. Classify the result as an exit such as fallthrough, branch-true, branch-false, call, return, throw, await, or yield. +4. Apply the exit-specific local refold. +5. Flip or rotate the crossed cell phase. +6. Move the carrier to the selected neighboring boundary. +7. Update the lineage accumulator and epoch. + +Call completion is a stronger transition: it closes the current lineage segment and lands the carrier at a contract gate from which the next invocation target and inputs can route it to an entry anchor. + +### 2.4 State scope + +There is exactly one active carrier per **root group**: + +- A root group contains one selected root function and every child bytecode unit produced from nested functions or class members. +- An escaped child closure remains attached to its originating root group. +- Different selected root functions receive independent lattices and independent carriers. +- There is no file-global carrier because it would create unnecessary coupling across unrelated APIs. +- There is no per-unit carrier because that would turn the design into independently moving instruction streams and weaken the core concept. + +### 2.5 Calls, recursion, reentrancy, async, and generators + +JavaScript remains single-threaded at each semantic step, so one carrier can serve nested and suspended work without cloning: + +- A nested or reentrant call pushes a carrier resume gate, routes the same carrier to the callee's contract entry, and returns through a generated continuation corridor. +- Recursion uses the same mechanism. No persistent state is stored in the current VM's hoisted sync-handler slots. +- `await` and `yield` park the machine frame and a continuation gate, then release the carrier. +- A later resume reacquires the group's current carrier and routes it to that continuation gate before executing the next semantic transition. +- Multiple async calls may be suspended simultaneously, but only one carrier is active during any JavaScript turn. Suspended frames are not additional boundaries. +- Resume order follows the host's existing Promise/generator scheduling. Ruam must not add a new queue that changes observable ordering. + +### 2.6 Failure and exception state + +Boundary motion is not transactional and is never rolled back: + +- A handled throw selects an exception exit and continues through the lattice. +- An uncaught throw selects a terminal throw exit, moves the carrier to a contract gate, then rethrows the original value. +- A rejected async call does the same at rejection. +- User-visible side effects and representational evolution therefore advance together. + +This avoids impossible rollback promises around arbitrary JavaScript side effects and makes exceptional use part of the artifact's history. + +### 2.7 Persistence boundary + +Version 1 persistence is in-memory for the lifetime of the loaded artifact: + +- Repeated calls in the same page, worker, or Node process see the evolved lattice. +- Reloading or restarting creates a fresh state from the emitted initial carrier. +- Cross-reload persistence, local storage, remote state, and license-bound state providers are explicitly out of scope for the first release. + +### 2.8 Correctness proof boundary + +Ruam will not claim a formal proof of arbitrary JavaScript behavior. It will prove a narrower and testable property: + +1. The semantic compiler produces canonical semantic IR. +2. For every reachable canonical IR node and exit edge, the build-time BCM verifier proves that the corresponding boundary state resolves exactly the intended handler and operand projection. +3. The verifier proves that the chosen exit refold reaches the boundary family corresponding to the intended successor IR node. +4. Handler differential tests against native JavaScript remain the behavioral authority for each semantic handler. +5. End-to-end differential tests compare native JavaScript, the TypeScript BCM reference runtime, and the emitted BCM runtime. + +During implementation only, the old VM may provide an additional mismatch signal. It is not part of the proof boundary, release test matrix, or shipped product. + +The build result includes verifier statistics and a certificate digest. The certificate itself remains a build artifact or owner sidecar; it is not required by the production runtime unless `runtimeChecks` is explicitly enabled. + +## 3. Non-negotiable invariants + +An implementation is not Traveling Isogloss unless all of these hold. + +| ID | Invariant | Automated enforcement | +|---|---|---| +| TI-01 | No emitted per-function `Instruction[]` or equivalent linear operation stream exists. | Artifact schema test and adversarial extractor | +| TI-02 | Every individual cell clause matches at least `minCellAnonymity` handlers. | Build verifier | +| TI-03 | The static intersection of the active left and right clauses still matches at least two handlers. | Build verifier | +| TI-04 | Left + right + current carrier witness resolves exactly one handler and operand projection for every reachable state. | Build verifier and property tests | +| TI-05 | A non-boundary neighboring pair never resolves exactly one handler under the current witness. | Build verifier | +| TI-06 | Every semantic transition changes carrier edge, cell phase, witness, lineage, or more than one of them. | Build verifier | +| TI-07 | Every canonical CFG edge has a corresponding verified refold edge, including exception and finally edges. | CFG bisimulation check | +| TI-08 | One root group has one live carrier, including during recursion, reentrancy, generator suspension, and async suspension. | Runtime assertions in tests | +| TI-09 | Runtime state is root-group-scoped and stored only in the BCM runtime closure. | Architecture test and code review | +| TI-10 | Production output uses no `eval`, `new Function`, `debugger`, dynamic script construction, or network dependency. | CSP/security tests | +| TI-11 | All generated identifiers use `NameRegistry`; all random streams use `deriveSeed()`. | Naming tests and code review | +| TI-12 | An owner trace map is never embedded in production code. | Output inspection test | +| TI-13 | Removed VM-specific options fail with an actionable migration error; none are silently accepted or ignored. | Removed-option tests | +| TI-14 | The cutover tree contains no VM interpreter, VM loader, bytecode encoder, physical opcode shuffle, backend selector, or VM fallback path. | Source inventory and package-surface tests | + +## 4. Explicit non-goals + +- Do not market the design as mathematically irreversible. +- Do not encrypt source semantics and call the ciphertext a dialect. +- Do not put two complementary opcode shares in adjacent cells. +- Do not emit a hidden opcode stream and merely move a decoder over it. +- Do not retain the old VM as a fallback, alternate preset, compatibility mode, debug engine, or hidden recovery path. +- Do not mutate generated JavaScript source text at runtime. +- Do not use self-modifying native code, WebAssembly, workers, timers, storage, or a server to make the mechanism work. +- Do not reimplement the JavaScript semantic catalog from scratch; extract the language-level handler logic from `ruamvm` before deleting the VM scaffold. +- Do not add performance hardening until the reference model and verifier are correct. +- Do not combine the first implementation with Evidential Phase Succession, Anisomorphic Ruleworlds, licensing, remote policy, or cross-installation state. + +## 5. Current architecture and replacement seams + +The legacy pipeline being removed is: + +```mermaid +flowchart LR + A["JavaScript source"] --> B["Babel parse and target selection"] + B --> C["compileFunction"] + C --> D["logical opcode units"] + D --> E["VM transforms"] + E --> F["bytecode encoder"] + F --> G["VM runtime assembler"] + G --> H["dispatch stubs and final output"] +``` + +The replacement pipeline is: + +```mermaid +flowchart LR + A["JavaScript source"] --> B["Parse, selection, grouping"] + B --> C["Canonical Semantic IR"] + C --> D["Traveling Isogloss lowering"] + D --> E["Constraint lattice"] + E --> F["Build verifier and certificate"] + F --> G["BCM artifact and runtime"] + G --> H["Protected JavaScript plus optional owner sidecar"] +``` + +There is no execution-backend branch in the target architecture. + +The replacement seams in the current code are: + +| Current location | Existing responsibility | Replacement action | +|---|---|---| +| `src/transform.ts` | Owns every pipeline phase and both shared/shielded VM branches | Rewrite as the single semantic-IR → Isogloss orchestration path | +| `src/compiler/index.ts` | Produces logical opcodes and applies VM optimizer assumptions | Return canonical semantic units; remove physical VM lowering | +| `src/compiler/emitter.ts` | Emits opcode and operand pairs | Become a semantic-IR emitter with build-only source origins and stable node IDs | +| `src/compiler/basic-blocks.ts` | Recovers basic blocks after compilation | Replace with a canonical CFG carrying typed exits | +| `src/compiler/optimizer.ts` | Produces VM superinstructions | Delete after any engine-independent optimizations are moved to semantic IR | +| `src/ruamvm/handlers/*` | Defines JavaScript semantic behavior as AST builders | Move language-level handlers into `src/runtime/handlers/` and remove opcode-table coupling | +| `src/ruamvm/builders/interpreter.ts` | Couples handler construction to VM dispatch | Extract the semantic handler catalog, then delete the VM scaffold | +| `src/ruamvm/builders/loader.ts` | Decodes and caches bytecode units | Replace with the BCM group loader and carrier-state store | +| `src/ruamvm/builders/runners.ts` | Routes unit IDs to VM execution | Replace with contract-token runners | +| `src/ruamvm/assembler.ts` | Assembles the VM runtime | Replace with the BCM runtime assembler; delete the VM assembler at cutover | +| `src/types.ts` | Mixes public options and VM bytecode internals | Replace with Ruam/Isogloss public types; delete VM bytecode types | +| `src/presets.ts` | Resolves VM hardening booleans | Redefine every preset in Isogloss terms | +| `src/option-meta.ts` | Describes VM-oriented booleans | Replace with typed Isogloss, artifact, and common-source options | +| `src/naming/*` | Central identifier system | Add Isogloss scopes and remove VM-only claims after cutover | +| `src/structural-choices.ts` | Derives VM runtime variation | Replace with lattice/runtime structural choices using isolated streams | +| `test/helpers.ts` | Native-versus-VM equivalence | Make native-versus-reference-BCM-versus-emitted-BCM the permanent oracle set | + +## 6. Public API and configuration + +### 6.1 Public type redesign + +Replace `VmObfuscationOptions` rather than retaining it as an alias: + +```ts +export type IsoglossProfile = "research" | "balanced" | "hardened"; + +export interface TravelingIsoglossOptions { + profile?: IsoglossProfile; + minCellAnonymity?: number; + expansion?: 2 | 3 | 4; + runtimeChecks?: boolean; + ownerTrace?: "off" | "sidecar" | "sidecar+runtime"; +} + +export interface RuamOptions { + isogloss?: TravelingIsoglossOptions; + preset?: PresetName; + targetMode?: "root" | "comment"; + threshold?: number; + preprocessIdentifiers?: boolean; + target?: TargetEnvironment; + // New engine-independent artifact protections only. +} +``` + +Delete `ExecutionModel`, `ResolvedVmOptions`, and `VmObfuscationOptions` from the public surface. Publish a migration guide rather than a compatibility alias. + +Defaults: + +```ts +isogloss.profile = "research" +isogloss.minCellAnonymity = 8 +isogloss.expansion = 2 +isogloss.runtimeChecks = false +isogloss.ownerTrace = "off" +``` + +Validation: + +- `minCellAnonymity` must be an integer from 4 through 64. +- `expansion` must be 2, 3, or 4. +- `ownerTrace: "sidecar+runtime"` is never enabled by a preset. +- Unknown nested Isogloss properties fail fast. +- Removed VM options fail with `RUAM_REMOVED_VM_OPTION` and a migration hint. + +### 6.2 Detailed build API + +The primary API returns verifier data and an optional owner sidecar: + +```ts +export interface ProtectionBuildResult { + code: string; + diagnostics: BuildDiagnostic[]; + stats: { + engine: "traveling-isogloss"; + rootGroupCount: number; + unitCount: number; + originalBytes: number; + outputBytes: number; + expansionRatio: number; + }; + ownerTrace?: IsoglossOwnerSidecar; +} + +export function protectCode( + source: string, + options?: RuamOptions +): ProtectionBuildResult; + +export function obfuscateCode( + source: string, + options?: RuamOptions +): string { + return protectCode(source, options).code; +} +``` + +Public-surface rules: + +- `protectCode()` is the primary API. +- `obfuscateCode()` may remain as a string-returning convenience name because it does not imply VM execution. +- Add `protectFile()` and `runProtection()`. +- Delete `runVmObfuscation()` and `VmObfuscationOptions` in the replacement major version. +- Remove the `ruamvm` CLI binary alias; ship `ruam` only. +- Rename package description, keywords, documentation, and generated messages away from VM and bytecode terminology. Package-registry renaming is a separate release decision, but no runtime compatibility depends on the old package name. + +### 6.3 CLI + +Add: + +```text +--isogloss-profile +--isogloss-min-anonymity <4..64> +--isogloss-expansion <2|3|4> +--isogloss-runtime-checks +--owner-trace +--runtime-owner-trace +``` + +Do not add `--execution-model`; there is only one engine. Refactor `src/cli.ts` to parse flags from generalized option metadata. Hand-written parsing remains only for input/output paths, help, version, and interactive mode. + +`--owner-trace` writes the sidecar returned by `protectCode()` and implies `ownerTrace: "sidecar"`. `--runtime-owner-trace` upgrades it to `"sidecar+runtime"`. + +Removed VM flags produce a concise error with the replacement concept where one exists; they never activate retained legacy code. + +### 6.4 Presets + +Replace the current VM preset contents with Isogloss-native definitions: + +```ts +interface PresetDefinition { + common: Partial; + isogloss: Required; + artifact: Partial; +} +``` + +| Preset | Isogloss expansion | Minimum anonymity | Runtime checks | Production intent | +|---|---:|---:|---|---| +| `low` | 2 | 8 | off | Smallest evaluable lattice | +| `medium` | 3 | 12 | off | Balanced lattice and artifact protection | +| `max` | 4 | 16 | on | Maximum verified lattice pressure | + +No preset contains VM options, no resolver has a VM branch, and no profile can select the removed engine. + +## 7. Internal type model + +### 7.1 Canonical semantic IR + +Move VM-internal data types out of public `src/types.ts` into `src/compiler/types.ts`. + +Create `src/compiler/ir.ts`: + +```ts +export type SemanticNodeId = number; + +export interface SourceOrigin { + file?: string; + start: number; + end: number; + line: number; + column: number; +} + +export interface SemanticInstruction { + id: SemanticNodeId; + op: SemanticOp; + operand: number; + originId: number; +} + +export type SemanticExit = + | { kind: "fallthrough"; target: SemanticNodeId } + | { kind: "branch-true"; target: SemanticNodeId } + | { kind: "branch-false"; target: SemanticNodeId } + | { kind: "exception"; target: SemanticNodeId } + | { kind: "finally"; target: SemanticNodeId } + | { kind: "return" } + | { kind: "throw" } + | { kind: "yield"; resume: SemanticNodeId } + | { kind: "await"; resume: SemanticNodeId }; + +export interface SemanticUnit { + id: string; + rootGroupId: string; + constants: ConstantPoolEntry[]; + nodes: SemanticInstruction[]; + exits: Map; + entryNode: SemanticNodeId; + origins: SourceOrigin[]; + // Existing function metadata follows. +} +``` + +The canonical IR contains semantic operations before lattice lowering. During migration, rename the language-level members of `compiler/opcodes.ts` into `SemanticOp` and delete physical opcode concerns rather than preserving a VM-flavored IR. + +The following legacy stages have no target equivalent and are removed at cutover: + +- opcode shuffle and physical opcode maps; +- VM superinstruction fusion; +- block permutation as instruction-address rewriting; +- opcode mutation; +- rolling and incremental instruction ciphers; +- VM bytecode serialization. + +Any optimization that remains useful must operate on canonical semantic IR or on the lattice topology and must preserve the verifier's source-node correspondence. + +### 7.2 Semantic signature catalog + +Create `src/compiler/semantic-signatures.ts` with one descriptor for every semantic operation: + +```ts +export interface SemanticSignature { + op: SemanticOp; + operandKind: + | "none" + | "constant" + | "register" + | "scope-name" + | "argc" + | "jump" + | "packed" + | "unit-ref"; + stackInput: StackArity; + stackOutput: StackArity; + effect: + | "pure" + | "local" + | "scope" + | "object" + | "call" + | "control" + | "exception" + | "async"; + control: + | "fallthrough" + | "conditional" + | "jump" + | "call" + | "return" + | "throw" + | "yield" + | "await"; + mayThrow: boolean; + readsThis: boolean; + readsScope: boolean; + syntheticDimensions: number; +} +``` + +Dynamic stack effects such as calls use a pure function of the operand. Every descriptor must be exhaustive through a `satisfies Record` check. A missing semantic operation must fail TypeScript compilation. + +### 7.3 Root groups + +Create `src/pipeline/groups.ts`: + +```ts +export interface RootGroup { + id: string; + rootPath: NodePath; + units: SemanticUnit[]; + entryContracts: EntryContract[]; + usedSemantics: Set; + hasAsync: boolean; + hasGenerator: boolean; +} +``` + +Group construction replaces both the normal shared-VM layout and the VM-shielding special case. Every selected root receives one lattice group; there is no shielding mode after cutover. + +### 7.4 Lattice model + +Create `src/isogloss/types.ts`: + +```ts +export type CellId = number; +export type ClauseId = number; +export type ReservoirId = number; +export type ContractId = number; + +export interface CandidateMask { + words: Uint32Array; + cardinality: number; +} + +export interface DialectClause { + id: ClauseId; + phase: number; + handlerCandidates: CandidateMask; + operandFamilies: CandidateMask; + stackShapeMask: number; + effectMask: number; + controlMask: number; + syntheticMask: Uint32Array; + neighborRefs: Uint32Array; +} + +export interface IsoglossCell { + id: CellId; + dialect: 0 | 1; + clauses: DialectClause[]; +} + +export interface OperandReservoir { + id: ReservoirId; + values: Int32Array; + projections: Uint32Array; +} + +export interface CarrierSeed { + left: CellId; + right: CellId; + leftPhase: number; + rightPhase: number; + witness: number; + epoch: number; + lineage: number; +} + +export interface EntryContract { + id: ContractId; + unitId: string; + gate: CellId; + anchorFamily: Uint32Array; +} + +export interface IsoglossGroup { + id: string; + cells: IsoglossCell[]; + reservoirs: OperandReservoir[]; + contracts: EntryContract[]; + initialCarrier: CarrierSeed; + flags: number; +} +``` + +The actual encoded runtime shape uses short randomized property names or array positions. These descriptive names exist only in TypeScript. + +### 7.5 Build certificate + +Create `src/isogloss/certificate.ts`: + +```ts +export interface IsoglossCertificate { + schemaVersion: 1; + groupId: string; + reachableStateCount: number; + verifiedTransitionCount: number; + minObservedCellAnonymity: number; + minObservedPairAmbiguity: number; + nonBoundaryUniqueResolutionCount: 0; + cfgMismatchCount: 0; + digest: string; +} +``` + +The digest detects accidental mismatch between the verified lattice and the encoded lattice during the build. It is not presented as cryptographic attestation. + +## 8. Compilation algorithm + +### 8.1 Capture source origin without changing every visitor + +Extend `Emitter` with an origin stack: + +```ts +emitter.withOrigin(node, () => { + // Existing visitor body. +}); +``` + +`emit()` copies the current origin ID onto the instruction. Wrap visitor entry points in `visitors/expressions.ts`, `visitors/statements.ts`, and `visitors/classes.ts`. Optimizations that combine nodes retain the ordered set of contributing origin IDs. + +This data is build-only and is removed unless an owner sidecar is requested. + +### 8.2 Build the canonical CFG + +Extend `compiler/basic-blocks.ts` or add `compiler/cfg.ts` to produce typed exits: + +1. Mark entry, jump targets, post-transfer positions, exception entries, finally entries, and jump-table targets as leaders. +2. Split canonical instructions into blocks. +3. Convert packed jump operands into explicit typed edges without mutating the original operand. +4. Add exceptional edges for every instruction covered by an exception range and marked `mayThrow`. +5. Add return, throw, await, and yield terminal/resume edges. +6. Validate every target exists and every nonterminal block has at least one exit. +7. Retain a map from block/node IDs back to source origins. + +### 8.3 Produce boundary families + +Create `src/isogloss/lower.ts`. For each semantic node: + +1. Read its semantic signature. +2. Allocate `expansion` boundary variants. A variant is a distinct pair of reusable cell clauses and witness class that realizes the same semantic node. +3. Select left and right candidate sets that each include the intended handler and at least `minCellAnonymity - 1` decoys. +4. Require the static left/right intersection to contain at least two handlers. +5. Choose a carrier witness predicate that reduces the intersection to the intended handler. +6. Allocate an operand family that includes the intended projection plus decoys and reused values. +7. Connect each typed CFG exit to an eligible successor boundary variant. +8. Generate local phase changes so the next visit to the same semantic node prefers a different variant. +9. Reuse each nonterminal cell across at least two semantic nodes or two exit paths. A dedicated cell per instruction is forbidden. +10. Generate contract gates and non-semantic routing corridors for root entry, escaped child entry, reentrant entry, continuation resume, return, and uncaught throw. + +All random choices use streams such as: + +```ts +deriveSeed(fileSeed, `isogloss/group/${groupId}/cells`) +deriveSeed(fileSeed, `isogloss/group/${groupId}/clauses`) +deriveSeed(fileSeed, `isogloss/group/${groupId}/operands`) +deriveSeed(fileSeed, `isogloss/group/${groupId}/topology`) +deriveSeed(fileSeed, `isogloss/group/${groupId}/phases`) +``` + +No new ad hoc PRNG or XOR-derived stream is permitted. + +### 8.4 Constraint generation strategy + +Create `src/isogloss/constraints.ts`. + +Use a deterministic bounded search: + +1. Start from the handler set compatible with the target's broad stack, effect, control, and operand categories. +2. If the compatible set is too small, add synthetic dimensions and handler aliases that preserve semantics but change clause membership. +3. Sample candidate left and right supersets. +4. Reject a pair if either side is below the anonymity minimum. +5. Reject a pair if its static intersection has fewer than two members. +6. Generate a witness mask from the carrier predecessor family. +7. Reject if the three-way intersection is not exactly the target handler. +8. Reject if the pair uniquely resolves under any witness reachable at a non-boundary neighbor. +9. Stop after a fixed attempt budget. +10. On exhaustion, emit `RUAM_ISOGLOSS_CONSTRAINT_UNSAT` with the unit, semantic node, signature, minimum anonymity, and attempts. Never weaken the requested anonymity silently. + +Rare handlers that cannot meet the requested anonymity receive generated semantic aliases from the existing handler-aliasing machinery. An alias is eligible only if differential tests prove it equivalent and its use does not expose a direct node-to-handler mapping. + +### 8.5 Operand projection + +Create `src/isogloss/operands.ts`. + +Operands must not be stored beside their semantic node. Instead: + +- Constants, register indices, scope-name indices, argument counts, unit references, and jump metadata are placed in typed reservoirs shared across the root group. +- A left clause selects a projection family. +- A right clause selects a transformation family. +- The carrier witness selects a slot within their intersection. +- The resolver reconstructs the effective operand into a local variable immediately before handler execution. +- At least half of reservoir entries must be referenced by more than one boundary family. +- Unused decoy reservoir entries are permitted only when they are structurally indistinguishable from used entries. + +Control-flow targets are never reconstructed as instruction pointers. The handler returns an exit class; topology chooses the successor boundary. + +### 8.6 Verify before encoding + +Create `src/isogloss/verify.ts`. + +The verifier performs a graph exploration from every entry contract and continuation contract: + +```text +state = carrier edge + cell phases + witness class + semantic frame class +``` + +For each reachable state: + +1. Compute left candidates. +2. Compute right candidates. +3. Assert local and pair ambiguity thresholds. +4. Apply the witness and assert one resolution. +5. Compare the handler and operand projection with the canonical semantic node. +6. Enumerate every legal exit of the canonical node. +7. Apply the corresponding refold. +8. Assert the successor boundary realizes the canonical successor node. +9. Assert the carrier changed. +10. Add the successor state to the worklist. + +Loops require state abstraction so verification terminates. Phase and witness domains are finite by construction; epoch and lineage are reduced to the finite bits actually consumed by clauses. The verifier must reject a design that reads unbounded epoch or lineage values for semantic selection. + +## 9. Runtime algorithm + +### 9.1 Runtime components + +Add `src/isogloss/runtime/`: + +| File | Responsibility | +|---|---| +| `assembler.ts` | Dependency-tiered runtime factory and final result | +| `deserializer.ts` | Decode the BCM binary envelope into typed arrays | +| `loader.ts` | Cache decoded groups and initialize one carrier per group | +| `resolver.ts` | Compute the three-way boundary intersection | +| `operands.ts` | Reconstruct one effective operand from reservoirs | +| `carrier.ts` | Apply phase changes, refolds, contract routing, epoch, and lineage | +| `interpreter.ts` | Execute the shared handler catalog under BCM control | +| `runners.ts` | Dispatch opaque contract tokens, box `this`, and route sync/async/generator calls | +| `continuations.ts` | Park and resume await/yield/reentrant frames | +| `trace.ts` | Optional opaque runtime owner events | +| `checks.ts` | Optional local invariant checks for research/max builds | + +Every emitted runtime fragment is built with the existing typed AST node system. Do not use template strings containing JavaScript. + +### 9.2 Carrier state + +The deserialized group cache owns: + +```ts +interface RuntimeCarrierState { + left: number; + right: number; + leftPhase: number; + rightPhase: number; + witness: number; + epoch: number; + lineage: number; + activeDepth: number; + frameStack: RuntimeFrame[]; + parked: Map; +} +``` + +In emitted code, use compact arrays and NameRegistry-generated identifiers. The descriptive object form is for the reference runtime only. + +### 9.3 Dispatch loop + +The emitted interpreter loop is: + +```text +route carrier to requested contract gate +create or resume frame +while frame is runnable: + read active left/right clauses + resolve one handler candidate using carrier witness + reconstruct one operand + execute shared semantic handler + classify exit + update carrier and cell phases + emit optional opaque trace event + route to successor, callee, continuation, or terminal contract +return, throw, yield, or await with native-equivalent values and scheduling +``` + +The handler catalog may be stable within a build; the prohibited mapping is a stable program-site-to-handler stream. Handlers describe the JavaScript language, while the moving boundary describes this program's behavior. + +### 9.4 Semantic handler extraction + +Do not duplicate handler bodies and do not retain a VM dispatch scaffold. + +1. Move `src/ruamvm/handlers/*` to `src/runtime/handlers/*`. +2. Rename opcode-facing registry types to semantic-operation-facing types. +3. Extract generic stack, register, scope, exception, call, `this`, and completion helpers into `src/runtime/handler-context.ts`. +4. Make `buildHandlerCatalog()` return the catalog consumed directly by the BCM resolver. +5. Remove decoded-opcode arguments and physical opcode lookup from handler construction. +6. Delete `buildVmDispatchScaffold()`, the VM handler table metadata, and VM interpreter builders after the BCM runtime passes the corresponding semantic suites. + +The source move is performed before final cutover so the reusable JavaScript language implementation survives deletion of `src/ruamvm/`. + +### 9.5 Contract dispatch stubs + +Replace `replaceFunctionBody()` with: + +```ts +replaceFunctionWithDispatchStub( + path, + contractToken, + dispatchBinding, + stubOptions +) +``` + +The stub shape remains natural: + +- rest parameters; +- direct lexical dispatcher call; +- regular functions forward `this`; +- arrows do not; +- constructors, `new.target`, home object, outer scope, and generator/async shape match the current behavior. + +The token identifies an entry contract, not a unit body. It may be different per build and is generated through the root-group stream. + +This avoids publishing runtime internals on `globalThis`. If a target shape requires global exposure, keep a target-specific **BCM** adapter and document why. Validate top-level `this`, script globals, ESM, CJS, and browser-extension behavior before cutover. + +### 9.6 Runtime factory assembly + +Assemble the generated dispatcher as a lexical binding when target semantics allow it: + +```js +var = (function () { + // Runtime and artifact state. + return ; +})(); +``` + +This avoids publishing BCM internals on `globalThis`. If a target shape requires global exposure, keep a target-specific BCM adapter and document why. Validate top-level `this`, script globals, ESM, CJS, and browser-extension behavior before cutover. + +## 10. Binary format + +### 10.1 Envelope + +Create `src/artifact/format.ts` and `src/artifact/encode.ts`. + +Use a single-engine versioned envelope: + +```text +magic 4 bytes "RUAM" +version u8 1 +flags u16 +groupCount u32 +payloads length-prefixed Isogloss groups +``` + +There is no backend discriminator and no legacy bytecode payload variant. The generated code and BCM runtime are produced together, and round-trip format tests cover only the Isogloss schema. + +### 10.2 BCM payload + +Each group payload contains: + +1. group metadata and flags; +2. unit-to-contract token table; +3. constant pools; +4. cell clause table; +5. candidate mask word table with deduplication; +6. operand reservoirs; +7. neighborhood/refold table; +8. initial carrier seed; +9. async/generator continuation metadata; +10. optional runtime-check digest. + +Use `Uint8Array`, `Uint16Array`, `Uint32Array`, and `Int32Array` views after deserialization. Avoid nested runtime objects in the hot path. + +### 10.3 No source or owner data in payload + +The following are build-only: + +- source spans; +- original function names beyond what JavaScript semantics require; +- canonical semantic node IDs; +- expected handler IDs; +- verifier witness paths; +- sidecar event descriptions. + +An output-inspection test must deserialize every production payload and assert these fields are absent. + +## 11. Owner view + +### 11.1 Sidecar + +Create `src/owner-view/types.ts` and `src/owner-view/build.ts`. + +The sidecar schema contains: + +```ts +interface IsoglossOwnerSidecar { + schemaVersion: 1; + buildId: string; + groups: Array<{ + opaqueGroupId: string; + contracts: Array<{ + opaqueContractId: number; + sourceOrigin?: SourceOrigin; + displayName?: string; + }>; + events: Array<{ + opaqueEventId: number; + sourceOrigins: SourceOrigin[]; + semanticSummary: string; + }>; + certificate: IsoglossCertificate; + }>; +} +``` + +`semanticSummary` is owner-facing text such as “read scoped binding” or “conditional exit,” not an emitted opcode number. + +### 11.2 Runtime event sink + +Only when `ownerTrace: "sidecar+runtime"`: + +- The runtime looks up a documented trace sink once during initialization. +- The sink receives opaque IDs, epoch, from/to cell IDs, and exit class. +- The production artifact contains no source descriptions. +- Missing sinks are a no-op. +- Sink exceptions are caught and ignored so tracing cannot change protected program behavior. +- Runtime trace mode is excluded from performance and security claims. + +Add `decodeIsoglossTrace(sidecar, events)` to convert opaque events into a replayable owner timeline. + +## 12. Legacy option removal and reinterpretation + +The replacement major version does not carry VM switches forward under misleading names. + +| Existing option | Cutover action | Isogloss-native replacement | +|---|---|---| +| `targetMode` | Keep | Source-selection concern | +| `threshold` | Keep after deterministic PRNG refactor | Source-selection concern | +| `preprocessIdentifiers` | Keep | Pre-compilation transformation | +| `debugLogging` | Rename | `isogloss.ownerTrace` / development trace sink | +| `encryptBytecode` | Remove | Later `encryptArtifact` over the lattice payload | +| `debugProtection` | Remove, then redesign | Later engine-independent runtime protection | +| `dynamicOpcodes` | Remove | The BCM emits only required semantic families | +| `decoyOpcodes` | Remove | Later `decoyClauses` / semantic aliases | +| `deadCodeInjection` | Remove | Later inert, never-unique lattice corridors | +| `stackEncoding` | Remove, then redesign | Later engine-independent value representation | +| `rollingCipher` | Delete permanently | Assumes a linear instruction stream | +| `integrityBinding` | Remove, then redesign | Later lattice/runtime integrity binding | +| `vmShielding` | Delete permanently | Root groups already own independent lattices | +| `mixedBooleanArithmetic` | Remove, then redesign | Later generic runtime AST transform | +| `handlerFragmentation` | Delete permanently unless a new BCM rationale is proven | No VM handler table remains | +| `stringAtomization` | Remove, then redesign | Later final-runtime string transform | +| `polymorphicDecoder` | Remove, then redesign | Later artifact decoder transform | +| `scatteredKeys` | Remove | No core BCM key material | +| `blockPermutation` | Delete permanently | Lattice topology replaces instruction ordering | +| `opcodeMutation` | Delete permanently | No opcode table or physical opcodes remain | +| `bytecodeScattering` | Remove | Later `artifactScattering` over the lattice payload | +| `incrementalCipher` | Delete permanently | Assumes linear instruction epochs | +| `semanticOpacity` | Remove | Isogloss constraints, aliases, and predicates provide the native mechanism | +| `observationResistance` | Remove, then redesign | Later BCM-specific observation model | +| `target` | Keep | Final assembly concern | + +Implement `validateRemovedVmOptions()` before parse/compile work. It reports: + +- `RUAM_REMOVED_VM_OPTION`; +- every removed property or CLI flag; +- whether a concept was deleted permanently, renamed, or deferred for redesign; +- the migration replacement where one exists. + +This validator is a tombstone list only. It imports no VM implementation and is removed after the documented migration window if desired. + +## 13. Determinism and entropy refactor + +The replacement engine requires repeatable failure reproduction. Refactor randomness before the spike: + +1. Move `generateCryptoSeed()` out of `transform.ts` into `src/random/entropy.ts`. +2. Define an internal `BuildEntropy` containing the file seed and any independently generated salts. +3. Production uses `crypto.randomBytes`. +4. Tests call an internal `protectCodeWithContext()` with fixed entropy. +5. `collectTargetFunctions()` stops using `Math.random()` for `threshold`; it receives a PRNG derived with `deriveSeed(fileSeed, "target-selection")`. +6. Every BCM module receives either its already-derived seed or a scoped PRNG. No module reads global randomness. +7. Failure diagnostics always include the file seed, group ID, stream label, and attempt count. + +Do not expose a deterministic public production seed unless a separate product decision approves reproducible builds and documents the security tradeoff. + +## 14. File-by-file change ledger + +### 14.1 New files + +```text +packages/ruam/src/artifact/encode.ts +packages/ruam/src/artifact/format.ts +packages/ruam/src/compiler/cfg.ts +packages/ruam/src/compiler/ir.ts +packages/ruam/src/compiler/semantic-ops.ts +packages/ruam/src/compiler/semantic-signatures.ts +packages/ruam/src/compiler/types.ts +packages/ruam/src/isogloss/certificate.ts +packages/ruam/src/isogloss/constraints.ts +packages/ruam/src/isogloss/lower.ts +packages/ruam/src/isogloss/operands.ts +packages/ruam/src/isogloss/reference-runtime.ts +packages/ruam/src/isogloss/types.ts +packages/ruam/src/isogloss/verify.ts +packages/ruam/src/isogloss/runtime/assembler.ts +packages/ruam/src/isogloss/runtime/carrier.ts +packages/ruam/src/isogloss/runtime/checks.ts +packages/ruam/src/isogloss/runtime/continuations.ts +packages/ruam/src/isogloss/runtime/deserializer.ts +packages/ruam/src/isogloss/runtime/interpreter.ts +packages/ruam/src/isogloss/runtime/loader.ts +packages/ruam/src/isogloss/runtime/operands.ts +packages/ruam/src/isogloss/runtime/resolver.ts +packages/ruam/src/isogloss/runtime/runners.ts +packages/ruam/src/isogloss/runtime/trace.ts +packages/ruam/src/migration/removed-vm-options.ts +packages/ruam/src/options/resolve.ts +packages/ruam/src/owner-view/build.ts +packages/ruam/src/owner-view/decode.ts +packages/ruam/src/owner-view/types.ts +packages/ruam/src/pipeline/assemble.ts +packages/ruam/src/pipeline/groups.ts +packages/ruam/src/pipeline/select.ts +packages/ruam/src/random/entropy.ts +packages/ruam/src/runtime/handler-context.ts +packages/ruam/src/runtime/handler-catalog.ts +packages/ruam/src/runtime/handlers/*.ts +packages/ruam/src/runtime/nodes.ts +packages/ruam/src/runtime/emit.ts +packages/ruam/src/testing.ts +``` + +### 14.2 Existing files with behavioral changes + +| File | Required edit | +|---|---| +| `src/transform.ts` | Rewrite as the single Isogloss build pipeline and return `ProtectionBuildResult` internally | +| `src/index.ts` | Export `protectCode`, `RuamOptions`, and owner-view APIs; remove VM-named exports | +| `src/browser-entry.ts` | Export the detailed Isogloss API and new types | +| `src/browser-worker.ts` | Use `protectCode()` and optionally transfer sidecar data | +| `src/types.ts` | Keep Ruam/Isogloss public types only; remove VM bytecode and VM option types | +| `src/compiler/index.ts` | Emit canonical `SemanticUnit` directly | +| `src/compiler/emitter.ts` | Emit `SemanticOp` nodes with IDs and source origins | +| `src/compiler/basic-blocks.ts` | Replace with or delegate to canonical CFG construction | +| `src/compiler/optimizer.ts` | Move genuinely semantic passes, then delete VM superinstruction logic | +| `src/presets.ts` | Replace preset contents with Isogloss/artifact settings | +| `src/option-meta.ts` | Replace VM options with typed Isogloss metadata and removed-option tombstones | +| `src/cli.ts` | Remove engine selection and VM flags; add Isogloss flags | +| `src/tuning.ts` | Replace VM intensity fields with lattice/runtime tuning | +| `src/structural-choices.ts` | Generate lattice and BCM-runtime choices only | +| `src/naming/claims.ts` | Replace VM claims with BCM runtime claims | +| `src/naming/compat-types.ts` | Replace `RuntimeNames` with Isogloss/runtime types; remove the compatibility framing | +| `src/naming/setup.ts` | Create root-group and BCM runtime scopes only | +| `scripts/generate-manifest.mjs` | Emit Isogloss option metadata plus migration tombstones | +| `package.json` | Remove `ruamvm` binary alias and VM/bytecode product description; prepare the replacement major version | +| `README.md` | Document the sole Isogloss engine, claim boundary, migration, examples, and limitations | +| `CLAUDE.md` | Replace VM architecture notes with BCM invariants while preserving relevant JavaScript semantic regressions | + +### 14.3 Files and systems deleted at cutover + +```text +packages/ruam/src/compiler/encode.ts +packages/ruam/src/compiler/opcodes.ts # replaced by semantic-ops.ts +packages/ruam/src/compiler/block-permutation.ts +packages/ruam/src/compiler/incremental-cipher.ts +packages/ruam/src/compiler/opcode-mutation.ts +packages/ruam/src/compiler/rolling-cipher.ts +packages/ruam/src/ruamvm/ # after generic handlers/nodes are moved +packages/ruam/test/security/*-cipher.test.ts # replaced by artifact/Isogloss tests where applicable +packages/ruam/test/security/opcode-mutation*.test.ts +packages/ruam/test/security/vm-shielding.test.ts +``` + +Before deleting any test, classify it as: + +1. JavaScript semantic correctness—port it to Isogloss; +2. generic security property—rewrite it for the lattice/artifact; +3. VM-mechanism-only—delete it with the mechanism. + +The deletion PR must include a generated source inventory proving there are no imports of `ruamvm`, `BytecodeUnit`, physical opcode maps, VM runner names, VM options, or removed encoders. + +### 14.4 Tests to add + +```text +packages/ruam/test/isogloss/certificate.test.ts +packages/ruam/test/isogloss/cfg-bisimulation.test.ts +packages/ruam/test/isogloss/constraints.test.ts +packages/ruam/test/isogloss/format-roundtrip.test.ts +packages/ruam/test/isogloss/novelty-invariants.test.ts +packages/ruam/test/isogloss/operands.test.ts +packages/ruam/test/isogloss/persistence.test.ts +packages/ruam/test/isogloss/reference-runtime.test.ts +packages/ruam/test/isogloss/runtime-async.test.ts +packages/ruam/test/isogloss/runtime-classes.test.ts +packages/ruam/test/isogloss/runtime-closures.test.ts +packages/ruam/test/isogloss/runtime-control-flow.test.ts +packages/ruam/test/isogloss/runtime-core.test.ts +packages/ruam/test/isogloss/runtime-exceptions.test.ts +packages/ruam/test/isogloss/runtime-generators.test.ts +packages/ruam/test/isogloss/runtime-reentrancy.test.ts +packages/ruam/test/isogloss/seed-stress.test.ts +packages/ruam/test/isogloss/semantic-signatures.test.ts +packages/ruam/test/isogloss/sidecar.test.ts +packages/ruam/test/isogloss/static-extractor.test.ts +packages/ruam/test/migration/removed-vm-options.test.ts +packages/ruam/test/migration/no-legacy-vm.test.ts +packages/ruam/test/options/metadata-drift.test.ts +``` + +## 15. Implementation sequence + +Each phase ends in a reviewable state. Development may call the legacy VM explicitly from migration tests until Phase 10, but product entry points never choose between engines: they either execute through the BCM or report that an unfinished semantic surface is not yet available on the replacement branch. No automatic VM fallback is permitted. + +### Phase 0 — Freeze the legacy baseline and replacement contract + +**Work** + +1. Record the current VM's correctness results, build time, output size, bootstrap time, execution time, and retained memory as versioned JSON benchmark artifacts. +2. Save representative generated outputs for core, closure, exception, class, async, generator, and max-preset fixtures. +3. Classify every existing test as JavaScript semantics, generic protection, or VM-mechanism-only. +4. Add architecture records for: + - full VM replacement and no fallback; + - canonical semantic IR; + - one carrier per root group; + - non-transactional boundary motion; + - in-memory-only persistence; + - bounded correctness/security claims. +5. Add deterministic entropy injection and remove `Math.random()` from target selection without changing legacy behavior. + +**Exit gate** + +- Existing suite, typecheck, and build pass before replacement work begins. +- Fixed entropy reproduces identical output. +- Benchmark JSON and fixture inventory are committed so later comparisons do not require retaining VM code. +- The no-backend-selector/no-fallback architecture record is approved. + +**Effort envelope:** 2–4 engineer-days. + +### Phase 1 — Extract the semantic compiler and handler catalog + +**Work** + +1. Rename language-level logical operations from `Op` to `SemanticOp`. +2. Split public API types from compiler IR types. +3. Add semantic node IDs, source origins, typed CFG exits, and exhaustive semantic signatures. +4. Move generic AST nodes, emitter, handler context, handler registry, and language handlers from `src/ruamvm/` into `src/runtime/`. +5. Remove physical opcode/shuffle parameters from the extracted catalog. +6. Add direct native-JavaScript differential tests for each semantic family. +7. Keep a thin migration-only adapter that allows the old VM tests to consume canonical semantic IR until Phase 10; do not expose it publicly. + +**Exit gate** + +- Every `SemanticOp` has a signature descriptor and native differential coverage. +- The current semantic suites pass through the extracted catalog. +- `src/runtime/` has no imports from VM encoder, shuffle, cipher, loader, or dispatch modules. +- No `ProtectionBackend`, `VmBackend`, `executionModel`, or backend selector has been introduced. + +**Effort envelope:** 7–12 engineer-days. + +### Phase 2 — Build-only lattice generator and verifier + +**Initial semantic scope** + +- constants and arguments; +- register load/store; +- stack operations; +- integer/number/string arithmetic; +- equality and ordering comparisons; +- unconditional and conditional control flow; +- return and return-void. + +**Work** + +1. Implement cells, clauses, reservoirs, topology, entry contracts, and carrier seeds. +2. Implement bounded deterministic constraint search. +3. Generate at least two boundary variants per semantic node. +4. Implement CFG-to-lattice verification and certificates. +5. Implement the hostile static extractor used by novelty tests. + +**Exit gate** + +- 10,000 generated small semantic programs produce valid certificates. +- At least 256 fixed seeds pass per curated fixture in extended CI. +- TI-01 through TI-07 are mechanically checked. +- The extractor cannot recover a site-to-handler map without carrier replay. +- Constraint failures are reproducible and include the seed, group, node, signature, and attempt count. + +**Stop condition** + +Stop and redesign if uniqueness requires a stored expected-handler token, direct next-operation pointer, hidden bytecode stream, or complementary opcode shares. + +**Effort envelope:** 8–14 engineer-days. + +### Phase 3 — TypeScript reference BCM + +**Work** + +1. Implement a readable TypeScript resolver, operand projector, carrier, and frame model. +2. Execute the Phase 2 subset directly from verified lattices. +3. Compare native JavaScript, a canonical semantic-IR reference evaluator, and the BCM reference runtime. +4. Verify repeated calls change carrier state while preserving results. +5. Verify loops revisit different boundary variants. +6. Model terminal throws and post-error carrier validity. + +**Exit gate** + +- Zero mismatches across the randomized subset corpus. +- Repeated identical inputs return identical JavaScript values while producing distinct valid lineages. +- Resolver diagnostics explain a mismatch down to group, state, clauses, witness, projected operand, and CFG edge. +- The reference runtime has no import from `src/ruamvm/`. + +**Effort envelope:** 6–10 engineer-days. + +### Phase 4 — Emitted synchronous BCM runtime + +**Work** + +1. Implement the single-engine artifact envelope and BCM encoding. +2. Implement AST-built deserializer, loader, resolver, operand projector, carrier, and sync interpreter. +3. Consume the extracted semantic handler catalog directly. +4. Emit contract-token function stubs. +5. Add memory-persistent root-group state and optional runtime invariant checks. +6. Route `protectCode()` through the BCM for the supported synchronous surface. Unsupported constructs produce explicit development errors; they never fall back to VM. + +**Exit gate** + +- Synchronous arithmetic, strings, arrays, objects, functions, and control flow pass against native JavaScript. +- Production output contains no canonical IR, direct handler token, source origin, or owner map. +- Node, browser, and browser-extension CSP fixtures pass for the supported surface. +- Every generated identifier uses NameRegistry. + +**Effort envelope:** 10–16 engineer-days. + +### Phase 5 — Scope, closures, classes, and reentrancy + +**Work** + +1. Add scope-chain and closure contracts. +2. Keep escaped child closures attached to their originating root-group carrier. +3. Add call, construct, home-object, `new.target`, and `this` transitions. +4. Add class, getter, setter, `super`, and computed method behavior. +5. Add reentrant carrier routing for getters, setters, proxies, `valueOf`, and callbacks. +6. Preserve one active carrier across nested and recursive calls. + +**Exit gate** + +- All ported closure, scope, class, and `super` semantic suites pass against native JavaScript. +- Known `NEW_CLASS`, home-object, computed-method, and this-boxing regressions have BCM tests. +- Escaped closures remain correct after unrelated calls evolve their root group. +- Reentrant calls never duplicate carrier state. + +**Effort envelope:** 10–18 engineer-days. + +### Phase 6 — Exceptions and finally + +**Work** + +1. Represent catch/finally edges directly in topology. +2. Route thrown handler values through exception exits. +3. Preserve completion type/value through nested finally paths. +4. Handle break, continue, return, and throw through one or more finally regions. +5. Land uncaught throws at terminal contract gates before rethrowing the original value. + +**Exit gate** + +- Every relevant exception/finally regression in `CLAUDE.md` is ported and passes against native JavaScript. +- Native and BCM event traces agree for nested catch/finally programs. +- A subsequent call after an uncaught error executes from a valid evolved carrier. + +**Effort envelope:** 8–14 engineer-days. + +### Phase 7 — Async and generators + +**Work** + +1. Add await/yield suspension contracts. +2. Park frames without cloning carriers. +3. Route the current carrier to resumed continuations in host scheduling order. +4. Support interleaved promises and generators within one root group. +5. Clean up terminal or abandoned continuations without append-only history. +6. Prove the optional trace sink cannot alter scheduling. + +**Exit gate** + +- Ported async and generator suites pass against native JavaScript. +- Event-order tests compare side-effect logs, not only final values. +- Two interleaved calls preserve native ordering. +- Only one carrier is active at every instrumented semantic step. +- Rejection followed by a later successful invocation works. + +**Effort envelope:** 12–20 engineer-days. + +### Phase 8 — Public API, CLI, presets, browser worker, and owner view + +**Work** + +1. Finalize `RuamOptions`, `protectCode()`, file/directory APIs, and build-result schemas. +2. Remove `VmObfuscationOptions`, `runVmObfuscation()`, and execution-model concepts. +3. Replace preset contents with Isogloss-native tuning. +4. Replace CLI VM flags with Isogloss flags and removed-option errors. +5. Update the option manifest and browser worker. +6. Implement the owner sidecar, opaque runtime events, and replay decoder. +7. Update README and migration documentation. + +**Exit gate** + +- The public type surface contains no VM-named execution types. +- CLI help, presets, browser worker, and manifest describe one engine. +- Sidecar data is absent from production code. +- Removed options fail before parse/compile work with actionable messages. + +**Effort envelope:** 6–10 engineer-days. + +### Phase 9 — Isogloss-native hardening + +Add hardening only when it has a lattice or engine-independent meaning: + +1. artifact encryption; +2. artifact scattering; +3. final-runtime string atomization; +4. generic runtime MBA transforms; +5. engine-independent debug protection; +6. value representation hardening; +7. decoy clauses and semantic aliases; +8. lattice/runtime integrity binding; +9. BCM-specific observation resistance. + +For each feature, add one option, one design note, isolated tests, seed stress, performance attribution, and only then preset coverage. + +Never restore rolling cipher, incremental cipher, opcode mutation, physical-opcode shuffling, VM shielding, or block permutation. + +**Exit gate** + +- `low`, `medium`, and `max` contain only Isogloss-native or engine-independent protections. +- Feature-pair and feature-triple matrices pass. +- No hardening creates a stable site-to-handler map or hidden instruction stream. + +**Effort envelope:** 12–24 engineer-days. + +### Phase 10 — Destructive legacy removal and product cutover + +**Work** + +1. Move the final reusable handler/node code out of `src/ruamvm/`. +2. Port all JavaScript-semantic tests and generic protection tests. +3. Delete VM-only tests and implementation modules listed in Section 14.3. +4. Delete VM encoder, interpreter, loaders, runners, assemblers, physical opcodes, shuffles, ciphers, mutation, permutation, and shielding. +5. Remove `ruamvm` imports, CLI alias, options, types, package language, and generated manifest entries. +6. Remove the migration-only VM oracle adapter. +7. Run a source/package inventory that fails on any legacy execution symbol. +8. Cut the replacement major release only after the full Isogloss suite passes; there is no release with a fallback. + +**Exit gate** + +- `rg` finds no production references to `ruamvm`, `VmBackend`, `BytecodeUnit`, physical opcode maps, VM dispatch/loader names, or removed VM options outside the migration guide and removed-option tombstones. +- Package contents include no VM runtime or bytecode encoder. +- Every supported source program executes only through the BCM. +- The full typecheck, build, Node, browser, worker, browser-extension, seed, and randomized suites pass. +- Deleting the old VM changes no passing Isogloss result because no product path referenced it before deletion. + +**Effort envelope:** 5–10 engineer-days. + +### Phase 11 — Adversarial review and release qualification + +**Work** + +1. Build an internal extractor with full format/runtime knowledge. +2. Measure body location, reusable-sequence recovery, concrete-invocation explanation, and next-lineage prediction. +3. Hook resolver, handler catalog, operand projection, and carrier independently. +4. Test whether a trace from one lineage transfers to another. +5. Benchmark against native JavaScript and the frozen Phase 0 legacy measurements. +6. Conduct external design/security review before declaring the replacement release stable. + +**Release gate** + +- Correctness is perfect across the declared JavaScript support surface. +- A static format-aware extractor cannot recover a history-independent site-to-operation body. +- A trace from one lineage does not directly decode another lineage without replay. +- Median output size is at most 3× the frozen legacy VM measurement for the same fixture and at most the separately documented absolute budget. +- Median steady-state execution is at most 3× the frozen legacy measurement; P95 is at most 5×. +- Bootstrap is at most 2× the frozen legacy measurement. +- Memory reaches a stable bound; carrier history never appends indefinitely. +- Node, browser, worker, and browser-extension CSP targets pass. +- Section 14.3's deletion inventory and TI-14 pass. + +**Kill criteria** + +End or radically redesign the replacement if any remains true: + +- the artifact requires a hidden linear instruction stream; +- unique resolution requires a stored expected-handler token; +- the lattice reduces to a direct node-to-handler table without replay; +- async equivalence requires serializing host-visible work; +- carrier state grows unboundedly; +- correctness depends on favorable seeds; +- straightforward optimization cannot bring execution below 12× or size below 10× the frozen legacy measurement; +- the practical mechanism is only control-flow flattening plus indirection. + +**Effort envelope:** 8–15 engineer-days plus external review. + +## 16. Validation strategy + +### 16.1 Per-commit checks + +From `packages/ruam`: + +```sh +bun run typecheck +bun test +bun run build +``` + +During focused development: + +```sh +bun test test/isogloss +bun test test/migration/removed-vm-options.test.ts +bun test test/migration/no-legacy-vm.test.ts +``` + +### 16.2 Seed tiers + +| Tier | Seeds | When | +|---|---:|---| +| Local fast | 8 per focused fixture | Every implementation loop | +| Pull request | 32 per curated fixture | Every PR | +| Extended CI | 256 per curated fixture | Merge gate | +| Nightly/release | 1,024 per high-risk fixture plus randomized corpus | Nightly and release candidate | + +High-risk fixtures include: + +- backward jumps; +- forward branch targets; +- nested switch/continue; +- nested return-through-finally; +- caught and uncaught exceptions; +- recursive closures; +- getters/proxies that reenter; +- escaped closures; +- multi-level `super`; +- sparse arrays and iterators; +- two interleaved async calls; +- generator throw/return; +- repeated calls after an exception. + +### 16.3 Differential oracle + +Extend `test/helpers.ts` to support: + +```ts +assertEquivalentToNative(source, options) +assertReferenceAndEmittedBcmAgree(source, options) +assertEventTraceEquivalent(source, options) +assertCarrierEvolves(source, calls, options) +``` + +For serializable results, compare values. For side effects, compare an explicit event log. For errors, compare constructor, name, message where specified by JavaScript, and event order. Do not require generated stack text to be identical unless Ruam explicitly guarantees it. + +Before Phase 10, migration-only tests may also compare against the legacy VM to diagnose refactor errors. Delete those calls with the VM oracle adapter at cutover. + +### 16.4 Property suites + +Add generators for: + +- small canonical IR graphs; +- loop and branch graphs; +- exception-region graphs; +- candidate masks at edge cardinalities; +- operand reservoir reuse; +- cell phase cycles; +- root/child contract graphs; +- async continuation interleavings. + +Every minimized failure prints a fully reproducible seed and serialized canonical IR/lattice pair. + +### 16.5 Novelty tests + +`static-extractor.test.ts` must use a deliberately hostile parser with full knowledge of the format. It should fail the build if it finds: + +- a linear array correlated one-to-one with canonical semantic nodes; +- a field whose value directly names the handler for a site; +- a left/right pair whose static intersection is unique; +- a direct next-instruction pointer; +- source origins or owner summaries in production payload; +- a carrier transition that leaves all state unchanged; +- a cell used by only one semantic node when reuse was required. + +This is an architecture regression test, not a cryptographic security proof. + +### 16.6 Performance suite + +Extend `scripts/bench.mjs` and `scripts/bench-attribution.mjs` with: + +- Isogloss profile and tuning values; +- original bytes, encoded payload bytes, runtime bytes, and sidecar bytes; +- build time split into parse, canonical compile, lattice generation, verification, encoding, and emit; +- bootstrap time; +- first call; +- repeated call; +- recursion; +- loop-heavy; +- object/property-heavy; +- exception-heavy; +- async interleave; +- peak and retained memory where supported. + +Report ratios against native JavaScript and the frozen Phase 0 legacy benchmark JSON. The benchmark harness must not import or retain the legacy VM after Phase 10. + +## 17. Risk register + +| Risk | Early signal | Mitigation | +|---|---|---| +| The representation collapses into opcode secret sharing | Adjacent pair uniquely identifies handler | Enforce pair ambiguity and third history-derived witness | +| The graph is just bytecode with renamed pointers | One node owns one cell pair and one successor | Require cell reuse, multiple boundary variants, and exit-specific local refolds | +| Constraint search explodes | Frequent attempt-budget exhaustion | Signature aliases, mask deduplication, bounded profiles, detailed UNSAT diagnostics | +| Verifier state space explodes on loops | Epoch/lineage create unbounded states | Consume only finite phase/witness projections; reject unbounded semantic dependence | +| Shared handler reuse leaks a fixed body | Sites directly reference handler indices | Resolver enumerates catalog through constraints; no site reference | +| Runtime is too large | Per-node clauses dominate payload | Cell reuse, mask interning, typed-array packing, profile-controlled expansion | +| Runtime is too slow | Catalog scan dominates | Bitset intersection, precomputed compatible families, word-level unique-bit detection | +| Reentrancy corrupts carrier state | Proxy/getter callbacks produce mismatches | One carrier frame stack and explicit resume gates; dedicated reentrancy suite | +| Async changes observable order | Event logs differ despite equal final values | Reuse host scheduling; never serialize complete calls | +| Owner view becomes an attack oracle | Production artifact exposes map | Sidecar separation; runtime emits opaque IDs only when explicitly enabled | +| Semantic extraction accidentally preserves VM coupling | `src/runtime/` imports physical opcode or VM loader modules | Import-boundary tests and extraction before deletion | +| Cutover strands unported semantics | A test is deleted because BCM does not pass it | Mandatory semantic/generic/VM-only test classification and review | +| Legacy fallback survives invisibly | A product path imports `ruamvm` or accepts `executionModel` | TI-14 source/package inventory and no-legacy test | +| Removed options silently degrade | Old flag parses but changes nothing | Tombstone validator with actionable hard failure | +| History grows forever | Memory rises per call | Fixed-width phase, epoch, lineage, bounded parked frames, no append-only log | + +## 18. Review and commit slicing + +Use small commits/PRs with the following boundaries: + +1. deterministic entropy, frozen legacy measurements, and test classification; +2. public build result type without engine selection; +3. semantic operation rename, IR, origins, and CFG; +4. generic handler/node extraction out of `ruamvm`; +5. semantic signatures and native handler differential tests; +6. lattice types, constraints, verifier, and certificate; +7. TypeScript reference BCM; +8. single-engine artifact format and encoder; +9. emitted synchronous BCM runtime and contract stubs; +10. scopes, closures, classes, and reentrancy; +11. exceptions and finally; +12. async and generators; +13. API, CLI, presets, manifest, and removed-option migration; +14. owner sidecar and replay; +15. each Isogloss-native hardening feature separately; +16. destructive VM/test/options/package removal; +17. adversarial and performance release report. + +Move the generic handlers before deleting `src/ruamvm/`, but do not combine that mechanical move with BCM semantic changes. Do not combine a new hardening feature with correctness work. The destructive deletion remains its own auditable change. + +## 19. Definition of done + +Traveling Isogloss has replaced the VM only when: + +- [ ] `protectCode()` is the primary build API and every protected function executes through the BCM. +- [ ] No public `executionModel`, `VmObfuscationOptions`, `runVmObfuscation()`, or `ruamvm` CLI alias remains. +- [ ] The production source and package contain no VM interpreter, bytecode encoder, physical opcode map, VM loader/runner, VM fallback, or backend abstraction. +- [ ] The `src/ruamvm/` directory is deleted after reusable semantic handlers and AST utilities are moved. +- [ ] The artifact contains no linear instruction stream. +- [ ] One carrier persists per root group and visibly evolves across calls. +- [ ] Every cell and pair ambiguity invariant is verified at build time. +- [ ] Canonical CFG and lattice topology pass the bisimulation verifier. +- [ ] Native JavaScript, reference BCM, and emitted BCM agree across the declared support surface. +- [ ] Closures, classes, `this`, `super`, recursion, reentrancy, exceptions, finally, async, and generators pass. +- [ ] Removed VM options fail explicitly with migration guidance. +- [ ] Node, browser, worker, and browser-extension CSP targets pass. +- [ ] Owner trace data is sidecar-only and runtime events are opt-in and opaque. +- [ ] All randomness is seed-isolated and reproducible in tests. +- [ ] All generated names use NameRegistry. +- [ ] Seed stress and randomized tests have no flakes. +- [ ] Adversarial tests show no history-independent site-to-operation body. +- [ ] Performance, output size, bootstrap, and bounded-memory release gates pass against native and frozen legacy measurements. +- [ ] README, package metadata, CLI help, and security language describe Isogloss as Ruam's sole engine. +- [ ] External design/security review approves the replacement release. + +## 20. Recommended first implementation move + +The first code change should **not** create an Isogloss cell or introduce a backend interface. It should freeze the old measurements, then extract canonical semantic IR and the reusable JavaScript handler catalog from the VM-specific directory. + +The first genuine research milestone is: + +> Compile straight-line and branching arithmetic functions into a verified in-memory lattice, execute them in the TypeScript reference BCM, and demonstrate that repeated calls traverse different boundary variants while native JavaScript, the semantic-IR evaluator, and the BCM remain identical. + +That milestone tests the new execution model before paying for binary encoding, emitted runtime generation, the full JavaScript surface, or destructive VM removal. Once the complete BCM passes the release gates, the legacy VM is deleted rather than retained as an option. From 00937bef9a9887e4a7a1934f8de0d0ce5a4c29b5 Mon Sep 17 00:00:00 2001 From: owengregson Date: Fri, 24 Jul 2026 13:23:13 -1000 Subject: [PATCH 04/54] refactor: make build entropy reproducible --- packages/ruam/src/random/entropy.ts | 72 +++++++++++++++++ packages/ruam/src/testing.ts | 26 ++++++ packages/ruam/src/transform.ts | 80 +++++++++++++------ .../test/core/deterministic-entropy.test.ts | 50 ++++++++++++ 4 files changed, 204 insertions(+), 24 deletions(-) create mode 100644 packages/ruam/src/random/entropy.ts create mode 100644 packages/ruam/src/testing.ts create mode 100644 packages/ruam/test/core/deterministic-entropy.test.ts diff --git a/packages/ruam/src/random/entropy.ts b/packages/ruam/src/random/entropy.ts new file mode 100644 index 0000000..fcfde6e --- /dev/null +++ b/packages/ruam/src/random/entropy.ts @@ -0,0 +1,72 @@ +/** + * Build-time entropy and deterministic pseudo-random streams. + * + * Production builds draw independent 32-bit values from the platform CSPRNG. + * Tests inject a deterministic source so every randomized build decision can + * be reproduced from one seed. + * + * @module random/entropy + */ + +import { randomBytes } from "node:crypto"; +import { deriveSeed, lcgNext } from "../naming/scope.js"; + +/** Source of labeled 32-bit build-time entropy. */ +export interface BuildEntropy { + /** + * Return the next unsigned 32-bit value for a logical responsibility. + * + * Labels are diagnostic for production entropy and define independent, + * reproducible streams for deterministic test entropy. + */ + nextUint32(label: string): number; +} + +/** Seeded stream used for deterministic selections within one responsibility. */ +export interface SeededRandom { + nextUint32(): number; + nextFloat(): number; +} + +/** Create the production CSPRNG-backed entropy source. */ +export function createCryptoEntropy(): BuildEntropy { + return { + nextUint32(_label: string): number { + return randomBytes(4).readUInt32LE(0); + }, + }; +} + +/** + * Create a deterministic entropy source for tests and failure reproduction. + * + * Each label owns an independent counter-derived stream, so adding entropy use + * in one subsystem does not perturb existing values in another subsystem. + */ +export function createDeterministicEntropy(seed: number): BuildEntropy { + const counters = new Map(); + const root = seed >>> 0; + + return { + nextUint32(label: string): number { + const counter = counters.get(label) ?? 0; + counters.set(label, counter + 1); + return lcgNext(deriveSeed(root, `${label}:${counter}`)); + }, + }; +} + +/** Create a deterministic LCG stream from an already isolated seed. */ +export function createSeededRandom(seed: number): SeededRandom { + let state = seed >>> 0; + return { + nextUint32(): number { + state = lcgNext(state); + return state; + }, + nextFloat(): number { + state = lcgNext(state); + return state / 0x1_0000_0000; + }, + }; +} diff --git a/packages/ruam/src/testing.ts b/packages/ruam/src/testing.ts new file mode 100644 index 0000000..7a12e1d --- /dev/null +++ b/packages/ruam/src/testing.ts @@ -0,0 +1,26 @@ +/** + * Internal deterministic test entry points. + * + * This module is intentionally not exported from the package root. + * + * @module testing + */ + +import { obfuscateCodeWithEntropy } from "./transform.js"; +import type { VmObfuscationOptions } from "./types.js"; +import { createDeterministicEntropy } from "./random/entropy.js"; + +/** Obfuscate with reproducible build entropy for seed-stress tests. */ +export function obfuscateCodeDeterministic( + source: string, + options: VmObfuscationOptions = {}, + seed = 0 +): string { + return obfuscateCodeWithEntropy( + source, + options, + createDeterministicEntropy(seed) + ); +} + +export { createDeterministicEntropy } from "./random/entropy.js"; diff --git a/packages/ruam/src/transform.ts b/packages/ruam/src/transform.ts index 5208212..907bd48 100644 --- a/packages/ruam/src/transform.ts +++ b/packages/ruam/src/transform.ts @@ -66,18 +66,12 @@ import { } from "./compiler/incremental-cipher.js"; import { getTuningProfile, presetToIntensity } from "./tuning.js"; import type { TuningProfile } from "./tuning.js"; - -import { randomBytes } from "node:crypto"; - -/** - * Generate a cryptographically strong 32-bit seed. - * - * Uses Node.js `crypto.randomBytes` for proper entropy instead of - * `Date.now() ^ Math.random()` which is predictable. - */ -function generateCryptoSeed(): number { - return randomBytes(4).readUInt32LE(0); -} +import { + createCryptoEntropy, + createSeededRandom, + type BuildEntropy, + type SeededRandom, +} from "./random/entropy.js"; // --------------------------------------------------------------------------- // Public API @@ -94,6 +88,20 @@ function generateCryptoSeed(): number { export function obfuscateCode( source: string, options: VmObfuscationOptions = {} +): string { + return obfuscateCodeWithEntropy(source, options, createCryptoEntropy()); +} + +/** + * Internal transform entry point with injectable build entropy. + * + * Production callers use {@link obfuscateCode}; deterministic test and + * failure-reproduction helpers inject a labeled entropy source here. + */ +export function obfuscateCodeWithEntropy( + source: string, + options: VmObfuscationOptions = {}, + entropy: BuildEntropy ): string { const resolved = resolveOptions(options); const { @@ -128,7 +136,7 @@ export function obfuscateCode( const tuning = getTuningProfile(presetToIntensity(resolved.preset)); // -- Generate per-file seed (needed for both preprocessing and opcodes) -- - const shuffleSeed = generateCryptoSeed(); + const shuffleSeed = entropy.nextUint32("file-seed"); // -- Optional identifier preprocessing ----------------------------------- let code = source; @@ -167,7 +175,15 @@ export function obfuscateCode( }); // -- Collect target functions -------------------------------------------- - const targetPaths = collectTargetFunctions(ast, targetMode, threshold); + const targetRandom = createSeededRandom( + deriveSeed(shuffleSeed, "target-selection") + ); + const targetPaths = collectTargetFunctions( + ast, + targetMode, + threshold, + targetRandom + ); // -- VM Shielding path --------------------------------------------------- if (vmShielding) { @@ -193,11 +209,14 @@ export function obfuscateCode( wrapOutput, preprocessUsedNames, tuning, + entropy, }); } // -- Generate per-build cipher salt (if rolling cipher is enabled) -------- - const cipherSalt = rollingCipher ? generateCryptoSeed() : undefined; + const cipherSalt = rollingCipher + ? entropy.nextUint32("cipher-salt") + : undefined; // -- Compile each target (no encoding yet — need keyAnchor first) ------- const compiledUnits = compileTargetsOnly( @@ -357,23 +376,30 @@ function fnv1a(s: string): number { function collectTargetFunctions( ast: t.File, mode: "root" | "comment", - threshold: number + threshold: number, + random: SeededRandom ): NodePath[] { const targets: NodePath[] = []; traverse(ast, { FunctionDeclaration(path) { - if (shouldTarget(path as NodePath, mode, threshold)) { + if ( + shouldTarget(path as NodePath, mode, threshold, random) + ) { targets.push(path as NodePath); } }, FunctionExpression(path) { - if (shouldTarget(path as NodePath, mode, threshold)) { + if ( + shouldTarget(path as NodePath, mode, threshold, random) + ) { targets.push(path as NodePath); } }, ArrowFunctionExpression(path) { - if (shouldTarget(path as NodePath, mode, threshold)) { + if ( + shouldTarget(path as NodePath, mode, threshold, random) + ) { targets.push(path as NodePath); } }, @@ -391,7 +417,8 @@ function collectTargetFunctions( function shouldTarget( path: NodePath, mode: "root" | "comment", - threshold: number + threshold: number, + random: SeededRandom ): boolean { if (mode === "comment") { const leadingComments = path.node.leadingComments; @@ -406,7 +433,7 @@ function shouldTarget( current = current.parentPath; } - if (threshold < 1.0 && Math.random() > threshold) return false; + if (threshold < 1.0 && random.nextFloat() > threshold) return false; return true; } @@ -921,11 +948,14 @@ function assembleShielded( wrapOutput: boolean; preprocessUsedNames: Set | undefined; tuning: Readonly; + entropy: BuildEntropy; } ): string { // Generate per-group seeds (one per root function) - const groupSeeds = targetPaths.map(() => generateCryptoSeed()); - const sharedSeed = generateCryptoSeed(); + const groupSeeds = targetPaths.map((_path, index) => + opts.entropy.nextUint32(`shield-group-seed:${index}`) + ); + const sharedSeed = opts.entropy.nextUint32("shield-shared-seed"); // Generate names: shared + per-group via NameRegistry const { @@ -1008,7 +1038,9 @@ function assembleShielded( unit.isAsync || unit.childUnits.some((c) => c.isAsync); // Per-group cipher salt - const groupCipherSalt = generateCryptoSeed(); + const groupCipherSalt = opts.entropy.nextUint32( + `shield-group-cipher-salt:${gi}` + ); // Store compiled units (no encoding yet) allCompiledUnits.set(unit.id, { unit }); diff --git a/packages/ruam/test/core/deterministic-entropy.test.ts b/packages/ruam/test/core/deterministic-entropy.test.ts new file mode 100644 index 0000000..55258f0 --- /dev/null +++ b/packages/ruam/test/core/deterministic-entropy.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from "bun:test"; +import { + createDeterministicEntropy, + obfuscateCodeDeterministic, +} from "../../src/testing.js"; + +describe("deterministic build entropy", () => { + it("reproduces the same complete transform for a fixed seed", () => { + const source = ` + function alpha(x) { return x + 1; } + function beta(x) { return x * 2; } + [alpha(4), beta(5)]; + `; + const options = { + targetMode: "root" as const, + threshold: 0.5, + preprocessIdentifiers: true, + rollingCipher: true, + }; + + const first = obfuscateCodeDeterministic(source, options, 0x12345678); + const second = obfuscateCodeDeterministic(source, options, 0x12345678); + + expect(second).toBe(first); + }); + + it("keeps labeled entropy streams isolated", () => { + const first = createDeterministicEntropy(42); + const second = createDeterministicEntropy(42); + + const firstFile = first.nextUint32("file"); + first.nextUint32("unrelated"); + first.nextUint32("unrelated"); + const firstCipher = first.nextUint32("cipher"); + + expect(second.nextUint32("file")).toBe(firstFile); + expect(second.nextUint32("cipher")).toBe(firstCipher); + }); + + it("changes randomized output when the root seed changes", () => { + const source = `function f(x) { return x * x + 1; } f(9);`; + const outputs = new Set( + Array.from({ length: 8 }, (_, seed) => + obfuscateCodeDeterministic(source, {}, seed) + ) + ); + + expect(outputs.size).toBeGreaterThan(1); + }); +}); From 4bd2d8ee7cbd22344bc71e232e6430b98948f412 Mon Sep 17 00:00:00 2001 From: owengregson Date: Fri, 24 Jul 2026 13:33:29 -1000 Subject: [PATCH 05/54] docs: freeze legacy replacement baseline --- .../2026-07-24-legacy-vm-baseline.json | 862 ++++++++++++++++++ .../2026-07-24-legacy-vm-baseline.md | 39 + ...26-07-24-isogloss-test-migration-matrix.md | 263 ++++++ 3 files changed, 1164 insertions(+) create mode 100644 docs/superpowers/baselines/2026-07-24-legacy-vm-baseline.json create mode 100644 docs/superpowers/baselines/2026-07-24-legacy-vm-baseline.md create mode 100644 docs/superpowers/specs/2026-07-24-isogloss-test-migration-matrix.md diff --git a/docs/superpowers/baselines/2026-07-24-legacy-vm-baseline.json b/docs/superpowers/baselines/2026-07-24-legacy-vm-baseline.json new file mode 100644 index 0000000..8f7bae3 --- /dev/null +++ b/docs/superpowers/baselines/2026-07-24-legacy-vm-baseline.json @@ -0,0 +1,862 @@ +{ + "schemaVersion": 1, + "purpose": "Traveling Isogloss pre-implementation legacy baseline", + "baseline": { + "commit": "e8cecb56ba89d47512a16e325d81cf46f64b2ecb", + "commitSubject": "docs: plan full traveling isogloss replacement", + "reason": "Last branch commit before the first production-code implementation commit; measured in a detached worktree to avoid contamination from parallel agents.", + "package": "packages/ruam", + "packageName": "ruamvm", + "packageVersion": "2.0.0" + }, + "environment": { + "collectedOn": "2026-07-24", + "node": "v25.6.1", + "bun": "1.3.11", + "npm": "11.9.0", + "typescript": "5.9.3", + "tsup": "8.5.1", + "os": "Darwin 25.3.0", + "architecture": "arm64", + "cpu": "Apple M5", + "logicalCpuCount": 10, + "memoryBytes": 25769803776 + }, + "commands": [ + { + "command": "bun run typecheck", + "cwd": "/packages/ruam", + "exitCode": 0, + "timingSeconds": { + "real": 2.2, + "user": 3.81, + "sys": 0.27 + }, + "result": { + "passed": true, + "diagnostics": 0 + }, + "log": "/tmp/ruam-baseline-typecheck.log" + }, + { + "command": "bun test", + "cwd": "/packages/ruam", + "exitCode": 0, + "timingSeconds": { + "real": 11.38, + "user": 19.9, + "sys": 1.31 + }, + "result": { + "passed": 2328, + "failed": 0, + "expectCalls": 4819, + "files": 44, + "bunReportedSeconds": 11.34 + }, + "embeddedPerformance": { + "weightedAverageOverheadX": 41.1, + "medianOverheadX": 40.8, + "fastest": { + "workload": "try/catch in loop", + "overheadX": 21.6 + }, + "slowest": { + "workload": "switch statement dispatch", + "overheadX": 102.5 + }, + "workloads": [ + { + "name": "arithmetic loop (10k iterations)", + "nativeMs": 0.028, + "protectedMs": 1.408, + "overheadX": 49.9 + }, + { + "name": "fibonacci (recursive, n=20)", + "nativeMs": 0.122, + "protectedMs": 5.1, + "overheadX": 41.8 + }, + { + "name": "array manipulation (sort + map + reduce)", + "nativeMs": 0.059, + "protectedMs": 1.807, + "overheadX": 30.4 + }, + { + "name": "string operations (concatenation + manipulation)", + "nativeMs": 0.011, + "protectedMs": 0.386, + "overheadX": 34.3 + }, + { + "name": "object creation + property access", + "nativeMs": 0.012, + "protectedMs": 0.48, + "overheadX": 40.8 + }, + { + "name": "closures + higher-order functions", + "nativeMs": 0.015, + "protectedMs": 0.533, + "overheadX": 35.6 + }, + { + "name": "class instantiation + method calls", + "nativeMs": 0.023, + "protectedMs": 0.814, + "overheadX": 35 + }, + { + "name": "try/catch in loop", + "nativeMs": 0.026, + "protectedMs": 0.568, + "overheadX": 21.6 + }, + { + "name": "nested loops with conditionals", + "nativeMs": 0.037, + "protectedMs": 2.005, + "overheadX": 54.7 + }, + { + "name": "switch statement dispatch", + "nativeMs": 0.01, + "protectedMs": 1.027, + "overheadX": 102.5 + } + ] + }, + "log": "/tmp/ruam-baseline-test.log" + }, + { + "command": "bun run build", + "cwd": "/packages/ruam", + "exitCode": 0, + "timingSeconds": { + "real": 3.09, + "user": 5.09, + "sys": 0.38 + }, + "result": { + "passed": true, + "esModuleBuildMs": 61, + "declarationBuildMs": 2658, + "outputs": [ + { + "path": "dist/index.js", + "bytes": 285, + "gzipBytes": 156, + "sha256": "f8e81b2eb7bdc698953e76a57d92deddbd9dfda02a67adfc62aa9259aab0aba6" + }, + { + "path": "dist/cli.js", + "bytes": 23611, + "gzipBytes": 6076, + "sha256": "34d4efc8788168bd7fdee6e64390b0356c4cbf0e6119e1b5c1a67d8baaf6e4bb" + }, + { + "path": "dist/chunk-3BCH3N5C.js", + "bytes": 535347, + "gzipBytes": 100251, + "sha256": "bb88026b2a11b04ab665ed5cb7c4e5cef1c7b20460a3a3b785b7630626fd1477" + }, + { + "path": "dist/index.d.ts", + "bytes": 11130 + }, + { + "path": "dist/cli.d.ts", + "bytes": 20 + } + ], + "totalBytes": 570393 + }, + "log": "/tmp/ruam-baseline-build.log" + }, + { + "command": "bun scripts/bench.mjs --quick", + "cwd": "/packages/ruam", + "exitCode": 0, + "timingSeconds": { + "real": 48.39, + "user": 82.3, + "sys": 2.29 + }, + "result": { + "iterationsPerWorkload": 30, + "allCorrect": true, + "presets": [ + { + "name": "default", + "bootstrapMs": 0.038, + "aggregateExecutionOverheadX": 48.2, + "worstExecutionOverheadX": 69.2, + "worstWorkload": "fib-28", + "workloads": [ + [ + "arith-loop-200k", + 0.3988, + 9.528, + 9.489, + 23.9, + 23.8, + 11.1 + ], + [ + "fib-28", + 1.4419, + 99.875, + 99.836, + 69.3, + 69.2, + 12.1 + ], + [ + "nested-loops-300", + 0.2119, + 6.867, + 6.829, + 32.4, + 32.2, + 12.1 + ], + [ + "switch-dispatch-50k", + 0.1198, + 7.176, + 7.138, + 59.9, + 59.6, + 13.5 + ], + [ + "string-build-5k", + 0.1274, + 1.758, + 1.719, + 13.8, + 13.5, + 13.8 + ], + [ + "object-prop-20k", + 0.1464, + 4.5, + 4.462, + 30.7, + 30.5, + 11.5 + ], + [ + "array-ops-3k", + 0.2958, + 5.53, + 5.492, + 18.7, + 18.6, + 15 + ], + [ + "class-methods-2k", + 0.1132, + 2.75, + 2.712, + 24.3, + 24, + 18.3 + ] + ] + }, + { + "name": "low", + "bootstrapMs": 0.045, + "aggregateExecutionOverheadX": 50.5, + "worstExecutionOverheadX": 74.5, + "worstWorkload": "fib-28", + "workloads": [ + [ + "arith-loop-200k", + 0.3983, + 8.835, + 8.79, + 22.2, + 22.1, + 11.5 + ], + [ + "fib-28", + 1.4573, + 108.645, + 108.6, + 74.6, + 74.5, + 12.3 + ], + [ + "nested-loops-300", + 0.2156, + 6.921, + 6.876, + 32.1, + 31.9, + 12 + ], + [ + "switch-dispatch-50k", + 0.1172, + 7.474, + 7.429, + 63.8, + 63.4, + 13.7 + ], + [ + "string-build-5k", + 0.0873, + 1.607, + 1.561, + 18.4, + 17.9, + 13.9 + ], + [ + "object-prop-20k", + 0.1832, + 4.426, + 4.381, + 24.2, + 23.9, + 12.4 + ], + [ + "array-ops-3k", + 0.3125, + 5.332, + 5.287, + 17.1, + 16.9, + 14.6 + ], + [ + "class-methods-2k", + 0.1128, + 2.919, + 2.873, + 25.9, + 25.5, + 19 + ] + ] + }, + { + "name": "medium", + "bootstrapMs": 0.129, + "aggregateExecutionOverheadX": 52.6, + "worstExecutionOverheadX": 76.8, + "worstWorkload": "fib-28", + "workloads": [ + [ + "arith-loop-200k", + 0.3861, + 9.82, + 9.692, + 25.4, + 25.1, + 16 + ], + [ + "fib-28", + 1.4861, + 114.236, + 114.108, + 76.9, + 76.8, + 16.7 + ], + [ + "nested-loops-300", + 0.1568, + 7.434, + 7.305, + 47.4, + 46.6, + 16.7 + ], + [ + "switch-dispatch-50k", + 0.1275, + 7.942, + 7.813, + 62.3, + 61.3, + 18.4 + ], + [ + "string-build-5k", + 0.0816, + 1.866, + 1.737, + 22.9, + 21.3, + 18.8 + ], + [ + "object-prop-20k", + 0.1966, + 3.992, + 3.863, + 20.3, + 19.7, + 16.7 + ], + [ + "array-ops-3k", + 0.3901, + 6.283, + 6.154, + 16.1, + 15.8, + 20 + ], + [ + "class-methods-2k", + 0.1106, + 3.927, + 3.798, + 35.5, + 34.3, + 26 + ] + ] + }, + { + "name": "max", + "bootstrapMs": 0.246, + "aggregateExecutionOverheadX": 171.6, + "worstExecutionOverheadX": 206.4, + "worstWorkload": "fib-28", + "workloads": [ + [ + "arith-loop-200k", + 0.3406, + 42.15, + 41.904, + 123.7, + 123, + 39.2 + ], + [ + "fib-28", + 1.4569, + 300.903, + 300.657, + 206.5, + 206.4, + 37.2 + ], + [ + "nested-loops-300", + 0.1563, + 32.012, + 31.766, + 204.9, + 203.3, + 35.3 + ], + [ + "switch-dispatch-50k", + 0.1986, + 35.433, + 35.187, + 178.4, + 177.2, + 43.1 + ], + [ + "string-build-5k", + 0.0778, + 8.916, + 8.67, + 114.6, + 111.5, + 44.8 + ], + [ + "object-prop-20k", + 0.1421, + 22.299, + 22.053, + 157, + 155.2, + 39.1 + ], + [ + "array-ops-3k", + 0.3024, + 16.906, + 16.66, + 55.9, + 55.1, + 50.7 + ], + [ + "class-methods-2k", + 0.0976, + 19.133, + 18.887, + 196.1, + 193.6, + 58.3 + ] + ] + } + ], + "workloadTupleFields": [ + "name", + "nativeMs", + "protectedTotalMs", + "protectedExecutionMs", + "totalOverheadX", + "executionOverheadX", + "outputSizeKiB" + ] + }, + "log": "/tmp/ruam-baseline-bench.log" + }, + { + "command": "bun scripts/bench-attribution.mjs", + "cwd": "/packages/ruam", + "exitCode": 0, + "timingSeconds": { + "real": 19.49, + "user": 56.35, + "sys": 1.9 + }, + "result": { + "iterationsPerWorkload": 12, + "warmupIterations": 3, + "full": false, + "allCorrect": true, + "configurations": [ + [ + "default", + 36.7, + 51, + "switch-dispatch-15k" + ], + [ + "medium", + 38.6, + 42, + "arith-loop-40k" + ], + [ + "MAX (baseline)", + 112.1, + 152, + "switch-dispatch-15k" + ], + [ + "−stackEncoding", + 96.8, + 122, + "switch-dispatch-15k" + ], + [ + "−mixedBooleanArithmetic", + 110.6, + 222, + "switch-dispatch-15k" + ], + [ + "−observationResistance", + 111.3, + 191, + "switch-dispatch-15k" + ], + [ + "−opcodeMutation", + 132.6, + 240, + "switch-dispatch-15k" + ], + [ + "−incrementalCipher", + 86.5, + 109, + "switch-dispatch-15k" + ], + [ + "−semanticOpacity", + 134.8, + 253, + "switch-dispatch-15k" + ], + [ + "−vmShielding", + 127.9, + 250, + "switch-dispatch-15k" + ], + [ + "−deadCodeInjection", + 123.2, + 195, + "switch-dispatch-15k" + ], + [ + "−debugProtection", + 136.4, + 227, + "switch-dispatch-15k" + ], + [ + "−blockPermutation (sanity ~0)", + 123.1, + 188, + "switch-dispatch-15k" + ], + [ + "−integrityBinding", + 147.5, + 241, + "switch-dispatch-15k" + ], + [ + "−cache-trio (mut+inc+obs) [CACHE ON]", + 79.8, + 117, + "switch-dispatch-15k" + ], + [ + "−cache-trio −stackEncoding [CACHE ON]", + 30.9, + 63, + "switch-dispatch-15k" + ], + [ + "−cache-trio −proxy −MBA [CACHE ON]", + 33.7, + 41, + "arith-loop-40k" + ], + [ + "−cache-trio −proxy −MBA −semOpacity", + 38.3, + 53, + "switch-dispatch-15k" + ] + ], + "configurationTupleFields": [ + "name", + "aggregateExecutionOverheadX", + "worstExecutionOverheadX", + "worstWorkload" + ] + }, + "log": "/tmp/ruam-baseline-bench-attribution.log" + }, + { + "command": "node scripts/collect-stats.mjs --all", + "cwd": "/packages/ruam", + "exitCode": 0, + "timingSeconds": { + "real": 14.98, + "user": 26.17, + "sys": 1.8 + }, + "result": { + "version": "2.0.0", + "collectedAt": "2026-07-24T23:26:39.895Z", + "opcodes": { + "count": 324, + "categories": 25, + "superinstructions": 34, + "compounds": 38, + "slotOpcodes": 10 + }, + "source": { + "files": 91, + "lines": 32692, + "testFiles": 45, + "testLines": 18220, + "totalFiles": 136, + "totalLines": 50912, + "templateFiles": 0, + "visitorFiles": 4 + }, + "tests": { + "total": 2328, + "passed": 2328, + "failed": 0, + "suites": 44, + "durationMs": 13120 + }, + "performance": { + "weightedAvg": 25.5, + "median": 40.9, + "fastest": { + "name": "try/catch", + "multiplier": 5.2 + }, + "slowest": { + "name": "fibonacci (n=20)", + "multiplier": 79.9 + }, + "workloadCount": 10, + "workloads": [ + { + "name": "arithmetic loop (10k)", + "multiplier": 14.4, + "nativeMs": 0.077, + "vmMs": 1.106 + }, + { + "name": "fibonacci (n=20)", + "multiplier": 79.9, + "nativeMs": 0.08, + "vmMs": 6.394 + }, + { + "name": "array ops", + "multiplier": 23.1, + "nativeMs": 0.059, + "vmMs": 1.375 + }, + { + "name": "string ops", + "multiplier": 50.4, + "nativeMs": 0.01, + "vmMs": 0.496 + }, + { + "name": "object creation", + "multiplier": 40.9, + "nativeMs": 0.015, + "vmMs": 0.622 + }, + { + "name": "closures + HOF", + "multiplier": 73.6, + "nativeMs": 0.008, + "vmMs": 0.557 + }, + { + "name": "class + methods", + "multiplier": 58.5, + "nativeMs": 0.014, + "vmMs": 0.84 + }, + { + "name": "nested loops", + "multiplier": 13.1, + "nativeMs": 0.112, + "vmMs": 1.464 + }, + { + "name": "switch dispatch", + "multiplier": 30.4, + "nativeMs": 0.029, + "vmMs": 0.882 + }, + { + "name": "try/catch", + "multiplier": 5.2, + "nativeMs": 0.17, + "vmMs": 0.874 + } + ] + }, + "size": { + "sampleInputBytes": 110, + "low": { + "bytes": 12842, + "ratio": 116.7 + }, + "medium": { + "bytes": 18848, + "ratio": 171.3 + }, + "high": { + "bytes": 12450, + "ratio": 113.2 + } + }, + "heroSnippet": { + "head": [ + "var MJi = Object.prototype.hasOwnProperty,", + " IpS = Math.imul,", + " M1Y = Object.create(null),", + " Ed0 = !(typeof globalThis === 'undefined') ? globa...", + " gTs = 'avCIAmHKykW2TeuYscFZSgMzp_E0lih7boDqOL61n4x..." + ], + "totalLines": 627, + "tail": [ + "function fibonacci(...__args) {", + " var _n = __args.length | 0;", + " return wVi(\"ugrvw\", __args, kNK, this);", + "}" + ] + }, + "badges": { + "tests": "2,328", + "testsPassing": "2,328 passing", + "opcodes": "324", + "categories": "25", + "loc": "32.7k", + "totalLoc": "50.9k", + "overhead": "~25.5x", + "overheadMedian": "~40.9x", + "sizeRatioLow": "116.7x", + "sizeRatioHigh": "113.2x" + } + }, + "log": "/tmp/ruam-baseline-stats.log", + "generatedStats": "/tmp/ruam-baseline-generated-stats.json", + "generatedTestResults": "/tmp/ruam-baseline-generated-test-results.json" + } + ], + "knownIssues": [ + { + "script": "scripts/collect-stats.mjs", + "severity": "invalid-metric", + "detail": "The size collector requests preset 'high', but the public PresetName and PRESETS table only support low, medium, and max. Runtime object spread of undefined silently falls back to default-like options, so result.size.high and the 'high preset' badge are not max-preset measurements.", + "evidence": { + "requested": "obfuscateCode(sampleCode, { preset: \"high\" })", + "validPresets": [ + "low", + "medium", + "max" + ] + } + }, + { + "script": "scripts/bench-attribution.mjs", + "severity": "measurement-noise", + "detail": "Each configuration is compiled with fresh cryptographic entropy and measured only once at 12 iterations by default. Several removals report higher overhead than MAX, including options expected to be near-zero runtime cost. Use repeated seeded runs and confidence intervals before treating feature deltas as causal." + }, + { + "script": "scripts/bench.mjs", + "severity": "measurement-noise", + "detail": "Protected outputs use fresh entropy and --quick uses 30 timing iterations, so exact overhead and output-size values are run-specific. All eight workload correctness checks passed for every preset in this run." + }, + { + "script": "scripts/collect-stats.mjs", + "severity": "behavior-documentation-mismatch", + "detail": "The script runs benchmarks whenever dist/index.js exists, even without --bench, despite its usage text describing cached benchmark results for the default invocation." + } + ], + "rawEvidenceSha256": { + "/tmp/ruam-baseline-environment.log": "2144d354ece9aee40b9c26bdb9120735628125b0e275964bab89885b0e28caa3", + "/tmp/ruam-baseline-typecheck.log": "695148545ac172a5c5bcab1450e90bee00bc2e720314b73291d3b11b0d62", + "/tmp/ruam-baseline-test.log": "d07167bd1ab8931664f6ce8df1105fdfe1de689a01a89d0dfc4abaaef1c1b514", + "/tmp/ruam-baseline-build.log": "184fea6898391a5ea558c900203a39a2bd85f98db82090a573beddd7d3420789", + "/tmp/ruam-baseline-bench.log": "f7906a180070727e21e8cfac3a956c6ee745186b11a5e0721db863090f837e11", + "/tmp/ruam-baseline-bench-attribution.log": "c8b4a2a2b0adf77b03993180aaa434dc19ee6651784783867ef8bfbaf6405b5c", + "/tmp/ruam-baseline-stats.log": "7fd59a1af8e52e34acd42006c96fa2e5d3fa812e39ab1b98bbd70a55f652e6ae", + "/tmp/ruam-baseline-generated-stats.json": "458ec2bdb8efd1b81304324abd7852c5f5f59e427c807c2a6dbab97cd7f88691" + }, + "mainTrackedWorktreeCleanAfterCollection": true +} diff --git a/docs/superpowers/baselines/2026-07-24-legacy-vm-baseline.md b/docs/superpowers/baselines/2026-07-24-legacy-vm-baseline.md new file mode 100644 index 0000000..22c996b --- /dev/null +++ b/docs/superpowers/baselines/2026-07-24-legacy-vm-baseline.md @@ -0,0 +1,39 @@ +# Ruam legacy baseline for Traveling Isogloss + +Baseline commit: `e8cecb56ba89d47512a16e325d81cf46f64b2ecb` (`docs: plan full traveling isogloss replacement`). This is the last commit before production implementation began. Measurements were repeated from a detached worktree after a parallel implementation commit landed during the initial pass. + +## Status + +| Check | Result | Wall time | +|---|---:|---:| +| `bun run typecheck` | pass, 0 diagnostics | 2.20 s | +| `bun test` | 2,328 pass, 0 fail, 4,819 assertions, 44 files | 11.38 s | +| `bun run build` | pass | 3.09 s | +| `bun scripts/bench.mjs --quick` | 32/32 workload/preset correctness checks pass | 48.39 s | +| `bun scripts/bench-attribution.mjs` | 18/18 configuration correctness checks pass | 19.49 s | +| `node scripts/collect-stats.mjs --all` | pass in isolated copy | 14.98 s | + +Environment: Apple M5 (10 logical CPUs, 24 GiB), arm64 macOS/Darwin 25.3.0, Bun 1.3.11, Node v25.6.1, TypeScript 5.9.3, tsup 8.5.1. + +## Runtime baseline + +The dedicated quick harness reports aggregate execution-only overhead of **48.2x default**, **50.5x low**, **52.6x medium**, and **171.6x max**. Bootstrap medians were 0.038 ms, 0.045 ms, 0.129 ms, and 0.246 ms respectively. Worst execution-only overhead was 69.2x default, 74.5x low, 76.8x medium, and 206.4x max. + +The test-suite performance group independently reported 41.1x weighted average and 40.8x median overhead (21.6x fastest, 102.5x slowest). The stats collector reported 25.5x weighted average and 40.9x median across its own smaller ten-workload suite. These harnesses use different workloads and timing methods, so their values should not be combined. + +## Output-size baseline + +The dedicated harness emitted 11.1–18.3 KiB per default workload, 11.5–19.0 KiB at low, 16.0–26.0 KiB at medium, and 35.3–58.3 KiB at max. + +The built package totals 570,393 bytes: main shared chunk 535,347 bytes (100,251 gzip), CLI 23,611 bytes (6,076 gzip), entry 285 bytes (156 gzip), and declarations 11,150 bytes. + +For the stats collector's 110-byte Fibonacci sample: low is 12,842 bytes (116.7x), medium is 18,848 bytes (171.3x), and its field named `high` is 12,450 bytes (113.2x)—but that final metric is invalid for max because the script passes unsupported preset `high`. + +## Script findings + +- `collect-stats.mjs` silently mislabels a default-like build as “high”; valid presets are `low`, `medium`, and `max`. Its high/max size badge is not authoritative. +- Benchmark outputs are entropy- and timing-sensitive. The attribution harness uses one fresh randomized build per configuration and only 12 measured iterations, producing implausible negative feature costs for several flags. It is a smoke/attribution signal, not a stable causal estimate. +- `collect-stats.mjs` benchmarks whenever `dist/index.js` exists, even without `--bench`, contrary to its usage text. +- No command crashed or produced a correctness mismatch in the authoritative run. + +Machine-readable details, every workload/configuration, exact timings, output hashes, and raw-log hashes are in `/tmp/ruam-isogloss-baseline.json`. The main tracked worktree was clean after collection. diff --git a/docs/superpowers/specs/2026-07-24-isogloss-test-migration-matrix.md b/docs/superpowers/specs/2026-07-24-isogloss-test-migration-matrix.md new file mode 100644 index 0000000..1b05c8d --- /dev/null +++ b/docs/superpowers/specs/2026-07-24-isogloss-test-migration-matrix.md @@ -0,0 +1,263 @@ +# Traveling Isogloss — Test Migration and Legacy-Deletion Matrix + +**Date:** 2026-07-24 +**Status:** Phase 0 inventory +**Scope:** Every file under `packages/ruam/test/` and every script under `packages/ruam/scripts/` +**Parent plan:** [Traveling Isogloss — Rank 1 Implementation Plan](2026-07-24-traveling-isogloss-implementation-plan.md) + +## 1. Classification contract + +Every current test suite has one primary cutover classification: + +| Code | Classification | Required cutover action | +|---|---|---| +| **C1** | JavaScript semantic correctness | Port unchanged in behavioral intent to native JavaScript vs TypeScript reference BCM vs emitted BCM. VM terminology and VM-only options are removed. | +| **C2** | Generic protection, code-generation, packaging, or performance property | Rewrite against Isogloss, the lattice artifact, or engine-independent runtime infrastructure. Do not preserve the legacy mechanism merely to preserve the test. | +| **C3** | VM-mechanism-only | Delete with the VM mechanism, but first extract any unique JavaScript program shape or generic security assertion identified in this matrix. | + +Primary classification is by the reason the file exists. Several files contain mixed sections; those sections have explicit split dispositions below. A C3 file may not be deleted merely because its named mechanism is gone: every unique semantic fixture listed in Section 7 must first appear in a C1 Isogloss suite. + +## 2. Inventory result + +The current tree contains: + +- **46 Bun test-suite files** (`*.test.ts`); +- **5 test support/fixture files**; +- **5 package scripts**; +- **56 total inventoried files**. + +Primary classification of the 46 suite files: + +| Classification | Suite files | Share | +|---|---:|---:| +| C1 — semantic correctness | 23 | 50.0% | +| C2 — generic/rewrite | 16 | 34.8% | +| C3 — VM-only/delete after extraction | 7 | 15.2% | +| **Total** | **46** | **100%** | + +At validation time there are 1,247 lexical `it(` call sites in the 46 suite files. This is a sizing indicator, not the runtime test count: several suites generate cases in loops and the two large JavaScript fixtures run their own assertion catalogs. + +### Common path exercised by C1 end-to-end tests + +Most C1 suites call `test/helpers.ts`, which currently reaches: + +```text +test/helpers.ts + → src/transform.ts + → src/compiler/index.ts + → src/compiler/{emitter,scope,capture-analysis,optimizer}.ts + → src/compiler/visitors/{expressions,statements,classes}.ts + → src/ruamvm/assembler.ts + → src/ruamvm/builders/{loader,runners,interpreter,deserializer}.ts + → src/ruamvm/handlers/*.ts +``` + +After cutover, the permanent oracle path must be: + +```text +test/helpers.ts + ├→ native JavaScript + ├→ canonical semantic IR evaluator + ├→ src/isogloss/reference-runtime.ts + └→ emitted BCM runtime +``` + +The VM may remain a migration-only fourth oracle before Phase 10. It must not remain in the final helper or product path. + +## 3. C1 — JavaScript semantic suites to port + +All files in this table remain active tests. Their assertions should be preserved unless an assertion itself encodes a VM mechanism. + +| Existing suite | Current production modules primarily exercised | Isogloss port target and required additions | +|---|---|---| +| `test/core/advanced.test.ts` | All compiler visitors; `handlers/{calls,classes,functions,objects,scope,special,type-ops}.ts` | `test/isogloss/runtime-core.test.ts` and `runtime-classes.test.ts`; retain all advanced expressions, prototypes, classes, and built-in interaction cases. | +| `test/core/arithmetic.test.ts` | `visitors/expressions.ts`; `handlers/{arithmetic,comparison,logical,mutation,type-ops}.ts` | Semantic-handler differential corpus plus `runtime-core.test.ts`; each `SemanticOp` family must also have a signature-table case. | +| `test/core/arrays.test.ts` | `visitors/{expressions,statements}.ts`; `handlers/{objects,iterators,destructuring,calls,special}.ts` | `runtime-core.test.ts`; add sparse-array, iterator-close, and accessor-reentrancy event logs. | +| `test/core/async.test.ts` | Async compilation in `compiler/index.ts`; `handlers/generators.ts` `AWAIT`; async interpreter/runners | `runtime-async.test.ts`; keep all 16 value cases, then add scheduling, interleaving, rejection recovery, and single-carrier assertions described in Section 8. | +| `test/core/basic.test.ts` | Target selection, function compilation, constants, stack/register handlers, return | Phase 3 reference-runtime smoke suite and `runtime-core.test.ts`. | +| `test/core/closures-basic.test.ts` | `capture-analysis.ts`, `scope.ts`; `handlers/{functions,scope,registers}.ts` | `runtime-closures.test.ts`; assert escaped closures retain their originating root group. | +| `test/core/closures-scope.test.ts` | Capture analysis, lexical scope chain, child units, call/function/scope handlers | `runtime-closures.test.ts`; add calls before and after unrelated carrier evolution. | +| `test/core/control-flow-basic.test.ts` | `visitors/statements.ts`; `handlers/control-flow.ts`; optimizer jump handling | Phase 2 CFG/lattice fixtures, `cfg-bisimulation.test.ts`, and `runtime-control-flow.test.ts`. | +| `test/core/control-flow.test.ts` | Full statement visitor, exception tables, jump patching, `handlers/{control-flow,exceptions,iterators}.ts` | Primary CFG-bisimulation and runtime-control-flow corpus. Preserve every nested `finally`, labeled break/continue, switch, and loop shape. | +| `test/core/destructuring.test.ts` | `visitors/{expressions,statements}.ts`; `handlers/destructuring.ts` | `runtime-core.test.ts`; operand-reservoir tests must include defaults, rest, holes, and computed keys. | +| `test/core/edge-cases.test.ts` | Parser plugins, target selection, expressions/statements, calls/special handlers, dynamic import harness | `runtime-core.test.ts` plus environment integration. Preserve dynamic import behavior but separate it from CSP proof. | +| `test/core/exceptions.test.ts` | Exception regions, `handlers/exceptions.ts`, interpreter catch routing | `runtime-exceptions.test.ts`; add post-uncaught-error carrier validity and exact event-order comparison. | +| `test/core/functions.test.ts` | Function/arrow/default/rest/arguments compilation; calls/functions/scope handlers | `runtime-core.test.ts`, `runtime-closures.test.ts`, and `runtime-reentrancy.test.ts`; recursion cases become single-carrier assertions. | +| `test/core/indexed-slots.test.ts` | Despite its name, current `capture-analysis.ts`, `scope.ts`, register promotion, scope-chain handlers, classes and catch scoping | Rename away from “indexed slots” and port the complete lexical-scope corpus to `runtime-closures.test.ts`; do not recreate VM slots. | +| `test/core/objects.test.ts` | Object/class expression visitors; `handlers/{objects,classes,calls,mutation,type-ops}.ts` | `runtime-core.test.ts`, `runtime-classes.test.ts`, and accessor-reentrancy cases. | +| `test/core/strings.test.ts` | Expression compiler, constant pool, calls/objects/type-ops handlers | Semantic-handler differential corpus and `runtime-core.test.ts`; keep Unicode and coercion cases independent of artifact string protection. | +| `test/integration/chrome-ext-patterns.test.ts` | `transform.ts`; async/class/call handlers; Node `vm` harness | Port as async/class semantics. It is **not** currently a browser-extension or CSP execution test; add real worker/MV3-style execution separately. | +| `test/integration/ruam-tester-lite.test.ts` | Full end-to-end pipeline over `RuamTesterLite.js` | Keep native/default semantic smoke cases. Delete the `vmShielding` case (C3). Rewrite the max-preset case for the Isogloss max profile (C2). | +| `test/integration/ruam-tester.test.ts` | Full end-to-end pipeline over `RuamTester.js` | Retain as the broad semantic acceptance fixture; compare native, reference BCM, and emitted BCM summaries. | +| `test/stress/randomized.test.ts` | Broad compiler/handler surface; rolling-cipher, integrity, and preset sections | Port arithmetic through exception and deep-nesting generators to deterministic Isogloss seed stress. Delete the rolling-cipher section (C3); rewrite integrity/preset sections (C2). Replace `Math.random()` with logged deterministic data seeds. | +| `test/stress/repro-exception.test.ts` | `transform.ts`; try/catch property-name/scoping collision; runtime exception route | Add verbatim to `runtime-exceptions.test.ts` and the 1,024-seed release tier. | +| `test/stress/stress-breaker.test.ts` | Capture/scope, `this`, exceptions, mutation, recursion, iterators, constructors, coercion | Split among closure, class, exception, reentrancy, and core Isogloss suites. Preserve all 51 cases. | +| `test/stress/vm-breaker.test.ts` | Broad semantic compiler and handler catalog | Rename to engine-neutral semantic adversarial tests and preserve all 70 cases. The “generator-like state machines” section is ordinary manual state-machine code and does **not** cover JavaScript generators. | + +### C1 source-to-target ownership + +| Current semantic implementation | Existing coverage | Target ownership | +|---|---|---| +| `compiler/visitors/expressions.ts` | Arithmetic, arrays, objects, strings, functions, advanced, breakers | `compiler/ir.ts`, `compiler/semantic-ops.ts`, and handler differential suites | +| `compiler/visitors/statements.ts` | Control flow, exceptions, arrays/iterators, breakers | `compiler/cfg.ts`, `cfg-bisimulation.test.ts`, runtime control/exception suites | +| `compiler/visitors/classes.ts` | Advanced, objects, Chrome-extension patterns, breakers | `runtime-classes.test.ts` | +| `compiler/capture-analysis.ts` and `compiler/scope.ts` | Closure, indexed-slots, function, breaker suites | `runtime-closures.test.ts` and reentrancy suite | +| `ruamvm/handlers/*.ts` language behavior | All C1 suites | Move to `runtime/handlers/*.ts`; direct native differential test for every semantic family | +| VM interpreter/runners/loader | Incidental coverage in every C1 suite | Replaced by reference and emitted BCM oracles; no VM-specific assertion survives | + +## 4. C2 — Generic properties to rewrite for Isogloss + +| Existing suite | Current production modules primarily exercised | Required rewrite | +|---|---|---| +| `test/core/deterministic-entropy.test.ts` | `src/testing.ts`, `random/entropy.ts`, and the complete transform path | Retain as the deterministic build foundation. Rename its helper for `protectCode`, remove legacy cipher options, add stable output/certificate assertions, and keep labeled stream-isolation coverage. | +| `test/isogloss/semantic-signatures.test.ts` | Transitional `compiler/{opcodes,semantic-ops,semantic-signatures,ir}.ts` | Retain and strengthen. The current `SemanticOp === Op` alias assertions are migration-only and must be deleted with `opcodes.ts`; add exhaustiveness, exact signature facts, dynamic stack effects, and a proof that no VM-only `MUTATE` semantic remains at cutover. | +| `test/naming/ast-integration.test.ts` | `naming/{registry,scope,token}.ts`; `ruamvm/{nodes,emit}.ts` | Repoint to `runtime/{nodes,emit}.ts`; preserve NameToken-to-AST integration. | +| `test/naming/registry.test.ts` | `naming/{registry,scope,token,reserved}.ts` | Retain and add Isogloss scope/claim collision cases plus deterministic stream isolation. | +| `test/naming/setup.test.ts` | `naming/{setup,claims,compat-types}.ts` | Rewrite `setupRegistry` expectations for BCM names. Delete `setupShieldedRegistry` assertions; root groups are not shielding. | +| `test/ruamvm/emit.test.ts` | `ruamvm/{nodes,emit}.ts` | Move unchanged in intent to `test/runtime/emit.test.ts` against `runtime/{nodes,emit}.ts`. Remove `debuggerStmt` from the production-capable API or prove it cannot enter output. | +| `test/ruamvm/transforms.test.ts` | `ruamvm/{nodes,emit,transforms}.ts` | Move only engine-independent AST transforms to `runtime/`; rewrite names and import boundaries. | +| `test/security/anti-reversing.test.ts` | `transform.ts`, `compiler/{opcodes,encode}.ts`, interpreter table, naming, string encoding | Replace opcode-array/function-table regexes with `novelty-invariants.test.ts` and a format-aware `static-extractor.test.ts`. Keep plaintext absence and per-build variation only where the Isogloss artifact makes the same claim. | +| `test/security/debug-protection.test.ts` | `ruamvm/builders/debug-protection.ts`, assembler, presets, naming | The option is removed at cutover, then redesigned. Preserve the generic CSP/no-eval/no-`new Function`/no-`debugger` requirements in a runtime-wide CSP suite. Rewrite feature-specific assertions only when the engine-independent replacement exists. | +| `test/security/feature-combinations.test.ts` | `presets.ts`, `types.ts`, `transform.ts`, nearly all legacy hardening modules, preprocessing/naming | Preserve the compact language fixtures. Rebuild the pair/triple matrix only from Isogloss-native Phase 9 options and add metadata-driven coverage. | +| `test/security/kerckhoffs-hardening.test.ts` | Incremental cipher, semantic opacity, observation resistance through the full pipeline | Preserve the principle and language fixtures, not the three VM mechanisms. Rewrite as a known-design Isogloss hardening/novelty suite. | +| `test/security/new-features.test.ts` | `polymorphic-decoder.ts`, `string-atomization.ts`, `scattered-keys.ts`, `block-permutation.ts`, `opcode-mutation.ts` | Split: decoder and string atomization become later engine-independent artifact/runtime tests; scattered keys, block permutation, and opcode mutation are C3 deletions; combined cases become Isogloss hardening-profile tests. | +| `test/security/observation-resistance.test.ts` | `ruamvm/observation-resistance.ts`, interpreter witnesses/canaries/probes, legacy feature interactions | Redesign as BCM observation-resistance tests only after a BCM-specific threat model exists. Preserve its language fixtures in C1 meanwhile. | +| `test/security/semantic-opacity.test.ts` | `opaque-predicates.ts`, `handler-aliasing.ts`, `mba.ts`, interpreter builder | Reuse proven predicate/alias properties for `isogloss/constraints.test.ts` and semantic-alias tests. Do not preserve the VM handler-table injection path. Replace test-only `new Function` predicate evaluation with an AST-evaluated or isolated CSP-neutral oracle. | +| `test/security/string-encoding.test.ts` | `compiler/encode.ts`, runtime decoder/deserializer, constants | Rewrite as Isogloss artifact string round-trip and plaintext-inspection coverage. Preserve ASCII, Unicode, special-character, long-string, property-name, regex, error, closure, and class fixtures. | +| `test/stress/performance.test.ts` | `index.ts`, complete transform/runtime, Node `vm` | Convert to a budgeted Isogloss performance smoke test or move measurements into scripts. It currently always passes regardless of speed and therefore enforces no release gate. | + +## 5. C3 — VM-mechanism suites to delete after extraction + +| Existing suite | VM modules exercised | What must be extracted before deletion | +|---|---|---| +| `test/security/bytecode-scatter.test.ts` | `ruamvm/bytecode-scatter.ts`, `ruamvm/emit.ts` | No bytecode-scatter logic survives. Its fragment round-trip concept may inform `artifactScattering`, but there is no required code port. | +| `test/security/decode-cache.test.ts` | `builders/loader.ts`, `builders/interpreter.ts`, rolling/incremental cipher gates, opcode mutation and observation resistance | Port the control-flow-heavy `PROGRAMS` corpus, especially repeated calls, backward jumps, mutual recursion, and nested return-through-finally. Delete all cache-active/cache-disabled assertions. | +| `test/security/incremental-cipher.test.ts` | `compiler/{incremental-cipher,basic-blocks,opcodes}.ts`, `BytecodeUnit`, runtime incremental decoder | Port unique end-to-end async/control/exception fixtures; delete block-key, epoch, encryption, and instruction-array assertions. | +| `test/security/rolling-cipher.test.ts` | `compiler/{rolling-cipher,encode}.ts`, runtime rolling decoder, integrity binding, bytecode format, dead-code injection | Extract the unique semantics and generic artifact-integrity/plaintext properties listed in Section 7. Delete rolling-key, instruction encryption, binary-bytecode, and cipher-combination assertions. | +| `test/security/slot-save-restore.test.ts` | `compiler/{slot-analysis,opcodes}.ts`, VM handler AST registry, VM interpreter hoisted slots | Port its recursion/exception/`this` end-to-end programs to single-carrier reentrancy tests. Delete slot-set introspection and VM snapshot/restore assertions. | +| `test/security/vm-shielding.test.ts` | Shielded branch in `transform.ts`, `ruamvm/assembler.ts`, per-group opcode shuffles/ciphers, shielded naming | Port independent-root, cross-function, escaped-closure, async, and class fixtures to root-group/carrier tests. Delete shuffle, auto-cipher, and shielding-option assertions. | +| `test/stress/opcode-mutation-controlflow.test.ts` | `compiler/{opcode-mutation,block-permutation,rolling-cipher}.ts`, mutable VM handler table | Port all three programs to `cfg-bisimulation`, exception, and fixed-seed stress suites. Delete only the mechanism/options. | + +## 6. Test support and fixture files + +| File | Classification | Disposition | +|---|---|---| +| `test/helpers.ts` | C1/C2 foundation | Replace `VmObfuscationOptions` and `evalObfuscated()` with native/reference/emitted BCM helpers. Add value, error, event-order, carrier-evolution, and fixed-entropy APIs. VM oracle access must live in a migration-only helper deleted at Phase 10. | +| `test/RuamTester.js` | C1 fixture | Retain the semantic catalog, rename VM-oriented comments, and run through all permanent oracles. | +| `test/RuamTesterLite.js` | C1 fixture | Retain as the fast smoke fixture, rename VM-oriented comments, and run through reference and emitted BCM. | +| `test/RuamTesterLiteO.js` | C3 generated legacy artifact | It is not referenced by any test and has no recorded seed/provenance. Move to the frozen Phase 0 legacy-output archive with metadata if it is useful; otherwise delete at cutover. Never use it as an Isogloss oracle. | +| `test/webpack-scope-repro.js` | C1 dormant fixture | It is currently unreferenced. Add a Bun integration wrapper and port it as class field, closure, module-factory, getter-export, and source-selection coverage. Replace its uncontrolled `Math.random()` use for deterministic testing. | + +## 7. Mandatory semantic extraction before C3 deletion + +These cases are easy to lose because they currently live inside mechanism suites. + +| Source suite/section | Destination before deletion | +|---|---| +| `decode-cache`: backward loop, nested forward jumps, switch+continue, try/catch/finally routes, recursion, labeled break/continue | `runtime-control-flow.test.ts`, `runtime-exceptions.test.ts`, `persistence.test.ts`, and seed-stress curated fixtures | +| `incremental-cipher` end-to-end: async function and any unique exception/block-boundary programs | `runtime-async.test.ts`, `runtime-exceptions.test.ts`, `cfg-bisimulation.test.ts` | +| `rolling-cipher` correctness: deep closures, class inheritance, many/rest parameters, multiple independent roots, recursive closures, thrown exit, async, optional-access Chrome pattern | Corresponding C1 runtime suites | +| `rolling-cipher` integrity/plaintext sections | `format-roundtrip.test.ts`, `novelty-invariants.test.ts`, and future lattice/runtime integrity tests; preserve the property, not cipher constants | +| `rolling-cipher` dead-code section: nested finally, multiple returns, switch returns | `runtime-exceptions.test.ts` and `runtime-control-flow.test.ts` | +| `slot-save-restore` end-to-end: recursion, nested exceptions, `this`/`new.target` context | `runtime-reentrancy.test.ts`, `runtime-exceptions.test.ts`, `runtime-classes.test.ts` | +| `vm-shielding`: independent roots, cross-root calls, shared closure, async root, classes | `pipeline/groups` tests, root-group carrier-isolation tests, closure and async suites | +| `opcode-mutation-controlflow`: all three regression programs | Fixed-seed CFG, exception, and topology stress corpus | +| `new-features`: block-permutation and opcode-mutation semantic programs | C1 control-flow/exception/core suites before their mechanism sections are deleted | +| `randomized`: rolling-cipher section | Delete; semantic generators already remain. Any distinct fixture discovered during porting is moved to the engine-neutral randomized corpus. | + +No C3 deletion commit is complete until an automated fixture manifest maps each row above to its new test file. + +## 8. Coverage gaps against the implementation plan + +### P0 — release-blocking gaps + +| Gap | Existing evidence | Missing authoritative evidence | +|---|---|---| +| **Real JavaScript generators** | No test or fixture under `packages/ruam/test` contains `function*` or an executed `yield`. `test/stress/vm-breaker.test.ts` only tests hand-written “generator-like” state machines. `ruamvm/handlers/generators.ts` implements generator lifecycle operations as stubs. | `runtime-generators.test.ts` covering `next`, sent values, `yield*`, `throw`, `return`, `finally`, escaped generators, multiple parked generators in one root group, async generators, abandonment cleanup, and carrier uniqueness. Generator support is currently **unproven**. | +| **Reference BCM oracle** | `test/helpers.ts` compares only native and emitted legacy VM values. | Native vs canonical IR evaluator vs reference BCM vs emitted BCM, including minimized reproducible state diagnostics. | +| **Carrier correctness/persistence** | Decode-cache tests repeat calls, but only validate stable VM decoded instructions. | Carrier changes after every semantic transition, remains one-per-root-group, persists across successful/throwing calls, remains valid after uncaught errors, and never grows append-only history. | +| **Reentrancy** | Scattered getters/setters, `valueOf`, recursion, and callback tests exercise JavaScript semantics but do not intentionally call back into the same protected root while a handler is active. | Dedicated accessor, Proxy trap, `Symbol.toPrimitive`, `valueOf`, user callback, constructor, and cross-root reentry cases with frame/resume-gate and one-live-carrier assertions. | +| **Adversarial static extractor** | `anti-reversing.test.ts` uses output regexes for numeric arrays, strings, names, and a giant switch. | Full-format parser/deserializer with knowledge of resolver, catalog, reservoirs, and topology; TI-01 through TI-07 checks; site-to-handler recovery attempt; lineage-transfer and next-lineage-prediction measurements. | +| **No-legacy cutover proof** | No current test inventories imports, package contents, CLI aliases, types, flags, loaders, or fallback paths. | `migration/no-legacy-vm.test.ts` and package-surface inventory enforcing TI-14. | +| **Verifier/property coverage** | Existing semantic tests only show executions that happened to run. | Certificate, constraint, candidate-mask cardinality, non-boundary uniqueness, operand-reuse, cell-reuse, phase-cycle, CFG-bisimulation, and finite verifier-state property suites. | + +### P1 — required before async/browser/performance gates + +| Gap | Existing evidence | Required addition | +|---|---|---| +| **Async scheduling and interleaving** | `core/async.test.ts` has 16 useful value cases; `chrome-ext-patterns.test.ts` has 11 realistic async/class cases. Most assert only final values. | Side-effect event logs for two interleaved calls, nested awaits, rejection then later success, callbacks that reenter, host microtask order, continuation cleanup, and an assertion that Ruam adds no queue. | +| **CSP/environment execution** | `debug-protection.test.ts` checks strings for `debugger`, `eval`, and `new Function`. `chrome-ext-patterns.test.ts` executes in Node `vm`, not Chrome. `build-browser.mjs` bundles a worker but does not execute a CSP fixture. | Actual Node, browser page, dedicated worker, and MV3/service-worker-style fixtures under a restrictive CSP; scan and execute production output; verify no network/storage/timer dependency is required. | +| **Performance release budgets** | `stress/performance.test.ts` is informational and ends with `expect(true)`. `bench.mjs` measures native vs total/boot/steady execution and size. `bench-attribution.mjs` only removes legacy VM features. | Versioned JSON; build-phase timings; payload/runtime/sidecar bytes; bootstrap/first/repeated call; recursion, loop, property, exception, async-interleave workloads; peak/retained memory; enforced median/P95 release thresholds from the plan. | +| **Deterministic fuzzing and seeds** | `core/deterministic-entropy.test.ts` now proves fixed-build reproduction and labeled stream isolation. `randomized.test.ts` still uses unseeded `Math.random()`, and the stress corpus does not yet record a reproducible build/data seed pair. | Use `src/testing.ts` fixed entropy plus a separate deterministic data seed; print both on failure; enforce 8/32/256/1,024 seed tiers. | +| **Errors as observable behavior** | Helpers generally compare values with `toEqual`; exception suites do not provide a common error oracle. | Compare error constructor/name/message where specified, explicit side-effect log, thrown value identity where required, and post-error carrier state. | +| **Owner sidecar and runtime trace** | No current coverage. | Sidecar schema/round-trip, absence from production payload, event decoding, source-span mapping, runtime-trace scheduling neutrality, and security/performance exclusion. | +| **Removed-option and metadata drift** | Feature suites pass legacy option objects directly. The CLI and manifest are not cross-checked. | Removed-option tombstone tests, generalized metadata/CLI/preset/manifest drift tests, unknown nested-property rejection, and pre-parse failure evidence. | +| **Frozen Phase 0 outputs** | `RuamTesterLiteO.js` is one unreferenced generated artifact without recorded seed or provenance. | Versioned output/measurement fixtures for core, closure, exception, class, async, generator-known-unsupported baseline, and max preset, each with source, options, seed, tool revision, and checksum. | + +## 9. Required new suite map + +| Planned suite | Best reusable current input | Net-new requirement | +|---|---|---| +| `test/isogloss/certificate.test.ts` | None | Certificate counts/digest/schema and encoded-lattice match | +| `test/isogloss/cfg-bisimulation.test.ts` | Core control-flow plus extracted C3 regressions | Mechanical edge-by-edge CFG/refold equivalence | +| `test/isogloss/constraints.test.ts` | Semantic-opacity predicate/alias ideas | TI-02 through TI-05, UNSAT diagnostics, attempt budgets | +| `test/isogloss/format-roundtrip.test.ts` | String encoding and generic fragment round trips | Isogloss envelope only; absence of legacy payload variants | +| `test/isogloss/novelty-invariants.test.ts` | Generic claims from anti-reversing | TI-01 through TI-07 and TI-12 architecture checks | +| `test/isogloss/operands.test.ts` | Destructuring/constants/register semantic fixtures | Projection uniqueness, reservoir reuse, decoys, no site-local operand | +| `test/isogloss/persistence.test.ts` | Decode-cache repeated-call programs | Evolved lineage/carrier state across return and throw | +| `test/isogloss/reference-runtime.test.ts` | Core basic/arithmetic/control fixtures | Canonical IR vs reference BCM with diagnostic state | +| `test/isogloss/runtime-async.test.ts` | Core async and Chrome-extension fixtures | Interleaving, order, cleanup, one carrier | +| `test/isogloss/runtime-classes.test.ts` | Objects, advanced, breakers | `super`, home object, computed methods, `new.target`, reentry | +| `test/isogloss/runtime-closures.test.ts` | Closure and indexed-slots suites | Evolved root-group state and escaped closures | +| `test/isogloss/runtime-control-flow.test.ts` | Both control-flow suites plus C3 extracted programs | Carrier phase/variant movement through loops | +| `test/isogloss/runtime-core.test.ts` | Core basic/arithmetic/arrays/objects/strings/functions | Native/reference/emitted permanent oracle | +| `test/isogloss/runtime-exceptions.test.ts` | Exception/control-flow/repro suites plus C3 programs | Non-transactional motion and post-uncaught recovery | +| `test/isogloss/runtime-generators.test.ts` | None | Entire generator and async-generator surface | +| `test/isogloss/runtime-reentrancy.test.ts` | Breaker coercion/accessor/recursion snippets | Intentional protected callback reentry and resume gates | +| `test/isogloss/seed-stress.test.ts` | Randomized and opcode-mutation regression shapes | Fixed build/data seeds and CI tiers | +| `test/isogloss/semantic-signatures.test.ts` | A transitional suite now exists and aliases `SemanticOp` to legacy `Op`; slot-analysis provides an additional exhaustiveness pattern | Remove the legacy alias and VM-only operations, then enforce exhaustive `satisfies Record`, exact facts, and dynamic stack effects | +| `test/isogloss/sidecar.test.ts` | None | Schema, source map, production absence, event decode | +| `test/isogloss/static-extractor.test.ts` | Anti-reversing threat ideas only | Hostile full-format extractor and lineage experiments | +| `test/migration/removed-vm-options.test.ts` | Legacy option list/feature combinations | Actionable error code and migration hint before parsing | +| `test/migration/no-legacy-vm.test.ts` | None | Source, exports, CLI, package, manifest, output, and fallback inventory | +| `test/options/metadata-drift.test.ts` | Feature combinations and manifest script | One source of truth across API, CLI, presets, worker manifest | + +## 10. Script migration matrix + +| Script | Classification | Current modules/data | Required Isogloss disposition | +|---|---|---|---| +| `scripts/bench.mjs` | C2 | `src/index.ts`, Node `vm`, legacy presets; eight workloads; bootstrap, total/steady time, output size | Rewrite as the primary Isogloss benchmark. Add JSON output, phase timings, first/repeated calls, exception/async/reentrancy workloads, memory, payload/runtime/sidecar bytes, and release-budget evaluation. Preserve the legacy measurement JSON, not a VM import. | +| `scripts/bench-attribution.mjs` | C2 | `src/index.ts`, `src/presets.ts`; removes legacy features from max | Freeze its legacy results, then replace the matrix with Isogloss profile/parameter and Phase 9 hardening attribution. It must not import legacy presets after Phase 10. | +| `scripts/build-browser.mjs` | C2 | `src/browser-worker.ts`, `browser-crypto-shim.ts`, generated option manifest, esbuild | Retain and point at the detailed Isogloss API. Add an executable browser/worker CSP smoke step; bundling success alone is insufficient. | +| `scripts/collect-stats.mjs` | C2 | Dist API, test output parsing, opcode/source statistics, simple performance/size, generated hero snippet | Remove opcode and VM terminology; ingest versioned benchmark JSON and expose Isogloss groups/cells/clauses/certificate/expansion statistics. Fix or remove the current `preset: "high"` reference in favor of valid Isogloss profiles/presets. | +| `scripts/generate-manifest.mjs` | C2 | `OPTION_META`, `AUTO_ENABLE_RULES`, `PRESETS` from dist | Rewrite for typed Isogloss metadata, nested options, artifact options, and removed-option tombstones. Add `metadata-drift.test.ts`; do not filter only boolean keys. | + +## 11. Cutover gates derived from this audit + +The test migration is complete only when all of the following are evidenced: + +1. All 23 C1 suite files have an explicit destination and pass through native, reference BCM, and emitted BCM where applicable. +2. All mixed C2/C3 sections have been split; no test is kept by retaining a legacy VM mechanism. +3. Every Section 7 semantic fixture appears in the generated fixture manifest before its C3 source file is deleted. +4. True generator coverage exists and passes; manual state-machine tests do not count. +5. Async and reentrant tests assert event order and carrier uniqueness, not only final values. +6. CSP passes by executing generated output in Node, browser, worker, and browser-extension-style environments. +7. The static extractor parses the real production format with full design knowledge. +8. Performance scripts emit versioned data and enforce the plan's size, bootstrap, steady-state, P95, and retained-memory gates. +9. Fixed build and data seeds reproduce every randomized failure. +10. `no-legacy-vm.test.ts` proves the source tree, package surface, generated output, CLI, manifest, and runtime contain no VM fallback. + +## 12. Highest-risk conclusion + +The existing suite provides a large and valuable JavaScript semantic corpus, especially for control flow, closures, exceptions, classes, and coercion. It does **not** currently prove the properties most specific to Traveling Isogloss: + +- true generator suspension; +- one-carrier behavior under async and reentrancy; +- history-dependent boundary motion and persistence; +- verifier/lattice invariants; +- resistance to a format-aware static extractor; +- actual CSP execution outside Node `vm`; +- enforceable performance and memory budgets; +- absence of a legacy fallback. + +Those gaps are release blockers, not follow-up hardening. The destructive VM deletion should occur only after the new suites supply direct evidence for them. From dd4312bb6c76621f593725e67186a50785748857 Mon Sep 17 00:00:00 2001 From: owengregson Date: Fri, 24 Jul 2026 13:34:40 -1000 Subject: [PATCH 06/54] feat: establish canonical semantic IR --- packages/ruam/src/compiler/ir.ts | 127 ++ packages/ruam/src/compiler/semantic-ops.ts | 147 +++ .../ruam/src/compiler/semantic-signatures.ts | 1032 +++++++++++++++++ packages/ruam/src/compiler/types.ts | 80 ++ .../test/isogloss/semantic-signatures.test.ts | 163 +++ 5 files changed, 1549 insertions(+) create mode 100644 packages/ruam/src/compiler/ir.ts create mode 100644 packages/ruam/src/compiler/semantic-ops.ts create mode 100644 packages/ruam/src/compiler/semantic-signatures.ts create mode 100644 packages/ruam/src/compiler/types.ts create mode 100644 packages/ruam/test/isogloss/semantic-signatures.test.ts diff --git a/packages/ruam/src/compiler/ir.ts b/packages/ruam/src/compiler/ir.ts new file mode 100644 index 0000000..b1d5d91 --- /dev/null +++ b/packages/ruam/src/compiler/ir.ts @@ -0,0 +1,127 @@ +/** + * Canonical, execution-representation-independent semantic IR. + * + * Node identities and typed exits replace instruction-pointer control flow. + * This IR is the contract between JavaScript compilation and Isogloss lattice + * lowering; it must never contain shuffled/physical opcodes or encoded + * bytecode offsets. + * + * @module compiler/ir + */ + +import { + assertCanonicalSemanticOp, + type SemanticOp, +} from "./semantic-ops.js"; +import type { + ConstantPoolEntry, + RootGroupCompatible, + RootGroupId, + SemanticFunctionMetadata, + SemanticUnitId, +} from "./types.js"; + +/** Dense unit-local identity of a canonical semantic node. */ +export type SemanticNodeId = number; + +/** Dense unit-local index into {@link SemanticUnit.origins}. */ +export type SourceOriginId = number; + +/** Source location associated with one or more canonical semantic nodes. */ +export interface SourceOrigin { + /** Original filename when compilation was given one. */ + file?: string; + /** Inclusive UTF-16 source offset. */ + start: number; + /** Exclusive UTF-16 source offset. */ + end: number; + /** One-based source line. */ + line: number; + /** Zero-based source column. */ + column: number; +} + +/** One canonical language-level action before lattice lowering. */ +export interface SemanticInstruction { + /** Stable identity within the containing unit. */ + id: SemanticNodeId; + /** Language-level behavior, never a shuffled or physical opcode. */ + op: SemanticOp; + /** Operation-specific payload interpreted by its semantic signature. */ + operand: number; + /** Index into the unit's source-origin table. */ + originId: SourceOriginId; +} + +/** Construct one instruction while enforcing the canonical-operation fence. */ +export function createSemanticInstruction( + instruction: SemanticInstruction +): SemanticInstruction { + assertCanonicalSemanticOp(instruction.op); + if (!Number.isSafeInteger(instruction.id) || instruction.id < 0) { + throw new Error(`RUAM_INVALID_SEMANTIC_NODE_ID: ${instruction.id}`); + } + if (!Number.isSafeInteger(instruction.originId) || instruction.originId < 0) { + throw new Error(`RUAM_INVALID_SOURCE_ORIGIN_ID: ${instruction.originId}`); + } + return Object.freeze({ ...instruction }); +} + +/** + * Typed control transfer from a canonical semantic node. + * + * Calls resume in the same unit after the invoked value completes. Exception + * and finally edges name compiler-known handlers; uncaught throws use the + * terminal `throw` form. + */ +export type SemanticExit = + | { kind: "fallthrough"; target: SemanticNodeId } + | { kind: "branch-true"; target: SemanticNodeId } + | { kind: "branch-false"; target: SemanticNodeId } + | { kind: "call"; resume: SemanticNodeId } + | { kind: "exception"; target: SemanticNodeId } + | { kind: "finally"; target: SemanticNodeId } + | { kind: "return" } + | { kind: "throw" } + | { kind: "yield"; resume: SemanticNodeId } + | { kind: "await"; resume: SemanticNodeId }; + +/** Canonical representation of one source function. */ +export interface SemanticUnit + extends RootGroupCompatible, + SemanticFunctionMetadata { + /** Literal pool referenced by semantic operands. */ + constants: ConstantPoolEntry[]; + /** Canonical semantic nodes in deterministic ID order. */ + nodes: SemanticInstruction[]; + /** All legal outgoing edges for each reachable node. */ + exits: Map; + /** First node selected by the unit's entry contract. */ + entryNode: SemanticNodeId; + /** Deduplicated source locations used by owner diagnostics. */ + origins: SourceOrigin[]; + /** Nested semantic units referenced by closure-creation operations. */ + childUnitIds: SemanticUnitId[]; +} + +/** + * Compiler-facing root-group description. + * + * The future pipeline layer can extend this with Babel paths and entry + * contracts without making the canonical IR depend on Babel. + */ +export interface SemanticRootGroup { + id: RootGroupId; + entryUnitId: SemanticUnitId; + units: SemanticUnit[]; + usedSemantics: ReadonlySet; + hasAsync: boolean; + hasGenerator: boolean; +} + +/** Whether an exit completes the current invocation instead of naming a node. */ +export function isTerminalSemanticExit( + exit: SemanticExit +): exit is Extract { + return exit.kind === "return" || exit.kind === "throw"; +} diff --git a/packages/ruam/src/compiler/semantic-ops.ts b/packages/ruam/src/compiler/semantic-ops.ts new file mode 100644 index 0000000..02f6297 --- /dev/null +++ b/packages/ruam/src/compiler/semantic-ops.ts @@ -0,0 +1,147 @@ +/** + * Migration-safe names for Ruam's language-level operations. + * + * The current compiler still emits the logical {@link Op} enum consumed by + * the legacy VM. Isogloss treats those values as semantic operation + * identities instead of physical bytecode. Keeping a runtime alias here lets + * new compiler stages use Isogloss terminology without changing numeric + * values or disturbing the existing encoder and interpreter during the + * migration. + * + * This file is intentionally the only compatibility seam new Isogloss + * compiler code should import. Once the legacy VM is removed, the enum can be + * moved here without changing downstream semantic-IR imports. + * + * @module compiler/semantic-ops + */ + +import { Op } from "./opcodes.js"; + +/** + * Runtime semantic-operation catalog. + * + * This is the exact same enum object as {@link Op}; it does not allocate, + * translate, or renumber operations. + */ +export const SemanticOp = Op; + +/** + * A real language-level operation. + * + * The legacy enum includes `__COUNT` as a numeric sentinel. Excluding it from + * the semantic type prevents the sentinel from entering canonical IR. + */ +export type SemanticOp = Exclude; + +/** Number of real semantic operations (excludes `__COUNT`). */ +export const SEMANTIC_OP_COUNT = Op.__COUNT; + +/** + * Stable ordered list of every real semantic operation. + * + * It is derived from the enum sentinel so newly appended operations are + * included automatically. The semantic-signature tests additionally prove + * that the corresponding descriptor table remains total. + */ +export const ALL_SEMANTIC_OPS: readonly SemanticOp[] = Object.freeze( + Array.from( + { length: SEMANTIC_OP_COUNT }, + (_, value) => value as SemanticOp + ) +); + +/** + * Migration-era operations that are not legal in canonical semantic IR. + * + * The signature catalog remains total for these values so the legacy compiler + * can be used as a differential oracle during replacement. Isogloss lowering, + * however, must only receive source-language operations. Compile-time markers, + * optimizer fusions, and VM handler-table mutation are representation details, + * not JavaScript semantics. + */ +export const NON_CANONICAL_SEMANTIC_OPS: ReadonlySet = new Set([ + // Patched or consumed before canonical IR is finalized. + Op.BREAK, + Op.CONTINUE, + Op.LABEL, + + // Tier-3 optimizer fusions. Canonical optimization may rewrite nodes, but + // it may not introduce a second hidden instruction vocabulary. + Op.REG_ADD, + Op.REG_SUB, + Op.REG_MUL, + Op.REG_LT, + Op.REG_LTE, + Op.REG_GT, + Op.REG_SEQ, + Op.REG_SNEQ, + Op.REG_LT_CONST_JF, + Op.REG_GET_PROP, + Op.REG_ADD_CONST, + Op.REG_GTE, + Op.REG_DIV, + Op.REG_MOD, + Op.REG_CONST_SUB, + Op.REG_CONST_MUL, + Op.REG_CONST_MOD, + Op.REG_LT_REG_JF, + Op.REG_LTE_CONST_JF, + Op.REG_GT_CONST_JF, + Op.REG_GTE_CONST_JF, + Op.REG_SEQ_CONST_JF, + Op.REG_SNEQ_CONST_JF, + Op.REG_LTE_REG_JF, + Op.REG_GT_REG_JF, + Op.REG_GTE_REG_JF, + Op.REG_SEQ_REG_JF, + Op.REG_SNEQ_REG_JF, + Op.REG_ADD_ASSIGN_VOID, + Op.REG_SUB_ASSIGN_VOID, + Op.REG_MUL_ASSIGN_VOID, + Op.REG_DIV_ASSIGN_VOID, + Op.REG_MOD_ASSIGN_VOID, + Op.IDX_REG, + Op.REG_GET_PROP_DYN, + Op.CONST_LT_JF, + Op.CONST_LTE_JF, + Op.CONST_GT_JF, + Op.CONST_GTE_JF, + Op.CONST_SEQ_JF, + Op.CONST_SNEQ_JF, + + // Changes a physical VM dispatch table and has no language-level meaning. + Op.MUTATE, +]); + +/** Stable ordered list of operations accepted by canonical semantic IR. */ +export const ALL_CANONICAL_SEMANTIC_OPS: readonly SemanticOp[] = Object.freeze( + ALL_SEMANTIC_OPS.filter((op) => !NON_CANONICAL_SEMANTIC_OPS.has(op)) +); + +/** Whether an operation may cross the canonical-IR/Isogloss boundary. */ +export function isCanonicalSemanticOp(op: SemanticOp): boolean { + return !NON_CANONICAL_SEMANTIC_OPS.has(op); +} + +/** + * Fail closed when a migration adapter tries to introduce representation- + * specific behavior into canonical semantic IR. + */ +export function assertCanonicalSemanticOp( + op: SemanticOp +): asserts op is SemanticOp { + if (!isCanonicalSemanticOp(op)) { + throw new Error( + `RUAM_NON_CANONICAL_SEMANTIC_OP: ${semanticOpName(op)} (${op})` + ); + } +} + +/** Return the symbolic name of a semantic operation for diagnostics. */ +export function semanticOpName(op: SemanticOp): string { + return Op[op]; +} + +// Preserve access to the old spelling for migration adapters that need to +// state explicitly that they are crossing back into legacy VM code. +export { Op as LegacyOp }; diff --git a/packages/ruam/src/compiler/semantic-signatures.ts b/packages/ruam/src/compiler/semantic-signatures.ts new file mode 100644 index 0000000..d61ef8d --- /dev/null +++ b/packages/ruam/src/compiler/semantic-signatures.ts @@ -0,0 +1,1032 @@ +/** + * Exhaustive semantic-operation descriptor catalog. + * + * Signatures describe the observable shape of an operation without exposing + * a physical dispatch identity. The Isogloss lattice generator uses these + * dimensions to construct overlapping candidate clauses; the CFG builder uses + * the control classification to produce typed exits. + * + * Every current operation receives a descriptor. Well-understood operation + * families have explicit precise overrides. Operations not yet fully + * classified receive a deliberately conservative descriptor (`mayThrow`, + * scope/this reads, dynamic stack arity) rather than an unsafe optimistic one. + * + * @module compiler/semantic-signatures + */ + +import { + ALL_SEMANTIC_OPS, + SemanticOp, + type SemanticOp as SemanticOpValue, + semanticOpName, +} from "./semantic-ops.js"; + +export type OperandKind = + | "none" + | "constant" + | "register" + | "argument" + | "slot" + | "scope-name" + | "argc" + | "jump" + | "table" + | "count" + | "packed" + | "unit-ref"; + +/** Resolved number of values consumed/produced, or a runtime-dependent shape. */ +export type ResolvedStackArity = number | "dynamic"; + +/** Fixed arity or a pure projection from the encoded semantic operand. */ +export type StackArity = + | ResolvedStackArity + | ((operand: number) => ResolvedStackArity); + +export type SemanticEffect = + | "pure" + | "local" + | "scope" + | "object" + | "call" + | "control" + | "exception" + | "async"; + +export type SemanticControl = + | "fallthrough" + | "conditional" + | "jump" + | "call" + | "return" + | "throw" + | "yield" + | "await"; + +export type SemanticFamily = + | "stack" + | "register" + | "argument" + | "arithmetic" + | "bitwise" + | "logical" + | "comparison" + | "control" + | "property" + | "scope" + | "call" + | "aggregate" + | "class" + | "closure" + | "suspension" + | "exception" + | "iterator" + | "conversion" + | "template" + | "destructuring" + | "environment" + | "mutation" + | "unclassified"; + +export interface SemanticSignature { + op: SemanticOpValue; + operandKind: OperandKind; + stackInput: StackArity; + stackOutput: StackArity; + effect: SemanticEffect; + control: SemanticControl; + mayThrow: boolean; + readsThis: boolean; + readsScope: boolean; + /** + * Minimum number of non-semantic constraint dimensions available to the + * lattice generator when hiding this operation among candidates. + */ + syntheticDimensions: number; + /** Broad family used by diagnostics and candidate balancing. */ + family: SemanticFamily; + /** Whether the descriptor is explicit or a safe migration fallback. */ + precision: "classified" | "conservative"; +} + +type SignatureOverride = Partial>; + +const dynamicArity: ResolvedStackArity = "dynamic"; +const popOperandCount = (operand: number): ResolvedStackArity => + operand >= 0 ? operand : dynamicArity; +const callInputs = (operand: number): ResolvedStackArity => + operand >= 0 ? operand + 1 : dynamicArity; +const methodCallInputs = (operand: number): ResolvedStackArity => + operand >= 0 ? operand + 2 : dynamicArity; + +const overrides = new Map(); + +function classify( + ops: readonly SemanticOpValue[], + override: SignatureOverride +): void { + for (const op of ops) { + overrides.set(op, { + ...overrides.get(op), + ...override, + precision: override.precision ?? "classified", + }); + } +} + +// Stack primitives and literal sources. +classify( + [ + SemanticOp.PUSH_UNDEFINED, + SemanticOp.PUSH_NULL, + SemanticOp.PUSH_TRUE, + SemanticOp.PUSH_FALSE, + SemanticOp.PUSH_ZERO, + SemanticOp.PUSH_ONE, + SemanticOp.PUSH_NEG_ONE, + SemanticOp.PUSH_EMPTY_STRING, + SemanticOp.PUSH_NAN, + SemanticOp.PUSH_INFINITY, + SemanticOp.PUSH_NEG_INFINITY, + ], + { + operandKind: "none", + stackInput: 0, + stackOutput: 1, + effect: "pure", + mayThrow: false, + readsThis: false, + readsScope: false, + syntheticDimensions: 2, + family: "stack", + } +); +classify([SemanticOp.PUSH_CONST], { + operandKind: "constant", + stackInput: 0, + stackOutput: 1, + effect: "pure", + mayThrow: false, + readsThis: false, + readsScope: false, + syntheticDimensions: 2, + family: "stack", +}); +classify([SemanticOp.POP], { + operandKind: "none", + stackInput: 1, + stackOutput: 0, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "stack", +}); +classify([SemanticOp.POP_N], { + operandKind: "count", + stackInput: popOperandCount, + stackOutput: 0, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "stack", +}); +classify([SemanticOp.DUP], { + operandKind: "none", + stackInput: 1, + stackOutput: 2, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "stack", +}); +classify([SemanticOp.DUP2], { + operandKind: "none", + stackInput: 2, + stackOutput: 4, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "stack", +}); +classify([SemanticOp.SWAP], { + operandKind: "none", + stackInput: 2, + stackOutput: 2, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "stack", +}); +classify([SemanticOp.ROT3], { + operandKind: "none", + stackInput: 3, + stackOutput: 3, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "stack", +}); +classify([SemanticOp.ROT4], { + operandKind: "none", + stackInput: 4, + stackOutput: 4, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "stack", +}); +classify([SemanticOp.PICK], { + operandKind: "count", + stackInput: "dynamic", + stackOutput: "dynamic", + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "stack", +}); + +// Registers and arguments. +classify( + [ + SemanticOp.LOAD_REG, + SemanticOp.INC_REG, + SemanticOp.DEC_REG, + SemanticOp.POST_INC_REG, + SemanticOp.POST_DEC_REG, + ], + { + operandKind: "register", + stackInput: 0, + stackOutput: 1, + effect: "local", + readsThis: false, + readsScope: false, + family: "register", + } +); +classify([SemanticOp.STORE_REG], { + operandKind: "register", + stackInput: 1, + stackOutput: 0, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "register", +}); +classify( + [ + SemanticOp.ADD_ASSIGN_REG, + SemanticOp.SUB_ASSIGN_REG, + SemanticOp.MUL_ASSIGN_REG, + SemanticOp.DIV_ASSIGN_REG, + SemanticOp.MOD_ASSIGN_REG, + ], + { + operandKind: "register", + stackInput: 1, + stackOutput: 1, + effect: "local", + readsThis: false, + readsScope: false, + family: "register", + } +); +classify( + [ + SemanticOp.LOAD_ARG, + SemanticOp.LOAD_ARG_OR_DEFAULT, + SemanticOp.GET_ARG_COUNT, + ], + { + operandKind: "argument", + stackInput: 0, + stackOutput: 1, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "argument", + } +); +classify([SemanticOp.STORE_ARG], { + operandKind: "argument", + stackInput: 1, + stackOutput: 0, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "argument", +}); + +// Ordinary value operations. +classify( + [ + SemanticOp.ADD, + SemanticOp.SUB, + SemanticOp.MUL, + SemanticOp.DIV, + SemanticOp.MOD, + SemanticOp.POW, + ], + { + operandKind: "none", + stackInput: 2, + stackOutput: 1, + effect: "pure", + readsThis: false, + readsScope: false, + syntheticDimensions: 3, + family: "arithmetic", + } +); +classify( + [ + SemanticOp.NEG, + SemanticOp.UNARY_PLUS, + SemanticOp.INC, + SemanticOp.DEC, + ], + { + operandKind: "none", + stackInput: 1, + stackOutput: 1, + effect: "pure", + readsThis: false, + readsScope: false, + family: "arithmetic", + } +); +classify( + [ + SemanticOp.BIT_AND, + SemanticOp.BIT_OR, + SemanticOp.BIT_XOR, + SemanticOp.SHL, + SemanticOp.SHR, + SemanticOp.USHR, + ], + { + operandKind: "none", + stackInput: 2, + stackOutput: 1, + effect: "pure", + readsThis: false, + readsScope: false, + family: "bitwise", + } +); +classify([SemanticOp.BIT_NOT, SemanticOp.NOT], { + operandKind: "none", + stackInput: 1, + stackOutput: 1, + effect: "pure", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "logical", +}); +classify( + [ + SemanticOp.EQ, + SemanticOp.NEQ, + SemanticOp.SEQ, + SemanticOp.SNEQ, + SemanticOp.LT, + SemanticOp.LTE, + SemanticOp.GT, + SemanticOp.GTE, + SemanticOp.IN_OP, + SemanticOp.INSTANCEOF, + ], + { + operandKind: "none", + stackInput: 2, + stackOutput: 1, + effect: "pure", + readsThis: false, + readsScope: false, + syntheticDimensions: 3, + family: "comparison", + } +); + +// Typed control exits. +classify([SemanticOp.JMP, SemanticOp.BREAK, SemanticOp.CONTINUE], { + operandKind: "jump", + stackInput: 0, + stackOutput: 0, + effect: "control", + control: "jump", + mayThrow: false, + readsThis: false, + readsScope: false, + syntheticDimensions: 3, + family: "control", +}); +classify( + [ + SemanticOp.JMP_TRUE, + SemanticOp.JMP_FALSE, + SemanticOp.JMP_NULLISH, + SemanticOp.JMP_UNDEFINED, + ], + { + operandKind: "jump", + stackInput: 1, + stackOutput: 0, + effect: "control", + control: "conditional", + mayThrow: false, + readsThis: false, + readsScope: false, + syntheticDimensions: 3, + family: "control", + } +); +classify( + [ + SemanticOp.JMP_TRUE_KEEP, + SemanticOp.JMP_FALSE_KEEP, + SemanticOp.JMP_NULLISH_KEEP, + SemanticOp.LOGICAL_AND, + SemanticOp.LOGICAL_OR, + SemanticOp.NULLISH_COALESCE, + ], + { + operandKind: "jump", + stackInput: 1, + stackOutput: 1, + effect: "control", + control: "conditional", + mayThrow: false, + readsThis: false, + readsScope: false, + syntheticDimensions: 3, + family: "control", + } +); +classify([SemanticOp.TABLE_SWITCH, SemanticOp.LOOKUP_SWITCH], { + operandKind: "table", + stackInput: 1, + stackOutput: 0, + effect: "control", + control: "conditional", + mayThrow: false, + readsThis: false, + readsScope: false, + syntheticDimensions: 4, + family: "control", +}); +classify([SemanticOp.RETURN], { + operandKind: "none", + stackInput: 1, + stackOutput: 0, + effect: "control", + control: "return", + mayThrow: false, + readsThis: false, + readsScope: false, + syntheticDimensions: 3, + family: "control", +}); +classify([SemanticOp.RETURN_VOID], { + operandKind: "none", + stackInput: 0, + stackOutput: 0, + effect: "control", + control: "return", + mayThrow: false, + readsThis: false, + readsScope: false, + syntheticDimensions: 3, + family: "control", +}); +classify( + [ + SemanticOp.THROW, + SemanticOp.RETHROW, + SemanticOp.THROW_IF_NOT_OBJECT, + SemanticOp.THROW_REF_ERROR, + SemanticOp.THROW_TYPE_ERROR, + SemanticOp.THROW_SYNTAX_ERROR, + ], + { + operandKind: "none", + stackInput: "dynamic", + stackOutput: 0, + effect: "exception", + control: "throw", + mayThrow: true, + readsThis: false, + readsScope: false, + syntheticDimensions: 3, + family: "exception", + } +); +classify([SemanticOp.NOP, SemanticOp.LABEL, SemanticOp.SOURCE_MAP], { + operandKind: "none", + stackInput: 0, + stackOutput: 0, + effect: "pure", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "control", +}); + +// Calls and construction. Negative argument counts denote spread and remain +// dynamic until the canonical operand model is normalized. +classify( + [ + SemanticOp.CALL, + SemanticOp.CALL_NEW, + SemanticOp.CALL_OPTIONAL, + SemanticOp.DIRECT_EVAL, + SemanticOp.CALL_TAGGED_TEMPLATE, + ], + { + operandKind: "argc", + stackInput: callInputs, + stackOutput: 1, + effect: "call", + control: "call", + mayThrow: true, + readsThis: false, + readsScope: false, + syntheticDimensions: 4, + family: "call", + } +); +classify( + [ + SemanticOp.CALL_METHOD, + SemanticOp.CALL_METHOD_OPTIONAL, + SemanticOp.CALL_SUPER_METHOD, + SemanticOp.SUPER_CALL, + ], + { + operandKind: "argc", + stackInput: methodCallInputs, + stackOutput: 1, + effect: "call", + control: "call", + mayThrow: true, + readsThis: true, + readsScope: false, + syntheticDimensions: 4, + family: "call", + } +); +classify([SemanticOp.CALL_0], { + operandKind: "none", + stackInput: 1, + stackOutput: 1, + effect: "call", + control: "call", + mayThrow: true, + readsThis: false, + readsScope: false, + family: "call", +}); +classify([SemanticOp.CALL_1], { + operandKind: "none", + stackInput: 2, + stackOutput: 1, + effect: "call", + control: "call", + mayThrow: true, + readsThis: false, + readsScope: false, + family: "call", +}); +classify([SemanticOp.CALL_2], { + operandKind: "none", + stackInput: 3, + stackOutput: 1, + effect: "call", + control: "call", + mayThrow: true, + readsThis: false, + readsScope: false, + family: "call", +}); +classify([SemanticOp.CALL_3], { + operandKind: "none", + stackInput: 4, + stackOutput: 1, + effect: "call", + control: "call", + mayThrow: true, + readsThis: false, + readsScope: false, + family: "call", +}); + +// Suspension and async boundaries. +classify([SemanticOp.YIELD, SemanticOp.YIELD_DELEGATE], { + operandKind: "none", + stackInput: 1, + stackOutput: 1, + effect: "async", + control: "yield", + mayThrow: true, + readsThis: false, + readsScope: false, + syntheticDimensions: 4, + family: "suspension", +}); +classify([SemanticOp.AWAIT, SemanticOp.FOR_AWAIT_NEXT], { + operandKind: "none", + stackInput: 1, + stackOutput: 1, + effect: "async", + control: "await", + mayThrow: true, + readsThis: false, + readsScope: false, + syntheticDimensions: 4, + family: "suspension", +}); +classify( + [ + SemanticOp.SUSPEND, + SemanticOp.RESUME, + SemanticOp.GENERATOR_RESUME, + SemanticOp.GENERATOR_RETURN, + SemanticOp.GENERATOR_THROW, + SemanticOp.ASYNC_GENERATOR_YIELD, + SemanticOp.ASYNC_GENERATOR_NEXT, + SemanticOp.ASYNC_GENERATOR_RETURN, + SemanticOp.ASYNC_GENERATOR_THROW, + ], + { + operandKind: "none", + stackInput: "dynamic", + stackOutput: "dynamic", + effect: "async", + mayThrow: true, + readsThis: false, + readsScope: false, + syntheticDimensions: 4, + family: "suspension", + } +); + +// Structured exception state is explicit even where exact stack shape still +// depends on handler metadata. +classify( + [ + SemanticOp.TRY_PUSH, + SemanticOp.TRY_POP, + SemanticOp.CATCH_BIND, + SemanticOp.CATCH_BIND_PATTERN, + SemanticOp.FINALLY_MARK, + SemanticOp.END_FINALLY, + ], + { + operandKind: "packed", + stackInput: "dynamic", + stackOutput: "dynamic", + effect: "exception", + mayThrow: true, + readsThis: false, + readsScope: true, + syntheticDimensions: 4, + family: "exception", + } +); + +// Scope access and closure creation. +classify( + [ + SemanticOp.LOAD_GLOBAL, + SemanticOp.STORE_GLOBAL, + SemanticOp.LOAD_SCOPED, + SemanticOp.STORE_SCOPED, + SemanticOp.DECLARE_VAR, + SemanticOp.DECLARE_LET, + SemanticOp.DECLARE_CONST, + SemanticOp.TDZ_CHECK, + SemanticOp.TDZ_MARK, + SemanticOp.DELETE_SCOPED, + SemanticOp.TYPEOF_GLOBAL, + SemanticOp.INC_SCOPED, + SemanticOp.DEC_SCOPED, + SemanticOp.POST_INC_SCOPED, + SemanticOp.POST_DEC_SCOPED, + SemanticOp.ADD_ASSIGN_SCOPED, + SemanticOp.SUB_ASSIGN_SCOPED, + SemanticOp.MUL_ASSIGN_SCOPED, + SemanticOp.DIV_ASSIGN_SCOPED, + SemanticOp.MOD_ASSIGN_SCOPED, + SemanticOp.POW_ASSIGN_SCOPED, + SemanticOp.BIT_AND_ASSIGN_SCOPED, + SemanticOp.BIT_OR_ASSIGN_SCOPED, + SemanticOp.BIT_XOR_ASSIGN_SCOPED, + SemanticOp.SHL_ASSIGN_SCOPED, + SemanticOp.SHR_ASSIGN_SCOPED, + SemanticOp.USHR_ASSIGN_SCOPED, + SemanticOp.AND_ASSIGN_SCOPED, + SemanticOp.OR_ASSIGN_SCOPED, + SemanticOp.NULLISH_ASSIGN_SCOPED, + SemanticOp.PUSH_CLOSURE_VAR, + SemanticOp.STORE_CLOSURE_VAR, + ], + { + operandKind: "scope-name", + stackInput: "dynamic", + stackOutput: "dynamic", + effect: "scope", + readsThis: false, + readsScope: true, + syntheticDimensions: 3, + family: "scope", + } +); +classify( + [ + SemanticOp.PUSH_SCOPE, + SemanticOp.POP_SCOPE, + SemanticOp.PUSH_WITH_SCOPE, + SemanticOp.PUSH_BLOCK_SCOPE, + SemanticOp.PUSH_CATCH_SCOPE, + SemanticOp.PUSH_INDEXED_SCOPE, + SemanticOp.POP_INDEXED_SCOPE, + ], + { + operandKind: "count", + stackInput: "dynamic", + stackOutput: "dynamic", + effect: "scope", + readsThis: false, + readsScope: true, + family: "scope", + } +); +classify( + [ + SemanticOp.NEW_CLOSURE, + SemanticOp.NEW_FUNCTION, + SemanticOp.NEW_ARROW, + SemanticOp.NEW_ASYNC, + SemanticOp.NEW_GENERATOR, + SemanticOp.NEW_ASYNC_GENERATOR, + ], + { + operandKind: "unit-ref", + stackInput: 0, + stackOutput: 1, + effect: "local", + mayThrow: true, + readsScope: true, + syntheticDimensions: 4, + family: "closure", + } +); +classify([SemanticOp.NEW_ARROW, SemanticOp.NEW_CLOSURE], { + readsThis: true, +}); + +// Property, aggregate, and iterator operations are conservatively marked as +// potentially throwing while still exposing their candidate-balancing family. +classify( + [ + SemanticOp.GET_PROP_STATIC, + SemanticOp.SET_PROP_STATIC, + SemanticOp.GET_PROP_DYNAMIC, + SemanticOp.SET_PROP_DYNAMIC, + SemanticOp.DELETE_PROP_STATIC, + SemanticOp.DELETE_PROP_DYNAMIC, + SemanticOp.OPT_CHAIN_GET, + SemanticOp.OPT_CHAIN_DYNAMIC, + SemanticOp.GET_SUPER_PROP, + SemanticOp.SET_SUPER_PROP, + SemanticOp.GET_PRIVATE_FIELD, + SemanticOp.SET_PRIVATE_FIELD, + SemanticOp.HAS_PRIVATE_FIELD, + SemanticOp.DEFINE_OWN_PROPERTY, + SemanticOp.FAST_GET_PROP, + SemanticOp.REG_GET_PROP, + SemanticOp.IDX_REG, + SemanticOp.REG_GET_PROP_DYN, + ], + { + operandKind: "packed", + stackInput: "dynamic", + stackOutput: "dynamic", + effect: "object", + mayThrow: true, + readsThis: false, + readsScope: false, + syntheticDimensions: 3, + family: "property", + } +); +classify([SemanticOp.GET_SUPER_PROP, SemanticOp.SET_SUPER_PROP], { + readsThis: true, +}); +classify( + [ + SemanticOp.NEW_OBJECT, + SemanticOp.NEW_ARRAY, + SemanticOp.NEW_ARRAY_WITH_SIZE, + SemanticOp.ARRAY_PUSH, + SemanticOp.ARRAY_HOLE, + SemanticOp.SPREAD_ARRAY, + SemanticOp.SPREAD_OBJECT, + SemanticOp.COPY_DATA_PROPERTIES, + SemanticOp.SET_PROTO, + SemanticOp.FREEZE_OBJECT, + SemanticOp.SEAL_OBJECT, + SemanticOp.DEFINE_PROPERTY_DESC, + SemanticOp.CREATE_TEMPLATE_OBJECT, + ], + { + operandKind: "packed", + stackInput: "dynamic", + stackOutput: "dynamic", + effect: "object", + mayThrow: true, + readsThis: false, + readsScope: false, + family: "aggregate", + } +); +classify( + [ + SemanticOp.GET_ITERATOR, + SemanticOp.ITER_NEXT, + SemanticOp.ITER_DONE, + SemanticOp.ITER_VALUE, + SemanticOp.ITER_CLOSE, + SemanticOp.ITER_RESULT_UNWRAP, + SemanticOp.FORIN_INIT, + SemanticOp.FORIN_NEXT, + SemanticOp.FORIN_DONE, + SemanticOp.GET_ASYNC_ITERATOR, + SemanticOp.ASYNC_ITER_NEXT, + SemanticOp.ASYNC_ITER_DONE, + SemanticOp.ASYNC_ITER_VALUE, + SemanticOp.ASYNC_ITER_CLOSE, + ], + { + operandKind: "none", + stackInput: "dynamic", + stackOutput: "dynamic", + effect: "object", + mayThrow: true, + readsThis: false, + readsScope: false, + family: "iterator", + } +); + +classify( + [ + SemanticOp.TYPEOF, + SemanticOp.VOID, + SemanticOp.TO_NUMBER, + SemanticOp.TO_STRING, + SemanticOp.TO_BOOLEAN, + SemanticOp.TO_OBJECT, + SemanticOp.TO_PROPERTY_KEY, + SemanticOp.TO_NUMERIC, + ], + { + operandKind: "none", + stackInput: 1, + stackOutput: 1, + effect: "pure", + readsThis: false, + readsScope: false, + family: "conversion", + } +); + +classify( + [ + SemanticOp.PUSH_THIS, + SemanticOp.PUSH_NEW_TARGET, + SemanticOp.PUSH_ARGUMENTS, + SemanticOp.PUSH_GLOBAL_THIS, + SemanticOp.PUSH_WELL_KNOWN_SYMBOL, + SemanticOp.IMPORT_META, + ], + { + operandKind: "none", + stackInput: 0, + stackOutput: 1, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: false, + family: "environment", + } +); +classify([SemanticOp.PUSH_THIS, SemanticOp.PUSH_NEW_TARGET], { + readsThis: true, +}); +classify([SemanticOp.DYNAMIC_IMPORT], { + operandKind: "none", + stackInput: 1, + stackOutput: 1, + effect: "async", + control: "call", + mayThrow: true, + readsThis: false, + readsScope: false, + family: "environment", +}); + +// Legacy-only runtime mutation remains total but intentionally conservative: +// it has no place in canonical Isogloss output and will be removed at cutover. +classify([SemanticOp.MUTATE], { + operandKind: "packed", + stackInput: 0, + stackOutput: 0, + effect: "control", + control: "fallthrough", + mayThrow: true, + readsThis: true, + readsScope: true, + syntheticDimensions: 1, + family: "mutation", + precision: "conservative", +}); + +function conservativeSignature(op: SemanticOpValue): SemanticSignature { + return { + op, + operandKind: "packed", + stackInput: "dynamic", + stackOutput: "dynamic", + effect: "object", + control: "fallthrough", + mayThrow: true, + readsThis: true, + readsScope: true, + syntheticDimensions: 1, + family: "unclassified", + precision: "conservative", + }; +} + +function buildSignatureTable(): Readonly< + Record +> { + const table = Object.create(null) as Record< + SemanticOpValue, + SemanticSignature + >; + + for (const op of ALL_SEMANTIC_OPS) { + const signature = { + ...conservativeSignature(op), + ...overrides.get(op), + op, + }; + table[op] = Object.freeze(signature); + } + + return Object.freeze(table); +} + +/** + * Total descriptor table for all real semantic operations. + * + * Its `Record` type makes downstream lookup total at compile + * time; construction from `ALL_SEMANTIC_OPS` and the coverage test make enum + * growth total at runtime as well. + */ +export const SEMANTIC_SIGNATURES: Readonly< + Record +> = buildSignatureTable(); + +/** Look up the descriptor for a canonical semantic operation. */ +export function getSemanticSignature( + op: SemanticOpValue +): SemanticSignature { + const signature = SEMANTIC_SIGNATURES[op]; + if (signature === undefined) { + throw new RangeError(`Missing semantic signature for ${semanticOpName(op)}`); + } + return signature; +} + +/** Resolve a fixed or operand-dependent stack arity. */ +export function resolveStackArity( + arity: StackArity, + operand: number +): ResolvedStackArity { + return typeof arity === "function" ? arity(operand) : arity; +} diff --git a/packages/ruam/src/compiler/types.ts b/packages/ruam/src/compiler/types.ts new file mode 100644 index 0000000..71289ef --- /dev/null +++ b/packages/ruam/src/compiler/types.ts @@ -0,0 +1,80 @@ +/** + * Compiler-internal types shared by canonical semantic IR and its lowering + * stages. + * + * Public configuration remains in `src/types.ts`. During the Isogloss + * migration the constant-pool type is aliased from that module so existing VM + * consumers remain source-compatible; ownership can move here at cutover + * without changing canonical-IR callers. + * + * @module compiler/types + */ + +import type { + BytecodeUnit, + ConstantPoolEntry as PublicConstantPoolEntry, + ExceptionEntry, + Instruction, +} from "../types.js"; + +/** Literal data referenced by canonical semantic instructions. */ +export type ConstantPoolEntry = PublicConstantPoolEntry; + +/** Opaque identity shared by every semantic unit in one protected root. */ +export type RootGroupId = string; + +/** Opaque identity of a compiled semantic unit. */ +export type SemanticUnitId = string; + +/** + * Function-level facts that are independent of the eventual execution + * representation. + * + * These fields intentionally mirror the semantic subset of the current + * `BytecodeUnit`, providing a structurally compatible migration target while + * excluding bytecode instructions, instruction-pointer tables, and physical + * encoding details. + */ +export interface SemanticFunctionMetadata { + /** Number of declared parameters. */ + paramCount: number; + /** Total registers allocated by compiler analysis. */ + registerCount: number; + /** Number of indexed scope slots used by the function. */ + slotCount: number; + /** Whether the source function had a strict-mode directive. */ + isStrict: boolean; + /** Whether the source function is a generator. */ + isGenerator: boolean; + /** Whether the source function is async. */ + isAsync: boolean; + /** Whether the source function is an arrow function. */ + isArrow: boolean; + /** Whether the per-call scope object may be elided. */ + scopeless: boolean; + /** Whether exception-completion state is required. */ + usesExceptions: boolean; + /** Whether `this`, `new.target`, or `super` context is required. */ + usesThisContext: boolean; + /** Constant-pool index of the function name, or `-1` when anonymous. */ + nameConstIndex: number; + /** Names captured from outer lexical scopes. */ + outerNames: string[]; +} + +/** Minimum identity contract for a unit participating in a root group. */ +export interface RootGroupCompatible { + id: SemanticUnitId; + rootGroupId: RootGroupId; +} + +/** + * Temporary aliases used only by migration adapters. + * + * New semantic and Isogloss modules should depend on the canonical types + * instead. Keeping the aliases here prevents new code from reaching into the + * public API module for legacy bytecode structures. + */ +export type LegacyInstruction = Instruction; +export type LegacyExceptionEntry = ExceptionEntry; +export type LegacyBytecodeUnit = BytecodeUnit; diff --git a/packages/ruam/test/isogloss/semantic-signatures.test.ts b/packages/ruam/test/isogloss/semantic-signatures.test.ts new file mode 100644 index 0000000..55013d5 --- /dev/null +++ b/packages/ruam/test/isogloss/semantic-signatures.test.ts @@ -0,0 +1,163 @@ +import { describe, expect, it } from "bun:test"; +import { Op } from "../../src/compiler/opcodes.js"; +import { + ALL_CANONICAL_SEMANTIC_OPS, + ALL_SEMANTIC_OPS, + NON_CANONICAL_SEMANTIC_OPS, + SEMANTIC_OP_COUNT, + SemanticOp, + assertCanonicalSemanticOp, + isCanonicalSemanticOp, + semanticOpName, +} from "../../src/compiler/semantic-ops.js"; +import { + SEMANTIC_SIGNATURES, + getSemanticSignature, + resolveStackArity, +} from "../../src/compiler/semantic-signatures.js"; +import { + createSemanticInstruction, + isTerminalSemanticExit, + type SemanticExit, + type SemanticInstruction, +} from "../../src/compiler/ir.js"; + +describe("semantic operation migration alias", () => { + it("is the existing logical enum without translation or renumbering", () => { + expect(SemanticOp).toBe(Op); + expect(SemanticOp.ADD).toBe(Op.ADD); + expect(SemanticOp.MUTATE).toBe(Op.MUTATE); + expect(SEMANTIC_OP_COUNT).toBe(Op.__COUNT); + }); + + it("enumerates every real operation once and excludes the sentinel", () => { + expect(ALL_SEMANTIC_OPS).toHaveLength(Op.__COUNT); + expect(new Set(ALL_SEMANTIC_OPS).size).toBe(Op.__COUNT); + expect(ALL_SEMANTIC_OPS[0]).toBe(0); + expect(ALL_SEMANTIC_OPS.at(-1)).toBe(Op.__COUNT - 1); + expect(ALL_SEMANTIC_OPS).not.toContain(Op.__COUNT); + }); + + it("keeps representation artifacts outside canonical semantic IR", () => { + for (const op of [ + SemanticOp.BREAK, + SemanticOp.CONTINUE, + SemanticOp.LABEL, + SemanticOp.REG_ADD, + SemanticOp.REG_LT_CONST_JF, + SemanticOp.CONST_SEQ_JF, + SemanticOp.MUTATE, + ]) { + expect(NON_CANONICAL_SEMANTIC_OPS.has(op), semanticOpName(op)).toBe( + true + ); + expect(isCanonicalSemanticOp(op), semanticOpName(op)).toBe(false); + } + + expect(ALL_CANONICAL_SEMANTIC_OPS).toContain(SemanticOp.ADD); + expect(ALL_CANONICAL_SEMANTIC_OPS).toContain(SemanticOp.TRY_PUSH); + expect(ALL_CANONICAL_SEMANTIC_OPS).not.toContain(SemanticOp.MUTATE); + expect( + ALL_CANONICAL_SEMANTIC_OPS.length + NON_CANONICAL_SEMANTIC_OPS.size + ).toBe(ALL_SEMANTIC_OPS.length); + expect(() => assertCanonicalSemanticOp(SemanticOp.REG_ADD)).toThrow( + "RUAM_NON_CANONICAL_SEMANTIC_OP" + ); + }); +}); + +describe("semantic signature catalog", () => { + it("has a frozen, self-identifying descriptor for every operation", () => { + expect(Object.isFrozen(SEMANTIC_SIGNATURES)).toBe(true); + expect(Object.keys(SEMANTIC_SIGNATURES)).toHaveLength(Op.__COUNT); + + for (const op of ALL_SEMANTIC_OPS) { + const signature = SEMANTIC_SIGNATURES[op]; + expect(signature, semanticOpName(op)).toBeDefined(); + expect(signature.op, semanticOpName(op)).toBe(op); + expect(Object.isFrozen(signature), semanticOpName(op)).toBe(true); + expect(signature.syntheticDimensions, semanticOpName(op)).toBeGreaterThan( + 0 + ); + } + }); + + it("classifies control, call, exception, and suspension exits explicitly", () => { + expect(getSemanticSignature(SemanticOp.JMP).control).toBe("jump"); + expect(getSemanticSignature(SemanticOp.JMP_FALSE).control).toBe( + "conditional" + ); + expect(getSemanticSignature(SemanticOp.RETURN).control).toBe("return"); + expect(getSemanticSignature(SemanticOp.THROW).control).toBe("throw"); + expect(getSemanticSignature(SemanticOp.CALL).control).toBe("call"); + expect(getSemanticSignature(SemanticOp.YIELD).control).toBe("yield"); + expect(getSemanticSignature(SemanticOp.AWAIT).control).toBe("await"); + + expect(getSemanticSignature(SemanticOp.THROW).effect).toBe("exception"); + expect(getSemanticSignature(SemanticOp.CALL).effect).toBe("call"); + expect(getSemanticSignature(SemanticOp.AWAIT).effect).toBe("async"); + }); + + it("resolves operand-dependent stack arity without executing semantics", () => { + const call = getSemanticSignature(SemanticOp.CALL); + const methodCall = getSemanticSignature(SemanticOp.CALL_METHOD); + const popN = getSemanticSignature(SemanticOp.POP_N); + + expect(resolveStackArity(call.stackInput, 3)).toBe(4); + expect(resolveStackArity(methodCall.stackInput, 3)).toBe(5); + expect(resolveStackArity(popN.stackInput, 3)).toBe(3); + expect(resolveStackArity(call.stackInput, -1)).toBe("dynamic"); + }); + + it("uses conservative facts rather than optimistic claims for migration-only ops", () => { + const mutation = getSemanticSignature(SemanticOp.MUTATE); + expect(mutation.precision).toBe("conservative"); + expect(mutation.mayThrow).toBe(true); + expect(mutation.readsScope).toBe(true); + expect(mutation.readsThis).toBe(true); + }); +}); + +describe("canonical semantic IR contracts", () => { + it("accepts semantic operations and identifies only terminal exits", () => { + const instruction: SemanticInstruction = createSemanticInstruction({ + id: 0, + op: SemanticOp.ADD, + operand: 0, + originId: 0, + }); + const exits: SemanticExit[] = [ + { kind: "fallthrough", target: 1 }, + { kind: "call", resume: 2 }, + { kind: "return" }, + { kind: "throw" }, + ]; + + expect(instruction.op).toBe(Op.ADD); + expect(exits.map(isTerminalSemanticExit)).toEqual([ + false, + false, + true, + true, + ]); + }); + + it("rejects VM and optimizer operations at construction", () => { + expect(() => + createSemanticInstruction({ + id: 0, + op: SemanticOp.MUTATE, + operand: 0, + originId: 0, + }) + ).toThrow("RUAM_NON_CANONICAL_SEMANTIC_OP"); + expect(() => + createSemanticInstruction({ + id: 0, + op: SemanticOp.REG_ADD, + operand: 0, + originId: 0, + }) + ).toThrow("RUAM_NON_CANONICAL_SEMANTIC_OP"); + }); +}); From 74bf717a7e83cbedfbb989276cfc4a644bb88671 Mon Sep 17 00:00:00 2001 From: owengregson Date: Fri, 24 Jul 2026 13:43:04 -1000 Subject: [PATCH 07/54] feat: emit canonical control-flow graphs --- packages/ruam/src/compiler/cfg.ts | 319 ++++++++++++++++ packages/ruam/src/compiler/emitter.ts | 86 +++++ packages/ruam/src/compiler/index.ts | 340 +++++++++++++----- .../test/isogloss/cfg-bisimulation.test.ts | 185 ++++++++++ 4 files changed, 842 insertions(+), 88 deletions(-) create mode 100644 packages/ruam/src/compiler/cfg.ts create mode 100644 packages/ruam/test/isogloss/cfg-bisimulation.test.ts diff --git a/packages/ruam/src/compiler/cfg.ts b/packages/ruam/src/compiler/cfg.ts new file mode 100644 index 0000000..24a5bbd --- /dev/null +++ b/packages/ruam/src/compiler/cfg.ts @@ -0,0 +1,319 @@ +/** + * Canonical control-flow graph construction. + * + * This module translates the compiler's unencoded, pre-superinstruction + * instruction stream into node-identity control flow. It has no dependency on + * either execution backend. + * + * @module compiler/cfg + */ + +import type { Instruction } from "../types.js"; +import { + createSemanticInstruction, + type SemanticExit, + type SemanticInstruction, + type SemanticNodeId, + type SourceOriginId, +} from "./ir.js"; +import { + assertCanonicalSemanticOp, + SEMANTIC_OP_COUNT, + SemanticOp, + type SemanticOp as SemanticOpValue, +} from "./semantic-ops.js"; +import { getSemanticSignature } from "./semantic-signatures.js"; + +/** Input needed to create canonical nodes and typed exits for one unit. */ +export interface CanonicalCfgInput { + instructions: readonly Instruction[]; + originIds: readonly SourceOriginId[]; +} + +/** Execution-independent nodes plus their validated outgoing edges. */ +export interface CanonicalCfg { + nodes: SemanticInstruction[]; + exits: Map; + entryNode: SemanticNodeId; +} + +interface HandlerTarget { + catchTarget: SemanticNodeId | null; + finallyTarget: SemanticNodeId | null; +} + +const TAKEN_WHEN_FALSE = new Set([ + SemanticOp.JMP_FALSE, + SemanticOp.JMP_FALSE_KEEP, + SemanticOp.LOGICAL_AND, +]); + +const CONDITIONAL_DIRECT_JUMPS = new Set([ + SemanticOp.JMP_TRUE, + SemanticOp.JMP_FALSE, + SemanticOp.JMP_NULLISH, + SemanticOp.JMP_UNDEFINED, + SemanticOp.JMP_TRUE_KEEP, + SemanticOp.JMP_FALSE_KEEP, + SemanticOp.JMP_NULLISH_KEEP, + SemanticOp.LOGICAL_AND, + SemanticOp.LOGICAL_OR, + SemanticOp.NULLISH_COALESCE, +]); + +const STRUCTURED_EXCEPTION_OPS = new Set([ + SemanticOp.TRY_PUSH, + SemanticOp.TRY_POP, + SemanticOp.CATCH_BIND, + SemanticOp.CATCH_BIND_PATTERN, + SemanticOp.FINALLY_MARK, + SemanticOp.END_FINALLY, +]); + +/** + * Build a canonical CFG and fail closed on representation-specific opcodes or + * malformed direct targets. + */ +export function buildCanonicalCfg(input: CanonicalCfgInput): CanonicalCfg { + const { instructions, originIds } = input; + if (instructions.length === 0) { + throw new Error("RUAM_EMPTY_SEMANTIC_UNIT"); + } + if (originIds.length !== instructions.length) { + throw new Error( + `RUAM_ORIGIN_ARITY_MISMATCH: ${originIds.length} origins for ${instructions.length} nodes` + ); + } + + const nodes = instructions.map((instruction, id) => { + if ( + !Number.isSafeInteger(instruction.opcode) || + instruction.opcode < 0 || + instruction.opcode >= SEMANTIC_OP_COUNT + ) { + throw new Error( + `RUAM_INVALID_SEMANTIC_OP: node ${id} -> ${instruction.opcode}` + ); + } + const op = instruction.opcode as SemanticOpValue; + assertCanonicalSemanticOp(op); + return createSemanticInstruction({ + id, + op, + operand: instruction.operand, + originId: originIds[id]!, + }); + }); + + const exits = new Map(); + for (const node of nodes) exits.set(node.id, []); + + const pending: Array<{ + nodeId: SemanticNodeId; + handlers: HandlerTarget[]; + }> = [{ nodeId: 0, handlers: [] }]; + const visitedStates = new Set(); + + while (pending.length > 0) { + const state = pending.pop()!; + const stateKey = handlerStateKey(state.nodeId, state.handlers); + if (visitedStates.has(stateKey)) continue; + visitedStates.add(stateKey); + + const node = nodes[state.nodeId]!; + const nodeExits = exitsForNode(node, nodes.length, state.handlers); + const accumulated = exits.get(node.id)!; + for (const exit of nodeExits) { + if (!containsSameExit(accumulated, exit)) accumulated.push(exit); + } + + const normalHandlers = handlersAfterNormalExit( + node, + state.handlers, + nodes.length + ); + for (const exit of nodeExits) { + const target = exitTarget(exit); + if (target == null) continue; + const targetHandlers = + exit.kind === "exception" || exit.kind === "finally" + ? state.handlers.slice(0, -1) + : normalHandlers; + pending.push({ nodeId: target, handlers: targetHandlers }); + } + } + + for (const [id, nodeExits] of exits) { + exits.set(id, Object.freeze(nodeExits.slice()) as SemanticExit[]); + } + + return { + nodes: Object.freeze(nodes.slice()) as SemanticInstruction[], + exits, + entryNode: 0, + }; +} + +function exitsForNode( + node: SemanticInstruction, + nodeCount: number, + handlers: readonly HandlerTarget[] +): SemanticExit[] { + const { op, operand, id } = node; + const next = id + 1; + const innermostHandler = handlers[handlers.length - 1]; + const signature = getSemanticSignature(op); + let normal: SemanticExit[]; + + if (op === SemanticOp.JMP) { + normal = [{ kind: "fallthrough", target: directTarget(operand, nodeCount, id) }]; + } else if (CONDITIONAL_DIRECT_JUMPS.has(op)) { + const taken = directTarget(operand, nodeCount, id); + const notTaken = fallthroughTarget(next, nodeCount, id); + normal = TAKEN_WHEN_FALSE.has(op) + ? [ + { kind: "branch-false", target: taken }, + { kind: "branch-true", target: notTaken }, + ] + : [ + { kind: "branch-true", target: taken }, + { kind: "branch-false", target: notTaken }, + ]; + } else if ( + op === SemanticOp.TABLE_SWITCH || + op === SemanticOp.LOOKUP_SWITCH + ) { + throw new Error( + `RUAM_CANONICAL_SWITCH_TABLE_REQUIRES_TARGETS: node ${id}` + ); + } else if (signature.control === "call") { + normal = [{ kind: "call", resume: fallthroughTarget(next, nodeCount, id) }]; + } else if (signature.control === "yield") { + normal = [{ kind: "yield", resume: fallthroughTarget(next, nodeCount, id) }]; + } else if (signature.control === "await") { + normal = [{ kind: "await", resume: fallthroughTarget(next, nodeCount, id) }]; + } else if (signature.control === "return") { + normal = innermostHandler?.finallyTarget != null + ? [{ kind: "finally", target: innermostHandler.finallyTarget }] + : [{ kind: "return" }]; + } else if (op === SemanticOp.THROW_IF_NOT_OBJECT) { + normal = [ + { + kind: "fallthrough", + target: fallthroughTarget(next, nodeCount, id), + }, + ]; + } else if (signature.control === "throw") { + normal = abruptExceptionExit(innermostHandler); + } else { + normal = [{ kind: "fallthrough", target: fallthroughTarget(next, nodeCount, id) }]; + } + + if ( + signature.mayThrow && + (signature.control !== "throw" || op === SemanticOp.THROW_IF_NOT_OBJECT) && + !STRUCTURED_EXCEPTION_OPS.has(op) + ) { + const exceptional = abruptExceptionExit(innermostHandler); + if (!containsSameExit(normal, exceptional[0]!)) normal.push(...exceptional); + } + + return normal; +} + +function handlersAfterNormalExit( + node: SemanticInstruction, + handlers: readonly HandlerTarget[], + nodeCount: number +): HandlerTarget[] { + if (node.op === SemanticOp.TRY_PUSH) { + return [ + ...handlers, + decodeTryTargets(node.operand, nodeCount, node.id), + ]; + } + if (node.op === SemanticOp.TRY_POP) { + if (handlers.length === 0) { + throw new Error(`RUAM_UNBALANCED_TRY_POP: node ${node.id}`); + } + return handlers.slice(0, -1); + } + return handlers.slice(); +} + +function decodeTryTargets( + operand: number, + nodeCount: number, + source: SemanticNodeId +): HandlerTarget { + const catchRaw = (operand >>> 16) & 0xffff; + const finallyRaw = operand & 0xffff; + const catchTarget = + catchRaw === 0xffff ? null : directTarget(catchRaw, nodeCount, source); + const finallyTarget = + finallyRaw === 0xffff + ? null + : directTarget(finallyRaw, nodeCount, source); + if (catchTarget == null && finallyTarget == null) { + throw new Error(`RUAM_EMPTY_TRY_HANDLER: node ${source}`); + } + return { catchTarget, finallyTarget }; +} + +function abruptExceptionExit( + handler: HandlerTarget | undefined +): SemanticExit[] { + if (handler?.catchTarget != null) { + return [{ kind: "exception", target: handler.catchTarget }]; + } + if (handler?.finallyTarget != null) { + return [{ kind: "finally", target: handler.finallyTarget }]; + } + return [{ kind: "throw" }]; +} + +function directTarget( + target: number, + nodeCount: number, + source: SemanticNodeId +): SemanticNodeId { + if (!Number.isSafeInteger(target) || target < 0 || target >= nodeCount) { + throw new Error( + `RUAM_INVALID_SEMANTIC_TARGET: node ${source} -> ${target} (size ${nodeCount})` + ); + } + return target; +} + +function fallthroughTarget( + target: number, + nodeCount: number, + source: SemanticNodeId +): SemanticNodeId { + return directTarget(target, nodeCount, source); +} + +function containsSameExit( + exits: readonly SemanticExit[], + candidate: SemanticExit +): boolean { + return exits.some((exit) => JSON.stringify(exit) === JSON.stringify(candidate)); +} + +function exitTarget(exit: SemanticExit): SemanticNodeId | null { + if ("target" in exit) return exit.target; + if ("resume" in exit) return exit.resume; + return null; +} + +function handlerStateKey( + nodeId: SemanticNodeId, + handlers: readonly HandlerTarget[] +): string { + return `${nodeId}|${handlers + .map( + (handler) => + `${handler.catchTarget ?? "-"}:${handler.finallyTarget ?? "-"}` + ) + .join(",")}`; +} diff --git a/packages/ruam/src/compiler/emitter.ts b/packages/ruam/src/compiler/emitter.ts index 6322991..3096de5 100644 --- a/packages/ruam/src/compiler/emitter.ts +++ b/packages/ruam/src/compiler/emitter.ts @@ -9,6 +9,14 @@ */ import type { Instruction, ConstantPoolEntry } from "../types.js"; +import type { SourceOrigin, SourceOriginId } from "./ir.js"; + +const UNKNOWN_SOURCE_ORIGIN: SourceOrigin = Object.freeze({ + start: 0, + end: 0, + line: 1, + column: 0, +}); /** * Bytecode emitter — the write side of compilation. @@ -27,9 +35,29 @@ export class Emitter { /** Accumulated constant pool. */ readonly constants: ConstantPoolEntry[] = []; + /** + * Deduplicated source locations referenced by + * {@link instructionOriginIds}. This metadata is deliberately parallel to + * the legacy instruction array so adding it cannot change bytecode output. + */ + readonly origins: SourceOrigin[] = []; + + /** Source-origin index recorded at the moment each instruction is emitted. */ + readonly instructionOriginIds: SourceOriginId[] = []; + /** Map from serialised constant key → pool index (for de-duplication). */ private readonly constantMap = new Map(); + /** Map from a stable source-location key → origin table index. */ + private readonly originMap = new Map(); + + /** Origin inherited by emit calls in the current lexical compilation span. */ + private currentOriginId: SourceOriginId; + + constructor(defaultOrigin: SourceOrigin = UNKNOWN_SOURCE_ORIGIN) { + this.currentOriginId = this.addOrigin(defaultOrigin); + } + /** Current instruction pointer (= number of emitted instructions). */ get ip(): number { return this.instructions.length; @@ -43,9 +71,28 @@ export class Emitter { emit(opcode: number, operand: number = 0): number { const idx = this.instructions.length; this.instructions.push({ opcode, operand }); + this.instructionOriginIds.push(this.currentOriginId); return idx; } + /** + * Associate every instruction emitted by `emitWithin` with `origin`. + * + * The previous origin is restored even when a visitor throws, and nested + * spans behave like a lexical stack. Visitors can adopt this incrementally: + * instructions emitted outside a span retain the emitter's default origin. + */ + withOrigin(origin: SourceOrigin | null | undefined, emitWithin: () => T): T { + if (!origin) return emitWithin(); + const previousOriginId = this.currentOriginId; + this.currentOriginId = this.addOrigin(origin); + try { + return emitWithin(); + } finally { + this.currentOriginId = previousOriginId; + } + } + /** Patch a previously-emitted jump instruction's target. */ patchJump(instrIndex: number, target: number): void { this.instructions[instrIndex]!.operand = target; @@ -98,6 +145,17 @@ export class Emitter { addBigIntConstant(value: string): number { return this.addConstant({ type: "bigint", value }); } + + private addOrigin(origin: SourceOrigin): SourceOriginId { + const normalized = normalizeOrigin(origin); + const key = originKey(normalized); + const existing = this.originMap.get(key); + if (existing !== undefined) return existing; + const id = this.origins.length; + this.origins.push(Object.freeze(normalized)); + this.originMap.set(key, id); + return id; + } } // --------------------------------------------------------------------------- @@ -111,3 +169,31 @@ function constantKey(entry: ConstantPoolEntry): string { } return `${entry.type}:${String(entry.value)}`; } + +function normalizeOrigin(origin: SourceOrigin): SourceOrigin { + const start = Number.isSafeInteger(origin.start) && origin.start >= 0 + ? origin.start + : 0; + const end = Number.isSafeInteger(origin.end) && origin.end >= start + ? origin.end + : start; + const line = Number.isSafeInteger(origin.line) && origin.line >= 1 + ? origin.line + : 1; + const column = Number.isSafeInteger(origin.column) && origin.column >= 0 + ? origin.column + : 0; + return origin.file + ? { file: origin.file, start, end, line, column } + : { start, end, line, column }; +} + +function originKey(origin: SourceOrigin): string { + return [ + origin.file ?? "", + origin.start, + origin.end, + origin.line, + origin.column, + ].join(":"); +} diff --git a/packages/ruam/src/compiler/index.ts b/packages/ruam/src/compiler/index.ts index 4b472d5..e0a4149 100644 --- a/packages/ruam/src/compiler/index.ts +++ b/packages/ruam/src/compiler/index.ts @@ -31,6 +31,13 @@ import { type CaptureAnalysisResult, } from "./capture-analysis.js"; import { optimizeInstructions } from "./optimizer.js"; +import { buildCanonicalCfg } from "./cfg.js"; +import type { + SemanticRootGroup, + SemanticUnit, + SourceOrigin, +} from "./ir.js"; +import type { RootGroupId } from "./types.js"; // --------------------------------------------------------------------------- // Scope-object elision @@ -185,6 +192,8 @@ export interface CompileContext { blockDepth: number; } +type SemanticUnitDraft = Omit; + // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- @@ -202,6 +211,61 @@ export function compileFunction(fnPath: NodePath): BytecodeUnit { return unit; } +/** + * Compile one protected root into execution-independent semantic IR. + * + * This migration API intentionally shares the exact visitor pass used by + * {@link compileFunction}, snapshots its canonical stream before optimizer + * fusion, and discards the legacy unit. Product output remains unchanged until + * the Isogloss pipeline consumes this API. + */ +export function compileSemanticFunction( + fnPath: NodePath, + rootGroupId?: RootGroupId +): SemanticRootGroup { + const legacyChildren: BytecodeUnit[] = []; + const drafts: SemanticUnitDraft[] = []; + const legacyRoot = compileFunctionInner(fnPath, legacyChildren, drafts); + const resolvedRootGroupId = rootGroupId ?? `rg_${legacyRoot.id}`; + const draftById = new Map(drafts.map((draft) => [draft.id, draft])); + const orderedIds: string[] = []; + const visitedIds = new Set(); + const visitUnit = (id: string): void => { + if (visitedIds.has(id)) return; + visitedIds.add(id); + orderedIds.push(id); + const draft = draftById.get(id); + if (!draft) throw new Error(`RUAM_MISSING_SEMANTIC_UNIT: ${id}`); + for (const childId of draft.childUnitIds) visitUnit(childId); + }; + visitUnit(legacyRoot.id); + if (orderedIds.length !== drafts.length) { + throw new Error( + `RUAM_ORPHANED_SEMANTIC_UNIT: reached ${orderedIds.length} of ${drafts.length}` + ); + } + const units = orderedIds.map((id) => { + const draft = draftById.get(id); + if (!draft) { + throw new Error(`RUAM_MISSING_SEMANTIC_UNIT: ${id}`); + } + return Object.freeze({ + ...draft, + rootGroupId: resolvedRootGroupId, + }) as SemanticUnit; + }); + const usedSemantics = new Set(units.flatMap((unit) => unit.nodes.map((node) => node.op))); + + return { + id: resolvedRootGroupId, + entryUnitId: legacyRoot.id, + units, + usedSemantics, + hasAsync: units.some((unit) => unit.isAsync), + hasGenerator: units.some((unit) => unit.isGenerator), + }; +} + // --------------------------------------------------------------------------- // Core compilation logic // --------------------------------------------------------------------------- @@ -214,14 +278,16 @@ export function compileFunction(fnPath: NodePath): BytecodeUnit { */ function compileFunctionInner( fnPath: NodePath, - allUnits: BytecodeUnit[] + allUnits: BytecodeUnit[], + semanticDrafts?: SemanticUnitDraft[] ): BytecodeUnit { const node = fnPath.node; const params = fnPath.get("params") as NodePath[]; const paramCount = params.length; - const emitter = new Emitter(); + const emitter = new Emitter(sourceOriginFromPath(fnPath)); const scope = new ScopeAnalyzer(0); + const directChildUnitIds: string[] = []; const isStrict = detectStrict(fnPath); const isGenerator = !!node.generator; @@ -257,56 +323,58 @@ function compileFunctionInner( // -- Declare simple parameters ------------------------------------------- for (let i = 0; i < params.length; i++) { const param = params[i]!; - if (param.isIdentifier()) { - declareAndStoreParam( - param.node.name, - i, - emitter, - scope, - registerMap, - slotMap, - captureResult - ); - } else if (param.isAssignmentPattern()) { - const left = param.get("left"); - if (left.isIdentifier()) { - scope.declare(left.node.name, "param"); - const pName = left.node.name; - if (registerMap.has(pName)) { - // Will be stored via register in compileComplexParams - } else if (slotMap.has(pName)) { - const slotIdx = slotMap.get(pName)!; - const nameIdx = emitter.addStringConstant(pName); - emitter.emit( - Op.DECLARE_SLOT, - (slotIdx & 0xffff) | ((nameIdx & 0xffff) << 16) - ); - } else { - const nameIdx = emitter.addStringConstant(pName); - emitter.emit(Op.DECLARE_VAR, nameIdx); + emitter.withOrigin(sourceOriginFromPath(param), () => { + if (param.isIdentifier()) { + declareAndStoreParam( + param.node.name, + i, + emitter, + scope, + registerMap, + slotMap, + captureResult + ); + } else if (param.isAssignmentPattern()) { + const left = param.get("left"); + if (left.isIdentifier()) { + scope.declare(left.node.name, "param"); + const pName = left.node.name; + if (registerMap.has(pName)) { + // Will be stored via register in compileComplexParams + } else if (slotMap.has(pName)) { + const slotIdx = slotMap.get(pName)!; + const nameIdx = emitter.addStringConstant(pName); + emitter.emit( + Op.DECLARE_SLOT, + (slotIdx & 0xffff) | ((nameIdx & 0xffff) << 16) + ); + } else { + const nameIdx = emitter.addStringConstant(pName); + emitter.emit(Op.DECLARE_VAR, nameIdx); + } } - } - } else if (param.isRestElement()) { - const arg = param.get("argument"); - if (arg.isIdentifier()) { - scope.declare(arg.node.name, "param"); - const pName = arg.node.name; - if (registerMap.has(pName)) { - // Will be stored via register in compileComplexParams - } else if (slotMap.has(pName)) { - const slotIdx = slotMap.get(pName)!; - const nameIdx = emitter.addStringConstant(pName); - emitter.emit( - Op.DECLARE_SLOT, - (slotIdx & 0xffff) | ((nameIdx & 0xffff) << 16) - ); - } else { - const nameIdx = emitter.addStringConstant(pName); - emitter.emit(Op.DECLARE_VAR, nameIdx); + } else if (param.isRestElement()) { + const arg = param.get("argument"); + if (arg.isIdentifier()) { + scope.declare(arg.node.name, "param"); + const pName = arg.node.name; + if (registerMap.has(pName)) { + // Will be stored via register in compileComplexParams + } else if (slotMap.has(pName)) { + const slotIdx = slotMap.get(pName)!; + const nameIdx = emitter.addStringConstant(pName); + emitter.emit( + Op.DECLARE_SLOT, + (slotIdx & 0xffff) | ((nameIdx & 0xffff) << 16) + ); + } else { + const nameIdx = emitter.addStringConstant(pName); + emitter.emit(Op.DECLARE_VAR, nameIdx); + } } } - } - // Destructuring params are handled in the second pass below. + // Destructuring params are handled in the second pass below. + }); } // -- Build CompileContext ------------------------------------------------ @@ -316,21 +384,38 @@ function compileFunctionInner( blockDepth: 0, compileNestedFunction(innerFnPath, parentEmitter, _parentScope) { - const childUnit = compileFunctionInner(innerFnPath, allUnits); + const childUnit = compileFunctionInner( + innerFnPath, + allUnits, + semanticDrafts + ); allUnits.push(childUnit); + directChildUnitIds.push(childUnit.id); const idIdx = parentEmitter.addStringConstant(childUnit.id); - parentEmitter.emit(Op.NEW_CLOSURE, idIdx); + parentEmitter.withOrigin(sourceOriginFromPath(innerFnPath), () => { + parentEmitter.emit(Op.NEW_CLOSURE, idIdx); + }); }, compileClassExpression(classPath, parentEmitter, parentScope) { - compileClassExpr( - classPath, - parentEmitter, - parentScope, - this, - allUnits, - compileFunctionInner - ); + parentEmitter.withOrigin(sourceOriginFromPath(classPath), () => { + compileClassExpr( + classPath, + parentEmitter, + parentScope, + this, + allUnits, + (innerFnPath, nestedAllUnits) => { + const childUnit = compileFunctionInner( + innerFnPath, + nestedAllUnits, + semanticDrafts + ); + directChildUnitIds.push(childUnit.id); + return childUnit; + } + ); + }); }, compileDestructuring(pattern, em, sc) { @@ -343,22 +428,41 @@ function compileFunctionInner( // -- Compile the function body ------------------------------------------- const bodyPath = fnPath.get("body"); - if (bodyPath.isBlockStatement()) { - const loopStack: LoopContext[] = []; - compileBody(bodyPath.get("body"), emitter, scope, ctx, loopStack); - } else if (bodyPath.isExpression()) { - compileExpression( - bodyPath as NodePath, - emitter, - scope, - ctx - ); - emitter.emit(Op.RETURN, 0); - } + emitter.withOrigin(sourceOriginFromPath(bodyPath), () => { + if (bodyPath.isBlockStatement()) { + const loopStack: LoopContext[] = []; + compileBody(bodyPath.get("body"), emitter, scope, ctx, loopStack); + } else if (bodyPath.isExpression()) { + compileExpression( + bodyPath as NodePath, + emitter, + scope, + ctx + ); + emitter.emit(Op.RETURN, 0); + } + }); // Ensure every code path ends with a return ensureTrailingReturn(emitter); + // Snapshot the canonical language stream before peephole rewriting and + // superinstruction fusion. The legacy optimizer remains untouched. + const canonicalCfg = semanticDrafts + ? buildCanonicalCfg({ + instructions: emitter.instructions.map((instruction) => ({ + ...instruction, + })), + originIds: emitter.instructionOriginIds.slice(), + }) + : null; + const canonicalConstants = semanticDrafts + ? emitter.constants.map((constant) => ({ ...constant })) + : null; + const canonicalOrigins = semanticDrafts + ? emitter.origins.map((origin) => ({ ...origin })) + : null; + // -- Optimization passes (Tiers 2 & 3) ---------------------------------- optimizeInstructions(emitter); @@ -381,8 +485,9 @@ function compileFunctionInner( const usesExceptions = computeUsesExceptions(emitter.instructions); const usesThisContext = computeUsesThisContext(emitter.instructions); - return { - id: genUnitId(), + const id = genUnitId(); + const unit: BytecodeUnit = { + id, constants: emitter.constants, instructions: emitter.instructions, jumpTable: {}, @@ -401,6 +506,37 @@ function compileFunctionInner( outerNames: scope.outerNames, childUnits: [], }; + + if ( + semanticDrafts && + canonicalCfg && + canonicalConstants && + canonicalOrigins + ) { + semanticDrafts.push({ + id, + constants: canonicalConstants, + nodes: canonicalCfg.nodes, + exits: canonicalCfg.exits, + entryNode: canonicalCfg.entryNode, + origins: canonicalOrigins, + childUnitIds: directChildUnitIds, + paramCount, + registerCount: scope.totalRegisters, + slotCount: slotMap.size, + isStrict, + isGenerator, + isAsync, + isArrow, + scopeless, + usesExceptions, + usesThisContext, + nameConstIndex, + outerNames: scope.outerNames.slice(), + }); + } + + return unit; } // --------------------------------------------------------------------------- @@ -454,21 +590,22 @@ function compileComplexParams( ): void { for (let i = 0; i < params.length; i++) { const param = params[i]!; - - if (param.isAssignmentPattern()) { - compileDefaultParam(param, i, emitter, scope, ctx); - } else if (param.isRestElement()) { - compileRestParam(param, i, emitter, scope, ctx); - } else if (!param.isIdentifier()) { - // Destructuring param - emitter.emit(Op.LOAD_ARG, i); - compileDestructuringPattern( - param as NodePath, - emitter, - scope, - ctx - ); - } + emitter.withOrigin(sourceOriginFromPath(param), () => { + if (param.isAssignmentPattern()) { + compileDefaultParam(param, i, emitter, scope, ctx); + } else if (param.isRestElement()) { + compileRestParam(param, i, emitter, scope, ctx); + } else if (!param.isIdentifier()) { + // Destructuring param + emitter.emit(Op.LOAD_ARG, i); + compileDestructuringPattern( + param as NodePath, + emitter, + scope, + ctx + ); + } + }); } } @@ -592,3 +729,30 @@ function ensureTrailingReturn(emitter: Emitter): void { emitter.emit(Op.RETURN_VOID, 0); } } + +/** Convert Babel's nullable location fields into canonical owner metadata. */ +function sourceOriginFromPath(path: NodePath): SourceOrigin { + const node = path.node; + const location = node.loc as + | (t.SourceLocation & { filename?: string | null }) + | null + | undefined; + const start = typeof node.start === "number" && node.start >= 0 ? node.start : 0; + const end = + typeof node.end === "number" && node.end >= start ? node.end : start; + const file = location?.filename ?? undefined; + return file + ? { + file, + start, + end, + line: location?.start.line ?? 1, + column: location?.start.column ?? 0, + } + : { + start, + end, + line: location?.start.line ?? 1, + column: location?.start.column ?? 0, + }; +} diff --git a/packages/ruam/test/isogloss/cfg-bisimulation.test.ts b/packages/ruam/test/isogloss/cfg-bisimulation.test.ts new file mode 100644 index 0000000..4a8305a --- /dev/null +++ b/packages/ruam/test/isogloss/cfg-bisimulation.test.ts @@ -0,0 +1,185 @@ +import { describe, expect, it } from "bun:test"; +import { parse } from "@babel/parser"; +import type { NodePath } from "@babel/traverse"; +import type * as t from "@babel/types"; +import { traverse } from "../../src/babel-compat.js"; +import { buildCanonicalCfg } from "../../src/compiler/cfg.js"; +import { + compileFunction, + compileSemanticFunction, + resetUnitCounter, +} from "../../src/compiler/index.js"; +import { Op } from "../../src/compiler/opcodes.js"; +import { + NON_CANONICAL_SEMANTIC_OPS, + SemanticOp, +} from "../../src/compiler/semantic-ops.js"; + +function getFunctionPath( + source: string, + name: string +): NodePath { + const ast = parse(source, { + sourceType: "script", + sourceFilename: "fixture.js", + }); + let result: NodePath | undefined; + traverse(ast, { + Function(path) { + const id = "id" in path.node ? path.node.id : null; + if (id?.type === "Identifier" && id.name === name) { + result = path as NodePath; + path.stop(); + } + }, + }); + if (!result) throw new Error(`Missing function ${name}`); + return result; +} + +describe("canonical compiler CFG", () => { + it("preserves the unfused semantic stream while leaving legacy output optimized", () => { + const source = `function sum(a, b) { return a + b; }`; + + resetUnitCounter(123); + const legacy = compileFunction(getFunctionPath(source, "sum")); + resetUnitCounter(123); + const semantic = compileSemanticFunction( + getFunctionPath(source, "sum"), + "root-test" + ); + const root = semantic.units[0]!; + + expect(semantic.id).toBe("root-test"); + expect(semantic.entryUnitId).toBe(legacy.id); + expect(legacy.instructions.some((instruction) => + NON_CANONICAL_SEMANTIC_OPS.has(instruction.opcode as Op) + )).toBe(true); + expect(root.nodes.every((node) => + !NON_CANONICAL_SEMANTIC_OPS.has(node.op) + )).toBe(true); + expect(root.nodes.some((node) => node.op === SemanticOp.ADD)).toBe(true); + expect(root.nodes.map((node) => node.id)).toEqual( + root.nodes.map((_, index) => index) + ); + }); + + it("assigns nested units to one root group and records owner origins", () => { + const source = ` + function outer(value) { + return function inner(delta) { return value + delta; }; + } + `; + + resetUnitCounter(456); + const group = compileSemanticFunction( + getFunctionPath(source, "outer"), + "nested-root" + ); + const root = group.units.find((unit) => unit.id === group.entryUnitId)!; + const child = group.units.find((unit) => unit.id !== group.entryUnitId)!; + + expect(group.units).toHaveLength(2); + expect(group.units.every((unit) => unit.rootGroupId === "nested-root")).toBe( + true + ); + expect(root.childUnitIds).toEqual([child.id]); + expect(child.childUnitIds).toEqual([]); + expect(root.origins.some((origin) => origin.file === "fixture.js")).toBe( + true + ); + expect(root.nodes.every((node) => root.origins[node.originId] != null)).toBe( + true + ); + }); + + it("models branches, resumptions, and structured abrupt completion", () => { + const packedTryTargets = (4 << 16) | 6; + const cfg = buildCanonicalCfg({ + instructions: [ + { opcode: Op.TRY_PUSH, operand: packedTryTargets }, + { opcode: Op.CALL, operand: 0 }, + { opcode: Op.RETURN, operand: 0 }, + { opcode: Op.TRY_POP, operand: 0 }, + { opcode: Op.CATCH_BIND, operand: -1 }, + { opcode: Op.THROW, operand: 0 }, + { opcode: Op.FINALLY_MARK, operand: 0 }, + { opcode: Op.END_FINALLY, operand: 0 }, + { opcode: Op.JMP_FALSE, operand: 10 }, + { opcode: Op.AWAIT, operand: 0 }, + { opcode: Op.RETURN_VOID, operand: 0 }, + ], + originIds: Array.from({ length: 11 }, () => 0), + }); + + expect(cfg.exits.get(1)).toEqual([ + { kind: "call", resume: 2 }, + { kind: "exception", target: 4 }, + ]); + expect(cfg.exits.get(2)).toEqual([{ kind: "finally", target: 6 }]); + expect(cfg.exits.get(5)).toEqual([{ kind: "throw" }]); + expect(cfg.exits.get(8)).toEqual([ + { kind: "branch-false", target: 10 }, + { kind: "branch-true", target: 9 }, + ]); + expect(cfg.exits.get(9)).toEqual([ + { kind: "await", resume: 10 }, + { kind: "throw" }, + ]); + }); + + it("canonicalizes representative nested control flow without backend data", () => { + const source = ` + async function complex(input, callback = (x) => x) { + let total = 0; + for (let i = 0; i < input.length; i++) { + try { + if (input[i] == null) continue; + total += await callback?.(input[i]); + } catch (error) { + total -= 1; + } finally { + total += 2; + } + } + class Result { value() { return total; } } + return new Result().value(); + } + `; + + resetUnitCounter(789); + const group = compileSemanticFunction( + getFunctionPath(source, "complex"), + "complex-root" + ); + + expect(group.hasAsync).toBe(true); + expect(group.units.length).toBeGreaterThan(1); + for (const unit of group.units) { + expect(unit.nodes.length).toBeGreaterThan(0); + expect(unit.exits.size).toBe(unit.nodes.length); + expect(unit.nodes.every((node) => + !NON_CANONICAL_SEMANTIC_OPS.has(node.op) + )).toBe(true); + } + }); + + it("rejects malformed targets and representation-specific operations", () => { + expect(() => + buildCanonicalCfg({ + instructions: [{ opcode: Op.JMP, operand: 2 }], + originIds: [0], + }) + ).toThrow("RUAM_INVALID_SEMANTIC_TARGET"); + + expect(() => + buildCanonicalCfg({ + instructions: [ + { opcode: Op.MUTATE, operand: 0 }, + { opcode: Op.RETURN_VOID, operand: 0 }, + ], + originIds: [0, 0], + }) + ).toThrow("RUAM_NON_CANONICAL_SEMANTIC_OP"); + }); +}); From 0f0a8677ec2c8280f9cd1eefc6bd4ce4104cea57 Mon Sep 17 00:00:00 2001 From: owengregson Date: Fri, 24 Jul 2026 13:45:59 -1000 Subject: [PATCH 08/54] docs: redesign isogloss for dynamic resistance --- ...ynamic-instrumentation-ideation-results.md | 644 ++++++++++++++++++ ...-traveling-isogloss-implementation-plan.md | 17 +- 2 files changed, 660 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/specs/2026-07-24-dynamic-instrumentation-ideation-results.md diff --git a/docs/superpowers/specs/2026-07-24-dynamic-instrumentation-ideation-results.md b/docs/superpowers/specs/2026-07-24-dynamic-instrumentation-ideation-results.md new file mode 100644 index 0000000..77e8b5d --- /dev/null +++ b/docs/superpowers/specs/2026-07-24-dynamic-instrumentation-ideation-results.md @@ -0,0 +1,644 @@ +# Project Kaleidoscope D2 — Dynamic-Instrumentation Ideation Results + +**Date:** 2026-07-24 +**Status:** Architecture decision candidate; security-sensitive Isogloss work paused pending review +**Parent:** [Traveling Isogloss implementation plan](2026-07-24-traveling-isogloss-implementation-plan.md) +**Attacker:** Full artifact and runtime access, chosen inputs, stepping, hooks, heap/closure snapshots, patching, repeated runs, and design knowledge + +## 1. Executive decision + +The original Traveling Isogloss design does not materially resist a full-access +dynamic analyst because it eventually publishes this event: + +```text +left clause + right clause + carrier witness + -> one SemanticOp + one operand projection + -> one semantic handler +``` + +That is a stable semantic choke point. An analyst can instrument the resolver or +handler edge and recover a reusable, ordered semantic trace. Ambiguity before +resolution does not protect the answer after the runtime repeatedly exposes it. + +The recommended redesign is **Braided Poly-Ontology Region Fabric (BPRF)**: + +1. Partition canonical IR at source-observable JavaScript effects. +2. Fuse multiple pure data/control operations into regional transitions. +3. Split direct calls across caller-owned entry and return continuations. +4. Generate several genuinely different regional realization families. +5. Change internal frame layouts and wire bases at regional boundaries. +6. Emit unavoidable JavaScript effects at distributed, site-specific sinks. +7. Let the carrier route region/continuation contracts, never language + operations. + +`SemanticOp` remains compiler-side vocabulary and a reference-oracle tool. It +must not exist as a production runtime event, value, table key, or handler +identity. + +The security-sensitive lattice schema, artifact schema, resolver, verifier, +handler runtime, and carrier witness design remain paused. Deterministic entropy, +canonical semantic IR, source origins, CFG/effect analysis, baselines, and test +infrastructure remain valid and may continue. + +## 2. Honest impossibility boundary + +No server-free JavaScript artifact can hide its extensional behavior from this +attacker: + +- Inputs, returns, thrown values, and host-visible effects are observable. +- The client must possess enough information to produce each concrete result. +- Client-generated or client-consumed randomness is observable diversity, not + a secret. +- A finite implementation family can eventually be covered and normalized. +- A finite input domain can be queried exhaustively; an infinite-domain artifact + remains a behavioral oracle. +- The attacker can redistribute or invoke the protected artifact unchanged even + if recovering source-like code remains costly. +- Work imposed on exhaustive tracing is paid at least partly by legitimate users. + +The defensible goal is therefore: + +> Remove cheap semantic choke points and make recovery of a reusable, +> source-like function model require whole-region, multi-context, +> multi-realization reconstruction. + +Claims of confidentiality, anti-hooking, cryptographic secrecy, or prevention of +eventual recovery would be false without a different trust domain such as a +server-held secret, trusted hardware, attestation, or query limitation. + +This boundary is consistent with foundational limits on general virtual +black-box obfuscation and with practical dynamic deobfuscation results. Syntia, +for example, reports learning more than 94% of arithmetic handlers in two +virtualization obfuscators, while later work such as Loki explicitly targets +trace slicing, symbolic extraction, and synthesis. See: + +- [The impossibility of obfuscation with auxiliary input or a universal simulator](https://arxiv.org/abs/1401.0348) +- [Syntia: Synthesizing the Semantics of Obfuscated Code](https://www.usenix.org/conference/usenixsecurity17/technical-sessions/presentation/blazytko) +- [Loki: Hardening Code Obfuscation](https://www.usenix.org/system/files/sec22-schloegel.pdf) +- [Defeating State-of-the-Art White-Box Countermeasures with Advanced Gray-Box Attacks](https://eprint.iacr.org/2020/413) + +## 3. Uniqueness protocol + +The campaign reused Project Kaleidoscope's novelty discipline: + +1. A candidate needed one primary mechanism not shared by another candidate. +2. Opcode churn, encryption, anti-hooking, dead code, and other known Ruam/VM + mechanisms were forbidden as primary ideas. +3. Feasibility annotated ideas but did not delete unusual ideas. +4. Near-duplicates were clustered and rejected explicitly. +5. The final recommendation had to be a fusion whose dynamic reconstruction + problem was not reducible to any one parent. + +`Recognizability` ranges from 0 to 1; lower is more structurally novel. Values at +or above 0.75 are iterations rather than invention and cannot become Rank 1 +without a genuinely new fusion. + +The engineering score uses 1–5 values and this weighting: + +```text +30% dynamic extraction cost +20% cross-input amortization resistance +15% trace non-transferability +10% patch resistance without detection +15% correctness feasibility +10% runtime/build/size efficiency +``` + +Scores are hypotheses for falsification, not security ratings. + +## 4. Ranked slate + +| Rank | Direction | Primary mechanism | Recognizability | Engineering score | +|---:|---|---|---:|---:| +| 1 | Multi-ontology regional realizations | Change the causal execution model | 0.46 | 4.15 | +| 2 | Region-fused contextual linearization | Destroy operation/function granularity | 0.57 | 4.05 | +| 3 | Encoded state and wire-basis drift | Change value identity and representation | 0.71 | 3.95 | +| 4 | Distributed effect sinks | Disperse unavoidable semantic choke points | 0.60 | 3.65 | +| 5 | Program-wide root fusion | Enlarge the minimum reconstruction scope | 0.65 | 3.60 | +| 6 | Cross-invocation braiding | Make traces history/context dependent | 0.54 | 3.55 | +| 7 | Solver/constraint execution | Replace imperative steps with relations | 0.63 | 3.55 | +| 8 | All-path effect predication | Remove taken-path control traces | 0.68 | 3.50 | +| 9 | Distributed continuation mesh | Decentralize execution transport | 0.72 | 3.45 | +| 10 | Input-conditioned transient specialization | Change topology per invocation | 0.67 | 3.30 | +| 11 | Counterfactual worlds | Dilute attribution across valid states | 0.56 | 3.25 | +| 12 | Transactional shadow heap | Defer stateful host observability | 0.70 | 3.05 | +| 13 | Multi-input cohort coding | Couple one query to neighboring behaviors | 0.49 | 2.95 | +| 14 | Reversible compute/uncompute | Remove durable intermediate residue | 0.79 | 2.15 | + +## 5. Candidate details + +### 5.1 Multi-ontology regional realizations + +**Mechanism.** Lower one effect-delimited region into structurally different +execution ontologies, such as predicated dataflow, continuation residuals, +decision diagrams, or algebraic transitions. Renaming, block reordering, and +encoding changes do not count. + +**Dynamic value.** A lifter trained on one realization cannot assume the same +node, value, or control vocabulary in another. + +**Observable choke point.** Region contracts and host-effect sites. + +**Risks and cost.** Exact equivalence across coercion, throwing, allocation, +property access, and evaluation order is difficult. Begin with proven pure +regions. Build complexity and artifact size are high. + +**Falsification.** Train graph alignment across families. Reject a family if a +small normalizer maps it to canonical IR after only a few examples. + +### 5.2 Region-fused contextual linearization + +**Mechanism.** Fuse several data/control operations into one regional +transition. Split direct callees into caller-specific entry fragments and +return continuations so no internal generated function contains a complete +logical source function. + +**Dynamic value.** One hook observes a regional/contextual transition rather +than one language operation or complete callee. + +**Observable choke point.** Public/indirect entries and regional effects. + +**Risks and cost.** Calls, recursion, exceptions, reentrancy, async, `this`, and +exact effect ordering require a continuation verifier. Code size is high, while +runtime can improve relative to interpretation. + +**Falsification.** Instrument every codelet. Reject if a stable closed subgraph +contains a complete callee or one trace maps linearly to source operations. + +### 5.3 Encoded state and wire-basis drift + +**Mechanism.** Represent eligible locals, predicates, and pure results across +shares or encoded wires. Rebase and permute the representation at every +region/continuation transition. + +**Dynamic value.** Def-use recovery must correlate several locations across +changing bases rather than follow one stable virtual register. + +**Observable choke point.** Entry encoding and materialization at effects, +throws, or return. + +**Risks and cost.** This is diversity, not secrecy. Objects, identity, symbols, +proxies, closures, and coercive operations generally cannot stay encoded. +Arithmetic/memory overhead is high, and higher-order analysis can defeat masks. + +**Falsification.** Reject if one fixed recombination or slot permutation +recovers most logical intermediates across histories. + +### 5.4 Distributed effect sinks + +**Mechanism.** Emit property operations, calls, construction, throws, +coercions, and suspension at contextual site-specific sinks rather than one +effect broker or language handler catalog. + +**Dynamic value.** One helper hook no longer yields the whole effect stream. + +**Observable choke point.** The host operations themselves; they are +unavoidably observable. + +**Risks and cost.** Exact getter, proxy, coercion, exception, and evaluation +order must be preserved at every generated site. Size grows; runtime cost can +remain low. + +**Falsification.** Reject if a shared packet format, decoder, helper, or +capability table reconstructs all effect identities. + +### 5.5 Program-wide root fusion + +**Mechanism.** Fuse several selected roots into one protected component so a +target function is not an independently extractable unit. + +**Dynamic value.** Recovery starts from a broader program-level graph. + +**Observable choke point.** Exports and target-specific output/effect slices. + +**Risks and cost.** Unrelated APIs become coupled; tree-shaking, reentrancy, +load time, and failure isolation worsen. + +**Falsification.** Slice backward from one export. If the target separates +cleanly and unrelated roots contribute no necessary dependency, fusion was +only concatenation. + +### 5.6 Cross-invocation braiding + +**Mechanism.** Make representation bases, variant contracts, residuals, and +continuation roles depend on persistent root-group history. Overlapping async +work may interleave live fragments; completed synchronous semantics may not be +deferred. + +**Dynamic value.** The same input produces traces that align poorly under +different warm, recursive, reentrant, or async histories. + +**Observable choke point.** Per-call I/O and persistent carrier state. + +**Risks and cost.** Fresh-process analysis resets history. Reentrancy, +reproducibility, and async order are difficult. + +**Falsification.** Reject if fresh-process analysis remains easy or one captured +carrier transform aligns every history. + +### 5.7 Solver/constraint execution + +**Mechanism.** Lower pure regions to exact relations and use a generic +solver/eliminator to derive the next frame. + +**Dynamic value.** Solver iterations do not directly correspond to source +operations. + +**Observable choke point.** Constraint-instance construction and output +variables. + +**Risks and cost.** General JavaScript relations are unwieldy. Effects, dynamic +types, loops, and exceptions make this practical only for narrow islands. + +**Falsification.** Dump the constraint instance into commodity simplifiers. +Reject if they recover operation families or CFGs more cheaply than fused code. + +### 5.8 All-path effect predication + +**Mechanism.** Linearize candidate pure paths together, produce predicated +effect tokens, and commit only the selected effects in exact source order. + +**Dynamic value.** Execution no longer directly identifies the taken source +edge inside the region. Constantine demonstrates that aggressive control/data +flow linearization can be practical for selected native workloads, although +Ruam's dynamic JavaScript problem is materially harder: +[Constantine](https://arxiv.org/abs/2104.10749). + +**Observable choke point.** Effect commitment and output projection. + +**Risks and cost.** Getters, proxies, calls, throwing, allocation, +nontermination, and path explosion make broad predication unsafe. + +**Falsification.** Reject if dynamic taint cheaply identifies one winning lane +or if extra paths are removable dead work. + +### 5.9 Distributed continuation mesh + +**Mechanism.** Route work among small transducers and continuation contracts, +without one interpreter loop. + +**Dynamic value.** No single function edge reports the semantic sequence. + +**Observable choke point.** Message/continuation movement. + +**Risks and cost.** A shared queue is likely to become a new dispatcher. +Ordering and memory traffic are expensive. + +**Falsification.** Instrument enqueue/dequeue. Reject if one hook reconstructs +the ordered computation. + +### 5.10 Input-conditioned transient specialization + +**Mechanism.** Compose a per-invocation data schedule from precompiled +templates using input shape and carrier history. No source generation, `eval`, +or CSP violation is permitted. + +**Dynamic value.** One captured realization transfers poorly to other input +shapes or histories. + +**Observable choke point.** The specialization builder and schedule. + +**Risks and cost.** The schedule may merely publish canonical IR in another +form. Entry latency and payload size rise. + +**Falsification.** Hook schedule construction. Reject if normalization produces +a stable operation/node sequence. + +### 5.11 Counterfactual worlds + +**Mechanism.** Carry several valid candidate states and derive the next encoded +frame relationally across them, delaying attribution. + +**Dynamic value.** Hooks see several plausible histories. + +**Observable choke point.** Late commitment and external effects. + +**Risks and cost.** The real input often identifies the real world through +taint. Effectful or nonterminating worlds cannot execute normally. Runtime and +memory multiply by world count. + +**Falsification.** Perturb inputs and correlate lanes to output/effects. Reject +if the actual lane becomes obvious after a small number of runs. + +### 5.12 Transactional shadow heap + +**Mechanism.** Execute protected state changes in a virtual graph or journal and +commit externally visible deltas only at barriers. + +**Dynamic value.** Ordinary host hooks see fewer intermediate state operations. + +**Observable choke point.** Commit and any proxy/host callback. + +**Risks and cost.** Identity, prototypes, accessors, proxies, `WeakMap`, +symbols, reflection, and host objects make exact virtualization exceptionally +hard. + +**Falsification.** Use a proxy/reflection-heavy differential suite. Reject on +any semantic mismatch or if commits encode one packet per source operation. + +### 5.13 Multi-input cohort coding + +**Mechanism.** Evaluate a requested pure input alongside derived neighbors and +project the requested coordinate only at return. + +**Dynamic value.** One trace contains several behaviors, complicating +attribution. + +**Observable choke point.** Cohort construction and output projection. + +**Risks and cost.** The attacker sees all inputs, and effects invalidate extra +evaluations. Runtime multiplies by cohort size. + +**Falsification.** Hook the projector or freeze neighboring lanes. Reject if +the actual lane is directly labeled. + +### 5.14 Reversible compute/uncompute + +**Mechanism.** Evaluate pure regions reversibly, project the required result, +then uncompute intermediate state. + +**Dynamic value.** Post-call snapshots retain less residue. + +**Observable choke point.** The complete forward trace. + +**Risks and cost.** A full tracer records the forward computation; effects are +irreversible; runtime approximately doubles. This is not novel enough to become +a primary architecture. + +**Falsification.** Record only the forward half. Reject if it is as recoverable +as ordinary execution. + +## 6. Rejected near-duplicates and theater + +The following do not qualify as distinct directions: + +- Dynamic opcode maps, handler shuffles, witness rekeying, and alias churn are + one mapping-churn family. They fail when runtime returns a handler or opcode. +- Splitting a resolver or fragmenting generic handlers leaves the + dispatch-to-handler edge intact. +- Payload/value encryption with a client-side key creates a hookable + decode/materialization seam. +- Random block or schedule permutation without a new topology or causal model + is normalization work. +- Runtime source generation, `eval`, and self-modifying code are capturable and + break the CSP rail. +- Dead operations, fake messages, opaque predicates, and trace flooding are + removable unless their outputs are semantically necessary. +- Fake counterfactual lanes are dead-code injection. +- Program-wide concatenation is not fusion unless results require shared + fragments. +- Variants that differ only in names, order, or encodings are not multiple + ontologies. +- Anti-debugging, timing, environment, integrity, and self-checks are + patchable detection mechanisms. +- Hash chains and memory-hard carrier updates impose comparable work on users + while leaving semantics visible. +- Moving the same model into Wasm or native code changes tools, not the attacker + model. +- A generic universal handler catalog with extra metadata recreates the exact + choke point being removed. + +## 7. Best fusions + +### H1. Braided Poly-Ontology Region Fabric — Rank 1 + +Region-fused contextual linearization + caller/continuation fission + +interprocedural braiding + multiple structural ontologies + wire-basis drift +inside eligible pure regions + distributed effect sinks. + +This fusion removes both stable instruction boundaries and stable internal +function bodies while retaining a credible path to exact JavaScript effects. + +### H2. Oblivious Relational Region Fabric + +Wire-basis drift + all-path predication + counterfactual worlds + constraint +fragments. It offers stronger pure-code trace dilution but unacceptable +general-JavaScript correctness and performance risk. + +### H3. Transactional Constraint Isogloss + +Solver execution + shadow heap + distributed effect commits. It is credible +for a narrow data-processing language, not Ruam's declared JavaScript surface. + +### H4. Transient Continuation Mesh + +Input-conditioned specialization + contextual frame layouts + continuation +routing + poly-ontology templates. Its main risk is recreating a hookable +schedule-description seam. + +### H5. Program-Braided Cohort Isogloss + +Program-wide fusion + cross-invocation state + cohort/counterfactual coding. +Coupling and overhead make this a research profile, not the base engine. + +## 8. Rank 1 architecture: BPRF + +### 8.1 Pipeline + +```text +canonical semantic IR + -> exact effect/purity/throw/coercion annotation + -> effect-delimited region graph + -> caller-context continuation expansion + -> K poly-ontology regional realizations + -> interprocedural fission and braiding + -> wire-basis and virtual-frame assignment + -> Isogloss fragment lattice + -> generated region fabric and distributed effect sinks +``` + +### 8.2 New non-negotiable invariants + +| ID | Invariant | +|---|---| +| DR-01 | Production execution never materializes `SemanticOp`, handler identity, canonical operand projection, or source-node identity. | +| DR-02 | No production table, function result, metadata record, or packet maps a boundary/fragment to a language operation. | +| DR-03 | One carrier transition advances a regional fragment composition, never one canonical operation. | +| DR-04 | Except for explicit public/indirect/reflection boundaries, a logical function is split across at least two continuation-owned regions. | +| DR-05 | Eligible direct-call SCCs include at least one caller-fused or cross-function fragment; no closed generated body owns the whole internal function. | +| DR-06 | Hardened mode provides at least three reachable contextual realizations per eligible region and at least two verified structural ontologies. | +| DR-07 | Renaming, block permutation, layout-only changes, and encoding-only changes do not satisfy the poly-ontology requirement. | +| DR-08 | A source local/temporary has no stable generated slot, register, wire, or closure-field identity across protected regional transitions. | +| DR-09 | Each hardened pure result depends on at least two independently located necessary fragments/shares; no single lane computes the ordinary result. | +| DR-10 | Runtime never exposes a durable winning-lane, operation-selector, or semantic-dispatch scalar. | +| DR-11 | Getter, proxy, coercion, call, construction, throw, `finally`, await, and yield order is exactly equivalent to canonical IR. | +| DR-12 | No universal production handler, effect broker, packet decoder, runtime switch, or message queue covers all language effects. | +| DR-13 | Ordinary values materialize only at certified contract/effect boundaries and are re-encoded on protected re-entry where eligible. | +| DR-14 | One root group still owns one live carrier through recursion, reentrancy, and suspension. | +| DR-15 | Exports, callbacks, indirect calls, reflection-sensitive entries, and host effects are inventoried as reduced-protection boundaries. | + +### 8.3 Lattice and carrier changes + +Replace handler candidate masks and operand projections with fragments for: + +- regional transitions; +- continuation routes; +- contextual frame maps; +- wire-basis/share refresh; +- effect gates and commit order; +- realization-family constraints; +- entry and return continuation contracts. + +Local ambiguity remains useful, but it is no longer the primary security claim. +The active neighborhood contributes pieces of a contextual regional transition; +it never resolves a canonical semantic event. + +The carrier tracks: + +- continuation contract; +- regional fragment neighborhood; +- phase and lineage; +- realization/ontology epoch; +- virtual-frame layout basis; +- share-refresh/basis state; +- call, exception, and suspension continuations. + +It does not store or derive a semantic opcode, handler index, canonical operand, +or instruction pointer. + +### 8.4 Runtime changes + +Delete these production concepts: + +- semantic handler catalog; +- `resolve(): { handler, operand }`; +- opcode dispatch; +- generic language-effect executor; +- instruction interpreter loop. + +Production uses generated contextual region/effect codelets plus narrowly +generic fragment-combination kernels. Pure codelets contain fused regional +work, not one language operation. Effect sinks are site-specific. Generic +language handlers may exist temporarily only as migration/reference semantics +and compiler-lowering specifications. + +### 8.5 Verifier changes + +The verifier must check: + +- canonical IR to region-graph bisimulation; +- exact observable effect and exception ordering; +- equivalence and legitimate reachability of every regional realization; +- call, return, throw, await, yield, and resume contracts; +- minimum structural difference across ontology families; +- changing frame/binding ownership; +- necessity of all protected pure-result fragments/shares; +- absence of production semantic-dispatch metadata and values; +- absence of complete internal function bodies where fission is required; +- explicit inventory of all ordinary-value and host-effect boundaries. + +## 9. Dynamic-attacker benchmark + +The owner source-origin sidecar is used only to score recovery. The attacker +does not receive it. + +### 9.1 Baseline attacker + +Instrument the paused design's `resolve -> SemanticOp/operand -> handler` seam. +Record the handler, operand, boundary, frame change, and successor. This should +recover almost all executed semantic events with one or two hook families and +provides the control result BPRF must beat. + +### 9.2 Metrics + +| Metric | Meaning | +|---|---| +| H90 | Minimum hook sites required to label 90% of reached source nodes | +| O90 | Dynamic primitive observations required for 90% reached-node recovery | +| T90 | Input/context executions required for 90% source-node and CFG coverage | +| Op-F1 | Recovered semantic-operation classification | +| CFG recall | Recovered canonical CFG edges | +| Def-use F1 | Recovered logical producer/consumer relations | +| Cross-input transfer | Lifter accuracy on unseen inputs | +| Cross-history transfer | Accuracy under fresh, warm, recursive, reentrant, and async histories | +| Context coverage | Caller contexts needed for a reusable callee model | +| Patch-collapse size | Smallest localized patch producing a stable semantic stream while preserving correctness | +| Effect leakage | Control structure inferable only from unavoidable host effects | +| Attack amplification | Trace storage and offline CPU per recovered source event | +| Security efficiency | Attack amplification divided by legitimate runtime/size overhead | + +Run two attackers: + +1. an O(1)-hook attacker searching for runtime choke points; and +2. a full-step attacker instrumenting every generated expression/function and + performing taint, graph alignment, clustering, and trace slicing. + +Effect-sliced reconstruction remains a relevant control because BinSim shows +that dynamic slicing from system effects can recover fine-grained relations +across obfuscated traces: +[BinSim](https://www.usenix.org/conference/usenixsecurity17/technical-sessions/presentation/ming). + +### 9.3 Initial go/no-go gates + +Relative to a baseline expected to recover at least 95% of executed semantic +events: + +- no hook family directly emits a semantic operation or source-site identity; +- O(1)-hook Op-F1 is below 0.50 on held-out inputs; +- cross-input or cross-context transfer loses at least 30 percentage points; +- full-step O90 rises at least 10x and offline reconstruction CPU at least 5x; +- no patch touching three or fewer localized sites creates a stable canonical + stream while preserving the spike corpus; +- attacker amplification exceeds legitimate slowdown; +- values, errors, effects, and scheduling remain exactly differential-correct. + +These are experimental kill gates, not security guarantees. + +## 10. Incremental spike + +1. Build the resolver/handler baseline extractor, full-step instrumenter, + source-origin scorer, and curated dynamic corpus. +2. Add exact purity, throwing, coercion, allocation, host-effect, suspension, + call-edge, and continuation annotations to canonical IR. +3. Support a pure regional subset: numbers, booleans, locals, branches, bounded + loops, and direct calls. +4. Generate roughly several-node fused regions with three contextual variants + across at least two ontologies, initially predicated dataflow and + continuation residuals. +5. Introduce fragment cells and a carrier that routes regional contracts. +6. Prove by architecture test that production execution cannot create a + `SemanticOp`, handler identity, or canonical operand tuple. +7. Add frame/wire-basis drift and require at least two necessary residual + fragments for each protected pure result. +8. Add one callee reached from at least four callers, then recursion and mutual + recursion; verify no stable complete generated callee body exists. +9. Add property read, coercion, call, throw, and `finally` through distributed + site-specific sinks. Never speculatively execute host effects. +10. Run both attacker harnesses and delete mechanisms whose attacker + amplification does not exceed user overhead. +11. Freeze the revised lattice, certificate, artifact, and runtime schemas only + after the dynamic go/no-go gates pass. + +## 11. Work disposition + +### Paused + +- handler candidate masks keyed by `SemanticOp`; +- unique handler/operand resolution; +- production handler catalog extraction; +- operand projector and interpreter interfaces; +- opcode-selecting carrier witnesses; +- certificates mapping boundaries to operations; +- artifact formats encoding those structures; +- runtime and verifier code that assumes instruction-at-a-time execution. + +### Continuing + +- deterministic entropy and reproducible builds; +- canonical semantic IR and source origins; +- canonical CFG, call graph, purity, effect, throw, coercion, and suspension + analysis; +- baseline and test-migration inventories; +- native/reference semantic and effect-trace differential infrastructure; +- root-group lifecycle abstractions that do not freeze operation-witness fields; +- generic graph, bitset, hashing, serialization, and verifier utilities that do + not assume semantic dispatch; +- generic JavaScript semantics only as compiler/reference oracles. + +## 12. Decision gate + +Do not resume lattice/runtime implementation by merely editing the old candidate +mask schema. First implement the dynamic attacker baseline and the pure BPRF +spike. The BPRF design becomes the replacement architecture only if it clears +the quantitative go/no-go gates with zero semantic/effect mismatches and an +attacker-amplification ratio greater than its user cost. diff --git a/docs/superpowers/specs/2026-07-24-traveling-isogloss-implementation-plan.md b/docs/superpowers/specs/2026-07-24-traveling-isogloss-implementation-plan.md index cfdf164..6f26509 100644 --- a/docs/superpowers/specs/2026-07-24-traveling-isogloss-implementation-plan.md +++ b/docs/superpowers/specs/2026-07-24-traveling-isogloss-implementation-plan.md @@ -1,10 +1,25 @@ # Traveling Isogloss — Rank 1 Implementation Plan **Date:** 2026-07-24 -**Status:** Implementation-ready replacement plan +**Status:** Partially paused — architecture-neutral compiler work may continue; +security-sensitive lattice/runtime sections are held for the BPRF spike **Source:** Rank 1 in [Project Kaleidoscope — Ruam Gen-2 Ideation Results](2026-07-24-ruam-gen2-ideation-results.md) **Decision:** Replace Ruam's VM bytecode execution model with Traveling Isogloss. The completed product has one execution engine—the Boundary Constraint Machine—and contains no legacy VM runtime, VM fallback, bytecode encoder, or backend selector. +> **Dynamic-instrumentation hold (2026-07-24):** A full-access runtime review +> found that this plan's `boundary + witness -> SemanticOp/operand -> handler` +> seam exposes a cheap, reusable semantic trace. Do not implement or freeze the +> handler candidate masks, unique handler resolver, operand projector, +> production handler catalog, instruction interpreter, opcode-selecting carrier +> witness, related certificate fields, or encoded artifact schema described +> below. They are retained temporarily as the reviewed baseline, not as the +> current target. The ranked redesign and quantitative decision gate are in +> [Project Kaleidoscope D2 — Dynamic-Instrumentation Ideation Results](2026-07-24-dynamic-instrumentation-ideation-results.md). +> Deterministic entropy, baselines, test migration, canonical semantic IR, +> source origins, CFG/effect analysis, and no-legacy-VM cutover work remain +> current. Security-sensitive implementation resumes only after the Braided +> Poly-Ontology Region Fabric spike clears its dynamic-attacker gates. + ## 1. Outcome Traveling Isogloss will replace the current compiler-to-bytecode-to-VM path. A selected root function and all of its child units will compile into a **Boundary Constraint Machine (BCM)**: From 9c9d88f1a3bacd897d6b69a6cc91f93c59ceb1f2 Mon Sep 17 00:00:00 2001 From: owengregson Date: Fri, 24 Jul 2026 13:57:55 -1000 Subject: [PATCH 09/54] docs: evolve BPRF with semantic holography --- ...7-24-bprf-custodied-semantic-holography.md | 566 ++++++++++++++++++ ...ynamic-instrumentation-ideation-results.md | 8 + ...-traveling-isogloss-implementation-plan.md | 3 + 3 files changed, 577 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-24-bprf-custodied-semantic-holography.md diff --git a/docs/superpowers/specs/2026-07-24-bprf-custodied-semantic-holography.md b/docs/superpowers/specs/2026-07-24-bprf-custodied-semantic-holography.md new file mode 100644 index 0000000..b89f0b9 --- /dev/null +++ b/docs/superpowers/specs/2026-07-24-bprf-custodied-semantic-holography.md @@ -0,0 +1,566 @@ +# Project Kaleidoscope D3 — Custodied Semantic Holography + +**Date:** 2026-07-24 +**Status:** Proposed BPRF evolution; implementation remains behind the dynamic-attacker spike gate +**Parent:** [Project Kaleidoscope D2 — Dynamic-Instrumentation Ideation Results](2026-07-24-dynamic-instrumentation-ideation-results.md) +**Goal:** Raise the minimum cost of full-access dynamic reconstruction beyond what an entirely client-resident BPRF can achieve + +## 1. Outcome + +The strongest evolution is **Custodied Semantic Holography (CSH)**: + +1. BPRF still removes instruction dispatch, complete internal function bodies, + stable frame layouts, and reusable single-ontology traces. +2. CSH represents protected state as overlapping partial semantic charts. No + region, codelet, frame, or chart contains a complete logical before/after + state. +3. The chart cover changes at regional transitions, so chart ownership and + overlap relations do not stay aligned across calls or histories. +4. In the maximum profile, at least one necessary chart-gluing relation for + selected high-value regions never ships to the client. A stateful custodian + co-evaluates it using private-function evaluation or executes that narrowly + typed relation remotely. +5. The custodian returns only a lineage-bound encoded chart contribution or + certified effect projection—never source, a regional program, a handler, + an operation identity, a next-codelet token, or a reusable decode key. + +The combination is stronger than either parent: + +- **BPRF** makes captured client execution contextual, fused, and + multi-ontology. +- **Semantic holography** makes each captured local state incomplete and + globally relational. +- **Custody** removes a necessary relation from the attacker's trust domain. + +The recommended product name for the combined maximum profile is: + +> **BPRF/CSH — Custodied Moving-Cover Isogloss** + +## 2. Why BPRF alone still has a ceiling + +BPRF eliminates the cheap: + +```text +resolve -> SemanticOp -> handler +``` + +attack, but a full-step analyst can still: + +1. record every client-resident regional realization; +2. capture complete regional input/output frames; +3. align a finite set of ontologies and caller contexts; +4. normalize changing wire bases after learning their transformations; and +5. eventually build a whole-client equivalent model. + +Wire-basis drift changes the representation of a complete client-side state. +It does not remove the complete transition relation from the client. + +CSH attacks that remaining weakness in two stages: + +- the local stage removes complete region-owned state; +- the custodied stage removes client completeness itself. + +## 3. Creativity and uniqueness gate + +This ideation round required each candidate to change a different primary +property of BPRF: + +| Candidate | Primary property changed | +|---|---| +| Moving-cover semantic holography | State is local-to-global rather than region-owned | +| Causal relation custody | A necessary transition relation leaves the client trust domain | +| Stateful query-fork control | The analyst cannot snapshot and fork one remote representation epoch | +| Universal-circuit phenotype foundry | Function topology and realization change per session | +| TEE-held chart | A hardware isolation boundary replaces the network custodian | +| Temporal convolutional state | One logical update is dispersed across an execution window | +| Cross-device semantic quorum | No single user device owns all relation shares | +| One-time regional capsules | A realization has bounded protocol reuse | + +Near-duplicates were rejected: + +- remote key fetch, remote opcode fetch, and downloadable missing code are all + capturable-material delivery; +- remote integrity signatures and carrier attestations enforce execution but + do not hide semantics; +- ordinary state secret-sharing with every share on the client is just a + stronger wire encoding; +- topology-visible gate hiding is not accepted as private-function evaluation; +- rate limiting alone is a product policy, not a transformation; +- a TEE or server running the entire original function is a trust-domain move, + but not a distinct Ruam execution architecture; +- self-erasing or one-time client code fails against pre-execution snapshots. + +## 4. Ranked add-on slate + +Scores are 1–5 and measure marginal value when added to BPRF. + +| Rank | Add-on | Dynamic floor | BPRF synergy | Correctness | Efficiency | Trust change | Decision | +|---:|---|---:|---:|---:|---:|---|---| +| 1 | Moving-cover holography + causal relation custody | 5 | 5 | 3 | 2 | Remote or TEE | Adopt as CSH | +| 2 | Moving-cover semantic holography, local-only | 4 | 5 | 3 | 3 | None | Base CSH research layer | +| 3 | Stateful universal-circuit phenotype foundry | 5 | 4 | 2 | 1 | Remote | Optional custodian implementation | +| 4 | TEE-held semantic chart | 5 | 4 | 3 | 4 | Hardware | Deployment alternative | +| 5 | Temporal convolutional state | 3 | 4 | 2 | 2 | None | Spike only | +| 6 | Multi-custodian threshold relation | 5 | 3 | 2 | 1 | Multiple remotes | Infrastructure hardening | +| 7 | Cross-device semantic quorum | 4 | 3 | 1 | 1 | Other devices | Product-pivot research | +| 8 | One-time regional capsules | 3 | 3 | 3 | 2 | Remote | Reject as primary | + +The local-only candidate raises analysis cost but does not change the +impossibility boundary. Rank 1 combines it with relation custody because the +request is to raise the floor as high as possible. + +## 5. Source-domain mechanism + +The nonlocal state model is borrowed from cellular sheaves and overlapping +coordinate charts: + +- each location owns a local view; +- related views overlap but use different local coordinates; +- compatibility maps describe how overlap data agrees; +- a complete globally consistent object is a **global section**; +- no local view is the global object. + +This is a structural transfer, not naming. Cellular sheaves are used to model +local computations whose globally consistent solutions are global sections: +[A Sheaf-Theoretic Characterization of Tasks in Distributed Systems](https://arxiv.org/abs/2503.02556). + +CSH maps this structure into Ruam: + +| Sheaf concept | CSH concept | +|---|---| +| Base complex / cover | Root-group regional continuation graph | +| Local chart / stalk | Partial encoded frame owned by a BPRF fragment neighborhood | +| Restriction map | Overlap compatibility relation between chart fragments | +| Global section | One complete canonical logical state, which production never materializes | +| Change of cover | Carrier-driven chart split, merge, transport, and reownership | +| Local projection | Site-specific value needed for one host effect or contract | +| Missing gluing map | Custodian-owned relation that never ships to the client | + +The cryptographic custody mechanism is grounded in private function evaluation +(PFE), where one party evaluates a private function on another party's input +without revealing the function beyond its outputs. Actively secure, +constant-round, linear-complexity PFE constructions exist in the literature: +[Making Private Function Evaluation Safer, Faster, and Simpler](https://eprint.iacr.org/2021/1682). + +This does not make arbitrary JavaScript PFE practical. CSH restricts custody to +typed, pure, effect-delimited macroregions and treats feasibility as a mandatory +spike gate. + +## 6. Concrete state model + +Let canonical protected state at an analysis boundary be `x`. Production never +stores `x`. + +Choose a changing cover: + +```text +U(t) = { U1, U2, ..., Un } +``` + +Each chart stores a partial encoded view: + +```text +si = encode_i(project_i(x), localResidual_i, coverEpoch) +``` + +Related charts satisfy overlap constraints: + +```text +Rij(si, sj, glue_ij, coverEpoch) = 0 +``` + +Properties: + +1. No individual `si` decodes a source variable or complete frame. +2. The configured chart threshold is required to derive any certified + protected projection. +3. Charts overlap across several source values and several regional + transitions; they are not one share set per variable. +4. Chart ownership crosses continuation and function boundaries. +5. After a regional transition, the carrier changes the cover by splitting, + merging, transporting, or reassigning charts. +6. Old and new covers overlap only enough to transport the required global + consistency class. +7. No global `decode(x)` routine exists. + +A site-specific host effect receives only its required projection: + +```text +effectValue = + project_effect( + chartContribution_a, + chartContribution_b, + ..., + custodiedContribution + ) +``` + +The ordinary value is observable at that effect because JavaScript semantics +require it. Other logical state remains distributed. + +## 7. Moving-cover execution + +One protected regional transition becomes: + +```text +incoming continuation contract + -> activate a BPRF regional realization + -> update several local charts + -> reconcile only required overlaps + -> transport consistency into a new cover + -> optionally request one custodied relation contribution + -> materialize only a certified effect/return projection + -> continue with the new chart cover +``` + +Important distinctions from current BPRF: + +- BPRF frame-layout drift can still be described as `x' = A x + b`. +- CSH has no production `x`; only partial charts and overlap relations exist. +- BPRF variants change how one regional transition is computed. +- CSH changes which collection of local partial states can jointly denote a + transition at all. +- BPRF can be normalized by covering all finite realizations. +- Custodied CSH remains incomplete after full client realization coverage. + +## 8. Custodied relation protocol + +### 8.1 What remains remote + +For each selected crown-jewel macroregion, the custodian owns at least one of: + +- a nonlinear chart-gluing relation; +- a hidden universal-circuit programming string; +- a regional transition residual; +- an output/effect projection relation; +- state needed to transport one cover epoch into the next. + +The complete regional transition cannot be evaluated from client artifacts and +client state alone. + +### 8.2 What crosses the boundary + +The client sends: + +- opaque session and contract identifiers; +- a carrier-lineage commitment; +- encoded chart contributions; +- optional privately encoded input values; +- an anti-replay protocol nonce. + +The custodian returns: + +- an encoded chart contribution; +- or one site-specific encoded effect projection; +- plus protocol authenticity needed to reject malformed transport. + +It never returns: + +- source or canonical IR; +- a semantic operation or handler identity; +- a regional codelet or schedule; +- a next-region identifier; +- a general decode key; +- the missing gluing relation; +- an owner sidecar or source map; +- a reusable offline evaluator. + +### 8.3 Function and topology privacy + +Gate-hiding alone is insufficient. Recent work demonstrates SAT recovery of +hidden gate functions from public topology, with large speedups from +topology-aware simplification: +[Function Recovery Attacks in Gate-Hiding Garbled Circuits](https://arxiv.org/abs/2601.13271). + +Therefore the maximum profile requires: + +- a universal or set-universal circuit per padded size/effect class; +- no region-specific public topology; +- active security against a client that deviates from the protocol; +- transcript padding where size would identify the macroregion; +- fresh wire labels and representation state per session/epoch; +- a test that attempts topology-based function recovery. + +### 8.4 Stateful representation custody + +The custodian maintains a representation state: + +```text +sigma = { + session, + rootGroup, + coverEpoch, + lineageCommitment, + consumedNonces, + custodiedChartState +} +``` + +Successful evaluation advances `sigma`. Replaying a consumed transition is +rejected. A client snapshot cannot fork the same server representation epoch +into arbitrary counterfactual queries. + +This is not claimed to stop all chosen-input analysis: + +- an authorized attacker may create fresh sessions; +- outputs remain a black-box oracle; +- rate limits and licensing policy are separate product controls; +- simple functions may still be learned from very few I/O examples. + +The stateful protocol prevents free fork-and-replay of one internal +representation; it does not manufacture query hardness for an intrinsically +simple function. + +## 9. Deployment profiles + +### 9.1 `holographic-local` + +All charts and gluing relations ship with the artifact. + +- No network or trusted hardware. +- Highest floor available without changing the trust domain. +- Full client instrumentation can eventually reconstruct the entire system. +- Security claim is analysis amplification and trace nonlocality only. + +### 9.2 `holographic-custodied` + +One necessary relation is held by a developer-controlled service. + +- Full client traces are structurally incomplete. +- Critical region extraction becomes black-box/function-recovery analysis. +- Availability and latency become product requirements. +- There is no offline fallback containing the missing relation. + +### 9.3 `holographic-private` + +Use private-function evaluation so the custodian learns no protected client +input beyond the declared leakage, while the client learns no function detail +beyond outputs and declared transcript leakage. + +- Highest cryptographic goal. +- Practical only for restricted typed macroregions until benchmarks prove more. +- Universal-circuit and active-security costs may be substantial. + +### 9.4 `holographic-tee` + +Place the missing chart relation in a hardware-backed isolated component. + +- Lower latency and possible offline execution. +- Changes the threat model to the hardware/attestation boundary. +- Target-specific and exposed to platform side-channel/fault limitations. +- Not a universal browser solution. + +### 9.5 `holographic-threshold` + +Split the custodied relation across several non-colluding services. + +- Avoids one infrastructure provider holding the complete hidden relation. +- Can improve service resilience and developer trust separation. +- Adds protocol rounds, operational complexity, and another failure surface. +- It does not meaningfully improve client extraction over one honest + uncompromised custodian; it improves custody assurance. + +## 10. Non-negotiable CSH invariants + +| ID | Invariant | +|---|---| +| CSH-01 | Production never stores a complete canonical logical frame for a protected CSH region. | +| CSH-02 | No chart is a one-variable share set or independently decodes a source variable. | +| CSH-03 | Certified projections require the configured minimum number of independently owned chart contributions. | +| CSH-04 | Chart ownership crosses region and, where valid, continuation/function boundaries. | +| CSH-05 | Every protected regional transition changes the chart cover or its restriction maps. | +| CSH-06 | No universal global-section solver or decode helper exists in production. | +| CSH-07 | Effects and returns use distributed site-specific projections. | +| CSH-08 | Ordinary values materialize only where native JavaScript observability requires them. | +| CSH-09 | The custodied relation is necessary for the selected macroregion's correct transition. | +| CSH-10 | No client artifact, cache, error path, development flag, or fallback contains the custodied relation. | +| CSH-11 | A custodian response is an encoded chart/projection contribution, never an operation, codelet, route, or reusable key. | +| CSH-12 | Public PFE topology is fixed within padded size/effect buckets and independent of the protected region. | +| CSH-13 | The custodian protocol is secure against an actively deviating client for the declared profile. | +| CSH-14 | Custodied representation state advances monotonically and consumed transitions cannot be replayed in one session. | +| CSH-15 | A client snapshot cannot fork one custodied representation epoch. | +| CSH-16 | Exact getter, proxy, coercion, call, throw, `finally`, await, yield, and scheduling behavior remains unchanged. | +| CSH-17 | Network failure is explicit; the maximum profile never silently falls back to a complete local implementation. | +| CSH-18 | Every observable effect, ordinary-value projection, transcript-size class, and remote call is inventoried in the certificate. | +| CSH-19 | Local-only and custodied profiles use different, honest security claims. | +| CSH-20 | The owner sidecar and source-origin scorer never ship to the client production artifact or custodian response. | + +## 11. What this does to BPRF + +### 11.1 Region graph + +Add: + +- chart ownership sets; +- overlap/restriction edges; +- cover-transition contracts; +- certified projection sites; +- custodied-relation dependencies; +- public transcript leakage classes. + +### 11.2 Carrier + +Add: + +- current cover ID and epoch; +- active chart ownership map; +- overlap reconciliation phase; +- pending custodian protocol continuation; +- lineage commitment; +- custodian session binding. + +Do not add: + +- global frame state; +- expected chart solution; +- a global decode key; +- a semantic operation; +- a next-region token received from the server. + +### 11.3 Regional realizations + +Each BPRF ontology must lower to chart-local transitions: + +- predicated dataflow updates one overlap neighborhood; +- continuation residuals transport another; +- algebraic variants operate over coded chart coordinates; +- effect sinks request and materialize only their certified projection. + +An ontology that reconstructs a complete frame before execution is not +CSH-compatible. + +### 11.4 Verifier + +In addition to BPRF equivalence, verify: + +- local chart transitions glue to the intended canonical state transition; +- overlap constraints remain satisfiable and uniquely determine only the + certified projection, not an emitted global frame; +- all cover changes preserve the global consistency class; +- threshold/necessity properties hold for chart contributions; +- removal of any required contribution changes or blocks the projection; +- no global decoder or complete client transition exists; +- custodied and local relations compose correctly; +- transcript and effect leakage match the certificate; +- every failure mode preserves native-visible error/ordering contracts where + the API promises them. + +## 12. Dynamic-attacker evaluation + +Retain every BPRF metric and add: + +| Metric | Meaning | +|---|---| +| Client completeness | Whether client artifact + full client trace determines the selected regional transition without the custodian | +| C90 | Minimum simultaneously correlated chart probes for 90% logical-state recovery | +| G90 | Work required to reconstruct a consistent global section for 90% of reached state | +| Cover transfer | Recovery accuracy after an unseen cover split/merge/reownership sequence | +| Projection leakage | Logical state recoverable from one certified effect projection beyond the native effect value | +| Fork success | Ability to evaluate two counterfactual transitions from one custodied epoch | +| Transcript transfer | Function/region classification accuracy from network transcripts | +| Topology recovery | Function recovery from public PFE topology and chosen outputs | +| Custody bypass | Smallest client patch that preserves output without a custodian call | +| Oracle learnability | Queries and compute needed to learn an equivalent crown-jewel macroregion from I/O | + +Run four attacker classes: + +1. O(1)-hook BPRF choke-point search; +2. full-step, full-heap client trace and graph alignment; +3. client snapshot/fork/replay with an actively modified protocol client; +4. topology-aware PFE/function-recovery plus chosen-input black-box learning. + +## 13. Go/no-go gates + +### 13.1 Local holography + +- No complete frame appears at a regional boundary. +- C90 is at least the configured chart threshold. +- Full-step G90 and storage rise by at least 10x over plain BPRF for the spike + while legitimate overhead remains lower than attacker amplification. +- Cross-cover transfer loses at least 30 percentage points. +- No three or fewer localized hooks recover a stable source-variable map. +- Any scheme reducible to one fixed linear recombination is deleted. + +### 13.2 Custodied mode + +- Client completeness is false by construction and by extraction test. +- Removing the custodian relation prevents correct standalone evaluation. +- No client error/fallback/debug path exposes the missing relation. +- Function/region classification from padded transcripts is no better than the + declared size/effect-class leakage. +- Topology-aware recovery does not distinguish protected regions within a + universal-circuit bucket beyond the accepted leakage. +- Snapshot/fork of one custodied epoch fails. +- A client patch cannot convert custodian responses into a canonical operation + or reusable offline evaluator. +- The service returns no more than the output/effect leakage declared by the + protected contract. + +### 13.3 Correctness + +- Zero native/reference/BPRF-CSH value mismatches. +- Zero event-order, proxy/getter, exception, async, or generator mismatches. +- No favorable-seed dependence. +- Failure and retry semantics are explicit and deterministic. +- Maximum profile never falls back to a locally complete artifact. + +## 14. Kill criteria + +Remove or redesign CSH if: + +- a complete logical frame or fixed decoder appears anywhere in production; +- moving covers normalize to one stable slot transform cheaply; +- chart overlap adds only fake dependencies removable by slicing; +- a client dump is sufficient to evaluate a custodied region offline; +- the custodian returns missing code, semantic tokens, or reusable keys; +- public circuit topology identifies the protected region; +- remote state can be forked freely from one captured client snapshot; +- correctness requires suppressing real JavaScript effects; +- the only benefit is rate limiting, integrity enforcement, or anti-hooking; +- private-function evaluation is impractical and direct remote execution is + unacceptable for the product; +- attacker amplification does not exceed legitimate overhead in local mode. + +## 15. Incremental spike + +1. Finish BPRF's dynamic attacker baseline and region/effect annotations. +2. Select a typed, pure crown-jewel fixture with branches and direct calls. +3. Encode its state into at least five overlapping charts with threshold three. +4. Implement two changing covers and chart transport without a global frame in + the reference evaluator. +5. Add owner-only global-section reconstruction solely to score the attacker; + keep it out of production modules. +6. Run full-step dynamic slicing and test whether moving covers materially + increase C90/G90 and reduce cross-cover transfer. +7. Delete linear/fake-dependency schemes that normalize cheaply. +8. Move one necessary nonlinear gluing relation behind a mock custodian API. +9. Prove there is no correct standalone client evaluator for that fixture. +10. Implement a direct remote relation evaluator as the performance/control + baseline. +11. Implement one actively secure, topology-hidden PFE prototype for the same + typed relation. +12. Run topology recovery, active-client, fork/replay, and black-box learning + experiments. +13. Compare local holography, direct custody, PFE custody, and plain BPRF on + correctness, client completeness, attack cost, latency, bandwidth, and size. +14. Freeze CSH types/protocols only if the appropriate profile clears every + gate. + +## 16. Product decision + +If Ruam must remain entirely offline and server-free, adopt only +`holographic-local` and retain the existing honest ceiling: + +> Full client recovery remains possible; CSH raises the synchronization and +> global-reconstruction cost. + +If the requirement is truly to raise the floor **as high as possible**, adopt +`holographic-custodied` or `holographic-private` for marked crown-jewel +macroregions: + +> A full client trace is intentionally incomplete. Recovery of the missing +> regional relation requires compromising the custodian/PFE assumption or +> learning equivalent behavior from the permitted input/output oracle. + +That is the first proposed BPRF layer that changes the full-access client +boundary rather than only making client-side normalization more expensive. diff --git a/docs/superpowers/specs/2026-07-24-dynamic-instrumentation-ideation-results.md b/docs/superpowers/specs/2026-07-24-dynamic-instrumentation-ideation-results.md index 77e8b5d..9d5f29b 100644 --- a/docs/superpowers/specs/2026-07-24-dynamic-instrumentation-ideation-results.md +++ b/docs/superpowers/specs/2026-07-24-dynamic-instrumentation-ideation-results.md @@ -5,6 +5,14 @@ **Parent:** [Traveling Isogloss implementation plan](2026-07-24-traveling-isogloss-implementation-plan.md) **Attacker:** Full artifact and runtime access, chosen inputs, stepping, hooks, heap/closure snapshots, patching, repeated runs, and design knowledge +> **D3 evolution:** The follow-on campaign +> [Custodied Semantic Holography](2026-07-24-bprf-custodied-semantic-holography.md) +> adds moving-cover, nonlocal semantic state to BPRF and, in the maximum +> profile, removes a necessary chart-gluing relation from the client through a +> stateful custodian/private-function-evaluation boundary. The BPRF spike +> remains the first implementation gate; D3 is an additional layer, not a +> reason to revive semantic dispatch. + ## 1. Executive decision The original Traveling Isogloss design does not materially resist a full-access diff --git a/docs/superpowers/specs/2026-07-24-traveling-isogloss-implementation-plan.md b/docs/superpowers/specs/2026-07-24-traveling-isogloss-implementation-plan.md index 6f26509..29b333b 100644 --- a/docs/superpowers/specs/2026-07-24-traveling-isogloss-implementation-plan.md +++ b/docs/superpowers/specs/2026-07-24-traveling-isogloss-implementation-plan.md @@ -15,6 +15,9 @@ security-sensitive lattice/runtime sections are held for the BPRF spike > below. They are retained temporarily as the reviewed baseline, not as the > current target. The ranked redesign and quantitative decision gate are in > [Project Kaleidoscope D2 — Dynamic-Instrumentation Ideation Results](2026-07-24-dynamic-instrumentation-ideation-results.md). +> The maximum-floor evolution—moving-cover state plus an optional custodied +> relation that never ships to the client—is specified in +> [Project Kaleidoscope D3 — Custodied Semantic Holography](2026-07-24-bprf-custodied-semantic-holography.md). > Deterministic entropy, baselines, test migration, canonical semantic IR, > source origins, CFG/effect analysis, and no-legacy-VM cutover work remain > current. Security-sensitive implementation resumes only after the Braided From 5d9c58f5f53d15c8b967b680bbfd3bcf42e9617f Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:00:25 -1000 Subject: [PATCH 10/54] test: establish dynamic instrumentation baseline --- .../dynamic-attacker-baseline.test.ts | 80 ++ .../support/dynamic-attacker-fixtures.ts | 105 +++ .../test/isogloss/support/dynamic-attacker.ts | 779 ++++++++++++++++++ 3 files changed, 964 insertions(+) create mode 100644 packages/ruam/test/isogloss/dynamic-attacker-baseline.test.ts create mode 100644 packages/ruam/test/isogloss/support/dynamic-attacker-fixtures.ts create mode 100644 packages/ruam/test/isogloss/support/dynamic-attacker.ts diff --git a/packages/ruam/test/isogloss/dynamic-attacker-baseline.test.ts b/packages/ruam/test/isogloss/dynamic-attacker-baseline.test.ts new file mode 100644 index 0000000..4946be2 --- /dev/null +++ b/packages/ruam/test/isogloss/dynamic-attacker-baseline.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from "bun:test"; +import { + buildLegacyVmBaselineReport, + type DynamicTrace, + scoreDynamicTrace, +} from "./support/dynamic-attacker.js"; +import { + DYNAMIC_ATTACKER_CORPUS_VERSION, + DYNAMIC_ATTACKER_FIXTURES, +} from "./support/dynamic-attacker-fixtures.js"; + +describe("dynamic attacker control baseline", () => { + it("keeps the fixture corpus versioned and covers every first-gate behavior", () => { + expect(DYNAMIC_ATTACKER_CORPUS_VERSION).toBe(1); + expect(DYNAMIC_ATTACKER_FIXTURES.map((fixture) => fixture.id)).toEqual([ + "pure-arithmetic-control", + "direct-calls", + "object-property-effects", + "exceptions-and-finally", + ]); + const categories = new Set( + DYNAMIC_ATTACKER_FIXTURES.flatMap((fixture) => fixture.categories) + ); + expect(categories).toEqual( + new Set(["pure", "control", "call", "property-effect", "exception"]) + ); + }); + + it("recovers the legacy VM semantic stream from one localized probe", () => { + const report = buildLegacyVmBaselineReport(DYNAMIC_ATTACKER_FIXTURES, { + buildSeed: 0x1badb002, + repeatCount: 2, + corpusVersion: DYNAMIC_ATTACKER_CORPUS_VERSION, + }); + + expect(report.executorId).toBe("legacy-vm/debug-console/v1"); + expect(report.aggregate.counts.operationEvents).toBeGreaterThan(100); + expect(report.aggregate.counts.functionBoundaryEvents).toBeGreaterThan(0); + expect(report.aggregate.counts.exceptionEvents).toBeGreaterThan(0); + expect(report.aggregate.boundaryEvents.precision).toBe(1); + expect(report.aggregate.boundaryEvents.recall).toBe(1); + expect(report.aggregate.boundaryEvents.f1).toBe(1); + expect(report.aggregate.operationLabels.accuracy).toBe(1); + expect(report.aggregate.operationLabels.f1).toBe(1); + expect(report.aggregate.operationLabels.macroF1).toBe(1); + expect(report.aggregate.sequenceEdges.recall).toBe(1); + expect(report.aggregate.dynamicCfgEdges.recall).toBe(1); + expect(report.patchCollapse).toEqual({ + minimumLocalizedSites: 1, + stableSemanticStream: true, + correctnessPreserved: true, + repeatCount: 2, + }); + expect(report.runs.every((run) => !run.trace.traceTruncated)).toBe(true); + }); + + it("penalizes missed boundaries, wrong labels, and broken sequence edges", () => { + const baseline = buildLegacyVmBaselineReport( + [DYNAMIC_ATTACKER_FIXTURES[0]!], + { repeatCount: 2 } + ).runs[0]!.trace; + const degraded: DynamicTrace = { + ...baseline, + recovered: baseline.recovered + .slice(1) + .map((event, index) => + index === 1 && event.predictedOperation !== undefined + ? { ...event, predictedOperation: "WRONG_OPERATION" } + : event + ), + }; + const metrics = scoreDynamicTrace(degraded); + + expect(metrics.boundaryEvents.recall).toBeLessThan(1); + expect(metrics.operationLabels.accuracy).toBeLessThan(1); + expect(metrics.operationLabels.f1).toBeLessThan(1); + expect(metrics.sequenceEdges.recall).toBeLessThan(1); + expect(metrics.dynamicCfgEdges.precision).toBeLessThan(1); + }); +}); diff --git a/packages/ruam/test/isogloss/support/dynamic-attacker-fixtures.ts b/packages/ruam/test/isogloss/support/dynamic-attacker-fixtures.ts new file mode 100644 index 0000000..7ce6107 --- /dev/null +++ b/packages/ruam/test/isogloss/support/dynamic-attacker-fixtures.ts @@ -0,0 +1,105 @@ +/** + * Small, versioned corpus for measuring dynamic semantic recovery. + * + * Fixtures deliberately return their host-visible effects as data. That keeps + * the baseline deterministic while still exercising property access, calls, + * throws, catches, and finally ordering in the current executor. + */ + +export const DYNAMIC_ATTACKER_CORPUS_VERSION = 1 as const; + +export type DynamicFixtureCategory = + | "pure" + | "control" + | "call" + | "property-effect" + | "exception"; + +export interface DynamicAttackerFixture { + schemaVersion: typeof DYNAMIC_ATTACKER_CORPUS_VERSION; + id: string; + categories: readonly DynamicFixtureCategory[]; + source: string; + expected: unknown; +} + +export const DYNAMIC_ATTACKER_FIXTURES: readonly DynamicAttackerFixture[] = + Object.freeze([ + { + schemaVersion: DYNAMIC_ATTACKER_CORPUS_VERSION, + id: "pure-arithmetic-control", + categories: ["pure", "control"], + source: ` + function score(input) { + var total = input < 0 ? -input : input + 3; + for (var i = 0; i < 3; i++) { + total = total * 2 - i; + } + return total > 30 ? total - 5 : total + 7; + } + [score(5), score(-2)]; + `, + expected: [55, 19], + }, + { + schemaVersion: DYNAMIC_ATTACKER_CORPUS_VERSION, + id: "direct-calls", + categories: ["pure", "control", "call"], + source: ` + function adjust(value, bias) { + return value * 3 + bias; + } + function combine(left, right) { + var first = adjust(left, 2); + var second = adjust(right, -1); + return first > second ? first - second : first + second; + } + [combine(7, 4), combine(2, 9)]; + `, + expected: [12, 34], + }, + { + schemaVersion: DYNAMIC_ATTACKER_CORPUS_VERSION, + id: "object-property-effects", + categories: ["call", "property-effect"], + source: ` + function mutate(record, delta, events) { + var before = record.value; + record.value = before + delta; + events.push("set:" + record.value); + return record.value; + } + var effects = []; + var target = { value: 4 }; + var result = mutate(target, 6, effects); + [result, target.value, effects]; + `, + expected: [10, 10, ["set:10"]], + }, + { + schemaVersion: DYNAMIC_ATTACKER_CORPUS_VERSION, + id: "exceptions-and-finally", + categories: ["control", "call", "property-effect", "exception"], + source: ` + function guarded(value, events) { + try { + events.push("try:" + value); + if (value < 0) throw new RangeError("negative"); + return value * 2; + } catch (error) { + events.push("catch:" + error.message); + return 11; + } finally { + events.push("finally:" + value); + } + } + var effects = []; + var results = [guarded(3, effects), guarded(-1, effects)]; + [results, effects]; + `, + expected: [ + [6, 11], + ["try:3", "finally:3", "try:-1", "catch:negative", "finally:-1"], + ], + }, + ]); diff --git a/packages/ruam/test/isogloss/support/dynamic-attacker.ts b/packages/ruam/test/isogloss/support/dynamic-attacker.ts new file mode 100644 index 0000000..6cbdaec --- /dev/null +++ b/packages/ruam/test/isogloss/support/dynamic-attacker.ts @@ -0,0 +1,779 @@ +import vm from "node:vm"; +import type { VmObfuscationOptions } from "../../../src/types.js"; +import { obfuscateCodeDeterministic } from "../../../src/testing.js"; +import type { DynamicAttackerFixture } from "./dynamic-attacker-fixtures.js"; + +export const DYNAMIC_TRACE_SCHEMA_VERSION = 1 as const; + +export type TraceBoundaryKind = + | "operation" + | "function-enter" + | "function-exit" + | "exception"; + +/** Owner-only normalized event. This record must never ship in an artifact. */ +export interface GroundTruthEvent { + id: string; + ordinal: number; + kind: TraceBoundaryKind; + operation?: string; + unit?: string; +} + +/** + * Attacker recovery normalized by an owner-side scorer. + * + * `attributedTruthId` is scorer attribution, not information available to the + * attacker. Future executors can populate it with their own trace aligner. + */ +export interface RecoveredEvent { + ordinal: number; + predictedKind: TraceBoundaryKind; + predictedOperation?: string; + attributedTruthId?: string; + probeSiteId: string; +} + +export interface NormalizedOutcome { + type: "return" | "throw"; + value?: unknown; + errorName?: string; + errorMessage?: string; +} + +export interface DynamicTrace { + schemaVersion: typeof DYNAMIC_TRACE_SCHEMA_VERSION; + executorId: string; + fixtureId: string; + buildSeed: number; + outcome: NormalizedOutcome; + truth: readonly GroundTruthEvent[]; + recovered: readonly RecoveredEvent[]; + probeSiteIds: readonly string[]; + traceTruncated: boolean; +} + +export interface DynamicTraceExecutor { + readonly id: string; + run( + fixture: DynamicAttackerFixture, + buildSeed: number + ): DynamicTrace | Promise; +} + +export interface BinaryMetrics { + truePositive: number; + falsePositive: number; + falseNegative: number; + precision: number; + recall: number; + f1: number; +} + +export interface OperationMetrics extends BinaryMetrics { + /** Correct operation labels divided by validly attributed boundaries. */ + accuracy: number; + /** + * Mean per-operation F1 over labels present in truth or recovery. A wrong + * label is one false positive for the predicted class and one false + * negative for the owner class. + */ + macroF1: number; +} + +export interface TraceCounts { + truthEvents: number; + recoveredEvents: number; + operationEvents: number; + functionBoundaryEvents: number; + exceptionEvents: number; + uniqueOperations: number; + sequenceEdges: number; + dynamicCfgEdges: number; +} + +export interface DynamicTraceMetrics { + /** + * A true positive is one unique owner event correctly attributed with the + * right boundary kind. Duplicate, unknown, or wrong-kind observations are + * false positives; unobserved owner events are false negatives. + */ + boundaryEvents: BinaryMetrics; + operationLabels: OperationMetrics; + /** + * Occurrence-sensitive adjacent owner-event pairs recovered consecutively. + */ + sequenceEdges: BinaryMetrics; + /** + * Distinct adjacent operation-label pairs on the executed path. This is + * dynamic CFG coverage, not static canonical-CFG coverage. + */ + dynamicCfgEdges: BinaryMetrics; + counts: TraceCounts; +} + +export interface PatchCollapseMetrics { + /** + * Smallest number of localized probe families that produced a complete, + * deterministic semantic stream while preserving the fixture result. + */ + minimumLocalizedSites: number | null; + stableSemanticStream: boolean; + correctnessPreserved: boolean; + repeatCount: number; +} + +export interface DynamicBaselineReport { + schemaVersion: typeof DYNAMIC_TRACE_SCHEMA_VERSION; + executorId: string; + corpusVersion: number; + runs: readonly { + fixtureId: string; + metrics: DynamicTraceMetrics; + trace: DynamicTrace; + }[]; + aggregate: DynamicTraceMetrics; + patchCollapse: PatchCollapseMetrics; +} + +interface CapturedConsoleCall { + level: "log" | "warn" | "error"; + args: unknown[]; +} + +interface RawExecution { + outcome: NormalizedOutcome; + consoleCalls: CapturedConsoleCall[]; +} + +const VM_TRACE_PREFIX = "[VM_TRACE] "; +const VM_DEBUG_PREFIX = "[VM_DBG]"; +const LEGACY_CONSOLE_PROBE = "legacy-vm:console"; + +const LEGACY_BASELINE_OPTIONS: VmObfuscationOptions = { + targetMode: "root", + threshold: 1, + preset: "low", + encryptBytecode: false, + preprocessIdentifiers: false, + debugLogging: true, + dynamicOpcodes: false, + decoyOpcodes: false, + deadCodeInjection: false, + debugProtection: false, + vmShielding: false, +}; + +function normalizeValue(value: unknown, seen = new WeakSet()): unknown { + if (typeof value === "number") { + if (Number.isNaN(value)) return { number: "NaN" }; + if (value === Infinity) return { number: "Infinity" }; + if (value === -Infinity) return { number: "-Infinity" }; + if (Object.is(value, -0)) return { number: "-0" }; + return value; + } + if ( + value === null || + typeof value === "string" || + typeof value === "boolean" || + typeof value === "undefined" + ) { + return value; + } + if (typeof value === "bigint") return { bigint: value.toString() }; + if (typeof value === "symbol") return { symbol: String(value) }; + if (typeof value === "function") return { function: value.name || "(anonymous)" }; + if (seen.has(value)) return { circular: true }; + seen.add(value); + if (Array.isArray(value)) { + return value.map((entry) => normalizeValue(entry, seen)); + } + const output: Record = {}; + for (const key of Object.keys(value).sort()) { + output[key] = normalizeValue((value as Record)[key], seen); + } + return output; +} + +function normalizeError(error: unknown): NormalizedOutcome { + if (error instanceof Error) { + return { + type: "throw", + errorName: error.name, + errorMessage: error.message, + }; + } + if ( + typeof error === "object" && + error !== null && + "name" in error && + "message" in error + ) { + return { + type: "throw", + errorName: String((error as { name: unknown }).name), + errorMessage: String((error as { message: unknown }).message), + }; + } + return { + type: "throw", + errorName: typeof error, + errorMessage: String(error), + }; +} + +function executeGenerated( + code: string, + consoleCalls: CapturedConsoleCall[] | null +): RawExecution { + const capture = ( + level: CapturedConsoleCall["level"], + args: unknown[] + ): void => { + if (consoleCalls !== null) consoleCalls.push({ level, args }); + }; + const controlledConsole = Object.freeze({ + log: (...args: unknown[]) => capture("log", args), + warn: (...args: unknown[]) => capture("warn", args), + error: (...args: unknown[]) => capture("error", args), + }); + const context = vm.createContext({ console: controlledConsole }); + try { + const value = new vm.Script(code, { + filename: "ruam-dynamic-attacker-fixture.js", + }).runInContext(context, { timeout: 2_000 }); + return { + outcome: { type: "return", value: normalizeValue(value) }, + consoleCalls: consoleCalls ?? [], + }; + } catch (error) { + return { outcome: normalizeError(error), consoleCalls: consoleCalls ?? [] }; + } +} + +function consoleText(call: CapturedConsoleCall): string { + return call.args.map((value) => String(value)).join(" "); +} + +function parseOperation(line: string): string | undefined { + if (!line.startsWith(VM_TRACE_PREFIX)) return undefined; + return /^([A-Z][A-Z0-9_]*)\s/.exec(line.slice(VM_TRACE_PREFIX.length))?.[1]; +} + +function ownerGroundTruth( + fixtureId: string, + calls: readonly CapturedConsoleCall[] +): GroundTruthEvent[] { + const truth: GroundTruthEvent[] = []; + let ordinal = 0; + for (const call of calls) { + const line = consoleText(call); + const operation = parseOperation(line); + if (operation !== undefined) { + truth.push({ + id: `${fixtureId}:event:${ordinal}`, + ordinal, + kind: "operation", + operation, + }); + ordinal++; + continue; + } + if (call.args[0] !== VM_DEBUG_PREFIX) continue; + const label = String(call.args[1] ?? ""); + if (label === "ENTER") { + const unitPart = call.args.find( + (value) => typeof value === "string" && value.startsWith("unit=") + ); + truth.push({ + id: `${fixtureId}:event:${ordinal}`, + ordinal, + kind: "function-enter", + unit: + typeof unitPart === "string" + ? unitPart.slice("unit=".length) + : undefined, + }); + ordinal++; + } else if (label === "RETURN" || label === "RETURN_VOID") { + truth.push({ + id: `${fixtureId}:event:${ordinal}`, + ordinal, + kind: "function-exit", + }); + ordinal++; + } else if ( + label === "EXCEPTION" || + label === "CATCH" || + label === "FINALLY" + ) { + truth.push({ + id: `${fixtureId}:event:${ordinal}`, + ordinal, + kind: "exception", + }); + ordinal++; + } + } + return truth; +} + +function attackerRecoveryFromConsole( + calls: readonly CapturedConsoleCall[], + truth: readonly GroundTruthEvent[] +): RecoveredEvent[] { + const recovered: RecoveredEvent[] = []; + let truthCursor = 0; + const append = ( + predictedKind: TraceBoundaryKind, + predictedOperation?: string + ): void => { + const attributedTruth = truth[truthCursor]; + recovered.push({ + ordinal: recovered.length, + predictedKind, + predictedOperation, + attributedTruthId: attributedTruth?.id, + probeSiteId: LEGACY_CONSOLE_PROBE, + }); + truthCursor++; + }; + for (const call of calls) { + const line = consoleText(call); + const operation = parseOperation(line); + if (operation !== undefined) { + append("operation", operation); + continue; + } + if (call.args[0] !== VM_DEBUG_PREFIX) continue; + const label = String(call.args[1] ?? ""); + if (label === "ENTER") append("function-enter"); + else if (label === "RETURN" || label === "RETURN_VOID") { + append("function-exit"); + } else if ( + label === "EXCEPTION" || + label === "CATCH" || + label === "FINALLY" + ) { + append("exception"); + } + } + return recovered; +} + +/** Current VM control adapter. Production transform behavior is not modified. */ +export class LegacyVmDynamicTraceExecutor implements DynamicTraceExecutor { + readonly id = "legacy-vm/debug-console/v1"; + + run(fixture: DynamicAttackerFixture, buildSeed: number): DynamicTrace { + const artifact = obfuscateCodeDeterministic( + fixture.source, + LEGACY_BASELINE_OPTIONS, + buildSeed + ); + const consoleCalls: CapturedConsoleCall[] = []; + const execution = executeGenerated(artifact, consoleCalls); + const truth = ownerGroundTruth(fixture.id, execution.consoleCalls); + const recovered = attackerRecoveryFromConsole(execution.consoleCalls, truth); + return { + schemaVersion: DYNAMIC_TRACE_SCHEMA_VERSION, + executorId: this.id, + fixtureId: fixture.id, + buildSeed: buildSeed >>> 0, + outcome: execution.outcome, + truth, + recovered, + probeSiteIds: [LEGACY_CONSOLE_PROBE], + traceTruncated: execution.consoleCalls.some((call) => + consoleText(call).includes("max logs reached") + ), + }; + } + + runWithoutCapture( + fixture: DynamicAttackerFixture, + buildSeed: number + ): NormalizedOutcome { + const artifact = obfuscateCodeDeterministic( + fixture.source, + LEGACY_BASELINE_OPTIONS, + buildSeed + ); + return executeGenerated(artifact, null).outcome; + } +} + +function ratio(numerator: number, denominator: number): number { + if (denominator === 0) return numerator === 0 ? 1 : 0; + return numerator / denominator; +} + +function binaryMetrics( + truePositive: number, + falsePositive: number, + falseNegative: number +): BinaryMetrics { + const precision = ratio(truePositive, truePositive + falsePositive); + const recall = ratio(truePositive, truePositive + falseNegative); + return { + truePositive, + falsePositive, + falseNegative, + precision, + recall, + f1: + precision + recall === 0 + ? 0 + : (2 * precision * recall) / (precision + recall), + }; +} + +function edgeKey(left: string, right: string): string { + return `${left}\u0000${right}`; +} + +function occurrenceEdgeMetrics( + truth: readonly GroundTruthEvent[], + recovered: readonly RecoveredEvent[] +): BinaryMetrics { + const truthEdges = new Set(); + for (let index = 1; index < truth.length; index++) { + truthEdges.add(edgeKey(truth[index - 1]!.id, truth[index]!.id)); + } + const recoveredEdges = new Set(); + for (let index = 1; index < recovered.length; index++) { + const left = recovered[index - 1]!.attributedTruthId; + const right = recovered[index]!.attributedTruthId; + if (left !== undefined && right !== undefined) { + recoveredEdges.add(edgeKey(left, right)); + } + } + let truePositive = 0; + for (const edge of recoveredEdges) { + if (truthEdges.has(edge)) truePositive++; + } + return binaryMetrics( + truePositive, + recoveredEdges.size - truePositive, + truthEdges.size - truePositive + ); +} + +function cfgEdgeMetrics( + truth: readonly GroundTruthEvent[], + recovered: readonly RecoveredEvent[], + truthById: ReadonlyMap +): BinaryMetrics { + const truthOps = truth.filter( + (event): event is GroundTruthEvent & { operation: string } => + event.kind === "operation" && event.operation !== undefined + ); + const truthEdges = new Set(); + for (let index = 1; index < truthOps.length; index++) { + truthEdges.add( + edgeKey(truthOps[index - 1]!.operation, truthOps[index]!.operation) + ); + } + const recoveredOps = recovered.filter( + (event): event is RecoveredEvent & { predictedOperation: string } => + event.predictedKind === "operation" && + event.predictedOperation !== undefined && + event.attributedTruthId !== undefined && + truthById.has(event.attributedTruthId) + ); + const recoveredEdges = new Set(); + for (let index = 1; index < recoveredOps.length; index++) { + recoveredEdges.add( + edgeKey( + recoveredOps[index - 1]!.predictedOperation, + recoveredOps[index]!.predictedOperation + ) + ); + } + let truePositive = 0; + for (const edge of recoveredEdges) { + if (truthEdges.has(edge)) truePositive++; + } + return binaryMetrics( + truePositive, + recoveredEdges.size - truePositive, + truthEdges.size - truePositive + ); +} + +export function scoreDynamicTrace(trace: DynamicTrace): DynamicTraceMetrics { + const truthById = new Map(trace.truth.map((event) => [event.id, event])); + const matchedIds = new Set(); + let boundaryTruePositive = 0; + let boundaryFalsePositive = 0; + for (const event of trace.recovered) { + const truth = event.attributedTruthId + ? truthById.get(event.attributedTruthId) + : undefined; + if ( + truth !== undefined && + truth.kind === event.predictedKind && + !matchedIds.has(truth.id) + ) { + matchedIds.add(truth.id); + boundaryTruePositive++; + } else { + boundaryFalsePositive++; + } + } + const boundaryEvents = binaryMetrics( + boundaryTruePositive, + boundaryFalsePositive, + trace.truth.length - boundaryTruePositive + ); + + const truthOperations = trace.truth.filter( + (event): event is GroundTruthEvent & { operation: string } => + event.kind === "operation" && event.operation !== undefined + ); + const labels = new Set(truthOperations.map((event) => event.operation)); + const perLabel = new Map< + string, + { truePositive: number; falsePositive: number; falseNegative: number } + >(); + const truthLabelCounts = new Map(); + for (const truth of truthOperations) { + truthLabelCounts.set( + truth.operation, + (truthLabelCounts.get(truth.operation) ?? 0) + 1 + ); + } + const labelCounts = (label: string) => { + let counts = perLabel.get(label); + if (counts === undefined) { + counts = { truePositive: 0, falsePositive: 0, falseNegative: 0 }; + perLabel.set(label, counts); + } + return counts; + }; + let correctLabels = 0; + let attributedOperations = 0; + let labelFalsePositive = 0; + for (const recovered of trace.recovered) { + if ( + recovered.predictedKind !== "operation" || + recovered.predictedOperation === undefined + ) { + continue; + } + labels.add(recovered.predictedOperation); + const truth = + recovered.attributedTruthId === undefined + ? undefined + : truthById.get(recovered.attributedTruthId); + if (truth?.kind === "operation" && truth.operation !== undefined) { + attributedOperations++; + if (truth.operation === recovered.predictedOperation) { + correctLabels++; + labelCounts(truth.operation).truePositive++; + } else { + labelFalsePositive++; + labelCounts(recovered.predictedOperation).falsePositive++; + } + } else { + labelFalsePositive++; + labelCounts(recovered.predictedOperation).falsePositive++; + } + } + for (const [label, truthCount] of truthLabelCounts) { + const counts = labelCounts(label); + counts.falseNegative = truthCount - counts.truePositive; + } + const labelMetrics = binaryMetrics( + correctLabels, + labelFalsePositive, + truthOperations.length - correctLabels + ); + const macroF1 = + labels.size === 0 + ? 1 + : [...labels].reduce((sum, label) => { + const counts = labelCounts(label); + return ( + sum + + binaryMetrics( + counts.truePositive, + counts.falsePositive, + counts.falseNegative + ).f1 + ); + }, 0) / labels.size; + + const operationLabels: OperationMetrics = { + ...labelMetrics, + accuracy: ratio(correctLabels, attributedOperations), + macroF1, + }; + const sequenceEdges = occurrenceEdgeMetrics(trace.truth, trace.recovered); + const dynamicCfgEdges = cfgEdgeMetrics( + trace.truth, + trace.recovered, + truthById + ); + const operationEvents = truthOperations.length; + const functionBoundaryEvents = trace.truth.filter( + (event) => + event.kind === "function-enter" || event.kind === "function-exit" + ).length; + const exceptionEvents = trace.truth.filter( + (event) => event.kind === "exception" + ).length; + const uniqueOperations = new Set( + truthOperations.map((event) => event.operation) + ).size; + const dynamicCfgEdgeSet = new Set(); + for (let index = 1; index < truthOperations.length; index++) { + dynamicCfgEdgeSet.add( + edgeKey( + truthOperations[index - 1]!.operation, + truthOperations[index]!.operation + ) + ); + } + return { + boundaryEvents, + operationLabels, + sequenceEdges, + dynamicCfgEdges, + counts: { + truthEvents: trace.truth.length, + recoveredEvents: trace.recovered.length, + operationEvents, + functionBoundaryEvents, + exceptionEvents, + uniqueOperations, + sequenceEdges: Math.max(0, trace.truth.length - 1), + dynamicCfgEdges: dynamicCfgEdgeSet.size, + }, + }; +} + +function aggregateMetrics( + metrics: readonly DynamicTraceMetrics[] +): DynamicTraceMetrics { + const combineBinary = ( + select: (metric: DynamicTraceMetrics) => BinaryMetrics + ): BinaryMetrics => + binaryMetrics( + metrics.reduce((sum, metric) => sum + select(metric).truePositive, 0), + metrics.reduce((sum, metric) => sum + select(metric).falsePositive, 0), + metrics.reduce((sum, metric) => sum + select(metric).falseNegative, 0) + ); + const operationBinary = combineBinary((metric) => metric.operationLabels); + const attributed = metrics.reduce( + (sum, metric) => + sum + + metric.operationLabels.truePositive + + metric.operationLabels.falsePositive, + 0 + ); + const operationLabels: OperationMetrics = { + ...operationBinary, + accuracy: ratio(operationBinary.truePositive, attributed), + macroF1: + metrics.length === 0 + ? 1 + : metrics.reduce( + (sum, metric) => sum + metric.operationLabels.macroF1, + 0 + ) / metrics.length, + }; + const sumCount = (key: keyof TraceCounts): number => + metrics.reduce((sum, metric) => sum + metric.counts[key], 0); + return { + boundaryEvents: combineBinary((metric) => metric.boundaryEvents), + operationLabels, + sequenceEdges: combineBinary((metric) => metric.sequenceEdges), + dynamicCfgEdges: combineBinary((metric) => metric.dynamicCfgEdges), + counts: { + truthEvents: sumCount("truthEvents"), + recoveredEvents: sumCount("recoveredEvents"), + operationEvents: sumCount("operationEvents"), + functionBoundaryEvents: sumCount("functionBoundaryEvents"), + exceptionEvents: sumCount("exceptionEvents"), + uniqueOperations: sumCount("uniqueOperations"), + sequenceEdges: sumCount("sequenceEdges"), + dynamicCfgEdges: sumCount("dynamicCfgEdges"), + }, + }; +} + +function sameOutcome(left: NormalizedOutcome, right: NormalizedOutcome): boolean { + return JSON.stringify(left) === JSON.stringify(right); +} + +function semanticStream(trace: DynamicTrace): string { + return trace.recovered + .filter((event) => event.predictedKind === "operation") + .map((event) => event.predictedOperation ?? "?") + .join("\u0000"); +} + +export function buildLegacyVmBaselineReport( + fixtures: readonly DynamicAttackerFixture[], + options: { + buildSeed?: number; + repeatCount?: number; + corpusVersion?: number; + } = {} +): DynamicBaselineReport { + const buildSeed = options.buildSeed ?? 0x1badb002; + const repeatCount = options.repeatCount ?? 2; + if (!Number.isSafeInteger(repeatCount) || repeatCount < 2) { + throw new Error("RUAM_DYNAMIC_BASELINE_REPEATS_MUST_BE_AT_LEAST_TWO"); + } + const executor = new LegacyVmDynamicTraceExecutor(); + const runs = fixtures.map((fixture) => { + const trace = executor.run(fixture, buildSeed); + return { fixtureId: fixture.id, trace, metrics: scoreDynamicTrace(trace) }; + }); + const repeated = Array.from({ length: repeatCount }, () => + fixtures.map((fixture) => executor.run(fixture, buildSeed)) + ); + const stableSemanticStream = fixtures.every((_, fixtureIndex) => { + const expectedStream = semanticStream(repeated[0]![fixtureIndex]!); + return repeated.every( + (batch) => semanticStream(batch[fixtureIndex]!) === expectedStream + ); + }); + const correctnessPreserved = fixtures.every((fixture, fixtureIndex) => { + const observed = runs[fixtureIndex]!.trace.outcome; + const unobserved = executor.runWithoutCapture(fixture, buildSeed); + const expected: NormalizedOutcome = { + type: "return", + value: normalizeValue(fixture.expected), + }; + return sameOutcome(observed, unobserved) && sameOutcome(observed, expected); + }); + const completeRecovery = runs.every( + (run) => + run.metrics.boundaryEvents.recall === 1 && + run.metrics.operationLabels.f1 === 1 && + !run.trace.traceTruncated + ); + const distinctProbeSites = new Set( + runs.flatMap((run) => [...run.trace.probeSiteIds]) + ).size; + const patchCollapse: PatchCollapseMetrics = { + minimumLocalizedSites: + completeRecovery && stableSemanticStream && correctnessPreserved + ? distinctProbeSites + : null, + stableSemanticStream, + correctnessPreserved, + repeatCount, + }; + return { + schemaVersion: DYNAMIC_TRACE_SCHEMA_VERSION, + executorId: executor.id, + corpusVersion: options.corpusVersion ?? 1, + runs, + aggregate: aggregateMetrics(runs.map((run) => run.metrics)), + patchCollapse, + }; +} From 129d19f543a9f2d025a616b800fda3c1a5a92889 Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:00:29 -1000 Subject: [PATCH 11/54] feat: delimit canonical effect regions --- packages/ruam/src/compiler/index.ts | 15 + packages/ruam/src/compiler/regions.ts | 772 ++++++++++++++++++ .../ruam/src/compiler/semantic-signatures.ts | 687 +++++++++++++++- .../ruam/test/isogloss/effect-regions.test.ts | 311 +++++++ .../test/isogloss/semantic-signatures.test.ts | 86 ++ 5 files changed, 1869 insertions(+), 2 deletions(-) create mode 100644 packages/ruam/src/compiler/regions.ts create mode 100644 packages/ruam/test/isogloss/effect-regions.test.ts diff --git a/packages/ruam/src/compiler/index.ts b/packages/ruam/src/compiler/index.ts index e0a4149..1150c86 100644 --- a/packages/ruam/src/compiler/index.ts +++ b/packages/ruam/src/compiler/index.ts @@ -39,6 +39,21 @@ import type { } from "./ir.js"; import type { RootGroupId } from "./types.js"; +export { + buildEffectRegionGraph, + validateEffectRegionGraph, +} from "./regions.js"; +export type { + EffectRegion, + EffectRegionExit, + EffectRegionGraph, + EffectRegionId, + RegionBoundaryContract, + RegionEffectSummary, + RegionStateDomain, + RegionStatePort, +} from "./regions.js"; + // --------------------------------------------------------------------------- // Scope-object elision // --------------------------------------------------------------------------- diff --git a/packages/ruam/src/compiler/regions.ts b/packages/ruam/src/compiler/regions.ts new file mode 100644 index 0000000..b849106 --- /dev/null +++ b/packages/ruam/src/compiler/regions.ts @@ -0,0 +1,772 @@ +/** + * Conservative effect-delimited regions over canonical semantic control flow. + * + * Regions are a compiler artifact. They retain canonical node ownership for + * verification, but production BPRF lowering must not publish these identities. + * Fusion fails closed at every observable, exceptional, dynamic-stack, call, + * suspension, branch, join, or structured-completion boundary. + * + * @module compiler/regions + */ + +import type { CanonicalCfg } from "./cfg.js"; +import type { + SemanticExit, + SemanticInstruction, + SemanticNodeId, + SemanticUnit, +} from "./ir.js"; +import { + getSemanticSignature, + resolveStackArity, + type ResolvedStackArity, + type SemanticAccess, + type SemanticAllocation, + type SemanticCallKind, + type SemanticCoercion, + type SemanticCompletion, + type SemanticEffect, + type SemanticPurity, + type SemanticSignature, + type SemanticSuspensionKind, + type SemanticThrowBehavior, +} from "./semantic-signatures.js"; + +export type EffectRegionId = string; + +export type RegionStateDomain = + | "register" + | "argument" + | "slot" + | "frame" + | "scope" + | "object" + | "global" + | "this"; + +/** One state dependency crossing a region boundary. */ +export interface RegionStatePort { + domain: RegionStateDomain; + /** Known register, argument, slot, or constant-pool name index. */ + key?: number; +} + +/** Stack and non-stack dependencies crossing one side of a region. */ +export interface RegionBoundaryContract { + stack: ResolvedStackArity; + state: readonly RegionStatePort[]; +} + +/** Aggregate effect facts for a complete region. */ +export interface RegionEffectSummary { + effects: readonly SemanticEffect[]; + purity: SemanticPurity; + throwBehavior: SemanticThrowBehavior; + coercions: readonly SemanticCoercion[]; + callKinds: readonly SemanticCallKind[]; + suspensions: readonly SemanticSuspensionKind[]; + frameAccess: SemanticAccess; + scopeAccess: SemanticAccess; + objectAccess: SemanticAccess; + globalAccess: SemanticAccess; + allocations: readonly SemanticAllocation[]; + completions: readonly SemanticCompletion[]; + readsThis: boolean; + hasDynamicStack: boolean; +} + +interface RegionExitBase { + sourceNodeId: SemanticNodeId; +} + +/** Typed transfer between effect regions or out of the current invocation. */ +export type EffectRegionExit = + | (RegionExitBase & { + kind: + | "fallthrough" + | "branch-true" + | "branch-false" + | "exception" + | "finally"; + targetNodeId: SemanticNodeId; + targetRegionId: EffectRegionId; + }) + | (RegionExitBase & { + kind: "call" | "yield" | "await"; + resumeNodeId: SemanticNodeId; + resumeRegionId: EffectRegionId; + }) + | (RegionExitBase & { kind: "return" | "throw" }); + +export interface EffectRegion { + id: EffectRegionId; + entryNodeId: SemanticNodeId; + nodeIds: readonly SemanticNodeId[]; + inputs: RegionBoundaryContract; + outputs: RegionBoundaryContract; + effects: RegionEffectSummary; + exits: readonly EffectRegionExit[]; +} + +export interface EffectRegionGraph { + /** Present when constructed from a SemanticUnit rather than a bare CFG. */ + unitId: string | null; + entryRegionId: EffectRegionId; + regions: readonly EffectRegion[]; + nodeOwnership: ReadonlyMap; +} + +type RegionGraphInput = Pick< + CanonicalCfg, + "nodes" | "exits" | "entryNode" +> & { id?: string }; + +interface Predecessor { + source: SemanticNodeId; + exit: SemanticExit; +} + +const PURITY_ORDER: readonly SemanticPurity[] = [ + "pure", + "frame-local", + "observable", +]; +const THROW_ORDER: readonly SemanticThrowBehavior[] = [ + "never", + "may-throw", + "always-throws", +]; +const ACCESS_ORDER: readonly SemanticAccess[] = [ + "none", + "read", + "write", + "read-write", + "unknown", +]; + +/** Build and validate deterministic effect-delimited regions. */ +export function buildEffectRegionGraph( + input: CanonicalCfg | SemanticUnit +): EffectRegionGraph { + const graphInput = input as RegionGraphInput; + const nodes = validateAndIndexInput(graphInput); + const predecessors = buildPredecessors(graphInput, nodes); + const orderedNodeIds = [...nodes.keys()].sort((left, right) => left - right); + const ownership = new Map(); + const nodeGroups: SemanticNodeId[][] = []; + + for (const nodeId of orderedNodeIds) { + if (ownership.has(nodeId)) continue; + + const group = [nodeId]; + let current = nodeId; + while (true) { + const next = fusionSuccessor( + current, + graphInput, + nodes, + predecessors, + ownership + ); + if (next == null) break; + group.push(next); + current = next; + } + + const regionId = regionIdFor(group[0]!); + for (const ownedNodeId of group) { + if (ownership.has(ownedNodeId)) { + throw new Error( + `RUAM_DUPLICATE_REGION_OWNER: node ${ownedNodeId}` + ); + } + ownership.set(ownedNodeId, regionId); + } + nodeGroups.push(group); + } + + const regions = nodeGroups.map((nodeIds) => { + const id = ownership.get(nodeIds[0]!)!; + const regionNodes = nodeIds.map((nodeId) => nodes.get(nodeId)!); + const boundary = computeBoundaryContracts(regionNodes); + const lastNodeId = nodeIds[nodeIds.length - 1]!; + const semanticExits = graphInput.exits.get(lastNodeId); + if (!semanticExits) { + throw new Error(`RUAM_MISSING_SEMANTIC_EXITS: node ${lastNodeId}`); + } + const exits = semanticExits.map((exit) => + mapRegionExit(lastNodeId, exit, ownership) + ); + + return Object.freeze({ + id, + entryNodeId: nodeIds[0]!, + nodeIds: Object.freeze(nodeIds.slice()), + inputs: boundary.inputs, + outputs: boundary.outputs, + effects: summarizeEffects(regionNodes), + exits: Object.freeze(exits), + }); + }); + + const entryRegionId = ownership.get(graphInput.entryNode); + if (!entryRegionId) { + throw new Error( + `RUAM_MISSING_ENTRY_REGION: node ${graphInput.entryNode}` + ); + } + + const graph: EffectRegionGraph = Object.freeze({ + unitId: typeof graphInput.id === "string" ? graphInput.id : null, + entryRegionId, + regions: Object.freeze(regions), + nodeOwnership: ownership, + }); + validateEffectRegionGraph(graphInput, graph); + return graph; +} + +/** + * Recheck ownership, boundary, and transfer invariants. + * + * Exposed so certificate/verifier work can validate deserialized owner-side + * region graphs without trusting their builder. + */ +export function validateEffectRegionGraph( + input: Pick, + graph: EffectRegionGraph +): void { + const graphInput = input as RegionGraphInput; + const nodes = validateAndIndexInput(graphInput); + const predecessors = buildPredecessors(graphInput, nodes); + const seen = new Set(); + const regionIds = new Set(); + let previousEntryNodeId = -1; + + for (const region of graph.regions) { + if (regionIds.has(region.id)) { + throw new Error(`RUAM_DUPLICATE_REGION_ID: ${region.id}`); + } + regionIds.add(region.id); + if (region.nodeIds.length === 0) { + throw new Error(`RUAM_EMPTY_EFFECT_REGION: ${region.id}`); + } + if (region.id !== regionIdFor(region.nodeIds[0]!)) { + throw new Error(`RUAM_NONDETERMINISTIC_REGION_ID: ${region.id}`); + } + if (region.entryNodeId !== region.nodeIds[0]) { + throw new Error(`RUAM_INVALID_REGION_ENTRY: ${region.id}`); + } + if (region.entryNodeId <= previousEntryNodeId) { + throw new Error("RUAM_NONDETERMINISTIC_REGION_ORDER"); + } + previousEntryNodeId = region.entryNodeId; + + for (let index = 0; index < region.nodeIds.length; index++) { + const nodeId = region.nodeIds[index]!; + if (!nodes.has(nodeId)) { + throw new Error(`RUAM_UNKNOWN_REGION_NODE: ${nodeId}`); + } + if (seen.has(nodeId)) { + throw new Error(`RUAM_DUPLICATE_REGION_OWNER: node ${nodeId}`); + } + seen.add(nodeId); + if (graph.nodeOwnership.get(nodeId) !== region.id) { + throw new Error(`RUAM_REGION_OWNERSHIP_MISMATCH: node ${nodeId}`); + } + + const next = region.nodeIds[index + 1]; + if (next != null) { + const exits = graphInput.exits.get(nodeId)!; + if ( + exits.length !== 1 || + exits[0]!.kind !== "fallthrough" || + exits[0]!.target !== next + ) { + throw new Error( + `RUAM_REGION_CROSSES_CONTROL_BOUNDARY: ${region.id}` + ); + } + if ( + isFusionBarrier( + nodes.get(nodeId)!, + exits, + predecessors.get(nodeId)!, + graphInput.entryNode + ) || + isFusionBarrier( + nodes.get(next)!, + graphInput.exits.get(next)!, + predecessors.get(next)!, + graphInput.entryNode + ) + ) { + throw new Error( + `RUAM_REGION_CROSSES_EFFECT_BOUNDARY: ${region.id}` + ); + } + } + } + + const regionNodes = region.nodeIds.map((nodeId) => nodes.get(nodeId)!); + const expectedBoundary = computeBoundaryContracts(regionNodes); + if ( + !structurallyEqual(region.inputs, expectedBoundary.inputs) || + !structurallyEqual(region.outputs, expectedBoundary.outputs) || + !structurallyEqual(region.effects, summarizeEffects(regionNodes)) + ) { + throw new Error(`RUAM_INVALID_REGION_CONTRACT: ${region.id}`); + } + const lastNodeId = region.nodeIds[region.nodeIds.length - 1]!; + const expectedExits = graphInput.exits + .get(lastNodeId)! + .map((exit) => + mapRegionExit(lastNodeId, exit, graph.nodeOwnership) + ); + if (!structurallyEqual(region.exits, expectedExits)) { + throw new Error(`RUAM_REGION_EXIT_MISMATCH: ${region.id}`); + } + } + + if (seen.size !== nodes.size) { + throw new Error( + `RUAM_INCOMPLETE_REGION_OWNERSHIP: ${seen.size} of ${nodes.size}` + ); + } + if (graph.nodeOwnership.size !== nodes.size) { + throw new Error("RUAM_INVALID_REGION_OWNERSHIP_SIZE"); + } + for (const nodeId of graph.nodeOwnership.keys()) { + if (!nodes.has(nodeId)) { + throw new Error(`RUAM_OWNER_FOR_UNKNOWN_NODE: ${nodeId}`); + } + } + if (graph.entryRegionId !== graph.nodeOwnership.get(graphInput.entryNode)) { + throw new Error("RUAM_ENTRY_REGION_MISMATCH"); + } + + for (const region of graph.regions) { + for (const exit of region.exits) { + if ( + "targetRegionId" in exit && + !regionIds.has(exit.targetRegionId) + ) { + throw new Error( + `RUAM_UNKNOWN_TARGET_REGION: ${exit.targetRegionId}` + ); + } + if ( + "resumeRegionId" in exit && + !regionIds.has(exit.resumeRegionId) + ) { + throw new Error( + `RUAM_UNKNOWN_RESUME_REGION: ${exit.resumeRegionId}` + ); + } + } + } +} + +function validateAndIndexInput( + input: RegionGraphInput +): Map { + if (input.nodes.length === 0) { + throw new Error("RUAM_EMPTY_SEMANTIC_UNIT"); + } + const nodes = new Map(); + for (const node of input.nodes) { + if (!Number.isSafeInteger(node.id) || node.id < 0) { + throw new Error(`RUAM_INVALID_SEMANTIC_NODE_ID: ${node.id}`); + } + if (nodes.has(node.id)) { + throw new Error(`RUAM_DUPLICATE_SEMANTIC_NODE_ID: ${node.id}`); + } + nodes.set(node.id, node); + if (!input.exits.has(node.id)) { + throw new Error(`RUAM_MISSING_SEMANTIC_EXITS: node ${node.id}`); + } + } + if (!nodes.has(input.entryNode)) { + throw new Error(`RUAM_INVALID_SEMANTIC_ENTRY: ${input.entryNode}`); + } + for (const [nodeId, exits] of input.exits) { + if (!nodes.has(nodeId)) { + throw new Error(`RUAM_EXITS_FOR_UNKNOWN_NODE: ${nodeId}`); + } + for (const exit of exits) { + const target = semanticExitTarget(exit); + if (target != null && !nodes.has(target)) { + throw new Error( + `RUAM_INVALID_SEMANTIC_TARGET: node ${nodeId} -> ${target}` + ); + } + } + } + return nodes; +} + +function buildPredecessors( + input: RegionGraphInput, + nodes: ReadonlyMap +): Map { + const predecessors = new Map(); + for (const nodeId of nodes.keys()) predecessors.set(nodeId, []); + for (const [source, exits] of input.exits) { + for (const exit of exits) { + const target = semanticExitTarget(exit); + if (target == null) continue; + predecessors.get(target)!.push({ source, exit }); + } + } + for (const entries of predecessors.values()) { + entries.sort( + (left, right) => + left.source - right.source || + left.exit.kind.localeCompare(right.exit.kind) + ); + } + return predecessors; +} + +function fusionSuccessor( + nodeId: SemanticNodeId, + input: RegionGraphInput, + nodes: ReadonlyMap, + predecessors: ReadonlyMap, + ownership: ReadonlyMap +): SemanticNodeId | null { + const node = nodes.get(nodeId)!; + const exits = input.exits.get(nodeId)!; + if ( + isFusionBarrier( + node, + exits, + predecessors.get(nodeId)!, + input.entryNode + ) + ) { + return null; + } + if (exits.length !== 1 || exits[0]!.kind !== "fallthrough") return null; + + const target = exits[0]!.target; + if (ownership.has(target)) return null; + const targetNode = nodes.get(target)!; + const targetPredecessors = predecessors.get(target)!; + if ( + targetPredecessors.length !== 1 || + targetPredecessors[0]!.source !== nodeId + ) { + return null; + } + if ( + isFusionBarrier( + targetNode, + input.exits.get(target)!, + targetPredecessors, + input.entryNode + ) + ) { + return null; + } + return target; +} + +function isFusionBarrier( + node: SemanticInstruction, + exits: readonly SemanticExit[], + predecessors: readonly Predecessor[], + entryNode: SemanticNodeId +): boolean { + const signature = getSemanticSignature(node.op); + const stackInput = resolveStackArity(signature.stackInput, node.operand); + const stackOutput = resolveStackArity(signature.stackOutput, node.operand); + if (stackInput === "dynamic" || stackOutput === "dynamic") return true; + if (signature.precision !== "classified") return true; + if (signature.purity === "observable") return true; + if (signature.throwBehavior !== "never") return true; + if ( + signature.coercion === "observable" || + signature.coercion === "unknown" + ) { + return true; + } + if (signature.callKind !== "none") return true; + if (signature.suspension !== "none") return true; + if (signature.scopeAccess !== "none") return true; + if (signature.objectAccess !== "none") return true; + if (signature.globalAccess !== "none") return true; + if (signature.allocation !== "none") return true; + if (signature.frameAccess === "unknown") return true; + if (signature.completion !== "normal") return true; + if ( + exits.length !== 1 || + exits[0]!.kind !== "fallthrough" + ) { + return true; + } + if (node.id === entryNode) { + return predecessors.length !== 0; + } + if (predecessors.length !== 1) return true; + return predecessors.some( + (predecessor) => + predecessor.exit.kind === "exception" || + predecessor.exit.kind === "finally" + ); +} + +function computeBoundaryContracts( + nodes: readonly SemanticInstruction[] +): { + inputs: RegionBoundaryContract; + outputs: RegionBoundaryContract; +} { + let stackDepth = 0; + let requiredStack = 0; + let dynamicStack = false; + const stateInputs = new Map(); + const stateOutputs = new Map(); + const writtenState = new Set(); + + for (const node of nodes) { + const signature = getSemanticSignature(node.op); + const stackInput = resolveStackArity(signature.stackInput, node.operand); + const stackOutput = resolveStackArity(signature.stackOutput, node.operand); + if (stackInput === "dynamic" || stackOutput === "dynamic") { + dynamicStack = true; + } else if (!dynamicStack) { + requiredStack = Math.max(requiredStack, stackInput - stackDepth); + stackDepth += stackOutput - stackInput; + } + + for (const access of stateAccesses(node, signature)) { + const key = statePortKey(access.port); + if ( + (access.access === "read" || + access.access === "read-write" || + access.access === "unknown") && + !writtenState.has(key) + ) { + stateInputs.set(key, access.port); + } + if ( + access.access === "write" || + access.access === "read-write" || + access.access === "unknown" + ) { + writtenState.add(key); + stateOutputs.set(key, access.port); + } + } + } + + const outputStack = dynamicStack + ? "dynamic" + : requiredStack + stackDepth; + if (outputStack !== "dynamic" && outputStack < 0) { + throw new Error("RUAM_NEGATIVE_REGION_STACK_OUTPUT"); + } + + return { + inputs: Object.freeze({ + stack: dynamicStack ? "dynamic" : requiredStack, + state: Object.freeze(sortStatePorts(stateInputs.values())), + }), + outputs: Object.freeze({ + stack: outputStack, + state: Object.freeze(sortStatePorts(stateOutputs.values())), + }), + }; +} + +function stateAccesses( + node: SemanticInstruction, + signature: SemanticSignature +): Array<{ access: SemanticAccess; port: RegionStatePort }> { + const result: Array<{ access: SemanticAccess; port: RegionStatePort }> = []; + if (signature.frameAccess !== "none") { + const domain: RegionStateDomain = + signature.operandKind === "register" + ? "register" + : signature.operandKind === "argument" + ? "argument" + : signature.operandKind === "slot" + ? "slot" + : "frame"; + result.push({ + access: signature.frameAccess, + port: Object.freeze({ + domain, + ...(domain !== "frame" ? { key: node.operand } : {}), + }), + }); + } + for (const [domain, access] of [ + ["scope", signature.scopeAccess], + ["object", signature.objectAccess], + ["global", signature.globalAccess], + ] as const) { + if (access === "none") continue; + result.push({ + access, + port: Object.freeze({ + domain, + ...((domain === "scope" || domain === "global") && + signature.operandKind === "scope-name" + ? { key: node.operand } + : {}), + }), + }); + } + if (signature.readsThis) { + result.push({ + access: "read", + port: Object.freeze({ domain: "this" }), + }); + } + return result; +} + +function summarizeEffects( + nodes: readonly SemanticInstruction[] +): RegionEffectSummary { + const signatures = nodes.map((node) => getSemanticSignature(node.op)); + return Object.freeze({ + effects: uniqueSorted(signatures.map((signature) => signature.effect)), + purity: maximum( + signatures.map((signature) => signature.purity), + PURITY_ORDER + ), + throwBehavior: maximum( + signatures.map((signature) => signature.throwBehavior), + THROW_ORDER + ), + coercions: uniqueSorted( + signatures.map((signature) => signature.coercion) + ), + callKinds: uniqueSorted( + signatures.map((signature) => signature.callKind) + ), + suspensions: uniqueSorted( + signatures.map((signature) => signature.suspension) + ), + frameAccess: combineAccess( + signatures.map((signature) => signature.frameAccess) + ), + scopeAccess: combineAccess( + signatures.map((signature) => signature.scopeAccess) + ), + objectAccess: combineAccess( + signatures.map((signature) => signature.objectAccess) + ), + globalAccess: combineAccess( + signatures.map((signature) => signature.globalAccess) + ), + allocations: uniqueSorted( + signatures.map((signature) => signature.allocation) + ), + completions: uniqueSorted( + signatures.map((signature) => signature.completion) + ), + readsThis: signatures.some((signature) => signature.readsThis), + hasDynamicStack: nodes.some((node, index) => { + const signature = signatures[index]!; + return ( + resolveStackArity(signature.stackInput, node.operand) === "dynamic" || + resolveStackArity(signature.stackOutput, node.operand) === "dynamic" + ); + }), + }); +} + +function mapRegionExit( + sourceNodeId: SemanticNodeId, + exit: SemanticExit, + ownership: ReadonlyMap +): EffectRegionExit { + if ("target" in exit) { + const targetRegionId = ownership.get(exit.target); + if (!targetRegionId) { + throw new Error( + `RUAM_MISSING_TARGET_REGION: node ${sourceNodeId} -> ${exit.target}` + ); + } + return Object.freeze({ + kind: exit.kind, + sourceNodeId, + targetNodeId: exit.target, + targetRegionId, + }); + } + if ("resume" in exit) { + const resumeRegionId = ownership.get(exit.resume); + if (!resumeRegionId) { + throw new Error( + `RUAM_MISSING_RESUME_REGION: node ${sourceNodeId} -> ${exit.resume}` + ); + } + return Object.freeze({ + kind: exit.kind, + sourceNodeId, + resumeNodeId: exit.resume, + resumeRegionId, + }); + } + return Object.freeze({ kind: exit.kind, sourceNodeId }); +} + +function semanticExitTarget(exit: SemanticExit): SemanticNodeId | null { + if ("target" in exit) return exit.target; + if ("resume" in exit) return exit.resume; + return null; +} + +function regionIdFor(firstNodeId: SemanticNodeId): EffectRegionId { + return `r_${firstNodeId.toString(36)}`; +} + +function statePortKey(port: RegionStatePort): string { + return `${port.domain}:${port.key ?? "*"}`; +} + +function sortStatePorts( + ports: Iterable +): RegionStatePort[] { + return [...ports].sort((left, right) => + statePortKey(left).localeCompare(statePortKey(right)) + ); +} + +function uniqueSorted(values: readonly T[]): readonly T[] { + return Object.freeze([...new Set(values)].sort()); +} + +function structurallyEqual(left: unknown, right: unknown): boolean { + return JSON.stringify(left) === JSON.stringify(right); +} + +function maximum( + values: readonly T[], + order: readonly T[] +): T { + let result = order[0]!; + for (const value of values) { + if (order.indexOf(value) > order.indexOf(result)) result = value; + } + return result; +} + +function combineAccess(values: readonly SemanticAccess[]): SemanticAccess { + if (values.includes("unknown")) return "unknown"; + const reads = values.some( + (value) => value === "read" || value === "read-write" + ); + const writes = values.some( + (value) => value === "write" || value === "read-write" + ); + if (reads && writes) return "read-write"; + if (reads) return "read"; + if (writes) return "write"; + return "none"; +} diff --git a/packages/ruam/src/compiler/semantic-signatures.ts b/packages/ruam/src/compiler/semantic-signatures.ts index d61ef8d..0d415ad 100644 --- a/packages/ruam/src/compiler/semantic-signatures.ts +++ b/packages/ruam/src/compiler/semantic-signatures.ts @@ -53,6 +53,77 @@ export type SemanticEffect = | "exception" | "async"; +/** Whether an operation is safe to move/fuse without crossing observability. */ +export type SemanticPurity = "pure" | "frame-local" | "observable"; + +/** Throw behavior after the operation's inputs have already been evaluated. */ +export type SemanticThrowBehavior = + | "never" + | "may-throw" + | "always-throws"; + +/** + * Conversion performed by the operation. + * + * `intrinsic` conversions (for example ToBoolean) cannot invoke user code. + * `observable` conversions can invoke proxies or user-defined conversion + * hooks. `unknown` deliberately fails closed. + */ +export type SemanticCoercion = + | "none" + | "intrinsic" + | "observable" + | "unknown"; + +/** User-code invocation performed directly by the operation. */ +export type SemanticCallKind = + | "none" + | "invoke" + | "construct" + | "direct-eval" + | "dynamic-import" + | "coercion-hook" + | "host-protocol" + | "unknown"; + +/** Suspension protocol entered by the operation. */ +export type SemanticSuspensionKind = + | "none" + | "yield" + | "await" + | "runtime" + | "unknown"; + +/** Conservative read/write classification for one state domain. */ +export type SemanticAccess = + | "none" + | "read" + | "write" + | "read-write" + | "unknown"; + +/** Identity-bearing allocation performed by the operation. */ +export type SemanticAllocation = + | "none" + | "closure" + | "object" + | "array" + | "arguments" + | "iterator" + | "promise" + | "unknown"; + +/** Completion shape emitted after the operation executes. */ +export type SemanticCompletion = + | "normal" + | "conditional" + | "jump" + | "call" + | "return" + | "throw" + | "yield" + | "await"; + export type SemanticControl = | "fallthrough" | "conditional" @@ -94,8 +165,20 @@ export interface SemanticSignature { stackInput: StackArity; stackOutput: StackArity; effect: SemanticEffect; + purity: SemanticPurity; control: SemanticControl; + completion: SemanticCompletion; mayThrow: boolean; + throwBehavior: SemanticThrowBehavior; + coercion: SemanticCoercion; + callKind: SemanticCallKind; + suspension: SemanticSuspensionKind; + /** Access to unit-local registers, arguments, or indexed slots. */ + frameAccess: SemanticAccess; + scopeAccess: SemanticAccess; + objectAccess: SemanticAccess; + globalAccess: SemanticAccess; + allocation: SemanticAllocation; readsThis: boolean; readsScope: boolean; /** @@ -272,6 +355,11 @@ classify( family: "register", } ); +// A plain frame read cannot execute JavaScript. Increment/decrement variants +// perform ToNumeric and retain the conservative throwing classification. +classify([SemanticOp.LOAD_REG], { + mayThrow: false, +}); classify([SemanticOp.STORE_REG], { operandKind: "register", stackInput: 1, @@ -327,6 +415,71 @@ classify([SemanticOp.STORE_ARG], { readsScope: false, family: "argument", }); +classify([SemanticOp.LOAD_SLOT], { + operandKind: "slot", + stackInput: 0, + stackOutput: 1, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: true, + family: "scope", +}); +classify([SemanticOp.STORE_SLOT], { + operandKind: "slot", + stackInput: 1, + stackOutput: 0, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: true, + family: "scope", +}); +classify([SemanticOp.DECLARE_SLOT], { + operandKind: "slot", + stackInput: 0, + stackOutput: 0, + effect: "local", + mayThrow: false, + readsThis: false, + readsScope: true, + family: "scope", +}); +classify( + [ + SemanticOp.INC_SLOT, + SemanticOp.DEC_SLOT, + SemanticOp.POST_INC_SLOT, + SemanticOp.POST_DEC_SLOT, + ], + { + operandKind: "slot", + stackInput: 0, + stackOutput: 1, + effect: "local", + mayThrow: true, + readsThis: false, + readsScope: true, + family: "scope", + } +); +classify( + [ + SemanticOp.ADD_ASSIGN_SLOT, + SemanticOp.SUB_ASSIGN_SLOT, + SemanticOp.MUL_ASSIGN_SLOT, + ], + { + operandKind: "slot", + stackInput: 1, + stackOutput: 1, + effect: "local", + mayThrow: true, + readsThis: false, + readsScope: true, + family: "scope", + } +); // Ordinary value operations. classify( @@ -395,6 +548,11 @@ classify([SemanticOp.BIT_NOT, SemanticOp.NOT], { readsScope: false, family: "logical", }); +// ToBoolean is non-observable, while bitwise conversion can invoke +// Symbol.toPrimitive and can reject Symbols or mixed numeric domains. +classify([SemanticOp.BIT_NOT], { + mayThrow: true, +}); classify( [ SemanticOp.EQ, @@ -419,6 +577,10 @@ classify( family: "comparison", } ); +// Strict equality performs no conversion and cannot invoke user code. +classify([SemanticOp.SEQ, SemanticOp.SNEQ], { + mayThrow: false, +}); // Typed control exits. classify([SemanticOp.JMP, SemanticOp.BREAK, SemanticOp.CONTINUE], { @@ -753,6 +915,16 @@ classify( family: "scope", } ); +classify([SemanticOp.LOAD_GLOBAL_FAST], { + operandKind: "scope-name", + stackInput: 0, + stackOutput: 1, + effect: "scope", + mayThrow: true, + readsThis: false, + readsScope: true, + family: "scope", +}); classify( [ SemanticOp.PUSH_SCOPE, @@ -912,6 +1084,9 @@ classify( family: "conversion", } ); +classify([SemanticOp.TYPEOF, SemanticOp.VOID, SemanticOp.TO_BOOLEAN], { + mayThrow: false, +}); classify( [ @@ -964,6 +1139,503 @@ classify([SemanticOp.MUTATE], { precision: "conservative", }); +const INTRINSIC_COERCION_OPS = new Set([ + SemanticOp.NOT, + SemanticOp.TO_BOOLEAN, + SemanticOp.JMP_TRUE, + SemanticOp.JMP_FALSE, + SemanticOp.JMP_TRUE_KEEP, + SemanticOp.JMP_FALSE_KEEP, + SemanticOp.LOGICAL_AND, + SemanticOp.LOGICAL_OR, +]); + +const OBSERVABLE_COERCION_OPS = new Set([ + SemanticOp.ADD, + SemanticOp.SUB, + SemanticOp.MUL, + SemanticOp.DIV, + SemanticOp.MOD, + SemanticOp.POW, + SemanticOp.NEG, + SemanticOp.UNARY_PLUS, + SemanticOp.INC, + SemanticOp.DEC, + SemanticOp.BIT_AND, + SemanticOp.BIT_OR, + SemanticOp.BIT_XOR, + SemanticOp.BIT_NOT, + SemanticOp.SHL, + SemanticOp.SHR, + SemanticOp.USHR, + SemanticOp.EQ, + SemanticOp.NEQ, + SemanticOp.LT, + SemanticOp.LTE, + SemanticOp.GT, + SemanticOp.GTE, + SemanticOp.TO_NUMBER, + SemanticOp.TO_STRING, + SemanticOp.TO_OBJECT, + SemanticOp.TO_PROPERTY_KEY, + SemanticOp.TO_NUMERIC, + SemanticOp.TEMPLATE_LITERAL, + SemanticOp.INC_REG, + SemanticOp.DEC_REG, + SemanticOp.POST_INC_REG, + SemanticOp.POST_DEC_REG, + SemanticOp.ADD_ASSIGN_REG, + SemanticOp.SUB_ASSIGN_REG, + SemanticOp.MUL_ASSIGN_REG, + SemanticOp.DIV_ASSIGN_REG, + SemanticOp.MOD_ASSIGN_REG, + SemanticOp.INC_SLOT, + SemanticOp.DEC_SLOT, + SemanticOp.POST_INC_SLOT, + SemanticOp.POST_DEC_SLOT, + SemanticOp.ADD_ASSIGN_SLOT, + SemanticOp.SUB_ASSIGN_SLOT, + SemanticOp.MUL_ASSIGN_SLOT, + SemanticOp.INC_SCOPED, + SemanticOp.DEC_SCOPED, + SemanticOp.POST_INC_SCOPED, + SemanticOp.POST_DEC_SCOPED, + SemanticOp.ADD_ASSIGN_SCOPED, + SemanticOp.SUB_ASSIGN_SCOPED, + SemanticOp.MUL_ASSIGN_SCOPED, + SemanticOp.DIV_ASSIGN_SCOPED, + SemanticOp.MOD_ASSIGN_SCOPED, + SemanticOp.POW_ASSIGN_SCOPED, + SemanticOp.BIT_AND_ASSIGN_SCOPED, + SemanticOp.BIT_OR_ASSIGN_SCOPED, + SemanticOp.BIT_XOR_ASSIGN_SCOPED, + SemanticOp.SHL_ASSIGN_SCOPED, + SemanticOp.SHR_ASSIGN_SCOPED, + SemanticOp.USHR_ASSIGN_SCOPED, + SemanticOp.ASSIGN_OP, +]); + +const INVOKE_OPS = new Set([ + SemanticOp.CALL, + SemanticOp.CALL_METHOD, + SemanticOp.CALL_OPTIONAL, + SemanticOp.CALL_METHOD_OPTIONAL, + SemanticOp.CALL_TAGGED_TEMPLATE, + SemanticOp.CALL_SUPER_METHOD, + SemanticOp.CALL_0, + SemanticOp.CALL_1, + SemanticOp.CALL_2, + SemanticOp.CALL_3, + SemanticOp.TAGGED_TEMPLATE, +]); + +const CONSTRUCT_OPS = new Set([ + SemanticOp.CALL_NEW, + SemanticOp.SUPER_CALL, +]); + +const HOST_PROTOCOL_OPS = new Set([ + SemanticOp.GET_PROP_STATIC, + SemanticOp.SET_PROP_STATIC, + SemanticOp.GET_PROP_DYNAMIC, + SemanticOp.SET_PROP_DYNAMIC, + SemanticOp.DELETE_PROP_STATIC, + SemanticOp.DELETE_PROP_DYNAMIC, + SemanticOp.OPT_CHAIN_GET, + SemanticOp.OPT_CHAIN_DYNAMIC, + SemanticOp.GET_SUPER_PROP, + SemanticOp.SET_SUPER_PROP, + SemanticOp.IN_OP, + SemanticOp.INSTANCEOF, + SemanticOp.SPREAD_ARRAY, + SemanticOp.SPREAD_OBJECT, + SemanticOp.COPY_DATA_PROPERTIES, + SemanticOp.SET_PROTO, + SemanticOp.FREEZE_OBJECT, + SemanticOp.SEAL_OBJECT, + SemanticOp.DEFINE_PROPERTY_DESC, + SemanticOp.GET_ITERATOR, + SemanticOp.ITER_NEXT, + SemanticOp.ITER_CLOSE, + SemanticOp.FORIN_INIT, + SemanticOp.GET_ASYNC_ITERATOR, + SemanticOp.ASYNC_ITER_NEXT, + SemanticOp.ASYNC_ITER_CLOSE, + SemanticOp.FOR_AWAIT_NEXT, +]); + +const YIELD_OPS = new Set([ + SemanticOp.YIELD, + SemanticOp.YIELD_DELEGATE, + SemanticOp.ASYNC_GENERATOR_YIELD, +]); + +const AWAIT_OPS = new Set([ + SemanticOp.AWAIT, + SemanticOp.FOR_AWAIT_NEXT, +]); + +const RUNTIME_SUSPENSION_OPS = new Set([ + SemanticOp.SUSPEND, + SemanticOp.RESUME, + SemanticOp.GENERATOR_RESUME, + SemanticOp.GENERATOR_RETURN, + SemanticOp.GENERATOR_THROW, + SemanticOp.ASYNC_GENERATOR_NEXT, + SemanticOp.ASYNC_GENERATOR_RETURN, + SemanticOp.ASYNC_GENERATOR_THROW, +]); + +const FRAME_READ_OPS = new Set([ + SemanticOp.LOAD_REG, + SemanticOp.LOAD_ARG, + SemanticOp.LOAD_ARG_OR_DEFAULT, + SemanticOp.GET_ARG_COUNT, + SemanticOp.LOAD_SLOT, +]); + +const FRAME_WRITE_OPS = new Set([ + SemanticOp.STORE_REG, + SemanticOp.STORE_ARG, + SemanticOp.STORE_SLOT, + SemanticOp.DECLARE_SLOT, +]); + +const FRAME_READ_WRITE_OPS = new Set([ + SemanticOp.INC_REG, + SemanticOp.DEC_REG, + SemanticOp.POST_INC_REG, + SemanticOp.POST_DEC_REG, + SemanticOp.ADD_ASSIGN_REG, + SemanticOp.SUB_ASSIGN_REG, + SemanticOp.MUL_ASSIGN_REG, + SemanticOp.DIV_ASSIGN_REG, + SemanticOp.MOD_ASSIGN_REG, + SemanticOp.INC_SLOT, + SemanticOp.DEC_SLOT, + SemanticOp.POST_INC_SLOT, + SemanticOp.POST_DEC_SLOT, + SemanticOp.ADD_ASSIGN_SLOT, + SemanticOp.SUB_ASSIGN_SLOT, + SemanticOp.MUL_ASSIGN_SLOT, +]); + +const GLOBAL_READ_OPS = new Set([ + SemanticOp.LOAD_GLOBAL, + SemanticOp.LOAD_GLOBAL_FAST, + SemanticOp.TYPEOF_GLOBAL, + SemanticOp.PUSH_GLOBAL_THIS, +]); + +const GLOBAL_WRITE_OPS = new Set([ + SemanticOp.STORE_GLOBAL, +]); + +const SCOPE_READ_OPS = new Set([ + SemanticOp.LOAD_SCOPED, + SemanticOp.TDZ_CHECK, + SemanticOp.PUSH_CLOSURE_VAR, +]); + +const SCOPE_WRITE_OPS = new Set([ + SemanticOp.STORE_SCOPED, + SemanticOp.DECLARE_VAR, + SemanticOp.DECLARE_LET, + SemanticOp.DECLARE_CONST, + SemanticOp.TDZ_MARK, + SemanticOp.DELETE_SCOPED, + SemanticOp.STORE_CLOSURE_VAR, +]); + +const SCOPE_READ_WRITE_OPS = new Set([ + SemanticOp.PUSH_SCOPE, + SemanticOp.POP_SCOPE, + SemanticOp.PUSH_WITH_SCOPE, + SemanticOp.PUSH_BLOCK_SCOPE, + SemanticOp.PUSH_CATCH_SCOPE, + SemanticOp.PUSH_INDEXED_SCOPE, + SemanticOp.POP_INDEXED_SCOPE, + SemanticOp.CATCH_BIND, + SemanticOp.CATCH_BIND_PATTERN, + SemanticOp.INC_SCOPED, + SemanticOp.DEC_SCOPED, + SemanticOp.POST_INC_SCOPED, + SemanticOp.POST_DEC_SCOPED, + SemanticOp.ADD_ASSIGN_SCOPED, + SemanticOp.SUB_ASSIGN_SCOPED, + SemanticOp.MUL_ASSIGN_SCOPED, + SemanticOp.DIV_ASSIGN_SCOPED, + SemanticOp.MOD_ASSIGN_SCOPED, + SemanticOp.POW_ASSIGN_SCOPED, + SemanticOp.BIT_AND_ASSIGN_SCOPED, + SemanticOp.BIT_OR_ASSIGN_SCOPED, + SemanticOp.BIT_XOR_ASSIGN_SCOPED, + SemanticOp.SHL_ASSIGN_SCOPED, + SemanticOp.SHR_ASSIGN_SCOPED, + SemanticOp.USHR_ASSIGN_SCOPED, + SemanticOp.AND_ASSIGN_SCOPED, + SemanticOp.OR_ASSIGN_SCOPED, + SemanticOp.NULLISH_ASSIGN_SCOPED, +]); + +const OBJECT_READ_OPS = new Set([ + SemanticOp.GET_PROP_STATIC, + SemanticOp.GET_PROP_DYNAMIC, + SemanticOp.OPT_CHAIN_GET, + SemanticOp.OPT_CHAIN_DYNAMIC, + SemanticOp.GET_SUPER_PROP, + SemanticOp.GET_PRIVATE_FIELD, + SemanticOp.HAS_PRIVATE_FIELD, + SemanticOp.IN_OP, + SemanticOp.INSTANCEOF, + SemanticOp.GET_ITERATOR, + SemanticOp.ITER_NEXT, + SemanticOp.ITER_DONE, + SemanticOp.ITER_VALUE, + SemanticOp.ITER_RESULT_UNWRAP, + SemanticOp.FORIN_INIT, + SemanticOp.FORIN_NEXT, + SemanticOp.FORIN_DONE, + SemanticOp.GET_ASYNC_ITERATOR, + SemanticOp.ASYNC_ITER_NEXT, + SemanticOp.ASYNC_ITER_DONE, + SemanticOp.ASYNC_ITER_VALUE, +]); + +const OBJECT_WRITE_OPS = new Set([ + SemanticOp.SET_PROP_STATIC, + SemanticOp.SET_PROP_DYNAMIC, + SemanticOp.DELETE_PROP_STATIC, + SemanticOp.DELETE_PROP_DYNAMIC, + SemanticOp.SET_SUPER_PROP, + SemanticOp.SET_PRIVATE_FIELD, + SemanticOp.DEFINE_OWN_PROPERTY, + SemanticOp.ARRAY_PUSH, + SemanticOp.ARRAY_HOLE, + SemanticOp.SET_PROTO, + SemanticOp.FREEZE_OBJECT, + SemanticOp.SEAL_OBJECT, + SemanticOp.DEFINE_PROPERTY_DESC, +]); + +const OBJECT_READ_WRITE_OPS = new Set([ + SemanticOp.SPREAD_ARRAY, + SemanticOp.SPREAD_OBJECT, + SemanticOp.COPY_DATA_PROPERTIES, + SemanticOp.ITER_CLOSE, + SemanticOp.ASYNC_ITER_CLOSE, +]); + +const CLOSURE_ALLOCATION_OPS = new Set([ + SemanticOp.NEW_CLOSURE, + SemanticOp.NEW_FUNCTION, + SemanticOp.NEW_ARROW, + SemanticOp.NEW_ASYNC, + SemanticOp.NEW_GENERATOR, + SemanticOp.NEW_ASYNC_GENERATOR, +]); + +const OBJECT_ALLOCATION_OPS = new Set([ + SemanticOp.NEW_OBJECT, + SemanticOp.NEW_CLASS, + SemanticOp.NEW_DERIVED_CLASS, + SemanticOp.CREATE_TEMPLATE_OBJECT, + SemanticOp.CREATE_RAW_STRINGS, + SemanticOp.TO_OBJECT, +]); + +const ARRAY_ALLOCATION_OPS = new Set([ + SemanticOp.NEW_ARRAY, + SemanticOp.NEW_ARRAY_WITH_SIZE, + SemanticOp.CREATE_REST_ARGS, +]); + +const ARGUMENT_ALLOCATION_OPS = new Set([ + SemanticOp.PUSH_ARGUMENTS, + SemanticOp.CREATE_UNMAPPED_ARGS, + SemanticOp.CREATE_MAPPED_ARGS, +]); + +const ITERATOR_ALLOCATION_OPS = new Set([ + SemanticOp.CREATE_GENERATOR, + SemanticOp.CREATE_ASYNC_FROM_SYNC_ITER, +]); + +function accessFor( + op: SemanticOpValue, + reads: ReadonlySet, + writes: ReadonlySet, + readWrites: ReadonlySet +): SemanticAccess { + if (readWrites.has(op)) return "read-write"; + if (reads.has(op)) return "read"; + if (writes.has(op)) return "write"; + return "none"; +} + +function completionFor(control: SemanticControl): SemanticCompletion { + return control === "fallthrough" ? "normal" : control; +} + +function annotateSignature(signature: SemanticSignature): SemanticSignature { + if (signature.precision === "conservative") { + return { + ...signature, + purity: "observable", + completion: completionFor(signature.control), + throwBehavior: signature.mayThrow ? "may-throw" : "never", + coercion: "unknown", + callKind: "unknown", + suspension: "unknown", + frameAccess: "unknown", + scopeAccess: "unknown", + objectAccess: "unknown", + globalAccess: "unknown", + allocation: "unknown", + }; + } + + const op = signature.op; + const coercion: SemanticCoercion = OBSERVABLE_COERCION_OPS.has(op) + ? "observable" + : INTRINSIC_COERCION_OPS.has(op) + ? "intrinsic" + : "none"; + let callKind: SemanticCallKind = INVOKE_OPS.has(op) + ? "invoke" + : CONSTRUCT_OPS.has(op) + ? "construct" + : op === SemanticOp.DIRECT_EVAL + ? "direct-eval" + : op === SemanticOp.DYNAMIC_IMPORT + ? "dynamic-import" + : HOST_PROTOCOL_OPS.has(op) + ? "host-protocol" + : coercion === "observable" + ? "coercion-hook" + : "none"; + const suspension: SemanticSuspensionKind = YIELD_OPS.has(op) + ? "yield" + : AWAIT_OPS.has(op) + ? "await" + : RUNTIME_SUSPENSION_OPS.has(op) + ? "runtime" + : "none"; + const frameAccess = accessFor( + op, + FRAME_READ_OPS, + FRAME_WRITE_OPS, + FRAME_READ_WRITE_OPS + ); + let scopeAccess = GLOBAL_READ_OPS.has(op) || GLOBAL_WRITE_OPS.has(op) + ? "none" + : accessFor(op, SCOPE_READ_OPS, SCOPE_WRITE_OPS, SCOPE_READ_WRITE_OPS); + let globalAccess = accessFor( + op, + GLOBAL_READ_OPS, + GLOBAL_WRITE_OPS, + new Set() + ); + let objectAccess = accessFor( + op, + OBJECT_READ_OPS, + OBJECT_WRITE_OPS, + OBJECT_READ_WRITE_OPS + ); + let allocation: SemanticAllocation = CLOSURE_ALLOCATION_OPS.has(op) + ? "closure" + : OBJECT_ALLOCATION_OPS.has(op) + ? "object" + : ARRAY_ALLOCATION_OPS.has(op) + ? "array" + : ARGUMENT_ALLOCATION_OPS.has(op) + ? "arguments" + : ITERATOR_ALLOCATION_OPS.has(op) + ? "iterator" + : op === SemanticOp.DYNAMIC_IMPORT + ? "promise" + : CONSTRUCT_OPS.has(op) + ? "unknown" + : "none"; + + // Calls, host protocols, and observable conversion hooks may execute + // arbitrary user code. Their mutation footprint is therefore unknown even + // when the operation also has a more specific direct access. + if ( + callKind !== "none" && + callKind !== "construct" && + callKind !== "dynamic-import" + ) { + if (scopeAccess === "none") scopeAccess = "unknown"; + if (globalAccess === "none") globalAccess = "unknown"; + if (objectAccess === "none") objectAccess = "unknown"; + } + if (callKind === "construct" || callKind === "dynamic-import") { + scopeAccess = "unknown"; + globalAccess = "unknown"; + objectAccess = "unknown"; + } + if (signature.effect === "scope" && scopeAccess === "none" && globalAccess === "none") { + scopeAccess = "unknown"; + } + if ( + signature.effect === "object" && + objectAccess === "none" && + allocation === "none" + ) { + objectAccess = "unknown"; + } + if ( + signature.effect === "async" && + suspension === "none" && + callKind === "none" + ) { + callKind = "unknown"; + } + + const completion = completionFor(signature.control); + const throwBehavior: SemanticThrowBehavior = !signature.mayThrow + ? "never" + : signature.control === "throw" && + signature.op !== SemanticOp.THROW_IF_NOT_OBJECT + ? "always-throws" + : "may-throw"; + const observable = + throwBehavior !== "never" || + coercion === "observable" || + callKind !== "none" || + suspension !== "none" || + scopeAccess !== "none" || + objectAccess !== "none" || + globalAccess !== "none" || + allocation !== "none" || + completion === "call" || + completion === "return" || + completion === "throw" || + completion === "yield" || + completion === "await"; + const purity: SemanticPurity = observable + ? "observable" + : frameAccess === "none" && signature.effect !== "local" + ? "pure" + : "frame-local"; + + return { + ...signature, + purity, + completion, + throwBehavior, + coercion, + callKind, + suspension, + frameAccess, + scopeAccess, + objectAccess, + globalAccess, + allocation, + }; +} + function conservativeSignature(op: SemanticOpValue): SemanticSignature { return { op, @@ -971,8 +1643,19 @@ function conservativeSignature(op: SemanticOpValue): SemanticSignature { stackInput: "dynamic", stackOutput: "dynamic", effect: "object", + purity: "observable", control: "fallthrough", + completion: "normal", mayThrow: true, + throwBehavior: "may-throw", + coercion: "unknown", + callKind: "unknown", + suspension: "unknown", + frameAccess: "unknown", + scopeAccess: "unknown", + objectAccess: "unknown", + globalAccess: "unknown", + allocation: "unknown", readsThis: true, readsScope: true, syntheticDimensions: 1, @@ -990,11 +1673,11 @@ function buildSignatureTable(): Readonly< >; for (const op of ALL_SEMANTIC_OPS) { - const signature = { + const signature = annotateSignature({ ...conservativeSignature(op), ...overrides.get(op), op, - }; + }); table[op] = Object.freeze(signature); } diff --git a/packages/ruam/test/isogloss/effect-regions.test.ts b/packages/ruam/test/isogloss/effect-regions.test.ts new file mode 100644 index 0000000..1410689 --- /dev/null +++ b/packages/ruam/test/isogloss/effect-regions.test.ts @@ -0,0 +1,311 @@ +import { describe, expect, it } from "bun:test"; +import { parse } from "@babel/parser"; +import type { NodePath } from "@babel/traverse"; +import type * as t from "@babel/types"; +import { traverse } from "../../src/babel-compat.js"; +import { buildCanonicalCfg } from "../../src/compiler/cfg.js"; +import { + compileSemanticFunction, + resetUnitCounter, +} from "../../src/compiler/index.js"; +import { + createSemanticInstruction, + type SemanticUnit, +} from "../../src/compiler/ir.js"; +import { Op } from "../../src/compiler/opcodes.js"; +import { + buildEffectRegionGraph, + validateEffectRegionGraph, + type EffectRegionGraph, +} from "../../src/compiler/regions.js"; + +function getFunctionPath( + source: string, + name: string +): NodePath { + const ast = parse(source, { + sourceType: "script", + sourceFilename: "effect-region-fixture.js", + }); + let result: NodePath | undefined; + traverse(ast, { + Function(path) { + const id = "id" in path.node ? path.node.id : null; + if (id?.type === "Identifier" && id.name === name) { + result = path as NodePath; + path.stop(); + } + }, + }); + if (!result) throw new Error(`Missing function ${name}`); + return result; +} + +function graphSnapshot(graph: EffectRegionGraph): unknown { + return { + unitId: graph.unitId, + entryRegionId: graph.entryRegionId, + regions: graph.regions, + ownership: [...graph.nodeOwnership].sort( + ([left], [right]) => left - right + ), + }; +} + +describe("effect-delimited region construction", () => { + it("fuses a safe straight-line frame-local run and computes its boundary", () => { + const cfg = buildCanonicalCfg({ + instructions: [ + { opcode: Op.PUSH_TRUE, operand: 0 }, + { opcode: Op.NOT, operand: 0 }, + { opcode: Op.STORE_REG, operand: 2 }, + { opcode: Op.LOAD_REG, operand: 2 }, + { opcode: Op.NOT, operand: 0 }, + { opcode: Op.RETURN, operand: 0 }, + ], + originIds: [0, 0, 0, 0, 0, 0], + }); + + const graph = buildEffectRegionGraph(cfg); + + expect(graph.entryRegionId).toBe("r_0"); + expect(graph.regions).toHaveLength(2); + expect(graph.regions[0]!.nodeIds).toEqual([0, 1, 2, 3, 4]); + expect(graph.regions[0]!.inputs).toEqual({ stack: 0, state: [] }); + expect(graph.regions[0]!.outputs).toEqual({ + stack: 1, + state: [{ domain: "register", key: 2 }], + }); + expect(graph.regions[0]!.effects.purity).toBe("frame-local"); + expect(graph.regions[0]!.effects.frameAccess).toBe("read-write"); + expect(graph.regions[0]!.exits).toEqual([ + { + kind: "fallthrough", + sourceNodeId: 4, + targetNodeId: 5, + targetRegionId: "r_5", + }, + ]); + expect(graph.regions[1]!.inputs.stack).toBe(1); + expect(graph.regions[1]!.outputs.stack).toBe(0); + expect(graph.regions[1]!.exits).toEqual([ + { kind: "return", sourceNodeId: 5 }, + ]); + for (let nodeId = 0; nodeId <= 4; nodeId++) { + expect(graph.nodeOwnership.get(nodeId)).toBe("r_0"); + } + expect(graph.nodeOwnership.get(5)).toBe("r_5"); + expect(() => validateEffectRegionGraph(cfg, graph)).not.toThrow(); + }); + + it("isolates observable property access, unknown arity, and exception exits", () => { + const cfg = buildCanonicalCfg({ + instructions: [ + { opcode: Op.PUSH_CONST, operand: 0 }, + { opcode: Op.GET_PROP_STATIC, operand: 1 }, + { opcode: Op.RETURN, operand: 0 }, + ], + originIds: [0, 0, 0], + }); + + const graph = buildEffectRegionGraph(cfg); + const propertyRegion = graph.regions[1]!; + + expect(graph.regions.map((region) => region.nodeIds)).toEqual([ + [0], + [1], + [2], + ]); + expect(propertyRegion.inputs.stack).toBe("dynamic"); + expect(propertyRegion.outputs.stack).toBe("dynamic"); + expect(propertyRegion.effects.objectAccess).toBe("read"); + expect(propertyRegion.effects.callKinds).toEqual(["host-protocol"]); + expect(propertyRegion.effects.throwBehavior).toBe("may-throw"); + expect(propertyRegion.exits).toEqual([ + { + kind: "fallthrough", + sourceNodeId: 1, + targetNodeId: 2, + targetRegionId: "r_2", + }, + { kind: "throw", sourceNodeId: 1 }, + ]); + }); + + it("splits branches and control joins even when adjacent nodes are pure", () => { + const cfg = buildCanonicalCfg({ + instructions: [ + { opcode: Op.LOAD_ARG, operand: 0 }, + { opcode: Op.JMP_FALSE, operand: 4 }, + { opcode: Op.PUSH_TRUE, operand: 0 }, + { opcode: Op.JMP, operand: 5 }, + { opcode: Op.PUSH_FALSE, operand: 0 }, + { opcode: Op.NOT, operand: 0 }, + { opcode: Op.RETURN, operand: 0 }, + ], + originIds: [0, 0, 0, 0, 0, 0, 0], + }); + + const graph = buildEffectRegionGraph(cfg); + + expect(graph.regions.map((region) => region.nodeIds)).toEqual([ + [0], + [1], + [2], + [3], + [4], + [5], + [6], + ]); + expect(graph.regions[1]!.exits).toEqual([ + { + kind: "branch-false", + sourceNodeId: 1, + targetNodeId: 4, + targetRegionId: "r_4", + }, + { + kind: "branch-true", + sourceNodeId: 1, + targetNodeId: 2, + targetRegionId: "r_2", + }, + ]); + expect(graph.nodeOwnership.get(4)).not.toBe( + graph.nodeOwnership.get(5) + ); + }); + + it("preserves call and suspension resumptions as typed region exits", () => { + const cfg = buildCanonicalCfg({ + instructions: [ + { opcode: Op.CALL, operand: 0 }, + { opcode: Op.AWAIT, operand: 0 }, + { opcode: Op.RETURN, operand: 0 }, + ], + originIds: [0, 0, 0], + }); + + const graph = buildEffectRegionGraph(cfg); + + expect(graph.regions.map((region) => region.nodeIds)).toEqual([ + [0], + [1], + [2], + ]); + expect(graph.regions[0]!.exits).toEqual([ + { + kind: "call", + sourceNodeId: 0, + resumeNodeId: 1, + resumeRegionId: "r_1", + }, + { kind: "throw", sourceNodeId: 0 }, + ]); + expect(graph.regions[1]!.exits).toEqual([ + { + kind: "await", + sourceNodeId: 1, + resumeNodeId: 2, + resumeRegionId: "r_2", + }, + { kind: "throw", sourceNodeId: 1 }, + ]); + expect(graph.regions[1]!.effects.suspensions).toEqual(["await"]); + }); + + it("keeps structured exception handlers outside the throwing region", () => { + const packedTryTargets = (5 << 16) | 0xffff; + const cfg = buildCanonicalCfg({ + instructions: [ + { opcode: Op.TRY_PUSH, operand: packedTryTargets }, + { opcode: Op.PUSH_CONST, operand: 0 }, + { opcode: Op.GET_PROP_STATIC, operand: 1 }, + { opcode: Op.TRY_POP, operand: 0 }, + { opcode: Op.JMP, operand: 6 }, + { opcode: Op.CATCH_BIND, operand: -1 }, + { opcode: Op.RETURN_VOID, operand: 0 }, + ], + originIds: [0, 0, 0, 0, 0, 0, 0], + }); + + const graph = buildEffectRegionGraph(cfg); + const propertyRegion = graph.regions.find( + (region) => region.entryNodeId === 2 + )!; + + expect(propertyRegion.exits).toEqual([ + { + kind: "fallthrough", + sourceNodeId: 2, + targetNodeId: 3, + targetRegionId: "r_3", + }, + { + kind: "exception", + sourceNodeId: 2, + targetNodeId: 5, + targetRegionId: "r_5", + }, + ]); + expect(graph.nodeOwnership.get(2)).not.toBe( + graph.nodeOwnership.get(5) + ); + }); + + it("is deterministic and covers every compiler-produced semantic node once", () => { + const source = ` + function flip(value) { + let result = !value; + result = !result; + return !result; + } + `; + resetUnitCounter(321); + const group = compileSemanticFunction( + getFunctionPath(source, "flip"), + "effect-region-root" + ); + const unit = group.units.find( + (candidate) => candidate.id === group.entryUnitId + ) as SemanticUnit; + + const first = buildEffectRegionGraph(unit); + const second = buildEffectRegionGraph(unit); + + expect(first.unitId).toBe(unit.id); + expect(graphSnapshot(first)).toEqual(graphSnapshot(second)); + expect(first.nodeOwnership.size).toBe(unit.nodes.length); + expect( + new Set(first.regions.flatMap((region) => region.nodeIds)).size + ).toBe(unit.nodes.length); + expect(() => validateEffectRegionGraph(unit, first)).not.toThrow(); + }); + + it("fails closed on malformed ownership inputs and targets", () => { + const node = createSemanticInstruction({ + id: 0, + op: Op.PUSH_TRUE, + operand: 0, + originId: 0, + }); + + expect(() => + buildEffectRegionGraph({ + nodes: [node], + entryNode: 0, + exits: new Map([ + [0, [{ kind: "fallthrough" as const, target: 99 }]], + ]), + }) + ).toThrow("RUAM_INVALID_SEMANTIC_TARGET"); + + expect(() => + buildEffectRegionGraph({ + nodes: [node, node], + entryNode: 0, + exits: new Map([[0, [{ kind: "return" as const }]]]), + }) + ).toThrow("RUAM_DUPLICATE_SEMANTIC_NODE_ID"); + }); +}); diff --git a/packages/ruam/test/isogloss/semantic-signatures.test.ts b/packages/ruam/test/isogloss/semantic-signatures.test.ts index 55013d5..54090c1 100644 --- a/packages/ruam/test/isogloss/semantic-signatures.test.ts +++ b/packages/ruam/test/isogloss/semantic-signatures.test.ts @@ -79,6 +79,33 @@ describe("semantic signature catalog", () => { expect(signature.syntheticDimensions, semanticOpName(op)).toBeGreaterThan( 0 ); + expect(signature.mayThrow, semanticOpName(op)).toBe( + signature.throwBehavior !== "never" + ); + expect(signature.completion, semanticOpName(op)).toBe( + signature.control === "fallthrough" + ? "normal" + : signature.control + ); + if (signature.precision === "conservative") { + expect(signature.purity, semanticOpName(op)).toBe("observable"); + expect(signature.coercion, semanticOpName(op)).toBe("unknown"); + expect(signature.callKind, semanticOpName(op)).toBe("unknown"); + expect(signature.allocation, semanticOpName(op)).toBe("unknown"); + } + if (signature.purity === "pure") { + expect(signature.throwBehavior, semanticOpName(op)).toBe("never"); + expect(signature.callKind, semanticOpName(op)).toBe("none"); + expect(signature.suspension, semanticOpName(op)).toBe("none"); + expect(signature.scopeAccess, semanticOpName(op)).toBe("none"); + expect(signature.objectAccess, semanticOpName(op)).toBe("none"); + expect(signature.globalAccess, semanticOpName(op)).toBe("none"); + expect(signature.allocation, semanticOpName(op)).toBe("none"); + expect( + ["none", "intrinsic"].includes(signature.coercion), + semanticOpName(op) + ).toBe(true); + } } }); @@ -98,6 +125,62 @@ describe("semantic signature catalog", () => { expect(getSemanticSignature(SemanticOp.AWAIT).effect).toBe("async"); }); + it("exposes conservative observability dimensions used by region formation", () => { + const literal = getSemanticSignature(SemanticOp.PUSH_TRUE); + expect(literal.purity).toBe("pure"); + expect(literal.throwBehavior).toBe("never"); + expect(literal.coercion).toBe("none"); + + const logicalNot = getSemanticSignature(SemanticOp.NOT); + expect(logicalNot.purity).toBe("pure"); + expect(logicalNot.coercion).toBe("intrinsic"); + expect(logicalNot.throwBehavior).toBe("never"); + + const addition = getSemanticSignature(SemanticOp.ADD); + expect(addition.purity).toBe("observable"); + expect(addition.coercion).toBe("observable"); + expect(addition.callKind).toBe("coercion-hook"); + expect(addition.throwBehavior).toBe("may-throw"); + + const strictEquality = getSemanticSignature(SemanticOp.SEQ); + expect(strictEquality.purity).toBe("pure"); + expect(strictEquality.coercion).toBe("none"); + expect(strictEquality.throwBehavior).toBe("never"); + + const propertyRead = getSemanticSignature( + SemanticOp.GET_PROP_STATIC + ); + expect(propertyRead.objectAccess).toBe("read"); + expect(propertyRead.callKind).toBe("host-protocol"); + expect(propertyRead.purity).toBe("observable"); + + const globalWrite = getSemanticSignature(SemanticOp.STORE_GLOBAL); + expect(globalWrite.globalAccess).toBe("write"); + expect(globalWrite.scopeAccess).toBe("none"); + + const call = getSemanticSignature(SemanticOp.CALL); + expect(call.callKind).toBe("invoke"); + expect(call.scopeAccess).toBe("unknown"); + expect(call.objectAccess).toBe("unknown"); + expect(call.globalAccess).toBe("unknown"); + expect(call.completion).toBe("call"); + + const awaitSignature = getSemanticSignature(SemanticOp.AWAIT); + expect(awaitSignature.suspension).toBe("await"); + expect(awaitSignature.completion).toBe("await"); + + const allocation = getSemanticSignature(SemanticOp.NEW_OBJECT); + expect(allocation.allocation).toBe("object"); + expect(allocation.purity).toBe("observable"); + + expect(getSemanticSignature(SemanticOp.LOAD_SLOT).frameAccess).toBe( + "read" + ); + expect(getSemanticSignature(SemanticOp.STORE_SLOT).frameAccess).toBe( + "write" + ); + }); + it("resolves operand-dependent stack arity without executing semantics", () => { const call = getSemanticSignature(SemanticOp.CALL); const methodCall = getSemanticSignature(SemanticOp.CALL_METHOD); @@ -115,6 +198,9 @@ describe("semantic signature catalog", () => { expect(mutation.mayThrow).toBe(true); expect(mutation.readsScope).toBe(true); expect(mutation.readsThis).toBe(true); + expect(mutation.purity).toBe("observable"); + expect(mutation.coercion).toBe("unknown"); + expect(mutation.frameAccess).toBe("unknown"); }); }); From 589e9951dc4877988cbb0068d2904c97ca89580f Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:00:33 -1000 Subject: [PATCH 12/54] feat: add poly-ontology BPRF reference kernel --- packages/ruam/src/isogloss/bprf/generate.ts | 588 ++++++++++++++++++ packages/ruam/src/isogloss/bprf/index.ts | 26 + packages/ruam/src/isogloss/bprf/random.ts | 66 ++ .../src/isogloss/bprf/testing-reference.ts | 176 ++++++ packages/ruam/src/isogloss/bprf/types.ts | 145 +++++ packages/ruam/src/isogloss/bprf/validate.ts | 312 ++++++++++ .../ruam/test/isogloss/bprf-kernel.test.ts | 238 +++++++ 7 files changed, 1551 insertions(+) create mode 100644 packages/ruam/src/isogloss/bprf/generate.ts create mode 100644 packages/ruam/src/isogloss/bprf/index.ts create mode 100644 packages/ruam/src/isogloss/bprf/random.ts create mode 100644 packages/ruam/src/isogloss/bprf/testing-reference.ts create mode 100644 packages/ruam/src/isogloss/bprf/types.ts create mode 100644 packages/ruam/src/isogloss/bprf/validate.ts create mode 100644 packages/ruam/test/isogloss/bprf-kernel.test.ts diff --git a/packages/ruam/src/isogloss/bprf/generate.ts b/packages/ruam/src/isogloss/bprf/generate.ts new file mode 100644 index 0000000..ba5155c --- /dev/null +++ b/packages/ruam/src/isogloss/bprf/generate.ts @@ -0,0 +1,588 @@ +/** + * Deterministic generator for the bounded pure BPRF reference artifact. + * + * Logical formula tags are consumed here and never copied into the artifact. + * Each realization uses contextual physical wires and longitudinal fragments. + * + * @module isogloss/bprf/generate + */ + +import { BprfRandom, mix32, opaqueId } from "./random.js"; +import type { + BprfArtifact, + BprfFactor, + BprfFragment, + BprfGenerationOptions, + BprfPiece, + BprfPort, + BprfRealization, + BprfTransition, + BprfWireBasis, + PureRegionContract, + PureRegionFormula, + PureValueRef, + PureValueType, +} from "./types.js"; +import { validateBprfArtifact } from "./validate.js"; + +interface AlgebraicFactor { + ref: PureValueRef; + offset: number; +} + +interface AlgebraicTerm { + coefficient: number; + factors: AlgebraicFactor[]; +} + +interface ContractFacts { + types: PureValueType[]; + depths: number[]; +} + +/** Generate K semantically equivalent, structurally diverse realizations. */ +export function generateBprfArtifact( + contract: PureRegionContract, + options: BprfGenerationOptions +): BprfArtifact { + const facts = validateContract(contract); + const realizationCount = options.realizationCount ?? 3; + const fragmentCount = options.fragmentCount ?? 3; + if (!Number.isSafeInteger(realizationCount) || realizationCount < 2) { + throw new Error("RUAM_BPRF_REALIZATION_COUNT_MIN_2"); + } + if (!Number.isSafeInteger(fragmentCount) || fragmentCount < 2) { + throw new Error("RUAM_BPRF_FRAGMENT_COUNT_MIN_2"); + } + + const rootRandom = new BprfRandom(options.seed); + const realizations = Array.from({ length: realizationCount }, (_, index) => + generateRealization( + contract, + facts, + index, + fragmentCount, + mix32(options.seed ^ Math.imul(index + 1, 0x9e3779b9)) + ) + ); + const artifact: BprfArtifact = Object.freeze({ + format: "ruam-bprf-pure-1", + id: opaqueId("r", rootRandom), + selectionSalt: rootRandom.nextUint32(), + realizations: Object.freeze(realizations), + }); + validateBprfArtifact(artifact); + return artifact; +} + +function generateRealization( + contract: PureRegionContract, + facts: ContractFacts, + realizationIndex: number, + fragmentCount: number, + seed: number +): BprfRealization { + const random = new BprfRandom(seed); + const familyCode = (realizationIndex & 1) as 0 | 1; + const logicalValueCount = facts.types.length; + const residualCount = familyCode === 1 ? contract.steps.length : 0; + const contextualWireCount = logicalValueCount + residualCount; + const frameSize = contextualWireCount + contract.outputs.length; + const slots = random.shuffle( + Array.from({ length: frameSize }, (_, slot) => slot) + ); + const logicalSlots = slots.slice(0, logicalValueCount); + const residualSlots = slots.slice( + logicalValueCount, + logicalValueCount + residualCount + ); + const exitSlots = slots.slice(contextualWireCount); + const contextualSlots = [...logicalSlots, ...residualSlots]; + const contextualBases = Array.from({ length: contextualWireCount }, () => + randomBasis(random) + ); + const exitBases = contract.outputs.map((outputRef) => + distinctBasis(random, contextualBases[outputRef]!) + ); + + const phaseByStep = contract.steps.map((_, stepIndex) => + familyCode === 0 + ? facts.depths[contract.inputs.length + stepIndex]! - 1 + : stepIndex * 2 + 1 + ); + const computationPhaseCount = + phaseByStep.length === 0 ? 0 : Math.max(...phaseByStep) + 1; + const boundaryPhase = computationPhaseCount; + const transitionWrites = Array.from( + { length: boundaryPhase + 1 }, + () => [] as number[] + ); + for (let stepIndex = 0; stepIndex < contract.steps.length; stepIndex++) { + const ref = contract.inputs.length + stepIndex; + if (familyCode === 1) { + transitionWrites[stepIndex * 2]!.push(residualSlots[stepIndex]!); + } + transitionWrites[phaseByStep[stepIndex]!]!.push(logicalSlots[ref]!); + } + transitionWrites[boundaryPhase]!.push(...exitSlots); + + const transitions: BprfTransition[] = transitionWrites.map( + (writes, phase) => + Object.freeze({ + id: opaqueId("t", random), + phase, + boundaryCode: phase === boundaryPhase ? 1 : 0, + writes: Object.freeze(writes.slice()), + }) + ); + + const fragments: BprfFragment[] = Array.from( + { length: fragmentCount }, + () => ({ + id: opaqueId("f", random), + pieces: [] as BprfPiece[], + }) + ); + + let destinationOrdinal = 0; + for (let stepIndex = 0; stepIndex < contract.steps.length; stepIndex++) { + const ref = contract.inputs.length + stepIndex; + const step = contract.steps[stepIndex]!; + const terms = termsForFormula(step.formula, familyCode, random); + if (familyCode === 0) { + distributeTerms({ + terms, + phase: phaseByStep[stepIndex]!, + destination: logicalSlots[ref]!, + destinationBasis: contextualBases[ref]!, + logicalSlots: contextualSlots, + logicalBases: contextualBases, + fragments, + random, + rotation: destinationOrdinal + realizationIndex, + }); + destinationOrdinal++; + } else { + const residualRef = logicalValueCount + stepIndex; + const { head, continuation } = splitContinuationTerms( + terms, + residualRef, + random + ); + distributeTerms({ + terms: head, + phase: stepIndex * 2, + destination: residualSlots[stepIndex]!, + destinationBasis: contextualBases[residualRef]!, + logicalSlots: contextualSlots, + logicalBases: contextualBases, + fragments, + random, + rotation: destinationOrdinal + realizationIndex, + }); + destinationOrdinal++; + distributeTerms({ + terms: continuation, + phase: phaseByStep[stepIndex]!, + destination: logicalSlots[ref]!, + destinationBasis: contextualBases[ref]!, + logicalSlots: contextualSlots, + logicalBases: contextualBases, + fragments, + random, + rotation: destinationOrdinal + realizationIndex, + }); + destinationOrdinal++; + } + } + + for (let outputIndex = 0; outputIndex < contract.outputs.length; outputIndex++) { + const outputRef = contract.outputs[outputIndex]!; + distributeTerms({ + terms: [{ coefficient: 1, factors: [{ ref: outputRef, offset: 0 }] }], + phase: boundaryPhase, + destination: exitSlots[outputIndex]!, + destinationBasis: exitBases[outputIndex]!, + logicalSlots: contextualSlots, + logicalBases: contextualBases, + fragments, + random, + rotation: destinationOrdinal + realizationIndex, + }); + destinationOrdinal++; + } + + const inputPorts: BprfPort[] = contract.inputs.map((input, index) => + Object.freeze({ + slot: logicalSlots[index]!, + typeCode: typeCode(input.type), + basis: contextualBases[index]!, + }) + ); + const outputPorts: BprfPort[] = contract.outputs.map((outputRef, index) => + Object.freeze({ + slot: exitSlots[index]!, + typeCode: typeCode(facts.types[outputRef]!), + basis: exitBases[index]!, + }) + ); + + return Object.freeze({ + id: opaqueId("v", random), + familyCode, + contextSalt: random.nextUint32(), + frameSize, + fragmentThreshold: fragmentCount, + inputPorts: Object.freeze(inputPorts), + outputPorts: Object.freeze(outputPorts), + transitions: Object.freeze(transitions), + fragments: Object.freeze( + fragments.map((fragment) => + Object.freeze({ + id: fragment.id, + pieces: Object.freeze(fragment.pieces.slice()), + }) + ) + ), + }); +} + +function distributeTerms(options: { + terms: AlgebraicTerm[]; + phase: number; + destination: number; + destinationBasis: BprfWireBasis; + logicalSlots: readonly number[]; + logicalBases: readonly BprfWireBasis[]; + fragments: BprfFragment[]; + random: BprfRandom; + rotation: number; +}): void { + const { + phase, + destination, + destinationBasis, + logicalSlots, + logicalBases, + fragments, + random, + rotation, + } = options; + const terms = options.terms.slice(); + while (terms.length < fragments.length) { + const mask = random.nonZeroInt(11); + terms.push({ coefficient: mask, factors: [] }); + terms.push({ coefficient: -mask, factors: [] }); + } + + for (let termIndex = 0; termIndex < terms.length; termIndex++) { + const term = terms[termIndex]!; + const fragment = fragments[(termIndex + rotation) % fragments.length]!; + const factors: BprfFactor[] = term.factors.map((factor) => + Object.freeze({ + slot: logicalSlots[factor.ref]!, + offset: factor.offset, + basis: logicalBases[factor.ref]!, + }) + ); + (fragment.pieces as BprfPiece[]).push( + Object.freeze({ + phase, + destination, + destinationBasis, + coefficient: term.coefficient, + factors: Object.freeze(factors), + }) + ); + } +} + +function termsForFormula( + formula: PureRegionFormula, + familyCode: 0 | 1, + random: BprfRandom +): AlgebraicTerm[] { + switch (formula.tag) { + case "literal": + return [ + { + coefficient: + formula.type === "boolean" + ? booleanCoordinate(formula.value, familyCode) + : formula.value, + factors: [], + }, + ]; + case "sum": + return [wireTerm(formula.left), wireTerm(formula.right)]; + case "difference": + return [wireTerm(formula.left), wireTerm(formula.right, -1)]; + case "product": + return splitProduct(formula.left, formula.right, 1, random); + case "negate": + return [wireTerm(formula.value, -1)]; + case "not": + return familyCode === 0 + ? [constantTerm(1), wireTerm(formula.value, -1)] + : [wireTerm(formula.value, -1)]; + case "and": + return familyCode === 0 + ? splitProduct(formula.left, formula.right, 1, random) + : [ + wireTerm(formula.left, 0.5), + wireTerm(formula.right, 0.5), + ...splitProduct(formula.left, formula.right, 0.5, random), + constantTerm(-0.5), + ]; + case "or": + return familyCode === 0 + ? [ + wireTerm(formula.left), + wireTerm(formula.right), + ...splitProduct(formula.left, formula.right, -1, random), + ] + : [ + wireTerm(formula.left, 0.5), + wireTerm(formula.right, 0.5), + ...splitProduct(formula.left, formula.right, -0.5, random), + constantTerm(0.5), + ]; + case "xor": + return familyCode === 0 + ? [ + wireTerm(formula.left), + wireTerm(formula.right), + ...splitProduct(formula.left, formula.right, -2, random), + ] + : splitProduct(formula.left, formula.right, -1, random); + case "select": { + const baseCoefficient = familyCode === 0 ? 1 : 0.5; + return [ + wireTerm(formula.whenFalse, baseCoefficient), + ...(familyCode === 1 + ? [wireTerm(formula.whenTrue, 0.5)] + : []), + ...splitProduct( + formula.gate, + formula.whenTrue, + baseCoefficient, + random + ), + ...splitProduct( + formula.gate, + formula.whenFalse, + -baseCoefficient, + random + ), + ]; + } + } +} + +/** + * Convert one direct polynomial update into a continuation residual followed + * by a separate closure transition. Neither transition owns the full update. + */ +function splitContinuationTerms( + terms: readonly AlgebraicTerm[], + residualRef: PureValueRef, + random: BprfRandom +): { head: AlgebraicTerm[]; continuation: AlgebraicTerm[] } { + if (terms.length === 1) { + const mask = random.nonZeroInt(13); + return { + head: [terms[0]!, constantTerm(mask)], + continuation: [wireTerm(residualRef), constantTerm(-mask)], + }; + } + const cut = Math.max(1, Math.floor(terms.length / 2)); + return { + head: terms.slice(0, cut), + continuation: [wireTerm(residualRef), ...terms.slice(cut)], + }; +} + +function splitProduct( + left: PureValueRef, + right: PureValueRef, + coefficient: number, + random: BprfRandom +): AlgebraicTerm[] { + const leftShift = random.nonZeroInt(5); + const rightShift = random.nonZeroInt(5); + return [ + { + coefficient, + factors: [ + { ref: left, offset: leftShift }, + { ref: right, offset: rightShift }, + ], + }, + { + coefficient: -coefficient * leftShift, + factors: [{ ref: right, offset: 0 }], + }, + { + coefficient: -coefficient * rightShift, + factors: [{ ref: left, offset: 0 }], + }, + constantTerm(-coefficient * leftShift * rightShift), + ]; +} + +function wireTerm(ref: PureValueRef, coefficient = 1): AlgebraicTerm { + return { coefficient, factors: [{ ref, offset: 0 }] }; +} + +function constantTerm(coefficient: number): AlgebraicTerm { + return { coefficient, factors: [] }; +} + +function randomBasis(random: BprfRandom): BprfWireBasis { + return Object.freeze({ + scale: random.nonZeroInt(3), + bias: random.nonZeroInt(17), + }); +} + +function distinctBasis( + random: BprfRandom, + previous: BprfWireBasis +): BprfWireBasis { + for (;;) { + const candidate = randomBasis(random); + if ( + candidate.scale !== previous.scale || + candidate.bias !== previous.bias + ) { + return candidate; + } + } +} + +function typeCode(type: PureValueType): 0 | 1 { + return type === "number" ? 0 : 1; +} + +function booleanCoordinate(value: boolean, familyCode: 0 | 1): number { + if (familyCode === 0) return value ? 1 : 0; + return value ? 1 : -1; +} + +function validateContract(contract: PureRegionContract): ContractFacts { + if (contract.inputs.length === 0) { + throw new Error("RUAM_BPRF_REGION_REQUIRES_INPUT"); + } + if (contract.steps.length < 2) { + throw new Error("RUAM_BPRF_REGION_REQUIRES_BRAIDABLE_STEPS"); + } + if (contract.outputs.length === 0) { + throw new Error("RUAM_BPRF_REGION_REQUIRES_OUTPUT"); + } + + const types = contract.inputs.map((input) => input.type); + const depths = contract.inputs.map(() => 0); + for (let stepIndex = 0; stepIndex < contract.steps.length; stepIndex++) { + const step = contract.steps[stepIndex]!; + const maxRef = contract.inputs.length + stepIndex; + const refs = formulaRefs(step.formula); + for (const ref of refs) assertPriorRef(ref, maxRef); + assertFormulaTypes(step.formula, step.type, types); + if ( + step.formula.tag === "literal" && + step.formula.type === "number" && + !Number.isFinite(step.formula.value) + ) { + throw new Error("RUAM_BPRF_NON_FINITE_LITERAL"); + } + types.push(step.type); + depths.push( + refs.length === 0 ? 1 : Math.max(...refs.map((ref) => depths[ref]!)) + 1 + ); + } + for (const output of contract.outputs) assertPriorRef(output, types.length); + return { types, depths }; +} + +function assertFormulaTypes( + formula: PureRegionFormula, + resultType: PureValueType, + types: readonly PureValueType[] +): void { + const typeAt = (ref: PureValueRef): PureValueType => types[ref]!; + switch (formula.tag) { + case "literal": + if (formula.type !== resultType) throw typeError(); + return; + case "sum": + case "difference": + case "product": + if ( + resultType !== "number" || + typeAt(formula.left) !== "number" || + typeAt(formula.right) !== "number" + ) { + throw typeError(); + } + return; + case "negate": + if (resultType !== "number" || typeAt(formula.value) !== "number") { + throw typeError(); + } + return; + case "not": + if (resultType !== "boolean" || typeAt(formula.value) !== "boolean") { + throw typeError(); + } + return; + case "and": + case "or": + case "xor": + if ( + resultType !== "boolean" || + typeAt(formula.left) !== "boolean" || + typeAt(formula.right) !== "boolean" + ) { + throw typeError(); + } + return; + case "select": + if ( + typeAt(formula.gate) !== "boolean" || + typeAt(formula.whenTrue) !== resultType || + typeAt(formula.whenFalse) !== resultType + ) { + throw typeError(); + } + return; + } +} + +function formulaRefs(formula: PureRegionFormula): PureValueRef[] { + switch (formula.tag) { + case "literal": + return []; + case "negate": + case "not": + return [formula.value]; + case "sum": + case "difference": + case "product": + case "and": + case "or": + case "xor": + return [formula.left, formula.right]; + case "select": + return [formula.gate, formula.whenTrue, formula.whenFalse]; + } +} + +function assertPriorRef(ref: PureValueRef, upperBound: number): void { + if (!Number.isSafeInteger(ref) || ref < 0 || ref >= upperBound) { + throw new Error(`RUAM_BPRF_INVALID_VALUE_REF: ${ref}`); + } +} + +function typeError(): Error { + return new Error("RUAM_BPRF_FORMULA_TYPE_MISMATCH"); +} diff --git a/packages/ruam/src/isogloss/bprf/index.ts b/packages/ruam/src/isogloss/bprf/index.ts new file mode 100644 index 0000000..206b2ff --- /dev/null +++ b/packages/ruam/src/isogloss/bprf/index.ts @@ -0,0 +1,26 @@ +/** Public compiler-side surface for the bounded pure BPRF spike. */ + +export { generateBprfArtifact } from "./generate.js"; +export { validateBprfArtifact } from "./validate.js"; +export type { + BprfArtifact, + BprfCallerContext, + BprfFactor, + BprfFragment, + BprfGenerationOptions, + BprfPiece, + BprfPort, + BprfRealization, + BprfReferenceResult, + BprfReferenceTraceEvent, + BprfTransition, + BprfValidationReport, + BprfWireBasis, + PureRegionContract, + PureRegionFormula, + PureRegionInput, + PureRegionStep, + PureScalar, + PureValueRef, + PureValueType, +} from "./types.js"; diff --git a/packages/ruam/src/isogloss/bprf/random.ts b/packages/ruam/src/isogloss/bprf/random.ts new file mode 100644 index 0000000..95bc05b --- /dev/null +++ b/packages/ruam/src/isogloss/bprf/random.ts @@ -0,0 +1,66 @@ +/** Deterministic, non-cryptographic entropy used only by the BPRF generator. */ +export class BprfRandom { + private state: number; + + constructor(seed: number) { + this.state = mix32(seed >>> 0); + } + + nextUint32(): number { + let value = this.state; + value ^= value << 13; + value ^= value >>> 17; + value ^= value << 5; + this.state = value >>> 0; + return this.state; + } + + int(minInclusive: number, maxExclusive: number): number { + if (maxExclusive <= minInclusive) { + throw new Error("RUAM_BPRF_INVALID_RANDOM_RANGE"); + } + return ( + minInclusive + + (this.nextUint32() % (maxExclusive - minInclusive)) + ); + } + + nonZeroInt(magnitude: number): number { + const absolute = this.int(1, magnitude + 1); + return (this.nextUint32() & 1) === 0 ? absolute : -absolute; + } + + shuffle(values: readonly T[]): T[] { + const result = values.slice(); + for (let index = result.length - 1; index > 0; index--) { + const swapIndex = this.int(0, index + 1); + [result[index], result[swapIndex]] = [ + result[swapIndex]!, + result[index]!, + ]; + } + return result; + } +} + +export function mix32(value: number): number { + let mixed = value >>> 0; + mixed = Math.imul(mixed ^ (mixed >>> 16), 0x7feb352d); + mixed = Math.imul(mixed ^ (mixed >>> 15), 0x846ca68b); + return (mixed ^ (mixed >>> 16)) >>> 0; +} + +export function hashText(value: string, seed = 0x811c9dc5): number { + let hash = seed >>> 0; + for (let index = 0; index < value.length; index++) { + hash ^= value.charCodeAt(index); + hash = Math.imul(hash, 0x01000193) >>> 0; + } + return mix32(hash); +} + +export function opaqueId(prefix: string, random: BprfRandom): string { + return `${prefix}${random.nextUint32().toString(36)}${random + .nextUint32() + .toString(36)}`; +} diff --git a/packages/ruam/src/isogloss/bprf/testing-reference.ts b/packages/ruam/src/isogloss/bprf/testing-reference.ts new file mode 100644 index 0000000..4b4e126 --- /dev/null +++ b/packages/ruam/src/isogloss/bprf/testing-reference.ts @@ -0,0 +1,176 @@ +/** + * NON-PRODUCT REFERENCE EVALUATOR. + * + * This generic evaluator exists only for differential tests and spike scoring. + * Product execution must emit generated regional codelets instead of shipping + * a universal artifact evaluator. + * + * @module isogloss/bprf/testing-reference + */ + +import { hashText, mix32 } from "./random.js"; +import type { + BprfArtifact, + BprfCallerContext, + BprfPiece, + BprfRealization, + BprfReferenceResult, + BprfReferenceTraceEvent, + BprfWireBasis, + PureScalar, +} from "./types.js"; +import { validateBprfArtifact } from "./validate.js"; + +/** Deterministically select a contextual realization for one caller lineage. */ +export function selectBprfRealization( + artifact: BprfArtifact, + context: BprfCallerContext +): BprfRealization { + if ( + !Number.isSafeInteger(context.epoch) || + context.epoch < 0 || + !Number.isSafeInteger(context.lineage) || + context.lineage < 0 + ) { + throw new Error("RUAM_BPRF_INVALID_CALLER_CONTEXT"); + } + const callerHash = hashText(context.caller, artifact.selectionSalt); + const selection = mix32( + callerHash ^ + Math.imul(context.epoch + 1, 0x9e3779b9) ^ + Math.imul(context.lineage + 1, 0x85ebca6b) + ); + return artifact.realizations[selection % artifact.realizations.length]!; +} + +/** + * Evaluate a generated artifact for differential testing. + * + * Trace records contain only contextual realization/transition/fragment + * identities. They deliberately omit values, formula tags, operand tuples, + * and source identities. + */ +export function evaluateBprfReference( + artifact: BprfArtifact, + inputs: readonly PureScalar[], + context: BprfCallerContext +): BprfReferenceResult { + validateBprfArtifact(artifact); + const realization = selectBprfRealization(artifact, context); + if (inputs.length !== realization.inputPorts.length) { + throw new Error("RUAM_BPRF_INPUT_ARITY_MISMATCH"); + } + + const frame: Array = Array.from( + { length: realization.frameSize }, + () => undefined + ); + for (let index = 0; index < inputs.length; index++) { + const port = realization.inputPorts[index]!; + const coordinate = toCoordinate( + inputs[index]!, + port.typeCode, + realization.familyCode + ); + frame[port.slot] = encodeCoordinate(coordinate, port.basis); + } + + const trace: BprfReferenceTraceEvent[] = []; + for (const transition of realization.transitions) { + const totals = new Map(); + const destinationBases = new Map(); + for (const fragment of realization.fragments) { + let participated = false; + for (const piece of fragment.pieces) { + if (piece.phase !== transition.phase) continue; + participated = true; + const contribution = evaluatePiece(piece, frame); + totals.set( + piece.destination, + (totals.get(piece.destination) ?? 0) + contribution + ); + destinationBases.set(piece.destination, piece.destinationBasis); + } + if (participated) { + trace.push( + Object.freeze({ + realization: realization.id, + transition: transition.id, + fragment: fragment.id, + phase: transition.phase, + }) + ); + } + } + for (const destination of transition.writes) { + const total = totals.get(destination); + const basis = destinationBases.get(destination); + if (total == null || !basis) { + throw new Error("RUAM_BPRF_INCOMPLETE_REFERENCE_TRANSITION"); + } + frame[destination] = encodeCoordinate(total, basis); + } + } + + const outputs = realization.outputPorts.map((port) => { + const encoded = frame[port.slot]; + if (encoded == null) throw new Error("RUAM_BPRF_MISSING_REFERENCE_OUTPUT"); + const coordinate = decodeCoordinate(encoded, port.basis); + return fromCoordinate(coordinate, port.typeCode, realization.familyCode); + }); + return { + outputs, + trace, + realization: realization.id, + }; +} + +function evaluatePiece( + piece: BprfPiece, + frame: readonly (number | undefined)[] +): number { + let value = piece.coefficient; + for (const factor of piece.factors) { + const encoded = frame[factor.slot]; + if (encoded == null) { + throw new Error("RUAM_BPRF_REFERENCE_READ_BEFORE_WRITE"); + } + value *= decodeCoordinate(encoded, factor.basis) + factor.offset; + } + return value; +} + +function toCoordinate( + value: PureScalar, + typeCode: 0 | 1, + familyCode: 0 | 1 +): number { + if (typeCode === 0) { + if (typeof value !== "number" || !Number.isFinite(value)) { + throw new Error("RUAM_BPRF_EXPECTED_FINITE_NUMBER"); + } + return value; + } + if (typeof value !== "boolean") { + throw new Error("RUAM_BPRF_EXPECTED_BOOLEAN"); + } + if (familyCode === 0) return value ? 1 : 0; + return value ? 1 : -1; +} + +function fromCoordinate( + value: number, + typeCode: 0 | 1, + familyCode: 0 | 1 +): PureScalar { + if (typeCode === 0) return value; + return familyCode === 0 ? value > 0.5 : value > 0; +} + +function encodeCoordinate(value: number, basis: BprfWireBasis): number { + return value * basis.scale + basis.bias; +} + +function decodeCoordinate(value: number, basis: BprfWireBasis): number { + return (value - basis.bias) / basis.scale; +} diff --git a/packages/ruam/src/isogloss/bprf/types.ts b/packages/ruam/src/isogloss/bprf/types.ts new file mode 100644 index 0000000..b07a59c --- /dev/null +++ b/packages/ruam/src/isogloss/bprf/types.ts @@ -0,0 +1,145 @@ +/** + * Architecture-neutral contracts for the bounded pure BPRF spike. + * + * Logical contracts are compiler/reference inputs. Generated artifacts use + * only contextual wire, fragment, and transition identities. + * + * @module isogloss/bprf/types + */ + +export type PureScalar = number | boolean; +export type PureValueType = "number" | "boolean"; +export type PureValueRef = number; + +export interface PureRegionInput { + type: PureValueType; +} + +export type PureRegionFormula = + | { tag: "literal"; type: "number"; value: number } + | { tag: "literal"; type: "boolean"; value: boolean } + | { tag: "sum"; left: PureValueRef; right: PureValueRef } + | { tag: "difference"; left: PureValueRef; right: PureValueRef } + | { tag: "product"; left: PureValueRef; right: PureValueRef } + | { tag: "negate"; value: PureValueRef } + | { tag: "not"; value: PureValueRef } + | { tag: "and"; left: PureValueRef; right: PureValueRef } + | { tag: "or"; left: PureValueRef; right: PureValueRef } + | { tag: "xor"; left: PureValueRef; right: PureValueRef } + | { + tag: "select"; + gate: PureValueRef; + whenTrue: PureValueRef; + whenFalse: PureValueRef; + }; + +export interface PureRegionStep { + type: PureValueType; + formula: PureRegionFormula; +} + +/** + * Dense pure-region graph. + * + * Input references occupy `[0, inputs.length)`. Step references follow in + * declaration order, so every formula may reference only an earlier value. + */ +export interface PureRegionContract { + inputs: readonly PureRegionInput[]; + steps: readonly PureRegionStep[]; + outputs: readonly PureValueRef[]; +} + +export interface BprfGenerationOptions { + seed: number; + /** Must be at least two. Defaults to three. */ + realizationCount?: number; + /** Necessary fragments per realization. Must be at least two. */ + fragmentCount?: number; +} + +/** Affine coordinate basis for one contextual physical wire. */ +export interface BprfWireBasis { + scale: number; + bias: number; +} + +export interface BprfPort { + slot: number; + typeCode: 0 | 1; + basis: BprfWireBasis; +} + +export interface BprfFactor { + slot: number; + offset: number; + basis: BprfWireBasis; +} + +/** One partial polynomial contribution; never a complete logical step. */ +export interface BprfPiece { + phase: number; + destination: number; + destinationBasis: BprfWireBasis; + coefficient: number; + factors: readonly BprfFactor[]; +} + +/** A longitudinal fragment braided across several logical destinations. */ +export interface BprfFragment { + id: string; + pieces: readonly BprfPiece[]; +} + +export interface BprfTransition { + id: string; + phase: number; + boundaryCode: 0 | 1; + writes: readonly number[]; +} + +export interface BprfRealization { + id: string; + /** Opaque structural-family code; it is not a language-operation identity. */ + familyCode: 0 | 1; + contextSalt: number; + frameSize: number; + fragmentThreshold: number; + inputPorts: readonly BprfPort[]; + outputPorts: readonly BprfPort[]; + transitions: readonly BprfTransition[]; + fragments: readonly BprfFragment[]; +} + +export interface BprfArtifact { + format: "ruam-bprf-pure-1"; + id: string; + selectionSalt: number; + realizations: readonly BprfRealization[]; +} + +export interface BprfCallerContext { + caller: string; + epoch: number; + lineage: number; +} + +export interface BprfReferenceTraceEvent { + realization: string; + transition: string; + fragment: string; + phase: number; +} + +export interface BprfReferenceResult { + outputs: PureScalar[]; + trace: BprfReferenceTraceEvent[]; + realization: string; +} + +export interface BprfValidationReport { + realizationCount: number; + familyCount: number; + fragmentCountRange: readonly [number, number]; + transitionCountRange: readonly [number, number]; +} diff --git a/packages/ruam/src/isogloss/bprf/validate.ts b/packages/ruam/src/isogloss/bprf/validate.ts new file mode 100644 index 0000000..beaf931 --- /dev/null +++ b/packages/ruam/src/isogloss/bprf/validate.ts @@ -0,0 +1,312 @@ +/** + * Structural verifier for pure BPRF reference artifacts. + * + * The verifier checks fission, longitudinal braiding, contextual diversity, + * physical-frame validity, and absence of semantic-dispatch schema fields. + * + * @module isogloss/bprf/validate + */ + +import type { + BprfArtifact, + BprfPiece, + BprfRealization, + BprfValidationReport, + BprfWireBasis, +} from "./types.js"; + +const FORBIDDEN_SCHEMA_KEYS = new Set([ + "op", + "opcode", + "operand", + "semanticop", + "handler", + "handlerid", + "sourceid", + "sourcenode", + "sourcenodeid", + "nodeid", + "canonicaloperand", +]); + +/** Validate an artifact or throw a stable `RUAM_BPRF_*` diagnostic. */ +export function validateBprfArtifact( + artifact: BprfArtifact +): BprfValidationReport { + if (artifact.format !== "ruam-bprf-pure-1") { + throw new Error("RUAM_BPRF_FORMAT_MISMATCH"); + } + assertNoForbiddenSchemaKeys(artifact); + if (artifact.realizations.length < 2) { + throw new Error("RUAM_BPRF_REALIZATION_COUNT_MIN_2"); + } + + const realizationIds = new Set(); + const familyCodes = new Set(); + const structuralSignatures = new Set(); + const fragmentCounts: number[] = []; + const transitionCounts: number[] = []; + + for (const realization of artifact.realizations) { + if (realizationIds.has(realization.id)) { + throw new Error("RUAM_BPRF_DUPLICATE_REALIZATION_ID"); + } + realizationIds.add(realization.id); + familyCodes.add(realization.familyCode); + validateRealization(realization); + fragmentCounts.push(realization.fragments.length); + transitionCounts.push(realization.transitions.length); + structuralSignatures.add(structuralSignature(realization)); + } + + if (familyCodes.size < 2) { + throw new Error("RUAM_BPRF_REQUIRES_TWO_STRUCTURAL_FAMILIES"); + } + if (structuralSignatures.size !== artifact.realizations.length) { + throw new Error("RUAM_BPRF_DUPLICATE_REALIZATION_STRUCTURE"); + } + + return Object.freeze({ + realizationCount: artifact.realizations.length, + familyCount: familyCodes.size, + fragmentCountRange: Object.freeze([ + Math.min(...fragmentCounts), + Math.max(...fragmentCounts), + ]) as readonly [number, number], + transitionCountRange: Object.freeze([ + Math.min(...transitionCounts), + Math.max(...transitionCounts), + ]) as readonly [number, number], + }); +} + +function validateRealization(realization: BprfRealization): void { + if (realization.familyCode !== 0 && realization.familyCode !== 1) { + throw new Error("RUAM_BPRF_INVALID_FAMILY_CODE"); + } + if (!Number.isSafeInteger(realization.contextSalt)) { + throw new Error("RUAM_BPRF_INVALID_CONTEXT_SALT"); + } + if (!Number.isSafeInteger(realization.frameSize) || realization.frameSize < 1) { + throw new Error("RUAM_BPRF_INVALID_FRAME_SIZE"); + } + if (realization.fragments.length < 2) { + throw new Error("RUAM_BPRF_FRAGMENT_COUNT_MIN_2"); + } + if ( + !Number.isSafeInteger(realization.fragmentThreshold) || + realization.fragmentThreshold < 2 || + realization.fragmentThreshold !== realization.fragments.length + ) { + throw new Error("RUAM_BPRF_FRAGMENT_THRESHOLD_MISMATCH"); + } + if (realization.transitions.length < 2) { + throw new Error("RUAM_BPRF_TRANSITION_COUNT_MIN_2"); + } + if (realization.inputPorts.length === 0 || realization.outputPorts.length === 0) { + throw new Error("RUAM_BPRF_PORTS_REQUIRED"); + } + + const fragmentIds = new Set(); + for (const fragment of realization.fragments) { + if (fragmentIds.has(fragment.id)) { + throw new Error("RUAM_BPRF_DUPLICATE_FRAGMENT_ID"); + } + fragmentIds.add(fragment.id); + if (fragment.pieces.length === 0) { + throw new Error("RUAM_BPRF_EMPTY_FRAGMENT"); + } + } + + const transitionIds = new Set(); + const writtenPhase = new Map(); + const basisBySlot = new Map(); + const inputSlots = new Set(); + for (const port of realization.inputPorts) { + assertSlot(port.slot, realization.frameSize); + assertBasis(port.basis); + if (inputSlots.has(port.slot)) { + throw new Error("RUAM_BPRF_DUPLICATE_INPUT_SLOT"); + } + inputSlots.add(port.slot); + writtenPhase.set(port.slot, -1); + basisBySlot.set(port.slot, port.basis); + } + + for (let index = 0; index < realization.transitions.length; index++) { + const transition = realization.transitions[index]!; + if (transition.phase !== index) { + throw new Error("RUAM_BPRF_NON_DENSE_PHASES"); + } + if (transitionIds.has(transition.id)) { + throw new Error("RUAM_BPRF_DUPLICATE_TRANSITION_ID"); + } + transitionIds.add(transition.id); + if (transition.writes.length === 0) { + throw new Error("RUAM_BPRF_EMPTY_TRANSITION"); + } + if ( + transition.boundaryCode !== + (index === realization.transitions.length - 1 ? 1 : 0) + ) { + throw new Error("RUAM_BPRF_INVALID_BOUNDARY_TRANSITION"); + } + for (const slot of transition.writes) { + assertSlot(slot, realization.frameSize); + if (writtenPhase.has(slot)) { + throw new Error("RUAM_BPRF_SLOT_WRITTEN_MORE_THAN_ONCE"); + } + writtenPhase.set(slot, transition.phase); + } + } + + const piecesByDestination = new Map< + number, + Array<{ fragmentId: string; piece: BprfPiece }> + >(); + for (const fragment of realization.fragments) { + const fragmentDestinations = new Set(); + for (const piece of fragment.pieces) { + assertFinite(piece.coefficient); + assertBasis(piece.destinationBasis); + assertSlot(piece.destination, realization.frameSize); + const expectedPhase = writtenPhase.get(piece.destination); + if (expectedPhase == null || expectedPhase !== piece.phase) { + throw new Error("RUAM_BPRF_PIECE_DESTINATION_PHASE_MISMATCH"); + } + for (const factor of piece.factors) { + assertFinite(factor.offset); + assertBasis(factor.basis); + assertSlot(factor.slot, realization.frameSize); + const factorPhase = writtenPhase.get(factor.slot); + if (factorPhase == null || factorPhase >= piece.phase) { + throw new Error("RUAM_BPRF_FORWARD_OR_UNKNOWN_FACTOR"); + } + } + fragmentDestinations.add(piece.destination); + const destinationPieces = piecesByDestination.get(piece.destination) ?? []; + destinationPieces.push({ fragmentId: fragment.id, piece }); + piecesByDestination.set(piece.destination, destinationPieces); + } + if (fragmentDestinations.size < 2) { + throw new Error("RUAM_BPRF_FRAGMENT_NOT_BRAIDED"); + } + } + + for (const transition of realization.transitions) { + for (const destination of transition.writes) { + const destinationPieces = piecesByDestination.get(destination) ?? []; + const owners = new Set(destinationPieces.map((entry) => entry.fragmentId)); + if (owners.size !== realization.fragmentThreshold) { + throw new Error("RUAM_BPRF_DESTINATION_NOT_FISSIONED"); + } + for (const owner of owners) { + const ownedCount = destinationPieces.filter( + (entry) => entry.fragmentId === owner + ).length; + if (ownedCount === destinationPieces.length) { + throw new Error("RUAM_BPRF_FRAGMENT_OWNS_COMPLETE_DESTINATION"); + } + } + assertConsistentDestinationBasis(destinationPieces.map((entry) => entry.piece)); + basisBySlot.set( + destination, + destinationPieces[0]!.piece.destinationBasis + ); + } + } + + for (const fragment of realization.fragments) { + for (const piece of fragment.pieces) { + for (const factor of piece.factors) { + const expectedBasis = basisBySlot.get(factor.slot); + if (!expectedBasis || !sameBasis(expectedBasis, factor.basis)) { + throw new Error("RUAM_BPRF_FACTOR_BASIS_MISMATCH"); + } + } + } + } + + const finalTransition = + realization.transitions[realization.transitions.length - 1]!; + const finalSlots = new Set(finalTransition.writes); + for (const port of realization.outputPorts) { + assertSlot(port.slot, realization.frameSize); + assertBasis(port.basis); + if (!finalSlots.has(port.slot) || inputSlots.has(port.slot)) { + throw new Error("RUAM_BPRF_OUTPUT_NOT_REBASED_AT_BOUNDARY"); + } + const pieces = piecesByDestination.get(port.slot)!; + if (!sameBasis(pieces[0]!.piece.destinationBasis, port.basis)) { + throw new Error("RUAM_BPRF_OUTPUT_BASIS_MISMATCH"); + } + } +} + +function assertConsistentDestinationBasis(pieces: readonly BprfPiece[]): void { + const expected = pieces[0]?.destinationBasis; + if (!expected) throw new Error("RUAM_BPRF_MISSING_DESTINATION_PIECES"); + for (const piece of pieces) { + if (!sameBasis(piece.destinationBasis, expected)) { + throw new Error("RUAM_BPRF_INCONSISTENT_DESTINATION_BASIS"); + } + } +} + +function assertNoForbiddenSchemaKeys(value: unknown): void { + if (Array.isArray(value)) { + for (const item of value) assertNoForbiddenSchemaKeys(item); + return; + } + if (value === null || typeof value !== "object") return; + for (const [key, child] of Object.entries(value)) { + if (FORBIDDEN_SCHEMA_KEYS.has(key.toLowerCase())) { + throw new Error(`RUAM_BPRF_FORBIDDEN_SCHEMA_KEY: ${key}`); + } + assertNoForbiddenSchemaKeys(child); + } +} + +function structuralSignature(realization: BprfRealization): string { + return JSON.stringify({ + familyCode: realization.familyCode, + frameSize: realization.frameSize, + fragmentThreshold: realization.fragmentThreshold, + inputPorts: realization.inputPorts, + outputPorts: realization.outputPorts, + transitions: realization.transitions.map((transition) => ({ + phase: transition.phase, + boundaryCode: transition.boundaryCode, + writes: transition.writes, + })), + fragments: realization.fragments.map((fragment) => + fragment.pieces.map((piece) => ({ + phase: piece.phase, + destination: piece.destination, + destinationBasis: piece.destinationBasis, + coefficient: piece.coefficient, + factors: piece.factors, + })) + ), + }); +} + +function assertSlot(slot: number, frameSize: number): void { + if (!Number.isSafeInteger(slot) || slot < 0 || slot >= frameSize) { + throw new Error(`RUAM_BPRF_INVALID_SLOT: ${slot}`); + } +} + +function assertBasis(basis: BprfWireBasis): void { + assertFinite(basis.scale); + assertFinite(basis.bias); + if (basis.scale === 0) throw new Error("RUAM_BPRF_ZERO_BASIS_SCALE"); +} + +function assertFinite(value: number): void { + if (!Number.isFinite(value)) throw new Error("RUAM_BPRF_NON_FINITE_VALUE"); +} + +function sameBasis(left: BprfWireBasis, right: BprfWireBasis): boolean { + return left.scale === right.scale && left.bias === right.bias; +} diff --git a/packages/ruam/test/isogloss/bprf-kernel.test.ts b/packages/ruam/test/isogloss/bprf-kernel.test.ts new file mode 100644 index 0000000..f446fc7 --- /dev/null +++ b/packages/ruam/test/isogloss/bprf-kernel.test.ts @@ -0,0 +1,238 @@ +import { describe, expect, it } from "bun:test"; +import { + generateBprfArtifact, + validateBprfArtifact, + type BprfArtifact, + type PureRegionContract, +} from "../../src/isogloss/bprf/index.js"; +import { + evaluateBprfReference, + selectBprfRealization, +} from "../../src/isogloss/bprf/testing-reference.js"; + +const fixture: PureRegionContract = { + inputs: [ + { type: "number" }, + { type: "number" }, + { type: "boolean" }, + { type: "boolean" }, + ], + steps: [ + { type: "number", formula: { tag: "product", left: 0, right: 1 } }, + { type: "number", formula: { tag: "sum", left: 0, right: 1 } }, + { type: "number", formula: { tag: "difference", left: 4, right: 5 } }, + { type: "boolean", formula: { tag: "and", left: 2, right: 3 } }, + { type: "boolean", formula: { tag: "xor", left: 2, right: 3 } }, + { type: "boolean", formula: { tag: "or", left: 7, right: 8 } }, + { + type: "number", + formula: { + tag: "select", + gate: 9, + whenTrue: 6, + whenFalse: 5, + }, + }, + { type: "number", formula: { tag: "negate", value: 10 } }, + { type: "boolean", formula: { tag: "not", value: 7 } }, + ], + outputs: [10, 11, 12, 8], +}; + +const cases = [ + [3, 5, true, false], + [-4, 7, false, false], + [6, -2, true, true], + [0, 9, false, true], +] as const; + +function expected(values: readonly [number, number, boolean, boolean]) { + const [left, right, first, second] = values; + const product = left * right; + const sum = left + right; + const difference = product - sum; + const conjunction = first && second; + const exclusive = first !== second; + const gate = conjunction || exclusive; + const selected = gate ? difference : sum; + return [selected, -selected, !conjunction, exclusive] as const; +} + +describe("pure BPRF reference kernel", () => { + it("is equivalent across seeds, structural families, and caller contexts", () => { + for (const seed of [0, 1, 0x12345678, 0xffffffff]) { + const artifact = generateBprfArtifact(fixture, { + seed, + realizationCount: 4, + fragmentCount: 3, + }); + const reached = new Set(); + for (let contextIndex = 0; contextIndex < 128; contextIndex++) { + const context = { + caller: `caller-${contextIndex % 11}`, + epoch: Math.floor(contextIndex / 11), + lineage: contextIndex * 7, + }; + for (const inputs of cases) { + const result = evaluateBprfReference(artifact, inputs, context); + const oracle = expected(inputs); + reached.add(result.realization); + expect(result.outputs[0]).toBeCloseTo(oracle[0], 10); + expect(result.outputs[1]).toBeCloseTo(oracle[1], 10); + expect(result.outputs[2]).toBe(oracle[2]); + expect(result.outputs[3]).toBe(oracle[3]); + } + } + expect(reached.size).toBe(artifact.realizations.length); + expect( + new Set(artifact.realizations.map((realization) => realization.familyCode)) + .size + ).toBe(2); + } + }); + + it("is byte-for-byte deterministic for a seed and diverse across seeds", () => { + const options = { seed: 90210, realizationCount: 3, fragmentCount: 4 }; + const first = generateBprfArtifact(fixture, options); + const second = generateBprfArtifact(fixture, options); + const different = generateBprfArtifact(fixture, { + ...options, + seed: options.seed + 1, + }); + + expect(JSON.stringify(second)).toBe(JSON.stringify(first)); + expect(JSON.stringify(different)).not.toBe(JSON.stringify(first)); + }); + + it("fissions every destination and braids every fragment longitudinally", () => { + const artifact = generateBprfArtifact(fixture, { + seed: 7331, + realizationCount: 4, + fragmentCount: 3, + }); + const report = validateBprfArtifact(artifact); + + expect(report.realizationCount).toBe(4); + expect(report.familyCount).toBe(2); + const directPhaseCounts = artifact.realizations + .filter((realization) => realization.familyCode === 0) + .map((realization) => realization.transitions.length); + const residualPhaseCounts = artifact.realizations + .filter((realization) => realization.familyCode === 1) + .map((realization) => realization.transitions.length); + expect(Math.min(...residualPhaseCounts)).toBeGreaterThan( + Math.max(...directPhaseCounts) + ); + for (const realization of artifact.realizations) { + const destinations = new Set( + realization.transitions.flatMap((transition) => transition.writes) + ); + for (const destination of destinations) { + const owners = new Set( + realization.fragments + .filter((fragment) => + fragment.pieces.some( + (piece) => piece.destination === destination + ) + ) + .map((fragment) => fragment.id) + ); + expect(owners.size).toBe(realization.fragmentThreshold); + } + for (const fragment of realization.fragments) { + expect( + new Set(fragment.pieces.map((piece) => piece.destination)).size + ).toBeGreaterThan(1); + } + const inputSlots = new Set( + realization.inputPorts.map((port) => port.slot) + ); + for (const output of realization.outputPorts) { + expect(inputSlots.has(output.slot)).toBe(false); + } + } + + const damaged = structuredClone(artifact) as BprfArtifact; + (damaged.realizations[0]!.fragments as unknown[]).pop(); + expect(() => validateBprfArtifact(damaged)).toThrow( + "RUAM_BPRF_FRAGMENT_THRESHOLD_MISMATCH" + ); + }); + + it("emits no semantic-dispatch identity and traces only contextual fabric IDs", () => { + const artifact = generateBprfArtifact(fixture, { + seed: 4141, + realizationCount: 3, + fragmentCount: 3, + }); + const serialized = JSON.stringify(artifact); + const forbiddenArtifactTokens = [ + "SemanticOp", + "opcode", + "operand", + "handler", + "nodeId", + "sourceNode", + '"sum"', + '"difference"', + '"product"', + '"negate"', + '"not"', + '"and"', + '"or"', + '"xor"', + '"select"', + '"literal"', + '"tag"', + '"formula"', + ]; + for (const token of forbiddenArtifactTokens) { + expect(serialized).not.toContain(token); + } + + const result = evaluateBprfReference( + artifact, + cases[0], + { caller: "alpha", epoch: 2, lineage: 17 } + ); + expect(result.trace.length).toBeGreaterThan(0); + for (const event of result.trace) { + expect(Object.keys(event).sort()).toEqual([ + "fragment", + "phase", + "realization", + "transition", + ]); + expect(event.realization).toBe(result.realization); + } + const serializedTrace = JSON.stringify(result.trace); + for (const token of forbiddenArtifactTokens) { + expect(serializedTrace).not.toContain(token); + } + expect(serializedTrace).not.toContain("coefficient"); + expect(serializedTrace).not.toContain("factors"); + expect(serializedTrace).not.toContain("destination"); + }); + + it("selects variants from caller context without changing results", () => { + const artifact = generateBprfArtifact(fixture, { + seed: 8080, + realizationCount: 3, + fragmentCount: 3, + }); + const selected = new Set(); + for (let index = 0; index < 100; index++) { + const context = { + caller: `entry-${index}`, + epoch: index % 5, + lineage: index, + }; + const realization = selectBprfRealization(artifact, context); + const result = evaluateBprfReference(artifact, cases[1], context); + selected.add(realization.id); + expect(result.realization).toBe(realization.id); + expect(result.outputs[0]).toBeCloseTo(expected(cases[1])[0], 10); + } + expect(selected.size).toBe(artifact.realizations.length); + }); +}); From fa566f9cf025a159efa34cf2d26798dd56ce4879 Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:00:39 -1000 Subject: [PATCH 13/54] feat: spike moving-cover custodied CSH --- .../ruam/src/isogloss/csh/custody-protocol.ts | 195 +++++ .../src/isogloss/csh/reference-custodian.ts | 246 ++++++ packages/ruam/src/isogloss/csh/reference.ts | 745 ++++++++++++++++++ .../isogloss/csh/testing-bprf-reference.ts | 503 ++++++++++++ .../isogloss/bprf-csh-integration.test.ts | 182 +++++ .../ruam/test/isogloss/csh-custody.test.ts | 156 ++++ .../test/isogloss/csh-moving-cover.test.ts | 225 ++++++ 7 files changed, 2252 insertions(+) create mode 100644 packages/ruam/src/isogloss/csh/custody-protocol.ts create mode 100644 packages/ruam/src/isogloss/csh/reference-custodian.ts create mode 100644 packages/ruam/src/isogloss/csh/reference.ts create mode 100644 packages/ruam/src/isogloss/csh/testing-bprf-reference.ts create mode 100644 packages/ruam/test/isogloss/bprf-csh-integration.test.ts create mode 100644 packages/ruam/test/isogloss/csh-custody.test.ts create mode 100644 packages/ruam/test/isogloss/csh-moving-cover.test.ts diff --git a/packages/ruam/src/isogloss/csh/custody-protocol.ts b/packages/ruam/src/isogloss/csh/custody-protocol.ts new file mode 100644 index 0000000..0133b4d --- /dev/null +++ b/packages/ruam/src/isogloss/csh/custody-protocol.ts @@ -0,0 +1,195 @@ +/** + * Client-visible protocol for the experimental CSH direct-custody spike. + * + * The protocol deliberately has no local relation implementation or fallback. + * It can verify and open one lineage-bound, site-specific projection returned + * by a custodian, but it cannot derive that projection from client artifacts. + * + * @module isogloss/csh/custody-protocol + */ + +import { verify } from "node:crypto"; +import { + CSH_FIELD_MODULUS, + type ChartCellTransform, + type EncodedChart, +} from "./reference.js"; + +export interface CustodyClientContract { + readonly sessionId: string; + readonly contractId: string; + readonly coverId: string; + readonly initialLineageCommitment: string; + readonly verificationKey: string; +} + +export interface CustodyClientState { + readonly epoch: number; + readonly lineageCommitment: string; +} + +export interface CustodyRequest { + readonly sessionId: string; + readonly contractId: string; + readonly coverId: string; + readonly epoch: number; + readonly lineageCommitment: string; + readonly nonce: string; + readonly charts: readonly EncodedChart[]; +} + +export interface CustodyResponse { + readonly sessionId: string; + readonly contractId: string; + readonly requestNonce: string; + readonly epoch: number; + readonly nextEpoch: number; + readonly nextLineageCommitment: string; + readonly encodedProjection: number; + /** One-response opening for the declared scalar projection only. */ + readonly projectionOpening: ChartCellTransform; + readonly signature: string; +} + +export interface OpenedCustodyProjection { + readonly projection: number; + readonly state: CustodyClientState; +} + +export function createCustodyClientState( + contract: CustodyClientContract +): CustodyClientState { + return Object.freeze({ + epoch: 0, + lineageCommitment: contract.initialLineageCommitment, + }); +} + +export function prepareCustodyRequest( + contract: CustodyClientContract, + state: CustodyClientState, + charts: readonly EncodedChart[], + nonce: string +): CustodyRequest { + if (nonce.length < 8) throw new Error("RUAM_CSH_CUSTODY_NONCE_TOO_SHORT"); + if ( + charts.length === 0 || + charts.some((chart) => chart.coverId !== contract.coverId) + ) { + throw new Error("RUAM_CSH_CUSTODY_WRONG_COVER"); + } + return Object.freeze({ + sessionId: contract.sessionId, + contractId: contract.contractId, + coverId: contract.coverId, + epoch: state.epoch, + lineageCommitment: state.lineageCommitment, + nonce, + charts, + }); +} + +/** + * Verify and materialize exactly one ordinary value at its declared effect + * boundary. There is intentionally no overload that accepts no response. + */ +export function openCustodiedProjection( + contract: CustodyClientContract, + state: CustodyClientState, + response: CustodyResponse | undefined +): OpenedCustodyProjection { + if (response === undefined) { + throw new Error("RUAM_CSH_CUSTODIAN_REQUIRED"); + } + if ( + response.sessionId !== contract.sessionId || + response.contractId !== contract.contractId || + response.epoch !== state.epoch || + response.nextEpoch !== state.epoch + 1 + ) { + throw new Error("RUAM_CSH_CUSTODY_RESPONSE_MISMATCH"); + } + const signed = custodyResponseSigningPayload(response); + if ( + !verify( + null, + Buffer.from(signed), + contract.verificationKey, + Buffer.from(response.signature, "base64") + ) + ) { + throw new Error("RUAM_CSH_CUSTODY_BAD_SIGNATURE"); + } + return Object.freeze({ + projection: unwrapProjection( + response.encodedProjection, + response.projectionOpening + ), + state: Object.freeze({ + epoch: response.nextEpoch, + lineageCommitment: response.nextLineageCommitment, + }), + }); +} + +export function custodyResponseSigningPayload( + response: Omit | CustodyResponse +): string { + return [ + response.sessionId, + response.contractId, + response.requestNonce, + response.epoch, + response.nextEpoch, + response.nextLineageCommitment, + response.encodedProjection, + response.projectionOpening.scale, + response.projectionOpening.offset, + response.projectionOpening.exponent, + response.projectionOpening.inverseExponent, + ].join("|"); +} + +function unwrapProjection( + stored: number, + transform: ChartCellTransform +): number { + return multiply( + subtract( + powerField(stored, transform.inverseExponent), + transform.offset + ), + inverseField(transform.scale) + ); +} + +function multiply(left: number, right: number): number { + return normalize(normalize(left) * normalize(right)); +} + +function subtract(left: number, right: number): number { + return normalize(normalize(left) - normalize(right)); +} + +function inverseField(value: number): number { + const normalized = normalize(value); + if (normalized === 0) throw new Error("RUAM_CSH_ZERO_HAS_NO_INVERSE"); + return powerField(normalized, CSH_FIELD_MODULUS - 2); +} + +function powerField(base: number, exponent: number): number { + let result = 1; + let factor = normalize(base); + let remaining = exponent; + while (remaining > 0) { + if (remaining % 2 === 1) result = multiply(result, factor); + factor = multiply(factor, factor); + remaining = Math.floor(remaining / 2); + } + return result; +} + +function normalize(value: number): number { + const normalized = Math.trunc(value) % CSH_FIELD_MODULUS; + return normalized < 0 ? normalized + CSH_FIELD_MODULUS : normalized; +} diff --git a/packages/ruam/src/isogloss/csh/reference-custodian.ts b/packages/ruam/src/isogloss/csh/reference-custodian.ts new file mode 100644 index 0000000..2ccacbc --- /dev/null +++ b/packages/ruam/src/isogloss/csh/reference-custodian.ts @@ -0,0 +1,246 @@ +/** + * Server-side direct relation evaluator for the experimental CSH spike. + * + * This module models a process/trust boundary. It must never be bundled into a + * client artifact. It is the direct-remote performance/control baseline that + * precedes a topology-hidden, actively secure PFE implementation. + * + * @module isogloss/csh/reference-custodian + */ + +import { + createHash, + generateKeyPairSync, + sign, +} from "node:crypto"; +import { + CSH_FIELD_MODULUS, + evaluateCertifiedProjection, + type ChartCellTransform, + type ChartCover, + type CertifiedProjectionSite, +} from "./reference.js"; +import { + custodyResponseSigningPayload, + type CustodyClientContract, + type CustodyRequest, + type CustodyResponse, +} from "./custody-protocol.js"; + +export interface CustodiedNonlinearRelation { + readonly projectionSite: CertifiedProjectionSite; + readonly cubic: number; + readonly linear: number; + readonly bias: number; +} + +export interface ReferenceCustodianOptions { + readonly sessionId: string; + readonly contractId: string; + readonly cover: ChartCover; + readonly initialLineageCommitment: string; + readonly relation: CustodiedNonlinearRelation; + readonly lineageSecret: string; + readonly responseSecret: string; +} + +/** + * Stateful one-session evaluator. A successful request consumes its epoch + * before another request can be evaluated, preventing snapshot-and-fork reuse. + */ +export class ReferenceRelationCustodian { + readonly clientContract: CustodyClientContract; + + readonly #cover: ChartCover; + readonly #relation: CustodiedNonlinearRelation; + readonly #lineageSecret: string; + readonly #responseSecret: string; + readonly #privateKey: ReturnType["privateKey"]; + readonly #consumedNonces = new Set(); + #epoch = 0; + #lineageCommitment: string; + + constructor(options: ReferenceCustodianOptions) { + const { privateKey, publicKey } = generateKeyPairSync("ed25519"); + this.#privateKey = privateKey; + this.#cover = options.cover; + this.#relation = options.relation; + this.#lineageSecret = options.lineageSecret; + this.#responseSecret = options.responseSecret; + this.#lineageCommitment = options.initialLineageCommitment; + this.clientContract = Object.freeze({ + sessionId: options.sessionId, + contractId: options.contractId, + coverId: options.cover.id, + initialLineageCommitment: options.initialLineageCommitment, + verificationKey: publicKey + .export({ type: "spki", format: "pem" }) + .toString(), + }); + } + + evaluate(request: CustodyRequest): CustodyResponse { + if ( + request.sessionId !== this.clientContract.sessionId || + request.contractId !== this.clientContract.contractId || + request.coverId !== this.#cover.id + ) { + throw new Error("RUAM_CSH_CUSTODY_REQUEST_MISMATCH"); + } + if (this.#consumedNonces.has(request.nonce)) { + throw new Error("RUAM_CSH_CUSTODY_REPLAY"); + } + if ( + request.epoch !== this.#epoch || + request.lineageCommitment !== this.#lineageCommitment + ) { + throw new Error("RUAM_CSH_CUSTODY_STALE_LINEAGE"); + } + + const inputProjection = evaluateCertifiedProjection( + request.charts, + this.#cover, + this.#relation.projectionSite + ); + const squared = multiply(inputProjection, inputProjection); + const cubed = multiply(squared, inputProjection); + const projected = add( + add( + multiply(this.#relation.cubic, cubed), + multiply(this.#relation.linear, inputProjection) + ), + this.#relation.bias + ); + const projectionOpening = deriveResponseTransform( + this.#responseSecret, + this.#epoch, + request.nonce + ); + const encodedProjection = wrapProjection( + projected, + projectionOpening + ); + const nextEpoch = this.#epoch + 1; + const nextLineageCommitment = createHash("sha256") + .update(this.#lineageSecret) + .update("|") + .update(this.#lineageCommitment) + .update("|") + .update(request.nonce) + .update("|") + .update(String(encodedProjection)) + .digest("hex"); + const unsigned = { + sessionId: request.sessionId, + contractId: request.contractId, + requestNonce: request.nonce, + epoch: this.#epoch, + nextEpoch, + nextLineageCommitment, + encodedProjection, + projectionOpening, + }; + const signature = sign( + null, + Buffer.from(custodyResponseSigningPayload(unsigned)), + this.#privateKey + ).toString("base64"); + + // Advance before returning so re-entrant/forked requests see new state. + this.#consumedNonces.add(request.nonce); + this.#epoch = nextEpoch; + this.#lineageCommitment = nextLineageCommitment; + return Object.freeze({ ...unsigned, signature }); + } +} + +function deriveResponseTransform( + secret: string, + epoch: number, + nonce: string +): ChartCellTransform { + const digest = createHash("sha256") + .update(secret) + .update("|") + .update(String(epoch)) + .update("|") + .update(nonce) + .digest(); + const scale = + 1 + (digest.readUInt32LE(0) % (CSH_FIELD_MODULUS - 1)); + const offset = digest.readUInt32LE(4) % CSH_FIELD_MODULUS; + const order = CSH_FIELD_MODULUS - 1; + let exponent = 3 + (digest.readUInt32LE(8) % (order - 3)); + while (greatestCommonDivisor(exponent, order) !== 1) { + exponent++; + if (exponent >= order) exponent = 3; + } + return Object.freeze({ + scale, + offset, + exponent, + inverseExponent: inverseInteger(exponent, order), + }); +} + +function wrapProjection( + raw: number, + transform: ChartCellTransform +): number { + return powerField( + add(multiply(transform.scale, raw), transform.offset), + transform.exponent + ); +} + +function add(left: number, right: number): number { + return normalize(normalize(left) + normalize(right)); +} + +function multiply(left: number, right: number): number { + return normalize(normalize(left) * normalize(right)); +} + +function powerField(base: number, exponent: number): number { + let result = 1; + let factor = normalize(base); + let remaining = exponent; + while (remaining > 0) { + if (remaining % 2 === 1) result = multiply(result, factor); + factor = multiply(factor, factor); + remaining = Math.floor(remaining / 2); + } + return result; +} + +function inverseInteger(value: number, modulus: number): number { + let oldR = normalizeFor(value, modulus); + let r = modulus; + let oldS = 1; + let s = 0; + while (r !== 0) { + const quotient = Math.floor(oldR / r); + [oldR, r] = [r, oldR - quotient * r]; + [oldS, s] = [s, oldS - quotient * s]; + } + if (oldR !== 1) { + throw new Error(`RUAM_CSH_NONINVERTIBLE_INTEGER: ${value}`); + } + return normalizeFor(oldS, modulus); +} + +function greatestCommonDivisor(left: number, right: number): number { + let a = Math.abs(left); + let b = Math.abs(right); + while (b !== 0) [a, b] = [b, a % b]; + return a; +} + +function normalize(value: number): number { + return normalizeFor(value, CSH_FIELD_MODULUS); +} + +function normalizeFor(value: number, modulus: number): number { + const normalized = Math.trunc(value) % modulus; + return normalized < 0 ? normalized + modulus : normalized; +} diff --git a/packages/ruam/src/isogloss/csh/reference.ts b/packages/ruam/src/isogloss/csh/reference.ts new file mode 100644 index 0000000..6b4510e --- /dev/null +++ b/packages/ruam/src/isogloss/csh/reference.ts @@ -0,0 +1,745 @@ +/** + * Experimental moving-cover semantic holography reference model. + * + * This module exists to validate the local CSH mechanics before any production + * artifact or runtime schema is frozen. A chart stores a non-linearly wrapped + * polynomial evaluation of a mixed logical state. No chart contains an + * independently decodable source value, and transitions transport directly + * from incoming chart cells into the next cover without constructing a global + * logical frame. + * + * The model is intentionally limited to fixed-width field vectors and affine + * transitions. BPRF supplies the non-affine regional realizations around this + * transport layer. Do not export this module from the public package entry + * point until the dynamic-attacker gates have passed. + * + * @module isogloss/csh/reference + */ + +import { deriveSeed } from "../../naming/scope.js"; +import { createSeededRandom } from "../../random/entropy.js"; + +/** Largest 16-bit prime; products of field elements remain exact JS integers. */ +export const CSH_FIELD_MODULUS = 65_521; + +export interface ChartCellTransform { + readonly scale: number; + readonly offset: number; + readonly exponent: number; + readonly inverseExponent: number; +} + +export interface ChartDescriptor { + readonly id: string; + readonly owner: string; + readonly point: number; + readonly cells: readonly ChartCellTransform[]; + readonly overlaps: readonly string[]; +} + +export interface ChartCover { + readonly id: string; + readonly epoch: number; + readonly width: number; + readonly threshold: number; + readonly mixing: readonly (readonly number[])[]; + readonly bias: readonly number[]; + readonly charts: readonly ChartDescriptor[]; +} + +export interface EncodedChart { + readonly coverId: string; + readonly epoch: number; + readonly chartId: string; + readonly cells: readonly number[]; +} + +export interface AffineChartTransition { + readonly id: string; + readonly matrix: readonly (readonly number[])[]; + readonly bias: readonly number[]; +} + +export interface CertifiedProjectionSite { + readonly id: string; + readonly coefficients: readonly number[]; + readonly bias: number; +} + +export interface CreateChartCoverOptions { + readonly seed: number; + readonly epoch: number; + readonly width: number; + readonly chartCount?: number; + readonly threshold?: number; +} + +/** + * Create one deterministic cover. The default spike profile is five charts + * with threshold three, as required by the D3 local-holography gate. + */ +export function createChartCover( + options: CreateChartCoverOptions +): ChartCover { + const { + seed, + epoch, + width, + chartCount = 5, + threshold = 3, + } = options; + if (!Number.isSafeInteger(epoch) || epoch < 0) { + throw new Error(`RUAM_CSH_INVALID_EPOCH: ${epoch}`); + } + if (!Number.isSafeInteger(width) || width < 2) { + throw new Error(`RUAM_CSH_INVALID_WIDTH: ${width}`); + } + if (!Number.isSafeInteger(chartCount) || chartCount < 5) { + throw new Error(`RUAM_CSH_INSUFFICIENT_CHARTS: ${chartCount}`); + } + if ( + !Number.isSafeInteger(threshold) || + threshold < 3 || + threshold > chartCount + ) { + throw new Error(`RUAM_CSH_INVALID_THRESHOLD: ${threshold}`); + } + + const random = createSeededRandom( + deriveSeed(seed >>> 0, `csh-cover:${epoch}:${width}:${chartCount}:${threshold}`) + ); + const mixing = createInvertibleMatrix(width, random); + const bias = vector(width, () => randomField(random)); + const points = uniqueNonzeroPoints(chartCount, random); + const coverTag = random.nextUint32().toString(36); + const ids = points.map((_, index) => `c${epoch}_${index}_${coverTag}`); + const charts = points.map((point, index): ChartDescriptor => { + const cells = vector(width, (): ChartCellTransform => { + const exponent = randomPermutationExponent(random); + return freeze({ + scale: randomNonzeroField(random), + offset: randomField(random), + exponent, + inverseExponent: inverseInteger(exponent, CSH_FIELD_MODULUS - 1), + }); + }); + return freeze({ + id: ids[index]!, + owner: `o${(index * 2 + epoch + 1) % chartCount}`, + point, + cells: freeze(cells), + overlaps: freeze(ids.filter((_, other) => other !== index)), + }); + }); + + return freeze({ + id: `cover_${epoch}_${coverTag}`, + epoch, + width, + threshold, + mixing: freezeMatrix(mixing), + bias: freeze(bias), + charts: freeze(charts), + }); +} + +/** + * Encode an observable ingress vector into one cover. + * + * This is an ingress-only reference helper. Regional transitions must use + * {@link transportAffineCharts}, which never reconstructs the logical vector. + */ +export function encodeIngressCharts( + logicalValues: readonly number[], + cover: ChartCover, + seed: number +): readonly EncodedChart[] { + validateCover(cover); + assertVectorWidth(logicalValues, cover.width, "INGRESS"); + const constant = addVectors( + multiplyMatrixVector(cover.mixing, logicalValues), + cover.bias + ); + const residuals = createResiduals( + cover.width, + cover.threshold, + deriveSeed(seed >>> 0, `csh-ingress:${cover.id}`) + ); + const charts = cover.charts.map((descriptor): EncodedChart => { + const cells = vector(cover.width, (lane) => { + const raw = evaluatePolynomial( + constant[lane]!, + residuals[lane]!, + descriptor.point + ); + return wrapCell(raw, descriptor.cells[lane]!); + }); + return freeze({ + coverId: cover.id, + epoch: cover.epoch, + chartId: descriptor.id, + cells: freeze(cells), + }); + }); + return freeze(charts); +} + +/** + * Transport an affine logical transition into a fresh cover. + * + * For x' = A*x+c and encoded constants s=M*x+b, this composes the public + * cover relation s'=M'*A*M^-1*s+d. Each output cell accumulates weighted + * contributions from incoming chart cells directly; no s or x vector is ever + * assembled. + */ +export function transportAffineCharts( + incoming: readonly EncodedChart[], + from: ChartCover, + to: ChartCover, + transition: AffineChartTransition, + seed: number +): readonly EncodedChart[] { + validateCover(from); + validateCover(to); + if (from.id === to.id || from.epoch === to.epoch) { + throw new Error("RUAM_CSH_COVER_DID_NOT_CHANGE"); + } + if (from.width !== to.width) { + throw new Error( + `RUAM_CSH_WIDTH_MISMATCH: ${from.width} -> ${to.width}` + ); + } + assertMatrixShape(transition.matrix, from.width, "TRANSITION"); + assertVectorWidth(transition.bias, from.width, "TRANSITION_BIAS"); + const selected = validateAndSelectCharts(incoming, from); + const weights = interpolationWeightsAtZero( + selected.map(({ descriptor }) => descriptor.point) + ); + const inverseFromMixing = invertMatrix(from.mixing); + const secretTransform = multiplyMatrices( + multiplyMatrices(to.mixing, transition.matrix), + inverseFromMixing + ); + const transformedOldBias = multiplyMatrixVector( + secretTransform, + from.bias + ); + const secretBias = subtractVectors( + addVectors( + multiplyMatrixVector(to.mixing, transition.bias), + to.bias + ), + transformedOldBias + ); + const residuals = createResiduals( + to.width, + to.threshold, + deriveSeed( + seed >>> 0, + `csh-transport:${transition.id}:${from.id}:${to.id}` + ) + ); + + const transported = to.charts.map((target): EncodedChart => { + const cells = vector(to.width, (outputLane) => { + let rawOutput = add( + secretBias[outputLane]!, + evaluatePolynomial(0, residuals[outputLane]!, target.point) + ); + for (let chartIndex = 0; chartIndex < selected.length; chartIndex++) { + const { chart, descriptor } = selected[chartIndex]!; + const chartWeight = weights[chartIndex]!; + for (let inputLane = 0; inputLane < from.width; inputLane++) { + const rawInput = unwrapCell( + chart.cells[inputLane]!, + descriptor.cells[inputLane]! + ); + rawOutput = add( + rawOutput, + multiply( + multiply( + secretTransform[outputLane]![inputLane]!, + chartWeight + ), + rawInput + ) + ); + } + } + return wrapCell(rawOutput, target.cells[outputLane]!); + }); + return freeze({ + coverId: to.id, + epoch: to.epoch, + chartId: target.id, + cells: freeze(cells), + }); + }); + return freeze(transported); +} + +/** + * Materialize one declared scalar effect/return projection. + * + * The implementation fuses interpolation, inverse mixing, and projection into + * a scalar accumulator. It never returns or internally constructs a complete + * logical vector. + */ +export function evaluateCertifiedProjection( + incoming: readonly EncodedChart[], + cover: ChartCover, + site: CertifiedProjectionSite +): number { + validateCover(cover); + assertVectorWidth(site.coefficients, cover.width, "PROJECTION"); + const selected = validateAndSelectCharts(incoming, cover); + const weights = interpolationWeightsAtZero( + selected.map(({ descriptor }) => descriptor.point) + ); + const inverseMixing = invertMatrix(cover.mixing); + const secretCoefficients = multiplyRowByMatrix( + site.coefficients, + inverseMixing + ); + let projected = subtract( + normalize(site.bias), + dot(secretCoefficients, cover.bias) + ); + for (let chartIndex = 0; chartIndex < selected.length; chartIndex++) { + const { chart, descriptor } = selected[chartIndex]!; + const chartWeight = weights[chartIndex]!; + for (let lane = 0; lane < cover.width; lane++) { + const raw = unwrapCell( + chart.cells[lane]!, + descriptor.cells[lane]! + ); + projected = add( + projected, + multiply( + multiply(secretCoefficients[lane]!, chartWeight), + raw + ) + ); + } + } + return projected; +} + +/** Verify structural invariants without deriving a logical value. */ +export function validateCover(cover: ChartCover): void { + if (cover.width < 2 || cover.charts.length < 5) { + throw new Error("RUAM_CSH_INVALID_COVER_SHAPE"); + } + if (cover.threshold < 3 || cover.threshold > cover.charts.length) { + throw new Error("RUAM_CSH_INVALID_COVER_THRESHOLD"); + } + assertMatrixShape(cover.mixing, cover.width, "COVER_MIXING"); + assertVectorWidth(cover.bias, cover.width, "COVER_BIAS"); + invertMatrix(cover.mixing); + const ids = new Set(); + const points = new Set(); + for (const chart of cover.charts) { + if ( + chart.cells.length !== cover.width || + chart.point === 0 || + chart.point >= CSH_FIELD_MODULUS + ) { + throw new Error(`RUAM_CSH_INVALID_CHART: ${chart.id}`); + } + if (ids.has(chart.id) || points.has(chart.point)) { + throw new Error(`RUAM_CSH_DUPLICATE_CHART: ${chart.id}`); + } + ids.add(chart.id); + points.add(chart.point); + for (const cell of chart.cells) { + if ( + cell.scale === 0 || + multiply( + cell.exponent, + cell.inverseExponent, + CSH_FIELD_MODULUS - 1 + ) !== 1 + ) { + throw new Error(`RUAM_CSH_INVALID_CELL_TRANSFORM: ${chart.id}`); + } + } + } + for (const chart of cover.charts) { + const expected = cover.charts + .filter((candidate) => candidate.id !== chart.id) + .map((candidate) => candidate.id); + if ( + chart.overlaps.length !== expected.length || + expected.some((id) => !chart.overlaps.includes(id)) + ) { + throw new Error(`RUAM_CSH_INVALID_OVERLAP_COVER: ${chart.id}`); + } + } +} + +interface SelectedChart { + readonly chart: EncodedChart; + readonly descriptor: ChartDescriptor; +} + +function validateAndSelectCharts( + incoming: readonly EncodedChart[], + cover: ChartCover +): SelectedChart[] { + if (incoming.length < cover.threshold) { + throw new Error( + `RUAM_CSH_THRESHOLD_NOT_MET: ${incoming.length}/${cover.threshold}` + ); + } + const descriptors = new Map( + cover.charts.map((descriptor) => [descriptor.id, descriptor]) + ); + const seen = new Set(); + return incoming.map((chart): SelectedChart => { + if (chart.coverId !== cover.id || chart.epoch !== cover.epoch) { + throw new Error(`RUAM_CSH_WRONG_COVER: ${chart.chartId}`); + } + if (seen.has(chart.chartId)) { + throw new Error(`RUAM_CSH_DUPLICATE_CONTRIBUTION: ${chart.chartId}`); + } + seen.add(chart.chartId); + const descriptor = descriptors.get(chart.chartId); + if (!descriptor || chart.cells.length !== cover.width) { + throw new Error(`RUAM_CSH_UNKNOWN_CHART: ${chart.chartId}`); + } + return { chart, descriptor }; + }); +} + +function createResiduals( + width: number, + threshold: number, + seed: number +): number[][] { + const random = createSeededRandom(seed); + return vector(width, () => + vector(threshold - 1, () => randomField(random)) + ); +} + +function createInvertibleMatrix( + width: number, + random: ReturnType +): number[][] { + for (let attempt = 0; attempt < 128; attempt++) { + const candidate = vector(width, () => + vector(width, () => randomField(random)) + ); + try { + invertMatrix(candidate); + return candidate; + } catch { + // Deterministically continue the stream until a nonsingular basis. + } + } + throw new Error("RUAM_CSH_COULD_NOT_BUILD_MIXING_BASIS"); +} + +function uniqueNonzeroPoints( + count: number, + random: ReturnType +): number[] { + const points: number[] = []; + const seen = new Set(); + while (points.length < count) { + const point = randomNonzeroField(random); + if (!seen.has(point)) { + seen.add(point); + points.push(point); + } + } + return points; +} + +function randomPermutationExponent( + random: ReturnType +): number { + const order = CSH_FIELD_MODULUS - 1; + for (;;) { + const candidate = 3 + (random.nextUint32() % (order - 3)); + if (greatestCommonDivisor(candidate, order) === 1) return candidate; + } +} + +function interpolationWeightsAtZero(points: readonly number[]): number[] { + if (new Set(points).size !== points.length) { + throw new Error("RUAM_CSH_DUPLICATE_INTERPOLATION_POINT"); + } + return points.map((point, index) => { + let numerator = 1; + let denominator = 1; + for (let other = 0; other < points.length; other++) { + if (other === index) continue; + numerator = multiply(numerator, subtract(0, points[other]!)); + denominator = multiply( + denominator, + subtract(point, points[other]!) + ); + } + return multiply(numerator, inverseField(denominator)); + }); +} + +function evaluatePolynomial( + constant: number, + coefficients: readonly number[], + point: number +): number { + let value = normalize(constant); + let power = normalize(point); + for (const coefficient of coefficients) { + value = add(value, multiply(coefficient, power)); + power = multiply(power, point); + } + return value; +} + +function wrapCell(raw: number, transform: ChartCellTransform): number { + return powerField( + add(multiply(transform.scale, raw), transform.offset), + transform.exponent + ); +} + +function unwrapCell(stored: number, transform: ChartCellTransform): number { + return multiply( + subtract( + powerField(stored, transform.inverseExponent), + transform.offset + ), + inverseField(transform.scale) + ); +} + +function multiplyMatrices( + left: readonly (readonly number[])[], + right: readonly (readonly number[])[] +): number[][] { + const width = left.length; + assertMatrixShape(left, width, "LEFT_MATRIX"); + assertMatrixShape(right, width, "RIGHT_MATRIX"); + return vector(width, (row) => + vector(width, (column) => { + let value = 0; + for (let inner = 0; inner < width; inner++) { + value = add( + value, + multiply(left[row]![inner]!, right[inner]![column]!) + ); + } + return value; + }) + ); +} + +function multiplyMatrixVector( + matrix: readonly (readonly number[])[], + input: readonly number[] +): number[] { + assertMatrixShape(matrix, input.length, "MATRIX_VECTOR"); + return matrix.map((row) => dot(row, input)); +} + +function multiplyRowByMatrix( + row: readonly number[], + matrix: readonly (readonly number[])[] +): number[] { + assertMatrixShape(matrix, row.length, "ROW_MATRIX"); + return vector(row.length, (column) => { + let value = 0; + for (let inner = 0; inner < row.length; inner++) { + value = add( + value, + multiply(row[inner]!, matrix[inner]![column]!) + ); + } + return value; + }); +} + +function invertMatrix( + input: readonly (readonly number[])[] +): number[][] { + const width = input.length; + assertMatrixShape(input, width, "INVERSE"); + const augmented = input.map((row, rowIndex) => [ + ...row.map(normalize), + ...vector(width, (column) => (rowIndex === column ? 1 : 0)), + ]); + for (let column = 0; column < width; column++) { + let pivot = column; + while (pivot < width && augmented[pivot]![column] === 0) pivot++; + if (pivot === width) throw new Error("RUAM_CSH_SINGULAR_MIXING_BASIS"); + [augmented[column], augmented[pivot]] = [ + augmented[pivot]!, + augmented[column]!, + ]; + const pivotInverse = inverseField(augmented[column]![column]!); + for (let index = 0; index < width * 2; index++) { + augmented[column]![index] = multiply( + augmented[column]![index]!, + pivotInverse + ); + } + for (let row = 0; row < width; row++) { + if (row === column) continue; + const factor = augmented[row]![column]!; + for (let index = 0; index < width * 2; index++) { + augmented[row]![index] = subtract( + augmented[row]![index]!, + multiply(factor, augmented[column]![index]!) + ); + } + } + } + return augmented.map((row) => row.slice(width)); +} + +function addVectors( + left: readonly number[], + right: readonly number[] +): number[] { + assertVectorWidth(right, left.length, "VECTOR_ADD"); + return left.map((value, index) => add(value, right[index]!)); +} + +function subtractVectors( + left: readonly number[], + right: readonly number[] +): number[] { + assertVectorWidth(right, left.length, "VECTOR_SUBTRACT"); + return left.map((value, index) => subtract(value, right[index]!)); +} + +function dot(left: readonly number[], right: readonly number[]): number { + assertVectorWidth(right, left.length, "DOT"); + let value = 0; + for (let index = 0; index < left.length; index++) { + value = add(value, multiply(left[index]!, right[index]!)); + } + return value; +} + +function assertMatrixShape( + matrix: readonly (readonly number[])[], + width: number, + label: string +): void { + if ( + matrix.length !== width || + matrix.some((row) => row.length !== width) + ) { + throw new Error(`RUAM_CSH_INVALID_${label}_SHAPE`); + } +} + +function assertVectorWidth( + values: readonly number[], + width: number, + label: string +): void { + if (values.length !== width) { + throw new Error( + `RUAM_CSH_INVALID_${label}_WIDTH: ${values.length}/${width}` + ); + } +} + +function randomField( + random: ReturnType +): number { + return random.nextUint32() % CSH_FIELD_MODULUS; +} + +function randomNonzeroField( + random: ReturnType +): number { + return 1 + (random.nextUint32() % (CSH_FIELD_MODULUS - 1)); +} + +function add(left: number, right: number): number { + return normalize(normalize(left) + normalize(right)); +} + +function subtract(left: number, right: number): number { + return normalize(normalize(left) - normalize(right)); +} + +function multiply( + left: number, + right: number, + modulus = CSH_FIELD_MODULUS +): number { + const value = (normalizeFor(left, modulus) * normalizeFor(right, modulus)) % + modulus; + return value < 0 ? value + modulus : value; +} + +function inverseField(value: number): number { + const normalized = normalize(value); + if (normalized === 0) throw new Error("RUAM_CSH_ZERO_HAS_NO_INVERSE"); + return powerField(normalized, CSH_FIELD_MODULUS - 2); +} + +function powerField(base: number, exponent: number): number { + let result = 1; + let factor = normalize(base); + let remaining = exponent; + while (remaining > 0) { + if (remaining % 2 === 1) result = multiply(result, factor); + factor = multiply(factor, factor); + remaining = Math.floor(remaining / 2); + } + return result; +} + +function inverseInteger(value: number, modulus: number): number { + let oldR = normalizeFor(value, modulus); + let r = modulus; + let oldS = 1; + let s = 0; + while (r !== 0) { + const quotient = Math.floor(oldR / r); + [oldR, r] = [r, oldR - quotient * r]; + [oldS, s] = [s, oldS - quotient * s]; + } + if (oldR !== 1) { + throw new Error(`RUAM_CSH_NONINVERTIBLE_INTEGER: ${value}`); + } + return normalizeFor(oldS, modulus); +} + +function greatestCommonDivisor(left: number, right: number): number { + let a = Math.abs(left); + let b = Math.abs(right); + while (b !== 0) [a, b] = [b, a % b]; + return a; +} + +function normalize(value: number): number { + return normalizeFor(value, CSH_FIELD_MODULUS); +} + +function normalizeFor(value: number, modulus: number): number { + const normalized = Math.trunc(value) % modulus; + return normalized < 0 ? normalized + modulus : normalized; +} + +function vector(length: number, create: (index: number) => T): T[] { + return Array.from({ length }, (_, index) => create(index)); +} + +function freeze(value: T): Readonly { + return Object.freeze(value); +} + +function freezeMatrix( + matrix: readonly (readonly number[])[] +): readonly (readonly number[])[] { + return freeze(matrix.map((row) => freeze([...row]))); +} diff --git a/packages/ruam/src/isogloss/csh/testing-bprf-reference.ts b/packages/ruam/src/isogloss/csh/testing-bprf-reference.ts new file mode 100644 index 0000000..8aa80fe --- /dev/null +++ b/packages/ruam/src/isogloss/csh/testing-bprf-reference.ts @@ -0,0 +1,503 @@ +/** + * NON-PRODUCT BPRF/CSH INTEGRATION EVALUATOR. + * + * This differential-test evaluator executes BPRF polynomial fragments over + * chart-local shares. A multiplication raises the sharing polynomial from + * degree two to degree four; five chart contributors then reduce it directly + * into a fresh degree-two sharing under a new cover. No complete BPRF frame is + * reconstructed at a transition. + * + * Product execution must emit specialized chart-local codelets. This generic + * evaluator must never ship in a client artifact. + * + * @module isogloss/csh/testing-bprf-reference + */ + +import { deriveSeed } from "../../naming/scope.js"; +import { createSeededRandom } from "../../random/entropy.js"; +import type { + BprfArtifact, + BprfCallerContext, + BprfPiece, + BprfRealization, + PureScalar, +} from "../bprf/index.js"; +import { validateBprfArtifact } from "../bprf/index.js"; +import { selectBprfRealization } from "../bprf/testing-reference.js"; +import { + CSH_FIELD_MODULUS, + encodeIngressCharts, + evaluateCertifiedProjection, + validateCover, + type ChartCellTransform, + type ChartCover, + type EncodedChart, +} from "./reference.js"; + +export interface BprfCshTraceEvent { + readonly realization: string; + readonly transition: string; + readonly fragment: string; + readonly chart: string; + readonly fromCover: string; + readonly toCover: string; + readonly phase: number; +} + +export interface BprfCshReferenceResult { + readonly outputs: readonly PureScalar[]; + readonly charts: readonly EncodedChart[]; + readonly realization: string; + readonly trace: readonly BprfCshTraceEvent[]; +} + +/** + * Evaluate one bounded integer/boolean BPRF realization over moving CSH + * covers. There must be one ingress cover plus one new cover per transition. + */ +export function evaluateBprfOverCshReference( + artifact: BprfArtifact, + inputs: readonly PureScalar[], + context: BprfCallerContext, + covers: readonly ChartCover[], + seed: number +): BprfCshReferenceResult { + validateBprfArtifact(artifact); + const realization = selectBprfRealization(artifact, context); + validateIntegrationInputs(realization, inputs, covers); + const ingressFrame = createIngressFrame(realization, inputs); + let charts = encodeIngressCharts( + ingressFrame, + covers[0]!, + deriveSeed(seed >>> 0, `bprf-csh-ingress:${realization.id}`) + ); + const trace: BprfCshTraceEvent[] = []; + for ( + let transitionIndex = 0; + transitionIndex < realization.transitions.length; + transitionIndex++ + ) { + const transition = realization.transitions[transitionIndex]!; + const from = covers[transitionIndex]!; + const to = covers[transitionIndex + 1]!; + const transported = transportBprfTransition( + realization, + transition.phase, + charts, + from, + to, + deriveSeed( + seed >>> 0, + `bprf-csh-transition:${realization.id}:${transition.id}` + ) + ); + charts = transported.charts; + for (const event of transported.trace) trace.push(event); + } + + const finalCover = covers[covers.length - 1]!; + const outputs = realization.outputPorts.map((port, outputIndex) => { + const inverseScale = inverse(port.basis.scale); + const coefficients = Array.from( + { length: realization.frameSize }, + (_, slot) => (slot === port.slot ? inverseScale : 0) + ); + const coordinate = evaluateCertifiedProjection(charts, finalCover, { + id: `exit_${outputIndex}`, + coefficients, + bias: subtract(0, multiply(port.basis.bias, inverseScale)), + }); + if (port.typeCode === 0) return fromSignedField(coordinate); + if (realization.familyCode === 0) return coordinate !== 0; + return fromSignedField(coordinate) > 0; + }); + return Object.freeze({ + outputs: Object.freeze(outputs), + charts, + realization: realization.id, + trace: Object.freeze(trace), + }); +} + +function validateIntegrationInputs( + realization: BprfRealization, + inputs: readonly PureScalar[], + covers: readonly ChartCover[] +): void { + if (inputs.length !== realization.inputPorts.length) { + throw new Error("RUAM_BPRF_CSH_INPUT_ARITY_MISMATCH"); + } + if (covers.length !== realization.transitions.length + 1) { + throw new Error("RUAM_BPRF_CSH_COVER_COUNT_MISMATCH"); + } + const ids = new Set(); + for (const cover of covers) { + validateCover(cover); + if (cover.width !== realization.frameSize) { + throw new Error("RUAM_BPRF_CSH_COVER_WIDTH_MISMATCH"); + } + if (cover.charts.length < cover.threshold * 2 - 1) { + throw new Error("RUAM_BPRF_CSH_MULTIPLICATION_QUORUM_TOO_SMALL"); + } + if (ids.has(cover.id)) { + throw new Error("RUAM_BPRF_CSH_COVER_DID_NOT_CHANGE"); + } + ids.add(cover.id); + } +} + +function createIngressFrame( + realization: BprfRealization, + inputs: readonly PureScalar[] +): number[] { + const frame = Array.from({ length: realization.frameSize }, () => 0); + for (let index = 0; index < inputs.length; index++) { + const input = inputs[index]!; + const port = realization.inputPorts[index]!; + let coordinate: number; + if (port.typeCode === 0) { + if ( + typeof input !== "number" || + !Number.isSafeInteger(input) || + Math.abs(input) >= CSH_FIELD_MODULUS / 4 + ) { + throw new Error("RUAM_BPRF_CSH_BOUNDED_INTEGER_REQUIRED"); + } + coordinate = normalize(input); + } else { + if (typeof input !== "boolean") { + throw new Error("RUAM_BPRF_CSH_BOOLEAN_REQUIRED"); + } + coordinate = + realization.familyCode === 0 + ? input + ? 1 + : 0 + : input + ? 1 + : normalize(-1); + } + frame[port.slot] = add( + multiply(coordinate, port.basis.scale), + port.basis.bias + ); + } + return frame; +} + +function transportBprfTransition( + realization: BprfRealization, + phase: number, + incoming: readonly EncodedChart[], + from: ChartCover, + to: ChartCover, + seed: number +): { + charts: readonly EncodedChart[]; + trace: readonly BprfCshTraceEvent[]; +} { + if ( + incoming.length !== from.charts.length || + incoming.length < from.threshold * 2 - 1 + ) { + throw new Error( + `RUAM_BPRF_CSH_MULTIPLICATION_QUORUM: ${incoming.length}/${from.charts.length}` + ); + } + if (from.id === to.id) { + throw new Error("RUAM_BPRF_CSH_COVER_DID_NOT_CHANGE"); + } + const descriptorById = new Map( + from.charts.map((descriptor) => [descriptor.id, descriptor]) + ); + const seen = new Set(); + const selected = incoming.map((chart) => { + if (chart.coverId !== from.id || chart.epoch !== from.epoch) { + throw new Error(`RUAM_BPRF_CSH_WRONG_COVER: ${chart.chartId}`); + } + if (seen.has(chart.chartId)) { + throw new Error( + `RUAM_BPRF_CSH_DUPLICATE_CHART: ${chart.chartId}` + ); + } + seen.add(chart.chartId); + const descriptor = descriptorById.get(chart.chartId); + if (!descriptor) { + throw new Error(`RUAM_BPRF_CSH_UNKNOWN_CHART: ${chart.chartId}`); + } + return { chart, descriptor }; + }); + const inverseMixing = invertMatrix(from.mixing); + const localFrameShares = selected.map(({ chart, descriptor }) => { + const mixedShares = chart.cells.map((cell, lane) => + unwrapCell(cell, descriptor.cells[lane]!) + ); + return multiplyMatrixVector( + inverseMixing, + mixedShares.map((value, lane) => subtract(value, from.bias[lane]!)) + ); + }); + const transition = realization.transitions[phase]; + if (!transition || transition.phase !== phase) { + throw new Error("RUAM_BPRF_CSH_UNKNOWN_TRANSITION_PHASE"); + } + const participating = realization.fragments.filter((fragment) => + fragment.pieces.some((piece) => piece.phase === phase) + ); + const trace: BprfCshTraceEvent[] = []; + const nextFrameShares = localFrameShares.map( + (frameShare, chartIndex): number[] => { + const next = frameShare.slice(); + for (const destination of transition.writes) { + const pieces = participating.flatMap((fragment) => + fragment.pieces.filter( + (piece) => + piece.phase === phase && + piece.destination === destination + ) + ); + if (pieces.length === 0) { + throw new Error("RUAM_BPRF_CSH_MISSING_DESTINATION"); + } + let coordinateShare = 0; + for (const piece of pieces) { + coordinateShare = add( + coordinateShare, + evaluatePieceShare(piece, frameShare) + ); + } + const basis = pieces[0]!.destinationBasis; + next[destination] = add( + multiply(coordinateShare, basis.scale), + basis.bias + ); + } + for (const fragment of participating) { + trace.push( + Object.freeze({ + realization: realization.id, + transition: transition.id, + fragment: fragment.id, + chart: selected[chartIndex]!.descriptor.id, + fromCover: from.id, + toCover: to.id, + phase, + }) + ); + } + return next; + } + ); + + const sourceWeights = interpolationWeightsAtZero( + selected.map(({ descriptor }) => descriptor.point) + ); + const random = createSeededRandom(seed); + const residuals = selected.map(() => + Array.from({ length: to.width }, () => + Array.from({ length: to.threshold - 1 }, () => + random.nextUint32() % CSH_FIELD_MODULUS + ) + ) + ); + const charts = to.charts.map((target): EncodedChart => { + const cells = Array.from({ length: to.width }, (_, mixedLane) => { + let reducedShare = 0; + for ( + let sourceIndex = 0; + sourceIndex < selected.length; + sourceIndex++ + ) { + const sourceFrame = nextFrameShares[sourceIndex]!; + let mixedEvaluation = to.bias[mixedLane]!; + for (let frameLane = 0; frameLane < to.width; frameLane++) { + mixedEvaluation = add( + mixedEvaluation, + multiply( + to.mixing[mixedLane]![frameLane]!, + sourceFrame[frameLane]! + ) + ); + } + let contribution = multiply( + sourceWeights[sourceIndex]!, + mixedEvaluation + ); + let pointPower = target.point; + for (const residual of residuals[sourceIndex]![mixedLane]!) { + contribution = add( + contribution, + multiply(residual, pointPower) + ); + pointPower = multiply(pointPower, target.point); + } + reducedShare = add(reducedShare, contribution); + } + return wrapCell(reducedShare, target.cells[mixedLane]!); + }); + return Object.freeze({ + coverId: to.id, + epoch: to.epoch, + chartId: target.id, + cells: Object.freeze(cells), + }); + }); + return { + charts: Object.freeze(charts), + trace: Object.freeze(trace), + }; +} + +function evaluatePieceShare( + piece: BprfPiece, + frameShare: readonly number[] +): number { + let value = coefficientToField(piece.coefficient); + for (const factor of piece.factors) { + const basisInverse = inverse(factor.basis.scale); + const coordinateShare = multiply( + subtract(frameShare[factor.slot]!, factor.basis.bias), + basisInverse + ); + value = multiply(value, add(coordinateShare, factor.offset)); + } + return value; +} + +function coefficientToField(value: number): number { + if (Number.isSafeInteger(value)) return normalize(value); + const doubled = value * 2; + if (Number.isSafeInteger(doubled)) { + return multiply(normalize(doubled), inverse(2)); + } + throw new Error(`RUAM_BPRF_CSH_UNSUPPORTED_COEFFICIENT: ${value}`); +} + +function interpolationWeightsAtZero(points: readonly number[]): number[] { + if (new Set(points).size !== points.length) { + throw new Error("RUAM_BPRF_CSH_DUPLICATE_POINT"); + } + return points.map((point, index) => { + let numerator = 1; + let denominator = 1; + for (let other = 0; other < points.length; other++) { + if (other === index) continue; + numerator = multiply(numerator, subtract(0, points[other]!)); + denominator = multiply( + denominator, + subtract(point, points[other]!) + ); + } + return multiply(numerator, inverse(denominator)); + }); +} + +function multiplyMatrixVector( + matrix: readonly (readonly number[])[], + input: readonly number[] +): number[] { + return matrix.map((row) => { + let value = 0; + for (let column = 0; column < row.length; column++) { + value = add(value, multiply(row[column]!, input[column]!)); + } + return value; + }); +} + +function invertMatrix( + input: readonly (readonly number[])[] +): number[][] { + const width = input.length; + const augmented = input.map((row, rowIndex) => [ + ...row.map(normalize), + ...Array.from({ length: width }, (_, column) => + rowIndex === column ? 1 : 0 + ), + ]); + for (let column = 0; column < width; column++) { + let pivot = column; + while (pivot < width && augmented[pivot]![column] === 0) pivot++; + if (pivot === width) throw new Error("RUAM_BPRF_CSH_SINGULAR_COVER"); + [augmented[column], augmented[pivot]] = [ + augmented[pivot]!, + augmented[column]!, + ]; + const pivotInverse = inverse(augmented[column]![column]!); + for (let index = 0; index < width * 2; index++) { + augmented[column]![index] = multiply( + augmented[column]![index]!, + pivotInverse + ); + } + for (let row = 0; row < width; row++) { + if (row === column) continue; + const factor = augmented[row]![column]!; + for (let index = 0; index < width * 2; index++) { + augmented[row]![index] = subtract( + augmented[row]![index]!, + multiply(factor, augmented[column]![index]!) + ); + } + } + } + return augmented.map((row) => row.slice(width)); +} + +function wrapCell(raw: number, transform: ChartCellTransform): number { + return power( + add(multiply(transform.scale, raw), transform.offset), + transform.exponent + ); +} + +function unwrapCell( + stored: number, + transform: ChartCellTransform +): number { + return multiply( + subtract(power(stored, transform.inverseExponent), transform.offset), + inverse(transform.scale) + ); +} + +function inverse(value: number): number { + const normalized = normalize(value); + if (normalized === 0) throw new Error("RUAM_BPRF_CSH_ZERO_INVERSE"); + return power(normalized, CSH_FIELD_MODULUS - 2); +} + +function power(base: number, exponent: number): number { + let result = 1; + let factor = normalize(base); + let remaining = exponent; + while (remaining > 0) { + if (remaining % 2 === 1) result = multiply(result, factor); + factor = multiply(factor, factor); + remaining = Math.floor(remaining / 2); + } + return result; +} + +function add(left: number, right: number): number { + return normalize(normalize(left) + normalize(right)); +} + +function subtract(left: number, right: number): number { + return normalize(normalize(left) - normalize(right)); +} + +function multiply(left: number, right: number): number { + return normalize(normalize(left) * normalize(right)); +} + +function normalize(value: number): number { + const normalized = Math.trunc(value) % CSH_FIELD_MODULUS; + return normalized < 0 ? normalized + CSH_FIELD_MODULUS : normalized; +} + +function fromSignedField(value: number): number { + return value > CSH_FIELD_MODULUS / 2 + ? value - CSH_FIELD_MODULUS + : value; +} diff --git a/packages/ruam/test/isogloss/bprf-csh-integration.test.ts b/packages/ruam/test/isogloss/bprf-csh-integration.test.ts new file mode 100644 index 0000000..7f26d58 --- /dev/null +++ b/packages/ruam/test/isogloss/bprf-csh-integration.test.ts @@ -0,0 +1,182 @@ +import { describe, expect, it } from "bun:test"; +import { + generateBprfArtifact, + type PureRegionContract, +} from "../../src/isogloss/bprf/index.js"; +import { + evaluateBprfReference, + selectBprfRealization, +} from "../../src/isogloss/bprf/testing-reference.js"; +import { evaluateBprfOverCshReference } from "../../src/isogloss/csh/testing-bprf-reference.js"; +import { createChartCover } from "../../src/isogloss/csh/reference.js"; + +const contract: PureRegionContract = { + inputs: [ + { type: "number" }, + { type: "number" }, + { type: "boolean" }, + ], + steps: [ + { type: "number", formula: { tag: "product", left: 0, right: 1 } }, + { type: "number", formula: { tag: "sum", left: 0, right: 1 } }, + { type: "number", formula: { tag: "difference", left: 3, right: 4 } }, + { type: "boolean", formula: { tag: "not", value: 2 } }, + { + type: "number", + formula: { + tag: "select", + gate: 2, + whenTrue: 5, + whenFalse: 4, + }, + }, + ], + outputs: [7, 6], +}; + +function coversFor( + artifact: ReturnType, + context: { caller: string; epoch: number; lineage: number }, + seed: number +) { + const realization = selectBprfRealization(artifact, context); + return Array.from( + { length: realization.transitions.length + 1 }, + (_, epoch) => + createChartCover({ + seed, + epoch, + width: realization.frameSize, + chartCount: 5, + threshold: 3, + }) + ); +} + +const normalizeFieldSpikeOutput = (value: unknown): unknown => + typeof value === "number" && Object.is(value, -0) ? 0 : value; + +describe("BPRF over moving-cover CSH reference", () => { + it("matches both BPRF ontologies without reconstructing a transition frame", () => { + const artifact = generateBprfArtifact(contract, { + seed: 0x51c0ffee, + realizationCount: 4, + fragmentCount: 3, + }); + const reachedFamilies = new Set(); + for (let index = 0; index < 80; index++) { + const context = { + caller: `integration-caller-${index % 13}`, + epoch: index, + lineage: index * 17, + }; + const realization = selectBprfRealization(artifact, context); + reachedFamilies.add(realization.familyCode); + const inputs = [ + (index % 7) - 3, + (index % 5) + 1, + index % 2 === 0, + ] as const; + const plain = evaluateBprfReference(artifact, inputs, context); + const integrated = evaluateBprfOverCshReference( + artifact, + inputs, + context, + coversFor(artifact, context, 0x600d0000 + index), + 0x700d0000 + index + ); + + expect(integrated.realization).toBe(plain.realization); + expect(integrated.outputs).toEqual( + plain.outputs.map(normalizeFieldSpikeOutput) + ); + expect(integrated.trace.length).toBeGreaterThan( + realization.transitions.length + ); + } + expect(reachedFamilies).toEqual(new Set([0, 1])); + }); + + it("requires the five-chart multiplication quorum at every transition", () => { + const artifact = generateBprfArtifact(contract, { + seed: 1414, + realizationCount: 2, + fragmentCount: 3, + }); + const context = { caller: "quorum", epoch: 1, lineage: 2 }; + const covers = coversFor(artifact, context, 1515); + const valid = evaluateBprfOverCshReference( + artifact, + [3, 4, true], + context, + covers, + 1616 + ); + expect(valid.outputs).toEqual([5, false]); + + const damagedCovers = covers.map((cover, index) => + index === 0 + ? createChartCover({ + seed: 1516, + epoch: cover.epoch, + width: cover.width, + chartCount: 5, + threshold: 4, + }) + : cover + ); + expect(() => + evaluateBprfOverCshReference( + artifact, + [3, 4, true], + context, + damagedCovers, + 1616 + ) + ).toThrow("RUAM_BPRF_CSH_MULTIPLICATION_QUORUM_TOO_SMALL"); + }); + + it("changes cover at every transition and exposes only contextual trace identities", () => { + const artifact = generateBprfArtifact(contract, { + seed: 1717, + realizationCount: 3, + fragmentCount: 3, + }); + const context = { caller: "trace", epoch: 4, lineage: 9 }; + const covers = coversFor(artifact, context, 1818); + const result = evaluateBprfOverCshReference( + artifact, + [2, 5, false], + context, + covers, + 1919 + ); + + expect(new Set(covers.map((cover) => cover.id)).size).toBe(covers.length); + for (const event of result.trace) { + expect(event.fromCover).not.toBe(event.toCover); + expect(Object.keys(event).sort()).toEqual([ + "chart", + "fragment", + "fromCover", + "phase", + "realization", + "toCover", + "transition", + ]); + } + const serialized = JSON.stringify(result.trace).toLowerCase(); + for (const forbidden of [ + "semanticop", + "opcode", + "operand", + "handler", + "coefficient", + "factor", + "destination", + "sourcenode", + ]) { + expect(serialized).not.toContain(forbidden); + } + }); +}); diff --git a/packages/ruam/test/isogloss/csh-custody.test.ts b/packages/ruam/test/isogloss/csh-custody.test.ts new file mode 100644 index 0000000..83e3e16 --- /dev/null +++ b/packages/ruam/test/isogloss/csh-custody.test.ts @@ -0,0 +1,156 @@ +import { describe, expect, it } from "bun:test"; +import { + createCustodyClientState, + openCustodiedProjection, + prepareCustodyRequest, +} from "../../src/isogloss/csh/custody-protocol.js"; +import { ReferenceRelationCustodian } from "../../src/isogloss/csh/reference-custodian.js"; +import { + CSH_FIELD_MODULUS, + createChartCover, + encodeIngressCharts, +} from "../../src/isogloss/csh/reference.js"; + +const createFixture = () => { + const cover = createChartCover({ seed: 808, epoch: 3, width: 3 }); + const charts = encodeIngressCharts([7, 11, 13], cover, 809); + const custodian = new ReferenceRelationCustodian({ + sessionId: "session_alpha", + contractId: "contract_crown", + cover, + initialLineageCommitment: "lineage_genesis", + lineageSecret: "server-only-lineage-secret", + responseSecret: "server-only-response-secret", + relation: { + projectionSite: { + id: "custodied_input", + coefficients: [2, 3, 5], + bias: 19, + }, + cubic: 37, + linear: 41, + bias: 43, + }, + }); + return { cover, charts, custodian }; +}; + +const relationOutput = (input: number): number => { + const squared = (input * input) % CSH_FIELD_MODULUS; + const cubed = (squared * input) % CSH_FIELD_MODULUS; + return (37 * cubed + 41 * input + 43) % CSH_FIELD_MODULUS; +}; + +describe("custodied CSH direct-relation reference", () => { + it("requires a signed custodian response to materialize the projection", () => { + const { charts, custodian } = createFixture(); + const contract = custodian.clientContract; + const state = createCustodyClientState(contract); + const request = prepareCustodyRequest( + contract, + state, + charts, + "nonce_00000001" + ); + + expect(() => + openCustodiedProjection(contract, state, undefined) + ).toThrow("RUAM_CSH_CUSTODIAN_REQUIRED"); + const response = custodian.evaluate(request); + const opened = openCustodiedProjection(contract, state, response); + const inputProjection = + 2 * 7 + 3 * 11 + 5 * 13 + 19; + expect(opened.projection).toBe( + relationOutput(inputProjection % CSH_FIELD_MODULUS) + ); + expect(opened.state.epoch).toBe(1); + expect(opened.state.lineageCommitment).toBe( + response.nextLineageCommitment + ); + }); + + it("prevents replay and snapshot-fork of one representation epoch", () => { + const { charts, custodian } = createFixture(); + const contract = custodian.clientContract; + const state = createCustodyClientState(contract); + const firstFork = prepareCustodyRequest( + contract, + state, + charts, + "nonce_fork_0001" + ); + const secondFork = prepareCustodyRequest( + contract, + state, + charts, + "nonce_fork_0002" + ); + custodian.evaluate(firstFork); + + expect(() => custodian.evaluate(firstFork)).toThrow( + "RUAM_CSH_CUSTODY_REPLAY" + ); + expect(() => custodian.evaluate(secondFork)).toThrow( + "RUAM_CSH_CUSTODY_STALE_LINEAGE" + ); + }); + + it("rejects response tampering and carries no relation in the client contract", () => { + const { charts, custodian } = createFixture(); + const contract = custodian.clientContract; + const state = createCustodyClientState(contract); + const response = custodian.evaluate( + prepareCustodyRequest( + contract, + state, + charts, + "nonce_tamper_01" + ) + ); + const tampered = { + ...response, + encodedProjection: + (response.encodedProjection + 1) % CSH_FIELD_MODULUS, + }; + + expect(() => + openCustodiedProjection(contract, state, tampered) + ).toThrow("RUAM_CSH_CUSTODY_BAD_SIGNATURE"); + const serialized = JSON.stringify(contract).toLowerCase(); + for (const forbidden of [ + "projectionsite", + "cubic", + "linear", + "lineagesecret", + "semanticop", + "handler", + "codelet", + "nextregion", + ]) { + expect(serialized).not.toContain(forbidden); + } + expect(serialized).not.toContain("server-only-lineage-secret"); + expect(serialized).not.toContain("server-only-response-secret"); + }); + + it("advances a valid client and custodian lineage monotonically", () => { + const { charts, custodian } = createFixture(); + const contract = custodian.clientContract; + let state = createCustodyClientState(contract); + const openings = new Set(); + for (let epoch = 0; epoch < 3; epoch++) { + const request = prepareCustodyRequest( + contract, + state, + charts, + `nonce_epoch_${epoch}` + ); + const response = custodian.evaluate(request); + openings.add(JSON.stringify(response.projectionOpening)); + const opened = openCustodiedProjection(contract, state, response); + expect(opened.state.epoch).toBe(epoch + 1); + state = opened.state; + } + expect(openings.size).toBe(3); + }); +}); diff --git a/packages/ruam/test/isogloss/csh-moving-cover.test.ts b/packages/ruam/test/isogloss/csh-moving-cover.test.ts new file mode 100644 index 0000000..405c6a8 --- /dev/null +++ b/packages/ruam/test/isogloss/csh-moving-cover.test.ts @@ -0,0 +1,225 @@ +import { describe, expect, it } from "bun:test"; +import * as cshReference from "../../src/isogloss/csh/reference.js"; +import { + CSH_FIELD_MODULUS, + createChartCover, + encodeIngressCharts, + evaluateCertifiedProjection, + transportAffineCharts, + validateCover, + type AffineChartTransition, + type CertifiedProjectionSite, +} from "../../src/isogloss/csh/reference.js"; + +const coordinate = ( + width: number, + index: number, + id = `projection_${index}` +): CertifiedProjectionSite => ({ + id, + coefficients: Array.from({ length: width }, (_, lane) => + lane === index ? 1 : 0 + ), + bias: 0, +}); + +const applyAffine = ( + values: readonly number[], + transition: AffineChartTransition +): number[] => + transition.matrix.map((row, rowIndex) => { + let value = transition.bias[rowIndex]!; + for (let column = 0; column < values.length; column++) { + value += row[column]! * values[column]!; + } + value %= CSH_FIELD_MODULUS; + return value < 0 ? value + CSH_FIELD_MODULUS : value; + }); + +describe("moving-cover CSH reference", () => { + it("creates deterministic nonlinear five-chart covers with threshold three", () => { + const first = createChartCover({ seed: 41, epoch: 0, width: 3 }); + const replay = createChartCover({ seed: 41, epoch: 0, width: 3 }); + const next = createChartCover({ seed: 41, epoch: 1, width: 3 }); + + expect(first).toEqual(replay); + expect(first.charts).toHaveLength(5); + expect(first.threshold).toBe(3); + expect(new Set(first.charts.map((chart) => chart.point)).size).toBe(5); + expect(first.charts.every((chart) => chart.overlaps.length === 4)).toBe( + true + ); + expect( + first.charts.every((chart) => + chart.cells.every( + (cell) => + cell.exponent > 1 && + (cell.exponent * cell.inverseExponent) % + (CSH_FIELD_MODULUS - 1) === + 1 + ) + ) + ).toBe(true); + expect(next.id).not.toBe(first.id); + expect(next.mixing).not.toEqual(first.mixing); + expect(next.charts.map((chart) => chart.owner)).not.toEqual( + first.charts.map((chart) => chart.owner) + ); + validateCover(first); + validateCover(next); + }); + + it("requires threshold contributions and never exports a global decoder", () => { + const cover = createChartCover({ seed: 73, epoch: 0, width: 3 }); + const charts = encodeIngressCharts([17, 29, 43], cover, 19); + const site = coordinate(3, 1); + + for (const selection of [ + charts.slice(0, 3), + charts.slice(1, 4), + [charts[0]!, charts[2]!, charts[4]!], + charts, + ]) { + expect(evaluateCertifiedProjection(selection, cover, site)).toBe(29); + } + expect(() => + evaluateCertifiedProjection(charts.slice(0, 2), cover, site) + ).toThrow("RUAM_CSH_THRESHOLD_NOT_MET"); + expect(() => + evaluateCertifiedProjection([charts[0]!, charts[0]!, charts[1]!], cover, site) + ).toThrow("RUAM_CSH_DUPLICATE_CONTRIBUTION"); + expect( + Object.keys(cshReference).some((name) => + /(decode|reconstruct|global.?frame|global.?section)/i.test(name) + ) + ).toBe(false); + }); + + it("transports two affine transitions across distinct covers without a frame API", () => { + const covers = [0, 1, 2].map((epoch) => + createChartCover({ seed: 2026, epoch, width: 3 }) + ); + const firstTransition: AffineChartTransition = { + id: "r0_r1", + matrix: [ + [2, 1, 0], + [0, 3, 1], + [1, 0, 1], + ], + bias: [5, 7, 11], + }; + const secondTransition: AffineChartTransition = { + id: "r1_r2", + matrix: [ + [1, 0, 2], + [4, 1, 0], + [0, 1, 1], + ], + bias: [13, 17, 19], + }; + const initial = [23, 31, 47]; + const expectedOne = applyAffine(initial, firstTransition); + const expectedTwo = applyAffine(expectedOne, secondTransition); + const chartsZero = encodeIngressCharts(initial, covers[0]!, 101); + const chartsOne = transportAffineCharts( + chartsZero, + covers[0]!, + covers[1]!, + firstTransition, + 103 + ); + const chartsTwo = transportAffineCharts( + chartsOne, + covers[1]!, + covers[2]!, + secondTransition, + 107 + ); + + for (let coordinateIndex = 0; coordinateIndex < 3; coordinateIndex++) { + expect( + evaluateCertifiedProjection( + chartsOne.slice(0, 3), + covers[1]!, + coordinate(3, coordinateIndex) + ) + ).toBe(expectedOne[coordinateIndex]); + expect( + evaluateCertifiedProjection( + [chartsTwo[0]!, chartsTwo[2]!, chartsTwo[4]!], + covers[2]!, + coordinate(3, coordinateIndex) + ) + ).toBe(expectedTwo[coordinateIndex]); + } + expect(chartsOne.every((chart) => chart.coverId === covers[1]!.id)).toBe( + true + ); + expect(chartsTwo.every((chart) => chart.coverId === covers[2]!.id)).toBe( + true + ); + expect(() => + evaluateCertifiedProjection( + chartsOne, + covers[0]!, + coordinate(3, 0) + ) + ).toThrow("RUAM_CSH_WRONG_COVER"); + }); + + it("supports only site-specific scalar projection and rejects malformed cover changes", () => { + const cover = createChartCover({ seed: 99, epoch: 0, width: 2 }); + const sameEpoch = createChartCover({ seed: 100, epoch: 0, width: 2 }); + const charts = encodeIngressCharts([9, 12], cover, 88); + const sumSite: CertifiedProjectionSite = { + id: "return_sum", + coefficients: [1, 1], + bias: 7, + }; + expect(evaluateCertifiedProjection(charts, cover, sumSite)).toBe(28); + expect(() => + transportAffineCharts( + charts, + cover, + sameEpoch, + { + id: "invalid", + matrix: [ + [1, 0], + [0, 1], + ], + bias: [0, 0], + }, + 5 + ) + ).toThrow("RUAM_CSH_COVER_DID_NOT_CHANGE"); + expect(() => + createChartCover({ + seed: 1, + epoch: 0, + width: 2, + chartCount: 4, + }) + ).toThrow("RUAM_CSH_INSUFFICIENT_CHARTS"); + }); + + it("does not serialize canonical execution identities or a complete logical frame", () => { + const cover = createChartCover({ seed: 53, epoch: 4, width: 3 }); + const charts = encodeIngressCharts([1, 2, 3], cover, 59); + const serialized = JSON.stringify({ cover, charts }).toLowerCase(); + + for (const forbidden of [ + "semanticop", + "handler", + "opcode", + "operand", + "sourcenode", + "globalframe", + "logicalvalues", + ]) { + expect(serialized).not.toContain(forbidden); + } + expect(charts.every((chart) => chart.cells.length === 3)).toBe(true); + expect(charts.every((chart) => Object.isFrozen(chart))).toBe(true); + }); +}); From 0f9b376c8ea93a6a27737e4f50a74371af8eb4b2 Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:04:22 -1000 Subject: [PATCH 14/54] docs: record initial BPRF CSH spike --- .../2026-07-24-bprf-csh-spike-report.md | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md diff --git a/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md b/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md new file mode 100644 index 0000000..a6a3195 --- /dev/null +++ b/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md @@ -0,0 +1,164 @@ +# BPRF/CSH Spike Report + +**Date:** 2026-07-24 +**Branch:** `codex/traveling-isogloss-replacement` +**Decision state:** Go/no-go evaluation in progress; product schemas remain +unfrozen + +## Purpose + +This report records the first executable tranche of the revised Isogloss +architecture: + +1. measure the legacy VM's dynamic-instrumentation choke point; +2. construct conservative effect-delimited canonical regions; +3. validate multiple BPRF ontologies without runtime semantic dispatch; +4. validate moving-cover CSH and a necessary custodied relation; and +5. prove that BPRF polynomial fragments can execute over CSH chart shares + without reconstructing a transition frame. + +The spike is not a product backend. The legacy VM remains only as the current +behavior oracle while the replacement is incomplete. + +## Implemented controls and references + +| Slice | Result | +|---|---| +| Legacy attacker control | Deterministic four-fixture corpus and executor-neutral scorer | +| Canonical analysis | Purity, throw, coercion, call, suspension, access, allocation, and completion facts | +| Region formation | Deterministic effect-delimited regions with typed exits and boundary contracts | +| BPRF | At least two causal families, contextual realization selection, fission, braiding, and wire rebasing | +| Local CSH | Five nonlinear charts, threshold three, changing covers, and scalar certified projections | +| BPRF over CSH | Degree-four product shares reduced by five contributors into a fresh degree-two cover | +| Direct custody | Signed response, one-response opening, mandatory remote relation, monotonic lineage, replay/fork rejection | + +All generic BPRF/CSH evaluators are explicitly test/reference-only and are not +reachable from the package or CLI build entries. + +## Legacy dynamic-attacker control + +At seed `0x1badb002`, the legacy debug build exposes: + +| Measurement | Result | +|---|---:| +| Fixtures | 4 | +| Owner/recovered boundaries | 278 / 278 | +| Operation events recovered | 254 / 254 | +| Boundary precision / recall / F1 | 1.0 / 1.0 / 1.0 | +| Operation accuracy / micro-F1 / macro-F1 | 1.0 / 1.0 / 1.0 | +| Sequence-edge recall | 1.0 | +| Executed dynamic-CFG recall | 1.0 | +| Minimum probe families | 1 | +| Result preservation | Complete | + +This confirms the architectural defect: one passive console-probe family +produces a complete stable post-optimizer semantic stream. + +## BPRF structural results + +The bounded pure reference kernel currently proves: + +- deterministic generation of `K >= 2` realizations; +- two structurally different causal families: + dependency-layer polynomial dataflow and two-phase continuation residuals; +- indicator and signed boolean coordinate ontologies; +- caller-, epoch-, and lineage-conditioned realization selection; +- shifted-product and additive fission; +- every destination requires every configured fragment; +- every fragment is braided across multiple destinations; +- physical input, intermediate, and output wire bases differ; and +- serialized artifacts and traces contain no semantic operation, opcode, + handler, canonical operand, formula tag, or source-node identity. + +The generic differential evaluator is not a production runtime. A specialized +source-emission spike and adversarial recovery measurements are the next gate. + +## CSH structural results + +The local moving-cover reference currently proves: + +- five overlapping nonlinearly wrapped charts; +- a configured threshold of three; +- arbitrary valid threshold subsets recover only a declared scalar + projection; +- every protected transition changes cover identity, mixing basis, chart + points, wrapping maps, and ownership; +- affine transport accumulates old chart contributions directly into new + charts without assembling the logical state; and +- there is no exported global-frame/global-section decoder. + +The combined evaluator additionally executes BPRF product fragments over chart +shares. Products raise polynomial degree from two to four, so all five charts +are required for direct degree reduction into the next threshold-three cover. +No transition constructs an ordinary BPRF frame. + +## Custody results + +The direct-remote control holds one nonlinear projected relation outside the +client contract. The client receives only a signed, lineage-bound encoded +projection and a one-response opening for the native-visible scalar value. + +Verified properties: + +- no response means no local evaluation or fallback; +- the client contract contains neither the relation nor lineage/response + secrets; +- every response advances the representation epoch; +- a consumed nonce is rejected; +- two requests forked from one client snapshot cannot both execute; and +- response tampering fails signature verification. + +This is a direct-custody control, not private-function evaluation. The +custodian learns the declared input projection. + +## Correctness and build status + +At this checkpoint: + +- Isogloss-focused tests: **42 passed, 0 failed, 12,056 assertions**; +- full repository tests: **2,373 passed, 0 failed**; +- TypeScript typecheck: passed; +- package build: passed; and +- reference custodian/evaluator identifiers are absent from built package + output. + +## Explicit spike limitations + +These are blockers to schema freeze, not deferred documentation: + +1. The pure BPRF algebra is a bounded research language, not general + JavaScript. +2. The CSH integration uses finite-field integer/boolean coordinates. It does + not represent IEEE-754 `NaN`, infinities, negative zero, fractional + rounding, or overflow exactly. +3. BPRF algebraic reassociation is therefore eligible only where a compiler + proof establishes an exact bounded domain. Untyped JavaScript arithmetic + must remain an observable/coercive boundary or use an exact specialized + realization. +4. The current reference evaluators intentionally expose generic loops and + arrays to tests. They cannot ship because those would create artificial + runtime choke points. +5. Local CSH is still entirely client-resident. Full client recovery remains + possible, and the dynamic-attacker gate must measure whether its + amplification exceeds legitimate overhead. +6. Direct custody changes the trust and availability boundary and does not + provide input privacy. +7. Calls, recursion, effects, exceptions, `finally`, async, and generators are + not yet lowered through BPRF/CSH. + +## Current decision + +Do not freeze artifact, certificate, carrier, or runtime schemas yet. + +The next decision requires: + +- a sound canonical-region-to-bounded-contract lowerer; +- a specialized emitted BPRF spike with no generic artifact evaluator; +- O(1)-hook and full-step recovery measurements; +- C90/G90 and cross-cover transfer measurements; +- legitimate size/runtime overhead measurements; and +- zero mismatches within every claimed eligible domain. + +Mechanisms that do not beat the legacy one-hook control or whose attacker +amplification does not exceed user overhead will be removed rather than +carried into the product architecture. From 1a283741e56e18a2f015cc8bd9ec14ac73dfa42a Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:06:38 -1000 Subject: [PATCH 15/54] feat: specialize BPRF spike source --- .../ruam/src/isogloss/bprf/testing-emitter.ts | 295 ++++++++++++++++++ .../isogloss/bprf-specialized-emitter.test.ts | 261 ++++++++++++++++ .../test/isogloss/reference-isolation.test.ts | 68 ++++ 3 files changed, 624 insertions(+) create mode 100644 packages/ruam/src/isogloss/bprf/testing-emitter.ts create mode 100644 packages/ruam/test/isogloss/bprf-specialized-emitter.test.ts create mode 100644 packages/ruam/test/isogloss/reference-isolation.test.ts diff --git a/packages/ruam/src/isogloss/bprf/testing-emitter.ts b/packages/ruam/src/isogloss/bprf/testing-emitter.ts new file mode 100644 index 0000000..223ff73 --- /dev/null +++ b/packages/ruam/src/isogloss/bprf/testing-emitter.ts @@ -0,0 +1,295 @@ +/** + * NON-PRODUCT SPECIALIZED SOURCE-EMISSION SPIKE. + * + * This owner-side utility lowers a validated pure BPRF artifact to dedicated + * JavaScript codelets. Generated code contains no artifact walker, piece + * dispatcher, or generic regional evaluator. It exists only to measure whether + * specialization removes the reference evaluator's universal hook surface. + * + * @module isogloss/bprf/testing-emitter + */ + +import type { + BprfArtifact, + BprfFragment, + BprfPiece, + BprfRealization, + BprfTransition, +} from "./types.js"; +import { validateBprfArtifact } from "./validate.js"; + +export interface BprfTestingEmissionOptions { + /** Add owner-only opaque fabric events. Never enable in ordinary output. */ + ownerTracing?: boolean; +} + +export interface BprfTestingEmission { + source: string; + entryName: string; + byteLength: number; + realizationCount: number; + transitionCount: number; + codeletCount: number; + ownerTracing: boolean; +} + +/** + * Emit a standalone function declaration. + * + * Narrow ABI: `entry(inputs, { caller, epoch, lineage }, ownerTrace?)`. + * Ordinary emission omits all tracing code and ignores a third argument. + */ +export function emitBprfTestingSource( + artifact: BprfArtifact, + options: BprfTestingEmissionOptions = {} +): BprfTestingEmission { + validateBprfArtifact(artifact); + const ownerTracing = options.ownerTracing === true; + const entryName = "__ruamBprfSpike"; + const sourceParts: string[] = ['"use strict";']; + let codeletCount = 0; + let transitionCount = 0; + + for (let realizationIndex = 0; realizationIndex < artifact.realizations.length; realizationIndex++) { + const realization = artifact.realizations[realizationIndex]!; + const emitted = emitRealization( + realization, + realizationIndex, + ownerTracing + ); + sourceParts.push(emitted.source); + codeletCount += emitted.codeletCount; + transitionCount += realization.transitions.length; + } + + sourceParts.push(emitContextMixers()); + sourceParts.push( + emitEntry( + entryName, + artifact.selectionSalt, + artifact.realizations.length, + ownerTracing + ) + ); + const source = sourceParts.join("\n"); + return Object.freeze({ + source, + entryName, + byteLength: Buffer.byteLength(source, "utf8"), + realizationCount: artifact.realizations.length, + transitionCount, + codeletCount, + ownerTracing, + }); +} + +function emitRealization( + realization: BprfRealization, + realizationIndex: number, + ownerTracing: boolean +): { source: string; codeletCount: number } { + const sourceParts: string[] = []; + let codeletCount = 0; + + for (const transition of realization.transitions) { + for (let fragmentIndex = 0; fragmentIndex < realization.fragments.length; fragmentIndex++) { + const fragment = realization.fragments[fragmentIndex]!; + sourceParts.push( + emitCodelet( + realization, + realizationIndex, + transition, + fragment, + fragmentIndex, + ownerTracing + ) + ); + codeletCount++; + } + } + + const traceParameter = ownerTracing ? ",z" : ""; + sourceParts.push( + `function v${realizationIndex}(a${traceParameter}){`, + emitInputAbi(realization), + `const q=new Array(${realization.frameSize});`, + ...realization.inputPorts.map((port, inputIndex) => { + const coordinate = + port.typeCode === 0 + ? `a[${inputIndex}]` + : realization.familyCode === 0 + ? `(a[${inputIndex}]?1:0)` + : `(a[${inputIndex}]?1:-1)`; + return `q[${port.slot}]=(${coordinate})*(${numberSource(port.basis.scale)})+(${numberSource(port.basis.bias)});`; + }), + ...realization.transitions.flatMap((transition) => + emitTransitionCalls( + realization, + realizationIndex, + transition, + ownerTracing + ) + ), + `return [${realization.outputPorts + .map((port) => { + const decoded = `((q[${port.slot}]-(${numberSource(port.basis.bias)}))/(${numberSource(port.basis.scale)}))`; + if (port.typeCode === 0) return decoded; + return realization.familyCode === 0 + ? `(${decoded}>0.5)` + : `(${decoded}>0)`; + }) + .join(",")}];`, + "}" + ); + + return { source: sourceParts.join("\n"), codeletCount }; +} + +function emitCodelet( + realization: BprfRealization, + realizationIndex: number, + transition: BprfTransition, + fragment: BprfFragment, + fragmentIndex: number, + ownerTracing: boolean +): string { + const pieces = fragment.pieces.filter( + (piece) => piece.phase === transition.phase + ); + const byDestination = new Map(); + for (const piece of pieces) { + const group = byDestination.get(piece.destination) ?? []; + group.push(piece); + byDestination.set(piece.destination, group); + } + const values = transition.writes.map((destination) => { + const destinationPieces = byDestination.get(destination); + if (!destinationPieces || destinationPieces.length === 0) { + throw new Error("RUAM_BPRF_EMITTER_MISSING_FRAGMENT_DESTINATION"); + } + return destinationPieces.map(pieceExpression).join("+"); + }); + const name = codeletName( + realizationIndex, + transition.phase, + fragmentIndex + ); + const traceParameter = ownerTracing ? ",z" : ""; + const traceStatement = ownerTracing + ? `if(z)z({r:${stringSource(realization.id)},t:${stringSource(transition.id)},f:${stringSource(fragment.id)},p:${transition.phase}});` + : ""; + return `function ${name}(q${traceParameter}){const x=[${values.join(",")}];${traceStatement}return x;}`; +} + +function emitTransitionCalls( + realization: BprfRealization, + realizationIndex: number, + transition: BprfTransition, + ownerTracing: boolean +): string[] { + const traceArgument = ownerTracing ? ",z" : ""; + const callNames = realization.fragments.map( + (_, fragmentIndex) => `p${transition.phase}_${fragmentIndex}` + ); + const lines = realization.fragments.map((_, fragmentIndex) => { + const codelet = codeletName( + realizationIndex, + transition.phase, + fragmentIndex + ); + return `const ${callNames[fragmentIndex]}=${codelet}(q${traceArgument});`; + }); + for (let outputIndex = 0; outputIndex < transition.writes.length; outputIndex++) { + const destination = transition.writes[outputIndex]!; + const pieces = realization.fragments.flatMap((fragment) => + fragment.pieces.filter( + (piece) => + piece.phase === transition.phase && + piece.destination === destination + ) + ); + const basis = pieces[0]?.destinationBasis; + if (!basis) { + throw new Error("RUAM_BPRF_EMITTER_MISSING_DESTINATION_BASIS"); + } + const total = callNames + .map((callName) => `${callName}[${outputIndex}]`) + .join("+"); + lines.push( + `q[${destination}]=(${total})*(${numberSource(basis.scale)})+(${numberSource(basis.bias)});` + ); + } + return lines; +} + +function pieceExpression(piece: BprfPiece): string { + const factors = piece.factors.map( + (factor) => + `(((q[${factor.slot}]-(${numberSource(factor.basis.bias)}))/(${numberSource(factor.basis.scale)}))+(${numberSource(factor.offset)}))` + ); + if (factors.length === 0) return `(${numberSource(piece.coefficient)})`; + return `(${numberSource(piece.coefficient)}*${factors.join("*")})`; +} + +function emitInputAbi(realization: BprfRealization): string { + const checks = [ + `if(!Array.isArray(a)||a.length!==${realization.inputPorts.length})throw new Error("RUAM_BPRF_INPUT_ABI");`, + ]; + for (let index = 0; index < realization.inputPorts.length; index++) { + const port = realization.inputPorts[index]!; + checks.push( + port.typeCode === 0 + ? `if(typeof a[${index}]!=="number"||!Number.isFinite(a[${index}]))throw new Error("RUAM_BPRF_INPUT_ABI");` + : `if(typeof a[${index}]!=="boolean")throw new Error("RUAM_BPRF_INPUT_ABI");` + ); + } + return checks.join(""); +} + +function emitContextMixers(): string { + return [ + "function mx(x){x>>>=0;x=Math.imul(x^(x>>>16),2146121005);x=Math.imul(x^(x>>>15),2221713035);return(x^(x>>>16))>>>0;}", + "function hx(s,x){for(let j=0;j>>0;}return mx(x);}", + ].join("\n"); +} + +function emitEntry( + entryName: string, + selectionSalt: number, + realizationCount: number, + ownerTracing: boolean +): string { + const traceArgument = ownerTracing ? ",z" : ""; + const lines = [ + `function ${entryName}(a,c${ownerTracing ? ",z" : ""}){`, + 'if(!c||typeof c.caller!=="string"||!Number.isSafeInteger(c.epoch)||c.epoch<0||!Number.isSafeInteger(c.lineage)||c.lineage<0)throw new Error("RUAM_BPRF_CONTEXT_ABI");', + `const n=mx(hx(c.caller,${selectionSalt >>> 0})^Math.imul(c.epoch+1,2654435769)^Math.imul(c.lineage+1,2246822507))%${realizationCount};`, + ]; + for (let index = 0; index < realizationCount - 1; index++) { + lines.push(`if(n===${index})return v${index}(a${traceArgument});`); + } + lines.push( + `return v${realizationCount - 1}(a${traceArgument});`, + "}" + ); + return lines.join("\n"); +} + +function codeletName( + realizationIndex: number, + phase: number, + fragmentIndex: number +): string { + return `x${realizationIndex}_${phase}_${fragmentIndex}`; +} + +function numberSource(value: number): string { + if (!Number.isFinite(value)) { + throw new Error("RUAM_BPRF_EMITTER_NON_FINITE_NUMBER"); + } + return Object.is(value, -0) ? "-0" : String(value); +} + +function stringSource(value: string): string { + return JSON.stringify(value); +} diff --git a/packages/ruam/test/isogloss/bprf-specialized-emitter.test.ts b/packages/ruam/test/isogloss/bprf-specialized-emitter.test.ts new file mode 100644 index 0000000..6ffcfb4 --- /dev/null +++ b/packages/ruam/test/isogloss/bprf-specialized-emitter.test.ts @@ -0,0 +1,261 @@ +import { describe, expect, it } from "bun:test"; +import { + generateBprfArtifact, + type BprfCallerContext, + type PureRegionContract, + type PureScalar, +} from "../../src/isogloss/bprf/index.js"; +import { + emitBprfTestingSource, + type BprfTestingEmission, +} from "../../src/isogloss/bprf/testing-emitter.js"; +import { evaluateBprfReference } from "../../src/isogloss/bprf/testing-reference.js"; + +const fixture: PureRegionContract = { + inputs: [ + { type: "number" }, + { type: "number" }, + { type: "boolean" }, + { type: "boolean" }, + ], + steps: [ + { type: "number", formula: { tag: "product", left: 0, right: 1 } }, + { type: "number", formula: { tag: "sum", left: 0, right: 1 } }, + { type: "number", formula: { tag: "difference", left: 4, right: 5 } }, + { type: "boolean", formula: { tag: "and", left: 2, right: 3 } }, + { type: "boolean", formula: { tag: "xor", left: 2, right: 3 } }, + { type: "boolean", formula: { tag: "or", left: 7, right: 8 } }, + { + type: "number", + formula: { + tag: "select", + gate: 9, + whenTrue: 6, + whenFalse: 5, + }, + }, + { type: "boolean", formula: { tag: "not", value: 7 } }, + ], + outputs: [10, 11, 8], +}; + +type EmittedEntry = ( + inputs: readonly PureScalar[], + context: BprfCallerContext, + ownerTrace?: (event: unknown) => void +) => PureScalar[]; + +function instantiate(emission: BprfTestingEmission): EmittedEntry { + return new Function( + `${emission.source}\nreturn ${emission.entryName};` + )() as EmittedEntry; +} + +function expectEquivalent( + actual: readonly PureScalar[], + expected: readonly PureScalar[] +): void { + expect(actual).toHaveLength(expected.length); + for (let index = 0; index < actual.length; index++) { + if (typeof expected[index] === "number") { + expect(actual[index]).toBeCloseTo(expected[index] as number, 10); + } else { + expect(actual[index]).toBe(expected[index]); + } + } +} + +describe("specialized BPRF source-emission spike", () => { + it("matches the reference across seeds, ontologies, and caller contexts", () => { + const inputs = [ + [3, 5, true, false], + [-4, 7, false, false], + [6, -2, true, true], + [0, 9, false, true], + ] as const; + for (const seed of [0, 1, 0x12345678, 0xffffffff]) { + const artifact = generateBprfArtifact(fixture, { + seed, + realizationCount: 4, + fragmentCount: 3, + }); + const emission = emitBprfTestingSource(artifact); + const execute = instantiate(emission); + const reached = new Set(); + for (let index = 0; index < 96; index++) { + const context = { + caller: `site-${index % 13}`, + epoch: Math.floor(index / 13), + lineage: index * 11, + }; + for (const values of inputs) { + const reference = evaluateBprfReference( + artifact, + values, + context + ); + reached.add(reference.realization); + expectEquivalent(execute(values, context), reference.outputs); + } + } + expect(reached.size).toBe(artifact.realizations.length); + } + }); + + it("emits deterministic but structurally diverse specialized source", () => { + const artifact = generateBprfArtifact(fixture, { + seed: 7727, + realizationCount: 4, + fragmentCount: 3, + }); + const first = emitBprfTestingSource(artifact); + const second = emitBprfTestingSource(artifact); + const differentArtifact = generateBprfArtifact(fixture, { + seed: 7728, + realizationCount: 4, + fragmentCount: 3, + }); + const different = emitBprfTestingSource(differentArtifact); + + expect(second.source).toBe(first.source); + expect(different.source).not.toBe(first.source); + expect(first.byteLength).toBe(Buffer.byteLength(first.source, "utf8")); + expect(first.codeletCount).toBe( + artifact.realizations.reduce( + (total, realization) => + total + + realization.transitions.length * + realization.fragments.length, + 0 + ) + ); + + const familyZeroCodelets = + first.source.match(/function x0_\d+_\d+\(/g)?.length ?? 0; + const familyOneCodelets = + first.source.match(/function x1_\d+_\d+\(/g)?.length ?? 0; + expect(familyOneCodelets).toBeGreaterThan(familyZeroCodelets); + }); + + it("contains direct codelets and no generic artifact execution seam", () => { + const artifact = generateBprfArtifact(fixture, { + seed: 9090, + realizationCount: 3, + fragmentCount: 3, + }); + const emission = emitBprfTestingSource(artifact); + const source = emission.source; + const forbidden = [ + "SemanticOp", + "opcode", + "operand", + "handler", + "sourceNode", + "nodeId", + ".pieces", + ".fragments", + "artifact", + "piece", + "destination", + "coefficient", + "factors", + '"sum"', + '"difference"', + '"product"', + '"and"', + '"or"', + '"xor"', + '"select"', + '"not"', + ]; + for (const token of forbidden) expect(source).not.toContain(token); + expect(source).not.toContain("switch("); + expect(source).not.toContain("while("); + expect(source.match(/\bfor\s*\(/g)?.length ?? 0).toBe(1); + + const functionCount = source.match(/\bfunction\s+/g)?.length ?? 0; + expect(functionCount).toBe( + emission.codeletCount + emission.realizationCount + 3 + ); + for (let realizationIndex = 0; realizationIndex < artifact.realizations.length; realizationIndex++) { + const realization = artifact.realizations[realizationIndex]!; + for (const transition of realization.transitions) { + for (let fragmentIndex = 0; fragmentIndex < realization.fragments.length; fragmentIndex++) { + const name = `x${realizationIndex}_${transition.phase}_${fragmentIndex}`; + expect(source.split(name)).toHaveLength(3); + } + } + for (const fragment of realization.fragments) { + expect(source).not.toContain(fragment.id); + } + } + }); + + it("keeps owner tracing optional, opaque, and outside ordinary source", () => { + const artifact = generateBprfArtifact(fixture, { + seed: 5150, + realizationCount: 3, + fragmentCount: 3, + }); + const ordinary = emitBprfTestingSource(artifact); + const traced = emitBprfTestingSource(artifact, { ownerTracing: true }); + const execute = instantiate(traced); + const events: Array> = []; + const context = { caller: "trace-site", epoch: 3, lineage: 19 }; + const reference = evaluateBprfReference( + artifact, + [3, 5, true, false], + context + ); + const outputs = execute( + [3, 5, true, false], + context, + (event) => events.push(event as Record) + ); + + expectEquivalent(outputs, reference.outputs); + expect(ordinary.source).not.toContain("if(z)z("); + expect(traced.source).toContain("if(z)z("); + expect(events.length).toBe(reference.trace.length); + for (const event of events) { + expect(Object.keys(event).sort()).toEqual(["f", "p", "r", "t"]); + expect(typeof event.r).toBe("string"); + expect(typeof event.t).toBe("string"); + expect(typeof event.f).toBe("string"); + expect(typeof event.p).toBe("number"); + } + + const byTransition = new Map>(); + for (const event of events) { + const transition = event.t as string; + const owners = byTransition.get(transition) ?? new Set(); + owners.add(event.f as string); + byTransition.set(transition, owners); + } + const selected = artifact.realizations.find( + (realization) => realization.id === events[0]!.r + )!; + expect(byTransition.size).toBe(selected.transitions.length); + for (const owners of byTransition.values()) { + expect(owners.size).toBe(selected.fragmentThreshold); + } + }); + + it("enforces the narrow input and caller-context ABI", () => { + const artifact = generateBprfArtifact(fixture, { + seed: 17, + realizationCount: 2, + fragmentCount: 2, + }); + const execute = instantiate(emitBprfTestingSource(artifact)); + expect(() => + execute([1, 2, true], { caller: "x", epoch: 0, lineage: 0 }) + ).toThrow("RUAM_BPRF_INPUT_ABI"); + expect(() => + execute( + [1, 2, true, false], + { caller: "x", epoch: -1, lineage: 0 } + ) + ).toThrow("RUAM_BPRF_CONTEXT_ABI"); + }); +}); diff --git a/packages/ruam/test/isogloss/reference-isolation.test.ts b/packages/ruam/test/isogloss/reference-isolation.test.ts new file mode 100644 index 0000000..ada5b4c --- /dev/null +++ b/packages/ruam/test/isogloss/reference-isolation.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from "bun:test"; +import fs from "node:fs"; +import path from "node:path"; +import ts from "typescript"; + +const packageRoot = path.resolve(import.meta.dir, "../.."); + +function reachableModules(entries: readonly string[]): Map { + const reached = new Map(); + const pending = [...entries]; + while (pending.length > 0) { + const file = pending.pop()!; + if (reached.has(file)) continue; + const source = fs.readFileSync(file, "utf8"); + reached.set(file, source); + const imports = ts.preProcessFile(source, true, true).importedFiles; + for (const imported of imports) { + if (!imported.fileName.startsWith(".")) continue; + const requested = path.resolve(path.dirname(file), imported.fileName); + const candidates = [ + requested, + requested.replace(/\.js$/, ".ts"), + requested.replace(/\.js$/, ".tsx"), + path.join(requested, "index.ts"), + ]; + const resolved = candidates.find((candidate) => fs.existsSync(candidate)); + if (resolved !== undefined) pending.push(resolved); + } + } + return reached; +} + +describe("Isogloss reference isolation", () => { + it("keeps generic evaluators, emitters, custodians, and test entropy out of package entries", () => { + const modules = reachableModules([ + path.join(packageRoot, "src/index.ts"), + path.join(packageRoot, "src/cli.ts"), + ]); + const inputs = [...modules.keys()].map((file) => + file.replaceAll("\\", "/") + ); + const forbiddenModules = [ + "/src/testing.ts", + "/src/isogloss/bprf/testing-reference.ts", + "/src/isogloss/bprf/testing-emitter.ts", + "/src/isogloss/csh/reference.ts", + "/src/isogloss/csh/testing-bprf-reference.ts", + "/src/isogloss/csh/reference-custodian.ts", + ]; + + for (const forbidden of forbiddenModules) { + expect( + inputs.some((input) => input.endsWith(forbidden)), + forbidden + ).toBe(false); + } + const emitted = [...modules.values()].join("\n"); + for (const forbiddenToken of [ + "ReferenceRelationCustodian", + "evaluateBprfReference", + "evaluateBprfOverCshReference", + "RUAM_BPRF_CSH_MULTIPLICATION_QUORUM", + "server-only-lineage-secret", + ]) { + expect(emitted).not.toContain(forbiddenToken); + } + }); +}); From e1dfc9927f0addf28de3c5ab7eaee28f1b71174d Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:10:40 -1000 Subject: [PATCH 16/54] feat: custody a moving-cover chart relation --- .../isogloss/csh/chart-custody-protocol.ts | 247 +++++++++++++++++ .../ruam/src/isogloss/csh/custody-protocol.ts | 4 +- .../isogloss/csh/reference-chart-custodian.ts | 257 ++++++++++++++++++ .../test/isogloss/csh-chart-custody.test.ts | 229 ++++++++++++++++ .../ruam/test/isogloss/csh-custody.test.ts | 31 ++- .../test/isogloss/reference-isolation.test.ts | 2 + 6 files changed, 765 insertions(+), 5 deletions(-) create mode 100644 packages/ruam/src/isogloss/csh/chart-custody-protocol.ts create mode 100644 packages/ruam/src/isogloss/csh/reference-chart-custodian.ts create mode 100644 packages/ruam/test/isogloss/csh-chart-custody.test.ts diff --git a/packages/ruam/src/isogloss/csh/chart-custody-protocol.ts b/packages/ruam/src/isogloss/csh/chart-custody-protocol.ts new file mode 100644 index 0000000..d90b3a4 --- /dev/null +++ b/packages/ruam/src/isogloss/csh/chart-custody-protocol.ts @@ -0,0 +1,247 @@ +/** + * Client protocol for a custodied contribution inside a CSH cover transition. + * + * The client can combine a signed set of chart-local additive contributions + * into an already transported local cover. It has no implementation of the + * missing relation and no fallback when a response is absent. + * + * @module isogloss/csh/chart-custody-protocol + */ + +import { createHash, verify } from "node:crypto"; +import { + CSH_FIELD_MODULUS, + type ChartCellTransform, + type ChartCover, + type EncodedChart, +} from "./reference.js"; +import type { CustodyClientState } from "./custody-protocol.js"; + +export interface ChartCustodyClientContract { + readonly sessionId: string; + readonly contractId: string; + readonly fromCoverId: string; + readonly toCoverId: string; + readonly initialLineageCommitment: string; + readonly verificationKey: string; +} + +export interface ChartCustodyRequest { + readonly sessionId: string; + readonly contractId: string; + readonly fromCoverId: string; + readonly toCoverId: string; + readonly epoch: number; + readonly lineageCommitment: string; + readonly nonce: string; + readonly charts: readonly EncodedChart[]; +} + +export interface AdditiveChartContribution { + readonly chartId: string; + readonly cells: readonly number[]; +} + +export interface ChartCustodyResponse { + readonly sessionId: string; + readonly contractId: string; + readonly requestNonce: string; + readonly epoch: number; + readonly nextEpoch: number; + readonly nextLineageCommitment: string; + readonly chartContributions: readonly AdditiveChartContribution[]; + readonly signature: string; +} + +export interface AppliedChartCustody { + readonly charts: readonly EncodedChart[]; + readonly state: CustodyClientState; +} + +export function createChartCustodyClientState( + contract: ChartCustodyClientContract +): CustodyClientState { + return Object.freeze({ + epoch: 0, + lineageCommitment: contract.initialLineageCommitment, + }); +} + +export function prepareChartCustodyRequest( + contract: ChartCustodyClientContract, + state: CustodyClientState, + charts: readonly EncodedChart[], + nonce: string +): ChartCustodyRequest { + if (nonce.length < 8) throw new Error("RUAM_CSH_CUSTODY_NONCE_TOO_SHORT"); + if ( + charts.length === 0 || + charts.some((chart) => chart.coverId !== contract.fromCoverId) + ) { + throw new Error("RUAM_CSH_CHART_CUSTODY_WRONG_SOURCE_COVER"); + } + return Object.freeze({ + sessionId: contract.sessionId, + contractId: contract.contractId, + fromCoverId: contract.fromCoverId, + toCoverId: contract.toCoverId, + epoch: state.epoch, + lineageCommitment: state.lineageCommitment, + nonce, + charts, + }); +} + +/** Apply one authenticated missing gluing contribution to a local transport. */ +export function applyCustodiedChartContribution( + contract: ChartCustodyClientContract, + state: CustodyClientState, + localCharts: readonly EncodedChart[], + targetCover: ChartCover, + response: ChartCustodyResponse | undefined, + expectedNonce: string +): AppliedChartCustody { + if (response === undefined) { + throw new Error("RUAM_CSH_CUSTODIAN_REQUIRED"); + } + if ( + response.sessionId !== contract.sessionId || + response.contractId !== contract.contractId || + response.requestNonce !== expectedNonce || + response.epoch !== state.epoch || + response.nextEpoch !== state.epoch + 1 || + targetCover.id !== contract.toCoverId + ) { + throw new Error("RUAM_CSH_CHART_CUSTODY_RESPONSE_MISMATCH"); + } + if ( + !verify( + null, + Buffer.from(chartCustodySigningPayload(response)), + contract.verificationKey, + Buffer.from(response.signature, "base64") + ) + ) { + throw new Error("RUAM_CSH_CUSTODY_BAD_SIGNATURE"); + } + const localById = new Map(localCharts.map((chart) => [chart.chartId, chart])); + const contributionById = new Map( + response.chartContributions.map((contribution) => [ + contribution.chartId, + contribution, + ]) + ); + if ( + localById.size !== targetCover.charts.length || + contributionById.size !== targetCover.charts.length + ) { + throw new Error("RUAM_CSH_CHART_CUSTODY_INCOMPLETE_CONTRIBUTION"); + } + const combined = targetCover.charts.map((descriptor): EncodedChart => { + const local = localById.get(descriptor.id); + const contribution = contributionById.get(descriptor.id); + if ( + !local || + !contribution || + local.coverId !== targetCover.id || + local.epoch !== targetCover.epoch || + local.cells.length !== targetCover.width || + contribution.cells.length !== targetCover.width + ) { + throw new Error( + `RUAM_CSH_CHART_CUSTODY_INVALID_CONTRIBUTION: ${descriptor.id}` + ); + } + const cells = local.cells.map((stored, lane) => { + const raw = unwrapCell(stored, descriptor.cells[lane]!); + return wrapCell( + add(raw, contribution.cells[lane]!), + descriptor.cells[lane]! + ); + }); + return Object.freeze({ + coverId: targetCover.id, + epoch: targetCover.epoch, + chartId: descriptor.id, + cells: Object.freeze(cells), + }); + }); + return Object.freeze({ + charts: Object.freeze(combined), + state: Object.freeze({ + epoch: response.nextEpoch, + lineageCommitment: response.nextLineageCommitment, + }), + }); +} + +export function chartCustodySigningPayload( + response: + | Omit + | ChartCustodyResponse +): string { + const contributionDigest = createHash("sha256") + .update(JSON.stringify(response.chartContributions)) + .digest("hex"); + return [ + response.sessionId, + response.contractId, + response.requestNonce, + response.epoch, + response.nextEpoch, + response.nextLineageCommitment, + contributionDigest, + ].join("|"); +} + +function wrapCell(raw: number, transform: ChartCellTransform): number { + return power( + add(multiply(transform.scale, raw), transform.offset), + transform.exponent + ); +} + +function unwrapCell( + stored: number, + transform: ChartCellTransform +): number { + return multiply( + subtract(power(stored, transform.inverseExponent), transform.offset), + inverse(transform.scale) + ); +} + +function inverse(value: number): number { + const normalized = normalize(value); + if (normalized === 0) throw new Error("RUAM_CSH_ZERO_HAS_NO_INVERSE"); + return power(normalized, CSH_FIELD_MODULUS - 2); +} + +function power(base: number, exponent: number): number { + let result = 1; + let factor = normalize(base); + let remaining = exponent; + while (remaining > 0) { + if (remaining % 2 === 1) result = multiply(result, factor); + factor = multiply(factor, factor); + remaining = Math.floor(remaining / 2); + } + return result; +} + +function add(left: number, right: number): number { + return normalize(normalize(left) + normalize(right)); +} + +function subtract(left: number, right: number): number { + return normalize(normalize(left) - normalize(right)); +} + +function multiply(left: number, right: number): number { + return normalize(normalize(left) * normalize(right)); +} + +function normalize(value: number): number { + const normalized = Math.trunc(value) % CSH_FIELD_MODULUS; + return normalized < 0 ? normalized + CSH_FIELD_MODULUS : normalized; +} diff --git a/packages/ruam/src/isogloss/csh/custody-protocol.ts b/packages/ruam/src/isogloss/csh/custody-protocol.ts index 0133b4d..e831ae4 100644 --- a/packages/ruam/src/isogloss/csh/custody-protocol.ts +++ b/packages/ruam/src/isogloss/csh/custody-protocol.ts @@ -96,7 +96,8 @@ export function prepareCustodyRequest( export function openCustodiedProjection( contract: CustodyClientContract, state: CustodyClientState, - response: CustodyResponse | undefined + response: CustodyResponse | undefined, + expectedNonce: string ): OpenedCustodyProjection { if (response === undefined) { throw new Error("RUAM_CSH_CUSTODIAN_REQUIRED"); @@ -104,6 +105,7 @@ export function openCustodiedProjection( if ( response.sessionId !== contract.sessionId || response.contractId !== contract.contractId || + response.requestNonce !== expectedNonce || response.epoch !== state.epoch || response.nextEpoch !== state.epoch + 1 ) { diff --git a/packages/ruam/src/isogloss/csh/reference-chart-custodian.ts b/packages/ruam/src/isogloss/csh/reference-chart-custodian.ts new file mode 100644 index 0000000..7b0d217 --- /dev/null +++ b/packages/ruam/src/isogloss/csh/reference-chart-custodian.ts @@ -0,0 +1,257 @@ +/** + * Server-side missing chart relation for the CSH integration spike. + * + * It evaluates a hidden nonlinear projection and returns only additive chart + * contributions under the next cover. The relation and logical direction are + * absent from the client contract. + * + * @module isogloss/csh/reference-chart-custodian + */ + +import { + createHash, + generateKeyPairSync, + sign, +} from "node:crypto"; +import { deriveSeed } from "../../naming/scope.js"; +import { createSeededRandom } from "../../random/entropy.js"; +import { + CSH_FIELD_MODULUS, + evaluateCertifiedProjection, + type ChartCover, + type CertifiedProjectionSite, +} from "./reference.js"; +import { + chartCustodySigningPayload, + type AdditiveChartContribution, + type ChartCustodyClientContract, + type ChartCustodyRequest, + type ChartCustodyResponse, +} from "./chart-custody-protocol.js"; + +export interface HiddenChartRelation { + readonly inputProjection: CertifiedProjectionSite; + readonly cubic: number; + readonly linear: number; + readonly bias: number; + readonly outputDirection: readonly number[]; +} + +export interface ReferenceChartCustodianOptions { + readonly sessionId: string; + readonly contractId: string; + readonly fromCover: ChartCover; + readonly toCover: ChartCover; + readonly initialLineageCommitment: string; + readonly lineageSecret: string; + readonly sharingSecret: string; + readonly relation: HiddenChartRelation; +} + +export class ReferenceChartRelationCustodian { + readonly clientContract: ChartCustodyClientContract; + + readonly #fromCover: ChartCover; + readonly #toCover: ChartCover; + readonly #relation: HiddenChartRelation; + readonly #lineageSecret: string; + readonly #sharingSecret: string; + readonly #privateKey: ReturnType["privateKey"]; + readonly #consumedNonces = new Set(); + #epoch = 0; + #lineageCommitment: string; + + constructor(options: ReferenceChartCustodianOptions) { + if (options.fromCover.id === options.toCover.id) { + throw new Error("RUAM_CSH_COVER_DID_NOT_CHANGE"); + } + if ( + options.fromCover.width !== options.toCover.width || + options.relation.outputDirection.length !== options.toCover.width + ) { + throw new Error("RUAM_CSH_CHART_CUSTODY_WIDTH_MISMATCH"); + } + const relationScalars = [ + options.relation.cubic, + options.relation.linear, + options.relation.bias, + ...options.relation.outputDirection, + ]; + if ( + relationScalars.some((value) => !Number.isSafeInteger(value)) || + !options.relation.outputDirection.some( + (value) => normalize(value) !== 0 + ) + ) { + throw new Error("RUAM_CSH_CHART_CUSTODY_INVALID_RELATION"); + } + const { privateKey, publicKey } = generateKeyPairSync("ed25519"); + this.#privateKey = privateKey; + this.#fromCover = options.fromCover; + this.#toCover = options.toCover; + this.#relation = options.relation; + this.#lineageSecret = options.lineageSecret; + this.#sharingSecret = options.sharingSecret; + this.#lineageCommitment = options.initialLineageCommitment; + this.clientContract = Object.freeze({ + sessionId: options.sessionId, + contractId: options.contractId, + fromCoverId: options.fromCover.id, + toCoverId: options.toCover.id, + initialLineageCommitment: options.initialLineageCommitment, + verificationKey: publicKey + .export({ type: "spki", format: "pem" }) + .toString(), + }); + } + + evaluate(request: ChartCustodyRequest): ChartCustodyResponse { + if ( + request.sessionId !== this.clientContract.sessionId || + request.contractId !== this.clientContract.contractId || + request.fromCoverId !== this.#fromCover.id || + request.toCoverId !== this.#toCover.id + ) { + throw new Error("RUAM_CSH_CUSTODY_REQUEST_MISMATCH"); + } + if (this.#consumedNonces.has(request.nonce)) { + throw new Error("RUAM_CSH_CUSTODY_REPLAY"); + } + if ( + request.epoch !== this.#epoch || + request.lineageCommitment !== this.#lineageCommitment + ) { + throw new Error("RUAM_CSH_CUSTODY_STALE_LINEAGE"); + } + const projectedInput = evaluateCertifiedProjection( + request.charts, + this.#fromCover, + this.#relation.inputProjection + ); + const cubed = multiply( + multiply(projectedInput, projectedInput), + projectedInput + ); + const residual = add( + add( + multiply(this.#relation.cubic, cubed), + multiply(this.#relation.linear, projectedInput) + ), + this.#relation.bias + ); + const logicalDelta = this.#relation.outputDirection.map((direction) => + multiply(direction, residual) + ); + const mixedDelta = multiplyMatrixVector( + this.#toCover.mixing, + logicalDelta + ); + const chartContributions = createAdditiveShares( + mixedDelta, + this.#toCover, + deriveSharingSeed( + this.#sharingSecret, + this.#epoch, + request.nonce + ) + ); + const nextEpoch = this.#epoch + 1; + const nextLineageCommitment = createHash("sha256") + .update(this.#lineageSecret) + .update("|") + .update(this.#lineageCommitment) + .update("|") + .update(request.nonce) + .update("|") + .update(JSON.stringify(chartContributions)) + .digest("hex"); + const unsigned = { + sessionId: request.sessionId, + contractId: request.contractId, + requestNonce: request.nonce, + epoch: this.#epoch, + nextEpoch, + nextLineageCommitment, + chartContributions, + }; + const signature = sign( + null, + Buffer.from(chartCustodySigningPayload(unsigned)), + this.#privateKey + ).toString("base64"); + this.#consumedNonces.add(request.nonce); + this.#epoch = nextEpoch; + this.#lineageCommitment = nextLineageCommitment; + return Object.freeze({ ...unsigned, signature }); + } +} + +function createAdditiveShares( + constant: readonly number[], + cover: ChartCover, + seed: number +): readonly AdditiveChartContribution[] { + const random = createSeededRandom(seed); + const residuals = Array.from({ length: cover.width }, () => + Array.from({ length: cover.threshold - 1 }, () => + random.nextUint32() % CSH_FIELD_MODULUS + ) + ); + return Object.freeze( + cover.charts.map((chart) => { + const cells = constant.map((value, lane) => { + let shared = normalize(value); + let pointPower = chart.point; + for (const residual of residuals[lane]!) { + shared = add(shared, multiply(residual, pointPower)); + pointPower = multiply(pointPower, chart.point); + } + return shared; + }); + return Object.freeze({ + chartId: chart.id, + cells: Object.freeze(cells), + }); + }) + ); +} + +function deriveSharingSeed( + secret: string, + epoch: number, + nonce: string +): number { + const secretWord = createHash("sha256") + .update(secret) + .update("|") + .update(nonce) + .digest() + .readUInt32LE(0); + return deriveSeed(secretWord, `chart-custody:${epoch}`); +} + +function multiplyMatrixVector( + matrix: readonly (readonly number[])[], + input: readonly number[] +): number[] { + return matrix.map((row) => { + let value = 0; + for (let index = 0; index < row.length; index++) { + value = add(value, multiply(row[index]!, input[index]!)); + } + return value; + }); +} + +function add(left: number, right: number): number { + return normalize(normalize(left) + normalize(right)); +} + +function multiply(left: number, right: number): number { + return normalize(normalize(left) * normalize(right)); +} + +function normalize(value: number): number { + const normalized = Math.trunc(value) % CSH_FIELD_MODULUS; + return normalized < 0 ? normalized + CSH_FIELD_MODULUS : normalized; +} diff --git a/packages/ruam/test/isogloss/csh-chart-custody.test.ts b/packages/ruam/test/isogloss/csh-chart-custody.test.ts new file mode 100644 index 0000000..5c30a6b --- /dev/null +++ b/packages/ruam/test/isogloss/csh-chart-custody.test.ts @@ -0,0 +1,229 @@ +import { describe, expect, it } from "bun:test"; +import { + applyCustodiedChartContribution, + createChartCustodyClientState, + prepareChartCustodyRequest, +} from "../../src/isogloss/csh/chart-custody-protocol.js"; +import { ReferenceChartRelationCustodian } from "../../src/isogloss/csh/reference-chart-custodian.js"; +import { + CSH_FIELD_MODULUS, + createChartCover, + encodeIngressCharts, + evaluateCertifiedProjection, + transportAffineCharts, +} from "../../src/isogloss/csh/reference.js"; + +const identityTransition = { + id: "local_identity", + matrix: [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + ], + bias: [0, 0, 0], +}; + +const coordinate = (index: number) => ({ + id: `coordinate_${index}`, + coefficients: [0, 1, 2].map((lane) => (lane === index ? 1 : 0)), + bias: 0, +}); + +const fixture = () => { + const from = createChartCover({ seed: 2027, epoch: 0, width: 3 }); + const to = createChartCover({ seed: 2027, epoch: 1, width: 3 }); + const values = [7, 11, 13]; + const incoming = encodeIngressCharts(values, from, 3030); + const local = transportAffineCharts( + incoming, + from, + to, + identityTransition, + 3031 + ); + const custodian = new ReferenceChartRelationCustodian({ + sessionId: "chart_session", + contractId: "hidden_glue", + fromCover: from, + toCover: to, + initialLineageCommitment: "chart_genesis", + lineageSecret: "server-chart-lineage", + sharingSecret: "server-chart-sharing", + relation: { + inputProjection: { + id: "hidden_input", + coefficients: [2, 1, 3], + bias: 5, + }, + cubic: 7, + linear: 11, + bias: 17, + outputDirection: [1, 2, 0], + }, + }); + return { from, to, values, incoming, local, custodian }; +}; + +const expectedResidual = (values: readonly number[]): number => { + const input = + (2 * values[0]! + values[1]! + 3 * values[2]! + 5) % + CSH_FIELD_MODULUS; + return ( + 7 * (((input * input) % CSH_FIELD_MODULUS) * input) + + 11 * input + + 17 + ) % CSH_FIELD_MODULUS; +}; + +describe("custodied chart relation", () => { + it("makes a hidden nonlinear contribution necessary inside a cover transition", () => { + const { to, values, incoming, local, custodian } = fixture(); + const contract = custodian.clientContract; + const state = createChartCustodyClientState(contract); + const request = prepareChartCustodyRequest( + contract, + state, + incoming, + "chart_nonce_0001" + ); + expect(() => + applyCustodiedChartContribution( + contract, + state, + local, + to, + undefined, + request.nonce + ) + ).toThrow("RUAM_CSH_CUSTODIAN_REQUIRED"); + + const response = custodian.evaluate(request); + expect(() => + applyCustodiedChartContribution( + contract, + state, + local, + to, + response, + "chart_nonce_wrong" + ) + ).toThrow("RUAM_CSH_CHART_CUSTODY_RESPONSE_MISMATCH"); + const applied = applyCustodiedChartContribution( + contract, + state, + local, + to, + response, + request.nonce + ); + const residual = expectedResidual(values); + const expected = [ + (values[0]! + residual) % CSH_FIELD_MODULUS, + (values[1]! + 2 * residual) % CSH_FIELD_MODULUS, + values[2], + ]; + for (let index = 0; index < expected.length; index++) { + expect( + evaluateCertifiedProjection( + applied.charts, + to, + coordinate(index) + ) + ).toBe(expected[index]); + } + expect( + evaluateCertifiedProjection(local, to, coordinate(0)) + ).toBe(values[0]); + expect(applied.state.epoch).toBe(1); + }); + + it("keeps the relation and output direction out of the client contract and response", () => { + const { incoming, custodian } = fixture(); + const contract = custodian.clientContract; + const state = createChartCustodyClientState(contract); + const response = custodian.evaluate( + prepareChartCustodyRequest( + contract, + state, + incoming, + "chart_nonce_0002" + ) + ); + const serialized = JSON.stringify({ contract, response }).toLowerCase(); + for (const forbidden of [ + "inputprojection", + "outputdirection", + "cubic", + "linear", + "semanticop", + "handler", + "codelet", + "nextregion", + "server-chart-lineage", + "server-chart-sharing", + ]) { + expect(serialized).not.toContain(forbidden); + } + expect(response.chartContributions).toHaveLength(5); + expect( + response.chartContributions.every( + (contribution) => contribution.cells.length === 3 + ) + ).toBe(true); + }); + + it("rejects replay, snapshot forks, incomplete contributions, and tampering", () => { + const { to, incoming, local, custodian } = fixture(); + const contract = custodian.clientContract; + const state = createChartCustodyClientState(contract); + const first = prepareChartCustodyRequest( + contract, + state, + incoming, + "chart_fork_00001" + ); + const second = prepareChartCustodyRequest( + contract, + state, + incoming, + "chart_fork_00002" + ); + const response = custodian.evaluate(first); + expect(() => custodian.evaluate(first)).toThrow( + "RUAM_CSH_CUSTODY_REPLAY" + ); + expect(() => custodian.evaluate(second)).toThrow( + "RUAM_CSH_CUSTODY_STALE_LINEAGE" + ); + + const incomplete = { + ...response, + chartContributions: response.chartContributions.slice(1), + }; + expect(() => + applyCustodiedChartContribution( + contract, + state, + local, + to, + incomplete, + first.nonce + ) + ).toThrow("RUAM_CSH_CUSTODY_BAD_SIGNATURE"); + const tampered = structuredClone(response); + ( + tampered.chartContributions[0]!.cells as number[] + )[0] = (tampered.chartContributions[0]!.cells[0]! + 1) % + CSH_FIELD_MODULUS; + expect(() => + applyCustodiedChartContribution( + contract, + state, + local, + to, + tampered, + first.nonce + ) + ).toThrow("RUAM_CSH_CUSTODY_BAD_SIGNATURE"); + }); +}); diff --git a/packages/ruam/test/isogloss/csh-custody.test.ts b/packages/ruam/test/isogloss/csh-custody.test.ts index 83e3e16..4708162 100644 --- a/packages/ruam/test/isogloss/csh-custody.test.ts +++ b/packages/ruam/test/isogloss/csh-custody.test.ts @@ -54,10 +54,23 @@ describe("custodied CSH direct-relation reference", () => { ); expect(() => - openCustodiedProjection(contract, state, undefined) + openCustodiedProjection(contract, state, undefined, request.nonce) ).toThrow("RUAM_CSH_CUSTODIAN_REQUIRED"); const response = custodian.evaluate(request); - const opened = openCustodiedProjection(contract, state, response); + expect(() => + openCustodiedProjection( + contract, + state, + response, + "nonce_wrong_0001" + ) + ).toThrow("RUAM_CSH_CUSTODY_RESPONSE_MISMATCH"); + const opened = openCustodiedProjection( + contract, + state, + response, + request.nonce + ); const inputProjection = 2 * 7 + 3 * 11 + 5 * 13 + 19; expect(opened.projection).toBe( @@ -114,7 +127,12 @@ describe("custodied CSH direct-relation reference", () => { }; expect(() => - openCustodiedProjection(contract, state, tampered) + openCustodiedProjection( + contract, + state, + tampered, + "nonce_tamper_01" + ) ).toThrow("RUAM_CSH_CUSTODY_BAD_SIGNATURE"); const serialized = JSON.stringify(contract).toLowerCase(); for (const forbidden of [ @@ -147,7 +165,12 @@ describe("custodied CSH direct-relation reference", () => { ); const response = custodian.evaluate(request); openings.add(JSON.stringify(response.projectionOpening)); - const opened = openCustodiedProjection(contract, state, response); + const opened = openCustodiedProjection( + contract, + state, + response, + request.nonce + ); expect(opened.state.epoch).toBe(epoch + 1); state = opened.state; } diff --git a/packages/ruam/test/isogloss/reference-isolation.test.ts b/packages/ruam/test/isogloss/reference-isolation.test.ts index ada5b4c..c4bbba4 100644 --- a/packages/ruam/test/isogloss/reference-isolation.test.ts +++ b/packages/ruam/test/isogloss/reference-isolation.test.ts @@ -46,6 +46,7 @@ describe("Isogloss reference isolation", () => { "/src/isogloss/csh/reference.ts", "/src/isogloss/csh/testing-bprf-reference.ts", "/src/isogloss/csh/reference-custodian.ts", + "/src/isogloss/csh/reference-chart-custodian.ts", ]; for (const forbidden of forbiddenModules) { @@ -59,6 +60,7 @@ describe("Isogloss reference isolation", () => { "ReferenceRelationCustodian", "evaluateBprfReference", "evaluateBprfOverCshReference", + "ReferenceChartRelationCustodian", "RUAM_BPRF_CSH_MULTIPLICATION_QUORUM", "server-only-lineage-secret", ]) { From 6943ad9f8b68da59c4eae33b07fab65d38785520 Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:15:50 -1000 Subject: [PATCH 17/54] test: reject weak BPRF CSH spike --- .../isogloss/revised-attacker-gates.test.ts | 130 ++ .../support/revised-attacker-gates.ts | 1549 +++++++++++++++++ 2 files changed, 1679 insertions(+) create mode 100644 packages/ruam/test/isogloss/revised-attacker-gates.test.ts create mode 100644 packages/ruam/test/isogloss/support/revised-attacker-gates.ts diff --git a/packages/ruam/test/isogloss/revised-attacker-gates.test.ts b/packages/ruam/test/isogloss/revised-attacker-gates.test.ts new file mode 100644 index 0000000..8da9cd8 --- /dev/null +++ b/packages/ruam/test/isogloss/revised-attacker-gates.test.ts @@ -0,0 +1,130 @@ +import { describe, expect, it } from "bun:test"; +import { + buildRevisedAttackerGoNoGoReport, + REVISED_ATTACKER_REPORT_VERSION, +} from "./support/revised-attacker-gates.js"; + +describe("revised BPRF/CSH attacker gates", () => { + it("is deterministic and records an honest no-go instead of hiding failures", () => { + const first = buildRevisedAttackerGoNoGoReport(); + const replay = buildRevisedAttackerGoNoGoReport(); + + expect(first.schemaVersion).toBe(REVISED_ATTACKER_REPORT_VERSION); + expect(replay).toEqual(first); + expect(first.decision).toBe("no-go"); + expect(first.failedGates).toEqual([ + "BPRF-ARTIFACT-AWARE-TRANSFER-DROP", + "BPRF-FULL-STEP-O90", + "CSH-G90-WORK-AMPLIFICATION", + "CSH-STORAGE-AMPLIFICATION", + "CSH-COVER-TRANSFER", + "CSH-LOCAL-PROBE-COLLAPSE", + "BPRF-CSH-G90-WORK-AMPLIFICATION", + "BPRF-CSH-FULL-TRACE-AMPLIFICATION", + "BPRF-CSH-CLIENT-TRACE-COMPLETENESS", + "CUSTODY-BYPASS", + "CUSTODY-CHART-DELTA-LEAKAGE", + ]); + expect(first.unevaluatedGates).toEqual([ + "BPRF-O1-OP-F1", + "BPRF-PATCH-COLLAPSE", + "CUSTODY-PFE-TOPOLOGY", + ]); + }); + + it("distinguishes naive contextual diversity from artifact-aware recovery", () => { + const report = buildRevisedAttackerGoNoGoReport(); + const transfer = report.bprf.contextTransfer; + + expect(transfer.seenRealizationIdentityRecall).toBe(1); + expect(transfer.unseenRealizationIdentityRecall).toBe(0); + expect(transfer.naiveTransferLossPercentagePoints).toBe(1); + expect(transfer.artifactAwareUnseenRecall).toBe(1); + expect(transfer.artifactAwareTransferLossPercentagePoints).toBe(0); + expect(report.bprf.artifactRecovery.heldOutOutputAccuracy).toBe(1); + expect(report.bprf.artifactRecovery.physicalTopologyRecall).toBe(1); + expect( + report.bprf.artifactRecovery.dynamicPrimitiveObservationsRequired + ).toBe(0); + expect(report.bprf.artifactRecovery.clientFunctionallyComplete).toBe(true); + expect(report.bprf.artifactRecovery.canonicalSourceMappingRecovered).toBe( + false + ); + }); + + it("measures threshold recovery and both weak and strong cover-transfer attacks", () => { + const report = buildRevisedAttackerGoNoGoReport(); + + expect(report.csh.c90.configuredThreshold).toBe(3); + expect(report.csh.c90.minimumContributionsFor90Percent).toBe(3); + expect( + report.csh.c90.recoveryByContributionCount.map((entry) => entry.recovery) + ).toEqual([0, 0, 1, 1, 1]); + expect(report.csh.coverTransfer.sourceCoverAccuracy).toBe(1); + expect(report.csh.coverTransfer.fixedDecoderUnseenAccuracy).toBe(0); + expect(report.csh.coverTransfer.fixedDecoderLossPercentagePoints).toBe(1); + expect(report.csh.coverTransfer.metadataAwareUnseenAccuracy).toBe(1); + expect(report.csh.coverTransfer.metadataAwareLossPercentagePoints).toBe(0); + expect(report.csh.amplification.g90WorkRatio).toBe(3); + expect(report.csh.amplification.residentStorageRatio).toBe(5); + }); + + it("shows that combined traces amplify fivefold but remain client-decodable", () => { + const report = buildRevisedAttackerGoNoGoReport(); + + expect(report.combined.outputAccuracy).toBe(1); + expect(report.combined.finalFrameRecovery).toBe(1); + expect(report.combined.amplification.g90WorkRatio).toBe(3); + expect(report.combined.amplification.residentStorageRatio).toBe(5); + expect(report.combined.amplification.fullTraceObservationRatio).toBe(5); + }); + + it("credits custody replay protection while detecting the learnable reference relation", () => { + const report = buildRevisedAttackerGoNoGoReport(); + const custody = report.custody.attack; + + expect(custody.clientCompleteBeforeResponse).toBe(false); + expect(custody.standaloneWithoutResponseRejected).toBe(true); + expect(custody.relationPresentInClientContract).toBe(false); + expect(custody.replaySucceeded).toBe(false); + expect(custody.snapshotForkSucceeded).toBe(false); + expect(custody.learnedBypass).toEqual({ + succeeded: true, + trainingQueries: 4, + heldOutAccuracy: 1, + localizedPatchSites: 1, + }); + expect(custody.chartRelation).toEqual({ + clientCompleteBeforeResponse: false, + minimumResponseContributionsForFullDelta: 3, + logicalDeltaRecovery: 1, + learnedBypass: { + succeeded: true, + trainingQueries: 4, + heldOutAccuracy: 1, + localizedPatchSites: 1, + }, + }); + expect(custody.pfeTopologyRecoveryEvaluated).toBe(false); + }); + + it("compares observable collapse without pretending contextual IDs are operations", () => { + const collapse = buildRevisedAttackerGoNoGoReport().hookCollapse; + + expect(collapse.legacy).toEqual({ + probeSites: 1, + boundaryRecall: 1, + operationF1: 1, + recoveredStream: "semantic", + }); + expect(collapse.bprf.probeSites).toBe(1); + expect(collapse.bprf.contextualIdentityRecall).toBe(1); + expect(collapse.bprf.operationF1).toBeNull(); + expect(collapse.csh.probeSites).toBe(1); + expect(collapse.csh.logicalStateRecovery).toBe(1); + expect(collapse.csh.operationF1).toBeNull(); + expect(collapse.combined.probeSites).toBe(1); + expect(collapse.combined.finalFrameRecovery).toBe(1); + expect(collapse.combined.operationF1).toBeNull(); + }); +}); diff --git a/packages/ruam/test/isogloss/support/revised-attacker-gates.ts b/packages/ruam/test/isogloss/support/revised-attacker-gates.ts new file mode 100644 index 0000000..f225db5 --- /dev/null +++ b/packages/ruam/test/isogloss/support/revised-attacker-gates.ts @@ -0,0 +1,1549 @@ +import { + generateBprfArtifact, + type BprfArtifact, + type BprfCallerContext, + type BprfRealization, + type BprfWireBasis, + type PureRegionContract, + type PureScalar, +} from "../../../src/isogloss/bprf/index.js"; +import { + evaluateBprfReference, + selectBprfRealization, +} from "../../../src/isogloss/bprf/testing-reference.js"; +import { + createCustodyClientState, + openCustodiedProjection, + prepareCustodyRequest, +} from "../../../src/isogloss/csh/custody-protocol.js"; +import { + applyCustodiedChartContribution, + createChartCustodyClientState, + prepareChartCustodyRequest, + type ChartCustodyResponse, +} from "../../../src/isogloss/csh/chart-custody-protocol.js"; +import { ReferenceChartRelationCustodian } from "../../../src/isogloss/csh/reference-chart-custodian.js"; +import { ReferenceRelationCustodian } from "../../../src/isogloss/csh/reference-custodian.js"; +import { + CSH_FIELD_MODULUS, + createChartCover, + encodeIngressCharts, + transportAffineCharts, + type ChartCover, + type EncodedChart, +} from "../../../src/isogloss/csh/reference.js"; +import { evaluateBprfOverCshReference } from "../../../src/isogloss/csh/testing-bprf-reference.js"; +import { buildLegacyVmBaselineReport } from "./dynamic-attacker.js"; +import { + DYNAMIC_ATTACKER_CORPUS_VERSION, + DYNAMIC_ATTACKER_FIXTURES, +} from "./dynamic-attacker-fixtures.js"; + +export const REVISED_ATTACKER_REPORT_VERSION = 1 as const; + +export type GateStatus = "pass" | "fail" | "not-evaluated"; + +export interface GateResult { + readonly id: string; + readonly status: GateStatus; + readonly measured: number | boolean | string | null; + readonly required: string; + readonly reason: string; +} + +export interface ContextTransferReport { + /** Exact contextual event-identity recall on another context using a seen realization. */ + readonly seenRealizationIdentityRecall: number; + /** Exact identity recall when the trained realization has never been seen. */ + readonly unseenRealizationIdentityRecall: number; + /** + * Recall on that same unseen realization when the attacker consumes the + * complete artifact instead of relying only on prior traces. + */ + readonly artifactAwareUnseenRecall: number; + readonly naiveTransferLossPercentagePoints: number; + readonly artifactAwareTransferLossPercentagePoints: number; + readonly contextsEnumeratedToReachAllRealizations: number; +} + +export interface ArtifactRecoveryReport { + readonly heldOutOutputAccuracy: number; + readonly physicalTopologyRecall: number; + readonly physicalDependencyEdges: number; + readonly pieceEvaluations: number; + readonly factorReads: number; + readonly dynamicPrimitiveObservationsRequired: number; + readonly clientFunctionallyComplete: boolean; + readonly canonicalSourceMappingRecovered: boolean; +} + +export interface C90Report { + readonly configuredThreshold: number; + readonly minimumContributionsFor90Percent: number | null; + readonly recoveryByContributionCount: readonly { + readonly contributions: number; + readonly recovery: number; + }[]; +} + +export interface CoverTransferReport { + readonly sourceCoverAccuracy: number; + readonly fixedDecoderUnseenAccuracy: number; + readonly metadataAwareUnseenAccuracy: number; + readonly fixedDecoderLossPercentagePoints: number; + readonly metadataAwareLossPercentagePoints: number; +} + +export interface AmplificationReport { + readonly plainStateObservationsForG90: number; + readonly cshCellReadsForG90: number; + readonly g90WorkRatio: number; + readonly residentStorageRatio: number; + readonly fullTraceObservationRatio: number; + readonly requiredRatio: number; +} + +export interface CustodyAttackReport { + readonly clientCompleteBeforeResponse: boolean; + readonly standaloneWithoutResponseRejected: boolean; + readonly relationPresentInClientContract: boolean; + readonly replaySucceeded: boolean; + readonly snapshotForkSucceeded: boolean; + readonly learnedBypass: { + readonly succeeded: boolean; + readonly trainingQueries: number; + readonly heldOutAccuracy: number; + readonly localizedPatchSites: number | null; + }; + readonly chartRelation: { + readonly clientCompleteBeforeResponse: boolean; + readonly minimumResponseContributionsForFullDelta: number | null; + readonly logicalDeltaRecovery: number; + readonly learnedBypass: { + readonly succeeded: boolean; + readonly trainingQueries: number; + readonly heldOutAccuracy: number; + readonly localizedPatchSites: number | null; + }; + }; + readonly pfeTopologyRecoveryEvaluated: boolean; +} + +export interface HookCollapseComparison { + readonly legacy: { + readonly probeSites: number; + readonly boundaryRecall: number; + readonly operationF1: number; + readonly recoveredStream: "semantic"; + }; + readonly bprf: { + readonly probeSites: number; + readonly contextualIdentityRecall: number; + readonly operationF1: null; + readonly artifactOutputRecovery: number; + readonly recoveredStream: "contextual-identity"; + }; + readonly csh: { + readonly probeSites: number; + readonly logicalStateRecovery: number; + readonly operationF1: null; + readonly recoveredStream: "chart-state"; + }; + readonly combined: { + readonly probeSites: number; + readonly contextualIdentityRecall: number; + readonly finalFrameRecovery: number; + readonly operationF1: null; + readonly recoveredStream: "chart-and-contextual-identity"; + }; +} + +export interface RevisedAttackerGoNoGoReport { + readonly schemaVersion: typeof REVISED_ATTACKER_REPORT_VERSION; + readonly decision: "go" | "no-go"; + readonly bprf: { + readonly contextTransfer: ContextTransferReport; + readonly artifactRecovery: ArtifactRecoveryReport; + readonly gates: readonly GateResult[]; + }; + readonly csh: { + readonly c90: C90Report; + readonly coverTransfer: CoverTransferReport; + readonly amplification: AmplificationReport; + readonly gates: readonly GateResult[]; + }; + readonly combined: { + readonly outputAccuracy: number; + readonly finalFrameRecovery: number; + readonly amplification: AmplificationReport; + readonly gates: readonly GateResult[]; + }; + readonly custody: { + readonly attack: CustodyAttackReport; + readonly gates: readonly GateResult[]; + }; + readonly hookCollapse: HookCollapseComparison; + readonly failedGates: readonly string[]; + readonly unevaluatedGates: readonly string[]; +} + +const BPRF_CONTRACT: PureRegionContract = { + inputs: [ + { type: "number" }, + { type: "number" }, + { type: "boolean" }, + ], + steps: [ + { type: "number", formula: { tag: "product", left: 0, right: 1 } }, + { type: "number", formula: { tag: "sum", left: 0, right: 1 } }, + { type: "number", formula: { tag: "difference", left: 3, right: 4 } }, + { type: "boolean", formula: { tag: "not", value: 2 } }, + { + type: "number", + formula: { + tag: "select", + gate: 2, + whenTrue: 5, + whenFalse: 4, + }, + }, + ], + outputs: [7, 6], +}; + +const BPRF_INPUTS = [ + [3, 5, true], + [-4, 7, false], + [6, -2, true], + [0, 9, false], + [8, 4, true], + [-3, -5, false], +] as const satisfies readonly (readonly PureScalar[])[]; + +interface RecoveredArtifactEvaluation { + readonly outputs: readonly PureScalar[]; + readonly pieceEvaluations: number; + readonly factorReads: number; +} + +function encodeCoordinate(value: number, basis: BprfWireBasis): number { + return value * basis.scale + basis.bias; +} + +function decodeCoordinate(value: number, basis: BprfWireBasis): number { + return (value - basis.bias) / basis.scale; +} + +/** + * Independent artifact attacker. It consumes no logical contract and imports + * no evaluator implementation; all executable algebra comes from the artifact. + */ +function evaluateRecoveredBprfArtifact( + realization: BprfRealization, + inputs: readonly PureScalar[] +): RecoveredArtifactEvaluation { + const frame: Array = Array.from( + { length: realization.frameSize }, + () => undefined + ); + for (let index = 0; index < inputs.length; index++) { + const port = realization.inputPorts[index]!; + const input = inputs[index]!; + const coordinate = + port.typeCode === 0 + ? (input as number) + : realization.familyCode === 0 + ? input + ? 1 + : 0 + : input + ? 1 + : -1; + frame[port.slot] = encodeCoordinate(coordinate, port.basis); + } + let pieceEvaluations = 0; + let factorReads = 0; + for (const transition of realization.transitions) { + const totals = new Map(); + const bases = new Map(); + for (const fragment of realization.fragments) { + for (const piece of fragment.pieces) { + if (piece.phase !== transition.phase) continue; + pieceEvaluations++; + let contribution = piece.coefficient; + for (const factor of piece.factors) { + factorReads++; + const stored = frame[factor.slot]; + if (stored === undefined) { + throw new Error("ATTACKER_BPRF_READ_BEFORE_WRITE"); + } + contribution *= + decodeCoordinate(stored, factor.basis) + factor.offset; + } + totals.set( + piece.destination, + (totals.get(piece.destination) ?? 0) + contribution + ); + bases.set(piece.destination, piece.destinationBasis); + } + } + for (const destination of transition.writes) { + const total = totals.get(destination); + const basis = bases.get(destination); + if (total === undefined || basis === undefined) { + throw new Error("ATTACKER_BPRF_INCOMPLETE_TRANSITION"); + } + frame[destination] = encodeCoordinate(total, basis); + } + } + const outputs = realization.outputPorts.map((port): PureScalar => { + const stored = frame[port.slot]; + if (stored === undefined) throw new Error("ATTACKER_BPRF_OUTPUT_MISSING"); + const coordinate = decodeCoordinate(stored, port.basis); + if (port.typeCode === 0) return coordinate; + return realization.familyCode === 0 ? coordinate > 0.5 : coordinate > 0; + }); + return { outputs, pieceEvaluations, factorReads }; +} + +function contextsByRealization( + artifact: BprfArtifact, + perRealization: number +): { + readonly contexts: ReadonlyMap; + readonly enumerated: number; +} { + const buckets = new Map( + artifact.realizations.map((realization) => [realization.id, []]) + ); + let enumerated = 0; + while ( + [...buckets.values()].some((contexts) => contexts.length < perRealization) + ) { + const context = { + caller: `attacker-context-${enumerated % 17}`, + epoch: Math.floor(enumerated / 17), + lineage: enumerated * 13, + }; + const selected = selectBprfRealization(artifact, context); + const bucket = buckets.get(selected.id)!; + if (bucket.length < perRealization) bucket.push(context); + enumerated++; + if (enumerated > 10_000) { + throw new Error("ATTACKER_BPRF_CONTEXT_ENUMERATION_EXHAUSTED"); + } + } + return { contexts: buckets, enumerated }; +} + +function traceIdentitySet( + result: ReturnType +): Set { + return new Set( + result.trace.map( + (event) => + `${event.realization}|${event.transition}|${event.fragment}|${event.phase}` + ) + ); +} + +function artifactIdentitySet(realization: BprfRealization): Set { + const identities = new Set(); + for (const transition of realization.transitions) { + for (const fragment of realization.fragments) { + if (fragment.pieces.some((piece) => piece.phase === transition.phase)) { + identities.add( + `${realization.id}|${transition.id}|${fragment.id}|${transition.phase}` + ); + } + } + } + return identities; +} + +function setRecall(expected: ReadonlySet, recovered: ReadonlySet): number { + if (expected.size === 0) return 1; + let matched = 0; + for (const value of expected) { + if (recovered.has(value)) matched++; + } + return matched / expected.size; +} + +function samePureOutputs( + left: readonly PureScalar[], + right: readonly PureScalar[] +): boolean { + return ( + left.length === right.length && + left.every((value, index) => { + const other = right[index]; + return typeof value === "number" && typeof other === "number" + ? Math.abs(value - other) < 1e-9 + : value === other; + }) + ); +} + +function runBprfExperiments(artifact: BprfArtifact): { + contextTransfer: ContextTransferReport; + artifactRecovery: ArtifactRecoveryReport; +} { + const contexts = contextsByRealization(artifact, 2); + const seenRealizations = artifact.realizations.slice( + 0, + Math.ceil(artifact.realizations.length / 2) + ); + const unseenRealizations = artifact.realizations.slice(seenRealizations.length); + const trainedIdentities = new Set(); + for (const realization of seenRealizations) { + const trainingContext = contexts.contexts.get(realization.id)![0]!; + for (const identity of traceIdentitySet( + evaluateBprfReference(artifact, BPRF_INPUTS[0], trainingContext) + )) { + trainedIdentities.add(identity); + } + } + const seenExpected = new Set(); + for (const realization of seenRealizations) { + const testContext = contexts.contexts.get(realization.id)![1]!; + for (const identity of traceIdentitySet( + evaluateBprfReference(artifact, BPRF_INPUTS[1], testContext) + )) { + seenExpected.add(identity); + } + } + const unseenExpected = new Set(); + const artifactAwareUnseen = new Set(); + for (const realization of unseenRealizations) { + const testContext = contexts.contexts.get(realization.id)![1]!; + for (const identity of traceIdentitySet( + evaluateBprfReference(artifact, BPRF_INPUTS[2], testContext) + )) { + unseenExpected.add(identity); + } + for (const identity of artifactIdentitySet(realization)) { + artifactAwareUnseen.add(identity); + } + } + const seenRecall = setRecall(seenExpected, trainedIdentities); + const unseenRecall = setRecall(unseenExpected, trainedIdentities); + const artifactAwareRecall = setRecall(unseenExpected, artifactAwareUnseen); + + let correctOutputs = 0; + let outputCases = 0; + let pieceEvaluations = 0; + let factorReads = 0; + for (const realization of artifact.realizations) { + const context = contexts.contexts.get(realization.id)![0]!; + for (const inputs of BPRF_INPUTS.slice(3)) { + const oracle = evaluateBprfReference(artifact, inputs, context); + const recovered = evaluateRecoveredBprfArtifact(realization, inputs); + if (samePureOutputs(oracle.outputs, recovered.outputs)) correctOutputs++; + outputCases++; + pieceEvaluations += recovered.pieceEvaluations; + factorReads += recovered.factorReads; + } + } + const topologyEdges = artifact.realizations.reduce( + (total, realization) => + total + + realization.fragments.reduce( + (fragmentTotal, fragment) => + fragmentTotal + + fragment.pieces.reduce( + (pieceTotal, piece) => pieceTotal + piece.factors.length, + 0 + ), + 0 + ), + 0 + ); + return { + contextTransfer: { + seenRealizationIdentityRecall: seenRecall, + unseenRealizationIdentityRecall: unseenRecall, + artifactAwareUnseenRecall: artifactAwareRecall, + naiveTransferLossPercentagePoints: seenRecall - unseenRecall, + artifactAwareTransferLossPercentagePoints: + seenRecall - artifactAwareRecall, + contextsEnumeratedToReachAllRealizations: contexts.enumerated, + }, + artifactRecovery: { + heldOutOutputAccuracy: correctOutputs / outputCases, + physicalTopologyRecall: 1, + physicalDependencyEdges: topologyEdges, + pieceEvaluations, + factorReads, + dynamicPrimitiveObservationsRequired: 0, + clientFunctionallyComplete: correctOutputs === outputCases, + canonicalSourceMappingRecovered: false, + }, + }; +} + +function normalizeField(value: number): number { + const normalized = Math.trunc(value) % CSH_FIELD_MODULUS; + return normalized < 0 ? normalized + CSH_FIELD_MODULUS : normalized; +} + +function fieldAdd(left: number, right: number): number { + return normalizeField(normalizeField(left) + normalizeField(right)); +} + +function fieldSubtract(left: number, right: number): number { + return normalizeField(normalizeField(left) - normalizeField(right)); +} + +function fieldMultiply(left: number, right: number): number { + return normalizeField(normalizeField(left) * normalizeField(right)); +} + +function fieldPower(base: number, exponent: number): number { + let result = 1; + let factor = normalizeField(base); + let remaining = exponent; + while (remaining > 0) { + if (remaining % 2 === 1) result = fieldMultiply(result, factor); + factor = fieldMultiply(factor, factor); + remaining = Math.floor(remaining / 2); + } + return result; +} + +function fieldInverse(value: number): number { + const normalized = normalizeField(value); + if (normalized === 0) throw new Error("ATTACKER_FIELD_ZERO_INVERSE"); + return fieldPower(normalized, CSH_FIELD_MODULUS - 2); +} + +function invertFieldMatrix(input: readonly (readonly number[])[]): number[][] { + const width = input.length; + const augmented = input.map((row, rowIndex) => [ + ...row.map(normalizeField), + ...Array.from({ length: width }, (_, column) => + rowIndex === column ? 1 : 0 + ), + ]); + for (let column = 0; column < width; column++) { + let pivot = column; + while (pivot < width && augmented[pivot]![column] === 0) pivot++; + if (pivot === width) throw new Error("ATTACKER_CSH_SINGULAR_MIXING"); + [augmented[column], augmented[pivot]] = [ + augmented[pivot]!, + augmented[column]!, + ]; + const pivotInverse = fieldInverse(augmented[column]![column]!); + for (let index = 0; index < width * 2; index++) { + augmented[column]![index] = fieldMultiply( + augmented[column]![index]!, + pivotInverse + ); + } + for (let row = 0; row < width; row++) { + if (row === column) continue; + const factor = augmented[row]![column]!; + for (let index = 0; index < width * 2; index++) { + augmented[row]![index] = fieldSubtract( + augmented[row]![index]!, + fieldMultiply(factor, augmented[column]![index]!) + ); + } + } + } + return augmented.map((row) => row.slice(width)); +} + +function multiplyFieldMatrixVector( + matrix: readonly (readonly number[])[], + vector: readonly number[] +): number[] { + return matrix.map((row) => + row.reduce( + (total, value, index) => + fieldAdd(total, fieldMultiply(value, vector[index]!)), + 0 + ) + ); +} + +function interpolationWeightsAtZero(points: readonly number[]): number[] { + return points.map((point, index) => { + let numerator = 1; + let denominator = 1; + for (let other = 0; other < points.length; other++) { + if (other === index) continue; + numerator = fieldMultiply( + numerator, + fieldSubtract(0, points[other]!) + ); + denominator = fieldMultiply( + denominator, + fieldSubtract(point, points[other]!) + ); + } + return fieldMultiply(numerator, fieldInverse(denominator)); + }); +} + +/** + * Independent full-state decoder assembled solely from public cover metadata. + * Its existence is intentionally counted against the local CSH gate. + */ +function decodeChartsFromPublicMetadata( + charts: readonly EncodedChart[], + cover: ChartCover +): number[] { + if (charts.length === 0) throw new Error("ATTACKER_CSH_CHARTS_REQUIRED"); + const descriptors = new Map( + cover.charts.map((descriptor) => [descriptor.id, descriptor]) + ); + const selected = charts.map((chart) => { + if (chart.coverId !== cover.id) { + throw new Error("ATTACKER_CSH_WRONG_COVER"); + } + const descriptor = descriptors.get(chart.chartId); + if (descriptor === undefined) { + throw new Error("ATTACKER_CSH_UNKNOWN_CHART"); + } + return { chart, descriptor }; + }); + const weights = interpolationWeightsAtZero( + selected.map(({ descriptor }) => descriptor.point) + ); + const mixedConstant = Array.from({ length: cover.width }, (_, lane) => { + let constant = 0; + for (let index = 0; index < selected.length; index++) { + const { chart, descriptor } = selected[index]!; + const transform = descriptor.cells[lane]!; + const raw = fieldMultiply( + fieldSubtract( + fieldPower(chart.cells[lane]!, transform.inverseExponent), + transform.offset + ), + fieldInverse(transform.scale) + ); + constant = fieldAdd( + constant, + fieldMultiply(weights[index]!, raw) + ); + } + return constant; + }); + return multiplyFieldMatrixVector( + invertFieldMatrix(cover.mixing), + mixedConstant.map((value, lane) => + fieldSubtract(value, cover.bias[lane]!) + ) + ); +} + +function exactFieldRecovery( + expected: readonly number[], + recovered: readonly number[] +): number { + let correct = 0; + for (let index = 0; index < expected.length; index++) { + if (normalizeField(expected[index]!) === recovered[index]) correct++; + } + return correct / expected.length; +} + +function runCshExperiments(): { + c90: C90Report; + coverTransfer: CoverTransferReport; + amplification: AmplificationReport; + logicalStateRecovery: number; +} { + const width = 4; + const covers = [0, 1, 2].map((epoch) => + createChartCover({ + seed: 0xc510, + epoch, + width, + chartCount: 5, + threshold: 3, + }) + ); + const logicalStates = Array.from({ length: 12 }, (_, index) => [ + index + 1, + index * 3 + 2, + index * 5 + 4, + index * 7 + 8, + ]); + const recoveryByContributionCount = Array.from( + { length: covers[0]!.charts.length }, + (_, countIndex) => { + const contributions = countIndex + 1; + let recoveredCoordinates = 0; + let totalCoordinates = 0; + for (let coverIndex = 0; coverIndex < covers.length; coverIndex++) { + const cover = covers[coverIndex]!; + for (let stateIndex = 0; stateIndex < logicalStates.length; stateIndex++) { + const state = logicalStates[stateIndex]!; + const charts = encodeIngressCharts( + state, + cover, + 0x9100 + coverIndex * 100 + stateIndex + ); + const recovered = decodeChartsFromPublicMetadata( + charts.slice(0, contributions), + cover + ); + recoveredCoordinates += + exactFieldRecovery(state, recovered) * state.length; + totalCoordinates += state.length; + } + } + return { + contributions, + recovery: recoveredCoordinates / totalCoordinates, + }; + } + ); + const c90Entry = recoveryByContributionCount.find( + (entry) => entry.recovery >= 0.9 + ); + + const sourceCover = covers[0]!; + const sourceState = logicalStates[0]!; + const sourceCharts = encodeIngressCharts(sourceState, sourceCover, 0xa001); + const sourceAccuracy = exactFieldRecovery( + sourceState, + decodeChartsFromPublicMetadata( + sourceCharts.slice(0, sourceCover.threshold), + sourceCover + ) + ); + let fixedCorrect = 0; + let metadataCorrect = 0; + let transferCases = 0; + for (const targetCover of covers.slice(1)) { + for (let index = 0; index < logicalStates.length; index++) { + const state = logicalStates[index]!; + const charts = encodeIngressCharts( + state, + targetCover, + 0xb000 + targetCover.epoch * 100 + index + ); + try { + fixedCorrect += exactFieldRecovery( + state, + decodeChartsFromPublicMetadata( + charts.slice(0, targetCover.threshold), + sourceCover + ) + ); + } catch { + // The source-cover decoder rejects target chart identities. + } + metadataCorrect += exactFieldRecovery( + state, + decodeChartsFromPublicMetadata( + charts.slice(0, targetCover.threshold), + targetCover + ) + ); + transferCases++; + } + } + const fixedAccuracy = fixedCorrect / transferCases; + const metadataAccuracy = metadataCorrect / transferCases; + const g90Targets = Math.ceil(width * 0.9); + const c90 = c90Entry?.contributions ?? null; + const g90Reads = c90 === null ? Number.POSITIVE_INFINITY : c90 * g90Targets; + return { + c90: { + configuredThreshold: sourceCover.threshold, + minimumContributionsFor90Percent: c90, + recoveryByContributionCount, + }, + coverTransfer: { + sourceCoverAccuracy: sourceAccuracy, + fixedDecoderUnseenAccuracy: fixedAccuracy, + metadataAwareUnseenAccuracy: metadataAccuracy, + fixedDecoderLossPercentagePoints: sourceAccuracy - fixedAccuracy, + metadataAwareLossPercentagePoints: sourceAccuracy - metadataAccuracy, + }, + amplification: { + plainStateObservationsForG90: g90Targets, + cshCellReadsForG90: g90Reads, + g90WorkRatio: g90Reads / g90Targets, + residentStorageRatio: sourceCover.charts.length, + fullTraceObservationRatio: sourceCover.charts.length, + requiredRatio: 10, + }, + logicalStateRecovery: metadataAccuracy, + }; +} + +function coversForCombined( + realization: BprfRealization, + seed: number +): readonly ChartCover[] { + return Array.from( + { length: realization.transitions.length + 1 }, + (_, epoch) => + createChartCover({ + seed, + epoch, + width: realization.frameSize, + chartCount: 5, + threshold: 3, + }) + ); +} + +function runCombinedExperiment( + artifact: BprfArtifact, + context: BprfCallerContext, + c90: number +): { + outputAccuracy: number; + finalFrameRecovery: number; + amplification: AmplificationReport; + contextualIdentityRecall: number; +} { + const realization = selectBprfRealization(artifact, context); + const covers = coversForCombined(realization, 0xc05e); + let correctOutputs = 0; + let outputCount = 0; + let finalFrameRecovery = 0; + let contextualIdentityRecall = 0; + let plainTraceEvents = 0; + let combinedTraceEvents = 0; + for (let index = 0; index < BPRF_INPUTS.length; index++) { + const inputs = BPRF_INPUTS[index]!; + const plain = evaluateBprfReference(artifact, inputs, context); + const combined = evaluateBprfOverCshReference( + artifact, + inputs, + context, + covers, + 0xc500 + index + ); + for (let outputIndex = 0; outputIndex < plain.outputs.length; outputIndex++) { + const left = combined.outputs[outputIndex]; + const right = plain.outputs[outputIndex]; + if ( + typeof left === "number" && + typeof right === "number" && + Math.abs(left - right) < 1e-9 + ) { + correctOutputs++; + } else if (left === right) { + correctOutputs++; + } + outputCount++; + } + const finalCover = covers[covers.length - 1]!; + const recoveredFrame = decodeChartsFromPublicMetadata( + combined.charts.slice(0, c90), + finalCover + ); + const recoveredOutputs = realization.outputPorts.map((port): PureScalar => { + const coordinate = fieldMultiply( + fieldSubtract(recoveredFrame[port.slot]!, port.basis.bias), + fieldInverse(port.basis.scale) + ); + if (port.typeCode === 0) { + const signed = + coordinate > CSH_FIELD_MODULUS / 2 + ? coordinate - CSH_FIELD_MODULUS + : coordinate; + return Object.is(signed, -0) ? 0 : signed; + } + return realization.familyCode === 0 + ? coordinate !== 0 + : coordinate > 0 && coordinate <= CSH_FIELD_MODULUS / 2; + }); + if (samePureOutputs(combined.outputs, recoveredOutputs)) { + finalFrameRecovery++; + } + const plainIdentities = new Set( + plain.trace.map( + (event) => + `${event.realization}|${event.transition}|${event.fragment}|${event.phase}` + ) + ); + const combinedIdentities = new Set( + combined.trace.map( + (event) => + `${event.realization}|${event.transition}|${event.fragment}|${event.phase}` + ) + ); + contextualIdentityRecall += setRecall(plainIdentities, combinedIdentities); + plainTraceEvents += plain.trace.length; + combinedTraceEvents += combined.trace.length; + } + const g90Targets = Math.ceil(realization.frameSize * 0.9); + return { + outputAccuracy: correctOutputs / outputCount, + finalFrameRecovery: finalFrameRecovery / BPRF_INPUTS.length, + contextualIdentityRecall: + contextualIdentityRecall / BPRF_INPUTS.length, + amplification: { + plainStateObservationsForG90: g90Targets, + cshCellReadsForG90: c90 * g90Targets, + g90WorkRatio: c90, + residentStorageRatio: + (covers[0]!.charts.length * realization.frameSize) / + realization.frameSize, + fullTraceObservationRatio: combinedTraceEvents / plainTraceEvents, + requiredRatio: 10, + }, + }; +} + +const CUSTODY_RELATION = { + projectionSite: { + id: "custodied_input", + coefficients: [2, 3, 5], + bias: 19, + }, + cubic: 37, + linear: 41, + bias: 43, +} as const; + +function createCustodyFixture(sessionOrdinal: number, firstCoordinate: number) { + const cover = createChartCover({ seed: 808, epoch: 3, width: 3 }); + const charts = encodeIngressCharts( + [firstCoordinate, 11, 13], + cover, + 809 + sessionOrdinal + ); + const custodian = new ReferenceRelationCustodian({ + sessionId: `session_${sessionOrdinal}`, + contractId: "contract_crown", + cover, + initialLineageCommitment: "lineage_genesis", + lineageSecret: "server-only-lineage-secret", + responseSecret: "server-only-response-secret", + relation: CUSTODY_RELATION, + }); + return { cover, charts, custodian }; +} + +function queryCustodian(sessionOrdinal: number, firstCoordinate: number): number { + const { charts, custodian } = createCustodyFixture( + sessionOrdinal, + firstCoordinate + ); + const contract = custodian.clientContract; + const state = createCustodyClientState(contract); + const request = prepareCustodyRequest( + contract, + state, + charts, + `nonce_query_${sessionOrdinal}` + ); + const response = custodian.evaluate(request); + return openCustodiedProjection( + contract, + state, + response, + request.nonce + ).projection; +} + +function interpolateFieldAt( + samples: readonly { x: number; y: number }[], + target: number +): number { + let result = 0; + for (let index = 0; index < samples.length; index++) { + let basis = 1; + for (let other = 0; other < samples.length; other++) { + if (other === index) continue; + basis = fieldMultiply( + basis, + fieldMultiply( + fieldSubtract(target, samples[other]!.x), + fieldInverse( + fieldSubtract(samples[index]!.x, samples[other]!.x) + ) + ) + ); + } + result = fieldAdd( + result, + fieldMultiply(samples[index]!.y, basis) + ); + } + return result; +} + +const CHART_IDENTITY_TRANSITION = { + id: "attacker_local_identity", + matrix: [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + ], + bias: [0, 0, 0], +} as const; + +const CHART_CUSTODY_RELATION = { + inputProjection: { + id: "hidden_input", + coefficients: [2, 1, 3], + bias: 5, + }, + cubic: 7, + linear: 11, + bias: 17, + outputDirection: [1, 2, 0], +} as const; + +function createChartCustodyFixture( + sessionOrdinal: number, + firstCoordinate: number +) { + const from = createChartCover({ seed: 2027, epoch: 0, width: 3 }); + const to = createChartCover({ seed: 2027, epoch: 1, width: 3 }); + const values = [firstCoordinate, 11, 13] as const; + const incoming = encodeIngressCharts(values, from, 3030 + sessionOrdinal); + const local = transportAffineCharts( + incoming, + from, + to, + CHART_IDENTITY_TRANSITION, + 4030 + sessionOrdinal + ); + const custodian = new ReferenceChartRelationCustodian({ + sessionId: `chart_session_${sessionOrdinal}`, + contractId: "hidden_glue", + fromCover: from, + toCover: to, + initialLineageCommitment: "chart_genesis", + lineageSecret: "server-chart-lineage", + sharingSecret: "server-chart-sharing", + relation: CHART_CUSTODY_RELATION, + }); + return { from, to, values, incoming, local, custodian }; +} + +function recoverLogicalDeltaFromChartResponse( + response: ChartCustodyResponse, + targetCover: ChartCover, + contributionCount: number +): number[] { + const selected = response.chartContributions.slice(0, contributionCount); + const descriptors = new Map( + targetCover.charts.map((descriptor) => [descriptor.id, descriptor]) + ); + const points = selected.map((contribution) => { + const descriptor = descriptors.get(contribution.chartId); + if (descriptor === undefined) { + throw new Error("ATTACKER_CHART_CUSTODY_UNKNOWN_CONTRIBUTION"); + } + return descriptor.point; + }); + const weights = interpolationWeightsAtZero(points); + const mixedDelta = Array.from({ length: targetCover.width }, (_, lane) => + selected.reduce( + (total, contribution, index) => + fieldAdd( + total, + fieldMultiply(weights[index]!, contribution.cells[lane]!) + ), + 0 + ) + ); + return multiplyFieldMatrixVector( + invertFieldMatrix(targetCover.mixing), + mixedDelta + ); +} + +function expectedChartLogicalDelta(firstCoordinate: number): number[] { + const projected = normalizeField( + 2 * firstCoordinate + 11 + 3 * 13 + 5 + ); + const residual = fieldAdd( + fieldAdd( + fieldMultiply( + CHART_CUSTODY_RELATION.cubic, + fieldMultiply(fieldMultiply(projected, projected), projected) + ), + fieldMultiply(CHART_CUSTODY_RELATION.linear, projected) + ), + CHART_CUSTODY_RELATION.bias + ); + return CHART_CUSTODY_RELATION.outputDirection.map((direction) => + fieldMultiply(direction, residual) + ); +} + +function queryChartCustodian( + sessionOrdinal: number, + firstCoordinate: number, + contributionCount = 3 +): { + readonly logicalDelta: readonly number[]; + readonly response: ChartCustodyResponse; +} { + const fixture = createChartCustodyFixture(sessionOrdinal, firstCoordinate); + const contract = fixture.custodian.clientContract; + const state = createChartCustodyClientState(contract); + const request = prepareChartCustodyRequest( + contract, + state, + fixture.incoming, + `chart_query_${sessionOrdinal}` + ); + const response = fixture.custodian.evaluate(request); + return { + logicalDelta: recoverLogicalDeltaFromChartResponse( + response, + fixture.to, + contributionCount + ), + response, + }; +} + +function runChartCustodyExperiment(): CustodyAttackReport["chartRelation"] { + const incomplete = createChartCustodyFixture(400, 7); + const incompleteContract = incomplete.custodian.clientContract; + const incompleteState = createChartCustodyClientState(incompleteContract); + const incompleteRequest = prepareChartCustodyRequest( + incompleteContract, + incompleteState, + incomplete.incoming, + "chart_missing_0400" + ); + let missingResponseRejected = false; + try { + applyCustodiedChartContribution( + incompleteContract, + incompleteState, + incomplete.local, + incomplete.to, + undefined, + incompleteRequest.nonce + ); + } catch (error) { + missingResponseRejected = String(error).includes( + "RUAM_CSH_CUSTODIAN_REQUIRED" + ); + } + + const probeInputs = [1, 3, 7, 11, 17, 23, 29, 31]; + const recoveryByCount = [1, 2, 3, 4, 5].map((contributionCount) => { + let recoveredCoordinates = 0; + let totalCoordinates = 0; + for (let index = 0; index < probeInputs.length; index++) { + const firstCoordinate = probeInputs[index]!; + const recovered = queryChartCustodian( + 500 + contributionCount * 100 + index, + firstCoordinate, + contributionCount + ).logicalDelta; + const expected = expectedChartLogicalDelta(firstCoordinate); + recoveredCoordinates += + exactFieldRecovery(expected, recovered) * expected.length; + totalCoordinates += expected.length; + } + return { + contributionCount, + recovery: recoveredCoordinates / totalCoordinates, + }; + }); + const minimumForFullDelta = + recoveryByCount.find((entry) => entry.recovery >= 0.9) + ?.contributionCount ?? null; + + const trainingX = [1, 7, 13, 19]; + const samples = trainingX.map((x, index) => ({ + x, + y: queryChartCustodian(1_100 + index, x).logicalDelta[0]!, + })); + const heldOutX = [3, 11, 23, 29]; + let correctPredictions = 0; + for (let index = 0; index < heldOutX.length; index++) { + const x = heldOutX[index]!; + const predictedResidual = interpolateFieldAt(samples, x); + const predicted = [ + predictedResidual, + fieldMultiply(2, predictedResidual), + 0, + ]; + const observed = queryChartCustodian( + 1_200 + index, + x + ).logicalDelta; + if (exactFieldRecovery(observed, predicted) === 1) { + correctPredictions++; + } + } + const heldOutAccuracy = correctPredictions / heldOutX.length; + return { + clientCompleteBeforeResponse: !missingResponseRejected, + minimumResponseContributionsForFullDelta: minimumForFullDelta, + logicalDeltaRecovery: + recoveryByCount.find((entry) => entry.contributionCount === 3) + ?.recovery ?? 0, + learnedBypass: { + succeeded: heldOutAccuracy === 1, + trainingQueries: trainingX.length, + heldOutAccuracy, + localizedPatchSites: heldOutAccuracy === 1 ? 1 : null, + }, + }; +} + +function runCustodyExperiment(): CustodyAttackReport { + const first = createCustodyFixture(100, 7); + const contract = first.custodian.clientContract; + const initialState = createCustodyClientState(contract); + let standaloneRejected = false; + try { + openCustodiedProjection( + contract, + initialState, + undefined, + "nonce_missing_response" + ); + } catch (error) { + standaloneRejected = String(error).includes("RUAM_CSH_CUSTODIAN_REQUIRED"); + } + const serializedContract = JSON.stringify(contract).toLowerCase(); + const relationPresentInClientContract = [ + "projectionsite", + "cubic", + "linear", + "server-only", + ].some((token) => serializedContract.includes(token)); + + const fork = createCustodyFixture(101, 9); + const forkContract = fork.custodian.clientContract; + const forkState = createCustodyClientState(forkContract); + const firstRequest = prepareCustodyRequest( + forkContract, + forkState, + fork.charts, + "nonce_fork_1001" + ); + const secondRequest = prepareCustodyRequest( + forkContract, + forkState, + fork.charts, + "nonce_fork_1002" + ); + fork.custodian.evaluate(firstRequest); + let replaySucceeded = true; + let snapshotForkSucceeded = true; + try { + fork.custodian.evaluate(firstRequest); + } catch { + replaySucceeded = false; + } + try { + fork.custodian.evaluate(secondRequest); + } catch { + snapshotForkSucceeded = false; + } + + const trainingX = [1, 7, 13, 19]; + const samples = trainingX.map((x, index) => ({ + x, + y: queryCustodian(200 + index, x), + })); + const heldOutX = [3, 11, 23, 29]; + let correct = 0; + for (let index = 0; index < heldOutX.length; index++) { + const x = heldOutX[index]!; + const predicted = interpolateFieldAt(samples, x); + const observed = queryCustodian(300 + index, x); + if (predicted === observed) correct++; + } + const heldOutAccuracy = correct / heldOutX.length; + const chartRelation = runChartCustodyExperiment(); + return { + clientCompleteBeforeResponse: !standaloneRejected, + standaloneWithoutResponseRejected: standaloneRejected, + relationPresentInClientContract, + replaySucceeded, + snapshotForkSucceeded, + learnedBypass: { + succeeded: heldOutAccuracy === 1, + trainingQueries: trainingX.length, + heldOutAccuracy, + localizedPatchSites: heldOutAccuracy === 1 ? 1 : null, + }, + chartRelation, + pfeTopologyRecoveryEvaluated: false, + }; +} + +function gate( + id: string, + status: GateStatus, + measured: GateResult["measured"], + required: string, + reason: string +): GateResult { + return Object.freeze({ id, status, measured, required, reason }); +} + +/** Build the deterministic, owner-only D2/D3 go/no-go report. */ +export function buildRevisedAttackerGoNoGoReport(): RevisedAttackerGoNoGoReport { + const artifact = generateBprfArtifact(BPRF_CONTRACT, { + seed: 0x51c0ffee, + realizationCount: 4, + fragmentCount: 3, + }); + const bprf = runBprfExperiments(artifact); + const csh = runCshExperiments(); + const context = contextsByRealization(artifact, 1).contexts.get( + artifact.realizations[0]!.id + )![0]!; + const combined = runCombinedExperiment( + artifact, + context, + csh.c90.minimumContributionsFor90Percent ?? 3 + ); + const custody = runCustodyExperiment(); + const legacy = buildLegacyVmBaselineReport(DYNAMIC_ATTACKER_FIXTURES, { + buildSeed: 0x1badb002, + repeatCount: 2, + corpusVersion: DYNAMIC_ATTACKER_CORPUS_VERSION, + }); + + const bprfGates = [ + gate( + "BPRF-NO-SEMANTIC-EVENT", + "pass", + false, + "no trace event emits a semantic operation or source identity", + "Reference traces contain only realization, transition, fragment, and phase identities." + ), + gate( + "BPRF-ARTIFACT-AWARE-TRANSFER-DROP", + bprf.contextTransfer.artifactAwareTransferLossPercentagePoints >= 0.3 + ? "pass" + : "fail", + bprf.contextTransfer.artifactAwareTransferLossPercentagePoints, + ">= 0.30 loss on unseen realizations/contexts", + "Naive identity transfer collapses, but all unseen realization topology and identities are enumerable from the shipped artifact." + ), + gate( + "BPRF-FULL-STEP-O90", + "fail", + bprf.artifactRecovery.dynamicPrimitiveObservationsRequired, + ">= 10x legacy dynamic observations for 90% recovery", + "The independent attacker evaluates held-out behavior exactly from artifact algebra with zero dynamic observations." + ), + gate( + "BPRF-O1-OP-F1", + "not-evaluated", + null, + "< 0.50 canonical operation F1", + "The pure reference has no canonical source-operation owner sidecar, so assigning zero would game the gate." + ), + gate( + "BPRF-PATCH-COLLAPSE", + "not-evaluated", + 1, + "no <=3-site patch emits a stable canonical stream", + "One reference-result probe emits a stable contextual stream, but it is not a canonical semantic stream and there is no product runtime to patch." + ), + ] as const; + + const cshGates = [ + gate( + "CSH-C90-THRESHOLD", + csh.c90.minimumContributionsFor90Percent !== null && + csh.c90.minimumContributionsFor90Percent >= + csh.c90.configuredThreshold + ? "pass" + : "fail", + csh.c90.minimumContributionsFor90Percent, + `>= configured threshold ${csh.c90.configuredThreshold}`, + "Fewer contributions fail empirically; the configured threshold reconstructs every tested coordinate." + ), + gate( + "CSH-G90-WORK-AMPLIFICATION", + csh.amplification.g90WorkRatio >= 10 ? "pass" : "fail", + csh.amplification.g90WorkRatio, + ">= 10x plain BPRF", + "Minimum correlated cell reads scale with threshold three, not ten." + ), + gate( + "CSH-STORAGE-AMPLIFICATION", + csh.amplification.residentStorageRatio >= 10 ? "pass" : "fail", + csh.amplification.residentStorageRatio, + ">= 10x plain BPRF", + "Five full-width charts provide only a fivefold resident-cell proxy." + ), + gate( + "CSH-COVER-TRANSFER", + csh.coverTransfer.metadataAwareLossPercentagePoints >= 0.3 + ? "pass" + : "fail", + csh.coverTransfer.metadataAwareLossPercentagePoints, + ">= 0.30 loss on unseen covers", + "A frozen cover decoder fails, but one metadata-driven algorithm recovers every unseen cover because all transforms, mixing, and biases are public." + ), + gate( + "CSH-LOCAL-PROBE-COLLAPSE", + "fail", + 1, + ">3 localized probes for a stable logical-state map", + "One result/chart-collection probe plus public cover metadata reconstructs the entire logical vector." + ), + ] as const; + + const combinedGates = [ + gate( + "BPRF-CSH-CORRECTNESS", + combined.outputAccuracy === 1 ? "pass" : "fail", + combined.outputAccuracy, + "1.0 held-out output accuracy", + "Combined reference outputs match plain BPRF on every measured input." + ), + gate( + "BPRF-CSH-G90-WORK-AMPLIFICATION", + combined.amplification.g90WorkRatio >= 10 ? "pass" : "fail", + combined.amplification.g90WorkRatio, + ">= 10x plain BPRF", + "The minimum final-frame recovery proxy remains threshold-times-width." + ), + gate( + "BPRF-CSH-FULL-TRACE-AMPLIFICATION", + combined.amplification.fullTraceObservationRatio >= 10 + ? "pass" + : "fail", + combined.amplification.fullTraceObservationRatio, + ">= 10x plain BPRF", + "Chart-local tracing multiplies contextual events by five, below the tenfold gate." + ), + gate( + "BPRF-CSH-CLIENT-TRACE-COMPLETENESS", + combined.finalFrameRecovery < 0.9 ? "pass" : "fail", + combined.finalFrameRecovery, + "< 0.90 final-frame recovery", + "The returned charts and public final-cover metadata reconstruct all held-out final frames." + ), + ] as const; + + const custodyGates = [ + gate( + "CUSTODY-CLIENT-INCOMPLETE", + !custody.clientCompleteBeforeResponse ? "pass" : "fail", + custody.clientCompleteBeforeResponse, + "false before custodian response", + "The client opening API rejects a missing response and its contract carries no relation." + ), + gate( + "CUSTODY-REPLAY-FORK", + !custody.replaySucceeded && !custody.snapshotForkSucceeded + ? "pass" + : "fail", + custody.replaySucceeded || custody.snapshotForkSucceeded, + "no replay or second fork succeeds", + "Consumed nonce and stale-lineage checks reject both attacks." + ), + gate( + "CUSTODY-BYPASS", + custody.learnedBypass.succeeded || + custody.chartRelation.learnedBypass.succeeded + ? "fail" + : "pass", + Math.max( + custody.learnedBypass.heldOutAccuracy, + custody.chartRelation.learnedBypass.heldOutAccuracy + ), + "no compact offline substitute preserves held-out outputs", + "Four fresh-session queries interpolate both bounded cubic references on the measured one-dimensional input slices; one-site substitutes predict every held-out query." + ), + gate( + "CUSTODY-CHART-DELTA-LEAKAGE", + custody.chartRelation.logicalDeltaRecovery < 0.9 ? "pass" : "fail", + custody.chartRelation.logicalDeltaRecovery, + "< 0.90 recovery of the hidden transition's logical delta from one response", + "Three intercepted additive chart contributions plus the public target cover reconstruct the complete per-input logical delta. This is not yet offline relation recovery, which is measured separately." + ), + gate( + "CUSTODY-PFE-TOPOLOGY", + "not-evaluated", + null, + "topology-hidden actively secure PFE recovery at accepted leakage", + "The committed reference is direct remote evaluation, not a universal-circuit PFE prototype." + ), + ] as const; + + const hookCollapse: HookCollapseComparison = { + legacy: { + probeSites: legacy.patchCollapse.minimumLocalizedSites ?? 1, + boundaryRecall: legacy.aggregate.boundaryEvents.recall, + operationF1: legacy.aggregate.operationLabels.f1, + recoveredStream: "semantic", + }, + bprf: { + probeSites: 1, + contextualIdentityRecall: 1, + operationF1: null, + artifactOutputRecovery: bprf.artifactRecovery.heldOutOutputAccuracy, + recoveredStream: "contextual-identity", + }, + csh: { + probeSites: 1, + logicalStateRecovery: csh.logicalStateRecovery, + operationF1: null, + recoveredStream: "chart-state", + }, + combined: { + probeSites: 1, + contextualIdentityRecall: combined.contextualIdentityRecall, + finalFrameRecovery: combined.finalFrameRecovery, + operationF1: null, + recoveredStream: "chart-and-contextual-identity", + }, + }; + + const allGates = [ + ...bprfGates, + ...cshGates, + ...combinedGates, + ...custodyGates, + ]; + const failedGates = allGates + .filter((result) => result.status === "fail") + .map((result) => result.id); + const unevaluatedGates = allGates + .filter((result) => result.status === "not-evaluated") + .map((result) => result.id); + return Object.freeze({ + schemaVersion: REVISED_ATTACKER_REPORT_VERSION, + decision: failedGates.length === 0 && unevaluatedGates.length === 0 + ? "go" + : "no-go", + bprf: Object.freeze({ + contextTransfer: Object.freeze(bprf.contextTransfer), + artifactRecovery: Object.freeze(bprf.artifactRecovery), + gates: Object.freeze(bprfGates), + }), + csh: Object.freeze({ + c90: Object.freeze(csh.c90), + coverTransfer: Object.freeze(csh.coverTransfer), + amplification: Object.freeze(csh.amplification), + gates: Object.freeze(cshGates), + }), + combined: Object.freeze({ + outputAccuracy: combined.outputAccuracy, + finalFrameRecovery: combined.finalFrameRecovery, + amplification: Object.freeze(combined.amplification), + gates: Object.freeze(combinedGates), + }), + custody: Object.freeze({ + attack: Object.freeze(custody), + gates: Object.freeze(custodyGates), + }), + hookCollapse: Object.freeze(hookCollapse), + failedGates: Object.freeze(failedGates), + unevaluatedGates: Object.freeze(unevaluatedGates), + }); +} From 2350fccf3a0cd02c549b38faebb51e39cbf9f2ee Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:22:16 -1000 Subject: [PATCH 18/54] feat: bind stateful masked CSH custody --- .../isogloss/csh/masked-custody-protocol.ts | 155 +++++ .../csh/reference-masked-custodian.ts | 548 ++++++++++++++++++ .../test/isogloss/csh-masked-custody.test.ts | 326 +++++++++++ .../test/isogloss/reference-isolation.test.ts | 2 + 4 files changed, 1031 insertions(+) create mode 100644 packages/ruam/src/isogloss/csh/masked-custody-protocol.ts create mode 100644 packages/ruam/src/isogloss/csh/reference-masked-custodian.ts create mode 100644 packages/ruam/test/isogloss/csh-masked-custody.test.ts diff --git a/packages/ruam/src/isogloss/csh/masked-custody-protocol.ts b/packages/ruam/src/isogloss/csh/masked-custody-protocol.ts new file mode 100644 index 0000000..e57493f --- /dev/null +++ b/packages/ruam/src/isogloss/csh/masked-custody-protocol.ts @@ -0,0 +1,155 @@ +/** + * Client protocol for statefully masked multi-transition CSH custody. + * + * The client carries charts for `logicalState + custodianMask`. It can perform + * an identity cover transport and combine a signed transition contribution, + * but neither the hidden transition nor the current/next representation mask + * appears in this contract. + * + * @module isogloss/csh/masked-custody-protocol + */ + +import { + applyCustodiedChartContribution, + prepareChartCustodyRequest, + type ChartCustodyClientContract, + type ChartCustodyRequest, + type ChartCustodyResponse, +} from "./chart-custody-protocol.js"; +import { + openCustodiedProjection, + type CustodyClientContract, + type CustodyClientState, + type CustodyRequest, + type CustodyResponse, + type OpenedCustodyProjection, +} from "./custody-protocol.js"; +import type { ChartCover, EncodedChart } from "./reference.js"; + +export interface MaskedCustodyClientContract { + readonly sessionId: string; + readonly contractId: string; + /** Ordered ingress, intermediate, and terminal covers. */ + readonly coverIds: readonly string[]; + readonly initialLineageCommitment: string; + readonly verificationKey: string; +} + +export function createMaskedCustodyClientState( + contract: MaskedCustodyClientContract +): CustodyClientState { + if (contract.coverIds.length < 2) { + throw new Error("RUAM_CSH_MASKED_CUSTODY_COVER_PATH_TOO_SHORT"); + } + return Object.freeze({ + epoch: 0, + lineageCommitment: contract.initialLineageCommitment, + }); +} + +export function prepareMaskedTransitionRequest( + contract: MaskedCustodyClientContract, + state: CustodyClientState, + charts: readonly EncodedChart[], + nonce: string +): ChartCustodyRequest { + const fixed = transitionContract(contract, state.epoch); + return prepareChartCustodyRequest(fixed, state, charts, nonce); +} + +export function applyMaskedTransitionResponse( + contract: MaskedCustodyClientContract, + state: CustodyClientState, + localIdentityTransport: readonly EncodedChart[], + targetCover: ChartCover, + response: ChartCustodyResponse | undefined, + expectedNonce: string +): { + readonly charts: readonly EncodedChart[]; + readonly state: CustodyClientState; +} { + const fixed = transitionContract(contract, state.epoch); + return applyCustodiedChartContribution( + fixed, + state, + localIdentityTransport, + targetCover, + response, + expectedNonce + ); +} + +export function prepareMaskedProjectionRequest( + contract: MaskedCustodyClientContract, + state: CustodyClientState, + charts: readonly EncodedChart[], + nonce: string +): CustodyRequest { + const terminalEpoch = contract.coverIds.length - 1; + if (state.epoch !== terminalEpoch) { + throw new Error( + `RUAM_CSH_MASKED_CUSTODY_NOT_AT_EXIT: ${state.epoch}/${terminalEpoch}` + ); + } + if ( + nonce.length < 8 || + charts.length === 0 || + charts.some( + (chart) => chart.coverId !== contract.coverIds[terminalEpoch] + ) + ) { + throw new Error("RUAM_CSH_MASKED_CUSTODY_INVALID_EXIT_REQUEST"); + } + return Object.freeze({ + sessionId: contract.sessionId, + contractId: contract.contractId, + coverId: contract.coverIds[terminalEpoch]!, + epoch: state.epoch, + lineageCommitment: state.lineageCommitment, + nonce, + charts, + }); +} + +export function openMaskedCustodyProjection( + contract: MaskedCustodyClientContract, + state: CustodyClientState, + response: CustodyResponse | undefined, + expectedNonce: string +): OpenedCustodyProjection { + const terminalEpoch = contract.coverIds.length - 1; + const fixed: CustodyClientContract = Object.freeze({ + sessionId: contract.sessionId, + contractId: contract.contractId, + coverId: contract.coverIds[terminalEpoch]!, + initialLineageCommitment: contract.initialLineageCommitment, + verificationKey: contract.verificationKey, + }); + return openCustodiedProjection( + fixed, + state, + response, + expectedNonce + ); +} + +function transitionContract( + contract: MaskedCustodyClientContract, + epoch: number +): ChartCustodyClientContract { + const fromCoverId = contract.coverIds[epoch]; + const toCoverId = contract.coverIds[epoch + 1]; + if (fromCoverId === undefined || toCoverId === undefined) { + throw new Error( + `RUAM_CSH_MASKED_CUSTODY_NO_TRANSITION: ${epoch}` + ); + } + return Object.freeze({ + sessionId: contract.sessionId, + contractId: contract.contractId, + fromCoverId, + toCoverId, + initialLineageCommitment: contract.initialLineageCommitment, + verificationKey: contract.verificationKey, + }); +} diff --git a/packages/ruam/src/isogloss/csh/reference-masked-custodian.ts b/packages/ruam/src/isogloss/csh/reference-masked-custodian.ts new file mode 100644 index 0000000..1696873 --- /dev/null +++ b/packages/ruam/src/isogloss/csh/reference-masked-custodian.ts @@ -0,0 +1,548 @@ +/** + * Server-side statefully masked CSH relation sequence. + * + * Client charts encode `x + mask(epoch)`. A transition response changes an + * identity-transported masked state into `F_epoch(x) + mask(epoch+1)` without + * returning either the logical state, the transition, or either mask. + * + * @module isogloss/csh/reference-masked-custodian + */ + +import { + createHmac, + generateKeyPairSync, + sign, +} from "node:crypto"; +import { + chartCustodySigningPayload, + type AdditiveChartContribution, + type ChartCustodyRequest, + type ChartCustodyResponse, +} from "./chart-custody-protocol.js"; +import { + custodyResponseSigningPayload, + type CustodyRequest, + type CustodyResponse, +} from "./custody-protocol.js"; +import type { MaskedCustodyClientContract } from "./masked-custody-protocol.js"; +import { + CSH_FIELD_MODULUS, + evaluateCertifiedProjection, + type ChartCellTransform, + type ChartCover, + type CertifiedProjectionSite, +} from "./reference.js"; + +export interface HiddenCubicTerm { + readonly inputProjection: readonly number[]; + readonly outputDirection: readonly number[]; + readonly coefficient: number; +} + +export interface HiddenMaskedTransition { + readonly linear: readonly (readonly number[])[]; + readonly bias: readonly number[]; + readonly cubicTerms: readonly HiddenCubicTerm[]; +} + +export interface ReferenceMaskedCustodianOptions { + readonly sessionId: string; + readonly contractId: string; + readonly covers: readonly ChartCover[]; + readonly transitions: readonly HiddenMaskedTransition[]; + readonly exitProjection: CertifiedProjectionSite; + readonly initialLineageCommitment: string; + readonly lineageSecret: string; + readonly maskSecret: string; + readonly sharingSecret: string; + readonly responseSecret: string; +} + +export class ReferenceMaskedChartCustodian { + readonly clientContract: MaskedCustodyClientContract; + + readonly #covers: readonly ChartCover[]; + readonly #transitions: readonly HiddenMaskedTransition[]; + readonly #exitProjection: CertifiedProjectionSite; + readonly #lineageSecret: string; + readonly #maskSecret: string; + readonly #sharingSecret: string; + readonly #responseSecret: string; + readonly #privateKey: ReturnType["privateKey"]; + readonly #consumedNonces = new Set(); + #epoch = 0; + #lineageCommitment: string; + #mask: number[]; + #expectedMaskedState: readonly number[] | undefined; + #terminated = false; + + constructor(options: ReferenceMaskedCustodianOptions) { + validateOptions(options); + const { privateKey, publicKey } = generateKeyPairSync("ed25519"); + this.#privateKey = privateKey; + this.#covers = options.covers; + this.#transitions = options.transitions; + this.#exitProjection = options.exitProjection; + this.#lineageSecret = options.lineageSecret; + this.#maskSecret = options.maskSecret; + this.#sharingSecret = options.sharingSecret; + this.#responseSecret = options.responseSecret; + this.#lineageCommitment = options.initialLineageCommitment; + this.#mask = Array.from( + { length: options.covers[0]!.width }, + () => 0 + ); + this.clientContract = Object.freeze({ + sessionId: options.sessionId, + contractId: options.contractId, + coverIds: Object.freeze(options.covers.map((cover) => cover.id)), + initialLineageCommitment: options.initialLineageCommitment, + verificationKey: publicKey + .export({ type: "spki", format: "pem" }) + .toString(), + }); + } + + evaluateTransition(request: ChartCustodyRequest): ChartCustodyResponse { + this.#assertLiveRequest(request); + const transition = this.#transitions[this.#epoch]; + const from = this.#covers[this.#epoch]; + const to = this.#covers[this.#epoch + 1]; + if (!transition || !from || !to) { + throw new Error("RUAM_CSH_MASKED_CUSTODY_NO_TRANSITION"); + } + if ( + request.fromCoverId !== from.id || + request.toCoverId !== to.id + ) { + throw new Error("RUAM_CSH_CUSTODY_REQUEST_MISMATCH"); + } + const masked = recoverVector(request.charts, from); + this.#assertExpectedMaskedState(masked); + const logical = masked.map((value, index) => + subtract(value, this.#mask[index]!) + ); + const nextLogical = evaluateHiddenTransition(transition, logical); + const nextMask = deriveVector( + this.#maskSecret, + this.#epoch, + request.nonce, + from.width + ); + const nextMasked = nextLogical.map((value, index) => + add(value, nextMask[index]!) + ); + const delta = nextMasked.map((value, index) => + subtract(value, masked[index]!) + ); + const mixedDelta = multiplyMatrixVector(to.mixing, delta); + const chartContributions = createAdditiveShares( + mixedDelta, + to, + this.#sharingSecret, + this.#epoch, + request.nonce + ); + const nextEpoch = this.#epoch + 1; + const nextLineageCommitment = nextLineage( + this.#lineageSecret, + this.#lineageCommitment, + request.nonce, + chartContributions + ); + const unsigned = { + sessionId: request.sessionId, + contractId: request.contractId, + requestNonce: request.nonce, + epoch: this.#epoch, + nextEpoch, + nextLineageCommitment, + chartContributions, + }; + const signature = sign( + null, + Buffer.from(chartCustodySigningPayload(unsigned)), + this.#privateKey + ).toString("base64"); + this.#consumedNonces.add(request.nonce); + this.#epoch = nextEpoch; + this.#lineageCommitment = nextLineageCommitment; + this.#mask = nextMask; + this.#expectedMaskedState = Object.freeze(nextMasked); + return Object.freeze({ ...unsigned, signature }); + } + + evaluateExitProjection(request: CustodyRequest): CustodyResponse { + this.#assertLiveRequest(request); + if (this.#epoch !== this.#transitions.length) { + throw new Error("RUAM_CSH_MASKED_CUSTODY_NOT_AT_EXIT"); + } + const terminalCover = this.#covers[this.#epoch]!; + if (request.coverId !== terminalCover.id) { + throw new Error("RUAM_CSH_CUSTODY_REQUEST_MISMATCH"); + } + const masked = recoverVector(request.charts, terminalCover); + this.#assertExpectedMaskedState(masked); + const logical = masked.map((value, index) => + subtract(value, this.#mask[index]!) + ); + const projected = add( + dot(this.#exitProjection.coefficients, logical), + this.#exitProjection.bias + ); + const projectionOpening = deriveResponseTransform( + this.#responseSecret, + this.#epoch, + request.nonce + ); + const encodedProjection = wrapProjection( + projected, + projectionOpening + ); + const nextEpoch = this.#epoch + 1; + const nextLineageCommitment = nextLineage( + this.#lineageSecret, + this.#lineageCommitment, + request.nonce, + encodedProjection + ); + const unsigned = { + sessionId: request.sessionId, + contractId: request.contractId, + requestNonce: request.nonce, + epoch: this.#epoch, + nextEpoch, + nextLineageCommitment, + encodedProjection, + projectionOpening, + }; + const signature = sign( + null, + Buffer.from(custodyResponseSigningPayload(unsigned)), + this.#privateKey + ).toString("base64"); + this.#consumedNonces.add(request.nonce); + this.#epoch = nextEpoch; + this.#lineageCommitment = nextLineageCommitment; + this.#terminated = true; + return Object.freeze({ ...unsigned, signature }); + } + + #assertLiveRequest( + request: ChartCustodyRequest | CustodyRequest + ): void { + if (this.#terminated) { + throw new Error("RUAM_CSH_MASKED_CUSTODY_TERMINATED"); + } + if ( + request.sessionId !== this.clientContract.sessionId || + request.contractId !== this.clientContract.contractId + ) { + throw new Error("RUAM_CSH_CUSTODY_REQUEST_MISMATCH"); + } + if (this.#consumedNonces.has(request.nonce)) { + throw new Error("RUAM_CSH_CUSTODY_REPLAY"); + } + if ( + request.epoch !== this.#epoch || + request.lineageCommitment !== this.#lineageCommitment + ) { + throw new Error("RUAM_CSH_CUSTODY_STALE_LINEAGE"); + } + } + + #assertExpectedMaskedState(masked: readonly number[]): void { + if ( + this.#expectedMaskedState !== undefined && + (masked.length !== this.#expectedMaskedState.length || + masked.some( + (value, index) => + value !== this.#expectedMaskedState![index] + )) + ) { + throw new Error("RUAM_CSH_MASKED_CUSTODY_STATE_SUBSTITUTION"); + } + } +} + +function validateOptions(options: ReferenceMaskedCustodianOptions): void { + if ( + options.covers.length < 2 || + options.transitions.length !== options.covers.length - 1 + ) { + throw new Error("RUAM_CSH_MASKED_CUSTODY_PATH_MISMATCH"); + } + const width = options.covers[0]!.width; + if ( + options.covers.some((cover) => cover.width !== width) || + new Set(options.covers.map((cover) => cover.id)).size !== + options.covers.length || + options.exitProjection.coefficients.length !== width + ) { + throw new Error("RUAM_CSH_MASKED_CUSTODY_WIDTH_MISMATCH"); + } + for (const transition of options.transitions) { + if ( + transition.linear.length !== width || + transition.linear.some((row) => row.length !== width) || + transition.bias.length !== width + ) { + throw new Error("RUAM_CSH_MASKED_CUSTODY_WIDTH_MISMATCH"); + } + for (const term of transition.cubicTerms) { + if ( + term.inputProjection.length !== width || + term.outputDirection.length !== width + ) { + throw new Error("RUAM_CSH_MASKED_CUSTODY_WIDTH_MISMATCH"); + } + } + } +} + +function evaluateHiddenTransition( + transition: HiddenMaskedTransition, + logical: readonly number[] +): number[] { + const output = multiplyMatrixVector(transition.linear, logical).map( + (value, index) => add(value, transition.bias[index]!) + ); + for (const term of transition.cubicTerms) { + const projected = dot(term.inputProjection, logical); + const cubic = multiply( + multiply(projected, projected), + projected + ); + for (let index = 0; index < output.length; index++) { + output[index] = add( + output[index]!, + multiply( + multiply(term.coefficient, cubic), + term.outputDirection[index]! + ) + ); + } + } + return output; +} + +function recoverVector( + charts: CustodyRequest["charts"], + cover: ChartCover +): number[] { + return Array.from({ length: cover.width }, (_, coordinate) => + evaluateCertifiedProjection(charts, cover, { + id: `server_projection_${coordinate}`, + coefficients: Array.from( + { length: cover.width }, + (_, index) => (index === coordinate ? 1 : 0) + ), + bias: 0, + }) + ); +} + +function createAdditiveShares( + constant: readonly number[], + cover: ChartCover, + secret: string, + epoch: number, + nonce: string +): readonly AdditiveChartContribution[] { + const residuals = Array.from({ length: cover.width }, (_, lane) => + Array.from({ length: cover.threshold - 1 }, (_, coefficient) => + deriveFieldElement( + secret, + "chart-share", + epoch, + nonce, + lane, + coefficient + ) + ) + ); + return Object.freeze( + cover.charts.map((chart) => { + const cells = constant.map((value, lane) => { + let shared = normalize(value); + let pointPower = chart.point; + for (const residual of residuals[lane]!) { + shared = add(shared, multiply(residual, pointPower)); + pointPower = multiply(pointPower, chart.point); + } + return shared; + }); + return Object.freeze({ + chartId: chart.id, + cells: Object.freeze(cells), + }); + }) + ); +} + +function deriveVector( + secret: string, + epoch: number, + nonce: string, + width: number +): number[] { + return Array.from( + { length: width }, + (_, lane) => + deriveFieldElement( + secret, + "representation-mask", + epoch, + nonce, + lane, + 0 + ) + ); +} + +function deriveFieldElement( + secret: string, + domain: string, + epoch: number, + nonce: string, + lane: number, + coefficient: number +): number { + return createHmac("sha256", secret) + .update(domain) + .update("|") + .update(String(epoch)) + .update("|") + .update(nonce) + .update("|") + .update(String(lane)) + .update("|") + .update(String(coefficient)) + .digest() + .readUInt32LE(0) % CSH_FIELD_MODULUS; +} + +function nextLineage( + secret: string, + current: string, + nonce: string, + value: unknown +): string { + return createHmac("sha256", secret) + .update("lineage") + .update("|") + .update(current) + .update("|") + .update(nonce) + .update("|") + .update(JSON.stringify(value)) + .digest("hex"); +} + +function deriveResponseTransform( + secret: string, + epoch: number, + nonce: string +): ChartCellTransform { + const digest = createHmac("sha256", secret) + .update("projection-opening") + .update("|") + .update(String(epoch)) + .update("|") + .update(nonce) + .digest(); + const scale = + 1 + (digest.readUInt32LE(0) % (CSH_FIELD_MODULUS - 1)); + const offset = digest.readUInt32LE(4) % CSH_FIELD_MODULUS; + const order = CSH_FIELD_MODULUS - 1; + let exponent = 3 + (digest.readUInt32LE(8) % (order - 3)); + while (gcd(exponent, order) !== 1) { + exponent++; + if (exponent >= order) exponent = 3; + } + return Object.freeze({ + scale, + offset, + exponent, + inverseExponent: inverseInteger(exponent, order), + }); +} + +function wrapProjection( + raw: number, + transform: ChartCellTransform +): number { + return power( + add(multiply(transform.scale, raw), transform.offset), + transform.exponent + ); +} + +function multiplyMatrixVector( + matrix: readonly (readonly number[])[], + input: readonly number[] +): number[] { + return matrix.map((row) => dot(row, input)); +} + +function dot(left: readonly number[], right: readonly number[]): number { + let value = 0; + for (let index = 0; index < left.length; index++) { + value = add(value, multiply(left[index]!, right[index]!)); + } + return value; +} + +function power(base: number, exponent: number): number { + let result = 1; + let factor = normalize(base); + let remaining = exponent; + while (remaining > 0) { + if (remaining % 2 === 1) result = multiply(result, factor); + factor = multiply(factor, factor); + remaining = Math.floor(remaining / 2); + } + return result; +} + +function inverseInteger(value: number, modulus: number): number { + let oldR = normalizeFor(value, modulus); + let r = modulus; + let oldS = 1; + let s = 0; + while (r !== 0) { + const quotient = Math.floor(oldR / r); + [oldR, r] = [r, oldR - quotient * r]; + [oldS, s] = [s, oldS - quotient * s]; + } + if (oldR !== 1) throw new Error("RUAM_CSH_NONINVERTIBLE_INTEGER"); + return normalizeFor(oldS, modulus); +} + +function gcd(left: number, right: number): number { + let a = Math.abs(left); + let b = Math.abs(right); + while (b !== 0) [a, b] = [b, a % b]; + return a; +} + +function add(left: number, right: number): number { + return normalize(normalize(left) + normalize(right)); +} + +function subtract(left: number, right: number): number { + return normalize(normalize(left) - normalize(right)); +} + +function multiply(left: number, right: number): number { + return normalize(normalize(left) * normalize(right)); +} + +function normalize(value: number): number { + return normalizeFor(value, CSH_FIELD_MODULUS); +} + +function normalizeFor(value: number, modulus: number): number { + const normalized = Math.trunc(value) % modulus; + return normalized < 0 ? normalized + modulus : normalized; +} diff --git a/packages/ruam/test/isogloss/csh-masked-custody.test.ts b/packages/ruam/test/isogloss/csh-masked-custody.test.ts new file mode 100644 index 0000000..85573fc --- /dev/null +++ b/packages/ruam/test/isogloss/csh-masked-custody.test.ts @@ -0,0 +1,326 @@ +import { describe, expect, it } from "bun:test"; +import { + applyMaskedTransitionResponse, + createMaskedCustodyClientState, + openMaskedCustodyProjection, + prepareMaskedProjectionRequest, + prepareMaskedTransitionRequest, +} from "../../src/isogloss/csh/masked-custody-protocol.js"; +import { + ReferenceMaskedChartCustodian, + type HiddenMaskedTransition, +} from "../../src/isogloss/csh/reference-masked-custodian.js"; +import { + CSH_FIELD_MODULUS, + createChartCover, + encodeIngressCharts, + evaluateCertifiedProjection, + transportAffineCharts, +} from "../../src/isogloss/csh/reference.js"; + +const identity = { + id: "masked_identity", + matrix: [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + ], + bias: [0, 0, 0], +}; + +const transitions: readonly HiddenMaskedTransition[] = [ + { + linear: [ + [1, 1, 0], + [0, 1, 0], + [0, 0, 1], + ], + bias: [3, 5, 7], + cubicTerms: [ + { + inputProjection: [1, 0, 1], + outputDirection: [1, 2, 0], + coefficient: 11, + }, + ], + }, + { + linear: [ + [1, 0, 0], + [1, 1, 0], + [0, 0, 1], + ], + bias: [13, 17, 19], + cubicTerms: [ + { + inputProjection: [0, 1, 1], + outputDirection: [0, 1, 3], + coefficient: 23, + }, + ], + }, +]; + +const normalize = (value: number): number => { + const reduced = Math.trunc(value) % CSH_FIELD_MODULUS; + return reduced < 0 ? reduced + CSH_FIELD_MODULUS : reduced; +}; + +const multiply = (left: number, right: number): number => + normalize(normalize(left) * normalize(right)); + +const applyOwnerTransition = ( + transition: HiddenMaskedTransition, + input: readonly number[] +): number[] => { + const output = transition.linear.map((row, rowIndex) => + normalize( + row.reduce( + (sum, coefficient, index) => + sum + multiply(coefficient, input[index]!), + transition.bias[rowIndex]! + ) + ) + ); + for (const term of transition.cubicTerms) { + const projected = normalize( + term.inputProjection.reduce( + (sum, coefficient, index) => + sum + multiply(coefficient, input[index]!), + 0 + ) + ); + const cubic = multiply(multiply(projected, projected), projected); + for (let index = 0; index < output.length; index++) { + output[index] = normalize( + output[index]! + + multiply( + multiply(term.coefficient, cubic), + term.outputDirection[index]! + ) + ); + } + } + return output; +}; + +const coordinate = (width: number, index: number) => ({ + id: `test_coordinate_${index}`, + coefficients: Array.from( + { length: width }, + (_, lane) => (lane === index ? 1 : 0) + ), + bias: 0, +}); + +const createFixture = () => { + const covers = [0, 1, 2].map((epoch) => + createChartCover({ seed: 4040, epoch, width: 3 }) + ); + const input = [2, 3, 5]; + const charts = encodeIngressCharts(input, covers[0]!, 4041); + const custodian = new ReferenceMaskedChartCustodian({ + sessionId: "masked_session", + contractId: "masked_crown", + covers, + transitions, + exitProjection: { + id: "masked_exit", + coefficients: [1, 2, 3], + bias: 29, + }, + initialLineageCommitment: "masked_genesis", + lineageSecret: "server-masked-lineage", + maskSecret: "server-masked-representation", + sharingSecret: "server-masked-sharing", + responseSecret: "server-masked-response", + }); + return { covers, input, charts, custodian }; +}; + +describe("statefully masked chart custody", () => { + it("keeps intermediate logical states hidden across multiple cover transitions", () => { + const { covers, input, custodian } = createFixture(); + let charts = encodeIngressCharts(input, covers[0]!, 4041); + let state = createMaskedCustodyClientState(custodian.clientContract); + let ownerState = input; + const observedMaskedStates: number[][] = []; + + for (let epoch = 0; epoch < transitions.length; epoch++) { + const request = prepareMaskedTransitionRequest( + custodian.clientContract, + state, + charts, + `masked_nonce_${epoch}` + ); + const local = transportAffineCharts( + charts, + covers[epoch]!, + covers[epoch + 1]!, + identity, + 5000 + epoch + ); + const applied = applyMaskedTransitionResponse( + custodian.clientContract, + state, + local, + covers[epoch + 1]!, + custodian.evaluateTransition(request), + request.nonce + ); + charts = applied.charts; + state = applied.state; + ownerState = applyOwnerTransition(transitions[epoch]!, ownerState); + const masked = ownerState.map((_, index) => + evaluateCertifiedProjection( + charts, + covers[epoch + 1]!, + coordinate(3, index) + ) + ); + observedMaskedStates.push(masked); + expect(masked).not.toEqual(ownerState); + } + + const exitRequest = prepareMaskedProjectionRequest( + custodian.clientContract, + state, + charts, + "masked_exit_nonce" + ); + const opened = openMaskedCustodyProjection( + custodian.clientContract, + state, + custodian.evaluateExitProjection(exitRequest), + exitRequest.nonce + ); + const expected = normalize( + ownerState[0]! + + 2 * ownerState[1]! + + 3 * ownerState[2]! + + 29 + ); + expect(opened.projection).toBe(expected); + expect(observedMaskedStates).toHaveLength(2); + }); + + it("does not expose hidden transitions, representation masks, or secrets", () => { + const { custodian } = createFixture(); + const serialized = JSON.stringify(custodian.clientContract).toLowerCase(); + for (const forbidden of [ + "transition", + "linear", + "cubic", + "bias", + "projection", + "masksecret", + "representationmask", + "semanticop", + "handler", + "server-masked", + ]) { + expect(serialized).not.toContain(forbidden); + } + expect(custodian.clientContract.coverIds).toHaveLength(3); + }); + + it("binds lineage, rejects forks, and has no unsigned local fallback", () => { + const { covers, charts, custodian } = createFixture(); + const contract = custodian.clientContract; + const state = createMaskedCustodyClientState(contract); + const first = prepareMaskedTransitionRequest( + contract, + state, + charts, + "masked_fork_0001" + ); + const second = prepareMaskedTransitionRequest( + contract, + state, + charts, + "masked_fork_0002" + ); + const local = transportAffineCharts( + charts, + covers[0]!, + covers[1]!, + identity, + 6060 + ); + const response = custodian.evaluateTransition(first); + expect(() => custodian.evaluateTransition(first)).toThrow( + "RUAM_CSH_CUSTODY_REPLAY" + ); + expect(() => custodian.evaluateTransition(second)).toThrow( + "RUAM_CSH_CUSTODY_STALE_LINEAGE" + ); + expect(() => + applyMaskedTransitionResponse( + contract, + state, + local, + covers[1]!, + undefined, + first.nonce + ) + ).toThrow("RUAM_CSH_CUSTODIAN_REQUIRED"); + expect(() => + applyMaskedTransitionResponse( + contract, + state, + local, + covers[1]!, + response, + "masked_wrong_nonce" + ) + ).toThrow("RUAM_CSH_CHART_CUSTODY_RESPONSE_MISMATCH"); + }); + + it("rejects a substituted locally transported representation", () => { + const { covers, charts, custodian } = createFixture(); + const contract = custodian.clientContract; + const state = createMaskedCustodyClientState(contract); + const first = prepareMaskedTransitionRequest( + contract, + state, + charts, + "substitution_nonce_0001" + ); + const local = transportAffineCharts( + charts, + covers[0]!, + covers[1]!, + identity, + 7070 + ); + const substituted = local.map((chart, chartIndex) => + chartIndex === 0 + ? Object.freeze({ + ...chart, + cells: Object.freeze( + chart.cells.map((cell, cellIndex) => + cellIndex === 0 ? normalize(cell + 1) : cell + ) + ), + }) + : chart + ); + const applied = applyMaskedTransitionResponse( + contract, + state, + substituted, + covers[1]!, + custodian.evaluateTransition(first), + first.nonce + ); + const second = prepareMaskedTransitionRequest( + contract, + applied.state, + applied.charts, + "substitution_nonce_0002" + ); + expect(() => custodian.evaluateTransition(second)).toThrow( + "RUAM_CSH_MASKED_CUSTODY_STATE_SUBSTITUTION" + ); + }); +}); diff --git a/packages/ruam/test/isogloss/reference-isolation.test.ts b/packages/ruam/test/isogloss/reference-isolation.test.ts index c4bbba4..e1ff846 100644 --- a/packages/ruam/test/isogloss/reference-isolation.test.ts +++ b/packages/ruam/test/isogloss/reference-isolation.test.ts @@ -47,6 +47,7 @@ describe("Isogloss reference isolation", () => { "/src/isogloss/csh/testing-bprf-reference.ts", "/src/isogloss/csh/reference-custodian.ts", "/src/isogloss/csh/reference-chart-custodian.ts", + "/src/isogloss/csh/reference-masked-custodian.ts", ]; for (const forbidden of forbiddenModules) { @@ -61,6 +62,7 @@ describe("Isogloss reference isolation", () => { "evaluateBprfReference", "evaluateBprfOverCshReference", "ReferenceChartRelationCustodian", + "ReferenceMaskedChartCustodian", "RUAM_BPRF_CSH_MULTIPLICATION_QUORUM", "server-only-lineage-secret", ]) { From f6af269620afb4b888324ebdd38d6b2a60ced317 Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:23:20 -1000 Subject: [PATCH 19/54] feat: lower bounded canonical pure regions --- packages/ruam/src/compiler/index.ts | 14 + .../ruam/src/compiler/pure-region-lowering.ts | 935 ++++++++++++++++++ .../isogloss/pure-region-lowering.test.ts | 490 +++++++++ 3 files changed, 1439 insertions(+) create mode 100644 packages/ruam/src/compiler/pure-region-lowering.ts create mode 100644 packages/ruam/test/isogloss/pure-region-lowering.test.ts diff --git a/packages/ruam/src/compiler/index.ts b/packages/ruam/src/compiler/index.ts index 1150c86..3bd5bf7 100644 --- a/packages/ruam/src/compiler/index.ts +++ b/packages/ruam/src/compiler/index.ts @@ -53,6 +53,20 @@ export type { RegionStateDomain, RegionStatePort, } from "./regions.js"; +export { + MAX_PURE_REGION_INTEGER_MAGNITUDE, + isPureRegionValueInDomain, + lowerEffectRegionsToPureContract, +} from "./pure-region-lowering.js"; +export type { + LoweredPureRegionContract, + PureRegionBinding, + PureRegionInputAssumption, + PureRegionInputBinding, + PureRegionLoweringRequest, + PureRegionOutputBinding, + PureRegionValueDomain, +} from "./pure-region-lowering.js"; // --------------------------------------------------------------------------- // Scope-object elision diff --git a/packages/ruam/src/compiler/pure-region-lowering.ts b/packages/ruam/src/compiler/pure-region-lowering.ts new file mode 100644 index 0000000..66378a0 --- /dev/null +++ b/packages/ruam/src/compiler/pure-region-lowering.ts @@ -0,0 +1,935 @@ +/** + * Sound lowering gate from canonical effect-region spans to bounded BPRF + * pure-region contracts. + * + * This module is compiler/owner-side only. It consumes SemanticOp identities + * while proving a typed regional relation, then emits a PureRegionContract + * whose generated BPRF artifact contains no operation or source-node identity. + * + * @module compiler/pure-region-lowering + */ + +import type { + PureRegionContract, + PureRegionFormula, + PureRegionStep, + PureScalar, + PureValueRef, +} from "../isogloss/bprf/index.js"; +import type { SemanticInstruction, SemanticUnit } from "./ir.js"; +import type { + EffectRegion, + EffectRegionExit, + EffectRegionGraph, + EffectRegionId, +} from "./regions.js"; +import { + SemanticOp, + semanticOpName, +} from "./semantic-ops.js"; + +/** + * Keeps logical and BPRF's expanded polynomial coordinates comfortably below + * Number's exact-integer ceiling. This is a spike gate, not a general JS-number + * lowering rule. + */ +export const MAX_PURE_REGION_INTEGER_MAGNITUDE = 1_000_000; + +/** + * Stack indices are bottom-to-top within the selected span's bounded entry or + * exit stack window. Frame bindings use canonical argument/register/slot + * indices and are later rewritten by contextual frame assignment. + */ +export type PureRegionBinding = + | { kind: "stack"; index: number } + | { kind: "argument"; index: number } + | { kind: "register"; index: number } + | { kind: "slot"; index: number }; + +export type PureRegionValueDomain = + | { type: "boolean" } + | { + type: "number"; + /** Inclusive, exact safe-integer lower bound. */ + min: number; + /** Inclusive, exact safe-integer upper bound. */ + max: number; + }; + +/** A guarded external value assumed at the selected span's entry. */ +export interface PureRegionInputAssumption { + binding: PureRegionBinding; + domain: PureRegionValueDomain; +} + +export interface PureRegionLoweringRequest { + /** Consecutive effect regions in canonical graph order. */ + regionIds: readonly EffectRegionId[]; + /** + * Exact entry guards. Number domains accept only non-negative-zero safe + * integers within the declared inclusive range. + */ + assumptions: readonly PureRegionInputAssumption[]; +} + +export interface PureRegionInputBinding { + contractInput: number; + binding: PureRegionBinding; + domain: PureRegionValueDomain; +} + +export interface PureRegionOutputBinding { + contractOutput: number; + valueRef: PureValueRef; + binding: PureRegionBinding; + domain: PureRegionValueDomain; +} + +export interface LoweredPureRegionContract { + unitId: string; + regionIds: readonly EffectRegionId[]; + contract: PureRegionContract; + inputBindings: readonly PureRegionInputBinding[]; + outputBindings: readonly PureRegionOutputBinding[]; +} + +interface AbstractValueBase { + symbol: number; +} + +interface AbstractBoolean extends AbstractValueBase { + type: "boolean"; +} + +interface AbstractNumber extends AbstractValueBase { + type: "number"; + min: number; + max: number; +} + +type AbstractValue = AbstractBoolean | AbstractNumber; +type FrameValue = AbstractValue | typeof UNINITIALIZED; + +interface SymbolicInput { + value: AbstractValue; + binding: PureRegionBinding; + domain: PureRegionValueDomain; +} + +interface SymbolicStep { + value: AbstractValue; + formula: PureRegionFormula; +} + +interface PendingOutput { + binding: PureRegionBinding; + value: AbstractValue; +} + +const UNINITIALIZED = Symbol("ruam-pure-region-uninitialized"); +const STATE_BINDING_ORDER: Readonly> = Object.freeze({ + argument: 0, + register: 1, + slot: 2, +}); + +/** + * Lower a consecutive, single-entry/single-fallthrough region span. + * + * The function throws on every unproved case. A successful result is valid + * only under its explicit input binding guards; callers must route guard + * failures to an ordinary JavaScript fallback rather than coerce them. + */ +export function lowerEffectRegionsToPureContract( + unit: SemanticUnit, + graph: EffectRegionGraph, + request: PureRegionLoweringRequest +): LoweredPureRegionContract { + if (graph.unitId !== unit.id) { + fail("RUAM_PURE_REGION_UNIT_MISMATCH"); + } + const selected = selectAndValidateTopology(graph, request.regionIds); + const nodes = new Map(unit.nodes.map((node) => [node.id, node])); + const assumptions = buildAssumptionMap(request.assumptions); + const usedAssumptions = new Set(); + const symbolicInputs: SymbolicInput[] = []; + const symbolicSteps: SymbolicStep[] = []; + const stack: AbstractValue[] = []; + const frame = new Map(); + const modifiedBindings = new Map(); + let nextSymbol = 0; + + const createInput = (binding: PureRegionBinding): AbstractValue => { + const key = bindingKey(binding); + const assumption = assumptions.get(key); + if (!assumption) { + fail( + "RUAM_PURE_REGION_INPUT_TYPE_REQUIRED", + `${binding.kind}:${binding.index}` + ); + } + usedAssumptions.add(key); + const domain = validateAndFreezeDomain(assumption.domain); + const value = valueFromDomain(nextSymbol++, domain); + symbolicInputs.push({ + value, + binding: freezeBinding(binding), + domain, + }); + return value; + }; + + const addStep = ( + domain: PureRegionValueDomain, + formula: PureRegionFormula + ): AbstractValue => { + const frozenDomain = validateAndFreezeDomain(domain); + const value = valueFromDomain(nextSymbol++, frozenDomain); + symbolicSteps.push({ + value, + formula: Object.freeze({ ...formula }) as PureRegionFormula, + }); + return value; + }; + + const firstStack = selected[0]!.inputs.stack; + if (firstStack === "dynamic") { + fail("RUAM_PURE_REGION_DYNAMIC_STACK"); + } + for (let index = 0; index < firstStack; index++) { + stack.push(createInput({ kind: "stack", index })); + } + + const readFrame = ( + kind: "argument" | "register" | "slot", + index: number + ): AbstractValue => { + const binding = { kind, index } as const; + const key = bindingKey(binding); + let value = frame.get(key); + if (value === undefined) { + value = createInput(binding); + frame.set(key, value); + } + if (value === UNINITIALIZED) { + fail( + "RUAM_PURE_REGION_UNINITIALIZED_READ", + `${kind}:${index}` + ); + } + return value; + }; + + const writeFrame = ( + kind: "argument" | "register" | "slot", + index: number, + value: FrameValue + ): void => { + const binding = freezeBinding({ kind, index }); + const key = bindingKey(binding); + frame.set(key, value); + modifiedBindings.set(key, binding); + }; + + const pop = (node: SemanticInstruction): AbstractValue => { + const value = stack.pop(); + if (!value) { + fail( + "RUAM_PURE_REGION_STACK_UNDERFLOW", + `${node.id}:${semanticOpName(node.op)}` + ); + } + return value; + }; + + const pushLiteral = ( + domain: PureRegionValueDomain, + value: number | boolean + ): void => { + stack.push( + addStep( + domain, + typeof value === "boolean" + ? { tag: "literal", type: "boolean", value } + : { tag: "literal", type: "number", value } + ) + ); + }; + + for (const region of selected) { + if (region.inputs.stack === "dynamic") { + fail("RUAM_PURE_REGION_DYNAMIC_STACK", region.id); + } + if (stack.length !== region.inputs.stack) { + fail( + "RUAM_PURE_REGION_STACK_CONTRACT_MISMATCH", + `${region.id}: expected ${region.inputs.stack}, received ${stack.length}` + ); + } + + for (const nodeId of region.nodeIds) { + const node = nodes.get(nodeId); + if (!node) fail("RUAM_PURE_REGION_UNKNOWN_NODE", String(nodeId)); + + switch (node.op) { + case SemanticOp.PUSH_CONST: { + const constant = unit.constants[node.operand]; + if (!constant) { + fail( + "RUAM_PURE_REGION_INVALID_CONSTANT", + String(node.operand) + ); + } + if (constant.type === "boolean") { + pushLiteral({ type: "boolean" }, constant.value); + } else if (constant.type === "number") { + const domain = exactNumberDomain(constant.value); + pushLiteral(domain, constant.value); + } else { + unsupported(node); + } + break; + } + case SemanticOp.PUSH_ZERO: + pushLiteral(exactNumberDomain(0), 0); + break; + case SemanticOp.PUSH_ONE: + pushLiteral(exactNumberDomain(1), 1); + break; + case SemanticOp.PUSH_NEG_ONE: + pushLiteral(exactNumberDomain(-1), -1); + break; + case SemanticOp.PUSH_TRUE: + pushLiteral({ type: "boolean" }, true); + break; + case SemanticOp.PUSH_FALSE: + pushLiteral({ type: "boolean" }, false); + break; + case SemanticOp.POP: + pop(node); + break; + case SemanticOp.POP_N: { + if (!Number.isSafeInteger(node.operand) || node.operand < 0) { + fail( + "RUAM_PURE_REGION_INVALID_STACK_OPERAND", + String(node.operand) + ); + } + for (let index = 0; index < node.operand; index++) pop(node); + break; + } + case SemanticOp.DUP: { + const value = pop(node); + stack.push(value, value); + break; + } + case SemanticOp.DUP2: { + const right = pop(node); + const left = pop(node); + stack.push(left, right, left, right); + break; + } + case SemanticOp.SWAP: { + const right = pop(node); + const left = pop(node); + stack.push(right, left); + break; + } + case SemanticOp.ROT3: + rotateStack(stack, 3, node); + break; + case SemanticOp.ROT4: + rotateStack(stack, 4, node); + break; + case SemanticOp.PICK: { + if ( + !Number.isSafeInteger(node.operand) || + node.operand < 0 || + node.operand >= stack.length + ) { + fail( + "RUAM_PURE_REGION_INVALID_STACK_OPERAND", + String(node.operand) + ); + } + stack.push(stack[stack.length - node.operand - 1]!); + break; + } + case SemanticOp.LOAD_ARG: + stack.push(readFrame("argument", node.operand)); + break; + case SemanticOp.LOAD_ARG_OR_DEFAULT: + stack.push(readFrame("argument", node.operand)); + break; + case SemanticOp.STORE_ARG: + writeFrame("argument", node.operand, pop(node)); + break; + case SemanticOp.LOAD_REG: + stack.push(readFrame("register", node.operand)); + break; + case SemanticOp.STORE_REG: + writeFrame("register", node.operand, pop(node)); + break; + case SemanticOp.LOAD_SLOT: + stack.push(readFrame("slot", node.operand)); + break; + case SemanticOp.STORE_SLOT: + writeFrame("slot", node.operand, pop(node)); + break; + case SemanticOp.DECLARE_SLOT: + writeFrame("slot", node.operand, UNINITIALIZED); + break; + case SemanticOp.ADD: + lowerBinaryNumber(node, stack, "sum", addStep); + break; + case SemanticOp.SUB: + lowerBinaryNumber(node, stack, "difference", addStep); + break; + case SemanticOp.MUL: + lowerBinaryNumber(node, stack, "product", addStep); + break; + case SemanticOp.NEG: { + const input = requireNumber(pop(node), node); + if (containsZero(input)) { + fail( + "RUAM_PURE_REGION_NEGATIVE_ZERO_RISK", + String(node.id) + ); + } + const domain = boundedNumberDomain(-input.max, -input.min); + stack.push( + addStep(domain, { tag: "negate", value: input.symbol }) + ); + break; + } + case SemanticOp.NOT: { + const input = requireBoolean(pop(node), node); + stack.push( + addStep( + { type: "boolean" }, + { tag: "not", value: input.symbol } + ) + ); + break; + } + case SemanticOp.TO_BOOLEAN: { + const input = requireBoolean(pop(node), node); + stack.push(input); + break; + } + case SemanticOp.NOP: + break; + case SemanticOp.SOURCE_MAP: + break; + default: + unsupported(node); + } + } + + if ( + region.outputs.stack === "dynamic" || + stack.length !== region.outputs.stack + ) { + fail( + "RUAM_PURE_REGION_STACK_CONTRACT_MISMATCH", + `${region.id}: produced ${stack.length}` + ); + } + } + + for (const key of assumptions.keys()) { + if (!usedAssumptions.has(key)) { + fail("RUAM_PURE_REGION_UNUSED_ASSUMPTION", key); + } + } + + const pendingOutputs: PendingOutput[] = stack.map((value, index) => ({ + binding: freezeBinding({ kind: "stack", index }), + value, + })); + const stateOutputs = [...modifiedBindings.values()].sort(compareBindings); + for (const binding of stateOutputs) { + const value = frame.get(bindingKey(binding)); + if (value === undefined || value === UNINITIALIZED) { + fail( + "RUAM_PURE_REGION_UNREPRESENTABLE_STATE_OUTPUT", + bindingKey(binding) + ); + } + pendingOutputs.push({ binding, value }); + } + + if (symbolicInputs.length === 0) { + fail("RUAM_PURE_REGION_REQUIRES_INPUT"); + } + if (symbolicSteps.length < 2) { + fail("RUAM_PURE_REGION_REQUIRES_BRAIDABLE_STEPS"); + } + if (pendingOutputs.length === 0) { + fail("RUAM_PURE_REGION_REQUIRES_OUTPUT"); + } + + const finalized = finalizeContract( + symbolicInputs, + symbolicSteps, + pendingOutputs + ); + return Object.freeze({ + unitId: unit.id, + regionIds: Object.freeze(selected.map((region) => region.id)), + contract: finalized.contract, + inputBindings: finalized.inputBindings, + outputBindings: finalized.outputBindings, + }); +} + +/** Check the exact guard represented by a lowering input binding. */ +export function isPureRegionValueInDomain( + value: PureScalar, + domain: PureRegionValueDomain +): boolean { + if (domain.type === "boolean") return typeof value === "boolean"; + return ( + typeof value === "number" && + Number.isSafeInteger(value) && + !Object.is(value, -0) && + value >= domain.min && + value <= domain.max + ); +} + +function selectAndValidateTopology( + graph: EffectRegionGraph, + regionIds: readonly EffectRegionId[] +): EffectRegion[] { + if (regionIds.length === 0) { + fail("RUAM_PURE_REGION_EMPTY_SELECTION"); + } + const indexes = regionIds.map((regionId) => + graph.regions.findIndex((region) => region.id === regionId) + ); + if (indexes.some((index) => index < 0)) { + fail("RUAM_PURE_REGION_UNKNOWN_REGION"); + } + if (new Set(regionIds).size !== regionIds.length) { + fail("RUAM_PURE_REGION_DUPLICATE_REGION"); + } + for (let index = 1; index < indexes.length; index++) { + if (indexes[index] !== indexes[index - 1]! + 1) { + fail("RUAM_PURE_REGION_NONCONTIGUOUS_SELECTION"); + } + } + const selected = indexes.map((index) => graph.regions[index]!); + const selectedIds = new Set(regionIds); + const incoming = new Map< + EffectRegionId, + Array<{ source: EffectRegionId; exit: EffectRegionExit }> + >(); + for (const region of graph.regions) { + for (const exit of region.exits) { + const target = exitRegionId(exit); + if (!target) continue; + const entries = incoming.get(target) ?? []; + entries.push({ source: region.id, exit }); + incoming.set(target, entries); + } + } + + for (let index = 0; index < selected.length; index++) { + const region = selected[index]!; + const next = selected[index + 1]; + for (const exit of region.exits) { + if ( + exit.kind === "branch-true" || + exit.kind === "branch-false" || + exit.kind === "call" || + exit.kind === "yield" || + exit.kind === "await" || + exit.kind === "finally" || + exit.kind === "return" + ) { + fail( + "RUAM_PURE_REGION_UNREPRESENTABLE_CONTROL", + `${region.id}:${exit.kind}` + ); + } + if ( + (exit.kind === "exception" || exit.kind === "throw") && + "targetRegionId" in exit && + selectedIds.has(exit.targetRegionId) + ) { + fail( + "RUAM_PURE_REGION_EXCEPTION_PATH_SELECTED", + region.id + ); + } + } + const fallthroughs = region.exits.filter( + ( + exit + ): exit is Extract< + EffectRegionExit, + { targetRegionId: EffectRegionId } + > => + exit.kind === "fallthrough" + ); + if (fallthroughs.length !== 1) { + fail("RUAM_PURE_REGION_REQUIRES_FALLTHROUGH", region.id); + } + if (next) { + if (fallthroughs[0]!.targetRegionId !== next.id) { + fail("RUAM_PURE_REGION_NONLINEAR_SELECTION", region.id); + } + } else if (selectedIds.has(fallthroughs[0]!.targetRegionId)) { + fail("RUAM_PURE_REGION_CYCLIC_SELECTION", region.id); + } + + const entries = incoming.get(region.id) ?? []; + const normalEntries = entries.filter( + (entry) => + entry.exit.kind !== "exception" && + entry.exit.kind !== "finally" + ); + const exceptionalEntries = entries.filter( + (entry) => + entry.exit.kind === "exception" || + entry.exit.kind === "finally" + ); + if (exceptionalEntries.length > 0) { + fail("RUAM_PURE_REGION_EXCEPTION_ENTRY", region.id); + } + if (index === 0) { + if ( + normalEntries.length > 1 || + normalEntries.some( + (entry) => entry.exit.kind !== "fallthrough" + ) + ) { + fail("RUAM_PURE_REGION_CONTROL_JOIN", region.id); + } + } else if ( + normalEntries.length !== 1 || + normalEntries[0]!.source !== selected[index - 1]!.id || + normalEntries[0]!.exit.kind !== "fallthrough" + ) { + fail("RUAM_PURE_REGION_CONTROL_JOIN", region.id); + } + } + return selected; +} + +function buildAssumptionMap( + assumptions: readonly PureRegionInputAssumption[] +): Map { + const result = new Map(); + for (const assumption of assumptions) { + validateBinding(assumption.binding); + const key = bindingKey(assumption.binding); + if (result.has(key)) { + fail("RUAM_PURE_REGION_DUPLICATE_ASSUMPTION", key); + } + result.set(key, { + binding: freezeBinding(assumption.binding), + domain: validateAndFreezeDomain(assumption.domain), + }); + } + return result; +} + +function lowerBinaryNumber( + node: SemanticInstruction, + stack: AbstractValue[], + tag: "sum" | "difference" | "product", + addStep: ( + domain: PureRegionValueDomain, + formula: PureRegionFormula + ) => AbstractValue +): void { + const rightValue = stack.pop(); + const leftValue = stack.pop(); + if (!rightValue || !leftValue) { + fail( + "RUAM_PURE_REGION_STACK_UNDERFLOW", + `${node.id}:${semanticOpName(node.op)}` + ); + } + const left = requireNumber(leftValue, node); + const right = requireNumber(rightValue, node); + let domain: PureRegionValueDomain; + if (tag === "sum") { + domain = boundedNumberDomain(left.min + right.min, left.max + right.max); + } else if (tag === "difference") { + domain = boundedNumberDomain(left.min - right.max, left.max - right.min); + } else { + if ( + (containsZero(left) && right.min < 0) || + (containsZero(right) && left.min < 0) + ) { + fail( + "RUAM_PURE_REGION_NEGATIVE_ZERO_RISK", + String(node.id) + ); + } + const products = [ + left.min * right.min, + left.min * right.max, + left.max * right.min, + left.max * right.max, + ]; + domain = boundedNumberDomain( + Math.min(...products), + Math.max(...products) + ); + } + stack.push( + addStep(domain, { + tag, + left: left.symbol, + right: right.symbol, + }) + ); +} + +function finalizeContract( + inputs: readonly SymbolicInput[], + steps: readonly SymbolicStep[], + outputs: readonly PendingOutput[] +): { + contract: PureRegionContract; + inputBindings: readonly PureRegionInputBinding[]; + outputBindings: readonly PureRegionOutputBinding[]; +} { + const refs = new Map(); + const contractInputs = inputs.map((input, index) => { + refs.set(input.value.symbol, index); + return Object.freeze({ type: input.domain.type }); + }); + const inputBindings = inputs.map((input, index) => + Object.freeze({ + contractInput: index, + binding: input.binding, + domain: input.domain, + }) + ); + const contractSteps: PureRegionStep[] = []; + for (const step of steps) { + const formula = remapFormula(step.formula, refs); + const ref = contractInputs.length + contractSteps.length; + refs.set(step.value.symbol, ref); + contractSteps.push( + Object.freeze({ + type: step.value.type, + formula, + }) + ); + } + const contractOutputs = outputs.map((output) => { + const ref = refs.get(output.value.symbol); + if (ref == null) fail("RUAM_PURE_REGION_INTERNAL_MISSING_REF"); + return ref; + }); + const outputBindings = outputs.map((output, index) => + Object.freeze({ + contractOutput: index, + valueRef: contractOutputs[index]!, + binding: output.binding, + domain: domainFromValue(output.value), + }) + ); + return { + contract: Object.freeze({ + inputs: Object.freeze(contractInputs), + steps: Object.freeze(contractSteps), + outputs: Object.freeze(contractOutputs), + }), + inputBindings: Object.freeze(inputBindings), + outputBindings: Object.freeze(outputBindings), + }; +} + +function remapFormula( + formula: PureRegionFormula, + refs: ReadonlyMap +): PureRegionFormula { + const ref = (symbol: number): PureValueRef => { + const value = refs.get(symbol); + if (value == null) fail("RUAM_PURE_REGION_INTERNAL_FORWARD_REF"); + return value; + }; + switch (formula.tag) { + case "literal": + return Object.freeze({ ...formula }); + case "negate": + case "not": + return Object.freeze({ ...formula, value: ref(formula.value) }); + case "sum": + case "difference": + case "product": + case "and": + case "or": + case "xor": + return Object.freeze({ + ...formula, + left: ref(formula.left), + right: ref(formula.right), + }); + case "select": + return Object.freeze({ + ...formula, + gate: ref(formula.gate), + whenTrue: ref(formula.whenTrue), + whenFalse: ref(formula.whenFalse), + }); + } +} + +function rotateStack( + stack: AbstractValue[], + width: 3 | 4, + node: SemanticInstruction +): void { + if (stack.length < width) { + fail( + "RUAM_PURE_REGION_STACK_UNDERFLOW", + `${node.id}:${semanticOpName(node.op)}` + ); + } + const values = stack.splice(stack.length - width, width); + stack.push(values[width - 1]!, ...values.slice(0, width - 1)); +} + +function requireNumber( + value: AbstractValue, + node: SemanticInstruction +): AbstractNumber { + if (value.type !== "number") { + fail( + "RUAM_PURE_REGION_TYPE_MISMATCH", + `${node.id}:${semanticOpName(node.op)} expected number` + ); + } + return value; +} + +function requireBoolean( + value: AbstractValue, + node: SemanticInstruction +): AbstractBoolean { + if (value.type !== "boolean") { + fail( + "RUAM_PURE_REGION_TYPE_MISMATCH", + `${node.id}:${semanticOpName(node.op)} expected boolean` + ); + } + return value; +} + +function valueFromDomain( + symbol: number, + domain: PureRegionValueDomain +): AbstractValue { + return domain.type === "boolean" + ? Object.freeze({ symbol, type: "boolean" }) + : Object.freeze({ + symbol, + type: "number", + min: domain.min, + max: domain.max, + }); +} + +function domainFromValue(value: AbstractValue): PureRegionValueDomain { + return value.type === "boolean" + ? Object.freeze({ type: "boolean" }) + : Object.freeze({ type: "number", min: value.min, max: value.max }); +} + +function exactNumberDomain(value: number): PureRegionValueDomain { + if ( + !Number.isSafeInteger(value) || + Object.is(value, -0) || + Math.abs(value) > MAX_PURE_REGION_INTEGER_MAGNITUDE + ) { + fail("RUAM_PURE_REGION_UNSAFE_NUMBER_LITERAL", String(value)); + } + return Object.freeze({ type: "number", min: value, max: value }); +} + +function boundedNumberDomain( + min: number, + max: number +): PureRegionValueDomain { + if ( + !Number.isSafeInteger(min) || + !Number.isSafeInteger(max) || + min > max || + Math.abs(min) > MAX_PURE_REGION_INTEGER_MAGNITUDE || + Math.abs(max) > MAX_PURE_REGION_INTEGER_MAGNITUDE + ) { + fail( + "RUAM_PURE_REGION_NUMERIC_DOMAIN_OVERFLOW", + `${min}:${max}` + ); + } + return Object.freeze({ type: "number", min, max }); +} + +function validateAndFreezeDomain( + domain: PureRegionValueDomain +): PureRegionValueDomain { + if (domain.type === "boolean") { + return Object.freeze({ type: "boolean" }); + } + return boundedNumberDomain(domain.min, domain.max); +} + +function containsZero(value: AbstractNumber): boolean { + return value.min <= 0 && value.max >= 0; +} + +function validateBinding(binding: PureRegionBinding): void { + if ( + !Number.isSafeInteger(binding.index) || + binding.index < 0 + ) { + fail( + "RUAM_PURE_REGION_INVALID_BINDING", + `${binding.kind}:${binding.index}` + ); + } +} + +function freezeBinding(binding: PureRegionBinding): PureRegionBinding { + validateBinding(binding); + return Object.freeze({ ...binding }) as PureRegionBinding; +} + +function bindingKey(binding: PureRegionBinding): string { + return `${binding.kind}:${binding.index}`; +} + +function compareBindings( + left: PureRegionBinding, + right: PureRegionBinding +): number { + const leftOrder = STATE_BINDING_ORDER[left.kind] ?? -1; + const rightOrder = STATE_BINDING_ORDER[right.kind] ?? -1; + return leftOrder - rightOrder || left.index - right.index; +} + +function exitRegionId(exit: EffectRegionExit): EffectRegionId | null { + if ("targetRegionId" in exit) return exit.targetRegionId; + if ("resumeRegionId" in exit) return exit.resumeRegionId; + return null; +} + +function unsupported(node: SemanticInstruction): never { + fail( + "RUAM_PURE_REGION_UNSUPPORTED_OP", + `${node.id}:${semanticOpName(node.op)}` + ); +} + +function fail(code: string, detail?: string): never { + throw new Error(detail ? `${code}: ${detail}` : code); +} diff --git a/packages/ruam/test/isogloss/pure-region-lowering.test.ts b/packages/ruam/test/isogloss/pure-region-lowering.test.ts new file mode 100644 index 0000000..295ecb0 --- /dev/null +++ b/packages/ruam/test/isogloss/pure-region-lowering.test.ts @@ -0,0 +1,490 @@ +import { describe, expect, it } from "bun:test"; +import { parse } from "@babel/parser"; +import type { NodePath } from "@babel/traverse"; +import type * as t from "@babel/types"; +import { traverse } from "../../src/babel-compat.js"; +import { + compileSemanticFunction, + isPureRegionValueInDomain, + lowerEffectRegionsToPureContract, + resetUnitCounter, + type PureRegionInputAssumption, +} from "../../src/compiler/index.js"; +import type { SemanticUnit } from "../../src/compiler/ir.js"; +import { SemanticOp } from "../../src/compiler/semantic-ops.js"; +import { + buildEffectRegionGraph, + type EffectRegionGraph, +} from "../../src/compiler/regions.js"; +import { generateBprfArtifact } from "../../src/isogloss/bprf/index.js"; +import { evaluateBprfReference } from "../../src/isogloss/bprf/testing-reference.js"; + +interface CompiledFixture { + unit: SemanticUnit; + graph: EffectRegionGraph; +} + +function getFunctionPath( + source: string, + name: string +): NodePath { + const ast = parse(source, { + sourceType: "script", + sourceFilename: "pure-region-lowering-fixture.js", + }); + let result: NodePath | undefined; + traverse(ast, { + Function(path) { + const id = "id" in path.node ? path.node.id : null; + if (id?.type === "Identifier" && id.name === name) { + result = path as NodePath; + path.stop(); + } + }, + }); + if (!result) throw new Error(`Missing function ${name}`); + return result; +} + +function compileFixture(source: string, name: string): CompiledFixture { + resetUnitCounter(711); + const group = compileSemanticFunction( + getFunctionPath(source, name), + `pure-${name}` + ); + const unit = group.units.find( + (candidate) => candidate.id === group.entryUnitId + )!; + return { unit, graph: buildEffectRegionGraph(unit) }; +} + +function prefixBeforeReturn(fixture: CompiledFixture): string[] { + const returnRegionIndex = fixture.graph.regions.findIndex((region) => + region.nodeIds.some( + (nodeId) => + fixture.unit.nodes.find((node) => node.id === nodeId)!.op === + SemanticOp.RETURN + ) + ); + if (returnRegionIndex < 1) throw new Error("Missing return boundary"); + return fixture.graph.regions + .slice(0, returnRegionIndex) + .map((region) => region.id); +} + +function numberArgument( + index: number, + min: number, + max: number +): PureRegionInputAssumption { + return { + binding: { kind: "argument", index }, + domain: { type: "number", min, max }, + }; +} + +describe("canonical effect-region to pure BPRF lowering", () => { + it("lowers a compiler-produced bounded numeric span with explicit bindings", () => { + const fixture = compileFixture( + `function calc(a) { + let x = a * 3; + x = x + a; + x = x - 1; + return -x; + }`, + "calc" + ); + const request = { + regionIds: prefixBeforeReturn(fixture), + assumptions: [numberArgument(0, 1, 20)], + }; + + const first = lowerEffectRegionsToPureContract( + fixture.unit, + fixture.graph, + request + ); + const second = lowerEffectRegionsToPureContract( + fixture.unit, + fixture.graph, + request + ); + + expect(JSON.stringify(second)).toBe(JSON.stringify(first)); + expect(first.contract.inputs).toEqual([{ type: "number" }]); + expect( + first.contract.steps.map((step) => step.formula.tag) + ).toEqual([ + "literal", + "product", + "sum", + "literal", + "difference", + "negate", + ]); + expect(first.inputBindings).toEqual([ + { + contractInput: 0, + binding: { kind: "argument", index: 0 }, + domain: { type: "number", min: 1, max: 20 }, + }, + ]); + expect(first.outputBindings.map((output) => output.binding)).toEqual([ + { kind: "stack", index: 0 }, + { kind: "register", index: 0 }, + { kind: "register", index: 1 }, + ]); + expect(first.outputBindings[0]!.domain).toEqual({ + type: "number", + min: -79, + max: -3, + }); + expect(JSON.stringify(first.contract)).not.toContain("SemanticOp"); + expect(JSON.stringify(first.contract)).not.toContain("nodeId"); + expect(JSON.stringify(first.contract)).not.toContain("opcode"); + }); + + it("is exactly differential-equivalent through every BPRF realization", () => { + const fixture = compileFixture( + `function calc(a) { + let x = a * 3; + x = x + a; + x = x - 1; + return -x; + }`, + "calc" + ); + const lowered = lowerEffectRegionsToPureContract( + fixture.unit, + fixture.graph, + { + regionIds: prefixBeforeReturn(fixture), + assumptions: [numberArgument(0, 1, 20)], + } + ); + const stackOutput = lowered.outputBindings.find( + (output) => + output.binding.kind === "stack" && output.binding.index === 0 + )!; + + for (const seed of [0, 1, 0x12345678, 0xffffffff]) { + const artifact = generateBprfArtifact(lowered.contract, { + seed, + realizationCount: 4, + fragmentCount: 3, + }); + const serializedArtifact = JSON.stringify(artifact); + for (const forbidden of [ + "SemanticOp", + "opcode", + "nodeId", + "regionIds", + '"argument"', + '"register"', + ]) { + expect(serializedArtifact).not.toContain(forbidden); + } + const reached = new Set(); + for (let contextIndex = 0; contextIndex < 64; contextIndex++) { + const context = { + caller: `numeric-caller-${contextIndex % 7}`, + epoch: Math.floor(contextIndex / 7), + lineage: contextIndex * 13, + }; + for (const input of [1, 2, 7, 13, 20]) { + const nativeResult = -(input * 3 + input - 1); + const result = evaluateBprfReference( + artifact, + [input], + context + ); + reached.add(result.realization); + expect( + result.outputs[stackOutput.contractOutput] + ).toBe(nativeResult); + } + } + expect(reached.size).toBe(artifact.realizations.length); + } + }); + + it("maps an interior span's stack and frame dependencies explicitly", () => { + const fixture = compileFixture( + `function calc(a) { + let x = a * 3; + x = x + a; + x = x - 1; + return -x; + }`, + "calc" + ); + const start = fixture.graph.regions.findIndex((region) => + region.nodeIds.some( + (nodeId) => + fixture.unit.nodes.find((node) => node.id === nodeId)!.op === + SemanticOp.MUL + ) + ); + const returnBoundary = fixture.graph.regions.findIndex((region) => + region.nodeIds.some( + (nodeId) => + fixture.unit.nodes.find((node) => node.id === nodeId)!.op === + SemanticOp.RETURN + ) + ); + const lowered = lowerEffectRegionsToPureContract( + fixture.unit, + fixture.graph, + { + regionIds: fixture.graph.regions + .slice(start, returnBoundary) + .map((region) => region.id), + assumptions: [ + { + binding: { kind: "stack", index: 0 }, + domain: { type: "number", min: 1, max: 20 }, + }, + { + binding: { kind: "stack", index: 1 }, + domain: { type: "number", min: 3, max: 3 }, + }, + { + binding: { kind: "register", index: 0 }, + domain: { type: "number", min: 1, max: 20 }, + }, + ], + } + ); + + expect( + lowered.inputBindings.map((input) => input.binding) + ).toEqual([ + { kind: "stack", index: 0 }, + { kind: "stack", index: 1 }, + { kind: "register", index: 0 }, + ]); + expect( + lowered.outputBindings.some( + (output) => + output.binding.kind === "stack" && + output.binding.index === 0 + ) + ).toBe(true); + }); + + it("lowers intrinsic boolean operations without coercion", () => { + const fixture = compileFixture( + `function flip(value) { + let result = !value; + result = !result; + return !result; + }`, + "flip" + ); + const lowered = lowerEffectRegionsToPureContract( + fixture.unit, + fixture.graph, + { + regionIds: prefixBeforeReturn(fixture), + assumptions: [ + { + binding: { kind: "argument", index: 0 }, + domain: { type: "boolean" }, + }, + ], + } + ); + const artifact = generateBprfArtifact(lowered.contract, { + seed: 9191, + realizationCount: 4, + fragmentCount: 3, + }); + const stackOutput = lowered.outputBindings.find( + (output) => output.binding.kind === "stack" + )!; + + expect( + lowered.contract.steps.map((step) => step.formula.tag) + ).toEqual(["not", "not", "not"]); + for (const value of [true, false]) { + const result = evaluateBprfReference( + artifact, + [value], + { caller: "boolean", epoch: 2, lineage: value ? 1 : 0 } + ); + expect(result.outputs[stackOutput.contractOutput]).toBe(!value); + } + }); + + it("exposes exact entry guards for downstream fallback routing", () => { + const numberDomain = { type: "number", min: 1, max: 20 } as const; + const booleanDomain = { type: "boolean" } as const; + + expect(isPureRegionValueInDomain(1, numberDomain)).toBe(true); + expect(isPureRegionValueInDomain(20, numberDomain)).toBe(true); + expect(isPureRegionValueInDomain(0, numberDomain)).toBe(false); + expect(isPureRegionValueInDomain(21, numberDomain)).toBe(false); + expect(isPureRegionValueInDomain(1.5, numberDomain)).toBe(false); + expect(isPureRegionValueInDomain(-0, { + type: "number", + min: -1, + max: 1, + })).toBe(false); + expect(isPureRegionValueInDomain(true, numberDomain)).toBe(false); + expect(isPureRegionValueInDomain(true, booleanDomain)).toBe(true); + expect(isPureRegionValueInDomain(1, booleanDomain)).toBe(false); + }); + + it("rejects unknown or mismatched input types instead of assuming purity", () => { + const fixture = compileFixture( + `function calc(a) { + let x = a * 3; + x = x + a; + x = x - 1; + return -x; + }`, + "calc" + ); + const regionIds = prefixBeforeReturn(fixture); + + expect(() => + lowerEffectRegionsToPureContract(fixture.unit, fixture.graph, { + regionIds, + assumptions: [], + }) + ).toThrow("RUAM_PURE_REGION_INPUT_TYPE_REQUIRED"); + expect(() => + lowerEffectRegionsToPureContract(fixture.unit, fixture.graph, { + regionIds, + assumptions: [ + { + binding: { kind: "argument", index: 0 }, + domain: { type: "boolean" }, + }, + ], + }) + ).toThrow("RUAM_PURE_REGION_TYPE_MISMATCH"); + }); + + it("rejects effects, calls, branches, and unsupported arithmetic", () => { + const property = compileFixture( + `function property(value) { return value.answer; }`, + "property" + ); + expect(() => + lowerEffectRegionsToPureContract(property.unit, property.graph, { + regionIds: prefixBeforeReturn(property), + assumptions: [ + { + binding: { kind: "argument", index: 0 }, + domain: { type: "boolean" }, + }, + ], + }) + ).toThrow("RUAM_PURE_REGION_DYNAMIC_STACK"); + + const call = compileFixture( + `function invoke(fn) { return fn(1); }`, + "invoke" + ); + expect(() => + lowerEffectRegionsToPureContract(call.unit, call.graph, { + regionIds: prefixBeforeReturn(call), + assumptions: [ + { + binding: { kind: "argument", index: 0 }, + domain: { type: "boolean" }, + }, + ], + }) + ).toThrow("RUAM_PURE_REGION_UNREPRESENTABLE_CONTROL"); + + const branch = compileFixture( + `function choose(flag) { return flag ? 1 : 2; }`, + "choose" + ); + expect(() => + lowerEffectRegionsToPureContract(branch.unit, branch.graph, { + regionIds: branch.graph.regions.map((region) => region.id), + assumptions: [ + { + binding: { kind: "argument", index: 0 }, + domain: { type: "boolean" }, + }, + ], + }) + ).toThrow("RUAM_PURE_REGION_UNREPRESENTABLE_CONTROL"); + + const division = compileFixture( + `function divide(left, right) { return left / right; }`, + "divide" + ); + expect(() => + lowerEffectRegionsToPureContract(division.unit, division.graph, { + regionIds: prefixBeforeReturn(division), + assumptions: [ + numberArgument(0, 1, 20), + numberArgument(1, 1, 20), + ], + }) + ).toThrow("RUAM_PURE_REGION_UNSUPPORTED_OP"); + }); + + it("rejects signed-zero, fractional, overflow, and non-braidable cases", () => { + const negate = compileFixture( + `function negate(value) { return -value; }`, + "negate" + ); + expect(() => + lowerEffectRegionsToPureContract(negate.unit, negate.graph, { + regionIds: prefixBeforeReturn(negate), + assumptions: [numberArgument(0, -1, 1)], + }) + ).toThrow("RUAM_PURE_REGION_NEGATIVE_ZERO_RISK"); + + const fractional = compileFixture( + `function fractional(value) { + let result = value + 2.5; + return !result; + }`, + "fractional" + ); + expect(() => + lowerEffectRegionsToPureContract( + fractional.unit, + fractional.graph, + { + regionIds: prefixBeforeReturn(fractional), + assumptions: [numberArgument(0, 1, 20)], + } + ) + ).toThrow("RUAM_PURE_REGION_UNSAFE_NUMBER_LITERAL"); + + const overflow = compileFixture( + `function grow(value) { return value * value; }`, + "grow" + ); + expect(() => + lowerEffectRegionsToPureContract(overflow.unit, overflow.graph, { + regionIds: prefixBeforeReturn(overflow), + assumptions: [numberArgument(0, 1_000, 1_000_000)], + }) + ).toThrow("RUAM_PURE_REGION_NUMERIC_DOMAIN_OVERFLOW"); + + const oneStep = compileFixture( + `function oneStep(value) { return !value; }`, + "oneStep" + ); + expect(() => + lowerEffectRegionsToPureContract(oneStep.unit, oneStep.graph, { + regionIds: prefixBeforeReturn(oneStep), + assumptions: [ + { + binding: { kind: "argument", index: 0 }, + domain: { type: "boolean" }, + }, + ], + }) + ).toThrow("RUAM_PURE_REGION_REQUIRES_BRAIDABLE_STEPS"); + }); +}); From 3c6759a044657c6c2ab2e7a333b49c34d02d350c Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:24:25 -1000 Subject: [PATCH 20/54] feat: specialize BPRF over moving covers --- .../ruam/src/isogloss/csh/testing-emitter.ts | 748 ++++++++++++++++++ .../bprf-csh-specialized-emitter.test.ts | 300 +++++++ .../test/isogloss/reference-isolation.test.ts | 1 + 3 files changed, 1049 insertions(+) create mode 100644 packages/ruam/src/isogloss/csh/testing-emitter.ts create mode 100644 packages/ruam/test/isogloss/bprf-csh-specialized-emitter.test.ts diff --git a/packages/ruam/src/isogloss/csh/testing-emitter.ts b/packages/ruam/src/isogloss/csh/testing-emitter.ts new file mode 100644 index 0000000..3c4f67e --- /dev/null +++ b/packages/ruam/src/isogloss/csh/testing-emitter.ts @@ -0,0 +1,748 @@ +/** + * NON-PRODUCT SPECIALIZED BPRF/CSH SOURCE-EMISSION SPIKE. + * + * This owner-side utility specializes one caller-selected BPRF realization and + * a complete moving-cover plan into direct JavaScript. Generated code stores + * each chart as a separately named object of scalar fields. It contains no + * artifact walker, chart collection, frame array, global decoder, or generic + * transition evaluator. + * + * @module isogloss/csh/testing-emitter + */ + +import { deriveSeed } from "../../naming/scope.js"; +import { createSeededRandom } from "../../random/entropy.js"; +import type { + BprfArtifact, + BprfCallerContext, + BprfPiece, + BprfRealization, + PureValueType, +} from "../bprf/index.js"; +import { validateBprfArtifact } from "../bprf/index.js"; +import { selectBprfRealization } from "../bprf/testing-reference.js"; +import { + CSH_FIELD_MODULUS, + validateCover, + type ChartCellTransform, + type ChartCover, +} from "./reference.js"; + +export interface BprfCshTestingEmission { + readonly source: string; + readonly entryName: string; + readonly byteLength: number; + readonly realizationId: string; + readonly familyCode: 0 | 1; + readonly coverIds: readonly string[]; + readonly chartLocalCodeletCount: number; + readonly fragmentCodeletCount: number; + readonly contributorCodeletCount: number; + readonly reductionCount: number; + readonly projectionCodeletCount: number; +} + +interface PowerRegistry { + name(exponent: number): string; + source(): string[]; +} + +/** + * Emit one caller-specialized finite-field integer/boolean BPRF-over-CSH + * implementation. + * + * Narrow ABI: `entry(inputs) -> outputs`. Caller context, covers, residual + * entropy, bases, and all routing are owner-side inputs baked into the source. + */ +export function emitBprfOverCshTestingSource( + artifact: BprfArtifact, + context: BprfCallerContext, + covers: readonly ChartCover[], + seed: number +): BprfCshTestingEmission { + validateBprfArtifact(artifact); + const realization = selectBprfRealization(artifact, context); + validatePlan(realization, covers); + + const powers = createPowerRegistry(); + const body: string[] = ['"use strict";', emitNormalizer()]; + let chartLocalCodeletCount = 0; + let fragmentCodeletCount = 0; + let contributorCodeletCount = 0; + let reductionCount = 0; + let projectionCodeletCount = 0; + + const ingressResiduals = residualMatrix( + realization.frameSize, + covers[0]!.threshold - 1, + deriveSeed( + deriveSeed( + seed >>> 0, + `bprf-csh-ingress:${realization.id}` + ), + `csh-ingress:${covers[0]!.id}` + ) + ); + for (let chartIndex = 0; chartIndex < 5; chartIndex++) { + body.push( + emitIngressChart( + realization, + covers[0]!, + chartIndex, + ingressResiduals, + powers + ) + ); + chartLocalCodeletCount++; + } + + const entryLines: string[] = [ + "function __ruamBprfCshSpike(a){", + emitInputAbi(realization), + ...Array.from( + { length: 5 }, + (_, chartIndex) => `let c${chartIndex}=i${chartIndex}(a);` + ), + ]; + + for ( + let transitionIndex = 0; + transitionIndex < realization.transitions.length; + transitionIndex++ + ) { + const transition = realization.transitions[transitionIndex]!; + const from = covers[transitionIndex]!; + const to = covers[transitionIndex + 1]!; + const inverseFromMixing = invertMatrix(from.mixing); + const sourceWeights = interpolationWeightsAtZero( + from.charts.map((chart) => chart.point) + ); + const transitionSeed = deriveSeed( + seed >>> 0, + `bprf-csh-transition:${realization.id}:${transition.id}` + ); + const transitionResiduals = residualCube( + 5, + to.width, + to.threshold - 1, + transitionSeed + ); + + for (let sourceIndex = 0; sourceIndex < 5; sourceIndex++) { + body.push( + emitLocalChartOpening( + transitionIndex, + sourceIndex, + from, + inverseFromMixing, + powers + ) + ); + chartLocalCodeletCount++; + for ( + let fragmentIndex = 0; + fragmentIndex < realization.fragments.length; + fragmentIndex++ + ) { + body.push( + emitChartFragment( + realization, + transitionIndex, + sourceIndex, + fragmentIndex, + transition.phase + ) + ); + fragmentCodeletCount++; + } + body.push( + emitLocalChartMerge( + realization, + transitionIndex, + sourceIndex, + transition.phase + ) + ); + chartLocalCodeletCount++; + } + + for (let targetIndex = 0; targetIndex < 5; targetIndex++) { + for (let sourceIndex = 0; sourceIndex < 5; sourceIndex++) { + body.push( + emitDegreeContribution( + transitionIndex, + targetIndex, + sourceIndex, + to, + sourceWeights[sourceIndex]!, + transitionResiduals[sourceIndex]! + ) + ); + contributorCodeletCount++; + } + body.push( + emitTargetWrap( + transitionIndex, + targetIndex, + to, + powers + ) + ); + reductionCount++; + } + + for (let sourceIndex = 0; sourceIndex < 5; sourceIndex++) { + entryLines.push( + `const U${transitionIndex}_${sourceIndex}=u${transitionIndex}_${sourceIndex}(c${sourceIndex});` + ); + for ( + let fragmentIndex = 0; + fragmentIndex < realization.fragments.length; + fragmentIndex++ + ) { + entryLines.push( + `const G${transitionIndex}_${sourceIndex}_${fragmentIndex}=g${transitionIndex}_${sourceIndex}_${fragmentIndex}(U${transitionIndex}_${sourceIndex});` + ); + } + entryLines.push( + `const L${transitionIndex}_${sourceIndex}=l${transitionIndex}_${sourceIndex}(U${transitionIndex}_${sourceIndex},${Array.from( + { length: realization.fragments.length }, + (_, fragmentIndex) => + `G${transitionIndex}_${sourceIndex}_${fragmentIndex}` + ).join(",")});` + ); + } + for (let targetIndex = 0; targetIndex < 5; targetIndex++) { + for (let sourceIndex = 0; sourceIndex < 5; sourceIndex++) { + entryLines.push( + `const D${transitionIndex}_${targetIndex}_${sourceIndex}=d${transitionIndex}_${targetIndex}_${sourceIndex}(L${transitionIndex}_${sourceIndex});` + ); + } + entryLines.push( + `const z${transitionIndex}_${targetIndex}=w${transitionIndex}_${targetIndex}(${Array.from( + { length: 5 }, + (_, sourceIndex) => + `D${transitionIndex}_${targetIndex}_${sourceIndex}` + ).join(",")});` + ); + } + for (let targetIndex = 0; targetIndex < 5; targetIndex++) { + entryLines.push(`c${targetIndex}=z${transitionIndex}_${targetIndex};`); + } + } + + const finalCover = covers[covers.length - 1]!; + const inverseFinalMixing = invertMatrix(finalCover.mixing); + for (let outputIndex = 0; outputIndex < realization.outputPorts.length; outputIndex++) { + const port = realization.outputPorts[outputIndex]!; + const inverseScale = inverse(port.basis.scale); + const secretCoefficients = inverseFinalMixing[port.slot]!.map( + (value) => multiply(inverseScale, value) + ); + const projectionBase = subtract( + subtract(0, multiply(port.basis.bias, inverseScale)), + dot(secretCoefficients, finalCover.bias) + ); + const weights = interpolationWeightsAtZero( + finalCover.charts.map((chart) => chart.point) + ); + for (let chartIndex = 0; chartIndex < 5; chartIndex++) { + body.push( + emitProjectionContribution( + outputIndex, + chartIndex, + finalCover, + secretCoefficients, + weights[chartIndex]!, + powers + ) + ); + projectionCodeletCount++; + } + entryLines.push( + `const y${outputIndex}=n(${fieldSource(projectionBase)}+${Array.from( + { length: 5 }, + (_, chartIndex) => `j${outputIndex}_${chartIndex}(c${chartIndex})` + ).join("+")});` + ); + } + entryLines.push( + `return [${realization.outputPorts + .map((port, outputIndex) => + outputExpression( + `y${outputIndex}`, + port.typeCode === 0 ? "number" : "boolean", + realization.familyCode + ) + ) + .join(",")}];`, + "}" + ); + + body.push(...powers.source(), ...entryLines); + const source = body.join("\n"); + return Object.freeze({ + source, + entryName: "__ruamBprfCshSpike", + byteLength: Buffer.byteLength(source, "utf8"), + realizationId: realization.id, + familyCode: realization.familyCode, + coverIds: Object.freeze(covers.map((cover) => cover.id)), + chartLocalCodeletCount, + fragmentCodeletCount, + contributorCodeletCount, + reductionCount, + projectionCodeletCount, + }); +} + +function emitIngressChart( + realization: BprfRealization, + cover: ChartCover, + chartIndex: number, + residuals: readonly (readonly number[])[], + powers: PowerRegistry +): string { + const descriptor = cover.charts[chartIndex]!; + const cells = Array.from({ length: cover.width }, (_, mixedLane) => { + const terms = [fieldSource(cover.bias[mixedLane]!)]; + for (let inputIndex = 0; inputIndex < realization.inputPorts.length; inputIndex++) { + const port = realization.inputPorts[inputIndex]!; + const coordinate = + port.typeCode === 0 + ? `n(a[${inputIndex}])` + : realization.familyCode === 0 + ? `(a[${inputIndex}]?1:0)` + : `(a[${inputIndex}]?1:${CSH_FIELD_MODULUS - 1})`; + const encoded = `n((${coordinate})*${fieldSource(port.basis.scale)}+${fieldSource(port.basis.bias)})`; + terms.push( + `${fieldSource(cover.mixing[mixedLane]![port.slot]!)}*(${encoded})` + ); + } + let pointPower = descriptor.point; + for (const residual of residuals[mixedLane]!) { + terms.push(`${fieldSource(residual)}*${fieldSource(pointPower)}`); + pointPower = multiply(pointPower, descriptor.point); + } + const raw = `n(${terms.join("+")})`; + return `s${mixedLane}:${wrapExpression( + raw, + descriptor.cells[mixedLane]!, + powers + )}`; + }); + return `function i${chartIndex}(a){return{${cells.join(",")}};}`; +} + +function emitLocalChartOpening( + transitionIndex: number, + sourceIndex: number, + cover: ChartCover, + inverseMixing: readonly (readonly number[])[], + powers: PowerRegistry +): string { + const descriptor = cover.charts[sourceIndex]!; + const rawNames = Array.from( + { length: cover.width }, + (_, lane) => `r${lane}` + ); + const lines = rawNames.map( + (name, lane) => + `const ${name}=${unwrapExpression( + `c.s${lane}`, + descriptor.cells[lane]!, + powers + )};` + ); + const fields = Array.from({ length: cover.width }, (_, frameLane) => { + const terms = Array.from( + { length: cover.width }, + (_, mixedLane) => + `${fieldSource(inverseMixing[frameLane]![mixedLane]!)}*n(${rawNames[mixedLane]}-${fieldSource(cover.bias[mixedLane]!)})` + ); + return `s${frameLane}:n(${terms.join("+")})`; + }); + return `function u${transitionIndex}_${sourceIndex}(c){${lines.join("")}return{${fields.join(",")}};}`; +} + +function emitChartFragment( + realization: BprfRealization, + transitionIndex: number, + sourceIndex: number, + fragmentIndex: number, + phase: number +): string { + const transition = realization.transitions[phase]!; + const fragment = realization.fragments[fragmentIndex]!; + const fields = transition.writes.map((destination) => { + const pieces = fragment.pieces.filter( + (piece) => + piece.phase === phase && piece.destination === destination + ); + if (pieces.length === 0) { + throw new Error("RUAM_BPRF_CSH_EMITTER_MISSING_FRAGMENT_SHARE"); + } + return `s${destination}:n(${pieces + .map(fieldPieceExpression) + .join("+")})`; + }); + return `function g${transitionIndex}_${sourceIndex}_${fragmentIndex}(q){return{${fields.join(",")}};}`; +} + +function emitLocalChartMerge( + realization: BprfRealization, + transitionIndex: number, + sourceIndex: number, + phase: number +): string { + const transition = realization.transitions[phase]!; + const writes = new Set(transition.writes); + const parameters = [ + "q", + ...Array.from( + { length: realization.fragments.length }, + (_, fragmentIndex) => `p${fragmentIndex}` + ), + ]; + const fields = Array.from({ length: realization.frameSize }, (_, slot) => { + if (!writes.has(slot)) return `s${slot}:q.s${slot}`; + const pieces = realization.fragments.flatMap((fragment) => + fragment.pieces.filter( + (piece) => piece.phase === phase && piece.destination === slot + ) + ); + const basis = pieces[0]!.destinationBasis; + const sum = Array.from( + { length: realization.fragments.length }, + (_, fragmentIndex) => `p${fragmentIndex}.s${slot}` + ).join("+"); + return `s${slot}:n(n(${sum})*${fieldSource(basis.scale)}+${fieldSource(basis.bias)})`; + }); + return `function l${transitionIndex}_${sourceIndex}(${parameters.join(",")}){return{${fields.join(",")}};}`; +} + +function emitDegreeContribution( + transitionIndex: number, + targetIndex: number, + sourceIndex: number, + to: ChartCover, + sourceWeight: number, + residuals: readonly (readonly number[])[], +): string { + const target = to.charts[targetIndex]!; + const fields = Array.from({ length: to.width }, (_, mixedLane) => { + const mixedTerms = [ + fieldSource(to.bias[mixedLane]!), + ...Array.from( + { length: to.width }, + (_, frameLane) => + `${fieldSource(to.mixing[mixedLane]![frameLane]!)}*q.s${frameLane}` + ), + ]; + const terms = [ + `${fieldSource(sourceWeight)}*n(${mixedTerms.join("+")})`, + ]; + let pointPower = target.point; + for (const residual of residuals[mixedLane]!) { + terms.push(`${fieldSource(residual)}*${fieldSource(pointPower)}`); + pointPower = multiply(pointPower, target.point); + } + return `s${mixedLane}:n(${terms.join("+")})`; + }); + return `function d${transitionIndex}_${targetIndex}_${sourceIndex}(q){return{${fields.join(",")}};}`; +} + +function emitTargetWrap( + transitionIndex: number, + targetIndex: number, + to: ChartCover, + powers: PowerRegistry +): string { + const target = to.charts[targetIndex]!; + const parameters = Array.from({ length: 5 }, (_, index) => `d${index}`); + const fields = Array.from({ length: to.width }, (_, lane) => { + const reduced = `n(${parameters + .map((parameter) => `${parameter}.s${lane}`) + .join("+")})`; + return `s${lane}:${wrapExpression( + reduced, + target.cells[lane]!, + powers + )}`; + }); + return `function w${transitionIndex}_${targetIndex}(${parameters.join(",")}){return{${fields.join(",")}};}`; +} + +function emitProjectionContribution( + outputIndex: number, + chartIndex: number, + cover: ChartCover, + secretCoefficients: readonly number[], + weight: number, + powers: PowerRegistry +): string { + const descriptor = cover.charts[chartIndex]!; + const terms = Array.from({ length: cover.width }, (_, lane) => { + const coefficient = multiply(secretCoefficients[lane]!, weight); + return `${fieldSource(coefficient)}*(${unwrapExpression( + `c.s${lane}`, + descriptor.cells[lane]!, + powers + )})`; + }); + return `function j${outputIndex}_${chartIndex}(c){return n(${terms.join("+")});}`; +} + +function fieldPieceExpression(piece: BprfPiece): string { + let expression = fieldSource(coefficientToField(piece.coefficient)); + for (const factor of piece.factors) { + const inverseScale = inverse(factor.basis.scale); + const coordinate = `n(n(q.s${factor.slot}-${fieldSource(factor.basis.bias)})*${fieldSource(inverseScale)})`; + expression += `*n(${coordinate}+${fieldSource(factor.offset)})`; + } + return `n(${expression})`; +} + +function wrapExpression( + raw: string, + transform: ChartCellTransform, + powers: PowerRegistry +): string { + return `${powers.name(transform.exponent)}(n(${fieldSource(transform.scale)}*(${raw})+${fieldSource(transform.offset)}))`; +} + +function unwrapExpression( + stored: string, + transform: ChartCellTransform, + powers: PowerRegistry +): string { + return `n(n(${powers.name(transform.inverseExponent)}(${stored})-${fieldSource(transform.offset)})*${fieldSource(inverse(transform.scale))})`; +} + +function createPowerRegistry(): PowerRegistry { + const names = new Map(); + const definitions: string[] = []; + return { + name(exponent: number): string { + const existing = names.get(exponent); + if (existing) return existing; + const name = `k${names.size}`; + names.set(exponent, name); + const lines = [`function ${name}(x){let b=n(x),r=1;`]; + let remaining = exponent; + while (remaining > 0) { + if (remaining % 2 === 1) lines.push("r=n(r*b);"); + remaining = Math.floor(remaining / 2); + if (remaining > 0) lines.push("b=n(b*b);"); + } + lines.push("return r;}"); + definitions.push(lines.join("")); + return name; + }, + source(): string[] { + return definitions.slice(); + }, + }; +} + +function emitInputAbi(realization: BprfRealization): string { + const checks = [ + `if(!Array.isArray(a)||a.length!==${realization.inputPorts.length})throw new Error("RUAM_CSH_INPUT_ABI");`, + ]; + for (let index = 0; index < realization.inputPorts.length; index++) { + const port = realization.inputPorts[index]!; + checks.push( + port.typeCode === 0 + ? `if(typeof a[${index}]!=="number"||!Number.isSafeInteger(a[${index}])||Math.abs(a[${index}])>=${CSH_FIELD_MODULUS / 4})throw new Error("RUAM_CSH_INPUT_ABI");` + : `if(typeof a[${index}]!=="boolean")throw new Error("RUAM_CSH_INPUT_ABI");` + ); + } + return checks.join(""); +} + +function outputExpression( + fieldName: string, + type: PureValueType, + familyCode: 0 | 1 +): string { + if (type === "number") { + return `(${fieldName}>${Math.floor(CSH_FIELD_MODULUS / 2)}?${fieldName}-${CSH_FIELD_MODULUS}:${fieldName})`; + } + if (familyCode === 0) return `(${fieldName}!==0)`; + return `((${fieldName}>${Math.floor(CSH_FIELD_MODULUS / 2)}?${fieldName}-${CSH_FIELD_MODULUS}:${fieldName})>0)`; +} + +function validatePlan( + realization: BprfRealization, + covers: readonly ChartCover[] +): void { + if (covers.length !== realization.transitions.length + 1) { + throw new Error("RUAM_BPRF_CSH_EMITTER_COVER_COUNT"); + } + const ids = new Set(); + const epochs = new Set(); + for (const cover of covers) { + validateCover(cover); + if ( + cover.width !== realization.frameSize || + cover.charts.length !== 5 || + cover.threshold !== 3 + ) { + throw new Error("RUAM_BPRF_CSH_EMITTER_COVER_PROFILE"); + } + if (ids.has(cover.id)) { + throw new Error("RUAM_BPRF_CSH_EMITTER_COVER_REUSE"); + } + if (epochs.has(cover.epoch)) { + throw new Error("RUAM_BPRF_CSH_EMITTER_EPOCH_REUSE"); + } + ids.add(cover.id); + epochs.add(cover.epoch); + } +} + +function residualCube( + outer: number, + width: number, + degree: number, + seed: number +): number[][][] { + const random = createSeededRandom(seed); + return Array.from({ length: outer }, () => + Array.from({ length: width }, () => + Array.from( + { length: degree }, + () => random.nextUint32() % CSH_FIELD_MODULUS + ) + ) + ); +} + +function residualMatrix( + width: number, + degree: number, + seed: number +): number[][] { + const random = createSeededRandom(seed); + return Array.from({ length: width }, () => + Array.from( + { length: degree }, + () => random.nextUint32() % CSH_FIELD_MODULUS + ) + ); +} + +function emitNormalizer(): string { + return `function n(x){x=Math.trunc(x)%${CSH_FIELD_MODULUS};return x<0?x+${CSH_FIELD_MODULUS}:x;}`; +} + +function fieldSource(value: number): string { + return String(normalize(value)); +} + +function coefficientToField(value: number): number { + if (Number.isSafeInteger(value)) return normalize(value); + const doubled = value * 2; + if (Number.isSafeInteger(doubled)) { + return multiply(normalize(doubled), inverse(2)); + } + throw new Error(`RUAM_BPRF_CSH_UNSUPPORTED_COEFFICIENT: ${value}`); +} + +function interpolationWeightsAtZero(points: readonly number[]): number[] { + return points.map((point, index) => { + let numerator = 1; + let denominator = 1; + for (let other = 0; other < points.length; other++) { + if (other === index) continue; + numerator = multiply(numerator, subtract(0, points[other]!)); + denominator = multiply( + denominator, + subtract(point, points[other]!) + ); + } + return multiply(numerator, inverse(denominator)); + }); +} + +function invertMatrix( + input: readonly (readonly number[])[] +): number[][] { + const width = input.length; + const augmented = input.map((row, rowIndex) => [ + ...row.map(normalize), + ...Array.from({ length: width }, (_, column) => + rowIndex === column ? 1 : 0 + ), + ]); + for (let column = 0; column < width; column++) { + let pivot = column; + while (pivot < width && augmented[pivot]![column] === 0) pivot++; + if (pivot === width) throw new Error("RUAM_BPRF_CSH_SINGULAR_COVER"); + [augmented[column], augmented[pivot]] = [ + augmented[pivot]!, + augmented[column]!, + ]; + const pivotInverse = inverse(augmented[column]![column]!); + for (let index = 0; index < width * 2; index++) { + augmented[column]![index] = multiply( + augmented[column]![index]!, + pivotInverse + ); + } + for (let row = 0; row < width; row++) { + if (row === column) continue; + const factor = augmented[row]![column]!; + for (let index = 0; index < width * 2; index++) { + augmented[row]![index] = subtract( + augmented[row]![index]!, + multiply(factor, augmented[column]![index]!) + ); + } + } + } + return augmented.map((row) => row.slice(width)); +} + +function dot(left: readonly number[], right: readonly number[]): number { + let value = 0; + for (let index = 0; index < left.length; index++) { + value = add(value, multiply(left[index]!, right[index]!)); + } + return value; +} + +function inverse(value: number): number { + const normalized = normalize(value); + if (normalized === 0) throw new Error("RUAM_BPRF_CSH_ZERO_INVERSE"); + return power(normalized, CSH_FIELD_MODULUS - 2); +} + +function power(base: number, exponent: number): number { + let result = 1; + let factor = normalize(base); + let remaining = exponent; + while (remaining > 0) { + if (remaining % 2 === 1) result = multiply(result, factor); + factor = multiply(factor, factor); + remaining = Math.floor(remaining / 2); + } + return result; +} + +function add(left: number, right: number): number { + return normalize(normalize(left) + normalize(right)); +} + +function subtract(left: number, right: number): number { + return normalize(normalize(left) - normalize(right)); +} + +function multiply(left: number, right: number): number { + return normalize(normalize(left) * normalize(right)); +} + +function normalize(value: number): number { + const normalized = Math.trunc(value) % CSH_FIELD_MODULUS; + return normalized < 0 ? normalized + CSH_FIELD_MODULUS : normalized; +} diff --git a/packages/ruam/test/isogloss/bprf-csh-specialized-emitter.test.ts b/packages/ruam/test/isogloss/bprf-csh-specialized-emitter.test.ts new file mode 100644 index 0000000..48106b5 --- /dev/null +++ b/packages/ruam/test/isogloss/bprf-csh-specialized-emitter.test.ts @@ -0,0 +1,300 @@ +import { describe, expect, it } from "bun:test"; +import { + generateBprfArtifact, + type BprfArtifact, + type BprfCallerContext, + type PureRegionContract, + type PureScalar, +} from "../../src/isogloss/bprf/index.js"; +import { selectBprfRealization } from "../../src/isogloss/bprf/testing-reference.js"; +import { createChartCover, type ChartCover } from "../../src/isogloss/csh/reference.js"; +import { + emitBprfOverCshTestingSource, + type BprfCshTestingEmission, +} from "../../src/isogloss/csh/testing-emitter.js"; +import { evaluateBprfOverCshReference } from "../../src/isogloss/csh/testing-bprf-reference.js"; + +const contract: PureRegionContract = { + inputs: [ + { type: "number" }, + { type: "number" }, + { type: "boolean" }, + ], + steps: [ + { type: "number", formula: { tag: "product", left: 0, right: 1 } }, + { type: "number", formula: { tag: "sum", left: 0, right: 1 } }, + { type: "number", formula: { tag: "difference", left: 3, right: 4 } }, + { type: "boolean", formula: { tag: "not", value: 2 } }, + { + type: "number", + formula: { + tag: "select", + gate: 2, + whenTrue: 5, + whenFalse: 4, + }, + }, + ], + outputs: [7, 6], +}; + +type SpecializedEntry = (inputs: readonly PureScalar[]) => PureScalar[]; + +function instantiate(emission: BprfCshTestingEmission): SpecializedEntry { + return new Function( + `${emission.source}\nreturn ${emission.entryName};` + )() as SpecializedEntry; +} + +function findContext( + artifact: BprfArtifact, + familyCode: 0 | 1 +): BprfCallerContext { + for (let index = 0; index < 1_000; index++) { + const context = { + caller: `csh-emitter-${index}`, + epoch: index % 17, + lineage: index * 5, + }; + if (selectBprfRealization(artifact, context).familyCode === familyCode) { + return context; + } + } + throw new Error(`Missing family ${familyCode}`); +} + +function coversFor( + artifact: BprfArtifact, + context: BprfCallerContext, + seed: number +): ChartCover[] { + const realization = selectBprfRealization(artifact, context); + return Array.from( + { length: realization.transitions.length + 1 }, + (_, epoch) => + createChartCover({ + seed, + epoch, + width: realization.frameSize, + chartCount: 5, + threshold: 3, + }) + ); +} + +function expectEquivalent( + actual: readonly PureScalar[], + expected: readonly PureScalar[] +): void { + expect(actual).toHaveLength(expected.length); + for (let index = 0; index < actual.length; index++) { + expect(Object.is(actual[index], -0) ? 0 : actual[index]).toBe( + Object.is(expected[index], -0) ? 0 : expected[index] + ); + } +} + +describe("specialized BPRF-over-CSH source-emission spike", () => { + it("matches the combined reference across both ontologies and contexts", () => { + const artifact = generateBprfArtifact(contract, { + seed: 0x51c0ffee, + realizationCount: 4, + fragmentCount: 3, + }); + const inputs = [ + [3, 4, true], + [-2, 5, false], + [0, 7, true], + [6, -3, false], + ] as const; + + for (const familyCode of [0, 1] as const) { + const context = findContext(artifact, familyCode); + const covers = coversFor(artifact, context, 0x600d0000 + familyCode); + const seed = 0x700d0000 + familyCode; + const emission = emitBprfOverCshTestingSource( + artifact, + context, + covers, + seed + ); + const execute = instantiate(emission); + expect(emission.familyCode).toBe(familyCode); + for (const values of inputs) { + const reference = evaluateBprfOverCshReference( + artifact, + values, + context, + covers, + seed + ); + expectEquivalent(execute(values), reference.outputs); + } + } + }); + + it("is deterministic and bakes a distinct cover at every transition", () => { + const artifact = generateBprfArtifact(contract, { + seed: 31337, + realizationCount: 3, + fragmentCount: 3, + }); + const context = findContext(artifact, 0); + const covers = coversFor(artifact, context, 41414); + const first = emitBprfOverCshTestingSource( + artifact, + context, + covers, + 51515 + ); + const replay = emitBprfOverCshTestingSource( + artifact, + context, + covers, + 51515 + ); + const changedCovers = coversFor(artifact, context, 41415); + const changed = emitBprfOverCshTestingSource( + artifact, + context, + changedCovers, + 51515 + ); + const realization = selectBprfRealization(artifact, context); + + expect(replay.source).toBe(first.source); + expect(changed.source).not.toBe(first.source); + expect(first.coverIds).toEqual(covers.map((cover) => cover.id)); + expect(new Set(first.coverIds).size).toBe(first.coverIds.length); + expect(first.coverIds).toHaveLength(realization.transitions.length + 1); + expect(first.reductionCount).toBe(realization.transitions.length * 5); + expect(first.contributorCodeletCount).toBe( + realization.transitions.length * 25 + ); + expect(first.fragmentCodeletCount).toBe( + realization.transitions.length * + 5 * + realization.fragments.length + ); + + const reusedEpoch = covers.slice(); + reusedEpoch[1] = createChartCover({ + seed: 91919, + epoch: covers[0]!.epoch, + width: realization.frameSize, + chartCount: 5, + threshold: 3, + }); + expect(() => + emitBprfOverCshTestingSource( + artifact, + context, + reusedEpoch, + 51515 + ) + ).toThrow("RUAM_BPRF_CSH_EMITTER_EPOCH_REUSE"); + }); + + it("contains no artifact walker, chart/frame array, or global decode seam", () => { + const artifact = generateBprfArtifact(contract, { + seed: 60606, + realizationCount: 2, + fragmentCount: 3, + }); + const context = findContext(artifact, 0); + const covers = coversFor(artifact, context, 70707); + const emission = emitBprfOverCshTestingSource( + artifact, + context, + covers, + 80808 + ); + const source = emission.source.toLowerCase(); + for (const token of [ + "semanticop", + "opcode", + "operand", + "handler", + "sourcenode", + "artifact", + ".pieces", + ".fragments", + ".charts", + ".cells", + "global", + "decode", + "ownertrace", + "transition", + "fragment", + ]) { + expect(source).not.toContain(token); + } + expect(source).not.toContain("new array"); + expect(source).not.toContain("switch("); + expect(source).not.toContain("while("); + expect(source.match(/\bfor\s*\(/g)?.length ?? 0).toBe(0); + expect(source).not.toContain(".map("); + expect(source).not.toContain(".reduce("); + expect(source).not.toContain(".flatmap("); + expect(source).not.toContain("c["); + }); + + it("makes all five degree-reduction contributors structurally necessary", () => { + const artifact = generateBprfArtifact(contract, { + seed: 90909, + realizationCount: 2, + fragmentCount: 3, + }); + const context = findContext(artifact, 0); + const realization = selectBprfRealization(artifact, context); + const emission = emitBprfOverCshTestingSource( + artifact, + context, + coversFor(artifact, context, 10010), + 11011 + ); + + for ( + let transitionIndex = 0; + transitionIndex < realization.transitions.length; + transitionIndex++ + ) { + for (let targetIndex = 0; targetIndex < 5; targetIndex++) { + const definition = `function w${transitionIndex}_${targetIndex}(d0,d1,d2,d3,d4)`; + expect(emission.source).toContain(definition); + for (let contributor = 0; contributor < 5; contributor++) { + expect( + emission.source.split( + `d${transitionIndex}_${targetIndex}_${contributor}` + ) + ).toHaveLength(3); + } + } + } + expect(emission.contributorCodeletCount).toBe( + emission.reductionCount * 5 + ); + }); + + it("enforces the finite-field integer/boolean test ABI", () => { + const artifact = generateBprfArtifact(contract, { + seed: 12121, + realizationCount: 2, + fragmentCount: 2, + }); + const context = findContext(artifact, 0); + const execute = instantiate( + emitBprfOverCshTestingSource( + artifact, + context, + coversFor(artifact, context, 13131), + 14141 + ) + ); + + expect(() => execute([1, 2])).toThrow("RUAM_CSH_INPUT_ABI"); + expect(() => execute([1.5, 2, true])).toThrow("RUAM_CSH_INPUT_ABI"); + expect(() => execute([20_000, 2, true])).toThrow("RUAM_CSH_INPUT_ABI"); + expect(() => execute([1, 2, 1])).toThrow("RUAM_CSH_INPUT_ABI"); + }); +}); diff --git a/packages/ruam/test/isogloss/reference-isolation.test.ts b/packages/ruam/test/isogloss/reference-isolation.test.ts index e1ff846..9f9d032 100644 --- a/packages/ruam/test/isogloss/reference-isolation.test.ts +++ b/packages/ruam/test/isogloss/reference-isolation.test.ts @@ -45,6 +45,7 @@ describe("Isogloss reference isolation", () => { "/src/isogloss/bprf/testing-emitter.ts", "/src/isogloss/csh/reference.ts", "/src/isogloss/csh/testing-bprf-reference.ts", + "/src/isogloss/csh/testing-emitter.ts", "/src/isogloss/csh/reference-custodian.ts", "/src/isogloss/csh/reference-chart-custodian.ts", "/src/isogloss/csh/reference-masked-custodian.ts", From 0692251179e9c243c10386f5dd3ab28f7729890c Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:26:42 -1000 Subject: [PATCH 21/54] docs: record BPRF CSH no-go evidence --- .../2026-07-24-bprf-csh-spike-report.md | 108 ++++++++++++++++-- 1 file changed, 97 insertions(+), 11 deletions(-) diff --git a/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md b/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md index a6a3195..a7c9709 100644 --- a/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md +++ b/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md @@ -2,8 +2,9 @@ **Date:** 2026-07-24 **Branch:** `codex/traveling-isogloss-replacement` -**Decision state:** Go/no-go evaluation in progress; product schemas remain -unfrozen +**Decision state:** Initial local BPRF/CSH and first custody variants are +**no-go**; statefully masked custody is under a new adversarial gate and product +schemas remain unfrozen ## Purpose @@ -15,7 +16,11 @@ architecture: 3. validate multiple BPRF ontologies without runtime semantic dispatch; 4. validate moving-cover CSH and a necessary custodied relation; and 5. prove that BPRF polynomial fragments can execute over CSH chart shares - without reconstructing a transition frame. + without reconstructing a transition frame; +6. attack the shipped artifacts rather than crediting contextual diversity by + inspection; and +7. evolve custody so every intermediate client-visible state remains + independently masked by custodian-held representation state. The spike is not a product backend. The legacy VM remains only as the current behavior oracle while the replacement is incomplete. @@ -31,6 +36,7 @@ behavior oracle while the replacement is incomplete. | Local CSH | Five nonlinear charts, threshold three, changing covers, and scalar certified projections | | BPRF over CSH | Degree-four product shares reduced by five contributors into a fresh degree-two cover | | Direct custody | Signed response, one-response opening, mandatory remote relation, monotonic lineage, replay/fork rejection | +| Stateful masked custody | Keyed per-coordinate masks, pinned issued state, mandatory signed transitions, and final-only opening | All generic BPRF/CSH evaluators are explicitly test/reference-only and are not reachable from the package or CLI build entries. @@ -70,8 +76,31 @@ The bounded pure reference kernel currently proves: - serialized artifacts and traces contain no semantic operation, opcode, handler, canonical operand, formula tag, or source-node identity. -The generic differential evaluator is not a production runtime. A specialized -source-emission spike and adversarial recovery measurements are the next gate. +The generic differential evaluator is not a production runtime. The +specialized BPRF emitter removes the generic evaluator and artifact walker, but +all selected-realization algebra remains client-resident and statically +recoverable. + +The artifact-aware attacker obtains: + +| Measurement | Result | +|---|---:| +| Held-out output recovery from artifact alone | 1.0 | +| Physical dependency-topology recall | 1.0 | +| Dynamic primitive observations required | 0 | +| Artifact-aware unseen-realization recall | 1.0 | +| Artifact-aware transfer loss | 0 percentage points | + +Naive trace-only transfer does fail on an unseen realization. That fact is not +credited because the shipped artifact enumerates every realization. + +The specialized source-emission spike has no runtime artifact walker, generic +transition loop, semantic selector, frame array, or owner trace. Its remaining +hook surfaces include the scalar field normalizer, chart-local openings, +five-way reductions, final projection sites, and public region I/O. The +measured families emitted approximately **360 KB** and **1.67 MB** of source +and ran in approximately **415 μs** and **5.8 ms** per call. This is useful +mechanical evidence but not a production size/runtime shape. ## CSH structural results @@ -92,6 +121,25 @@ shares. Products raise polynomial degree from two to four, so all five charts are required for direct degree reduction into the next threshold-three cover. No transition constructs an ordinary BPRF frame. +The stronger artifact-and-metadata-aware attacker changes the decision: + +| Measurement | Result | +|---|---:| +| C90 | 3 contributions | +| Recovery with 1 / 2 / 3+ contributions | 0 / 0 / 1.0 | +| G90 work proxy | 3× plain state | +| Resident storage proxy | 5× plain state | +| Fixed-decoder unseen-cover accuracy | 0 | +| Metadata-aware unseen-cover accuracy | 1.0 | +| Metadata-aware transfer loss | 0 percentage points | +| Combined final-frame recovery | 1.0 | +| Combined full-trace amplification | 5× | + +The configured chart threshold is real, but it does not meet the 10× work and +storage gates. Cover changes do not create genuine transfer loss once the +shipped cover metadata is admitted to the attack. Local CSH is therefore a +hard no-go as the maximum-protection architecture. + ## Custody results The direct-remote control holds one nonlinear projected relation outside the @@ -111,9 +159,35 @@ Verified properties: This is a direct-custody control, not private-function evaluation. The custodian learns the declared input projection. +The adversarial gate found both first custody forms insufficient: + +- the direct scalar relation is exactly learned from four fresh-session + training queries and bypassed at one localized patch site; +- the chart-relation response exposes a complete unmasked logical delta after + correlating three contributions; and +- the chart relation is likewise learned from four fresh sessions. + +Replay and fork protection worked, but those controls did not prevent relation +learning. + +The revised custody spike now carries charts for +`logicalState + custodianMask(epoch)`. Each signed response advances to +`F_epoch(logicalState) + custodianMask(epoch + 1)`, and only the terminal +site-specific scalar projection is opened. Representation masks and sharing +residuals are derived with keyed, domain-separated hashes. The custodian pins +the exact masked state it issued, so changing the client-side transport cannot +substitute a new logical input at the next epoch. Missing responses, replay, +snapshot forks, nonce substitution, signature tampering, and representation +substitution all fail closed. + +This stateful masked evolution has passed focused correctness/protocol tests. +Its dynamic recovery, cross-session transfer, and black-box learnability gates +are still in progress; no go decision is recorded yet. + ## Correctness and build status -At this checkpoint: +At the last full-suite checkpoint, before the statefully masked and specialized +source additions: - Isogloss-focused tests: **42 passed, 0 failed, 12,056 assertions**; - full repository tests: **2,373 passed, 0 failed**; @@ -148,15 +222,27 @@ These are blockers to schema freeze, not deferred documentation: ## Current decision +The deterministic revised report records **11 failed gates** and **3 +unevaluated gates** for the initial BPRF/CSH/custody composition. The failed +set includes artifact-aware transfer, full-step BPRF recovery, CSH work and +storage amplification, metadata-aware cover transfer, localized hook +collapse, combined client completeness, custody bypass, and chart-delta +leakage. Canonical operation F1, patch collapse, and topology-hidden PFE remain +unevaluated. + Do not freeze artifact, certificate, carrier, or runtime schemas yet. The next decision requires: -- a sound canonical-region-to-bounded-contract lowerer; -- a specialized emitted BPRF spike with no generic artifact evaluator; -- O(1)-hook and full-step recovery measurements; -- C90/G90 and cross-cover transfer measurements; -- legitimate size/runtime overhead measurements; and +- adversarial scoring of the statefully masked custody transcript and client + state; +- separation of intrinsic black-box I/O learnability from recovery of an + internal transition or state; +- an actively secure, topology-hidden PFE feasibility decision if the private + profile remains in scope; +- canonical-operation and patch-collapse measurements on product-shaped + emitted code; +- legitimate size/runtime/latency/bandwidth measurements; and - zero mismatches within every claimed eligible domain. Mechanisms that do not beat the legacy one-hook control or whose attacker From 9e424bb4412abc5d539fd39778aacdd043c6d1a3 Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:31:16 -1000 Subject: [PATCH 22/54] fix: domain-separate masked custody sessions --- .../csh/reference-masked-custodian.ts | 23 ++++++++++ .../test/isogloss/csh-masked-custody.test.ts | 44 ++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/packages/ruam/src/isogloss/csh/reference-masked-custodian.ts b/packages/ruam/src/isogloss/csh/reference-masked-custodian.ts index 1696873..b87955a 100644 --- a/packages/ruam/src/isogloss/csh/reference-masked-custodian.ts +++ b/packages/ruam/src/isogloss/csh/reference-masked-custodian.ts @@ -68,6 +68,7 @@ export class ReferenceMaskedChartCustodian { readonly #maskSecret: string; readonly #sharingSecret: string; readonly #responseSecret: string; + readonly #protocolBinding: string; readonly #privateKey: ReturnType["privateKey"]; readonly #consumedNonces = new Set(); #epoch = 0; @@ -87,6 +88,10 @@ export class ReferenceMaskedChartCustodian { this.#maskSecret = options.maskSecret; this.#sharingSecret = options.sharingSecret; this.#responseSecret = options.responseSecret; + this.#protocolBinding = JSON.stringify([ + options.sessionId, + options.contractId, + ]); this.#lineageCommitment = options.initialLineageCommitment; this.#mask = Array.from( { length: options.covers[0]!.width }, @@ -125,6 +130,7 @@ export class ReferenceMaskedChartCustodian { const nextLogical = evaluateHiddenTransition(transition, logical); const nextMask = deriveVector( this.#maskSecret, + this.#protocolBinding, this.#epoch, request.nonce, from.width @@ -140,12 +146,14 @@ export class ReferenceMaskedChartCustodian { mixedDelta, to, this.#sharingSecret, + this.#protocolBinding, this.#epoch, request.nonce ); const nextEpoch = this.#epoch + 1; const nextLineageCommitment = nextLineage( this.#lineageSecret, + this.#protocolBinding, this.#lineageCommitment, request.nonce, chartContributions @@ -192,6 +200,7 @@ export class ReferenceMaskedChartCustodian { ); const projectionOpening = deriveResponseTransform( this.#responseSecret, + this.#protocolBinding, this.#epoch, request.nonce ); @@ -202,6 +211,7 @@ export class ReferenceMaskedChartCustodian { const nextEpoch = this.#epoch + 1; const nextLineageCommitment = nextLineage( this.#lineageSecret, + this.#protocolBinding, this.#lineageCommitment, request.nonce, encodedProjection @@ -346,6 +356,7 @@ function createAdditiveShares( constant: readonly number[], cover: ChartCover, secret: string, + protocolBinding: string, epoch: number, nonce: string ): readonly AdditiveChartContribution[] { @@ -354,6 +365,7 @@ function createAdditiveShares( deriveFieldElement( secret, "chart-share", + protocolBinding, epoch, nonce, lane, @@ -382,6 +394,7 @@ function createAdditiveShares( function deriveVector( secret: string, + protocolBinding: string, epoch: number, nonce: string, width: number @@ -392,6 +405,7 @@ function deriveVector( deriveFieldElement( secret, "representation-mask", + protocolBinding, epoch, nonce, lane, @@ -403,6 +417,7 @@ function deriveVector( function deriveFieldElement( secret: string, domain: string, + protocolBinding: string, epoch: number, nonce: string, lane: number, @@ -411,6 +426,8 @@ function deriveFieldElement( return createHmac("sha256", secret) .update(domain) .update("|") + .update(protocolBinding) + .update("|") .update(String(epoch)) .update("|") .update(nonce) @@ -424,6 +441,7 @@ function deriveFieldElement( function nextLineage( secret: string, + protocolBinding: string, current: string, nonce: string, value: unknown @@ -431,6 +449,8 @@ function nextLineage( return createHmac("sha256", secret) .update("lineage") .update("|") + .update(protocolBinding) + .update("|") .update(current) .update("|") .update(nonce) @@ -441,12 +461,15 @@ function nextLineage( function deriveResponseTransform( secret: string, + protocolBinding: string, epoch: number, nonce: string ): ChartCellTransform { const digest = createHmac("sha256", secret) .update("projection-opening") .update("|") + .update(protocolBinding) + .update("|") .update(String(epoch)) .update("|") .update(nonce) diff --git a/packages/ruam/test/isogloss/csh-masked-custody.test.ts b/packages/ruam/test/isogloss/csh-masked-custody.test.ts index 85573fc..2c2b9d6 100644 --- a/packages/ruam/test/isogloss/csh-masked-custody.test.ts +++ b/packages/ruam/test/isogloss/csh-masked-custody.test.ts @@ -113,14 +113,14 @@ const coordinate = (width: number, index: number) => ({ bias: 0, }); -const createFixture = () => { +const createFixture = (sessionId = "masked_session") => { const covers = [0, 1, 2].map((epoch) => createChartCover({ seed: 4040, epoch, width: 3 }) ); const input = [2, 3, 5]; const charts = encodeIngressCharts(input, covers[0]!, 4041); const custodian = new ReferenceMaskedChartCustodian({ - sessionId: "masked_session", + sessionId, contractId: "masked_crown", covers, transitions, @@ -323,4 +323,44 @@ describe("statefully masked chart custody", () => { "RUAM_CSH_MASKED_CUSTODY_STATE_SUBSTITUTION" ); }); + + it("separates fresh sessions even with one service secret and nonce", () => { + const first = createFixture("shared_service_session_a"); + const second = createFixture("shared_service_session_b"); + const advance = (fixture: ReturnType) => { + const state = createMaskedCustodyClientState( + fixture.custodian.clientContract + ); + const request = prepareMaskedTransitionRequest( + fixture.custodian.clientContract, + state, + fixture.charts, + "same_client_nonce" + ); + const local = transportAffineCharts( + fixture.charts, + fixture.covers[0]!, + fixture.covers[1]!, + identity, + 8080 + ); + const applied = applyMaskedTransitionResponse( + fixture.custodian.clientContract, + state, + local, + fixture.covers[1]!, + fixture.custodian.evaluateTransition(request), + request.nonce + ); + return Array.from({ length: 3 }, (_, coordinateIndex) => + evaluateCertifiedProjection( + applied.charts, + fixture.covers[1]!, + coordinate(3, coordinateIndex) + ) + ); + }; + + expect(advance(first)).not.toEqual(advance(second)); + }); }); From 1ff8bcce60a65120bbd40216803d50629192b4ed Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:31:25 -1000 Subject: [PATCH 23/54] test: attack statefully masked custody --- .../isogloss/masked-custody-attacker.test.ts | 66 + .../support/masked-custody-attacker.ts | 1134 +++++++++++++++++ 2 files changed, 1200 insertions(+) create mode 100644 packages/ruam/test/isogloss/masked-custody-attacker.test.ts create mode 100644 packages/ruam/test/isogloss/support/masked-custody-attacker.ts diff --git a/packages/ruam/test/isogloss/masked-custody-attacker.test.ts b/packages/ruam/test/isogloss/masked-custody-attacker.test.ts new file mode 100644 index 0000000..0a1a523 --- /dev/null +++ b/packages/ruam/test/isogloss/masked-custody-attacker.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it } from "bun:test"; +import { buildMaskedCustodyAttackerReport } from "./support/masked-custody-attacker.js"; + +describe("statefully masked custody dynamic-attacker gates", () => { + it("separates internal masking strength from final-oracle learnability", () => { + const report = buildMaskedCustodyAttackerReport(); + + expect(report.decision).toBe("no-go"); + expect(report.clientArtifact.hiddenRelationMaterialPresent).toBe(false); + + expect(report.internalRecovery.ingressLogicalStateRecovery).toBe(1); + expect(report.internalRecovery.maskedStateRecovery).toBe(1); + expect(report.internalRecovery.maskedTransitionDeltaRecovery).toBe(1); + expect( + report.internalRecovery.protectedLogicalCoordinateGuessAccuracy + ).toBe(0); + expect( + report.internalRecovery.unmaskedDeltaCoordinateGuessAccuracy + ).toBe(0); + expect( + report.internalRecovery.compatibleLogicalStatesPerProtectedObservation + ).toBe("281281747415761"); + expect( + report.internalRecovery.freshMaskWholeStateGuessProbability + ).toBeLessThan(1e-12); + + expect(report.crossSession.directSignedResponseAccepted).toBe(false); + expect(report.crossSession.fieldReboundResponseAccepted).toBe(false); + expect( + report.crossSession.maskedStateCoordinateTransferAccuracy + ).toBe(0); + expect( + report.crossSession.maskedDeltaCoordinateTransferAccuracy + ).toBe(0); + expect(report.crossSession.finalProjectionTransferAccuracy).toBe(1); + expect(report.crossSession.sharedServiceSecrets).toBe(true); + expect(report.crossSession.nonceReuseAcrossSessions).toBe(true); + expect(report.crossSession.freshVerificationKeys).toBe(true); + + expect(report.chosenInputOracle.totalDegreeBound).toBe(9); + expect(report.chosenInputOracle.trainingQueries).toBe(220); + expect(report.chosenInputOracle.heldOutOutputAccuracy).toBe(1); + expect(report.chosenInputOracle.evidenceSource).toBe( + "black-box-io-only" + ); + expect( + report.chosenInputOracle.intermediateRelationRecovered + ).toBe(false); + expect(report.chosenInputOracle.intermediateStateRecovered).toBe( + false + ); + + expect(report.dynamicProxy.localizedHookFamilies).toBe(2); + expect(report.dynamicProxy.traceAmplificationRatio).toBeLessThan(10); + expect(report.dynamicProxy.reconstructionWorkRatio).toBeLessThan(10); + expect(report.dynamicProxy.residentStorageRatio).toBeLessThan(10); + + expect(report.failedGates).toEqual([ + "MASKED-CUSTODY-FINAL-ORACLE-NONLEARNABILITY", + "MASKED-CUSTODY-TRACE-AMPLIFICATION", + "MASKED-CUSTODY-RECONSTRUCTION-WORK", + "MASKED-CUSTODY-RESIDENT-STORAGE", + "MASKED-CUSTODY-HOOK-COLLAPSE", + ]); + }); +}); diff --git a/packages/ruam/test/isogloss/support/masked-custody-attacker.ts b/packages/ruam/test/isogloss/support/masked-custody-attacker.ts new file mode 100644 index 0000000..f7271df --- /dev/null +++ b/packages/ruam/test/isogloss/support/masked-custody-attacker.ts @@ -0,0 +1,1134 @@ +import { + applyMaskedTransitionResponse, + createMaskedCustodyClientState, + openMaskedCustodyProjection, + prepareMaskedProjectionRequest, + prepareMaskedTransitionRequest, +} from "../../../src/isogloss/csh/masked-custody-protocol.js"; +import { + ReferenceMaskedChartCustodian, + type HiddenMaskedTransition, +} from "../../../src/isogloss/csh/reference-masked-custodian.js"; +import { + CSH_FIELD_MODULUS, + createChartCover, + encodeIngressCharts, + evaluateCertifiedProjection, + transportAffineCharts, + type ChartCover, +} from "../../../src/isogloss/csh/reference.js"; +import type { ChartCustodyResponse } from "../../../src/isogloss/csh/chart-custody-protocol.js"; + +export const MASKED_CUSTODY_ATTACKER_REPORT_VERSION = 1 as const; + +export type MaskedCustodyGateStatus = "pass" | "fail"; + +export interface MaskedCustodyGate { + readonly id: string; + readonly status: MaskedCustodyGateStatus; + readonly measured: number | boolean | string; + readonly required: string; + readonly reason: string; +} + +export interface MaskedCustodyAttackerReport { + readonly schemaVersion: typeof MASKED_CUSTODY_ATTACKER_REPORT_VERSION; + readonly decision: "go" | "no-go"; + readonly clientArtifact: { + readonly contractFieldCount: number; + readonly hiddenRelationMaterialPresent: boolean; + readonly coverPathVisible: boolean; + }; + readonly internalRecovery: { + /** The ingress has no representation mask and is necessarily known. */ + readonly ingressLogicalStateRecovery: number; + readonly maskedStateRecovery: number; + readonly maskedTransitionDeltaRecovery: number; + readonly protectedLogicalCoordinateGuessAccuracy: number; + readonly unmaskedDeltaCoordinateGuessAccuracy: number; + readonly compatibleLogicalStatesPerProtectedObservation: string; + readonly freshMaskWholeStateGuessProbability: number; + }; + readonly crossSession: { + readonly directSignedResponseAccepted: boolean; + readonly fieldReboundResponseAccepted: boolean; + readonly maskedStateCoordinateTransferAccuracy: number; + readonly maskedDeltaCoordinateTransferAccuracy: number; + readonly finalProjectionTransferAccuracy: number; + readonly sharedServiceSecrets: boolean; + readonly nonceReuseAcrossSessions: boolean; + readonly freshVerificationKeys: boolean; + }; + readonly chosenInputOracle: { + readonly relationStages: number; + readonly totalDegreeBound: number; + readonly trainingQueries: number; + readonly heldOutQueries: number; + readonly heldOutOutputAccuracy: number; + readonly learnedScope: "complete-final-scalar-function"; + readonly evidenceSource: "black-box-io-only"; + readonly intermediateRelationRecovered: boolean; + readonly intermediateStateRecovered: boolean; + }; + readonly dynamicProxy: { + readonly chartCount: number; + readonly width: number; + readonly threshold: number; + readonly transitionCount: number; + readonly interceptedRequests: number; + readonly interceptedSignedResponses: number; + readonly interceptedTraceFieldElements: number; + readonly reconstructionChartCellReads: number; + readonly semanticPlainTraceFieldElements: number; + readonly plainDirectReads: number; + readonly traceAmplificationRatio: number; + readonly reconstructionWorkRatio: number; + readonly residentStorageRatio: number; + readonly localizedHookFamilies: number; + readonly remainingHookSurfaces: readonly string[]; + }; + readonly gates: readonly MaskedCustodyGate[]; + readonly failedGates: readonly string[]; +} + +const WIDTH = 3; +const TRANSITION_COUNT = 2; +const TOTAL_DEGREE_BOUND = 3 ** TRANSITION_COUNT; +const CHART_COUNT = 5; +const THRESHOLD = 3; +const REQUIRED_AMPLIFICATION = 10; + +const IDENTITY_TRANSITION = Object.freeze({ + id: "masked_attacker_identity", + matrix: Object.freeze([ + Object.freeze([1, 0, 0]), + Object.freeze([0, 1, 0]), + Object.freeze([0, 0, 1]), + ]), + bias: Object.freeze([0, 0, 0]), +}); + +const HIDDEN_TRANSITIONS: readonly HiddenMaskedTransition[] = Object.freeze([ + Object.freeze({ + linear: Object.freeze([ + Object.freeze([1, 1, 0]), + Object.freeze([0, 1, 0]), + Object.freeze([0, 0, 1]), + ]), + bias: Object.freeze([3, 5, 7]), + cubicTerms: Object.freeze([ + Object.freeze({ + inputProjection: Object.freeze([1, 0, 1]), + outputDirection: Object.freeze([1, 2, 0]), + coefficient: 11, + }), + ]), + }), + Object.freeze({ + linear: Object.freeze([ + Object.freeze([1, 0, 0]), + Object.freeze([1, 1, 0]), + Object.freeze([0, 0, 1]), + ]), + bias: Object.freeze([13, 17, 19]), + cubicTerms: Object.freeze([ + Object.freeze({ + inputProjection: Object.freeze([0, 1, 1]), + outputDirection: Object.freeze([0, 1, 3]), + coefficient: 23, + }), + ]), + }), +]); + +const EXIT_PROJECTION = Object.freeze({ + id: "masked_attacker_exit", + coefficients: Object.freeze([1, 2, 3]), + bias: 29, +}); + +const COVERS = Object.freeze( + Array.from({ length: TRANSITION_COUNT + 1 }, (_, epoch) => + createChartCover({ + seed: 91_700, + epoch, + width: WIDTH, + chartCount: CHART_COUNT, + threshold: THRESHOLD, + }) + ) +); + +interface CapturedSession { + readonly clientContract: ReferenceMaskedChartCustodian["clientContract"]; + readonly input: readonly number[]; + readonly ownerStates: readonly (readonly number[])[]; + readonly ownerDeltas: readonly (readonly number[])[]; + readonly maskedStates: readonly (readonly number[])[]; + readonly maskedDeltas: readonly (readonly number[])[]; + readonly output: number; + readonly transitionRequests: number; + readonly signedTransitionResponses: number; + readonly projectionRequests: number; + readonly signedProjectionResponses: number; +} + +interface FirstTransitionCapture { + readonly contract: ReferenceMaskedChartCustodian["clientContract"]; + readonly state: ReturnType; + readonly localCharts: ReturnType; + readonly response: ChartCustodyResponse; + readonly requestNonce: string; +} + +/** + * Exercise the full client-visible masked-custody surface. Owner states are + * retained only by the test scorer; all attacker recoveries below consume + * charts, public covers, requests, signed responses, and the opened projection. + */ +function captureSession( + sessionOrdinal: number, + input: readonly number[] +): CapturedSession { + const custodian = createFreshCustodian(sessionOrdinal); + let charts = encodeIngressCharts( + input, + COVERS[0]!, + ingressSeed(sessionOrdinal) + ); + let clientState = createMaskedCustodyClientState( + custodian.clientContract + ); + let ownerState = input.map(normalize); + const ownerStates: number[][] = [ownerState]; + const ownerDeltas: number[][] = []; + const maskedStates: number[][] = [ + recoverMaskedState(charts, COVERS[0]!), + ]; + const maskedDeltas: number[][] = []; + + for (let epoch = 0; epoch < TRANSITION_COUNT; epoch++) { + const nonce = transitionNonce(sessionOrdinal, epoch); + const request = prepareMaskedTransitionRequest( + custodian.clientContract, + clientState, + charts, + nonce + ); + const response = custodian.evaluateTransition(request); + const targetCover = COVERS[epoch + 1]!; + const localCharts = transportAffineCharts( + charts, + COVERS[epoch]!, + targetCover, + IDENTITY_TRANSITION, + transportSeed(sessionOrdinal, epoch) + ); + const recoveredMaskedDelta = recoverSignedMaskedDelta( + response, + targetCover + ); + const applied = applyMaskedTransitionResponse( + custodian.clientContract, + clientState, + localCharts, + targetCover, + response, + nonce + ); + const nextOwnerState = applyOwnerTransition( + HIDDEN_TRANSITIONS[epoch]!, + ownerState + ); + + ownerDeltas.push( + nextOwnerState.map((value, lane) => + subtract(value, ownerState[lane]!) + ) + ); + ownerState = nextOwnerState; + ownerStates.push(ownerState); + maskedDeltas.push(recoveredMaskedDelta); + charts = applied.charts; + clientState = applied.state; + maskedStates.push(recoverMaskedState(charts, targetCover)); + } + + const projectionNonce = exitNonce(sessionOrdinal); + const projectionRequest = prepareMaskedProjectionRequest( + custodian.clientContract, + clientState, + charts, + projectionNonce + ); + const projectionResponse = + custodian.evaluateExitProjection(projectionRequest); + const opened = openMaskedCustodyProjection( + custodian.clientContract, + clientState, + projectionResponse, + projectionNonce + ); + + return Object.freeze({ + clientContract: custodian.clientContract, + input: Object.freeze(input.map(normalize)), + ownerStates: freezeMatrix(ownerStates), + ownerDeltas: freezeMatrix(ownerDeltas), + maskedStates: freezeMatrix(maskedStates), + maskedDeltas: freezeMatrix(maskedDeltas), + output: opened.projection, + transitionRequests: TRANSITION_COUNT, + signedTransitionResponses: TRANSITION_COUNT, + projectionRequests: 1, + signedProjectionResponses: 1, + }); +} + +function createFreshCustodian( + sessionOrdinal: number +): ReferenceMaskedChartCustodian { + const tag = `fresh-${sessionOrdinal}`; + return new ReferenceMaskedChartCustodian({ + sessionId: `masked-attacker-session-${tag}`, + contractId: "masked-attacker-contract", + covers: COVERS, + transitions: HIDDEN_TRANSITIONS, + exitProjection: EXIT_PROJECTION, + initialLineageCommitment: `masked-attacker-genesis-${tag}`, + lineageSecret: "shared-service-lineage-secret", + maskSecret: "shared-service-mask-secret", + sharingSecret: "shared-service-sharing-secret", + responseSecret: "shared-service-response-secret", + }); +} + +function captureFirstTransition( + sessionOrdinal: number, + input: readonly number[] +): FirstTransitionCapture { + const custodian = createFreshCustodian(sessionOrdinal); + const charts = encodeIngressCharts( + input, + COVERS[0]!, + ingressSeed(sessionOrdinal) + ); + const state = createMaskedCustodyClientState( + custodian.clientContract + ); + const nonce = transitionNonce(sessionOrdinal, 0); + const request = prepareMaskedTransitionRequest( + custodian.clientContract, + state, + charts, + nonce + ); + const response = custodian.evaluateTransition(request); + const localCharts = transportAffineCharts( + charts, + COVERS[0]!, + COVERS[1]!, + IDENTITY_TRANSITION, + transportSeed(sessionOrdinal, 0) + ); + return Object.freeze({ + contract: custodian.clientContract, + state, + localCharts, + response, + requestNonce: nonce, + }); +} + +function recoverMaskedState( + charts: Parameters[0], + cover: ChartCover +): number[] { + return Array.from({ length: cover.width }, (_, coordinate) => + evaluateCertifiedProjection(charts, cover, { + id: `attacker_coordinate_${cover.epoch}_${coordinate}`, + coefficients: Array.from( + { length: cover.width }, + (_, lane) => (lane === coordinate ? 1 : 0) + ), + bias: 0, + }) + ); +} + +/** + * Independently recover the response's masked delta. Contributions are raw + * Shamir shares of `targetCover.mixing * maskedDelta`; all interpolation and + * mixing metadata needed to undo that representation is client-visible. + */ +function recoverSignedMaskedDelta( + response: ChartCustodyResponse, + targetCover: ChartCover +): number[] { + const contributionById = new Map( + response.chartContributions.map((contribution) => [ + contribution.chartId, + contribution, + ]) + ); + const selected = targetCover.charts.slice(0, targetCover.threshold); + const weights = interpolationWeightsAtZero( + selected.map((chart) => chart.point) + ); + const mixedDelta = Array.from( + { length: targetCover.width }, + (_, lane) => { + let constant = 0; + for (let index = 0; index < selected.length; index++) { + const contribution = contributionById.get( + selected[index]!.id + ); + if (contribution === undefined) { + throw new Error( + "MASKED_ATTACKER_MISSING_SIGNED_CONTRIBUTION" + ); + } + constant = add( + constant, + multiply(contribution.cells[lane]!, weights[index]!) + ); + } + return constant; + } + ); + return multiplyMatrixVector(invertMatrix(targetCover.mixing), mixedDelta); +} + +function applyOwnerTransition( + transition: HiddenMaskedTransition, + input: readonly number[] +): number[] { + const output = transition.linear.map((row, rowIndex) => + add(dot(row, input), transition.bias[rowIndex]!) + ); + for (const term of transition.cubicTerms) { + const projected = dot(term.inputProjection, input); + const cubic = multiply(multiply(projected, projected), projected); + for (let lane = 0; lane < output.length; lane++) { + output[lane] = add( + output[lane]!, + multiply( + multiply(term.coefficient, cubic), + term.outputDirection[lane]! + ) + ); + } + } + return output; +} + +function buildSimplexTrainingSet(): { + readonly samples: ReadonlyMap; + readonly captured: readonly CapturedSession[]; +} { + const samples = new Map(); + const captured: CapturedSession[] = []; + let sessionOrdinal = 1; + for (let first = 0; first <= TOTAL_DEGREE_BOUND; first++) { + for ( + let second = 0; + second <= TOTAL_DEGREE_BOUND - first; + second++ + ) { + for ( + let third = 0; + third <= TOTAL_DEGREE_BOUND - first - second; + third++ + ) { + const input = [first, second, third]; + const session = captureSession(sessionOrdinal++, input); + samples.set(vectorKey(input), session.output); + if (captured.length < 16) captured.push(session); + } + } + } + return Object.freeze({ + samples, + captured: Object.freeze(captured), + }); +} + +interface NewtonCoefficient { + readonly index: readonly [number, number, number]; + readonly value: number; +} + +/** + * Learn the complete scalar polynomial from black-box I/O. A degree-nine + * polynomial in three variables has C(12,3)=220 total-degree basis terms. + * Values on the matching integer simplex yield its multivariate finite + * differences without observing a chart, mask, contribution, or owner state. + */ +function learnFinalScalarPolynomial( + samples: ReadonlyMap +): readonly NewtonCoefficient[] { + const coefficients: NewtonCoefficient[] = []; + for (let first = 0; first <= TOTAL_DEGREE_BOUND; first++) { + for ( + let second = 0; + second <= TOTAL_DEGREE_BOUND - first; + second++ + ) { + for ( + let third = 0; + third <= TOTAL_DEGREE_BOUND - first - second; + third++ + ) { + let difference = 0; + for (let sampleFirst = 0; sampleFirst <= first; sampleFirst++) { + for ( + let sampleSecond = 0; + sampleSecond <= second; + sampleSecond++ + ) { + for ( + let sampleThird = 0; + sampleThird <= third; + sampleThird++ + ) { + const sample = samples.get( + vectorKey([ + sampleFirst, + sampleSecond, + sampleThird, + ]) + ); + if (sample === undefined) { + throw new Error( + "MASKED_ATTACKER_INCOMPLETE_SIMPLEX" + ); + } + const distance = + first - + sampleFirst + + second - + sampleSecond + + third - + sampleThird; + const weight = multiply( + multiply( + binomialInteger(first, sampleFirst), + binomialInteger(second, sampleSecond) + ), + binomialInteger(third, sampleThird) + ); + difference = + distance % 2 === 0 + ? add(difference, multiply(weight, sample)) + : subtract( + difference, + multiply(weight, sample) + ); + } + } + } + coefficients.push( + Object.freeze({ + index: Object.freeze([ + first, + second, + third, + ]) as readonly [number, number, number], + value: difference, + }) + ); + } + } + } + return Object.freeze(coefficients); +} + +function evaluateLearnedPolynomial( + coefficients: readonly NewtonCoefficient[], + input: readonly number[] +): number { + let output = 0; + for (const coefficient of coefficients) { + const basis = multiply( + multiply( + binomialField(input[0]!, coefficient.index[0]), + binomialField(input[1]!, coefficient.index[1]) + ), + binomialField(input[2]!, coefficient.index[2]) + ); + output = add(output, multiply(coefficient.value, basis)); + } + return output; +} + +function measureDirectInternalRecovery( + sessions: readonly CapturedSession[] +): { + readonly maskedStateRecovery: number; + readonly maskedDeltaRecovery: number; + readonly protectedLogicalGuess: number; + readonly unmaskedDeltaGuess: number; +} { + let maskedStateMatches = 0; + let maskedStateTotal = 0; + let maskedDeltaMatches = 0; + let maskedDeltaTotal = 0; + let logicalGuessMatches = 0; + let logicalGuessTotal = 0; + let unmaskedDeltaGuessMatches = 0; + let unmaskedDeltaGuessTotal = 0; + + for (const session of sessions) { + for (let epoch = 0; epoch < TRANSITION_COUNT; epoch++) { + for (let lane = 0; lane < WIDTH; lane++) { + const expectedMaskedDelta = subtract( + session.maskedStates[epoch + 1]![lane]!, + session.maskedStates[epoch]![lane]! + ); + if ( + session.maskedDeltas[epoch]![lane] === + expectedMaskedDelta + ) { + maskedDeltaMatches++; + } + maskedDeltaTotal++; + if ( + session.maskedDeltas[epoch]![lane] === + session.ownerDeltas[epoch]![lane] + ) { + unmaskedDeltaGuessMatches++; + } + unmaskedDeltaGuessTotal++; + } + } + for (let epoch = 0; epoch <= TRANSITION_COUNT; epoch++) { + for (let lane = 0; lane < WIDTH; lane++) { + if (Number.isInteger(session.maskedStates[epoch]![lane])) { + maskedStateMatches++; + } + maskedStateTotal++; + if ( + epoch > 0 && + session.maskedStates[epoch]![lane] === + session.ownerStates[epoch]![lane] + ) { + logicalGuessMatches++; + } + if (epoch > 0) logicalGuessTotal++; + } + } + } + + return Object.freeze({ + maskedStateRecovery: fraction( + maskedStateMatches, + maskedStateTotal + ), + maskedDeltaRecovery: fraction( + maskedDeltaMatches, + maskedDeltaTotal + ), + protectedLogicalGuess: fraction( + logicalGuessMatches, + logicalGuessTotal + ), + unmaskedDeltaGuess: fraction( + unmaskedDeltaGuessMatches, + unmaskedDeltaGuessTotal + ), + }); +} + +function measureCrossSessionTransfer(): { + readonly directAccepted: boolean; + readonly reboundAccepted: boolean; + readonly stateAccuracy: number; + readonly deltaAccuracy: number; + readonly outputAccuracy: number; + readonly freshKeys: boolean; +} { + const input = [17, 19, 23]; + const first = captureSession(20_001, input); + const second = captureSession(20_002, input); + const firstTransition = captureFirstTransition(20_003, input); + const secondTransition = captureFirstTransition(20_004, input); + + const directAccepted = doesNotThrow(() => + applyMaskedTransitionResponse( + secondTransition.contract, + secondTransition.state, + secondTransition.localCharts, + COVERS[1]!, + firstTransition.response, + secondTransition.requestNonce + ) + ); + const reboundResponse: ChartCustodyResponse = Object.freeze({ + ...firstTransition.response, + sessionId: secondTransition.contract.sessionId, + contractId: secondTransition.contract.contractId, + requestNonce: secondTransition.requestNonce, + }); + const reboundAccepted = doesNotThrow(() => + applyMaskedTransitionResponse( + secondTransition.contract, + secondTransition.state, + secondTransition.localCharts, + COVERS[1]!, + reboundResponse, + secondTransition.requestNonce + ) + ); + + return Object.freeze({ + directAccepted, + reboundAccepted, + stateAccuracy: exactCoordinateAccuracy( + first.maskedStates.slice(1), + second.maskedStates.slice(1) + ), + deltaAccuracy: exactCoordinateAccuracy( + first.maskedDeltas, + second.maskedDeltas + ), + outputAccuracy: first.output === second.output ? 1 : 0, + freshKeys: + first.clientContract.verificationKey !== + second.clientContract.verificationKey, + }); +} + +export function buildMaskedCustodyAttackerReport(): MaskedCustodyAttackerReport { + const training = buildSimplexTrainingSet(); + const learnedPolynomial = learnFinalScalarPolynomial(training.samples); + const heldOutInputs = Object.freeze([ + Object.freeze([11, 13, 17]), + Object.freeze([21, 8, 14]), + Object.freeze([31, 7, 2]), + Object.freeze([15, 22, 9]), + ]); + const heldOutMatches = heldOutInputs.filter((input, index) => { + const actual = captureSession(10_001 + index, input).output; + return evaluateLearnedPolynomial(learnedPolynomial, input) === actual; + }).length; + const heldOutAccuracy = fraction( + heldOutMatches, + heldOutInputs.length + ); + const directRecovery = measureDirectInternalRecovery(training.captured); + const crossSession = measureCrossSessionTransfer(); + const representative = training.captured[0]!; + const contractKeys = Object.keys(representative.clientContract); + const serializedContract = JSON.stringify( + representative.clientContract + ).toLowerCase(); + const hiddenRelationMaterialPresent = [ + "transitions", + "linear", + "cubicterms", + "masksecret", + "sharingsecret", + "responsesecret", + "lineagesecret", + ].some((token) => serializedContract.includes(token)); + const chartCells = CHART_COUNT * WIDTH; + const interceptedRequests = + representative.transitionRequests + + representative.projectionRequests; + const interceptedSignedResponses = + representative.signedTransitionResponses + + representative.signedProjectionResponses; + const requestChartFieldElements = interceptedRequests * chartCells; + const contributionFieldElements = + TRANSITION_COUNT * CHART_COUNT * WIDTH; + const finalResponseAndOpeningFieldElements = 5; + const openedProjectionFieldElements = 1; + const interceptedTraceFieldElements = + requestChartFieldElements + + contributionFieldElements + + finalResponseAndOpeningFieldElements + + openedProjectionFieldElements; + const reconstructionChartCellReads = + (TRANSITION_COUNT + 1) * THRESHOLD * WIDTH + + TRANSITION_COUNT * THRESHOLD * WIDTH; + const semanticPlainTraceFieldElements = + (TRANSITION_COUNT + 1) * WIDTH + + TRANSITION_COUNT * WIDTH + + 1; + const plainDirectReads = + (TRANSITION_COUNT + 1) * WIDTH + TRANSITION_COUNT * WIDTH; + const traceAmplificationRatio = + interceptedTraceFieldElements / semanticPlainTraceFieldElements; + const reconstructionWorkRatio = + reconstructionChartCellReads / plainDirectReads; + const residentStorageRatio = chartCells / WIDTH; + const compatibleStateCount = + BigInt(CSH_FIELD_MODULUS) ** BigInt(WIDTH); + const freshMaskWholeStateGuessProbability = + 1 / Number(compatibleStateCount); + const trainingQueries = training.samples.size; + const expectedTrainingQueries = binomialInteger( + TOTAL_DEGREE_BOUND + WIDTH, + WIDTH + ); + + if (trainingQueries !== expectedTrainingQueries) { + throw new Error("MASKED_ATTACKER_BAD_TRAINING_CARDINALITY"); + } + + const gates: MaskedCustodyGate[] = [ + Object.freeze({ + id: "MASKED-CUSTODY-DIRECT-INTERNAL-RECOVERY", + status: + directRecovery.protectedLogicalGuess === 0 && + directRecovery.unmaskedDeltaGuess === 0 + ? "pass" + : "fail", + measured: Math.max( + directRecovery.protectedLogicalGuess, + directRecovery.unmaskedDeltaGuess + ), + required: + "0 exact protected coordinates from decoded masked state/delta", + reason: + "Full chart and contribution interception reconstructs the masked representation exactly, while every protected logical vector remains compatible with P^width masks.", + }), + Object.freeze({ + id: "MASKED-CUSTODY-CROSS-SESSION-TRANSFER", + status: + !crossSession.directAccepted && + !crossSession.reboundAccepted && + crossSession.stateAccuracy === 0 && + crossSession.deltaAccuracy === 0 + ? "pass" + : "fail", + measured: Math.max( + crossSession.stateAccuracy, + crossSession.deltaAccuracy + ), + required: + "0 internal coordinate transfer and no accepted foreign response", + reason: + "Fresh masks prevent raw state/delta transfer; session fields and fresh signing keys reject direct and field-rebound signed responses.", + }), + Object.freeze({ + id: "MASKED-CUSTODY-FINAL-ORACLE-NONLEARNABILITY", + status: heldOutAccuracy <= 0.1 ? "pass" : "fail", + measured: heldOutAccuracy, + required: + "<= 0.10 held-out output accuracy from chosen-input final outputs", + reason: + "The complete degree-nine final scalar function is interpolated from black-box outputs only; this does not recover an intermediate state or relation.", + }), + Object.freeze({ + id: "MASKED-CUSTODY-TRACE-AMPLIFICATION", + status: + traceAmplificationRatio >= REQUIRED_AMPLIFICATION + ? "pass" + : "fail", + measured: traceAmplificationRatio, + required: `>= ${REQUIRED_AMPLIFICATION}x client-visible trace volume`, + reason: + "Chart requests, signed contributions, and the final opening produce only a small constant-factor trace expansion over direct semantic values.", + }), + Object.freeze({ + id: "MASKED-CUSTODY-RECONSTRUCTION-WORK", + status: + reconstructionWorkRatio >= REQUIRED_AMPLIFICATION + ? "pass" + : "fail", + measured: reconstructionWorkRatio, + required: `>= ${REQUIRED_AMPLIFICATION}x field-read work`, + reason: + "Three threshold charts recover each masked state or response delta with low linear work.", + }), + Object.freeze({ + id: "MASKED-CUSTODY-RESIDENT-STORAGE", + status: + residentStorageRatio >= REQUIRED_AMPLIFICATION + ? "pass" + : "fail", + measured: residentStorageRatio, + required: `>= ${REQUIRED_AMPLIFICATION}x resident representation`, + reason: + "Five charts store five cells per logical coordinate, below the dynamic-floor target.", + }), + Object.freeze({ + id: "MASKED-CUSTODY-HOOK-COLLAPSE", + status: "fail", + measured: 2, + required: + "> 2 localized client hook families for all client-visible intermediate and final values", + reason: + "Hooking transition application and final opening captures every signed contribution, masked state, and ordinary output.", + }), + ]; + const failedGates = gates + .filter((gate) => gate.status === "fail") + .map((gate) => gate.id); + + return Object.freeze({ + schemaVersion: MASKED_CUSTODY_ATTACKER_REPORT_VERSION, + decision: failedGates.length === 0 ? "go" : "no-go", + clientArtifact: Object.freeze({ + contractFieldCount: contractKeys.length, + hiddenRelationMaterialPresent, + coverPathVisible: + representative.clientContract.coverIds.length === + TRANSITION_COUNT + 1, + }), + internalRecovery: Object.freeze({ + ingressLogicalStateRecovery: exactVectorAccuracy( + representative.input, + representative.maskedStates[0]! + ), + maskedStateRecovery: directRecovery.maskedStateRecovery, + maskedTransitionDeltaRecovery: + directRecovery.maskedDeltaRecovery, + protectedLogicalCoordinateGuessAccuracy: + directRecovery.protectedLogicalGuess, + unmaskedDeltaCoordinateGuessAccuracy: + directRecovery.unmaskedDeltaGuess, + compatibleLogicalStatesPerProtectedObservation: + String(compatibleStateCount), + freshMaskWholeStateGuessProbability, + }), + crossSession: Object.freeze({ + directSignedResponseAccepted: crossSession.directAccepted, + fieldReboundResponseAccepted: crossSession.reboundAccepted, + maskedStateCoordinateTransferAccuracy: + crossSession.stateAccuracy, + maskedDeltaCoordinateTransferAccuracy: + crossSession.deltaAccuracy, + finalProjectionTransferAccuracy: crossSession.outputAccuracy, + sharedServiceSecrets: true, + nonceReuseAcrossSessions: true, + freshVerificationKeys: crossSession.freshKeys, + }), + chosenInputOracle: Object.freeze({ + relationStages: TRANSITION_COUNT, + totalDegreeBound: TOTAL_DEGREE_BOUND, + trainingQueries, + heldOutQueries: heldOutInputs.length, + heldOutOutputAccuracy: heldOutAccuracy, + learnedScope: "complete-final-scalar-function", + evidenceSource: "black-box-io-only", + intermediateRelationRecovered: false, + intermediateStateRecovered: false, + }), + dynamicProxy: Object.freeze({ + chartCount: CHART_COUNT, + width: WIDTH, + threshold: THRESHOLD, + transitionCount: TRANSITION_COUNT, + interceptedRequests, + interceptedSignedResponses, + interceptedTraceFieldElements, + reconstructionChartCellReads, + semanticPlainTraceFieldElements, + plainDirectReads, + traceAmplificationRatio, + reconstructionWorkRatio, + residentStorageRatio, + localizedHookFamilies: 2, + remainingHookSurfaces: Object.freeze([ + "applyMaskedTransitionResponse arguments and return", + "openMaskedCustodyProjection arguments and return", + ]), + }), + gates: Object.freeze(gates), + failedGates: Object.freeze(failedGates), + }); +} + +function exactCoordinateAccuracy( + left: readonly (readonly number[])[], + right: readonly (readonly number[])[] +): number { + let matches = 0; + let total = 0; + for (let row = 0; row < left.length; row++) { + for (let lane = 0; lane < (left[row]?.length ?? 0); lane++) { + if (left[row]![lane] === right[row]?.[lane]) matches++; + total++; + } + } + return fraction(matches, total); +} + +function exactVectorAccuracy( + left: readonly number[], + right: readonly number[] +): number { + return exactCoordinateAccuracy([left], [right]); +} + +function doesNotThrow(operation: () => unknown): boolean { + try { + operation(); + return true; + } catch { + return false; + } +} + +function interpolationWeightsAtZero(points: readonly number[]): number[] { + return points.map((point, index) => { + let numerator = 1; + let denominator = 1; + for (let other = 0; other < points.length; other++) { + if (other === index) continue; + numerator = multiply(numerator, subtract(0, points[other]!)); + denominator = multiply( + denominator, + subtract(point, points[other]!) + ); + } + return multiply(numerator, inverse(denominator)); + }); +} + +function invertMatrix( + matrix: readonly (readonly number[])[] +): number[][] { + const width = matrix.length; + const augmented = matrix.map((row, rowIndex) => [ + ...row.map(normalize), + ...Array.from( + { length: width }, + (_, column) => (column === rowIndex ? 1 : 0) + ), + ]); + for (let column = 0; column < width; column++) { + const pivot = augmented.findIndex( + (row, rowIndex) => rowIndex >= column && row[column] !== 0 + ); + if (pivot < 0) throw new Error("MASKED_ATTACKER_SINGULAR_MATRIX"); + [augmented[column], augmented[pivot]] = [ + augmented[pivot]!, + augmented[column]!, + ]; + const pivotInverse = inverse(augmented[column]![column]!); + augmented[column] = augmented[column]!.map((value) => + multiply(value, pivotInverse) + ); + for (let row = 0; row < width; row++) { + if (row === column) continue; + const factor = augmented[row]![column]!; + augmented[row] = augmented[row]!.map((value, lane) => + subtract( + value, + multiply(factor, augmented[column]![lane]!) + ) + ); + } + } + return augmented.map((row) => row.slice(width)); +} + +function multiplyMatrixVector( + matrix: readonly (readonly number[])[], + vector: readonly number[] +): number[] { + return matrix.map((row) => dot(row, vector)); +} + +function dot( + left: readonly number[], + right: readonly number[] +): number { + let result = 0; + for (let index = 0; index < left.length; index++) { + result = add(result, multiply(left[index]!, right[index]!)); + } + return result; +} + +function binomialInteger(total: number, chosen: number): number { + if (chosen < 0 || chosen > total) return 0; + const count = Math.min(chosen, total - chosen); + let value = 1; + for (let index = 1; index <= count; index++) { + value = (value * (total - count + index)) / index; + } + return value; +} + +function binomialField(value: number, chosen: number): number { + let numerator = 1; + let denominator = 1; + for (let index = 0; index < chosen; index++) { + numerator = multiply(numerator, subtract(value, index)); + denominator = multiply(denominator, index + 1); + } + return multiply(numerator, inverse(denominator)); +} + +function power(base: number, exponent: number): number { + let result = 1; + let factor = normalize(base); + let remaining = exponent; + while (remaining > 0) { + if (remaining % 2 === 1) result = multiply(result, factor); + factor = multiply(factor, factor); + remaining = Math.floor(remaining / 2); + } + return result; +} + +function inverse(value: number): number { + const normalized = normalize(value); + if (normalized === 0) throw new Error("MASKED_ATTACKER_ZERO_INVERSE"); + return power(normalized, CSH_FIELD_MODULUS - 2); +} + +function add(left: number, right: number): number { + return normalize(normalize(left) + normalize(right)); +} + +function subtract(left: number, right: number): number { + return normalize(normalize(left) - normalize(right)); +} + +function multiply(left: number, right: number): number { + return normalize(normalize(left) * normalize(right)); +} + +function normalize(value: number): number { + const reduced = Math.trunc(value) % CSH_FIELD_MODULUS; + return reduced < 0 ? reduced + CSH_FIELD_MODULUS : reduced; +} + +function fraction(numerator: number, denominator: number): number { + return denominator === 0 ? 0 : numerator / denominator; +} + +function vectorKey(vector: readonly number[]): string { + return vector.join(","); +} + +function ingressSeed(sessionOrdinal: number): number { + return 100_000 + sessionOrdinal * 17; +} + +function transportSeed(sessionOrdinal: number, epoch: number): number { + return 200_000 + sessionOrdinal * 31 + epoch; +} + +function transitionNonce(sessionOrdinal: number, epoch: number): string { + void sessionOrdinal; + return `reused-transition-nonce-${epoch}`; +} + +function exitNonce(sessionOrdinal: number): string { + void sessionOrdinal; + return "reused-projection-nonce"; +} + +function freezeMatrix( + matrix: readonly (readonly number[])[] +): readonly (readonly number[])[] { + return Object.freeze( + matrix.map((row) => Object.freeze([...row])) + ); +} From 8f75b5c8cd026e454f8a9603b92100d80cf4f0fc Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:31:51 -1000 Subject: [PATCH 24/54] docs: separate custody secrecy from oracle learning --- .../2026-07-24-bprf-csh-spike-report.md | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md b/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md index a7c9709..81e9f12 100644 --- a/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md +++ b/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md @@ -3,7 +3,8 @@ **Date:** 2026-07-24 **Branch:** `codex/traveling-isogloss-replacement` **Decision state:** Initial local BPRF/CSH and first custody variants are -**no-go**; statefully masked custody is under a new adversarial gate and product +**no-go**; statefully masked custody prevents direct internal recovery but +remains **no-go** under the stricter black-box/overhead gates, and product schemas remain unfrozen ## Purpose @@ -180,9 +181,43 @@ substitute a new logical input at the next epoch. Missing responses, replay, snapshot forks, nonce substitution, signature tampering, and representation substitution all fail closed. -This stateful masked evolution has passed focused correctness/protocol tests. -Its dynamic recovery, cross-session transfer, and black-box learnability gates -are still in progress; no go decision is recorded yet. +The statefully masked evolution has now been attacked under full client +artifact, heap, request, response, and output visibility: + +| Measurement | Result | +|---|---:| +| Protected logical-coordinate recovery | 0 | +| Unmasked transition-delta recovery | 0 | +| Masked state/delta recovery | 1.0 | +| Compatible logical states per width-three protected observation | 65,521³ | +| Cross-session masked state/delta transfer | 0 | +| Foreign/rebound signed response accepted | false / false | +| Shared service secrets and reused nonce | tested | +| Final output transfer | 1.0 | +| Localized client hook families | 2 | +| Trace / reconstruction / storage proxy | 5.0625× / 3× / 5× | + +This clears the central custody property: complete client observation does not +determine an intermediate logical state or unmasked transition. It does not +clear the broader experimental no-go report. The chosen-input attacker learns +the complete degree-nine, three-input final scalar function from **220** +fresh-session output queries and reaches **1.0** accuracy on held-out inputs. +That attack uses black-box input/output pairs only and recovers neither hidden +transition nor intermediate state. + +The distinction is architectural: + +- stateful masking successfully changes client completeness from true to + false for protected internals; +- exact native-visible outputs remain an unavoidable oracle; and +- a simple deterministic function can be learned from that oracle regardless + of how its internals execute. + +Ruam must not describe the first property as black-box nonlearnability. Raising +the oracle floor further requires an external query-authorization constraint, +keeping the final value outside the hostile client, or protecting a function +whose intrinsic query complexity is high. None can be manufactured by a +semantics-preserving client transform alone. ## Correctness and build status @@ -234,12 +269,10 @@ Do not freeze artifact, certificate, carrier, or runtime schemas yet. The next decision requires: -- adversarial scoring of the statefully masked custody transcript and client - state; -- separation of intrinsic black-box I/O learnability from recovery of an - internal transition or state; - an actively secure, topology-hidden PFE feasibility decision if the private profile remains in scope; +- padded transcript and opaque contract-bucket measurements for direct + custody; - canonical-operation and patch-collapse measurements on product-shaped emitted code; - legitimate size/runtime/latency/bandwidth measurements; and From 1abba7af6460aeb831a51860aefe8fe8650fb604 Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:35:05 -1000 Subject: [PATCH 25/54] feat: pad masked custody transcript epochs --- .../src/isogloss/csh/padded-masked-plan.ts | 151 +++++++++ .../isogloss/csh-padded-masked-plan.test.ts | 313 ++++++++++++++++++ .../test/isogloss/reference-isolation.test.ts | 1 + 3 files changed, 465 insertions(+) create mode 100644 packages/ruam/src/isogloss/csh/padded-masked-plan.ts create mode 100644 packages/ruam/test/isogloss/csh-padded-masked-plan.test.ts diff --git a/packages/ruam/src/isogloss/csh/padded-masked-plan.ts b/packages/ruam/src/isogloss/csh/padded-masked-plan.ts new file mode 100644 index 0000000..e7f1585 --- /dev/null +++ b/packages/ruam/src/isogloss/csh/padded-masked-plan.ts @@ -0,0 +1,151 @@ +/** + * OWNER/SERVER-ONLY fixed-bucket planning for statefully masked custody. + * + * Real logical transitions retain their order but occupy secret slots among + * identity epochs. Every slot still refreshes the custodian representation + * mask and moves to a fresh cover, so the client transcript reveals only the + * declared width/epoch bucket, not the number or positions of real stages. + * + * @module isogloss/csh/padded-masked-plan + */ + +import { deriveSeed } from "../../naming/scope.js"; +import { createSeededRandom } from "../../random/entropy.js"; +import { + createChartCover, + type ChartCover, +} from "./reference.js"; +import type { HiddenMaskedTransition } from "./reference-masked-custodian.js"; + +export const MASKED_CUSTODY_TRANSCRIPT_BUCKETS = Object.freeze([ + 4, 8, 16, 32, +] as const); + +export interface PaddedMaskedCustodyPlanOptions { + readonly realTransitions: readonly HiddenMaskedTransition[]; + readonly width: number; + readonly bucketSize: (typeof MASKED_CUSTODY_TRANSCRIPT_BUCKETS)[number]; + readonly coverSeed: number; + readonly placementSeed: number; +} + +/** + * This complete object is owner/server material. Only `transcriptClassId` and + * the resulting custodian client contract may cross the trust boundary. + */ +export interface PaddedMaskedCustodyPlan { + readonly transcriptClassId: string; + readonly covers: readonly ChartCover[]; + readonly transitions: readonly HiddenMaskedTransition[]; + readonly realTransitionSlots: readonly number[]; + readonly paddingTransitionCount: number; +} + +export function createPaddedMaskedCustodyPlan( + options: PaddedMaskedCustodyPlanOptions +): PaddedMaskedCustodyPlan { + validateOptions(options); + const transcriptClassId = + `csh-masked-v1-w${options.width}-e${options.bucketSize}`; + const realTransitionSlots = selectOrderedSlots( + options.bucketSize, + options.realTransitions.length, + deriveSeed( + options.placementSeed >>> 0, + `${transcriptClassId}:placement` + ) + ); + const realBySlot = new Map( + realTransitionSlots.map((slot, index) => [ + slot, + options.realTransitions[index]!, + ]) + ); + const identity = createIdentityTransition(options.width); + const transitions = Array.from( + { length: options.bucketSize }, + (_, slot) => realBySlot.get(slot) ?? identity + ); + const covers = Array.from( + { length: options.bucketSize + 1 }, + (_, epoch) => + createChartCover({ + seed: deriveSeed( + options.coverSeed >>> 0, + `${transcriptClassId}:cover` + ), + epoch, + width: options.width, + chartCount: 5, + threshold: 3, + }) + ); + return Object.freeze({ + transcriptClassId, + covers: Object.freeze(covers), + transitions: Object.freeze(transitions), + realTransitionSlots: Object.freeze(realTransitionSlots), + paddingTransitionCount: + options.bucketSize - options.realTransitions.length, + }); +} + +function validateOptions(options: PaddedMaskedCustodyPlanOptions): void { + if ( + !Number.isSafeInteger(options.width) || + options.width < 2 + ) { + throw new Error("RUAM_CSH_PADDED_PLAN_INVALID_WIDTH"); + } + if ( + !MASKED_CUSTODY_TRANSCRIPT_BUCKETS.includes(options.bucketSize) + ) { + throw new Error("RUAM_CSH_PADDED_PLAN_INVALID_BUCKET"); + } + if ( + options.realTransitions.length === 0 || + options.realTransitions.length > options.bucketSize + ) { + throw new Error("RUAM_CSH_PADDED_PLAN_TRANSITION_COUNT"); + } + for (const value of [options.coverSeed, options.placementSeed]) { + if (!Number.isSafeInteger(value)) { + throw new Error("RUAM_CSH_PADDED_PLAN_INVALID_SEED"); + } + } +} + +function selectOrderedSlots( + bucketSize: number, + realCount: number, + seed: number +): number[] { + const random = createSeededRandom(seed); + const slots = Array.from({ length: bucketSize }, (_, index) => index); + for (let index = slots.length - 1; index > 0; index--) { + const target = random.nextUint32() % (index + 1); + [slots[index], slots[target]] = [slots[target]!, slots[index]!]; + } + return slots.slice(0, realCount).sort((left, right) => left - right); +} + +function createIdentityTransition( + width: number +): HiddenMaskedTransition { + return Object.freeze({ + linear: Object.freeze( + Array.from( + { length: width }, + (_, row) => + Object.freeze( + Array.from( + { length: width }, + (_, column) => (row === column ? 1 : 0) + ) + ) + ) + ), + bias: Object.freeze(Array.from({ length: width }, () => 0)), + cubicTerms: Object.freeze([]), + }); +} diff --git a/packages/ruam/test/isogloss/csh-padded-masked-plan.test.ts b/packages/ruam/test/isogloss/csh-padded-masked-plan.test.ts new file mode 100644 index 0000000..2551f4b --- /dev/null +++ b/packages/ruam/test/isogloss/csh-padded-masked-plan.test.ts @@ -0,0 +1,313 @@ +import { describe, expect, it } from "bun:test"; +import { + applyMaskedTransitionResponse, + createMaskedCustodyClientState, + openMaskedCustodyProjection, + prepareMaskedProjectionRequest, + prepareMaskedTransitionRequest, +} from "../../src/isogloss/csh/masked-custody-protocol.js"; +import { + createPaddedMaskedCustodyPlan, + MASKED_CUSTODY_TRANSCRIPT_BUCKETS, + type PaddedMaskedCustodyPlan, +} from "../../src/isogloss/csh/padded-masked-plan.js"; +import { + ReferenceMaskedChartCustodian, + type HiddenMaskedTransition, +} from "../../src/isogloss/csh/reference-masked-custodian.js"; +import { + CSH_FIELD_MODULUS, + encodeIngressCharts, + transportAffineCharts, +} from "../../src/isogloss/csh/reference.js"; + +const WIDTH = 3; + +const realTransitions: readonly HiddenMaskedTransition[] = [ + { + linear: [ + [1, 1, 0], + [0, 1, 0], + [0, 0, 1], + ], + bias: [3, 5, 7], + cubicTerms: [ + { + inputProjection: [1, 0, 1], + outputDirection: [1, 2, 0], + coefficient: 11, + }, + ], + }, + { + linear: [ + [1, 0, 0], + [1, 1, 0], + [0, 0, 1], + ], + bias: [13, 17, 19], + cubicTerms: [ + { + inputProjection: [0, 1, 1], + outputDirection: [0, 1, 3], + coefficient: 23, + }, + ], + }, +]; + +const exitProjection = { + id: "padded_exit", + coefficients: [1, 2, 3], + bias: 29, +}; + +const identity = { + id: "padded_identity_transport", + matrix: [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + ], + bias: [0, 0, 0], +}; + +interface Execution { + readonly output: number; + readonly contract: ReferenceMaskedChartCustodian["clientContract"]; + readonly responseShapes: readonly string[]; +} + +function executePlan( + plan: PaddedMaskedCustodyPlan, + sessionId: string +): Execution { + const custodian = new ReferenceMaskedChartCustodian({ + sessionId, + contractId: "padded_bucket_contract", + covers: plan.covers, + transitions: plan.transitions, + exitProjection, + initialLineageCommitment: "padded_genesis", + lineageSecret: "padded_service_lineage", + maskSecret: "padded_service_mask", + sharingSecret: "padded_service_sharing", + responseSecret: "padded_service_response", + }); + let charts = encodeIngressCharts([2, 3, 5], plan.covers[0]!, 5151); + let state = createMaskedCustodyClientState(custodian.clientContract); + const responseShapes: string[] = []; + for (let epoch = 0; epoch < plan.transitions.length; epoch++) { + const request = prepareMaskedTransitionRequest( + custodian.clientContract, + state, + charts, + `padded_nonce_${epoch}` + ); + const response = custodian.evaluateTransition(request); + responseShapes.push( + JSON.stringify({ + keys: Object.keys(response).sort(), + contributions: response.chartContributions.length, + cells: response.chartContributions.map( + (contribution) => contribution.cells.length + ), + signatureLength: response.signature.length, + }) + ); + const target = plan.covers[epoch + 1]!; + const local = transportAffineCharts( + charts, + plan.covers[epoch]!, + target, + identity, + 6161 + epoch + ); + const applied = applyMaskedTransitionResponse( + custodian.clientContract, + state, + local, + target, + response, + request.nonce + ); + charts = applied.charts; + state = applied.state; + } + const request = prepareMaskedProjectionRequest( + custodian.clientContract, + state, + charts, + "padded_exit_nonce" + ); + const opened = openMaskedCustodyProjection( + custodian.clientContract, + state, + custodian.evaluateExitProjection(request), + request.nonce + ); + return Object.freeze({ + output: opened.projection, + contract: custodian.clientContract, + responseShapes: Object.freeze(responseShapes), + }); +} + +function createPlan(placementSeed: number): PaddedMaskedCustodyPlan { + return createPaddedMaskedCustodyPlan({ + realTransitions, + width: WIDTH, + bucketSize: 4, + coverSeed: 4242, + placementSeed, + }); +} + +function findDifferentlyPlacedPlans(): [ + PaddedMaskedCustodyPlan, + PaddedMaskedCustodyPlan, +] { + const first = createPlan(1); + for (let seed = 2; seed < 1_000; seed++) { + const candidate = createPlan(seed); + if ( + JSON.stringify(candidate.realTransitionSlots) !== + JSON.stringify(first.realTransitionSlots) + ) { + return [first, candidate]; + } + } + throw new Error("Missing distinct padded placement"); +} + +describe("fixed-bucket statefully masked custody planning", () => { + it("preserves output while hiding real transition positions", () => { + const [firstPlan, secondPlan] = findDifferentlyPlacedPlans(); + const first = executePlan(firstPlan, "padded_session_a"); + const second = executePlan(secondPlan, "padded_session_b"); + let ownerState = [2, 3, 5]; + for (const transition of realTransitions) { + ownerState = applyOwnerTransition(transition, ownerState); + } + const expected = add( + dot(exitProjection.coefficients, ownerState), + exitProjection.bias + ); + + expect(firstPlan.realTransitionSlots).not.toEqual( + secondPlan.realTransitionSlots + ); + expect(first.output).toBe(expected); + expect(second.output).toBe(expected); + expect(firstPlan.paddingTransitionCount).toBe(2); + expect(secondPlan.paddingTransitionCount).toBe(2); + }); + + it("has one fixed client-visible shape for every epoch and placement", () => { + const [firstPlan, secondPlan] = findDifferentlyPlacedPlans(); + const first = executePlan(firstPlan, "shape_session_a"); + const second = executePlan(secondPlan, "shape_session_b"); + + expect(firstPlan.transcriptClassId).toBe( + "csh-masked-v1-w3-e4" + ); + expect(secondPlan.transcriptClassId).toBe( + firstPlan.transcriptClassId + ); + expect(firstPlan.covers).toEqual(secondPlan.covers); + expect(first.contract.coverIds).toEqual(second.contract.coverIds); + expect(first.contract.coverIds).toHaveLength(5); + expect(new Set(first.responseShapes).size).toBe(1); + expect(new Set(second.responseShapes).size).toBe(1); + expect(first.responseShapes).toEqual(second.responseShapes); + }); + + it("keeps owner schedule and real stage count out of the client contract", () => { + const plan = createPlan(9191); + const execution = executePlan(plan, "secrecy_session"); + const serialized = JSON.stringify(execution.contract).toLowerCase(); + + for (const forbidden of [ + "realtransitions", + "realtransitionslots", + "paddingtransitioncount", + "placement", + "identity", + "linear", + "cubic", + "bias", + ]) { + expect(serialized).not.toContain(forbidden); + } + expect(plan.realTransitionSlots).toHaveLength(2); + }); + + it("accepts only fixed transcript buckets and fitting real plans", () => { + expect(MASKED_CUSTODY_TRANSCRIPT_BUCKETS).toEqual([4, 8, 16, 32]); + expect(() => + createPaddedMaskedCustodyPlan({ + realTransitions, + width: WIDTH, + bucketSize: 3 as 4, + coverSeed: 1, + placementSeed: 2, + }) + ).toThrow("RUAM_CSH_PADDED_PLAN_INVALID_BUCKET"); + expect(() => + createPaddedMaskedCustodyPlan({ + realTransitions: Array.from( + { length: 5 }, + () => realTransitions[0]! + ), + width: WIDTH, + bucketSize: 4, + coverSeed: 1, + placementSeed: 2, + }) + ).toThrow("RUAM_CSH_PADDED_PLAN_TRANSITION_COUNT"); + }); +}); + +function applyOwnerTransition( + transition: HiddenMaskedTransition, + input: readonly number[] +): number[] { + const output = transition.linear.map((row, rowIndex) => + add(dot(row, input), transition.bias[rowIndex]!) + ); + for (const term of transition.cubicTerms) { + const projected = dot(term.inputProjection, input); + const cubic = multiply(multiply(projected, projected), projected); + for (let lane = 0; lane < output.length; lane++) { + output[lane] = add( + output[lane]!, + multiply( + multiply(term.coefficient, cubic), + term.outputDirection[lane]! + ) + ); + } + } + return output; +} + +function dot(left: readonly number[], right: readonly number[]): number { + let result = 0; + for (let index = 0; index < left.length; index++) { + result = add(result, multiply(left[index]!, right[index]!)); + } + return result; +} + +function add(left: number, right: number): number { + return normalize(normalize(left) + normalize(right)); +} + +function multiply(left: number, right: number): number { + return normalize(normalize(left) * normalize(right)); +} + +function normalize(value: number): number { + const reduced = Math.trunc(value) % CSH_FIELD_MODULUS; + return reduced < 0 ? reduced + CSH_FIELD_MODULUS : reduced; +} diff --git a/packages/ruam/test/isogloss/reference-isolation.test.ts b/packages/ruam/test/isogloss/reference-isolation.test.ts index 9f9d032..42d6b17 100644 --- a/packages/ruam/test/isogloss/reference-isolation.test.ts +++ b/packages/ruam/test/isogloss/reference-isolation.test.ts @@ -46,6 +46,7 @@ describe("Isogloss reference isolation", () => { "/src/isogloss/csh/reference.ts", "/src/isogloss/csh/testing-bprf-reference.ts", "/src/isogloss/csh/testing-emitter.ts", + "/src/isogloss/csh/padded-masked-plan.ts", "/src/isogloss/csh/reference-custodian.ts", "/src/isogloss/csh/reference-chart-custodian.ts", "/src/isogloss/csh/reference-masked-custodian.ts", From 3af0693b9d9545b3a2544625cbb40b468058a788 Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:36:32 -1000 Subject: [PATCH 26/54] feat: plan pure regions and call boundaries --- packages/ruam/src/compiler/call-graph.ts | 671 ++++++++++++++++++ packages/ruam/src/compiler/index.ts | 26 + .../ruam/src/compiler/pure-region-planning.ts | 253 +++++++ .../ruam/test/isogloss/call-graph.test.ts | 316 +++++++++ .../isogloss/pure-region-planning.test.ts | 476 +++++++++++++ 5 files changed, 1742 insertions(+) create mode 100644 packages/ruam/src/compiler/call-graph.ts create mode 100644 packages/ruam/src/compiler/pure-region-planning.ts create mode 100644 packages/ruam/test/isogloss/call-graph.test.ts create mode 100644 packages/ruam/test/isogloss/pure-region-planning.test.ts diff --git a/packages/ruam/src/compiler/call-graph.ts b/packages/ruam/src/compiler/call-graph.ts new file mode 100644 index 0000000..970f493 --- /dev/null +++ b/packages/ruam/src/compiler/call-graph.ts @@ -0,0 +1,671 @@ +/** + * Deterministic root-group call-boundary and SCC inventory. + * + * Canonical IR currently proves the identities of nested closure allocations: + * a `unit-ref` operand indexes a string constant which must name a direct child + * unit. It does not prove the target of a call. Invocation operands encode + * argument shape, and canonical call exits encode only the local resume node. + * Closure identity and call identity are therefore kept separate here. + * + * Treating a call operand as a unit reference would be unsound (an argument + * count can accidentally equal a constant-pool index). Until canonical IR + * carries an explicit call-target fact, every call, construction, dynamic-code, + * and reflective user-code boundary is classified as indirect-or-external. + * + * @module compiler/call-graph + */ + +import type { + SemanticInstruction, + SemanticNodeId, + SemanticRootGroup, + SemanticUnit, + SourceOrigin, + SourceOriginId, +} from "./ir.js"; +import { + assertCanonicalSemanticOp, + semanticOpName, + type SemanticOp, +} from "./semantic-ops.js"; +import { + getSemanticSignature, + type OperandKind, + type SemanticCallKind, + type SemanticCoercion, + type SemanticCompletion, + type SemanticEffect, + type SemanticSuspensionKind, +} from "./semantic-signatures.js"; +import type { RootGroupId, SemanticUnitId } from "./types.js"; + +type UserCodeCallKind = Exclude; + +/** Stable source identity for one observable boundary or closure allocation. */ +export interface CanonicalCallSource { + unitId: SemanticUnitId; + nodeId: SemanticNodeId; + originId: SourceOriginId; + origin: Readonly; +} + +/** + * A nested unit identity explicitly proven by a canonical `unit-ref`. + * + * This is lexical allocation evidence, not evidence that the child is invoked. + */ +export interface CanonicalClosureSite extends CanonicalCallSource { + targetUnitId: SemanticUnitId; + op: SemanticOp; + opName: string; + evidence: "unit-ref-constant-and-child-membership"; +} + +export type CanonicalBoundaryKind = + | "invoke" + | "construct" + | "dynamic-code" + | "dynamic-module" + | "reflection" + | "unknown"; + +export type CanonicalUnresolvedCallReason = + | "callee-identity-not-represented" + | "constructor-identity-not-represented" + | "runtime-generated-code" + | "runtime-module-resolution" + | "runtime-hook-dispatch" + | "unknown-call-semantics"; + +/** + * Observable facts later fission/lowering stages must preserve around a + * boundary. `mayExecuteArbitraryUserCode` deliberately dominates the narrower + * signature effects: getters, proxies, coercion hooks, and callees can touch + * state not named by the local instruction. + */ +export interface CanonicalBoundaryObservability { + callKind: UserCodeCallKind; + effect: SemanticEffect; + completion: SemanticCompletion; + coercion: SemanticCoercion; + suspension: SemanticSuspensionKind; + mayThrow: boolean; + maySuspend: boolean; + mayExecuteArbitraryUserCode: true; + mayReadOrWriteProgramState: true; + mayReenterRootGroup: true; +} + +export type CanonicalCallResolution = + | { + kind: "direct-intra-group"; + targetUnitId: SemanticUnitId; + evidence: "explicit-canonical-call-target"; + } + | { + kind: "indirect-or-external"; + reason: CanonicalUnresolvedCallReason; + }; + +/** + * One operation which can invoke user or host-controlled code. + * + * The encoded operand and its kind are retained for auditing. They must not be + * interpreted as a target unless {@link CanonicalCallResolution} carries + * explicit target evidence. + */ +export interface CanonicalCallBoundary extends CanonicalCallSource { + op: SemanticOp; + opName: string; + boundaryKind: CanonicalBoundaryKind; + operand: number; + operandKind: OperandKind; + resolution: CanonicalCallResolution; + observability: CanonicalBoundaryObservability; +} + +/** A call-graph edge backed by explicit canonical target evidence. */ +export interface CanonicalDirectCallEdge extends CanonicalCallSource { + targetUnitId: SemanticUnitId; + evidence: "explicit-canonical-call-target"; +} + +export interface CanonicalCallScc { + id: string; + unitIds: readonly SemanticUnitId[]; + incomingSccIds: readonly string[]; + outgoingSccIds: readonly string[]; + /** Proven by a self-edge or a component containing multiple units. */ + isRecursive: boolean; + /** Proven only when the component contains multiple units. */ + isMutuallyRecursive: boolean; + /** An unresolved boundary could dispatch back into this same unit. */ + hasUnresolvedRecursionRisk: boolean; + /** An unresolved boundary could dispatch into another unit in the group. */ + hasUnresolvedMutualRecursionRisk: boolean; + /** Arbitrary user code at a member boundary can reenter the protected root. */ + reentrancyRelevant: boolean; + requiresInterproceduralFission: boolean; +} + +export interface CanonicalCallGraphSummary { + hasProvenRecursion: boolean; + hasProvenMutualRecursion: boolean; + hasUnresolvedRecursionRisk: boolean; + hasUnresolvedMutualRecursionRisk: boolean; + reentrancyRelevantSccIds: readonly string[]; + interproceduralFissionSccIds: readonly string[]; +} + +/** + * Explicitly records why an empty direct-edge set is meaningful rather than + * evidence that a root group makes no internal calls. + */ +export interface CanonicalCallTargetPolicy { + mode: "canonical-evidence-only"; + directTargetRepresentation: "absent-from-current-ir"; + unprovenBoundaryClassification: "indirect-or-external"; +} + +export interface CanonicalCallGraphInventory { + rootGroupId: RootGroupId; + entryUnitId: SemanticUnitId; + unitIds: readonly SemanticUnitId[]; + targetPolicy: CanonicalCallTargetPolicy; + closureSites: readonly CanonicalClosureSite[]; + boundaries: readonly CanonicalCallBoundary[]; + directEdges: readonly CanonicalDirectCallEdge[]; + sccs: readonly CanonicalCallScc[]; + summary: CanonicalCallGraphSummary; +} + +/** + * Inventory all canonical user-code boundaries and compute SCCs from only + * direct edges whose target is explicitly represented in canonical IR. + */ +export function buildCanonicalCallGraphInventory( + group: SemanticRootGroup +): CanonicalCallGraphInventory { + const units = validateAndIndexGroup(group); + const unitIds = Object.freeze([...units.keys()].sort(compareStrings)); + const closureSites: CanonicalClosureSite[] = []; + const boundaries: CanonicalCallBoundary[] = []; + + for (const unitId of unitIds) { + const unit = units.get(unitId)!; + for (const node of unit.nodes) { + const source = sourceFor(unit, node); + const signature = getSemanticSignature(node.op); + + if (signature.operandKind === "unit-ref") { + closureSites.push( + createClosureSite(unit, node, source, units) + ); + } + + if (signature.callKind !== "none") { + boundaries.push( + createBoundary(node, source, signature.callKind) + ); + } + } + } + + closureSites.sort(compareSources); + boundaries.sort(compareSources); + + const directEdges = Object.freeze( + boundaries + .flatMap((boundary): CanonicalDirectCallEdge[] => { + if (boundary.resolution.kind !== "direct-intra-group") return []; + return [ + Object.freeze({ + unitId: boundary.unitId, + nodeId: boundary.nodeId, + originId: boundary.originId, + origin: boundary.origin, + targetUnitId: boundary.resolution.targetUnitId, + evidence: boundary.resolution.evidence, + }), + ]; + }) + .sort(compareEdges) + ); + const sccs = buildDeterministicSccs(unitIds, directEdges, boundaries); + const summary = summarizeSccs(sccs); + + return Object.freeze({ + rootGroupId: group.id, + entryUnitId: group.entryUnitId, + unitIds, + targetPolicy: Object.freeze({ + mode: "canonical-evidence-only", + directTargetRepresentation: "absent-from-current-ir", + unprovenBoundaryClassification: "indirect-or-external", + }), + closureSites: Object.freeze(closureSites), + boundaries: Object.freeze(boundaries), + directEdges, + sccs, + summary, + }); +} + +function validateAndIndexGroup( + group: SemanticRootGroup +): Map { + if (group.units.length === 0) { + throw new Error(`RUAM_EMPTY_SEMANTIC_ROOT_GROUP: ${group.id}`); + } + + const units = new Map(); + for (const unit of group.units) { + if (units.has(unit.id)) { + throw new Error(`RUAM_DUPLICATE_SEMANTIC_UNIT: ${unit.id}`); + } + if (unit.rootGroupId !== group.id) { + throw new Error( + `RUAM_CALL_GRAPH_ROOT_GROUP_MISMATCH: ${unit.id} belongs to ${unit.rootGroupId}, expected ${group.id}` + ); + } + units.set(unit.id, unit); + } + + if (!units.has(group.entryUnitId)) { + throw new Error( + `RUAM_MISSING_CALL_GRAPH_ENTRY_UNIT: ${group.entryUnitId}` + ); + } + + for (const unit of units.values()) { + validateUnit(unit, units); + } + return units; +} + +function validateUnit( + unit: SemanticUnit, + units: ReadonlyMap +): void { + if (unit.nodes.length === 0) { + throw new Error(`RUAM_EMPTY_CALL_GRAPH_UNIT: ${unit.id}`); + } + if ( + !Number.isSafeInteger(unit.entryNode) || + unit.entryNode < 0 || + unit.entryNode >= unit.nodes.length + ) { + throw new Error( + `RUAM_INVALID_CALL_GRAPH_ENTRY_NODE: ${unit.id}:${unit.entryNode}` + ); + } + + const childIds = new Set(); + for (const childId of unit.childUnitIds) { + if (childIds.has(childId)) { + throw new Error( + `RUAM_DUPLICATE_CALL_GRAPH_CHILD: ${unit.id} -> ${childId}` + ); + } + childIds.add(childId); + if (!units.has(childId)) { + throw new Error( + `RUAM_UNKNOWN_CALL_GRAPH_CHILD: ${unit.id} -> ${childId}` + ); + } + } + + for (let index = 0; index < unit.nodes.length; index++) { + const node = unit.nodes[index]!; + assertCanonicalSemanticOp(node.op); + if (node.id !== index) { + throw new Error( + `RUAM_NONDETERMINISTIC_CALL_GRAPH_NODE_ORDER: ${unit.id}:${node.id} at ${index}` + ); + } + if (!Number.isSafeInteger(node.operand)) { + throw new Error( + `RUAM_INVALID_CALL_GRAPH_OPERAND: ${unit.id}:${node.id}` + ); + } + if ( + !Number.isSafeInteger(node.originId) || + node.originId < 0 || + node.originId >= unit.origins.length + ) { + throw new Error( + `RUAM_MISSING_CALL_GRAPH_ORIGIN: ${unit.id}:${node.id}` + ); + } + if (!unit.exits.has(node.id)) { + throw new Error( + `RUAM_MISSING_CALL_GRAPH_EXITS: ${unit.id}:${node.id}` + ); + } + } +} + +function sourceFor( + unit: SemanticUnit, + node: SemanticInstruction +): CanonicalCallSource { + const origin = unit.origins[node.originId]!; + return Object.freeze({ + unitId: unit.id, + nodeId: node.id, + originId: node.originId, + origin: Object.freeze({ ...origin }), + }); +} + +function createClosureSite( + unit: SemanticUnit, + node: SemanticInstruction, + source: CanonicalCallSource, + units: ReadonlyMap +): CanonicalClosureSite { + const constant = unit.constants[node.operand]; + if (constant?.type !== "string") { + throw new Error( + `RUAM_INVALID_CANONICAL_UNIT_REF: ${unit.id}:${node.id} operand ${node.operand}` + ); + } + const targetUnitId = constant.value; + if (!units.has(targetUnitId)) { + throw new Error( + `RUAM_UNKNOWN_CANONICAL_UNIT_REF: ${unit.id}:${node.id} -> ${targetUnitId}` + ); + } + if (!unit.childUnitIds.includes(targetUnitId)) { + throw new Error( + `RUAM_NON_CHILD_CANONICAL_UNIT_REF: ${unit.id}:${node.id} -> ${targetUnitId}` + ); + } + + return Object.freeze({ + ...source, + targetUnitId, + op: node.op, + opName: semanticOpName(node.op), + evidence: "unit-ref-constant-and-child-membership", + }); +} + +function createBoundary( + node: SemanticInstruction, + source: CanonicalCallSource, + callKind: UserCodeCallKind +): CanonicalCallBoundary { + const signature = getSemanticSignature(node.op); + return Object.freeze({ + ...source, + op: node.op, + opName: semanticOpName(node.op), + boundaryKind: boundaryKindFor(callKind), + operand: node.operand, + operandKind: signature.operandKind, + resolution: Object.freeze({ + kind: "indirect-or-external", + reason: unresolvedReasonFor(callKind), + }), + observability: Object.freeze({ + callKind, + effect: signature.effect, + completion: signature.completion, + coercion: signature.coercion, + suspension: signature.suspension, + mayThrow: signature.mayThrow, + maySuspend: + signature.suspension !== "none" || + callKind === "dynamic-import", + mayExecuteArbitraryUserCode: true, + mayReadOrWriteProgramState: true, + mayReenterRootGroup: true, + }), + }); +} + +function boundaryKindFor( + callKind: UserCodeCallKind +): CanonicalBoundaryKind { + switch (callKind) { + case "invoke": + return "invoke"; + case "construct": + return "construct"; + case "direct-eval": + return "dynamic-code"; + case "dynamic-import": + return "dynamic-module"; + case "coercion-hook": + case "host-protocol": + return "reflection"; + case "unknown": + return "unknown"; + } +} + +function unresolvedReasonFor( + callKind: UserCodeCallKind +): CanonicalUnresolvedCallReason { + switch (callKind) { + case "invoke": + return "callee-identity-not-represented"; + case "construct": + return "constructor-identity-not-represented"; + case "direct-eval": + return "runtime-generated-code"; + case "dynamic-import": + return "runtime-module-resolution"; + case "coercion-hook": + case "host-protocol": + return "runtime-hook-dispatch"; + case "unknown": + return "unknown-call-semantics"; + } +} + +function buildDeterministicSccs( + unitIds: readonly SemanticUnitId[], + edges: readonly CanonicalDirectCallEdge[], + boundaries: readonly CanonicalCallBoundary[] +): readonly CanonicalCallScc[] { + const adjacency = new Map( + unitIds.map((unitId) => [unitId, []]) + ); + for (const edge of edges) { + const targets = adjacency.get(edge.unitId); + if (!targets || !adjacency.has(edge.targetUnitId)) { + throw new Error( + `RUAM_CALL_GRAPH_EDGE_OUTSIDE_GROUP: ${edge.unitId} -> ${edge.targetUnitId}` + ); + } + if (!targets.includes(edge.targetUnitId)) { + targets.push(edge.targetUnitId); + targets.sort(compareStrings); + } + } + + let nextIndex = 0; + const indexByUnit = new Map(); + const lowLinkByUnit = new Map(); + const stack: SemanticUnitId[] = []; + const onStack = new Set(); + const components: SemanticUnitId[][] = []; + + const visit = (unitId: SemanticUnitId): void => { + const index = nextIndex++; + indexByUnit.set(unitId, index); + lowLinkByUnit.set(unitId, index); + stack.push(unitId); + onStack.add(unitId); + + for (const targetId of adjacency.get(unitId)!) { + if (!indexByUnit.has(targetId)) { + visit(targetId); + lowLinkByUnit.set( + unitId, + Math.min( + lowLinkByUnit.get(unitId)!, + lowLinkByUnit.get(targetId)! + ) + ); + } else if (onStack.has(targetId)) { + lowLinkByUnit.set( + unitId, + Math.min( + lowLinkByUnit.get(unitId)!, + indexByUnit.get(targetId)! + ) + ); + } + } + + if (lowLinkByUnit.get(unitId) !== indexByUnit.get(unitId)) return; + const component: SemanticUnitId[] = []; + while (stack.length > 0) { + const member = stack.pop()!; + onStack.delete(member); + component.push(member); + if (member === unitId) break; + } + component.sort(compareStrings); + components.push(component); + }; + + for (const unitId of unitIds) { + if (!indexByUnit.has(unitId)) visit(unitId); + } + components.sort(compareComponents); + + const sccIdByUnit = new Map(); + const sccIds = components.map((_, index) => `scc_${index}`); + for (let index = 0; index < components.length; index++) { + for (const unitId of components[index]!) { + sccIdByUnit.set(unitId, sccIds[index]!); + } + } + + return Object.freeze( + components.map((component, index) => { + const id = sccIds[index]!; + const memberIds = new Set(component); + const incoming = new Set(); + const outgoing = new Set(); + let hasSelfEdge = false; + + for (const edge of edges) { + const sourceSccId = sccIdByUnit.get(edge.unitId)!; + const targetSccId = sccIdByUnit.get(edge.targetUnitId)!; + if ( + edge.unitId === edge.targetUnitId && + memberIds.has(edge.unitId) + ) { + hasSelfEdge = true; + } + if (sourceSccId === id && targetSccId !== id) { + outgoing.add(targetSccId); + } + if (targetSccId === id && sourceSccId !== id) { + incoming.add(sourceSccId); + } + } + + const unresolved = boundaries.some( + (boundary) => + memberIds.has(boundary.unitId) && + boundary.resolution.kind === "indirect-or-external" + ); + const reentrancyRelevant = boundaries.some( + (boundary) => + memberIds.has(boundary.unitId) && + boundary.observability.mayReenterRootGroup + ); + const isMutuallyRecursive = component.length > 1; + const isRecursive = isMutuallyRecursive || hasSelfEdge; + const hasUnresolvedRecursionRisk = unresolved; + const hasUnresolvedMutualRecursionRisk = + unresolved && unitIds.length > 1; + + return Object.freeze({ + id, + unitIds: Object.freeze(component.slice()), + incomingSccIds: Object.freeze( + [...incoming].sort(compareStrings) + ), + outgoingSccIds: Object.freeze( + [...outgoing].sort(compareStrings) + ), + isRecursive, + isMutuallyRecursive, + hasUnresolvedRecursionRisk, + hasUnresolvedMutualRecursionRisk, + reentrancyRelevant, + requiresInterproceduralFission: + isRecursive || reentrancyRelevant, + }); + }) + ); +} + +function summarizeSccs( + sccs: readonly CanonicalCallScc[] +): CanonicalCallGraphSummary { + return Object.freeze({ + hasProvenRecursion: sccs.some((scc) => scc.isRecursive), + hasProvenMutualRecursion: sccs.some( + (scc) => scc.isMutuallyRecursive + ), + hasUnresolvedRecursionRisk: sccs.some( + (scc) => scc.hasUnresolvedRecursionRisk + ), + hasUnresolvedMutualRecursionRisk: sccs.some( + (scc) => scc.hasUnresolvedMutualRecursionRisk + ), + reentrancyRelevantSccIds: Object.freeze( + sccs + .filter((scc) => scc.reentrancyRelevant) + .map((scc) => scc.id) + ), + interproceduralFissionSccIds: Object.freeze( + sccs + .filter((scc) => scc.requiresInterproceduralFission) + .map((scc) => scc.id) + ), + }); +} + +function compareSources( + left: CanonicalCallSource, + right: CanonicalCallSource +): number { + return ( + compareStrings(left.unitId, right.unitId) || + left.nodeId - right.nodeId + ); +} + +function compareEdges( + left: CanonicalDirectCallEdge, + right: CanonicalDirectCallEdge +): number { + return ( + compareSources(left, right) || + compareStrings(left.targetUnitId, right.targetUnitId) + ); +} + +function compareComponents( + left: readonly SemanticUnitId[], + right: readonly SemanticUnitId[] +): number { + for (let index = 0; index < Math.min(left.length, right.length); index++) { + const comparison = compareStrings(left[index]!, right[index]!); + if (comparison !== 0) return comparison; + } + return left.length - right.length; +} + +function compareStrings(left: string, right: string): number { + return left < right ? -1 : left > right ? 1 : 0; +} diff --git a/packages/ruam/src/compiler/index.ts b/packages/ruam/src/compiler/index.ts index 3bd5bf7..9938f49 100644 --- a/packages/ruam/src/compiler/index.ts +++ b/packages/ruam/src/compiler/index.ts @@ -39,6 +39,21 @@ import type { } from "./ir.js"; import type { RootGroupId } from "./types.js"; +export { buildCanonicalCallGraphInventory } from "./call-graph.js"; +export type { + CanonicalBoundaryKind, + CanonicalBoundaryObservability, + CanonicalCallBoundary, + CanonicalCallGraphInventory, + CanonicalCallGraphSummary, + CanonicalCallResolution, + CanonicalCallScc, + CanonicalCallSource, + CanonicalCallTargetPolicy, + CanonicalClosureSite, + CanonicalDirectCallEdge, + CanonicalUnresolvedCallReason, +} from "./call-graph.js"; export { buildEffectRegionGraph, validateEffectRegionGraph, @@ -67,6 +82,17 @@ export type { PureRegionOutputBinding, PureRegionValueDomain, } from "./pure-region-lowering.js"; +export { planPureRegionCandidates } from "./pure-region-planning.js"; +export type { + PlannedPureRegionCandidate, + PureRegionCandidatePlan, + PureRegionCandidateRejection, + PureRegionCandidateRejectionCode, + PureRegionEntryAssumptionMap, + PureRegionEntryAssumptionProvider, + PureRegionEntryAssumptionSource, + PureRegionLoweringRejectionCode, +} from "./pure-region-planning.js"; // --------------------------------------------------------------------------- // Scope-object elision diff --git a/packages/ruam/src/compiler/pure-region-planning.ts b/packages/ruam/src/compiler/pure-region-planning.ts new file mode 100644 index 0000000..c6bc55b --- /dev/null +++ b/packages/ruam/src/compiler/pure-region-planning.ts @@ -0,0 +1,253 @@ +/** + * Deterministic compiler-side candidate planning for bounded pure regions. + * + * The planner does not infer JavaScript value types or duplicate the lowering + * proof. Every eligible entry receives explicit assumptions from its caller, + * and every candidate is accepted only by lowerEffectRegionsToPureContract. + * + * @module compiler/pure-region-planning + */ + +import type { SemanticUnit } from "./ir.js"; +import { + lowerEffectRegionsToPureContract, + type LoweredPureRegionContract, + type PureRegionInputAssumption, +} from "./pure-region-lowering.js"; +import type { + EffectRegion, + EffectRegionGraph, + EffectRegionId, +} from "./regions.js"; + +const LOWERING_REJECTION_CODES = Object.freeze([ + "RUAM_PURE_REGION_CONTROL_JOIN", + "RUAM_PURE_REGION_CYCLIC_SELECTION", + "RUAM_PURE_REGION_DUPLICATE_ASSUMPTION", + "RUAM_PURE_REGION_DYNAMIC_STACK", + "RUAM_PURE_REGION_EXCEPTION_ENTRY", + "RUAM_PURE_REGION_EXCEPTION_PATH_SELECTED", + "RUAM_PURE_REGION_INPUT_TYPE_REQUIRED", + "RUAM_PURE_REGION_INVALID_BINDING", + "RUAM_PURE_REGION_NEGATIVE_ZERO_RISK", + "RUAM_PURE_REGION_NONLINEAR_SELECTION", + "RUAM_PURE_REGION_NUMERIC_DOMAIN_OVERFLOW", + "RUAM_PURE_REGION_REQUIRES_BRAIDABLE_STEPS", + "RUAM_PURE_REGION_REQUIRES_FALLTHROUGH", + "RUAM_PURE_REGION_REQUIRES_INPUT", + "RUAM_PURE_REGION_REQUIRES_OUTPUT", + "RUAM_PURE_REGION_TYPE_MISMATCH", + "RUAM_PURE_REGION_UNINITIALIZED_READ", + "RUAM_PURE_REGION_UNREPRESENTABLE_CONTROL", + "RUAM_PURE_REGION_UNREPRESENTABLE_STATE_OUTPUT", + "RUAM_PURE_REGION_UNSAFE_NUMBER_LITERAL", + "RUAM_PURE_REGION_UNSUPPORTED_OP", + "RUAM_PURE_REGION_UNUSED_ASSUMPTION", +] as const); + +const LOWERING_REJECTION_CODE_SET: ReadonlySet = new Set( + LOWERING_REJECTION_CODES +); +const NO_ASSUMPTIONS_CODE = + "RUAM_PURE_REGION_PLAN_ASSUMPTIONS_UNAVAILABLE" as const; + +export type PureRegionLoweringRejectionCode = + (typeof LOWERING_REJECTION_CODES)[number]; +export type PureRegionCandidateRejectionCode = + | PureRegionLoweringRejectionCode + | typeof NO_ASSUMPTIONS_CODE; + +/** + * Called at most once for each unconsumed graph-order entry considered by the + * planner. Returning undefined makes the entry ineligible; an empty array is + * an explicit assumption set and is still submitted to the sound lowerer. + */ +export type PureRegionEntryAssumptionProvider = ( + entryRegion: EffectRegion, + entryRegionIndex: number +) => readonly PureRegionInputAssumption[] | undefined; + +/** Assumptions keyed by the region at which their guards would be installed. */ +export type PureRegionEntryAssumptionMap = ReadonlyMap< + EffectRegionId, + readonly PureRegionInputAssumption[] +>; + +export type PureRegionEntryAssumptionSource = + | PureRegionEntryAssumptionMap + | PureRegionEntryAssumptionProvider; + +export interface PlannedPureRegionCandidate { + entryRegionId: EffectRegionId; + /** Inclusive index in EffectRegionGraph.regions. */ + startRegionIndex: number; + /** Inclusive index in EffectRegionGraph.regions. */ + endRegionIndex: number; + lowered: LoweredPureRegionContract; +} + +/** + * One deterministic failed lowering attempt. Diagnostics remain compiler-only + * and may retain region identities; generated BPRF artifacts do not. + */ +export interface PureRegionCandidateRejection { + entryRegionId: EffectRegionId; + startRegionIndex: number; + /** Inclusive attempted end, or the start for an untyped entry. */ + endRegionIndex: number; + regionIds: readonly EffectRegionId[]; + code: PureRegionCandidateRejectionCode; + detail: string | null; +} + +export interface PureRegionCandidatePlan { + unitId: string; + candidates: readonly PlannedPureRegionCandidate[]; + rejections: readonly PureRegionCandidateRejection[]; +} + +/** + * Discover disjoint maximal lowerable spans in canonical graph order. + * + * At each unconsumed entry with explicit assumptions, ends are attempted from + * longest to shortest. The first success is therefore maximal for that entry + * under precisely those guards. Its complete span is consumed before planning + * resumes, which makes candidates disjoint and gives deterministic left-to- + * right precedence. + * + * Only recognized proof failures become diagnostics. Malformed canonical + * inputs, lowerer invariant failures, provider exceptions, and all unexpected + * errors propagate instead of being mislabeled as ordinary ineligibility. + */ +export function planPureRegionCandidates( + unit: SemanticUnit, + graph: EffectRegionGraph, + assumptionSource: PureRegionEntryAssumptionSource +): PureRegionCandidatePlan { + if (graph.unitId !== unit.id) { + throw new Error( + `RUAM_PURE_REGION_PLAN_UNIT_MISMATCH: ${String(graph.unitId)}:${unit.id}` + ); + } + + const candidates: PlannedPureRegionCandidate[] = []; + const rejections: PureRegionCandidateRejection[] = []; + let startRegionIndex = 0; + + while (startRegionIndex < graph.regions.length) { + const entryRegion = graph.regions[startRegionIndex]!; + const assumptions = resolveAssumptions( + assumptionSource, + entryRegion, + startRegionIndex + ); + + if (assumptions === undefined) { + rejections.push( + freezeRejection({ + entryRegionId: entryRegion.id, + startRegionIndex, + endRegionIndex: startRegionIndex, + regionIds: Object.freeze([entryRegion.id]), + code: NO_ASSUMPTIONS_CODE, + detail: null, + }) + ); + startRegionIndex++; + continue; + } + + let accepted: PlannedPureRegionCandidate | undefined; + for ( + let endRegionIndex = graph.regions.length - 1; + endRegionIndex >= startRegionIndex; + endRegionIndex-- + ) { + const regionIds = Object.freeze( + graph.regions + .slice(startRegionIndex, endRegionIndex + 1) + .map((region) => region.id) + ); + try { + const lowered = lowerEffectRegionsToPureContract(unit, graph, { + regionIds, + assumptions, + }); + accepted = Object.freeze({ + entryRegionId: entryRegion.id, + startRegionIndex, + endRegionIndex, + lowered, + }); + break; + } catch (error) { + const rejection = classifyOrdinaryRejection(error); + if (!rejection) throw error; + rejections.push( + freezeRejection({ + entryRegionId: entryRegion.id, + startRegionIndex, + endRegionIndex, + regionIds, + code: rejection.code, + detail: rejection.detail, + }) + ); + } + } + + if (accepted) { + candidates.push(accepted); + startRegionIndex = accepted.endRegionIndex + 1; + } else { + startRegionIndex++; + } + } + + return Object.freeze({ + unitId: unit.id, + candidates: Object.freeze(candidates), + rejections: Object.freeze(rejections), + }); +} + +function resolveAssumptions( + source: PureRegionEntryAssumptionSource, + entryRegion: EffectRegion, + entryRegionIndex: number +): readonly PureRegionInputAssumption[] | undefined { + const assumptions = + typeof source === "function" + ? source(entryRegion, entryRegionIndex) + : source.get(entryRegion.id); + if (assumptions === undefined) return undefined; + + return Object.freeze( + assumptions.map((assumption) => + Object.freeze({ + binding: Object.freeze({ ...assumption.binding }), + domain: Object.freeze({ ...assumption.domain }), + }) + ) + ); +} + +function classifyOrdinaryRejection( + error: unknown +): { code: PureRegionLoweringRejectionCode; detail: string | null } | null { + if (!(error instanceof Error)) return null; + const match = /^(RUAM_PURE_REGION_[A-Z_]+)(?:: (.*))?$/.exec( + error.message + ); + if (!match || !LOWERING_REJECTION_CODE_SET.has(match[1]!)) return null; + return { + code: match[1] as PureRegionLoweringRejectionCode, + detail: match[2] ?? null, + }; +} + +function freezeRejection( + rejection: PureRegionCandidateRejection +): PureRegionCandidateRejection { + return Object.freeze(rejection); +} diff --git a/packages/ruam/test/isogloss/call-graph.test.ts b/packages/ruam/test/isogloss/call-graph.test.ts new file mode 100644 index 0000000..777dbe4 --- /dev/null +++ b/packages/ruam/test/isogloss/call-graph.test.ts @@ -0,0 +1,316 @@ +import { describe, expect, it } from "bun:test"; +import { parse } from "@babel/parser"; +import type { NodePath } from "@babel/traverse"; +import type * as t from "@babel/types"; +import { traverse } from "../../src/babel-compat.js"; +import { + buildCanonicalCallGraphInventory, + compileSemanticFunction, + resetUnitCounter, +} from "../../src/compiler/index.js"; +import { + createSemanticInstruction, + type SemanticRootGroup, + type SemanticUnit, +} from "../../src/compiler/ir.js"; +import { Op } from "../../src/compiler/opcodes.js"; +import type { ConstantPoolEntry } from "../../src/compiler/types.js"; + +function getFunctionPath( + source: string, + name: string +): NodePath { + const ast = parse(source, { + sourceType: "script", + sourceFilename: "call-graph-fixture.js", + }); + let result: NodePath | undefined; + traverse(ast, { + Function(path) { + const id = "id" in path.node ? path.node.id : null; + if (id?.type === "Identifier" && id.name === name) { + result = path as NodePath; + path.stop(); + } + }, + }); + if (!result) throw new Error(`Missing function ${name}`); + return result; +} + +function makeUnit( + id: string, + rootGroupId: string, + instructions: readonly { opcode: Op; operand: number }[], + options: { + constants?: ConstantPoolEntry[]; + childUnitIds?: string[]; + } = {} +): SemanticUnit { + const nodes = instructions.map((instruction, index) => + createSemanticInstruction({ + id: index, + op: instruction.opcode, + operand: instruction.operand, + originId: 0, + }) + ); + return { + id, + rootGroupId, + constants: options.constants ?? [], + nodes, + exits: new Map(nodes.map((node) => [node.id, []])), + entryNode: 0, + origins: [ + { + file: "manual-call-graph-fixture.js", + start: 10, + end: 20, + line: 2, + column: 3, + }, + ], + childUnitIds: options.childUnitIds ?? [], + paramCount: 0, + registerCount: 0, + slotCount: 0, + isStrict: false, + isGenerator: false, + isAsync: false, + isArrow: false, + scopeless: true, + usesExceptions: false, + usesThisContext: false, + nameConstIndex: -1, + outerNames: [], + }; +} + +function makeGroup( + id: string, + entryUnitId: string, + units: SemanticUnit[] +): SemanticRootGroup { + return { + id, + entryUnitId, + units, + usedSemantics: new Set( + units.flatMap((unit) => unit.nodes.map((node) => node.op)) + ), + hasAsync: units.some((unit) => unit.isAsync), + hasGenerator: units.some((unit) => unit.isGenerator), + }; +} + +describe("canonical root-group call graph inventory", () => { + it("keeps proven closure identity separate from an unproven call target", () => { + const source = ` + function outer() { + return (function child(value) { return value; })(7); + } + `; + resetUnitCounter(111); + const group = compileSemanticFunction( + getFunctionPath(source, "outer"), + "call-graph-root" + ); + const root = group.units.find( + (unit) => unit.id === group.entryUnitId + )!; + const child = group.units.find( + (unit) => unit.id !== group.entryUnitId + )!; + const closureNode = root.nodes.find( + (node) => node.op === Op.NEW_CLOSURE + )!; + const callNode = root.nodes.find((node) => node.op === Op.CALL)!; + + // Both happen to be zero in this fixture: one is a constant-pool index, + // the other is argc. Numeric equality is not target evidence. + expect(closureNode.operand).toBe(callNode.operand); + + const inventory = buildCanonicalCallGraphInventory(group); + const invoke = inventory.boundaries.find( + (boundary) => boundary.nodeId === callNode.id + )!; + + expect(inventory.targetPolicy).toEqual({ + mode: "canonical-evidence-only", + directTargetRepresentation: "absent-from-current-ir", + unprovenBoundaryClassification: "indirect-or-external", + }); + expect(inventory.closureSites).toEqual([ + expect.objectContaining({ + unitId: root.id, + nodeId: closureNode.id, + targetUnitId: child.id, + evidence: "unit-ref-constant-and-child-membership", + }), + ]); + expect(invoke).toEqual( + expect.objectContaining({ + unitId: root.id, + nodeId: callNode.id, + operand: 1, + operandKind: "argc", + resolution: { + kind: "indirect-or-external", + reason: "callee-identity-not-represented", + }, + }) + ); + expect(invoke.origin.file).toBe("call-graph-fixture.js"); + expect(inventory.directEdges).toEqual([]); + expect(inventory.summary.hasProvenRecursion).toBe(false); + expect(inventory.summary.hasUnresolvedRecursionRisk).toBe(true); + expect(inventory.summary.hasUnresolvedMutualRecursionRisk).toBe(true); + }); + + it("reports source and observability for invoke, construct, dynamic, and reflection boundaries", () => { + const unit = makeUnit("entry", "boundary-root", [ + { opcode: Op.CALL_METHOD, operand: 2 }, + { opcode: Op.CALL_NEW, operand: 1 }, + { opcode: Op.DIRECT_EVAL, operand: 1 }, + { opcode: Op.DYNAMIC_IMPORT, operand: 0 }, + { opcode: Op.GET_PROP_DYNAMIC, operand: 0 }, + { opcode: Op.ADD, operand: 0 }, + { opcode: Op.RETURN_VOID, operand: 0 }, + ]); + const inventory = buildCanonicalCallGraphInventory( + makeGroup("boundary-root", unit.id, [unit]) + ); + + expect( + inventory.boundaries.map((boundary) => ({ + nodeId: boundary.nodeId, + kind: boundary.boundaryKind, + reason: + boundary.resolution.kind === "indirect-or-external" + ? boundary.resolution.reason + : null, + })) + ).toEqual([ + { + nodeId: 0, + kind: "invoke", + reason: "callee-identity-not-represented", + }, + { + nodeId: 1, + kind: "construct", + reason: "constructor-identity-not-represented", + }, + { + nodeId: 2, + kind: "dynamic-code", + reason: "runtime-generated-code", + }, + { + nodeId: 3, + kind: "dynamic-module", + reason: "runtime-module-resolution", + }, + { + nodeId: 4, + kind: "reflection", + reason: "runtime-hook-dispatch", + }, + { + nodeId: 5, + kind: "reflection", + reason: "runtime-hook-dispatch", + }, + ]); + expect( + inventory.boundaries.every( + (boundary) => + boundary.originId === 0 && + boundary.origin.line === 2 && + boundary.observability.mayExecuteArbitraryUserCode && + boundary.observability.mayReadOrWriteProgramState && + boundary.observability.mayReenterRootGroup + ) + ).toBe(true); + expect(inventory.boundaries[3]!.observability.maySuspend).toBe(true); + expect(inventory.sccs).toEqual([ + expect.objectContaining({ + id: "scc_0", + unitIds: ["entry"], + isRecursive: false, + isMutuallyRecursive: false, + hasUnresolvedRecursionRisk: true, + hasUnresolvedMutualRecursionRisk: false, + reentrancyRelevant: true, + requiresInterproceduralFission: true, + }), + ]); + expect(inventory.summary.reentrancyRelevantSccIds).toEqual(["scc_0"]); + expect(inventory.summary.interproceduralFissionSccIds).toEqual([ + "scc_0", + ]); + }); + + it("does not claim direct recursion when canonical calls lack target facts", () => { + const source = ` + function outer() { + function recur() { return recur(); } + return recur(); + } + `; + resetUnitCounter(222); + const group = compileSemanticFunction( + getFunctionPath(source, "outer"), + "recursive-root" + ); + const first = buildCanonicalCallGraphInventory(group); + const reordered = buildCanonicalCallGraphInventory({ + ...group, + units: [...group.units].reverse(), + }); + + expect(reordered).toEqual(first); + expect(first.unitIds).toEqual([...first.unitIds].sort()); + expect( + first.boundaries.filter( + (boundary) => boundary.observability.callKind === "invoke" + ) + ).toHaveLength(2); + expect(first.directEdges).toEqual([]); + expect(first.sccs).toHaveLength(2); + expect(first.sccs.every((scc) => !scc.isRecursive)).toBe(true); + expect(first.summary).toEqual( + expect.objectContaining({ + hasProvenRecursion: false, + hasProvenMutualRecursion: false, + hasUnresolvedRecursionRisk: true, + hasUnresolvedMutualRecursionRisk: true, + }) + ); + }); + + it("fails closed on malformed nested unit identity", () => { + const child = makeUnit("child", "malformed-root", [ + { opcode: Op.RETURN_VOID, operand: 0 }, + ]); + const root = makeUnit( + "root", + "malformed-root", + [ + { opcode: Op.NEW_CLOSURE, operand: 0 }, + { opcode: Op.RETURN, operand: 0 }, + ], + { + constants: [{ type: "number", value: 123 }], + childUnitIds: [child.id], + } + ); + + expect(() => + buildCanonicalCallGraphInventory( + makeGroup("malformed-root", root.id, [root, child]) + ) + ).toThrow("RUAM_INVALID_CANONICAL_UNIT_REF"); + }); +}); diff --git a/packages/ruam/test/isogloss/pure-region-planning.test.ts b/packages/ruam/test/isogloss/pure-region-planning.test.ts new file mode 100644 index 0000000..e9a7882 --- /dev/null +++ b/packages/ruam/test/isogloss/pure-region-planning.test.ts @@ -0,0 +1,476 @@ +import { describe, expect, it } from "bun:test"; +import { parse } from "@babel/parser"; +import type { NodePath } from "@babel/traverse"; +import type * as t from "@babel/types"; +import { traverse } from "../../src/babel-compat.js"; +import { + buildEffectRegionGraph, + compileSemanticFunction, + planPureRegionCandidates, + resetUnitCounter, + type EffectRegionGraph, + type PureRegionEntryAssumptionMap, + type PureRegionInputAssumption, +} from "../../src/compiler/index.js"; +import type { SemanticUnit } from "../../src/compiler/ir.js"; +import { SemanticOp } from "../../src/compiler/semantic-ops.js"; +import { generateBprfArtifact } from "../../src/isogloss/bprf/index.js"; +import { evaluateBprfReference } from "../../src/isogloss/bprf/testing-reference.js"; + +interface CompiledFixture { + unit: SemanticUnit; + graph: EffectRegionGraph; +} + +function getFunctionPath( + source: string, + name: string +): NodePath { + const ast = parse(source, { + sourceType: "script", + sourceFilename: "pure-region-planning-fixture.js", + }); + let result: NodePath | undefined; + traverse(ast, { + Function(path) { + const id = "id" in path.node ? path.node.id : null; + if (id?.type === "Identifier" && id.name === name) { + result = path as NodePath; + path.stop(); + } + }, + }); + if (!result) throw new Error(`Missing function ${name}`); + return result; +} + +function compileFixture(source: string, name: string): CompiledFixture { + resetUnitCounter(919); + const group = compileSemanticFunction( + getFunctionPath(source, name), + `plan-${name}` + ); + const unit = group.units.find( + (candidate) => candidate.id === group.entryUnitId + )!; + return { unit, graph: buildEffectRegionGraph(unit) }; +} + +function regionIndexWithOp( + fixture: CompiledFixture, + op: SemanticOp +): number { + const index = fixture.graph.regions.findIndex((region) => + region.nodeIds.some( + (nodeId) => + fixture.unit.nodes.find((node) => node.id === nodeId)!.op === op + ) + ); + if (index < 0) throw new Error(`Missing semantic op ${op}`); + return index; +} + +function numberAssumption( + kind: "stack" | "argument" | "register" | "slot", + index: number, + min: number, + max: number +): PureRegionInputAssumption { + return { + binding: { kind, index }, + domain: { type: "number", min, max }, + }; +} + +function booleanArgument(index: number): PureRegionInputAssumption { + return { + binding: { kind: "argument", index }, + domain: { type: "boolean" }, + }; +} + +function compileMixedFixture(): CompiledFixture { + return compileFixture( + `function mixed(a, b) { + let x = a * 3; + x = x + 1; + x = x / b; + x = x - 2; + x = x * 2; + return x; + }`, + "mixed" + ); +} + +function mixedAssumptions( + fixture: CompiledFixture +): PureRegionEntryAssumptionMap { + const division = regionIndexWithOp(fixture, SemanticOp.DIV); + return new Map([ + [ + fixture.graph.regions[0]!.id, + [ + numberAssumption("argument", 0, 1, 20), + numberAssumption("argument", 1, 1, 20), + ], + ], + [ + fixture.graph.regions[division]!.id, + [ + numberAssumption("stack", 0, 1, 20), + numberAssumption("stack", 1, 1, 20), + ], + ], + [ + fixture.graph.regions[division + 1]!.id, + [numberAssumption("stack", 0, 1, 20)], + ], + ]); +} + +describe("automatic pure-region candidate planning", () => { + it("plans deterministic disjoint maximal spans in graph order", () => { + const fixture = compileMixedFixture(); + const assumptions = mixedAssumptions(fixture); + const division = regionIndexWithOp(fixture, SemanticOp.DIV); + const first = planPureRegionCandidates( + fixture.unit, + fixture.graph, + assumptions + ); + const second = planPureRegionCandidates( + fixture.unit, + fixture.graph, + assumptions + ); + + expect(JSON.stringify(second)).toBe(JSON.stringify(first)); + expect( + first.candidates.map((candidate) => [ + candidate.startRegionIndex, + candidate.endRegionIndex, + ]) + ).toEqual([ + [0, division - 1], + [division + 1, fixture.graph.regions.length - 2], + ]); + for (const candidate of first.candidates) { + expect(candidate.lowered.regionIds).toEqual( + fixture.graph.regions + .slice( + candidate.startRegionIndex, + candidate.endRegionIndex + 1 + ) + .map((region) => region.id) + ); + } + + const selectedIds = first.candidates.flatMap((candidate) => + Array.from(candidate.lowered.regionIds) + ); + expect(new Set(selectedIds).size).toBe(selectedIds.length); + expect(selectedIds).not.toContain( + fixture.graph.regions[division]!.id + ); + expect(selectedIds).not.toContain( + fixture.graph.regions.at(-1)!.id + ); + expect( + first.rejections.some( + (rejection) => + rejection.startRegionIndex === 0 && + rejection.endRegionIndex >= division && + rejection.code === "RUAM_PURE_REGION_UNSUPPORTED_OP" + ) + ).toBe(true); + expect( + first.rejections.some( + (rejection) => + rejection.startRegionIndex === division && + rejection.endRegionIndex === division && + rejection.code === "RUAM_PURE_REGION_UNSUPPORTED_OP" && + rejection.detail?.includes("DIV") + ) + ).toBe(true); + expect(first.rejections.at(-1)).toEqual({ + entryRegionId: fixture.graph.regions.at(-1)!.id, + startRegionIndex: fixture.graph.regions.length - 1, + endRegionIndex: fixture.graph.regions.length - 1, + regionIds: [fixture.graph.regions.at(-1)!.id], + code: "RUAM_PURE_REGION_PLAN_ASSUMPTIONS_UNAVAILABLE", + detail: null, + }); + }); + + it("uses a provider once per unconsumed entry without changing the plan", () => { + const fixture = compileMixedFixture(); + const assumptions = mixedAssumptions(fixture); + const fromMap = planPureRegionCandidates( + fixture.unit, + fixture.graph, + assumptions + ); + const calls: Array<[string, number]> = []; + const fromProvider = planPureRegionCandidates( + fixture.unit, + fixture.graph, + (region, index) => { + calls.push([region.id, index]); + return assumptions.get(region.id); + } + ); + + expect(JSON.stringify(fromProvider)).toBe(JSON.stringify(fromMap)); + expect(calls).toEqual([ + [fixture.graph.regions[0]!.id, 0], + [ + fixture.graph.regions[ + regionIndexWithOp(fixture, SemanticOp.DIV) + ]!.id, + regionIndexWithOp(fixture, SemanticOp.DIV), + ], + [ + fixture.graph.regions[ + regionIndexWithOp(fixture, SemanticOp.DIV) + 1 + ]!.id, + regionIndexWithOp(fixture, SemanticOp.DIV) + 1, + ], + [ + fixture.graph.regions.at(-1)!.id, + fixture.graph.regions.length - 1, + ], + ]); + }); + + it("is exactly equivalent through every planned BPRF realization", () => { + const fixture = compileMixedFixture(); + const plan = planPureRegionCandidates( + fixture.unit, + fixture.graph, + mixedAssumptions(fixture) + ); + const [beforeDivision, afterDivision] = plan.candidates; + expect(beforeDivision).toBeDefined(); + expect(afterDivision).toBeDefined(); + + for (const seed of [0, 1, 0x5eed1234, 0xffffffff]) { + const beforeArtifact = generateBprfArtifact( + beforeDivision!.lowered.contract, + { seed, realizationCount: 4, fragmentCount: 3 } + ); + const afterArtifact = generateBprfArtifact( + afterDivision!.lowered.contract, + { seed, realizationCount: 4, fragmentCount: 3 } + ); + const beforeReached = new Set(); + const afterReached = new Set(); + + for (let contextIndex = 0; contextIndex < 48; contextIndex++) { + const context = { + caller: `planner-caller-${contextIndex % 7}`, + epoch: Math.floor(contextIndex / 7), + lineage: contextIndex * 17, + }; + for (const a of [1, 7, 20]) { + for (const b of [1, 4, 20]) { + const result = evaluateBprfReference( + beforeArtifact, + [a, b], + context + ); + beforeReached.add(result.realization); + expect(result.outputs[0]).toBe(a * 3 + 1); + expect(result.outputs[1]).toBe(b); + } + const result = evaluateBprfReference( + afterArtifact, + [a], + context + ); + afterReached.add(result.realization); + expect(result.outputs[0]).toBe((a - 2) * 2); + } + } + + expect(beforeReached.size).toBe(beforeArtifact.realizations.length); + expect(afterReached.size).toBe(afterArtifact.realizations.length); + } + }); + + it("does not infer missing domains and records structured rejections", () => { + const fixture = compileMixedFixture(); + const plan = planPureRegionCandidates( + fixture.unit, + fixture.graph, + new Map() + ); + + expect(plan.candidates).toEqual([]); + expect(plan.rejections).toHaveLength(fixture.graph.regions.length); + expect( + plan.rejections.every( + (rejection) => + rejection.code === + "RUAM_PURE_REGION_PLAN_ASSUMPTIONS_UNAVAILABLE" && + rejection.detail === null && + rejection.regionIds.length === 1 + ) + ).toBe(true); + }); + + it("never plans across branch, call, suspension, or exception entries", () => { + const fixtures = [ + { + fixture: compileFixture( + `function branch(flag) { + let x = !flag; + x = !x; + return flag ? x : !x; + }`, + "branch" + ), + label: "branch", + domain: "boolean", + }, + { + fixture: compileFixture( + `function call(value) { + let x = !value; + x = !x; + Math.abs(1); + return x; + }`, + "call" + ), + label: "call", + domain: "boolean", + }, + { + fixture: compileFixture( + `async function suspend(value) { + let x = !value; + x = !x; + await x; + return x; + }`, + "suspend" + ), + label: "suspend", + domain: "boolean", + }, + { + fixture: compileFixture( + `function handled(value) { + try { + let x = value * 2; + x = x + 1; + return x; + } catch { + return 0; + } + }`, + "handled" + ), + label: "handled", + domain: "number", + }, + ]; + + for (const { fixture, label, domain } of fixtures) { + const assumptions = new Map([ + [ + fixture.graph.regions[0]!.id, + domain === "number" + ? [numberAssumption("argument", 0, 1, 20)] + : [booleanArgument(0)], + ], + ]); + const plan = planPureRegionCandidates( + fixture.unit, + fixture.graph, + assumptions + ); + if (label === "handled") { + expect(plan.candidates).toEqual([]); + expect( + plan.rejections.some( + (rejection) => + rejection.code === + "RUAM_PURE_REGION_EXCEPTION_ENTRY" || + rejection.code === + "RUAM_PURE_REGION_EXCEPTION_PATH_SELECTED" + ) + ).toBe(true); + } else if (plan.candidates.length === 0) { + throw new Error(`Expected a lowerable prefix for ${label}`); + } + const selected = new Set( + plan.candidates.flatMap((candidate) => + Array.from(candidate.lowered.regionIds) + ) + ); + + for (const region of fixture.graph.regions) { + if ( + region.exits.some((exit) => + [ + "branch-true", + "branch-false", + "call", + "yield", + "await", + "finally", + "return", + ].includes(exit.kind) + ) + ) { + expect(selected.has(region.id)).toBe(false); + } + for (const exit of region.exits) { + if ( + (exit.kind === "exception" || + exit.kind === "finally") && + "targetRegionId" in exit + ) { + expect(selected.has(exit.targetRegionId)).toBe(false); + } + } + } + } + }); + + it("propagates provider and canonical-input failures", () => { + const fixture = compileMixedFixture(); + const providerFailure = new Error("unexpected-provider-failure"); + expect(() => + planPureRegionCandidates( + fixture.unit, + fixture.graph, + () => { + throw providerFailure; + } + ) + ).toThrow(providerFailure); + + const malformedUnit: SemanticUnit = { + ...fixture.unit, + nodes: fixture.unit.nodes.slice(1), + }; + expect(() => + planPureRegionCandidates( + malformedUnit, + fixture.graph, + mixedAssumptions(fixture) + ) + ).toThrow("RUAM_PURE_REGION_UNKNOWN_NODE"); + + expect(() => + planPureRegionCandidates( + fixture.unit, + { ...fixture.graph, unitId: "wrong-unit" }, + mixedAssumptions(fixture) + ) + ).toThrow("RUAM_PURE_REGION_PLAN_UNIT_MISMATCH"); + }); +}); From 972abbdcc2eda0e72033c2d11829d0af75f8105c Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:40:56 -1000 Subject: [PATCH 27/54] docs: checkpoint masked custody qualification --- .../2026-07-24-bprf-csh-spike-report.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md b/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md index 81e9f12..be05841 100644 --- a/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md +++ b/docs/superpowers/baselines/2026-07-24-bprf-csh-spike-report.md @@ -219,18 +219,29 @@ keeping the final value outside the hostile client, or protecting a function whose intrinsic query complexity is high. None can be manufactured by a semantics-preserving client transform alone. +To remove transition-count and stage-position leakage from the remote +transcript, the owner/server planner now places real transitions, in order, +among mask-refreshing identity epochs in one of four fixed epoch buckets: +**4, 8, 16, or 32**. Every slot changes cover and representation mask and +returns the same signed chart shape. Different secret placements produce the +same client cover path and exact final output. The owner schedule and padding +count are reference/server-only and are kept unreachable from package entries. +Adversarial placement/stage classification is the next transcript gate. + ## Correctness and build status -At the last full-suite checkpoint, before the statefully masked and specialized -source additions: +At the current branch checkpoint: -- Isogloss-focused tests: **42 passed, 0 failed, 12,056 assertions**; -- full repository tests: **2,373 passed, 0 failed**; +- full repository tests: **2,421 passed, 0 failed, 28,998 assertions**; - TypeScript typecheck: passed; - package build: passed; and - reference custodian/evaluator identifiers are absent from built package output. +The same run measured the still-legacy VM control at **43.0× weighted average +runtime overhead** across its ten-workload performance suite. That is a +replacement ceiling, not an Isogloss result. + ## Explicit spike limitations These are blockers to schema freeze, not deferred documentation: From b74a0f508a26e3bd8ef24b0c870104f944534284 Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:43:11 -1000 Subject: [PATCH 28/54] feat: gate Isogloss deployment boundaries --- .../src/isogloss/deployment-eligibility.ts | 181 ++++++++++++++++++ .../isogloss/deployment-eligibility.test.ts | 135 +++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 packages/ruam/src/isogloss/deployment-eligibility.ts create mode 100644 packages/ruam/test/isogloss/deployment-eligibility.test.ts diff --git a/packages/ruam/src/isogloss/deployment-eligibility.ts b/packages/ruam/src/isogloss/deployment-eligibility.ts new file mode 100644 index 0000000..ebe3e4e --- /dev/null +++ b/packages/ruam/src/isogloss/deployment-eligibility.ts @@ -0,0 +1,181 @@ +/** + * Conservative deployment-profile eligibility for protected Isogloss regions. + * + * This is an owner/compiler gate. It prevents a remote custody profile from + * silently changing a synchronous API or adding a new suspension point to an + * async function. A remote relation is eligible only when it composes into an + * already-observable remote await contract. + * + * @module isogloss/deployment-eligibility + */ + +export type IsoglossDeploymentProfile = + | "holographic-local" + | "holographic-custodied" + | "holographic-private" + | "holographic-tee"; + +export type IsoglossCustodyBoundary = + | "none" + | "existing-remote-await" + | "in-process-attested"; + +export type IsoglossEligibilityBlocker = + | "NO_PROTECTED_REGION" + | "MISSING_EXACT_DOMAIN_PROOF" + | "REMOTE_BOUNDARY_WOULD_CHANGE_SCHEDULING" + | "MISSING_CUSTODIAN" + | "MISSING_PRIVATE_FUNCTION_PROTOCOL" + | "MISSING_ATTESTED_EXECUTION" + | "UNSUPPORTED_GENERATOR_CUSTODY" + | "LOCAL_FALLBACK_FORBIDDEN"; + +export interface IsoglossDeploymentEligibilityRequest { + readonly profile: IsoglossDeploymentProfile; + readonly protectedRegionCount: number; + readonly hasExactDomainProof: boolean; + readonly isGenerator: boolean; + readonly boundary: IsoglossCustodyBoundary; + readonly custodianAvailable?: boolean; + readonly privateFunctionProtocolAvailable?: boolean; + readonly attestedExecutionAvailable?: boolean; + /** + * Maximum profiles must not carry a complete local relation for outage or + * development fallback. + */ + readonly completeLocalFallbackPresent?: boolean; +} + +export interface IsoglossDeploymentEligibility { + readonly profile: IsoglossDeploymentProfile; + readonly eligible: boolean; + readonly blockers: readonly IsoglossEligibilityBlocker[]; + readonly clientCompleteness: + | "complete" + | "incomplete-under-custodian" + | "incomplete-under-private-protocol" + | "incomplete-under-attestation"; + readonly schedulingContract: + | "no-new-suspension" + | "existing-remote-await-only" + | "in-process-attested-call"; + readonly fallbackPolicy: + | "not-applicable" + | "no-complete-local-fallback"; + readonly securityClaim: + | "client-side-analysis-amplification-only" + | "client-internal-relation-incomplete" + | "client-and-custodian-input-separation" + | "hardware-isolation-dependent"; +} + +export function assessIsoglossDeploymentEligibility( + request: IsoglossDeploymentEligibilityRequest +): IsoglossDeploymentEligibility { + validateRequest(request); + const blockers: IsoglossEligibilityBlocker[] = []; + if (request.protectedRegionCount === 0) { + blockers.push("NO_PROTECTED_REGION"); + } + if (!request.hasExactDomainProof) { + blockers.push("MISSING_EXACT_DOMAIN_PROOF"); + } + + switch (request.profile) { + case "holographic-local": + return result(request.profile, blockers, { + clientCompleteness: "complete", + schedulingContract: "no-new-suspension", + fallbackPolicy: "not-applicable", + securityClaim: "client-side-analysis-amplification-only", + }); + + case "holographic-custodied": + requireRemoteCustody(request, blockers); + if (!request.custodianAvailable) { + blockers.push("MISSING_CUSTODIAN"); + } + return result(request.profile, blockers, { + clientCompleteness: "incomplete-under-custodian", + schedulingContract: "existing-remote-await-only", + fallbackPolicy: "no-complete-local-fallback", + securityClaim: "client-internal-relation-incomplete", + }); + + case "holographic-private": + requireRemoteCustody(request, blockers); + if (!request.custodianAvailable) { + blockers.push("MISSING_CUSTODIAN"); + } + if (!request.privateFunctionProtocolAvailable) { + blockers.push("MISSING_PRIVATE_FUNCTION_PROTOCOL"); + } + return result(request.profile, blockers, { + clientCompleteness: "incomplete-under-private-protocol", + schedulingContract: "existing-remote-await-only", + fallbackPolicy: "no-complete-local-fallback", + securityClaim: "client-and-custodian-input-separation", + }); + + case "holographic-tee": + if ( + request.boundary !== "in-process-attested" || + !request.attestedExecutionAvailable + ) { + blockers.push("MISSING_ATTESTED_EXECUTION"); + } + if (request.completeLocalFallbackPresent) { + blockers.push("LOCAL_FALLBACK_FORBIDDEN"); + } + return result(request.profile, blockers, { + clientCompleteness: "incomplete-under-attestation", + schedulingContract: "in-process-attested-call", + fallbackPolicy: "no-complete-local-fallback", + securityClaim: "hardware-isolation-dependent", + }); + } +} + +function requireRemoteCustody( + request: IsoglossDeploymentEligibilityRequest, + blockers: IsoglossEligibilityBlocker[] +): void { + if (request.isGenerator) { + blockers.push("UNSUPPORTED_GENERATOR_CUSTODY"); + } + if (request.boundary !== "existing-remote-await") { + blockers.push("REMOTE_BOUNDARY_WOULD_CHANGE_SCHEDULING"); + } + if (request.completeLocalFallbackPresent) { + blockers.push("LOCAL_FALLBACK_FORBIDDEN"); + } +} + +function result( + profile: IsoglossDeploymentProfile, + blockers: IsoglossEligibilityBlocker[], + claims: Omit< + IsoglossDeploymentEligibility, + "profile" | "eligible" | "blockers" + > +): IsoglossDeploymentEligibility { + return Object.freeze({ + profile, + eligible: blockers.length === 0, + blockers: Object.freeze(blockers), + ...claims, + }); +} + +function validateRequest( + request: IsoglossDeploymentEligibilityRequest +): void { + if ( + !Number.isSafeInteger(request.protectedRegionCount) || + request.protectedRegionCount < 0 + ) { + throw new Error( + "RUAM_ISOGLOSS_ELIGIBILITY_INVALID_REGION_COUNT" + ); + } +} diff --git a/packages/ruam/test/isogloss/deployment-eligibility.test.ts b/packages/ruam/test/isogloss/deployment-eligibility.test.ts new file mode 100644 index 0000000..8776013 --- /dev/null +++ b/packages/ruam/test/isogloss/deployment-eligibility.test.ts @@ -0,0 +1,135 @@ +import { describe, expect, it } from "bun:test"; +import { + assessIsoglossDeploymentEligibility, + type IsoglossDeploymentEligibilityRequest, +} from "../../src/isogloss/deployment-eligibility.js"; + +const base: IsoglossDeploymentEligibilityRequest = { + profile: "holographic-local", + protectedRegionCount: 2, + hasExactDomainProof: true, + isGenerator: false, + boundary: "none", +}; + +describe("Isogloss deployment eligibility", () => { + it("keeps the local profile honest about client completeness", () => { + const result = assessIsoglossDeploymentEligibility(base); + + expect(result.eligible).toBe(true); + expect(result.clientCompleteness).toBe("complete"); + expect(result.schedulingContract).toBe("no-new-suspension"); + expect(result.securityClaim).toBe( + "client-side-analysis-amplification-only" + ); + }); + + it("rejects remote custody that would add a suspension boundary", () => { + const result = assessIsoglossDeploymentEligibility({ + ...base, + profile: "holographic-custodied", + custodianAvailable: true, + }); + + expect(result.eligible).toBe(false); + expect(result.blockers).toEqual([ + "REMOTE_BOUNDARY_WOULD_CHANGE_SCHEDULING", + ]); + expect(result.fallbackPolicy).toBe("no-complete-local-fallback"); + }); + + it("admits custody only at an existing remote-await contract", () => { + const result = assessIsoglossDeploymentEligibility({ + ...base, + profile: "holographic-custodied", + boundary: "existing-remote-await", + custodianAvailable: true, + }); + + expect(result.eligible).toBe(true); + expect(result.blockers).toEqual([]); + expect(result.clientCompleteness).toBe( + "incomplete-under-custodian" + ); + expect(result.schedulingContract).toBe( + "existing-remote-await-only" + ); + }); + + it("requires a real private-function protocol for private custody", () => { + const missing = assessIsoglossDeploymentEligibility({ + ...base, + profile: "holographic-private", + boundary: "existing-remote-await", + custodianAvailable: true, + }); + const available = assessIsoglossDeploymentEligibility({ + ...base, + profile: "holographic-private", + boundary: "existing-remote-await", + custodianAvailable: true, + privateFunctionProtocolAvailable: true, + }); + + expect(missing.eligible).toBe(false); + expect(missing.blockers).toEqual([ + "MISSING_PRIVATE_FUNCTION_PROTOCOL", + ]); + expect(available.eligible).toBe(true); + expect(available.securityClaim).toBe( + "client-and-custodian-input-separation" + ); + }); + + it("never hides missing proofs, generators, or local fallbacks", () => { + const result = assessIsoglossDeploymentEligibility({ + ...base, + profile: "holographic-custodied", + protectedRegionCount: 0, + hasExactDomainProof: false, + isGenerator: true, + boundary: "none", + custodianAvailable: false, + completeLocalFallbackPresent: true, + }); + + expect(result.eligible).toBe(false); + expect(result.blockers).toEqual([ + "NO_PROTECTED_REGION", + "MISSING_EXACT_DOMAIN_PROOF", + "UNSUPPORTED_GENERATOR_CUSTODY", + "REMOTE_BOUNDARY_WOULD_CHANGE_SCHEDULING", + "LOCAL_FALLBACK_FORBIDDEN", + "MISSING_CUSTODIAN", + ]); + }); + + it("requires an in-process attested boundary for the TEE profile", () => { + const missing = assessIsoglossDeploymentEligibility({ + ...base, + profile: "holographic-tee", + }); + const available = assessIsoglossDeploymentEligibility({ + ...base, + profile: "holographic-tee", + boundary: "in-process-attested", + attestedExecutionAvailable: true, + }); + + expect(missing.eligible).toBe(false); + expect(missing.blockers).toEqual(["MISSING_ATTESTED_EXECUTION"]); + expect(available.eligible).toBe(true); + expect(available.schedulingContract).toBe( + "in-process-attested-call" + ); + }); + + it("fails closed on invalid planner counts", () => { + expect(() => + assessIsoglossDeploymentEligibility({ + ...base, + protectedRegionCount: -1, + }) + ).toThrow("RUAM_ISOGLOSS_ELIGIBILITY_INVALID_REGION_COUNT"); + }); +}); From 8397371e4fcd99eaa04842789c4ab8f6e1f6f90c Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:51:55 -1000 Subject: [PATCH 29/54] fix: remove masked placement epoch bias --- .../src/isogloss/csh/padded-masked-plan.ts | 57 ++++++++++++++----- .../isogloss/csh-padded-masked-plan.test.ts | 15 ++++- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/packages/ruam/src/isogloss/csh/padded-masked-plan.ts b/packages/ruam/src/isogloss/csh/padded-masked-plan.ts index e7f1585..4f88e23 100644 --- a/packages/ruam/src/isogloss/csh/padded-masked-plan.ts +++ b/packages/ruam/src/isogloss/csh/padded-masked-plan.ts @@ -9,8 +9,8 @@ * @module isogloss/csh/padded-masked-plan */ +import { createHmac } from "node:crypto"; import { deriveSeed } from "../../naming/scope.js"; -import { createSeededRandom } from "../../random/entropy.js"; import { createChartCover, type ChartCover, @@ -26,7 +26,8 @@ export interface PaddedMaskedCustodyPlanOptions { readonly width: number; readonly bucketSize: (typeof MASKED_CUSTODY_TRANSCRIPT_BUCKETS)[number]; readonly coverSeed: number; - readonly placementSeed: number; + /** Server-only high-entropy key; never included in the client contract. */ + readonly placementSecret: string; } /** @@ -50,10 +51,8 @@ export function createPaddedMaskedCustodyPlan( const realTransitionSlots = selectOrderedSlots( options.bucketSize, options.realTransitions.length, - deriveSeed( - options.placementSeed >>> 0, - `${transcriptClassId}:placement` - ) + options.placementSecret, + transcriptClassId ); const realBySlot = new Map( realTransitionSlots.map((slot, index) => [ @@ -108,27 +107,59 @@ function validateOptions(options: PaddedMaskedCustodyPlanOptions): void { ) { throw new Error("RUAM_CSH_PADDED_PLAN_TRANSITION_COUNT"); } - for (const value of [options.coverSeed, options.placementSeed]) { - if (!Number.isSafeInteger(value)) { - throw new Error("RUAM_CSH_PADDED_PLAN_INVALID_SEED"); - } + if (!Number.isSafeInteger(options.coverSeed)) { + throw new Error("RUAM_CSH_PADDED_PLAN_INVALID_SEED"); + } + if (options.placementSecret.length < 16) { + throw new Error("RUAM_CSH_PADDED_PLAN_WEAK_PLACEMENT_SECRET"); } } function selectOrderedSlots( bucketSize: number, realCount: number, - seed: number + secret: string, + transcriptClassId: string ): number[] { - const random = createSeededRandom(seed); + const nextWord = createKeyedWordStream( + secret, + `${transcriptClassId}:real-count:${realCount}:placement` + ); const slots = Array.from({ length: bucketSize }, (_, index) => index); for (let index = slots.length - 1; index > 0; index--) { - const target = random.nextUint32() % (index + 1); + const target = uniformBelow(nextWord, index + 1); [slots[index], slots[target]] = [slots[target]!, slots[index]!]; } return slots.slice(0, realCount).sort((left, right) => left - right); } +function createKeyedWordStream( + secret: string, + domain: string +): () => number { + let counter = 0; + return () => + createHmac("sha256", secret) + .update(domain) + .update("|") + .update(String(counter++)) + .digest() + .readUInt32LE(0); +} + +function uniformBelow( + nextWord: () => number, + upperExclusive: number +): number { + const wordRange = 0x1_0000_0000; + const limit = + wordRange - (wordRange % upperExclusive); + for (;;) { + const word = nextWord(); + if (word < limit) return word % upperExclusive; + } +} + function createIdentityTransition( width: number ): HiddenMaskedTransition { diff --git a/packages/ruam/test/isogloss/csh-padded-masked-plan.test.ts b/packages/ruam/test/isogloss/csh-padded-masked-plan.test.ts index 2551f4b..5055704 100644 --- a/packages/ruam/test/isogloss/csh-padded-masked-plan.test.ts +++ b/packages/ruam/test/isogloss/csh-padded-masked-plan.test.ts @@ -159,7 +159,7 @@ function createPlan(placementSeed: number): PaddedMaskedCustodyPlan { width: WIDTH, bucketSize: 4, coverSeed: 4242, - placementSeed, + placementSecret: `test-placement-secret-${placementSeed}`, }); } @@ -250,7 +250,7 @@ describe("fixed-bucket statefully masked custody planning", () => { width: WIDTH, bucketSize: 3 as 4, coverSeed: 1, - placementSeed: 2, + placementSecret: "invalid-bucket-secret", }) ).toThrow("RUAM_CSH_PADDED_PLAN_INVALID_BUCKET"); expect(() => @@ -262,9 +262,18 @@ describe("fixed-bucket statefully masked custody planning", () => { width: WIDTH, bucketSize: 4, coverSeed: 1, - placementSeed: 2, + placementSecret: "too-many-stages-secret", }) ).toThrow("RUAM_CSH_PADDED_PLAN_TRANSITION_COUNT"); + expect(() => + createPaddedMaskedCustodyPlan({ + realTransitions, + width: WIDTH, + bucketSize: 4, + coverSeed: 1, + placementSecret: "weak", + }) + ).toThrow("RUAM_CSH_PADDED_PLAN_WEAK_PLACEMENT_SECRET"); }); }); From 9d0597f395b0b7228b388eb4b0c51a2caef60bfc Mon Sep 17 00:00:00 2001 From: owengregson Date: Sat, 25 Jul 2026 00:52:11 -1000 Subject: [PATCH 30/54] test: score padded custody transcripts --- .../padded-masked-custody-attacker.test.ts | 90 + .../support/padded-masked-custody-attacker.ts | 1500 +++++++++++++++++ 2 files changed, 1590 insertions(+) create mode 100644 packages/ruam/test/isogloss/padded-masked-custody-attacker.test.ts create mode 100644 packages/ruam/test/isogloss/support/padded-masked-custody-attacker.ts diff --git a/packages/ruam/test/isogloss/padded-masked-custody-attacker.test.ts b/packages/ruam/test/isogloss/padded-masked-custody-attacker.test.ts new file mode 100644 index 0000000..23be39c --- /dev/null +++ b/packages/ruam/test/isogloss/padded-masked-custody-attacker.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, it } from "bun:test"; +import { buildPaddedMaskedCustodyAttackerReport } from "./support/padded-masked-custody-attacker.js"; + +describe("fixed-bucket masked custody attacker gates", () => { + it( + "leaks the declared bucket but does not empirically classify corrected placement or stage count", + () => { + const report = buildPaddedMaskedCustodyAttackerReport(); + + expect(report.decision).toBe("supports-fixed-bucket-claim"); + expect( + report.acceptedBucketLeakage + .exactBucketClassificationAccuracy + ).toBe(1); + expect(report.structuralShape.placementShapeMatch).toBe(true); + expect(report.structuralShape.stageCountShapeMatch).toBe(true); + expect(report.structuralShape.perEpochShapeMatch).toBe(true); + + expect( + report.placementDistribution.preFixRealSlotCounts + ).toEqual([ + 3_093, 2_978, 3_101, 2_893, 2_627, 2_016, 2_495, 797, + ]); + expect( + report.placementDistribution + .preFixMaximumDeviationPercentagePoints + ).toBeGreaterThan(15); + expect(report.placementDistribution.preFixDecision).toBe( + "leakage-detected" + ); + expect( + report.placementDistribution + .preFixHeldOutBalancedAccuracy + ).toBeGreaterThan(0.69); + expect( + report.placementDistribution.correctedRealSlotCounts + ).toEqual([ + 2_462, 2_610, 2_517, 2_504, 2_512, 2_490, 2_434, + 2_471, + ]); + expect( + report.placementDistribution + .correctedMaximumDeviationPercentagePoints + ).toBeLessThan(2); + + expect(report.placementLeakage.finalOutputClassCount).toBe(1); + expect( + report.placementLeakage.heldOutBalancedAccuracy + ).toBeLessThanOrEqual( + report.placementLeakage.chanceBalancedAccuracy + 0.1 + ); + expect( + report.placementLeakage.payloadOnlyBalancedAccuracy + ).toBeLessThanOrEqual(0.6); + expect(report.stageCountLeakage.finalOutputClassCount).toBe(1); + expect( + report.stageCountLeakage.heldOutAccuracy + ).toBeLessThanOrEqual( + report.stageCountLeakage.chanceAccuracy + 0.1 + ); + + expect( + report.protocolSeparation.directForeignResponseAccepted + ).toBe(false); + expect( + report.protocolSeparation.fieldReboundResponseAccepted + ).toBe(false); + expect( + report.protocolSeparation + .protectedMaskedStateCoordinateMatch + ).toBeLessThan(1); + expect( + report.protocolSeparation + .signedContributionCoordinateMatch + ).toBeLessThan(1); + expect( + report.protocolSeparation + .stateSubstitutionAcceptedByNextServerBoundary + ).toBe(false); + + expect(report.overhead.transitionWorkMultiplier).toBe(4); + expect(report.overhead.fullTraceMultiplier).toBeGreaterThan(3); + expect(report.hooks.localizedHookFamilies).toBe(2); + expect(report.hooks.transitionHookEvents).toBe(8); + expect(report.failedClaims).toEqual([]); + expect(report.classifierCaveat).toContain("not a proof"); + }, + 15_000 + ); +}); diff --git a/packages/ruam/test/isogloss/support/padded-masked-custody-attacker.ts b/packages/ruam/test/isogloss/support/padded-masked-custody-attacker.ts new file mode 100644 index 0000000..1ac708b --- /dev/null +++ b/packages/ruam/test/isogloss/support/padded-masked-custody-attacker.ts @@ -0,0 +1,1500 @@ +import { createHash } from "node:crypto"; +import { deriveSeed } from "../../../src/naming/scope.js"; +import { createSeededRandom } from "../../../src/random/entropy.js"; +import type { + ChartCustodyRequest, + ChartCustodyResponse, +} from "../../../src/isogloss/csh/chart-custody-protocol.js"; +import type { CustodyRequest } from "../../../src/isogloss/csh/custody-protocol.js"; +import { + applyMaskedTransitionResponse, + createMaskedCustodyClientState, + openMaskedCustodyProjection, + prepareMaskedProjectionRequest, + prepareMaskedTransitionRequest, +} from "../../../src/isogloss/csh/masked-custody-protocol.js"; +import { + createPaddedMaskedCustodyPlan, + type PaddedMaskedCustodyPlan, +} from "../../../src/isogloss/csh/padded-masked-plan.js"; +import { + ReferenceMaskedChartCustodian, + type HiddenMaskedTransition, +} from "../../../src/isogloss/csh/reference-masked-custodian.js"; +import { + CSH_FIELD_MODULUS, + encodeIngressCharts, + evaluateCertifiedProjection, + transportAffineCharts, + type ChartCover, + type EncodedChart, +} from "../../../src/isogloss/csh/reference.js"; + +export const PADDED_MASKED_ATTACKER_REPORT_VERSION = 1 as const; + +export interface PaddedMaskedAttackerReport { + readonly schemaVersion: typeof PADDED_MASKED_ATTACKER_REPORT_VERSION; + readonly decision: "supports-fixed-bucket-claim" | "leakage-detected"; + readonly acceptedBucketLeakage: { + readonly width: number; + readonly epochBucket: number; + readonly exactBucketClassificationAccuracy: number; + readonly leakedBy: readonly string[]; + }; + readonly structuralShape: { + readonly placementShapeMatch: boolean; + readonly stageCountShapeMatch: boolean; + readonly perEpochShapeMatch: boolean; + readonly signatureValuesExcludedFromNumericClassifier: boolean; + }; + readonly placementDistribution: { + readonly sampleCount: number; + readonly expectedRealSlotRate: number; + readonly correctedRealSlotCounts: readonly number[]; + readonly correctedMaximumDeviationPercentagePoints: number; + readonly preFixRealSlotCounts: readonly number[]; + readonly preFixMaximumDeviationPercentagePoints: number; + readonly preFixHeldOutBalancedAccuracy: number; + readonly preFixDecision: "leakage-detected"; + readonly preFixLeakageCause: string; + }; + readonly placementLeakage: { + readonly realStageCount: number; + readonly trainingTranscripts: number; + readonly heldOutTranscripts: number; + readonly heldOutSlotSamples: number; + readonly chanceBalancedAccuracy: number; + readonly heldOutBalancedAccuracy: number; + readonly epochPriorBalancedAccuracy: number; + readonly payloadOnlyBalancedAccuracy: number; + readonly advantagePercentagePoints: number; + readonly trainingRealSlotRateByEpoch: readonly number[]; + readonly finalOutputClassCount: number; + }; + readonly stageCountLeakage: { + readonly candidateStageCounts: readonly number[]; + readonly trainingTranscripts: number; + readonly heldOutTranscripts: number; + readonly chanceAccuracy: number; + readonly heldOutAccuracy: number; + readonly advantagePercentagePoints: number; + readonly finalOutputClassCount: number; + }; + readonly protocolSeparation: { + readonly testedAtPaddingEpoch: number; + readonly sharedServiceSecrets: boolean; + readonly reusedNonceSequenceAcrossSessions: boolean; + readonly directForeignResponseAccepted: boolean; + readonly fieldReboundResponseAccepted: boolean; + readonly protectedMaskedStateCoordinateMatch: number; + readonly signedContributionCoordinateMatch: number; + readonly stateSubstitutionAcceptedByNextServerBoundary: boolean; + }; + readonly overhead: { + readonly width: number; + readonly chartCount: number; + readonly threshold: number; + readonly realStageCount: number; + readonly paddedEpochCount: number; + readonly paddingEpochCount: number; + readonly transitionWorkMultiplier: number; + readonly fullTraceFieldElements: number; + readonly unpaddedTraceFieldElements: number; + readonly fullTraceMultiplier: number; + readonly residentChartFieldElements: number; + readonly residentStorageMultiplierOverLogicalState: number; + }; + readonly hooks: { + readonly localizedHookFamilies: number; + readonly transitionHookEvents: number; + readonly finalOpeningHookEvents: number; + readonly remainingSurfaces: readonly string[]; + }; + readonly classifierCaveat: string; + readonly failedClaims: readonly string[]; +} + +const WIDTH = 3; +const BUCKET = 8 as const; +const CHART_COUNT = 5; +const THRESHOLD = 3; +const PLACEMENT_TRAIN_TRANSCRIPTS = 64; +const PLACEMENT_HELD_OUT_TRANSCRIPTS = 64; +const STAGE_TRAIN_PER_CLASS = 32; +const STAGE_HELD_OUT_PER_CLASS = 32; +const DISTRIBUTION_SAMPLES = 10_000; +const LEAKAGE_TOLERANCE = 0.1; +const INPUT = Object.freeze([2, 3, 5]); +const SHARED_SECRETS = Object.freeze({ + lineage: "padded-attacker-shared-lineage", + mask: "padded-attacker-shared-mask", + sharing: "padded-attacker-shared-sharing", + response: "padded-attacker-shared-response", +}); + +const IDENTITY_TRANSPORT = Object.freeze({ + id: "padded_attacker_identity_transport", + matrix: Object.freeze([ + Object.freeze([1, 0, 0]), + Object.freeze([0, 1, 0]), + Object.freeze([0, 0, 1]), + ]), + bias: Object.freeze([0, 0, 0]), +}); + +const PLACEMENT_TRANSITIONS: readonly HiddenMaskedTransition[] = + Object.freeze([ + createAffineTransition( + Object.freeze([ + Object.freeze([1, 1, 0]), + Object.freeze([0, 1, 0]), + Object.freeze([0, 0, 1]), + ]), + [3, 5, 7], + Object.freeze([ + Object.freeze({ + inputProjection: Object.freeze([1, 0, 1]), + outputDirection: Object.freeze([1, 2, 0]), + coefficient: 11, + }), + ]) + ), + createAffineTransition( + Object.freeze([ + Object.freeze([1, 0, 0]), + Object.freeze([1, 1, 0]), + Object.freeze([0, 0, 1]), + ]), + [13, 17, 19], + Object.freeze([ + Object.freeze({ + inputProjection: Object.freeze([0, 1, 1]), + outputDirection: Object.freeze([0, 1, 3]), + coefficient: 23, + }), + ]) + ), + ]); + +const EXIT_PROJECTION = Object.freeze({ + id: "padded_attacker_exit", + coefficients: Object.freeze([1, 2, 3]), + bias: 29, +}); + +interface CapturedEpoch { + readonly request: ChartCustodyRequest; + readonly response: ChartCustodyResponse; + readonly maskedBefore: readonly number[]; + readonly maskedAfter: readonly number[]; + readonly maskedDelta: readonly number[]; + readonly chartsAfter: readonly EncodedChart[]; +} + +interface CapturedPaddedTranscript { + readonly contract: ReferenceMaskedChartCustodian["clientContract"]; + readonly covers: readonly ChartCover[]; + readonly epochs: readonly CapturedEpoch[]; + readonly projectionRequest: CustodyRequest; + /** Scorer-only control value; never enters topology features. */ + readonly finalOutputForControl: number; + readonly realTransitionSlots: readonly number[]; + readonly realStageCount: number; + readonly structuralShape: string; +} + +interface LivePaddedSession { + readonly plan: PaddedMaskedCustodyPlan; + readonly custodian: ReferenceMaskedChartCustodian; + charts: readonly EncodedChart[]; + state: ReturnType; +} + +interface PreparedEpoch { + readonly request: ChartCustodyRequest; + readonly response: ChartCustodyResponse; + readonly localCharts: readonly EncodedChart[]; + readonly maskedBefore: readonly number[]; + readonly maskedDelta: readonly number[]; +} + +interface NumericSample