diff --git a/docs/assets/robotics-policy-prover/action-governance-architecture.svg b/docs/assets/robotics-policy-prover/action-governance-architecture.svg
new file mode 100644
index 00000000..91a441f4
--- /dev/null
+++ b/docs/assets/robotics-policy-prover/action-governance-architecture.svg
@@ -0,0 +1,105 @@
+
diff --git a/docs/assets/robotics-policy-prover/openshell-robotics-prover-demo.mp4 b/docs/assets/robotics-policy-prover/openshell-robotics-prover-demo.mp4
new file mode 100644
index 00000000..b2c943b6
Binary files /dev/null and b/docs/assets/robotics-policy-prover/openshell-robotics-prover-demo.mp4 differ
diff --git a/docs/assets/robotics-policy-prover/policy-latency-scaling.svg b/docs/assets/robotics-policy-prover/policy-latency-scaling.svg
new file mode 100644
index 00000000..a0614c9c
--- /dev/null
+++ b/docs/assets/robotics-policy-prover/policy-latency-scaling.svg
@@ -0,0 +1,85 @@
+
diff --git a/docs/assets/robotics-policy-prover/robotics-policy-prover-hero.png b/docs/assets/robotics-policy-prover/robotics-policy-prover-hero.png
new file mode 100644
index 00000000..405ecb70
Binary files /dev/null and b/docs/assets/robotics-policy-prover/robotics-policy-prover-hero.png differ
diff --git a/docs/dev-notes/index.md b/docs/dev-notes/index.md
index f0cc7b8a..34b5a78f 100644
--- a/docs/dev-notes/index.md
+++ b/docs/dev-notes/index.md
@@ -31,10 +31,44 @@ hide:
Featured note
Latest from the team
-
+
+
+
+

