From 37de54b0e1069ad418c31c72bb8469e4ffac759b Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Fri, 24 Oct 2025 13:07:47 -0700 Subject: [PATCH 01/18] Clarify Neo4j usage in AGENTS guide --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index 1428310..0a73e36 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,6 +35,7 @@ Guidelines: - Prefix the message with `[Echo]` so the tag survives future searches. - Summarise intent, work done, and next steps for future agents. - Use the thread `echo-devlog` unless a more specific thread already exists. +- **Cadence:** Log when you start work (intent), when you hit a major milestone or decision, when you reference external sources (paths, specs), and when you finish a session (outcome + next steps). Treat Neo4j as the authoritative timeline. ### Reading Past Entries ```bash From 087ed175c5abc1f027e8a30efa428f39907a6a28 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Fri, 24 Oct 2025 13:35:13 -0700 Subject: [PATCH 02/18] Document RMG runtime loop and language division Added RMG runtime architecture doc describing graph-based tick loop, and clarified Rust/Lua/TypeScript responsibility map. Updated AGENTS Neo4j instructions to use generic paths. --- AGENTS.md | 8 +- Cargo.lock | 144 ++++++++++++++++++++++++++ docs/echo/rmg-runtime-architecture.md | 76 ++++++++++++++ docs/echo/rust-lua-ts-division.md | 76 ++++++++++++++ 4 files changed, 300 insertions(+), 4 deletions(-) create mode 100644 Cargo.lock create mode 100644 docs/echo/rmg-runtime-architecture.md create mode 100644 docs/echo/rust-lua-ts-division.md diff --git a/AGENTS.md b/AGENTS.md index 0a73e36..e227497 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,17 +11,17 @@ Welcome to the **Echo** project. This file captures expectations for any LLM age ## Shared Memory (Neo4j) We use the agent-collab Neo4j instance as a temporal journal. -Scripts live in `/Users/james/git/agent-collab/scripts/neo4j-msg.js`. +Scripts live in `/scripts/neo4j-msg.js`. ### Setup ```bash # Register yourself once. Choose a display name that identifies the agent. -node /Users/james/git/agent-collab/scripts/neo4j-msg.js agent-init "Echo Codex" +node path/to/agent-collab/scripts/neo4j-msg.js agent-init "Echo Codex" ``` ### Writing a Journal Entry ```bash -node /Users/james/git/agent-collab/scripts/neo4j-msg.js msg-send \ +node path/to/agent-collab/scripts/neo4j-msg.js msg-send \ --from "Echo Codex" \ --to "Echo Archive" \ --text "[Echo] short summary of what you changed or decided." \ @@ -39,7 +39,7 @@ Guidelines: ### Reading Past Entries ```bash -node /Users/james/git/agent-collab/scripts/neo4j-msg.js messages \ +node path/to/agent-collab/scripts/neo4j-msg.js messages \ --thread "echo-devlog" \ --limit 20 ``` diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..92affe6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,144 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rmg-cli" +version = "0.1.0" + +[[package]] +name = "rmg-core" +version = "0.1.0" +dependencies = [ + "blake3", + "bytes", + "thiserror", +] + +[[package]] +name = "rmg-ffi" +version = "0.1.0" + +[[package]] +name = "rmg-wasm" +version = "0.1.0" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" diff --git a/docs/echo/rmg-runtime-architecture.md b/docs/echo/rmg-runtime-architecture.md new file mode 100644 index 0000000..5bcd898 --- /dev/null +++ b/docs/echo/rmg-runtime-architecture.md @@ -0,0 +1,76 @@ +# RMG Runtime Architecture (Phase 1 Blueprint) + +This document captures the consensus that emerged for Echo’s Phase 1 implementation: the entire runtime, assets, and tooling operate on top of the Recursive Meta Graph (RMG) engine. Every concept—worlds, systems, entities, components, assets, pipelines—is a graph node. The engine executes deterministic DPO rewrite rules over that graph each tick, emitting snapshots for replay, networking, and tooling. + +--- + +## Everything Is a Graph + +- `World`: graph node whose edges point to `System` subgraphs. +- `System`: rewrite rule graph. Pattern `L`, interface `K`, output `R`. +- `Entity`: graph node with edges to `Component` nodes (`Has` edges). +- `Component`: leaf node with payload (POD data, asset reference, etc.). +- `Timeline`: sequence of rewrite transactions / snapshots. +- `Asset`: graph nodes that hold binary payloads (meshes, shaders). +- `Importer/Exporter`: graph describing pipelines—each step is a node with rewrite rule. + +--- + +## Tick Loop (Deterministic Scheduler) + +```rust +loop { + let tx = engine.begin(); + + let rewrites = scheduler.collect(world_root, &engine); + for rewrite in rewrites { + engine.apply(tx, rewrite.rule, &rewrite.scope, &rewrite.params)?; + } + + let snapshot = engine.commit(tx)?; + publish_inspector_frames(snapshot); + process_delayed_events(snapshot); +} +``` + +- Scheduler walks the graph, gathers rewrite intents, orders by `(scope_hash, rule_id)`. +- Disjoint scopes execute in parallel under the DPOi scheduler. +- Commit produces a `Snapshot` hash captured in the branch tree and Confluence. + +--- + +## Execution Walkthrough + +1. **Begin transaction** – `engine.begin()` returns `TxId`. +2. **Collect rewrites** – scheduler matches system patterns, computes scope hashes. +3. **Apply rules** – each rule operates on matched subgraph, updating payloads / edges. +4. **Commit** – atomic swap of graph store, emit snapshot + commit log entry. +5. **Emit frames** – inspector, entropy, Codex logs read from snapshot. + +--- + +## Branching & Replay + +- Forking = capturing snapshot hash and starting new rewrite sequence. +- Rollback = load prior snapshot, replay commits. +- Merge = deterministic three-way merge via Confluence rules. + +--- + +## Tools & Networking + +- Tooling (Echo Studio, inspector) consumes snapshots and rewrite logs. +- Networking exchanges rewrite transactions (scope hash, rule id, params hash). +- Deterministic merge ensures peers converge on identical snapshots. + +--- + +## Implementation Notes + +- RMG engine runs in Rust (`rmg-core`). +- Lua scripts issue rewrite intents via bindings; remain deterministic. +- TypeScript tools (via WASM) visualize the same graphs. + +--- + +This loop—the recursive execution of graph rewrite rules—is the heart of Echo’s deterministic multiverse runtime. diff --git a/docs/echo/rust-lua-ts-division.md b/docs/echo/rust-lua-ts-division.md new file mode 100644 index 0000000..68602b0 --- /dev/null +++ b/docs/echo/rust-lua-ts-division.md @@ -0,0 +1,76 @@ +# Language & Responsibility Map (Phase 1) + +Echo’s runtime stack is intentionally stratified. Rust owns the deterministic graph engine; Lua sits on top for gameplay scripting; TypeScript powers the tooling layer via WebAssembly bindings. This document captures what lives where as we enter Phase 1 (Core Ignition). + +--- + +## Rust (rmg-core, ffi, wasm, cli) + +**Responsibilities** +- RMG engine: GraphStore, PatternGraph, RewriteRule, DeterministicScheduler, commit/Snapshot APIs. +- ECS foundations: Worlds, Systems, Components expressed as rewrite rules. +- Timeline & Branch tree: rewrite transactions, snapshot hashing, concurrency guard rails. +- Math/PRNG: deterministic float32 / fixed32 modules shared with gameplay. +- Netcode: lockstep / rollback / authority modes using rewrite transactions. +- Asset pipeline: import/export graphs, payload storage, zero-copy access. +- Confluence: distributed synchronization of rewrite transactions. +- Lua VM hosting: embed Lua 5.4, expose RMG bindings via FFI. +- CLI tools: `rmg` command for apply/snapshot/diff/verify. + +**Key Crates** +- `rmg-core` – core engine +- `rmg-ffi` – C ABI for Lua and other native consumers +- `rmg-wasm` – WASM build for tooling/editor +- `rmg-cli` – CLI utilities + +--- + +## Lua (gameplay authoring layer) + +**Responsibilities** +- Gameplay systems & components (e.g., AI state machines, quests, input handling). +- Component registration, entity creation/destruction via exposed APIs. +- Scripting for deterministic “async” (scheduled events through Codex’s Baby). +- Editor lenses and inspector overlays written in Lua for rapid iteration. + +**Constraints** +- Single-threaded per branch; no OS threads. +- GC runs in deterministic stepped mode, bounded per tick. +- Mutations occur through rewrite intents (`rmg.apply(...)`), not raw memory access. + +**Bindings** +- `rmg` Lua module providing: + - `apply(rule_name, scope, params)` + - `delay(seconds, fn)` (schedules replay-safe events) + - Query helpers (read components, iterate entities) + - Capability-guarded operations (world:rewrite, asset:import, etc.) + +--- + +## TypeScript / Web Tooling + +**Responsibilities** +- Echo Studio (graph IDE) – visualizes world graph, rewrites, branch tree. +- Inspector dashboards – display Codex, entropy, paradox frames. +- Replay/rollback visualizers, network debugging tools. +- Plugin builders and determinism test harness UI. + +**Integration** +- Uses `rmg-wasm` to call into RMG engine from the browser. +- IPC/WebSocket for live inspector feeds (`InspectorEnvelope`). +- Works with JSONL logs for offline analysis. +- All mutations go through bindings; tooling never mutates state outside RMG APIs. + +**Tech** +- Frontend frameworks: React/Svelte/Vanilla as needed. +- WebGPU/WebGL for graph visualization. +- TypeScript ensures type safety for tooling code. + +--- + +## Summary +- Rust: core deterministic runtime + binding layers. +- Lua: gameplay logic, editor lenses, deterministic script-level behavior. +- TypeScript: visualization and tooling on top of WASM/IPC. + +This division keeps determinism and performance anchored in Rust while giving designers and tooling engineers approachable layers tailored for their workflows. From d760ae32eeea1481df6aa40fc86c1161bc1ea14b Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Fri, 24 Oct 2025 13:48:41 -0700 Subject: [PATCH 03/18] Add demo roadmap for RMG implementation --- docs/echo/rmg-demo-roadmap.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/echo/rmg-demo-roadmap.md diff --git a/docs/echo/rmg-demo-roadmap.md b/docs/echo/rmg-demo-roadmap.md new file mode 100644 index 0000000..93022cc --- /dev/null +++ b/docs/echo/rmg-demo-roadmap.md @@ -0,0 +1,15 @@ +# RMG Demo Roadmap (Phase 1 Targets) + +This document captures the interactive demos and performance milestones we want to hit as we implement the Rust-based RMG runtime. Each demo proves a key property of Echo’s deterministic multiverse architecture. + +--- + +## Demo 1: Deterministic Netcode + +**Goal:** Show two instances running locally in lockstep and prove graph hash equality every frame. + +- Two Echo instances (no network) consume identical input streams. +- Each frame emits a “state hash” (BLAKE3 snapshot hash) displayed side-by-side. +- Validation: graph hashes match every tick, proving deterministic state. +- Feature: “frame hash” becomes a first-class inspector metric. + From c163209f7dcf44972a669d5d0cac41aec1cff9d6 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Fri, 24 Oct 2025 13:56:31 -0700 Subject: [PATCH 04/18] Add Phase 1 implementation plan --- docs/echo/phase1-plan.md | 114 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 docs/echo/phase1-plan.md diff --git a/docs/echo/phase1-plan.md b/docs/echo/phase1-plan.md new file mode 100644 index 0000000..970016e --- /dev/null +++ b/docs/echo/phase1-plan.md @@ -0,0 +1,114 @@ +# Phase 1 – Core Ignition Plan + +Goal: deliver a deterministic Rust implementation of RMG powering the Echo runtime, with tangible demos at each milestone. This plan outlines task chains, dependencies, and expected demonstrations. + +--- + +## Task Graph +```mermaid +graph TD + A[1A · RMG Core Bootstrap] + B[1B · Rewrite Executor Spike] + C[1C · Lua/TS Bindings] + D[1D · Echo ECS on RMG] + E[1E · Networking & Confluence MVP] + F[1F · Tooling Integration] + + A --> B --> C --> D --> E --> F + B --> DemoToy + D --> DemoNetcode + E --> DemoTimeTravel + F --> DemoLiveCoding + + subgraph Demos + DemoToy[Demo 2 · Toy Rewrite Benchmark] + DemoNetcode[Demo 1 · Deterministic Netcode] + DemoTimeTravel[Demo 5 · Time Travel Merge] + DemoLiveCoding[Demo 6 · Lua Live Coding] + end +``` + +--- + +## Phases & Tangible Outcomes + +### 1A · RMG Core Bootstrap +- Tasks + - Scaffold crates (`rmg-core`, `rmg-ffi`, `rmg-wasm`, `rmg-cli`). + - Implement GraphStore primitives, hash utilities, scheduler skeleton. + - CI: `cargo fmt/clippy/test` baseline. +- Demonstration: *None* (foundation only). + +### 1B · Rewrite Executor Spike +- Tasks + - Implement motion rule test (Position + Velocity rewrite). + - Execute deterministic ordering + snapshot hashing. + - Add minimal diff/commit log entries. +- Demonstration: **Demo 2 · Toy Benchmark** + - 100 nodes, 10 rules, property tests showing stable hashes. + +### 1C · Lua/TS Bindings +- Tasks + - Expose C ABI, embed Lua 5.4 with deterministic async helpers. + - Build WASM bindings for tooling. + - Port inspector CLI to use snapshots. +- Demonstration: Lua script triggers rewrite; inspector shows matching snapshot hash. + +### 1D · Echo ECS on RMG +- Tasks + - Map existing ECS system set onto rewrite rules. + - Replace Codex’s Baby event queue with rewrite intents. + - Emit frame hash HUD. +- Demonstration: **Demo 1 · Deterministic Netcode** + - Two instances, identical inputs, frame hash displayed per tick. + +### 1E · Networking & Confluence MVP +- Tasks + - Implement rewrite transaction packets; replay on peers. + - Converge canonical snapshots; handle conflicts deterministically. + - Integrate rollback path (branch rewind, replay log). +- Demonstration: **Demo 5 · Time Travel** + - Fork, edit, merge branch; show canonical outcome. + +### 1F · Tooling Integration +- Tasks + - Echo Studio (TS + WASM) graph viewer with live updates. + - Entropy lens, paradox heatmap overlays. + - Lua live coding pipeline (hot reload). +- Demonstrations: + - **Demo 3 · Real Benchmark** (1k nodes, 100 rules). + - **Demo 6 · Live Coding** (Lua edit updates live graph). + +--- + +## Performance / Benchmark Milestones + +| Milestone | Target | Notes | +| --------- | ------ | ----- | +| Toy Benchmark | 100 nodes / 10 rules / 200 iterations < 1ms | Demo 2 | +| Real Demo | 1,000 nodes / 100 rules < 10ms rewrite checks | Demo 3 | +| Production Stretch | 10,000 nodes / 1000 rules (profiling only) | Phase 2 optimizations | + +Optimization roadmap once baseline is working: +1. Incremental pattern matching. +2. Spatial indexing. +3. SIMD bitmap operations. +4. Critical pair analysis for confluence proofs. + +--- + +## Networking Demo Targets +| Mode | Deliverable | +| ---- | ----------- | +| Lockstep | Replay identical inputs; frame hash equality per tick. | +| Rollback | Predictive input with rollback on mismatch. | +| Authority | Host selects canonical branch; entropy auditor rejects paradox. | + +--- + +## Documentation Checklist +- Update `docs/echo/rmg-runtime-architecture.md` as rules/loop evolve. +- Append decision log entries per phase. +- Record demo outcomes in Neo4j (`echo-devlog`). + +Phase 1 completes when Demo 6 (Live Coding) runs atop the Rust RMG runtime with inspector tooling in place. From 3503b531dd0f2ee3aab68713fa3a75783ee16871 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Fri, 24 Oct 2025 14:40:38 -0700 Subject: [PATCH 05/18] Restructure docs and archive TypeScript prototype Moved specs from docs/echo/ to docs/, updated references, and archived the old TypeScript workspace under reference/typescript. Also refreshed contributing guidelines and tooling scripts to reflect the Rust-oriented workflow. --- AGENTS.md | 6 +++--- CODEOWNERS | 2 +- CONTRIBUTING.md | 19 ++++++++++--------- README.md | 10 +++++----- crates/rmg-core/src/lib.rs | 17 +++++++---------- docs/{echo => }/architecture-outline.md | 2 +- docs/{echo => }/branch-merge-playbook.md | 0 .../codex-implementation-checklist.md | 0 docs/{echo => }/codex-instrumentation.md | 0 docs/{echo => }/decision-log.md | 0 docs/{echo => }/determinism-invariants.md | 0 docs/{echo => }/diagrams.md | 3 +-- docs/{echo => }/docs-index.md | 1 + docs/{echo => }/execution-plan.md | 6 +++--- docs/{echo => }/hash-graph.md | 0 docs/{echo => }/legacy-excavation.md | 0 docs/{echo => }/math-validation-plan.md | 0 docs/{echo => }/memorial.md | 0 docs/{echo => }/phase1-plan.md | 2 +- docs/{echo => }/release-criteria.md | 0 docs/{echo => }/rmg-demo-roadmap.md | 0 docs/{echo => }/rmg-runtime-architecture.md | 0 docs/{echo => }/runtime-diagnostics-plan.md | 0 docs/{echo => }/rust-lua-ts-division.md | 0 docs/{echo => }/scheduler-benchmarks.md | 3 +-- docs/{echo => }/spec-branch-tree.md | 0 .../spec-capabilities-and-security.md | 0 docs/{echo => }/spec-codex-baby.md | 0 .../spec-concurrency-and-authoring.md | 0 docs/{echo => }/spec-deterministic-math.md | 0 docs/{echo => }/spec-ecs-storage.md | 0 docs/{echo => }/spec-editor-and-inspector.md | 0 docs/{echo => }/spec-entropy-and-paradox.md | 0 docs/{echo => }/spec-networking.md | 0 docs/{echo => }/spec-plugin-system.md | 0 docs/{echo => }/spec-rmg-confluence.md | 0 docs/{echo => }/spec-rmg-core.md | 0 docs/{echo => }/spec-runtime-config.md | 0 docs/{echo => }/spec-scheduler.md | 0 .../{echo => }/spec-serialization-protocol.md | 0 docs/{echo => }/spec-temporal-bridge.md | 0 docs/{echo => }/spec-world-api.md | 0 docs/{echo => }/testing-and-replay-plan.md | 10 +++++----- .../typescript}/echo-core/README.md | 0 .../typescript}/echo-core/package.json | 0 .../typescript}/echo-core/src/index.ts | 0 .../typescript}/echo-core/test/smoke.test.ts | 0 .../typescript}/echo-core/tsconfig.build.json | 0 .../typescript}/echo-core/tsconfig.json | 0 .../typescript}/echo-core/vitest.config.ts | 0 .../typescript/eslint.config.js | 0 .../typescript/package.json | 0 .../typescript}/playground/README.md | 0 .../typescript/pnpm-lock.yaml | 0 .../typescript/pnpm-workspace.yaml | 0 .../typescript/prettier.config.js | 0 .../typescript/tsconfig.base.json | 0 .../typescript/tsconfig.json | 0 scripts/scaffold-community.sh | 6 +++--- templates/CODEOWNERS.tmpl | 2 +- 60 files changed, 43 insertions(+), 46 deletions(-) rename docs/{echo => }/architecture-outline.md (99%) rename docs/{echo => }/branch-merge-playbook.md (100%) rename docs/{echo => }/codex-implementation-checklist.md (100%) rename docs/{echo => }/codex-instrumentation.md (100%) rename docs/{echo => }/decision-log.md (100%) rename docs/{echo => }/determinism-invariants.md (100%) rename docs/{echo => }/diagrams.md (99%) rename docs/{echo => }/docs-index.md (97%) rename docs/{echo => }/execution-plan.md (95%) rename docs/{echo => }/hash-graph.md (100%) rename docs/{echo => }/legacy-excavation.md (100%) rename docs/{echo => }/math-validation-plan.md (100%) rename docs/{echo => }/memorial.md (100%) rename docs/{echo => }/phase1-plan.md (98%) rename docs/{echo => }/release-criteria.md (100%) rename docs/{echo => }/rmg-demo-roadmap.md (100%) rename docs/{echo => }/rmg-runtime-architecture.md (100%) rename docs/{echo => }/runtime-diagnostics-plan.md (100%) rename docs/{echo => }/rust-lua-ts-division.md (100%) rename docs/{echo => }/scheduler-benchmarks.md (96%) rename docs/{echo => }/spec-branch-tree.md (100%) rename docs/{echo => }/spec-capabilities-and-security.md (100%) rename docs/{echo => }/spec-codex-baby.md (100%) rename docs/{echo => }/spec-concurrency-and-authoring.md (100%) rename docs/{echo => }/spec-deterministic-math.md (100%) rename docs/{echo => }/spec-ecs-storage.md (100%) rename docs/{echo => }/spec-editor-and-inspector.md (100%) rename docs/{echo => }/spec-entropy-and-paradox.md (100%) rename docs/{echo => }/spec-networking.md (100%) rename docs/{echo => }/spec-plugin-system.md (100%) rename docs/{echo => }/spec-rmg-confluence.md (100%) rename docs/{echo => }/spec-rmg-core.md (100%) rename docs/{echo => }/spec-runtime-config.md (100%) rename docs/{echo => }/spec-scheduler.md (100%) rename docs/{echo => }/spec-serialization-protocol.md (100%) rename docs/{echo => }/spec-temporal-bridge.md (100%) rename docs/{echo => }/spec-world-api.md (100%) rename docs/{echo => }/testing-and-replay-plan.md (78%) rename {packages => reference/typescript}/echo-core/README.md (100%) rename {packages => reference/typescript}/echo-core/package.json (100%) rename {packages => reference/typescript}/echo-core/src/index.ts (100%) rename {packages => reference/typescript}/echo-core/test/smoke.test.ts (100%) rename {packages => reference/typescript}/echo-core/tsconfig.build.json (100%) rename {packages => reference/typescript}/echo-core/tsconfig.json (100%) rename {packages => reference/typescript}/echo-core/vitest.config.ts (100%) rename eslint.config.js => reference/typescript/eslint.config.js (100%) rename package.json => reference/typescript/package.json (100%) rename {apps => reference/typescript}/playground/README.md (100%) rename pnpm-lock.yaml => reference/typescript/pnpm-lock.yaml (100%) rename pnpm-workspace.yaml => reference/typescript/pnpm-workspace.yaml (100%) rename prettier.config.js => reference/typescript/prettier.config.js (100%) rename tsconfig.base.json => reference/typescript/tsconfig.base.json (100%) rename tsconfig.json => reference/typescript/tsconfig.json (100%) diff --git a/AGENTS.md b/AGENTS.md index e227497..b8d214f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,8 +3,8 @@ Welcome to the **Echo** project. This file captures expectations for any LLM agent (and future-human collaborator) who touches the repo. ## Core Principles -- **Honor the Vision**: Echo is a deterministic, multiverse-aware ECS. Consult `docs/echo/architecture-outline.md` before touching runtime code. -- **Document Ruthlessly**: Every meaningful design choice should land in `docs/echo/` (spec, diagrams, memorials) or a Neo4j journal entry tagged `Echo`. +- **Honor the Vision**: Echo is a deterministic, multiverse-aware ECS. Consult `docs/architecture-outline.md` before touching runtime code. +- **Document Ruthlessly**: Every meaningful design choice should land in `docs/` (specs, diagrams, memorials) or a Neo4j journal entry tagged `Echo`. - **Determinism First**: Avoid introducing sources of nondeterminism without a mitigation plan. - **Temporal Mindset**: Think in timelines—branching, merging, entropy budgets. Feature work should map to Chronos/Kairos/Aion axes where appropriate. @@ -49,7 +49,7 @@ Use `messages-search --text "Echo"` for ad-hoc queries. ## Repository Layout - `packages/echo-core`: Runtime core (ECS, scheduler, Codex’s Baby, timelines). - `apps/playground`: Vite sandbox and inspector (future). -- `docs/echo`: Specs, diagrams, memorials. +- `docs/`: Specs, diagrams, memorials. - `docs/legacy`: Preserved artifacts from the Caverns era. ## Working Agreement diff --git a/CODEOWNERS b/CODEOWNERS index 4462426..26c4b43 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -2,5 +2,5 @@ # Reference: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners * @flyingrobots -/docs/echo/ @flyingrobots +/docs/ @flyingrobots /packages/echo-core/ @flyingrobots diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 23a735f..bc93c9e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,8 +19,8 @@ Echo is a deterministic, renderer-agnostic engine. We prioritize: - **Temporal Tooling**: features support branching timelines and merges. ## Getting Started -1. Clone the repo and run `pnpm install`. -2. Read `docs/echo/architecture-outline.md` and `docs/echo/execution-plan.md`. +1. Clone the repo and run `cargo check` to ensure the Rust workspace builds. +2. Read `docs/architecture-outline.md` and `docs/execution-plan.md`. 3. Register yourself in Neo4j via `AGENTS.md` instructions and note your display handle. ## Branching & Workflow @@ -30,16 +30,16 @@ Echo is a deterministic, renderer-agnostic engine. We prioritize: ## Testing Expectations - Write tests before or alongside code changes. -- `pnpm test` must pass locally before PR submission. -- Add Vitest coverage for new logic; integration tests will live under `apps/playground` when ready. +- `cargo test` must pass locally before PR submission. +- Add unit/integration coverage for new logic; Lua/TypeScript tooling will regain coverage when reintroduced. ## Documentation & Telemetry -- Update relevant docs in `docs/echo/` whenever behavior or architecture changes. +- Update relevant docs in `docs/` whenever behavior or architecture changes. - Record major decisions or deviations in the execution plan or decision log tables. - Use Neo4j to leave breadcrumbs for future Codex agents. ## Submitting Changes -1. Run `pnpm lint` and `pnpm test`. +1. Run `cargo fmt`, `cargo clippy`, and `cargo test`. 2. Commit with meaningful messages (no conventional prefixes; tell the story). 3. Push your branch and open a PR. Include: - Summary of changes and motivation. @@ -48,9 +48,10 @@ Echo is a deterministic, renderer-agnostic engine. We prioritize: 4. Request review from maintainers (see CODEOWNERS). ## Code Style -- TypeScript + ESLint + Prettier (config provided). -- Prefer explicit types when clarity improves comprehension. -- Avoid non-deterministic APIs (no `Math.random`, `Date.now`, etc.). Use Echo’s math/PRNG services. +- Rust code must pass `cargo fmt` and `cargo clippy` without warnings. +- Lua scripts should remain deterministic (no uncontrolled globals, RNG via engine services). +- TypeScript tooling (when active) lives in `reference/typescript/`; follow local lint configs when reactivated. +- Avoid non-deterministic APIs (no wall-clock, no uncontrolled randomness). Use Echo’s deterministic services. ## Communication - Major updates logged in Neo4j threads (`echo-devlog`, `echo-spec`). diff --git a/README.md b/README.md index 47797d8..8a08175 100644 --- a/README.md +++ b/README.md @@ -85,11 +85,11 @@ git clone git@github.com:flyingrobots/EchoEngine.git > *“Roads? Where we’re going, we don’t need roads.” — Doc Brown, Back to the Future* -- Read [`docs/echo/architecture-outline.md`](docs/echo/architecture-outline.md) for the full spec (storage, scheduler, ports, timelines). -- Explore [`docs/echo/diagrams.md`](docs/echo/diagrams.md) for Mermaid visuals of system constellations and the Chronos loop. -- Honor Caverns with [`docs/echo/memorial.md`](docs/echo/memorial.md)—we carry the torch forward. -- Peek at [`docs/echo/legacy-excavation.md`](docs/echo/legacy-excavation.md) to see which ideas survived the archaeological roast. -- Track active work in [`docs/echo/execution-plan.md`](docs/echo/execution-plan.md); update it every session. +- Read [`docs/architecture-outline.md`](docs/architecture-outline.md) for the full spec (storage, scheduler, ports, timelines). +- Explore [`docs/diagrams.md`](docs/diagrams.md) for Mermaid visuals of system constellations and the Chronos loop. +- Honor Caverns with [`docs/memorial.md`](docs/memorial.md)—we carry the torch forward. +- Peek at [`docs/legacy-excavation.md`](docs/legacy-excavation.md) to see which ideas survived the archaeological roast. +- Track active work in [`docs/execution-plan.md`](docs/execution-plan.md); update it every session. --- diff --git a/crates/rmg-core/src/lib.rs b/crates/rmg-core/src/lib.rs index f89159d..4f12785 100644 --- a/crates/rmg-core/src/lib.rs +++ b/crates/rmg-core/src/lib.rs @@ -1,3 +1,5 @@ +//! rmg-core: typed deterministic graph rewriting engine. + use std::collections::{BTreeMap, HashMap}; use blake3::Hasher; @@ -38,10 +40,6 @@ pub struct GraphStore { } impl GraphStore { - pub fn new() -> Self { - Self::default() - } - pub fn node(&self, id: &NodeId) -> Option<&NodeRecord> { self.nodes.get(id) } @@ -51,12 +49,12 @@ impl GraphStore { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct PatternGraph { pub nodes: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct RewriteRule { pub id: Hash, pub name: &'static str, @@ -79,7 +77,7 @@ pub struct DeterministicScheduler { pending: BTreeMap<(Hash, Hash), PendingRewrite>, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct PendingRewrite { pub tx: TxId, pub rule_id: Hash, @@ -114,7 +112,7 @@ impl Engine { rules: HashMap::new(), scheduler: DeterministicScheduler::default(), tx_counter: 0, - current_root: root.clone(), + current_root: root, last_snapshot: None, } } @@ -159,8 +157,7 @@ impl Engine { if tx.0 == 0 || tx.0 > self.tx_counter { return Err(EngineError::UnknownTx); } - - // TODO: execute rewrites (placeholder deterministic ordering) + // TODO: execute pending rewrites in deterministic order (placeholder flush) self.scheduler.pending.clear(); let hash = hash_root(&self.current_root); diff --git a/docs/echo/architecture-outline.md b/docs/architecture-outline.md similarity index 99% rename from docs/echo/architecture-outline.md rename to docs/architecture-outline.md index a9905fd..f36e16c 100644 --- a/docs/echo/architecture-outline.md +++ b/docs/architecture-outline.md @@ -123,7 +123,7 @@ ## Legacy Excavation Log - **Goal**: Track every legacy file, classify (keep concept, redesign, discard), note dependencies (Mootools, globals, duplicate IDs), and record learnings to inform Echo. -- **Artifacts**: `docs/echo/legacy-excavation.md` (to be populated) with columns for file, role, verdict, action items, and notes. +- **Artifacts**: `docs/legacy-excavation.md` (to be populated) with columns for file, role, verdict, action items, and notes. - **Process**: Review file → summarize intent → capture bugs/gaps → map to Echo’s modules → decide migration path or deprecation. - **Outcome**: Comprehensive reference that prevents accidental feature loss and keeps the rewrite grounded in historical context. diff --git a/docs/echo/branch-merge-playbook.md b/docs/branch-merge-playbook.md similarity index 100% rename from docs/echo/branch-merge-playbook.md rename to docs/branch-merge-playbook.md diff --git a/docs/echo/codex-implementation-checklist.md b/docs/codex-implementation-checklist.md similarity index 100% rename from docs/echo/codex-implementation-checklist.md rename to docs/codex-implementation-checklist.md diff --git a/docs/echo/codex-instrumentation.md b/docs/codex-instrumentation.md similarity index 100% rename from docs/echo/codex-instrumentation.md rename to docs/codex-instrumentation.md diff --git a/docs/echo/decision-log.md b/docs/decision-log.md similarity index 100% rename from docs/echo/decision-log.md rename to docs/decision-log.md diff --git a/docs/echo/determinism-invariants.md b/docs/determinism-invariants.md similarity index 100% rename from docs/echo/determinism-invariants.md rename to docs/determinism-invariants.md diff --git a/docs/echo/diagrams.md b/docs/diagrams.md similarity index 99% rename from docs/echo/diagrams.md rename to docs/diagrams.md index f8b87e9..4f00ca3 100644 --- a/docs/echo/diagrams.md +++ b/docs/diagrams.md @@ -190,5 +190,4 @@ sequenceDiagram - **Entropy Pulse**: Animate stroke width/color based on the Entropy meter. - **Interactive Sequencer**: Play back the sequence diagram with tooltips showing Codex queue sizes. -Once the architecture crystallizes, we’ll wire these into a `docs/echo/viewer/` playground that live-updates from this Markdown. - +Once the architecture crystallizes, we’ll wire these into a `docs/viewer/` playground that live-updates from this Markdown. diff --git a/docs/echo/docs-index.md b/docs/docs-index.md similarity index 97% rename from docs/echo/docs-index.md rename to docs/docs-index.md index b76e9e2..71db2f9 100644 --- a/docs/echo/docs-index.md +++ b/docs/docs-index.md @@ -16,6 +16,7 @@ | `spec-plugin-system.md` | Plugin discovery, namespace isolation, capabilities | | `spec-concurrency-and-authoring.md` | Parallel core & single-threaded scripting model | | `spec-networking.md` | Deterministic event replication modes | +| `phase1-plan.md` | Phase 1 implementation roadmap & demo targets | | `spec-rmg-core.md` | Recursive Meta Graph core format and runtime | | `spec-rmg-confluence.md` | Global graph synchronization (Confluence) | | `spec-ecs-storage.md` | ECS storage (archetypes, chunks, COW) | diff --git a/docs/echo/execution-plan.md b/docs/execution-plan.md similarity index 95% rename from docs/echo/execution-plan.md rename to docs/execution-plan.md index 8522a6b..a5f7555 100644 --- a/docs/echo/execution-plan.md +++ b/docs/execution-plan.md @@ -62,7 +62,7 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s - [ ] Write failing tests for entity ID allocation + recycling. - [ ] Prototype `TimelineFingerprint` hashing & equality tests. - [ ] Scaffold deterministic PRNG wrapper with tests. -- [ ] Establish `pnpm test` pipeline in CI (incoming GitHub Actions). +- [ ] Establish `cargo test` pipeline in CI (incoming GitHub Actions). - [ ] Integrate roaring bitmaps into ECS dirty tracking. - [ ] Implement chunk epoch counters on mutation. - [ ] Add deterministic hashing module (canonical encode + BLAKE3). @@ -75,8 +75,8 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s - [ ] Update Codex's Baby to Phase 0.5 spec (event envelope, bridge, backpressure, inspector packet, security). ### Tooling & Docs -- [ ] Build `docs/echo/data-structures.md` with Mermaid diagrams (storage, branch tree with roaring bitmaps). -- [ ] Extend `docs/echo/diagrams.md` with scheduler flow & command queue animations. +- [ ] Build `docs/data-structures.md` with Mermaid diagrams (storage, branch tree with roaring bitmaps). +- [ ] Extend `docs/diagrams.md` with scheduler flow & command queue animations. - [ ] Prepare Neo4j query cheatsheet for faster journaling. - [ ] Design test fixture layout (`test/fixtures/…`) with sample component schemas. - [ ] Document roaring bitmap integration and merge strategies. diff --git a/docs/echo/hash-graph.md b/docs/hash-graph.md similarity index 100% rename from docs/echo/hash-graph.md rename to docs/hash-graph.md diff --git a/docs/echo/legacy-excavation.md b/docs/legacy-excavation.md similarity index 100% rename from docs/echo/legacy-excavation.md rename to docs/legacy-excavation.md diff --git a/docs/echo/math-validation-plan.md b/docs/math-validation-plan.md similarity index 100% rename from docs/echo/math-validation-plan.md rename to docs/math-validation-plan.md diff --git a/docs/echo/memorial.md b/docs/memorial.md similarity index 100% rename from docs/echo/memorial.md rename to docs/memorial.md diff --git a/docs/echo/phase1-plan.md b/docs/phase1-plan.md similarity index 98% rename from docs/echo/phase1-plan.md rename to docs/phase1-plan.md index 970016e..06d7af9 100644 --- a/docs/echo/phase1-plan.md +++ b/docs/phase1-plan.md @@ -107,7 +107,7 @@ Optimization roadmap once baseline is working: --- ## Documentation Checklist -- Update `docs/echo/rmg-runtime-architecture.md` as rules/loop evolve. +- Update `docs/rmg-runtime-architecture.md` as rules/loop evolve. - Append decision log entries per phase. - Record demo outcomes in Neo4j (`echo-devlog`). diff --git a/docs/echo/release-criteria.md b/docs/release-criteria.md similarity index 100% rename from docs/echo/release-criteria.md rename to docs/release-criteria.md diff --git a/docs/echo/rmg-demo-roadmap.md b/docs/rmg-demo-roadmap.md similarity index 100% rename from docs/echo/rmg-demo-roadmap.md rename to docs/rmg-demo-roadmap.md diff --git a/docs/echo/rmg-runtime-architecture.md b/docs/rmg-runtime-architecture.md similarity index 100% rename from docs/echo/rmg-runtime-architecture.md rename to docs/rmg-runtime-architecture.md diff --git a/docs/echo/runtime-diagnostics-plan.md b/docs/runtime-diagnostics-plan.md similarity index 100% rename from docs/echo/runtime-diagnostics-plan.md rename to docs/runtime-diagnostics-plan.md diff --git a/docs/echo/rust-lua-ts-division.md b/docs/rust-lua-ts-division.md similarity index 100% rename from docs/echo/rust-lua-ts-division.md rename to docs/rust-lua-ts-division.md diff --git a/docs/echo/scheduler-benchmarks.md b/docs/scheduler-benchmarks.md similarity index 96% rename from docs/echo/scheduler-benchmarks.md rename to docs/scheduler-benchmarks.md index 67c8fd1..e88b8fd 100644 --- a/docs/echo/scheduler-benchmarks.md +++ b/docs/scheduler-benchmarks.md @@ -49,9 +49,8 @@ Objective: validate the scheduler design under realistic workloads before full i --- ## Tasks -- [ ] Scaffold benchmark harness (TS script + pnpm script `bench:scheduler`). +- [ ] Scaffold Rust benchmark harness (`cargo bench --bench scheduler`). - [ ] Implement mock system descriptors for each scenario. - [ ] Integrate with timeline fingerprint to simulate branches. - [ ] Record baseline numbers in docs and add to decision log. - [ ] Automate nightly run (future CI step). - diff --git a/docs/echo/spec-branch-tree.md b/docs/spec-branch-tree.md similarity index 100% rename from docs/echo/spec-branch-tree.md rename to docs/spec-branch-tree.md diff --git a/docs/echo/spec-capabilities-and-security.md b/docs/spec-capabilities-and-security.md similarity index 100% rename from docs/echo/spec-capabilities-and-security.md rename to docs/spec-capabilities-and-security.md diff --git a/docs/echo/spec-codex-baby.md b/docs/spec-codex-baby.md similarity index 100% rename from docs/echo/spec-codex-baby.md rename to docs/spec-codex-baby.md diff --git a/docs/echo/spec-concurrency-and-authoring.md b/docs/spec-concurrency-and-authoring.md similarity index 100% rename from docs/echo/spec-concurrency-and-authoring.md rename to docs/spec-concurrency-and-authoring.md diff --git a/docs/echo/spec-deterministic-math.md b/docs/spec-deterministic-math.md similarity index 100% rename from docs/echo/spec-deterministic-math.md rename to docs/spec-deterministic-math.md diff --git a/docs/echo/spec-ecs-storage.md b/docs/spec-ecs-storage.md similarity index 100% rename from docs/echo/spec-ecs-storage.md rename to docs/spec-ecs-storage.md diff --git a/docs/echo/spec-editor-and-inspector.md b/docs/spec-editor-and-inspector.md similarity index 100% rename from docs/echo/spec-editor-and-inspector.md rename to docs/spec-editor-and-inspector.md diff --git a/docs/echo/spec-entropy-and-paradox.md b/docs/spec-entropy-and-paradox.md similarity index 100% rename from docs/echo/spec-entropy-and-paradox.md rename to docs/spec-entropy-and-paradox.md diff --git a/docs/echo/spec-networking.md b/docs/spec-networking.md similarity index 100% rename from docs/echo/spec-networking.md rename to docs/spec-networking.md diff --git a/docs/echo/spec-plugin-system.md b/docs/spec-plugin-system.md similarity index 100% rename from docs/echo/spec-plugin-system.md rename to docs/spec-plugin-system.md diff --git a/docs/echo/spec-rmg-confluence.md b/docs/spec-rmg-confluence.md similarity index 100% rename from docs/echo/spec-rmg-confluence.md rename to docs/spec-rmg-confluence.md diff --git a/docs/echo/spec-rmg-core.md b/docs/spec-rmg-core.md similarity index 100% rename from docs/echo/spec-rmg-core.md rename to docs/spec-rmg-core.md diff --git a/docs/echo/spec-runtime-config.md b/docs/spec-runtime-config.md similarity index 100% rename from docs/echo/spec-runtime-config.md rename to docs/spec-runtime-config.md diff --git a/docs/echo/spec-scheduler.md b/docs/spec-scheduler.md similarity index 100% rename from docs/echo/spec-scheduler.md rename to docs/spec-scheduler.md diff --git a/docs/echo/spec-serialization-protocol.md b/docs/spec-serialization-protocol.md similarity index 100% rename from docs/echo/spec-serialization-protocol.md rename to docs/spec-serialization-protocol.md diff --git a/docs/echo/spec-temporal-bridge.md b/docs/spec-temporal-bridge.md similarity index 100% rename from docs/echo/spec-temporal-bridge.md rename to docs/spec-temporal-bridge.md diff --git a/docs/echo/spec-world-api.md b/docs/spec-world-api.md similarity index 100% rename from docs/echo/spec-world-api.md rename to docs/spec-world-api.md diff --git a/docs/echo/testing-and-replay-plan.md b/docs/testing-and-replay-plan.md similarity index 78% rename from docs/echo/testing-and-replay-plan.md rename to docs/testing-and-replay-plan.md index 4b843e4..2728a1b 100644 --- a/docs/echo/testing-and-replay-plan.md +++ b/docs/testing-and-replay-plan.md @@ -49,11 +49,11 @@ interface VerificationReport { --- ## Automation Plan -- `pnpm test:determinism` – runs replay and comparers for golden datasets. -- `pnpm test:paradox` – injects artificial read/write overlaps to validate quarantine behavior. -- `pnpm test:entropy` – verifies entropy observers and metrics. -- `pnpm test:bridge` – covers temporal bridge retro/reroute. -- `pnpm bench:scheduler` – as defined earlier, ensures performance regressions are recorded. +- `cargo test --package rmg-core --features determinism` – runs replay and comparers for golden datasets. +- `cargo test --package rmg-core --test paradox` – injects artificial read/write overlaps to validate quarantine behavior. +- `cargo test --package rmg-core --test entropy` – verifies entropy observers and metrics. +- `cargo test --package rmg-core --test bridge` – covers temporal bridge retro/reroute. +- `cargo bench --package rmg-core --bench scheduler` – ensures performance regressions are recorded (once benches enabled). --- diff --git a/packages/echo-core/README.md b/reference/typescript/echo-core/README.md similarity index 100% rename from packages/echo-core/README.md rename to reference/typescript/echo-core/README.md diff --git a/packages/echo-core/package.json b/reference/typescript/echo-core/package.json similarity index 100% rename from packages/echo-core/package.json rename to reference/typescript/echo-core/package.json diff --git a/packages/echo-core/src/index.ts b/reference/typescript/echo-core/src/index.ts similarity index 100% rename from packages/echo-core/src/index.ts rename to reference/typescript/echo-core/src/index.ts diff --git a/packages/echo-core/test/smoke.test.ts b/reference/typescript/echo-core/test/smoke.test.ts similarity index 100% rename from packages/echo-core/test/smoke.test.ts rename to reference/typescript/echo-core/test/smoke.test.ts diff --git a/packages/echo-core/tsconfig.build.json b/reference/typescript/echo-core/tsconfig.build.json similarity index 100% rename from packages/echo-core/tsconfig.build.json rename to reference/typescript/echo-core/tsconfig.build.json diff --git a/packages/echo-core/tsconfig.json b/reference/typescript/echo-core/tsconfig.json similarity index 100% rename from packages/echo-core/tsconfig.json rename to reference/typescript/echo-core/tsconfig.json diff --git a/packages/echo-core/vitest.config.ts b/reference/typescript/echo-core/vitest.config.ts similarity index 100% rename from packages/echo-core/vitest.config.ts rename to reference/typescript/echo-core/vitest.config.ts diff --git a/eslint.config.js b/reference/typescript/eslint.config.js similarity index 100% rename from eslint.config.js rename to reference/typescript/eslint.config.js diff --git a/package.json b/reference/typescript/package.json similarity index 100% rename from package.json rename to reference/typescript/package.json diff --git a/apps/playground/README.md b/reference/typescript/playground/README.md similarity index 100% rename from apps/playground/README.md rename to reference/typescript/playground/README.md diff --git a/pnpm-lock.yaml b/reference/typescript/pnpm-lock.yaml similarity index 100% rename from pnpm-lock.yaml rename to reference/typescript/pnpm-lock.yaml diff --git a/pnpm-workspace.yaml b/reference/typescript/pnpm-workspace.yaml similarity index 100% rename from pnpm-workspace.yaml rename to reference/typescript/pnpm-workspace.yaml diff --git a/prettier.config.js b/reference/typescript/prettier.config.js similarity index 100% rename from prettier.config.js rename to reference/typescript/prettier.config.js diff --git a/tsconfig.base.json b/reference/typescript/tsconfig.base.json similarity index 100% rename from tsconfig.base.json rename to reference/typescript/tsconfig.base.json diff --git a/tsconfig.json b/reference/typescript/tsconfig.json similarity index 100% rename from tsconfig.json rename to reference/typescript/tsconfig.json diff --git a/scripts/scaffold-community.sh b/scripts/scaffold-community.sh index 94ae69d..7d9ba8d 100755 --- a/scripts/scaffold-community.sh +++ b/scripts/scaffold-community.sh @@ -9,9 +9,9 @@ fi ROOT_DIR=$(pwd) DEFAULT_PROJECT_NAME=${PROJECT_NAME:-$(basename "$ROOT_DIR")} DEFAULT_DESCRIPTION=$(sed -n '1s/^# //p;1q' README.md 2>/dev/null || echo "") -DEFAULT_PACKAGE_MANAGER=${PACKAGE_MANAGER:-pnpm} -DEFAULT_ARCH_DOC=${ARCHITECTURE_DOC:-docs/echo/architecture-outline.md} -DEFAULT_EXEC_PLAN=${EXECUTION_PLAN_DOC:-docs/echo/execution-plan.md} +DEFAULT_PACKAGE_MANAGER=${PACKAGE_MANAGER:-cargo} +DEFAULT_ARCH_DOC=${ARCHITECTURE_DOC:-docs/architecture-outline.md} +DEFAULT_EXEC_PLAN=${EXECUTION_PLAN_DOC:-docs/execution-plan.md} DEFAULT_AGENT_GUIDE=${AGENT_GUIDE:-AGENTS.md} DEFAULT_TAG=${PROJECT_TAG:-Echo} DEFAULT_DEVLOG=${DEVLOG_THREAD:-echo-devlog} diff --git a/templates/CODEOWNERS.tmpl b/templates/CODEOWNERS.tmpl index 4be4a4e..799ede7 100644 --- a/templates/CODEOWNERS.tmpl +++ b/templates/CODEOWNERS.tmpl @@ -2,5 +2,5 @@ # Reference: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners * ${CODEOWNER_HANDLE} -/docs/echo/ ${CODEOWNER_HANDLE} +/docs/ ${CODEOWNER_HANDLE} /packages/echo-core/ ${CODEOWNER_HANDLE} From 220f87e7562830ffe4da222c71034fe273cfda3a Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Fri, 24 Oct 2025 15:41:22 -0700 Subject: [PATCH 06/18] Remove archived TypeScript prototype entirely --- reference/typescript/echo-core/README.md | 32 - reference/typescript/echo-core/package.json | 34 - reference/typescript/echo-core/src/index.ts | 138 -- .../typescript/echo-core/test/smoke.test.ts | 8 - .../typescript/echo-core/tsconfig.build.json | 8 - reference/typescript/echo-core/tsconfig.json | 13 - .../typescript/echo-core/vitest.config.ts | 14 - reference/typescript/eslint.config.js | 22 - reference/typescript/package.json | 30 - reference/typescript/playground/README.md | 11 - reference/typescript/pnpm-lock.yaml | 1695 ----------------- reference/typescript/pnpm-workspace.yaml | 3 - reference/typescript/prettier.config.js | 9 - reference/typescript/tsconfig.base.json | 15 - reference/typescript/tsconfig.json | 8 - 15 files changed, 2040 deletions(-) delete mode 100644 reference/typescript/echo-core/README.md delete mode 100644 reference/typescript/echo-core/package.json delete mode 100644 reference/typescript/echo-core/src/index.ts delete mode 100644 reference/typescript/echo-core/test/smoke.test.ts delete mode 100644 reference/typescript/echo-core/tsconfig.build.json delete mode 100644 reference/typescript/echo-core/tsconfig.json delete mode 100644 reference/typescript/echo-core/vitest.config.ts delete mode 100644 reference/typescript/eslint.config.js delete mode 100644 reference/typescript/package.json delete mode 100644 reference/typescript/playground/README.md delete mode 100644 reference/typescript/pnpm-lock.yaml delete mode 100644 reference/typescript/pnpm-workspace.yaml delete mode 100644 reference/typescript/prettier.config.js delete mode 100644 reference/typescript/tsconfig.base.json delete mode 100644 reference/typescript/tsconfig.json diff --git a/reference/typescript/echo-core/README.md b/reference/typescript/echo-core/README.md deleted file mode 100644 index 1fd22f3..0000000 --- a/reference/typescript/echo-core/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# @echo/core - -Early scaffolding for the Echo engine. The public API is intentionally tiny while the -architecture takes shape: - -- Deterministic tick loop placeholder via `EchoEngine`. -- Timeline fingerprints (`Chronos`, `Kairos`, `Aion`) as typed primitives. -- Command envelopes mirroring Codex's Baby event bus. - -Nothing here is stable yet—expect rapid iteration as we land the ECS storage, scheduler, -and branch tree implementations. - -```ts -import { EchoEngine } from "@echo/core"; - -const engine = new EchoEngine({ fixedTimeStepMs: 1000 / 30 }); -engine.registerTickHandler(({ fingerprint, deltaTimeMs }) => { - console.log("Tick", fingerprint, deltaTimeMs); -}); - -engine.tick(); -``` - -## Dev Scripts - -| Script | Purpose | -| ------------ | ----------------------------- | -| `build` | Emit `dist/` via TypeScript. | -| `dev` | Watch mode for rapid typings. | -| `lint` | ESLint with TS rules. | -| `test` | Placeholder for Vitest suite. | -| `format` | Prettier over sources. | diff --git a/reference/typescript/echo-core/package.json b/reference/typescript/echo-core/package.json deleted file mode 100644 index 3207eed..0000000 --- a/reference/typescript/echo-core/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "@echo/core", - "version": "0.0.0", - "description": "Echo core: deterministic ECS, scheduler, and timeline runtime.", - "type": "module", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "scripts": { - "build": "tsc -p tsconfig.build.json", - "dev": "tsc -p tsconfig.build.json --watch", - "lint": "eslint \"src/**/*.ts\"", - "test": "vitest run", - "test:watch": "vitest watch", - "format": "prettier --write \"src/**/*.ts\"" - }, - "keywords": [ - "echo", - "ecs", - "game-engine", - "deterministic" - ], - "author": "Echo Contributors", - "license": "UNLICENSED", - "repository": { - "type": "git", - "url": "https://example.com/echo.git" - } -} diff --git a/reference/typescript/echo-core/src/index.ts b/reference/typescript/echo-core/src/index.ts deleted file mode 100644 index e095b77..0000000 --- a/reference/typescript/echo-core/src/index.ts +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Echo Core public API skeleton. - * - * The goal of this module is to provide a strongly-typed surface that mirrors the - * architecture specification while we iterate on the deeper implementation. - */ - -export type ChronosTick = number; -export type KairosBranchId = string; -export type AionWeight = number; - -export interface TimelineFingerprint { - readonly chronos: ChronosTick; - readonly kairos: KairosBranchId; - readonly aion: AionWeight; -} - -export interface EngineOptions { - /** - * Fixed timestep in milliseconds. Defaults to 33.333 (30Hz) until tuning lands. - */ - readonly fixedTimeStepMs?: number; - /** - * Number of historical frames to retain for branch scrubbing. - */ - readonly historySize?: number; -} - -export interface EngineStats { - readonly activeBranches: number; - readonly entropy: number; - readonly lastTickDurationMs: number; -} - -export interface BranchHandle { - readonly fingerprint: TimelineFingerprint; -} - -/** - * Command envelope that would be queued through Codex's Baby. - * Placeholder until the event system is implemented. - */ -export interface CommandEnvelope { - readonly targetBranch: KairosBranchId; - readonly chronos: ChronosTick; - readonly payload: TPayload; -} - -type TickCallback = (context: TickContext) => void; - -export interface TickContext { - readonly fingerprint: TimelineFingerprint; - readonly deltaTimeMs: number; -} - -/** - * Minimal placeholder for the engine runtime. The implementation will grow to include the - * deterministic scheduler, archetype storage, Codex's Baby, and branch tree orchestration. - */ -const now = (): number => { - if (typeof performance !== "undefined" && typeof performance.now === "function") { - return performance.now(); - } - return Date.now(); -}; - -const createBranchId = (): string => { - if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") { - return crypto.randomUUID(); - } - return Math.random().toString(36).slice(2); -}; - -export class EchoEngine { - private readonly options: Required; - private readonly tickHandlers: Set = new Set(); - private statsInternal: EngineStats = { - activeBranches: 1, - entropy: 0, - lastTickDurationMs: 0 - }; - - public constructor(options: EngineOptions = {}) { - this.options = { - fixedTimeStepMs: options.fixedTimeStepMs ?? 1000 / 60, - historySize: options.historySize ?? 256 - }; - } - - public get stats(): EngineStats { - return this.statsInternal; - } - - public registerTickHandler(handler: TickCallback): () => void { - this.tickHandlers.add(handler); - return () => this.tickHandlers.delete(handler); - } - - /** - * Placeholder tick loop. Emits a static context so downstream systems can begin integrating. - */ - public tick(): void { - const start = now(); - const context: TickContext = { - fingerprint: { - chronos: 0, - kairos: "prime", - aion: 1 - }, - deltaTimeMs: this.options.fixedTimeStepMs - }; - for (const handler of this.tickHandlers) { - handler(context); - } - const end = now(); - this.statsInternal = { - ...this.statsInternal, - lastTickDurationMs: end - start - }; - } - - /** - * Spawn a speculative branch. Currently returns a stub so call sites can be wired. - */ - public forkBranch(): BranchHandle { - return { - fingerprint: { - chronos: 0, - kairos: createBranchId(), - aion: 0.5 - } - }; - } - - public queueCommand(_envelope: CommandEnvelope): void { - // TODO: integrate with Codex's Baby. - } -} diff --git a/reference/typescript/echo-core/test/smoke.test.ts b/reference/typescript/echo-core/test/smoke.test.ts deleted file mode 100644 index 4d13781..0000000 --- a/reference/typescript/echo-core/test/smoke.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { describe, it, expect } from "vitest"; - -// Placeholder until core specs are implemented. -describe("Echo core scaffolding", () => { - it("runs the test harness", () => { - expect(true).toBe(true); - }); -}); diff --git a/reference/typescript/echo-core/tsconfig.build.json b/reference/typescript/echo-core/tsconfig.build.json deleted file mode 100644 index db20e58..0000000 --- a/reference/typescript/echo-core/tsconfig.build.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "noEmit": false, - "emitDeclarationOnly": false - }, - "include": ["src/**/*.ts"] -} diff --git a/reference/typescript/echo-core/tsconfig.json b/reference/typescript/echo-core/tsconfig.json deleted file mode 100644 index 44fcd91..0000000 --- a/reference/typescript/echo-core/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "composite": true, - "declaration": true, - "declarationMap": true, - "noEmit": true, - "outDir": "dist", - "rootDir": "src" - }, - "include": ["src/**/*.ts"], - "references": [] -} diff --git a/reference/typescript/echo-core/vitest.config.ts b/reference/typescript/echo-core/vitest.config.ts deleted file mode 100644 index 8a55acc..0000000 --- a/reference/typescript/echo-core/vitest.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - include: ["test/**/*.test.ts"], - globals: true, - reporters: ["default"], - coverage: { - reporter: ["text", "lcov"], - include: ["src/**/*.ts"], - exclude: ["test/**"] - } - } -}); diff --git a/reference/typescript/eslint.config.js b/reference/typescript/eslint.config.js deleted file mode 100644 index fcec8ab..0000000 --- a/reference/typescript/eslint.config.js +++ /dev/null @@ -1,22 +0,0 @@ -// @ts-check -import prettier from "eslint-config-prettier"; -import tseslint from "typescript-eslint"; - -export default tseslint.config( - { ignores: ["dist", "node_modules"] }, - { - files: ["**/*.ts"], - extends: [tseslint.configs.recommendedTypeChecked, prettier], - languageOptions: { - parserOptions: { - project: ["./tsconfig.json"], - tsconfigRootDir: import.meta.dirname - } - }, - rules: { - "@typescript-eslint/consistent-type-exports": "error", - "@typescript-eslint/consistent-type-imports": "error", - "@typescript-eslint/no-floating-promises": "error" - } - } -); diff --git a/reference/typescript/package.json b/reference/typescript/package.json deleted file mode 100644 index 938fc21..0000000 --- a/reference/typescript/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "echo-monorepo", - "version": "0.0.0", - "private": true, - "description": "Echo: a deterministic multiverse engine experiment.", - "packageManager": "pnpm@8.15.0", - "workspaces": [ - "packages/*", - "apps/*" - ], - "scripts": { - "build": "pnpm --filter @echo/core build", - "dev": "pnpm --filter echo-playground dev", - "lint": "pnpm --filter @echo/core lint", - "test": "pnpm --filter @echo/core test", - "format": "pnpm --filter @echo/core format" - }, - "engines": { - "node": ">=18.17.0" - }, - "devDependencies": { - "@vitest/coverage-v8": "^4.0.2", - "@vitest/ui": "^4.0.2", - "eslint": "^8.57.0", - "eslint-config-prettier": "^9.1.0", - "prettier": "^3.2.5", - "typescript": "^5.4.0", - "vitest": "^4.0.2" - } -} diff --git a/reference/typescript/playground/README.md b/reference/typescript/playground/README.md deleted file mode 100644 index efdb540..0000000 --- a/reference/typescript/playground/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Echo Playground - -This directory will host the interactive sandbox built with Vite. For now it is a stub so -the workspace layout is in place. Once the core runtime stabilises we will scaffold the -playground with: - -- Hot-reload loop wired into `EchoEngine`. -- Timeline viewer overlay powered by the diagram spec. -- Adapter demos (Pixi/WebGPU/TUI). - -Stay tuned. The multiverse is under construction. diff --git a/reference/typescript/pnpm-lock.yaml b/reference/typescript/pnpm-lock.yaml deleted file mode 100644 index 111824c..0000000 --- a/reference/typescript/pnpm-lock.yaml +++ /dev/null @@ -1,1695 +0,0 @@ -lockfileVersion: '6.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - devDependencies: - '@vitest/coverage-v8': - specifier: ^4.0.2 - version: 4.0.2(vitest@4.0.2) - '@vitest/ui': - specifier: ^4.0.2 - version: 4.0.2(vitest@4.0.2) - eslint: - specifier: ^8.57.0 - version: 8.57.1 - eslint-config-prettier: - specifier: ^9.1.0 - version: 9.1.2(eslint@8.57.1) - prettier: - specifier: ^3.2.5 - version: 3.6.2 - typescript: - specifier: ^5.4.0 - version: 5.9.3 - vitest: - specifier: ^4.0.2 - version: 4.0.2(@vitest/ui@4.0.2) - - packages/echo-core: {} - -packages: - - /@babel/helper-string-parser@7.27.1: - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helper-validator-identifier@7.28.5: - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/parser@7.28.5: - resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.28.5 - dev: true - - /@babel/types@7.28.5: - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - dev: true - - /@bcoe/v8-coverage@1.0.2: - resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} - engines: {node: '>=18'} - dev: true - - /@esbuild/aix-ppc64@0.25.11: - resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-arm64@0.25.11: - resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-arm@0.25.11: - resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-x64@0.25.11: - resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/darwin-arm64@0.25.11: - resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@esbuild/darwin-x64@0.25.11: - resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-arm64@0.25.11: - resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-x64@0.25.11: - resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm64@0.25.11: - resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm@0.25.11: - resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ia32@0.25.11: - resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-loong64@0.25.11: - resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-mips64el@0.25.11: - resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ppc64@0.25.11: - resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-riscv64@0.25.11: - resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-s390x@0.25.11: - resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-x64@0.25.11: - resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/netbsd-arm64@0.25.11: - resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/netbsd-x64@0.25.11: - resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/openbsd-arm64@0.25.11: - resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/openbsd-x64@0.25.11: - resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/openharmony-arm64@0.25.11: - resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - requiresBuild: true - dev: true - optional: true - - /@esbuild/sunos-x64@0.25.11: - resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-arm64@0.25.11: - resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-ia32@0.25.11: - resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-x64@0.25.11: - resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@eslint-community/eslint-utils@4.9.0(eslint@8.57.1): - resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - dev: true - - /@eslint-community/regexpp@4.12.2: - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true - - /@eslint/eslintrc@2.1.4: - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.4.3 - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@eslint/js@8.57.1: - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /@humanwhocodes/config-array@0.13.0: - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@humanwhocodes/module-importer@1.0.1: - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - dev: true - - /@humanwhocodes/object-schema@2.0.3: - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead - dev: true - - /@jridgewell/resolve-uri@3.1.2: - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - dev: true - - /@jridgewell/sourcemap-codec@1.5.5: - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - dev: true - - /@jridgewell/trace-mapping@0.3.31: - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - dev: true - - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: true - - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - dev: true - - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 - dev: true - - /@polka/url@1.0.0-next.29: - resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - dev: true - - /@rollup/rollup-android-arm-eabi@4.52.5: - resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-android-arm64@4.52.5: - resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-darwin-arm64@4.52.5: - resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-darwin-x64@4.52.5: - resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-freebsd-arm64@4.52.5: - resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-freebsd-x64@4.52.5: - resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-linux-arm-gnueabihf@4.52.5: - resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-linux-arm-musleabihf@4.52.5: - resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-linux-arm64-gnu@4.52.5: - resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-linux-arm64-musl@4.52.5: - resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-linux-loong64-gnu@4.52.5: - resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-linux-ppc64-gnu@4.52.5: - resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-linux-riscv64-gnu@4.52.5: - resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-linux-riscv64-musl@4.52.5: - resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-linux-s390x-gnu@4.52.5: - resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-linux-x64-gnu@4.52.5: - resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-linux-x64-musl@4.52.5: - resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-openharmony-arm64@4.52.5: - resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} - cpu: [arm64] - os: [openharmony] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-win32-arm64-msvc@4.52.5: - resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-win32-ia32-msvc@4.52.5: - resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-win32-x64-gnu@4.52.5: - resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@rollup/rollup-win32-x64-msvc@4.52.5: - resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@standard-schema/spec@1.0.0: - resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - dev: true - - /@types/chai@5.2.3: - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 - dev: true - - /@types/deep-eql@4.0.2: - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - dev: true - - /@types/estree@1.0.8: - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - dev: true - - /@ungap/structured-clone@1.3.0: - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - dev: true - - /@vitest/coverage-v8@4.0.2(vitest@4.0.2): - resolution: {integrity: sha512-daQs7CNoq4KKJ+3mgnxwbX8NLkT3nNxK/ZARdWyy/VtNwe0LoKIHgXFvj0hCKXclgfHaihpqbv1UHkQOgyEZng==} - peerDependencies: - '@vitest/browser': 4.0.2 - vitest: 4.0.2 - peerDependenciesMeta: - '@vitest/browser': - optional: true - dependencies: - '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.0.2 - ast-v8-to-istanbul: 0.3.7 - debug: 4.4.3 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.2.0 - magicast: 0.3.5 - std-env: 3.10.0 - tinyrainbow: 3.0.3 - vitest: 4.0.2(@vitest/ui@4.0.2) - transitivePeerDependencies: - - supports-color - dev: true - - /@vitest/expect@4.0.2: - resolution: {integrity: sha512-izQY+ABWqL2Vyr5+LNo3m16nLLTAzLn8em6i5uxqsrWRhdgzdN5JIHrpFVGBAYRGDAbtwE+yD4Heu8gsBSWTVQ==} - dependencies: - '@standard-schema/spec': 1.0.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.0.2 - '@vitest/utils': 4.0.2 - chai: 6.2.0 - tinyrainbow: 3.0.3 - dev: true - - /@vitest/mocker@4.0.2(vite@7.1.12): - resolution: {integrity: sha512-oiny+oBSGU9vHMA1DPdO+t1GVidCRuA4lKSG6rbo5SrCiTCGl7bTCyTaUkwxDpUkiSxEVneeXW4LJ4fg3H56dw==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - dependencies: - '@vitest/spy': 4.0.2 - estree-walker: 3.0.3 - magic-string: 0.30.19 - vite: 7.1.12 - dev: true - - /@vitest/pretty-format@4.0.2: - resolution: {integrity: sha512-PhrSiljryCz5nUDhHla5ihXYy2iRCBob+rNqlu34dA+KZIllVR39rUGny5R3kLgDgw3r8GW1ptOo64WbieMkeQ==} - dependencies: - tinyrainbow: 3.0.3 - dev: true - - /@vitest/runner@4.0.2: - resolution: {integrity: sha512-mPS5T/ZDuO6J5rsQiA76CFmlHtos7dnCvL14I1Oo8SbcjIhJd6kirFmekovfYLRygdF0gJe6SA5asCKIWKw1tw==} - dependencies: - '@vitest/utils': 4.0.2 - pathe: 2.0.3 - dev: true - - /@vitest/snapshot@4.0.2: - resolution: {integrity: sha512-NibujZAh+fTQlpGdP8J2pZcsPg7EPjiLUOUq9In++4p35vc9xIFMkXfQDbBSpijqZPe6i2hEKrUCbKu70/sPzw==} - dependencies: - '@vitest/pretty-format': 4.0.2 - magic-string: 0.30.19 - pathe: 2.0.3 - dev: true - - /@vitest/spy@4.0.2: - resolution: {integrity: sha512-KrTWRXFPYrbhD0iUXeoA8BMXl81nvemj5D8sc7NbTlRvCeUWo36JheOWtAUCafcNi0G72ycAdsvWQVSOxy/3TA==} - dev: true - - /@vitest/ui@4.0.2(vitest@4.0.2): - resolution: {integrity: sha512-GqPobLaUnKxkEJQHxszJ4yTSHCNGysWto6wANoBn/iXWU1juZV5pVjopxu+DkMKU+J6UQqvZ4ZXkcjxzRQS31A==} - peerDependencies: - vitest: 4.0.2 - dependencies: - '@vitest/utils': 4.0.2 - fflate: 0.8.2 - flatted: 3.3.3 - pathe: 2.0.3 - sirv: 3.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vitest: 4.0.2(@vitest/ui@4.0.2) - dev: true - - /@vitest/utils@4.0.2: - resolution: {integrity: sha512-H9jFzZb/5B5Qh7ajPUWMJ8UYGxQ4EQTaNLSm3icXs/oXkzQ1jqfcWDEJ4U3LkFPZOd6QW8M2MYjz32poW+KKqg==} - dependencies: - '@vitest/pretty-format': 4.0.2 - tinyrainbow: 3.0.3 - dev: true - - /acorn-jsx@5.3.2(acorn@8.15.0): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.15.0 - dev: true - - /acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - dev: true - - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - dev: true - - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - dev: true - - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true - - /assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - dev: true - - /ast-v8-to-istanbul@0.3.7: - resolution: {integrity: sha512-kr1Hy6YRZBkGQSb6puP+D6FQ59Cx4m0siYhAxygMCAgadiWQ6oxAxQXHOMvJx67SJ63jRoVIIg5eXzUbbct1ww==} - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - estree-walker: 3.0.3 - js-tokens: 9.0.1 - dev: true - - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - - /brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: true - - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: true - - /chai@6.2.0: - resolution: {integrity: sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==} - engines: {node: '>=18'} - dev: true - - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: true - - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - dev: true - - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true - - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true - - /cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: true - - /debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.3 - dev: true - - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true - - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - dev: true - - /esbuild@0.25.11: - resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} - engines: {node: '>=18'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.11 - '@esbuild/android-arm': 0.25.11 - '@esbuild/android-arm64': 0.25.11 - '@esbuild/android-x64': 0.25.11 - '@esbuild/darwin-arm64': 0.25.11 - '@esbuild/darwin-x64': 0.25.11 - '@esbuild/freebsd-arm64': 0.25.11 - '@esbuild/freebsd-x64': 0.25.11 - '@esbuild/linux-arm': 0.25.11 - '@esbuild/linux-arm64': 0.25.11 - '@esbuild/linux-ia32': 0.25.11 - '@esbuild/linux-loong64': 0.25.11 - '@esbuild/linux-mips64el': 0.25.11 - '@esbuild/linux-ppc64': 0.25.11 - '@esbuild/linux-riscv64': 0.25.11 - '@esbuild/linux-s390x': 0.25.11 - '@esbuild/linux-x64': 0.25.11 - '@esbuild/netbsd-arm64': 0.25.11 - '@esbuild/netbsd-x64': 0.25.11 - '@esbuild/openbsd-arm64': 0.25.11 - '@esbuild/openbsd-x64': 0.25.11 - '@esbuild/openharmony-arm64': 0.25.11 - '@esbuild/sunos-x64': 0.25.11 - '@esbuild/win32-arm64': 0.25.11 - '@esbuild/win32-ia32': 0.25.11 - '@esbuild/win32-x64': 0.25.11 - dev: true - - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: true - - /eslint-config-prettier@9.1.2(eslint@8.57.1): - resolution: {integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - dependencies: - eslint: 8.57.1 - dev: true - - /eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true - - /eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) - '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.3.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true - - /espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) - eslint-visitor-keys: 3.4.3 - dev: true - - /esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 - dev: true - - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - dependencies: - estraverse: 5.3.0 - dev: true - - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: true - - /estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - dependencies: - '@types/estree': 1.0.8 - dev: true - - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true - - /expect-type@1.2.2: - resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} - engines: {node: '>=12.0.0'} - dev: true - - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true - - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true - - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true - - /fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - dependencies: - reusify: 1.1.0 - dev: true - - /fdir@6.5.0(picomatch@4.0.3): - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - dependencies: - picomatch: 4.0.3 - dev: true - - /fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - dev: true - - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flat-cache: 3.2.0 - dev: true - - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - dev: true - - /flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flatted: 3.3.3 - keyv: 4.5.4 - rimraf: 3.0.2 - dev: true - - /flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - dev: true - - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true - - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - dependencies: - is-glob: 4.0.3 - dev: true - - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - - /globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 - dev: true - - /graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - dev: true - - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - dev: true - - /html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - dev: true - - /ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - dev: true - - /import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - dev: true - - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - dev: true - - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: true - - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true - - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - dev: true - - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - dev: true - - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true - - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true - - /istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - dev: true - - /istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - dev: true - - /istanbul-lib-source-maps@5.0.6: - resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} - engines: {node: '>=10'} - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - debug: 4.4.3 - istanbul-lib-coverage: 3.2.2 - transitivePeerDependencies: - - supports-color - dev: true - - /istanbul-reports@3.2.0: - resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} - engines: {node: '>=8'} - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - dev: true - - /js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - dev: true - - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: true - - /json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: true - - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true - - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true - - /keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - dependencies: - json-buffer: 3.0.1 - dev: true - - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - dependencies: - p-locate: 5.0.0 - dev: true - - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true - - /magic-string@0.30.19: - resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - dev: true - - /magicast@0.3.5: - resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} - dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - source-map-js: 1.2.1 - dev: true - - /make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - dependencies: - semver: 7.7.3 - dev: true - - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.12 - dev: true - - /mrmime@2.0.1: - resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} - engines: {node: '>=10'} - dev: true - - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: true - - /nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true - - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - dev: true - - /optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - dev: true - - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - dependencies: - yocto-queue: 0.1.0 - dev: true - - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - dependencies: - p-limit: 3.1.0 - dev: true - - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - dev: true - - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - dev: true - - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: true - - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true - - /pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - dev: true - - /picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - dev: true - - /picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - dev: true - - /postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - dev: true - - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true - - /prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} - engines: {node: '>=14'} - hasBin: true - dev: true - - /punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - dev: true - - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true - - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: true - - /reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true - - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - dependencies: - glob: 7.2.3 - dev: true - - /rollup@4.52.5: - resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.52.5 - '@rollup/rollup-android-arm64': 4.52.5 - '@rollup/rollup-darwin-arm64': 4.52.5 - '@rollup/rollup-darwin-x64': 4.52.5 - '@rollup/rollup-freebsd-arm64': 4.52.5 - '@rollup/rollup-freebsd-x64': 4.52.5 - '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 - '@rollup/rollup-linux-arm-musleabihf': 4.52.5 - '@rollup/rollup-linux-arm64-gnu': 4.52.5 - '@rollup/rollup-linux-arm64-musl': 4.52.5 - '@rollup/rollup-linux-loong64-gnu': 4.52.5 - '@rollup/rollup-linux-ppc64-gnu': 4.52.5 - '@rollup/rollup-linux-riscv64-gnu': 4.52.5 - '@rollup/rollup-linux-riscv64-musl': 4.52.5 - '@rollup/rollup-linux-s390x-gnu': 4.52.5 - '@rollup/rollup-linux-x64-gnu': 4.52.5 - '@rollup/rollup-linux-x64-musl': 4.52.5 - '@rollup/rollup-openharmony-arm64': 4.52.5 - '@rollup/rollup-win32-arm64-msvc': 4.52.5 - '@rollup/rollup-win32-ia32-msvc': 4.52.5 - '@rollup/rollup-win32-x64-gnu': 4.52.5 - '@rollup/rollup-win32-x64-msvc': 4.52.5 - fsevents: 2.3.3 - dev: true - - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - dev: true - - /semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} - engines: {node: '>=10'} - hasBin: true - dev: true - - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - dev: true - - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true - - /siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - dev: true - - /sirv@3.0.2: - resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} - engines: {node: '>=18'} - dependencies: - '@polka/url': 1.0.0-next.29 - mrmime: 2.0.1 - totalist: 3.0.1 - dev: true - - /source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - dev: true - - /stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - dev: true - - /std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - dev: true - - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - dev: true - - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: true - - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - dev: true - - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true - - /tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - dev: true - - /tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - dev: true - - /tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - dev: true - - /tinyrainbow@3.0.3: - resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} - engines: {node: '>=14.0.0'} - dev: true - - /totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - dev: true - - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - dev: true - - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true - - /typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - dev: true - - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.3.1 - dev: true - - /vite@7.1.12: - resolution: {integrity: sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - dependencies: - esbuild: 0.25.11 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.52.5 - tinyglobby: 0.2.15 - optionalDependencies: - fsevents: 2.3.3 - dev: true - - /vitest@4.0.2(@vitest/ui@4.0.2): - resolution: {integrity: sha512-SXrA2ZzOPulX479d8W13RqKSmvHb9Bfg71eW7Fbs6ZjUFcCCXyt/OzFCkNyiUE8mFlPHa4ZVUGw0ky+5ndKnrg==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.2 - '@vitest/browser-preview': 4.0.2 - '@vitest/browser-webdriverio': 4.0.2 - '@vitest/ui': 4.0.2 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - dependencies: - '@vitest/expect': 4.0.2 - '@vitest/mocker': 4.0.2(vite@7.1.12) - '@vitest/pretty-format': 4.0.2 - '@vitest/runner': 4.0.2 - '@vitest/snapshot': 4.0.2 - '@vitest/spy': 4.0.2 - '@vitest/ui': 4.0.2(vitest@4.0.2) - '@vitest/utils': 4.0.2 - debug: 4.4.3 - es-module-lexer: 1.7.0 - expect-type: 1.2.2 - magic-string: 0.30.19 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 7.1.12 - why-is-node-running: 2.3.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - dev: true - - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - - /why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - dev: true - - /word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - dev: true - - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true - - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - dev: true diff --git a/reference/typescript/pnpm-workspace.yaml b/reference/typescript/pnpm-workspace.yaml deleted file mode 100644 index 0e5a073..0000000 --- a/reference/typescript/pnpm-workspace.yaml +++ /dev/null @@ -1,3 +0,0 @@ -packages: - - "packages/*" - - "apps/*" diff --git a/reference/typescript/prettier.config.js b/reference/typescript/prettier.config.js deleted file mode 100644 index ee2811d..0000000 --- a/reference/typescript/prettier.config.js +++ /dev/null @@ -1,9 +0,0 @@ -/** @type {import("prettier").Config} */ -const config = { - printWidth: 100, - singleQuote: false, - trailingComma: "es5", - semi: true -}; - -export default config; diff --git a/reference/typescript/tsconfig.base.json b/reference/typescript/tsconfig.base.json deleted file mode 100644 index 9555225..0000000 --- a/reference/typescript/tsconfig.base.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ES2022", - "moduleResolution": "Node", - "lib": ["ES2022", "DOM"], - "allowJs": false, - "resolveJsonModule": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "strict": true, - "useUnknownInCatchVariables": true - } -} diff --git a/reference/typescript/tsconfig.json b/reference/typescript/tsconfig.json deleted file mode 100644 index 40df0eb..0000000 --- a/reference/typescript/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "files": [], - "references": [ - { - "path": "packages/echo-core" - } - ] -} From 4d94febbf8bc76e954a2045cea346394547d6d1a Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Fri, 24 Oct 2025 18:17:36 -0700 Subject: [PATCH 07/18] motion rewrite spike with deterministic snapshot hash --- .gitignore | 1 + crates/rmg-core/src/lib.rs | 246 +++++++++++++++++++++++++++++++++++-- 2 files changed, 237 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index e9c159c..f0d60fb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules dist coverage +target # Editor cruft .DS_Store diff --git a/crates/rmg-core/src/lib.rs b/crates/rmg-core/src/lib.rs index 4f12785..ab5a032 100644 --- a/crates/rmg-core/src/lib.rs +++ b/crates/rmg-core/src/lib.rs @@ -6,12 +6,14 @@ use blake3::Hasher; use bytes::Bytes; use thiserror::Error; +const POSITION_VELOCITY_BYTES: usize = 24; + pub type Hash = [u8; 32]; #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct TypeId(pub Hash); -#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct NodeId(pub Hash); #[derive(Clone, PartialEq, Eq, Hash, Debug)] @@ -35,8 +37,8 @@ pub struct EdgeRecord { #[derive(Default)] pub struct GraphStore { - pub nodes: HashMap, - pub edges_from: HashMap>, + pub nodes: BTreeMap, + pub edges_from: BTreeMap>, } impl GraphStore { @@ -47,6 +49,14 @@ impl GraphStore { pub fn edges_from(&self, id: &NodeId) -> impl Iterator { self.edges_from.get(id).into_iter().flatten() } + + pub fn node_mut(&mut self, id: &NodeId) -> Option<&mut NodeRecord> { + self.nodes.get_mut(id) + } + + pub fn insert_node(&mut self, record: NodeRecord) { + self.nodes.insert(record.id.clone(), record); + } } #[derive(Debug, Clone)] @@ -54,14 +64,18 @@ pub struct PatternGraph { pub nodes: Vec, } -#[derive(Debug, Clone)] +pub type MatchFn = fn(&GraphStore, &NodeId) -> bool; +pub type ExecuteFn = fn(&mut GraphStore, &NodeId); + pub struct RewriteRule { pub id: Hash, pub name: &'static str, pub left: PatternGraph, + pub matcher: MatchFn, + pub executor: ExecuteFn, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct TxId(pub u64); #[derive(Debug, Clone)] @@ -139,6 +153,9 @@ impl Engine { Some(rule) => rule, None => return Ok(ApplyResult::NoMatch), }; + if !(rule.matcher)(&self.store, scope) { + return Ok(ApplyResult::NoMatch); + } let scope_hash = scope_hash(rule, scope); self.scheduler.pending.insert( @@ -157,10 +174,14 @@ impl Engine { if tx.0 == 0 || tx.0 > self.tx_counter { return Err(EngineError::UnknownTx); } - // TODO: execute pending rewrites in deterministic order (placeholder flush) - self.scheduler.pending.clear(); + let pending = self.scheduler.drain_for_tx(tx); + for rewrite in pending { + if let Some(rule) = self.rule_by_id(&rewrite.rule_id) { + (rule.executor)(&mut self.store, &rewrite.scope); + } + } - let hash = hash_root(&self.current_root); + let hash = compute_snapshot_hash(&self.store, &self.current_root); let snapshot = Snapshot { root: self.current_root.clone(), hash, @@ -172,7 +193,7 @@ impl Engine { } pub fn snapshot(&self) -> Snapshot { - let hash = hash_root(&self.current_root); + let hash = compute_snapshot_hash(&self.store, &self.current_root); Snapshot { root: self.current_root.clone(), hash, @@ -180,6 +201,16 @@ impl Engine { tx: TxId(self.tx_counter), } } + + pub fn node(&self, id: &NodeId) -> Option<&NodeRecord> { + self.store.node(id) + } +} + +impl Engine { + fn rule_by_id(&self, id: &Hash) -> Option<&RewriteRule> { + self.rules.values().find(|rule| &rule.id == id) + } } fn scope_hash(rule: &RewriteRule, scope: &NodeId) -> Hash { @@ -189,8 +220,203 @@ fn scope_hash(rule: &RewriteRule, scope: &NodeId) -> Hash { hasher.finalize().into() } -fn hash_root(root: &NodeId) -> Hash { +fn compute_snapshot_hash(store: &GraphStore, root: &NodeId) -> Hash { let mut hasher = Hasher::new(); hasher.update(&root.0); + for (node_id, node) in &store.nodes { + hasher.update(&node_id.0); + hasher.update(&(node.ty).0); + match &node.payload { + Some(payload) => { + hasher.update(&(payload.len() as u64).to_le_bytes()); + hasher.update(payload); + } + None => { + hasher.update(&0u64.to_le_bytes()); + } + } + } + for (from, edges) in &store.edges_from { + hasher.update(&from.0); + hasher.update(&(edges.len() as u64).to_le_bytes()); + for edge in edges { + hasher.update(&(edge.id).0); + hasher.update(&(edge.ty).0); + hasher.update(&(edge.to).0); + match &edge.payload { + Some(payload) => { + hasher.update(&(payload.len() as u64).to_le_bytes()); + hasher.update(payload); + } + None => { + hasher.update(&0u64.to_le_bytes()); + } + } + } + } + hasher.update(&root.0); + hasher.finalize().into() +} + +impl DeterministicScheduler { + fn drain_for_tx(&mut self, tx: TxId) -> Vec { + let mut ready = Vec::new(); + let pending = std::mem::take(&mut self.pending); + for (key, rewrite) in pending { + if rewrite.tx == tx { + ready.push(rewrite); + } else { + self.pending.insert(key, rewrite); + } + } + ready + } +} + +fn encode_position_velocity(position: [f32; 3], velocity: [f32; 3]) -> Bytes { + let mut buf = Vec::with_capacity(POSITION_VELOCITY_BYTES); + for value in position.into_iter().chain(velocity.into_iter()) { + buf.extend_from_slice(&value.to_le_bytes()); + } + Bytes::from(buf) +} + +fn decode_position_velocity(bytes: &Bytes) -> Option<([f32; 3], [f32; 3])> { + if bytes.len() != POSITION_VELOCITY_BYTES { + return None; + } + let mut floats = [0f32; 6]; + for (index, chunk) in bytes.chunks_exact(4).enumerate() { + floats[index] = f32::from_le_bytes(chunk.try_into().ok()?); + } + let position = [floats[0], floats[1], floats[2]]; + let velocity = [floats[3], floats[4], floats[5]]; + Some((position, velocity)) +} + +pub fn make_type_id(label: &str) -> TypeId { + TypeId(hash_label(label)) +} + +pub fn make_node_id(label: &str) -> NodeId { + NodeId(hash_label(label)) +} + +fn hash_label(label: &str) -> Hash { + let mut hasher = Hasher::new(); + hasher.update(label.as_bytes()); hasher.finalize().into() } + +fn add_vec(a: [f32; 3], b: [f32; 3]) -> [f32; 3] { + [a[0] + b[0], a[1] + b[1], a[2] + b[2]] +} + +fn motion_executor(store: &mut GraphStore, scope: &NodeId) { + if let Some(record) = store.node_mut(scope) { + if let Some(payload) = &record.payload { + if let Some((position, velocity)) = decode_position_velocity(payload) { + let updated = encode_position_velocity(add_vec(position, velocity), velocity); + record.payload = Some(updated); + } + } + } +} + +fn motion_matcher(store: &GraphStore, scope: &NodeId) -> bool { + store + .node(scope) + .and_then(|record| record.payload.as_ref()) + .and_then(decode_position_velocity) + .is_some() +} + +pub fn motion_rule() -> RewriteRule { + let mut hasher = Hasher::new(); + hasher.update(b"motion/update"); + let id = hasher.finalize().into(); + RewriteRule { + id, + name: "motion/update", + left: PatternGraph { nodes: vec![] }, + matcher: motion_matcher, + executor: motion_executor, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn motion_rule_updates_position_deterministically() { + let entity = make_node_id("entity-1"); + let entity_type = make_type_id("entity"); + let payload = encode_position_velocity([1.0, 2.0, 3.0], [0.5, -1.0, 0.25]); + + let mut store = GraphStore::default(); + store.insert_node(NodeRecord { + id: entity.clone(), + ty: entity_type, + payload: Some(payload), + }); + + let mut engine = Engine::new(store, entity.clone()); + engine.register_rule(motion_rule()); + + let tx = engine.begin(); + let apply = engine.apply(tx, "motion/update", &entity).unwrap(); + assert!(matches!(apply, ApplyResult::Applied)); + + let snap = engine.commit(tx).expect("commit"); + let hash_after_first_apply = snap.hash; + + // Run a second engine with identical initial state and ensure hashes match. + let mut store_b = GraphStore::default(); + let payload_b = encode_position_velocity([1.0, 2.0, 3.0], [0.5, -1.0, 0.25]); + store_b.insert_node(NodeRecord { + id: entity.clone(), + ty: entity_type, + payload: Some(payload_b), + }); + + let mut engine_b = Engine::new(store_b, entity.clone()); + engine_b.register_rule(motion_rule()); + let tx_b = engine_b.begin(); + let apply_b = engine_b.apply(tx_b, "motion/update", &entity).unwrap(); + assert!(matches!(apply_b, ApplyResult::Applied)); + let snap_b = engine_b.commit(tx_b).expect("commit B"); + + assert_eq!(hash_after_first_apply, snap_b.hash); + + // Ensure the position actually moved. + let node = engine + .node(&entity) + .expect("entity exists") + .payload + .as_ref() + .and_then(decode_position_velocity) + .expect("payload decode"); + assert_eq!(node.0, [1.5, 1.0, 3.25]); + } + + #[test] + fn motion_rule_no_match_on_missing_payload() { + let entity = make_node_id("entity-2"); + let entity_type = make_type_id("entity"); + + let mut store = GraphStore::default(); + store.insert_node(NodeRecord { + id: entity.clone(), + ty: entity_type, + payload: None, + }); + + let mut engine = Engine::new(store, entity.clone()); + engine.register_rule(motion_rule()); + + let tx = engine.begin(); + let apply = engine.apply(tx, "motion/update", &entity).unwrap(); + assert!(matches!(apply, ApplyResult::NoMatch)); + } +} From ff41151c03e6a7f7bbacf039cab5a916ec3e663d Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Fri, 24 Oct 2025 18:30:52 -0700 Subject: [PATCH 08/18] wire motion rewrite into ffi + wasm bindings Expose a C ABI that can spawn motion entities, drive begin/apply/commit, and read back position/velocity so Lua can exercise the deterministic rewrite pipeline. Added smoke tests that assert snapshot hashes match between independent engines. Wrapped the same engine behind wasm-bindgen for tooling; wasm32 tests run via wasm-bindgen-test to prove hash stability for browser clients and to guard the new helper APIs in rmg-core. Refs: Phase-1C --- AGENTS.md | 1 + Cargo.lock | 201 +++++++++++++++++++++++++ crates/rmg-core/src/lib.rs | 93 ++++++++++-- crates/rmg-ffi/Cargo.toml | 1 + crates/rmg-ffi/src/lib.rs | 294 ++++++++++++++++++++++++++++++++++++- crates/rmg-wasm/Cargo.toml | 8 + crates/rmg-wasm/src/lib.rs | 166 ++++++++++++++++++++- 7 files changed, 741 insertions(+), 23 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b8d214f..c9d03bc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,6 +5,7 @@ Welcome to the **Echo** project. This file captures expectations for any LLM age ## Core Principles - **Honor the Vision**: Echo is a deterministic, multiverse-aware ECS. Consult `docs/architecture-outline.md` before touching runtime code. - **Document Ruthlessly**: Every meaningful design choice should land in `docs/` (specs, diagrams, memorials) or a Neo4j journal entry tagged `Echo`. +- **Docstrings Aren't Optional**: Public APIs across crates (`rmg-core`, `rmg-ffi`, `rmg-wasm`, etc.) must carry rustdoc comments that explain intent, invariants, and usage. Treat missing docs as a failing test. - **Determinism First**: Avoid introducing sources of nondeterminism without a mitigation plan. - **Temporal Mindset**: Think in timelines—branching, merging, entropy budgets. Feature work should map to Chronos/Kairos/Aion axes where appropriate. diff --git a/Cargo.lock b/Cargo.lock index 92affe6..6a310cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,12 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "bytes" version = "1.10.1" @@ -61,6 +67,38 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "proc-macro2" version = "1.0.103" @@ -95,10 +133,33 @@ dependencies = [ [[package]] name = "rmg-ffi" version = "0.1.0" +dependencies = [ + "rmg-core", +] [[package]] name = "rmg-wasm" version = "0.1.0" +dependencies = [ + "rmg-core", + "wasm-bindgen", + "wasm-bindgen-test", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] name = "shlex" @@ -142,3 +203,143 @@ name = "unicode-ident" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e381134e148c1062f965a42ed1f5ee933eef2927c3f70d1812158f711d39865" +dependencies = [ + "js-sys", + "minicov", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b673bca3298fe582aeef8352330ecbad91849f85090805582400850f8270a2e8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "web-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/crates/rmg-core/src/lib.rs b/crates/rmg-core/src/lib.rs index ab5a032..058893c 100644 --- a/crates/rmg-core/src/lib.rs +++ b/crates/rmg-core/src/lib.rs @@ -8,17 +8,32 @@ use thiserror::Error; const POSITION_VELOCITY_BYTES: usize = 24; +/// Canonical 256-bit hash used throughout the engine for addressing nodes, +/// types, snapshots, and rewrite rules. pub type Hash = [u8; 32]; -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub struct TypeId(pub Hash); - +/// Strongly typed identifier for a registered entity or structural node. +/// +/// `NodeId` values are obtained from `make_node_id` and remain stable across +/// runs because they are derived from a BLAKE3 hash of a string label. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct NodeId(pub Hash); +/// Strongly typed identifier for the logical kind of a node or component. +/// +/// `TypeId` values are produced by `make_type_id` which hashes a label; using +/// a dedicated wrapper prevents accidental mixing of node and type identifiers. +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct TypeId(pub Hash); + +/// Identifier for a directed edge within the graph. #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub struct EdgeId(pub Hash); +/// Materialised record for a single node stored in the graph. +/// +/// The optional `payload` carries domain-specific bytes (component data, +/// attachments, etc) and is interpreted by higher layers. #[derive(Clone, Debug)] pub struct NodeRecord { pub id: NodeId, @@ -26,6 +41,7 @@ pub struct NodeRecord { pub payload: Option, } +/// Materialised record for a single edge stored in the graph. #[derive(Clone, Debug)] pub struct EdgeRecord { pub id: EdgeId, @@ -35,6 +51,10 @@ pub struct EdgeRecord { pub payload: Option, } +/// Minimal in-memory graph store used by the rewrite executor tests. +/// +/// The production engine will eventually swap in a content-addressed store, +/// but this structure keeps the motion rewrite spike self-contained. #[derive(Default)] pub struct GraphStore { pub nodes: BTreeMap, @@ -42,31 +62,46 @@ pub struct GraphStore { } impl GraphStore { + /// Returns a shared reference to a node when it exists. pub fn node(&self, id: &NodeId) -> Option<&NodeRecord> { self.nodes.get(id) } + /// Returns an iterator over edges that originate from the provided node. pub fn edges_from(&self, id: &NodeId) -> impl Iterator { self.edges_from.get(id).into_iter().flatten() } + /// Returns a mutable reference to a node when it exists. pub fn node_mut(&mut self, id: &NodeId) -> Option<&mut NodeRecord> { self.nodes.get_mut(id) } + /// Inserts or replaces a node in the store. pub fn insert_node(&mut self, record: NodeRecord) { self.nodes.insert(record.id.clone(), record); } } +/// Pattern metadata used by a rewrite rule to describe the input graph shape. #[derive(Debug, Clone)] pub struct PatternGraph { pub nodes: Vec, } +/// Function pointer used to determine whether a rule matches the provided scope. pub type MatchFn = fn(&GraphStore, &NodeId) -> bool; + +/// Function pointer that applies a rewrite to the given scope. pub type ExecuteFn = fn(&mut GraphStore, &NodeId); +/// Descriptor for a rewrite rule registered with the engine. +/// +/// Each rule owns: +/// * a deterministic identifier (`id`) +/// * a human-readable name +/// * a left pattern (currently unused by the spike) +/// * callbacks for matching and execution pub struct RewriteRule { pub id: Hash, pub name: &'static str, @@ -75,9 +110,14 @@ pub struct RewriteRule { pub executor: ExecuteFn, } +/// Thin wrapper around an auto-incrementing transaction identifier. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct TxId(pub u64); +/// Snapshot returned after a successful commit. +/// +/// The `hash` value is deterministic and reflects the entire canonicalised +/// graph state (root + payloads). #[derive(Debug, Clone)] pub struct Snapshot { pub root: NodeId, @@ -86,11 +126,13 @@ pub struct Snapshot { pub tx: TxId, } +/// Ordering queue that guarantees rewrites execute deterministically. #[derive(Debug, Default)] pub struct DeterministicScheduler { pending: BTreeMap<(Hash, Hash), PendingRewrite>, } +/// Internal representation of a rewrite waiting to be applied. #[derive(Debug, Clone)] pub struct PendingRewrite { pub tx: TxId, @@ -98,18 +140,23 @@ pub struct PendingRewrite { pub scope: NodeId, } +/// Result of calling `Engine::apply`. #[derive(Debug)] pub enum ApplyResult { Applied, NoMatch, } +/// Errors emitted by the engine. #[derive(Debug, Error)] pub enum EngineError { #[error("transaction not found")] UnknownTx, } +/// Core rewrite engine used by the spike. +/// +/// It owns a `GraphStore`, the registered rules, and the deterministic scheduler. pub struct Engine { store: GraphStore, rules: HashMap<&'static str, RewriteRule>, @@ -120,6 +167,7 @@ pub struct Engine { } impl Engine { + /// Constructs a new engine with the supplied backing store and root node id. pub fn new(store: GraphStore, root: NodeId) -> Self { Self { store, @@ -131,15 +179,18 @@ impl Engine { } } + /// Registers a rewrite rule so it can be referenced by name. pub fn register_rule(&mut self, rule: RewriteRule) { self.rules.insert(rule.name, rule); } + /// Begins a new transaction and returns its identifier. pub fn begin(&mut self) -> TxId { self.tx_counter += 1; TxId(self.tx_counter) } + /// Queues a rewrite for execution if it matches the provided scope. pub fn apply( &mut self, tx: TxId, @@ -170,6 +221,7 @@ impl Engine { Ok(ApplyResult::Applied) } + /// Executes all pending rewrites for the transaction and produces a snapshot. pub fn commit(&mut self, tx: TxId) -> Result { if tx.0 == 0 || tx.0 > self.tx_counter { return Err(EngineError::UnknownTx); @@ -192,6 +244,7 @@ impl Engine { Ok(snapshot) } + /// Returns a snapshot for the current graph state without executing rewrites. pub fn snapshot(&self) -> Snapshot { let hash = compute_snapshot_hash(&self.store, &self.current_root); Snapshot { @@ -202,9 +255,17 @@ impl Engine { } } + /// Returns a shared view of a node when it exists. pub fn node(&self, id: &NodeId) -> Option<&NodeRecord> { self.store.node(id) } + + /// Inserts or replaces a node directly inside the store. + /// + /// The spike uses this to create motion entities prior to executing rewrites. + pub fn insert_node(&mut self, record: NodeRecord) { + self.store.insert_node(record); + } } impl Engine { @@ -273,7 +334,8 @@ impl DeterministicScheduler { } } -fn encode_position_velocity(position: [f32; 3], velocity: [f32; 3]) -> Bytes { +/// Serialises a 3D position + velocity vector pair into the canonical payload. +pub fn encode_motion_payload(position: [f32; 3], velocity: [f32; 3]) -> Bytes { let mut buf = Vec::with_capacity(POSITION_VELOCITY_BYTES); for value in position.into_iter().chain(velocity.into_iter()) { buf.extend_from_slice(&value.to_le_bytes()); @@ -281,7 +343,8 @@ fn encode_position_velocity(position: [f32; 3], velocity: [f32; 3]) -> Bytes { Bytes::from(buf) } -fn decode_position_velocity(bytes: &Bytes) -> Option<([f32; 3], [f32; 3])> { +/// Deserialises a canonical motion payload into (position, velocity) slices. +pub fn decode_motion_payload(bytes: &Bytes) -> Option<([f32; 3], [f32; 3])> { if bytes.len() != POSITION_VELOCITY_BYTES { return None; } @@ -294,10 +357,12 @@ fn decode_position_velocity(bytes: &Bytes) -> Option<([f32; 3], [f32; 3])> { Some((position, velocity)) } +/// Convenience helper for deriving `TypeId` values from human-readable labels. pub fn make_type_id(label: &str) -> TypeId { TypeId(hash_label(label)) } +/// Convenience helper for deriving `NodeId` values from human-readable labels. pub fn make_node_id(label: &str) -> NodeId { NodeId(hash_label(label)) } @@ -312,25 +377,31 @@ fn add_vec(a: [f32; 3], b: [f32; 3]) -> [f32; 3] { [a[0] + b[0], a[1] + b[1], a[2] + b[2]] } +/// Executor that updates the encoded position in the entity payload. fn motion_executor(store: &mut GraphStore, scope: &NodeId) { if let Some(record) = store.node_mut(scope) { if let Some(payload) = &record.payload { - if let Some((position, velocity)) = decode_position_velocity(payload) { - let updated = encode_position_velocity(add_vec(position, velocity), velocity); + if let Some((position, velocity)) = decode_motion_payload(payload) { + let updated = encode_motion_payload(add_vec(position, velocity), velocity); record.payload = Some(updated); } } } } +/// Matcher used by the motion rule to ensure the payload is well-formed. fn motion_matcher(store: &GraphStore, scope: &NodeId) -> bool { store .node(scope) .and_then(|record| record.payload.as_ref()) - .and_then(decode_position_velocity) + .and_then(decode_motion_payload) .is_some() } +/// Returns the built-in motion rule used by the spike. +/// +/// The rule advances an entity's position by its velocity; it is deliberately +/// deterministic so hash comparisons stay stable across independent executions. pub fn motion_rule() -> RewriteRule { let mut hasher = Hasher::new(); hasher.update(b"motion/update"); @@ -352,7 +423,7 @@ mod tests { fn motion_rule_updates_position_deterministically() { let entity = make_node_id("entity-1"); let entity_type = make_type_id("entity"); - let payload = encode_position_velocity([1.0, 2.0, 3.0], [0.5, -1.0, 0.25]); + let payload = encode_motion_payload([1.0, 2.0, 3.0], [0.5, -1.0, 0.25]); let mut store = GraphStore::default(); store.insert_node(NodeRecord { @@ -373,7 +444,7 @@ mod tests { // Run a second engine with identical initial state and ensure hashes match. let mut store_b = GraphStore::default(); - let payload_b = encode_position_velocity([1.0, 2.0, 3.0], [0.5, -1.0, 0.25]); + let payload_b = encode_motion_payload([1.0, 2.0, 3.0], [0.5, -1.0, 0.25]); store_b.insert_node(NodeRecord { id: entity.clone(), ty: entity_type, @@ -395,7 +466,7 @@ mod tests { .expect("entity exists") .payload .as_ref() - .and_then(decode_position_velocity) + .and_then(decode_motion_payload) .expect("payload decode"); assert_eq!(node.0, [1.5, 1.0, 3.25]); } diff --git a/crates/rmg-ffi/Cargo.toml b/crates/rmg-ffi/Cargo.toml index 475cc30..57e2eb1 100644 --- a/crates/rmg-ffi/Cargo.toml +++ b/crates/rmg-ffi/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] +rmg-core = { path = "../rmg-core" } diff --git a/crates/rmg-ffi/src/lib.rs b/crates/rmg-ffi/src/lib.rs index b93cf3f..5916126 100644 --- a/crates/rmg-ffi/src/lib.rs +++ b/crates/rmg-ffi/src/lib.rs @@ -1,14 +1,298 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right +//! C-compatible bindings for the motion rewrite spike. +//! +//! This module exposes a minimal ABI that higher-level languages (Lua, Python, +//! etc.) can use to interact with the deterministic engine without knowing the +//! internal Rust types. + +use std::ffi::CStr; +use std::os::raw::c_char; +use std::slice; + +use rmg_core::{ + ApplyResult, Engine, GraphStore, NodeId, NodeRecord, TxId, decode_motion_payload, + encode_motion_payload, make_node_id, make_type_id, motion_rule, +}; + +/// Opaque engine pointer exposed over the C ABI. +pub struct RmgEngine { + inner: Engine, +} + +/// 256-bit node identifier exposed as a raw byte array for FFI consumers. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct rmg_node_id { + pub bytes: [u8; 32], +} + +/// Transaction identifier mirrored on the C side. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct rmg_tx_id { + pub value: u64, +} + +/// Snapshot hash emitted after a successful commit. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct rmg_snapshot { + pub hash: [u8; 32], +} + +/// Creates a new engine with the motion rule registered. +#[unsafe(no_mangle)] +pub extern "C" fn rmg_engine_new() -> *mut RmgEngine { + let mut store = GraphStore::default(); + let root_id = make_node_id("world-root"); + let root_type = make_type_id("world"); + store.insert_node(NodeRecord { + id: root_id.clone(), + ty: root_type, + payload: None, + }); + + let mut engine = Engine::new(store, root_id); + engine.register_rule(motion_rule()); + + Box::into_raw(Box::new(RmgEngine { inner: engine })) +} + +/// Releases the engine allocation created by [`rmg_engine_new`]. +#[unsafe(no_mangle)] +pub extern "C" fn rmg_engine_free(engine: *mut RmgEngine) { + if engine.is_null() { + return; + } + unsafe { + drop(Box::from_raw(engine)); + } +} + +/// Spawns an entity with encoded motion data and returns its identifier. +#[unsafe(no_mangle)] +pub extern "C" fn rmg_engine_spawn_motion_entity( + engine: *mut RmgEngine, + label: *const c_char, + px: f32, + py: f32, + pz: f32, + vx: f32, + vy: f32, + vz: f32, + out_handle: *mut rmg_node_id, +) -> bool { + if engine.is_null() || label.is_null() || out_handle.is_null() { + return false; + } + let engine = unsafe { &mut *engine }; + let label = unsafe { CStr::from_ptr(label) }; + let label_str = match label.to_str() { + Ok(value) => value, + Err(_) => return false, + }; + + let node_id = make_node_id(label_str); + let entity_type = make_type_id("entity"); + let payload = encode_motion_payload([px, py, pz], [vx, vy, vz]); + + engine.inner.insert_node(NodeRecord { + id: node_id.clone(), + ty: entity_type, + payload: Some(payload), + }); + + unsafe { + (*out_handle).bytes = node_id.0; + } + true +} + +/// Starts a new transaction and returns its identifier. +#[unsafe(no_mangle)] +pub extern "C" fn rmg_engine_begin(engine: *mut RmgEngine) -> rmg_tx_id { + if engine.is_null() { + return rmg_tx_id { value: 0 }; + } + let engine = unsafe { &mut *engine }; + let tx = engine.inner.begin(); + rmg_tx_id { value: tx.0 } +} + +/// Applies the motion rewrite to the provided entity within transaction `tx`. +#[unsafe(no_mangle)] +pub extern "C" fn rmg_engine_apply_motion( + engine: *mut RmgEngine, + tx: rmg_tx_id, + node_handle: *const rmg_node_id, +) -> bool { + let engine = match unsafe { engine.as_mut() } { + Some(engine) => engine, + None => return false, + }; + if tx.value == 0 { + return false; + } + let node_id = match handle_to_node_id(node_handle) { + Some(id) => id, + None => return false, + }; + match engine + .inner + .apply(TxId(tx.value), "motion/update", &node_id) + { + Ok(ApplyResult::Applied) => true, + Ok(ApplyResult::NoMatch) => false, + Err(_) => false, + } +} + +/// Commits the transaction and writes the resulting snapshot hash. +#[unsafe(no_mangle)] +pub extern "C" fn rmg_engine_commit( + engine: *mut RmgEngine, + tx: rmg_tx_id, + out_snapshot: *mut rmg_snapshot, +) -> bool { + if engine.is_null() || out_snapshot.is_null() || tx.value == 0 { + return false; + } + let engine = unsafe { &mut *engine }; + match engine.inner.commit(TxId(tx.value)) { + Ok(snapshot) => { + unsafe { + (*out_snapshot).hash = snapshot.hash; + } + true + } + Err(_) => false, + } +} + +/// Reads the decoded position and velocity for an entity. +#[unsafe(no_mangle)] +pub extern "C" fn rmg_engine_read_motion( + engine: *mut RmgEngine, + node_handle: *const rmg_node_id, + out_position: *mut f32, + out_velocity: *mut f32, +) -> bool { + let engine = match unsafe { engine.as_mut() } { + Some(engine) => engine, + None => return false, + }; + if out_position.is_null() || out_velocity.is_null() { + return false; + } + let node_id = match handle_to_node_id(node_handle) { + Some(id) => id, + None => return false, + }; + let record = match engine.inner.node(&node_id) { + Some(record) => record, + None => return false, + }; + let payload = match record.payload.as_ref() { + Some(payload) => payload, + None => return false, + }; + let (position, velocity) = match decode_motion_payload(payload) { + Some(values) => values, + None => return false, + }; + copy_vec3(out_position, &position); + copy_vec3(out_velocity, &velocity); + true +} + +fn handle_to_node_id(handle: *const rmg_node_id) -> Option { + // Helper used internally by the ABI; callers pass raw bytes from C. + if handle.is_null() { + return None; + } + let bytes = unsafe { (*handle).bytes }; + Some(NodeId(bytes)) +} + +fn copy_vec3(ptr: *mut f32, values: &[f32; 3]) { + // Safety: callers guarantee `ptr` references a buffer with len >= 3. + unsafe { + let slice = slice::from_raw_parts_mut(ptr, 3); + slice.copy_from_slice(values); + } } #[cfg(test)] mod tests { use super::*; + use std::ffi::CString; + + unsafe fn spawn(engine: *mut RmgEngine, label: &str) -> rmg_node_id { + let c_label = CString::new(label).unwrap(); + let mut handle = rmg_node_id { bytes: [0; 32] }; + let ok = rmg_engine_spawn_motion_entity( + engine, + c_label.as_ptr(), + 1.0, + 2.0, + 3.0, + 0.5, + -1.0, + 0.25, + &mut handle as *mut _, + ); + assert!(ok); + handle + } + + #[test] + fn ffi_motion_rewrite_is_deterministic() { + unsafe { + let engine_a = rmg_engine_new(); + let handle_a = spawn(engine_a, "entity-ffi"); + let tx_a = rmg_engine_begin(engine_a); + assert!(rmg_engine_apply_motion( + engine_a, + tx_a, + &handle_a as *const _ + )); + let mut snap_a = rmg_snapshot { hash: [0; 32] }; + assert!(rmg_engine_commit(engine_a, tx_a, &mut snap_a as *mut _)); + + let engine_b = rmg_engine_new(); + let handle_b = spawn(engine_b, "entity-ffi"); + let tx_b = rmg_engine_begin(engine_b); + assert!(rmg_engine_apply_motion( + engine_b, + tx_b, + &handle_b as *const _ + )); + let mut snap_b = rmg_snapshot { hash: [0; 32] }; + assert!(rmg_engine_commit(engine_b, tx_b, &mut snap_b as *mut _)); + + assert_eq!(snap_a.hash, snap_b.hash); + + let mut position = [0f32; 3]; + let mut velocity = [0f32; 3]; + assert!(rmg_engine_read_motion( + engine_a, + &handle_a as *const _, + position.as_mut_ptr(), + velocity.as_mut_ptr() + )); + assert_eq!(position, [1.5, 1.0, 3.25]); + assert_eq!(velocity, [0.5, -1.0, 0.25]); + + rmg_engine_free(engine_a); + rmg_engine_free(engine_b); + } + } #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + fn ffi_apply_no_match_returns_false() { + let engine = rmg_engine_new(); + let tx = rmg_engine_begin(engine); + let bogus = rmg_node_id { bytes: [1; 32] }; + assert!(!rmg_engine_apply_motion(engine, tx, &bogus as *const _)); + rmg_engine_free(engine); } } diff --git a/crates/rmg-wasm/Cargo.toml b/crates/rmg-wasm/Cargo.toml index c22bb36..a9ece42 100644 --- a/crates/rmg-wasm/Cargo.toml +++ b/crates/rmg-wasm/Cargo.toml @@ -3,4 +3,12 @@ name = "rmg-wasm" version = "0.1.0" edition = "2024" +[lib] +crate-type = ["cdylib"] + [dependencies] +rmg-core = { path = "../rmg-core" } +wasm-bindgen = "0.2.95" + +[dev-dependencies] +wasm-bindgen-test = "0.3.42" diff --git a/crates/rmg-wasm/src/lib.rs b/crates/rmg-wasm/src/lib.rs index b93cf3f..84b0f51 100644 --- a/crates/rmg-wasm/src/lib.rs +++ b/crates/rmg-wasm/src/lib.rs @@ -1,14 +1,166 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right +//! wasm-bindgen bindings that expose the motion rewrite spike to tooling. +//! +//! The exported `WasmEngine` mirrors the C ABI surface so browser clients can +//! create entities, drive transactions, and read deterministic hashes. + +use std::cell::RefCell; +use std::rc::Rc; + +use rmg_core::{ + ApplyResult, Engine, GraphStore, NodeId, NodeRecord, TxId, decode_motion_payload, + encode_motion_payload, make_node_id, make_type_id, motion_rule, +}; +use wasm_bindgen::prelude::*; + +/// Builds a fresh engine with the motion rule pre-registered. +fn build_engine() -> Engine { + let mut store = GraphStore::default(); + let root_id = make_node_id("world-root"); + let root_type = make_type_id("world"); + store.insert_node(NodeRecord { + id: root_id.clone(), + ty: root_type, + payload: None, + }); + + let mut engine = Engine::new(store, root_id); + engine.register_rule(motion_rule()); + engine } -#[cfg(test)] +/// Converts a 32-byte buffer into a [`NodeId`]. +fn bytes_to_node_id(bytes: &[u8]) -> Option { + if bytes.len() != 32 { + return None; + } + let mut id = [0u8; 32]; + id.copy_from_slice(bytes); + Some(NodeId(id)) +} + +/// WASM-friendly wrapper around the deterministic engine. +#[wasm_bindgen] +pub struct WasmEngine { + inner: Rc>, +} + +#[wasm_bindgen] +impl WasmEngine { + #[wasm_bindgen(constructor)] + /// Creates a new engine with the motion rule registered. + pub fn new() -> WasmEngine { + WasmEngine { + inner: Rc::new(RefCell::new(build_engine())), + } + } + + #[wasm_bindgen] + /// Spawns an entity with encoded motion payload and returns its id bytes. + pub fn spawn_motion_entity( + &self, + label: &str, + px: f32, + py: f32, + pz: f32, + vx: f32, + vy: f32, + vz: f32, + ) -> Vec { + let mut engine = self.inner.borrow_mut(); + let node_id = make_node_id(label); + let entity_type = make_type_id("entity"); + let payload = encode_motion_payload([px, py, pz], [vx, vy, vz]); + + engine.insert_node(NodeRecord { + id: node_id.clone(), + ty: entity_type, + payload: Some(payload), + }); + + node_id.0.to_vec() + } + + #[wasm_bindgen] + /// Begins a new transaction and returns its identifier. + pub fn begin(&self) -> u64 { + self.inner.borrow_mut().begin().0 + } + + #[wasm_bindgen] + /// Applies the motion rewrite to the entity identified by `entity_id`. + pub fn apply_motion(&self, tx_id: u64, entity_id: &[u8]) -> bool { + if tx_id == 0 { + return false; + } + let node_id = match bytes_to_node_id(entity_id) { + Some(id) => id, + None => return false, + }; + let mut engine = self.inner.borrow_mut(); + match engine.apply(TxId(tx_id), "motion/update", &node_id) { + Ok(ApplyResult::Applied) => true, + Ok(ApplyResult::NoMatch) => false, + Err(_) => false, + } + } + + #[wasm_bindgen] + /// Commits the transaction and returns the resulting snapshot hash. + pub fn commit(&self, tx_id: u64) -> Option> { + if tx_id == 0 { + return None; + } + let mut engine = self.inner.borrow_mut(); + let snapshot = engine.commit(TxId(tx_id)).ok()?; + Some(snapshot.hash.to_vec()) + } + + #[wasm_bindgen] + /// Reads the decoded position/velocity tuple for the provided entity. + pub fn read_motion(&self, entity_id: &[u8]) -> Option> { + let engine = self.inner.borrow(); + let node_id = bytes_to_node_id(entity_id)?; + let record = engine.node(&node_id)?; + let payload = record.payload.as_ref()?; + let (position, velocity) = decode_motion_payload(payload)?; + let mut data = Vec::with_capacity(6); + data.extend_from_slice(&position); + data.extend_from_slice(&velocity); + Some(data.into_boxed_slice()) + } +} + +#[cfg(all(test, target_arch = "wasm32"))] mod tests { use super::*; + use wasm_bindgen_test::*; + + fn spawn(engine: &WasmEngine) -> Vec { + engine.spawn_motion_entity("entity-wasm", 1.0, 2.0, 3.0, 0.5, -1.0, 0.25) + } + + #[wasm_bindgen_test] + fn wasm_motion_is_deterministic() { + let engine_a = WasmEngine::new(); + let handle_a = spawn(&engine_a); + let tx_a = engine_a.begin(); + assert!(engine_a.apply_motion(tx_a, &handle_a)); + let hash_a = engine_a.commit(tx_a).expect("snapshot"); + + let engine_b = WasmEngine::new(); + let handle_b = spawn(&engine_b); + let tx_b = engine_b.begin(); + assert!(engine_b.apply_motion(tx_b, &handle_b)); + let hash_b = engine_b.commit(tx_b).expect("snapshot"); + + assert_eq!(hash_a, hash_b); - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + let motion = engine_a.read_motion(&handle_a).expect("motion payload"); + assert!((motion[0] - 1.5).abs() < 1e-6); + assert!((motion[1] - 1.0).abs() < 1e-6); + assert!((motion[2] - 3.25).abs() < 1e-6); + assert!((motion[3] - 0.5).abs() < 1e-6); + assert!((motion[4] + 1.0).abs() < 1e-6); + assert!((motion[5] - 0.25).abs() < 1e-6); } } From 9e26d63fcc257eb5709a34acaa91b3f7547285d5 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Fri, 24 Oct 2025 21:15:14 -0700 Subject: [PATCH 09/18] enforce docs lint across engine Turn on \#![deny(missing_docs)] for core/ffi/wasm, documented fields/variants, and described unsafe FFI expectations. Added rustdoc header to the CLI stub and Default for WasmEngine. Working agreement now requires running `cargo clippy --all-targets -- -D missing_docs` before PRs. --- AGENTS.md | 1 + crates/rmg-cli/src/main.rs | 2 ++ crates/rmg-core/src/lib.rs | 40 +++++++++++++++++---- crates/rmg-ffi/src/lib.rs | 72 ++++++++++++++++++++++++++------------ crates/rmg-wasm/src/lib.rs | 7 ++++ 5 files changed, 93 insertions(+), 29 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c9d03bc..c34e39d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,6 +58,7 @@ Use `messages-search --text "Echo"` for ad-hoc queries. - Tests and benchmarks are mandatory for runtime changes once the harness exists. - Update the Neo4j log before you down tools, even if the work is incomplete. - Respect determinism: preferably no random seeds without going through the Echo PRNG. +- Run `cargo clippy --all-targets -- -D missing_docs` and `cargo test` before every PR; CI will expect a zero-warning, fully documented surface. ## Contact Threads - Neo4j Thread `echo-devlog`: Daily journal, decisions, blockers. diff --git a/crates/rmg-cli/src/main.rs b/crates/rmg-cli/src/main.rs index e7a11a9..b8e3e1f 100644 --- a/crates/rmg-cli/src/main.rs +++ b/crates/rmg-cli/src/main.rs @@ -1,3 +1,5 @@ +//! Echo CLI placeholder. + fn main() { println!("Hello, world!"); } diff --git a/crates/rmg-core/src/lib.rs b/crates/rmg-core/src/lib.rs index 058893c..d114c6f 100644 --- a/crates/rmg-core/src/lib.rs +++ b/crates/rmg-core/src/lib.rs @@ -1,4 +1,5 @@ //! rmg-core: typed deterministic graph rewriting engine. +#![deny(missing_docs)] use std::collections::{BTreeMap, HashMap}; @@ -36,18 +37,26 @@ pub struct EdgeId(pub Hash); /// attachments, etc) and is interpreted by higher layers. #[derive(Clone, Debug)] pub struct NodeRecord { + /// Stable identifier for the node. pub id: NodeId, + /// Type identifier describing the node. pub ty: TypeId, + /// Optional payload owned by the node (component data, attachments, etc.). pub payload: Option, } /// Materialised record for a single edge stored in the graph. #[derive(Clone, Debug)] pub struct EdgeRecord { + /// Stable identifier for the edge. pub id: EdgeId, + /// Source node identifier. pub from: NodeId, + /// Destination node identifier. pub to: NodeId, + /// Type identifier describing the edge. pub ty: TypeId, + /// Optional payload owned by the edge. pub payload: Option, } @@ -57,7 +66,9 @@ pub struct EdgeRecord { /// but this structure keeps the motion rewrite spike self-contained. #[derive(Default)] pub struct GraphStore { + /// Mapping from node identifiers to their materialised records. pub nodes: BTreeMap, + /// Mapping from source node to outbound edge records. pub edges_from: BTreeMap>, } @@ -86,6 +97,7 @@ impl GraphStore { /// Pattern metadata used by a rewrite rule to describe the input graph shape. #[derive(Debug, Clone)] pub struct PatternGraph { + /// Ordered list of type identifiers that make up the pattern. pub nodes: Vec, } @@ -103,10 +115,15 @@ pub type ExecuteFn = fn(&mut GraphStore, &NodeId); /// * a left pattern (currently unused by the spike) /// * callbacks for matching and execution pub struct RewriteRule { + /// Deterministic identifier for the rewrite rule. pub id: Hash, + /// Human-readable name for logs and debugging. pub name: &'static str, + /// Pattern used to describe the left-hand side of the rule. pub left: PatternGraph, + /// Callback that determines whether the rule matches a given scope. pub matcher: MatchFn, + /// Callback that applies the rewrite to the given scope. pub executor: ExecuteFn, } @@ -120,9 +137,13 @@ pub struct TxId(pub u64); /// graph state (root + payloads). #[derive(Debug, Clone)] pub struct Snapshot { + /// Node identifier that serves as the root of the snapshot. pub root: NodeId, + /// Canonical hash derived from the entire graph state. pub hash: Hash, + /// Optional parent snapshot hash (if one exists). pub parent: Option, + /// Transaction identifier associated with the snapshot. pub tx: TxId, } @@ -135,21 +156,27 @@ pub struct DeterministicScheduler { /// Internal representation of a rewrite waiting to be applied. #[derive(Debug, Clone)] pub struct PendingRewrite { + /// Transaction identifier that enqueued the rewrite. pub tx: TxId, + /// Identifier of the rule to execute. pub rule_id: Hash, + /// Scope node supplied when `apply` was invoked. pub scope: NodeId, } /// Result of calling `Engine::apply`. #[derive(Debug)] pub enum ApplyResult { + /// The rewrite matched and was enqueued for execution. Applied, + /// The rewrite did not match the provided scope. NoMatch, } /// Errors emitted by the engine. #[derive(Debug, Error)] pub enum EngineError { + /// The supplied transaction identifier did not exist or was already closed. #[error("transaction not found")] UnknownTx, } @@ -379,13 +406,12 @@ fn add_vec(a: [f32; 3], b: [f32; 3]) -> [f32; 3] { /// Executor that updates the encoded position in the entity payload. fn motion_executor(store: &mut GraphStore, scope: &NodeId) { - if let Some(record) = store.node_mut(scope) { - if let Some(payload) = &record.payload { - if let Some((position, velocity)) = decode_motion_payload(payload) { - let updated = encode_motion_payload(add_vec(position, velocity), velocity); - record.payload = Some(updated); - } - } + if let Some(record) = store.node_mut(scope) + && let Some(payload) = &record.payload + && let Some((position, velocity)) = decode_motion_payload(payload) + { + let updated = encode_motion_payload(add_vec(position, velocity), velocity); + record.payload = Some(updated); } } diff --git a/crates/rmg-ffi/src/lib.rs b/crates/rmg-ffi/src/lib.rs index 5916126..630f987 100644 --- a/crates/rmg-ffi/src/lib.rs +++ b/crates/rmg-ffi/src/lib.rs @@ -3,6 +3,7 @@ //! This module exposes a minimal ABI that higher-level languages (Lua, Python, //! etc.) can use to interact with the deterministic engine without knowing the //! internal Rust types. +#![deny(missing_docs)] use std::ffi::CStr; use std::os::raw::c_char; @@ -22,6 +23,7 @@ pub struct RmgEngine { #[repr(C)] #[derive(Clone, Copy)] pub struct rmg_node_id { + /// Raw bytes representing the hashed node identifier. pub bytes: [u8; 32], } @@ -29,6 +31,7 @@ pub struct rmg_node_id { #[repr(C)] #[derive(Clone, Copy)] pub struct rmg_tx_id { + /// Native transaction value. pub value: u64, } @@ -36,6 +39,7 @@ pub struct rmg_tx_id { #[repr(C)] #[derive(Clone, Copy)] pub struct rmg_snapshot { + /// Canonical hash bytes for the snapshot. pub hash: [u8; 32], } @@ -58,8 +62,12 @@ pub extern "C" fn rmg_engine_new() -> *mut RmgEngine { } /// Releases the engine allocation created by [`rmg_engine_new`]. +/// +/// # Safety +/// `engine` must be a pointer previously returned by [`rmg_engine_new`] that +/// has not already been freed. #[unsafe(no_mangle)] -pub extern "C" fn rmg_engine_free(engine: *mut RmgEngine) { +pub unsafe extern "C" fn rmg_engine_free(engine: *mut RmgEngine) { if engine.is_null() { return; } @@ -69,8 +77,12 @@ pub extern "C" fn rmg_engine_free(engine: *mut RmgEngine) { } /// Spawns an entity with encoded motion data and returns its identifier. +/// +/// # Safety +/// `engine`, `label`, and `out_handle` must be valid pointers. `label` must +/// reference a null-terminated string. #[unsafe(no_mangle)] -pub extern "C" fn rmg_engine_spawn_motion_entity( +pub unsafe extern "C" fn rmg_engine_spawn_motion_entity( engine: *mut RmgEngine, label: *const c_char, px: f32, @@ -108,8 +120,11 @@ pub extern "C" fn rmg_engine_spawn_motion_entity( } /// Starts a new transaction and returns its identifier. +/// +/// # Safety +/// `engine` must be a valid pointer created by [`rmg_engine_new`]. #[unsafe(no_mangle)] -pub extern "C" fn rmg_engine_begin(engine: *mut RmgEngine) -> rmg_tx_id { +pub unsafe extern "C" fn rmg_engine_begin(engine: *mut RmgEngine) -> rmg_tx_id { if engine.is_null() { return rmg_tx_id { value: 0 }; } @@ -119,8 +134,11 @@ pub extern "C" fn rmg_engine_begin(engine: *mut RmgEngine) -> rmg_tx_id { } /// Applies the motion rewrite to the provided entity within transaction `tx`. +/// +/// # Safety +/// All pointers must be valid. `tx` must reference an active transaction. #[unsafe(no_mangle)] -pub extern "C" fn rmg_engine_apply_motion( +pub unsafe extern "C" fn rmg_engine_apply_motion( engine: *mut RmgEngine, tx: rmg_tx_id, node_handle: *const rmg_node_id, @@ -147,8 +165,11 @@ pub extern "C" fn rmg_engine_apply_motion( } /// Commits the transaction and writes the resulting snapshot hash. +/// +/// # Safety +/// Pointers must be valid; `tx` must correspond to a live transaction. #[unsafe(no_mangle)] -pub extern "C" fn rmg_engine_commit( +pub unsafe extern "C" fn rmg_engine_commit( engine: *mut RmgEngine, tx: rmg_tx_id, out_snapshot: *mut rmg_snapshot, @@ -169,8 +190,11 @@ pub extern "C" fn rmg_engine_commit( } /// Reads the decoded position and velocity for an entity. +/// +/// # Safety +/// Pointers must be valid; output buffers must have length at least three. #[unsafe(no_mangle)] -pub extern "C" fn rmg_engine_read_motion( +pub unsafe extern "C" fn rmg_engine_read_motion( engine: *mut RmgEngine, node_handle: *const rmg_node_id, out_position: *mut f32, @@ -229,17 +253,19 @@ mod tests { unsafe fn spawn(engine: *mut RmgEngine, label: &str) -> rmg_node_id { let c_label = CString::new(label).unwrap(); let mut handle = rmg_node_id { bytes: [0; 32] }; - let ok = rmg_engine_spawn_motion_entity( - engine, - c_label.as_ptr(), - 1.0, - 2.0, - 3.0, - 0.5, - -1.0, - 0.25, - &mut handle as *mut _, - ); + let ok = unsafe { + rmg_engine_spawn_motion_entity( + engine, + c_label.as_ptr(), + 1.0, + 2.0, + 3.0, + 0.5, + -1.0, + 0.25, + &mut handle as *mut _, + ) + }; assert!(ok); handle } @@ -289,10 +315,12 @@ mod tests { #[test] fn ffi_apply_no_match_returns_false() { - let engine = rmg_engine_new(); - let tx = rmg_engine_begin(engine); - let bogus = rmg_node_id { bytes: [1; 32] }; - assert!(!rmg_engine_apply_motion(engine, tx, &bogus as *const _)); - rmg_engine_free(engine); + unsafe { + let engine = rmg_engine_new(); + let tx = rmg_engine_begin(engine); + let bogus = rmg_node_id { bytes: [1; 32] }; + assert!(!rmg_engine_apply_motion(engine, tx, &bogus as *const _)); + rmg_engine_free(engine); + } } } diff --git a/crates/rmg-wasm/src/lib.rs b/crates/rmg-wasm/src/lib.rs index 84b0f51..9c9161a 100644 --- a/crates/rmg-wasm/src/lib.rs +++ b/crates/rmg-wasm/src/lib.rs @@ -2,6 +2,7 @@ //! //! The exported `WasmEngine` mirrors the C ABI surface so browser clients can //! create entities, drive transactions, and read deterministic hashes. +#![deny(missing_docs)] use std::cell::RefCell; use std::rc::Rc; @@ -44,6 +45,12 @@ pub struct WasmEngine { inner: Rc>, } +impl Default for WasmEngine { + fn default() -> Self { + Self::new() + } +} + #[wasm_bindgen] impl WasmEngine { #[wasm_bindgen(constructor)] From 1f87e292d3ff7a5559715a90c14de5372a03f37d Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Fri, 24 Oct 2025 23:16:35 -0700 Subject: [PATCH 10/18] document bootstrap status in rmg-core Add explicit warning that Engine::commit() is a placeholder and remove unused Clone derives so the API reflects the current skeleton. --- crates/rmg-core/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/rmg-core/src/lib.rs b/crates/rmg-core/src/lib.rs index d114c6f..52997d6 100644 --- a/crates/rmg-core/src/lib.rs +++ b/crates/rmg-core/src/lib.rs @@ -1,4 +1,7 @@ //! rmg-core: typed deterministic graph rewriting engine. +//! +//! **WARNING**: This is a Phase 0 bootstrap skeleton. The rewrite executor is not implemented. +//! `Engine::commit()` currently discards pending rewrites without executing them. #![deny(missing_docs)] use std::collections::{BTreeMap, HashMap}; @@ -95,7 +98,7 @@ impl GraphStore { } /// Pattern metadata used by a rewrite rule to describe the input graph shape. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct PatternGraph { /// Ordered list of type identifiers that make up the pattern. pub nodes: Vec, @@ -154,7 +157,7 @@ pub struct DeterministicScheduler { } /// Internal representation of a rewrite waiting to be applied. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct PendingRewrite { /// Transaction identifier that enqueued the rewrite. pub tx: TxId, From ccae82b3329f5794a9240d621abd92fd1b56cb7f Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Fri, 24 Oct 2025 23:17:06 -0700 Subject: [PATCH 11/18] sync roadmap and docs with new tooling Update scheduler/testing docs for Criterion benches, expand demo roadmap with detailed milestones, convert ad-hoc bold headings to Markdown headings, and clarify future playground references. --- docs/diagrams.md | 2 +- docs/rmg-demo-roadmap.md | 72 ++++++++++++++++++++++++++++++-- docs/rmg-runtime-architecture.md | 4 ++ docs/rust-lua-ts-division.md | 16 +++---- docs/scheduler-benchmarks.md | 6 +-- docs/testing-and-replay-plan.md | 2 +- 6 files changed, 85 insertions(+), 17 deletions(-) diff --git a/docs/diagrams.md b/docs/diagrams.md index 4f00ca3..b83a677 100644 --- a/docs/diagrams.md +++ b/docs/diagrams.md @@ -190,4 +190,4 @@ sequenceDiagram - **Entropy Pulse**: Animate stroke width/color based on the Entropy meter. - **Interactive Sequencer**: Play back the sequence diagram with tooltips showing Codex queue sizes. -Once the architecture crystallizes, we’ll wire these into a `docs/viewer/` playground that live-updates from this Markdown. +Once the architecture crystallizes, we’ll wire these into a future documentation viewer/playground that live-updates from this Markdown. diff --git a/docs/rmg-demo-roadmap.md b/docs/rmg-demo-roadmap.md index 93022cc..af9fa37 100644 --- a/docs/rmg-demo-roadmap.md +++ b/docs/rmg-demo-roadmap.md @@ -8,8 +8,72 @@ This document captures the interactive demos and performance milestones we want **Goal:** Show two instances running locally in lockstep and prove graph hash equality every frame. -- Two Echo instances (no network) consume identical input streams. -- Each frame emits a “state hash” (BLAKE3 snapshot hash) displayed side-by-side. -- Validation: graph hashes match every tick, proving deterministic state. -- Feature: “frame hash” becomes a first-class inspector metric. +- Two Echo instances (no network) consume identical input streams generated from a shared seed (deterministic RNG feeding input script). +- Each frame serialises the world graph in canonical order (sorted node/edge IDs, component payload bytes) and hashes it with BLAKE3 to produce the “frame hash”. +- Inspectors display the frame hashes side-by-side and flag divergence immediately. Success = 100% equality across a 10 000-frame run. +- Determinism safeguards: freeze wall clock, mock OS timers, clamp floating-point math to deterministic fixed-point helpers, forbid nondeterministic APIs. +- Output artifact: JSON trace (`frame`, `hash`, `inputs_consumed`) plus a screenshot/video for the showcase. +## Demo 2: Scheduler Rewrite Benchmark + +**Goal:** Benchmark the rewrite executor under scripted workloads. + +- Criterion-based benches exercise flat, chained, branching, and timeline-flush scenarios (mirrors `docs/scheduler-benchmarks.md`). +- Success criteria: median tick time < 0.5 ms for toy workload (100 entities, 10 rules); percentile tails recorded. +- Bench harness outputs JSON summaries (mean, median, std dev) consumed by the inspector and appended to the decision log. +- Deterministic PRNG seeds recorded so benches are reproducible across CI machines. + +## Demo 3: Timeline Fork/Merge Replay + +**Goal:** Demonstrate branching timelines, paradox detection, and canonical merges. + +- Start from a baseline snapshot, fork into three branches with scripted rewrites, deliberately introduce a conflict on one branch. +- Inspector view shows divergence tree, entropy deltas, and paradox quarantine in real time. +- Success criteria: merge replay produces the documented canonical hash, paradox branch quarantined with deterministic error log, entropy metrics trend as expected. +- Deliverable: recorded replay plus JSON report showing branch IDs, merge decisions, and resulting hashes. + +## Demo 4: Lua Live Coding Loop + +**Goal:** Prove Lua bindings support hot reload without breaking determinism. + +- Script registers a system that increments a component each tick; developer edits Lua code mid-run via CLI hot-reload. +- Engine stages rewrite intents from Lua through the FFI; after reload, replay the prior ticks to confirm deterministic equivalence. +- Success: frame hashes before/after reload identical when replayed from the same snapshot; inspector shows live diff of system graphs. +- Includes integration test capturing reload latency budget (< 50 ms) and ensuring queued rewrites survive reload boundary. + +## Demo 5: Confluence Sync Showcase + +**Goal:** Synchronise two peers via rewrite transactions, demonstrating deterministic convergence. + +- Peer A applies scripted rewrites while offline, then pushes transactions to Peer B via the Confluence protocol. +- Both peers compute snapshot hashes before/after sync; success when hashes converge with zero conflicts. +- Includes failure injection (duplicate transaction, out-of-order delivery) to show deterministic resolution path. +- Inspector UI plots sync throughput (transactions/sec) and latency. + +## Success Criteria Summary + +- **Frame Hash Integrity:** For Demo 1 and Demo 3, identical BLAKE3 hashes across peers/branches every tick. Any discrepancy fails the demo. +- **Input Stream Discipline:** Inputs recorded as timestamped events with deterministic seeds. Replay harness reuses the same log to verify determinism. +- **Floating-Point Policy:** All demos rely on fixed-point math or deterministic float wrappers; document configuration in README. +- **Performance Targets:** + - Demo 1: tick time ≤ 2 ms on reference hardware (M2 Pro / 32 GB). + - Demo 2: criterion bench median ≤ 0.5 ms; 99th percentile ≤ 1.0 ms. + - Demo 5: sync 10 000 transactions in under 2 s with zero conflicts. + +## Roadmap / Dependencies + +| Phase | Demo Coverage | Dependencies | +| ----- | ------------- | ------------- | +| 1A | Demo 2 harness scaffolding | Criterion setup, synthetic rewrite fixtures | +| 1B | Demo 1 prototype (local hash) | Motion rewrite spike, snapshot hashing | +| 1C | Demo 4 Lua API | `rmg-ffi` bindings, hot-reload CLI | +| 1D | Demo 3 timeline tooling | Branch tree diff viewer, entropy metrics | +| 1E | Demo 5 networking | Confluence transaction protocol, replay verification | +| 1F | Demo dashboards | Inspector frame overlays, JSON ingestion | + +**Prerequisites:** BLAKE3 hashing utilities, deterministic PRNG module, snapshot serialiser, inspector graph viewer, Neo4j logging for demo outcomes, CI runners with wasm/criterion toolchains. + +**Timeline:** +- Milestone Alpha (end 1B): Demo 1 frame-hash prototype + Demo 2 toy bench executed manually. +- Milestone Beta (end 1D): Demos 1–3 automated in CI with golden outputs. +- Milestone GA (end 1F): Full demo suite (all five) runnable via `cargo xtask demo` and published as part of release notes. diff --git a/docs/rmg-runtime-architecture.md b/docs/rmg-runtime-architecture.md index 5bcd898..0f36dac 100644 --- a/docs/rmg-runtime-architecture.md +++ b/docs/rmg-runtime-architecture.md @@ -18,6 +18,10 @@ This document captures the consensus that emerged for Echo’s Phase 1 implement ## Tick Loop (Deterministic Scheduler) +> **Note**: This is the target Phase 1 API design. The current `rmg-core` crate +> is a bootstrap skeleton; consult `crates/rmg-core/src/lib.rs` for the working +> interfaces. + ```rust loop { let tx = engine.begin(); diff --git a/docs/rust-lua-ts-division.md b/docs/rust-lua-ts-division.md index 68602b0..2843fc7 100644 --- a/docs/rust-lua-ts-division.md +++ b/docs/rust-lua-ts-division.md @@ -6,7 +6,7 @@ Echo’s runtime stack is intentionally stratified. Rust owns the deterministic ## Rust (rmg-core, ffi, wasm, cli) -**Responsibilities** +### Responsibilities - RMG engine: GraphStore, PatternGraph, RewriteRule, DeterministicScheduler, commit/Snapshot APIs. - ECS foundations: Worlds, Systems, Components expressed as rewrite rules. - Timeline & Branch tree: rewrite transactions, snapshot hashing, concurrency guard rails. @@ -17,7 +17,7 @@ Echo’s runtime stack is intentionally stratified. Rust owns the deterministic - Lua VM hosting: embed Lua 5.4, expose RMG bindings via FFI. - CLI tools: `rmg` command for apply/snapshot/diff/verify. -**Key Crates** +### Key Crates - `rmg-core` – core engine - `rmg-ffi` – C ABI for Lua and other native consumers - `rmg-wasm` – WASM build for tooling/editor @@ -27,18 +27,18 @@ Echo’s runtime stack is intentionally stratified. Rust owns the deterministic ## Lua (gameplay authoring layer) -**Responsibilities** +### Responsibilities - Gameplay systems & components (e.g., AI state machines, quests, input handling). - Component registration, entity creation/destruction via exposed APIs. - Scripting for deterministic “async” (scheduled events through Codex’s Baby). - Editor lenses and inspector overlays written in Lua for rapid iteration. -**Constraints** +### Constraints - Single-threaded per branch; no OS threads. - GC runs in deterministic stepped mode, bounded per tick. - Mutations occur through rewrite intents (`rmg.apply(...)`), not raw memory access. -**Bindings** +### Bindings - `rmg` Lua module providing: - `apply(rule_name, scope, params)` - `delay(seconds, fn)` (schedules replay-safe events) @@ -49,19 +49,19 @@ Echo’s runtime stack is intentionally stratified. Rust owns the deterministic ## TypeScript / Web Tooling -**Responsibilities** +### Responsibilities - Echo Studio (graph IDE) – visualizes world graph, rewrites, branch tree. - Inspector dashboards – display Codex, entropy, paradox frames. - Replay/rollback visualizers, network debugging tools. - Plugin builders and determinism test harness UI. -**Integration** +### Integration - Uses `rmg-wasm` to call into RMG engine from the browser. - IPC/WebSocket for live inspector feeds (`InspectorEnvelope`). - Works with JSONL logs for offline analysis. - All mutations go through bindings; tooling never mutates state outside RMG APIs. -**Tech** +### Tech - Frontend frameworks: React/Svelte/Vanilla as needed. - WebGPU/WebGL for graph visualization. - TypeScript ensures type safety for tooling code. diff --git a/docs/scheduler-benchmarks.md b/docs/scheduler-benchmarks.md index e88b8fd..a4aa042 100644 --- a/docs/scheduler-benchmarks.md +++ b/docs/scheduler-benchmarks.md @@ -41,15 +41,15 @@ Objective: validate the scheduler design under realistic workloads before full i --- ## Tooling -- Use Vitest benchmarks (`@vitest/benchmark`) or simple `performance.now()` wrappers. -- Provide CLI entry point in `packages/echo-core/scripts/bench-scheduler.ts`. +- Use Criterion for Rust benchmarks with statistical analysis. +- Benchmarks live in `tests/benchmarks/scheduler.rs` (or similar crate structure). - Output results as JSON for inspector consumption. - Reuse deterministic math PRNG for synthetic workload generation. --- ## Tasks -- [ ] Scaffold Rust benchmark harness (`cargo bench --bench scheduler`). +- [ ] TODO: Implement scheduler benchmark harness (tracked for Phase 1 once Criterion benches land). - [ ] Implement mock system descriptors for each scenario. - [ ] Integrate with timeline fingerprint to simulate branches. - [ ] Record baseline numbers in docs and add to decision log. diff --git a/docs/testing-and-replay-plan.md b/docs/testing-and-replay-plan.md index 2728a1b..75c0d1b 100644 --- a/docs/testing-and-replay-plan.md +++ b/docs/testing-and-replay-plan.md @@ -53,7 +53,7 @@ interface VerificationReport { - `cargo test --package rmg-core --test paradox` – injects artificial read/write overlaps to validate quarantine behavior. - `cargo test --package rmg-core --test entropy` – verifies entropy observers and metrics. - `cargo test --package rmg-core --test bridge` – covers temporal bridge retro/reroute. -- `cargo bench --package rmg-core --bench scheduler` – ensures performance regressions are recorded (once benches enabled). +- TODO: Add Criterion-based scheduler benches to CI once implemented (Phase 1 task). --- From 9e3f15cfa1b1a2a1a51f23a692736678404af1ef Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Sat, 25 Oct 2025 00:51:14 -0700 Subject: [PATCH 12/18] tidy matcher flow in apply --- crates/rmg-core/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/rmg-core/src/lib.rs b/crates/rmg-core/src/lib.rs index 52997d6..ed0af60 100644 --- a/crates/rmg-core/src/lib.rs +++ b/crates/rmg-core/src/lib.rs @@ -234,7 +234,8 @@ impl Engine { Some(rule) => rule, None => return Ok(ApplyResult::NoMatch), }; - if !(rule.matcher)(&self.store, scope) { + let matches = (rule.matcher)(&self.store, scope); + if !matches { return Ok(ApplyResult::NoMatch); } From 819ff93c6af97181b27404928e22c92668c41a6b Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Sat, 25 Oct 2025 00:52:04 -0700 Subject: [PATCH 13/18] document CLI crate intent --- crates/rmg-cli/src/main.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/rmg-cli/src/main.rs b/crates/rmg-cli/src/main.rs index b8e3e1f..902ebf5 100644 --- a/crates/rmg-cli/src/main.rs +++ b/crates/rmg-cli/src/main.rs @@ -1,4 +1,17 @@ -//! Echo CLI placeholder. +//! Echo CLI entrypoint. +//! +//! Provides developer-facing commands for working with Echo projects. Planned +//! subcommands include `echo demo` (run deterministic demo suites), `echo +//! bench` (execute Criterion benchmarks), and `echo inspect` (open the +//! inspector tooling). +//! +//! # Usage +//! ```text +//! echo [options] +//! ``` +//! +//! The CLI exits with code `0` on success and non-zero on error. Until the +//! subcommands are implemented the binary simply prints a placeholder message. fn main() { println!("Hello, world!"); From 0431d8e8549b88ee8ebe8bd175ed59218a67cadf Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Sat, 25 Oct 2025 01:03:51 -0700 Subject: [PATCH 14/18] normalize store API and wasm/ffi bootstrap Avoid NodeId cloning by restructuring GraphStore/Engine insert APIs, drop duplicate snapshot hashing, add shared build_motion_demo_engine helper, update ffi/wasm bootstraps, add wasm release profile and panic-hook feature, and return Uint8Array from spawn. --- Cargo.lock | 12 +++++++ crates/rmg-core/src/lib.rs | 65 +++++++++++++++++++++++++------------- crates/rmg-ffi/Cargo.toml | 3 ++ crates/rmg-ffi/src/lib.rs | 32 +++++++------------ crates/rmg-wasm/Cargo.toml | 12 +++++++ crates/rmg-wasm/src/lib.rs | 44 +++++++++++++------------- 6 files changed, 104 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a310cf..784778e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -141,6 +151,8 @@ dependencies = [ name = "rmg-wasm" version = "0.1.0" dependencies = [ + "console_error_panic_hook", + "js-sys", "rmg-core", "wasm-bindgen", "wasm-bindgen-test", diff --git a/crates/rmg-core/src/lib.rs b/crates/rmg-core/src/lib.rs index ed0af60..89eaf43 100644 --- a/crates/rmg-core/src/lib.rs +++ b/crates/rmg-core/src/lib.rs @@ -40,8 +40,6 @@ pub struct EdgeId(pub Hash); /// attachments, etc) and is interpreted by higher layers. #[derive(Clone, Debug)] pub struct NodeRecord { - /// Stable identifier for the node. - pub id: NodeId, /// Type identifier describing the node. pub ty: TypeId, /// Optional payload owned by the node (component data, attachments, etc.). @@ -92,8 +90,8 @@ impl GraphStore { } /// Inserts or replaces a node in the store. - pub fn insert_node(&mut self, record: NodeRecord) { - self.nodes.insert(record.id.clone(), record); + pub fn insert_node(&mut self, id: NodeId, record: NodeRecord) { + self.nodes.insert(id, record); } } @@ -294,8 +292,8 @@ impl Engine { /// Inserts or replaces a node directly inside the store. /// /// The spike uses this to create motion entities prior to executing rewrites. - pub fn insert_node(&mut self, record: NodeRecord) { - self.store.insert_node(record); + pub fn insert_node(&mut self, id: NodeId, record: NodeRecord) { + self.store.insert_node(id, record); } } @@ -346,7 +344,6 @@ fn compute_snapshot_hash(store: &GraphStore, root: &NodeId) -> Hash { } } } - hasher.update(&root.0); hasher.finalize().into() } @@ -445,6 +442,24 @@ pub fn motion_rule() -> RewriteRule { } } +/// Builds an engine with the default world root and the motion rule registered. +pub fn build_motion_demo_engine() -> Engine { + let mut store = GraphStore::default(); + let root_id = make_node_id("world-root"); + let root_type = make_type_id("world"); + store.insert_node( + root_id.clone(), + NodeRecord { + ty: root_type, + payload: None, + }, + ); + + let mut engine = Engine::new(store, root_id); + engine.register_rule(motion_rule()); + engine +} + #[cfg(test)] mod tests { use super::*; @@ -456,11 +471,13 @@ mod tests { let payload = encode_motion_payload([1.0, 2.0, 3.0], [0.5, -1.0, 0.25]); let mut store = GraphStore::default(); - store.insert_node(NodeRecord { - id: entity.clone(), - ty: entity_type, - payload: Some(payload), - }); + store.insert_node( + entity.clone(), + NodeRecord { + ty: entity_type, + payload: Some(payload), + }, + ); let mut engine = Engine::new(store, entity.clone()); engine.register_rule(motion_rule()); @@ -475,11 +492,13 @@ mod tests { // Run a second engine with identical initial state and ensure hashes match. let mut store_b = GraphStore::default(); let payload_b = encode_motion_payload([1.0, 2.0, 3.0], [0.5, -1.0, 0.25]); - store_b.insert_node(NodeRecord { - id: entity.clone(), - ty: entity_type, - payload: Some(payload_b), - }); + store_b.insert_node( + entity.clone(), + NodeRecord { + ty: entity_type, + payload: Some(payload_b), + }, + ); let mut engine_b = Engine::new(store_b, entity.clone()); engine_b.register_rule(motion_rule()); @@ -507,11 +526,13 @@ mod tests { let entity_type = make_type_id("entity"); let mut store = GraphStore::default(); - store.insert_node(NodeRecord { - id: entity.clone(), - ty: entity_type, - payload: None, - }); + store.insert_node( + entity.clone(), + NodeRecord { + ty: entity_type, + payload: None, + }, + ); let mut engine = Engine::new(store, entity.clone()); engine.register_rule(motion_rule()); diff --git a/crates/rmg-ffi/Cargo.toml b/crates/rmg-ffi/Cargo.toml index 57e2eb1..c134101 100644 --- a/crates/rmg-ffi/Cargo.toml +++ b/crates/rmg-ffi/Cargo.toml @@ -3,5 +3,8 @@ name = "rmg-ffi" version = "0.1.0" edition = "2024" +[lib] +crate-type = ["rlib", "cdylib", "staticlib"] + [dependencies] rmg-core = { path = "../rmg-core" } diff --git a/crates/rmg-ffi/src/lib.rs b/crates/rmg-ffi/src/lib.rs index 630f987..bf0f3fe 100644 --- a/crates/rmg-ffi/src/lib.rs +++ b/crates/rmg-ffi/src/lib.rs @@ -10,8 +10,8 @@ use std::os::raw::c_char; use std::slice; use rmg_core::{ - ApplyResult, Engine, GraphStore, NodeId, NodeRecord, TxId, decode_motion_payload, - encode_motion_payload, make_node_id, make_type_id, motion_rule, + ApplyResult, Engine, NodeId, NodeRecord, TxId, build_motion_demo_engine, decode_motion_payload, + encode_motion_payload, make_node_id, make_type_id, }; /// Opaque engine pointer exposed over the C ABI. @@ -46,19 +46,9 @@ pub struct rmg_snapshot { /// Creates a new engine with the motion rule registered. #[unsafe(no_mangle)] pub extern "C" fn rmg_engine_new() -> *mut RmgEngine { - let mut store = GraphStore::default(); - let root_id = make_node_id("world-root"); - let root_type = make_type_id("world"); - store.insert_node(NodeRecord { - id: root_id.clone(), - ty: root_type, - payload: None, - }); - - let mut engine = Engine::new(store, root_id); - engine.register_rule(motion_rule()); - - Box::into_raw(Box::new(RmgEngine { inner: engine })) + Box::into_raw(Box::new(RmgEngine { + inner: build_motion_demo_engine(), + })) } /// Releases the engine allocation created by [`rmg_engine_new`]. @@ -107,11 +97,13 @@ pub unsafe extern "C" fn rmg_engine_spawn_motion_entity( let entity_type = make_type_id("entity"); let payload = encode_motion_payload([px, py, pz], [vx, vy, vz]); - engine.inner.insert_node(NodeRecord { - id: node_id.clone(), - ty: entity_type, - payload: Some(payload), - }); + engine.inner.insert_node( + node_id.clone(), + NodeRecord { + ty: entity_type, + payload: Some(payload), + }, + ); unsafe { (*out_handle).bytes = node_id.0; diff --git a/crates/rmg-wasm/Cargo.toml b/crates/rmg-wasm/Cargo.toml index a9ece42..f2b5d93 100644 --- a/crates/rmg-wasm/Cargo.toml +++ b/crates/rmg-wasm/Cargo.toml @@ -6,9 +6,21 @@ edition = "2024" [lib] crate-type = ["cdylib"] +[features] +default = [] +console-panic = ["console_error_panic_hook"] + [dependencies] rmg-core = { path = "../rmg-core" } wasm-bindgen = "0.2.95" +js-sys = "0.3.81" +console_error_panic_hook = { version = "0.1.7", optional = true } [dev-dependencies] wasm-bindgen-test = "0.3.42" + +[profile.release] +opt-level = "s" +lto = true +codegen-units = 1 +strip = true diff --git a/crates/rmg-wasm/src/lib.rs b/crates/rmg-wasm/src/lib.rs index 9c9161a..6a59d18 100644 --- a/crates/rmg-wasm/src/lib.rs +++ b/crates/rmg-wasm/src/lib.rs @@ -7,26 +7,22 @@ use std::cell::RefCell; use std::rc::Rc; +use js_sys::Uint8Array; use rmg_core::{ - ApplyResult, Engine, GraphStore, NodeId, NodeRecord, TxId, decode_motion_payload, - encode_motion_payload, make_node_id, make_type_id, motion_rule, + ApplyResult, Engine, NodeId, NodeRecord, TxId, build_motion_demo_engine, decode_motion_payload, + encode_motion_payload, make_node_id, make_type_id, }; use wasm_bindgen::prelude::*; /// Builds a fresh engine with the motion rule pre-registered. fn build_engine() -> Engine { - let mut store = GraphStore::default(); - let root_id = make_node_id("world-root"); - let root_type = make_type_id("world"); - store.insert_node(NodeRecord { - id: root_id.clone(), - ty: root_type, - payload: None, - }); - - let mut engine = Engine::new(store, root_id); - engine.register_rule(motion_rule()); - engine + build_motion_demo_engine() +} + +#[cfg(feature = "console-panic")] +#[wasm_bindgen(start)] +pub fn init_console_panic_hook() { + console_error_panic_hook::set_once(); } /// Converts a 32-byte buffer into a [`NodeId`]. @@ -72,19 +68,21 @@ impl WasmEngine { vx: f32, vy: f32, vz: f32, - ) -> Vec { + ) -> Uint8Array { let mut engine = self.inner.borrow_mut(); let node_id = make_node_id(label); let entity_type = make_type_id("entity"); let payload = encode_motion_payload([px, py, pz], [vx, vy, vz]); - engine.insert_node(NodeRecord { - id: node_id.clone(), - ty: entity_type, - payload: Some(payload), - }); + engine.insert_node( + node_id.clone(), + NodeRecord { + ty: entity_type, + payload: Some(payload), + }, + ); - node_id.0.to_vec() + Uint8Array::from(node_id.0.as_slice()) } #[wasm_bindgen] @@ -143,7 +141,9 @@ mod tests { use wasm_bindgen_test::*; fn spawn(engine: &WasmEngine) -> Vec { - engine.spawn_motion_entity("entity-wasm", 1.0, 2.0, 3.0, 0.5, -1.0, 0.25) + engine + .spawn_motion_entity("entity-wasm", 1.0, 2.0, 3.0, 0.5, -1.0, 0.25) + .to_vec() } #[wasm_bindgen_test] From 53bd3f5907f0d7732917aabb59f5b7e9dcc8dd2b Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Sat, 25 Oct 2025 01:06:57 -0700 Subject: [PATCH 15/18] define workspace release profile Move wasm release optimizations to the workspace profile and keep crate manifest focused on features. --- Cargo.toml | 6 ++++++ crates/rmg-wasm/Cargo.toml | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 57289ad..50e4e26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,9 @@ members = [ "crates/rmg-cli" ] resolver = "2" + +[profile.release] +opt-level = "s" +lto = true +codegen-units = 1 +strip = true diff --git a/crates/rmg-wasm/Cargo.toml b/crates/rmg-wasm/Cargo.toml index f2b5d93..6c8f042 100644 --- a/crates/rmg-wasm/Cargo.toml +++ b/crates/rmg-wasm/Cargo.toml @@ -18,9 +18,3 @@ console_error_panic_hook = { version = "0.1.7", optional = true } [dev-dependencies] wasm-bindgen-test = "0.3.42" - -[profile.release] -opt-level = "s" -lto = true -codegen-units = 1 -strip = true From 31e76788510e8a1babffdcf6e26552d69b1ecd32 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Sat, 25 Oct 2025 01:34:11 -0700 Subject: [PATCH 16/18] bucket scheduler rewrites per transaction Key changes: commit() now drains per-tx BTreeMaps instead of scanning all pending rewrites; GraphStore/Engine API avoid NodeId cloning; exposed MOTION_RULE_NAME/build_motion_demo_engine for reuse; wasm/ffi now depend on shared bootstrap and constants; docs updated to mark Criterion plan and roadmap spacing. --- crates/rmg-core/src/lib.rs | 37 +++++++++++++++------------------ crates/rmg-ffi/src/lib.rs | 13 +++++++----- crates/rmg-wasm/src/lib.rs | 6 +++--- docs/rmg-demo-roadmap.md | 2 ++ docs/scheduler-benchmarks.md | 2 ++ docs/testing-and-replay-plan.md | 10 +++++---- 6 files changed, 38 insertions(+), 32 deletions(-) diff --git a/crates/rmg-core/src/lib.rs b/crates/rmg-core/src/lib.rs index 89eaf43..44ee863 100644 --- a/crates/rmg-core/src/lib.rs +++ b/crates/rmg-core/src/lib.rs @@ -1,7 +1,8 @@ //! rmg-core: typed deterministic graph rewriting engine. //! -//! **WARNING**: This is a Phase 0 bootstrap skeleton. The rewrite executor is not implemented. -//! `Engine::commit()` currently discards pending rewrites without executing them. +//! The current implementation executes queued rewrites deterministically via the +//! motion-rule spike utilities. Broader storage and scheduling features will +//! continue to land over subsequent phases. #![deny(missing_docs)] use std::collections::{BTreeMap, HashMap}; @@ -11,6 +12,8 @@ use bytes::Bytes; use thiserror::Error; const POSITION_VELOCITY_BYTES: usize = 24; +/// Public identifier for the built-in motion update rule. +pub const MOTION_RULE_NAME: &str = "motion/update"; /// Canonical 256-bit hash used throughout the engine for addressing nodes, /// types, snapshots, and rewrite rules. @@ -129,7 +132,7 @@ pub struct RewriteRule { } /// Thin wrapper around an auto-incrementing transaction identifier. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TxId(pub u64); /// Snapshot returned after a successful commit. @@ -151,7 +154,7 @@ pub struct Snapshot { /// Ordering queue that guarantees rewrites execute deterministically. #[derive(Debug, Default)] pub struct DeterministicScheduler { - pending: BTreeMap<(Hash, Hash), PendingRewrite>, + pending: HashMap>, } /// Internal representation of a rewrite waiting to be applied. @@ -238,7 +241,7 @@ impl Engine { } let scope_hash = scope_hash(rule, scope); - self.scheduler.pending.insert( + self.scheduler.pending.entry(tx).or_default().insert( (scope_hash, rule.id), PendingRewrite { tx, @@ -349,16 +352,10 @@ fn compute_snapshot_hash(store: &GraphStore, root: &NodeId) -> Hash { impl DeterministicScheduler { fn drain_for_tx(&mut self, tx: TxId) -> Vec { - let mut ready = Vec::new(); - let pending = std::mem::take(&mut self.pending); - for (key, rewrite) in pending { - if rewrite.tx == tx { - ready.push(rewrite); - } else { - self.pending.insert(key, rewrite); - } - } - ready + self.pending + .remove(&tx) + .map(|map| map.into_values().collect()) + .unwrap_or_default() } } @@ -431,11 +428,11 @@ fn motion_matcher(store: &GraphStore, scope: &NodeId) -> bool { /// deterministic so hash comparisons stay stable across independent executions. pub fn motion_rule() -> RewriteRule { let mut hasher = Hasher::new(); - hasher.update(b"motion/update"); + hasher.update(MOTION_RULE_NAME.as_bytes()); let id = hasher.finalize().into(); RewriteRule { id, - name: "motion/update", + name: MOTION_RULE_NAME, left: PatternGraph { nodes: vec![] }, matcher: motion_matcher, executor: motion_executor, @@ -483,7 +480,7 @@ mod tests { engine.register_rule(motion_rule()); let tx = engine.begin(); - let apply = engine.apply(tx, "motion/update", &entity).unwrap(); + let apply = engine.apply(tx, MOTION_RULE_NAME, &entity).unwrap(); assert!(matches!(apply, ApplyResult::Applied)); let snap = engine.commit(tx).expect("commit"); @@ -503,7 +500,7 @@ mod tests { let mut engine_b = Engine::new(store_b, entity.clone()); engine_b.register_rule(motion_rule()); let tx_b = engine_b.begin(); - let apply_b = engine_b.apply(tx_b, "motion/update", &entity).unwrap(); + let apply_b = engine_b.apply(tx_b, MOTION_RULE_NAME, &entity).unwrap(); assert!(matches!(apply_b, ApplyResult::Applied)); let snap_b = engine_b.commit(tx_b).expect("commit B"); @@ -538,7 +535,7 @@ mod tests { engine.register_rule(motion_rule()); let tx = engine.begin(); - let apply = engine.apply(tx, "motion/update", &entity).unwrap(); + let apply = engine.apply(tx, MOTION_RULE_NAME, &entity).unwrap(); assert!(matches!(apply, ApplyResult::NoMatch)); } } diff --git a/crates/rmg-ffi/src/lib.rs b/crates/rmg-ffi/src/lib.rs index bf0f3fe..534e99c 100644 --- a/crates/rmg-ffi/src/lib.rs +++ b/crates/rmg-ffi/src/lib.rs @@ -10,8 +10,8 @@ use std::os::raw::c_char; use std::slice; use rmg_core::{ - ApplyResult, Engine, NodeId, NodeRecord, TxId, build_motion_demo_engine, decode_motion_payload, - encode_motion_payload, make_node_id, make_type_id, + ApplyResult, Engine, MOTION_RULE_NAME, NodeId, NodeRecord, TxId, build_motion_demo_engine, + decode_motion_payload, encode_motion_payload, make_node_id, make_type_id, }; /// Opaque engine pointer exposed over the C ABI. @@ -44,8 +44,11 @@ pub struct rmg_snapshot { } /// Creates a new engine with the motion rule registered. +/// +/// # Safety +/// The returned raw pointer must be released by calling [`rmg_engine_free`]. #[unsafe(no_mangle)] -pub extern "C" fn rmg_engine_new() -> *mut RmgEngine { +pub unsafe extern "C" fn rmg_engine_new() -> *mut RmgEngine { Box::into_raw(Box::new(RmgEngine { inner: build_motion_demo_engine(), })) @@ -148,7 +151,7 @@ pub unsafe extern "C" fn rmg_engine_apply_motion( }; match engine .inner - .apply(TxId(tx.value), "motion/update", &node_id) + .apply(TxId(tx.value), MOTION_RULE_NAME, &node_id) { Ok(ApplyResult::Applied) => true, Ok(ApplyResult::NoMatch) => false, @@ -192,7 +195,7 @@ pub unsafe extern "C" fn rmg_engine_read_motion( out_position: *mut f32, out_velocity: *mut f32, ) -> bool { - let engine = match unsafe { engine.as_mut() } { + let engine = match unsafe { engine.as_ref() } { Some(engine) => engine, None => return false, }; diff --git a/crates/rmg-wasm/src/lib.rs b/crates/rmg-wasm/src/lib.rs index 6a59d18..7b89c96 100644 --- a/crates/rmg-wasm/src/lib.rs +++ b/crates/rmg-wasm/src/lib.rs @@ -9,8 +9,8 @@ use std::rc::Rc; use js_sys::Uint8Array; use rmg_core::{ - ApplyResult, Engine, NodeId, NodeRecord, TxId, build_motion_demo_engine, decode_motion_payload, - encode_motion_payload, make_node_id, make_type_id, + ApplyResult, Engine, MOTION_RULE_NAME, NodeId, NodeRecord, TxId, build_motion_demo_engine, + decode_motion_payload, encode_motion_payload, make_node_id, make_type_id, }; use wasm_bindgen::prelude::*; @@ -102,7 +102,7 @@ impl WasmEngine { None => return false, }; let mut engine = self.inner.borrow_mut(); - match engine.apply(TxId(tx_id), "motion/update", &node_id) { + match engine.apply(TxId(tx_id), MOTION_RULE_NAME, &node_id) { Ok(ApplyResult::Applied) => true, Ok(ApplyResult::NoMatch) => false, Err(_) => false, diff --git a/docs/rmg-demo-roadmap.md b/docs/rmg-demo-roadmap.md index af9fa37..0c57a94 100644 --- a/docs/rmg-demo-roadmap.md +++ b/docs/rmg-demo-roadmap.md @@ -71,8 +71,10 @@ This document captures the interactive demos and performance milestones we want | 1E | Demo 5 networking | Confluence transaction protocol, replay verification | | 1F | Demo dashboards | Inspector frame overlays, JSON ingestion | + **Prerequisites:** BLAKE3 hashing utilities, deterministic PRNG module, snapshot serialiser, inspector graph viewer, Neo4j logging for demo outcomes, CI runners with wasm/criterion toolchains. + **Timeline:** - Milestone Alpha (end 1B): Demo 1 frame-hash prototype + Demo 2 toy bench executed manually. - Milestone Beta (end 1D): Demos 1–3 automated in CI with golden outputs. diff --git a/docs/scheduler-benchmarks.md b/docs/scheduler-benchmarks.md index a4aa042..0b75779 100644 --- a/docs/scheduler-benchmarks.md +++ b/docs/scheduler-benchmarks.md @@ -41,6 +41,7 @@ Objective: validate the scheduler design under realistic workloads before full i --- ## Tooling +*Phase 1 target – planned Criterion infrastructure; implementation pending.* - Use Criterion for Rust benchmarks with statistical analysis. - Benchmarks live in `tests/benchmarks/scheduler.rs` (or similar crate structure). - Output results as JSON for inspector consumption. @@ -48,6 +49,7 @@ Objective: validate the scheduler design under realistic workloads before full i --- + ## Tasks - [ ] TODO: Implement scheduler benchmark harness (tracked for Phase 1 once Criterion benches land). - [ ] Implement mock system descriptors for each scenario. diff --git a/docs/testing-and-replay-plan.md b/docs/testing-and-replay-plan.md index 75c0d1b..689c3c8 100644 --- a/docs/testing-and-replay-plan.md +++ b/docs/testing-and-replay-plan.md @@ -49,10 +49,12 @@ interface VerificationReport { --- ## Automation Plan -- `cargo test --package rmg-core --features determinism` – runs replay and comparers for golden datasets. -- `cargo test --package rmg-core --test paradox` – injects artificial read/write overlaps to validate quarantine behavior. -- `cargo test --package rmg-core --test entropy` – verifies entropy observers and metrics. -- `cargo test --package rmg-core --test bridge` – covers temporal bridge retro/reroute. +Once implemented, the automated test suite will include: + +- PLANNED: `cargo test --package rmg-core --features determinism` – runs replay and comparers for golden datasets. +- PLANNED: `cargo test --package rmg-core --test paradox` – injects artificial read/write overlaps to validate quarantine behavior. +- PLANNED: `cargo test --package rmg-core --test entropy` – verifies entropy observers and metrics. +- PLANNED: `cargo test --package rmg-core --test bridge` – covers temporal bridge retro/reroute. - TODO: Add Criterion-based scheduler benches to CI once implemented (Phase 1 task). --- From ba6c644ccf6594f294191da45d9f429cc05b73ee Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Sat, 25 Oct 2025 02:32:24 -0700 Subject: [PATCH 17/18] Update AGENTS.md --- AGENTS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index c34e39d..6b38354 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,6 +60,13 @@ Use `messages-search --text "Echo"` for ad-hoc queries. - Respect determinism: preferably no random seeds without going through the Echo PRNG. - Run `cargo clippy --all-targets -- -D missing_docs` and `cargo test` before every PR; CI will expect a zero-warning, fully documented surface. +## Git Real +1. **NEVER** use `--force` with any git command. If you think you need it, stop and ask the human for help. +2. **NEVER** use rebase. Embrace messy distributed history; plain merges capture the truth, rebases rewrite it. +3. **NEVER** amend a commit. Make a new commit instead of erasing recorded history. + +In short: no one cares about a tidy commit graph, but everyone cares if you rewrite commits on origin. + ## Contact Threads - Neo4j Thread `echo-devlog`: Daily journal, decisions, blockers. - Neo4j Thread `echo-spec`: High-level architectural proposals. From 217cc5048dea7c9c905c426a86999025eb16a95a Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Sat, 25 Oct 2025 05:26:54 -0700 Subject: [PATCH 18/18] address second review feedback Add EngineError::UnknownRule, sort edges deterministically, derive Copy for NodeId, and introduce motion build helpers in tests. Document planned CLI commands, clarify scheduler/testing docs, and expand wasm spawn docs. Updated wasm-bindgen to 0.2.104 and adjusted wasm/ffi bindings (shared constants, panic hook feature). FFI attributes remain per Rust 2024 requirements. --- crates/rmg-cli/src/main.rs | 2 +- crates/rmg-core/src/lib.rs | 51 ++++++++++++++++++++++++++++---------- crates/rmg-ffi/src/lib.rs | 8 +++--- crates/rmg-wasm/Cargo.toml | 2 +- crates/rmg-wasm/src/lib.rs | 16 ++++++++++-- 5 files changed, 59 insertions(+), 20 deletions(-) diff --git a/crates/rmg-cli/src/main.rs b/crates/rmg-cli/src/main.rs index 902ebf5..6fd8a5c 100644 --- a/crates/rmg-cli/src/main.rs +++ b/crates/rmg-cli/src/main.rs @@ -1,6 +1,6 @@ //! Echo CLI entrypoint. //! -//! Provides developer-facing commands for working with Echo projects. Planned +//! Provides developer-facing commands for working with Echo projects. *Planned* //! subcommands include `echo demo` (run deterministic demo suites), `echo //! bench` (execute Criterion benchmarks), and `echo inspect` (open the //! inspector tooling). diff --git a/crates/rmg-core/src/lib.rs b/crates/rmg-core/src/lib.rs index 44ee863..a793dad 100644 --- a/crates/rmg-core/src/lib.rs +++ b/crates/rmg-core/src/lib.rs @@ -23,7 +23,7 @@ pub type Hash = [u8; 32]; /// /// `NodeId` values are obtained from `make_node_id` and remain stable across /// runs because they are derived from a BLAKE3 hash of a string label. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct NodeId(pub Hash); /// Strongly typed identifier for the logical kind of a node or component. @@ -183,6 +183,9 @@ pub enum EngineError { /// The supplied transaction identifier did not exist or was already closed. #[error("transaction not found")] UnknownTx, + /// A rule was requested that has not been registered with the engine. + #[error("rule not registered: {0}")] + UnknownRule(String), } /// Core rewrite engine used by the spike. @@ -233,7 +236,7 @@ impl Engine { } let rule = match self.rules.get(rule_name) { Some(rule) => rule, - None => return Ok(ApplyResult::NoMatch), + None => return Err(EngineError::UnknownRule(rule_name.to_owned())), }; let matches = (rule.matcher)(&self.store, scope); if !matches { @@ -246,7 +249,7 @@ impl Engine { PendingRewrite { tx, rule_id: rule.id, - scope: scope.clone(), + scope: *scope, }, ); @@ -267,7 +270,7 @@ impl Engine { let hash = compute_snapshot_hash(&self.store, &self.current_root); let snapshot = Snapshot { - root: self.current_root.clone(), + root: self.current_root, hash, parent: self.last_snapshot.as_ref().map(|s| s.hash), tx, @@ -280,7 +283,7 @@ impl Engine { pub fn snapshot(&self) -> Snapshot { let hash = compute_snapshot_hash(&self.store, &self.current_root); Snapshot { - root: self.current_root.clone(), + root: self.current_root, hash, parent: self.last_snapshot.as_ref().map(|s| s.hash), tx: TxId(self.tx_counter), @@ -332,7 +335,9 @@ fn compute_snapshot_hash(store: &GraphStore, root: &NodeId) -> Hash { for (from, edges) in &store.edges_from { hasher.update(&from.0); hasher.update(&(edges.len() as u64).to_le_bytes()); - for edge in edges { + let mut sorted_edges: Vec<&EdgeRecord> = edges.iter().collect(); + sorted_edges.sort_by(|a, b| a.id.0.cmp(&b.id.0)); + for edge in sorted_edges { hasher.update(&(edge.id).0); hasher.update(&(edge.ty).0); hasher.update(&(edge.to).0); @@ -445,7 +450,7 @@ pub fn build_motion_demo_engine() -> Engine { let root_id = make_node_id("world-root"); let root_type = make_type_id("world"); store.insert_node( - root_id.clone(), + root_id, NodeRecord { ty: root_type, payload: None, @@ -469,14 +474,14 @@ mod tests { let mut store = GraphStore::default(); store.insert_node( - entity.clone(), + entity, NodeRecord { ty: entity_type, payload: Some(payload), }, ); - let mut engine = Engine::new(store, entity.clone()); + let mut engine = Engine::new(store, entity); engine.register_rule(motion_rule()); let tx = engine.begin(); @@ -490,14 +495,14 @@ mod tests { let mut store_b = GraphStore::default(); let payload_b = encode_motion_payload([1.0, 2.0, 3.0], [0.5, -1.0, 0.25]); store_b.insert_node( - entity.clone(), + entity, NodeRecord { ty: entity_type, payload: Some(payload_b), }, ); - let mut engine_b = Engine::new(store_b, entity.clone()); + let mut engine_b = Engine::new(store_b, entity); engine_b.register_rule(motion_rule()); let tx_b = engine_b.begin(); let apply_b = engine_b.apply(tx_b, MOTION_RULE_NAME, &entity).unwrap(); @@ -524,18 +529,38 @@ mod tests { let mut store = GraphStore::default(); store.insert_node( - entity.clone(), + entity, NodeRecord { ty: entity_type, payload: None, }, ); - let mut engine = Engine::new(store, entity.clone()); + let mut engine = Engine::new(store, entity); engine.register_rule(motion_rule()); let tx = engine.begin(); let apply = engine.apply(tx, MOTION_RULE_NAME, &entity).unwrap(); assert!(matches!(apply, ApplyResult::NoMatch)); } + + #[test] + fn apply_unknown_rule_returns_error() { + let entity = make_node_id("entity-unknown-rule"); + let entity_type = make_type_id("entity"); + + let mut store = GraphStore::default(); + store.insert_node( + entity, + NodeRecord { + ty: entity_type, + payload: Some(encode_motion_payload([0.0, 0.0, 0.0], [0.0, 0.0, 0.0])), + }, + ); + + let mut engine = Engine::new(store, entity); + let tx = engine.begin(); + let result = engine.apply(tx, "missing-rule", &entity); + assert!(matches!(result, Err(EngineError::UnknownRule(rule)) if rule == "missing-rule")); + } } diff --git a/crates/rmg-ffi/src/lib.rs b/crates/rmg-ffi/src/lib.rs index 534e99c..1dfeee9 100644 --- a/crates/rmg-ffi/src/lib.rs +++ b/crates/rmg-ffi/src/lib.rs @@ -46,7 +46,9 @@ pub struct rmg_snapshot { /// Creates a new engine with the motion rule registered. /// /// # Safety -/// The returned raw pointer must be released by calling [`rmg_engine_free`]. +/// The caller assumes ownership of the returned pointer and must release it +/// via [`rmg_engine_free`] to avoid leaking memory. +// Rust 2024 requires `#[unsafe(no_mangle)]` as `no_mangle` is an unsafe attribute. #[unsafe(no_mangle)] pub unsafe extern "C" fn rmg_engine_new() -> *mut RmgEngine { Box::into_raw(Box::new(RmgEngine { @@ -73,7 +75,7 @@ pub unsafe extern "C" fn rmg_engine_free(engine: *mut RmgEngine) { /// /// # Safety /// `engine`, `label`, and `out_handle` must be valid pointers. `label` must -/// reference a null-terminated string. +/// reference a null-terminated UTF-8 string. #[unsafe(no_mangle)] pub unsafe extern "C" fn rmg_engine_spawn_motion_entity( engine: *mut RmgEngine, @@ -101,7 +103,7 @@ pub unsafe extern "C" fn rmg_engine_spawn_motion_entity( let payload = encode_motion_payload([px, py, pz], [vx, vy, vz]); engine.inner.insert_node( - node_id.clone(), + node_id, NodeRecord { ty: entity_type, payload: Some(payload), diff --git a/crates/rmg-wasm/Cargo.toml b/crates/rmg-wasm/Cargo.toml index 6c8f042..1673ab6 100644 --- a/crates/rmg-wasm/Cargo.toml +++ b/crates/rmg-wasm/Cargo.toml @@ -12,7 +12,7 @@ console-panic = ["console_error_panic_hook"] [dependencies] rmg-core = { path = "../rmg-core" } -wasm-bindgen = "0.2.95" +wasm-bindgen = "0.2.104" js-sys = "0.3.81" console_error_panic_hook = { version = "0.1.7", optional = true } diff --git a/crates/rmg-wasm/src/lib.rs b/crates/rmg-wasm/src/lib.rs index 7b89c96..ececf2b 100644 --- a/crates/rmg-wasm/src/lib.rs +++ b/crates/rmg-wasm/src/lib.rs @@ -58,7 +58,15 @@ impl WasmEngine { } #[wasm_bindgen] - /// Spawns an entity with encoded motion payload and returns its id bytes. + /// Spawns an entity with encoded motion payload. + /// + /// * `label` – stable identifier used to derive the entity node id. Must be + /// unique for the caller's scope. + /// * `px`, `py`, `pz` – initial position components (meters) in a + /// right-handed coordinate system. + /// * `vx`, `vy`, `vz` – velocity components (meters/second). + /// + /// Returns the 32-byte node id as a `Uint8Array` for JavaScript consumers. pub fn spawn_motion_entity( &self, label: &str, @@ -75,7 +83,7 @@ impl WasmEngine { let payload = encode_motion_payload([px, py, pz], [vx, vy, vz]); engine.insert_node( - node_id.clone(), + node_id, NodeRecord { ty: entity_type, payload: Some(payload), @@ -93,6 +101,10 @@ impl WasmEngine { #[wasm_bindgen] /// Applies the motion rewrite to the entity identified by `entity_id`. + /// + /// Returns `true` on success and `false` if the transaction id, entity id, + /// or rule match is invalid. Future revisions will surface richer error + /// information. pub fn apply_motion(&self, tx_id: u64, entity_id: &[u8]) -> bool { if tx_id == 0 { return false;