diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8fc974e..f93cb306 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -168,9 +168,94 @@ jobs: - name: Run Echo release gate run: npm run release-gate:jedit-echo + edict-replace-range: + name: edict / replace-range package and pure runtime chain + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout Jedit + uses: actions/checkout@v6.0.2 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + path: jedit + + - name: Verify literal submitted head + working-directory: jedit + env: + EXPECTED_JEDIT_HEAD: ${{ github.event.pull_request.head.sha || github.sha }} + run: test "$(git rev-parse HEAD)" = "$EXPECTED_JEDIT_HEAD" + + - name: Read pinned toolchain commits + id: toolchain + working-directory: jedit + run: | + node --input-type=module <<'NODE' >> "$GITHUB_OUTPUT" + import { readFileSync } from "node:fs"; + const lock = JSON.parse( + readFileSync("edict/replace-range/edict.toolchain-lock.json", "utf8"), + ); + console.log(`edict_commit=${lock.edict.commit}`); + console.log(`echo_commit=${lock.echo.commit}`); + console.log(`rust_toolchain=${lock.rust.toolchain}`); + console.log(`node_version=${lock.validation.nodeVersion}`); + NODE + + - name: Checkout pinned Edict + uses: actions/checkout@v6.0.2 + with: + repository: flyingrobots/edict + ref: ${{ steps.toolchain.outputs.edict_commit }} + path: edict + + - name: Checkout pinned Echo + uses: actions/checkout@v6.0.2 + with: + repository: flyingrobots/echo + ref: ${{ steps.toolchain.outputs.echo_commit }} + path: echo + + - name: Setup Node.js + uses: actions/setup-node@v6.4.0 + with: + node-version: ${{ steps.toolchain.outputs.node_version }} + cache: 'npm' + cache-dependency-path: jedit/package-lock.json + + - name: Setup pinned Rust + run: rustup toolchain install '${{ steps.toolchain.outputs.rust_toolchain }}' --profile minimal + + - name: Install Jedit dependencies + working-directory: jedit + run: npm ci + + - name: Compile the pinned test-only pure runtime host + working-directory: jedit + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/runtime-target + run: cargo +${{ steps.toolchain.outputs.rust_toolchain }} test --locked --manifest-path edict/replace-range/tests/runtime/Cargo.toml --test compiled_boundary --no-run + + - name: Verify exact package and pure runtime chain with adversarial regressions + working-directory: jedit + env: + EDICT_REPO: ${{ github.workspace }}/edict + ECHO_REPO: ${{ github.workspace }}/echo + CARGO_TARGET_DIR: ${{ github.workspace }}/runtime-target + run: node --test edict/replace-range/tests/proof-harness.spec.mjs + + - name: Verify authoritative checkouts remain clean + run: | + for repository in jedit edict echo; do + if [[ -n "$(git -C "$repository" status --porcelain=v1 --untracked-files=all)" ]]; then + echo "$repository changed during the package-chain gate" >&2 + git -C "$repository" status --short >&2 + exit 1 + fi + done + check: name: check - needs: [ plan, build, test-shards, quality, release-gate ] + needs: [ plan, build, test-shards, quality, release-gate, edict-replace-range ] if: always() runs-on: ubuntu-latest @@ -182,9 +267,10 @@ jobs: TEST_SHARDS_RESULT: ${{ needs.test-shards.result }} QUALITY_RESULT: ${{ needs.quality.result }} RELEASE_GATE_RESULT: ${{ needs.release-gate.result }} + EDICT_REPLACE_RANGE_RESULT: ${{ needs.edict-replace-range.result }} run: | failed=0 - for result in "$PLAN_RESULT" "$BUILD_RESULT" "$QUALITY_RESULT"; do + for result in "$PLAN_RESULT" "$BUILD_RESULT" "$QUALITY_RESULT" "$EDICT_REPLACE_RANGE_RESULT"; do if [[ "$result" != "success" ]]; then failed=1 fi @@ -205,5 +291,6 @@ jobs: echo "| test shards | $TEST_SHARDS_RESULT |" echo "| quality | $QUALITY_RESULT |" echo "| release gate | $RELEASE_GATE_RESULT |" + echo "| edict / replace-range package chain | $EDICT_REPLACE_RANGE_RESULT |" } >> "$GITHUB_STEP_SUMMARY" exit "$failed" diff --git a/.gitignore b/.gitignore index 733790b0..ff1f05b4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ title-recordings/ .wesley-cache/ .jedit-cache/ .jedit/ +.build/ +.vendor.edict-lawpack-build.lock +.jedit-text.edict-lawpack-build.lock .obsidian/ .claude/ src/generated/jedit/structural-history-replace-text-range.wesley.generated.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e9687470..c36e2017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +- Added the Jedit-owned `ReplaceRange.edict` application root and canonical + `jedit.text@1` lawpack publication. With Edict #201, its executable integration + gate now proves that real source lowers into generic pure Target IR and reaches + Echo's checked lowerer through the exact generic pure target configuration. + Echo's generic pure-program lowerer now emits the exact compiler-produced + package, and its structurally separate verifier independently accepts the + package relation. The integration gate pins clean Edict and Echo revisions, + verifies the committed lawpack closure without repairing it, and rejects + authoritative-input mutation outside its disposable build root. Runtime + evaluation and rope mutation remain unimplemented. - Made `ReplaceRange` corroborate the selected Head/root byte extent and exact requested-range materialization before no-op classification, so truncated or absent retained roots fail with a typed malformed-rope obstruction. diff --git a/edict/replace-range/README.md b/edict/replace-range/README.md new file mode 100644 index 00000000..8e17aeba --- /dev/null +++ b/edict/replace-range/README.md @@ -0,0 +1,111 @@ +# ReplaceRange Edict application + +This directory is the Jedit-owned application root for issue #296. It keeps +the authored, built, and evidentiary artifact classes separate: + +- `edict.lawpack.json` is the reviewable `jedit.text@1` authoring input; +- `vendor/jedit-text/` is Edict's canonical published lawpack closure; +- `src/ReplaceRange.edict` is the application-owned source; +- `edict.toolchain-lock.json` pins the exact build implementations and + provider; +- `edict.build-lock.json` pins the exact source closure and emitted artifact + chain; +- `edict.executable-subject-lock.json` pins the verified executable meaning; +- `.build/` contains disposable provider and compiler outputs; +- `contracts/jedit/lawpacks/replace-range-v1/` remains the independent schema + and oracle corpus and is not executable input. + +## Current executable boundary + +The checked-in source is the first compiler-pressure slice, not a completed +text mutation. It binds exact-length nominal identities, the selected basis, +range, replacement bytes, operation profile, budget, imported helper +implementation, and a pure conditional into Edict Core. It intentionally does +not claim to traverse or rewrite a rope yet. + +With the merged Edict #201, the public application build lowers those Core `let` +nodes into generic, source-ordered Target IR and independently verifies the compiler-owned +result projection. The pinned Echo #724 candidate emits a distinct +`compiler-produced-bounded-pure/v1` executable package containing the exact +Core, lawpack exports, Target IR, and result projection. Echo's structurally +separate verifier independently reconstructs that package relation and emits an +accepted report bound to retained canonical `echo.executable-subject/v1` bytes. + +The compiler pin is Edict main `3f81f759e921a69b04fe8cf8e62e62f8f3dc7b7e`. +Core retains the imported nominal definitions and their shared exact-length +`NodeId` representation; record fields refer to those named definitions. +The nominal identity test follows that reference chain, and repeated public +builds must reproduce identical Core, Target, package, and verifier-report bytes. +The Echo provider remains pinned to the open #724 candidate +`49e9efb68001dfd78563d18bac9359a87671e431`; this evidence does not claim that +provider or this Jedit application has landed on its repository's main branch. + +The application gate passes that accepted package unchanged to Echo's generic +pure evaluator at `8c725d699241a7e3adee482029031ff6bade25fa`, the open #726 +candidate. A separate test-only Rust host consumes the fresh public-build +output, not the fixture retained in Echo. It checks both conditional branches, +the imported helper result, exact projected bytes, deterministic repetition, +the reversed-range constraint, malformed identity bytes, and package-pin +substitution. Its dependencies and their full resolution are pinned in +`tests/runtime/Cargo.toml` and `Cargo.lock` and bound into the build closure. + +This is pure computation only. No graph or rope is mutated, no Tick is settled, +and no WAL or recovery evidence is produced. The test host enables Echo's +trusted-host API only in this standalone unpublished test crate. It is not a +production dependency or an alternate editor route. The authored rope algorithm +and generic stateful execution remain unfinished under #296. + +## Reproduce + +The build script requires exact local Edict and Echo checkouts: + +```bash +EDICT_REPO=/path/to/edict \ +ECHO_REPO=/path/to/echo \ +CARGO_TARGET_DIR=/path/to/disposable-runtime-build-cache \ + ./edict/replace-range/tests/build.sh +``` + +`edict.toolchain-lock.json` pins the exact Edict commit and CLI release, Rust +toolchain, Echo commit, provider identity, provider manifest bytes, and lowerer +and verifier components. It also pins the Node and npm releases, the resolved +`cbor-x` package and integrity, and the package, lockfile, and CI invocation +bytes that perform validation. The script refuses non-Git roots, wrong commits, +dirty checkouts, or validation-environment drift before invoking either +toolchain. GitHub's package-chain job checks out and verifies the literal pull +request head; ordinary matrix jobs may separately exercise the synthetic merge. + +`edict.build-lock.json` binds the exact source and validation closure to the +Core, Target IR, result projection, executable package, verification report, +and executable-subject identities. The raw Echo report remains a separately +identified artifact. A canonical-JSON Jedit evidence envelope binds that artifact +and subject to the exact provider release, verifier component, diagnostic ABI, +report ABI, and outcome; that verifier-specific envelope is the transitional +`VerificationReportId`. `edict.executable-subject-lock.json` separately records +the subject identity and the exact package, Target IR, and result-projection +references it contains. The harness recomputes every digest from emitted bytes +and refuses a valid report paired with a substituted package or verifier. The +locks contain no self-referential Jedit commit; an exact PR head remains an +external review coordinate. + +The script verifies the committed `jedit.text@1` closure through Edict's public +lawpack `checkOnly` boundary. It never republishes or repairs that authoritative +tree. It then copies Echo's checked provider package into `.build/`, invokes +Edict's public application build, and requires the generic pure package and an +accepted independent-verifier report. Snapshots cover the application inputs +outside `.build/`, the pinned project validation files, and every tracked Edict +and Echo file; any content, identity, or timestamp mutation fails the run. Only +`.build/` is disposable output. `CARGO_TARGET_DIR` optionally selects a shared +disposable Cargo cache; otherwise runtime build output stays under `.build/`. +The runtime witness runs with `--locked` and must emit its completion marker +after all assertions pass, so a zero-test Cargo invocation cannot satisfy the +gate. The provider stays pinned to #724; the runtime is independently pinned +to #726. Neither is described as a released or installed product dependency. +Distinct nominal `BufferId` and `HeadId` +contracts retain the same exact 32-byte representation, while a negative +compiler witness proves that neither can cross the imported lawpack boundary as +the other. + +The next gate must exercise an authored rope consequence under generic Echo +stateful execution and compare it with the independent oracle. This pure +boundary witness is not a substitute for that evidence. diff --git a/edict/replace-range/edict.application.json b/edict/replace-range/edict.application.json new file mode 100644 index 00000000..f3e59f82 --- /dev/null +++ b/edict/replace-range/edict.application.json @@ -0,0 +1,20 @@ +{ + "schema": "edict.application/v1", + "coordinate": "jedit.text.replace_range@1", + "sources": [ + "src/ReplaceRange.edict" + ], + "lawpacks": [ + { + "manifest": "vendor/jedit-text/manifest.cbor", + "exports": "vendor/jedit-text/exports.cbor", + "adapter": "vendor/jedit-text/adapter.cbor", + "targetConfiguration": "vendor/jedit-text/echo-operation-configuration.cbor" + } + ], + "target": { + "profile": "echo.dpo@1", + "providerPackage": ".build/echo-provider" + }, + "outputDirectory": ".build/application" +} diff --git a/edict/replace-range/edict.build-lock.json b/edict/replace-range/edict.build-lock.json new file mode 100644 index 00000000..5cfba4c3 --- /dev/null +++ b/edict/replace-range/edict.build-lock.json @@ -0,0 +1,140 @@ +{ + "schema": "jedit.edict-build-lock/v1", + "applicationCoordinate": "jedit.text.replace_range@1", + "operationCoordinate": "jedit.text.replace_range@1.replaceRange", + "toolchainLockSha256": "5323f86f94f6b1ac5b1bb25c7104f703cc633ff3cc57df5caaa637a7a8455e75", + "sourceClosure": { + "schema": "jedit.edict-source-closure/v1", + "digest": "sha256:19017995722b8b038571f59b54cffb8929636a0115d148b266d51b6b769e7242", + "files": [ + { + "path": "application/edict.application.json", + "sha256": "3182577627cd1f88bf590f3a8481ef53476c83d783aa142182e7c65eb1f72f8b" + }, + { + "path": "application/edict.lawpack.json", + "sha256": "150abd5971b40ed70d2b1a1765a1a690e120ad9e44239dae732dc54e6ae04f99" + }, + { + "path": "application/edict.toolchain-lock.json", + "sha256": "5323f86f94f6b1ac5b1bb25c7104f703cc633ff3cc57df5caaa637a7a8455e75" + }, + { + "path": "application/src/ReplaceRange.edict", + "sha256": "51797e77062585a9f59083ee8a1da3faaf486ef7691e1b62203369a9d9356ee0" + }, + { + "path": "application/tests/assert-build-output.mjs", + "sha256": "e862848d2e3fdc0acb10b0e31fef4c6481ba9ed94ce6fb28a090b30fb47f43a2" + }, + { + "path": "application/tests/build.sh", + "sha256": "b5827a1e2a4b64b214406ee3d0cdc9499059922f9878649946c0358fbbd6ea6d" + }, + { + "path": "application/tests/package-chain.mjs", + "sha256": "cdb4d2d9661960b0b5890bf5bbae8b07e4f82365378ddb4c36342e42fad05927" + }, + { + "path": "application/tests/proof-harness.spec.mjs", + "sha256": "ada20b6786401dc629f141220ced5041bc694458821149f4197b0e69dc619fae" + }, + { + "path": "application/tests/runtime/Cargo.toml", + "sha256": "8cdf2a89e74d70388e872bbf30484053a639423c9b3c3934e99d67c3254fe53b" + }, + { + "path": "application/tests/runtime/Cargo.lock", + "sha256": "22699d9bfad35c2ecc1bcab2a6bf6b5eeb97cb596e0169ebd37cb18ab2f01b7c" + }, + { + "path": "application/tests/runtime/compiled_boundary.rs", + "sha256": "0bef519a74dbc9383dad7c1408d1fdaf962842c3f4df17bd3de58699154d818d" + }, + { + "path": "application/tests/verification-evidence.mjs", + "sha256": "3022f86f5c96d61f32ced3bec211d08ef7bbcdbc6e772cc61e2019eba0e42218" + }, + { + "path": "application/vendor/jedit-text/edict.lawpack-output.json", + "sha256": "141c0000932ba19cff687e4173e40b0a38410f70bd511c9bc51f3fb3f73ee62b" + }, + { + "path": "application/vendor/jedit-text/exports.cbor", + "sha256": "cab9c575da49cb33289e45fc45afb9d7f05558dada734be7eb3acfbbabb1d4db" + }, + { + "path": "application/vendor/jedit-text/exports.sha256", + "sha256": "9bb11d9f8153c18af53b484526d9980b05c27eb20aa97e7623d1bf1ed5a69aea" + }, + { + "path": "application/vendor/jedit-text/manifest.cbor", + "sha256": "0323ed8e9510e473d329166d421dc7d8e1da353065c1cd2dee77e6313ba9bf91" + }, + { + "path": "application/vendor/jedit-text/manifest.sha256", + "sha256": "316cf602502a45cbab89b77b6344854edee750eaf1f0c8167da48d19ead75c22" + }, + { + "path": "project/.github/workflows/ci.yml", + "sha256": "bc19286ef44ce7d8ba0fd8a3e39a8ec0313d07ed3084fbe15e25ef42d82bc8f1" + }, + { + "path": "project/package.json", + "sha256": "735a70d8e48f118695529446780d547f8f200dcb0fa453099aa2241d820d9eb5" + }, + { + "path": "project/package-lock.json", + "sha256": "d39f4b39e262e55355315e0a38ad2d10953ae5f6cd48b9fe3a530a128afd01d2" + } + ] + }, + "lawpackRelease": { + "coordinate": "jedit.text@1", + "digest": "sha256:95758c1605894672cc9069fde01bb8b6e11842b053102660c9cd4f4d6f34d64e" + }, + "provider": { + "coordinate": "echo.edict-provider@1", + "digest": "sha256:b065242a81179df02cd450763c8266e866bbca959308f5198b791a919050e0c2" + }, + "artifacts": { + "core": { + "coordinate": "jedit.text.replace_range@1", + "domain": "edict.core.module/v1", + "digest": "sha256:8150aeee1766bde5b2c416a3c9ef9dd4960bde140e9d1b190a966d00bbdf7787" + }, + "targetIr": { + "coordinate": "echo.span-ir/v1", + "domain": "edict.target-ir.artifact/v1", + "digest": "sha256:2c18d7d5e7d3bf9689e66e7b04c07824641836f6e23ed8396099ca8e24e79ff9" + }, + "resultProjection": { + "coordinate": "jedit.text.replace_range@1.replaceRange", + "domain": "edict.result-projection.artifact/v1", + "digest": "sha256:f804baa01c357e5ed54e86f752ea7f8201e454ad0b47c10813fde8c7dc2aeba6" + }, + "executablePackage": { + "coordinate": "executable-operation-package.echo", + "domain": "echo.operation-package/v1", + "digest": "sha256:b9052d3c40878a9fcba7768be67c8dba50cf0f751682339dfcdd04155871415b", + "rawSha256": "1860ab8a6cb9a4bfd63a1db99d1c2bc00aa53dae52a9d1998295654379a95d85" + }, + "verificationReportArtifact": { + "coordinate": "verifier-report.echo-operation", + "domain": "echo.operation-package-verifier-report/v1", + "digest": "sha256:12afbc22ce618b4e40b712235191657f7451a9dfc397531bd0448fbde5f3ae5c", + "rawSha256": "d96e5d21eecbc1e5bbf6ce76ab438d55cc8e74f6843121d4ffb0f4343e648267" + }, + "verificationReport": { + "coordinate": "jedit.edict-verification-evidence/v1", + "domain": "jedit.edict-verification-evidence/v1", + "digest": "sha256:ee2a9c8332640579d184c5bc16118e152490be8213dc0f23c955bab84fd08a18", + "rawSha256": "ab68a3e18ba9c83fb9988e5f7b9fc4e7a5b96a257218717ef4d66f7e2b8ba0e2" + }, + "executableSubject": { + "coordinate": "echo.executable-subject/v1", + "domain": "echo.executable-subject/v1", + "digest": "sha256:93d1c2ee6b9798648f2706ef6ffbb0c426afd886331d23d452605fbc3bcdaccb" + } + } +} diff --git a/edict/replace-range/edict.executable-subject-lock.json b/edict/replace-range/edict.executable-subject-lock.json new file mode 100644 index 00000000..d4edbb3d --- /dev/null +++ b/edict/replace-range/edict.executable-subject-lock.json @@ -0,0 +1,20 @@ +{ + "schema": "jedit.edict-executable-subject-lock/v1", + "reference": { + "coordinate": "echo.executable-subject/v1", + "digest": "sha256:93d1c2ee6b9798648f2706ef6ffbb0c426afd886331d23d452605fbc3bcdaccb" + }, + "rawSha256": "b36bf152e5bd5693f02e6eb1a58e698e332830218cf306719f11529422e74486", + "package": { + "coordinate": "executable-operation-package.echo", + "digest": "sha256:b9052d3c40878a9fcba7768be67c8dba50cf0f751682339dfcdd04155871415b" + }, + "targetIr": { + "coordinate": "echo.span-ir/v1", + "digest": "sha256:2c18d7d5e7d3bf9689e66e7b04c07824641836f6e23ed8396099ca8e24e79ff9" + }, + "resultProjection": { + "coordinate": "jedit.text.replace_range@1.replaceRange", + "digest": "sha256:f804baa01c357e5ed54e86f752ea7f8201e454ad0b47c10813fde8c7dc2aeba6" + } +} diff --git a/edict/replace-range/edict.lawpack.json b/edict/replace-range/edict.lawpack.json new file mode 100644 index 00000000..2652d166 --- /dev/null +++ b/edict/replace-range/edict.lawpack.json @@ -0,0 +1,214 @@ +{ + "schema": "edict.lawpack-build/v1", + "outputDirectory": "vendor/jedit-text", + "lawpack": { + "schema": "edict.lawpack-authoring/v1", + "id": "jedit.text", + "version": "1", + "acceptedCoreAbi": [ + "edict.core/v1" + ], + "dependencies": [], + "exportsCoordinate": "jedit.text.exports/v1", + "exports": { + "types": [ + { + "coordinate": "jedit.text@1.NodeId", + "definition": "Bytes" + }, + { + "coordinate": "jedit.text@1.BufferId", + "definition": "Nominal" + }, + { + "coordinate": "jedit.text@1.HeadId", + "definition": "Nominal" + }, + { + "coordinate": "jedit.text@1.ReplacementBytes", + "definition": "Bytes" + }, + { + "coordinate": "jedit.text@1.ObstructionDetail", + "definition": "String" + } + ], + "constants": [ + { + "coordinate": "jedit.text@1.maxCreatedLeaves", + "type": "U64", + "value": 4096 + }, + { + "coordinate": "jedit.text@1.maxCreatedBranches", + "type": "U64", + "value": 4096 + } + ], + "pureFunctions": [ + { + "source": "edict", + "coordinate": "jedit.text@1.maxCreatedLeafCount", + "typeParameters": [], + "parameterTypes": [], + "returnType": "U64", + "costTemplate": "jedit.text@1.replaceRangeBudget", + "determinismClass": "total", + "body": { + "params": [], + "body": { + "locals": [], + "bindings": [], + "result": { + "kind": "const", + "value": { + "kind": "int", + "width": "U64", + "value": 4096 + } + } + } + } + } + ], + "effects": [], + "obstructions": [ + { + "coordinate": "jedit.text@1.RangeOrderInvalid", + "authorityClass": "domainMappable", + "payloadSchema": "jedit.text@1.ObstructionDetail" + }, + { + "coordinate": "jedit.text@1.RangeOutOfBounds", + "authorityClass": "domainMappable", + "payloadSchema": "jedit.text@1.ObstructionDetail" + }, + { + "coordinate": "jedit.text@1.Utf8BoundaryInvalid", + "authorityClass": "domainMappable", + "payloadSchema": "jedit.text@1.ObstructionDetail" + }, + { + "coordinate": "jedit.text@1.NoOp", + "authorityClass": "domainMappable", + "payloadSchema": "jedit.text@1.ObstructionDetail" + }, + { + "coordinate": "jedit.text@1.BasisNotCanonical", + "authorityClass": "domainMappable", + "payloadSchema": "jedit.text@1.ObstructionDetail" + }, + { + "coordinate": "jedit.text@1.ArithmeticOverflow", + "authorityClass": "integrityFault", + "payloadSchema": "jedit.text@1.ObstructionDetail" + }, + { + "coordinate": "jedit.text@1.FactMissing", + "authorityClass": "resourceFault", + "payloadSchema": "jedit.text@1.ObstructionDetail" + }, + { + "coordinate": "jedit.text@1.FactMalformed", + "authorityClass": "integrityFault", + "payloadSchema": "jedit.text@1.ObstructionDetail" + }, + { + "coordinate": "jedit.text@1.ContentIdentityMismatch", + "authorityClass": "integrityFault", + "payloadSchema": "jedit.text@1.ObstructionDetail" + }, + { + "coordinate": "jedit.text@1.MalformedRope", + "authorityClass": "integrityFault", + "payloadSchema": "jedit.text@1.ObstructionDetail" + } + ], + "operationProfiles": { + "jedit.text@1.replaceRange": { + "opticTemplate": { + "opticKind": "affectReintegration", + "boundaryKind": "affect", + "supportPolicy": "jedit.text@1.basisBoundSupport", + "lossDisposition": "jedit.text@1.lossless", + "basisTemplate": "jedit.text@1.exactHeadBasis", + "apertureRequirement": { + "kind": "abstractFootprintObligation", + "reference": "jedit.text@1.replaceRangeFootprint" + } + }, + "effectPredicate": "jedit.text@1.replaceRangeEffects" + } + } + }, + "targetAdapters": [ + { + "coordinate": "jedit.text.echo-adapter/v1", + "output": "adapter.cbor", + "acceptedTargetProfile": { + "id": "echo.dpo@1", + "digest": "sha256:2e2494121aecf5e6a2d920f5fb85408825d394765fad41484c416397c920fb04" + }, + "acceptedTargetIr": { + "id": "echo.span-ir/v1", + "digest": "sha256:0057167e68f50c99dcce087b3e1cd677d17c5d1dc238bdb52d89469e1472fc2f" + }, + "operationProfiles": { + "jedit.text@1.replaceRange": { + "core": "continuum.profile.read-write/v1", + "semanticEffects": [], + "budgetObligation": "jedit.text@1.replaceRangeBudget", + "targetConfiguration": { + "local": "echo-config" + } + } + }, + "effectImplementations": {}, + "budgets": { + "jedit.text@1.replaceRangeBudget": { + "maxSteps": 1048576, + "maxAllocatedBytes": 16777216, + "maxOutputBytes": 8388608 + } + } + } + ], + "verifier": { + "class": "declarative", + "ruleset": { + "local": "verifier-rules" + } + }, + "compatibility": { + "id": "jedit.text.compatibility/v1", + "digest": "sha256:2adf83f798205f52636ad64ca45178f33a8e1cc663874f8a6113484c945e3052" + }, + "conformanceFixtureCorpus": { + "id": "jedit.text.ReplaceRange.oracle@1", + "digest": "sha256:b9b4707dd5e07897cbe7af36d88928401887e6af5d167d1a88a8d6181b4dbb0e" + }, + "localResources": [ + { + "name": "echo-config", + "coordinate": "echo.operation-lowering-configuration/v1", + "output": "echo-operation-configuration.cbor", + "value": { + "apiVersion": "echo.operation-lowering-configuration/v1", + "programKind": "compiler-produced-bounded-pure/v1" + } + }, + { + "name": "verifier-rules", + "coordinate": "jedit.text.verifier-rules/v1", + "output": "verifier-rules.cbor", + "value": { + "schema": "jedit.text.schema@1", + "schemaDigest": "sha256:f49b0ff6c9c7010d3a72b46734d47832853f496739a083edc41dddec3431bac7", + "oracleIsExecutableInput": false, + "runtimeOntology": "generic-bounded-graph" + } + } + ] + }, + "dependencyBundles": [] +} diff --git a/edict/replace-range/edict.toolchain-lock.json b/edict/replace-range/edict.toolchain-lock.json new file mode 100644 index 00000000..a3caa289 --- /dev/null +++ b/edict/replace-range/edict.toolchain-lock.json @@ -0,0 +1,59 @@ +{ + "schema": "jedit.edict-toolchain-lock/v1", + "rust": { + "toolchain": "1.94.0", + "rustcVersion": "rustc 1.94.0 (4a4ef493e 2026-03-02)", + "cargoVersion": "cargo 1.94.0 (85eff7c80 2026-01-15)" + }, + "validation": { + "nodeVersion": "22.23.1", + "processVersion": "v22.23.1", + "npmVersion": "10.9.8", + "cborX": { + "version": "1.6.4", + "integrity": "sha512-UGKHjp6RHC6QuZ2yy5LCKm7MojM4716DwoSaqwQpaH4DvZvbBTGcoDNTiG9Y2lByXZYFEs9WRkS5tLl96IrF1Q==" + }, + "files": [ + { + "path": "package.json", + "sha256": "735a70d8e48f118695529446780d547f8f200dcb0fa453099aa2241d820d9eb5" + }, + { + "path": "package-lock.json", + "sha256": "d39f4b39e262e55355315e0a38ad2d10953ae5f6cd48b9fe3a530a128afd01d2" + }, + { + "path": ".github/workflows/ci.yml", + "sha256": "bc19286ef44ce7d8ba0fd8a3e39a8ec0313d07ed3084fbe15e25ef42d82bc8f1" + } + ] + }, + "edict": { + "commit": "3f81f759e921a69b04fe8cf8e62e62f8f3dc7b7e", + "cliVersion": "0.11.0-alpha.1" + }, + "echo": { + "commit": "49e9efb68001dfd78563d18bac9359a87671e431", + "provider": { + "coordinate": "echo.edict-provider@1", + "digest": "sha256:b065242a81179df02cd450763c8266e866bbca959308f5198b791a919050e0c2", + "manifestSha256": "c5b9fb2fe3a0dc4dad282621a97413225c555be0071f3502b3272952069d42dc", + "targetProfile": { + "coordinate": "echo.dpo@1", + "digest": "sha256:2e2494121aecf5e6a2d920f5fb85408825d394765fad41484c416397c920fb04" + }, + "lowerer": { + "coordinate": "echo.dpo.lowerer/component@1", + "sha256": "e842063bd8d8ec12fff5b392a9d6bc646e2e3025c7a0c93726f53eed3cb5a0bf" + }, + "verifier": { + "coordinate": "echo.dpo.verifier/component@1", + "sha256": "174cf8758815bf2b9f2ef575517aa6d144ed3f2d1995fc65d25a211b9dea82d9", + "contract": { + "coordinate": "echo.dpo.verifier/v1", + "digest": "sha256:ca96b190728de3d668072ec1bd37d24e5197e7bd9bd54f70966d8c566b9b67f2" + } + } + } + } +} diff --git a/edict/replace-range/src/ReplaceRange.edict b/edict/replace-range/src/ReplaceRange.edict new file mode 100644 index 00000000..6079dbeb --- /dev/null +++ b/edict/replace-range/src/ReplaceRange.edict @@ -0,0 +1,41 @@ +package jedit.text.replace_range@1; + +use lawpack jedit.text@1 digest "sha256:95758c1605894672cc9069fde01bb8b6e11842b053102660c9cd4f4d6f34d64e" as text; + +type ReplaceRangeInput = { + bufferId: text.BufferId, + basisHeadId: text.HeadId, + startByte: U64, + endByte: U64, + replacement: text.ReplacementBytes, +}; + +type ReplaceRangeBoundary = { + bufferId: text.BufferId, + basisHeadId: text.HeadId, + startByte: U64, + endByte: U64, + replacement: text.ReplacementBytes, + rangeIsEmpty: U32, + createdLeafCeiling: U64, +}; + +intent replaceRange(input: ReplaceRangeInput) returns ReplaceRangeBoundary + profile text.replaceRange + basis input.basisHeadId + budget <= text.replaceRangeBudget + where input.startByte <= input.endByte +{ + let createdLeafCeiling: U64 = text.maxCreatedLeafCount(); + let rangeIsEmpty = if input.startByte == input.endByte then 1u32 else 0u32; + + return { + bufferId: input.bufferId, + basisHeadId: input.basisHeadId, + startByte: input.startByte, + endByte: input.endByte, + replacement: input.replacement, + rangeIsEmpty, + createdLeafCeiling, + }; +} diff --git a/edict/replace-range/tests/assert-build-output.mjs b/edict/replace-range/tests/assert-build-output.mjs new file mode 100644 index 00000000..7eb4a87d --- /dev/null +++ b/edict/replace-range/tests/assert-build-output.mjs @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: Apache-2.0 OR LicenseRef-MIND-UCAL-1.0 +// © James Ross Ω FLYING•ROBOTS + +import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; +import { readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; + +import { decode } from "cbor-x"; + +import { + canonicalArtifactDigest, + digestText, + verificationEvidence, +} from "./verification-evidence.mjs"; + +function assertResourceReference(reference, coordinate, digest) { + assert.equal(reference.id, coordinate); + assert.deepEqual(reference.digest, ["sha256", digest]); +} + +const SOURCE_CLOSURE_INPUTS = [ + ["application/edict.application.json", "edict.application.json"], + ["application/edict.lawpack.json", "edict.lawpack.json"], + ["application/edict.toolchain-lock.json", "edict.toolchain-lock.json"], + ["application/src/ReplaceRange.edict", "src/ReplaceRange.edict"], + ["application/tests/assert-build-output.mjs", "tests/assert-build-output.mjs"], + ["application/tests/build.sh", "tests/build.sh"], + ["application/tests/package-chain.mjs", "tests/package-chain.mjs"], + ["application/tests/proof-harness.spec.mjs", "tests/proof-harness.spec.mjs"], + ["application/tests/runtime/Cargo.toml", "tests/runtime/Cargo.toml"], + ["application/tests/runtime/Cargo.lock", "tests/runtime/Cargo.lock"], + ["application/tests/runtime/compiled_boundary.rs", "tests/runtime/compiled_boundary.rs"], + [ + "application/tests/verification-evidence.mjs", + "tests/verification-evidence.mjs", + ], + [ + "application/vendor/jedit-text/edict.lawpack-output.json", + "vendor/jedit-text/edict.lawpack-output.json", + ], + ["application/vendor/jedit-text/exports.cbor", "vendor/jedit-text/exports.cbor"], + [ + "application/vendor/jedit-text/exports.sha256", + "vendor/jedit-text/exports.sha256", + ], + ["application/vendor/jedit-text/manifest.cbor", "vendor/jedit-text/manifest.cbor"], + [ + "application/vendor/jedit-text/manifest.sha256", + "vendor/jedit-text/manifest.sha256", + ], + ["project/.github/workflows/ci.yml", "../../.github/workflows/ci.yml"], + ["project/package.json", "../../package.json"], + ["project/package-lock.json", "../../package-lock.json"], +]; + +function sha256(bytes) { + return createHash("sha256").update(bytes).digest(); +} + +function lengthPrefix(length) { + const prefix = Buffer.alloc(8); + prefix.writeBigUInt64LE(BigInt(length)); + return prefix; +} + +async function sourceClosure(applicationRoot) { + const hasher = createHash("sha256"); + hasher.update("jedit.edict-source-closure/v1\0"); + const files = []; + for (const [identityPath, relative] of SOURCE_CLOSURE_INPUTS) { + const bytes = await readFile(path.join(applicationRoot, relative)); + const relativeBytes = Buffer.from(identityPath, "utf8"); + hasher.update(lengthPrefix(relativeBytes.length)); + hasher.update(relativeBytes); + hasher.update(lengthPrefix(bytes.length)); + hasher.update(bytes); + files.push({ path: identityPath, sha256: sha256(bytes).toString("hex") }); + } + return { + schema: "jedit.edict-source-closure/v1", + digest: `sha256:${hasher.digest("hex")}`, + files, + }; +} + +const [outputDirectory, mode] = process.argv.slice(2); +assert.ok(outputDirectory, "pass the Edict application output directory"); +assert.ok( + mode === undefined || mode === "--write-locks", + "the only supported mode is --write-locks", +); +const applicationRoot = path.resolve(outputDirectory, "../.."); + +const packageBytes = await readFile( + path.join(outputDirectory, "executable-operation-package.cbor"), +); +const reportBytes = await readFile( + path.join(outputDirectory, "verification-report.cbor"), +); +const executablePackage = decode(packageBytes); +const report = decode(reportBytes); +const program = decode(executablePackage.program); + +const packageDigest = canonicalArtifactDigest( + "echo.operation-package/v1", + packageBytes, +); +assertResourceReference( + report.package, + "executable-operation-package.echo", + packageDigest, +); + +assert.equal(executablePackage.schema, "echo.operation-package/v1"); +assert.equal( + executablePackage.package_kind, + "compiler-produced-bounded-pure/v1", +); +assert.equal( + executablePackage.operation_coordinate, + "jedit.text.replace_range@1.replaceRange", +); +assert.equal(program.schema, "echo.compiler-produced-pure-program/v1"); +assert.equal(program.kind, "compiler-produced-bounded-pure/v1"); +assert.equal(program.intent, "replaceRange"); +for (const artifact of [ + "core_artifact", + "lawpack_exports_artifact", + "result_projection_artifact", + "target_ir_artifact", +]) { + assert.ok(Buffer.isBuffer(program[artifact]), `${artifact} must be retained`); + assert.ok(program[artifact].length > 0, `${artifact} must not be empty`); +} + +const coreDigest = canonicalArtifactDigest( + "edict.core.module/v1", + program.core_artifact, +); +const coreArtifact = decode(program.core_artifact); +assert.deepEqual(executablePackage.semantic_closure.core_identity, coreDigest); +assert.deepEqual( + executablePackage.semantic_closure.canonical_meaning_identity, + coreDigest, +); + +const targetIrDigest = canonicalArtifactDigest( + "edict.target-ir.artifact/v1", + program.target_ir_artifact, +); +assert.deepEqual( + executablePackage.semantic_closure.target_ir_identity, + targetIrDigest, +); +assertResourceReference(report.targetIr, "echo.span-ir/v1", targetIrDigest); + +const resultProjectionDigest = canonicalArtifactDigest( + "edict.result-projection.artifact/v1", + program.result_projection_artifact, +); + +assert.equal( + report.apiVersion, + "echo.operation-package-verifier-report/v1", +); +assert.equal(report.outcome, "accepted"); +assert.equal(report.diagnosticBytes.length, 0); +assert.equal( + report.applicationResultProjection.id, + "jedit.text.replace_range@1.replaceRange", +); +assertResourceReference( + report.applicationResultProjection, + "jedit.text.replace_range@1.replaceRange", + resultProjectionDigest, +); + +assert.equal( + report.executableSubject.reference.id, + "echo.executable-subject/v1", +); +assert.ok( + Buffer.isBuffer(report.executableSubject.bytes), + "the verifier report must retain the executable subject bytes", +); +const executableSubjectDigest = canonicalArtifactDigest( + "echo.executable-subject/v1", + report.executableSubject.bytes, +); +assertResourceReference( + report.executableSubject.reference, + "echo.executable-subject/v1", + executableSubjectDigest, +); +const executableSubject = decode(report.executableSubject.bytes); +assert.equal(executableSubject.apiVersion, "echo.executable-subject/v1"); +assert.deepEqual(executableSubject.package, report.package); +assert.deepEqual(executableSubject.targetIr, report.targetIr); +assert.deepEqual( + executableSubject.applicationResultProjection, + report.applicationResultProjection, +); + +const toolchainLockBytes = await readFile( + path.join(applicationRoot, "edict.toolchain-lock.json"), +); +const toolchainLock = JSON.parse(toolchainLockBytes); +const lawpackReleaseDigest = ( + await readFile( + path.join(applicationRoot, "vendor", "jedit-text", "manifest.sha256"), + "utf8", + ) +).trim(); +const reportDigest = canonicalArtifactDigest( + "echo.operation-package-verifier-report/v1", + reportBytes, +); +const reportArtifactId = { + coordinate: "verifier-report.echo-operation", + domain: "echo.operation-package-verifier-report/v1", + digest: digestText(reportDigest), + rawSha256: sha256(reportBytes).toString("hex"), +}; +const evidence = verificationEvidence({ + executableSubjectId: { + coordinate: report.executableSubject.reference.id, + digest: digestText(executableSubjectDigest), + }, + provider: toolchainLock.echo.provider, + reportArtifactId, + report, +}); +const computedBuildLock = { + schema: "jedit.edict-build-lock/v1", + applicationCoordinate: coreArtifact.coordinate, + operationCoordinate: executablePackage.operation_coordinate, + toolchainLockSha256: sha256(toolchainLockBytes).toString("hex"), + sourceClosure: await sourceClosure(applicationRoot), + lawpackRelease: { + coordinate: executablePackage.semantic_closure.lawpack_coordinate, + digest: lawpackReleaseDigest, + }, + provider: { + coordinate: toolchainLock.echo.provider.coordinate, + digest: toolchainLock.echo.provider.digest, + }, + artifacts: { + core: { + coordinate: coreArtifact.coordinate, + domain: "edict.core.module/v1", + digest: digestText(coreDigest), + }, + targetIr: { + coordinate: report.targetIr.id, + domain: "edict.target-ir.artifact/v1", + digest: digestText(targetIrDigest), + }, + resultProjection: { + coordinate: report.applicationResultProjection.id, + domain: "edict.result-projection.artifact/v1", + digest: digestText(resultProjectionDigest), + }, + executablePackage: { + coordinate: report.package.id, + domain: "echo.operation-package/v1", + digest: digestText(packageDigest), + rawSha256: sha256(packageBytes).toString("hex"), + }, + verificationReportArtifact: reportArtifactId, + verificationReport: evidence.identity, + executableSubject: { + coordinate: report.executableSubject.reference.id, + domain: "echo.executable-subject/v1", + digest: digestText(executableSubjectDigest), + }, + }, +}; +const computedSubjectLock = { + schema: "jedit.edict-executable-subject-lock/v1", + reference: { + coordinate: report.executableSubject.reference.id, + digest: digestText(executableSubjectDigest), + }, + rawSha256: sha256(report.executableSubject.bytes).toString("hex"), + package: { + coordinate: report.package.id, + digest: digestText(packageDigest), + }, + targetIr: { + coordinate: report.targetIr.id, + digest: digestText(targetIrDigest), + }, + resultProjection: { + coordinate: report.applicationResultProjection.id, + digest: digestText(resultProjectionDigest), + }, +}; + +const buildLockPath = path.join(applicationRoot, "edict.build-lock.json"); +const subjectLockPath = path.join( + applicationRoot, + "edict.executable-subject-lock.json", +); +if (mode === "--write-locks") { + await writeFile(buildLockPath, `${JSON.stringify(computedBuildLock, null, 2)}\n`); + await writeFile(subjectLockPath, `${JSON.stringify(computedSubjectLock, null, 2)}\n`); +} else { + assert.deepEqual( + JSON.parse(await readFile(buildLockPath, "utf8")), + computedBuildLock, + "the exact build closure differs from edict.build-lock.json", + ); + assert.deepEqual( + JSON.parse(await readFile(subjectLockPath, "utf8")), + computedSubjectLock, + "the executable subject differs from edict.executable-subject-lock.json", + ); +} diff --git a/edict/replace-range/tests/build.sh b/edict/replace-range/tests/build.sh new file mode 100755 index 00000000..00009a3b --- /dev/null +++ b/edict/replace-range/tests/build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +project_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd) +application_root="$project_root/edict/replace-range" + +exec node "$application_root/tests/package-chain.mjs" diff --git a/edict/replace-range/tests/package-chain.mjs b/edict/replace-range/tests/package-chain.mjs new file mode 100644 index 00000000..4dc9657f --- /dev/null +++ b/edict/replace-range/tests/package-chain.mjs @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: Apache-2.0 OR LicenseRef-MIND-UCAL-1.0 +// © James Ross Ω FLYING•ROBOTS + +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { + cp, + lstat, + mkdir, + readFile, + readlink, + realpath, + readdir, + rm, +} from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +import { decode } from "cbor-x"; + +const testsDirectory = path.dirname(fileURLToPath(import.meta.url)); +const applicationRoot = path.resolve(testsDirectory, ".."); +const projectRoot = path.resolve(applicationRoot, "../.."); +const lockPath = path.join(applicationRoot, "edict.toolchain-lock.json"); +const edictRepository = process.env.EDICT_REPO; +const echoRepository = process.env.ECHO_REPO; + +function run(command, arguments_, options = {}) { + const result = spawnSync(command, arguments_, { + cwd: options.cwd, + encoding: "utf8", + env: { ...process.env, ...options.env }, + input: options.input, + timeout: options.timeout ?? 120_000, + }); + if (options.emit !== false && result.stdout) { + process.stdout.write(result.stdout); + } + if (options.emit !== false && result.stderr) { + process.stderr.write(result.stderr); + } + if (result.error) { + throw result.error; + } + if (result.status !== 0) { + throw new Error(`${command} exited with status ${result.status}`); + } + return result.stdout.trim(); +} + +function git(repository, ...arguments_) { + return run("git", ["-C", repository, ...arguments_], { emit: false }); +} + +async function sha256(filePath) { + return createHash("sha256").update(await readFile(filePath)).digest("hex"); +} + +async function verifyGitCheckout(repository, expectedCommit, label) { + assert.ok(repository, `${label} repository path is required`); + const [requestedRoot, discoveredRoot] = await Promise.all([ + realpath(repository), + realpath(git(repository, "rev-parse", "--show-toplevel")), + ]); + assert.equal(discoveredRoot, requestedRoot, `${label} path must be the checkout root`); + assert.equal(git(repository, "rev-parse", "HEAD"), expectedCommit, `${label} commit mismatch`); + assert.equal( + git(repository, "status", "--porcelain=v1", "--untracked-files=all"), + "", + `${label} checkout must be clean`, + ); +} + +function providerArtifact(manifest, role) { + const matches = manifest.artifacts.filter((artifact) => artifact.role === role); + assert.equal(matches.length, 1, `provider must contain one ${role} artifact`); + return matches[0]; +} + +async function verifyProvider(lock) { + const providerRoot = path.join( + echoRepository, + "schemas", + "edict-provider", + "package", + "v1", + ); + await rejectSymlinks(providerRoot); + const manifestPath = path.join(providerRoot, "provider-manifest.echo.json"); + const manifest = JSON.parse(await readFile(manifestPath, "utf8")); + assert.equal(manifest.provider.coordinate, lock.coordinate); + assert.equal(manifest.provider.digest, lock.digest); + assert.equal(await sha256(manifestPath), lock.manifestSha256); + + for (const [role, fileName, expected] of [ + ["lowerer.echo-dpo", "lowerer.echo-dpo.component.wasm", lock.lowerer], + ["verifier.echo-dpo", "verifier.echo-dpo.component.wasm", lock.verifier], + ]) { + const artifact = providerArtifact(manifest, role); + assert.equal(artifact.resource.coordinate, expected.coordinate); + assert.equal(artifact.resource.digest, `sha256:${expected.sha256}`); + assert.equal( + await sha256(path.join(providerRoot, "components", fileName)), + expected.sha256, + ); + } + const targetProfileArtifact = providerArtifact( + manifest, + "target-profile.echo-dpo", + ); + assert.equal( + targetProfileArtifact.resource.coordinate, + lock.targetProfile.coordinate, + ); + assert.equal(targetProfileArtifact.resource.digest, lock.targetProfile.digest); + const targetProfile = decode( + await readFile( + path.join( + providerRoot, + "generated", + "primary", + "target-profile.echo-dpo.cbor", + ), + ), + ); + assert.equal(targetProfile.verifier.id, lock.verifier.contract.coordinate); + assert.equal( + `sha256:${Buffer.from(targetProfile.verifier.digest[1]).toString("hex")}`, + lock.verifier.contract.digest, + ); + return providerRoot; +} + +async function rejectSymlinks(root) { + for (const entry of await readdir(root, { withFileTypes: true })) { + const entryPath = path.join(root, entry.name); + assert.equal(entry.isSymbolicLink(), false, `symbolic link is forbidden: ${entryPath}`); + if (entry.isDirectory()) { + await rejectSymlinks(entryPath); + } + } +} + +async function collectDirectoryFiles(root, relative = "") { + const files = []; + const directory = path.join(root, relative); + for (const entry of await readdir(directory, { withFileTypes: true })) { + if (relative === "" && entry.name === ".build") { + continue; + } + const entryRelative = path.join(relative, entry.name); + if (entry.isDirectory()) { + files.push(...(await collectDirectoryFiles(root, entryRelative))); + } else { + files.push(entryRelative); + } + } + return files; +} + +function trackedFiles(repository) { + return git(repository, "ls-files", "-z") + .split("\0") + .filter(Boolean) + .sort(); +} + +async function snapshotFile(namespace, root, relative) { + const filePath = path.join(root, relative); + const metadata = await lstat(filePath, { bigint: true }); + const common = { + path: `${namespace}/${relative.split(path.sep).join("/")}`, + mode: metadata.mode.toString(), + size: metadata.size.toString(), + modifiedNanoseconds: metadata.mtimeNs.toString(), + inode: metadata.ino.toString(), + }; + if (metadata.isSymbolicLink()) { + return { ...common, kind: "symlink", target: await readlink(filePath) }; + } + if (metadata.isFile()) { + return { ...common, kind: "file", sha256: await sha256(filePath) }; + } + return { ...common, kind: "other" }; +} + +async function snapshotAuthoritativeInputs(lock) { + const roots = [ + ["jedit", applicationRoot, (await collectDirectoryFiles(applicationRoot)).sort()], + [ + "jedit-project", + projectRoot, + lock.validation.files.map((file) => file.path).sort(), + ], + ["edict", edictRepository, trackedFiles(edictRepository)], + ["echo", echoRepository, trackedFiles(echoRepository)], + ]; + const snapshot = []; + for (const [namespace, root, files] of roots) { + for (const relative of files) { + snapshot.push(await snapshotFile(namespace, root, relative)); + } + } + return snapshot; +} + +async function verifyVersions(lock) { + assert.equal(process.version, lock.validation.processVersion); + assert.equal(run("npm", ["--version"]), lock.validation.npmVersion); + const packageLock = JSON.parse( + await readFile(path.join(projectRoot, "package-lock.json")), + ); + assert.equal( + packageLock.packages["node_modules/cbor-x"].version, + lock.validation.cborX.version, + ); + assert.equal( + packageLock.packages["node_modules/cbor-x"].integrity, + lock.validation.cborX.integrity, + ); + for (const file of lock.validation.files) { + assert.equal( + await sha256(path.join(projectRoot, file.path)), + file.sha256, + `${file.path} differs from the validation-environment lock`, + ); + } + assert.equal( + run("rustc", [`+${lock.rust.toolchain}`, "--version"]), + lock.rust.rustcVersion, + ); + assert.equal( + run("cargo", [`+${lock.rust.toolchain}`, "--version"]), + lock.rust.cargoVersion, + ); + run( + "cargo", + [`+${lock.rust.toolchain}`, "build", "--locked", "-p", "edict-cli"], + { cwd: edictRepository }, + ); + const edictBinary = path.join(edictRepository, "target", "debug", "edict"); + const version = JSON.parse(run(edictBinary, ["--version"])); + assert.equal(version.version, lock.edict.cliVersion); + return edictBinary; +} + +async function build() { + const lock = JSON.parse(await readFile(lockPath, "utf8")); + assert.equal(lock.schema, "jedit.edict-toolchain-lock/v1"); + await verifyGitCheckout(edictRepository, lock.edict.commit, "Edict"); + await verifyGitCheckout(echoRepository, lock.echo.commit, "Echo"); + const providerSource = await verifyProvider(lock.echo.provider); + const before = await snapshotAuthoritativeInputs(lock); + let failure; + try { + const edictBinary = await verifyVersions(lock); + run(edictBinary, [], { + cwd: applicationRoot, + input: `${JSON.stringify({ + schema: "edict.compiler.settings/v1", + type: "compilerSettings", + operation: "build", + lawpack: "edict.lawpack.json", + checkOnly: true, + })}\n`, + }); + + const buildRoot = path.join(applicationRoot, ".build"); + const providerDestination = path.join(buildRoot, "echo-provider"); + const applicationOutput = path.join(buildRoot, "application"); + await rm(providerDestination, { recursive: true, force: true }); + await rm(applicationOutput, { recursive: true, force: true }); + await mkdir(providerDestination, { recursive: true }); + await cp(providerSource, providerDestination, { recursive: true }); + await rejectSymlinks(providerDestination); + + run(edictBinary, [], { + cwd: applicationRoot, + input: `${JSON.stringify({ + schema: "edict.compiler.settings/v1", + type: "compilerSettings", + operation: "build", + application: "edict.application.json", + })}\n`, + }); + run( + process.execPath, + [path.join(testsDirectory, "assert-build-output.mjs"), applicationOutput], + { cwd: applicationRoot }, + ); + const buildLock = JSON.parse( + await readFile(path.join(applicationRoot, "edict.build-lock.json"), "utf8"), + ); + const runtimeOutput = run( + "cargo", + [ + `+${lock.rust.toolchain}`, "test", "--locked", "--manifest-path", + path.join(testsDirectory, "runtime", "Cargo.toml"), + "--test", "compiled_boundary", "freshly_compiled_boundary_executes", + "--", "--exact", "--nocapture", + ], + { + cwd: applicationRoot, + timeout: 600_000, + env: { + CARGO_TARGET_DIR: process.env.CARGO_TARGET_DIR + ?? path.join(buildRoot, "runtime-target"), + JEDIT_VERIFIED_PACKAGE: path.join( + applicationOutput, "executable-operation-package.cbor", + ), + JEDIT_VERIFIED_PACKAGE_PIN: + buildLock.artifacts.executablePackage.digest.slice("sha256:".length), + }, + }, + ); + assert.ok( + runtimeOutput.includes("JEDIT_EDICT_PURE_RUNTIME_OK"), + "runtime conformance witness did not execute", + ); + } catch (error) { + failure = error; + } + + const after = await snapshotAuthoritativeInputs(lock); + let mutationFailure; + try { + assert.deepEqual(after, before, "authoritative inputs changed during package-chain build"); + } catch (error) { + mutationFailure = error; + } + if (failure && mutationFailure) { + throw new AggregateError( + [failure, mutationFailure], + "build failed and authoritative inputs changed", + ); + } + if (mutationFailure) { + throw mutationFailure; + } + if (failure) { + throw failure; + } +} + +await build(); diff --git a/edict/replace-range/tests/proof-harness.spec.mjs b/edict/replace-range/tests/proof-harness.spec.mjs new file mode 100644 index 00000000..ec9bb5e5 --- /dev/null +++ b/edict/replace-range/tests/proof-harness.spec.mjs @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: Apache-2.0 OR LicenseRef-MIND-UCAL-1.0 +// © James Ross Ω FLYING•ROBOTS + +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { cp, mkdtemp, mkdir, readFile, rm, symlink, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; + +import { decode, encode } from "cbor-x"; + +import { verificationEvidence } from "./verification-evidence.mjs"; + +const testDirectory = path.dirname(fileURLToPath(import.meta.url)); +const applicationRoot = path.resolve(testDirectory, ".."); +const projectRoot = path.resolve(applicationRoot, "../.."); +const edictRepository = process.env.EDICT_REPO; +const echoRepository = process.env.ECHO_REPO; + +function requireToolchainEnvironment() { + assert.ok(edictRepository, "EDICT_REPO must name the exact Edict checkout"); + assert.ok(echoRepository, "ECHO_REPO must name the exact Echo checkout"); +} + +async function fixture() { + const root = await mkdtemp(path.join(os.tmpdir(), "jedit-replace-range-proof-")); + const fixtureProject = path.join(root, "jedit"); + const fixtureApplication = path.join(fixtureProject, "edict", "replace-range"); + await mkdir(path.dirname(fixtureApplication), { recursive: true }); + await cp(applicationRoot, fixtureApplication, { + recursive: true, + filter: (source) => source !== path.join(applicationRoot, ".build"), + }); + await mkdir(path.join(fixtureProject, ".github", "workflows"), { + recursive: true, + }); + for (const relative of [ + ".github/workflows/ci.yml", + "package.json", + "package-lock.json", + ]) { + await cp(path.join(projectRoot, relative), path.join(fixtureProject, relative)); + } + await symlink( + path.join(projectRoot, "node_modules"), + path.join(fixtureProject, "node_modules"), + "dir", + ); + return { + root, + project: fixtureProject, + application: fixtureApplication, + async dispose() { + await rm(root, { recursive: true, force: true }); + }, + }; +} + +function runBuild(fixtureProject, overrides = {}) { + return spawnSync("bash", ["edict/replace-range/tests/build.sh"], { + cwd: fixtureProject, + encoding: "utf8", + env: { + ...process.env, + EDICT_REPO: edictRepository, + ECHO_REPO: echoRepository, + ...overrides, + }, + timeout: 600_000, + }); +} + +function assertCommandCompleted(result) { + assert.equal(result.error, undefined, result.error?.message); + assert.notEqual(result.status, null, `command terminated by ${result.signal}`); +} + +test("rejects_non_git_toolchain_inputs", { timeout: 120_000 }, async () => { + requireToolchainEnvironment(); + const subject = await fixture(); + try { + const fakeEdict = path.join(subject.root, "fake-edict"); + const fakeEcho = path.join(subject.root, "fake-echo"); + const fakeEdictBinary = path.join(fakeEdict, "target", "debug", "edict"); + const providerSource = path.join( + echoRepository, + "schemas", + "edict-provider", + "package", + "v1", + ); + const fakeProvider = path.join( + fakeEcho, + "schemas", + "edict-provider", + "package", + "v1", + ); + await mkdir(path.dirname(fakeEdictBinary), { recursive: true }); + await mkdir(path.dirname(fakeProvider), { recursive: true }); + await symlink(path.join(edictRepository, "target", "debug", "edict"), fakeEdictBinary); + await cp(providerSource, fakeProvider, { recursive: true }); + + const result = runBuild(subject.project, { + EDICT_REPO: fakeEdict, + ECHO_REPO: fakeEcho, + }); + assertCommandCompleted(result); + assert.notEqual( + result.status, + 0, + "non-Git toolchain directories must be rejected before publication or application build", + ); + } finally { + await subject.dispose(); + } +}); + +test("rejects_wrong_commit_toolchain_inputs", { timeout: 120_000 }, async () => { + requireToolchainEnvironment(); + const subject = await fixture(); + try { + const lockPath = path.join(subject.application, "edict.toolchain-lock.json"); + const lock = JSON.parse(await readFile(lockPath, "utf8")); + lock.edict.commit = "0".repeat(40); + await writeFile(lockPath, `${JSON.stringify(lock, null, 2)}\n`); + + const result = runBuild(subject.project); + assertCommandCompleted(result); + assert.notEqual(result.status, 0, "a wrong Edict commit must be rejected"); + } finally { + await subject.dispose(); + } +}); + +test("lawpack_check_only_rejects_drift_without_repairing_it", { timeout: 120_000 }, async () => { + requireToolchainEnvironment(); + const subject = await fixture(); + try { + const digestPath = path.join( + subject.application, + "vendor", + "jedit-text", + "manifest.sha256", + ); + const corruptDigest = `sha256:${"0".repeat(64)}\n`; + await writeFile(digestPath, corruptDigest); + + const result = runBuild(subject.project); + assertCommandCompleted(result); + assert.notEqual(result.status, 0, "drift must fail instead of being repaired"); + assert.equal( + await readFile(digestPath, "utf8"), + corruptDigest, + "check-only validation must leave the drifted artifact untouched", + ); + } finally { + await subject.dispose(); + } +}); + +test( + "rejects_package_whose_recomputed_identity_differs_from_report_subject", + { timeout: 120_000 }, + async () => { + requireToolchainEnvironment(); + const subject = await fixture(); + try { + const build = runBuild(subject.project); + assertCommandCompleted(build); + assert.equal(build.status, 0, build.stderr); + + const outputDirectory = path.join(subject.application, ".build", "application"); + const packagePath = path.join(outputDirectory, "executable-operation-package.cbor"); + const executablePackage = decode(await readFile(packagePath)); + const program = decode(executablePackage.program); + const alteredCore = Buffer.from(program.core_artifact); + alteredCore[0] ^= 1; + program.core_artifact = alteredCore; + executablePackage.program = encode(program); + await writeFile(packagePath, encode(executablePackage)); + + const assertion = spawnSync( + process.execPath, + [path.join(subject.application, "tests", "assert-build-output.mjs"), outputDirectory], + { cwd: subject.project, encoding: "utf8", timeout: 30_000 }, + ); + assertCommandCompleted(assertion); + assert.notEqual( + assertion.status, + 0, + "a report for the original package must not authorize altered embedded Core bytes", + ); + } finally { + await subject.dispose(); + } + }, +); + +test( + "preserves_nominal_exact_length_buffer_and_head_identities", + { timeout: 120_000 }, + async () => { + requireToolchainEnvironment(); + const subject = await fixture(); + try { + const build = runBuild(subject.project); + assertCommandCompleted(build); + assert.equal(build.status, 0, build.stderr); + + const packageBytes = await readFile( + path.join( + subject.application, + ".build", + "application", + "executable-operation-package.cbor", + ), + ); + const executablePackage = decode(packageBytes); + const program = decode(executablePackage.program); + const core = decode(program.core_artifact); + for (const recordName of ["ReplaceRangeInput", "ReplaceRangeBoundary"]) { + const record = core.types[recordName]; + assert.equal(record.kind, "Record"); + for (const [field, contract] of [ + ["bufferId", "jedit.text@1.BufferId"], + ["basisHeadId", "jedit.text@1.HeadId"], + ]) { + assert.equal(record.fields[field], contract); + const nominal = core.types[record.fields[field]]; + assert.equal(nominal.kind, "Nominal"); + assert.equal(nominal.contract, contract); + assert.deepEqual( + core.types[nominal.representation], + { kind: "Bytes", min: 32, max: 32 }, + `${recordName}.${field} must resolve to its nominal exact 32-byte contract`, + ); + } + } + } finally { + await subject.dispose(); + } + }, +); + +test("freshly_compiled_package_must_execute_in_echo", { timeout: 120_000 }, async () => { + requireToolchainEnvironment(); + const subject = await fixture(); + try { + const result = runBuild(subject.project); + assertCommandCompleted(result); + assert.equal(result.status, 0, result.stderr); + assert.ok( + result.stdout.includes("JEDIT_EDICT_PURE_RUNTIME_OK"), + "a verified package alone is insufficient: the runtime conformance test must execute", + ); + } finally { + await subject.dispose(); + } +}); + +test("repeated_public_builds_preserve_core_and_package_bytes", { timeout: 120_000 }, async () => { + requireToolchainEnvironment(); + const subject = await fixture(); + try { + const outputDirectory = path.join(subject.application, ".build", "application"); + const readArtifacts = async () => { + const packageBytes = await readFile( + path.join(outputDirectory, "executable-operation-package.cbor"), + ); + const program = decode(decode(packageBytes).program); + return { + core: program.core_artifact, + targetIr: program.target_ir_artifact, + projection: program.result_projection_artifact, + package: packageBytes, + report: await readFile(path.join(outputDirectory, "verification-report.cbor")), + }; + }; + const first = runBuild(subject.project); + assertCommandCompleted(first); + assert.equal(first.status, 0, first.stderr); + const firstBytes = await readArtifacts(); + const second = runBuild(subject.project); + assertCommandCompleted(second); + assert.equal(second.status, 0, second.stderr); + assert.deepEqual( + await readArtifacts(), + firstBytes, + "every artifact must be byte-identical for the same source and closure", + ); + } finally { + await subject.dispose(); + } +}); + +test("rejects_buffer_id_head_id_substitution", { timeout: 120_000 }, async () => { + requireToolchainEnvironment(); + const subject = await fixture(); + try { + const sourcePath = path.join( + subject.application, + "src", + "ReplaceRange.edict", + ); + const source = await readFile(sourcePath, "utf8"); + const crossed = source + .replace("bufferId: input.bufferId,", "bufferId: input.basisHeadId,") + .replace("basisHeadId: input.basisHeadId,", "basisHeadId: input.bufferId,"); + assert.notEqual( + crossed, + source, + "the negative witness must cross the two identities", + ); + await writeFile(sourcePath, crossed); + + const result = runBuild(subject.project); + assertCommandCompleted(result); + assert.notEqual(result.status, 0, "BufferId and HeadId must not be substitutable"); + assert.match( + `${result.stdout}\n${result.stderr}`, + /TypeMismatch/, + "the imported nominal boundary must reject the crossed assignment", + ); + } finally { + await subject.dispose(); + } +}); + +test("verification_report_identity_is_verifier_specific", () => { + const common = { + executableSubjectId: { + coordinate: "echo.executable-subject/v1", + digest: `sha256:${"1".repeat(64)}`, + }, + reportArtifactId: { + coordinate: "verifier-report.echo-operation", + domain: "echo.operation-package-verifier-report/v1", + digest: `sha256:${"2".repeat(64)}`, + rawSha256: "3".repeat(64), + }, + report: { + apiVersion: "echo.operation-package-verifier-report/v1", + diagnosticAbi: { + id: "edict.diagnostics/v1", + digest: ["sha256", Buffer.alloc(32, 4)], + }, + outcome: "accepted", + }, + }; + const provider = { + coordinate: "echo.edict-provider@1", + digest: `sha256:${"5".repeat(64)}`, + verifier: { + coordinate: "echo.dpo.verifier/component@1", + sha256: "6".repeat(64), + contract: { + coordinate: "echo.dpo.verifier/v1", + digest: `sha256:${"8".repeat(64)}`, + }, + }, + targetProfile: { + coordinate: "echo.dpo@1", + digest: `sha256:${"9".repeat(64)}`, + }, + }; + const original = verificationEvidence({ ...common, provider }); + const changed = verificationEvidence({ + ...common, + provider: { + ...provider, + verifier: { ...provider.verifier, sha256: "7".repeat(64) }, + }, + }); + assert.notEqual( + original.identity.digest, + changed.identity.digest, + "different verifier identities must produce different VerificationReportIds", + ); +}); + +test("rejects_drifted_build_and_executable_subject_locks", { timeout: 120_000 }, async () => { + requireToolchainEnvironment(); + const subject = await fixture(); + try { + const lockPath = path.join(subject.application, "edict.build-lock.json"); + const lock = JSON.parse(await readFile(lockPath, "utf8")); + lock.artifacts.core.digest = `sha256:${"0".repeat(64)}`; + await writeFile(lockPath, `${JSON.stringify(lock, null, 2)}\n`); + + const build = runBuild(subject.project); + assertCommandCompleted(build); + assert.notEqual( + build.status, + 0, + "a changed exact-artifact expectation must fail the package-chain gate", + ); + } finally { + await subject.dispose(); + } +}); + +test("rejects_drifted_executable_subject_lock", { timeout: 120_000 }, async () => { + requireToolchainEnvironment(); + const subject = await fixture(); + try { + const lockPath = path.join( + subject.application, + "edict.executable-subject-lock.json", + ); + const lock = JSON.parse(await readFile(lockPath, "utf8")); + lock.reference.digest = `sha256:${"0".repeat(64)}`; + await writeFile(lockPath, `${JSON.stringify(lock, null, 2)}\n`); + + const build = runBuild(subject.project); + assertCommandCompleted(build); + assert.notEqual( + build.status, + 0, + "a changed executable-subject expectation must fail the package-chain gate", + ); + } finally { + await subject.dispose(); + } +}); + +test("required_ci_executes_the_exact_package_chain", async () => { + const workflow = await readFile( + path.join(projectRoot, ".github", "workflows", "ci.yml"), + "utf8", + ); + assert.match(workflow, /^ edict-replace-range:\n/m); + assert.match(workflow, /name: edict \/ replace-range package and pure runtime chain/); + assert.match( + workflow, + /node --test edict\/replace-range\/tests\/proof-harness\.spec\.mjs/, + ); + assert.match( + workflow, + /ref: \$\{\{ github\.event\.pull_request\.head\.sha \|\| github\.sha \}\}/, + ); + assert.match(workflow, /git rev-parse HEAD/); + assert.match( + workflow, + /needs: \[ plan, build, test-shards, quality, release-gate, edict-replace-range \]/, + ); +}); diff --git a/edict/replace-range/tests/runtime/Cargo.lock b/edict/replace-range/tests/runtime/Cargo.lock new file mode 100644 index 00000000..3701589c --- /dev/null +++ b/edict/replace-range/tests/runtime/Cargo.lock @@ -0,0 +1,786 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "blake3" +version = "1.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae" +dependencies = [ + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.1", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cap-fs-ext" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78e5a3368ae89b7cb68186411452b4b9fac8b41be9c19bf3f47c2d2c8e36e6b" +dependencies = [ + "cap-primitives", + "cap-std", + "io-lifetimes 3.0.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "cap-primitives" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5f74729fd2f44701d1a8eb47e906cdb3ccd9ec0f02baad85a744b791940b18" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes 3.0.1", + "ipnet", + "maybe-owned", + "rustix", + "rustix-linux-procfs", + "windows-sys 0.61.2", + "winx", +] + +[[package]] +name = "cap-std" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7281235d6e96d3544ca18bba9049be92f4190f8d923e3caef1b5f66cfa752608" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes 3.0.1", + "rustix", +] + +[[package]] +name = "cc" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "echo-edict-canonical" +version = "0.1.0" +source = "git+https://github.com/flyingrobots/echo?rev=8c725d699241a7e3adee482029031ff6bade25fa#8c725d699241a7e3adee482029031ff6bade25fa" +dependencies = [ + "hex", + "sha2", +] + +[[package]] +name = "echo-registry-api" +version = "0.1.0" +source = "git+https://github.com/flyingrobots/echo?rev=8c725d699241a7e3adee482029031ff6bade25fa#8c725d699241a7e3adee482029031ff6bade25fa" + +[[package]] +name = "echo-runtime-schema" +version = "0.1.0" +source = "git+https://github.com/flyingrobots/echo?rev=8c725d699241a7e3adee482029031ff6bade25fa#8c725d699241a7e3adee482029031ff6bade25fa" + +[[package]] +name = "echo-wasm-abi" +version = "0.1.0" +source = "git+https://github.com/flyingrobots/echo?rev=8c725d699241a7e3adee482029031ff6bade25fa#8c725d699241a7e3adee482029031ff6bade25fa" +dependencies = [ + "blake3", + "ciborium", + "echo-registry-api", + "half", + "serde", + "serde-value", + "thiserror 2.0.20", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" + +[[package]] +name = "fs-set-times" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +dependencies = [ + "io-lifetimes 2.0.4", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "io-extras" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20fd6de4ccfcc187e38bc21cfa543cb5a302cb86a8b114eb7f0bf0dc9f8ac00f" +dependencies = [ + "io-lifetimes 3.0.1", + "windows-sys 0.60.2", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + +[[package]] +name = "io-lifetimes" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0fb0570afe1fed943c5c3d4102d5358592d8625fda6a0007fdbe65a92fba96" + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "jedit-edict-runtime-conformance" +version = "0.0.0" +dependencies = [ + "echo-edict-canonical", + "warp-core", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustix-linux-procfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" +dependencies = [ + "once_cell", + "rustix", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "warp-core" +version = "0.1.1" +source = "git+https://github.com/flyingrobots/echo?rev=8c725d699241a7e3adee482029031ff6bade25fa#8c725d699241a7e3adee482029031ff6bade25fa" +dependencies = [ + "blake3", + "bytemuck", + "bytes", + "cap-fs-ext", + "cap-std", + "echo-edict-canonical", + "echo-registry-api", + "echo-runtime-schema", + "echo-wasm-abi", + "hex", + "rustc-hash", + "sha2", + "thiserror 1.0.69", + "warp-math", +] + +[[package]] +name = "warp-math" +version = "0.1.0" +source = "git+https://github.com/flyingrobots/echo?rev=8c725d699241a7e3adee482029031ff6bade25fa#8c725d699241a7e3adee482029031ff6bade25fa" +dependencies = [ + "libm", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winx" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" +dependencies = [ + "bitflags", + "windows-sys 0.59.0", +] + +[[package]] +name = "zerocopy" +version = "0.8.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] diff --git a/edict/replace-range/tests/runtime/Cargo.toml b/edict/replace-range/tests/runtime/Cargo.toml new file mode 100644 index 00000000..e8d8fd5d --- /dev/null +++ b/edict/replace-range/tests/runtime/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "jedit-edict-runtime-conformance" +version = "0.0.0" +edition = "2021" +publish = false + +# This standalone, test-only trusted host is never a product dependency. +[workspace] + +[dev-dependencies] +echo-edict-canonical = { git = "https://github.com/flyingrobots/echo", rev = "8c725d699241a7e3adee482029031ff6bade25fa" } +warp-core = { git = "https://github.com/flyingrobots/echo", rev = "8c725d699241a7e3adee482029031ff6bade25fa", features = ["trusted_runtime"] } + +[[test]] +name = "compiled_boundary" +path = "compiled_boundary.rs" diff --git a/edict/replace-range/tests/runtime/compiled_boundary.rs b/edict/replace-range/tests/runtime/compiled_boundary.rs new file mode 100644 index 00000000..8d229fb1 --- /dev/null +++ b/edict/replace-range/tests/runtime/compiled_boundary.rs @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 OR LicenseRef-MIND-UCAL-1.0 +// © James Ross Ω FLYING•ROBOTS +//! Test-only trusted host. No native Jedit planner or product host is linked. + +use std::{env, fs}; + +use echo_edict_canonical::{encode_canonical_cbor_v1 as encode, CanonicalValueV1 as Value}; +use warp_core::edict_pure::{evaluate, EvaluationError, EvaluationLimits}; + +const ID_BYTES: usize = 32; +const MAX_PACKAGE_BYTES: usize = 32_768; +const MAX_INPUT_BYTES: usize = 2_097_152; +const MAX_STEPS: u64 = 10_000; +const MAX_ALLOCATED_BYTES: u64 = 16_777_216; +const MAX_OUTPUT_BYTES: u64 = 2_097_152; + +fn record(fields: impl IntoIterator) -> Value { + Value::Map( + fields + .into_iter() + .map(|(name, value)| (Value::Text(name.into()), value)) + .collect(), + ) +} + +fn input(start: u64, end: u64, buffer_id_size: usize) -> Vec { + encode(&record([ + ("bufferId", Value::Bytes(vec![1; buffer_id_size])), + ("basisHeadId", Value::Bytes(vec![2; ID_BYTES])), + ("startByte", Value::Integer(start.into())), + ("endByte", Value::Integer(end.into())), + ("replacement", Value::Bytes("λ\r\n".as_bytes().to_vec())), + ])) + .expect("canonical input") +} + +fn limits() -> EvaluationLimits { + EvaluationLimits { + max_package_bytes: MAX_PACKAGE_BYTES, + max_input_bytes: MAX_INPUT_BYTES, + max_steps: MAX_STEPS, + max_allocated_bytes: MAX_ALLOCATED_BYTES, + max_output_bytes: MAX_OUTPUT_BYTES, + } +} + +#[test] +fn freshly_compiled_boundary_executes() { + // The driver validates the separate verifier report and build locks before + // supplying this pin. This fixture alone does not establish authorization. + let package = fs::read(env::var("JEDIT_VERIFIED_PACKAGE").expect("package path")) + .expect("fresh compiler package"); + let pin_text = env::var("JEDIT_VERIFIED_PACKAGE_PIN").expect("verified package pin"); + assert_eq!(pin_text.len(), ID_BYTES * 2); + let mut pin = [0; ID_BYTES]; + for (index, byte) in pin.iter_mut().enumerate() { + *byte = u8::from_str_radix(&pin_text[index * 2..index * 2 + 2], 16) + .expect("hexadecimal package pin"); + } + + // Literal expectations catch a skipped helper, constant result, wrong + // branch, or changed field projection without reproducing the evaluator. + for (start, end, range_is_empty) in [(7, 7, 1), (7, 11, 0)] { + let supplied = input(start, end, ID_BYTES); + let result = evaluate(&package, pin, &supplied, limits()).expect("pure result"); + let expected = record([ + ("bufferId", Value::Bytes(vec![1; ID_BYTES])), + ("basisHeadId", Value::Bytes(vec![2; ID_BYTES])), + ("startByte", Value::Integer(start.into())), + ("endByte", Value::Integer(end.into())), + ("replacement", Value::Bytes("λ\r\n".as_bytes().to_vec())), + ("rangeIsEmpty", Value::Integer(range_is_empty)), + ("createdLeafCeiling", Value::Integer(4096)), + ]); + assert_eq!(result.output, encode(&expected).expect("expected result")); + assert_eq!( + result, + evaluate(&package, pin, &supplied, limits()).unwrap() + ); + assert!(result.steps > 0); + assert!(result.allocated_bytes > 0); + } + assert_eq!( + evaluate(&package, pin, &input(11, 7, ID_BYTES), limits()), + Err(EvaluationError::InputConstraintFailed("where.0".into())) + ); + assert_eq!( + evaluate(&package, pin, &input(7, 11, ID_BYTES - 1), limits()), + Err(EvaluationError::InvalidInput) + ); + pin[0] ^= 1; + assert_eq!( + evaluate(&package, pin, &input(7, 11, ID_BYTES), limits()), + Err(EvaluationError::PackageIdentityMismatch) + ); + // The driver requires this marker, so zero discovered tests cannot pass. + println!("JEDIT_EDICT_PURE_RUNTIME_OK"); +} diff --git a/edict/replace-range/tests/verification-evidence.mjs b/edict/replace-range/tests/verification-evidence.mjs new file mode 100644 index 00000000..2286e294 --- /dev/null +++ b/edict/replace-range/tests/verification-evidence.mjs @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 OR LicenseRef-MIND-UCAL-1.0 +// © James Ross Ω FLYING•ROBOTS + +import { createHash } from "node:crypto"; + +import { encode } from "cbor-x"; + +export function canonicalArtifactDigest(domain, canonicalArtifactBytes) { + const preimage = Buffer.concat([ + Buffer.from([0x83]), + Buffer.from(encode("edict.digest/v1")), + Buffer.from(encode(domain)), + Buffer.from(canonicalArtifactBytes), + ]); + return createHash("sha256").update(preimage).digest(); +} + +export function digestText(bytes) { + return `sha256:${Buffer.from(bytes).toString("hex")}`; +} + +function canonicalJson(value) { + if (Array.isArray(value)) { + return value.map(canonicalJson); + } + if (value !== null && typeof value === "object") { + return Object.fromEntries( + Object.keys(value) + .sort() + .map((key) => [key, canonicalJson(value[key])]), + ); + } + return value; +} + +function resourceReference(reference) { + return { + coordinate: reference.id, + digest: digestText(reference.digest[1]), + }; +} + +export function verificationEvidence({ + executableSubjectId, + provider, + reportArtifactId, + report, +}) { + const envelope = { + apiVersion: "jedit.edict-verification-evidence/v1", + reportArtifact: reportArtifactId, + executableSubject: executableSubjectId, + providerRelease: { + coordinate: provider.coordinate, + digest: provider.digest, + }, + verifierComponent: { + coordinate: provider.verifier.coordinate, + digest: `sha256:${provider.verifier.sha256}`, + }, + verificationPolicy: { + apiVersion: "jedit.edict-verification-policy/v1", + verifierContract: provider.verifier.contract, + targetProfile: provider.targetProfile, + reportAbi: report.apiVersion, + diagnosticAbi: resourceReference(report.diagnosticAbi), + }, + outcome: report.outcome, + }; + const bytes = Buffer.from(JSON.stringify(canonicalJson(envelope)), "utf8"); + return { + envelope, + bytes, + identity: { + coordinate: "jedit.edict-verification-evidence/v1", + domain: "jedit.edict-verification-evidence/v1", + digest: digestText( + canonicalArtifactDigest("jedit.edict-verification-evidence/v1", bytes), + ), + rawSha256: createHash("sha256").update(bytes).digest("hex"), + }, + }; +} diff --git a/edict/replace-range/vendor/jedit-text/adapter.cbor b/edict/replace-range/vendor/jedit-text/adapter.cbor new file mode 100644 index 00000000..99e3ce62 Binary files /dev/null and b/edict/replace-range/vendor/jedit-text/adapter.cbor differ diff --git a/edict/replace-range/vendor/jedit-text/adapter.sha256 b/edict/replace-range/vendor/jedit-text/adapter.sha256 new file mode 100644 index 00000000..4260e281 --- /dev/null +++ b/edict/replace-range/vendor/jedit-text/adapter.sha256 @@ -0,0 +1 @@ +sha256:b8d9f15ef18dc99691ab1e034ca40a8b8ac52a1bc1b27597284f1edbaf84f049 diff --git a/edict/replace-range/vendor/jedit-text/echo-operation-configuration.cbor b/edict/replace-range/vendor/jedit-text/echo-operation-configuration.cbor new file mode 100644 index 00000000..ee60987a --- /dev/null +++ b/edict/replace-range/vendor/jedit-text/echo-operation-configuration.cbor @@ -0,0 +1 @@ +¢japiVersionx(echo.operation-lowering-configuration/v1kprogramKindx!compiler-produced-bounded-pure/v1 \ No newline at end of file diff --git a/edict/replace-range/vendor/jedit-text/echo-operation-configuration.sha256 b/edict/replace-range/vendor/jedit-text/echo-operation-configuration.sha256 new file mode 100644 index 00000000..a708943c --- /dev/null +++ b/edict/replace-range/vendor/jedit-text/echo-operation-configuration.sha256 @@ -0,0 +1 @@ +sha256:bcb811b2d00d9824a256a26e48c14401c87fff509f2f75deb2ba703003a3f6d4 diff --git a/edict/replace-range/vendor/jedit-text/edict.lawpack-output.json b/edict/replace-range/vendor/jedit-text/edict.lawpack-output.json new file mode 100644 index 00000000..59079533 --- /dev/null +++ b/edict/replace-range/vendor/jedit-text/edict.lawpack-output.json @@ -0,0 +1,67 @@ +{ + "schema": "edict.lawpack-output/v1", + "lawpackId": "jedit.text", + "lawpackVersion": "1", + "artifacts": [ + { + "path": "manifest.cbor", + "kind": "manifest", + "coordinate": "edict.lawpack/v1", + "digest": "sha256:95758c1605894672cc9069fde01bb8b6e11842b053102660c9cd4f4d6f34d64e" + }, + { + "path": "manifest.sha256", + "kind": "manifestDigest", + "coordinate": "edict.lawpack/v1", + "digest": "sha256:95758c1605894672cc9069fde01bb8b6e11842b053102660c9cd4f4d6f34d64e" + }, + { + "path": "exports.cbor", + "kind": "exports", + "coordinate": "jedit.text.exports/v1", + "digest": "sha256:cbbc79fd92f09f341a1fae221a2a869bb64e15c511af39333e0453cd87fe456a" + }, + { + "path": "exports.sha256", + "kind": "exportsDigest", + "coordinate": "jedit.text.exports/v1", + "digest": "sha256:cbbc79fd92f09f341a1fae221a2a869bb64e15c511af39333e0453cd87fe456a" + }, + { + "path": "echo-operation-configuration.cbor", + "kind": "localResource", + "coordinate": "echo.operation-lowering-configuration/v1", + "digest": "sha256:bcb811b2d00d9824a256a26e48c14401c87fff509f2f75deb2ba703003a3f6d4" + }, + { + "path": "echo-operation-configuration.sha256", + "kind": "localResourceDigest", + "coordinate": "echo.operation-lowering-configuration/v1", + "digest": "sha256:bcb811b2d00d9824a256a26e48c14401c87fff509f2f75deb2ba703003a3f6d4" + }, + { + "path": "verifier-rules.cbor", + "kind": "localResource", + "coordinate": "jedit.text.verifier-rules/v1", + "digest": "sha256:364fb4ff98ae64fcd8d793aae3d7cb133ff8a1ce661330cea47688b13a0a823f" + }, + { + "path": "verifier-rules.sha256", + "kind": "localResourceDigest", + "coordinate": "jedit.text.verifier-rules/v1", + "digest": "sha256:364fb4ff98ae64fcd8d793aae3d7cb133ff8a1ce661330cea47688b13a0a823f" + }, + { + "path": "adapter.cbor", + "kind": "adapter", + "coordinate": "jedit.text.echo-adapter/v1", + "digest": "sha256:b8d9f15ef18dc99691ab1e034ca40a8b8ac52a1bc1b27597284f1edbaf84f049" + }, + { + "path": "adapter.sha256", + "kind": "adapterDigest", + "coordinate": "jedit.text.echo-adapter/v1", + "digest": "sha256:b8d9f15ef18dc99691ab1e034ca40a8b8ac52a1bc1b27597284f1edbaf84f049" + } + ] +} diff --git a/edict/replace-range/vendor/jedit-text/exports.cbor b/edict/replace-range/vendor/jedit-text/exports.cbor new file mode 100644 index 00000000..80925499 Binary files /dev/null and b/edict/replace-range/vendor/jedit-text/exports.cbor differ diff --git a/edict/replace-range/vendor/jedit-text/exports.sha256 b/edict/replace-range/vendor/jedit-text/exports.sha256 new file mode 100644 index 00000000..87a0814b --- /dev/null +++ b/edict/replace-range/vendor/jedit-text/exports.sha256 @@ -0,0 +1 @@ +sha256:cbbc79fd92f09f341a1fae221a2a869bb64e15c511af39333e0453cd87fe456a diff --git a/edict/replace-range/vendor/jedit-text/manifest.cbor b/edict/replace-range/vendor/jedit-text/manifest.cbor new file mode 100644 index 00000000..7bbc2c45 Binary files /dev/null and b/edict/replace-range/vendor/jedit-text/manifest.cbor differ diff --git a/edict/replace-range/vendor/jedit-text/manifest.sha256 b/edict/replace-range/vendor/jedit-text/manifest.sha256 new file mode 100644 index 00000000..be4e2d8b --- /dev/null +++ b/edict/replace-range/vendor/jedit-text/manifest.sha256 @@ -0,0 +1 @@ +sha256:95758c1605894672cc9069fde01bb8b6e11842b053102660c9cd4f4d6f34d64e diff --git a/edict/replace-range/vendor/jedit-text/verifier-rules.cbor b/edict/replace-range/vendor/jedit-text/verifier-rules.cbor new file mode 100644 index 00000000..ef8836e5 --- /dev/null +++ b/edict/replace-range/vendor/jedit-text/verifier-rules.cbor @@ -0,0 +1 @@ +¤fschemasjedit.text.schema@1lschemaDigestxGsha256:f49b0ff6c9c7010d3a72b46734d47832853f496739a083edc41dddec3431bac7oruntimeOntologyugeneric-bounded-graphworacleIsExecutableInputô \ No newline at end of file diff --git a/edict/replace-range/vendor/jedit-text/verifier-rules.sha256 b/edict/replace-range/vendor/jedit-text/verifier-rules.sha256 new file mode 100644 index 00000000..f8f7a387 --- /dev/null +++ b/edict/replace-range/vendor/jedit-text/verifier-rules.sha256 @@ -0,0 +1 @@ +sha256:364fb4ff98ae64fcd8d793aae3d7cb133ff8a1ce661330cea47688b13a0a823f diff --git a/package-lock.json b/package-lock.json index 801e5a11..d76f6e89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@flyingrobots/bijou-i18n-tools-node": "^7.0.0", "@flyingrobots/bijou-i18n-tools-xlsx": "^7.0.0", "@types/node": "^22.0.0", + "cbor-x": "^1.6.4", "semver": "^7.8.5", "tsx": "^4.22.4", "typescript": "^5.9.3" diff --git a/package.json b/package.json index d0490ecb..8161bb24 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@flyingrobots/bijou-i18n-tools-node": "^7.0.0", "@flyingrobots/bijou-i18n-tools-xlsx": "^7.0.0", "@types/node": "^22.0.0", + "cbor-x": "^1.6.4", "semver": "^7.8.5", "tsx": "^4.22.4", "typescript": "^5.9.3"