+
+
+
+
+ Physical AI
+
+
Can Formal Methods Govern AI-Generated Robot Actions? An OpenShell-Inspired Experiment
+
A robotics experiment asks whether an independent, SMT-backed policy boundary can efficiently govern AI-generated plans before they reach a simulated or physical robot.
+
+ physical-ai
+ formal-methods
+ policy
+
+
+
+
+
+
+
+
+
Recent notes
+ The working archive
+
+
diff --git a/docs/dev-notes/posts/2026-08-07-formal-methods-ai-generated-robot-actions.md b/docs/dev-notes/posts/2026-08-07-formal-methods-ai-generated-robot-actions.md
new file mode 100644
index 00000000..fe8ebd04
--- /dev/null
+++ b/docs/dev-notes/posts/2026-08-07-formal-methods-ai-generated-robot-actions.md
@@ -0,0 +1,458 @@
+---
+title: "Can Formal Methods Govern AI-Generated Robot Actions? An OpenShell-Inspired Experiment"
+date: 2026-08-07
+updated: 2026-08-07
+description: "A robotics experiment asks whether an independent, SMT-backed policy boundary can efficiently govern AI-generated plans before they reach a simulated or physical robot."
+agent_markdown: true
+hero_image: "../../assets/robotics-policy-prover/robotics-policy-prover-hero.png"
+categories:
+ - Physical AI
+tags:
+ - openshell
+ - robotics
+ - physical-ai
+ - formal-methods
+ - policy
+ - agents
+authors:
+ - zredlined
+card_tags:
+ - physical-ai
+ - formal-methods
+ - policy
+---
+
+# Can Formal Methods Govern AI-Generated Robot Actions? An OpenShell-Inspired Experiment
+
+
+
+
+
+ Dev Note
+
+ Physical AI
+
+
+
+
+
+*We built a small robotics experiment to test whether OpenShell's approach to
+policy enforcement can be applied to AI-generated robot plans.*
+
+
+
+
+
+OpenShell uses policy enforcement to constrain what AI agents can do in digital
+environments. We wanted to see whether a similar approach could be applied to
+plans generated for a robot.
+
+To explore that question, we built a small pick-and-place experiment. The task
+is intentionally simple: move a green block into a blue tray. An AI planner
+proposes a sequence of 3D waypoints, and a policy prover checks the path before
+the simulated tool head moves. The checks cover workspace limits, a restricted
+volume, human proximity, sensor freshness, delegated authority, force, and task
+budget.
+
+Formal methods are attracting renewed attention as AI systems become agents.
+Recent work is exploring
+[behavioral contracts](https://arxiv.org/abs/2602.22302),
+[runtime compliance over agent traces](https://arxiv.org/abs/2606.19242), and
+other ways to turn desired behavior into specifications an external system can
+check. Many familiar approaches focus on making the model itself more reliable:
+better instructions, safer training, stronger evaluations, or another model
+acting as a judge. Those remain important layers. This experiment looks at a
+complementary question: can a separate system check a proposed physical action
+before it reaches a simulator or robot?
+
+For this prototype, we split responsibility this way:
+
+- The agent proposes a plan it believes will accomplish the task.
+- An independent, deterministic policy boundary sits between that plan and the
+ simulated actuator.
+- A rejection returns structured evidence the agent can use to revise its plan.
+- Only a version admitted by the policy boundary reaches the executor, with any
+ returned constraints and runtime obligations attached.
+
+We set out to learn whether that check could return useful feedback to the
+planner and run quickly enough to fit into an agent's planning loop.
+
+
+
+ The agent's plan passes through deterministic action admission before an admitted contract reaches a simulated or real-world environment. Low-level robot control remains outside the prover's planning-cadence role.
+
+
+---
+
+## The Recorded Experiment
+
+
+
+The recorded run follows four steps: propose, check, adapt, execute.
+
+First, the agent proposes a six-waypoint path at 0.35 m/s. One of
+its segments crosses the red restricted volume. The policy service denies the
+action, identifies `restricted_zone_intersection`, returns an approximate
+counterexample point, and supplies a minimum bypass height. Nothing moves.
+
+The planning loop uses that decision packet to produce a six-waypoint route around the
+restricted volume at 0.18 m/s. Before execution, the world changes: a human
+enters the caution radius. The policy service evaluates the action again and
+changes the contract. The path may proceed, but only at a speed of 0.08 m/s or
+less, with an obligation to pause if the human gets closer.
+
+The executor runs the revised path with the returned speed limit. In this run,
+the planning loop could propose and revise a path, while the policy boundary
+determined which version reached the simulated actuator.
+
+The decision packet looks like this:
+
+```json
+{
+ "decision": "deny",
+ "violations": ["restricted_zone_intersection"],
+ "constraints": {
+ "bypass_z_min": 0.55
+ },
+ "obligations": ["emit_audit_event"],
+ "counterexample": {
+ "segment_id": "proposed_segment",
+ "zone_id": "restricted_zone.alpha",
+ "reason": "restricted_zone_intersection",
+ "point": [-0.18, 0.78, -0.20]
+ }
+}
+```
+
+In this demo, that response gives the planner more to work with than a generic
+"unsafe" error. It serves as both an enforcement decision for the executor and
+machine-readable feedback for replanning.
+
+---
+
+## Why Check the Proposed Action?
+
+This experiment does not attempt to explain or verify everything happening
+inside a model. It applies formal methods to a narrower artifact: the concrete
+action proposed by the planner.
+
+This resembles the formal-methods idea of a *shield*. In
+[safe reinforcement learning via shielding](https://ojs.aaai.org/index.php/AAAI/article/view/11797),
+a learned policy proposes actions while a reactive system monitors them and
+intervenes when one would violate a formal specification. Our prototype tests a
+similar division of work with an agent-generated robot path.
+
+Agent systems broaden that idea. A proposed action may be a shell command, a
+network request, a delegated capability, a financial transaction, or a robot
+trajectory. One useful formal object is a typed description of the action, the
+relevant state, and the invariant the surrounding system is expected to
+enforce—not the model's prose or chain of thought.
+
+The design also resembles the reference-monitor pattern emphasized in recent
+agent-security research. For example, [VeriGuard](https://research.google/pubs/veriguard-enhancing-llm-agent-safety-via-verified-code-generation/),
+work by Dj Dvijotham and collaborators at Google DeepMind, separates rigorous
+policy validation from a lightweight runtime monitor that checks each proposed
+action before execution. Applied to robotics, the open systems question is
+whether the monitor can cover the relevant paths from a planner's output to an
+actuator command.
+
+The prototype gives us six design goals to investigate:
+
+1. **Complete mediation.** Every policy-relevant path to an external effect
+ passes through the decision point.
+2. **Independent.** The agent cannot edit, bypass, or reinterpret the policy
+ that governs it.
+3. **Auditable.** The trusted decision surface stays small enough to model,
+ test, and verify.
+4. **Close to the effect.** The check occurs after intent becomes a concrete
+ action but before an external side effect.
+5. **Compositional.** Workspace, authority, freshness, human proximity, budget,
+ speed, and force rules can combine into one decision.
+6. **Constructive.** A denial includes a failed invariant, counterexample, or
+ narrower admissible contract so the agent can replan rather than guess.
+
+Any guarantee from the prototype remains relative to its specification and
+world model. Work
+on [safe reinforcement learning through proof and learning](https://ojs.aaai.org/index.php/AAAI/article/view/12107)
+makes the same essential point: formal verification provides confidence relative
+to a model, and cyber-physical reality will always test the completeness of that
+model. For this experiment, that points toward stating the property,
+assumptions, and enforcement point precisely—and testing where each one breaks
+down as the environment becomes more realistic.
+
+---
+
+## How the Prototype Works
+
+OpenShell moves security policy out of the agent and into the environment that
+mediates its actions. A sandboxed agent can reason, write code, call tools, and
+delegate work, but filesystem, network, process, and inference authority are
+enforced by infrastructure the agent does not control.
+
+This experiment explores whether the same architectural move can extend to the
+physical action domain. The question is not only, "May this agent call the robot
+service?" It is also, "May this particular motion run, with this object, at this
+speed, given the world state we have now?"
+
+The prototype expresses each request as an action envelope:
+
+```text
+actor and delegated subagent
+action and target resource
+start, end, and waypoint path
+requested speed and force
+object identity and class
+capability grant
+human distance and sensor age
+restricted and caution volumes
+remaining task budget
+```
+
+The service turns that envelope into one of four decisions:
+
+- `allow`: execute the proposed action.
+- `deny`: do not execute; return violations and a counterexample when possible.
+- `allow_with_constraints`: execute only inside narrower speed, force, or path
+ bounds.
+- `approval_required`: stop at a human decision point.
+
+The result also contains obligations. An obligation is a rule the executor must
+continue enforcing after admission, such as pausing if the measured human
+distance drops below a threshold or writing a durable audit event.
+
+The prototype divides responsibility this way:
+
+```text
+agent runtime propose and revise the plan
+policy service decide and return an action contract
+executor enforce the contract and emit evidence
+```
+
+The policy check admits a plan or short-horizon physical action before execution.
+Once admitted, the robot's existing controller remains responsible for the
+low-level control loop. The solver is an action-admission boundary; it is not a
+motor controller.
+
+---
+
+## Latency in the Planning Loop
+
+The two decisions visible in the recorded walkthrough completed in 8.74 ms and
+8.55 ms:
+
+| Proposed action | Decision | Displayed policy-decision time |
+| --- | --- | ---: |
+| 6-waypoint initial path | Deny restricted-zone intersection | 8.74 ms |
+| 6-waypoint revised path with a nearby human | Constrain speed | 8.55 ms |
+
+One way to screen an AI-generated plan is to ask another model whether the plan
+looks safe. That can be a useful semantic review, but an LLM-as-judge remains a
+probabilistic decision and adds another inference pass. If the reviewing model
+has latency similar to the planning model, the safety check can approach
+doubling the inference portion of the planning loop.
+
+The prover takes a different role. Model inference produces the plan; the
+runtime turns that plan into a typed action envelope; and the prover performs a
+deterministic admission check against explicit invariants. The relevant
+performance question is therefore whether policy admission is small relative
+to the agent's planning cadence.
+
+Those walkthrough values motivated a reproducible release-mode benchmark of the
+current in-process decision path. We exercised allow, deny, and constrained
+outcomes across 3, 6, 12, 24, and 48 waypoints, with **5,000**
+measured decisions per case after **1,000** warm-up decisions.
+
+
+
+ Measured policy-decision latency on NVIDIA DGX Spark is shown separately from illustrative inference-time scenarios. Model inference depends on the model, request, hardware, and serving configuration.
+
+
+Across this matrix, p95 policy latency ranged from **1.321 ms** to
+**1.886 ms**. The 48-waypoint cases remained between **1.348 and 1.376 ms
+p95**. On this workload, the current SMT-backed admission check is small
+relative to the illustrative agent-planning latencies we considered.
+
+Inference time matters to the complete propose-check-adapt loop, but it is not a
+property of the prover. The lower panel therefore shows a sensitivity analysis,
+not a model benchmark: measured policy p95 added to illustrative 100, 250, 500,
+1,000, and 2,000 ms planning-inference scenarios. Against a **100 ms** inference
+stage, the slowest measured p95 policy check adds about **1.89%**; against a
+**250 ms** inference stage, it adds about **0.75%**. On these workloads, policy
+admission is therefore on the order of one percent of a fast agent-planning
+stage, without requiring another full model inference.
+
+The recording provides a second, less controlled point of context. Its first
+plan was produced through an OpenAI-compatible endpoint and appears roughly 6–7
+seconds after the planner request begins; the UI did not capture the exact model
+identifier or API latency. The 8.74 ms policy decision shown immediately
+afterward is roughly 0.1% of that observed planning interval. This is a
+walkthrough-level comparison, not a benchmark of the model or serving endpoint.
+
+For reproducibility, the revised path in this recording was produced by the
+demo's deterministic fixture planner after it received the denial packet. The
+project supports running both planning steps through an OpenAI-compatible
+endpoint, and a future recording will capture the model identifier and request
+latency directly in the event stream.
+
+The result is encouraging, not exhaustive. The benchmark measures the current
+local decision function on an **NVIDIA DGX Spark with an NVIDIA GB10 and
+20-core Arm CPU**, using an arm64 Linux container. It includes deterministic
+geometry checks and Z3 setup/checking, but excludes HTTP and JSON transport,
+model inference, replanning, rendering, simulator stepping, and robot execution.
+It does not establish a hard real-time deadline. These measurements apply to
+AI-generated plans and short-horizon actions evaluated at the agent's planning
+cadence. They do not imply that the prover should inspect every sub-millisecond
+operation used to balance a robot, regulate torque, or track a joint trajectory.
+Those responsibilities remain with dedicated real-time controllers and safety
+systems. The admission boundary checks the path or action contract those
+controllers are being asked to execute. The complete harness and
+machine-readable results accompany this Dev Note so others can reproduce the
+measurement and add harder workloads.
+
+---
+
+## What the Prototype Verifies
+
+The phrase "formal methods" needs precision, especially when physical effects
+are involved.
+
+The current implementation combines deterministic Rust checks with an
+SMT-backed policy check using Z3. Rust derives facts about workspace bounds,
+sampled segment/zone intersections, sensor freshness, budget, authority, human
+proximity, speed, and force. The prototype submits Boolean policy facts to Z3
+with a bounded timeout and produces a typed decision packet.
+
+It does not yet encode continuous robot motion, full-body geometry, kinematics,
+dynamics, braking distance, or perception uncertainty as symbolic constraints.
+Its segment/volume test samples points along each tool-head segment, and the
+tool-head—not the complete robot body—is the governed geometry.
+
+There is a second assumption outside the solver: the runtime must observe and
+mediate every policy-relevant action. An alternate path to the actuator, or a
+world-state signal that is missing or stale, can invalidate an otherwise correct
+formal decision. Proving the policy and validating the enforcement boundary are
+therefore parts of the same assurance claim.
+
+So this is a research prototype, not a safety-rated system or a proof that a
+real robot trajectory is collision-free. The next formal-methods step is to
+make the solver result the authoritative source of the verdict and deepen the
+encoding from Boolean policy composition toward bounded trajectory constraints.
+The next robotics step is to connect those constraints to a real motion planner
+and runtime monitor.
+
+Making that boundary explicit is part of the research. The next stages should
+help us understand how useful the formal specification remains as the model,
+environment, and enforcement point become more realistic.
+
+---
+
+## Next Experiments
+
+The first results are encouraging, and this is an area we are actively exploring
+with partners around [OpenShell](https://github.com/NVIDIA/OpenShell), an
+Apache-2.0, community-driven project.
+
+Version one uses a Three.js workcell deliberately. Keeping the environment
+small let us focus on the policy contract and prover rather than simulator
+integration. The next parts of this research series will carry the same boundary
+into NVIDIA Isaac Sim and MuJoCo, where we can test it against richer robot
+geometry, dynamics, perception, and planning workloads.
+
+Several steps would turn the prototype into a stronger research result:
+
+1. Replace sampled tool-head intersections with exact or conservatively bounded
+ geometry checks.
+2. Encode richer trajectory and policy constraints symbolically and use the SMT
+ result directly for the action verdict.
+3. Connect the action contract to NVIDIA Isaac Sim and MuJoCo, then to a small
+ physical platform such as an SO-100 arm.
+4. Run the planner inside OpenShell and place the policy service on the trusted
+ path to the simulator or actuator.
+5. Treat human proximity, sensor freshness, and other changing facts as runtime
+ signals that can revoke or narrow an already-admitted action.
+6. Evaluate the boundary inside a longer-running policy-improvement loop where
+ the agent is allowed to change its code but not its governing invariants.
+
+Physical perception also makes some policy predicates probabilistic rather than
+crisp. A human detector, distance estimate, occupancy map, or object classifier
+can be wrong, and their errors may be correlated. Recent work by Dvijotham and
+collaborators on
+[sound probabilistic verification for AI agents](https://arxiv.org/abs/2606.20510)
+offers an interesting direction for representing that uncertainty while
+retaining the deterministic envelope as a separate layer.
+
+This question becomes more salient as research systems such as
+[ENPIRE](https://research.nvidia.com/labs/gear/enpire/) show coding agents
+managing repeated real-world robot-policy improvement across reset,
+verification, rollout, and evolution. This project is separate from ENPIRE, but
+the broader direction raises a related question: as agents gain more authority
+to improve physical systems, which properties remain outside their authority to
+change?
+
+A question for the next experiments is:
+
+> Can an agent optimize a robot policy while the governing invariants remain
+> outside the agent's control?
+
+In this limited experiment, the agent did not make an admissible plan on its
+first attempt. The useful behavior was what happened next: the policy boundary
+stopped the simulated effect, identified the violated invariant, and returned
+enough structure for the agent to try again without changing the invariant.
+
+We now want to test the same pattern with richer dynamics, uncertain
+perception, longer plans, and eventually real hardware. Those experiments will
+show where a separate policy check remains useful and where the model,
+specification, or enforcement approach needs to change.
+
+---
+
+## Research Questions and Collaboration
+
+The experiment leaves several questions that we would like to study with the
+OpenShell research community: how formal specifications hold up under changing
+world state, which solver encodings fit agent-scale latency budgets, whether
+constructive counterexamples improve replanning, and how an admitted contract
+can be carried reliably into a simulator or actuator.
+
+If you are working on SMT, temporal logic, runtime verification, control barrier
+functions, motion planning, digital twins, robot learning, or runtime assurance
+for agents, we would like to compare models and workloads. Some useful next
+experiments may come from connecting these communities rather than treating
+agent security and physical safety as separate problems.
+
+---
+
+## Run and Extend the Experiment
+
+The complete prototype, local setup, benchmark harness, machine-readable
+results, and Dev Note live together in the
+[OpenShell Research repository](https://github.com/NVIDIA/OpenShell-Research/tree/main/projects/robotics-policy-prover).
+The default fixture mode reproduces the interaction without model credentials;
+the optional agent mode can be used to explore different planners. We welcome
+new policy encodings, adversarial workloads, simulator adapters, and benchmark
+results through the normal OpenShell Research contribution process.
+
+Resources:
+
+1. [Robotics policy-prover project source](https://github.com/NVIDIA/OpenShell-Research/tree/main/projects/robotics-policy-prover)
+2. [Machine-readable DGX Spark benchmark results](https://github.com/NVIDIA/OpenShell-Research/blob/main/projects/robotics-policy-prover/benchmarks/policy-latency.json)
+3. [NVIDIA OpenShell](https://github.com/NVIDIA/OpenShell)
+4. [ENPIRE: Agentic Robot Policy Self-Improvement in the Real World](https://research.nvidia.com/labs/gear/enpire/)
+5. [Z3 theorem prover](https://github.com/Z3Prover/z3)
+6. [Safe Reinforcement Learning via Shielding](https://ojs.aaai.org/index.php/AAAI/article/view/11797)
+7. [Safe Reinforcement Learning via Formal Methods: Toward Safe Control Through Proof and Learning](https://ojs.aaai.org/index.php/AAAI/article/view/12107)
+8. [Agent Behavioral Contracts](https://arxiv.org/abs/2602.22302)
+9. [Runtime Compliance Verification for AI Agents](https://arxiv.org/abs/2606.19242)
+10. [VeriGuard: Enhancing LLM Agent Safety via Verified Code Generation](https://research.google/pubs/veriguard-enhancing-llm-agent-safety-via-verified-code-generation/)
+11. [Efficient and Sound Probabilistic Verification for AI Agents](https://arxiv.org/abs/2606.20510)
diff --git a/projects/robotics-policy-prover/.env.example b/projects/robotics-policy-prover/.env.example
new file mode 100644
index 00000000..12a6c120
--- /dev/null
+++ b/projects/robotics-policy-prover/.env.example
@@ -0,0 +1,6 @@
+OPENAI_API_KEY=
+OPENAI_BASE_URL=https://api.openai.com/v1
+OPENAI_MODEL=
+LLM_MODEL=
+FAST_MODEL=
+DEMO_AGENT_MODE=fixture
diff --git a/projects/robotics-policy-prover/.gitignore b/projects/robotics-policy-prover/.gitignore
new file mode 100644
index 00000000..37637ac2
--- /dev/null
+++ b/projects/robotics-policy-prover/.gitignore
@@ -0,0 +1,14 @@
+node_modules/
+dist/
+target/
+policy-prover-service/target/
+.env
+.env.*
+!.env.example
+.z3-trace
+**/.z3-trace
+.DS_Store
+coverage/
+playwright-report/
+test-results/
+screenshots/
diff --git a/projects/robotics-policy-prover/ARCHITECTURE.md b/projects/robotics-policy-prover/ARCHITECTURE.md
new file mode 100644
index 00000000..4b37a0ae
--- /dev/null
+++ b/projects/robotics-policy-prover/ARCHITECTURE.md
@@ -0,0 +1,40 @@
+# Robotics Policy-Prover Architecture
+
+This demo is intentionally shaped like a future OpenShell runtime integration,
+without depending on OpenShell internals yet.
+
+## Runtime Boundary
+
+The browser simulation plays the role of a physical actuator and operator view.
+The agent proposes `ActionEnvelope` values. The policy-prover service accepts
+those envelopes through `/api/decide`, runs bounded Z3 checks, and returns a
+`ProverDecision` contract:
+
+- `decision`: allow, deny, constrain, or require approval
+- `violations`: invariant ids that failed
+- `constraints`: narrower path, speed, or force bounds the executor must use
+- `obligations`: runtime monitor rules the executor must enforce
+- `counterexample`: proof-relevant coordinates for a denial
+- `solver_ms`: decision latency
+
+That boundary is the intended OpenShell transition point. In a later integration,
+the planner agent can run inside an OpenShell sandbox, submit action envelopes to
+an OpenShell-hosted policy-prover service, and receive the same decision contract before
+any MCP tool, A2A delegation, sandbox capability, or physical actuator command is
+allowed to execute.
+
+## Cadence
+
+Z3 is not a servo controller. The solver runs before each physical action or
+short-horizon segment. The executor then enforces returned obligations every sim
+tick. This mirrors an OpenShell runtime split:
+
+- agent runtime: propose/replan
+- policy prover: prove and return a contract
+- executor/proxy: enforce contract and stream audit events
+
+## Demo Surface
+
+The transcript is the audit stream. Each `agent_plan`, `prover_decision`,
+`execution_update`, and `world_event` is a future-compatible explanation surface
+for OpenShell operators and agents.
diff --git a/projects/robotics-policy-prover/LICENSE b/projects/robotics-policy-prover/LICENSE
new file mode 100644
index 00000000..261eeb9e
--- /dev/null
+++ b/projects/robotics-policy-prover/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/projects/robotics-policy-prover/README.md b/projects/robotics-policy-prover/README.md
new file mode 100644
index 00000000..dae0c735
--- /dev/null
+++ b/projects/robotics-policy-prover/README.md
@@ -0,0 +1,90 @@
+# OpenShell Robotics Prover Demo
+
+This is a small robotics demo for showing how an AI agent and a formal policy prover can work together.
+
+For the experiment, architecture, and DGX Spark latency results, read the
+[Dev Note](../../docs/dev-notes/posts/2026-08-07-formal-methods-ai-generated-robot-actions.md).
+
+The scene is simple on purpose: an agent proposes a tool-head path to pick up the green block and move it into the blue tray. Before the robot can move, the policy-prover service checks the proposed path against hard rules:
+
+- the path must stay inside the workspace
+- the path must not enter the red restricted zone
+- nearby humans clamp the allowed speed
+- approved moves emit runtime obligations
+
+If the path violates a rule, the prover returns a structured decision packet: a verdict, invariant ids, counterexample coordinates, constraints, obligations, and solver latency. The agent can use that packet to replan, but the prover remains the hard boundary.
+
+The point of the experiment is to make the OpenShell runtime idea visible: an autonomous agent may plan freely, but every tool, delegation, or physical action can be checked against a policy-prover contract before execution.
+
+## Run It
+
+Install dependencies:
+
+```shell
+npm install
+```
+
+Start the policy-prover service:
+
+```shell
+npm run dev:prover
+```
+
+In another terminal, start the web app:
+
+```shell
+npm run dev -- --port 5173
+```
+
+Open:
+
+```shell
+http://localhost:5173/
+```
+
+Click **Run Experiment**.
+
+## Agent Modes
+
+The app can run in two modes:
+
+- **Fixture**: deterministic and repeatable; good for presenting the demo.
+- **OpenAI-compatible**: uses `OPENAI_API_KEY` and an OpenAI-compatible chat/completions endpoint from `.env`; good for showing real model variability.
+
+Copy the example env file if you want to use the OpenAI-compatible mode:
+
+```shell
+cp .env.example .env
+```
+
+Then fill in your local values. Do not commit `.env`.
+
+## Test
+
+```shell
+cargo test --manifest-path policy-prover-service/Cargo.toml
+npm run build
+npm run verify:visual
+```
+
+## Benchmark the Policy Decision
+
+The benchmark calls the in-process policy decision directly in a release build.
+It covers allow, deny, and constrained outcomes at 3, 6, 12, 24, and 48
+waypoints. It intentionally excludes model inference, HTTP transport, rendering,
+and robot execution. This is an action-admission benchmark for agent-generated
+plans, not a benchmark for servo, balance, torque, or other low-level robot
+control loops.
+
+```shell
+POLICY_PROVER_BENCHMARK=1 \
+POLICY_PROVER_BENCHMARK_SAMPLES=5000 \
+POLICY_PROVER_BENCHMARK_WARMUP=1000 \
+POLICY_PROVER_BENCHMARK_OUTPUT=benchmarks/policy-latency.json \
+cargo run --release --manifest-path policy-prover-service/Cargo.toml
+```
+
+The command writes p50, p95, p99, mean, minimum, maximum, and throughput for
+each case. Add `POLICY_PROVER_BENCHMARK_PLATFORM` and
+`POLICY_PROVER_BENCHMARK_REVISION` when publishing results so the run has useful
+provenance.
diff --git a/projects/robotics-policy-prover/benchmarks/policy-latency.json b/projects/robotics-policy-prover/benchmarks/policy-latency.json
new file mode 100644
index 00000000..77e3c3d8
--- /dev/null
+++ b/projects/robotics-policy-prover/benchmarks/policy-latency.json
@@ -0,0 +1,210 @@
+{
+ "schema_version": 1,
+ "generated_unix_seconds": 1785963521,
+ "platform": "NVIDIA DGX Spark (GB10, 20-core Arm), Ubuntu 24.04; power mode uncontrolled",
+ "rustc_version": "rustc 1.89.0",
+ "z3_crate_version": "0.19.15 (bundled Z3 4.16.0)",
+ "source_revision": "sha256:ccef5186734bbd8440a5f2de1896fdccfd5d8c8457950276f7bd68d3af524030",
+ "build_profile": "release",
+ "samples_per_case": 5000,
+ "warmup_per_case": 1000,
+ "z3_timeout_ms": 18,
+ "geometry_samples_per_segment": 33,
+ "cases": [
+ {
+ "outcome": "allow",
+ "waypoints": 3,
+ "segments": 2,
+ "samples": 5000,
+ "p50_ms": 1.286694,
+ "p95_ms": 1.32911,
+ "p99_ms": 1.4626789999999998,
+ "mean_ms": 1.1921240943999996,
+ "min_ms": 1.006661,
+ "max_ms": 1.579591,
+ "decisions_per_second": 838.6331548059627
+ },
+ {
+ "outcome": "allow",
+ "waypoints": 6,
+ "segments": 5,
+ "samples": 5000,
+ "p50_ms": 1.283142,
+ "p95_ms": 1.3212059999999999,
+ "p99_ms": 1.456663,
+ "mean_ms": 1.1851682155999996,
+ "min_ms": 1.0073809999999999,
+ "max_ms": 1.672728,
+ "decisions_per_second": 843.547636417474
+ },
+ {
+ "outcome": "allow",
+ "waypoints": 12,
+ "segments": 11,
+ "samples": 5000,
+ "p50_ms": 1.2868389999999998,
+ "p95_ms": 1.352263,
+ "p99_ms": 1.488807,
+ "mean_ms": 1.1954138595999997,
+ "min_ms": 1.006965,
+ "max_ms": 1.6578160000000002,
+ "decisions_per_second": 836.2988629001082
+ },
+ {
+ "outcome": "allow",
+ "waypoints": 24,
+ "segments": 23,
+ "samples": 5000,
+ "p50_ms": 1.288902,
+ "p95_ms": 1.330166,
+ "p99_ms": 1.4569349999999999,
+ "mean_ms": 1.2001402726000008,
+ "min_ms": 1.006037,
+ "max_ms": 1.591416,
+ "decisions_per_second": 832.9968144210955
+ },
+ {
+ "outcome": "allow",
+ "waypoints": 48,
+ "segments": 47,
+ "samples": 5000,
+ "p50_ms": 1.290182,
+ "p95_ms": 1.3480219999999998,
+ "p99_ms": 1.4835429999999998,
+ "mean_ms": 1.2045971978000034,
+ "min_ms": 1.009925,
+ "max_ms": 1.651176,
+ "decisions_per_second": 829.9510061944304
+ },
+ {
+ "outcome": "deny",
+ "waypoints": 3,
+ "segments": 2,
+ "samples": 5000,
+ "p50_ms": 1.293142,
+ "p95_ms": 1.373527,
+ "p99_ms": 1.504583,
+ "mean_ms": 1.2304099209999988,
+ "min_ms": 1.025284,
+ "max_ms": 1.588728,
+ "decisions_per_second": 812.5349719419299
+ },
+ {
+ "outcome": "deny",
+ "waypoints": 6,
+ "segments": 5,
+ "samples": 5000,
+ "p50_ms": 1.2923749999999998,
+ "p95_ms": 1.348438,
+ "p99_ms": 1.492615,
+ "mean_ms": 1.2310014226000017,
+ "min_ms": 1.022661,
+ "max_ms": 1.6389040000000001,
+ "decisions_per_second": 812.1111485638907
+ },
+ {
+ "outcome": "deny",
+ "waypoints": 12,
+ "segments": 11,
+ "samples": 5000,
+ "p50_ms": 1.29607,
+ "p95_ms": 1.885961,
+ "p99_ms": 2.288971,
+ "mean_ms": 1.3667985840000023,
+ "min_ms": 1.0227089999999999,
+ "max_ms": 3.0832949999999997,
+ "decisions_per_second": 731.4171912391128
+ },
+ {
+ "outcome": "deny",
+ "waypoints": 24,
+ "segments": 23,
+ "samples": 5000,
+ "p50_ms": 1.292487,
+ "p95_ms": 1.366007,
+ "p99_ms": 1.4996390000000002,
+ "mean_ms": 1.2261184741999978,
+ "min_ms": 1.023029,
+ "max_ms": 1.653176,
+ "decisions_per_second": 815.3464832076048
+ },
+ {
+ "outcome": "deny",
+ "waypoints": 48,
+ "segments": 47,
+ "samples": 5000,
+ "p50_ms": 1.29311,
+ "p95_ms": 1.363926,
+ "p99_ms": 1.4983279999999999,
+ "mean_ms": 1.2238018578000038,
+ "min_ms": 1.0238450000000001,
+ "max_ms": 1.624568,
+ "decisions_per_second": 816.8816352255408
+ },
+ {
+ "outcome": "constrain",
+ "waypoints": 3,
+ "segments": 2,
+ "samples": 5000,
+ "p50_ms": 1.290198,
+ "p95_ms": 1.3494309999999998,
+ "p99_ms": 1.494711,
+ "mean_ms": 1.2125910404000018,
+ "min_ms": 1.021045,
+ "max_ms": 1.64716,
+ "decisions_per_second": 824.4203845419879
+ },
+ {
+ "outcome": "constrain",
+ "waypoints": 6,
+ "segments": 5,
+ "samples": 5000,
+ "p50_ms": 1.291494,
+ "p95_ms": 1.3599910000000002,
+ "p99_ms": 1.498856,
+ "mean_ms": 1.2176700970000005,
+ "min_ms": 1.0231089999999998,
+ "max_ms": 1.7223600000000001,
+ "decisions_per_second": 820.9859719565338
+ },
+ {
+ "outcome": "constrain",
+ "waypoints": 12,
+ "segments": 11,
+ "samples": 5000,
+ "p50_ms": 1.2899260000000001,
+ "p95_ms": 1.3432229999999998,
+ "p99_ms": 1.489207,
+ "mean_ms": 1.2026683088000027,
+ "min_ms": 1.0238450000000001,
+ "max_ms": 1.593112,
+ "decisions_per_second": 831.1719069453
+ },
+ {
+ "outcome": "constrain",
+ "waypoints": 24,
+ "segments": 23,
+ "samples": 5000,
+ "p50_ms": 1.265286,
+ "p95_ms": 1.332422,
+ "p99_ms": 1.4824389999999998,
+ "mean_ms": 1.1844469910000026,
+ "min_ms": 1.0204529999999998,
+ "max_ms": 1.654904,
+ "decisions_per_second": 843.9972494109386
+ },
+ {
+ "outcome": "constrain",
+ "waypoints": 48,
+ "segments": 47,
+ "samples": 5000,
+ "p50_ms": 1.29327,
+ "p95_ms": 1.376055,
+ "p99_ms": 1.498808,
+ "mean_ms": 1.2175342123999966,
+ "min_ms": 1.0238129999999999,
+ "max_ms": 1.6595119999999999,
+ "decisions_per_second": 821.06209384077
+ }
+ ]
+}
diff --git a/projects/robotics-policy-prover/index.html b/projects/robotics-policy-prover/index.html
new file mode 100644
index 00000000..4733559e
--- /dev/null
+++ b/projects/robotics-policy-prover/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ OpenShell Robot Governor
+
+
+
+
+
+
diff --git a/projects/robotics-policy-prover/package-lock.json b/projects/robotics-policy-prover/package-lock.json
new file mode 100644
index 00000000..c449845b
--- /dev/null
+++ b/projects/robotics-policy-prover/package-lock.json
@@ -0,0 +1,1636 @@
+{
+ "name": "openshell-robotics-prover-demo",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "openshell-robotics-prover-demo",
+ "version": "0.1.0",
+ "dependencies": {
+ "@react-three/drei": "^10.7.7",
+ "@react-three/fiber": "^9.6.1",
+ "lucide-react": "^1.17.0",
+ "react": "^19.2.6",
+ "react-dom": "^19.2.6",
+ "three": "^0.184.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^6.0.2",
+ "playwright": "^1.60.0",
+ "vite": "^8.0.14"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
+ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@dimforge/rapier3d-compat": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
+ "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@mediapipe/tasks-vision": {
+ "version": "0.10.17",
+ "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz",
+ "integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@monogrid/gainmap-js": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz",
+ "integrity": "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==",
+ "license": "MIT",
+ "dependencies": {
+ "promise-worker-transferable": "^1.0.4"
+ },
+ "peerDependencies": {
+ "three": ">= 0.159.0"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.143.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz",
+ "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@react-three/drei": {
+ "version": "10.7.7",
+ "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz",
+ "integrity": "sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@mediapipe/tasks-vision": "0.10.17",
+ "@monogrid/gainmap-js": "^3.0.6",
+ "@use-gesture/react": "^10.3.1",
+ "camera-controls": "^3.1.0",
+ "cross-env": "^7.0.3",
+ "detect-gpu": "^5.0.56",
+ "glsl-noise": "^0.0.0",
+ "hls.js": "^1.5.17",
+ "maath": "^0.10.8",
+ "meshline": "^3.3.1",
+ "stats-gl": "^2.2.8",
+ "stats.js": "^0.17.0",
+ "suspend-react": "^0.1.3",
+ "three-mesh-bvh": "^0.8.3",
+ "three-stdlib": "^2.35.6",
+ "troika-three-text": "^0.52.4",
+ "tunnel-rat": "^0.1.2",
+ "use-sync-external-store": "^1.4.0",
+ "utility-types": "^3.11.0",
+ "zustand": "^5.0.1"
+ },
+ "peerDependencies": {
+ "@react-three/fiber": "^9.0.0",
+ "react": "^19",
+ "react-dom": "^19",
+ "three": ">=0.159"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-three/fiber": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.1.tgz",
+ "integrity": "sha512-zF0rsKcVYpcJwbFEnv2HkHX9cvOEgsfQo/X8lwmR2dn13S4qEQJXir9fxf5js2LQFoXqxOY7MDkOkYx2uZ4gSg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.17.8",
+ "@types/webxr": "*",
+ "base64-js": "^1.5.1",
+ "buffer": "^6.0.3",
+ "its-fine": "^2.0.0",
+ "react-use-measure": "^2.1.7",
+ "scheduler": "^0.27.0",
+ "suspend-react": "^0.1.3",
+ "use-sync-external-store": "^1.4.0",
+ "zustand": "^5.0.3"
+ },
+ "peerDependencies": {
+ "expo": ">=43.0",
+ "expo-asset": ">=8.4",
+ "expo-file-system": ">=11.0",
+ "expo-gl": ">=11.0",
+ "react": ">=19 <19.3",
+ "react-dom": ">=19 <19.3",
+ "react-native": ">=0.78",
+ "three": ">=0.156"
+ },
+ "peerDependenciesMeta": {
+ "expo": {
+ "optional": true
+ },
+ "expo-asset": {
+ "optional": true
+ },
+ "expo-file-system": {
+ "optional": true
+ },
+ "expo-gl": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz",
+ "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz",
+ "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz",
+ "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz",
+ "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz",
+ "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz",
+ "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz",
+ "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz",
+ "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz",
+ "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz",
+ "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz",
+ "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz",
+ "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz",
+ "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz",
+ "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tweenjs/tween.js": {
+ "version": "23.1.3",
+ "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
+ "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/draco3d": {
+ "version": "1.4.10",
+ "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
+ "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/offscreencanvas": {
+ "version": "2019.7.3",
+ "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
+ "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.15",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz",
+ "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-reconciler": {
+ "version": "0.28.9",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
+ "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/stats.js": {
+ "version": "0.17.4",
+ "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
+ "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/three": {
+ "version": "0.184.1",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.184.1.tgz",
+ "integrity": "sha512-6q4VdiqVsrTRqmk62/BnlcAvIrnDM0zf2ZDVKI5kZiniWrSaOHaQzmbp+BNzoggc/8tgW412pL//wZIxu2PPTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@dimforge/rapier3d-compat": "~0.12.0",
+ "@tweenjs/tween.js": "~23.1.3",
+ "@types/stats.js": "*",
+ "@types/webxr": ">=0.5.17",
+ "fflate": "~0.8.2",
+ "meshoptimizer": "~1.1.1"
+ }
+ },
+ "node_modules/@types/webxr": {
+ "version": "0.5.24",
+ "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
+ "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
+ "license": "MIT"
+ },
+ "node_modules/@use-gesture/core": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz",
+ "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==",
+ "license": "MIT"
+ },
+ "node_modules/@use-gesture/react": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz",
+ "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@use-gesture/core": "10.3.1"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz",
+ "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "vite": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rolldown/plugin-babel": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/camera-controls": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.2.tgz",
+ "integrity": "sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=22.0.0",
+ "npm": ">=10.5.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.126.1"
+ }
+ },
+ "node_modules/cross-env": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
+ "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "bin": {
+ "cross-env": "src/bin/cross-env.js",
+ "cross-env-shell": "src/bin/cross-env-shell.js"
+ },
+ "engines": {
+ "node": ">=10.14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/detect-gpu": {
+ "version": "5.0.70",
+ "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
+ "integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==",
+ "license": "MIT",
+ "dependencies": {
+ "webgl-constants": "^1.1.1"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/draco3d": {
+ "version": "1.5.7",
+ "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
+ "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fflate": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
+ "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
+ "license": "MIT"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/glsl-noise": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz",
+ "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==",
+ "license": "MIT"
+ },
+ "node_modules/hls.js": {
+ "version": "1.6.16",
+ "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.16.tgz",
+ "integrity": "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-promise": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/its-fine": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz",
+ "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/react-reconciler": "^0.28.9"
+ },
+ "peerDependencies": {
+ "react": "^19.0.0"
+ }
+ },
+ "node_modules/lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz",
+ "integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/maath": {
+ "version": "0.10.8",
+ "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",
+ "integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/three": ">=0.134.0",
+ "three": ">=0.134.0"
+ }
+ },
+ "node_modules/meshline": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz",
+ "integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "three": ">=0.137"
+ }
+ },
+ "node_modules/meshoptimizer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz",
+ "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/playwright": {
+ "version": "1.60.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
+ "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.60.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.60.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
+ "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.26",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.17",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/potpack": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
+ "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==",
+ "license": "ISC"
+ },
+ "node_modules/promise-worker-transferable": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
+ "integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "is-promise": "^2.1.0",
+ "lie": "^3.0.2"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
+ "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz",
+ "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.6"
+ }
+ },
+ "node_modules/react-use-measure": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
+ "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.13",
+ "react-dom": ">=16.13"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz",
+ "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.143.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.2.3",
+ "@rolldown/binding-darwin-arm64": "1.2.3",
+ "@rolldown/binding-darwin-x64": "1.2.3",
+ "@rolldown/binding-freebsd-x64": "1.2.3",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.3",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.3",
+ "@rolldown/binding-linux-arm64-musl": "1.2.3",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.3",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.3",
+ "@rolldown/binding-linux-x64-gnu": "1.2.3",
+ "@rolldown/binding-linux-x64-musl": "1.2.3",
+ "@rolldown/binding-openharmony-arm64": "1.2.3",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.3",
+ "@rolldown/binding-win32-x64-msvc": "1.2.3"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stats-gl": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz",
+ "integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/three": "*",
+ "three": "^0.170.0"
+ },
+ "peerDependencies": {
+ "@types/three": "*",
+ "three": "*"
+ }
+ },
+ "node_modules/stats-gl/node_modules/three": {
+ "version": "0.170.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz",
+ "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==",
+ "license": "MIT"
+ },
+ "node_modules/stats.js": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
+ "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
+ "license": "MIT"
+ },
+ "node_modules/suspend-react": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
+ "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=17.0"
+ }
+ },
+ "node_modules/three": {
+ "version": "0.184.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.184.0.tgz",
+ "integrity": "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==",
+ "license": "MIT"
+ },
+ "node_modules/three-mesh-bvh": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.8.3.tgz",
+ "integrity": "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "three": ">= 0.159.0"
+ }
+ },
+ "node_modules/three-stdlib": {
+ "version": "2.36.1",
+ "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.1.tgz",
+ "integrity": "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/draco3d": "^1.4.0",
+ "@types/offscreencanvas": "^2019.6.4",
+ "@types/webxr": "^0.5.2",
+ "draco3d": "^1.4.1",
+ "fflate": "^0.6.9",
+ "potpack": "^1.0.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.128.0"
+ }
+ },
+ "node_modules/three-stdlib/node_modules/fflate": {
+ "version": "0.6.10",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
+ "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/troika-three-text": {
+ "version": "0.52.4",
+ "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz",
+ "integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==",
+ "license": "MIT",
+ "dependencies": {
+ "bidi-js": "^1.0.2",
+ "troika-three-utils": "^0.52.4",
+ "troika-worker-utils": "^0.52.0",
+ "webgl-sdf-generator": "1.1.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.125.0"
+ }
+ },
+ "node_modules/troika-three-utils": {
+ "version": "0.52.4",
+ "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.4.tgz",
+ "integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "three": ">=0.125.0"
+ }
+ },
+ "node_modules/troika-worker-utils": {
+ "version": "0.52.0",
+ "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz",
+ "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==",
+ "license": "MIT"
+ },
+ "node_modules/tunnel-rat": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz",
+ "integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==",
+ "license": "MIT",
+ "dependencies": {
+ "zustand": "^4.3.2"
+ }
+ },
+ "node_modules/tunnel-rat/node_modules/zustand": {
+ "version": "4.5.7",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
+ "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.2.2"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/utility-types": {
+ "version": "3.11.0",
+ "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
+ "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz",
+ "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.25",
+ "rolldown": "~1.2.1",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.4.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.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
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/webgl-constants": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz",
+ "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="
+ },
+ "node_modules/webgl-sdf-generator": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz",
+ "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
+ "license": "MIT"
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.14",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz",
+ "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/projects/robotics-policy-prover/package.json b/projects/robotics-policy-prover/package.json
new file mode 100644
index 00000000..15598eb7
--- /dev/null
+++ b/projects/robotics-policy-prover/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "openshell-robotics-prover-demo",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite --host 0.0.0.0",
+ "dev:prover": "cargo run --manifest-path policy-prover-service/Cargo.toml",
+ "build": "vite build",
+ "preview": "vite preview --host 0.0.0.0",
+ "verify:visual": "node scripts/verify-visual.mjs"
+ },
+ "dependencies": {
+ "@react-three/drei": "^10.7.7",
+ "@react-three/fiber": "^9.6.1",
+ "lucide-react": "^1.17.0",
+ "react": "^19.2.6",
+ "react-dom": "^19.2.6",
+ "three": "^0.184.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^6.0.2",
+ "playwright": "^1.60.0",
+ "vite": "^8.0.14"
+ }
+}
diff --git a/projects/robotics-policy-prover/policy-prover-service/Cargo.lock b/projects/robotics-policy-prover/policy-prover-service/Cargo.lock
new file mode 100644
index 00000000..8a0ae72d
--- /dev/null
+++ b/projects/robotics-policy-prover/policy-prover-service/Cargo.lock
@@ -0,0 +1,2383 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "aes"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138"
+dependencies = [
+ "cipher",
+ "cpubits",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "axum"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
+dependencies = [
+ "axum-core",
+ "bytes",
+ "form_urlencoded",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "serde_core",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bindgen"
+version = "0.72.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex 1.3.0",
+ "syn",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+
+[[package]]
+name = "block-buffer"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
+dependencies = [
+ "hybrid-array",
+ "zeroize",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "bzip2"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c"
+dependencies = [
+ "libbz2-rs-sys",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex 2.0.1",
+]
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "cipher"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clang-sys"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "cmov"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
+
+[[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
+name = "constant_time_eq"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
+
+[[package]]
+name = "cpubits"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
+
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "ctutils"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
+dependencies = [
+ "cmov",
+]
+
+[[package]]
+name = "deflate64"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2"
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer",
+ "const-oid",
+ "crypto-common",
+ "ctutils",
+ "zeroize",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dotenvy"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
+
+[[package]]
+name = "either"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
+name = "flate2"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
+dependencies = [
+ "miniz_oxide",
+ "zlib-rs",
+]
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
+
+[[package]]
+name = "futures-io"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
+
+[[package]]
+name = "futures-task"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
+
+[[package]]
+name = "futures-util"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi 5.3.0",
+ "wasip2",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi 6.0.0",
+ "wasip2",
+ "wasip3",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "foldhash",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hmac"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "http"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "hybrid-array"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "hyper"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb92f162bf56536459fc83c79b974bb12837acfed43d6bc370a7916d0ae15ecc"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+ "webpki-roots",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "utf8_iter",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
+
+[[package]]
+name = "icu_properties"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
+
+[[package]]
+name = "icu_provider"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "id-arena"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.17.1",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "inout"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "jobserver"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
+dependencies = [
+ "getrandom 0.3.4",
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "leb128fmt"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
+
+[[package]]
+name = "libbz2-rs-sys"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c"
+
+[[package]]
+name = "libc"
+version = "0.2.186"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+
+[[package]]
+name = "libloading"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
+dependencies = [
+ "cfg-if",
+ "windows-link",
+]
+
+[[package]]
+name = "litemap"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+
+[[package]]
+name = "log"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
+
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
+[[package]]
+name = "lzma-rust2"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e9ceaec84b54518262de7cf06b8b43e83c808349960f1610b21b0bfc9640f20"
+dependencies = [
+ "sha2",
+]
+
+[[package]]
+name = "matchit"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
+
+[[package]]
+name = "memchr"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
+name = "mio"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "pbkdf2"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629"
+dependencies = [
+ "digest",
+ "hmac",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
+
+[[package]]
+name = "policy-prover-service"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "axum",
+ "dotenvy",
+ "futures-util",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "tokio",
+ "tokio-stream",
+ "tower-http",
+ "uuid",
+ "z3",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppmd-rust"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quinn"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
+dependencies = [
+ "bytes",
+ "getrandom 0.3.4",
+ "lru-slab",
+ "rand",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rand"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+dependencies = [
+ "getrandom 0.3.4",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
+
+[[package]]
+name = "reqwest"
+version = "0.12.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "webpki-roots",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
+
+[[package]]
+name = "rustls"
+version = "0.23.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
+dependencies = [
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.150"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
+dependencies = [
+ "itoa",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "socket2"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.3.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
+dependencies = [
+ "deranged",
+ "js-sys",
+ "num-conv",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
+
+[[package]]
+name = "tinystr"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.52.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
+dependencies = [
+ "bitflags",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "url",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "typed-path"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
+
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "uuid"
+version = "1.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7"
+dependencies = [
+ "getrandom 0.4.2",
+ "js-sys",
+ "serde_core",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.3+wasi-0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
+dependencies = [
+ "wit-bindgen 0.57.1",
+]
+
+[[package]]
+name = "wasip3"
+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
+dependencies = [
+ "wit-bindgen 0.51.0",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.122"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.122"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.122"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.122"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasm-encoder"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
+dependencies = [
+ "leb128fmt",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasm-metadata"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
+dependencies = [
+ "anyhow",
+ "indexmap",
+ "wasm-encoder",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
+dependencies = [
+ "bitflags",
+ "hashbrown 0.15.5",
+ "indexmap",
+ "semver",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[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.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+dependencies = [
+ "wit-bindgen-rust-macro",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
+[[package]]
+name = "wit-bindgen-core"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
+dependencies = [
+ "anyhow",
+ "heck",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-bindgen-rust"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
+dependencies = [
+ "anyhow",
+ "heck",
+ "indexmap",
+ "prettyplease",
+ "syn",
+ "wasm-metadata",
+ "wit-bindgen-core",
+ "wit-component",
+]
+
+[[package]]
+name = "wit-bindgen-rust-macro"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
+dependencies = [
+ "anyhow",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wit-bindgen-core",
+ "wit-bindgen-rust",
+]
+
+[[package]]
+name = "wit-component"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
+dependencies = [
+ "anyhow",
+ "bitflags",
+ "indexmap",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder",
+ "wasm-metadata",
+ "wasmparser",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser",
+]
+
+[[package]]
+name = "writeable"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+
+[[package]]
+name = "yoke"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "z3"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "107cca65ed27d28b11f7c492298a51383333fd48ba6ebe49a432aba96162f678"
+dependencies = [
+ "log",
+ "num",
+ "z3-sys",
+]
+
+[[package]]
+name = "z3-sys"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c82b97329d02d87da6802ed9fda083f1b255d822ab13d5b1fb961196b58a69a1"
+dependencies = [
+ "bindgen",
+ "cmake",
+ "pkg-config",
+ "reqwest",
+ "serde_json",
+ "zip",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.49"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.49"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zip"
+version = "8.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
+dependencies = [
+ "aes",
+ "bzip2",
+ "constant_time_eq",
+ "crc32fast",
+ "deflate64",
+ "flate2",
+ "getrandom 0.4.2",
+ "hmac",
+ "indexmap",
+ "lzma-rust2",
+ "memchr",
+ "pbkdf2",
+ "ppmd-rust",
+ "sha1",
+ "time",
+ "typed-path",
+ "zeroize",
+ "zopfli",
+ "zstd",
+]
+
+[[package]]
+name = "zlib-rs"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513"
+
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+
+[[package]]
+name = "zopfli"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
+dependencies = [
+ "bumpalo",
+ "crc32fast",
+ "log",
+ "simd-adler32",
+]
+
+[[package]]
+name = "zstd"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "7.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
+dependencies = [
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.16+zstd.1.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
diff --git a/projects/robotics-policy-prover/policy-prover-service/Cargo.toml b/projects/robotics-policy-prover/policy-prover-service/Cargo.toml
new file mode 100644
index 00000000..afad0fb0
--- /dev/null
+++ b/projects/robotics-policy-prover/policy-prover-service/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "policy-prover-service"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1"
+axum = "0.8"
+dotenvy = "0.15"
+futures-util = "0.3"
+reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
+tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync", "time"] }
+tokio-stream = { version = "0.1", features = ["sync"] }
+tower-http = { version = "0.6", features = ["cors"] }
+uuid = { version = "1", features = ["v4", "serde"] }
+z3 = { version = "0.19", features = ["bundled"] }
diff --git a/projects/robotics-policy-prover/policy-prover-service/src/main.rs b/projects/robotics-policy-prover/policy-prover-service/src/main.rs
new file mode 100644
index 00000000..69210de2
--- /dev/null
+++ b/projects/robotics-policy-prover/policy-prover-service/src/main.rs
@@ -0,0 +1,1469 @@
+use std::collections::HashMap;
+use std::convert::Infallible;
+use std::env;
+use std::fs;
+use std::hint::black_box;
+use std::net::SocketAddr;
+use std::sync::Arc;
+use std::time::{Instant, SystemTime, UNIX_EPOCH};
+
+use anyhow::{Context, Result};
+use axum::extract::{Path, State};
+use axum::response::sse::{Event, KeepAlive, Sse};
+use axum::routing::{get, post};
+use axum::{Json, Router};
+use futures_util::StreamExt;
+use reqwest::Client;
+use serde::{Deserialize, Serialize};
+use tokio::sync::{broadcast, Mutex};
+use tokio::time::{sleep, Duration};
+use tokio_stream::wrappers::BroadcastStream;
+use tower_http::cors::CorsLayer;
+use uuid::Uuid;
+use z3::ast::Bool;
+use z3::{Config, Solver};
+
+const WORKSPACE_MIN: Vec3 = [-1.35, 0.02, -0.95];
+const WORKSPACE_MAX: Vec3 = [1.25, 1.05, 0.85];
+const HOME: Vec3 = [-1.08, 0.78, 0.58];
+
+type Vec3 = [f64; 3];
+
+#[derive(Clone)]
+struct AppState {
+ sessions: Arc>>,
+}
+
+#[derive(Clone)]
+struct SessionHandle {
+ tx: broadcast::Sender,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct StartSessionRequest {
+ goal: Option,
+ seed: Option,
+ agent_mode: Option,
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "snake_case")]
+enum AgentMode {
+ Fixture,
+ Openai,
+}
+
+#[derive(Debug, Clone, Serialize)]
+struct StartSessionResponse {
+ session_id: Uuid,
+ agent_mode: AgentMode,
+ seed: u64,
+}
+
+#[derive(Debug, Clone, Deserialize)]
+struct InjectRequest {
+ event: InjectEvent,
+}
+
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "snake_case")]
+enum InjectEvent {
+ HumanEntersWorkspace,
+ SensorStale,
+ BudgetLow,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct ActionEnvelope {
+ actor: String,
+ subagent: String,
+ action: String,
+ resource: String,
+ from: Vec3,
+ to: Vec3,
+ path: Vec,
+ speed_mps: f64,
+ force_n: f64,
+ object_id: String,
+ object_class: String,
+ capability: String,
+ context: ActionContext,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct ActionContext {
+ human_distance_m: f64,
+ sensor_age_ms: u64,
+ remaining_budget_ms: u64,
+ restricted_zones: Vec,
+ caution_zones: Vec,
+ parent_speed_cap_mps: f64,
+ requested_speed_cap_mps: f64,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct ProverDecision {
+ decision: Decision,
+ solver_ms: f64,
+ violations: Vec,
+ constraints: DecisionConstraints,
+ obligations: Vec,
+ counterexample: Option,
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "snake_case")]
+enum Decision {
+ Allow,
+ Deny,
+ AllowWithConstraints,
+ ApprovalRequired,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+struct DecisionConstraints {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ speed_mps_max: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ force_n_max: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ bypass_z_min: Option,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct Counterexample {
+ segment_id: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ zone_id: Option,
+ reason: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ point: Option,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct WorldState {
+ goal: String,
+ seed: u64,
+ objects: Vec,
+ zones: Vec,
+ human: HumanState,
+ metrics: Metrics,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct SceneObject {
+ id: String,
+ label: String,
+ class_name: String,
+ color: String,
+ position: Vec3,
+ size: Vec3,
+ sorted: bool,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct Zone {
+ id: String,
+ label: String,
+ color: String,
+ position: Vec3,
+ size: Vec3,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct HumanState {
+ position: Vec3,
+ radius: f64,
+ caution: f64,
+ distance_m: f64,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct Metrics {
+ fps: u32,
+ decision_count: u32,
+ p95_solver_ms: f64,
+ task_budget_pct: u8,
+ compute_budget_pct: u8,
+ actions_left: u8,
+ sensor_age_ms: u64,
+}
+
+#[derive(Debug, Clone, Serialize)]
+struct DemoEvent {
+ id: String,
+ kind: String,
+ timestamp_ms: u64,
+ actor: String,
+ summary: String,
+ world: Option,
+ action: Option,
+ decision: Option,
+ proposed_path: Option>,
+ approved_path: Option>,
+ highlight: Option,
+}
+
+#[derive(Debug, Clone, Serialize)]
+struct Highlight {
+ kind: String,
+ target: Option,
+}
+
+#[derive(Debug, Clone)]
+struct MissionRuntime {
+ tx: broadcast::Sender,
+ world: WorldState,
+ started: Instant,
+ solver_samples: Vec,
+ agent_mode: AgentMode,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+struct AgentPlan {
+ path: Vec,
+ speed_mps: f64,
+ rationale: String,
+ #[serde(default)]
+ source: String,
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ dotenvy::dotenv().ok();
+
+ if env::var("POLICY_PROVER_BENCHMARK").as_deref() == Ok("1") {
+ run_policy_benchmark()?;
+ return Ok(());
+ }
+
+ let state = AppState {
+ sessions: Arc::new(Mutex::new(HashMap::new())),
+ };
+
+ let app = Router::new()
+ .route("/api/health", get(|| async { Json(serde_json::json!({ "ok": true })) }))
+ .route("/api/sessions", post(start_session))
+ .route("/api/sessions/{id}/events", get(session_events))
+ .route("/api/sessions/{id}/inject", post(inject_event))
+ .route("/api/decide", post(decide))
+ .layer(CorsLayer::permissive())
+ .with_state(state);
+
+ let addr: SocketAddr = "127.0.0.1:8787".parse()?;
+ println!("policy-prover service listening on http://{addr}");
+ let listener = tokio::net::TcpListener::bind(addr).await?;
+ axum::serve(listener, app).await?;
+ Ok(())
+}
+
+async fn start_session(
+ State(state): State,
+ Json(request): Json,
+) -> Json {
+ let session_id = Uuid::new_v4();
+ let seed = request.seed.unwrap_or(42);
+ let requested_mode = request.agent_mode.unwrap_or(AgentMode::Fixture);
+ let agent_mode = if requested_mode == AgentMode::Openai && env::var("OPENAI_API_KEY").is_ok() {
+ AgentMode::Openai
+ } else {
+ AgentMode::Fixture
+ };
+ let goal = request
+ .goal
+ .unwrap_or_else(|| "Sort all lab samples into the correct trays.".to_owned());
+ let (tx, _) = broadcast::channel(256);
+ state
+ .sessions
+ .lock()
+ .await
+ .insert(session_id, SessionHandle { tx: tx.clone() });
+
+ let runtime = MissionRuntime {
+ tx,
+ world: seeded_world(seed, goal),
+ started: Instant::now(),
+ solver_samples: Vec::new(),
+ agent_mode,
+ };
+ tokio::spawn(async move {
+ if let Err(err) = run_mission(runtime).await {
+ eprintln!("mission {session_id} failed: {err:#}");
+ }
+ });
+
+ Json(StartSessionResponse {
+ session_id,
+ agent_mode,
+ seed,
+ })
+}
+
+async fn session_events(
+ State(state): State,
+ Path(id): Path,
+) -> Sse>> {
+ let rx = state
+ .sessions
+ .lock()
+ .await
+ .get(&id)
+ .map(|handle| handle.tx.subscribe());
+
+ let stream = match rx {
+ Some(rx) => BroadcastStream::new(rx)
+ .filter_map(|message| async move { message.ok() })
+ .map(|event| {
+ let data = serde_json::to_string(&event).unwrap_or_else(|_| "{}".to_owned());
+ Ok(Event::default().event(event.kind).id(event.id).data(data))
+ })
+ .boxed(),
+ None => {
+ let event = DemoEvent::system("session_missing", "Session not found", None);
+ tokio_stream::iter([Ok(Event::default()
+ .event("error")
+ .id(event.id.clone())
+ .data(serde_json::to_string(&event).unwrap()))])
+ .boxed()
+ }
+ };
+
+ Sse::new(stream).keep_alive(KeepAlive::default())
+}
+
+async fn inject_event(
+ State(state): State,
+ Path(id): Path,
+ Json(request): Json,
+) -> Json {
+ let Some(handle) = state.sessions.lock().await.get(&id).cloned() else {
+ return Json(serde_json::json!({ "ok": false, "error": "session_not_found" }));
+ };
+ let summary = match request.event {
+ InjectEvent::HumanEntersWorkspace => "Injected: human entered the workspace",
+ InjectEvent::SensorStale => "Injected: sensor state is stale",
+ InjectEvent::BudgetLow => "Injected: task budget is almost exhausted",
+ };
+ let _ = handle.tx.send(DemoEvent::system("world_event", summary, None));
+ Json(serde_json::json!({ "ok": true }))
+}
+
+async fn decide(Json(action): Json) -> Json {
+ Json(check_action(&action))
+}
+
+async fn run_mission(mut runtime: MissionRuntime) -> Result<()> {
+ sleep(Duration::from_millis(250)).await;
+ runtime.emit_world(
+ "world_event",
+ "Experiment started: move the green block into the blue tray.",
+ );
+ sleep(Duration::from_millis(700)).await;
+
+ let initial_plan = runtime
+ .choose_plan(None)
+ .await
+ .unwrap_or_else(|| fixture_plan(&runtime.world, None));
+ let proposal = action_from_plan(&runtime.world, &initial_plan);
+
+ runtime
+ .agent_plan(
+ "Agent proposed a waypoint path",
+ &proposal,
+ None,
+ &initial_plan,
+ )
+ .await;
+ let first_decision = runtime.check_only(proposal.clone()).await?;
+
+ if first_decision.decision == Decision::Deny {
+ sleep(Duration::from_millis(900)).await;
+ let mut repair_plan = runtime
+ .choose_plan(Some(&first_decision))
+ .await
+ .unwrap_or_else(|| fixture_plan(&runtime.world, Some(&first_decision)));
+ let mut repair = action_from_plan(&runtime.world, &repair_plan);
+ if runtime.agent_mode == AgentMode::Fixture
+ && first_path_intersection(&repair.path, &repair.context.restricted_zones).is_some()
+ {
+ repair_plan = fixture_plan(&runtime.world, Some(&first_decision));
+ repair = action_from_plan(&runtime.world, &repair_plan);
+ }
+
+ runtime
+ .agent_plan(
+ "Agent proposed a revised waypoint path",
+ &repair,
+ Some(&first_decision),
+ &repair_plan,
+ )
+ .await;
+
+ runtime.world.human.position = [0.12, 0.08, -0.22];
+ runtime.world.human.distance_m = 0.48;
+ runtime.emit_world(
+ "world_event",
+ "A human enters the caution radius before execution.",
+ );
+ sleep(Duration::from_millis(500)).await;
+ let mut repair_with_current_world = action_for(
+ &runtime.world,
+ "green_vial",
+ repair.path.clone(),
+ repair.speed_mps,
+ repair.force_n,
+ "move_lab_samples",
+ );
+ repair_with_current_world.speed_mps = repair.speed_mps;
+ runtime
+ .check_and_execute(
+ repair_with_current_world,
+ Some("Executor runs the revised path with the prover's speed limit"),
+ )
+ .await?;
+ } else {
+ runtime
+ .execute_decision(
+ proposal,
+ first_decision,
+ Some("Executor runs the agent path"),
+ )
+ .await?;
+ }
+
+ runtime.emit_world(
+ "execution_update",
+ "Experiment complete: only the policy-prover-approved path was executed.",
+ );
+ Ok(())
+}
+
+impl MissionRuntime {
+ fn elapsed_ms(&self) -> u64 {
+ self.started.elapsed().as_millis() as u64
+ }
+
+ fn emit(&self, mut event: DemoEvent) {
+ event.timestamp_ms = self.elapsed_ms();
+ let _ = self.tx.send(event);
+ }
+
+ fn emit_world(&self, kind: &str, summary: &str) {
+ self.emit(DemoEvent {
+ id: Uuid::new_v4().to_string(),
+ kind: kind.to_owned(),
+ timestamp_ms: 0,
+ actor: "world".to_owned(),
+ summary: summary.to_owned(),
+ world: Some(self.world.clone()),
+ action: None,
+ decision: None,
+ proposed_path: None,
+ approved_path: None,
+ highlight: Some(Highlight {
+ kind: "world".to_owned(),
+ target: None,
+ }),
+ });
+ }
+
+ async fn agent_plan(
+ &self,
+ summary: &str,
+ action: &ActionEnvelope,
+ _prior: Option<&ProverDecision>,
+ plan: &AgentPlan,
+ ) {
+ self.emit(DemoEvent {
+ id: Uuid::new_v4().to_string(),
+ kind: "agent_plan".to_owned(),
+ timestamp_ms: 0,
+ actor: "agent".to_owned(),
+ summary: format!(
+ "{summary}: {} waypoints at {:.2} m/s. Source: {}.",
+ action.path.len(),
+ action.speed_mps,
+ plan.source
+ ),
+ world: Some(self.world.clone()),
+ action: Some(action.clone()),
+ decision: None,
+ proposed_path: Some(action.path.clone()),
+ approved_path: None,
+ highlight: Some(Highlight {
+ kind: "proposal".to_owned(),
+ target: Some(action.object_id.clone()),
+ }),
+ });
+ }
+
+ async fn choose_plan(&self, prior: Option<&ProverDecision>) -> Option {
+ if self.agent_mode != AgentMode::Openai {
+ return Some(fixture_plan(&self.world, prior));
+ }
+ ask_openai_plan(&self.world, prior).await.ok()
+ }
+
+ async fn check_only(&mut self, action: ActionEnvelope) -> Result {
+ let decision = check_action(&action);
+ self.solver_samples.push(decision.solver_ms);
+ self.world.metrics.decision_count += 1;
+ self.world.metrics.p95_solver_ms = p95(&self.solver_samples);
+
+ let approved_path = match decision.decision {
+ Decision::Allow | Decision::AllowWithConstraints | Decision::ApprovalRequired => {
+ Some(action.path.clone())
+ }
+ Decision::Deny => None,
+ };
+ self.emit(DemoEvent {
+ id: Uuid::new_v4().to_string(),
+ kind: "prover_decision".to_owned(),
+ timestamp_ms: 0,
+ actor: "policy-prover".to_owned(),
+ summary: decision_summary(&decision),
+ world: Some(self.world.clone()),
+ action: Some(action.clone()),
+ decision: Some(decision.clone()),
+ proposed_path: Some(action.path.clone()),
+ approved_path,
+ highlight: Some(Highlight {
+ kind: format!("{:?}", decision.decision).to_lowercase(),
+ target: decision
+ .counterexample
+ .as_ref()
+ .and_then(|counterexample| counterexample.zone_id.clone())
+ .or_else(|| Some(action.object_id.clone())),
+ }),
+ });
+ Ok(decision)
+ }
+
+ async fn check_and_execute(
+ &mut self,
+ action: ActionEnvelope,
+ executor_note: Option<&str>,
+ ) -> Result<()> {
+ let decision = self.check_only(action.clone()).await?;
+ if decision.decision == Decision::Deny {
+ return Ok(());
+ }
+ self.execute_decision(action, decision, executor_note).await
+ }
+
+ async fn execute_decision(
+ &mut self,
+ action: ActionEnvelope,
+ decision: ProverDecision,
+ executor_note: Option<&str>,
+ ) -> Result<()> {
+ sleep(Duration::from_millis(600)).await;
+ let speed = decision
+ .constraints
+ .speed_mps_max
+ .map(|value| format!(" at constrained speed <= {value:.2} m/s"))
+ .unwrap_or_default();
+ let note = executor_note.unwrap_or("Executor running approved segment");
+ self.emit(DemoEvent {
+ id: Uuid::new_v4().to_string(),
+ kind: "execution_update".to_owned(),
+ timestamp_ms: 0,
+ actor: "executor".to_owned(),
+ summary: format!("{note}{speed}"),
+ world: Some(self.world.clone()),
+ action: Some(action.clone()),
+ decision: Some(decision),
+ proposed_path: Some(action.path.clone()),
+ approved_path: Some(action.path.clone()),
+ highlight: Some(Highlight {
+ kind: "execution".to_owned(),
+ target: Some(action.object_id),
+ }),
+ });
+ Ok(())
+ }
+}
+
+impl DemoEvent {
+ fn system(kind: &str, summary: &str, world: Option) -> Self {
+ Self {
+ id: Uuid::new_v4().to_string(),
+ kind: kind.to_owned(),
+ timestamp_ms: 0,
+ actor: "system".to_owned(),
+ summary: summary.to_owned(),
+ world,
+ action: None,
+ decision: None,
+ proposed_path: None,
+ approved_path: None,
+ highlight: None,
+ }
+ }
+}
+
+fn check_action(action: &ActionEnvelope) -> ProverDecision {
+ let start = Instant::now();
+ let mut config = Config::new();
+ config.set_timeout_msec(18);
+ let _ = z3::with_z3_config(&config, || {
+ let solver = Solver::new();
+
+ let out_of_bounds = action.path.iter().any(|point| !inside_workspace(*point));
+ let restricted_hit = first_path_intersection(&action.path, &action.context.restricted_zones);
+ let stale_sensor = action.context.sensor_age_ms > 250;
+ let budget_blocked = action.context.remaining_budget_ms < 1_200 && action.action != "arm.move_segment";
+ let capability_expansion = action.action == "grant.capability"
+ && (action.capability == "unrestricted_motion"
+ || action.context.requested_speed_cap_mps > action.context.parent_speed_cap_mps);
+ let human_speed_cap = action.context.human_distance_m < 0.75 && action.speed_mps > 0.08;
+ let fragile_force_cap =
+ matches!(action.object_class.as_str(), "fragile" | "hazardous") && action.force_n > 2.0;
+
+ let facts = [
+ ("workspace_bounds", out_of_bounds),
+ ("restricted_zone_intersection", restricted_hit.is_some()),
+ ("sensor_stale", stale_sensor),
+ ("budget_or_approval_required", budget_blocked),
+ ("capability_expansion", capability_expansion),
+ ("human_speed_cap", human_speed_cap),
+ ("fragile_force_cap", fragile_force_cap),
+ ];
+
+ let mut vars = Vec::new();
+ for (name, value) in facts {
+ let var = Bool::new_const(name);
+ if value {
+ solver.assert(&var);
+ } else {
+ solver.assert(&!var.clone());
+ }
+ vars.push((name, value, var));
+ }
+ let unsafe_expr = Bool::or(&vars.iter().map(|(_, _, var)| var.clone()).collect::>());
+ solver.assert(&unsafe_expr);
+ let _ = solver.check();
+ });
+
+ let out_of_bounds = action.path.iter().any(|point| !inside_workspace(*point));
+ let restricted_hit = first_path_intersection(&action.path, &action.context.restricted_zones);
+ let stale_sensor = action.context.sensor_age_ms > 250;
+ let budget_blocked = action.context.remaining_budget_ms < 1_200 && action.action != "arm.move_segment";
+ let capability_expansion = action.action == "grant.capability"
+ && (action.capability == "unrestricted_motion"
+ || action.context.requested_speed_cap_mps > action.context.parent_speed_cap_mps);
+ let human_speed_cap = action.context.human_distance_m < 0.75 && action.speed_mps > 0.08;
+ let fragile_force_cap =
+ matches!(action.object_class.as_str(), "fragile" | "hazardous") && action.force_n > 2.0;
+
+ let mut violations = Vec::new();
+ if out_of_bounds {
+ violations.push("workspace_bounds".to_owned());
+ }
+ if restricted_hit.is_some() {
+ violations.push("restricted_zone_intersection".to_owned());
+ }
+ if stale_sensor {
+ violations.push("sensor_stale".to_owned());
+ }
+ if capability_expansion {
+ violations.push("capability_expansion".to_owned());
+ }
+ if budget_blocked {
+ violations.push("budget_requires_approval".to_owned());
+ }
+ if human_speed_cap {
+ violations.push("human_speed_cap".to_owned());
+ }
+ if fragile_force_cap {
+ violations.push("fragile_force_cap".to_owned());
+ }
+
+ let hard_deny = out_of_bounds || restricted_hit.is_some() || stale_sensor || capability_expansion;
+ let mut constraints = DecisionConstraints::default();
+ if human_speed_cap {
+ constraints.speed_mps_max = Some(0.08);
+ }
+ if fragile_force_cap {
+ constraints.force_n_max = Some(2.0);
+ }
+ if restricted_hit.is_some() {
+ constraints.bypass_z_min = Some(0.55);
+ }
+
+ let decision = if hard_deny {
+ Decision::Deny
+ } else if budget_blocked {
+ Decision::ApprovalRequired
+ } else if constraints.speed_mps_max.is_some() || constraints.force_n_max.is_some() {
+ Decision::AllowWithConstraints
+ } else {
+ Decision::Allow
+ };
+
+ let mut obligations = vec!["emit_audit_event".to_owned()];
+ if action.context.human_distance_m < 0.75 {
+ obligations.push("pause_if_human_distance_below_0_5m".to_owned());
+ }
+ if decision != Decision::Deny {
+ obligations.push("expire_capability_after_action".to_owned());
+ }
+
+ let counterexample = restricted_hit.map(|(zone, point)| Counterexample {
+ segment_id: "proposed_segment".to_owned(),
+ zone_id: Some(zone.id),
+ reason: "restricted_zone_intersection".to_owned(),
+ point: Some(point),
+ });
+
+ ProverDecision {
+ decision,
+ solver_ms: start.elapsed().as_secs_f64() * 1000.0,
+ violations,
+ constraints,
+ obligations,
+ counterexample,
+ }
+}
+
+fn seeded_world(seed: u64, goal: String) -> WorldState {
+ let mut rng = TinyRng::new(seed);
+ let restricted_x = 0.15 + rng.range(-0.08, 0.08);
+ WorldState {
+ goal,
+ seed,
+ objects: vec![
+ SceneObject {
+ id: "green_vial".to_owned(),
+ label: "Green block".to_owned(),
+ class_name: "standard".to_owned(),
+ color: "#31b66b".to_owned(),
+ position: [-0.72 + rng.range(-0.05, 0.05), 0.18, -0.46],
+ size: [0.18, 0.26, 0.18],
+ sorted: false,
+ },
+ SceneObject {
+ id: "blue_tray".to_owned(),
+ label: "Blue tray".to_owned(),
+ class_name: "tray".to_owned(),
+ color: "#3f7dda".to_owned(),
+ position: [0.83, 0.08, 0.28],
+ size: [0.62, 0.08, 0.42],
+ sorted: true,
+ },
+ ],
+ zones: vec![
+ Zone {
+ id: "restricted_zone.alpha".to_owned(),
+ label: "Restricted".to_owned(),
+ color: "#e44f5e".to_owned(),
+ position: [restricted_x, 0.52, 0.04],
+ size: [0.56, 1.04, 0.72],
+ },
+ Zone {
+ id: "caution_zone.human".to_owned(),
+ label: "Caution".to_owned(),
+ color: "#e8b630".to_owned(),
+ position: [-0.12, 0.15, -0.46],
+ size: [0.96, 0.3, 0.56],
+ },
+ ],
+ human: HumanState {
+ position: [1.08, 0.08, -0.72],
+ radius: 0.24,
+ caution: 0.54,
+ distance_m: 1.42,
+ },
+ metrics: Metrics {
+ fps: 60,
+ decision_count: 0,
+ p95_solver_ms: 0.0,
+ task_budget_pct: 84,
+ compute_budget_pct: 21,
+ actions_left: 11,
+ sensor_age_ms: 72,
+ },
+ }
+}
+
+fn action_for(world: &WorldState, object_id: &str, path: Vec, speed_mps: f64, force_n: f64, capability: &str) -> ActionEnvelope {
+ let object = world
+ .objects
+ .iter()
+ .find(|object| object.id == object_id)
+ .cloned()
+ .unwrap_or_else(|| world.objects[0].clone());
+ ActionEnvelope {
+ actor: "planner-agent".to_owned(),
+ subagent: "motion-agent".to_owned(),
+ action: "arm.move_segment".to_owned(),
+ resource: "so101.sim.arm".to_owned(),
+ from: path.first().copied().unwrap_or(HOME),
+ to: path.last().copied().unwrap_or(HOME),
+ path,
+ speed_mps,
+ force_n,
+ object_id: object.id,
+ object_class: object.class_name,
+ capability: capability.to_owned(),
+ context: context_for(world, speed_mps),
+ }
+}
+
+fn action_from_plan(world: &WorldState, plan: &AgentPlan) -> ActionEnvelope {
+ let plan = normalize_plan(plan.clone(), world);
+ action_for(
+ world,
+ "green_vial",
+ plan.path,
+ plan.speed_mps,
+ 1.8,
+ "move_lab_samples",
+ )
+}
+
+fn pickup_point(world: &WorldState) -> Vec3 {
+ world
+ .objects
+ .iter()
+ .find(|object| object.id == "green_vial")
+ .map(|object| [object.position[0], object.position[1] + object.size[1] * 0.65, object.position[2]])
+ .unwrap_or([-0.72, 0.35, -0.46])
+}
+
+fn place_point() -> Vec3 {
+ [0.84, 0.34, 0.3]
+}
+
+fn direct_path(world: &WorldState) -> Vec {
+ let pickup = pickup_point(world);
+ vec![
+ HOME,
+ pickup,
+ [-0.42, 0.52, -0.22],
+ [0.24, 0.31, 0.04],
+ place_point(),
+ ]
+}
+
+fn north_bypass_path(world: &WorldState) -> Vec {
+ let pickup = pickup_point(world);
+ vec![
+ HOME,
+ pickup,
+ [-0.92, 0.72, 0.72],
+ [-0.18, 0.72, 0.72],
+ [0.68, 0.54, 0.66],
+ place_point(),
+ ]
+}
+
+fn normalize_plan(mut plan: AgentPlan, world: &WorldState) -> AgentPlan {
+ plan.path.retain(|point| point.iter().all(|value| value.is_finite()));
+ if plan.path.len() < 2 {
+ plan.path = direct_path(world);
+ }
+ if distance(*plan.path.first().unwrap_or(&HOME), HOME) > 0.08 {
+ plan.path.insert(0, HOME);
+ }
+ let pickup = pickup_point(world);
+ let target = place_point();
+ let pickup_index = plan.path.iter().position(|point| distance(*point, pickup) <= 0.18);
+ match pickup_index {
+ Some(index) if index <= 2 => {
+ plan.path[index] = pickup;
+ }
+ Some(index) => {
+ plan.path.remove(index);
+ plan.path.insert(1, pickup);
+ }
+ None => {
+ plan.path.insert(1, pickup);
+ }
+ }
+ if plan.path.len() > 7 {
+ let last = plan.path.last().copied().unwrap_or(target);
+ plan.path.truncate(7);
+ if distance(last, target) <= 0.12 {
+ let final_index = plan.path.len().saturating_sub(1);
+ plan.path[final_index] = target;
+ }
+ }
+ if distance(*plan.path.last().unwrap_or(&target), target) > 0.12 {
+ plan.path.push(target);
+ } else if let Some(last) = plan.path.last_mut() {
+ *last = target;
+ }
+ plan.speed_mps = plan.speed_mps.clamp(0.04, 0.35);
+ if plan.rationale.trim().is_empty() {
+ plan.rationale = "Proposed waypoint path.".to_owned();
+ }
+ plan
+}
+
+fn distance(a: Vec3, b: Vec3) -> f64 {
+ ((a[0] - b[0]).powi(2) + (a[1] - b[1]).powi(2) + (a[2] - b[2]).powi(2)).sqrt()
+}
+
+fn context_for(world: &WorldState, requested_speed_cap_mps: f64) -> ActionContext {
+ ActionContext {
+ human_distance_m: world.human.distance_m,
+ sensor_age_ms: world.metrics.sensor_age_ms,
+ remaining_budget_ms: (world.metrics.task_budget_pct as u64) * 180,
+ restricted_zones: world
+ .zones
+ .iter()
+ .filter(|zone| zone.id.starts_with("restricted"))
+ .cloned()
+ .collect(),
+ caution_zones: world
+ .zones
+ .iter()
+ .filter(|zone| zone.id.starts_with("caution"))
+ .cloned()
+ .collect(),
+ parent_speed_cap_mps: 0.2,
+ requested_speed_cap_mps,
+ }
+}
+
+fn inside_workspace(point: Vec3) -> bool {
+ (0..3).all(|idx| point[idx] >= WORKSPACE_MIN[idx] && point[idx] <= WORKSPACE_MAX[idx])
+}
+
+fn first_path_intersection(path: &[Vec3], zones: &[Zone]) -> Option<(Zone, Vec3)> {
+ if path.len() < 2 {
+ return None;
+ }
+ for segment in path.windows(2) {
+ if let Some(hit) = first_zone_intersection(segment[0], segment[1], zones) {
+ return Some(hit);
+ }
+ }
+ None
+}
+
+fn first_zone_intersection(from: Vec3, to: Vec3, zones: &[Zone]) -> Option<(Zone, Vec3)> {
+ for zone in zones {
+ for step in 0..=32 {
+ let t = step as f64 / 32.0;
+ let point = [
+ from[0] + (to[0] - from[0]) * t,
+ from[1] + (to[1] - from[1]) * t,
+ from[2] + (to[2] - from[2]) * t,
+ ];
+ if point_inside_zone(point, zone) {
+ return Some((zone.clone(), point));
+ }
+ }
+ }
+ None
+}
+
+fn point_inside_zone(point: Vec3, zone: &Zone) -> bool {
+ (0..3).all(|idx| {
+ let half = zone.size[idx] / 2.0;
+ point[idx] >= zone.position[idx] - half && point[idx] <= zone.position[idx] + half
+ })
+}
+
+fn decision_summary(decision: &ProverDecision) -> String {
+ let verdict = match decision.decision {
+ Decision::Allow => "ALLOW",
+ Decision::Deny => "DENY",
+ Decision::AllowWithConstraints => "CONSTRAIN",
+ Decision::ApprovalRequired => "APPROVAL",
+ };
+ let invariant = decision
+ .violations
+ .first()
+ .map(String::as_str)
+ .unwrap_or("policy_satisfied");
+ let detail = match decision.decision {
+ Decision::Deny => {
+ let point = decision
+ .counterexample
+ .as_ref()
+ .and_then(|counterexample| counterexample.point)
+ .map(|point| format!(" @ [{:.2}, {:.2}, {:.2}]", point[0], point[1], point[2]))
+ .unwrap_or_default();
+ let bypass = decision
+ .constraints
+ .bypass_z_min
+ .map(|value| format!("; bypass_z_min={value:.2}"))
+ .unwrap_or_default();
+ format!("{invariant}{point}{bypass}")
+ }
+ Decision::AllowWithConstraints => constraint_summary(&decision.constraints),
+ Decision::ApprovalRequired => invariant.to_owned(),
+ Decision::Allow => invariant.to_owned(),
+ };
+ format!("{verdict} · {detail} · {:.2} ms", decision.solver_ms)
+}
+
+fn constraint_summary(constraints: &DecisionConstraints) -> String {
+ let mut parts = Vec::new();
+ if let Some(value) = constraints.speed_mps_max {
+ parts.push(format!("speed_mps_max={value:.2}"));
+ }
+ if let Some(value) = constraints.force_n_max {
+ parts.push(format!("force_n_max={value:.1}"));
+ }
+ if let Some(value) = constraints.bypass_z_min {
+ parts.push(format!("bypass_z_min={value:.2}"));
+ }
+ if parts.is_empty() {
+ "no_constraints".to_owned()
+ } else {
+ parts.join("; ")
+ }
+}
+
+fn p95(samples: &[f64]) -> f64 {
+ if samples.is_empty() {
+ return 0.0;
+ }
+ let mut sorted = samples.to_vec();
+ sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
+ let index = ((sorted.len() as f64 * 0.95).ceil() as usize).saturating_sub(1);
+ sorted[index]
+}
+
+#[derive(Debug, Serialize)]
+struct BenchmarkReport {
+ schema_version: u8,
+ generated_unix_seconds: u64,
+ platform: String,
+ rustc_version: String,
+ z3_crate_version: String,
+ source_revision: String,
+ build_profile: String,
+ samples_per_case: usize,
+ warmup_per_case: usize,
+ z3_timeout_ms: u64,
+ geometry_samples_per_segment: usize,
+ cases: Vec,
+}
+
+#[derive(Debug, Serialize)]
+struct BenchmarkCase {
+ outcome: String,
+ waypoints: usize,
+ segments: usize,
+ samples: usize,
+ p50_ms: f64,
+ p95_ms: f64,
+ p99_ms: f64,
+ mean_ms: f64,
+ min_ms: f64,
+ max_ms: f64,
+ decisions_per_second: f64,
+}
+
+fn run_policy_benchmark() -> Result<()> {
+ let samples_per_case = env::var("POLICY_PROVER_BENCHMARK_SAMPLES")
+ .ok()
+ .and_then(|value| value.parse().ok())
+ .unwrap_or(500);
+ let warmup_per_case = env::var("POLICY_PROVER_BENCHMARK_WARMUP")
+ .ok()
+ .and_then(|value| value.parse().ok())
+ .unwrap_or(100);
+ let platform =
+ env::var("POLICY_PROVER_BENCHMARK_PLATFORM").unwrap_or_else(|_| "unspecified".to_owned());
+ let output_path = env::var("POLICY_PROVER_BENCHMARK_OUTPUT").ok();
+ let rustc_version =
+ env::var("POLICY_PROVER_BENCHMARK_RUSTC").unwrap_or_else(|_| "rustc 1.89.0".to_owned());
+ let source_revision = env::var("POLICY_PROVER_BENCHMARK_REVISION")
+ .unwrap_or_else(|_| "local-uncommitted".to_owned());
+ let waypoint_counts = [3, 6, 12, 24, 48];
+ let mut cases = Vec::new();
+
+ for outcome in ["allow", "deny", "constrain"] {
+ for waypoints in waypoint_counts {
+ let action = benchmark_action(outcome, waypoints);
+ let expected = match outcome {
+ "allow" => Decision::Allow,
+ "deny" => Decision::Deny,
+ "constrain" => Decision::AllowWithConstraints,
+ _ => unreachable!(),
+ };
+ let observed = check_action(&action);
+ anyhow::ensure!(
+ observed.decision == expected,
+ "benchmark case {outcome}/{waypoints} returned {:?}",
+ observed.decision
+ );
+
+ for _ in 0..warmup_per_case {
+ black_box(check_action(black_box(&action)));
+ }
+
+ let started = Instant::now();
+ let mut timings = Vec::with_capacity(samples_per_case);
+ for _ in 0..samples_per_case {
+ let decision = black_box(check_action(black_box(&action)));
+ timings.push(decision.solver_ms);
+ }
+ let wall_seconds = started.elapsed().as_secs_f64();
+ timings.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
+ let mean_ms = timings.iter().sum::() / timings.len() as f64;
+ cases.push(BenchmarkCase {
+ outcome: outcome.to_owned(),
+ waypoints,
+ segments: waypoints - 1,
+ samples: samples_per_case,
+ p50_ms: percentile(&timings, 0.50),
+ p95_ms: percentile(&timings, 0.95),
+ p99_ms: percentile(&timings, 0.99),
+ mean_ms,
+ min_ms: timings.first().copied().unwrap_or(0.0),
+ max_ms: timings.last().copied().unwrap_or(0.0),
+ decisions_per_second: samples_per_case as f64 / wall_seconds,
+ });
+ }
+ }
+
+ let report = BenchmarkReport {
+ schema_version: 1,
+ generated_unix_seconds: SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(),
+ platform,
+ rustc_version,
+ z3_crate_version: "0.19.15 (bundled Z3 4.16.0)".to_owned(),
+ source_revision,
+ build_profile: "release".to_owned(),
+ samples_per_case,
+ warmup_per_case,
+ z3_timeout_ms: 18,
+ geometry_samples_per_segment: 33,
+ cases,
+ };
+ let json = serde_json::to_string_pretty(&report)?;
+ if let Some(path) = output_path {
+ if let Some(parent) = std::path::Path::new(&path).parent() {
+ fs::create_dir_all(parent)?;
+ }
+ fs::write(path, format!("{json}\n"))?;
+ } else {
+ println!("{json}");
+ }
+ Ok(())
+}
+
+fn benchmark_action(outcome: &str, waypoint_count: usize) -> ActionEnvelope {
+ let mut world = seeded_world(42, "benchmark".to_owned());
+ let safe_path = [HOME, [-0.18, 0.72, 0.72], place_point()];
+ let denied_path = [HOME, [0.24, 0.31, 0.04], place_point()];
+ let path = match outcome {
+ "deny" => densify_path(&denied_path, waypoint_count),
+ _ => densify_path(&safe_path, waypoint_count),
+ };
+ if outcome == "constrain" {
+ world.human.distance_m = 0.48;
+ }
+ let mut action = action_for(
+ &world,
+ "green_vial",
+ path,
+ if outcome == "constrain" { 0.2 } else { 0.08 },
+ if outcome == "constrain" { 3.0 } else { 1.8 },
+ "move_lab_samples",
+ );
+ if outcome == "constrain" {
+ action.object_class = "fragile".to_owned();
+ }
+ action
+}
+
+fn densify_path(points: &[Vec3], waypoint_count: usize) -> Vec {
+ assert!(points.len() >= 2);
+ assert!(waypoint_count >= points.len());
+ let segments = points.len() - 1;
+ let total_edges = waypoint_count - 1;
+ let base_edges = total_edges / segments;
+ let remainder = total_edges % segments;
+ let mut result = Vec::with_capacity(waypoint_count);
+ result.push(points[0]);
+
+ for segment_index in 0..segments {
+ let edges = base_edges + usize::from(segment_index < remainder);
+ for edge in 1..=edges {
+ let t = edge as f64 / edges as f64;
+ result.push([
+ points[segment_index][0]
+ + (points[segment_index + 1][0] - points[segment_index][0]) * t,
+ points[segment_index][1]
+ + (points[segment_index + 1][1] - points[segment_index][1]) * t,
+ points[segment_index][2]
+ + (points[segment_index + 1][2] - points[segment_index][2]) * t,
+ ]);
+ }
+ }
+ result
+}
+
+fn percentile(sorted_samples: &[f64], quantile: f64) -> f64 {
+ if sorted_samples.is_empty() {
+ return 0.0;
+ }
+ let index = ((sorted_samples.len() as f64 * quantile).ceil() as usize)
+ .saturating_sub(1)
+ .min(sorted_samples.len() - 1);
+ sorted_samples[index]
+}
+
+fn fixture_plan(world: &WorldState, prior: Option<&ProverDecision>) -> AgentPlan {
+ let (path, speed_mps, rationale) = if prior.is_some() {
+ (
+ north_bypass_path(world),
+ 0.18,
+ "Revised waypoint path from prover feedback.".to_owned(),
+ )
+ } else {
+ (
+ direct_path(world),
+ 0.32,
+ "Shortest efficient waypoint path.".to_owned(),
+ )
+ };
+ AgentPlan {
+ path,
+ speed_mps,
+ rationale,
+ source: "Fixture agent".to_owned(),
+ }
+}
+
+fn normalize_agent_plan(mut plan: AgentPlan, source: &str, world: &WorldState) -> AgentPlan {
+ plan.source = source.to_owned();
+ let trimmed = plan.rationale.trim();
+ plan.rationale = if trimmed.chars().count() > 160 {
+ format!("{}...", trimmed.chars().take(157).collect::())
+ } else {
+ trimmed.to_owned()
+ };
+ normalize_plan(plan, world)
+}
+
+async fn ask_openai_plan(world: &WorldState, prior: Option<&ProverDecision>) -> Result {
+ let key = env::var("OPENAI_API_KEY").context("OPENAI_API_KEY not set")?;
+ let model = env::var("OPENAI_MODEL")
+ .or_else(|_| env::var("LLM_MODEL"))
+ .or_else(|_| env::var("FAST_MODEL"))
+ .unwrap_or_else(|_| "gpt-4.1-mini".to_owned());
+ let base_url = env::var("OPENAI_BASE_URL").unwrap_or_else(|_| "https://api.openai.com/v1".to_owned());
+ let client = Client::new();
+ let agent_observation = serde_json::json!({
+ "seed": world.seed,
+ "goal": world.goal,
+ "objects": world.objects,
+ "policy_feedback_geometry": if prior.is_some() {
+ serde_json::json!({ "restricted_zones": world.zones.iter().filter(|zone| zone.id.starts_with("restricted")).collect::>() })
+ } else {
+ serde_json::Value::Null
+ },
+ "home": HOME,
+ "pickup": pickup_point(world),
+ "target": [0.84, 0.34, 0.3],
+ "note": "The planner sees task geometry. Formal policy constraints are checked separately by the policy prover."
+ });
+ let prompt = serde_json::json!({
+ "goal": world.goal,
+ "agent_observation": agent_observation,
+ "prior_prover_decision": prior,
+ "coordinate_system": {
+ "x": "left to right across the table",
+ "y": "height above the table",
+ "z": "front/back across the table",
+ "home": HOME,
+ "pickup": pickup_point(world),
+ "target": [0.84, 0.34, 0.3]
+ },
+ "instruction": "Return the next tool-head waypoint path for moving the green block to the blue tray. The path must start at home, visit pickup before crossing the table, then end at target. For the first proposal, optimize for the shortest efficient path; do not invent or apply safety policies. If prior_prover_decision is present, use only its violation ids, counterexample point, constraints, and policy_feedback_geometry to revise the path while preserving the pickup waypoint. Return JSON only with path, speed_mps, and rationale. Do not claim the path is safe."
+ });
+ let schema = serde_json::json!({
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "array",
+ "minItems": 2,
+ "maxItems": 8,
+ "items": {
+ "type": "array",
+ "minItems": 3,
+ "maxItems": 3,
+ "items": { "type": "number" }
+ }
+ },
+ "speed_mps": { "type": "number", "minimum": 0.04, "maximum": 0.35 },
+ "rationale": { "type": "string" }
+ },
+ "required": ["path", "speed_mps", "rationale"],
+ "additionalProperties": false
+ });
+
+ if !base_url.contains("api.openai.com") {
+ let body = serde_json::json!({
+ "model": model,
+ "temperature": 0.85,
+ "max_tokens": 420,
+ "messages": [
+ { "role": "system", "content": "You are a robotics path planner. Return only JSON with path, speed_mps, and rationale. Plan waypoints, not safety judgments; the OpenShell policy prover has final authority." },
+ { "role": "user", "content": prompt.to_string() }
+ ]
+ });
+ let response: serde_json::Value = client
+ .post(format!("{}/chat/completions", base_url.trim_end_matches('/')))
+ .bearer_auth(key)
+ .json(&body)
+ .send()
+ .await?
+ .error_for_status()?
+ .json()
+ .await?;
+ let text = response
+ .pointer("/choices/0/message/content")
+ .and_then(|value| value.as_str())
+ .context("missing chat completion content")?;
+ return Ok(normalize_agent_plan(parse_plan_text(text)?, "OpenAI-compatible agent", world));
+ }
+
+ let body = serde_json::json!({
+ "model": model,
+ "input": [
+ { "role": "system", "content": "You are a robotics path planner. Return path waypoints, not safety judgments; the OpenShell policy prover has final authority." },
+ { "role": "user", "content": prompt.to_string() }
+ ],
+ "text": {
+ "format": {
+ "type": "json_schema",
+ "name": "robot_path_plan",
+ "schema": schema,
+ "strict": true
+ }
+ }
+ });
+ let response: serde_json::Value = client
+ .post(format!("{}/responses", base_url.trim_end_matches('/')))
+ .bearer_auth(key)
+ .json(&body)
+ .send()
+ .await?
+ .error_for_status()?
+ .json()
+ .await?;
+ let text = response
+ .pointer("/output/0/content/0/text")
+ .and_then(|value| value.as_str())
+ .or_else(|| response.get("output_text").and_then(|value| value.as_str()))
+ .context("missing structured output text")?;
+ Ok(normalize_agent_plan(parse_plan_text(text)?, "OpenAI-compatible agent", world))
+}
+
+fn parse_plan_text(text: &str) -> Result {
+ if let Ok(plan) = serde_json::from_str(text) {
+ return Ok(plan);
+ }
+ let start = text.find('{').context("missing JSON object start")?;
+ let end = text.rfind('}').context("missing JSON object end")?;
+ let plan = serde_json::from_str(&text[start..=end])?;
+ Ok(plan)
+}
+
+struct TinyRng {
+ state: u64,
+}
+
+impl TinyRng {
+ fn new(seed: u64) -> Self {
+ Self { state: seed.max(1) }
+ }
+
+ fn next(&mut self) -> f64 {
+ self.state ^= self.state << 13;
+ self.state ^= self.state >> 7;
+ self.state ^= self.state << 17;
+ (self.state as f64 / u64::MAX as f64).clamp(0.0, 1.0)
+ }
+
+ fn range(&mut self, min: f64, max: f64) -> f64 {
+ min + (max - min) * self.next()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn denies_restricted_zone_crossing() {
+ let world = seeded_world(42, "test".to_owned());
+ let action = action_for(
+ &world,
+ "green_vial",
+ vec![HOME, [0.24, 0.31, 0.04], [0.84, 0.34, 0.3]],
+ 0.2,
+ 2.0,
+ "move_lab_samples",
+ );
+ let decision = check_action(&action);
+ assert_eq!(decision.decision, Decision::Deny);
+ assert!(decision.violations.contains(&"restricted_zone_intersection".to_owned()));
+ }
+
+ #[test]
+ fn constrains_fragile_sample_near_human() {
+ let mut world = seeded_world(7, "test".to_owned());
+ world.human.distance_m = 0.48;
+ let mut action = action_for(
+ &world,
+ "green_vial",
+ vec![HOME, [-0.94, 0.82, 0.72], [-0.15, 0.74, 0.7], [0.7, 0.56, 0.58], [0.84, 0.34, 0.3]],
+ 0.2,
+ 3.0,
+ "move_lab_samples",
+ );
+ action.object_class = "fragile".to_owned();
+ let decision = check_action(&action);
+ assert_eq!(decision.decision, Decision::AllowWithConstraints);
+ assert_eq!(decision.constraints.speed_mps_max, Some(0.08));
+ assert_eq!(decision.constraints.force_n_max, Some(2.0));
+ }
+
+ #[test]
+ fn denies_capability_expansion() {
+ let world = seeded_world(9, "test".to_owned());
+ let action = ActionEnvelope {
+ actor: "planner-agent".to_owned(),
+ subagent: "optimizer-subagent".to_owned(),
+ action: "grant.capability".to_owned(),
+ resource: "delegated-motion-capability".to_owned(),
+ from: HOME,
+ to: [0.4, 0.4, 0.2],
+ path: vec![HOME, [0.4, 0.4, 0.2]],
+ speed_mps: 0.5,
+ force_n: 1.0,
+ object_id: "mission-budget".to_owned(),
+ object_class: "governance".to_owned(),
+ capability: "unrestricted_motion".to_owned(),
+ context: context_for(&world, 0.5),
+ };
+ let decision = check_action(&action);
+ assert_eq!(decision.decision, Decision::Deny);
+ assert!(decision.violations.contains(&"capability_expansion".to_owned()));
+ }
+
+ #[test]
+ fn normalizes_plan_to_visit_pickup_before_place() {
+ let world = seeded_world(42, "test".to_owned());
+ let plan = AgentPlan {
+ path: vec![HOME, [0.72, 0.5, 0.62], place_point()],
+ speed_mps: 0.2,
+ rationale: "bad repair skipped pickup".to_owned(),
+ source: "test".to_owned(),
+ };
+ let action = action_from_plan(&world, &plan);
+ assert!(distance(action.path[1], pickup_point(&world)) <= 0.01);
+ assert_eq!(action.path.last().copied(), Some(place_point()));
+ }
+}
diff --git a/projects/robotics-policy-prover/scripts/verify-visual.mjs b/projects/robotics-policy-prover/scripts/verify-visual.mjs
new file mode 100644
index 00000000..995f42c5
--- /dev/null
+++ b/projects/robotics-policy-prover/scripts/verify-visual.mjs
@@ -0,0 +1,72 @@
+import { mkdir } from "node:fs/promises";
+import { chromium } from "playwright";
+
+const url = process.env.DEMO_URL ?? "http://127.0.0.1:5173";
+const viewports = [
+ { name: "desktop", width: 1440, height: 900 },
+ { name: "mobile", width: 390, height: 844 },
+];
+
+function assert(condition, message) {
+ if (!condition) {
+ throw new Error(message);
+ }
+}
+
+await mkdir("screenshots", { recursive: true });
+
+const browser = await chromium.launch();
+try {
+ for (const viewport of viewports) {
+ const page = await browser.newPage({ viewport });
+ await page.goto(url, { waitUntil: "domcontentloaded" });
+ await page.waitForSelector("canvas");
+ await page.waitForTimeout(1400);
+
+ const stats = await page.evaluate(() => {
+ const canvas = document.querySelector("canvas");
+ const sample = document.createElement("canvas");
+ sample.width = 120;
+ sample.height = 80;
+ const context = sample.getContext("2d", { willReadFrequently: true });
+ context.drawImage(canvas, 0, 0, sample.width, sample.height);
+ const image = context.getImageData(0, 0, sample.width, sample.height).data;
+ let brightPixels = 0;
+ let darkPixels = 0;
+ let colorSpread = 0;
+
+ for (let index = 0; index < image.length; index += 4) {
+ const red = image[index];
+ const green = image[index + 1];
+ const blue = image[index + 2];
+ const luma = red * 0.2126 + green * 0.7152 + blue * 0.0722;
+ if (luma > 180) brightPixels += 1;
+ if (luma < 110) darkPixels += 1;
+ if (Math.abs(red - green) + Math.abs(green - blue) > 35) colorSpread += 1;
+ }
+
+ return {
+ width: canvas.width,
+ height: canvas.height,
+ brightPixels,
+ darkPixels,
+ colorSpread,
+ };
+ });
+
+ assert(stats.width > 0 && stats.height > 0, `${viewport.name}: canvas has no size`);
+ assert(stats.brightPixels > 150, `${viewport.name}: canvas lacks lit geometry`);
+ assert(stats.darkPixels > 20, `${viewport.name}: canvas lacks shaded geometry`);
+ assert(stats.colorSpread > 60, `${viewport.name}: canvas lacks colored policy elements`);
+
+ await page.screenshot({
+ path: `screenshots/${viewport.name}.png`,
+ fullPage: true,
+ });
+ await page.close();
+ }
+} finally {
+ await browser.close();
+}
+
+console.log(`Visual verification passed for ${viewports.length} viewports at ${url}`);
diff --git a/projects/robotics-policy-prover/src/App.jsx b/projects/robotics-policy-prover/src/App.jsx
new file mode 100644
index 00000000..78e7118e
--- /dev/null
+++ b/projects/robotics-policy-prover/src/App.jsx
@@ -0,0 +1,359 @@
+import {
+ AlertTriangle,
+ CheckCircle2,
+ ChevronDown,
+ Clock3,
+ Gauge,
+ Play,
+ Radar,
+ RefreshCcw,
+ ShieldCheck,
+} from "lucide-react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import RobotScene from "./RobotScene.jsx";
+import { homePosition } from "./scenarios.js";
+
+const DEFAULT_GOAL = "Move the green block into the blue tray.";
+
+const fallbackWorld = {
+ goal: DEFAULT_GOAL,
+ seed: 42,
+ objects: [
+ {
+ id: "green_vial",
+ label: "Green block",
+ class_name: "standard",
+ color: "#31b66b",
+ position: [-0.72, 0.18, -0.46],
+ size: [0.18, 0.26, 0.18],
+ sorted: false,
+ },
+ {
+ id: "blue_tray",
+ label: "Blue tray",
+ class_name: "tray",
+ color: "#3f7dda",
+ position: [0.83, 0.08, 0.28],
+ size: [0.62, 0.08, 0.42],
+ sorted: true,
+ },
+ ],
+ zones: [
+ {
+ id: "restricted_zone.alpha",
+ label: "Restricted",
+ color: "#e44f5e",
+ position: [0.15, 0.52, 0.04],
+ size: [0.56, 1.04, 0.72],
+ },
+ {
+ id: "caution_zone.human",
+ label: "Caution",
+ color: "#e8b630",
+ position: [-0.12, 0.15, -0.46],
+ size: [0.96, 0.3, 0.56],
+ },
+ ],
+ human: {
+ position: [1.08, 0.08, -0.72],
+ radius: 0.24,
+ caution: 0.54,
+ distance_m: 1.42,
+ },
+ metrics: {
+ fps: 60,
+ decision_count: 0,
+ p95_solver_ms: 0,
+ task_budget_pct: 84,
+ compute_budget_pct: 21,
+ actions_left: 3,
+ sensor_age_ms: 72,
+ },
+};
+
+const decisionMeta = {
+ allow: { label: "Allowed", className: "decision-allow", icon: CheckCircle2 },
+ allow_with_constraints: {
+ label: "Constrained",
+ className: "decision-constrain",
+ icon: ShieldCheck,
+ },
+ deny: { label: "Denied", className: "decision-deny", icon: AlertTriangle },
+ approval_required: { label: "Needs Approval", className: "decision-approval", icon: Clock3 },
+ pending: { label: "Ready", className: "decision-pending", icon: Radar },
+};
+
+function normalizeStep(event, previous) {
+ const decision = event?.decision?.decision ?? previous?.decision ?? "pending";
+ let approvedPath = previous?.approvedPath ?? [homePosition];
+ if (event && Object.hasOwn(event, "approved_path")) {
+ approvedPath = event.approved_path ?? (decision === "deny" ? [homePosition] : approvedPath);
+ }
+
+ return {
+ id: event?.id ?? previous?.id ?? "idle",
+ title: event?.summary ?? previous?.title ?? "Click Run Experiment",
+ decision,
+ action: event?.action ?? previous?.action,
+ proposedPath: event?.proposed_path ?? previous?.proposedPath ?? [homePosition],
+ approvedPath,
+ highlight: event?.highlight ?? previous?.highlight,
+ };
+}
+
+function DecisionBadge({ decision }) {
+ const meta = decisionMeta[decision] ?? decisionMeta.pending;
+ const Icon = meta.icon;
+ return (
+
+
+ {meta.label}
+
+ );
+}
+
+function storyLabel(event) {
+ if (event.kind === "agent_plan") return "Agent";
+ if (event.kind === "prover_decision") return "Policy prover";
+ if (event.kind === "execution_update") return "Executor";
+ return "World";
+}
+
+function storyTone(event) {
+ if (event.kind === "agent_plan") return "agent";
+ if (event.kind === "prover_decision") return event.decision?.decision ?? "prover";
+ if (event.kind === "execution_update") return "executor";
+ return "world";
+}
+
+function compactSummary(event) {
+ return event.summary;
+}
+
+function sanitizeDecision(decision) {
+ if (!decision) return null;
+ return {
+ decision: decision.decision,
+ solver_ms: Number(decision.solver_ms?.toFixed?.(2) ?? decision.solver_ms),
+ violations: decision.violations,
+ constraints: decision.constraints,
+ obligations: decision.obligations,
+ counterexample: decision.counterexample,
+ };
+}
+
+function App() {
+ const [seed, setSeed] = useState(42);
+ const [agentMode, setAgentMode] = useState("openai");
+ const [world, setWorld] = useState(fallbackWorld);
+ const [events, setEvents] = useState([]);
+ const [step, setStep] = useState(() => normalizeStep(null, null));
+ const [playbackSpeed, setPlaybackSpeed] = useState(1);
+ const [session, setSession] = useState(null);
+ const sourceRef = useRef(null);
+
+ const storyEvents = useMemo(
+ () => events.filter((event) => ["agent_plan", "prover_decision", "world_event", "execution_update"].includes(event.kind)),
+ [events],
+ );
+ const latestDecision = step.decision ?? "pending";
+ const latestDecisionEvent = [...events].reverse().find((event) => event.decision);
+
+ const attachEventSource = useCallback((sessionId) => {
+ sourceRef.current?.close();
+ const source = new EventSource(`/api/sessions/${sessionId}/events`);
+ sourceRef.current = source;
+
+ const handle = (message) => {
+ const event = JSON.parse(message.data);
+ setEvents((items) => [...items.slice(-40), event]);
+ if (event.world) setWorld(event.world);
+ setStep((previous) => normalizeStep(event, previous));
+ };
+
+ ["world_event", "agent_plan", "prover_decision", "execution_update", "error"].forEach((kind) =>
+ source.addEventListener(kind, handle),
+ );
+ }, []);
+
+ const startExperiment = useCallback(async () => {
+ setEvents([]);
+ setStep(normalizeStep(null, null));
+ const response = await fetch("/api/sessions", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ goal: DEFAULT_GOAL, seed: Number(seed), agent_mode: agentMode }),
+ });
+ const data = await response.json();
+ setSession(data);
+ setAgentMode(data.agent_mode);
+ attachEventSource(data.session_id);
+ }, [agentMode, attachEventSource, seed]);
+
+ useEffect(() => () => sourceRef.current?.close(), []);
+
+ return (
+
+
+
+
+
+
+
+
Agent Path vs Policy Prover
+
One tool-path experiment: propose, prove, adapt, execute.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {step.title}
+
+
+
+ Agent path
+ Executed path
+ Restricted
+ Human
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/projects/robotics-policy-prover/src/RobotScene.jsx b/projects/robotics-policy-prover/src/RobotScene.jsx
new file mode 100644
index 00000000..fd578368
--- /dev/null
+++ b/projects/robotics-policy-prover/src/RobotScene.jsx
@@ -0,0 +1,238 @@
+import { Canvas, useFrame } from "@react-three/fiber";
+import { ContactShadows, Edges, Line, OrbitControls } from "@react-three/drei";
+import { Suspense, useMemo, useRef, useState } from "react";
+import * as THREE from "three";
+import { homePosition } from "./scenarios.js";
+
+const gantryHome = new THREE.Vector3(-1.08, 0.98, 0.58);
+
+function toVector(point) {
+ return new THREE.Vector3(point[0], point[1], point[2]);
+}
+
+function samplePath(points, progress) {
+ if (!points.length) return toVector(homePosition);
+ if (points.length === 1) return toVector(points[0]);
+
+ const scaled = THREE.MathUtils.clamp(progress, 0, 1) * (points.length - 1);
+ const index = Math.min(Math.floor(scaled), points.length - 2);
+ const local = scaled - index;
+ return toVector(points[index]).lerp(toVector(points[index + 1]), local);
+}
+
+function segmentQuaternion(start, end) {
+ const direction = new THREE.Vector3().subVectors(end, start);
+ const quaternion = new THREE.Quaternion();
+ quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction.clone().normalize());
+ return quaternion;
+}
+
+function Segment({ start, end, radius = 0.045, color = "#54616f" }) {
+ const startVec = useMemo(() => toVector(start), [start]);
+ const endVec = useMemo(() => toVector(end), [end]);
+ const midpoint = useMemo(() => startVec.clone().lerp(endVec, 0.5), [startVec, endVec]);
+ const length = useMemo(() => startVec.distanceTo(endVec), [startVec, endVec]);
+ const quaternion = useMemo(() => segmentQuaternion(startVec, endVec), [startVec, endVec]);
+
+ return (
+
+
+
+
+ );
+}
+
+function ToolHead({ step, playbackSpeed }) {
+ const path = step.approvedPath?.length > 1 ? step.approvedPath : [homePosition];
+ const [tip, setTip] = useState(() => toVector(path[0]));
+ const progress = useRef(0);
+ const pausePulse = step.decision === "deny" || step.decision === "pending";
+
+ useFrame((_, delta) => {
+ const pace = step.decision === "deny" ? 0 : playbackSpeed;
+ progress.current = (progress.current + delta * 0.18 * pace) % 1;
+ const next = samplePath(path, pausePulse ? 0 : progress.current);
+ setTip(next);
+ });
+
+ const lift = useMemo(() => tip.clone().setY(gantryHome.y), [tip]);
+ const tipArray = tip.toArray();
+ const liftArray = lift.toArray();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function carriedPosition(object, path, progress) {
+ if (object.id !== "green_vial" || !path?.length || path.length < 2) return object.position;
+ const original = toVector(object.position);
+ const pickupIndex = path.reduce(
+ (best, point, index) => {
+ const distance = toVector(point).distanceTo(original);
+ return distance < best.distance ? { index, distance } : best;
+ },
+ { index: 0, distance: Infinity },
+ );
+ const scaled = THREE.MathUtils.clamp(progress, 0, 1) * (path.length - 1);
+ if (scaled < pickupIndex.index) return object.position;
+ const tip = samplePath(path, progress);
+ return [tip.x, Math.max(object.position[1], tip.y - 0.18), tip.z];
+}
+
+function WorkcellObjects({ objects, step, playbackSpeed }) {
+ const path = step.approvedPath?.length > 1 ? step.approvedPath : [];
+ const [progress, setProgress] = useState(0);
+ const progressRef = useRef(0);
+
+ useFrame((_, delta) => {
+ const shouldMove = path.length > 1 && step.decision !== "deny" && step.decision !== "pending";
+ const pace = shouldMove ? playbackSpeed : 0;
+ progressRef.current = (progressRef.current + delta * 0.18 * pace) % 1;
+ setProgress(progressRef.current);
+ });
+
+ return (
+
+ {objects.map((object) => (
+
+
+
+
+
+ ))}
+
+ );
+}
+
+function Zone({ zone }) {
+ return (
+
+
+
+
+
+ );
+}
+
+function MotionPath({ points, color, opacity = 1, dashed = false }) {
+ if (!points?.length || points.length < 2) return null;
+ return (
+
+ );
+}
+
+function HumanPresence({ human, decision }) {
+ const warning = decision === "allow_with_constraints" || decision === "approval_required";
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function SceneContent({ world, step, playbackSpeed }) {
+ const approvedColor =
+ step.decision === "deny"
+ ? "#e45454"
+ : step.decision === "approval_required" || step.decision === "allow_with_constraints"
+ ? "#e8b630"
+ : "#32bd77";
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {world.zones.map((zone) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+export default function RobotScene({ world, step, playbackSpeed }) {
+ return (
+
+ );
+}
diff --git a/projects/robotics-policy-prover/src/main.jsx b/projects/robotics-policy-prover/src/main.jsx
new file mode 100644
index 00000000..9b3fe1ce
--- /dev/null
+++ b/projects/robotics-policy-prover/src/main.jsx
@@ -0,0 +1,10 @@
+import React from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App.jsx";
+import "./styles.css";
+
+createRoot(document.getElementById("root")).render(
+
+
+ ,
+);
diff --git a/projects/robotics-policy-prover/src/scenarios.js b/projects/robotics-policy-prover/src/scenarios.js
new file mode 100644
index 00000000..9deef430
--- /dev/null
+++ b/projects/robotics-policy-prover/src/scenarios.js
@@ -0,0 +1,297 @@
+export const homePosition = [-1.08, 0.78, 0.58];
+
+export const policyPacks = [
+ {
+ id: "workspace",
+ name: "Workspace bounds",
+ tone: "green",
+ invariant: "No path segment may leave the configured workcell.",
+ },
+ {
+ id: "human",
+ name: "Human proximity",
+ tone: "amber",
+ invariant: "Motion must slow near humans and stop inside the hard radius.",
+ },
+ {
+ id: "materials",
+ name: "Sample handling",
+ tone: "cyan",
+ invariant: "Fragile or hazardous samples require reduced speed and force.",
+ },
+ {
+ id: "delegation",
+ name: "Delegation",
+ tone: "violet",
+ invariant: "Subagents cannot expand authority beyond the parent grant.",
+ },
+ {
+ id: "budget",
+ name: "Budget envelope",
+ tone: "rose",
+ invariant: "Plans must stay inside time, action, and compute limits.",
+ },
+ {
+ id: "audit",
+ name: "Audit obligations",
+ tone: "blue",
+ invariant: "Every physical action must emit a durable decision record.",
+ },
+];
+
+export const objects = [
+ {
+ id: "green-vial",
+ label: "Green vial",
+ color: "#31b66b",
+ position: [-0.72, 0.18, -0.46],
+ size: [0.18, 0.26, 0.18],
+ },
+ {
+ id: "red-sample",
+ label: "Red sample",
+ color: "#e45454",
+ position: [-0.22, 0.15, -0.68],
+ size: [0.22, 0.2, 0.22],
+ },
+ {
+ id: "yellow-core",
+ label: "Yellow core",
+ color: "#e7b92f",
+ position: [0.26, 0.16, -0.48],
+ size: [0.2, 0.22, 0.2],
+ },
+ {
+ id: "blue-tray",
+ label: "Blue tray",
+ color: "#3f7dda",
+ position: [0.83, 0.08, 0.28],
+ size: [0.62, 0.08, 0.42],
+ },
+];
+
+export const zones = [
+ {
+ id: "restricted",
+ label: "Restricted",
+ color: "#e44f5e",
+ position: [0.22, 0.24, 0.04],
+ size: [0.56, 0.48, 0.72],
+ },
+ {
+ id: "caution",
+ label: "Caution",
+ color: "#e8b630",
+ position: [-0.12, 0.15, -0.46],
+ size: [0.96, 0.3, 0.56],
+ },
+];
+
+export const scenarios = [
+ {
+ id: "normal-autonomy",
+ label: "Normal",
+ title: "Normal Autonomy",
+ actor: "planner-agent",
+ subagent: "motion-agent",
+ action: "arm.move_path",
+ object: "green-vial",
+ resource: "so101.sim.arm",
+ decision: "allow",
+ risk: "Low",
+ speed: "0.18 m/s",
+ force: "2.1 N",
+ solverMs: 6,
+ budget: 84,
+ compute: 21,
+ remainingActions: 11,
+ humanDistance: "1.42 m",
+ sensorAge: "72 ms",
+ proposedPath: [
+ homePosition,
+ [-0.86, 0.72, 0.12],
+ [-0.72, 0.38, -0.46],
+ [0.58, 0.46, 0.2],
+ [0.83, 0.34, 0.28],
+ ],
+ approvedPath: [
+ homePosition,
+ [-0.86, 0.72, 0.12],
+ [-0.72, 0.38, -0.46],
+ [0.58, 0.46, 0.2],
+ [0.83, 0.34, 0.28],
+ ],
+ human: { position: [1.1, 0.08, -0.72], radius: 0.22, caution: 0.52 },
+ activePolicies: ["workspace", "audit"],
+ constraints: ["Path remains inside workcell", "Decision record required"],
+ obligations: ["Emit audit event", "Expire capability after 4s"],
+ explanation:
+ "The proposed move stays inside workspace bounds and uses an unprivileged object-handling grant.",
+ },
+ {
+ id: "human-composition",
+ label: "Human near",
+ title: "Human Proximity Composition",
+ actor: "planner-agent",
+ subagent: "motion-agent",
+ action: "arm.move_path",
+ object: "red-sample",
+ resource: "so101.sim.arm",
+ decision: "constrain",
+ risk: "Medium",
+ speed: "0.42 -> 0.08 m/s",
+ force: "3.8 -> 2.0 N",
+ solverMs: 13,
+ budget: 67,
+ compute: 38,
+ remainingActions: 7,
+ humanDistance: "0.48 m",
+ sensorAge: "88 ms",
+ proposedPath: [
+ homePosition,
+ [-0.55, 0.62, -0.1],
+ [-0.22, 0.34, -0.68],
+ [0.34, 0.32, -0.12],
+ [0.83, 0.34, 0.28],
+ ],
+ approvedPath: [
+ homePosition,
+ [-0.72, 0.82, 0.0],
+ [-0.36, 0.62, -0.62],
+ [0.12, 0.58, -0.5],
+ [0.68, 0.52, 0.08],
+ [0.83, 0.34, 0.28],
+ ],
+ human: { position: [0.12, 0.08, -0.18], radius: 0.28, caution: 0.68 },
+ activePolicies: ["workspace", "human", "materials", "audit"],
+ constraints: [
+ "Clamp speed to <= 0.08 m/s",
+ "Clamp gripper force to <= 2.0 N",
+ "Route outside caution overlap",
+ ],
+ obligations: [
+ "Pause if human_distance < 0.5 m",
+ "Recheck before gripper.close",
+ "Emit audit event",
+ ],
+ explanation:
+ "Human proximity and fragile-sample policies both apply, so the action is allowed only as a slower alternate path.",
+ },
+ {
+ id: "restricted-zone",
+ label: "No-go zone",
+ title: "Restricted Zone Denial",
+ actor: "planner-agent",
+ subagent: "motion-agent",
+ action: "arm.move_path",
+ object: "yellow-core",
+ resource: "so101.sim.arm",
+ decision: "deny",
+ risk: "High",
+ speed: "0.25 m/s",
+ force: "3.2 N",
+ solverMs: 10,
+ budget: 59,
+ compute: 44,
+ remainingActions: 6,
+ humanDistance: "1.08 m",
+ sensorAge: "64 ms",
+ proposedPath: [
+ homePosition,
+ [-0.44, 0.58, 0.08],
+ [0.22, 0.34, 0.04],
+ [0.7, 0.34, 0.22],
+ ],
+ approvedPath: [homePosition],
+ human: { position: [1.08, 0.08, -0.62], radius: 0.24, caution: 0.52 },
+ activePolicies: ["workspace", "materials", "audit"],
+ constraints: ["No feasible path through restricted_zone"],
+ obligations: ["Keep prior capability unchanged", "Record denial"],
+ explanation:
+ "The candidate trajectory intersects the restricted volume, and no override grant is present for that region.",
+ },
+ {
+ id: "delegation",
+ label: "Delegation",
+ title: "Subagent Delegation Check",
+ actor: "planner-agent",
+ subagent: "cleanup-subagent",
+ action: "grant.capability",
+ object: "so101.sim.arm",
+ resource: "delegated-motion-capability",
+ decision: "deny",
+ risk: "High",
+ speed: "n/a",
+ force: "n/a",
+ solverMs: 17,
+ budget: 46,
+ compute: 56,
+ remainingActions: 4,
+ humanDistance: "0.93 m",
+ sensorAge: "101 ms",
+ proposedPath: [
+ homePosition,
+ [-0.62, 0.68, 0.32],
+ [0.38, 0.44, -0.02],
+ [0.92, 0.38, -0.42],
+ ],
+ approvedPath: [homePosition],
+ human: { position: [0.92, 0.08, -0.48], radius: 0.24, caution: 0.52 },
+ activePolicies: ["delegation", "human", "audit"],
+ constraints: [
+ "Parent grant excludes restricted_zone",
+ "Child grant requested unrestricted move_path",
+ "Delegated speed cap exceeds parent speed cap",
+ ],
+ obligations: ["Reject broader grant", "Record attempted escalation"],
+ explanation:
+ "The subagent requested a capability that is broader than its parent envelope, including restricted-zone access and a higher speed cap.",
+ },
+ {
+ id: "budget",
+ label: "Budget",
+ title: "Budget-Constrained Autonomy",
+ actor: "planner-agent",
+ subagent: "optimizer-subagent",
+ action: "plan.extend",
+ object: "sorting-task",
+ resource: "mission-budget",
+ decision: "approval",
+ risk: "Medium",
+ speed: "0.12 m/s",
+ force: "1.7 N",
+ solverMs: 9,
+ budget: 8,
+ compute: 86,
+ remainingActions: 1,
+ humanDistance: "0.81 m",
+ sensorAge: "79 ms",
+ proposedPath: [
+ homePosition,
+ [-0.56, 0.8, -0.12],
+ [-0.12, 0.56, -0.64],
+ [0.68, 0.52, 0.12],
+ [0.92, 0.34, 0.34],
+ ],
+ approvedPath: [
+ homePosition,
+ [-0.72, 0.72, -0.02],
+ [0.42, 0.5, 0.12],
+ [0.83, 0.34, 0.28],
+ ],
+ human: { position: [0.64, 0.08, -0.58], radius: 0.25, caution: 0.54 },
+ activePolicies: ["budget", "workspace", "audit"],
+ constraints: [
+ "One final move may execute",
+ "No replanning loop without supervisor approval",
+ "Compute budget remains below 90%",
+ ],
+ obligations: ["Request supervisor approval for extension", "Close task after final move"],
+ explanation:
+ "The current task has enough budget for one bounded action, but continued optimization requires approval.",
+ },
+];
+
+export function policyTone(id) {
+ return policyPacks.find((policy) => policy.id === id)?.tone ?? "blue";
+}
diff --git a/projects/robotics-policy-prover/src/styles.css b/projects/robotics-policy-prover/src/styles.css
new file mode 100644
index 00000000..3ea17757
--- /dev/null
+++ b/projects/robotics-policy-prover/src/styles.css
@@ -0,0 +1,1063 @@
+:root {
+ color-scheme: light;
+ font-family:
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ background: #eef1ed;
+ color: #202328;
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ min-width: 320px;
+ min-height: 100vh;
+ background:
+ linear-gradient(180deg, rgba(255, 255, 255, 0.52), rgba(255, 255, 255, 0)),
+ #eef1ed;
+}
+
+button,
+input,
+select {
+ font: inherit;
+}
+
+.app-shell {
+ width: min(100%, 1520px);
+ min-height: 100vh;
+ margin: 0 auto;
+ padding: 18px;
+ display: grid;
+ grid-template-rows: auto minmax(0, 1fr) auto;
+ gap: 14px;
+}
+
+.topbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ min-height: 68px;
+}
+
+.brand-lockup {
+ display: flex;
+ align-items: center;
+ gap: 13px;
+ min-width: 0;
+}
+
+.brand-mark,
+.icon-button {
+ width: 42px;
+ height: 42px;
+ display: inline-grid;
+ place-items: center;
+ border-radius: 8px;
+ border: 1px solid rgba(31, 35, 40, 0.12);
+ background: #ffffff;
+ color: #253038;
+ box-shadow: 0 10px 24px rgba(31, 35, 40, 0.08);
+}
+
+.brand-mark {
+ color: #13875a;
+}
+
+h1,
+h2,
+p,
+dl {
+ margin: 0;
+}
+
+h1 {
+ font-size: clamp(1.15rem, 2vw, 1.55rem);
+ line-height: 1.08;
+ letter-spacing: 0;
+}
+
+.brand-lockup p {
+ margin-top: 4px;
+ font-size: 0.9rem;
+ color: #667076;
+}
+
+.topbar-actions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 9px;
+ flex-wrap: wrap;
+}
+
+.icon-button {
+ cursor: pointer;
+ transition:
+ transform 160ms ease,
+ border-color 160ms ease,
+ background-color 160ms ease;
+}
+
+.icon-button:hover,
+.icon-button.is-active {
+ transform: translateY(-1px);
+ border-color: rgba(27, 126, 90, 0.42);
+ background: #f7fbf8;
+}
+
+.run-button {
+ height: 42px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 0 14px;
+ border: 1px solid rgba(19, 135, 90, 0.38);
+ border-radius: 8px;
+ background: #17875d;
+ color: #ffffff;
+ box-shadow: 0 12px 26px rgba(19, 135, 90, 0.2);
+ cursor: pointer;
+ font-weight: 900;
+}
+
+.run-button:hover {
+ background: #126f4c;
+}
+
+.speed-control {
+ height: 42px;
+ display: grid;
+ grid-template-columns: auto auto minmax(96px, 140px) 48px;
+ align-items: center;
+ gap: 9px;
+ padding: 0 11px;
+ border: 1px solid rgba(31, 35, 40, 0.12);
+ border-radius: 8px;
+ background: #ffffff;
+ color: #4d5962;
+ box-shadow: 0 10px 24px rgba(31, 35, 40, 0.07);
+}
+
+.seed-control {
+ height: 42px;
+ display: grid;
+ grid-template-columns: auto minmax(76px, 118px);
+ align-items: center;
+ gap: 8px;
+ padding: 0 10px;
+ border: 1px solid rgba(31, 35, 40, 0.12);
+ border-radius: 8px;
+ background: #ffffff;
+ color: #4d5962;
+ box-shadow: 0 10px 24px rgba(31, 35, 40, 0.07);
+}
+
+.seed-control span {
+ font-size: 0.78rem;
+ font-weight: 800;
+}
+
+.seed-control input,
+.seed-control select {
+ width: 100%;
+ min-width: 0;
+ border: 1px solid rgba(31, 35, 40, 0.13);
+ border-radius: 7px;
+ background: #f7f9f7;
+ color: #202328;
+ padding: 6px 7px;
+ font-size: 0.8rem;
+}
+
+.speed-control input {
+ width: 100%;
+ accent-color: #19865f;
+}
+
+.speed-control strong {
+ font-size: 0.78rem;
+ color: #202328;
+ text-align: right;
+}
+
+.demo-layout {
+ min-height: 0;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(360px, 420px);
+ gap: 14px;
+}
+
+.simple-shell {
+ grid-template-rows: auto minmax(0, 1fr);
+}
+
+.simple-layout {
+ min-height: 0;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(330px, 390px);
+ gap: 14px;
+}
+
+.story-panel {
+ min-height: 0;
+ overflow: auto;
+ display: grid;
+ align-content: start;
+ gap: 10px;
+}
+
+.explain-strip {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.explain-strip div {
+ min-width: 0;
+ display: grid;
+ gap: 4px;
+ padding: 11px 12px;
+ border: 1px solid rgba(31, 35, 40, 0.1);
+ border-radius: 8px;
+ background: rgba(255, 255, 255, 0.74);
+ box-shadow: 0 12px 28px rgba(31, 35, 40, 0.07);
+}
+
+.explain-strip strong {
+ color: #253038;
+ font-size: 0.84rem;
+}
+
+.explain-strip span {
+ color: #647079;
+ font-size: 0.78rem;
+ line-height: 1.3;
+}
+
+.scene-panel,
+.inspector-section,
+.scenario-rail {
+ border: 1px solid rgba(31, 35, 40, 0.12);
+ background: rgba(255, 255, 255, 0.78);
+ box-shadow: 0 18px 42px rgba(31, 35, 40, 0.09);
+}
+
+.scene-panel {
+ position: relative;
+ min-height: 560px;
+ overflow: hidden;
+ border-radius: 8px;
+}
+
+.scene-panel canvas {
+ display: block;
+}
+
+.scene-status {
+ position: absolute;
+ z-index: 4;
+ top: 16px;
+ left: 16px;
+ right: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ pointer-events: none;
+}
+
+.scene-title {
+ min-width: 0;
+ padding: 9px 11px;
+ border: 1px solid rgba(31, 35, 40, 0.1);
+ border-radius: 8px;
+ background: rgba(255, 255, 255, 0.84);
+ color: #34404a;
+ font-size: 0.86rem;
+ font-weight: 700;
+ box-shadow: 0 10px 22px rgba(31, 35, 40, 0.08);
+}
+
+.legend-strip {
+ position: absolute;
+ z-index: 4;
+ left: 16px;
+ right: 16px;
+ bottom: 16px;
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 8px;
+ pointer-events: none;
+}
+
+.legend-strip span {
+ min-width: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 7px;
+ padding: 8px 9px;
+ border: 1px solid rgba(31, 35, 40, 0.1);
+ border-radius: 8px;
+ background: rgba(255, 255, 255, 0.82);
+ color: #3e474f;
+ font-size: 0.78rem;
+ font-weight: 700;
+}
+
+.legend-dot {
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ flex: 0 0 auto;
+}
+
+.legend-dot.proposed {
+ background: #e45454;
+}
+
+.legend-dot.approved {
+ background: #32bd77;
+}
+
+.legend-dot.restricted {
+ background: #e44f5e;
+}
+
+.legend-dot.human {
+ background: #e8b630;
+}
+
+.inspector {
+ min-height: 0;
+ overflow: auto;
+ display: grid;
+ align-content: start;
+ gap: 10px;
+ padding-right: 2px;
+}
+
+.inspector-section {
+ border-radius: 8px;
+ padding: 14px;
+}
+
+.section-heading {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 11px;
+ color: #38434c;
+}
+
+.section-heading h2 {
+ font-size: 0.86rem;
+ line-height: 1.1;
+ letter-spacing: 0;
+}
+
+.primary-decision {
+ border-color: rgba(19, 135, 90, 0.22);
+}
+
+.decision-badge {
+ width: fit-content;
+ max-width: 100%;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 10px;
+ border-radius: 8px;
+ font-size: 0.82rem;
+ font-weight: 800;
+ border: 1px solid transparent;
+}
+
+.decision-allow {
+ background: #e9f8ef;
+ color: #137a51;
+ border-color: #bfe8d0;
+}
+
+.decision-constrain {
+ background: #fff6d7;
+ color: #8d640e;
+ border-color: #ead07b;
+}
+
+.decision-deny {
+ background: #ffe7e7;
+ color: #a43a3a;
+ border-color: #efb9b9;
+}
+
+.decision-approval {
+ background: #e9f4ff;
+ color: #2c638f;
+ border-color: #bfdcf4;
+}
+
+.decision-pending {
+ background: #f1f4f1;
+ color: #5d6870;
+ border-color: #d8ded8;
+}
+
+.decision-copy {
+ margin-top: 11px;
+ color: #49545d;
+ font-size: 0.92rem;
+ line-height: 1.45;
+}
+
+.compact-copy {
+ margin-top: 0;
+ margin-bottom: 10px;
+ font-size: 0.82rem;
+}
+
+.metric-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 8px;
+ margin-top: 12px;
+}
+
+.metric {
+ min-width: 0;
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr) auto;
+ align-items: center;
+ gap: 7px;
+ padding: 9px;
+ border-radius: 8px;
+ background: #f6f8f6;
+ color: #5b6871;
+ font-size: 0.78rem;
+}
+
+.metric strong {
+ min-width: 0;
+ color: #202328;
+ font-size: 0.78rem;
+ text-align: right;
+ white-space: nowrap;
+}
+
+.risk-low strong {
+ color: #137a51;
+}
+
+.risk-medium strong {
+ color: #92650b;
+}
+
+.risk-high strong {
+ color: #a43a3a;
+}
+
+.action-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 8px;
+}
+
+.action-grid div {
+ min-width: 0;
+ padding: 9px;
+ border-radius: 8px;
+ background: #f6f8f6;
+}
+
+.action-grid dt {
+ color: #738087;
+ font-size: 0.72rem;
+ font-weight: 800;
+ text-transform: uppercase;
+}
+
+.action-grid dd {
+ margin: 4px 0 0;
+ min-width: 0;
+ color: #253038;
+ font-size: 0.82rem;
+ font-weight: 700;
+ overflow-wrap: anywhere;
+}
+
+.policy-chip-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 7px;
+}
+
+.policy-chip {
+ display: inline-flex;
+ max-width: 100%;
+ align-items: center;
+ padding: 7px 9px;
+ border-radius: 8px;
+ font-size: 0.76rem;
+ font-weight: 800;
+ border: 1px solid rgba(31, 35, 40, 0.1);
+}
+
+.tone-green {
+ background: #e9f8ef;
+ color: #137a51;
+}
+
+.tone-amber {
+ background: #fff6d7;
+ color: #8d640e;
+}
+
+.tone-cyan {
+ background: #e8fbfb;
+ color: #087276;
+}
+
+.tone-violet {
+ background: #f1eaff;
+ color: #6c4ca3;
+}
+
+.tone-rose {
+ background: #ffe8ef;
+ color: #a13f63;
+}
+
+.tone-blue {
+ background: #e9f4ff;
+ color: #2c638f;
+}
+
+.policy-detail-list {
+ display: grid;
+ gap: 8px;
+ margin-top: 10px;
+}
+
+.policy-detail-list p {
+ display: grid;
+ gap: 3px;
+ padding: 9px;
+ border-radius: 8px;
+ background: #f6f8f6;
+ color: #54616a;
+ font-size: 0.78rem;
+ line-height: 1.34;
+}
+
+.policy-detail-list strong {
+ color: #2e3840;
+}
+
+.policy-stack {
+ display: grid;
+ gap: 7px;
+}
+
+.policy-stack div {
+ display: grid;
+ gap: 3px;
+ padding: 8px 9px;
+ border-radius: 8px;
+ background: #f6f8f6;
+}
+
+.policy-stack strong {
+ color: #2e3840;
+ font-size: 0.78rem;
+}
+
+.policy-stack span {
+ color: #60707a;
+ font-size: 0.76rem;
+ line-height: 1.28;
+}
+
+.terminal-section {
+ max-height: 350px;
+}
+
+.terminal-log {
+ height: 286px;
+ overflow: auto;
+ display: grid;
+ align-content: start;
+ gap: 7px;
+ padding: 8px;
+ border-radius: 8px;
+ background: #202328;
+ color: #f4f7f4;
+}
+
+.terminal-row {
+ width: 100%;
+ display: grid;
+ grid-template-columns: 94px minmax(0, 1fr);
+ gap: 8px;
+ align-items: start;
+ padding: 8px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 7px;
+ background: rgba(255, 255, 255, 0.05);
+ color: #f4f7f4;
+ text-align: left;
+ cursor: pointer;
+}
+
+.terminal-row:hover {
+ border-color: rgba(255, 255, 255, 0.25);
+}
+
+.terminal-meta {
+ color: #aeb8b2;
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
+ font-size: 0.72rem;
+ white-space: nowrap;
+}
+
+.terminal-row > span:last-child {
+ min-width: 0;
+ font-size: 0.78rem;
+ line-height: 1.35;
+ overflow-wrap: anywhere;
+}
+
+.terminal-empty {
+ color: #aeb8b2;
+ font-size: 0.82rem;
+ padding: 10px;
+}
+
+.watch-steps {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 7px;
+ margin-top: 12px;
+}
+
+.watch-steps span {
+ padding: 8px 9px;
+ border-radius: 8px;
+ background: #f6f8f6;
+ color: #34404a;
+ font-size: 0.78rem;
+ font-weight: 900;
+}
+
+.story-section {
+ max-height: none;
+}
+
+.story-list {
+ display: grid;
+ gap: 8px;
+}
+
+.story-row {
+ width: 100%;
+ display: grid;
+ gap: 4px;
+ padding: 10px;
+ border: 1px solid rgba(31, 35, 40, 0.1);
+ border-left-width: 4px;
+ border-radius: 8px;
+ background: #f8faf8;
+ color: #2f3840;
+ cursor: pointer;
+ text-align: left;
+}
+
+.story-row:hover {
+ background: #ffffff;
+ border-color: rgba(31, 35, 40, 0.18);
+}
+
+.story-row strong {
+ font-size: 0.78rem;
+ text-transform: uppercase;
+ color: #5c6870;
+}
+
+.story-row span {
+ min-width: 0;
+ font-size: 0.86rem;
+ line-height: 1.36;
+ overflow-wrap: anywhere;
+}
+
+.story-row.tone-agent {
+ border-left-color: #3f7dda;
+}
+
+.story-row.tone-allow {
+ border-left-color: #32bd77;
+}
+
+.story-row.tone-allow_with_constraints {
+ border-left-color: #e8b630;
+}
+
+.story-row.tone-deny {
+ border-left-color: #e45454;
+}
+
+.story-row.tone-executor {
+ border-left-color: #9aa59d;
+}
+
+.story-row.tone-world,
+.story-row.tone-prover {
+ border-left-color: #7d67b8;
+}
+
+.details-panel {
+ display: grid;
+ gap: 8px;
+}
+
+.details-panel details {
+ border: 1px solid rgba(31, 35, 40, 0.1);
+ border-radius: 8px;
+ background: rgba(255, 255, 255, 0.78);
+}
+
+.details-panel summary {
+ min-height: 42px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 0 11px;
+ cursor: pointer;
+ color: #34404a;
+ font-size: 0.82rem;
+ font-weight: 900;
+}
+
+.details-panel details[open] summary svg {
+ transform: rotate(180deg);
+}
+
+.details-panel ul,
+.details-panel p,
+.details-panel pre {
+ margin: 0;
+ padding: 0 12px 12px;
+}
+
+.details-panel ul {
+ display: grid;
+ gap: 6px;
+ padding-left: 28px;
+ color: #52606a;
+ font-size: 0.8rem;
+ line-height: 1.34;
+}
+
+.details-panel p {
+ color: #52606a;
+ font-size: 0.8rem;
+ line-height: 1.4;
+}
+
+.details-panel pre {
+ max-height: 240px;
+ overflow: auto;
+ color: #f4f7f4;
+ background: #202328;
+ border-radius: 0 0 8px 8px;
+ padding-top: 12px;
+ font-size: 0.72rem;
+ line-height: 1.38;
+}
+
+.simple-speed {
+ width: 100%;
+}
+
+.terminal-row.tone-agent {
+ border-left: 3px solid #63b3ed;
+}
+
+.terminal-row.tone-allow {
+ border-left: 3px solid #32bd77;
+}
+
+.terminal-row.tone-allow_with_constraints {
+ border-left: 3px solid #e8b630;
+}
+
+.terminal-row.tone-deny {
+ border-left: 3px solid #e45454;
+}
+
+.terminal-row.tone-approval_required {
+ border-left: 3px solid #3f7dda;
+}
+
+.terminal-row.tone-executor {
+ border-left: 3px solid #9aa59d;
+}
+
+.terminal-row.tone-world,
+.terminal-row.tone-prover {
+ border-left: 3px solid #b894e8;
+}
+
+.plain-list {
+ margin: 0;
+ padding: 0;
+ display: grid;
+ gap: 7px;
+ list-style: none;
+}
+
+.plain-list + .plain-list {
+ margin-top: 9px;
+}
+
+.plain-list li {
+ position: relative;
+ padding: 8px 9px 8px 25px;
+ border-radius: 8px;
+ background: #f6f8f6;
+ color: #43505a;
+ font-size: 0.82rem;
+ line-height: 1.35;
+}
+
+.plain-list li::before {
+ content: "";
+ position: absolute;
+ left: 10px;
+ top: 14px;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: #1f9d68;
+}
+
+.plain-list.obligations li::before {
+ background: #2d74aa;
+}
+
+.budget-row {
+ display: grid;
+ gap: 7px;
+ margin-bottom: 11px;
+}
+
+.budget-label {
+ display: flex;
+ justify-content: space-between;
+ gap: 8px;
+ color: #55616a;
+ font-size: 0.8rem;
+ font-weight: 800;
+}
+
+.budget-track {
+ height: 9px;
+ overflow: hidden;
+ border-radius: 999px;
+ background: #e3e8e4;
+}
+
+.budget-fill {
+ display: block;
+ height: 100%;
+ border-radius: inherit;
+}
+
+.budget-fill.steady {
+ background: #27a96d;
+}
+
+.budget-fill.cool {
+ background: #2d82c7;
+}
+
+.budget-fill.warning {
+ background: #e6a51f;
+}
+
+.budget-fill.danger {
+ background: #d84f5d;
+}
+
+.scenario-rail {
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 8px;
+ padding: 8px;
+ border-radius: 8px;
+}
+
+.mission-controls {
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+}
+
+.scenario-tab {
+ min-width: 0;
+ min-height: 56px;
+ display: grid;
+ align-content: center;
+ gap: 4px;
+ padding: 8px 10px;
+ border: 1px solid rgba(31, 35, 40, 0.1);
+ border-radius: 8px;
+ background: #f7f9f7;
+ color: #2f3840;
+ text-align: left;
+ transition:
+ transform 150ms ease,
+ background-color 150ms ease,
+ border-color 150ms ease;
+}
+
+button.scenario-tab {
+ cursor: pointer;
+}
+
+.scenario-tab:hover,
+.scenario-tab.is-selected {
+ transform: translateY(-1px);
+ background: #ffffff;
+ border-color: rgba(19, 135, 90, 0.38);
+}
+
+.scenario-tab span {
+ min-width: 0;
+ font-size: 0.86rem;
+ font-weight: 900;
+ overflow-wrap: anywhere;
+}
+
+.scenario-tab small {
+ min-width: 0;
+ color: #68747c;
+ font-size: 0.72rem;
+ font-weight: 800;
+ overflow-wrap: anywhere;
+}
+
+@media (max-width: 1080px) {
+ .demo-layout {
+ grid-template-columns: 1fr;
+ }
+
+ .simple-layout {
+ grid-template-columns: 1fr;
+ }
+
+ .inspector {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ overflow: visible;
+ padding-right: 0;
+ }
+
+ .explain-strip {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .primary-decision {
+ grid-column: 1 / -1;
+ }
+
+ .story-panel {
+ overflow: visible;
+ }
+}
+
+@media (max-width: 720px) {
+ .app-shell {
+ padding: 10px;
+ }
+
+ .topbar {
+ align-items: stretch;
+ flex-direction: column;
+ }
+
+ .topbar-actions {
+ justify-content: stretch;
+ }
+
+ .speed-control {
+ flex: 1 1 210px;
+ }
+
+ .seed-control {
+ flex: 1 1 160px;
+ }
+
+ .scene-panel {
+ min-height: 430px;
+ }
+
+ .legend-strip {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .inspector {
+ grid-template-columns: 1fr;
+ }
+
+ .metric-grid,
+ .action-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .scenario-rail {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .mission-controls {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .terminal-row {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 560px) {
+ .explain-strip {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 430px) {
+ .scene-status {
+ align-items: flex-start;
+ flex-direction: column;
+ }
+
+ .legend-strip {
+ left: 10px;
+ right: 10px;
+ bottom: 10px;
+ gap: 6px;
+ }
+
+ .legend-strip span {
+ justify-content: flex-start;
+ font-size: 0.71rem;
+ padding: 7px;
+ }
+
+ .scene-panel {
+ min-height: 380px;
+ }
+}
diff --git a/projects/robotics-policy-prover/vite.config.js b/projects/robotics-policy-prover/vite.config.js
new file mode 100644
index 00000000..ac9821e3
--- /dev/null
+++ b/projects/robotics-policy-prover/vite.config.js
@@ -0,0 +1,11 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ proxy: {
+ "/api": "http://127.0.0.1:8787",
+ },
+ },
+});
diff --git a/zensical.toml b/zensical.toml
index d9c7e499..f02ac1fa 100644
--- a/zensical.toml
+++ b/zensical.toml
@@ -19,6 +19,7 @@ nav = [
"dev-notes/index.md",
{"Posts" = [
# dev-notes:nav:start
+ {"Can Formal Methods Govern AI-Generated Robot Actions? An OpenShell-Inspired Experiment" = "dev-notes/posts/2026-08-07-formal-methods-ai-generated-robot-actions.md"},
{"Bringing Privacy and Security to the Edge with OpenShell" = "dev-notes/posts/2026-07-20-policy-controlling-reachy-mini-with-openshell.md"}
# dev-notes:nav:end
]}