diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..eafb74d3c1cdbcbaf7a75b62ef3e03135364f875 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.gradle/ +build/ +**/build/ +*.class +*.jar +!gradle/wrapper/gradle-wrapper.jar +.idea/ +*.iml +*.iws +*.ipr +out/ +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..9556923cc183e690e19e87a8906939812356a0ac --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +All notable changes to the ORTHO-32 Java SDK will be documented here. +Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and Semantic Versioning. + +## [0.1.0] - 2026-08-14 + +### Added +- Initial SDK release with simulator transport, all public modules +- Public packages: `org.ortho32`, `device`, `fabric`, `compute`, `tensor`, `trace`, `verify`, `security`, `agent`, `system`, `transport`, `exceptions` +- Core types: `OrthoRuntime`, `OrthoSession` (AutoCloseable), `OrthoContext`, `OrthoVersion`, `OrthoResult`, `OrthoDevice` family +- Fabric types: `FabricCommand`, `FabricCompletion`, `FabricAddress/Opcode/Epoch/Slot` — logical API, no BAR access +- Tensor types: `Tensor`, `TensorShape/Buffer/Job/Operation/Result`, `TensorLatency` (TMUL=4, TLOAD=5, TSTORE=5, Scratchpad=2) — cycles are architectural integers +- Trace types: `ExecutionTrace`, `CycleRecord`, `TraceHash` (SHA-256 canonical), `TraceComparator` — H=0 determinism +- Verification types: 12 theorems, `TheoremId/Result`, `VerificationStatus` (VERIFIED != TESTED != ASSUMED), `CrossVerificationResult` with MISMATCH blocking +- Security types: `Capability`, `Permission`, `SecurityContext`, `Attestation` +- Agent types: `AgentIntent`, `AgentTask`, `AgentResult` via typed intents +- Transport: `OrthoTransport` (internal), `SimulatorTransport` (deterministic, no hardware), `NativeHostTransport`, `PCIe/USB/Ethernet` stubs — transport-agnostic API +- Schemas: `fabric-command`, `fabric-completion`, `execution-trace`, `theorem-result`, `attestation` +- Examples: `device-list`, `tensor-run`, `trace-replay`, `proof-verify`, `agent-client` +- Integration tests: `SimulatorTransportTest`, `TensorJobTest`, `DeterminismTest`, `TraceComparatorTest`, `VerificationStatusTest`, `CrossVerifyTest`, `SessionLifecycleTest`, `TransportAgnosticTest` — all runnable offline +- Build: Gradle Kotlin DSL, Java 21 toolchain, reproducible builds, sources & Javadoc JARs, Maven publication, dependency locking, checksums +- Compatibility tuple: `SDK-Version + Fabric-Protocol-Version + Device-ABI-Version` + +[0.1.0]: https://github.com/ortho32/ortho32-sdk-java/releases/tag/v0.1.0 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..927a2460d82fcd270ca2f48f1ce8f362a965d1a8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,110 @@ +TRI-LICENSE STRUCTURE +===================== + +This project is available under THREE licensing options: + +1. Business Source License 1.1 (BSL-1.1) + - Source-available with commercial restrictions + - No managed service offerings at enterprise scale + - Converts to AGPL-3.0 after transition period (Change Date: 2028-08-15) + +2. GNU Affero General Public License v3.0 (AGPL-3.0) + - Strong network copyleft + - SaaS/network distribution triggers source disclosure + - All modifications must be AGPL-3.0 + +3. Mozilla Public License 2.0 (MPL-2.0) + Commercial Dual License + - Weak copyleft (file-level) + - Can combine with proprietary code + - Modified files must remain MPL-2.0 + - Commercial license available for copyleft bypass + +================================================================================ + +WHICH LICENSE APPLIES? + +License selection is determined by use case. Use the policy engine: + + https://github.com/SNAPKITTYWEST/license-policy-engine + + swipl -q -t halt -f license_policy.pl -- select + +Use cases: + - saas_wrapper → AGPL-3.0 + - enterprise_restricted → BSL-1.1 + - file_level_mod → MPL-2.0 + - copyleft_bypass → Commercial + - open_source_redistribution → AGPL-3.0 + +================================================================================ + +ORTHO-32 ECOSYSTEM + +This repository is part of the ORTHO-32 deterministic processor ecosystem. +All ORTHO-32 connected repositories share this tri-license structure. + +Ecosystem repos: + - ORTHO32 (ISA, RTL, Lean 4 proofs) + - ortho32-host (C#/.NET runtime spine) + - ortho32-desktop (Swift UI) + - ortho32-api (Python/FastAPI) + - ortho32-ai-gateway (MUMPS) + - ortho32-mcp (AWK MCP server) + - ortho32-sdk-java (JVM SDK) + - ortho32-isolation (Formal isolation proofs) + +================================================================================ + +COPYRIGHT HOLDER + +Copyright (C) 2026 Bel Esprit D'Accord Irrevocable Trust +SnapKitty Collective Limited (FLP) + +Contact: jessicalw34@gmail.com +Web: https://github.com/SNAPKITTYWEST + +================================================================================ + +================================================================================ + +PATENT RETALIATION + +No patent license is granted by this license. If you initiate patent +litigation against the copyright holder, any co-author, or any +downstream user of this software — including cross-claims or +counterclaims in a lawsuit — your license under this software +terminates automatically and immediately without notice. + +================================================================================ + +LITIGATION WARNING — WILLFUL INFRINGEMENT + +Any use of this software beyond the permissions granted above +constitutes infringement. Willful infringement entitles the +copyright holder to seek: + + - Statutory damages for willful infringement + - Recovery of attorneys fees and litigation costs + - Injunctive relief to prevent continued infringement + - Disgorgement of profits derived from infringing use + +Jurisdiction: United States of America +Governing Law: Federal copyright law (17 U.S.C.) and applicable state law + +================================================================================ + +STRONG COPYLEFT — NETWORK DEPLOYMENT + +Network deployment of this software (SaaS, API, hosted service, +managed service) is NOT eligible for the MPL-2.0 option. +Network deployment MANDATES AGPL-3.0: + + - All source code of the deployed application must be disclosed + - All modifications must be released under AGPL-3.0 + - All combined works must be released under AGPL-3.0 + - There is no opt-out. The copyleft follows the network. + +================================================================================ + +Copyright (C) 2026 Bel Esprit D'Accord Irrevocable Trust (EIN 42-697643) +All rights reserved except as expressly granted above. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000000000000000000000000000000000000..afa7a30935db6590fd2b80250425433db0ceaf1b --- /dev/null +++ b/NOTICE @@ -0,0 +1,141 @@ +NOTICE — SnapKitty Sovereign IP Estate +======================================= + +This repository is part of the SnapKitty Sovereign IP Estate, held by: + + Bel Esprit D'Accord Irrevocable Trust (EIN 42-697643) + SnapKitty Collective Limited (FLP) + + Operators: Ahmad Ali Parr · Jessica Westerhoff + Contact: ahmedparr93@gmail.com | jessicalw34@gmail.com + Web: https://github.com/SNAPKITTYWEST + +All repositories under github.com/SNAPKITTYWEST and +github.com/BEL-ESPRIT-D-ACCORD-TRUST-HOLDINGS constitute a single +unified IP estate. Licensing of one component does not grant rights +to any other component. + +================================================================================ + +PATENT NOTICE + +No patent license is granted by this license. If you initiate patent +litigation against the copyright holder, any co-author, or any +downstream user of this software — including cross-claims or +counterclaims — your license under this software terminates +automatically and immediately without notice. + +================================================================================ + +LITIGATION WARNING + +Willful infringement of this license entitles the copyright holder to +seek the maximum available remedies under applicable law, including: + + - Statutory damages for willful infringement + - Recovery of attorneys fees and litigation costs + - Injunctive relief to prevent continued infringement + - Disgorgement of profits derived from infringing use + +Jurisdiction: State of [jurisdiction TBD by counsel] +Governing Law: Laws of the United States of America + +Any use of this software beyond the permissions granted in the LICENSE +file constitutes infringement and triggers these remedies. + +================================================================================ + +STRONG COPYLEFT NETWORK NOTICE + +Any deployment of this software over a network — including SaaS, +API services, hosted tools, or managed services — triggers the +AGPL-3.0 license option. Network deployment is NOT eligible for +the MPL-2.0 option. The policy engine enforces this: + + saas_wrapper → AGPL-3.0 (mandatory) + network_deployment → AGPL-3.0 (mandatory) + api_service → AGPL-3.0 (mandatory) + enterprise_restricted → BSL-1.1 + file_level_mod → MPL-2.0 (non-network only) + research_evaluation → BSL-1.1 (free) + +If you deploy this software over a network, you MUST release all +modified and combined source code under AGPL-3.0. There is no +exception. There is no opt-out. The copyleft follows the network. + +================================================================================ + +ATTRIBUTION REQUIREMENTS + +Any permitted use must display the following in documentation, +UI, and all derivative works: + + "Built with SnapKitty sovereign software. + Copyright (C) 2026 Bel Esprit D'Accord Irrevocable Trust. + https://github.com/SNAPKITTYWEST" + +Removing, obscuring, or misrepresenting this attribution +constitutes willful infringement. + +================================================================================ + +Copyright (C) 2026 Bel Esprit D'Accord Irrevocable Trust (EIN 42-697643) +All rights reserved except as expressly granted in the LICENSE file. + +================================================================================ + +PUBLISHED PRIOR ART — ZENODO DOI RECORD + +The following peer-reviewed, DOI-stamped publications establish prior art +for all IP in this repository and across the SnapKitty sovereign stack. +Both works are immutable, publicly timestamped, and citable in any +legal, academic, or patent proceeding. + +Publication 1: + Title: The Sovereign Stack: From Attention Exhaustion to + Computational Jurisdiction — A Unified Theory of + Deterministic Agent Architecture + Authors: Ahmad Ali Parr, Jessica Westerhoff + DOI: 10.5281/zenodo.21816366 + Date: 2026-08-06 + URL: https://zenodo.org/records/21816366 + Covers: Sovereign Tick Runtime, Jordan-algebraic routing, + NAND completeness kernel, Phase Mirror Dissonance, + Softmax simplex critique, HyperKitty constraint DSL, + Resonance block capture, proof-gated deterministic + execution, cryptographic provenance methodology. + +Publication 2: + Title: Erdos-789-lean/Idris-2 — Erdos-Straus Distinct-Sum-Sizes + Problem: An Elementary Quadratic Lower Bound for the + Greedy Sequence + Authors: Ahmad Ali Parr + DOI: 10.5281/zenodo.21869107 + Date: 2026-08-10 + URL: https://zenodo.org/records/21869107 + Covers: Elementary combinatorial proof of g_k >= (k^2+1)/2, + O(sqrt(n)) extremal bound, Straus 1966 upper bound + recovery, Lean 4 + Idris 2 dual formalization, + gap-counting framework for DSS construction. + +Any claim of independent invention of the mathematical results, +architectural patterns, or formal verification structures described +in these publications must predate the DOI timestamps above. +No such prior claim exists. + +================================================================================ + +Publication 3: + Title: SNAPKITTYWEST: Sovereign Compute Architecture with + Linear Types, WORM Seals, Goldilocks Field Arithmetic, + and Settlement Witnesses + Authors: Snapkitty Collective, Jessica Westerhoff + DOI: 10.5281/zenodo.21132094 + Date: 2026-07-02 + URL: https://zenodo.org/records/21132094 + Covers: sovereign-multiplicity, Goldilocks field arithmetic + (p=2^64-2^32+1), WORM seals, linear type hierarchy + (lin/aff/un/cap/seal), PIRTM compiler infrastructure, + agent governance, topological quantum computing, + snapaddr canonical addressing, root-fontana + constitutional compiler. Git history anchor: 2026-05-07. diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..2563efaef6d57cfd8bd3636812148f261bd6e296 --- /dev/null +++ b/README.md @@ -0,0 +1,197 @@ +# ORTHO-32 Java SDK + +Official JVM developer surface for ORTHO-32 — a deterministic, tensor-augmented ORTHO architecture. + +> Java Application → ORTHO Java SDK → ORTHO Protocol / Native Bridge → ORTHO Host Service → Transport → ORTHO Fabric → Hardware + +Applications program against stable, typed Java APIs (`OrthoDevice`, `FabricCommand`, `TensorJob`, `ExecutionTrace`, `TheoremResult`) and never touch PCIe BARs, DMA windows, or raw MMIO. The SDK hides transport internals; the same code runs against a simulator, FPGA, ASIC, PCIe, USB, or Ethernet device. + +## What is ORTHO-32? + +ORTHO-32 is a 32-bit ORTHO ISA with deterministic arbitration, a 4-cycle TMUL tensor unit, cycle-accurate tracing (H=0 determinism), and 12 mechanically-checked theorems (Lean4 + HOL Light). Fabric commands are logically addressed, epoch-ordered, and completed with architectural-cycle timestamps — not wall-clock time. + +## What this SDK provides + +- **Stable Java APIs** over ORTHO system contracts: `org.ortho32`, `org.ortho32.device`, `fabric`, `compute`, `tensor`, `trace`, `verify`, `security`, `agent`, `system`, `transport`, `exceptions` +- **Immutable value objects, records, `CompletableFuture` async, `Flow.Publisher` streaming, typed exceptions, `AutoCloseable` resources** +- **SimulatorTransport** — deterministic fake fabric for offline development, CI, and tests (no hardware required) +- **JSON Schemas** for `FabricCommand`, `FabricCompletion`, `ExecutionTrace`, `TheoremResult`, `Attestation` +- **Examples & integration tests** validated against `SimulatorTransport` + +## Quick-start (5 lines) + +Add the SDK to your Gradle project and run with the simulator (Java 21): + +```kotlin +dependencies { implementation("dev.ortho32:ortho32-sdk:0.1.0") } +``` + +```java +try (OrthoRuntime runtime = OrthoRuntime.open(); + OrthoSession session = runtime.openSession()) { + + OrthoDevice device = session.devices().firstAvailable(); + + TensorJob job = TensorJob.builder() + .device(device) + .operation(TensorOperation.TMUL) + .inputA(matrixA) + .inputB(matrixB) + .build(); + + TensorResult result = session.tensor().submit(job).join(); + + System.out.println("cycles: " + result.cycles()); + System.out.println("trace: " + result.traceHash()); +} +``` +No drivers, no hardware — `OrthoRuntime.open()` defaults to `SimulatorTransport` when no device is present. Swap to PCIe/USB/Ethernet by changing transport, not application code. + +## Module overview + +| Module | Artifact | Purpose | +|---|---|---| +| `ortho-core` | `org.ortho32` | `OrthoRuntime`, `OrthoSession`, `OrthoContext`, `OrthoVersion`, `OrthoResult` | +| `ortho-device` | `org.ortho32.device` | `OrthoDevice`, `OrthoDeviceId/Info/Capabilities/State` | +| `ortho-fabric` | `org.ortho32.fabric` | `FabricCommand`, `FabricCompletion`, `FabricAddress/Opcode/Epoch/Slot` | +| `ortho-compute` | `org.ortho32.compute` | Scalar compute submission | +| `ortho-tensor` | `org.ortho32.tensor` | `Tensor`, `TensorShape/Buffer/Job/Operation/Result`, `TensorLatency` | +| `ortho-trace` | `org.ortho32.trace` | `ExecutionTrace`, `CycleRecord`, `TraceHash`, `TraceComparator` | +| `ortho-verify` | `org.ortho32.verify` | `TheoremId`, `TheoremResult`, `VerificationStatus/Report`, `CrossVerificationResult` | +| `ortho-security` | `org.ortho32.security` | `Capability`, `Permission`, `SecurityContext`, `Attestation` | +| `ortho-agent` | `org.ortho32.agent` | `AgentIntent`, `AgentTask`, `AgentResult`, `AgentCapability` | +| `ortho-transport` | `org.ortho32.transport` | `OrthoTransport` (internal), `SimulatorTransport`, `NativeHostTransport`, `PCIe/USB/Ethernet` | +| `ortho-system` | `org.ortho32.system` | System utilities, versioning | + +Per-module JARs + aggregate `ortho32-sdk.jar`, sources JAR, Javadoc JAR, Maven publication metadata, reproducible builds, checksums, dependency locking. + +## Transport options + +All transports expose the same `OrthoSession` API. Application code is transport-agnostic. + +| Transport | Class | Use | +|---|---|---| +| **Simulator** | `SimulatorTransport` | Deterministic fake fabric, offline tests, CI. Required. No hardware. | +| **PCIe** | `PCIeTransport` | Native host service over PCIe BAR | +| **USB** | `USBTransport` | USB-attached ORTHO device | +| **Ethernet** | `EthernetTransport` | Remote ORTHO fabric over UDP/TCP | +| **Native** | `NativeHostTransport` | JNI bridge to ORTHO Host Service | + +```java +// Explicit simulator (tests/examples) +OrthoRuntime runtime = OrthoRuntime.open(new SimulatorTransport()); +// Production - auto-detect or inject via service loader +OrthoRuntime runtime = OrthoRuntime.open(); +``` + +> Rule: `FabricCommand` is the logical API. Applications never write PCIe BAR registers. + +## Versioning + +Semantic versioning for public Java APIs. Compatibility is a tuple: + +``` +SDK-Version + Fabric-Protocol-Version + Device-ABI-Version +``` + +Access via `OrthoVersion`: + +```java +OrthoVersion v = OrthoRuntime.open().version(); +System.out.println(v.sdkVersion()); // e.g. 0.1.0 +System.out.println(v.fabricProtocolVersion()); // e.g. 1 +System.out.println(v.deviceABIVersion()); // e.g. 2 +``` + +- **SDK version** — bumped on API change; follows SemVer +- **Fabric protocol version** — bumped on wire-format change +- **Device ABI version** — bumped on scratchpad / register map change +- `OrthoRuntime` checks compatibility on `openSession()` and fails fast on mismatch + +See `CHANGELOG.md` for history. + +## Build instructions + +Requirements: Java 21 toolchain, Gradle 8.x + +```bash +git clone https://github.com/ortho32/ortho32-sdk-java.git +cd ortho32-sdk-java +./gradlew build # all modules, tests with SimulatorTransport +./gradlew :integration-tests:test --info # integration tests only (no hardware) +./gradlew publishToMavenLocal # artifacts to ~/.m2 +./gradlew javadoc # aggregated Javadoc +``` + +Reproducible builds, dependency locking (`gradle.lockfile`), checksums. Gradle Kotlin DSL. + +## Examples + +All examples run without hardware via `SimulatorTransport`. + +| Example | Path | Description | +|---|---|---| +| device-list | `examples/device-list` | Enumerate and print available devices | +| tensor-run | `examples/tensor-run` | Build `TensorJob`, submit `TMUL`, print cycles & `traceHash` | +| trace-replay | `examples/trace-replay` | Capture trace, compute hash, replay, compare (H=0) | +| proof-verify | `examples/proof-verify` | Verify 12 theorems via `VerifyClient`, table with Lean/HOL columns | +| agent-client | `examples/agent-client` | Dispatch `AgentTask` via `AgentClient`, print result | + +Run: + +```bash +./gradlew :examples:device-list:run +./gradlew :examples:tensor-run:run +./gradlew :examples:trace-replay:run +./gradlew :examples:proof-verify:run +./gradlew :examples:agent-client:run +``` + +## API style + +- Immutable value objects; `record` where appropriate +- No raw native pointers in public API +- No `Map` when schema exists +- Async via `CompletableFuture`, streaming via `Flow.Publisher` +- Typed exceptions (`OrthoException` hierarchy) +- `AutoCloseable` sessions and transports + +## Contributing + +1. Fork, create feature branch +2. `./gradlew build` — must pass with `SimulatorTransport` (no hardware) +3. Add schema + Javadoc for new public types +4. Update `CHANGELOG.md` +5. PR with determinism proof (`TraceHash` equality) where applicable + +License: MIT. See `LICENSE`. + + +--- + +## Sovereign Boundary + +This repository operates under the **SnapKitty Method**: public by default, sovereign by construction. + +``` +CODE → PUBLIC (this repository) +PROOF → PUBLIC (Lean 4 / formal verification artifacts) +SPEC → PUBLIC (interfaces, schemas, invariants) +HISTORY → PUBLIC (cryptographic provenance, WORM-sealed) + +AUTHORITY → SOVEREIGN (Bel Esprit D'Accord Irrevocable Trust) +STATE → SOVEREIGN (credentials, private data, operational secrets) +EXECUTION → AUTHORIZED (requires sovereign state — not in this repo) +``` + +> **"Here is the machine. You do not own the state it operates on."** + +Reading the source does not grant execution authority. Forking the repo does not grant deployment rights. The code is verifiable. The authority is not transferable. + +**[→ Full architecture: SOVEREIGN_METHOD.md](./SOVEREIGN_METHOD.md)** + +**[→ License terms: LICENSE](./LICENSE)** · **[→ IP estate: NOTICE](./NOTICE)** + +--- + +*Copyright (C) 2026 Bel Esprit D'Accord Irrevocable Trust (EIN 42-697643) · `Ω = TRUST ∧ CODE`* diff --git a/SOVEREIGN_METHOD.md b/SOVEREIGN_METHOD.md new file mode 100644 index 0000000000000000000000000000000000000000..254c1f4f75ebef76ca85b284aa4bcb48865ccdfb --- /dev/null +++ b/SOVEREIGN_METHOD.md @@ -0,0 +1,139 @@ +# THE SNAPKITTY METHOD + +## PUBLIC BY DEFAULT. SOVEREIGN BY CONSTRUCTION. + +> **Nothing needs to be hidden if the boundary between public knowledge and sovereign execution is formally defined.** + +--- + +## The Boundary + +``` +CODE → PUBLIC +PROOF → PUBLIC +SPEC → PUBLIC +TESTS → PUBLIC +HISTORY → PUBLIC +PROVENANCE → PUBLIC + +AUTHORITY → SOVEREIGN +STATE → SOVEREIGN +SECRETS → SOVEREIGN +EXECUTION → AUTHORIZED +``` + +The source is public. The **state, credentials, execution authority, and deployment boundaries** remain sovereign. + +This is not: *"Here is the fake public version."* + +This is: **"Here is the machine. You don't own the state it operates on."** + +--- + +## No Stubs. Proofs Instead. + +If something can be published safely → publish the **real implementation**. + +If something cannot be published → publish the **formal boundary** describing what it is allowed to do. + +No theatrical fake API. No TODO implementations. No repository whose only purpose is to look open. + +--- + +## Cryptographic Provenance + +Every artifact carries a cryptographic provenance record: + +``` +ARTIFACT → HASH → SEAL → COMMIT → VERIFICATION → IMMUTABLE PROVENANCE +``` + +The repository is an evidence trail. Not merely a source-code dump. + +``` +SHA-256 · Merkle relationships · Ed25519 signatures · WORM records · reproducible builds · formal verification +``` + +The objective is not: *"This code exists."* + +It is: **"This artifact existed in this state, was produced through this transformation, and can be independently verified."** + +--- + +## Automated Provenance Pipeline + +``` +COMMIT → SCHEMA CHECK → SECRET CHECK → LICENSE CHECK → PROVENANCE CHECK + → TEST → FORMAL VERIFICATION → SEAL → PUBLISH +``` + +A failed invariant stops publication. The system does not negotiate with the failure. + +``` +INVARIANT FAILED → NO → NO SEAL → NO RELEASE +``` + +--- + +## AI Agents — Constrained Builders + +Agents operate inside explicit boundaries: + +``` +INPUT → AGENT → PROPOSED CHANGE → TEST → STATIC ANALYSIS + → FORMAL CHECK → HUMAN / POLICY GATE → CRYPTOGRAPHIC SEAL → MERGE +``` + +An agent cannot declare its own output correct. It must satisfy an external constraint. + +> **An agent may propose state transitions. It may not unilaterally define truth.** + +--- + +## The Zero-Sorry Boundary + +``` +THEOREM → LEAN 4 → PROOF → ZERO SORRY → VERIFIED ARTIFACT +``` + +The repository contains the proof. The verifier independently executes the proof. + +Instead of: *"Trust the developer."* + +The architecture moves toward: **"Verify the invariant."** + +--- + +## Public Code ≠ Public Authority + +``` +Open Source ≠ Open Authority +Public Code ≠ Public Control +``` + +A public repository exposes architecture, algorithms, interfaces, schemas, tests, proofs, build systems, and documentation — without exposing credentials, deployment authority, operational state, or execution control. + +That is the central SnapKitty architectural boundary. + +--- + +## The Principle + +> **Don't hide the machine.** +> +> **Make the machine verifiable.** +> +> **Keep control at the execution boundary.** + +**PUBLIC SUBSTRATE. SOVEREIGN STATE. FORMAL CONSTRAINTS. CRYPTOGRAPHIC PROVENANCE. ZERO-SORRY WHERE PROVABLE.** + +--- + +## Trust + +**Bel Esprit D'Accord Irrevocable Trust (EIN 42-697643)** +SnapKitty Collective Limited (FLP) +Operators: Ahmad Ali Parr · Jessica Westerhoff +Web: https://github.com/SNAPKITTYWEST + +`Ω = TRUST ∧ CODE` diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..dfe9d95038f7e258643de798ff520490acb67e51 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,112 @@ +import org.gradle.api.tasks.bundling.AbstractArchiveTask +import java.security.MessageDigest + +plugins { + `java-library` + `maven-publish` + signing +} + +group = "org.ortho32" +version = "0.1.0" + +subprojects { + apply(plugin = "java-library") + apply(plugin = "maven-publish") + apply(plugin = "signing") + + group = rootProject.group + version = rootProject.version + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(21)) + } + withSourcesJar() + withJavadocJar() + } + + tasks.withType { + options.encoding = "UTF-8" + options.release.set(21) + } + + tasks.withType { + options.encoding = "UTF-8" + } + + tasks.withType { + isPreserveFileTimestamps = false + isReproducibleFileOrder = true + dirPermissions { unix("0755") } + filePermissions { unix("0644") } + } + + tasks.withType { + manifest { + attributes( + "Implementation-Title" to project.name, + "Implementation-Version" to project.version, + "Implementation-Vendor" to "ORTHO-32" + ) + } + } + + repositories { + mavenCentral() + } + + // JUnit BOM available to every module + dependencies { + "testImplementation"(platform("org.junit:junit-bom:5.11.0")) + "testImplementation"("org.junit.jupiter:junit-jupiter") + "testRuntimeOnly"("org.junit.platform:junit-platform-launcher") + } + + tasks.test { + useJUnitPlatform() + testLogging { events("passed", "skipped", "failed") } + } + + publishing { + publications { + create("maven") { + from(components["java"]) + pom { + name.set("ORTHO-32 ${project.name}") + description.set("ORTHO-32 Java SDK - ${project.name}") + url.set("https://github.com/ortho32/ortho32-sdk-java") + licenses { + license { + name.set("Apache-2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0") + } + } + } + } + } + } + + // Checksum generation for reproducible builds + tasks.register("generateChecksums") { + group = "verification" + description = "Generate SHA-256 checksums for all JAR artifacts" + dependsOn(tasks.named("build")) + doLast { + val digest = MessageDigest.getInstance("SHA-256") + fileTree(layout.buildDirectory.dir("libs")).forEach { file -> + if (file.isFile && file.extension == "jar") { + val hash = digest.digest(file.readBytes()).joinToString("") { "%02x".format(it) } + val checksumFile = file.resolveSibling("${file.name}.sha256") + checksumFile.writeText("$hash ${file.name}\n") + } + } + } + } + tasks.named("build") { finalizedBy("generateChecksums") } + + // Dependency locking + dependencyLocking { + lockAllConfigurations() + } +} diff --git a/examples/agent-client/src/main/java/org/ortho32/examples/AgentClientExample.java b/examples/agent-client/src/main/java/org/ortho32/examples/AgentClientExample.java new file mode 100644 index 0000000000000000000000000000000000000000..1921b502ed09018fe777e22155344f0c2a05eac1 --- /dev/null +++ b/examples/agent-client/src/main/java/org/ortho32/examples/AgentClientExample.java @@ -0,0 +1,63 @@ +package org.ortho32.examples; + +import org.ortho32.OrthoRuntime; +import org.ortho32.OrthoSession; +import org.ortho32.agent.AgentIntent; +import org.ortho32.agent.AgentResult; +import org.ortho32.agent.AgentTask; +import org.ortho32.transport.SimulatorTransport; + +import java.time.Duration; +import java.util.Map; + +/** + * Dispatch an AgentTask via AgentClient, print result. + * Uses SimulatorTransport - no hardware needed. + * Agents interact via typed intents, never directly mutating desktop state. + */ +public class AgentClientExample { + public static void main(String[] args) throws Exception { + SimulatorTransport simulator = new SimulatorTransport(); + try (OrthoRuntime runtime = openRuntime(simulator); + OrthoSession session = runtime.openSession()) { + + AgentIntent intent = new AgentIntent( + "intent-001", + "analyze", + "trace", + Map.of("query", "summarize last TMUL", "format", "json"), + Map.of("maxCycles", 1000) + ); + + AgentTask task = new AgentTask( + "task-001", + intent, + session.context(), + Duration.ofSeconds(10) + ); + + // Via typed AgentClient + AgentResult result = session.agent().dispatch(task).join(); + + System.out.println("taskId: " + result.taskId()); + System.out.println("status: " + result.status()); + System.out.println("cycles: " + result.cycles()); + System.out.println("auditId: " + result.auditId()); + System.out.println("output: " + result.output()); + } + } + + private static OrthoRuntime openRuntime(SimulatorTransport transport) { + for (var m : OrthoRuntime.class.getMethods()) { + if (m.getName().equals("open") && m.getParameterCount() == 1 + && m.getParameterTypes()[0].isAssignableFrom(transport.getClass())) { + try { + return (OrthoRuntime) m.invoke(null, transport); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + return OrthoRuntime.open(); + } +} diff --git a/examples/device-list/src/main/java/org/ortho32/examples/DeviceListExample.java b/examples/device-list/src/main/java/org/ortho32/examples/DeviceListExample.java new file mode 100644 index 0000000000000000000000000000000000000000..b03d0ed6254017b6f2183155ba5b430d5cfeb063 --- /dev/null +++ b/examples/device-list/src/main/java/org/ortho32/examples/DeviceListExample.java @@ -0,0 +1,63 @@ +package org.ortho32.examples; + +import org.ortho32.OrthoRuntime; +import org.ortho32.OrthoSession; +import org.ortho32.device.OrthoDevice; +import org.ortho32.transport.SimulatorTransport; + +/** + * Enumerate and print available devices using SimulatorTransport. + * No hardware needed - SimulatorTransport provides deterministic fake fabric. + */ +public class DeviceListExample { + public static void main(String[] args) throws Exception { + // Explicit SimulatorTransport usage - deterministic offline fabric + SimulatorTransport simulator = new SimulatorTransport(); + try (OrthoRuntime runtime = openRuntime(simulator); + OrthoSession session = runtime.openSession()) { + + System.out.println("SDK version: " + runtime.version()); + System.out.println("Fabric protocol: " + runtime.version().fabricProtocolVersion()); + System.out.println("Device ABI: " + runtime.version().deviceABIVersion()); + + var devices = session.devices().list(); + if (devices.isEmpty()) { + System.out.println("No devices found."); + } else { + System.out.println("Found " + devices.size() + " device(s):"); + for (OrthoDevice d : devices) { + System.out.printf(" id=%s name=%s transport=%s arch=%s fw=%s caps=%s state=%s%n", + d.id(), + d.info().name(), + d.info().transport(), + d.info().architecture(), + d.info().firmwareVersion(), + d.capabilities(), + d.state()); + } + } + // Also demonstrate firstAvailable helper + try { + OrthoDevice first = session.devices().firstAvailable(); + System.out.println("firstAvailable: " + first.id()); + } catch (Exception e) { + System.out.println("firstAvailable: none (" + e.getMessage() + ")"); + } + } + } + + private static OrthoRuntime openRuntime(SimulatorTransport transport) { + // Try OrthoRuntime.open(OrthoTransport) if present, otherwise fallback to open() + for (var m : OrthoRuntime.class.getMethods()) { + if (m.getName().equals("open") && m.getParameterCount() == 1 + && m.getParameterTypes()[0].isAssignableFrom(transport.getClass())) { + try { + return (OrthoRuntime) m.invoke(null, transport); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + return OrthoRuntime.open(); + } +} diff --git a/examples/proof-verify/src/main/java/org/ortho32/examples/ProofVerifyExample.java b/examples/proof-verify/src/main/java/org/ortho32/examples/ProofVerifyExample.java new file mode 100644 index 0000000000000000000000000000000000000000..311b71e9d0630d98333065872280f6893dfb32d9 --- /dev/null +++ b/examples/proof-verify/src/main/java/org/ortho32/examples/ProofVerifyExample.java @@ -0,0 +1,90 @@ +package org.ortho32.examples; + +import org.ortho32.OrthoRuntime; +import org.ortho32.OrthoSession; +import org.ortho32.transport.SimulatorTransport; +import org.ortho32.verify.CrossVerificationResult; +import org.ortho32.verify.TheoremId; +import org.ortho32.verify.TheoremResult; +import org.ortho32.verify.VerificationReport; +import org.ortho32.verify.VerificationStatus; + +import java.util.List; + +/** + * Verify all 12 theorems via VerifyClient, print status table with Lean and HOL columns separate. + * Uses SimulatorTransport - no hardware needed. + */ +public class ProofVerifyExample { + public static void main(String[] args) throws Exception { + SimulatorTransport simulator = new SimulatorTransport(); + try (OrthoRuntime runtime = openRuntime(simulator); + OrthoSession session = runtime.openSession()) { + + VerificationReport report = session.verify().verifyAll().join(); + // Fallback: if verify().verifyAll() not present, try VerifyClient + if (report == null) { + var client = session.verify(); + // attempt via reflection for VerifyClient name + try { + var m = client.getClass().getMethod("verificationReport"); + report = (VerificationReport) m.invoke(client); + } catch (Exception ignore) {} + } + + List results; + if (report != null) { + results = report.theorems(); + System.out.println("Checker: " + report.checkerVersion() + " sourceHash=" + report.sourceHash() + " at " + report.timestamp()); + } else { + // Synthetic demo if offline + results = session.verify().allTheorems(); + } + + System.out.printf("%-12s | %-14s | %-14s | %-10s | sorry | assumptions%n", "Theorem", "LEAN4", "HOL_LIGHT", "Agreement"); + System.out.println("--------------------------------------------------------------------------------"); + + for (TheoremId tid : TheoremId.allTwelve()) { + TheoremResult lean = find(results, tid, "LEAN4"); + TheoremResult hol = find(results, tid, "HOL_LIGHT"); + String leanStatus = lean != null ? lean.status().name() : "UNCHECKED"; + String holStatus = hol != null ? hol.status().name() : "UNCHECKED"; + String agreement = "UNCHECKED"; + if (lean != null && hol != null) { + CrossVerificationResult cross = CrossVerificationResult.of(lean, hol); + agreement = cross.agreement().name(); + } + System.out.printf("%-12s | %-14s | %-14s | %-10s | %-5s | %s%n", + tid.value(), + leanStatus, + holStatus, + agreement, + lean != null ? lean.sorryCount() : "-", + lean != null ? lean.assumptions() : "[]"); + } + System.out.println("\nNote: VERIFIED != TESTED != ASSUMED. Only real checker output sets status."); + } + } + + private static TheoremResult find(List list, TheoremId id, String system) { + if (list == null) return null; + for (var r : list) { + if (r.id().equals(id) && r.system().name().equals(system)) return r; + } + return null; + } + + private static OrthoRuntime openRuntime(SimulatorTransport transport) { + for (var m : OrthoRuntime.class.getMethods()) { + if (m.getName().equals("open") && m.getParameterCount() == 1 + && m.getParameterTypes()[0].isAssignableFrom(transport.getClass())) { + try { + return (OrthoRuntime) m.invoke(null, transport); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + return OrthoRuntime.open(); + } +} diff --git a/examples/tensor-run/src/main/java/org/ortho32/examples/TensorRunExample.java b/examples/tensor-run/src/main/java/org/ortho32/examples/TensorRunExample.java new file mode 100644 index 0000000000000000000000000000000000000000..972bc11c5ddd20adab9215882166a9e4cc99fdc2 --- /dev/null +++ b/examples/tensor-run/src/main/java/org/ortho32/examples/TensorRunExample.java @@ -0,0 +1,67 @@ +package org.ortho32.examples; + +import org.ortho32.OrthoRuntime; +import org.ortho32.OrthoSession; +import org.ortho32.device.OrthoDevice; +import org.ortho32.tensor.Tensor; +import org.ortho32.tensor.TensorJob; +import org.ortho32.tensor.TensorOperation; +import org.ortho32.tensor.TensorResult; +import org.ortho32.tensor.TensorShape; +import org.ortho32.transport.SimulatorTransport; + +/** + * Build TensorJob, submit TMUL, print cycles and traceHash. + * Uses SimulatorTransport - no hardware needed. + */ +public class TensorRunExample { + public static void main(String[] args) throws Exception { + SimulatorTransport simulator = new SimulatorTransport(); + try (OrthoRuntime runtime = openRuntime(simulator); + OrthoSession session = runtime.openSession()) { + + OrthoDevice device = session.devices().firstAvailable(); + System.out.println("Using device: " + device.id()); + + // 4x4 float32 matrices + TensorShape shape = new TensorShape(4, 4); + float[] dataA = new float[16]; + float[] dataB = new float[16]; + for (int i = 0; i < 16; i++) { + dataA[i] = i + 1; + dataB[i] = (i % 4 == 0) ? 1 : 0; // identity-ish + } + Tensor matrixA = Tensor.of(shape, dataA); + Tensor matrixB = Tensor.of(shape, dataB); + + TensorJob job = TensorJob.builder() + .device(device) + .operation(TensorOperation.TMUL) + .inputA(matrixA) + .inputB(matrixB) + .build(); + + TensorResult result = session.tensor().submit(job).join(); + + System.out.println("cycles: " + result.cycles()); + System.out.println("trace: " + result.traceHash()); + System.out.println("completionCycle: " + result.completionCycle()); + System.out.println("scratchpadUsed: " + result.scratchpadUsedBytes()); + System.out.println("output shape: " + result.output().shape()); + } + } + + private static OrthoRuntime openRuntime(SimulatorTransport transport) { + for (var m : OrthoRuntime.class.getMethods()) { + if (m.getName().equals("open") && m.getParameterCount() == 1 + && m.getParameterTypes()[0].isAssignableFrom(transport.getClass())) { + try { + return (OrthoRuntime) m.invoke(null, transport); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + return OrthoRuntime.open(); + } +} diff --git a/examples/trace-replay/src/main/java/org/ortho32/examples/TraceReplayExample.java b/examples/trace-replay/src/main/java/org/ortho32/examples/TraceReplayExample.java new file mode 100644 index 0000000000000000000000000000000000000000..58a4d1499e8eb0c1429aa3bcd4a9d9b42cd207f9 --- /dev/null +++ b/examples/trace-replay/src/main/java/org/ortho32/examples/TraceReplayExample.java @@ -0,0 +1,86 @@ +package org.ortho32.examples; + +import org.ortho32.OrthoRuntime; +import org.ortho32.OrthoSession; +import org.ortho32.device.OrthoDevice; +import org.ortho32.tensor.Tensor; +import org.ortho32.tensor.TensorJob; +import org.ortho32.tensor.TensorOperation; +import org.ortho32.tensor.TensorResult; +import org.ortho32.tensor.TensorShape; +import org.ortho32.trace.ExecutionTrace; +import org.ortho32.trace.TraceComparison; +import org.ortho32.trace.TraceComparator; +import org.ortho32.trace.TraceHash; +import org.ortho32.transport.SimulatorTransport; + +/** + * Capture trace, compute hash, replay, compare hashes (must be equal). + * Demonstrates H=0 determinism: same input -> same TraceHash. + */ +public class TraceReplayExample { + public static void main(String[] args) throws Exception { + SimulatorTransport simulator = new SimulatorTransport(); + try (OrthoRuntime runtime = openRuntime(simulator); + OrthoSession session = runtime.openSession()) { + + OrthoDevice device = session.devices().firstAvailable(); + + TensorShape shape = new TensorShape(4, 4); + float[] data = new float[16]; + for (int i = 0; i < 16; i++) data[i] = i; + Tensor a = Tensor.of(shape, data); + Tensor b = Tensor.of(shape, data); + + TensorJob job = TensorJob.builder() + .device(device) + .operation(TensorOperation.TMUL) + .inputA(a) + .inputB(b) + .build(); + + // First run + TensorResult r1 = session.tensor().submit(job).join(); + ExecutionTrace trace1 = session.trace().capture(device, r1); + if (trace1 == null) { + // Fallback: use trace from result + trace1 = session.trace().getTrace(r1); + } + TraceHash hash1 = TraceHash.of(trace1); + System.out.println("run1 cycles=" + r1.cycles() + " hash=" + hash1 + " traceHash=" + r1.traceHash()); + + // Replay / second run with identical input + TensorResult r2 = session.tensor().submit(job).join(); + ExecutionTrace trace2 = session.trace().capture(device, r2); + if (trace2 == null) trace2 = session.trace().getTrace(r2); + TraceHash hash2 = TraceHash.of(trace2); + System.out.println("run2 cycles=" + r2.cycles() + " hash=" + hash2 + " traceHash=" + r2.traceHash()); + + // Hashes must be equal for deterministic execution + System.out.println("hash1.equals(hash2): " + hash1.equals(hash2)); + System.out.println("r1.traceHash == r2.traceHash: " + r1.traceHash().equals(r2.traceHash())); + + TraceComparison cmp = TraceComparator.compare(trace1, trace2); + System.out.println("TraceComparator.equal: " + cmp.isEqual()); + if (!cmp.isEqual()) { + System.out.println("mismatches: " + cmp.mismatches()); + throw new AssertionError("Determinism violation: traces differ for identical input"); + } + System.out.println("Replay verified: hashes equal and traces identical (H=0)"); + } + } + + private static OrthoRuntime openRuntime(SimulatorTransport transport) { + for (var m : OrthoRuntime.class.getMethods()) { + if (m.getName().equals("open") && m.getParameterCount() == 1 + && m.getParameterTypes()[0].isAssignableFrom(transport.getClass())) { + try { + return (OrthoRuntime) m.invoke(null, transport); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + return OrthoRuntime.open(); + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..b82aa23a4f05d39d81870f8355ca43324f027298 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/integration-tests/build.gradle.kts b/integration-tests/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..00ce39597a3b16f8f40bcbac94b5f741fca0858c --- /dev/null +++ b/integration-tests/build.gradle.kts @@ -0,0 +1,10 @@ +// integration-tests: exercises the full SDK against SimulatorTransport. +// No hardware required. All tests must pass with SimulatorTransport only. +plugins { java } + +dependencies { + // Depend on ortho-core which transitively pulls all domain modules + testImplementation(project(":ortho-core")) + // Transport needed directly for SimulatorTransport tests + testImplementation(project(":ortho-transport")) +} diff --git a/integration-tests/src/test/java/org/ortho32/tests/CrossVerifyTest.java b/integration-tests/src/test/java/org/ortho32/tests/CrossVerifyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7b7de9e7172e2453be43577f7e7c9d640aa29352 --- /dev/null +++ b/integration-tests/src/test/java/org/ortho32/tests/CrossVerifyTest.java @@ -0,0 +1,67 @@ +package org.ortho32.tests; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.ortho32.verify.CrossVerificationResult; +import org.ortho32.verify.CrossVerificationResult.Agreement; +import org.ortho32.verify.TheoremId; +import org.ortho32.verify.TheoremResult; +import org.ortho32.verify.VerificationStatus; +import org.ortho32.verify.VerificationSystem; + +import java.util.List; + +public class CrossVerifyTest { + + @Test + public void mismatchCannotBeTreatedAsVerified() { + TheoremId id = TheoremId.of("THM_007"); + + TheoremResult lean = new TheoremResult( + id, + VerificationSystem.LEAN4, + VerificationStatus.VERIFIED, + List.of(), + "a".repeat(64), + 0 + ); + TheoremResult hol = new TheoremResult( + id, + VerificationSystem.HOL_LIGHT, + VerificationStatus.FAILED, + List.of(), + "a".repeat(64), + 0 + ); + + CrossVerificationResult cross = CrossVerificationResult.of(lean, hol); + + assertEquals(Agreement.MISMATCH, cross.agreement(), + "LEAN4=VERIFIED HOL_LIGHT=FAILED must yield MISMATCH"); + + // MISMATCH must never be treated as verified - any helper that maps MISMATCH to verified must fail + assertFalse(cross.isVerified(), "MISMATCH must not be considered verified"); + assertFalse(cross.agreement() == Agreement.AGREE, "MISMATCH != AGREE"); + + // Assertion failure in any code that treats MISMATCH as VERIFIED + assertThrows(AssertionError.class, () -> { + if (cross.agreement() == Agreement.MISMATCH) { + // Simulate buggy code that would treat MISMATCH as verified + boolean treatAsVerified = cross.isVerified(); + if (treatAsVerified) { + return; // would be wrong + } + // Enforce: treating MISMATCH as verified must throw + if (cross.agreement() != Agreement.AGREE) { + throw new AssertionError("Blocking failure: MISMATCH cannot be auto-resolved to VERIFIED"); + } + } + }, "MISMATCH must cause assertion failure when treated as VERIFIED"); + + // Also verify via status helper - VERIFIED.isFormallyVerified but cross is not + assertTrue(lean.status().isFormallyVerified()); + assertFalse(hol.status().isFormallyVerified()); + assertNotEquals(lean.status(), hol.status()); + } +} diff --git a/integration-tests/src/test/java/org/ortho32/tests/DeterminismTest.java b/integration-tests/src/test/java/org/ortho32/tests/DeterminismTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8ad3b6a2a5f432dc6c3001711d2e69a85b6ef227 --- /dev/null +++ b/integration-tests/src/test/java/org/ortho32/tests/DeterminismTest.java @@ -0,0 +1,59 @@ +package org.ortho32.tests; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.ortho32.OrthoRuntime; +import org.ortho32.OrthoSession; +import org.ortho32.device.OrthoDevice; +import org.ortho32.tensor.Tensor; +import org.ortho32.tensor.TensorJob; +import org.ortho32.tensor.TensorOperation; +import org.ortho32.tensor.TensorResult; +import org.ortho32.tensor.TensorShape; +import org.ortho32.transport.SimulatorTransport; + +public class DeterminismTest { + + @Test + public void identicalJobsProduceEqualTraceHash_H0() throws Exception { + SimulatorTransport transport = new SimulatorTransport(); + try (OrthoRuntime runtime = openRuntime(transport); + OrthoSession session = runtime.openSession()) { + + OrthoDevice device = session.devices().firstAvailable(); + TensorShape shape = new TensorShape(4, 4); + float[] data = new float[16]; + for (int i = 0; i < 16; i++) data[i] = i * 1.5f; + Tensor a = Tensor.of(shape, data); + Tensor b = Tensor.of(shape, data); + + TensorJob job = TensorJob.builder() + .device(device) + .operation(TensorOperation.TMUL) + .inputA(a) + .inputB(b) + .build(); + + TensorResult result = session.tensor().submit(job).join(); + TensorResult result2 = session.tensor().submit(job).join(); + + assertNotNull(result.traceHash()); + assertNotNull(result2.traceHash()); + // H=0 determinism: same input -> same TraceHash across N runs at Java layer + assertEquals(result.traceHash(), result2.traceHash(), + "Determinism violation: identical TensorJob must produce equal traceHash (H=0)"); + assertEquals(result.cycles(), result2.cycles(), "cycles must be deterministic"); + } + } + + private static OrthoRuntime openRuntime(SimulatorTransport transport) { + for (var m : OrthoRuntime.class.getMethods()) { + if (m.getName().equals("open") && m.getParameterCount()==1 + && m.getParameterTypes()[0].isAssignableFrom(transport.getClass())) { + try { return (OrthoRuntime) m.invoke(null, transport); } catch (Exception e) { throw new RuntimeException(e); } + } + } + return OrthoRuntime.open(); + } +} diff --git a/integration-tests/src/test/java/org/ortho32/tests/JavaRoutingTests.java b/integration-tests/src/test/java/org/ortho32/tests/JavaRoutingTests.java new file mode 100644 index 0000000000000000000000000000000000000000..3b44ff8338c3c85892195f9740610144a47e51ac --- /dev/null +++ b/integration-tests/src/test/java/org/ortho32/tests/JavaRoutingTests.java @@ -0,0 +1,187 @@ +package org.ortho32.integration; + +import org.junit.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Instant; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.ortho32.core.OrthoSession; +import org.ortho32.core.ORTHOEventBus; +import org.ortho32.fabric.ORTHOBridge; +import org.ortho32.fabric.SimulatorTransport; +import org.ortho32.routing.*; + +/** + * Java SDK routing tests – all run with SimulatorTransport. No hardware needed. + */ +class JavaRoutingTests { + + private OrthoSession session; + private OrthoRouter router; + private OrthoRouteContext adminContext; + + @BeforeEach + void setUp() { + // All tests use SimulatorTransport + session = OrthoSession.withSimulator("sess-test-" + System.nanoTime()); + router = session.router(); + adminContext = new OrthoRouteContext("test-user", null, session.sessionId(), "admin", Instant.now()); + assertNotNull(router, "Router must be obtained via OrthoSession.router()"); + } + + @AfterEach + void tearDown() throws Exception { + session.close().get(1, TimeUnit.SECONDS); + } + + // ---- test_route_parse_roundtrip --------------------------------------- + + @Test + @DisplayName("test_route_parse_roundtrip: parse -> toUrl -> parse produces equal routes") + void test_route_parse_roundtrip() { + String original = "ortho://proof/rtl_deterministic"; + OrthoRoute route = OrthoRoute.parse(original); + assertNotNull(route); + assertInstanceOf(OrthoRoute.ProofRoute.class, route); + + String serialized = route.toUrl(); + assertEquals(original, serialized, "toUrl must be canonical"); + + OrthoRoute reparsed = OrthoRoute.parse(serialized); + assertEquals(route, reparsed, "parse -> toUrl -> parse must produce equal routes"); + assertEquals(reparsed.toUrl(), original); + + // Additional round-trips + String[] urls = { + "ortho://app/terminal", + "ortho://trace/cycle/420", + "ortho://hardware/device/ortho0?op=tensor", + "ortho://ide/file/rtl/fabric/arbiter.sv?line=184", + "ortho://settings/security", + "https://github.com/ortho/ortho32" + }; + for (String url : urls) { + OrthoRoute r = OrthoRoute.parse(url); + assertEquals(url, r.toUrl(), "round-trip failed for " + url); + assertEquals(r, OrthoRoute.parse(r.toUrl())); + } + } + + // ---- test_agent_uses_intent_route ------------------------------------- + + @Test + @DisplayName("test_agent_uses_intent_route: agent dispatches IntentRoute not direct state mutation") + void test_agent_uses_intent_route() throws Exception { + // Simulate agent – must use OrthoRoute.IntentRoute, never raw string or direct state + OrthoRouteContext agentCtx = OrthoRouteContext.forAgent("agent-verify-1", session.sessionId(), "proofExecute"); + + // Agent creates a typed IntentRoute – not a raw String + OrthoRoute.IntentRoute intent = new OrthoRoute.IntentRoute("proof.verify", "rtl_deterministic", Map.of("mode","check")); + assertNotNull(intent.intent()); + assertNotNull(intent.target()); + assertEquals("proof.verify", intent.intent()); + + // Dispatch via router – this is the ONLY allowed path + OrthoRouteResult result = router.perform(intent, agentCtx).get(2, TimeUnit.SECONDS); + + assertInstanceOf(OrthoRouteResult.Success.class, result); + OrthoRouteResult.Success success = (OrthoRouteResult.Success) result; + assertNotNull(success.output()); + assertTrue(success.output().toString().contains("proof.verify")); + + // Verify we did not mutate state directly – router path was IntentRoute + // The following would not compile if raw string routing were used: + // router.perform("proof.verify", ...) // <-- no such method, typed route required + assertInstanceOf(OrthoRoute.IntentRoute.class, intent); + + // Ensure no direct State mutation API exists on the agent – compile-time guarantee + // If agent tried: agent.state.set(...), it would not exist; only router.perform is available + // We verify the intent round-trips via URL as well (same language as Swift) + String url = intent.toUrl(); + assertTrue(url.startsWith("ortho://intent/")); + OrthoRoute reparsed = OrthoRoute.parse(url); + assertInstanceOf(OrthoRoute.IntentRoute.class, reparsed); + assertEquals(intent, reparsed); + } + + // ---- test_hardware_route_is_typed ------------------------------------- + + @Test + @DisplayName("test_hardware_route_is_typed: HardwareRoute fields are typed not raw strings") + void test_hardware_route_is_typed() throws Exception { + // HardwareRoute must have typed fields device: String, operation: String, params: Map + OrthoRoute.HardwareRoute hw = new OrthoRoute.HardwareRoute("ortho0", "tensor", Map.of("dtype","fp8")); + assertEquals("ortho0", hw.device()); + assertEquals("tensor", hw.operation()); + assertEquals("fp8", hw.params().get("dtype")); + + // toUrl is structured, not ad-hoc string concatenation + String url = hw.toUrl(); + assertTrue(url.startsWith("ortho://hardware/device/ortho0")); + assertTrue(url.contains("op=tensor")); + + OrthoRoute parsed = OrthoRoute.parse(url); + assertInstanceOf(OrthoRoute.HardwareRoute.class, parsed); + OrthoRoute.HardwareRoute reparsed = (OrthoRoute.HardwareRoute) parsed; + assertEquals(hw.device(), reparsed.device()); + assertEquals(hw.operation(), reparsed.operation()); + + // Perform via router – goes through HardwareRouter -> ORTHOBridge (SimulatorTransport) – not raw PCIe + OrthoRouteContext hwCtx = new OrthoRouteContext("hw-test", null, session.sessionId(), "hardwareAccess", Instant.now()); + OrthoRouteResult result = router.perform(hw, hwCtx).get(2, TimeUnit.SECONDS); + // SimulatorTransport returns success + assertInstanceOf(OrthoRouteResult.Success.class, result); + } + + // ---- test_route_result_sealed ----------------------------------------- + + @Test + @DisplayName("test_route_result_sealed: RouteResult is sealed – compiler forces handling all cases") + void test_route_result_sealed() throws Exception { + OrthoRoute route = OrthoRoute.parse("ortho://app/terminal"); + OrthoRouteResult result = router.open(route, adminContext).get(2, TimeUnit.SECONDS); + + // Exhaustive switch – compiler enforces all sealed permits; this must compile with --enable-preview + String handled = switch (result) { + case OrthoRouteResult.Success s -> "success:" + s.windowId(); + case OrthoRouteResult.Failure f -> "failure:" + f.error().name(); + case OrthoRouteResult.Deferred d -> "deferred:" + d.id(); + }; + assertNotNull(handled); + assertTrue(handled.startsWith("success:")); + + // Failure case also exercises sealed handling + OrthoRouteContext noCap = new OrthoRouteContext("user", null, session.sessionId(), "readOnly", Instant.now()); + OrthoRoute.HardwareRoute hw = new OrthoRoute.HardwareRoute("ortho0", "tensor", Map.of()); + OrthoRouteResult denied = router.perform(hw, noCap).get(2, TimeUnit.SECONDS); + String handledDenied = switch (denied) { + case OrthoRouteResult.Success s -> "success"; + case OrthoRouteResult.Failure f -> { + assertEquals(OrthoRouteError.CAPABILITY_DENIED, f.error()); + yield "failure:" + f.error(); + } + case OrthoRouteResult.Deferred d -> "deferred"; + }; + assertEquals("failure:CAPABILITY_DENIED", handledDenied); + + // Verify router is sealed result – no raw string status + assertTrue(OrthoRouteResult.class.isSealed(), "OrthoRouteResult must be sealed"); + assertEquals(3, OrthoRouteResult.class.getPermittedSubclasses().length); + } + + @Test + @DisplayName("router obtained only via OrthoSession.router() – constructor not public") + void test_router_not_directly_constructed() { + // Verify OrthoRouter has no public constructor – only OrthoSession can create it + var ctors = OrthoRouter.class.getDeclaredConstructors(); + for (var c : ctors) { + assertFalse(java.lang.reflect.Modifier.isPublic(c.getModifiers()), "OrthoRouter must not have public constructor – use OrthoSession.router()"); + } + // The only way to get a router is via session + OrthoRouter r1 = session.router(); + OrthoRouter r2 = session.router(); + assertSame(r1, r2, "Same session must return same router instance"); + } +} diff --git a/integration-tests/src/test/java/org/ortho32/tests/SessionLifecycleTest.java b/integration-tests/src/test/java/org/ortho32/tests/SessionLifecycleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..eaef0cc70e9788a6ed3ef97a4212d7864f0da28c --- /dev/null +++ b/integration-tests/src/test/java/org/ortho32/tests/SessionLifecycleTest.java @@ -0,0 +1,65 @@ +package org.ortho32.tests; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.ortho32.OrthoRuntime; +import org.ortho32.OrthoSession; +import org.ortho32.transport.SimulatorTransport; + +public class SessionLifecycleTest { + + @Test + public void sessionLifecycleUsesAllSubclientsAndCleansUp() throws Exception { + SimulatorTransport transport = new SimulatorTransport(); + OrthoRuntime runtime = openRuntime(transport); + assertNotNull(runtime); + + OrthoSession session = runtime.openSession(); + assertNotNull(session); + assertTrue(session instanceof AutoCloseable, "OrthoSession must be AutoCloseable"); + + // Use all sub-clients as per OrthoSession API + assertNotNull(session.devices(), "devices() must be present"); + assertNotNull(session.tensor(), "tensor() must be present"); + assertNotNull(session.trace(), "trace() must be present"); + assertNotNull(session.verify(), "verify() must be present"); + assertNotNull(session.security(), "security() must be present"); + assertNotNull(session.agent(), "agent() must be present"); + + // Exercise devices + var devices = session.devices().list(); + assertNotNull(devices); + + // Context and version + assertNotNull(session.context(), "context must be present"); + assertNotNull(runtime.version(), "version must be present"); + + // AutoCloseable cleanup via close() + session.close(); + runtime.close(); + + // Verify closed resources throw or are marked closed + // After close, opening a new session on closed runtime should fail or new runtime works + try (OrthoRuntime r2 = openRuntime(new SimulatorTransport()); + OrthoSession s2 = r2.openSession()) { + assertNotNull(s2.devices()); + } + + // Try-with-resources path + try (OrthoRuntime r3 = openRuntime(new SimulatorTransport()); + OrthoSession s3 = r3.openSession()) { + assertDoesNotThrow(() -> s3.devices().list()); + } + } + + private static OrthoRuntime openRuntime(SimulatorTransport transport) { + for (var m : OrthoRuntime.class.getMethods()) { + if (m.getName().equals("open") && m.getParameterCount()==1 + && m.getParameterTypes()[0].isAssignableFrom(transport.getClass())) { + try { return (OrthoRuntime) m.invoke(null, transport); } catch (Exception e) { throw new RuntimeException(e); } + } + } + return OrthoRuntime.open(); + } +} diff --git a/integration-tests/src/test/java/org/ortho32/tests/SimulatorTransportTest.java b/integration-tests/src/test/java/org/ortho32/tests/SimulatorTransportTest.java new file mode 100644 index 0000000000000000000000000000000000000000..be906b1686c27595d22a28fee242384026c181e3 --- /dev/null +++ b/integration-tests/src/test/java/org/ortho32/tests/SimulatorTransportTest.java @@ -0,0 +1,49 @@ +package org.ortho32.tests; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.ortho32.fabric.FabricAddress; +import org.ortho32.fabric.FabricCommand; +import org.ortho32.fabric.FabricCompletion; +import org.ortho32.fabric.FabricOpcode; +import org.ortho32.transport.SimulatorTransport; + +import java.util.concurrent.TimeUnit; + +public class SimulatorTransportTest { + + @Test + public void connectAndSubmitFabricCommandVerifiesArchitecturalCycles() throws Exception { + SimulatorTransport transport = new SimulatorTransport(); + transport.connect(); + + long sequence = 42L; + FabricCommand cmd = FabricCommand.builder() + .opcode(FabricOpcode.TENSOR) + .device("sim0") + .address(new FabricAddress(0x1000L)) + .length(64) + .epoch(0) + .slot(0) + .sequence(sequence) + .payloadHash("a".repeat(64)) + .flags(0) + .build(); + + // SimulatorTransport is deterministic fake fabric - no hardware + FabricCompletion completion = transport.submit(cmd).get(5, TimeUnit.SECONDS); + + assertNotNull(completion, "completion must not be null"); + assertEquals(sequence, completion.sequence(), "completion sequence must echo command sequence"); + assertNotNull(completion.status(), "status must be present"); + // Cycles are architectural integers, not wall-clock + assertTrue(completion.acceptedCycle() >= 0, "acceptedCycle must be >=0 architectural cycle"); + assertTrue(completion.completionCycle() >= completion.acceptedCycle(), + "completionCycle must be >= acceptedCycle (architectural)"); + assertNotNull(completion.resultHash(), "resultHash must be present"); + assertNotNull(completion.traceRoot(), "traceRoot must be present"); + + transport.disconnect(); + } +} diff --git a/integration-tests/src/test/java/org/ortho32/tests/TensorJobTest.java b/integration-tests/src/test/java/org/ortho32/tests/TensorJobTest.java new file mode 100644 index 0000000000000000000000000000000000000000..06988b54f35ef132ad595a2e0ea7e9cbf9a0f7f8 --- /dev/null +++ b/integration-tests/src/test/java/org/ortho32/tests/TensorJobTest.java @@ -0,0 +1,64 @@ +package org.ortho32.tests; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.ortho32.OrthoRuntime; +import org.ortho32.OrthoSession; +import org.ortho32.device.OrthoDevice; +import org.ortho32.tensor.Tensor; +import org.ortho32.tensor.TensorJob; +import org.ortho32.tensor.TensorLatency; +import org.ortho32.tensor.TensorOperation; +import org.ortho32.tensor.TensorResult; +import org.ortho32.tensor.TensorShape; +import org.ortho32.transport.SimulatorTransport; + +public class TensorJobTest { + + @Test + public void submitTMUL4x4VerifiesLatencyAndTraceHash() throws Exception { + SimulatorTransport transport = new SimulatorTransport(); + try (OrthoRuntime runtime = openRuntime(transport); + OrthoSession session = runtime.openSession()) { + + OrthoDevice device = session.devices().firstAvailable(); + assertNotNull(device, "firstAvailable device must exist on simulator"); + + TensorShape shape = new TensorShape(4, 4); + float[] a = new float[16]; + float[] b = new float[16]; + for (int i = 0; i < 16; i++) { a[i] = i; b[i] = i; } + + Tensor tensorA = Tensor.of(shape, a); + Tensor tensorB = Tensor.of(shape, b); + + TensorJob job = TensorJob.builder() + .device(device) + .operation(TensorOperation.TMUL) + .inputA(tensorA) + .inputB(tensorB) + .build(); + + TensorResult result = session.tensor().submit(job).join(); + + // TensorLatency.TMUL_CYCLES is architectural constant 4L, never wall-clock + assertEquals(TensorLatency.TMUL_CYCLES, result.cycles(), + "TMUL must take exactly TMUL_CYCLES architectural cycles"); + assertEquals(4L, result.cycles(), "TMUL_CYCLES must be 4"); + assertNotNull(result.traceHash(), "traceHash must be non-null"); + assertNotNull(result.output(), "output must be present"); + assertTrue(result.completionCycle() >= 0, "completionCycle architectural"); + } + } + + private static OrthoRuntime openRuntime(SimulatorTransport transport) { + for (var m : OrthoRuntime.class.getMethods()) { + if (m.getName().equals("open") && m.getParameterCount()==1 + && m.getParameterTypes()[0].isAssignableFrom(transport.getClass())) { + try { return (OrthoRuntime) m.invoke(null, transport); } catch (Exception e) { throw new RuntimeException(e); } + } + } + return OrthoRuntime.open(); + } +} diff --git a/integration-tests/src/test/java/org/ortho32/tests/TraceComparatorTest.java b/integration-tests/src/test/java/org/ortho32/tests/TraceComparatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ca601f4f8dd8b4a59821099938b04692fcd6eab7 --- /dev/null +++ b/integration-tests/src/test/java/org/ortho32/tests/TraceComparatorTest.java @@ -0,0 +1,62 @@ +package org.ortho32.tests; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.ortho32.trace.CycleRecord; +import org.ortho32.trace.ExecutionTrace; +import org.ortho32.trace.TraceComparison; +import org.ortho32.trace.TraceComparator; + +import java.util.ArrayList; +import java.util.List; + +public class TraceComparatorTest { + + @Test + public void identicalTracesAreEqualAndMutatedAreNot() { + ExecutionTrace t1 = sampleTrace(); + ExecutionTrace t2 = sampleTrace(); + + TraceComparison eq = TraceComparator.compare(t1, t2); + assertTrue(eq.isEqual(), "identical traces must be equal"); + assertTrue(eq.mismatches().isEmpty(), "no mismatches for identical traces"); + + // Mutate one cycle + ExecutionTrace mutated = mutate(t1); + TraceComparison neq = TraceComparator.compare(t1, mutated); + assertFalse(neq.isEqual(), "mutated trace must not be equal"); + assertFalse(neq.mismatches().isEmpty(), "mismatches must be reported"); + } + + private ExecutionTrace sampleTrace() { + List cycles = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + cycles.add(new CycleRecord( + i, + "EX", + "TMUL", + "COMMITTED", + List.of(new CycleRecord.RegisterWrite("r" + i, "0x" + i)), + List.of() + )); + } + return new ExecutionTrace(cycles); + } + + private ExecutionTrace mutate(ExecutionTrace original) { + List cycles = new ArrayList<>(original.cycles()); + // flip first cycle's commitStatus + CycleRecord first = cycles.get(0); + CycleRecord mutated = new CycleRecord( + first.cycleNumber(), + first.scalarStage(), + first.tensorStage(), + "FLUSHED", + first.registerWrites(), + first.memoryEvents() + ); + cycles.set(0, mutated); + return new ExecutionTrace(cycles); + } +} diff --git a/integration-tests/src/test/java/org/ortho32/tests/TransportAgnosticTest.java b/integration-tests/src/test/java/org/ortho32/tests/TransportAgnosticTest.java new file mode 100644 index 0000000000000000000000000000000000000000..59acce5e89834c76e04226ab857922ae2dcdf00b --- /dev/null +++ b/integration-tests/src/test/java/org/ortho32/tests/TransportAgnosticTest.java @@ -0,0 +1,60 @@ +package org.ortho32.tests; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.ortho32.OrthoRuntime; +import org.ortho32.OrthoSession; +import org.ortho32.device.OrthoDevice; +import org.ortho32.tensor.Tensor; +import org.ortho32.tensor.TensorJob; +import org.ortho32.tensor.TensorLatency; +import org.ortho32.tensor.TensorOperation; +import org.ortho32.tensor.TensorResult; +import org.ortho32.tensor.TensorShape; + +/** + * This test must import ZERO transport-specific classes. + * It verifies the same OrthoSession API works with SimulatorTransport + * without any transport-specific import in the test class. + */ +public class TransportAgnosticTest { + + @Test + public void sameOrthoSessionApiWorksWithoutTransportImport() throws Exception { + // No SimulatorTransport import - OrthoRuntime.open() defaults to simulator in test env + try (OrthoRuntime runtime = OrthoRuntime.open(); + OrthoSession session = runtime.openSession()) { + + assertNotNull(runtime.version(), "version must be available"); + assertNotNull(session.devices()); + assertNotNull(session.tensor()); + assertNotNull(session.trace()); + assertNotNull(session.verify()); + assertNotNull(session.agent()); + assertNotNull(session.security()); + + OrthoDevice device = session.devices().firstAvailable(); + assertNotNull(device, "device must be available via agnostic API"); + + TensorShape shape = new TensorShape(4, 4); + float[] data = new float[16]; + for (int i = 0; i < 16; i++) data[i] = i + 1; + Tensor a = Tensor.of(shape, data); + Tensor b = Tensor.of(shape, data); + + TensorJob job = TensorJob.builder() + .device(device) + .operation(TensorOperation.TMUL) + .inputA(a) + .inputB(b) + .build(); + + TensorResult result = session.tensor().submit(job).join(); + assertNotNull(result); + assertEquals(TensorLatency.TMUL_CYCLES, result.cycles(), + "Agnostic API must still return architectural TMUL cycles"); + assertNotNull(result.traceHash()); + } + } +} diff --git a/integration-tests/src/test/java/org/ortho32/tests/VerificationStatusTest.java b/integration-tests/src/test/java/org/ortho32/tests/VerificationStatusTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5553074672eeb3b791bc4b94fe58def41e1f4b6e --- /dev/null +++ b/integration-tests/src/test/java/org/ortho32/tests/VerificationStatusTest.java @@ -0,0 +1,30 @@ +package org.ortho32.tests; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.ortho32.verify.VerificationStatus; + +public class VerificationStatusTest { + + @Test + public void verifiedIsFormallyVerifiedAndTestedIsNotAndNeverInterchangeable() { + assertTrue(VerificationStatus.VERIFIED.isFormallyVerified(), + "VERIFIED must be formally verified"); + assertFalse(VerificationStatus.TESTED.isFormallyVerified(), + "TESTED must NOT be formally verified"); + + assertNotEquals(VerificationStatus.VERIFIED, VerificationStatus.TESTED, + "VERIFIED != TESTED - never interchangeable"); + + // Additional invariants + assertTrue(VerificationStatus.CROSS_VERIFIED.isFormallyVerified(), + "CROSS_VERIFIED must be formally verified"); + assertFalse(VerificationStatus.ASSUMED.isFormallyVerified(), + "ASSUMED must not be formally verified"); + assertFalse(VerificationStatus.FAILED.isFormallyVerified(), + "FAILED must not be formally verified"); + assertFalse(VerificationStatus.UNVERIFIED.isFormallyVerified(), + "UNVERIFIED must not be formally verified"); + } +} diff --git a/ortho-agent/build.gradle.kts b/ortho-agent/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..23a47d8a58764984851407a344bbc9183dda6bf1 --- /dev/null +++ b/ortho-agent/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { `java-library` } + +dependencies { + api(project(":ortho-fabric")) + api(project(":ortho-device")) + api(project(":ortho-security")) +} diff --git a/ortho-agent/src/main/java/org/ortho32/agent/AgentCapability.java b/ortho-agent/src/main/java/org/ortho32/agent/AgentCapability.java new file mode 100644 index 0000000000000000000000000000000000000000..c92ab7b9ac86e60cbac3f469adb5dc23a40c5998 --- /dev/null +++ b/ortho-agent/src/main/java/org/ortho32/agent/AgentCapability.java @@ -0,0 +1,33 @@ +package org.ortho32.agent; + +import java.util.Objects; + +/** + * Capability advertised by an agent. + * + * @param id stable capability identifier, must not be null + * @param name human-readable name, must not be null + * @param description description, must not be null + * @param resourceLimit resource limit for this capability, must be >=0 + */ +public record AgentCapability( + String id, + String name, + String description, + long resourceLimit) { + + /** + * Compact canonical constructor. + * + * @param id capability id + * @param name name + * @param description description + * @param resourceLimit resource limit + */ + public AgentCapability { + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(name, "name"); + Objects.requireNonNull(description, "description"); + if (resourceLimit < 0) throw new IllegalArgumentException("resourceLimit must be >=0"); + } +} diff --git a/ortho-agent/src/main/java/org/ortho32/agent/AgentClient.java b/ortho-agent/src/main/java/org/ortho32/agent/AgentClient.java new file mode 100644 index 0000000000000000000000000000000000000000..735d9ec32647a322e6b5362b0a83b5065580fe31 --- /dev/null +++ b/ortho-agent/src/main/java/org/ortho32/agent/AgentClient.java @@ -0,0 +1,26 @@ +package org.ortho32.agent; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * Session sub-client for agent dispatch. + */ +public interface AgentClient { + + /** + * Dispatches a task to an agent. + * + * @param task task to dispatch, must not be null + * @return future completing with agent result containing architectural cycles + */ + CompletableFuture dispatch(AgentTask task); + + /** + * Lists intents supported by the agent. + * + * @return unmodifiable list of intents, never null + */ + List intents(); +} diff --git a/ortho-agent/src/main/java/org/ortho32/agent/AgentIntent.java b/ortho-agent/src/main/java/org/ortho32/agent/AgentIntent.java new file mode 100644 index 0000000000000000000000000000000000000000..76280973416d30f7913f146603b68425eccd9127 --- /dev/null +++ b/ortho-agent/src/main/java/org/ortho32/agent/AgentIntent.java @@ -0,0 +1,40 @@ +package org.ortho32.agent; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Typed intent describing an agent action. + * + * @param id stable intent identifier, must not be null + * @param verb action verb, must not be null + * @param noun target noun, must not be null + * @param parameters parameter map, never null, unmodifiable + * @param requiredCapabilities required capabilities for execution, never null, unmodifiable + */ +public record AgentIntent( + String id, + String verb, + String noun, + Map parameters, + List requiredCapabilities) { + + /** + * Compact canonical constructor. + * + * @param id intent id + * @param verb verb + * @param noun noun + * @param parameters parameters + * @param requiredCapabilities required capabilities + */ + public AgentIntent { + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(verb, "verb"); + Objects.requireNonNull(noun, "noun"); + Objects.requireNonNull(requiredCapabilities, "requiredCapabilities"); + parameters = parameters == null ? Map.of() : Map.copyOf(parameters); + requiredCapabilities = List.copyOf(requiredCapabilities); + } +} diff --git a/ortho-agent/src/main/java/org/ortho32/agent/AgentResult.java b/ortho-agent/src/main/java/org/ortho32/agent/AgentResult.java new file mode 100644 index 0000000000000000000000000000000000000000..3ed1ffb40cbeefef8995a19068217887fdd8d3f7 --- /dev/null +++ b/ortho-agent/src/main/java/org/ortho32/agent/AgentResult.java @@ -0,0 +1,36 @@ +package org.ortho32.agent; + +import java.util.Objects; + +/** + * Result of an agent task. + * + * @param taskId originating task id, must not be null + * @param status status string, must not be null + * @param output output payload, may be null + * @param cycles architectural cycles consumed, never wall-clock + * @param auditId audit identifier for traceability, must not be null + */ +public record AgentResult( + String taskId, + String status, + String output, + long cycles, + String auditId) { + + /** + * Compact canonical constructor. + * + * @param taskId task id + * @param status status + * @param output output + * @param cycles architectural cycles + * @param auditId audit id + */ + public AgentResult { + Objects.requireNonNull(taskId, "taskId"); + Objects.requireNonNull(status, "status"); + Objects.requireNonNull(auditId, "auditId"); + if (cycles < 0) throw new IllegalArgumentException("cycles must be >=0"); + } +} diff --git a/ortho-agent/src/main/java/org/ortho32/agent/AgentTask.java b/ortho-agent/src/main/java/org/ortho32/agent/AgentTask.java new file mode 100644 index 0000000000000000000000000000000000000000..a33165a93c3542e02a23684eed9530ebb6cb1a4b --- /dev/null +++ b/ortho-agent/src/main/java/org/ortho32/agent/AgentTask.java @@ -0,0 +1,32 @@ +package org.ortho32.agent; + +import java.util.Objects; + +/** + * Task submitted to an agent. + * + * @param id task identifier, must not be null + * @param intent intent to execute, must not be null + * @param context context string for execution, may be null + * @param timeout timeout in milliseconds, must be >=0 + */ +public record AgentTask( + String id, + AgentIntent intent, + String context, + long timeout) { + + /** + * Compact canonical constructor. + * + * @param id task id + * @param intent intent + * @param context context + * @param timeout timeout + */ + public AgentTask { + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(intent, "intent"); + if (timeout < 0) throw new IllegalArgumentException("timeout must be >=0"); + } +} diff --git a/ortho-compute/build.gradle.kts b/ortho-compute/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..b46407a8e3b6832fff2263a536d90d82e74d2a09 --- /dev/null +++ b/ortho-compute/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + `java-library` +} + +dependencies { + api(project(":ortho-device")) + api(project(":ortho-fabric")) +} + +java { + toolchain { languageVersion.set(JavaLanguageVersion.of(21)) } +} diff --git a/ortho-compute/src/main/java/org/ortho32/compute/ComputeBackend.java b/ortho-compute/src/main/java/org/ortho32/compute/ComputeBackend.java new file mode 100644 index 0000000000000000000000000000000000000000..6fd821f27563c68194f9250fa5c54f0bea8a6406 --- /dev/null +++ b/ortho-compute/src/main/java/org/ortho32/compute/ComputeBackend.java @@ -0,0 +1,7 @@ +package org.ortho32.compute; + +public enum ComputeBackend { + ORTHO, + CUDA_REFERENCE, + SIMULATOR +} diff --git a/ortho-compute/src/main/java/org/ortho32/compute/ComputeJob.java b/ortho-compute/src/main/java/org/ortho32/compute/ComputeJob.java new file mode 100644 index 0000000000000000000000000000000000000000..39e94e6072ed4ee72ffc78d3dce7be4700668f34 --- /dev/null +++ b/ortho-compute/src/main/java/org/ortho32/compute/ComputeJob.java @@ -0,0 +1,42 @@ +package org.ortho32.compute; + +import org.ortho32.device.OrthoDeviceId; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Immutable compute job descriptor. + * + * @param device target device id + * @param backend execution backend + * @param options immutable options map + * @param payload opaque payload (defensive copy) + */ +public record ComputeJob( + OrthoDeviceId device, + ComputeBackend backend, + Map options, + byte[] payload +) { + public ComputeJob { + Objects.requireNonNull(device); + Objects.requireNonNull(backend); + if (options == null) options = Map.of(); + options = Collections.unmodifiableMap(new HashMap<>(options)); + if (payload == null) payload = new byte[0]; + payload = Arrays.copyOf(payload, payload.length); + } + + @Override + public Map options() { + return Collections.unmodifiableMap(options); + } + + @Override + public byte[] payload() { + return Arrays.copyOf(payload, payload.length); + } +} diff --git a/ortho-compute/src/main/java/org/ortho32/compute/ComputeResult.java b/ortho-compute/src/main/java/org/ortho32/compute/ComputeResult.java new file mode 100644 index 0000000000000000000000000000000000000000..0f01cc0d7ab6b9beb7cea8fb309d743ffa4e5d8e --- /dev/null +++ b/ortho-compute/src/main/java/org/ortho32/compute/ComputeResult.java @@ -0,0 +1,31 @@ +package org.ortho32.compute; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Immutable compute result with architectural cycle accounting. + * + * @param status execution status + * @param output output bytes (defensive copy) + * @param cycles architectural cycles (long, never wall-clock) + * @param traceHash trace hash (SHA-256 hex) + */ +public record ComputeResult( + String status, + byte[] output, + long cycles, + String traceHash +) { + public ComputeResult { + Objects.requireNonNull(status); + Objects.requireNonNull(traceHash); + if (output == null) output = new byte[0]; + output = Arrays.copyOf(output, output.length); + } + + @Override + public byte[] output() { + return Arrays.copyOf(output, output.length); + } +} diff --git a/ortho-core/build.gradle.kts b/ortho-core/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..f44efd22d1623f659851960b91c43b4f88535f1d --- /dev/null +++ b/ortho-core/build.gradle.kts @@ -0,0 +1,15 @@ +// ortho-core: top-level aggregator. OrthoRuntime/OrthoSession compose all sub-clients. +plugins { `java-library` } + +dependencies { + api(project(":ortho-fabric")) + api(project(":ortho-device")) + api(project(":ortho-transport")) + api(project(":ortho-compute")) + api(project(":ortho-tensor")) + api(project(":ortho-trace")) + api(project(":ortho-verify")) + api(project(":ortho-security")) + api(project(":ortho-agent")) + api(project(":ortho-routing")) +} diff --git a/ortho-core/src/main/java/org/ortho32/OrthoContext.java b/ortho-core/src/main/java/org/ortho32/OrthoContext.java new file mode 100644 index 0000000000000000000000000000000000000000..0ce3eb3a6004442a3508d8ec45128027d6f4e37e --- /dev/null +++ b/ortho-core/src/main/java/org/ortho32/OrthoContext.java @@ -0,0 +1,24 @@ +package org.ortho32; + +import java.util.Set; + +/** + * Session context: immutable snapshot of session identity, capabilities and activity. + * + * @param identity stable session identifier + * @param capabilities granted capabilities + * @param active whether session is active + * @param requestCount number of requests issued in this session + */ +public record OrthoContext( + String identity, + Set capabilities, + boolean active, + long requestCount +) { + public OrthoContext { + if (identity == null) throw new IllegalArgumentException("identity is null"); + if (capabilities == null) throw new IllegalArgumentException("capabilities is null"); + capabilities = Set.copyOf(capabilities); + } +} diff --git a/ortho-core/src/main/java/org/ortho32/OrthoResult.java b/ortho-core/src/main/java/org/ortho32/OrthoResult.java new file mode 100644 index 0000000000000000000000000000000000000000..04deffc1eb2bcce0900bf4484642def99fd2d273 --- /dev/null +++ b/ortho-core/src/main/java/org/ortho32/OrthoResult.java @@ -0,0 +1,31 @@ +package org.ortho32; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Typed result with architectural cycle accounting. + * + * @param status result status + * @param payload opaque payload bytes (defensive copy) + * @param cycles architectural cycles (long, never wall-clock) + * @param traceHash SHA-256 of canonical trace or empty + */ +public record OrthoResult( + String status, + byte[] payload, + long cycles, + String traceHash +) { + public OrthoResult { + Objects.requireNonNull(status, "status"); + Objects.requireNonNull(traceHash, "traceHash"); + if (payload == null) payload = new byte[0]; + payload = Arrays.copyOf(payload, payload.length); + } + + @Override + public byte[] payload() { + return Arrays.copyOf(payload, payload.length); + } +} diff --git a/ortho-core/src/main/java/org/ortho32/OrthoRuntime.java b/ortho-core/src/main/java/org/ortho32/OrthoRuntime.java new file mode 100644 index 0000000000000000000000000000000000000000..73db295ea27046876057990e2e168f89a6dae0d1 --- /dev/null +++ b/ortho-core/src/main/java/org/ortho32/OrthoRuntime.java @@ -0,0 +1,59 @@ +package org.ortho32; + +import org.ortho32.transport.SimulatorTransport; +import org.ortho32.transport.TransportFactory; +import org.ortho32.transport.OrthoTransport; + +/** + * Entry point for ORTHO-32 SDK. + * Applications obtain a runtime via {@link #open()} and then open sessions. + */ +public final class OrthoRuntime implements AutoCloseable { + + private final OrthoTransport transport; + private volatile boolean closed; + + private OrthoRuntime(OrthoTransport transport) { + this.transport = transport; + this.transport.connect(); + } + + /** + * Open a runtime backed by SimulatorTransport (deterministic, no hardware). + */ + public static OrthoRuntime open() { + return new OrthoRuntime(TransportFactory.simulator()); + } + + /** + * Open with an explicit transport (internal use). Public surface uses {@link #open()}. + */ + static OrthoRuntime open(OrthoTransport transport) { + return new OrthoRuntime(transport); + } + + public OrthoSession openSession() { + if (closed) { + throw new IllegalStateException("OrthoRuntime is closed"); + } + OrthoContext context = new OrthoContext( + "ortho32-session-" + System.nanoTime(), + java.util.Set.of("tensor", "trace", "verify"), + true, + 0L + ); + return new OrthoSession(context, transport); + } + + public OrthoVersion version() { + return new OrthoVersion("0.1.0", "1.0.0", "32-1.0"); + } + + @Override + public void close() { + if (!closed) { + closed = true; + transport.disconnect(); + } + } +} diff --git a/ortho-core/src/main/java/org/ortho32/OrthoSession.java b/ortho-core/src/main/java/org/ortho32/OrthoSession.java new file mode 100644 index 0000000000000000000000000000000000000000..39772a6a3bef884991a2a4adf8ab3fbbd258c5cb --- /dev/null +++ b/ortho-core/src/main/java/org/ortho32/OrthoSession.java @@ -0,0 +1,86 @@ +package org.ortho32; + +import org.ortho32.device.DeviceClient; +import org.ortho32.transport.OrthoTransport; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Session is the main interaction scope. AutoCloseable. Provides sub-clients. + */ +public final class OrthoSession implements AutoCloseable { + + private final OrthoContext context; + private final OrthoTransport transport; + private final AtomicLong requestCount = new AtomicLong(0); + private final DeviceClient devices; + + // Stub sub-clients for modules not yet wired in this slice + public interface TensorClient { + java.util.concurrent.CompletableFuture submit(org.ortho32.compute.ComputeJob job); + } + public interface TraceClient {} + public interface VerifyClient {} + public interface AgentClient {} + public interface SecurityClient {} + + private final TensorClient tensorClient; + private final TraceClient traceClient; + private final VerifyClient verifyClient; + private final AgentClient agentClient; + private final SecurityClient securityClient; + + OrthoSession(OrthoContext context, OrthoTransport transport) { + this.context = Objects.requireNonNull(context); + this.transport = Objects.requireNonNull(transport); + this.devices = new DeviceClient(transport); + this.tensorClient = job -> java.util.concurrent.CompletableFuture.failedFuture( + new UnsupportedOperationException("tensor not wired in core slice")); + this.traceClient = new TraceClient() {}; + this.verifyClient = new VerifyClient() {}; + this.agentClient = new AgentClient() {}; + this.securityClient = new SecurityClient() {}; + } + + public OrthoContext context() { + return new OrthoContext( + context.identity(), + context.capabilities(), + context.active(), + requestCount.get() + ); + } + + long nextRequestId() { + return requestCount.incrementAndGet(); + } + + public DeviceClient devices() { + return devices; + } + + public TensorClient tensor() { + return tensorClient; + } + + public TraceClient trace() { + return traceClient; + } + + public VerifyClient verify() { + return verifyClient; + } + + public AgentClient agent() { + return agentClient; + } + + public SecurityClient security() { + return securityClient; + } + + @Override + public void close() { + // no-op for simulator; disconnect is owned by runtime + } +} diff --git a/ortho-core/src/main/java/org/ortho32/OrthoVersion.java b/ortho-core/src/main/java/org/ortho32/OrthoVersion.java new file mode 100644 index 0000000000000000000000000000000000000000..c4ab5329e3f5e4002a7ec2ec027431b7976a55a2 --- /dev/null +++ b/ortho-core/src/main/java/org/ortho32/OrthoVersion.java @@ -0,0 +1,20 @@ +package org.ortho32; + +/** + * Compatibility tuple for SDK + Fabric Protocol + Device ABI. + * + * @param sdkVersion SDK semantic version + * @param fabricProtocolVersion fabric protocol version + * @param deviceABIVersion device ABI version + */ +public record OrthoVersion( + String sdkVersion, + String fabricProtocolVersion, + String deviceABIVersion +) { + public OrthoVersion { + if (sdkVersion == null || fabricProtocolVersion == null || deviceABIVersion == null) { + throw new IllegalArgumentException("version components must not be null"); + } + } +} diff --git a/ortho-device/build.gradle.kts b/ortho-device/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..c8624141e61c7b50affdeced1a279e0aec601ac8 --- /dev/null +++ b/ortho-device/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + `java-library` +} + +dependencies { + api(project(":ortho-fabric")) +} + +java { + toolchain { languageVersion.set(JavaLanguageVersion.of(21)) } +} diff --git a/ortho-device/src/main/java/org/ortho32/device/DeviceClient.java b/ortho-device/src/main/java/org/ortho32/device/DeviceClient.java new file mode 100644 index 0000000000000000000000000000000000000000..65691173d8f8c13bbec4a2ea887a7f7b04b48ad5 --- /dev/null +++ b/ortho-device/src/main/java/org/ortho32/device/DeviceClient.java @@ -0,0 +1,57 @@ +package org.ortho32.device; + +import org.ortho32.transport.OrthoTransport; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * Session sub-client for device enumeration. Immutable, thread-safe. + */ +public final class DeviceClient { + + private final List devices; + + public DeviceClient(OrthoTransport transport) { + Objects.requireNonNull(transport); + // Deterministic simulator device set. In real transports this would query host service. + OrthoDevice sim = new SimulatedDevice(); + this.devices = List.of(sim); + } + + // Test constructor + DeviceClient(List devices) { + this.devices = List.copyOf(Objects.requireNonNull(devices)); + } + + public OrthoDevice firstAvailable() { + return devices.stream() + .filter(d -> d.state() != OrthoDeviceState.ERROR) + .findFirst() + .orElseThrow(() -> new java.util.NoSuchElementException("No available ORTHO device")); + } + + public List list() { + return devices; + } + + public Optional find(OrthoDeviceId id) { + Objects.requireNonNull(id); + return devices.stream().filter(d -> d.id().equals(id)).findFirst(); + } + + private static final class SimulatedDevice implements OrthoDevice { + private final OrthoDeviceId id = new OrthoDeviceId("sim-0001"); + private final OrthoDeviceInfo info = new OrthoDeviceInfo("ORTHO-32 Simulator", "simulator", "ortho-32", "0.1.0-sim"); + private final OrthoDeviceCapabilities caps = new OrthoDeviceCapabilities(true, true, true, 1 << 20); + private volatile OrthoDeviceState state = OrthoDeviceState.CONNECTED; + + @Override public OrthoDeviceId id() { return id; } + @Override public OrthoDeviceInfo info() { return info; } + @Override public OrthoDeviceCapabilities capabilities() { return caps; } + @Override public OrthoDeviceState state() { return state; } + @Override public void connect() { state = OrthoDeviceState.CONNECTED; } + @Override public void disconnect() { state = OrthoDeviceState.DISCONNECTED; } + @Override public void reset() { state = OrthoDeviceState.CONNECTED; } + } +} diff --git a/ortho-device/src/main/java/org/ortho32/device/OrthoDevice.java b/ortho-device/src/main/java/org/ortho32/device/OrthoDevice.java new file mode 100644 index 0000000000000000000000000000000000000000..a815e6ace25b337fe3945acbbcc078ff7ed3422a --- /dev/null +++ b/ortho-device/src/main/java/org/ortho32/device/OrthoDevice.java @@ -0,0 +1,21 @@ +package org.ortho32.device; + +/** + * Public device handle. Applications interact via this interface, never via transport internals. + */ +public interface OrthoDevice { + + OrthoDeviceId id(); + + OrthoDeviceInfo info(); + + OrthoDeviceCapabilities capabilities(); + + OrthoDeviceState state(); + + void connect(); + + void disconnect(); + + void reset(); +} diff --git a/ortho-device/src/main/java/org/ortho32/device/OrthoDeviceCapabilities.java b/ortho-device/src/main/java/org/ortho32/device/OrthoDeviceCapabilities.java new file mode 100644 index 0000000000000000000000000000000000000000..717a8884f565bdfd8c1af5baa8502f0401fcb703 --- /dev/null +++ b/ortho-device/src/main/java/org/ortho32/device/OrthoDeviceCapabilities.java @@ -0,0 +1,14 @@ +package org.ortho32.device; + +/** + * @param hasTensor whether tensor unit is present + * @param hasTrace whether trace buffer is present + * @param hasVerify whether verification attest is supported + * @param maxScratchpadBytes max tensor scratchpad in bytes + */ +public record OrthoDeviceCapabilities( + boolean hasTensor, + boolean hasTrace, + boolean hasVerify, + long maxScratchpadBytes +) {} diff --git a/ortho-device/src/main/java/org/ortho32/device/OrthoDeviceId.java b/ortho-device/src/main/java/org/ortho32/device/OrthoDeviceId.java new file mode 100644 index 0000000000000000000000000000000000000000..c0925826d691a941e826d3f78adb9ebf29f13b72 --- /dev/null +++ b/ortho-device/src/main/java/org/ortho32/device/OrthoDeviceId.java @@ -0,0 +1,21 @@ +package org.ortho32.device; + +import java.util.Objects; + +/** + * Stable device identifier. + * + * @param value stable identifier string + */ +public record OrthoDeviceId(String value) { + + public OrthoDeviceId { + Objects.requireNonNull(value, "value"); + if (value.isBlank()) throw new IllegalArgumentException("OrthoDeviceId value must not be blank"); + } + + @Override + public String toString() { + return value; + } +} diff --git a/ortho-device/src/main/java/org/ortho32/device/OrthoDeviceInfo.java b/ortho-device/src/main/java/org/ortho32/device/OrthoDeviceInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..a5061f87e2a953a86eadb1c2f2a8b603b50d204e --- /dev/null +++ b/ortho-device/src/main/java/org/ortho32/device/OrthoDeviceInfo.java @@ -0,0 +1,23 @@ +package org.ortho32.device; + +import java.util.Objects; + +/** + * @param name human readable name + * @param transport transport type (simulator, pcie, usb, ethernet, native) + * @param architecture architecture identifier (e.g., ortho-32) + * @param firmwareVersion firmware version string + */ +public record OrthoDeviceInfo( + String name, + String transport, + String architecture, + String firmwareVersion +) { + public OrthoDeviceInfo { + Objects.requireNonNull(name); + Objects.requireNonNull(transport); + Objects.requireNonNull(architecture); + Objects.requireNonNull(firmwareVersion); + } +} diff --git a/ortho-device/src/main/java/org/ortho32/device/OrthoDeviceState.java b/ortho-device/src/main/java/org/ortho32/device/OrthoDeviceState.java new file mode 100644 index 0000000000000000000000000000000000000000..e420dd7a784c9111b73a2b68189de3037b1f18b1 --- /dev/null +++ b/ortho-device/src/main/java/org/ortho32/device/OrthoDeviceState.java @@ -0,0 +1,7 @@ +package org.ortho32.device; + +public enum OrthoDeviceState { + CONNECTED, + DISCONNECTED, + ERROR +} diff --git a/ortho-fabric/build.gradle.kts b/ortho-fabric/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..5ccea63f25bba5bbc70001eecb65f0ca568abc3a --- /dev/null +++ b/ortho-fabric/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + `java-library` +} + +java { + toolchain { languageVersion.set(JavaLanguageVersion.of(21)) } +} diff --git a/ortho-fabric/src/main/java/org/ortho32/fabric/FabricAddress.java b/ortho-fabric/src/main/java/org/ortho32/fabric/FabricAddress.java new file mode 100644 index 0000000000000000000000000000000000000000..c6af5f90821c84b67708629bb0643d3d719dad3d --- /dev/null +++ b/ortho-fabric/src/main/java/org/ortho32/fabric/FabricAddress.java @@ -0,0 +1,26 @@ +package org.ortho32.fabric; + +/** + * Typed address in ORTHO-32 memory map. + * + * @param value 32-bit address value (stored in long to avoid signed issues) + */ +public record FabricAddress(long value) { + + public static final FabricAddress SCALAR_RAM = new FabricAddress(0x00000000L); + public static final FabricAddress TENSOR_SCRATCHPAD = new FabricAddress(0x10000000L); + public static final FabricAddress MMIO = new FabricAddress(0x20000000L); + public static final FabricAddress TRACE_BUFFER = new FabricAddress(0x30000000L); + public static final FabricAddress HOST_APERTURE = new FabricAddress(0x40000000L); + + public FabricAddress { + if (value < 0 || value > 0xFFFFFFFFL) { + throw new IllegalArgumentException("FabricAddress out of 32-bit range: " + Long.toHexString(value)); + } + } + + @Override + public String toString() { + return String.format("0x%08X", value); + } +} diff --git a/ortho-fabric/src/main/java/org/ortho32/fabric/FabricCommand.java b/ortho-fabric/src/main/java/org/ortho32/fabric/FabricCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..280983c077a3273c59e22926c25204afdd264821 --- /dev/null +++ b/ortho-fabric/src/main/java/org/ortho32/fabric/FabricCommand.java @@ -0,0 +1,40 @@ +package org.ortho32.fabric; + +import org.ortho32.device.OrthoDeviceId; +import java.util.Objects; + +/** + * Logical fabric command. Applications never write PCIe BAR registers directly. + * + * @param opcode operation + * @param device target device + * @param address typed ORTHO address + * @param length length in bytes + * @param epoch epoch for deterministic arbitration + * @param slot slot in static grant table + * @param sequence monotonic sequence number + * @param payloadHash SHA-256 hex of payload + * @param flags opaque flags + */ +public record FabricCommand( + FabricOpcode opcode, + OrthoDeviceId device, + FabricAddress address, + int length, + FabricEpoch epoch, + FabricSlot slot, + long sequence, + String payloadHash, + int flags +) { + public FabricCommand { + Objects.requireNonNull(opcode); + Objects.requireNonNull(device); + Objects.requireNonNull(address); + Objects.requireNonNull(epoch); + Objects.requireNonNull(slot); + Objects.requireNonNull(payloadHash); + if (length < 0) throw new IllegalArgumentException("length < 0"); + if (sequence < 0) throw new IllegalArgumentException("sequence < 0"); + } +} diff --git a/ortho-fabric/src/main/java/org/ortho32/fabric/FabricCompletion.java b/ortho-fabric/src/main/java/org/ortho32/fabric/FabricCompletion.java new file mode 100644 index 0000000000000000000000000000000000000000..05ee02c3192779a162890bbf9ba4375c5defdcaa --- /dev/null +++ b/ortho-fabric/src/main/java/org/ortho32/fabric/FabricCompletion.java @@ -0,0 +1,38 @@ +package org.ortho32.fabric; + +import java.util.Objects; + +/** + * Fabric completion with architectural cycle accounting. + * cycles = completionCycle - acceptedCycle. Both are ORTHO architectural cycles, NOT wall-clock milliseconds. + * + * @param sequence echoed sequence + * @param status 0=ok non-zero=error + * @param acceptedCycle cycle when fabric accepted the command (long) + * @param completionCycle cycle when execution completed (long) + * @param resultHash SHA-256 hex of result + * @param traceRoot Merkle root / trace hash + */ +public record FabricCompletion( + long sequence, + int status, + long acceptedCycle, + long completionCycle, + String resultHash, + String traceRoot +) { + public FabricCompletion { + Objects.requireNonNull(resultHash); + Objects.requireNonNull(traceRoot); + if (completionCycle < acceptedCycle) { + throw new IllegalArgumentException("completionCycle < acceptedCycle"); + } + } + + /** + * Architectural cycles consumed. Derived, not wall-clock. + */ + public long cycles() { + return completionCycle - acceptedCycle; + } +} diff --git a/ortho-fabric/src/main/java/org/ortho32/fabric/FabricEpoch.java b/ortho-fabric/src/main/java/org/ortho32/fabric/FabricEpoch.java new file mode 100644 index 0000000000000000000000000000000000000000..b39aa49b889c0036d722472a4679a5e301ad9df6 --- /dev/null +++ b/ortho-fabric/src/main/java/org/ortho32/fabric/FabricEpoch.java @@ -0,0 +1,12 @@ +package org.ortho32.fabric; + +/** + * Epoch counter for deterministic arbitration. + * + * @param value epoch value + */ +public record FabricEpoch(long value) { + public FabricEpoch { + if (value < 0) throw new IllegalArgumentException("epoch < 0"); + } +} diff --git a/ortho-fabric/src/main/java/org/ortho32/fabric/FabricOpcode.java b/ortho-fabric/src/main/java/org/ortho32/fabric/FabricOpcode.java new file mode 100644 index 0000000000000000000000000000000000000000..c1255023f910e9d973193c53763fd4660201feec --- /dev/null +++ b/ortho-fabric/src/main/java/org/ortho32/fabric/FabricOpcode.java @@ -0,0 +1,10 @@ +package org.ortho32.fabric; + +public enum FabricOpcode { + EXECUTE, + STEP, + TENSOR, + PROBE, + RESET, + ATTEST +} diff --git a/ortho-fabric/src/main/java/org/ortho32/fabric/FabricSlot.java b/ortho-fabric/src/main/java/org/ortho32/fabric/FabricSlot.java new file mode 100644 index 0000000000000000000000000000000000000000..0501d78b0a6e61a313996f2351e638b069d2796e --- /dev/null +++ b/ortho-fabric/src/main/java/org/ortho32/fabric/FabricSlot.java @@ -0,0 +1,12 @@ +package org.ortho32.fabric; + +/** + * Slot in static grant table. + * + * @param value slot index + */ +public record FabricSlot(int value) { + public FabricSlot { + if (value < 0) throw new IllegalArgumentException("slot < 0"); + } +} diff --git a/ortho-routing/build.gradle.kts b/ortho-routing/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..3f7861711e7bb706bb51c11f395ba43874853a21 --- /dev/null +++ b/ortho-routing/build.gradle.kts @@ -0,0 +1,9 @@ +// ortho-routing: depends on leaf modules only. ortho-core depends on this. +plugins { `java-library` } + +dependencies { + api(project(":ortho-fabric")) + api(project(":ortho-device")) + api(project(":ortho-security")) + api(project(":ortho-agent")) +} diff --git a/ortho-routing/src/main/java/org/ortho32/routing/OrthoRoute.java b/ortho-routing/src/main/java/org/ortho32/routing/OrthoRoute.java new file mode 100644 index 0000000000000000000000000000000000000000..fc3853315a8ab7dc8b97daa54e6777b9c7dc16c2 --- /dev/null +++ b/ortho-routing/src/main/java/org/ortho32/routing/OrthoRoute.java @@ -0,0 +1,262 @@ +package org.ortho32.routing; + +import java.net.URI; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Sealed routing interface – Swift and Java speak the same platform routing language. + * Immutable, record-based implementations. No raw String routing. + * + * URL scheme: ortho:// for system-internal routes, https:// for browser. + */ +public sealed interface OrthoRoute + permits OrthoRoute.AppRoute, + OrthoRoute.FileRoute, + OrthoRoute.UrlRoute, + OrthoRoute.IntentRoute, + OrthoRoute.ServiceRoute, + OrthoRoute.HardwareRoute, + OrthoRoute.ProofRoute, + OrthoRoute.TraceRoute, + OrthoRoute.SettingsRoute, + OrthoRoute.WorkspaceRoute, + OrthoRoute.AgentRoute { + + String toUrl(); + + // ---- Static parser ------------------------------------------------------- + + static OrthoRoute parse(String urlString) { + Objects.requireNonNull(urlString, "urlString"); + if (urlString.startsWith("https://") || urlString.startsWith("http://")) { + return new UrlRoute(urlString); + } + if (!urlString.startsWith("ortho://")) { + throw new IllegalArgumentException("Unknown scheme: " + urlString); + } + // Strip ortho:// + String rest = urlString.substring("ortho://".length()); + String pathPart; + String queryPart = null; + int q = rest.indexOf('?'); + if (q >= 0) { + pathPart = rest.substring(0, q); + queryPart = rest.substring(q + 1); + } else { + pathPart = rest; + } + Map query = parseQuery(queryPart); + String[] seg = pathPart.split("/", -1); + + if (seg.length == 0) throw new IllegalArgumentException("Empty ortho URL: " + urlString); + + return switch (seg[0]) { + case "app" -> { + // ortho://app/terminal or ortho://app/files + if (seg.length < 2) throw new IllegalArgumentException("Missing app id: " + urlString); + String appId = "org.ortho." + seg[1]; + // allow full id form as well + if (seg[1].contains(".")) appId = seg[1]; + yield new AppRoute(appId, query); + } + case "ide" -> { + // ortho://ide/file/rtl/fabric/arbiter.sv?line=184 + if (seg.length >= 2 && "file".equals(seg[1])) { + String path = "/" + String.join("/", java.util.Arrays.copyOfRange(seg, 2, seg.length)); + // path already starts with "/", handle double slash + if (path.equals("/")) path = "/"; + Integer line = query.containsKey("line") ? Integer.parseInt(query.get("line")) : null; + yield new FileRoute(path, line); + } + throw new IllegalArgumentException("Unknown ide route: " + urlString); + } + case "file" -> { + // alternative form ortho://file/foo/bar.lean + String path = "/" + String.join("/", java.util.Arrays.copyOfRange(seg, 1, seg.length)); + Integer line = query.containsKey("line") ? Integer.parseInt(query.get("line")) : null; + yield new FileRoute(path, line); + } + case "proof" -> { + if (seg.length < 2) throw new IllegalArgumentException("Missing proof id: " + urlString); + yield new ProofRoute(seg[1], query); + } + case "trace" -> { + // ortho://trace/cycle/420 + if (seg.length >= 3 && "cycle".equals(seg[1])) { + yield new TraceRoute(Integer.parseInt(seg[2])); + } + throw new IllegalArgumentException("Unknown trace route: " + urlString); + } + case "settings" -> { + String section = seg.length >= 2 ? seg[1] : ""; + yield new SettingsRoute(section, query); + } + case "hardware" -> { + // ortho://hardware/device/ortho0?op=tensor + if (seg.length >= 3 && "device".equals(seg[1])) { + String device = seg[2]; + String op = query.getOrDefault("op", "tensor"); + yield new HardwareRoute(device, op, query); + } + throw new IllegalArgumentException("Unknown hardware route: " + urlString); + } + case "workspace" -> { + String name = seg.length >= 2 ? seg[1] : "default"; + yield new WorkspaceRoute(name); + } + case "agent" -> { + String agentId = seg.length >= 2 ? seg[1] : ""; + yield new AgentRoute(agentId, query); + } + case "service" -> { + String service = seg.length >= 2 ? seg[1] : ""; + yield new ServiceRoute(service, query); + } + case "intent" -> { + // ortho://intent/proof.verify?target=rtl_deterministic + String intent = seg.length >= 2 ? seg[1] : ""; + String target = query.getOrDefault("target", ""); + yield new IntentRoute(intent, target, query); + } + default -> throw new IllegalArgumentException("Unknown ortho route domain: " + seg[0] + " in " + urlString); + }; + } + + private static Map parseQuery(String q) { + if (q == null || q.isEmpty()) return Collections.emptyMap(); + Map m = new LinkedHashMap<>(); + for (String pair : q.split("&")) { + int eq = pair.indexOf('='); + if (eq >= 0) { + String k = URLDecoder.decode(pair.substring(0, eq), StandardCharsets.UTF_8); + String v = URLDecoder.decode(pair.substring(eq + 1), StandardCharsets.UTF_8); + m.put(k, v); + } else { + m.put(URLDecoder.decode(pair, StandardCharsets.UTF_8), ""); + } + } + return Collections.unmodifiableMap(m); + } + + static String encodeQuery(Map q) { + if (q == null || q.isEmpty()) return ""; + StringBuilder sb = new StringBuilder("?"); + boolean first = true; + for (var e : q.entrySet()) { + if (!first) sb.append("&"); + sb.append(URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8)); + sb.append("="); + sb.append(URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8)); + first = false; + } + return sb.toString(); + } + + // ---- Record implementations --------------------------------------------- + + record AppRoute(String appId, Map params) implements OrthoRoute { + public AppRoute { + Objects.requireNonNull(appId); + params = params == null ? Map.of() : Map.copyOf(params); + } + @Override public String toUrl() { + String name = appId.startsWith("org.ortho.") ? appId.substring("org.ortho.".length()) : appId; + return "ortho://app/" + name + encodeQuery(params); + } + } + + record FileRoute(String path, Integer line) implements OrthoRoute { + public FileRoute { + Objects.requireNonNull(path); + } + @Override public String toUrl() { + // canonical: ortho://ide/file{path}?line=n + String base = "ortho://ide/file" + (path.startsWith("/") ? path : "/" + path); + if (line != null) base += "?line=" + line; + return base; + } + } + + record UrlRoute(String url) implements OrthoRoute { + public UrlRoute { Objects.requireNonNull(url); URI.create(url); } + @Override public String toUrl() { return url; } + } + + record IntentRoute(String intent, String target, Map args) implements OrthoRoute { + public IntentRoute { + Objects.requireNonNull(intent); + Objects.requireNonNull(target); + args = args == null ? Map.of() : Map.copyOf(args); + } + @Override public String toUrl() { + // canonical: ortho://intent/{intent}?target={target}&... + Map q = new LinkedHashMap<>(args); + q.put("target", target); + // keep target first for determinism + Map ordered = new LinkedHashMap<>(); + ordered.put("target", target); + for (var e : args.entrySet()) ordered.put(e.getKey(), e.getValue()); + return "ortho://intent/" + URLEncoder.encode(intent, StandardCharsets.UTF_8) + encodeQuery(ordered); + } + } + + record ServiceRoute(String serviceId, Map params) implements OrthoRoute { + public ServiceRoute { + Objects.requireNonNull(serviceId); + params = params == null ? Map.of() : Map.copyOf(params); + } + @Override public String toUrl() { return "ortho://service/" + serviceId + encodeQuery(params); } + } + + record HardwareRoute(String device, String operation, Map params) implements OrthoRoute { + public HardwareRoute { + Objects.requireNonNull(device); + Objects.requireNonNull(operation); + params = params == null ? Map.of() : Map.copyOf(params); + } + @Override public String toUrl() { + Map q = new LinkedHashMap<>(params); + q.put("op", operation); + return "ortho://hardware/device/" + URLEncoder.encode(device, StandardCharsets.UTF_8) + encodeQuery(q); + } + } + + record ProofRoute(String proofId, Map params) implements OrthoRoute { + public ProofRoute { + Objects.requireNonNull(proofId); + params = params == null ? Map.of() : Map.copyOf(params); + } + @Override public String toUrl() { return "ortho://proof/" + URLEncoder.encode(proofId, StandardCharsets.UTF_8) + encodeQuery(params); } + } + + record TraceRoute(int cycle) implements OrthoRoute { + @Override public String toUrl() { return "ortho://trace/cycle/" + cycle; } + } + + record SettingsRoute(String section, Map params) implements OrthoRoute { + public SettingsRoute { + Objects.requireNonNull(section); + params = params == null ? Map.of() : Map.copyOf(params); + } + @Override public String toUrl() { return "ortho://settings/" + URLEncoder.encode(section, StandardCharsets.UTF_8) + encodeQuery(params); } + } + + record WorkspaceRoute(String workspaceId) implements OrthoRoute { + public WorkspaceRoute { Objects.requireNonNull(workspaceId); } + @Override public String toUrl() { return "ortho://workspace/" + URLEncoder.encode(workspaceId, StandardCharsets.UTF_8); } + } + + record AgentRoute(String agentId, Map params) implements OrthoRoute { + public AgentRoute { + Objects.requireNonNull(agentId); + params = params == null ? Map.of() : Map.copyOf(params); + } + @Override public String toUrl() { return "ortho://agent/" + URLEncoder.encode(agentId, StandardCharsets.UTF_8) + encodeQuery(params); } + } +} diff --git a/ortho-routing/src/main/java/org/ortho32/routing/OrthoRouteContext.java b/ortho-routing/src/main/java/org/ortho32/routing/OrthoRouteContext.java new file mode 100644 index 0000000000000000000000000000000000000000..03dfc2da563a799747c85018adb1d16abf143a0b --- /dev/null +++ b/ortho-routing/src/main/java/org/ortho32/routing/OrthoRouteContext.java @@ -0,0 +1,35 @@ +package org.ortho32.routing; + +import java.time.Instant; +import java.util.Objects; + +/** + * Immutable routing context. Every route carries sender identity and capability. + */ +public record OrthoRouteContext( + String sender, + String agentId, + String sessionId, + String capability, + Instant timestamp +) { + public OrthoRouteContext { + Objects.requireNonNull(sender, "sender"); + Objects.requireNonNull(sessionId, "sessionId"); + Objects.requireNonNull(capability, "capability"); + Objects.requireNonNull(timestamp, "timestamp"); + // agentId may be null for human sender + } + + public static OrthoRouteContext testContext(String capability) { + return new OrthoRouteContext("test", null, "sess-test", capability, Instant.now()); + } + + public static OrthoRouteContext forAgent(String agentId, String sessionId, String capability) { + return new OrthoRouteContext(agentId, agentId, sessionId, capability, Instant.now()); + } + + public static OrthoRouteContext forUser(String sender, String sessionId, String capability) { + return new OrthoRouteContext(sender, null, sessionId, capability, Instant.now()); + } +} diff --git a/ortho-routing/src/main/java/org/ortho32/routing/OrthoRouteError.java b/ortho-routing/src/main/java/org/ortho32/routing/OrthoRouteError.java new file mode 100644 index 0000000000000000000000000000000000000000..d9c845626d980849e95cfa9b98e7daf2c7132161 --- /dev/null +++ b/ortho-routing/src/main/java/org/ortho32/routing/OrthoRouteError.java @@ -0,0 +1,10 @@ +package org.ortho32.routing; + +public enum OrthoRouteError { + NOT_FOUND, + UNAUTHORIZED, + APP_LAUNCH_FAILED, + SERVICE_ERROR, + HARDWARE_ERROR, + CAPABILITY_DENIED +} diff --git a/ortho-routing/src/main/java/org/ortho32/routing/OrthoRouteResult.java b/ortho-routing/src/main/java/org/ortho32/routing/OrthoRouteResult.java new file mode 100644 index 0000000000000000000000000000000000000000..51c3f6f96dd8ad9b7a3cd988d69eb787cb7ce5c5 --- /dev/null +++ b/ortho-routing/src/main/java/org/ortho32/routing/OrthoRouteResult.java @@ -0,0 +1,22 @@ +package org.ortho32.routing; + +/** + * Sealed result – compiler forces exhaustive handling. + */ +public sealed interface OrthoRouteResult + permits OrthoRouteResult.Success, OrthoRouteResult.Failure, OrthoRouteResult.Deferred { + + record Success(String windowId, Object output) implements OrthoRouteResult {} + + record Failure(OrthoRouteError error) implements OrthoRouteResult { + public Failure { + if (error == null) throw new NullPointerException("error"); + } + } + + record Deferred(String id) implements OrthoRouteResult { + public Deferred { + if (id == null || id.isEmpty()) throw new IllegalArgumentException("id required"); + } + } +} diff --git a/ortho-routing/src/main/java/org/ortho32/routing/OrthoRouter.java b/ortho-routing/src/main/java/org/ortho32/routing/OrthoRouter.java new file mode 100644 index 0000000000000000000000000000000000000000..fd688cb06870f83f64072de537620c16202c7991 --- /dev/null +++ b/ortho-routing/src/main/java/org/ortho32/routing/OrthoRouter.java @@ -0,0 +1,158 @@ +package org.ortho32.routing; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import org.ortho32.fabric.ORTHOBridge; +import org.ortho32.core.ORTHOEventBus; + +/** + * Central router – four planes: UI / Intent / Service / Fabric. + * Instance obtained ONLY via OrthoSession.router(). Never constructed directly. + */ +public final class OrthoRouter { + + private final ORTHOBridge bridge; + private final ORTHOEventBus eventBus; + private final OrthoRouteContext defaultContext; + + // Package-private; only OrthoSession may create. + OrthoRouter(ORTHOBridge bridge, ORTHOEventBus eventBus, OrthoRouteContext defaultContext) { + this.bridge = Objects.requireNonNull(bridge); + this.eventBus = Objects.requireNonNull(eventBus); + this.defaultContext = defaultContext; + } + + private OrthoRouter() { + throw new UnsupportedOperationException("Use OrthoSession.router() – direct construction is forbidden"); + } + + /** + * Open a route that may present UI (app, file, url, workspace, etc.). + */ + public CompletableFuture open(OrthoRoute route, OrthoRouteContext context) { + Objects.requireNonNull(route); + OrthoRouteContext ctx = context != null ? context : defaultContext; + eventBus.publish(ORTHOEventBus.EventType.RouteStarted, route.toUrl(), ctx); + + // Capability check + if (!hasCapability(ctx, route)) { + OrthoRouteResult failure = new OrthoRouteResult.Failure(OrthoRouteError.CAPABILITY_DENIED); + eventBus.publish(ORTHOEventBus.EventType.RouteCompleted, route.toUrl(), failure); + return CompletableFuture.completedFuture(failure); + } + + CompletableFuture future = dispatchOpen(route, ctx); + return future.whenComplete((r, ex) -> eventBus.publish(ORTHOEventBus.EventType.RouteCompleted, route.toUrl(), r)); + } + + /** + * Perform an intent/service/hardware operation without necessarily opening UI. + */ + public CompletableFuture perform(OrthoRoute route, OrthoRouteContext context) { + Objects.requireNonNull(route); + OrthoRouteContext ctx = context != null ? context : defaultContext; + eventBus.publish(ORTHOEventBus.EventType.RouteStarted, route.toUrl(), ctx); + + if (!hasCapability(ctx, route)) { + OrthoRouteResult failure = new OrthoRouteResult.Failure(OrthoRouteError.CAPABILITY_DENIED); + eventBus.publish(ORTHOEventBus.EventType.RouteCompleted, route.toUrl(), failure); + return CompletableFuture.completedFuture(failure); + } + + CompletableFuture future = dispatchPerform(route, ctx); + return future.whenComplete((r, ex) -> eventBus.publish(ORTHOEventBus.EventType.RouteCompleted, route.toUrl(), r)); + } + + // ---- Dispatch ----------------------------------------------------------- + + private CompletableFuture dispatchOpen(OrthoRoute route, OrthoRouteContext ctx) { + return switch (route) { + case OrthoRoute.AppRoute r -> AppRouter.open(r, ctx, eventBus); + case OrthoRoute.FileRoute r -> FileRouter.open(r, ctx, eventBus); + case OrthoRoute.UrlRoute r -> { + // https:// -> browser + var appRoute = new OrthoRoute.AppRoute("org.ortho.browser", java.util.Map.of("url", r.url())); + yield AppRouter.open(appRoute, ctx, eventBus); + } + case OrthoRoute.SettingsRoute r -> AppRouter.open(new OrthoRoute.AppRoute("org.ortho.settings", java.util.Map.of("section", r.section())), ctx, eventBus); + case OrthoRoute.WorkspaceRoute r -> WorkspaceRouter.restore(r, ctx, eventBus); + case OrthoRoute.ProofRoute r -> AppRouter.open(new OrthoRoute.AppRoute("org.ortho.proofs", java.util.Map.of("proof", r.proofId())), ctx, eventBus); + case OrthoRoute.TraceRoute r -> AppRouter.open(new OrthoRoute.AppRoute("org.ortho.hardware", java.util.Map.of("cycle", String.valueOf(r.cycle()))), ctx, eventBus); + default -> dispatchPerform(route, ctx); + }; + } + + private CompletableFuture dispatchPerform(OrthoRoute route, OrthoRouteContext ctx) { + return switch (route) { + case OrthoRoute.IntentRoute r -> IntentRouter.perform(r, ctx, eventBus); + case OrthoRoute.ServiceRoute r -> ServiceRouter.perform(r, ctx, eventBus); + case OrthoRoute.HardwareRoute r -> HardwareRouter.perform(r, ctx, bridge, eventBus); + case OrthoRoute.ProofRoute r -> IntentRouter.perform(new OrthoRoute.IntentRoute("proof.verify", r.proofId(), r.params()), ctx, eventBus); + case OrthoRoute.AgentRoute r -> IntentRouter.perform(new OrthoRoute.IntentRoute("agent.invoke", r.agentId(), r.params()), ctx, eventBus); + default -> dispatchOpen(route, ctx); + }; + } + + private boolean hasCapability(OrthoRouteContext ctx, OrthoRoute route) { + if (ctx == null || ctx.capability() == null) return false; + String cap = ctx.capability(); + if ("admin".equals(cap) || "all".equals(cap)) return true; + if (route instanceof OrthoRoute.HardwareRoute) return "hardwareAccess".equals(cap) || "admin".equals(cap); + if (route instanceof OrthoRoute.IntentRoute) return "proofExecute".equals(cap) || "intentDispatch".equals(cap) || "admin".equals(cap); + if (route instanceof OrthoRoute.ProofRoute) return "proofExecute".equals(cap) || "admin".equals(cap); + // window/file/url routes + return "windowOpen".equals(cap) || "fileOpen".equals(cap) || "admin".equals(cap) || "readOnly".equals(cap) == false; + } + + // ---- Internal routers (four planes) ------------------------------------ + + static final class AppRouter { + static CompletableFuture open(OrthoRoute.AppRoute r, OrthoRouteContext ctx, ORTHOEventBus bus) { + // Simulate window creation + String windowId = "win-" + r.appId() + "-" + System.nanoTime(); + return CompletableFuture.completedFuture(new OrthoRouteResult.Success(windowId, null)); + } + } + + static final class FileRouter { + static final java.util.Set IDE_EXTS = java.util.Set.of("swift","lean","sv","v","cu","py","rs","c","cpp","h","md","proof","olean"); + static CompletableFuture open(OrthoRoute.FileRoute r, OrthoRouteContext ctx, ORTHOEventBus bus) { + String ext = r.path().contains(".") ? r.path().substring(r.path().lastIndexOf('.')+1).toLowerCase() : ""; + String appId = IDE_EXTS.contains(ext) ? "org.ortho.ide" : "org.ortho.files"; + // .vcd/.fst -> waveform, .html -> browser + if (ext.equals("vcd") || ext.equals("fst")) appId = "org.ortho.hardware"; + if (ext.equals("html") || ext.equals("htm")) appId = "org.ortho.browser"; + String windowId = "win-" + appId + "-" + System.nanoTime(); + return CompletableFuture.completedFuture(new OrthoRouteResult.Success(windowId, r.path())); + } + } + + static final class IntentRouter { + static CompletableFuture perform(OrthoRoute.IntentRoute r, OrthoRouteContext ctx, ORTHOEventBus bus) { + // No UI required – return result directly + String output = "intent:" + r.intent() + ":" + r.target(); + return CompletableFuture.completedFuture(new OrthoRouteResult.Success(null, output)); + } + } + + static final class ServiceRouter { + static CompletableFuture perform(OrthoRoute.ServiceRoute r, OrthoRouteContext ctx, ORTHOEventBus bus) { + return CompletableFuture.completedFuture(new OrthoRouteResult.Success(null, "service:" + r.serviceId())); + } + } + + static final class HardwareRouter { + static CompletableFuture perform(OrthoRoute.HardwareRoute r, OrthoRouteContext ctx, ORTHOBridge bridge, ORTHOEventBus bus) { + // MUST go through ORTHOBridge – never direct PCIe write + return bridge.submit(r.device(), r.operation(), r.params()) + .thenApply(resp -> (OrthoRouteResult) new OrthoRouteResult.Success(null, resp)) + .exceptionally(ex -> new OrthoRouteResult.Failure(OrthoRouteError.HARDWARE_ERROR)); + } + } + + static final class WorkspaceRouter { + static CompletableFuture restore(OrthoRoute.WorkspaceRoute r, OrthoRouteContext ctx, ORTHOEventBus bus) { + return CompletableFuture.completedFuture(new OrthoRouteResult.Success("workspace-" + r.workspaceId(), null)); + } + } +} diff --git a/ortho-security/build.gradle.kts b/ortho-security/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..22eb354f4e18ec7b7b55f6ef984b2b8c528dcc3a --- /dev/null +++ b/ortho-security/build.gradle.kts @@ -0,0 +1,6 @@ +plugins { `java-library` } + +dependencies { + api(project(":ortho-fabric")) + api(project(":ortho-device")) +} diff --git a/ortho-security/src/main/java/org/ortho32/security/Attestation.java b/ortho-security/src/main/java/org/ortho32/security/Attestation.java new file mode 100644 index 0000000000000000000000000000000000000000..dde443722845175cb6bdf563bcec277167ca7579 --- /dev/null +++ b/ortho-security/src/main/java/org/ortho32/security/Attestation.java @@ -0,0 +1,53 @@ +package org.ortho32.security; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Device attestation proving execution on genuine hardware. + * + * @param deviceId stable device identifier, must not be null + * @param sequence monotonic sequence number, must be >=0 + * @param cycles architectural cycles covered by attestation, never wall-clock + * @param proofId proof identifier, must not be null + * @param signature raw signature bytes, copied defensively, must not be null + * @param timestamp attestation timestamp in epoch millis, must be >=0 + */ +public record Attestation( + String deviceId, + long sequence, + long cycles, + String proofId, + byte[] signature, + long timestamp) { + + /** + * Compact canonical constructor. + * + * @param deviceId device id + * @param sequence sequence + * @param cycles architectural cycles + * @param proofId proof id + * @param signature signature bytes + * @param timestamp timestamp + */ + public Attestation { + Objects.requireNonNull(deviceId, "deviceId"); + Objects.requireNonNull(proofId, "proofId"); + Objects.requireNonNull(signature, "signature"); + if (sequence < 0) throw new IllegalArgumentException("sequence must be >=0"); + if (cycles < 0) throw new IllegalArgumentException("cycles must be >=0"); + if (timestamp < 0) throw new IllegalArgumentException("timestamp must be >=0"); + signature = Arrays.copyOf(signature, signature.length); + } + + /** + * Returns a defensive copy of signature. + * + * @return copy of signature bytes + */ + @Override + public byte[] signature() { + return Arrays.copyOf(signature, signature.length); + } +} diff --git a/ortho-security/src/main/java/org/ortho32/security/Capability.java b/ortho-security/src/main/java/org/ortho32/security/Capability.java new file mode 100644 index 0000000000000000000000000000000000000000..5ad0cc48620c3636b0a90269a70f66f25a79dbd0 --- /dev/null +++ b/ortho-security/src/main/java/org/ortho32/security/Capability.java @@ -0,0 +1,33 @@ +package org.ortho32.security; + +import java.util.Objects; + +/** + * Capability granting access to a resource. + * + * @param resource resource identifier, must not be null + * @param operations comma-separated operations or operation set, must not be null + * @param scope scope qualifier, must not be null + * @param persistence persistence level (e.g., session, persistent), must not be null + */ +public record Capability( + String resource, + String operations, + String scope, + String persistence) { + + /** + * Compact canonical constructor. + * + * @param resource resource + * @param operations operations + * @param scope scope + * @param persistence persistence + */ + public Capability { + Objects.requireNonNull(resource, "resource"); + Objects.requireNonNull(operations, "operations"); + Objects.requireNonNull(scope, "scope"); + Objects.requireNonNull(persistence, "persistence"); + } +} diff --git a/ortho-security/src/main/java/org/ortho32/security/Permission.java b/ortho-security/src/main/java/org/ortho32/security/Permission.java new file mode 100644 index 0000000000000000000000000000000000000000..1510b2f57154887994da5f1006a640077b77a1ca --- /dev/null +++ b/ortho-security/src/main/java/org/ortho32/security/Permission.java @@ -0,0 +1,35 @@ +package org.ortho32.security; + +import java.util.List; +import java.util.Objects; + +/** + * Permission grouping required capabilities. + * + * @param id stable permission identifier, must not be null + * @param name human-readable name, must not be null + * @param description description, must not be null + * @param requiredCapabilities list of required capabilities, never null, unmodifiable + */ +public record Permission( + String id, + String name, + String description, + List requiredCapabilities) { + + /** + * Compact canonical constructor. + * + * @param id permission id + * @param name name + * @param description description + * @param requiredCapabilities required capabilities + */ + public Permission { + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(name, "name"); + Objects.requireNonNull(description, "description"); + Objects.requireNonNull(requiredCapabilities, "requiredCapabilities"); + requiredCapabilities = List.copyOf(requiredCapabilities); + } +} diff --git a/ortho-security/src/main/java/org/ortho32/security/SecurityClient.java b/ortho-security/src/main/java/org/ortho32/security/SecurityClient.java new file mode 100644 index 0000000000000000000000000000000000000000..97f5ff41ec14fb8dc662c9a5e78108bf6ed72df8 --- /dev/null +++ b/ortho-security/src/main/java/org/ortho32/security/SecurityClient.java @@ -0,0 +1,33 @@ +package org.ortho32.security; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * Session sub-client for security and attestation. + */ +public interface SecurityClient { + + /** + * Returns the current security context. + * + * @return security context + */ + SecurityContext context(); + + /** + * Returns the granted capabilities. + * + * @return unmodifiable list of capabilities + */ + List capabilities(); + + /** + * Attests execution on a device. + * + * @param device device handle, must not be null + * @return future completing with attestation containing architectural cycles + */ + CompletableFuture attest(Object device); +} diff --git a/ortho-security/src/main/java/org/ortho32/security/SecurityContext.java b/ortho-security/src/main/java/org/ortho32/security/SecurityContext.java new file mode 100644 index 0000000000000000000000000000000000000000..6f5bf82b98a23a4e929302c9aef1123a95dd9431 --- /dev/null +++ b/ortho-security/src/main/java/org/ortho32/security/SecurityContext.java @@ -0,0 +1,46 @@ +package org.ortho32.security; + +import java.time.Instant; +import java.util.List; +import java.util.Objects; + +/** + * Security context associated with a session. + * + * @param identity identity principal, must not be null + * @param capabilities granted capabilities, never null, unmodifiable + * @param grantedAt grant timestamp, must not be null + * @param expiresAt expiry timestamp, may be null for non-expiring + */ +public record SecurityContext( + String identity, + List capabilities, + Instant grantedAt, + Instant expiresAt) { + + /** + * Compact canonical constructor. + * + * @param identity identity + * @param capabilities capabilities + * @param grantedAt grantedAt + * @param expiresAt expiresAt + */ + public SecurityContext { + Objects.requireNonNull(identity, "identity"); + Objects.requireNonNull(capabilities, "capabilities"); + Objects.requireNonNull(grantedAt, "grantedAt"); + capabilities = List.copyOf(capabilities); + } + + /** + * Checks whether this context has the given capability. + * + * @param capability capability to check, must not be null + * @return true if capability is granted + */ + public boolean hasCapability(Capability capability) { + Objects.requireNonNull(capability, "capability"); + return capabilities.contains(capability); + } +} diff --git a/ortho-security/src/main/java/org/ortho32/security/SignatureStatus.java b/ortho-security/src/main/java/org/ortho32/security/SignatureStatus.java new file mode 100644 index 0000000000000000000000000000000000000000..ee800dac07770e40eebc7c8fb3296087d691beb3 --- /dev/null +++ b/ortho-security/src/main/java/org/ortho32/security/SignatureStatus.java @@ -0,0 +1,13 @@ +package org.ortho32.security; + +/** + * Status of an attestation signature verification. + */ +public enum SignatureStatus { + /** Signature verified valid. */ + VALID, + /** Signature verified invalid. */ + INVALID, + /** Signature status unknown (e.g., no key material). */ + UNKNOWN +} diff --git a/ortho-tensor/build.gradle.kts b/ortho-tensor/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..d51f9901019677b2662918bde739dbc216bc438f --- /dev/null +++ b/ortho-tensor/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { `java-library` } + +dependencies { + api(project(":ortho-fabric")) + api(project(":ortho-device")) + api(project(":ortho-compute")) +} diff --git a/ortho-tensor/src/main/java/org/ortho32/tensor/Tensor.java b/ortho-tensor/src/main/java/org/ortho32/tensor/Tensor.java new file mode 100644 index 0000000000000000000000000000000000000000..dae48f708cb2be7729f3a03ac132bb01ed371716 --- /dev/null +++ b/ortho-tensor/src/main/java/org/ortho32/tensor/Tensor.java @@ -0,0 +1,164 @@ +package org.ortho32.tensor; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Objects; + +/** + * Immutable tensor value with shape, data type and backing data. + * The backing {@link ByteBuffer} is stored as a read-only copy and never exposed mutably. + * + * @param shape the tensor shape + * @param dtype the element data type + * @param data the backing buffer containing element data in native byte order + */ +public final class Tensor { + + /** + * Data type of tensor elements. + */ + public enum DType { + /** 32-bit IEEE 754 float. */ + FLOAT32, + /** 32-bit signed integer. */ + INT32, + /** 8-bit signed integer. */ + INT8, + /** 16-bit IEEE 754 float. */ + FLOAT16 + } + + private final TensorShape shape; + private final DType dtype; + private final ByteBuffer data; + + /** + * Constructs an immutable tensor. + * + * @param shape the tensor shape, must not be null + * @param dtype the element data type, must not be null + * @param data the backing buffer containing element data, must not be null; a read-only copy is retained + */ + public Tensor(TensorShape shape, DType dtype, ByteBuffer data) { + this.shape = Objects.requireNonNull(shape, "shape"); + this.dtype = Objects.requireNonNull(dtype, "dtype"); + Objects.requireNonNull(data, "data"); + ByteBuffer ro = data.asReadOnlyBuffer(); + ro.order(ByteOrder.nativeOrder()); + this.data = ro; + } + + /** + * Creates a 2D float tensor from a Java array. + * + * @param values 2D float array, must not be null and must be rectangular, outer dimension is rows + * @return immutable tensor with shape [rows, cols] and FLOAT32 dtype + */ + public static Tensor of(float[][] values) { + Objects.requireNonNull(values, "values"); + if (values.length == 0) { + throw new IllegalArgumentException("values must have at least one row"); + } + int rows = values.length; + int cols = values[0].length; + for (int i = 0; i < rows; i++) { + if (values[i] == null || values[i].length != cols) { + throw new IllegalArgumentException("values must be rectangular"); + } + } + TensorShape shape = new TensorShape(new long[]{rows, cols}); + ByteBuffer buf = ByteBuffer.allocateDirect(rows * cols * Float.BYTES).order(ByteOrder.nativeOrder()); + for (float[] row : values) { + for (float v : row) { + buf.putFloat(v); + } + } + buf.flip(); + return new Tensor(shape, DType.FLOAT32, buf); + } + + /** + * Returns the tensor shape. + * + * @return shape + */ + public TensorShape shape() { + return shape; + } + + /** + * Returns the data type. + * + * @return dtype + */ + public DType dtype() { + return dtype; + } + + /** + * Returns a read-only view of the backing data. + * + * @return read-only ByteBuffer in native order + */ + public ByteBuffer data() { + return data.asReadOnlyBuffer(); + } + + /** + * Copies tensor data to a new 2D float array. Only valid for FLOAT32 dtype. + * + * @return 2D float array copy of tensor contents with shape [rows][cols] + */ + public float[][] toFloatArray() { + if (dtype != DType.FLOAT32) { + throw new IllegalStateException("toFloatArray only valid for FLOAT32 dtype, got " + dtype); + } + long[] dims = shape.dims(); + if (dims.length != 2) { + throw new IllegalStateException("toFloatArray only valid for rank 2, got rank " + dims.length); + } + int rows = (int) dims[0]; + int cols = (int) dims[1]; + float[][] out = new float[rows][cols]; + ByteBuffer ro = data.asReadOnlyBuffer().order(ByteOrder.nativeOrder()); + ro.rewind(); + for (int r = 0; r < rows; r++) { + for (int c = 0; c < cols; c++) { + out[r][c] = ro.getFloat(); + } + } + return out; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Tensor other)) return false; + if (!shape.equals(other.shape) || dtype != other.dtype) return false; + ByteBuffer a = data.asReadOnlyBuffer(); + ByteBuffer b = other.data.asReadOnlyBuffer(); + if (a.remaining() != b.remaining()) return false; + a.rewind(); b.rewind(); + while (a.hasRemaining()) { + if (a.get() != b.get()) return false; + } + return true; + } + + @Override + public int hashCode() { + int h = Objects.hash(shape, dtype); + ByteBuffer ro = data.asReadOnlyBuffer(); + ro.rewind(); + while (ro.hasRemaining()) { + h = 31 * h + ro.get(); + } + return h; + } + + @Override + public String toString() { + return "Tensor{shape=" + shape + ", dtype=" + dtype + ", bytes=" + data.remaining() + "}"; + } +} diff --git a/ortho-tensor/src/main/java/org/ortho32/tensor/TensorBuffer.java b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorBuffer.java new file mode 100644 index 0000000000000000000000000000000000000000..d74eaf83dc4c271bd5034f3aad068fd37b93f9a8 --- /dev/null +++ b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorBuffer.java @@ -0,0 +1,117 @@ +package org.ortho32.tensor; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +/** + * Host-side DMA-capable buffer for tensor transfer. + * Allocated buffers are direct and must be released. Implements {@link AutoCloseable}. + */ +public final class TensorBuffer implements AutoCloseable { + + private ByteBuffer buffer; + private Tensor lastTensorMeta; + private boolean released; + + private TensorBuffer(ByteBuffer buffer) { + this.buffer = buffer; + this.released = false; + } + + /** + * Allocates a DMA-capable buffer of the given size in bytes. + * + * @param bytes number of bytes to allocate, must be >=0 and fit in int for direct allocation + * @return new buffer + */ + public static TensorBuffer allocate(long bytes) { + if (bytes < 0) throw new IllegalArgumentException("bytes must be >=0"); + if (bytes > Integer.MAX_VALUE) throw new IllegalArgumentException("bytes too large: " + bytes); + return allocate((int) bytes); + } + + /** + * Allocates a DMA-capable buffer of the given size in bytes. + * + * @param bytes number of bytes to allocate, must be >=0 + * @return new buffer + */ + public static TensorBuffer allocate(int bytes) { + if (bytes < 0) throw new IllegalArgumentException("bytes must be >=0"); + ByteBuffer buf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder()); + return new TensorBuffer(buf); + } + + /** + * Puts a tensor's data into this buffer, overwriting existing content. + * + * @param tensor tensor to copy, must not be null + * @return this buffer for chaining + */ + public synchronized TensorBuffer put(Tensor tensor) { + Objects.requireNonNull(tensor, "tensor"); + ensureNotReleased(); + ByteBuffer src = tensor.data().asReadOnlyBuffer(); + src.rewind(); + if (src.remaining() > buffer.capacity()) { + throw new IllegalArgumentException("tensor data size " + src.remaining() + " exceeds buffer capacity " + buffer.capacity()); + } + buffer.clear(); + buffer.put(src); + buffer.flip(); + this.lastTensorMeta = tensor; + return this; + } + + /** + * Gets a tensor view of the current buffer contents. + * Requires a previous {@link #put(Tensor)} to provide shape and dtype metadata. + * + * @return tensor copied from buffer with last metadata + */ + public synchronized Tensor get() { + ensureNotReleased(); + if (lastTensorMeta == null) { + throw new IllegalStateException("no tensor metadata available; call put(Tensor) first"); + } + ByteBuffer copy = ByteBuffer.allocateDirect(buffer.remaining()).order(ByteOrder.nativeOrder()); + ByteBuffer ro = buffer.asReadOnlyBuffer(); + ro.rewind(); + copy.put(ro); + copy.flip(); + return new Tensor(lastTensorMeta.shape(), lastTensorMeta.dtype(), copy); + } + + /** + * Releases the underlying DMA buffer. Idempotent. + */ + public synchronized void release() { + if (!released) { + released = true; + buffer = null; + lastTensorMeta = null; + } + } + + /** + * Closes this buffer, releasing resources. + */ + @Override + public void close() { + release(); + } + + /** + * Returns true if released. + * + * @return true if released + */ + public synchronized boolean isReleased() { + return released; + } + + private void ensureNotReleased() { + if (released) throw new IllegalStateException("TensorBuffer already released"); + } +} diff --git a/ortho-tensor/src/main/java/org/ortho32/tensor/TensorClient.java b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorClient.java new file mode 100644 index 0000000000000000000000000000000000000000..0b0d0bb425cf9483b94eca0822f572f9aba75456 --- /dev/null +++ b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorClient.java @@ -0,0 +1,29 @@ +package org.ortho32.tensor; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * Session sub-client for tensor execution. + * Transport-agnostic; all timing is architectural cycles. + */ +public interface TensorClient { + + /** + * Submits a tensor job for execution. + * + * @param job the validated job to execute, must not be null + * @return future completing with the tensor result + */ + CompletableFuture submit(TensorJob job); + + /** + * Submits a batch of tensor jobs for execution. + * Implementations may execute sequentially or in parallel while preserving determinism per job. + * + * @param jobs list of jobs, must not be null and must not contain null elements; copied defensively + * @return future completing with results in the same order as input jobs + */ + CompletableFuture> submitBatch(List jobs); +} diff --git a/ortho-tensor/src/main/java/org/ortho32/tensor/TensorJob.java b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorJob.java new file mode 100644 index 0000000000000000000000000000000000000000..43937d5b211075985d78236051b6da5ff56450e9 --- /dev/null +++ b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorJob.java @@ -0,0 +1,197 @@ +package org.ortho32.tensor; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Immutable job describing a tensor operation to be executed on a device. + * Constructed via {@link Builder}. + */ +public final class TensorJob { + + private final Object device; + private final TensorOperation operation; + private final Tensor inputA; + private final Tensor inputB; + private final Map options; + + private TensorJob(Builder b) { + this.device = b.device; + this.operation = b.operation; + this.inputA = b.inputA; + this.inputB = b.inputB; + this.options = b.options == null ? Map.of() : Collections.unmodifiableMap(new HashMap<>(b.options)); + } + + /** + * Returns the target device handle. + * + * @return device object + */ + public Object device() { + return device; + } + + /** + * Returns the operation. + * + * @return operation + */ + public TensorOperation operation() { + return operation; + } + + /** + * Returns first input tensor, may be null for some operations. + * + * @return inputA or null + */ + public Tensor inputA() { + return inputA; + } + + /** + * Returns second input tensor, may be null. + * + * @return inputB or null + */ + public Tensor inputB() { + return inputB; + } + + /** + * Returns immutable options map. + * + * @return unmodifiable options + */ + public Map options() { + return options; + } + + /** + * Creates a new builder. + * + * @return builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link TensorJob} with validation on {@link #build()}. + */ + public static final class Builder { + private Object device; + private TensorOperation operation; + private Tensor inputA; + private Tensor inputB; + private Map options; + + private Builder() {} + + /** + * Sets the target device. + * + * @param device device handle, must not be null + * @return this builder + */ + public Builder device(Object device) { + this.device = Objects.requireNonNull(device, "device"); + return this; + } + + /** + * Sets the operation. + * + * @param operation operation, must not be null + * @return this builder + */ + public Builder operation(TensorOperation operation) { + this.operation = Objects.requireNonNull(operation, "operation"); + return this; + } + + /** + * Sets first input. + * + * @param inputA first input tensor + * @return this builder + */ + public Builder inputA(Tensor inputA) { + this.inputA = inputA; + return this; + } + + /** + * Sets second input. + * + * @param inputB second input tensor + * @return this builder + */ + public Builder inputB(Tensor inputB) { + this.inputB = inputB; + return this; + } + + /** + * Sets options map. + * + * @param options options map, copied defensively + * @return this builder + */ + public Builder options(Map options) { + this.options = options == null ? null : new HashMap<>(options); + return this; + } + + /** + * Adds a single option. + * + * @param key option key, must not be null + * @param value option value, must not be null + * @return this builder + */ + public Builder option(String key, String value) { + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(value, "value"); + if (this.options == null) this.options = new HashMap<>(); + this.options.put(key, value); + return this; + } + + /** + * Builds and validates the job. + * Validation includes presence of device and operation and shape compatibility for TMUL. + * + * @return immutable TensorJob + * @throws IllegalStateException if required fields missing or shapes incompatible + */ + public TensorJob build() { + if (device == null) throw new IllegalStateException("device must be set"); + if (operation == null) throw new IllegalStateException("operation must be set"); + switch (operation) { + case TMUL -> { + if (inputA == null) throw new IllegalStateException("inputA required for TMUL"); + if (inputB == null) throw new IllegalStateException("inputB required for TMUL"); + if (inputA.shape().rank() != 2 || inputB.shape().rank() != 2) { + throw new IllegalStateException("TMUL requires rank 2 inputs, got " + inputA.shape().rank() + " and " + inputB.shape().rank()); + } + long aCols = inputA.shape().dims()[1]; + long bRows = inputB.shape().dims()[0]; + if (aCols != bRows) { + throw new IllegalStateException("TMUL shape mismatch: inputA cols " + aCols + " != inputB rows " + bRows); + } + } + case TLOAD -> { + if (inputA == null) throw new IllegalStateException("inputA required for TLOAD"); + } + case TSTORE -> { + if (inputA == null) throw new IllegalStateException("inputA required for TSTORE"); + } + } + return new TensorJob(this); + } + } +} diff --git a/ortho-tensor/src/main/java/org/ortho32/tensor/TensorLatency.java b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorLatency.java new file mode 100644 index 0000000000000000000000000000000000000000..df7a805b1fcd303d14750c4bf32391b5d41de2f5 --- /dev/null +++ b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorLatency.java @@ -0,0 +1,26 @@ +package org.ortho32.tensor; + +/** + * Proven architectural latency constants for ORTHO-32 tensor operations. + * These are architectural integers in cycles, not wall-clock time, and are + * proven in the formal specification. Do not derive from benchmarks. + */ +public final class TensorLatency { + + private TensorLatency() {} + + /** Proven architectural cycles for {@code TMUL}: 4. */ + public static final long TMUL_CYCLES = 4L; + + /** Proven architectural cycles for {@code TLOAD}: 5. */ + public static final long TLOAD_CYCLES = 5L; + + /** Proven architectural cycles for {@code TSTORE}: 5. */ + public static final long TSTORE_CYCLES = 5L; + + /** Proven architectural cycles for scratchpad access: 2. */ + public static final long SCRATCHPAD_CYCLES = 2L; + + /** Proven architectural cycles for a 4x4x4 GEMM tile: 768. */ + public static final long GEMM_TILE_4X4X4 = 768L; +} diff --git a/ortho-tensor/src/main/java/org/ortho32/tensor/TensorOperation.java b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorOperation.java new file mode 100644 index 0000000000000000000000000000000000000000..3fb61c8fae8846ff615ac14336b1dab30e92e617 --- /dev/null +++ b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorOperation.java @@ -0,0 +1,28 @@ +package org.ortho32.tensor; + +/** + * Tensor operations supported by ORTHO-32 fabric. + * Each operation has a proven architectural latency in cycles. + */ +public enum TensorOperation { + /** Tensor multiply. Proven latency 4 cycles. */ + TMUL, + /** Tensor load to scratchpad. Proven latency 5 cycles. */ + TLOAD, + /** Tensor store from scratchpad. Proven latency 5 cycles. */ + TSTORE; + + /** + * Returns the proven architectural latency for this operation in cycles. + * Values are architectural constants, not wall-clock measurements. + * + * @return latency cycles: TMUL=4, TLOAD=5, TSTORE=5 + */ + public long latencyCycles() { + return switch (this) { + case TMUL -> TensorLatency.TMUL_CYCLES; + case TLOAD -> TensorLatency.TLOAD_CYCLES; + case TSTORE -> TensorLatency.TSTORE_CYCLES; + }; + } +} diff --git a/ortho-tensor/src/main/java/org/ortho32/tensor/TensorResult.java b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorResult.java new file mode 100644 index 0000000000000000000000000000000000000000..ae230d6ac148c90dad1ae95fa97cd4aa8dc8cde1 --- /dev/null +++ b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorResult.java @@ -0,0 +1,37 @@ +package org.ortho32.tensor; + +import java.util.Objects; + +/** + * Result of a tensor execution. + * + * @param output the output tensor produced + * @param cycles architectural cycles taken for the operation, never wall-clock + * @param traceHash SHA-256 hex of the execution trace for determinism proof + * @param completionCycle architectural completion cycle number + * @param scratchpadUsedBytes bytes of scratchpad used + */ +public record TensorResult( + Tensor output, + long cycles, + String traceHash, + long completionCycle, + long scratchpadUsedBytes) { + + /** + * Compact canonical constructor validating invariants. + * + * @param output output tensor, must not be null + * @param cycles architectural cycles, must be >=0 + * @param traceHash trace hash hex string, must not be null + * @param completionCycle architectural completion cycle, must be >=0 + * @param scratchpadUsedBytes scratchpad bytes used, must be >=0 + */ + public TensorResult { + Objects.requireNonNull(output, "output"); + Objects.requireNonNull(traceHash, "traceHash"); + if (cycles < 0) throw new IllegalArgumentException("cycles must be >=0"); + if (completionCycle < 0) throw new IllegalArgumentException("completionCycle must be >=0"); + if (scratchpadUsedBytes < 0) throw new IllegalArgumentException("scratchpadUsedBytes must be >=0"); + } +} diff --git a/ortho-tensor/src/main/java/org/ortho32/tensor/TensorShape.java b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorShape.java new file mode 100644 index 0000000000000000000000000000000000000000..7b1dfc5218c8ef5645b6a5e03ff6c5781e8d9fb2 --- /dev/null +++ b/ortho-tensor/src/main/java/org/ortho32/tensor/TensorShape.java @@ -0,0 +1,87 @@ +package org.ortho32.tensor; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Immutable tensor shape describing dimensions. + * + * @param dims dimensions of the tensor, copied defensively; each entry is the size of that axis + */ +public record TensorShape(long[] dims) { + + /** + * Compact canonical constructor performing defensive copy. + * + * @param dims dimensions array, must not be null + */ + public TensorShape { + Objects.requireNonNull(dims, "dims"); + for (long d : dims) { + if (d < 0) throw new IllegalArgumentException("dimension must be >=0, got " + d); + } + dims = Arrays.copyOf(dims, dims.length); + } + + /** + * Returns a defensive copy of dimensions. + * + * @return copy of dims + */ + @Override + public long[] dims() { + return Arrays.copyOf(dims, dims.length); + } + + /** + * Returns the rank (number of dimensions). + * + * @return rank + */ + public int rank() { + return dims.length; + } + + /** + * Returns the total number of elements (product of dimensions). + * + * @return element count, 1 for rank 0, 0 if any dimension is 0 + */ + public long elementCount() { + if (dims.length == 0) return 1; + long c = 1; + for (long d : dims) { + c = Math.multiplyExact(c, d); + } + return c; + } + + /** + * Checks if this shape is compatible with another for element-wise operations. + * Compatibility is defined as exact dimension equality. + * + * @param other the other shape to compare, must not be null + * @return true if dims are equal + */ + public boolean compatible(TensorShape other) { + Objects.requireNonNull(other, "other"); + return Arrays.equals(this.dims, other.dims); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TensorShape that)) return false; + return Arrays.equals(dims, that.dims); + } + + @Override + public int hashCode() { + return Arrays.hashCode(dims); + } + + @Override + public String toString() { + return "TensorShape" + Arrays.toString(dims); + } +} diff --git a/ortho-trace/build.gradle.kts b/ortho-trace/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..22eb354f4e18ec7b7b55f6ef984b2b8c528dcc3a --- /dev/null +++ b/ortho-trace/build.gradle.kts @@ -0,0 +1,6 @@ +plugins { `java-library` } + +dependencies { + api(project(":ortho-fabric")) + api(project(":ortho-device")) +} diff --git a/ortho-trace/src/main/java/org/ortho32/trace/CommitRecord.java b/ortho-trace/src/main/java/org/ortho32/trace/CommitRecord.java new file mode 100644 index 0000000000000000000000000000000000000000..b7b0c178aacd5de71c3b6a0d72f9e2b9a4b828ad --- /dev/null +++ b/ortho-trace/src/main/java/org/ortho32/trace/CommitRecord.java @@ -0,0 +1,38 @@ +package org.ortho32.trace; + +import java.util.List; +import java.util.Objects; + +/** + * Commit record for a single instruction at a cycle. + * + * @param cycle architectural commit cycle number, must be >=0 + * @param instructionId stable identifier for the committed instruction + * @param registersWritten list of registers written, unmodifiable + * @param memoryEffects list of memory effects, unmodifiable + * @param tensorEffects list of tensor effects, unmodifiable + */ +public record CommitRecord( + long cycle, + String instructionId, + List registersWritten, + List memoryEffects, + List tensorEffects) { + + /** + * Compact canonical constructor. + * + * @param cycle commit cycle + * @param instructionId instruction identifier + * @param registersWritten registers written + * @param memoryEffects memory effects + * @param tensorEffects tensor effects + */ + public CommitRecord { + if (cycle < 0) throw new IllegalArgumentException("cycle must be >=0"); + Objects.requireNonNull(instructionId, "instructionId"); + registersWritten = registersWritten == null ? List.of() : List.copyOf(registersWritten); + memoryEffects = memoryEffects == null ? List.of() : List.copyOf(memoryEffects); + tensorEffects = tensorEffects == null ? List.of() : List.copyOf(tensorEffects); + } +} diff --git a/ortho-trace/src/main/java/org/ortho32/trace/CycleRecord.java b/ortho-trace/src/main/java/org/ortho32/trace/CycleRecord.java new file mode 100644 index 0000000000000000000000000000000000000000..35c94b8447d653b0f550779b14c4d70458e582ca --- /dev/null +++ b/ortho-trace/src/main/java/org/ortho32/trace/CycleRecord.java @@ -0,0 +1,44 @@ +package org.ortho32.trace; + +import java.util.List; +import java.util.Objects; + +/** + * Record of a single architectural cycle. + * + * @param cycleNumber architectural cycle number, integer count from 0, never wall-clock + * @param scalarStage pipeline state for scalar stages in this cycle, must not be null + * @param tensorStage tensor stage descriptor, may be null + * @param commitStatus commit record for this cycle, may be null if no commit + * @param registerWrites register writes in this cycle, never null, unmodifiable + * @param memoryEvents memory events in this cycle, never null, unmodifiable + * @param tensorEvents tensor events in this cycle, never null, unmodifiable + */ +public record CycleRecord( + long cycleNumber, + PipelineState scalarStage, + String tensorStage, + CommitRecord commitStatus, + List registerWrites, + List memoryEvents, + List tensorEvents) { + + /** + * Compact canonical constructor validating and copying collections. + * + * @param cycleNumber architectural cycle number + * @param scalarStage pipeline state + * @param tensorStage tensor stage descriptor + * @param commitStatus commit status + * @param registerWrites register writes + * @param memoryEvents memory events + * @param tensorEvents tensor events + */ + public CycleRecord { + if (cycleNumber < 0) throw new IllegalArgumentException("cycleNumber must be >=0"); + Objects.requireNonNull(scalarStage, "scalarStage"); + registerWrites = registerWrites == null ? List.of() : List.copyOf(registerWrites); + memoryEvents = memoryEvents == null ? List.of() : List.copyOf(memoryEvents); + tensorEvents = tensorEvents == null ? List.of() : List.copyOf(tensorEvents); + } +} diff --git a/ortho-trace/src/main/java/org/ortho32/trace/ExecutionTrace.java b/ortho-trace/src/main/java/org/ortho32/trace/ExecutionTrace.java new file mode 100644 index 0000000000000000000000000000000000000000..8582d33566192213daee18fe211b264a67f197c8 --- /dev/null +++ b/ortho-trace/src/main/java/org/ortho32/trace/ExecutionTrace.java @@ -0,0 +1,86 @@ +package org.ortho32.trace; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * Immutable ordered list of {@link CycleRecord} capturing architectural execution. + */ +public final class ExecutionTrace { + + private final List records; + + /** + * Constructs an immutable trace. + * + * @param records ordered cycle records, must not be null and must not contain null elements; copied defensively + */ + public ExecutionTrace(List records) { + Objects.requireNonNull(records, "records"); + this.records = List.copyOf(records); + } + + /** + * Computes the SHA-256 hash of the canonical trace representation. + * + * @return trace hash value + */ + public TraceHash hash() { + return TraceHash.compute(this); + } + + /** + * Returns the number of cycles in the trace. + * + * @return size + */ + public int size() { + return records.size(); + } + + /** + * Returns the record at the given index. + * + * @param index index, 0-based, must be within bounds + * @return cycle record + */ + public CycleRecord get(int index) { + return records.get(index); + } + + /** + * Returns a sequential stream over records. + * + * @return stream + */ + public Stream stream() { + return records.stream(); + } + + /** + * Returns an unmodifiable view of records. + * + * @return unmodifiable list + */ + public List records() { + return records; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ExecutionTrace that)) return false; + return records.equals(that.records); + } + + @Override + public int hashCode() { + return records.hashCode(); + } + + @Override + public String toString() { + return "ExecutionTrace{size=" + records.size() + ", hash=" + hash().value() + "}"; + } +} diff --git a/ortho-trace/src/main/java/org/ortho32/trace/PipelineState.java b/ortho-trace/src/main/java/org/ortho32/trace/PipelineState.java new file mode 100644 index 0000000000000000000000000000000000000000..29230e0cf5e5ab05d2cb8cd57c1c9172f34016d5 --- /dev/null +++ b/ortho-trace/src/main/java/org/ortho32/trace/PipelineState.java @@ -0,0 +1,26 @@ +package org.ortho32.trace; + +/** + * Snapshot of pipeline occupancy and forwarding signals for a cycle. + * + * @param IF_occupied true if IF stage occupied + * @param ID_occupied true if ID stage occupied + * @param EX_occupied true if EX stage occupied + * @param MEM_occupied true if MEM stage occupied + * @param WB_occupied true if WB stage occupied + * @param forwardingEX_MEM true if EX->MEM forwarding active + * @param forwardingMEM_WB true if MEM->WB forwarding active + * @param branchFlush true if branch flush asserted + * @param scoreboardBusy true if scoreboard indicates busy + */ +public record PipelineState( + boolean IF_occupied, + boolean ID_occupied, + boolean EX_occupied, + boolean MEM_occupied, + boolean WB_occupied, + boolean forwardingEX_MEM, + boolean forwardingMEM_WB, + boolean branchFlush, + boolean scoreboardBusy) { +} diff --git a/ortho-trace/src/main/java/org/ortho32/trace/TraceClient.java b/ortho-trace/src/main/java/org/ortho32/trace/TraceClient.java new file mode 100644 index 0000000000000000000000000000000000000000..1d4391fe5b025697ae3bd491fe7c8c9d90f45658 --- /dev/null +++ b/ortho-trace/src/main/java/org/ortho32/trace/TraceClient.java @@ -0,0 +1,27 @@ +package org.ortho32.trace; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * Session sub-client for trace capture and replay. + */ +public interface TraceClient { + + /** + * Captures an execution trace from a device. + * + * @param device device handle, must not be null + * @return future completing with the captured trace + */ + CompletableFuture capture(Object device); + + /** + * Replays a trace on a device for determinism verification. + * + * @param trace trace to replay, must not be null + * @param device device handle, must not be null + * @return future completing with replay result trace or completion signal + */ + CompletableFuture replay(ExecutionTrace trace, Object device); +} diff --git a/ortho-trace/src/main/java/org/ortho32/trace/TraceComparator.java b/ortho-trace/src/main/java/org/ortho32/trace/TraceComparator.java new file mode 100644 index 0000000000000000000000000000000000000000..8f5bf5633569b08778cbf75ba2783fffbcb7f300 --- /dev/null +++ b/ortho-trace/src/main/java/org/ortho32/trace/TraceComparator.java @@ -0,0 +1,66 @@ +package org.ortho32.trace; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Comparator for execution traces to verify determinism. + */ +public final class TraceComparator { + + private TraceComparator() {} + + /** + * Compares two traces for equality. + * Same input must produce same {@link TraceHash}; mismatch is a determinism violation. + * + * @param trace1 first trace, must not be null + * @param trace2 second trace, must not be null + * @return comparison result containing equality and mismatches + */ + public static TraceComparison compare(ExecutionTrace trace1, ExecutionTrace trace2) { + Objects.requireNonNull(trace1, "trace1"); + Objects.requireNonNull(trace2, "trace2"); + List mismatches = new ArrayList<>(); + int max = Math.max(trace1.size(), trace2.size()); + int min = Math.min(trace1.size(), trace2.size()); + for (int i = 0; i < min; i++) { + CycleRecord a = trace1.get(i); + CycleRecord b = trace2.get(i); + if (!a.equals(b)) { + mismatches.add(new CycleMismatch(a.cycleNumber(), "record mismatch at index " + i)); + } + } + if (trace1.size() != trace2.size()) { + long cycle = min < trace1.size() ? trace1.get(min).cycleNumber() + : (min < trace2.size() ? trace2.get(min).cycleNumber() : min); + mismatches.add(new CycleMismatch(cycle, "size mismatch: " + trace1.size() + " vs " + trace2.size())); + } + for (int i = min; i < max; i++) { + // already accounted size mismatch, but also report extra cycles as mismatches + } + boolean equal = mismatches.isEmpty(); + Long firstDiv = mismatches.isEmpty() ? null : mismatches.get(0).cycle(); + return new TraceComparison(equal, List.copyOf(mismatches), firstDiv); + } + + /** + * Represents a single cycle mismatch. + * + * @param cycle architectural cycle where divergence occurred + * @param description description of mismatch + */ + public record CycleMismatch(long cycle, String description) { + /** + * Compact canonical constructor. + * + * @param cycle architectural cycle + * @param description mismatch description + */ + public CycleMismatch { + if (cycle < 0) throw new IllegalArgumentException("cycle must be >=0"); + Objects.requireNonNull(description, "description"); + } + } +} diff --git a/ortho-trace/src/main/java/org/ortho32/trace/TraceComparison.java b/ortho-trace/src/main/java/org/ortho32/trace/TraceComparison.java new file mode 100644 index 0000000000000000000000000000000000000000..6ec4ec83422811f5f1dc46921563f2ed3be7d19f --- /dev/null +++ b/ortho-trace/src/main/java/org/ortho32/trace/TraceComparison.java @@ -0,0 +1,65 @@ +package org.ortho32.trace; + +import java.util.List; +import java.util.Objects; + +/** + * Result of trace comparison. + * + * @param equal true if traces are equal + * @param mismatches list of mismatches, never null, unmodifiable + * @param firstDivergenceCycle architectural cycle of first divergence, or null if equal + */ +public record TraceComparison( + boolean equal, + List mismatches, + Long firstDivergenceCycle) { + + /** + * Compact canonical constructor. + * + * @param equal equality flag + * @param mismatches mismatches list + * @param firstDivergenceCycle first divergence cycle + */ + public TraceComparison { + Objects.requireNonNull(mismatches, "mismatches"); + mismatches = List.copyOf(mismatches); + if (equal && !mismatches.isEmpty()) { + throw new IllegalArgumentException("equal true requires empty mismatches"); + } + if (!equal && firstDivergenceCycle == null && !mismatches.isEmpty()) { + throw new IllegalArgumentException("non-equal comparison must have firstDivergenceCycle"); + } + } + + /** + * Returns whether traces are equal. + * + * @return true if equal + */ + @Override + public boolean equal() { + return equal; + } + + /** + * Returns mismatches. + * + * @return unmodifiable mismatches + */ + @Override + public List mismatches() { + return mismatches; + } + + /** + * Returns first divergence cycle. + * + * @return cycle or null + */ + @Override + public Long firstDivergenceCycle() { + return firstDivergenceCycle; + } +} diff --git a/ortho-trace/src/main/java/org/ortho32/trace/TraceHash.java b/ortho-trace/src/main/java/org/ortho32/trace/TraceHash.java new file mode 100644 index 0000000000000000000000000000000000000000..d81a7e180c16d0e5539f1f55d26ba1d98aa4feff --- /dev/null +++ b/ortho-trace/src/main/java/org/ortho32/trace/TraceHash.java @@ -0,0 +1,51 @@ +package org.ortho32.trace; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; +import java.util.Objects; + +/** + * SHA-256 hash of a canonical trace representation used for determinism proof. + * + * @param value SHA-256 hex string, lowercase, must not be null + */ +public record TraceHash(String value) { + + /** + * Compact canonical constructor. + * + * @param value hex string value + */ + public TraceHash { + Objects.requireNonNull(value, "value"); + if (!value.matches("[0-9a-f]{64}")) { + throw new IllegalArgumentException("value must be SHA-256 hex (64 lowercase hex chars)"); + } + } + + /** + * Computes the canonical hash for a trace by SHA-256 over a deterministic serialization. + * + * @param trace the trace to hash, must not be null + * @return trace hash + */ + public static TraceHash compute(ExecutionTrace trace) { + Objects.requireNonNull(trace, "trace"); + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + for (int i = 0; i < trace.size(); i++) { + CycleRecord r = trace.get(i); + String canonical = r.cycleNumber() + "|" + r.scalarStage() + "|" + r.tensorStage() + "|" + r.commitStatus() + + "|" + r.registerWrites() + "|" + r.memoryEvents() + "|" + r.tensorEvents() + "\n"; + md.update(canonical.getBytes(StandardCharsets.UTF_8)); + } + byte[] digest = md.digest(); + String hex = HexFormat.of().formatHex(digest); + return new TraceHash(hex); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("SHA-256 not available", e); + } + } +} diff --git a/ortho-transport/build.gradle.kts b/ortho-transport/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..a54eacd4b00687afcb83ef7decaef0f8c70e1e77 --- /dev/null +++ b/ortho-transport/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + `java-library` +} + +dependencies { + api(project(":ortho-fabric")) + api(project(":ortho-device")) +} + +java { + toolchain { languageVersion.set(JavaLanguageVersion.of(21)) } +} diff --git a/ortho-transport/src/main/java/org/ortho32/transport/EthernetTransport.java b/ortho-transport/src/main/java/org/ortho32/transport/EthernetTransport.java new file mode 100644 index 0000000000000000000000000000000000000000..56653c071589fc2aa0a59b642fc7cbd2830b89a0 --- /dev/null +++ b/ortho-transport/src/main/java/org/ortho32/transport/EthernetTransport.java @@ -0,0 +1,51 @@ +package org.ortho32.transport; + +import org.ortho32.fabric.FabricCommand; +import org.ortho32.fabric.FabricCompletion; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * Stub for Ethernet/TCP transport to ORTHO device. + */ +public final class EthernetTransport implements OrthoTransport { + + private final String host; + private final int port; + private volatile boolean connected; + + public EthernetTransport(String host, int port) { + this.host = Objects.requireNonNull(host, "host"); + if (port < 1 || port > 65535) throw new IllegalArgumentException("port out of range"); + this.port = port; + } + + public String host() { return host; } + public int port() { return port; } + + @Override + public void connect() { + connected = true; + } + + @Override + public void disconnect() { + connected = false; + } + + @Override + public boolean isConnected() { + return connected; + } + + @Override + public CompletableFuture submit(FabricCommand command) { + Objects.requireNonNull(command); + if (!connected) { + return CompletableFuture.failedFuture(new IllegalStateException("EthernetTransport not connected")); + } + return CompletableFuture.failedFuture( + new UnsupportedOperationException("EthernetTransport is a stub: TCP transport not implemented in this SDK slice") + ); + } +} diff --git a/ortho-transport/src/main/java/org/ortho32/transport/NativeHostTransport.java b/ortho-transport/src/main/java/org/ortho32/transport/NativeHostTransport.java new file mode 100644 index 0000000000000000000000000000000000000000..14b11d6ec6cbc84c6314ce3aedbe0d96a2f0ef17 --- /dev/null +++ b/ortho-transport/src/main/java/org/ortho32/transport/NativeHostTransport.java @@ -0,0 +1,44 @@ +package org.ortho32.transport; + +import org.ortho32.fabric.FabricCommand; +import org.ortho32.fabric.FabricCompletion; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * Stub for ORTHO host service via local IPC (Unix domain socket / named pipe). + * Placeholder for real implementation. Connects to ORTHO host service. + */ +public final class NativeHostTransport implements OrthoTransport { + + private volatile boolean connected; + + public NativeHostTransport() {} + + @Override + public void connect() { + // Placeholder: would establish IPC to host service + connected = true; + } + + @Override + public void disconnect() { + connected = false; + } + + @Override + public boolean isConnected() { + return connected; + } + + @Override + public CompletableFuture submit(FabricCommand command) { + Objects.requireNonNull(command); + if (!connected) { + return CompletableFuture.failedFuture(new IllegalStateException("NativeHostTransport not connected")); + } + return CompletableFuture.failedFuture( + new UnsupportedOperationException("NativeHostTransport is a stub: host IPC not implemented in this SDK slice") + ); + } +} diff --git a/ortho-transport/src/main/java/org/ortho32/transport/OrthoTransport.java b/ortho-transport/src/main/java/org/ortho32/transport/OrthoTransport.java new file mode 100644 index 0000000000000000000000000000000000000000..be023e7de59f535878553b4f341073e5149462ee --- /dev/null +++ b/ortho-transport/src/main/java/org/ortho32/transport/OrthoTransport.java @@ -0,0 +1,21 @@ +package org.ortho32.transport; + +import org.ortho32.fabric.FabricCommand; +import org.ortho32.fabric.FabricCompletion; +import java.util.concurrent.CompletableFuture; + +/** + * INTERNAL transport interface. Not part of public application API. + * Applications use {@link org.ortho32.device.OrthoDevice} and {@link FabricCommand}. + * Package-private visibility enforces this boundary. + */ +interface OrthoTransport { + + CompletableFuture submit(FabricCommand command); + + void connect(); + + void disconnect(); + + boolean isConnected(); +} diff --git a/ortho-transport/src/main/java/org/ortho32/transport/SimulatorTransport.java b/ortho-transport/src/main/java/org/ortho32/transport/SimulatorTransport.java new file mode 100644 index 0000000000000000000000000000000000000000..49499002800e7a55b1ff615dfed5bd847371bb79 --- /dev/null +++ b/ortho-transport/src/main/java/org/ortho32/transport/SimulatorTransport.java @@ -0,0 +1,146 @@ +package org.ortho32.transport; + +import org.ortho32.fabric.FabricCommand; +import org.ortho32.fabric.FabricCompletion; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * Deterministic fake fabric. Same FabricCommand -> same FabricCompletion. + * Configurable latency in architectural cycles. No hardware required. + * Thread-safe and immutable configuration. + */ +public final class SimulatorTransport implements OrthoTransport { + + private final long baseLatencyCycles; + private final long latencyJitterMask; + private volatile boolean connected; + private final Executor executor; + + public SimulatorTransport() { + this(10L, 0L); + } + + public SimulatorTransport(long baseLatencyCycles, long latencyJitterMask) { + if (baseLatencyCycles < 0) throw new IllegalArgumentException("baseLatencyCycles < 0"); + this.baseLatencyCycles = baseLatencyCycles; + this.latencyJitterMask = latencyJitterMask; + this.executor = Executors.newCachedThreadPool(r -> { + Thread t = new Thread(r, "ortho-sim-transport"); + t.setDaemon(true); + return t; + }); + } + + @Override + public void connect() { + connected = true; + } + + @Override + public void disconnect() { + connected = false; + } + + @Override + public boolean isConnected() { + return connected; + } + + @Override + public CompletableFuture submit(FabricCommand command) { + Objects.requireNonNull(command, "command"); + if (!connected) { + return CompletableFuture.failedFuture(new IllegalStateException("SimulatorTransport not connected")); + } + // Deterministic computation, optionally async with configurable delay simulation (not wall-clock cycles) + // We return immediately; cycles are architectural, not wall-clock. + FabricCompletion completion = deterministicCompletion(command); + return CompletableFuture.completedFuture(completion); + // To simulate async latency without breaking determinism, alternative: + // return CompletableFuture.supplyAsync(() -> deterministicCompletion(command), executor); + } + + private FabricCompletion deterministicCompletion(FabricCommand cmd) { + // Deterministic acceptedCycle derived from sequence and opcode + long acceptedCycle = 1000L + cmd.sequence() * 100L + cmd.opcode().ordinal() * 7L; + + // Deterministic extra cycles derived from hash + long hashMix = mixHash(cmd); + long extra = baseLatencyCycles + (hashMix & latencyJitterMask); + + // Tensor opcode has higher latency per spec: TLOAD/TSTORE 5, TMUL 4, but fabric-level TENSOR is 5 + scratchpad 2 + // We model that deterministically: add opcode-dependent constant + long opcodeCycles = switch (cmd.opcode()) { + case TENSOR -> 5L; + case EXECUTE -> 4L; + case STEP -> 1L; + case PROBE -> 2L; + case RESET -> 10L; + case ATTEST -> 8L; + }; + long completionCycle = acceptedCycle + opcodeCycles + extra + (cmd.length() % 3); + + String resultHash = sha256Hex(canonicalBytes(cmd) + "|result|" + completionCycle); + String traceRoot = sha256Hex(canonicalBytes(cmd) + "|trace|" + acceptedCycle + "|" + completionCycle); + + int status = 0; // deterministic success; could inject error for specific payloadHash if needed + if ("error".equals(cmd.payloadHash())) { + status = 1; + } + + return new FabricCompletion( + cmd.sequence(), + status, + acceptedCycle, + completionCycle, + resultHash, + traceRoot + ); + } + + private static long mixHash(FabricCommand cmd) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(canonicalBytes(cmd).getBytes(StandardCharsets.UTF_8)); + byte[] d = md.digest(); + // first 8 bytes as long big-endian + long v = 0; + for (int i = 0; i < 8; i++) { + v = (v << 8) | (d[i] & 0xFFL); + } + return v & Long.MAX_VALUE; + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + } + + private static String canonicalBytes(FabricCommand cmd) { + // Canonical deterministic string: all fields in fixed order + return cmd.opcode().name() + "|" + + cmd.device().value() + "|" + + Long.toHexString(cmd.address().value()) + "|" + + cmd.length() + "|" + + cmd.epoch().value() + "|" + + cmd.slot().value() + "|" + + cmd.sequence() + "|" + + cmd.payloadHash() + "|" + + cmd.flags(); + } + + private static String sha256Hex(String input) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8)); + return HexFormat.of().formatHex(hash); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/ortho-transport/src/main/java/org/ortho32/transport/TransportFactory.java b/ortho-transport/src/main/java/org/ortho32/transport/TransportFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..7dde68bb75c907650ea837d814246df0bfe726ed --- /dev/null +++ b/ortho-transport/src/main/java/org/ortho32/transport/TransportFactory.java @@ -0,0 +1,27 @@ +package org.ortho32.transport; + +/** + * Factory for transports. Applications should not use this directly; + * they should use {@link org.ortho32.OrthoRuntime}. This factory is for + * advanced use and testing. + */ +public final class TransportFactory { + + private TransportFactory() {} + + public static OrthoTransport simulator() { + return new SimulatorTransport(); + } + + public static OrthoTransport simulator(long baseLatencyCycles, long jitterMask) { + return new SimulatorTransport(baseLatencyCycles, jitterMask); + } + + public static OrthoTransport nativeHost() { + return new NativeHostTransport(); + } + + public static OrthoTransport ethernet(String host, int port) { + return new EthernetTransport(host, port); + } +} diff --git a/ortho-verify/build.gradle.kts b/ortho-verify/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..d1afb7375fe5ae62d11e0e292d31873a044d6e74 --- /dev/null +++ b/ortho-verify/build.gradle.kts @@ -0,0 +1,5 @@ +plugins { `java-library` } + +dependencies { + api(project(":ortho-trace")) +} diff --git a/ortho-verify/src/main/java/org/ortho32/verify/CrossVerificationResult.java b/ortho-verify/src/main/java/org/ortho32/verify/CrossVerificationResult.java new file mode 100644 index 0000000000000000000000000000000000000000..553f41849bf5390f09f2fddae6fc73af390b1d1f --- /dev/null +++ b/ortho-verify/src/main/java/org/ortho32/verify/CrossVerificationResult.java @@ -0,0 +1,50 @@ +package org.ortho32.verify; + +import java.util.Objects; + +/** + * Cross-verification result comparing Lean4 and HOL Light outcomes. + * MISMATCH is a blocking failure and cannot be auto-resolved. + * + * @param leanResult result from Lean4, may be null if UNCHECKED + * @param holResult result from HOL Light, may be null if UNCHECKED + * @param agreement agreement level, must not be null + */ +public record CrossVerificationResult( + TheoremResult leanResult, + TheoremResult holResult, + Agreement agreement) { + + /** + * Agreement between checkers. + */ + public enum Agreement { + /** Both checkers agree. */ + AGREE, + /** Checkers disagree; blocking failure. */ + MISMATCH, + /** At least one checker did not run. */ + UNCHECKED + } + + /** + * Compact canonical constructor. + * + * @param leanResult Lean result + * @param holResult HOL Light result + * @param agreement agreement + */ + public CrossVerificationResult { + Objects.requireNonNull(agreement, "agreement"); + if (agreement == Agreement.MISMATCH) { + // MISMATCH is blocking; caller must handle explicitly. + } + if (agreement == Agreement.AGREE) { + Objects.requireNonNull(leanResult, "leanResult required for AGREE"); + Objects.requireNonNull(holResult, "holResult required for AGREE"); + if (leanResult.status() != holResult.status() && !(leanResult.status().isFormallyVerified() && holResult.status().isFormallyVerified())) { + // Still AGREE only if both formally verified; otherwise mismatch would be used. + } + } + } +} diff --git a/ortho-verify/src/main/java/org/ortho32/verify/TheoremId.java b/ortho-verify/src/main/java/org/ortho32/verify/TheoremId.java new file mode 100644 index 0000000000000000000000000000000000000000..b3ef1d1e71fa791bffd1d6acc50e6388abe72414 --- /dev/null +++ b/ortho-verify/src/main/java/org/ortho32/verify/TheoremId.java @@ -0,0 +1,31 @@ +package org.ortho32.verify; + +/** + * Stable identifier for each of the 12 ORTHO-32 formal theorems. + */ +public enum TheoremId { + /** ISA is deterministic. */ + ISA_DETERMINISTIC, + /** Register R0 is invariant zero. */ + R0_INVARIANT, + /** Pipeline integrity is preserved. */ + PIPELINE_INTEGRITY_PRESERVED, + /** Forwarding correctness EX to MEM. */ + FORWARDING_CORRECTNESS_EX_MEM, + /** Forwarding correctness MEM to WB. */ + FORWARDING_CORRECTNESS_MEM_WB, + /** Branch flush correctness. */ + BRANCH_FLUSH_CORRECT, + /** RTL is deterministic. */ + RTL_DETERMINISTIC, + /** Single refinement step correctness. */ + REFINEMENT_STEP, + /** RTL refines ISA. */ + RTL_REFINES_ISA, + /** Tensor latency contract holds. */ + TENSOR_LATENCY_CONTRACT, + /** Scalar pipeline has no stalls under specified conditions. */ + SCALAR_NO_STALL, + /** Memory is deterministic. */ + MEMORY_DETERMINISTIC +} diff --git a/ortho-verify/src/main/java/org/ortho32/verify/TheoremResult.java b/ortho-verify/src/main/java/org/ortho32/verify/TheoremResult.java new file mode 100644 index 0000000000000000000000000000000000000000..f507fcb0f5c8024983f30afe62fe5e9d3e3baab2 --- /dev/null +++ b/ortho-verify/src/main/java/org/ortho32/verify/TheoremResult.java @@ -0,0 +1,46 @@ +package org.ortho32.verify; + +import java.util.List; +import java.util.Objects; + +/** + * Result of attempting to verify a theorem with a specific checker. + * + * @param id theorem identifier, must not be null + * @param system verification system used, must not be null + * @param status verification status, must not be null + * @param assumptions list of assumptions, never null, unmodifiable + * @param sourceHash hash of source artifact verified, must not be null + * @param sorryCount number of sorries/admits remaining, must be >=0 + * @param checkerVersion version string of checker, must not be null + */ +public record TheoremResult( + TheoremId id, + VerificationSystem system, + VerificationStatus status, + List assumptions, + String sourceHash, + int sorryCount, + String checkerVersion) { + + /** + * Compact canonical constructor. + * + * @param id theorem id + * @param system verification system + * @param status verification status + * @param assumptions assumptions + * @param sourceHash source hash + * @param sorryCount sorry count + * @param checkerVersion checker version + */ + public TheoremResult { + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(system, "system"); + Objects.requireNonNull(status, "status"); + Objects.requireNonNull(sourceHash, "sourceHash"); + Objects.requireNonNull(checkerVersion, "checkerVersion"); + assumptions = assumptions == null ? List.of() : List.copyOf(assumptions); + if (sorryCount < 0) throw new IllegalArgumentException("sorryCount must be >=0"); + } +} diff --git a/ortho-verify/src/main/java/org/ortho32/verify/VerificationReport.java b/ortho-verify/src/main/java/org/ortho32/verify/VerificationReport.java new file mode 100644 index 0000000000000000000000000000000000000000..c35207905183b7c3f826559317d3850828f01c13 --- /dev/null +++ b/ortho-verify/src/main/java/org/ortho32/verify/VerificationReport.java @@ -0,0 +1,38 @@ +package org.ortho32.verify; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Aggregated verification report. + * + * @param theorems list of theorem results, never null, unmodifiable + * @param timestamp report timestamp, must not be null + * @param checkerVersions map of system to version, never null, unmodifiable + * @param sourceHash hash of source tree, must not be null + */ +public record VerificationReport( + List theorems, + Instant timestamp, + Map checkerVersions, + String sourceHash) { + + /** + * Compact canonical constructor. + * + * @param theorems theorem results + * @param timestamp timestamp + * @param checkerVersions checker versions + * @param sourceHash source hash + */ + public VerificationReport { + Objects.requireNonNull(theorems, "theorems"); + Objects.requireNonNull(timestamp, "timestamp"); + Objects.requireNonNull(checkerVersions, "checkerVersions"); + Objects.requireNonNull(sourceHash, "sourceHash"); + theorems = List.copyOf(theorems); + checkerVersions = Map.copyOf(checkerVersions); + } +} diff --git a/ortho-verify/src/main/java/org/ortho32/verify/VerificationStatus.java b/ortho-verify/src/main/java/org/ortho32/verify/VerificationStatus.java new file mode 100644 index 0000000000000000000000000000000000000000..f6be1cb592cb730a2afcf42cc7feed2c6dc4703f --- /dev/null +++ b/ortho-verify/src/main/java/org/ortho32/verify/VerificationStatus.java @@ -0,0 +1,41 @@ +package org.ortho32.verify; + +/** + * Verification status with seven distinct, non-interchangeable states. + * Only {@link #VERIFIED} and {@link #CROSS_VERIFIED} are considered formally verified. + */ +public enum VerificationStatus { + /** Checked by a proof assistant with zero sorries. */ + VERIFIED, + /** Verified independently by both Lean4 and HOL Light with agreement. */ + CROSS_VERIFIED, + /** Executed tests passed. */ + TESTED, + /** Observed on hardware/simulator. */ + OBSERVED, + /** Assumed without proof. */ + ASSUMED, + /** Not yet verified. */ + UNVERIFIED, + /** Verification attempted and failed. */ + FAILED; + + /** + * Returns true only for VERIFIED and CROSS_VERIFIED. + * + * @return true if formally verified + */ + public boolean isFormallyVerified() { + return this == VERIFIED || this == CROSS_VERIFIED; + } + + /** + * Returns true if this status blocks a verified claim. + * All statuses except VERIFIED and CROSS_VERIFIED block verified claims. + * + * @return true if blocks verified claim + */ + public boolean blocksVerifiedClaim() { + return !isFormallyVerified(); + } +} diff --git a/ortho-verify/src/main/java/org/ortho32/verify/VerificationSystem.java b/ortho-verify/src/main/java/org/ortho32/verify/VerificationSystem.java new file mode 100644 index 0000000000000000000000000000000000000000..fd6491cd7685a71eb28daa1ccf950a3208a2c957 --- /dev/null +++ b/ortho-verify/src/main/java/org/ortho32/verify/VerificationSystem.java @@ -0,0 +1,11 @@ +package org.ortho32.verify; + +/** + * Verification system capable of checking theorems. + */ +public enum VerificationSystem { + /** Lean 4 checker. */ + LEAN4, + /** HOL Light checker. */ + HOL_LIGHT +} diff --git a/ortho-verify/src/main/java/org/ortho32/verify/VerifyClient.java b/ortho-verify/src/main/java/org/ortho32/verify/VerifyClient.java new file mode 100644 index 0000000000000000000000000000000000000000..2e78a991e5b5f1b9777c5fe886efcd6d204d8587 --- /dev/null +++ b/ortho-verify/src/main/java/org/ortho32/verify/VerifyClient.java @@ -0,0 +1,35 @@ +package org.ortho32.verify; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * Session sub-client for verification queries. + */ +public interface VerifyClient { + + /** + * Verifies a single theorem. + * + * @param id theorem identifier, must not be null + * @return future completing with theorem result + */ + CompletableFuture verify(TheoremId id); + + /** + * Returns the full verification report. + * + * @return future completing with report + */ + CompletableFuture report(); + + /** + * Cross-verifies a theorem with both Lean4 and HOL Light. + * A {@link CrossVerificationResult.Agreement#MISMATCH} must be treated as a blocking failure + * and cannot be auto-resolved by the caller. + * + * @param id theorem identifier, must not be null + * @return future completing with cross-verification result + */ + CompletableFuture crossVerify(TheoremId id); +} diff --git a/schemas/attestation.schema.json b/schemas/attestation.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..cb611616fcc173db88488439d986f7c45533fe24 --- /dev/null +++ b/schemas/attestation.schema.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ortho32.dev/schemas/attestation.schema.json", + "title": "Attestation", + "description": "Device attestation binding device, sequence, architectural cycles and proof to a signature.", + "type": "object", + "properties": { + "deviceId": { + "type": "string", + "description": "Stable device identifier" + }, + "sequence": { + "type": "integer", + "minimum": 0, + "description": "Sequence number attested" + }, + "cycles": { + "type": "integer", + "minimum": 0, + "description": "Architectural cycles for attested execution, integer logical time" + }, + "proofId": { + "type": "string", + "description": "Identifier of proof / theorem attested" + }, + "signature": { + "type": "string", + "pattern": "^[0-9a-fA-F]+$", + "description": "Hex-encoded signature over deviceId+sequence+cycles+proofId" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO-8601 timestamp of attestation" + } + }, + "required": ["deviceId", "sequence", "cycles", "proofId", "signature", "timestamp"], + "additionalProperties": false +} diff --git a/schemas/execution-trace.schema.json b/schemas/execution-trace.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..930bbf60432667da856867cda68c5e83a7c90d86 --- /dev/null +++ b/schemas/execution-trace.schema.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ortho32.dev/schemas/execution-trace.schema.json", + "title": "ExecutionTrace", + "description": "Immutable ordered list of CycleRecord. Same input must produce same canonical TraceHash across N runs (H=0 determinism). All cycles are architectural integers.", + "type": "array", + "items": { + "$ref": "#/$defs/CycleRecord" + }, + "$defs": { + "CycleRecord": { + "title": "CycleRecord", + "type": "object", + "properties": { + "cycleNumber": { + "type": "integer", + "minimum": 0, + "description": "Architectural cycle number, integer logical time, not wall-clock" + }, + "scalarStage": { + "type": "string", + "enum": ["IF", "ID", "EX", "MEM", "WB", "STALL", "FLUSH"], + "description": "Scalar pipeline stage occupancy" + }, + "tensorStage": { + "type": "string", + "enum": ["IDLE", "TLOAD", "TMUL", "TSTORE", "WAIT"], + "description": "Tensor pipeline stage" + }, + "commitStatus": { + "type": "string", + "enum": ["COMMITTED", "FLUSHED", "STALLED", "EXCEPTION"], + "description": "Commit status for this cycle" + }, + "registerWrites": { + "type": "array", + "description": "Register writes committed this cycle", + "items": { + "type": "object", + "properties": { + "register": { "type": "string" }, + "value": { "type": "string" } + }, + "required": ["register", "value"], + "additionalProperties": false + } + }, + "memoryEvents": { + "type": "array", + "description": "Memory read/write events", + "items": { + "type": "object", + "properties": { + "address": { "type": "string", "pattern": "^0x[0-9a-fA-F]+$" }, + "type": { "type": "string", "enum": ["READ", "WRITE"] }, + "value": { "type": "string" } + }, + "required": ["address", "type"], + "additionalProperties": false + } + }, + "pipelineState": { + "type": "object", + "description": "IF/ID/EX/MEM/WB occupancy, forwarding events, branch flushes", + "properties": { + "ifOccupied": { "type": "boolean" }, + "idOccupied": { "type": "boolean" }, + "exOccupied": { "type": "boolean" }, + "memOccupied": { "type": "boolean" }, + "wbOccupied": { "type": "boolean" }, + "forwarding": { "type": "array", "items": { "type": "string" } }, + "branchFlush": { "type": "boolean" } + }, + "additionalProperties": true + }, + "instructionId": { + "type": "string", + "description": "Instruction identifier retired or in flight" + } + }, + "required": ["cycleNumber", "scalarStage", "tensorStage", "commitStatus", "registerWrites", "memoryEvents"], + "additionalProperties": true + } + } +} diff --git a/schemas/fabric-command.schema.json b/schemas/fabric-command.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..50f3bf8281167368a121c7b3ecf6789a52b460f2 --- /dev/null +++ b/schemas/fabric-command.schema.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ortho32.dev/schemas/fabric-command.schema.json", + "title": "FabricCommand", + "description": "Logical fabric command submitted to ORTHO-32 fabric. Applications program against FabricCommand only and never write PCIe BAR registers directly.", + "type": "object", + "properties": { + "opcode": { + "type": "string", + "enum": ["EXECUTE", "STEP", "TENSOR", "PROBE", "RESET", "ATTEST"], + "description": "Fabric opcode. Logical API, not a BAR offset." + }, + "device": { + "type": "string", + "description": "Stable target device identifier (OrthoDeviceId)" + }, + "address": { + "type": "string", + "pattern": "^0x[0-9a-fA-F]+$", + "description": "Typed address in ORTHO memory map (FabricAddress), hex-encoded" + }, + "length": { + "type": "integer", + "minimum": 0, + "description": "Payload length in bytes" + }, + "epoch": { + "type": "integer", + "minimum": 0, + "description": "Epoch counter for deterministic arbitration (FabricEpoch)" + }, + "slot": { + "type": "integer", + "minimum": 0, + "description": "Slot in static grant table (FabricSlot)" + }, + "sequence": { + "type": "integer", + "minimum": 0, + "description": "Monotonic sequence number for completion matching" + }, + "payloadHash": { + "type": "string", + "pattern": "^[0-9a-fA-F]{64}$", + "description": "SHA-256 hex digest of payload (64 hex chars)" + }, + "flags": { + "type": "integer", + "minimum": 0, + "description": "Bit flags for command options" + } + }, + "required": ["opcode", "device", "address", "length", "epoch", "slot", "sequence", "payloadHash", "flags"], + "additionalProperties": false +} diff --git a/schemas/fabric-completion.schema.json b/schemas/fabric-completion.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..03cec7d0679efeacdfcc16c169f9f55cffa0bddd --- /dev/null +++ b/schemas/fabric-completion.schema.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ortho32.dev/schemas/fabric-completion.schema.json", + "title": "FabricCompletion", + "description": "Fabric completion for a FabricCommand. Cycles are architectural integers (deterministic logical time) not wall-clock nanoseconds. Completion is matched by sequence.", + "type": "object", + "properties": { + "sequence": { + "type": "integer", + "minimum": 0, + "description": "Echoed sequence from FabricCommand for matching" + }, + "status": { + "type": "string", + "enum": ["OK", "ERROR", "TIMEOUT", "REJECTED"], + "description": "Completion status" + }, + "acceptedCycle": { + "type": "integer", + "minimum": 0, + "description": "Architectural cycle when fabric accepted the command. Integer logical time, NOT wall-clock. Deterministic." + }, + "completionCycle": { + "type": "integer", + "minimum": 0, + "description": "Architectural cycle when command completed. Integer logical time, NOT wall-clock. Deterministic. Must be >= acceptedCycle." + }, + "resultHash": { + "type": "string", + "pattern": "^[0-9a-fA-F]{64}$", + "description": "SHA-256 hex of result payload" + }, + "traceRoot": { + "type": "string", + "pattern": "^[0-9a-fA-F]{64}$", + "description": "Merkle root (SHA-256 hex) of ExecutionTrace for this command" + } + }, + "required": ["sequence", "status", "acceptedCycle", "completionCycle", "resultHash", "traceRoot"], + "additionalProperties": false +} diff --git a/schemas/theorem-result.schema.json b/schemas/theorem-result.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..f17a97a5dcb80247d47631adec1eba2cf7dfd1d9 --- /dev/null +++ b/schemas/theorem-result.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ortho32.dev/schemas/theorem-result.schema.json", + "title": "TheoremResult", + "description": "Result for one of the 12 ORTHO-32 theorems from a specific proof system. Status is set only from real checker output; VERIFIED != TESTED != ASSUMED.", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Stable identifier for theorem (e.g., THM_001)" + }, + "system": { + "type": "string", + "enum": ["LEAN4", "HOL_LIGHT"], + "description": "Proof system that checked the theorem" + }, + "status": { + "type": "string", + "enum": ["VERIFIED", "CROSS_VERIFIED", "TESTED", "OBSERVED", "ASSUMED", "UNVERIFIED", "FAILED"], + "description": "VerificationStatus. VERIFIED requires zero sorries and successful checker run." + }, + "assumptions": { + "type": "array", + "items": { "type": "string" }, + "description": "Assumptions/axioms used" + }, + "sourceHash": { + "type": "string", + "pattern": "^[0-9a-fA-F]{64}$", + "description": "SHA-256 hex of source checked" + }, + "sorryCount": { + "type": "integer", + "minimum": 0, + "description": "Number of sorries/admitted axioms. Must be 0 for VERIFIED." + } + }, + "required": ["id", "system", "status", "assumptions", "sourceHash", "sorryCount"], + "additionalProperties": false +} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..85e1f243858ef0daf9b0342677dd24bca4fd8e41 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,16 @@ +rootProject.name = "ortho32-sdk-java" + +include( + ":ortho-fabric", + ":ortho-device", + ":ortho-transport", + ":ortho-compute", + ":ortho-tensor", + ":ortho-trace", + ":ortho-verify", + ":ortho-security", + ":ortho-agent", + ":ortho-routing", + ":ortho-core", + ":integration-tests" +)