From 5323e3ce4d240c440ddb17d14836ddbddb36f7a9 Mon Sep 17 00:00:00 2001 From: jeremydixon22 Date: Tue, 11 Aug 2026 06:26:44 -0400 Subject: [PATCH] Harden the pre-release developer path --- .github/pull_request_template.md | 9 + .github/workflows/aws-live-qualification.yml | 60 ++++ .github/workflows/ci.yml | 4 + .../python-runtime-qualification.yml | 3 + PUBLIC-EXPORT-MANIFEST.json | 79 +++-- README.md | 2 +- benchmarks/retrieval/README.md | 14 +- .../ripgrep-vs-vyral-local-2026-08-11.json | 68 ++--- docs/guides/source-native-retrieval.md | 21 ++ docs/maintainers/releasing.md | 10 + examples/README.md | 10 + examples/python/retrieval_migration.py | 172 +++++++++++ packaging/publication-cohort.json | 78 +++++ qualification/README.md | 7 + runtimes/python/README.md | 11 + runtimes/python/src/vyral_runtime/_starter.py | 2 +- runtimes/python/src/vyral_runtime/host/cli.py | 48 ++- runtimes/python/tests/test_host_cli.py | 23 +- scripts/audit-github-launch-controls.py | 99 ++++++- scripts/export-public-tree.py | 7 + scripts/test-audit-github-launch-controls.py | 39 +++ .../test-validate-aws-live-qualification.sh | 94 ++++++ scripts/validate-aws-live-qualification.sh | 202 +++++++++++++ scripts/verify-publication-cohort.py | 206 +++++++++++++ scripts/verify-publication-policy.py | 9 + scripts/verify-python-runtime-install.py | 27 +- scripts/verify-release-artifacts.sh | 12 + scripts/verify-source-quickstart.py | 276 ++++++++++++++++++ 28 files changed, 1511 insertions(+), 81 deletions(-) create mode 100644 .github/workflows/aws-live-qualification.yml create mode 100644 examples/python/retrieval_migration.py create mode 100644 packaging/publication-cohort.json create mode 100755 scripts/test-validate-aws-live-qualification.sh create mode 100755 scripts/validate-aws-live-qualification.sh create mode 100644 scripts/verify-publication-cohort.py create mode 100644 scripts/verify-source-quickstart.py diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0b57a22..360ac44 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,6 +10,15 @@ Describe the portable behavior or documentation changed and why. - [ ] No credentials, customer data, local paths, generated artifacts, or deployment identities are included. - [ ] Release artifacts and public export were rehearsed when a package, client, container, or workflow changed. +## Public information review + +Does this change expose new public information? **No / Yes** + +If yes, identify the newly public design, strategy, operational detail, example, +diagram, evidence, or roadmap material and record why publication is intentional. +Internal planning and private operational context remain outside the public +repository unless their publication is explicitly authorized. + ## Compatibility and operations Call out breaking behavior, migrations, new permissions, deployment changes, and known limits. Write diff --git a/.github/workflows/aws-live-qualification.yml b/.github/workflows/aws-live-qualification.yml new file mode 100644 index 0000000..aa57311 --- /dev/null +++ b/.github/workflows/aws-live-qualification.yml @@ -0,0 +1,60 @@ +name: AWS Live Qualification + +on: + workflow_dispatch: + inputs: + provider_version: + description: AWS managed-service version label for the redacted receipt + required: true + type: string + +permissions: + contents: read + id-token: write + +concurrency: + group: aws-live-qualification + cancel-in-progress: false + +jobs: + qualify: + name: S3, DynamoDB, and SQS live gate + runs-on: ubuntu-latest + timeout-minutes: 35 + environment: aws-live-qualification + env: + DOTNET_CLI_TELEMETRY_OPTOUT: "1" + DOTNET_NOLOGO: "1" + VYRAL_AWS_LIVE_PROVIDER_VERSION: ${{ inputs.provider_version }} + VYRAL_AWS_LIVE_AUTH_MODE: github_oidc + VYRAL_AWS_LIVE_RESULT_PATH: artifacts/qualification/aws-live-gate.json + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Authenticate with a short-lived AWS role session + uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 + with: + role-to-assume: ${{ secrets.AWS_LIVE_ROLE_ARN }} + aws-region: ${{ secrets.AWS_LIVE_REGION }} + role-session-name: vyral-live-qualification + mask-aws-account-id: true + + - name: Set up .NET + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 + with: + dotnet-version: 10.0.x + + - name: Restore + run: dotnet restore Vyral.sln + + - name: Run isolated AWS live qualification + run: scripts/validate-aws-live-qualification.sh + + - name: Upload redacted live evidence + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: aws-live-gate-${{ github.sha }} + path: artifacts/qualification/aws-live-gate.json + if-no-files-found: warn diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f3a0e8..85200d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,6 +80,9 @@ jobs: # This module intentionally has no go.sum yet; setup-go's cache lookup warns on it. cache: false + - name: Verify the install-free source quickstart + run: python -I scripts/verify-source-quickstart.py + - name: Test JavaScript client working-directory: clients/javascript run: | @@ -119,6 +122,7 @@ jobs: python3 scripts/verify-public-sdk-surface.py python3 scripts/verify-public-contract-schemas.py python3 scripts/verify-markdown-links.py + python3 scripts/verify-publication-cohort.py python3 scripts/verify-publication-policy.py python3 scripts/generate-sdk-types.py python3 scripts/sync-python-runtime-contracts.py diff --git a/.github/workflows/python-runtime-qualification.yml b/.github/workflows/python-runtime-qualification.yml index 41aacaa..f4bc4d6 100644 --- a/.github/workflows/python-runtime-qualification.yml +++ b/.github/workflows/python-runtime-qualification.yml @@ -43,6 +43,9 @@ jobs: cache: pip cache-dependency-path: runtimes/python/pyproject.toml + - name: Verify the install-free source quickstart + run: python -I scripts/verify-source-quickstart.py + - name: Install qualification tooling run: >- python -m pip install diff --git a/PUBLIC-EXPORT-MANIFEST.json b/PUBLIC-EXPORT-MANIFEST.json index 12ffddd..20c0267 100644 --- a/PUBLIC-EXPORT-MANIFEST.json +++ b/PUBLIC-EXPORT-MANIFEST.json @@ -1,5 +1,5 @@ { - "fileCount": 837, + "fileCount": 844, "files": [ { "mode": "644", @@ -49,12 +49,17 @@ { "mode": "644", "path": ".github/pull_request_template.md", - "sha256": "ac9923dbb8f62d8b8daef7742187a99cdbade669c4c728de3c297e5e3915b48d" + "sha256": "2329c64ad1437e0590b25827751bb075b50c6b1bcb26aa07b9dc472553221827" + }, + { + "mode": "644", + "path": ".github/workflows/aws-live-qualification.yml", + "sha256": "4f6ce30537ce142d0140a24a435e75f66e9c07e3cc4665ea0a35640102418c98" }, { "mode": "644", "path": ".github/workflows/ci.yml", - "sha256": "f2ee6e2e92d2cd36fe5fc65928be57e2c77620c062c75ce7e867320937a66036" + "sha256": "56c55a1fa8d81450b3332c67dad30974568fe431a3e91e76530153c57d52df74" }, { "mode": "644", @@ -84,7 +89,7 @@ { "mode": "644", "path": ".github/workflows/python-runtime-qualification.yml", - "sha256": "8593187e599e4a4d3659c68aa2a0db339dbdc2df00558169fd48b6da8816e326" + "sha256": "31ad9f66d0d3207ab026809cc4f6c8d6d74459aa1218bced85134347e8a22868" }, { "mode": "644", @@ -134,7 +139,7 @@ { "mode": "644", "path": "README.md", - "sha256": "4181ac731be532f77e8044780902d57f925da784e169d147094ccc4a3311840e" + "sha256": "918e59fa888aab5ec1951d4522feda022dc324e938ca5ef4f3c715841ecfe16a" }, { "mode": "644", @@ -174,7 +179,7 @@ { "mode": "644", "path": "benchmarks/retrieval/README.md", - "sha256": "2eb3d5e8bdb8518fa3ddadf29b4deb6d29d1fb465f1172a63c0648cc46669b94" + "sha256": "12d5f1486cb4b3a8b142882a080d9d4e144e24af0d26ada479b004f467352d68" }, { "mode": "644", @@ -184,7 +189,7 @@ { "mode": "644", "path": "benchmarks/retrieval/ripgrep-vs-vyral-local-2026-08-11.json", - "sha256": "20fd30ca51b43ca7ac8a36a95be3dff346fd94af39c3d6fdc57ad1d0c2c4c273" + "sha256": "8bfcc43b4ba24f02be810860c7bc011814aa4f5d45a67df3a99fa95900687ba3" }, { "mode": "644", @@ -604,12 +609,12 @@ { "mode": "644", "path": "docs/guides/source-native-retrieval.md", - "sha256": "3bc7161ed4b1d83ee3a8461968bf7bc681ca49a38b4535ec81763629ce0e941e" + "sha256": "c6e22d6137d6d1544d752b722dc634c69f4429207cd298f2b590948528ba54a7" }, { "mode": "644", "path": "docs/maintainers/releasing.md", - "sha256": "a63504fa1f48133f87dc0f3871a5b925bfa1f29a0472f7684938568b44732f43" + "sha256": "888945792fed15e89bd1241c6d828b758a6daaba9130a79eeeff74bd8fc375ab" }, { "mode": "644", @@ -634,7 +639,7 @@ { "mode": "644", "path": "examples/README.md", - "sha256": "6ebe3db180f7c8c18bfa35fb433168cc6e4e8523848b39e0c20b15d7b570ef01" + "sha256": "a6b9159b28ace04cb37f1398e863235dd8ba87030e70899cedaf70ac5e5fd79f" }, { "mode": "644", @@ -666,6 +671,11 @@ "path": "examples/python/rag_quickstart.py", "sha256": "6210fcbcfaa8419b30576096cc48f82ff87a8c1de04e0acb3aa64d7edfdc7559" }, + { + "mode": "644", + "path": "examples/python/retrieval_migration.py", + "sha256": "dc7ae7b1fb37aacb9df88116535e8baf4261067775963eb5546638c34224d2f4" + }, { "mode": "644", "path": "examples/python/source_native_search.py", @@ -676,10 +686,15 @@ "path": "packaging/nuget/README.md", "sha256": "bb1b13e60a8bb75ef79976cf530f32719b4d37fabb1a8a0e9dc9570cb370751a" }, + { + "mode": "644", + "path": "packaging/publication-cohort.json", + "sha256": "fbb6e176bc2fdeff3b8fb071ee34565577ce4e13d8bacdf392fb6e830ef684fc" + }, { "mode": "644", "path": "qualification/README.md", - "sha256": "1adbe334d87252798238886fda01c0eb477f1e59f95831ed30c99de1aa4aea01" + "sha256": "dc72562fe45928550130c661474035f2f92eef0ca9ae82b58967d2ad70ee0759" }, { "mode": "644", @@ -699,7 +714,7 @@ { "mode": "644", "path": "runtimes/python/README.md", - "sha256": "f2ae828afaf0af80241b1acfc38f2f536d02d217fe129a6c08190707fe536b20" + "sha256": "7a092b02573f2b01bfa4ae5b53227db9e0075a5bae7047db8eafe4f8b9a6574e" }, { "mode": "644", @@ -839,7 +854,7 @@ { "mode": "644", "path": "runtimes/python/src/vyral_runtime/_starter.py", - "sha256": "7c628c66603796ecfc3e083221cccb9a4ba54da6739323e4cbd13b064349f5a4" + "sha256": "2f0a5b393b97cc813462199aa46f19c527bcb9f35726a02087ecf852f01bf96c" }, { "mode": "644", @@ -1019,7 +1034,7 @@ { "mode": "644", "path": "runtimes/python/src/vyral_runtime/host/cli.py", - "sha256": "a4216db3116918da043902525d84a8a73a1d9563e016b933310818636de813f0" + "sha256": "e9d168517ba4fa3b73368af7982f5175c7104101eec498c69435a522d8e1e530" }, { "mode": "644", @@ -1254,7 +1269,7 @@ { "mode": "644", "path": "runtimes/python/tests/test_host_cli.py", - "sha256": "38776f48036cd936a80a95e99a71361e290362d03c79caa37ad05c68243731cc" + "sha256": "1e81661bf783c45fa1f719a330ab435307ed2a9481c7e96319260d98581229ee" }, { "mode": "644", @@ -1459,7 +1474,7 @@ { "mode": "644", "path": "scripts/audit-github-launch-controls.py", - "sha256": "5a76b0a411c0ca89becfbbbf37cdcce5ffc8a7b637129d42a8ded8233afdf349" + "sha256": "56c8d03cbabbaa7bee80a5c73c23ac7a10bf4a44a38fa15503c8adc23d246750" }, { "mode": "755", @@ -1484,7 +1499,7 @@ { "mode": "755", "path": "scripts/export-public-tree.py", - "sha256": "3854825cf1d2c38c4a87633479d6b1a68f4209644430b78dcc0f487f70fc0ff5" + "sha256": "d846346a72c2c748f070c01ca544c5be9824339b0be86263b7c5cd90b1d65bd9" }, { "mode": "755", @@ -1574,7 +1589,7 @@ { "mode": "644", "path": "scripts/test-audit-github-launch-controls.py", - "sha256": "4314da8422e7ffa65f62c3c142deebf43dbbaf25b156c4affc12afc0ef2aecc3" + "sha256": "87b8ad83721e5c3ad30b00b1470c7bff04940594cb502aa580af5130c48f2bb5" }, { "mode": "755", @@ -1616,6 +1631,11 @@ "path": "scripts/test-scan-release-secrets.sh", "sha256": "9989cdae3cb02028d9b4a61750f3ee0dbfcd4bf9cbcf31cf29f13252d9c8a962" }, + { + "mode": "755", + "path": "scripts/test-validate-aws-live-qualification.sh", + "sha256": "74bc95e68629d76cea581c950e7fbc07c59b029491a6c8508e9a97845991145d" + }, { "mode": "755", "path": "scripts/test-validate-aws-opensearch-live.sh", @@ -1646,6 +1666,11 @@ "path": "scripts/validate-aws-execution-live.sh", "sha256": "567bcb9753925874312829c3054143045e8aa18809124731918c4957a1189d18" }, + { + "mode": "755", + "path": "scripts/validate-aws-live-qualification.sh", + "sha256": "652819a9a14acc628996525f065d9ed1427281b3fd8a41c892263576e1e71200" + }, { "mode": "755", "path": "scripts/validate-aws-opensearch-live.sh", @@ -1766,10 +1791,15 @@ "path": "scripts/verify-public-sdk-surface.py", "sha256": "342889e432211f89016bc3cdb0770140e531f4c12c120fded0a5fb4fd4a60aea" }, + { + "mode": "644", + "path": "scripts/verify-publication-cohort.py", + "sha256": "7855de2e76c8db7a456858c1a845d30424723fb0f6065a5d2a8cb646129f2385" + }, { "mode": "644", "path": "scripts/verify-publication-policy.py", - "sha256": "aaebed0e149ce65eb36333961abfa78a212dc8a5ac39b61c7e8d7c900e8eebff" + "sha256": "49657eb43f9963eff79c10f760c419c719016a9389d047daaedcf29473e5393f" }, { "mode": "755", @@ -1799,7 +1829,7 @@ { "mode": "755", "path": "scripts/verify-python-runtime-install.py", - "sha256": "e58289de89a7b26a1ec86cb30413351532f6f011d736814bf43e30150f8235fe" + "sha256": "857db8f58f699da296fc13e8c8117e11e5741d03cc551dfe73a3cd4b3278dba9" }, { "mode": "755", @@ -1829,7 +1859,7 @@ { "mode": "755", "path": "scripts/verify-release-artifacts.sh", - "sha256": "b84d3aa20a74f49ebce0f34b18c79099335dea9f6c9808eb9182febfc65cc92e" + "sha256": "35fa42e39f087c7a68ed50e357edbad3f9595ca01720d5c5a138a78ebca6d0c0" }, { "mode": "755", @@ -1841,6 +1871,11 @@ "path": "scripts/verify-runtime-conformance.py", "sha256": "14f4e46a8093804e522982f980d474441ce3829413cbb7b6923bddb7fac5764d" }, + { + "mode": "644", + "path": "scripts/verify-source-quickstart.py", + "sha256": "b04c73826aa710f4e9b5b9ed711cf178f918adc9edf4812ebc1aa4c1f4dce01b" + }, { "mode": "755", "path": "scripts/verify-version-policy.py", @@ -4189,5 +4224,5 @@ ], "schemaVersion": 1, "sourceDirty": false, - "treeSha256": "e6b7959543b600c6eed8e33b420b1cecd373519e2a212af9f81f7817bc9f5058" + "treeSha256": "1bb721ab03e70d9fefeaa9581469cb8755da58764705bca6a36b1e81acdd9aa4" } diff --git a/README.md b/README.md index a7ff3a9..2092a39 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Create a small editable application when you are ready to build: ```bash ./scripts/vyral init -python ./vyral_app.py +./scripts/vyral run ./vyral_app.py ``` The installed command is simply `vyral`; `vyral-runtime` remains a compatibility diff --git a/benchmarks/retrieval/README.md b/benchmarks/retrieval/README.md index 879ad8d..6319bba 100644 --- a/benchmarks/retrieval/README.md +++ b/benchmarks/retrieval/README.md @@ -26,22 +26,22 @@ justified. | Ambiguous single terms, 2 cases | 1.00 | `all` | 1.00 | | Queries expected to return nothing, 2 cases | 1.00 | `all` | 1.00 | -On this machine, ripgrep's warm p50/p95 was 16.734/33.398 ms. Indexed Vyral -lexical `all` was 1.172/1.508 ms, and the prefix policy was 1.292/1.738 ms. +On this machine, ripgrep's warm p50/p95 was 16.707/33.120 ms. Indexed Vyral +lexical `all` was 1.152/1.421 ms, and the prefix policy was 1.202/1.524 ms. Vyral is decisively faster after indexing. The default lexical `any` policy had -a 291.765 ms p95 because common fixture terms produced broad candidate sets; +a 286.457 ms p95 because common fixture terms produced broad candidate sets; its term-retrieval recall was 1.00, but returned precision was 0.425. The tradeoff appears before the first query. The ripgrep adapter initialized in -2.668 ms and used the existing source tree directly. Mirroring 2,016 documents -into the local Vyral store took 29,158.337 ms and produced a 13,324,288-byte +2.632 ms and used the existing source tree directly. Mirroring 2,016 documents +into the local Vyral store took 26,510.524 ms and produced a 13,324,288-byte database for a 288,995-byte fixture corpus. These ratios are fixture-specific; the small generated documents emphasize per-record overhead and should not be generalized to larger records. After a source edit, ripgrep returned the new canary without an index refresh -in 16.589 ms. Vyral correctly remained stale until the changed record was -upserted, then returned it in 1.851 ms. Sensitive-path canaries were excluded, +in 16.796 ms. Vyral correctly remained stale until the changed record was +upserted, then returned it in 2.052 ms. Sensitive-path canaries were excluded, the absolute root was not disclosed, and the adapter emitted line citations bound to source SHA-256 revisions. diff --git a/benchmarks/retrieval/ripgrep-vs-vyral-local-2026-08-11.json b/benchmarks/retrieval/ripgrep-vs-vyral-local-2026-08-11.json index 3244dcd..1876d87 100644 --- a/benchmarks/retrieval/ripgrep-vs-vyral-local-2026-08-11.json +++ b/benchmarks/retrieval/ripgrep-vs-vyral-local-2026-08-11.json @@ -36,60 +36,60 @@ "absentBeforeEdit": true, "path": "live/freshness.md", "ripgrepVisibleWithoutIndexRefresh": true, - "ripgrepWriteToResultMs": 16.589, - "vyralPostRefreshQueryMs": 1.851, - "vyralRecordRefreshMs": 17.377, + "ripgrepWriteToResultMs": 16.796, + "vyralPostRefreshQueryMs": 2.052, + "vyralRecordRefreshMs": 15.431, "vyralStaleBeforeRecordRefresh": true, - "vyralStaleQueryMs": 1.452, + "vyralStaleQueryMs": 1.418, "vyralVisibleAfterRecordRefresh": true }, - "generatedAtUtc": "2026-08-11T08:31:53.999398Z", + "generatedAtUtc": "2026-08-11T10:30:05.402797Z", "latencyMs": { "iterations": 30, "queriesPerIteration": 18, "variants": { "ripgrep-fixed": { - "coldFirstQueryMs": 17.152, + "coldFirstQueryMs": 33.288, "warmAllCasesMs": { "count": 540, - "max": 34.845, - "mean": 19.579, - "min": 16.212, - "p50": 16.734, - "p95": 33.398 + "max": 34.168, + "mean": 18.323, + "min": 16.206, + "p50": 16.707, + "p95": 33.12 } }, "vyral-lexical-all": { - "coldFirstQueryMs": 1.591, + "coldFirstQueryMs": 1.124, "warmAllCasesMs": { "count": 540, - "max": 1.84, - "mean": 1.147, - "min": 0.699, - "p50": 1.172, - "p95": 1.508 + "max": 1.98, + "mean": 1.106, + "min": 0.681, + "p50": 1.152, + "p95": 1.421 } }, "vyral-lexical-any": { - "coldFirstQueryMs": 2.231, + "coldFirstQueryMs": 2.539, "warmAllCasesMs": { "count": 540, - "max": 429.971, - "mean": 19.426, - "min": 0.701, - "p50": 1.492, - "p95": 291.765 + "max": 400.225, + "mean": 19.099, + "min": 0.704, + "p50": 1.538, + "p95": 286.457 } }, "vyral-lexical-prefix": { - "coldFirstQueryMs": 1.117, + "coldFirstQueryMs": 1.186, "warmAllCasesMs": { "count": 540, - "max": 2.296, - "mean": 1.289, - "min": 0.744, - "p50": 1.292, - "p95": 1.738 + "max": 2.142, + "mean": 1.218, + "min": 0.733, + "p50": 1.202, + "p95": 1.524 } } } @@ -1697,12 +1697,12 @@ }, "schemaVersion": "vyral.retrieval.ripgrep-comparison.v1", "setupMs": { - "ripgrepAdapterInitialize": 2.668, - "vyralRecordIngest": 29158.337, - "vyralStoreInitialize": 43.954, - "writeCorpus": 205.486 + "ripgrepAdapterInitialize": 2.632, + "vyralRecordIngest": 26510.524, + "vyralStoreInitialize": 34.567, + "writeCorpus": 204.953 }, - "sourceCommit": "9931243b07d18f0c959a7182455fdb032cf15904", + "sourceCommit": "468ce979b81b772b56809a154f5dad242c4ca262", "sourceDirty": false, "storageBytes": { "sourceCorpus": 288995, diff --git a/docs/guides/source-native-retrieval.md b/docs/guides/source-native-retrieval.md index dd458ea..4560d1d 100644 --- a/docs/guides/source-native-retrieval.md +++ b/docs/guides/source-native-retrieval.md @@ -105,6 +105,27 @@ or consistently lower query latency. The [retained report](../../benchmarks/retrieval/README.md) describes the fixture, limitations, complete metrics, and reproduction command. +## Executable migration walkthrough + +Run the bundled walkthrough from a source checkout: + +```bash +python3 examples/python/retrieval_migration.py +``` + +The first stage uses the bounded adapter to find one exact phrase directly in +an authorized Markdown tree. It also shows that a reordered version of that +phrase is not a fixed-string match. The second stage deliberately copies the +three example documents into a local Vyral collection and retrieves the +reordered query with lexical `all` matching and no embeddings. + +This is the intended migration boundary: do not build duplicate state while +direct search meets the measured need. Introduce an index when the application +needs governed records, partitions, filters, snapshots, order-tolerant terms, +prefixes, or lower repeated-query latency. Replace the walkthrough's authored +fixture with an application-owned ingestion policy; never turn arbitrary +filesystem traversal into an implicit REST or MCP capability. + ## Corpus comparison recipe Use a labeled fixture that resembles the real corpus. Keep the source revision, diff --git a/docs/maintainers/releasing.md b/docs/maintainers/releasing.md index 8d57ef0..08a014f 100644 --- a/docs/maintainers/releasing.md +++ b/docs/maintainers/releasing.md @@ -69,6 +69,16 @@ Each release candidate must be built from a reviewed commit in protected CI. Bef The version lines and maturity promises are defined in the [stability policy](../reference/stability.md) and enforced by `scripts/verify-version-policy.py`. Source versions do not prove registry publication. +The exact proposed first cohort is recorded in +[`packaging/publication-cohort.json`](../../packaging/publication-cohort.json). +CI requires `publicationAuthorized: false` and rejects drift between that +manifest and package metadata while the repository remains build-only. +Changing the manifest alone never authorizes publication: a separate reviewed +maintainer decision must introduce the exact protected-environment publishers, +registry trust relationships, signed release tag, and release notes. The +Python-native `vyral-runtime`, provider-specific packages, Temporal packages, +and prototype integrations remain outside this cohort. + Before the first public release, the repository owner must also configure the hosted controls that cannot be represented in source: protected release branches, required release-integrity, dependency-review, and CodeQL checks, Dependency Graph/Dependabot alerts, secret scanning with diff --git a/examples/README.md b/examples/README.md index b3f524b..1c096b2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -56,6 +56,16 @@ python3 examples/python/source_native_search.py \ --root . ``` +Run the complete source-native to governed-index walkthrough: + +```bash +python3 examples/python/retrieval_migration.py +``` + +It proves that an exact literal is immediately available without an index, +then deliberately copies the same small corpus into Vyral and retrieves a +reordered query through zero-embedding lexical `all` matching. + The example searches only allowlisted `*.py` and `*.md` paths by default and prints root-relative line citations with source revisions. It does not expose a REST or MCP tool. See the diff --git a/examples/python/retrieval_migration.py b/examples/python/retrieval_migration.py new file mode 100644 index 0000000..bb78136 --- /dev/null +++ b/examples/python/retrieval_migration.py @@ -0,0 +1,172 @@ +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import sys +import tempfile + + +sys.path.insert( + 0, + str(Path(__file__).resolve().parents[2] / "runtimes/python/src"), +) + +from vyral_runtime import VyralRuntime # noqa: E402 +from vyral_runtime.integrations.ripgrep import ( # noqa: E402 + RipgrepAdapterOptions, + RipgrepSearchAdapter, + RipgrepSearchRequest, +) + + +_COLLECTION = "retrieval-migration" +_PARTITION = "walkthrough" +_EXACT_QUERY = "accepted work survives restart" +_REORDERED_QUERY = "restart accepted work" +_DOCUMENTS = { + "execution.md": ( + "A durable receipt proves that accepted work survives restart before " + "a worker dispatches the handler." + ), + "retrieval.md": ( + "Lexical retrieval supports governed records, filters, stable " + "snapshots, and term-order tolerance." + ), + "operations.md": ( + "Source-native fixed-string search sees authorized text edits without " + "waiting for an index refresh." + ), +} + + +def _walkthrough() -> dict[str, object]: + with tempfile.TemporaryDirectory( + prefix="vyral-retrieval-migration-" + ) as temporary: + root = Path(temporary) + source_root = root / "knowledge" + source_root.mkdir() + for name, content in _DOCUMENTS.items(): + (source_root / name).write_text(content + "\n", encoding="utf-8") + + source = RipgrepSearchAdapter( + source_root, + RipgrepAdapterOptions(include_globs=("*.md",)), + ) + exact = source.search( + RipgrepSearchRequest(_EXACT_QUERY, limit=5) + ) + reordered = source.search( + RipgrepSearchRequest(_REORDERED_QUERY, limit=5) + ) + + with VyralRuntime.open_local(root / "indexed") as runtime: + runtime.records.create_collection({"name": _COLLECTION}) + for name, content in _DOCUMENTS.items(): + runtime.records.upsert_record( + _COLLECTION, + { + "id": name, + "partitionKey": _PARTITION, + "type": "source.document", + "metadata": {"path": name}, + "content": {"text": content}, + "sources": [ + { + "id": name, + "kind": "walkthrough", + "uri": f"vyral-example://retrieval/{name}", + "label": name, + } + ], + }, + ) + indexed = runtime.retrieval.search( + { + "query": _REORDERED_QUERY, + "collections": [_COLLECTION], + "partitionKeys": [_PARTITION], + "searchMode": "lexical", + "lexical": { + "fields": ["/content/text"], + "matchMode": "all", + }, + "limit": 5, + "includeTrace": True, + } + ) + + return { + "schemaVersion": "vyral.retrieval-migration-example.v1", + "sourceNative": { + "mode": "fixed-string", + "query": _EXACT_QUERY, + "matches": [ + { + "sourceUri": match.source_uri, + "sourceRevision": match.source_revision, + } + for match in exact.matches + ], + "reorderedQuery": _REORDERED_QUERY, + "reorderedMatchCount": len(reordered.matches), + "indexRequired": False, + }, + "indexed": { + "mode": "lexical-all", + "query": _REORDERED_QUERY, + "results": [ + { + "recordId": match.record.id, + "score": match.score, + "sourceUri": ( + match.record.sources[0].get("uri") + if match.record.sources + else None + ), + } + for match in indexed.results + ], + "embeddingUsed": False, + "governedPartition": _PARTITION, + }, + "decision": ( + "Keep source-native search while exactness and immediate " + "freshness are sufficient; copy intentionally selected " + "documents into Vyral when governed records, stable snapshots, " + "filters, or term-order tolerance justify an index." + ), + } + + +def main() -> None: + parser = argparse.ArgumentParser( + description=( + "Run the source-native to indexed-lexical migration walkthrough." + ) + ) + parser.add_argument("--json", action="store_true") + arguments = parser.parse_args() + result = _walkthrough() + if arguments.json: + print(json.dumps(result, indent=2, sort_keys=True)) + return + source = result["sourceNative"] + indexed = result["indexed"] + assert isinstance(source, dict) + assert isinstance(indexed, dict) + print( + "source-native: " + f"exact={len(source['matches'])} " + f"reordered={source['reorderedMatchCount']} index=none" + ) + print( + "indexed lexical: " + f"reordered={len(indexed['results'])} embeddings=unused" + ) + print(result["decision"]) + + +if __name__ == "__main__": + main() diff --git a/packaging/publication-cohort.json b/packaging/publication-cohort.json new file mode 100644 index 0000000..1f22d54 --- /dev/null +++ b/packaging/publication-cohort.json @@ -0,0 +1,78 @@ +{ + "schemaVersion": "vyral.publication-cohort.v1", + "publicationAuthorized": false, + "artifacts": [ + { + "ecosystem": "nuget", + "name": "Vyral.Abstractions", + "version": "0.3.0", + "source": "src/Vyral.Abstractions/Vyral.Abstractions.csproj", + "environment": "publish-nuget", + "maturity": "public" + }, + { + "ecosystem": "nuget", + "name": "Vyral.Local", + "version": "0.3.0", + "source": "src/Vyral.Local/Vyral.Local.csproj", + "environment": "publish-nuget", + "maturity": "public" + }, + { + "ecosystem": "nuget", + "name": "Vyral.Primitives", + "version": "0.2.0", + "source": "src/Vyral.Primitives/Vyral.Primitives.csproj", + "environment": "publish-nuget", + "maturity": "public" + }, + { + "ecosystem": "nuget", + "name": "Vyral.Execution", + "version": "0.2.0", + "source": "src/Vyral.Execution/Vyral.Execution.csproj", + "environment": "publish-nuget", + "maturity": "public" + }, + { + "ecosystem": "nuget", + "name": "Vyral.Execution.Local", + "version": "0.2.0", + "source": "src/Vyral.Execution.Local/Vyral.Execution.Local.csproj", + "environment": "publish-nuget", + "maturity": "preview" + }, + { + "ecosystem": "pypi", + "name": "vyral-client", + "version": "0.3.0", + "source": "clients/python/pyproject.toml", + "environment": "publish-pypi", + "maturity": "public" + }, + { + "ecosystem": "npm", + "name": "@vyral/client", + "version": "0.3.0", + "source": "clients/javascript/package.json", + "environment": "publish-npm", + "maturity": "public" + }, + { + "ecosystem": "container", + "name": "ghcr.io/univeracity/vyral-server", + "version": "0.3.0", + "source": "Dockerfile", + "environment": "publish-container", + "maturity": "mixed" + } + ], + "excludedFromFirstCohort": [ + "cloud-provider packages", + "provider-specific execution adapters", + "Temporal packages", + "vyral-runtime", + "prototype integrations" + ], + "authorizationBoundary": "A reviewed maintainer decision must replace the build-only publication policy and authorize exact registry publishers. Changing this file alone never authorizes a push." +} diff --git a/qualification/README.md b/qualification/README.md index e636e28..07087f9 100644 --- a/qualification/README.md +++ b/qualification/README.md @@ -22,6 +22,13 @@ operational receipt deliberately leaves this checked-in maturity baseline unchan the Google adapter remains a separate review that must validate the receipt, cleanup evidence, provider scope, and the full level requirements below. +The manual `AWS Live Qualification` workflow follows the same separation of duties. It uses a +short-lived GitHub OIDC session and an isolated least-privilege role to exercise S3, DynamoDB, and +SQS, then uploads only a redacted `aws-live-gate-` receipt. Raw provider logs, resource +identifiers, account identifiers, and credentials are not published. The receipt proves the live +gate and cleanup for that commit; it does not automatically promote an adapter or claim coverage +for managed OpenSearch or a consumer environment. + Generate the release copy with: ```bash diff --git a/runtimes/python/README.md b/runtimes/python/README.md index 5808c7a..d81ebc1 100644 --- a/runtimes/python/README.md +++ b/runtimes/python/README.md @@ -128,6 +128,17 @@ withheld, so direct source use or the editable install is the public pre-release path. Once a qualified wheel is published, the install command can be replaced with `python -m pip install vyral-runtime` without changing the local commands. +From a source checkout, run the generated application through the same launcher +that created it so no editable installation is required: + +```bash +./scripts/vyral init +./scripts/vyral run ./vyral_app.py +``` + +After installation, the equivalent commands are `vyral init` and +`vyral run ./vyral_app.py`. + The quickstart JSON includes measured `firstCitationMs`, `durableReceiptMs`, `restartRecoveryMs`, and `completedMs` milestones. Artifact qualification runs the installed wheel and sdist through the generated editable application, its diff --git a/runtimes/python/src/vyral_runtime/_starter.py b/runtimes/python/src/vyral_runtime/_starter.py index 30e08d6..188f0e4 100644 --- a/runtimes/python/src/vyral_runtime/_starter.py +++ b/runtimes/python/src/vyral_runtime/_starter.py @@ -121,7 +121,7 @@ def to_dict(self) -> dict[str, object]: "createdPath": str(self.created_path), "stateRootPath": str(self.state_root_path), "appId": self.app_id, - "runArguments": ["python", str(self.created_path)], + "runArguments": ["vyral", "run", str(self.created_path)], } diff --git a/runtimes/python/src/vyral_runtime/host/cli.py b/runtimes/python/src/vyral_runtime/host/cli.py index 65d8c05..2945740 100644 --- a/runtimes/python/src/vyral_runtime/host/cli.py +++ b/runtimes/python/src/vyral_runtime/host/cli.py @@ -6,6 +6,7 @@ import json import os from pathlib import Path +import runpy import sys from typing import Sequence @@ -33,6 +34,8 @@ def main( return _quickstart_main([], display_name) if selected and selected[0] == "init": return _init_main(selected[1:], display_name) + if selected and selected[0] == "run": + return _run_main(selected[1:], display_name) if selected and selected[0] == "quickstart": return _quickstart_main(selected[1:], display_name) if selected and selected[0] == "inspect": @@ -55,6 +58,7 @@ def _serve_main(argv: Sequence[str], display_name: str) -> int: f" {display_name} # run the local proof\n" f" {display_name} inspect\n" f" {display_name} init\n" + f" {display_name} run ./vyral_app.py\n" f" {display_name} serve --root ./.vyral\n\n" "The explicit 'serve' command is also accepted before the " "server options." @@ -227,17 +231,57 @@ def _init_main(argv: Sequence[str], display_name: str) -> int: except (OSError, ValueError) as error: parser.error(str(error)) if arguments.json: - print(json.dumps(result.to_dict(), indent=2, sort_keys=True)) + payload = result.to_dict() + payload["runArguments"] = [ + display_name, + "run", + str(result.created_path), + ] + print(json.dumps(payload, indent=2, sort_keys=True)) return 0 print(f"Created editable Vyral application: {result.created_path}") print("Run it:") - print(f" {sys.executable} {result.created_path}") + print(f" {display_name} run {result.created_path}") print(f"Durable state will remain visible at: {result.state_root_path}") print("Rerun unchanged to observe idempotent replay with no dispatch.") print("After editing the work, increment RUN_VERSION to admit a new run.") return 0 +def _run_main(argv: Sequence[str], display_name: str) -> int: + parser = argparse.ArgumentParser( + prog=f"{display_name} run", + description=( + "Run an explicit Python application with the active Vyral " + "runtime. This keeps source-checkout and installed commands " + "identical." + ), + ) + parser.add_argument("path", help="Python application to execute.") + parser.add_argument( + "arguments", + nargs=argparse.REMAINDER, + help="Arguments passed to the application.", + ) + arguments = parser.parse_args(argv) + target = Path(arguments.path).expanduser().resolve() + if target.suffix.casefold() != ".py": + parser.error("the application path must end with .py") + if not target.is_file(): + parser.error(f"application does not exist: {target}") + + previous_argv = sys.argv + previous_path = list(sys.path) + sys.argv = [str(target), *arguments.arguments] + sys.path.insert(0, str(target.parent)) + try: + runpy.run_path(str(target), run_name="__main__") + finally: + sys.argv = previous_argv + sys.path[:] = previous_path + return 0 + + def _quickstart_main(argv: Sequence[str], display_name: str) -> int: parser = argparse.ArgumentParser( prog=f"{display_name} quickstart", diff --git a/runtimes/python/tests/test_host_cli.py b/runtimes/python/tests/test_host_cli.py index e4947ce..a72e994 100644 --- a/runtimes/python/tests/test_host_cli.py +++ b/runtimes/python/tests/test_host_cli.py @@ -24,6 +24,7 @@ def test_help_discovers_local_single_player_commands(self) -> None: self.assertEqual(0, raised.exception.code) self.assertIn("vyral init", output.getvalue()) self.assertIn("vyral inspect", output.getvalue()) + self.assertIn("vyral run ./vyral_app.py", output.getvalue()) self.assertIn("vyral # run the local proof", output.getvalue()) def test_init_creates_an_editable_application_without_server_extra( @@ -48,11 +49,31 @@ def test_init_creates_an_editable_application_without_server_extra( result["stateRootPath"], ) self.assertEqual( - ["python", str(target.resolve())], + ["vyral", "run", str(target.resolve())], result["runArguments"], ) self.assertIn("@vyral(", target.read_text(encoding="utf-8")) + def test_source_launcher_run_executes_an_explicit_application(self) -> None: + with tempfile.TemporaryDirectory( + prefix="vyral-cli-run-" + ) as temporary: + target = Path(temporary) / "application.py" + target.write_text( + "from vyral_runtime import VyralRuntime\n" + "import sys\n" + "print(VyralRuntime().readiness().status, sys.argv[1:])\n", + encoding="utf-8", + ) + output = StringIO() + with redirect_stdout(output): + status = main( + ["run", str(target), "alpha", "beta"], + display_name="./scripts/vyral", + ) + self.assertEqual(0, status) + self.assertIn("ok ['alpha', 'beta']", output.getvalue()) + def test_quickstart_subcommand_does_not_require_server_extra(self) -> None: result = SimpleNamespace( root_path="/tmp/vyral-demo", diff --git a/scripts/audit-github-launch-controls.py b/scripts/audit-github-launch-controls.py index 2a28eae..0489f8f 100644 --- a/scripts/audit-github-launch-controls.py +++ b/scripts/audit-github-launch-controls.py @@ -24,6 +24,11 @@ "Review dependency changes", "Verify releasable artifacts", } +REQUIRED_BRANCH_RULE_TYPES = { + "deletion", + "non_fast_forward", + "pull_request", +} def _run(*arguments: str) -> subprocess.CompletedProcess[str]: @@ -111,10 +116,47 @@ def _required_checks(protection: Any) -> set[str]: return output +def _ruleset_rules(payload: Any) -> list[dict[str, Any]]: + if not isinstance(payload, list): + return [] + return [item for item in payload if isinstance(item, dict)] + + +def _ruleset_required_checks(rules: list[dict[str, Any]]) -> set[str]: + output: set[str] = set() + for rule in rules: + if rule.get("type") != "required_status_checks": + continue + parameters = rule.get("parameters") + if not isinstance(parameters, dict): + continue + checks = parameters.get("required_status_checks", []) + output.update( + str(item.get("context")) + for item in checks + if isinstance(item, dict) and item.get("context") + ) + return output + + +def _ruleset_protects_branch(rules: list[dict[str, Any]]) -> bool: + configured = {str(rule.get("type")) for rule in rules if rule.get("type")} + return REQUIRED_BRANCH_RULE_TYPES.issubset(configured) + + def _contains_required_checks(actual: set[str]) -> bool: return REQUIRED_CHECK_CONTEXTS.issubset(actual) +def _uses_squash_only_merge_policy(repository: Any) -> bool: + return ( + isinstance(repository, dict) + and repository.get("allow_squash_merge") is True + and repository.get("allow_merge_commit") is False + and repository.get("allow_rebase_merge") is False + ) + + def audit(repository: str) -> dict[str, Any]: repo_status, repo = _api(f"repos/{repository}") if repo_status != 200 or not isinstance(repo, dict): @@ -134,16 +176,45 @@ def audit(repository: str) -> dict[str, Any]: }, ) ) + controls.append( + _control( + "squash_only_merge_policy", + _uses_squash_only_merge_policy(repo), + state="pending", + evidence={ + "allowMergeCommit": repo.get("allow_merge_commit"), + "allowRebaseMerge": repo.get("allow_rebase_merge"), + "allowSquashMerge": repo.get("allow_squash_merge"), + }, + ) + ) protection_status, protection = _api( f"repos/{repository}/branches/{branch}/protection" ) - checks = _required_checks(protection) - protection_unavailable = protection_status in {403, 404} + rules_status, rules_payload = _api( + f"repos/{repository}/rules/branches/{branch}" + ) + rules = _ruleset_rules(rules_payload) + legacy_checks = _required_checks(protection) + ruleset_checks = _ruleset_required_checks(rules) + checks = legacy_checks | ruleset_checks + legacy_protected = protection_status == 200 + ruleset_protected = ( + rules_status == 200 and _ruleset_protects_branch(rules) + ) + protected = legacy_protected or ruleset_protected + protection_unavailable = ( + protection_status in {403, 404} + and rules_status in {403, 404} + ) + configured_rule_types = sorted( + str(rule.get("type")) for rule in rules if rule.get("type") + ) controls.append( _control( "protected_default_branch", - protection_status == 200, + protected, state=( "unavailable" if protection_unavailable @@ -151,19 +222,25 @@ def audit(repository: str) -> dict[str, Any]: ), evidence={ "branch": branch, - "httpStatus": protection_status, - "message": ( - protection.get("message") - if isinstance(protection, dict) - else None - ), + "branchProtectionHttpStatus": protection_status, + "rulesetHttpStatus": rules_status, + "configuredRuleTypes": configured_rule_types, + "requiredRuleTypes": sorted(REQUIRED_BRANCH_RULE_TYPES), + "sources": [ + source + for source, enabled in ( + ("branch_protection", legacy_protected), + ("ruleset", ruleset_protected), + ) + if enabled + ], }, ) ) controls.append( _control( "required_release_checks", - protection_status == 200 and _contains_required_checks(checks), + protected and _contains_required_checks(checks), state=( "unavailable" if protection_unavailable @@ -171,6 +248,8 @@ def audit(repository: str) -> dict[str, Any]: ), evidence={ "configured": sorted(checks), + "branchProtectionConfigured": sorted(legacy_checks), + "rulesetConfigured": sorted(ruleset_checks), "requiredContexts": sorted(REQUIRED_CHECK_CONTEXTS), }, ) diff --git a/scripts/export-public-tree.py b/scripts/export-public-tree.py index 353b7a3..074b0d2 100755 --- a/scripts/export-public-tree.py +++ b/scripts/export-public-tree.py @@ -127,6 +127,7 @@ REQUIRED_FILES = { ".gitattributes", + ".github/workflows/aws-live-qualification.yml", ".github/workflows/ci.yml", ".github/workflows/python-runtime-qualification.yml", "benchmarks/retrieval/README.md", @@ -143,8 +144,10 @@ "docs/maintainers/releasing.md", "docs/reference/execution-runtime-limitations.md", "docs/reference/stability.md", + "examples/python/retrieval_migration.py", "LICENSE", "packaging/nuget/README.md", + "packaging/publication-cohort.json", "README.md", "ROADMAP.md", "contracts/public-sdk-surface.json", @@ -166,6 +169,7 @@ "scripts/test-export-public-tree.py", "scripts/test-verify-oci-image-identity.py", "scripts/test-python-runtime-platform-matrix.py", + "scripts/test-validate-aws-live-qualification.sh", "scripts/verify-python-runtime-external-worker.sh", "scripts/verify-python-runtime-install.py", "scripts/verify-python-runtime-mcp-conformance.sh", @@ -180,7 +184,10 @@ "scripts/verify-oci-image-identity.py", "scripts/verify-public-export.sh", "scripts/verify-publication-policy.py", + "scripts/verify-publication-cohort.py", "scripts/verify-ripgrep-retrieval-report.py", + "scripts/verify-source-quickstart.py", + "scripts/validate-aws-live-qualification.sh", "scripts/write-python-runtime-platform-receipt.py", } diff --git a/scripts/test-audit-github-launch-controls.py b/scripts/test-audit-github-launch-controls.py index 7dee56f..31d96a5 100644 --- a/scripts/test-audit-github-launch-controls.py +++ b/scripts/test-audit-github-launch-controls.py @@ -39,6 +39,45 @@ def main() -> int: if MODULE._contains_required_checks(expected_checks - {"Analyze go"}): raise SystemExit("The audit accepted an incomplete CodeQL matrix.") + rules = [ + {"type": "deletion"}, + {"type": "non_fast_forward"}, + {"type": "pull_request", "parameters": {}}, + { + "type": "required_status_checks", + "parameters": { + "required_status_checks": [ + {"context": context, "integration_id": 15368} + for context in sorted(expected_checks) + ] + }, + }, + ] + if not MODULE._ruleset_protects_branch(rules): + raise SystemExit("Applicable ruleset protection was not recognized.") + if MODULE._ruleset_protects_branch( + [rule for rule in rules if rule["type"] != "pull_request"] + ): + raise SystemExit("An incomplete ruleset was accepted as branch protection.") + if MODULE._ruleset_required_checks(rules) != expected_checks: + raise SystemExit("Ruleset status-check contexts were not parsed.") + if not MODULE._uses_squash_only_merge_policy( + { + "allow_merge_commit": False, + "allow_rebase_merge": False, + "allow_squash_merge": True, + } + ): + raise SystemExit("The intended squash-only merge policy was rejected.") + if MODULE._uses_squash_only_merge_policy( + { + "allow_merge_commit": False, + "allow_rebase_merge": True, + "allow_squash_merge": True, + } + ): + raise SystemExit("A rebase-enabled repository passed squash-only policy.") + response = subprocess.CompletedProcess( args=("gh", "api"), returncode=0, diff --git a/scripts/test-validate-aws-live-qualification.sh b/scripts/test-validate-aws-live-qualification.sh new file mode 100755 index 0000000..a16673f --- /dev/null +++ b/scripts/test-validate-aws-live-qualification.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WORK="$(mktemp -d "${TMPDIR:-/tmp}/vyral-aws-live-test-XXXXXX")" +cleanup() { + rm -rf -- "$WORK" +} +trap cleanup EXIT +mkdir -p "$WORK/bin" + +cat >"$WORK/bin/aws" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +if [[ "$1 $2" == "sts get-caller-identity" ]]; then + printf '%s\n' 'arn:aws:sts::123456789012:assumed-role/test-role/test-session' + exit 0 +fi +exit 90 +EOF + +cat >"$WORK/storage" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +printf '%s\n' 'private-test-marker' +printf '%s\n' 'aws-storage-live-gate=ok' +printf '%s\n' 'aws-storage-live-cleanup=dynamodb:deleted bucket:deleted' +EOF + +cat >"$WORK/execution" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +printf '%s\n' 'private-test-marker' +printf '%s\n' 'aws-execution-live-gate=ok' +printf 'aws-execution-live-cleanup=table:deleted queue:%s\n' "${QUEUE_RESULT:-deleted}" +EOF +chmod +x "$WORK/bin/aws" "$WORK/storage" "$WORK/execution" + +output="$( + PATH="$WORK/bin:$PATH" \ + VYRAL_AWS_LIVE_TEST_MODE=true \ + VYRAL_AWS_LIVE_AUTH_MODE=test_harness \ + VYRAL_AWS_LIVE_PROVIDER_VERSION=test-managed \ + VYRAL_AWS_LIVE_RESULT_PATH="$WORK/passed.json" \ + VYRAL_AWS_STORAGE_GATE_COMMAND="$WORK/storage" \ + VYRAL_AWS_EXECUTION_GATE_COMMAND="$WORK/execution" \ + "$ROOT/scripts/validate-aws-live-qualification.sh" +)" + +[[ "$output" == *"result:passed"* ]] +[[ "$output" != *"private-test-marker"* ]] +jq -e ' + .schemaVersion == 1 and + .gate == "aws_s3_dynamodb_sqs_live" and + .result == "passed" and + .environmentClass == "deterministic_fixture" and + .authentication.mode == "test_harness" and + .authentication.rootCaller == false and + .isolation.identifiersRedacted == true and + .checks.storage == true and + .checks.execution == true and + .cleanup.result == "passed" and + .limitations.managedOpenSearchCovered == false and + .limitations.automaticQualificationPromotion == false +' "$WORK/passed.json" >/dev/null +! rg -q '123456789012|test-role|test-session|private-test-marker' \ + "$WORK/passed.json" + +set +e +failed_output="$( + PATH="$WORK/bin:$PATH" \ + QUEUE_RESULT=delete-pending \ + VYRAL_AWS_LIVE_TEST_MODE=true \ + VYRAL_AWS_LIVE_AUTH_MODE=test_harness \ + VYRAL_AWS_LIVE_PROVIDER_VERSION=test-managed \ + VYRAL_AWS_LIVE_RESULT_PATH="$WORK/failed.json" \ + VYRAL_AWS_STORAGE_GATE_COMMAND="$WORK/storage" \ + VYRAL_AWS_EXECUTION_GATE_COMMAND="$WORK/execution" \ + "$ROOT/scripts/validate-aws-live-qualification.sh" 2>&1 +)" +failed_status="$?" +set -e +[[ "$failed_status" -eq 1 ]] +[[ "$failed_output" == *"result:failed"* ]] +[[ "$failed_output" != *"private-test-marker"* ]] +jq -e ' + .result == "failed" and + .checks.storage == true and + .checks.execution == false and + .cleanup.result == "failed" and + .cleanup.executionResourcesRemoved == false +' "$WORK/failed.json" >/dev/null + +printf '%s\n' 'aws-live-qualification-gate-test=ok' diff --git a/scripts/validate-aws-live-qualification.sh b/scripts/validate-aws-live-qualification.sh new file mode 100755 index 0000000..3323302 --- /dev/null +++ b/scripts/validate-aws-live-qualification.sh @@ -0,0 +1,202 @@ +#!/usr/bin/env bash +set -euo pipefail +umask 077 + +# Runs the isolated S3/DynamoDB storage gate and DynamoDB/SQS execution gate +# under one short-lived, non-root identity, then emits a redacted receipt. + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +require() { + local name="$1" + if [[ -z "${!name:-}" ]]; then + printf '%s is required.\n' "$name" >&2 + exit 2 + fi +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || { + printf 'Required command not found: %s\n' "$1" >&2 + exit 2 + } +} + +require_command aws +require_command git +require_command jq +require VYRAL_AWS_LIVE_PROVIDER_VERSION +require VYRAL_AWS_LIVE_AUTH_MODE +require VYRAL_AWS_LIVE_RESULT_PATH + +PROVIDER_VERSION="$VYRAL_AWS_LIVE_PROVIDER_VERSION" +AUTH_MODE="$VYRAL_AWS_LIVE_AUTH_MODE" +RESULT_PATH="$VYRAL_AWS_LIVE_RESULT_PATH" +TEST_MODE="${VYRAL_AWS_LIVE_TEST_MODE:-false}" + +if [[ ! "$PROVIDER_VERSION" =~ ^[A-Za-z0-9][A-Za-z0-9._+-]{0,159}$ ]]; then + printf '%s\n' 'VYRAL_AWS_LIVE_PROVIDER_VERSION has an invalid format.' >&2 + exit 2 +fi +if [[ "$TEST_MODE" == true ]]; then + if [[ "$AUTH_MODE" != "test_harness" ]]; then + printf '%s\n' 'AWS live test mode requires test_harness authentication.' >&2 + exit 2 + fi + require VYRAL_AWS_STORAGE_GATE_COMMAND + require VYRAL_AWS_EXECUTION_GATE_COMMAND + STORAGE_GATE="$VYRAL_AWS_STORAGE_GATE_COMMAND" + EXECUTION_GATE="$VYRAL_AWS_EXECUTION_GATE_COMMAND" +else + if [[ "$AUTH_MODE" != "github_oidc" && "$AUTH_MODE" != "assume_role" ]]; then + printf '%s\n' 'AWS live authentication must be github_oidc or assume_role.' >&2 + exit 2 + fi + STORAGE_GATE="$ROOT/scripts/validate-aws-storage-live.sh" + EXECUTION_GATE="$ROOT/scripts/validate-aws-execution-live.sh" + if [[ -n "$(git status --porcelain --untracked-files=all)" ]]; then + printf '%s\n' 'AWS live qualification requires a clean source commit.' >&2 + exit 2 + fi +fi + +if [[ ! -x "$STORAGE_GATE" || ! -x "$EXECUTION_GATE" ]]; then + printf '%s\n' 'AWS live qualification gate commands must be executable.' >&2 + exit 2 +fi + +WORK="$(mktemp -d "${TMPDIR:-/tmp}/vyral-aws-live-XXXXXX")" +cleanup() { + rm -rf -- "$WORK" +} +trap cleanup EXIT +mkdir -p "$(dirname "$RESULT_PATH")" + +if ! CALLER_ARN="$( + aws sts get-caller-identity --query Arn --output text \ + >"$WORK/caller.txt" 2>"$WORK/sts.err" && cat "$WORK/caller.txt" +)"; then + printf '%s\n' 'AWS live qualification could not verify its caller.' >&2 + exit 2 +fi +if [[ -z "$CALLER_ARN" || "$CALLER_ARN" == *":root" ]]; then + printf '%s\n' 'AWS live qualification refuses a root caller.' >&2 + exit 2 +fi + +set +e +"$STORAGE_GATE" >"$WORK/storage.log" 2>&1 +STORAGE_EXIT="$?" +"$EXECUTION_GATE" >"$WORK/execution.log" 2>&1 +EXECUTION_EXIT="$?" +set -e + +STORAGE_CLEANUP=false +if grep -Fq \ + 'aws-storage-live-cleanup=dynamodb:deleted bucket:deleted' \ + "$WORK/storage.log"; then + STORAGE_CLEANUP=true +fi +EXECUTION_CLEANUP=false +if grep -Fq \ + 'aws-execution-live-cleanup=table:deleted queue:deleted' \ + "$WORK/execution.log"; then + EXECUTION_CLEANUP=true +fi +STORAGE_PASSED=false +if (( STORAGE_EXIT == 0 )) && \ + grep -Fq 'aws-storage-live-gate=ok' "$WORK/storage.log" && \ + [[ "$STORAGE_CLEANUP" == true ]]; then + STORAGE_PASSED=true +fi +EXECUTION_PASSED=false +if (( EXECUTION_EXIT == 0 )) && \ + grep -Fq 'aws-execution-live-gate=ok' "$WORK/execution.log" && \ + [[ "$EXECUTION_CLEANUP" == true ]]; then + EXECUTION_PASSED=true +fi + +GATE_RESULT=failed +if [[ "$STORAGE_PASSED" == true && "$EXECUTION_PASSED" == true ]]; then + GATE_RESULT=passed +fi +SOURCE_TREE_CLEAN=true +if [[ -n "$(git status --porcelain --untracked-files=all)" ]]; then + SOURCE_TREE_CLEAN=false +fi +ENVIRONMENT_CLASS=live_managed +SHORT_LIVED=true +if [[ "$TEST_MODE" == true ]]; then + ENVIRONMENT_CLASS=deterministic_fixture + SHORT_LIVED=false +fi + +jq --null-input \ + --arg result "$GATE_RESULT" \ + --arg environmentClass "$ENVIRONMENT_CLASS" \ + --arg providerVersion "$PROVIDER_VERSION" \ + --arg authMode "$AUTH_MODE" \ + --arg testedAtUtc "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --arg testCommit "$(git rev-parse HEAD)" \ + --argjson sourceTreeClean "$SOURCE_TREE_CLEAN" \ + --argjson shortLived "$SHORT_LIVED" \ + --argjson storageExitCode "$STORAGE_EXIT" \ + --argjson executionExitCode "$EXECUTION_EXIT" \ + --argjson storagePassed "$STORAGE_PASSED" \ + --argjson executionPassed "$EXECUTION_PASSED" \ + --argjson storageCleanup "$STORAGE_CLEANUP" \ + --argjson executionCleanup "$EXECUTION_CLEANUP" \ + '{ + schemaVersion: 1, + gate: "aws_s3_dynamodb_sqs_live", + result: $result, + environmentClass: $environmentClass, + scope: "s3_dynamodb_storage_and_dynamodb_sqs_execution", + providerVersion: $providerVersion, + testedAtUtc: $testedAtUtc, + testCommit: $testCommit, + sourceTreeClean: $sourceTreeClean, + authentication: { + mode: $authMode, + shortLivedSession: $shortLived, + rootCaller: false, + secretsRedacted: true + }, + isolation: { + runScopedResourceNames: true, + privateEncryptedBucket: true, + prefixedDynamoTables: true, + prefixedSqsQueue: true, + identifiersRedacted: true + }, + storageExitCode: $storageExitCode, + executionExitCode: $executionExitCode, + checks: { + storage: $storagePassed, + execution: $executionPassed, + s3: $storagePassed, + dynamodb: ($storagePassed and $executionPassed), + sqs: $executionPassed + }, + cleanup: { + result: (if $storageCleanup and $executionCleanup then "passed" else "failed" end), + storageResourcesRemoved: $storageCleanup, + executionResourcesRemoved: $executionCleanup, + identifiersRedacted: true + }, + limitations: { + managedOpenSearchCovered: false, + consumerEnvironmentCovered: false, + automaticQualificationPromotion: false + } + }' >"$RESULT_PATH" + +printf 'aws-live-qualification=result:%s storage:%s execution:%s cleanup:%s\n' \ + "$GATE_RESULT" "$STORAGE_PASSED" "$EXECUTION_PASSED" \ + "$([[ "$STORAGE_CLEANUP" == true && "$EXECUTION_CLEANUP" == true ]] && printf passed || printf failed)" +if [[ "$GATE_RESULT" != passed ]]; then + printf '%s\n' \ + 'AWS live qualification failed; raw provider logs were not published.' >&2 + exit 1 +fi diff --git a/scripts/verify-publication-cohort.py b/scripts/verify-publication-cohort.py new file mode 100644 index 0000000..42585d0 --- /dev/null +++ b/scripts/verify-publication-cohort.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +"""Verify the inert first-publication cohort and its package metadata.""" + +from __future__ import annotations + +import json +from pathlib import Path +import re +import xml.etree.ElementTree as ElementTree + + +ROOT = Path(__file__).resolve().parents[1] +MANIFEST = ROOT / "packaging" / "publication-cohort.json" +EXPECTED = ( + ( + "nuget", + "Vyral.Abstractions", + "0.3.0", + "src/Vyral.Abstractions/Vyral.Abstractions.csproj", + "publish-nuget", + "public", + ), + ( + "nuget", + "Vyral.Local", + "0.3.0", + "src/Vyral.Local/Vyral.Local.csproj", + "publish-nuget", + "public", + ), + ( + "nuget", + "Vyral.Primitives", + "0.2.0", + "src/Vyral.Primitives/Vyral.Primitives.csproj", + "publish-nuget", + "public", + ), + ( + "nuget", + "Vyral.Execution", + "0.2.0", + "src/Vyral.Execution/Vyral.Execution.csproj", + "publish-nuget", + "public", + ), + ( + "nuget", + "Vyral.Execution.Local", + "0.2.0", + "src/Vyral.Execution.Local/Vyral.Execution.Local.csproj", + "publish-nuget", + "preview", + ), + ( + "pypi", + "vyral-client", + "0.3.0", + "clients/python/pyproject.toml", + "publish-pypi", + "public", + ), + ( + "npm", + "@vyral/client", + "0.3.0", + "clients/javascript/package.json", + "publish-npm", + "public", + ), + ( + "container", + "ghcr.io/univeracity/vyral-server", + "0.3.0", + "Dockerfile", + "publish-container", + "mixed", + ), +) +EXCLUDED = { + "cloud-provider packages", + "provider-specific execution adapters", + "Temporal packages", + "vyral-runtime", + "prototype integrations", +} + + +def _dotnet_identity(path: Path) -> tuple[str, str]: + project = ElementTree.parse(path).getroot() + package_id = next( + ( + element.text + for element in project.iter("PackageId") + if element.text + ), + path.stem, + ) + version = next( + ( + element.text + for element in project.iter("Version") + if element.text + ), + None, + ) + if version is None: + defaults = ElementTree.parse(ROOT / "Directory.Build.props").getroot() + version = next( + ( + element.text + for element in defaults.iter("VyralReleaseVersion") + if element.text + ), + None, + ) + if not package_id or not version: + raise SystemExit(f"Unable to resolve NuGet identity for {path}.") + return package_id, version + + +def _python_identity(path: Path) -> tuple[str, str]: + content = path.read_text(encoding="utf-8") + name = re.search(r'^name\s*=\s*"([^"]+)"', content, re.MULTILINE) + version = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE) + if name is None or version is None: + raise SystemExit(f"Unable to resolve Python identity for {path}.") + return name.group(1), version.group(1) + + +def _source_identity( + ecosystem: str, + source: Path, + declared_name: str, + declared_version: str, +) -> tuple[str, str]: + if ecosystem == "nuget": + return _dotnet_identity(source) + if ecosystem == "pypi": + return _python_identity(source) + if ecosystem == "npm": + package = json.loads(source.read_text(encoding="utf-8")) + return str(package.get("name")), str(package.get("version")) + if ecosystem == "container": + return declared_name, declared_version + raise SystemExit(f"Unsupported publication ecosystem: {ecosystem}") + + +def main() -> int: + manifest = json.loads(MANIFEST.read_text(encoding="utf-8")) + if manifest.get("schemaVersion") != "vyral.publication-cohort.v1": + raise SystemExit("Publication cohort schemaVersion is invalid.") + if manifest.get("publicationAuthorized") is not False: + raise SystemExit( + "The repository must remain build-only until an explicit release " + "authorization changes the publication policy." + ) + artifacts = manifest.get("artifacts") + if not isinstance(artifacts, list): + raise SystemExit("Publication cohort artifacts must be an array.") + actual = tuple( + ( + item.get("ecosystem"), + item.get("name"), + item.get("version"), + item.get("source"), + item.get("environment"), + item.get("maturity"), + ) + for item in artifacts + if isinstance(item, dict) + ) + if actual != EXPECTED: + raise SystemExit( + "The first publication cohort changed without updating its " + "reviewed verifier boundary." + ) + excluded = manifest.get("excludedFromFirstCohort") + if not isinstance(excluded, list) or set(excluded) != EXCLUDED: + raise SystemExit("Publication cohort exclusions are incomplete.") + + for ecosystem, name, version, relative, _environment, _maturity in EXPECTED: + source = ROOT / relative + if not source.is_file(): + raise SystemExit(f"Publication source is missing: {relative}") + actual_name, actual_version = _source_identity( + ecosystem, + source, + name, + version, + ) + if (actual_name, actual_version) != (name, version): + raise SystemExit( + f"Publication identity drift for {relative}: " + f"{actual_name} {actual_version}" + ) + + print( + "publication-cohort=ok mode=build-only " + f"artifacts={len(EXPECTED)} excluded={len(EXCLUDED)}" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/verify-publication-policy.py b/scripts/verify-publication-policy.py index 185ecfe..d3d6adc 100644 --- a/scripts/verify-publication-policy.py +++ b/scripts/verify-publication-policy.py @@ -3,6 +3,7 @@ from __future__ import annotations +import json from pathlib import Path import re @@ -68,6 +69,14 @@ def main() -> int: errors.append(f"{name} no longer fails closed behind the automation gate") all_workflows = "\n".join(workflow_text.values()) + cohort = json.loads( + _read(ROOT / "packaging" / "publication-cohort.json") + ) + if cohort.get("publicationAuthorized") is not False: + errors.append( + "publication-cohort.json must remain explicitly unauthorized " + "while source policy is build-only" + ) if re.search( r"VYRAL_ENABLE_AUTOMATED_WORKFLOWS\s*[:=]\s*['\"]?true\b", all_workflows, diff --git a/scripts/verify-python-runtime-install.py b/scripts/verify-python-runtime-install.py index b7c4568..47e3992 100755 --- a/scripts/verify-python-runtime-install.py +++ b/scripts/verify-python-runtime-install.py @@ -294,7 +294,14 @@ def _starter_smoke( ) first_started_at = perf_counter() - first = _command(str(python), str(target), capture_output=True) + first = _command( + str(python), + "-m", + "vyral_runtime", + "run", + str(target), + capture_output=True, + ) first_run_ms = round((perf_counter() - first_started_at) * 1_000, 3) first_run_id = _starter_run_id(first.stdout, "first run") if not all( @@ -313,7 +320,14 @@ def _starter_smoke( ) replay_started_at = perf_counter() - replay = _command(str(python), str(target), capture_output=True) + replay = _command( + str(python), + "-m", + "vyral_runtime", + "run", + str(target), + capture_output=True, + ) replay_run_ms = round((perf_counter() - replay_started_at) * 1_000, 3) replay_run_id = _starter_run_id(replay.stdout, "replay") if ( @@ -342,7 +356,14 @@ def _starter_smoke( with target.open("w", encoding="utf-8", newline="\n") as stream: stream.write(versioned_source) versioned_started_at = perf_counter() - versioned = _command(str(python), str(target), capture_output=True) + versioned = _command( + str(python), + "-m", + "vyral_runtime", + "run", + str(target), + capture_output=True, + ) versioned_run_ms = round( (perf_counter() - versioned_started_at) * 1_000, 3, diff --git a/scripts/verify-release-artifacts.sh b/scripts/verify-release-artifacts.sh index bfb5a82..cdf3e79 100755 --- a/scripts/verify-release-artifacts.sh +++ b/scripts/verify-release-artifacts.sh @@ -88,6 +88,7 @@ while IFS= read -r -d '' script; do done < <(find scripts deploy -type f -name '*.sh' -print0 | sort -z) printf 'release-shell-syntax=ok\n' python3 scripts/verify-publication-policy.py +python3 scripts/verify-publication-cohort.py python3 scripts/test-audit-github-launch-controls.py python3 scripts/test-verify-oci-image-identity.py @@ -107,6 +108,7 @@ jq empty qualification/adapter-qualification.schema.json qualification/adapter-q scripts/generate-adapter-qualification.sh "$QUALIFICATION_DIR/adapter-qualification.json" cp qualification/adapter-qualification.schema.json "$QUALIFICATION_DIR/adapter-qualification.schema.json" scripts/test-validate-google-execution-live.sh +scripts/test-validate-aws-live-qualification.sh scripts/test-validate-aws-opensearch-live.sh scripts/test-scan-release-history.sh scripts/test-validate-canonical-mysql-benchmark-report.sh @@ -117,6 +119,7 @@ python3 scripts/verify-ripgrep-retrieval-report.py \ ripgrep_comparison="$ARTIFACT_ROOT/ripgrep-comparison.json" ripgrep_tampered="$ARTIFACT_ROOT/ripgrep-comparison-tampered.json" ripgrep_user_result="$ARTIFACT_ROOT/ripgrep-user-path.json" +ripgrep_migration_result="$ARTIFACT_ROOT/ripgrep-migration-path.json" python3 scripts/benchmark-ripgrep-retrieval.py \ --output "$ripgrep_comparison" \ --noise-documents 60 \ @@ -150,6 +153,15 @@ jq -e ' and (.sourceRevision | startswith("sha256:")) ) ' "$ripgrep_user_result" >/dev/null +python3 examples/python/retrieval_migration.py \ + --json > "$ripgrep_migration_result" +jq -e ' + (.sourceNative.matches | length == 1) + and (.sourceNative.reorderedMatchCount == 0) + and (.indexed.results | length == 1) + and (.indexed.results[0].recordId == "execution.md") + and (.indexed.embeddingUsed == false) +' "$ripgrep_migration_result" >/dev/null printf 'ripgrep-retrieval-admission-gate=ok\n' if [[ -n "${VYRAL_PUBLIC_HISTORY_DENYLIST_FILE:-}" || -n "${VYRAL_PUBLIC_HISTORY_DENYLIST:-}" ]]; then diff --git a/scripts/verify-source-quickstart.py b/scripts/verify-source-quickstart.py new file mode 100644 index 0000000..a70794a --- /dev/null +++ b/scripts/verify-source-quickstart.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python3 +"""Prove the useful source-checkout path without installing Vyral.""" + +from __future__ import annotations + +import json +import os +from pathlib import Path +import platform +import re +import subprocess +import sys +import tempfile +from time import perf_counter +from typing import Any, Mapping + + +ROOT = Path(__file__).resolve().parents[1] +LAUNCHER = ROOT / "scripts" / "vyral" +FIRST_USE_BUDGET_MS = 5 * 60 * 1_000 + + +class SourceQuickstartError(RuntimeError): + """Raised when a clean source-checkout experience is not useful.""" + + +def _run( + working_directory: Path, + *arguments: str, +) -> subprocess.CompletedProcess[str]: + environment = os.environ.copy() + environment["PYTHONNOUSERSITE"] = "1" + return subprocess.run( + [sys.executable, "-I", str(LAUNCHER), *arguments], + cwd=working_directory, + env=environment, + check=True, + capture_output=True, + text=True, + timeout=300, + ) + + +def _json( + working_directory: Path, + *arguments: str, +) -> Mapping[str, Any]: + completed = _run(working_directory, *arguments) + try: + payload = json.loads(completed.stdout) + except json.JSONDecodeError as error: + raise SourceQuickstartError( + f"Source command did not return JSON: {' '.join(arguments)}" + ) from error + if not isinstance(payload, Mapping): + raise SourceQuickstartError("Source command JSON must be an object.") + return payload + + +def _mapping(value: object, label: str) -> Mapping[str, Any]: + if not isinstance(value, Mapping): + raise SourceQuickstartError(f"{label} must be an object.") + return value + + +def _number(value: object, label: str) -> float: + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise SourceQuickstartError(f"{label} must be numeric.") + selected = float(value) + if selected < 0: + raise SourceQuickstartError(f"{label} must not be negative.") + return selected + + +def _run_id(output: str) -> str: + match = re.search(r"Accepted receipt: run=([^ ]+)", output) + if match is None: + raise SourceQuickstartError( + "The editable starter returned no durable run identity." + ) + return match.group(1) + + +def main() -> int: + started = perf_counter() + with tempfile.TemporaryDirectory( + prefix="vyral-source-first-use-" + ) as temporary: + working_directory = Path(temporary) + state_root = working_directory / "quickstart-state" + + first_started = perf_counter() + first = _json( + working_directory, + "quickstart", + "--root", + str(state_root), + "--json", + ) + first_command_ms = round( + (perf_counter() - first_started) * 1_000, + 3, + ) + retrieval = _mapping(first.get("retrieval"), "retrieval") + embedding = _mapping(first.get("embedding"), "embedding") + execution = _mapping(first.get("execution"), "execution") + timings = _mapping(first.get("timings"), "timings") + citations = retrieval.get("citations") + if ( + first.get("topology") != "local-single-node" + or retrieval.get("mode") != "lexical" + or not isinstance(citations, list) + or not citations + or embedding.get("used") is not False + or embedding.get("requiresNetwork") is not False + or execution.get("admittedStatus") != "queued" + or execution.get("persistedStatusAfterReopen") != "queued" + or execution.get("completedStatus") != "succeeded" + or execution.get("dispatchedRuns") != 1 + ): + raise SourceQuickstartError( + "The source quickstart did not prove cited zero-embedding " + "retrieval and durable restart completion." + ) + first_citation_ms = _number( + timings.get("firstCitationMs"), + "timings.firstCitationMs", + ) + if max(first_command_ms, first_citation_ms) > FIRST_USE_BUDGET_MS: + raise SourceQuickstartError( + "The source quickstart exceeded the five-minute first-use " + "budget." + ) + + replay = _json( + working_directory, + "quickstart", + "--root", + str(state_root), + "--json", + ) + replay_retrieval = _mapping( + replay.get("retrieval"), "replay.retrieval" + ) + replay_execution = _mapping( + replay.get("execution"), "replay.execution" + ) + if ( + replay_execution.get("runId") != execution.get("runId") + or replay_execution.get("admissionReplayed") is not True + or replay_execution.get("dispatchedRuns") != 0 + or replay_retrieval.get("createdChunks") != 0 + or replay_retrieval.get("reusedChunks") != 3 + ): + raise SourceQuickstartError( + "The second isolated source command did not replay the " + "persisted corpus and run." + ) + + inspection = _json( + working_directory, + "inspect", + "--root", + str(state_root), + "--json", + ) + providers = _mapping( + inspection.get("providers"), "inspection.providers" + ) + if ( + inspection.get("topology") != "local-single-node" + or _mapping(providers.get("records"), "records").get("healthy") + is not True + or _mapping( + providers.get("execution"), "execution" + ).get("healthy") + is not True + ): + raise SourceQuickstartError( + "The source quickstart did not leave inspectable healthy state." + ) + + reset = _json( + working_directory, + "quickstart", + "--root", + str(state_root), + "--reset", + "--json", + ) + if state_root.exists() or not isinstance( + reset.get("removedRootPath"), str + ): + raise SourceQuickstartError( + "The source quickstart did not safely reset its owned state." + ) + + app_path = working_directory / "vyral_app.py" + created = _json( + working_directory, + "init", + "--path", + str(app_path), + "--json", + ) + run_arguments = created.get("runArguments") + if ( + created.get("createdPath") != str(app_path.resolve()) + or not isinstance(run_arguments, list) + or len(run_arguments) != 3 + or run_arguments[1:] != ["run", str(app_path.resolve())] + ): + raise SourceQuickstartError( + "Source init did not return one truthful launcher command." + ) + + starter_first = _run( + working_directory, + "run", + str(app_path), + ) + starter_replay = _run( + working_directory, + "run", + str(app_path), + ) + first_run_id = _run_id(starter_first.stdout) + replay_run_id = _run_id(starter_replay.stdout) + if ( + first_run_id != replay_run_id + or "status=queued replayed=false" not in starter_first.stdout + or "status=succeeded dispatched=1" not in starter_first.stdout + or "status=succeeded replayed=true" not in starter_replay.stdout + or "status=succeeded dispatched=0" not in starter_replay.stdout + ): + raise SourceQuickstartError( + "The source-generated application did not complete and replay " + "through the advertised launcher." + ) + + elapsed_ms = round((perf_counter() - started) * 1_000, 3) + if elapsed_ms > FIRST_USE_BUDGET_MS: + raise SourceQuickstartError( + "The complete source-checkout path exceeded five minutes." + ) + + print( + json.dumps( + { + "schemaVersion": "vyral.source-first-use.v1", + "status": "passed", + "platform": platform.system().lower(), + "pythonVersion": platform.python_version(), + "isolatedInterpreter": True, + "packageInstallationRequired": False, + "networkRequired": False, + "firstCommandMs": first_command_ms, + "firstCitationMs": first_citation_ms, + "citationCount": len(citations), + "durableRestartCompleted": True, + "secondProcessReplayed": True, + "generatedApplicationCompleted": True, + "generatedApplicationReplayed": True, + "safeReset": True, + "completePathMs": elapsed_ms, + "budgetMs": FIRST_USE_BUDGET_MS, + }, + indent=2, + sort_keys=True, + ) + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())