diff --git a/.github/PULL_REQUEST_TEMPLATE/catalog.md b/.github/PULL_REQUEST_TEMPLATE/catalog.md new file mode 100644 index 0000000..e8c0086 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/catalog.md @@ -0,0 +1,13 @@ +# Catalog submission + +- Versioned wheel URL and attached preflight.json: +- Source/build and usage/test evidence: +- First submission: ownership and code/asset rights links (updates may reuse them): +- Update: changes and any access/configuration/data migration steps (or none): + +Please revise this same PR. Do not fill invented reviewer/approval fields. +Existing README, release notes and logs are sufficient evidence formats. + + diff --git a/.github/workflows/catalog-admission.yml b/.github/workflows/catalog-admission.yml new file mode 100644 index 0000000..2cd42e4 --- /dev/null +++ b/.github/workflows/catalog-admission.yml @@ -0,0 +1,57 @@ +name: Catalog admission +on: + pull_request_target: + types: [opened, synchronize, reopened, ready_for_review] +permissions: + contents: read +jobs: + catalog-admission: + runs-on: windows-2025 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.base.sha }} + path: trusted + fetch-depth: 0 + persist-credentials: false + - uses: actions/checkout@v7 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: proposal + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@v7 + with: + python-version: '3.12.10' + - uses: actions/checkout@v7 + with: + repository: Code-Amadeus/Amadeus + ref: 1840c8e4f646992ddaebea3139c8895f29743a8f + path: host + persist-credentials: false + - name: Create private environments from trusted hashed requirements + shell: pwsh + run: | + python -I -m venv admission-env + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + admission-env/Scripts/python.exe -I -m pip install --require-hashes -r trusted/requirements-dev.txt + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + python -I -m venv checker-env + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + checker-env/Scripts/python.exe -I -m pip install --require-hashes -r host/tools/requirements-extension-authoring.txt + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - name: Inspect proposal only as data with protected-base tools + shell: pwsh + env: + CATALOG_BASE: ${{ github.event.pull_request.base.sha }} + run: | + admission-env/Scripts/python.exe trusted/tools/check_catalog_ci.py --proposal proposal --base $env:CATALOG_BASE --host host --python checker-env/Scripts/python.exe --artifacts artifacts --evidence evidence + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - uses: actions/upload-artifact@v7 + if: always() + with: + name: catalog-admission-evidence + path: evidence/*.json + if-no-files-found: ignore diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index cb6a9a9..2d86192 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -48,3 +48,50 @@ jobs: first, second = (path.read_bytes() for path in wheels) assert first == second, 'Windows and Linux SDK wheels differ' print(f'{wheels[0].name} sha256={hashlib.sha256(first).hexdigest()}') + + catalog-contracts: + runs-on: windows-2025 + timeout-minutes: 20 + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + with: + path: proposal + fetch-depth: 0 + - uses: actions/setup-python@v7 + with: + python-version: '3.12.10' + - uses: actions/checkout@v7 + with: + repository: Code-Amadeus/Amadeus + # Reviewed D1 checker. Publish this commit before enabling the remote job. + ref: 1840c8e4f646992ddaebea3139c8895f29743a8f + path: host + - name: Install catalog tests and fixed static checker + shell: pwsh + run: | + python -m pip install --require-hashes -r proposal/requirements-dev.txt + python -m venv host/build/catalog-checker + host/build/catalog-checker/Scripts/python.exe -m pip install --require-hashes -r host/tools/requirements-extension-authoring.txt + - name: Build only the pinned Host reference packages + shell: pwsh + run: | + New-Item -ItemType Directory -Force host/build/extension-authoring-review-wheels | Out-Null + $destination = (Resolve-Path host/build/extension-authoring-review-wheels).Path + foreach ($project in Get-ChildItem host/examples/extensions -Directory) { + Push-Location $project.FullName + python -I -c 'from setuptools.build_meta import build_wheel; import sys; build_wheel(sys.argv[1])' $destination + $result = $LASTEXITCODE + Pop-Location + if ($result -ne 0) { exit $result } + } + - name: Test catalog and local admission separation + working-directory: proposal + shell: pwsh + run: | + $env:AMADEUS_CATALOG_HOST = (Resolve-Path ../host).Path + $env:AMADEUS_CATALOG_PYTHON = (Resolve-Path ../host/build/catalog-checker/Scripts/python.exe).Path + python -m pytest -q tests/test_catalog.py tests/test_catalog_submission.py + # Admission runs independently from protected-base workflow/code. + # This job executes proposal tests only; it must never gate publication. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 34a6b01..c35c1fd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,3 +30,7 @@ The build tool canonicalizes SDK text/ZIP metadata and recalculates RECORD per CI compares artifacts built independently on Windows and Linux. Do not replace that cross-platform comparison with two builds on the same machine. The Host's pinned wheel must match this canonical artifact; runtime support remains >=3.11. + + +For author preflight, ownership/transfer, review invalidation, PR responsibilities, +revision and verified distribution, follow the [submission workflow](catalog/SUBMISSION.md). diff --git a/README.md b/README.md index 54ea4a4..0601af2 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,10 @@ Public design direction and discussion for the Amadeus extension system. > **Status: Draft specification v0.1** > -> A first draft of the extension protocol is available for discussion. Unmerged Amadeus experiments implement TTS/MCP/Skill lifecycle and data-only desktop themes; no stable SDK has been released. Interfaces and compatibility guarantees may change until 1.0. +> A first draft of the extension protocol is available for discussion. Amadeus main implements an experimental subset with TTS/MCP/Skill lifecycle and data-only desktop themes; no stable SDK has been released. Interfaces and compatibility guarantees may change until 1.0. + +The implemented Host subset is documented in the [Host profile](https://github.com/Code-Amadeus/Amadeus/blob/fd17e707d8793480003f929bc8c78a8d134e41b5/docs/extension_host_profile_v0.1.md). +Manifest validation here is not a claim that every draft contribution can run. ## Why this repository exists @@ -51,6 +54,14 @@ python tools/validate_manifest.py examples/mimo-tts python -m pytest ``` +## Curated catalog experiment + +See [catalog tooling and review policy](catalog/README.md). The empty bootstrap +contains no reviewed packages. The Host experiment implements store browsing, downloads, +manual updates and durable safety, with a complete local Git/HTTPS/desktop demonstration; +these branches have not been released publicly. Start with the +[submission and publication guide](catalog/SUBMISSION.md). + ## Current design direction ### Capability discovery within a prompt budget diff --git a/catalog/README.md b/catalog/README.md new file mode 100644 index 0000000..61c78ff --- /dev/null +++ b/catalog/README.md @@ -0,0 +1,161 @@ +# Curated catalog v1 (experimental, no public store yet) + +This directory defines distribution facts, not new extension execution rights. +The sole official index URL will be +`https://raw.githubusercontent.com/Code-Amadeus/Amadeus-Extensions/main/catalog/v1/index.json` +on protected main. There is no signature in v1: repository access, +branch protection, independent review, maintainer 2FA and least-privilege Actions +are the trust root. These publication controls must be verified before S3. +No real package is reviewed or published by the empty sequence-0 bootstrap. + +## Inputs and review + +Put one JSON file per artifact below `packages/`. Hand-maintained keys are exactly: + +```json +{ + "wheel": {"url": "https://example.invalid/package-1.0.0-py3-none-any.whl", "sha256": "<64 lowercase hex>"}, + "publisher": {"id": "publisher", "maintainers": ["author"], "package_ids": ["org.example.package"], "evidence": "https://..."}, + "review": {"sha256": "", "policy": "curated-v1", "reviewer": "independent-reviewer", "date": "2026-10-21", "evidence": "https://..."}, + "withdrawal": null +} +``` + +A reviewer must verify the ownership evidence and independently inspect the +actual wheel using [the public checklist](REVIEW_CHECKLIST.md). Reviewer/maintainer identities are GitHub logins, compared without case. These JSON strings +are review records, not authentication; protected-branch PR approval establishes +their authority. Maintainer examples follow the same independent-review rule. +Every new version is reviewed, including when the source repository appears unchanged. +Never enter a review record merely because automated checks passed. + +The generator derives identity, size, author/license/homepage/description, Host +and Python constraints, platform list, dependencies and contribution summaries +from that artifact. It never modifies or adds catalog fields to the manifest. +`policy.json` is the single v1 admission policy used by the catalog tooling. +Fish Audio remains valid for local installation but its third-party dependencies +exclude it from this first catalog. A future dependency closure must pin every +transitive wheel URL/hash and install offline with `--no-index --find-links` +plus `--require-hashes --only-binary=:all:`; this is not implemented in v1. + +Artifacts are stored locally as `/`. The optional +`--fetch` path allows HTTPS, system TLS verification, bounded redirects, time and +size, no URL credentials/cookies. It only downloads bytes. Host S1 downloads must +use Electron net; this maintenance tool is not a replacement Host downloader. +Artifact mirrors may supply the same digest-pinned bytes; index mirrors require +future signatures. Never replace the official index trust root with a mirror. + +## Fixed checker and publication + +`toolchain.json` fixes Host commit, Windows, CPython 3.12.10 and the hash of the +Host's generated minimal dependency list. Obtain that exact Host checkout; the +tools reject a different or modified checker. Install its minimal environment +with `pip --require-hashes`. The Host pin is currently a local experimental commit +and must be pushed/reviewed before GitHub Actions can retrieve it. No SDK or Host +protocol pin is changed by this catalog work. + +1. Commit proposed artifact/review/ownership facts. CI uses protected-base tools + to inspect their bytes without importing or installing package code. Human + review must finish before the input PR merges into main. +2. A separate publication PR selects an already merged input commit. Select an + already reviewed generator commit. The generator tree must be clean. +3. Run the fixed generator from that commit, with the current main index as + `--previous` and the next integer sequence. Source time comes from the input + commit, never the wall clock. Repeat builds yield identical bytes. + +```powershell +python tools/build_catalog.py --source-repo . --source-commit --approved-ref origin/main --sequence --previous --host --python --artifacts --output catalog/v1/index.json --fetch +python tools/check_catalog.py --index catalog/v1/index.json --previous +python tools/check_catalog.py --wheel --previous-wheel --output +``` + +`check_catalog.py` emits additions/removals/changes, text diffs and binary hashes; +initial review emits the whole file inventory. Truncated text diffs are marked: +review the full artifact for those files. Manifest, METADATA/dependencies and +startup scripts appear in the same content diff. Do not treat a diff as approval. + +4. Immediately before merging, CI rechecks against current protected main. + Concurrent publication PRs must rebase and obtain review again. No workflow + pushes to main or executes submitted plugin code with publication privileges. + The bootstrap is a separately reviewed code change admitting only an empty + index; subsequent catalog PRs execute protected-base tooling. Generator changes + are reviewed before a later PR selects them for publication. + +An unchanged index may be rebuilt exactly. A changed index must increment by one, +including content rollback. An existing ID/version cannot disappear or acquire +new bytes; retain it and mark withdrawal. Unchanged reviewed facts may be reused +from the previous protected-main index at the same Host/policy pin. This preserves +security publication when an old download is unavailable; the download/display +cache is never used as authority. New bytes/reviews/ownership or a changed checker +pin still require artifact inspection. + +## Withdrawal, safety and future clients + +`withdrawal` is null or `{ "reason": "maintainer" | "copyright", "evidence": "https://..." }`. +It stops catalog installation/recommendation, not execution of existing local copies. +`security.json` retains one explicit latest decision per ID/version/SHA-256: +`status` is `blocked` or `allowed`, `reason` is `security` or `security-resolved`, +with evidence and the publication sequence that changed the decision. A known +fact cannot disappear; explicit unban requires a higher publication sequence. +Safety applies to both local and catalog installations. The Host experiment now implements durable settings, +backup recovery and activation enforcement; see its `docs/extension_catalog.md`. +These local changes still require Host integration and release qualification. + +Stable index structure is validated without running the full manifest validator. +Unknown contribution kind, role, contract, runtime or delivery makes that entire +package incompatible; compatible neighbors remain visible. Mixed packages are not +partially installed. Security structure is parsed independently of those entries. +Unknown format major, invalid safety structure or identity conflicts reject the +new snapshot, retaining prior safety/high-water facts in S1. Shared examples are +in `fixtures/compatibility.json`; the Python compatibility function specifies the +capability part only. Host API/Python/platform/dependency checks and main-process +installation rechecks are implemented by the Host experiment, separately from this publisher. + +Display strings are untrusted plain text. The experimental S2 client renders no remote images and +validates HTTPS homepage links in main before opening an external browser. +Host S3 preparation records fixed old-reader cross-checks; this repository alone +does not certify a released Electron client. + +S2 detail views consume generated `display.access`: declared OS access and TTS/MCP deployment, command/arguments or endpoint templates. Configuration defaults, bearer tokens and environment values are excluded. Older entries without this optional display fact remain readable and must show that access details are unavailable until wheel inspection. This does not change catalog format or runtime contracts. + + +## First catalog review preparation + +[Candidate review materials](review-candidates/README.md) inventory exact reference +wheel bytes and static checks. They are not catalog entries or approvals. Keep the +bootstrap empty until independent review, public artifacts, protected publication +and final release qualification are complete. The Host experimental S3 checklist +is `docs/verification/extensions/store-v1.md`; no release is implied by this material. + + +For author preflight, ownership/transfer, review invalidation, PR responsibilities, +revision and verified distribution, follow the [submission workflow](SUBMISSION.md). + +## Versioned delivery and independent admission + +The v1 public path is permanent: catalog/v1/index.json always retains the strict +amadeus.catalog/v1 format. A new wire format uses another path. Maintainers must +continue publishing understood safety facts and increasing sequences on supported +older paths; switching v2 clients does not retire v1 safety delivery. Retirement +requires an explicit client-support decision, never silently replacing v1 bytes +with v2. There is no unversioned redirect used by clients. + +Proposal tests run in validate.yml and confer no admission authority. The separate +catalog-admission.yml runs from the protected base via pull_request_target with +read-only permissions, fresh private Python environments and base-pinned hashed +dependencies. Proposal files are data only: its requirements, tests, workflow, +sitecustomize and .pth files are never installed or executed by admission. No +cache or artifacts from proposal jobs are reused. + +This first empty bootstrap requires human review of the workflow itself: the +trusted workflow cannot certify its own introduction before it exists on main. +After bootstrap, require the catalog-admission check from GitHub Actions before +accepting any package/publication PR, and verify a deliberately invalid proposal +is rejected on that protected path. Do not treat proposal unit tests or a local +job simulation as that governance check. + +The fixed Host commit is retained by the annotated tag +[extension-catalog-checker-d1](https://github.com/Code-Amadeus/Amadeus/tree/extension-catalog-checker-d1). +This is a Git retention ref, not a Release or catalog publication. The toolchain +still verifies the immutable commit hash, never a movable tag. After the Host +prototype reaches main, requalify its checker there (including real v1 icon wheels) +before updating the toolchain. Until then, the fixed checker rejects icon manifests. diff --git a/catalog/REVIEW_CHECKLIST.md b/catalog/REVIEW_CHECKLIST.md new file mode 100644 index 0000000..d59bb6c --- /dev/null +++ b/catalog/REVIEW_CHECKLIST.md @@ -0,0 +1,54 @@ +# Artifact review checklist — curated-v1 + +Review the bytes whose SHA-256 is recorded, not only the source repository. +“Reviewed” means this artifact completed this checklist; it is not a security +warranty, an OS sandbox, a permission grant or approval of future versions. +Record reviewer, date, policy version, digest and evidence on every version. +An author/publisher maintainer cannot be their own reviewer. + +- [ ] Fetch the exact wheel, verify digest, and retain its full file inventory. + Confirm package ownership/transfer independently of manifest author text or PR account. +- [ ] Run the fixed Windows/CPython/Host checker. Record its commit and environment. + Confirm ID, X.Y.Z version, wheel filename, METADATA and entry point agree. + Never replace bytes at an existing ID/version, including build suffixes. +- [ ] Verify filename and WHEEL both specify py3-none-any, with purelib true. + No native or executable binaries, .pth startup hooks or opaque/unreviewable code. + Inspect signatures/content as well as suffixes; automated detection is incomplete. +- [ ] Effective dependencies are empty or the compatible Host SDK only. No direct + dependency URLs, source builds, hidden installers or runtime package downloads. +- [ ] MCP starts only Python or reviewed package scripts. Python -m must name a + packaged module; script targets stay under the package. No -c, pip, npx/uvx or + configuration values selecting executables/modules. Test script executability + on declared platforms; Windows should use Python plus a package module/.py. +- [ ] Inspect every execution entry, script and Skill instruction. Search and + reason about network access, code downloads/eval, subprocesses and persistence. + Remote API responses may be data, not fetched executable code. Static CI cannot + establish this item; run approved contracts only in an isolated uncredentialed job. +- [ ] Check actual file/network behavior, secret handling and disclosures. No + hidden Host imports or permissions; Main Chat receives no new execution authority. + Retention and explicit deletion must match the documented lifecycle. +- [ ] Check code license and separate rights for every image, sound or other + asset. Character artwork needs provenance/permission; a code license alone is + insufficient. Current themes use installed fonts, not newly bundled fonts. +- [ ] Review install, use, update, cancellation/close, disable, uninstall/reinstall + evidence for this contribution. Label static, process, native Agent, GUI and + paid/real-service coverage separately. Do not promise automatic startup rollback. +- [ ] Compare with the previous reviewed wheel using check_catalog.py. Read the + complete changed files when the diff is truncated, and review unchanged code + as needed. Initial versions require full review; every later version still + completes the whole checklist. Independently built source comparison is supporting + evidence, not a substitute for examining the actual distribution. +- [ ] Record remaining limitations and the independent approval in the review PR. + A CI pass alone must never create a reviewed record. + +For later removal, use copyright withdrawal for asset/license complaints, +maintainer withdrawal for ordinary retirement, and security decisions for unsafe +artifacts. Do not relabel copyright removal as a security ban or delete user data. + +Before first public release: protect main, require reviewed merges, verify +maintainer two-factor authentication and least-privilege workflows, and ensure +submission execution jobs have no publishing credentials. S3 owns that evidence. + + +For author preflight, ownership/transfer, review invalidation, PR responsibilities, +revision and verified distribution, follow the [submission workflow](SUBMISSION.md). diff --git a/catalog/SUBMISSION.md b/catalog/SUBMISSION.md new file mode 100644 index 0000000..98dd161 --- /dev/null +++ b/catalog/SUBMISSION.md @@ -0,0 +1,157 @@ +# Submission and publication operations (curated-v1) + +This guide is executable workflow guidance for the experimental catalog. The +public main branch may still have no catalog. An empty bootstrap or automated +preflight does not approve a package. Runtime contracts and curated-v1 admission +are separate: a valid locally installable package can be ineligible for this store. + +## Author: submit once, revise the same PR + +Before building, read policy.json and REVIEW_CHECKLIST.md. V1 accepts X.Y.Z, +py3-none-any, no effective dependencies except the compatible SDK, and reviewed +package/Python commands only. Fish Audio with third-party dependencies currently +belongs to local installation. Do not remove needed dependencies to pass the store. + +Run the fixed Windows/CPython/checker environment from toolchain.json: + +~~~powershell +python tools/preflight_submission.py --wheel --host --python --output preflight.json +~~~ + +The report distinguishes package-format and target-Host acceptance (the same +Host owner checks both), curated policy, and pending human/runtime review. On Host +failure it does not claim that the package format passed. It never imports package +code, invents an approval or requires authors to maintain derived catalog metadata. + +Open one submission PR using the short catalog template: +- Link the versioned wheel and attach preflight.json; do not retype its metadata. +- Link source/build and basic usage/validation evidence. +- On first submission, link ownership and code/asset rights evidence. Reuse the + established evidence on updates unless control or rights changed. +- On updates, summarize changes and any access/configuration/data migration steps. + If there are no breaking changes, say so; no separate migration report is needed. + +The author revises this same PR. Maintainers prepare catalog JSON and the second, +possibly batched publication PR. No account dashboard, second author form, separate +status report or mandatory fixed set of attachments is required. Existing README, +release notes, build logs and license files can provide the evidence directly. + +The author does not populate a fictional reviewer or grant themselves approval. +A draft PR may contain materials before catalog/packages facts are complete; +required catalog CI can remain failing while those facts are incomplete. The +authorized independent reviewer adds the final existing review fields only after +review. Reports/attachments are evidence, not runtime manifest or catalog fields. + +## Ownership and responsible people + +For first ownership, establish control of the source repository/artifact publishing +location and proposed namespace from existing repository/release evidence. Ask for +a repository challenge only when that evidence leaves control ambiguous. +A display author name, matching PR account, fork or preflight alone is insufficient. +Record evidence in publisher.evidence and the PR. Inspect links independently. + +For updates, use the previously reviewed publisher/maintainer record. A submitter +outside that set needs explicit authorization from a recorded maintainer, verified +through the established source/account channel. For transfers, obtain the current +holder's authorization and the recipient's acceptance; review changed control and +record the evidence. Silence, abandoned appearance or an unavailable URL is not +authorization to transfer. Disputes wait for an explicit decision. + +Maintainers assign one contact/reviewer to carry the submission through publication. +Review and publication may be handled by the same eligible maintainer; backup is +an internal team arrangement, not another person the author must recruit. A reviewer +cannot be an author or publisher maintainer. If nobody eligible is available, say +what is waiting. No fabricated identities, timeout approval or self-review. + +Use existing PR review states and cross-links; labels are optional convenience: +needs-preflight → needs-review → changes-requested → approved-awaiting-publication +→ published. Labels communicate status; protected-branch review is the authority. +The triage assignee updates the next responsible person and missing evidence. +No promised review SLA; security actions do not wait for ordinary release batches. + +## Review and invalidation + +Approval covers exact wheel SHA-256, curated policy, fixed checker/environment, +ownership/rights and runtime evidence at the reviewed PR head. Material changes to +bytes, URL/control, rights or qualification invalidate the affected evidence and +need relevant checks and renewed non-author review. A wording-only correction +must not force another full runtime qualification. Every +new version requires review. Configure dismiss-stale-approvals and approval after +the last push on the real protected branch before public release. + +Approval to execute tests in an isolated environment is NOT approval to publish. +Never run submitted package code in a write-credential/publishing job. The wheel +inventory/diff and author report assist, but do not replace, the public checklist. +A changes-requested PR stays open; revisions and fresh preflight attach to the +same PR. The reviewer records which prior evidence/approval was superseded. + +## Publication owner: input PR → publication PR → verified delivery + +1. Merge reviewed inputs only after required checks and independent approval. +2. Prepare a separate publication PR from that committed input and reviewed + generator. Link each included submission/withdrawal/security decision and list + exact ID/version/digest, next sequence and current-main base. The author does + not need to author this second PR. +3. Follow catalog/README.md for build_catalog.py, check_catalog.py and protected-base + check_catalog_ci.py. Rebuild after any main movement; repeat approval as required. +4. Use verify_publication.py against a candidate endpoint and expected generated + file before release, then the client's exact official index URL after merge. + Use an empty fresh artifact cache (the tool always does this). Only mark + published after the live bytes and newly distributed/relocated assets pass. + +~~~powershell +python tools/verify_publication.py --index-url --expected catalog/v1/index.json --previous --output delivery.json +~~~ + +The explicit expected file pins the sequence/content; a stale or different served +index fails even if otherwise valid. Newly available versions, restored distribution, +unblocked assets and changed URLs are freshly downloaded and digest/size checked. +Unchanged available versions may be skipped. Withdrawn/blocked assets are skipped: +an unavailable old wheel must not prevent a safety-only publication. Record skipped +scope honestly. Availability is a point-in-time check, not an uptime guarantee. +The publication owner links the receipt back to submissions; only then applies +published status and notifies authors through the normal authorized review process. + +## Source failures, complaints and recovery + +Use versioned asset URLs, never mutable latest aliases. Immutable release assets +are preferred; content hashes remain mandatory. A broken URL may change through a +reviewed input PR only if ID/version/digest remain the same; confirm new hosting +control and fresh download. Different bytes always require a new version. +No silent URL rewrite or replacement of the official index trust root. + +Triage download failures via an ordinary catalog issue with ID/version/digest, +stage and sanitized error. Functional bugs go to the plugin's source issue tracker. +Copyright/rights reports go to catalog maintainers and cause copyright withdrawal. +Sensitive security reports follow the Host SECURITY.md policy, not public raw logs. + +The triage owner routes a security report to a security reviewer and publication +owner. The independent reviewer records exact affected artifacts and evidence; +the publication owner issues a separate higher-sequence safety PR and verifies +the served index/client result. A false positive requires explicit higher-sequence +allowed decisions, not deleting the history or rolling back the index. Copyright +withdrawal remains independent of security unblocking. + +## Installation handoff + +After install, direct users to Configure credentials (TTS may need a paid external +account), Select compatible Agent (MCP/Skill), or Appearance selection (theme). +Saved backend changes require restart; distinguish saved and currently running +versions. For breaking configuration/data updates, provide manual steps before +installation, including whether the old running version can continue. No generic +migration engine, automatic rollback or secret-purpose guessing is promised. + +A display cache has no install-authorizing TTL: the current Host refreshes the +official index for EVERY store installation. Offline/cached browsing and installed +management remain available, but a stale cache cannot authorize a new store install. +Known bans remain durable independently of that cache. + +## Local qualification versus public gates + +The local demonstration uses real Git commits, a rejected submission and revision, +fixed tools, HTTPS and real packages. Demo-author/reviewer and approved refs are +explicit simulations. A non-author using only this guide to submit/revise and an +independent user installing in a clean environment remain public qualification +evidence; local automation does not manufacture those identities or approvals. + +GitHub controls reference: [protected-branch reviews](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-pull-request-reviews-before-merging). diff --git a/catalog/VERIFICATION.md b/catalog/VERIFICATION.md new file mode 100644 index 0000000..9392e77 --- /dev/null +++ b/catalog/VERIFICATION.md @@ -0,0 +1,148 @@ +# S0 local verification — 2026-10-21 + +Experimental branch: `codex/extension-community-preview`. Nothing was pushed or +published. The empty `index.json` is a sequence-0 bootstrap, not a reviewed package +listing. Its `source_commit` and `generator_commit` identify the actual committed +generator/input; no wall-clock generation field is used. + +The fixed Host checker is `1840c8e4f646992ddaebea3139c8895f29743a8f`, CPython 3.12.10, +Windows. Its authoring dependencies were installed by hash into an independent +environment. The protocol repository's own requirements-dev.txt was also installed +by hash into a separate clean environment (including pytest 9.0.3 and packaging +26.3). No submitted package was imported or installed by catalog tooling. + +- Full protocol/SDK/catalog suite: **121 passed, 1 skipped** in that clean environment. + The skip is Windows symbolic-link creation privilege. After the final case-insensitive + reviewer check, catalog tests were rerun: **28 passed**. +- Actual Host reference wheels: Handoff, CSV Audit, Notes MCP, desktop theme and + MiMo generate catalog entries under synthetic review facts. Fish Audio passes + the same Host checker but is excluded only by the v1 third-party dependency policy. + Synthetic ownership/review URLs are fixtures only and never enter `packages/`. +- Negative real-wheel variants: altered tags, native suffix/magic, build suffix, + disclosed npx command, pip module, -c, direct URL and third-party dependency. + Each remains a separate catalog rejection; generic Host acceptance is unchanged. +- Identity, ownership, independent review (including login case), per-digest review, + duplicate JSON keys, malformed review date, URL credentials and protocol downgrade + have rejection cases. Immutable ID/version bytes, sequence conflict/rollback, + explicit unban and persistent safety facts have transition cases. +- Future kind/role/contract, unknown delivery/runtime and mixed packages have shared + compatibility fixtures. Legal security records remain parseable beside unknown + contribution values; bad safety/format/identity rejects the entire new snapshot. + This is a reference-consumer contract, not a shipped Electron catalog client. +- A committed synthetic input repository was generated twice with the actual fixed + Host checker: byte-identical output and the input commit timestamp. Corrupting the + cached artifact was rejected. The empty bootstrap also passed a second --check run. +- Protected-base CI entry point passed locally against unchanged empty index/source. + GitHub Actions itself was not run: the Host pin must first be made available + remotely. Workflow has Windows/Python pins, read-only permissions, protected-base + artifact inspection, independent generator/input ancestry checks, and review diff + upload. Public branch protection/2FA/reviewer governance remain S3 acceptance. + +Run the tests with explicit fixed-checker paths (the implementation branch may +advance while this checker remains pinned): + +```powershell +$env:AMADEUS_CATALOG_HOST = '' +$env:AMADEUS_CATALOG_PYTHON = '' +python -m pytest -q -rs +``` + +The generator uses a private wheel snapshot, with size/digest inspection before +policy checks. Unchanged derived facts can be reused only from the prior protected +main index at the same Host/policy pin. This allows withdrawal/security publication +when old assets are unavailable; arbitrary download or display caches confer no trust. + +Not implemented here: Host network fetch or mirrors, durable safety settings, +activation bans, store UI, user-triggered update flows, real public review approvals, +repository governance changes, or external-author qualification. No stable SDK or +new runtime contribution is introduced. + + +## S2 display follow-up (2026-10-21, local experiment) + +Commit `58d3892d55c6a7ec6f8626153f503291bb10ab9a` adds optional generated +`display.access` for the Host detail view. It excludes configuration defaults, +environment values and bearer tokens; publication and runtime contracts are unchanged. +Local `tests/test_catalog.py`: 18 passed, 11 skipped because no explicit historical +1840c8e Host checkout was supplied for those cases. Ruff passed. Those skipped fixed +Host checks are not claimed as requalified here. + +Host experiment `24d410e47b920d69445a3ced5163df676180321f` pins this generator separately +from its SDK/manifest source and runs the real renderer/IPC/net/installer journey. +Its `docs/verification/extensions/community-preview-batch4-2026-10-21.md` records the +27-check UI qualification with actual reference wheels. Catalog ownership/review data +in that journey is fixture authority, not real approvals. No index publication or main +merge occurred; the bootstrap remains empty. This section supplements the S0 baseline +above, rather than claiming its historical fixed-checker CI has rerun. + + +## S3 local review preparation (2026-10-21) + +`e09f431` adds unreviewed reference dossiers and current client status links. +The eight candidate wheels pass the fixed `1840c8e` exported static checker and +curated-v1 policy; full inventories/candidate-version diffs are recorded under +`review-candidates/`. No catalog approval, ownership claim, artifact URL or safety +publication was added. The bootstrap remains empty. + +Host code `ee45e337428de4ccd21d9f0252497a5c49a32270` packages the checker closure and +qualifies actual source and packaged shells with 15 existing shell checks plus +14 plugin checks: theme, mirror update, offline state, Tone Runner and Notes MCP. +Confirmations and index authority are fixtures. Fixed unmodified S1/S2 readers +accept compatible neighbors and understood safety beside future entries. The Host's +`docs/verification/extensions/community-preview-batch5-2026-10-21.md` records hashes, +exact dirty/commit boundaries and limitations. Catalog tests here remain 18 passed, +11 explicitly skipped fixed-checkout cases; exported checker evidence is separate. + +Read-only GitHub configuration checks found no main protection/applicable rules, +read-only default workflow tokens with no PR approval permission, and no enforced +organization 2FA. Individual maintainer 2FA is unverified. No settings were changed; +these remain release gates, alongside independent review and public reachable pins. + + +## Draft integration qualification (2026-10-23) + +Before pushing the catalog draft, the full protocol suite at 8fb39ba ran on +Windows / Python 3.12.10 with the explicit historical Host checker and its minimal +hashed environment: **132 passed, 1 skipped** (Windows symlink privilege). +The Host experiment incorporated main 5382b329 and reuses its dependency fixes. +A fresh publisher-only run of the six-phase local Git/HTTPS demonstration passed +input/publication CI, exact rebuild and candidate/live delivery verification in +all phases; malformed submission and publication negatives remained enforced. +Review identities and approvals were synthetic; the public index is still empty. + +Host draft must be pushed first so the fixed checker 1840c8e becomes remotely +reachable. Remote Actions results are separate from the local qualification. +No protected-branch change, real publisher approval, public artifact or index +release is authorized by this draft. + +Parallel icon protocol PR #4 has no file overlap here, but its optional icon +field is rejected by the current fixed Host checker. After the icon contract +and Host implementation are integrated, qualify a real icon-bearing wheel and +update the fixed checker and dependent pins explicitly; do not infer catalog +acceptance from schema acceptance alone. Host UI PR #255 also overlaps store +settings/IPC files and needs combined UI and admission verification on integration. + + +## Audit remediation (2026-10-23) + +The public v1 index moved to catalog/v1/index.json before publication. Supported +clients retain safety delivery on the immutable-format path; a future format +uses a new path. The fixed Host SHA is retained by extension-catalog-checker-d1, +not a Release; CI still verifies the SHA rather than trusting the tag. + +Admission no longer runs after proposal tests in a shared interpreter/job. +The new read-only pull_request_target workflow uses protected-base executable +inputs and fresh environments, installs only trusted hashed dependencies, and +treats proposal files as data. Proposal tests remain separate development checks. +Local reproduction confirmed -I executes .pth in a contaminated venv, whereas +fresh admission ignored malicious proposal Python/requirements/tests, accepted +valid empty data and rejected invalid safety records. This does not certify the +workflow's first introduction: after bootstrap reaches main, configure and +exercise its required admission check before accepting any actual package. + +After the code change: complete protocol suite 132 passed / 1 existing Windows +symlink skip; fresh six-phase local Git/HTTPS demonstration and 12 candidate/live +checks passed. Host real Electron exercises passed 27 UI and 30 transport/install/ +settings-recovery checks. Human approvals were synthetic and the public index is +still empty. Icon v1 is handled separately in #4/#255; this historical checker +cannot admit it until requalification on Host main. diff --git a/catalog/fixtures/compatibility.json b/catalog/fixtures/compatibility.json new file mode 100644 index 0000000..6aee8ac --- /dev/null +++ b/catalog/fixtures/compatibility.json @@ -0,0 +1,145 @@ +{ + "capabilities": { + "delivery": [ + "python-wheel" + ], + "runtime": [ + "none", + "python" + ], + "contributions": [ + { + "kind": "voice_backend", + "role": "tts", + "contract": "amadeus.voice.tts/v0" + }, + { + "kind": "mcp_server", + "role": "", + "contract": "amadeus.mcp-server/v0" + }, + { + "kind": "skill", + "role": "", + "contract": "amadeus.skill/v0" + }, + { + "kind": "theme", + "role": "", + "contract": "amadeus.ui.theme/v0" + } + ] + }, + "cases": [ + { + "name": "current", + "compatibility": { + "delivery": "python-wheel", + "runtime": "none", + "contributions": [ + { + "kind": "skill", + "role": "", + "contract": "amadeus.skill/v0" + } + ] + }, + "compatible": true + }, + { + "name": "unknown-kind", + "compatibility": { + "delivery": "python-wheel", + "runtime": "none", + "contributions": [ + { + "kind": "future", + "role": "", + "contract": "amadeus.skill/v0" + } + ] + }, + "compatible": false + }, + { + "name": "unknown-contract", + "compatibility": { + "delivery": "python-wheel", + "runtime": "none", + "contributions": [ + { + "kind": "skill", + "role": "", + "contract": "amadeus.skill/v99" + } + ] + }, + "compatible": false + }, + { + "name": "unknown-role", + "compatibility": { + "delivery": "python-wheel", + "runtime": "none", + "contributions": [ + { + "kind": "skill", + "role": "future", + "contract": "amadeus.skill/v0" + } + ] + }, + "compatible": false + }, + { + "name": "unknown-delivery", + "compatibility": { + "delivery": "future", + "runtime": "none", + "contributions": [ + { + "kind": "skill", + "role": "", + "contract": "amadeus.skill/v0" + } + ] + }, + "compatible": false + }, + { + "name": "unknown-runtime", + "compatibility": { + "delivery": "python-wheel", + "runtime": "future", + "contributions": [ + { + "kind": "skill", + "role": "", + "contract": "amadeus.skill/v0" + } + ] + }, + "compatible": false + }, + { + "name": "mixed-package", + "compatibility": { + "delivery": "python-wheel", + "runtime": "none", + "contributions": [ + { + "kind": "skill", + "role": "", + "contract": "amadeus.skill/v0" + }, + { + "kind": "future", + "role": "", + "contract": "future/v0" + } + ] + }, + "compatible": false + } + ] +} diff --git a/catalog/packages/.gitkeep b/catalog/packages/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/catalog/policy.json b/catalog/policy.json new file mode 100644 index 0000000..6d02cf3 --- /dev/null +++ b/catalog/policy.json @@ -0,0 +1,20 @@ +{ + "version": "curated-v1", + "wheel_tag": "py3-none-any", + "version_pattern": "(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)", + "allowed_dependencies": [ + "amadeus-extension-sdk" + ], + "native_suffixes": [ + ".pyd", + ".so", + ".dll", + ".exe", + ".dylib", + ".com", + ".msi", + ".wasm" + ], + "max_wheel_bytes": 268435456, + "max_index_bytes": 4194304 +} diff --git a/catalog/review-candidates/README.md b/catalog/review-candidates/README.md new file mode 100644 index 0000000..33a47f5 --- /dev/null +++ b/catalog/review-candidates/README.md @@ -0,0 +1,51 @@ +# Reference wheel candidates — not reviewed + +`reference-wheels.json` records eight local candidate artifacts: Tone TTS, Notes +MCP, Handoff Skill and desktop theme at 0.1.0/0.2.0. It contains their byte digests, +full file inventories/content diffs, static checker results, and exact checker +source hashes. No item is approved or published, and these are not external-author +evidence. The 0.1.0 → 0.2.0 comparisons use earlier **candidates**, not approved wheels. + +The checker files are unmodified Git blobs from Host +`1840c8e4f646992ddaebea3139c8895f29743a8f`, run with Windows CPython 3.12.10 and +the existing minimal dependency environment. Its lock SHA-256 is recorded. +The policy/diff implementation is the unchanged Host-vendored copy of protocol +`58d3892d55c6a7ec6f8626153f503291bb10ab9a`. All eight pass static Host wheel checks +and curated-v1 automated policy. This is an exported checker inspection, not a +claim that remote CI or `verify_host` ran against a full clean Git checkout. + +The actual wheel bytes are retained in the Host experiment under +`build/extension-author-journey/4005cfb5b41a/wheels`. This is a local evidence path, +not a public download URL. A new build may produce different bytes: reviewers must +receive and check these exact artifacts, or regenerate this packet for the new bytes. +The candidate IDs are illustrative `com.example.*` identities; confirm/allocate +publisher ownership before publication. An identity change requires a new wheel +and fresh review, not a catalog alias over the current bytes. + +For each version, a non-author reviewer must: + +1. Obtain the exact wheel, verify the recorded digest, and inspect all unpacked + files using [curated-v1](../REVIEW_CHECKLIST.md). An inventory/diff is only evidence. +2. Confirm package ownership, code license and separate material rights. These + examples declare MIT-0, but that declaration is not the reviewer's rights finding. +3. Review every executable entry/script/Skill instruction and actual access; + validate runtime behavior against Host D2/S1/S2/S3 evidence. Tone generates + synthetic PCM and must not be described as useful speech synthesis. +4. Record their own approval and evidence, fix findings and re-check changed bytes. +5. Only then create reviewed package input facts and immutable artifact URLs; + follow the separate publication PR and increasing-sequence procedure. + +The candidate file intentionally has no reviewer/date approval or download URL. +A maintainer must not translate `host_static_check: passed` into a reviewed record. +No candidate was added to `packages/`, `security.json`, or `index.json`. + +To reproduce the underlying checks with the exact bytes and pinned tools: + +```powershell + -I /tools/extension_package.py check-wheel +python tools/check_catalog.py --wheel --output +python tools/check_catalog.py --wheel <0.2.0-wheel> --previous-wheel <0.1.0-candidate-wheel> --output +``` + +The Host's shipping-shell fixture records synthetic authority and automated +native confirmations explicitly; it does not establish public reviewer identity. diff --git a/catalog/review-candidates/reference-wheels.json b/catalog/review-candidates/reference-wheels.json new file mode 100644 index 0000000..aeddbfe --- /dev/null +++ b/catalog/review-candidates/reference-wheels.json @@ -0,0 +1,2441 @@ +{ + "review_status": "not_reviewed", + "source_host_commit": "1840c8e4f646992ddaebea3139c8895f29743a8f", + "generator_commit": "58d3892d55c6a7ec6f8626153f503291bb10ab9a", + "platform": "win32", + "python": "3.12.10", + "requirements_sha256": "eca122787362dc7d39b309b931c5e17ce7fb0507f79524589f2978bb018c17e7", + "checker_files": { + "agent_host/__init__.py": "e0beb1b2027bc5484e2038279f543970ab06a7830394a510d4618df5d8450e04", + "agent_host/skills.py": "f7407f6d617b849354816470415ceb0c8985f5c300157845c40d1572bd2720be", + "pyproject.toml": "1bb4c7d83082bd76f4fbc079457c44eca944af7b5c0d1031493ed4d3365db18d", + "server/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "server/extensions/__init__.py": "4a8205c2b8537721b4a8dca066e5c3a2d96e1248d01fa6368ebcec4de5762f9f", + "server/extensions/amadeus-extension.v0.schema.json": "d8a47722fe2a154b5d92c0411f21f4e06bd29885c18d374ba934feb8dc9641f7", + "server/extensions/manifest.py": "966bc24d753e69f64f8898f5caaa9c86fab092b5a82d77da9a7c5dec9e84de97", + "server/extensions/package.py": "120ce9cbcb2ceb0797099906c6ef4d3139b801550d3883f7a417b88b70f169af", + "server/extensions/profile.json": "c62538270da9c82f6fe85303175a739ab35816863140a21e0a298515aaf48314", + "server/extensions/profile.py": "b5e6edfc0edc86f693af9e7a079db216d89685a44126ee55694b4f50ab821e90", + "server/extensions/protocol-source.json": "347906c1f8dc232b84ad0364aab7c14655ae9f0bcaea23dfe7072fe970529500", + "server/extensions/runner.py": "ff85336fb5b1c15b2ee3adb1533dede2116bb66b0f709485ba51d9845b9f3627", + "server/extensions/runtime.py": "3aa95347a01feaabb50aef6540210f825ebcf742ca030e061ac42830fddc92e3", + "server/extensions/sdk.py": "b4c2f17dc4acfc24ce9db0bdc64f03438a31d598c4f4680984c4de21b6be0663", + "server/extensions/snapshot.py": "7d26a0d2d6f2a2fb9ba217f1b15d20935c0d1b6ac9e733638f6c9248693143c5", + "server/extensions/theme-tokens.v0.json": "deffcdb549add19f09e49bdf5036b6d856c3f324f983947b3e9a7b83c611c9e1", + "server/extensions/tts.py": "673e26fa921bebca050f007ad3ccd890e8f3b6cce06e61b297f09b2cacd990e7", + "tools/extension_package.py": "071bef1fcaf9d766c9aaf6697fba223b46b8ac5f47090141de85671a7ea24f88", + "tools/requirements-extension-authoring.txt": "eca122787362dc7d39b309b931c5e17ce7fb0507f79524589f2978bb018c17e7" + }, + "artifacts": [ + { + "id": "com.example.handoff-skill", + "version": "0.1.0", + "filename": "amadeus_example_handoff_skill-0.1.0-py3-none-any.whl", + "sha256": "20571002ac5be0f90720d845af0c32c3168cda508ad83acf61ac0a71861c7fab", + "size": 4316, + "host_static_check": "passed", + "catalog_static_policy": "passed", + "license": "MIT-0", + "review_status": "not_reviewed", + "inventory": [ + { + "path": "amadeus_example_handoff/__init__.py", + "status": "added", + "before": null, + "after": "f58538f7c6e81df7a22ca20f2896e825c9f0c2789196d9c610d11d0187df30cd", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+\"\"\"Declarative resources; the Host never imports this package.\"\"\"" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff/amadeus-extension.json", + "status": "added", + "before": null, + "after": "5898615d073b9c279ac161810230d430c2c30e365b075a4c0e61bd0d14a989d0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+{\"license\": \"MIT-0\", \"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.handoff-skill\", \"version\": \"0.1.0\", \"display_name\": {\"en\": \"Work handoff Skill\", \"zh-CN\": \"\\u5de5\\u4f5c\\u4ea4\\u63a5 Skill\"}, \"description\": {\"en\": \"Write a concise handoff using the supplied facts and a packaged report layout.\", \"zh-CN\": \"\\u6839\\u636e\\u63d0\\u4f9b\\u7684\\u4e8b\\u5b9e\\u53ca\\u968f\\u5305\\u6a21\\u677f\\u6574\\u7406\\u5de5\\u4f5c\\u4ea4\\u63a5\\u3002\"}, \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\", \"platforms\": [\"win32\", \"darwin\", \"linux\"]}, \"runtime\": {\"kind\": \"none\"}, \"contributes\": {\"skill\": [{\"id\": \"handoff\", \"contract\": \"amadeus.skill/v0\", \"path\": \"skills/handoff-brief\", \"title\": {\"en\": \"Work handoff\", \"zh-CN\": \"\\u5de5\\u4f5c\\u4ea4\\u63a5\"}}]}}" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff/skills/handoff-brief/SKILL.md", + "status": "added", + "before": null, + "after": "6a714e52fc79ebe16218a27c2c380fdb6e2218f134dbc836b3d345270167c3db", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,13 @@", + "+---", + "+name: handoff-brief", + "+description: Prepare a concise work handoff or completion report from supplied changes, verification results and remaining work.", + "+---", + "+", + "+Read `references/layout.json` before drafting. Use its heading and section names", + "+in order, and put its revision marker at the bottom. Do not infer that a test was", + "+run or an item completed unless the supplied evidence says so.", + "+", + "+Read the user's specified input file. Its JSON has `changes`, `verification` and", + "+`remaining` lists. Write the requested Markdown output in the authorized work", + "+directory, using one bullet per supplied item. If no output path was given,", + "+return the report in the response. Do not edit source code or contact services." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff/skills/handoff-brief/references/layout.json", + "status": "added", + "before": null, + "after": "a85d517e126db69aa6966507709d0bec5e11efe4592e9d1b57d6f61cc5c70032", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+{", + "+ \"heading\": \"Work handoff\",", + "+ \"sections\": [\"Changes\", \"Verification\", \"Remaining work\"],", + "+ \"revision_marker\": \"handoff-layout-v1\"", + "+}" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.1.0.dist-info/METADATA", + "status": "added", + "before": null, + "after": "b32735d5972090302c1ed60f7dacd6be4e8f61f026d1eb99a766cd9a2f2587c2", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+Metadata-Version: 2.4", + "+Name: amadeus-example-handoff-skill", + "+Version: 0.1.0", + "+Summary: Portable Work handoff Skill for selected Amadeus Agents", + "+License-Expression: MIT-0", + "+Requires-Python: >=3.12", + "+License-File: LICENSE", + "+Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.1.0.dist-info/RECORD", + "status": "added", + "before": null, + "after": "42041f5a4aa209f4371ffa074d8142688022e477be8d127e706624bdcf72c52a", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,10 @@", + "+amadeus_example_handoff/__init__.py,sha256=9YU498boHfeiLKIPKJboJcnwwniRltnGENEdAYffMM0,66", + "+amadeus_example_handoff/amadeus-extension.json,sha256=WJhhXQc7nCeawWGBAjDUMMLDDjZbB1pMDmG9DRSpidA,774", + "+amadeus_example_handoff/skills/handoff-brief/SKILL.md,sha256=anFOUvx56-FiGKJ8LDgP224iGPE028g2s9NFJwFnw9s,700", + "+amadeus_example_handoff/skills/handoff-brief/references/layout.json,sha256=qF1RfhJttpqmlmUHcJ0L7F4R7-RZLp0bV9b2HMXHADI,135", + "+amadeus_example_handoff_skill-0.1.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "+amadeus_example_handoff_skill-0.1.0.dist-info/METADATA,sha256=syc11ZcgkDAsHtYPfazWvk6PYfAm0euZp2bNmi8lh8I,240", + "+amadeus_example_handoff_skill-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "+amadeus_example_handoff_skill-0.1.0.dist-info/entry_points.txt,sha256=ktRh6xchLranST5PLcWhn3Melr1ZkOT1vXWkOjOYkrM,73", + "+amadeus_example_handoff_skill-0.1.0.dist-info/top_level.txt,sha256=gUkMLQe1uQhDx8u9MdKH9xeSSEmBCKdk45n1eaxbo0c,24", + "+amadeus_example_handoff_skill-0.1.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.1.0.dist-info/WHEEL", + "status": "added", + "before": null, + "after": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+Wheel-Version: 1.0", + "+Generator: setuptools (83.0.0)", + "+Root-Is-Purelib: true", + "+Tag: py3-none-any", + "+" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.1.0.dist-info/entry_points.txt", + "status": "added", + "before": null, + "after": "92d461eb17212eb6a7493e4f2dc5a19f731e96bd5990e4f5bd75a43a339892b3", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,2 @@", + "+[amadeus.extensions]", + "+com.example.handoff-skill = amadeus_example_handoff" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.1.0.dist-info/licenses/LICENSE", + "status": "added", + "before": null, + "after": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,7 @@", + "+MIT No Attribution", + "+", + "+Copyright (c) 2026 Amadeus contributors", + "+", + "+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "+", + "+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.1.0.dist-info/top_level.txt", + "status": "added", + "before": null, + "after": "81490c2d07b5b90843c7cbbd31d287f7179248498108a764e399f579ac5ba347", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+amadeus_example_handoff" + ], + "diff_truncated": false + } + ], + "previous_candidate_diff": null + }, + { + "id": "com.example.handoff-skill", + "version": "0.2.0", + "filename": "amadeus_example_handoff_skill-0.2.0-py3-none-any.whl", + "sha256": "ac0c09ae15d842c6057312971ec0118ab3bf07643a68d0129519fea635b7fe87", + "size": 4318, + "host_static_check": "passed", + "catalog_static_policy": "passed", + "license": "MIT-0", + "review_status": "not_reviewed", + "inventory": [ + { + "path": "amadeus_example_handoff/__init__.py", + "status": "added", + "before": null, + "after": "f58538f7c6e81df7a22ca20f2896e825c9f0c2789196d9c610d11d0187df30cd", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+\"\"\"Declarative resources; the Host never imports this package.\"\"\"" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff/amadeus-extension.json", + "status": "added", + "before": null, + "after": "e9e93ec64070f964e474adedc7bf3e157a17e5606cd811a4f60970e0e8ac223a", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+{\"license\": \"MIT-0\", \"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.handoff-skill\", \"version\": \"0.2.0\", \"display_name\": {\"en\": \"Work handoff Skill\", \"zh-CN\": \"\\u5de5\\u4f5c\\u4ea4\\u63a5 Skill\"}, \"description\": {\"en\": \"Write a concise handoff using the supplied facts and a packaged report layout.\", \"zh-CN\": \"\\u6839\\u636e\\u63d0\\u4f9b\\u7684\\u4e8b\\u5b9e\\u53ca\\u968f\\u5305\\u6a21\\u677f\\u6574\\u7406\\u5de5\\u4f5c\\u4ea4\\u63a5\\u3002\"}, \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\", \"platforms\": [\"win32\", \"darwin\", \"linux\"]}, \"runtime\": {\"kind\": \"none\"}, \"contributes\": {\"skill\": [{\"id\": \"handoff\", \"contract\": \"amadeus.skill/v0\", \"path\": \"skills/handoff-brief\", \"title\": {\"en\": \"Work handoff\", \"zh-CN\": \"\\u5de5\\u4f5c\\u4ea4\\u63a5\"}}]}}" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff/skills/handoff-brief/SKILL.md", + "status": "added", + "before": null, + "after": "6a714e52fc79ebe16218a27c2c380fdb6e2218f134dbc836b3d345270167c3db", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,13 @@", + "+---", + "+name: handoff-brief", + "+description: Prepare a concise work handoff or completion report from supplied changes, verification results and remaining work.", + "+---", + "+", + "+Read `references/layout.json` before drafting. Use its heading and section names", + "+in order, and put its revision marker at the bottom. Do not infer that a test was", + "+run or an item completed unless the supplied evidence says so.", + "+", + "+Read the user's specified input file. Its JSON has `changes`, `verification` and", + "+`remaining` lists. Write the requested Markdown output in the authorized work", + "+directory, using one bullet per supplied item. If no output path was given,", + "+return the report in the response. Do not edit source code or contact services." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff/skills/handoff-brief/references/layout.json", + "status": "added", + "before": null, + "after": "ac3dbdef43addd55851420525150b3ce6a217d5778d27bcdcfed26d6e41bc17f", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+{", + "+ \"heading\": \"Work handoff\",", + "+ \"sections\": [\"Changes\", \"Verification\", \"Remaining work\"],", + "+ \"revision_marker\": \"handoff-layout-v2\"", + "+}" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.2.0.dist-info/METADATA", + "status": "added", + "before": null, + "after": "8182ec867a5d21c44b062a14dc7674e536167a3a3310d88c8b19208a57dc5fc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+Metadata-Version: 2.4", + "+Name: amadeus-example-handoff-skill", + "+Version: 0.2.0", + "+Summary: Portable Work handoff Skill for selected Amadeus Agents", + "+License-Expression: MIT-0", + "+Requires-Python: >=3.12", + "+License-File: LICENSE", + "+Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.2.0.dist-info/RECORD", + "status": "added", + "before": null, + "after": "86ee936cfbce04c6dc1631bf1186279081fb2cf55a85be747cd48dfa54c4bd3d", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,10 @@", + "+amadeus_example_handoff/__init__.py,sha256=9YU498boHfeiLKIPKJboJcnwwniRltnGENEdAYffMM0,66", + "+amadeus_example_handoff/amadeus-extension.json,sha256=6ek-xkBw-WTkdK3tx78-FXoX5WBs2BGk9glw4OisIjo,774", + "+amadeus_example_handoff/skills/handoff-brief/SKILL.md,sha256=anFOUvx56-FiGKJ8LDgP224iGPE028g2s9NFJwFnw9s,700", + "+amadeus_example_handoff/skills/handoff-brief/references/layout.json,sha256=rD2970Ot3VWFFCBSUVCzzmohfVd40nvNz-0m1uQbwX8,140", + "+amadeus_example_handoff_skill-0.2.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "+amadeus_example_handoff_skill-0.2.0.dist-info/METADATA,sha256=gYLshnpdIcRLBioU3HZ05TYWejozENiMixkgilfcX8A,240", + "+amadeus_example_handoff_skill-0.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "+amadeus_example_handoff_skill-0.2.0.dist-info/entry_points.txt,sha256=ktRh6xchLranST5PLcWhn3Melr1ZkOT1vXWkOjOYkrM,73", + "+amadeus_example_handoff_skill-0.2.0.dist-info/top_level.txt,sha256=gUkMLQe1uQhDx8u9MdKH9xeSSEmBCKdk45n1eaxbo0c,24", + "+amadeus_example_handoff_skill-0.2.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.2.0.dist-info/WHEEL", + "status": "added", + "before": null, + "after": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+Wheel-Version: 1.0", + "+Generator: setuptools (83.0.0)", + "+Root-Is-Purelib: true", + "+Tag: py3-none-any", + "+" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.2.0.dist-info/entry_points.txt", + "status": "added", + "before": null, + "after": "92d461eb17212eb6a7493e4f2dc5a19f731e96bd5990e4f5bd75a43a339892b3", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,2 @@", + "+[amadeus.extensions]", + "+com.example.handoff-skill = amadeus_example_handoff" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.2.0.dist-info/licenses/LICENSE", + "status": "added", + "before": null, + "after": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,7 @@", + "+MIT No Attribution", + "+", + "+Copyright (c) 2026 Amadeus contributors", + "+", + "+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "+", + "+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.2.0.dist-info/top_level.txt", + "status": "added", + "before": null, + "after": "81490c2d07b5b90843c7cbbd31d287f7179248498108a764e399f579ac5ba347", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+amadeus_example_handoff" + ], + "diff_truncated": false + } + ], + "previous_candidate_diff": [ + { + "path": "amadeus_example_handoff/amadeus-extension.json", + "status": "modified", + "before": "5898615d073b9c279ac161810230d430c2c30e365b075a4c0e61bd0d14a989d0", + "after": "e9e93ec64070f964e474adedc7bf3e157a17e5606cd811a4f60970e0e8ac223a", + "text_diff": [ + "--- ", + "+++ ", + "@@ -1 +1 @@", + "-{\"license\": \"MIT-0\", \"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.handoff-skill\", \"version\": \"0.1.0\", \"display_name\": {\"en\": \"Work handoff Skill\", \"zh-CN\": \"\\u5de5\\u4f5c\\u4ea4\\u63a5 Skill\"}, \"description\": {\"en\": \"Write a concise handoff using the supplied facts and a packaged report layout.\", \"zh-CN\": \"\\u6839\\u636e\\u63d0\\u4f9b\\u7684\\u4e8b\\u5b9e\\u53ca\\u968f\\u5305\\u6a21\\u677f\\u6574\\u7406\\u5de5\\u4f5c\\u4ea4\\u63a5\\u3002\"}, \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\", \"platforms\": [\"win32\", \"darwin\", \"linux\"]}, \"runtime\": {\"kind\": \"none\"}, \"contributes\": {\"skill\": [{\"id\": \"handoff\", \"contract\": \"amadeus.skill/v0\", \"path\": \"skills/handoff-brief\", \"title\": {\"en\": \"Work handoff\", \"zh-CN\": \"\\u5de5\\u4f5c\\u4ea4\\u63a5\"}}]}}", + "+{\"license\": \"MIT-0\", \"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.handoff-skill\", \"version\": \"0.2.0\", \"display_name\": {\"en\": \"Work handoff Skill\", \"zh-CN\": \"\\u5de5\\u4f5c\\u4ea4\\u63a5 Skill\"}, \"description\": {\"en\": \"Write a concise handoff using the supplied facts and a packaged report layout.\", \"zh-CN\": \"\\u6839\\u636e\\u63d0\\u4f9b\\u7684\\u4e8b\\u5b9e\\u53ca\\u968f\\u5305\\u6a21\\u677f\\u6574\\u7406\\u5de5\\u4f5c\\u4ea4\\u63a5\\u3002\"}, \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\", \"platforms\": [\"win32\", \"darwin\", \"linux\"]}, \"runtime\": {\"kind\": \"none\"}, \"contributes\": {\"skill\": [{\"id\": \"handoff\", \"contract\": \"amadeus.skill/v0\", \"path\": \"skills/handoff-brief\", \"title\": {\"en\": \"Work handoff\", \"zh-CN\": \"\\u5de5\\u4f5c\\u4ea4\\u63a5\"}}]}}" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff/skills/handoff-brief/references/layout.json", + "status": "modified", + "before": "a85d517e126db69aa6966507709d0bec5e11efe4592e9d1b57d6f61cc5c70032", + "after": "ac3dbdef43addd55851420525150b3ce6a217d5778d27bcdcfed26d6e41bc17f", + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,5 +1,5 @@", + " {", + " \"heading\": \"Work handoff\",", + " \"sections\": [\"Changes\", \"Verification\", \"Remaining work\"],", + "- \"revision_marker\": \"handoff-layout-v1\"", + "+ \"revision_marker\": \"handoff-layout-v2\"", + " }" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.1.0.dist-info/METADATA", + "status": "removed", + "before": "b32735d5972090302c1ed60f7dacd6be4e8f61f026d1eb99a766cd9a2f2587c2", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,8 +0,0 @@", + "-Metadata-Version: 2.4", + "-Name: amadeus-example-handoff-skill", + "-Version: 0.1.0", + "-Summary: Portable Work handoff Skill for selected Amadeus Agents", + "-License-Expression: MIT-0", + "-Requires-Python: >=3.12", + "-License-File: LICENSE", + "-Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.1.0.dist-info/RECORD", + "status": "removed", + "before": "42041f5a4aa209f4371ffa074d8142688022e477be8d127e706624bdcf72c52a", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,10 +0,0 @@", + "-amadeus_example_handoff/__init__.py,sha256=9YU498boHfeiLKIPKJboJcnwwniRltnGENEdAYffMM0,66", + "-amadeus_example_handoff/amadeus-extension.json,sha256=WJhhXQc7nCeawWGBAjDUMMLDDjZbB1pMDmG9DRSpidA,774", + "-amadeus_example_handoff/skills/handoff-brief/SKILL.md,sha256=anFOUvx56-FiGKJ8LDgP224iGPE028g2s9NFJwFnw9s,700", + "-amadeus_example_handoff/skills/handoff-brief/references/layout.json,sha256=qF1RfhJttpqmlmUHcJ0L7F4R7-RZLp0bV9b2HMXHADI,135", + "-amadeus_example_handoff_skill-0.1.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "-amadeus_example_handoff_skill-0.1.0.dist-info/METADATA,sha256=syc11ZcgkDAsHtYPfazWvk6PYfAm0euZp2bNmi8lh8I,240", + "-amadeus_example_handoff_skill-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "-amadeus_example_handoff_skill-0.1.0.dist-info/entry_points.txt,sha256=ktRh6xchLranST5PLcWhn3Melr1ZkOT1vXWkOjOYkrM,73", + "-amadeus_example_handoff_skill-0.1.0.dist-info/top_level.txt,sha256=gUkMLQe1uQhDx8u9MdKH9xeSSEmBCKdk45n1eaxbo0c,24", + "-amadeus_example_handoff_skill-0.1.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.1.0.dist-info/WHEEL", + "status": "removed", + "before": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,5 +0,0 @@", + "-Wheel-Version: 1.0", + "-Generator: setuptools (83.0.0)", + "-Root-Is-Purelib: true", + "-Tag: py3-none-any", + "-" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.1.0.dist-info/entry_points.txt", + "status": "removed", + "before": "92d461eb17212eb6a7493e4f2dc5a19f731e96bd5990e4f5bd75a43a339892b3", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,2 +0,0 @@", + "-[amadeus.extensions]", + "-com.example.handoff-skill = amadeus_example_handoff" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.1.0.dist-info/licenses/LICENSE", + "status": "removed", + "before": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,7 +0,0 @@", + "-MIT No Attribution", + "-", + "-Copyright (c) 2026 Amadeus contributors", + "-", + "-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "-", + "-THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.1.0.dist-info/top_level.txt", + "status": "removed", + "before": "81490c2d07b5b90843c7cbbd31d287f7179248498108a764e399f579ac5ba347", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1 +0,0 @@", + "-amadeus_example_handoff" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.2.0.dist-info/METADATA", + "status": "added", + "before": null, + "after": "8182ec867a5d21c44b062a14dc7674e536167a3a3310d88c8b19208a57dc5fc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+Metadata-Version: 2.4", + "+Name: amadeus-example-handoff-skill", + "+Version: 0.2.0", + "+Summary: Portable Work handoff Skill for selected Amadeus Agents", + "+License-Expression: MIT-0", + "+Requires-Python: >=3.12", + "+License-File: LICENSE", + "+Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.2.0.dist-info/RECORD", + "status": "added", + "before": null, + "after": "86ee936cfbce04c6dc1631bf1186279081fb2cf55a85be747cd48dfa54c4bd3d", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,10 @@", + "+amadeus_example_handoff/__init__.py,sha256=9YU498boHfeiLKIPKJboJcnwwniRltnGENEdAYffMM0,66", + "+amadeus_example_handoff/amadeus-extension.json,sha256=6ek-xkBw-WTkdK3tx78-FXoX5WBs2BGk9glw4OisIjo,774", + "+amadeus_example_handoff/skills/handoff-brief/SKILL.md,sha256=anFOUvx56-FiGKJ8LDgP224iGPE028g2s9NFJwFnw9s,700", + "+amadeus_example_handoff/skills/handoff-brief/references/layout.json,sha256=rD2970Ot3VWFFCBSUVCzzmohfVd40nvNz-0m1uQbwX8,140", + "+amadeus_example_handoff_skill-0.2.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "+amadeus_example_handoff_skill-0.2.0.dist-info/METADATA,sha256=gYLshnpdIcRLBioU3HZ05TYWejozENiMixkgilfcX8A,240", + "+amadeus_example_handoff_skill-0.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "+amadeus_example_handoff_skill-0.2.0.dist-info/entry_points.txt,sha256=ktRh6xchLranST5PLcWhn3Melr1ZkOT1vXWkOjOYkrM,73", + "+amadeus_example_handoff_skill-0.2.0.dist-info/top_level.txt,sha256=gUkMLQe1uQhDx8u9MdKH9xeSSEmBCKdk45n1eaxbo0c,24", + "+amadeus_example_handoff_skill-0.2.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.2.0.dist-info/WHEEL", + "status": "added", + "before": null, + "after": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+Wheel-Version: 1.0", + "+Generator: setuptools (83.0.0)", + "+Root-Is-Purelib: true", + "+Tag: py3-none-any", + "+" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.2.0.dist-info/entry_points.txt", + "status": "added", + "before": null, + "after": "92d461eb17212eb6a7493e4f2dc5a19f731e96bd5990e4f5bd75a43a339892b3", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,2 @@", + "+[amadeus.extensions]", + "+com.example.handoff-skill = amadeus_example_handoff" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.2.0.dist-info/licenses/LICENSE", + "status": "added", + "before": null, + "after": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,7 @@", + "+MIT No Attribution", + "+", + "+Copyright (c) 2026 Amadeus contributors", + "+", + "+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "+", + "+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_handoff_skill-0.2.0.dist-info/top_level.txt", + "status": "added", + "before": null, + "after": "81490c2d07b5b90843c7cbbd31d287f7179248498108a764e399f579ac5ba347", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+amadeus_example_handoff" + ], + "diff_truncated": false + } + ] + }, + { + "id": "com.example.notes-mcp", + "version": "0.1.0", + "filename": "amadeus_example_notes_mcp-0.1.0-py3-none-any.whl", + "sha256": "a249c1233ab259ead7d401619e5f898ce95dfff993fd0447b0a47d20c2869b5f", + "size": 5201, + "host_static_check": "passed", + "catalog_static_policy": "passed", + "license": "MIT-0", + "review_status": "not_reviewed", + "inventory": [ + { + "path": "amadeus_example_notes/__init__.py", + "status": "added", + "before": null, + "after": "21ce9e48f50ffe58bbcffda4a37db8b7ead966b1500224165cb29febc90bd9ef", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+\"\"\"Local notes MCP example; independent from Amadeus and its execution authority.\"\"\"" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes/__main__.py", + "status": "added", + "before": null, + "after": "7db7e9920a2d07ed3dd369d7871d506dd63909fd8b310a3c2e2193f96328e161", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,96 @@", + "+\"\"\"A small stdio MCP service for package-owned notes; no Host imports.\"\"\"", + "+from __future__ import annotations", + "+", + "+import json", + "+import os", + "+import re", + "+import sys", + "+import tempfile", + "+from pathlib import Path", + "+", + "+", + "+_NAME = re.compile(r\"[a-zA-Z0-9_-]{1,64}\")", + "+_MAX_LINE = 128 * 1024", + "+_TOOLS = [", + "+ {\"name\": \"list_notes\", \"description\": \"List names of notes stored by this extension.\",", + "+ \"inputSchema\": {\"type\": \"object\", \"properties\": {}, \"additionalProperties\": False}},", + "+ {\"name\": \"read_note\", \"description\": \"Read one note from this extension's data directory.\",", + "+ \"inputSchema\": {\"type\": \"object\", \"properties\": {\"name\": {\"type\": \"string\"}}, \"required\": [\"name\"], \"additionalProperties\": False}},", + "+ {\"name\": \"save_note\", \"description\": \"Save a named note in this extension's data directory.\",", + "+ \"inputSchema\": {\"type\": \"object\", \"properties\": {\"name\": {\"type\": \"string\"}, \"text\": {\"type\": \"string\", \"maxLength\": 10000}}, \"required\": [\"name\", \"text\"], \"additionalProperties\": False}},", + "+]", + "+", + "+", + "+def _tool(name, arguments, root: Path):", + "+ if not isinstance(arguments, dict):", + "+ raise ValueError(\"Tool arguments must be an object\")", + "+ if root.is_symlink() or (hasattr(root, \"is_junction\") and root.is_junction()):", + "+ raise ValueError(\"Notes storage cannot be a link\")", + "+ root.mkdir(parents=True, exist_ok=True)", + "+ if name == \"list_notes\":", + "+ return json.dumps(sorted(item.stem for item in root.glob(\"*.txt\")", + "+ if item.is_file() and not item.is_symlink() and _NAME.fullmatch(item.stem)))", + "+ note = arguments.get(\"name\")", + "+ if not isinstance(note, str) or not _NAME.fullmatch(note):", + "+ raise ValueError(\"Note name must contain 1 to 64 letters, digits, underscores or hyphens\")", + "+ target = root / (note + \".txt\")", + "+ if target.is_symlink():", + "+ raise ValueError(\"Notes cannot be symbolic links\")", + "+ if name == \"read_note\":", + "+ if target.stat().st_size > 40000:", + "+ raise ValueError(\"Note exceeds the size limit\")", + "+ return target.read_text(encoding=\"utf-8\")", + "+ if name != \"save_note\":", + "+ raise ValueError(\"Unknown notes tool\")", + "+ text = arguments.get(\"text\")", + "+ if not isinstance(text, str) or len(text) > 10000:", + "+ raise ValueError(\"Note text must be at most 10000 characters\")", + "+ descriptor, temporary = tempfile.mkstemp(dir=root, prefix=\".note-\", suffix=\".tmp\")", + "+ try:", + "+ with os.fdopen(descriptor, \"w\", encoding=\"utf-8\") as output:", + "+ output.write(text)", + "+ output.flush()", + "+ os.fsync(output.fileno())", + "+ os.replace(temporary, target)", + "+ finally:", + "+ Path(temporary).unlink(missing_ok=True)", + "+ return \"Note saved.\"", + "+", + "+", + "+def serve(input_stream, output_stream, root: Path):", + "+ while raw := input_stream.readline(_MAX_LINE + 1):", + "+ if len(raw) > _MAX_LINE:", + "+ return", + "+ try:", + "+ request = json.loads(raw)", + "+ except (ValueError, UnicodeError):", + "+ continue", + "+ if not isinstance(request, dict) or \"id\" not in request:", + "+ continue", + "+ method, params = request.get(\"method\"), request.get(\"params\") or {}", + "+ response = {\"jsonrpc\": \"2.0\", \"id\": request[\"id\"]}", + "+ if method == \"initialize\":", + "+ response[\"result\"] = {\"protocolVersion\": params.get(\"protocolVersion\", \"2025-06-18\"),", + "+ \"capabilities\": {\"tools\": {\"listChanged\": False}},", + "+ \"serverInfo\": {\"name\": \"amadeus-example-notes\", \"version\": \"0.1.0\"}}", + "+ elif method == \"ping\":", + "+ response[\"result\"] = {}", + "+ elif method == \"tools/list\":", + "+ response[\"result\"] = {\"tools\": _TOOLS}", + "+ elif method == \"tools/call\":", + "+ try:", + "+ text = _tool(params.get(\"name\"), params.get(\"arguments\", {}), root)", + "+ response[\"result\"] = {\"content\": [{\"type\": \"text\", \"text\": text}], \"isError\": False}", + "+ except (ValueError, OSError):", + "+ response[\"result\"] = {\"content\": [{\"type\": \"text\", \"text\": \"The note operation failed; check its name and data directory.\"}], \"isError\": True}", + "+ else:", + "+ response[\"error\"] = {\"code\": -32601, \"message\": \"Method not found\"}", + "+ output_stream.write(json.dumps(response, ensure_ascii=True) + \"\\n\")", + "+ output_stream.flush()", + "+", + "+", + "+if __name__ == \"__main__\":", + "+ directory = os.environ.get(\"AMADEUS_NOTES_DIR\")", + "+ if not directory:", + "+ raise SystemExit(\"AMADEUS_NOTES_DIR is required\")", + "+ serve(sys.stdin, sys.stdout, Path(directory))" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes/amadeus-extension.json", + "status": "added", + "before": null, + "after": "f47482ba46558271b32f1e5a570db77439a16a3c3b3c51f899009a18c733af52", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+{\"license\": \"MIT-0\", \"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.notes-mcp\", \"version\": \"0.1.0\", \"display_name\": {\"en\": \"Local notes MCP\", \"zh-CN\": \"\\u672c\\u5730\\u7b14\\u8bb0 MCP\"}, \"description\": {\"en\": \"List, read and save notes in this package's own data directory through selected Work Providers.\", \"zh-CN\": \"\\u901a\\u8fc7\\u9009\\u5b9a\\u7684 Work Provider \\u5217\\u51fa\\u3001\\u8bfb\\u53d6\\u548c\\u4fdd\\u5b58\\u6b64\\u6269\\u5c55\\u6570\\u636e\\u76ee\\u5f55\\u4e2d\\u7684\\u7b14\\u8bb0\\u3002\"}, \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\", \"platforms\": [\"win32\"]}, \"runtime\": {\"kind\": \"python\", \"requires_python\": \">=3.12\"}, \"contributes\": {\"mcp_server\": [{\"id\": \"notes\", \"contract\": \"amadeus.mcp-server/v0\", \"transport\": \"stdio\", \"command\": \"${python}\", \"args\": [\"-I\", \"-m\", \"amadeus_example_notes\"], \"env\": {\"AMADEUS_NOTES_DIR\": \"${data}/notes\"}}]}}" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.1.0.dist-info/METADATA", + "status": "added", + "before": null, + "after": "0f4026b7c7603e2f64a976236a7b64cd85515111157601276f7590fa3c1979d7", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+Metadata-Version: 2.4", + "+Name: amadeus-example-notes-mcp", + "+Version: 0.1.0", + "+Summary: Independent local notes MCP package for Amadeus Work Providers", + "+License-Expression: MIT-0", + "+Requires-Python: >=3.12", + "+License-File: LICENSE", + "+Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.1.0.dist-info/RECORD", + "status": "added", + "before": null, + "after": "66a144c87ed61659ed85b310b2720236e9c5b29a894cc8a1b8c35a91f31ca9ca", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,9 @@", + "+amadeus_example_notes/__init__.py,sha256=Ic6eSPUP_li7z_2ko324t-rZZrFQAiQWXLKf68kL2e8,85", + "+amadeus_example_notes/__main__.py,sha256=fbfpkgotB-0902nXhx1QbdY5Cf2LMQo8LiGT-WMo4WE,4465", + "+amadeus_example_notes/amadeus-extension.json,sha256=9HSCukZVgnGzLx5aVw23dDmhajw7PFH4mQCaGMczr1I,891", + "+amadeus_example_notes_mcp-0.1.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "+amadeus_example_notes_mcp-0.1.0.dist-info/METADATA,sha256=D0Amt8dgPi9kqXYjantkzYVRUREVdgEnb3WQ-jwZedc,243", + "+amadeus_example_notes_mcp-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "+amadeus_example_notes_mcp-0.1.0.dist-info/entry_points.txt,sha256=CVYh8FjHbezFO-SnQAeqvbegwj-Hc43390-sH-yr5ZI,67", + "+amadeus_example_notes_mcp-0.1.0.dist-info/top_level.txt,sha256=3wwSxqp4Genc66JSRkb15-JAPKg5ua-XmSBJ9b4sYEs,22", + "+amadeus_example_notes_mcp-0.1.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.1.0.dist-info/WHEEL", + "status": "added", + "before": null, + "after": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+Wheel-Version: 1.0", + "+Generator: setuptools (83.0.0)", + "+Root-Is-Purelib: true", + "+Tag: py3-none-any", + "+" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.1.0.dist-info/entry_points.txt", + "status": "added", + "before": null, + "after": "095621f058c76decc53be4a74007aabdb7a0c23f87738df7f74fac1fecabe592", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,2 @@", + "+[amadeus.extensions]", + "+com.example.notes-mcp = amadeus_example_notes" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.1.0.dist-info/licenses/LICENSE", + "status": "added", + "before": null, + "after": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,7 @@", + "+MIT No Attribution", + "+", + "+Copyright (c) 2026 Amadeus contributors", + "+", + "+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "+", + "+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.1.0.dist-info/top_level.txt", + "status": "added", + "before": null, + "after": "df0c12c6aa7819e9dceba2524646f5e7e2403ca839b9af97992049f5be2c604b", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+amadeus_example_notes" + ], + "diff_truncated": false + } + ], + "previous_candidate_diff": null + }, + { + "id": "com.example.notes-mcp", + "version": "0.2.0", + "filename": "amadeus_example_notes_mcp-0.2.0-py3-none-any.whl", + "sha256": "79ea4e1c513beaaef1292a9984f706a7ca0fd93899c7010c69c34d90624b8750", + "size": 5201, + "host_static_check": "passed", + "catalog_static_policy": "passed", + "license": "MIT-0", + "review_status": "not_reviewed", + "inventory": [ + { + "path": "amadeus_example_notes/__init__.py", + "status": "added", + "before": null, + "after": "21ce9e48f50ffe58bbcffda4a37db8b7ead966b1500224165cb29febc90bd9ef", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+\"\"\"Local notes MCP example; independent from Amadeus and its execution authority.\"\"\"" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes/__main__.py", + "status": "added", + "before": null, + "after": "7db7e9920a2d07ed3dd369d7871d506dd63909fd8b310a3c2e2193f96328e161", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,96 @@", + "+\"\"\"A small stdio MCP service for package-owned notes; no Host imports.\"\"\"", + "+from __future__ import annotations", + "+", + "+import json", + "+import os", + "+import re", + "+import sys", + "+import tempfile", + "+from pathlib import Path", + "+", + "+", + "+_NAME = re.compile(r\"[a-zA-Z0-9_-]{1,64}\")", + "+_MAX_LINE = 128 * 1024", + "+_TOOLS = [", + "+ {\"name\": \"list_notes\", \"description\": \"List names of notes stored by this extension.\",", + "+ \"inputSchema\": {\"type\": \"object\", \"properties\": {}, \"additionalProperties\": False}},", + "+ {\"name\": \"read_note\", \"description\": \"Read one note from this extension's data directory.\",", + "+ \"inputSchema\": {\"type\": \"object\", \"properties\": {\"name\": {\"type\": \"string\"}}, \"required\": [\"name\"], \"additionalProperties\": False}},", + "+ {\"name\": \"save_note\", \"description\": \"Save a named note in this extension's data directory.\",", + "+ \"inputSchema\": {\"type\": \"object\", \"properties\": {\"name\": {\"type\": \"string\"}, \"text\": {\"type\": \"string\", \"maxLength\": 10000}}, \"required\": [\"name\", \"text\"], \"additionalProperties\": False}},", + "+]", + "+", + "+", + "+def _tool(name, arguments, root: Path):", + "+ if not isinstance(arguments, dict):", + "+ raise ValueError(\"Tool arguments must be an object\")", + "+ if root.is_symlink() or (hasattr(root, \"is_junction\") and root.is_junction()):", + "+ raise ValueError(\"Notes storage cannot be a link\")", + "+ root.mkdir(parents=True, exist_ok=True)", + "+ if name == \"list_notes\":", + "+ return json.dumps(sorted(item.stem for item in root.glob(\"*.txt\")", + "+ if item.is_file() and not item.is_symlink() and _NAME.fullmatch(item.stem)))", + "+ note = arguments.get(\"name\")", + "+ if not isinstance(note, str) or not _NAME.fullmatch(note):", + "+ raise ValueError(\"Note name must contain 1 to 64 letters, digits, underscores or hyphens\")", + "+ target = root / (note + \".txt\")", + "+ if target.is_symlink():", + "+ raise ValueError(\"Notes cannot be symbolic links\")", + "+ if name == \"read_note\":", + "+ if target.stat().st_size > 40000:", + "+ raise ValueError(\"Note exceeds the size limit\")", + "+ return target.read_text(encoding=\"utf-8\")", + "+ if name != \"save_note\":", + "+ raise ValueError(\"Unknown notes tool\")", + "+ text = arguments.get(\"text\")", + "+ if not isinstance(text, str) or len(text) > 10000:", + "+ raise ValueError(\"Note text must be at most 10000 characters\")", + "+ descriptor, temporary = tempfile.mkstemp(dir=root, prefix=\".note-\", suffix=\".tmp\")", + "+ try:", + "+ with os.fdopen(descriptor, \"w\", encoding=\"utf-8\") as output:", + "+ output.write(text)", + "+ output.flush()", + "+ os.fsync(output.fileno())", + "+ os.replace(temporary, target)", + "+ finally:", + "+ Path(temporary).unlink(missing_ok=True)", + "+ return \"Note saved.\"", + "+", + "+", + "+def serve(input_stream, output_stream, root: Path):", + "+ while raw := input_stream.readline(_MAX_LINE + 1):", + "+ if len(raw) > _MAX_LINE:", + "+ return", + "+ try:", + "+ request = json.loads(raw)", + "+ except (ValueError, UnicodeError):", + "+ continue", + "+ if not isinstance(request, dict) or \"id\" not in request:", + "+ continue", + "+ method, params = request.get(\"method\"), request.get(\"params\") or {}", + "+ response = {\"jsonrpc\": \"2.0\", \"id\": request[\"id\"]}", + "+ if method == \"initialize\":", + "+ response[\"result\"] = {\"protocolVersion\": params.get(\"protocolVersion\", \"2025-06-18\"),", + "+ \"capabilities\": {\"tools\": {\"listChanged\": False}},", + "+ \"serverInfo\": {\"name\": \"amadeus-example-notes\", \"version\": \"0.1.0\"}}", + "+ elif method == \"ping\":", + "+ response[\"result\"] = {}", + "+ elif method == \"tools/list\":", + "+ response[\"result\"] = {\"tools\": _TOOLS}", + "+ elif method == \"tools/call\":", + "+ try:", + "+ text = _tool(params.get(\"name\"), params.get(\"arguments\", {}), root)", + "+ response[\"result\"] = {\"content\": [{\"type\": \"text\", \"text\": text}], \"isError\": False}", + "+ except (ValueError, OSError):", + "+ response[\"result\"] = {\"content\": [{\"type\": \"text\", \"text\": \"The note operation failed; check its name and data directory.\"}], \"isError\": True}", + "+ else:", + "+ response[\"error\"] = {\"code\": -32601, \"message\": \"Method not found\"}", + "+ output_stream.write(json.dumps(response, ensure_ascii=True) + \"\\n\")", + "+ output_stream.flush()", + "+", + "+", + "+if __name__ == \"__main__\":", + "+ directory = os.environ.get(\"AMADEUS_NOTES_DIR\")", + "+ if not directory:", + "+ raise SystemExit(\"AMADEUS_NOTES_DIR is required\")", + "+ serve(sys.stdin, sys.stdout, Path(directory))" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes/amadeus-extension.json", + "status": "added", + "before": null, + "after": "43f481ef9e4a50c4091921d70ab68c0f7228c5803f3eedbf65b85cafcb29f77c", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+{\"license\": \"MIT-0\", \"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.notes-mcp\", \"version\": \"0.2.0\", \"display_name\": {\"en\": \"Local notes MCP\", \"zh-CN\": \"\\u672c\\u5730\\u7b14\\u8bb0 MCP\"}, \"description\": {\"en\": \"List, read and save notes in this package's own data directory through selected Work Providers.\", \"zh-CN\": \"\\u901a\\u8fc7\\u9009\\u5b9a\\u7684 Work Provider \\u5217\\u51fa\\u3001\\u8bfb\\u53d6\\u548c\\u4fdd\\u5b58\\u6b64\\u6269\\u5c55\\u6570\\u636e\\u76ee\\u5f55\\u4e2d\\u7684\\u7b14\\u8bb0\\u3002\"}, \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\", \"platforms\": [\"win32\"]}, \"runtime\": {\"kind\": \"python\", \"requires_python\": \">=3.12\"}, \"contributes\": {\"mcp_server\": [{\"id\": \"notes\", \"contract\": \"amadeus.mcp-server/v0\", \"transport\": \"stdio\", \"command\": \"${python}\", \"args\": [\"-I\", \"-m\", \"amadeus_example_notes\"], \"env\": {\"AMADEUS_NOTES_DIR\": \"${data}/notes\"}}]}}" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.2.0.dist-info/METADATA", + "status": "added", + "before": null, + "after": "9458579149fc0deebdaf70c951d5fb65f7db53bca57ded719dc242b0128313db", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+Metadata-Version: 2.4", + "+Name: amadeus-example-notes-mcp", + "+Version: 0.2.0", + "+Summary: Independent local notes MCP package for Amadeus Work Providers", + "+License-Expression: MIT-0", + "+Requires-Python: >=3.12", + "+License-File: LICENSE", + "+Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.2.0.dist-info/RECORD", + "status": "added", + "before": null, + "after": "b8f397bbe3eb5043197b59170b5c63f17e1800a603b7e18a40dc0e0b12270fa3", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,9 @@", + "+amadeus_example_notes/__init__.py,sha256=Ic6eSPUP_li7z_2ko324t-rZZrFQAiQWXLKf68kL2e8,85", + "+amadeus_example_notes/__main__.py,sha256=fbfpkgotB-0902nXhx1QbdY5Cf2LMQo8LiGT-WMo4WE,4465", + "+amadeus_example_notes/amadeus-extension.json,sha256=Q_SB755KUMQJGSHXCraMD3IoxYA_Pu2_Zbhcr8sp93w,891", + "+amadeus_example_notes_mcp-0.2.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "+amadeus_example_notes_mcp-0.2.0.dist-info/METADATA,sha256=lFhXkUn8De69r3DJUdX7ZffbU7ylfe1xncJCsBKDE9s,243", + "+amadeus_example_notes_mcp-0.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "+amadeus_example_notes_mcp-0.2.0.dist-info/entry_points.txt,sha256=CVYh8FjHbezFO-SnQAeqvbegwj-Hc43390-sH-yr5ZI,67", + "+amadeus_example_notes_mcp-0.2.0.dist-info/top_level.txt,sha256=3wwSxqp4Genc66JSRkb15-JAPKg5ua-XmSBJ9b4sYEs,22", + "+amadeus_example_notes_mcp-0.2.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.2.0.dist-info/WHEEL", + "status": "added", + "before": null, + "after": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+Wheel-Version: 1.0", + "+Generator: setuptools (83.0.0)", + "+Root-Is-Purelib: true", + "+Tag: py3-none-any", + "+" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.2.0.dist-info/entry_points.txt", + "status": "added", + "before": null, + "after": "095621f058c76decc53be4a74007aabdb7a0c23f87738df7f74fac1fecabe592", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,2 @@", + "+[amadeus.extensions]", + "+com.example.notes-mcp = amadeus_example_notes" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.2.0.dist-info/licenses/LICENSE", + "status": "added", + "before": null, + "after": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,7 @@", + "+MIT No Attribution", + "+", + "+Copyright (c) 2026 Amadeus contributors", + "+", + "+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "+", + "+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.2.0.dist-info/top_level.txt", + "status": "added", + "before": null, + "after": "df0c12c6aa7819e9dceba2524646f5e7e2403ca839b9af97992049f5be2c604b", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+amadeus_example_notes" + ], + "diff_truncated": false + } + ], + "previous_candidate_diff": [ + { + "path": "amadeus_example_notes/amadeus-extension.json", + "status": "modified", + "before": "f47482ba46558271b32f1e5a570db77439a16a3c3b3c51f899009a18c733af52", + "after": "43f481ef9e4a50c4091921d70ab68c0f7228c5803f3eedbf65b85cafcb29f77c", + "text_diff": [ + "--- ", + "+++ ", + "@@ -1 +1 @@", + "-{\"license\": \"MIT-0\", \"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.notes-mcp\", \"version\": \"0.1.0\", \"display_name\": {\"en\": \"Local notes MCP\", \"zh-CN\": \"\\u672c\\u5730\\u7b14\\u8bb0 MCP\"}, \"description\": {\"en\": \"List, read and save notes in this package's own data directory through selected Work Providers.\", \"zh-CN\": \"\\u901a\\u8fc7\\u9009\\u5b9a\\u7684 Work Provider \\u5217\\u51fa\\u3001\\u8bfb\\u53d6\\u548c\\u4fdd\\u5b58\\u6b64\\u6269\\u5c55\\u6570\\u636e\\u76ee\\u5f55\\u4e2d\\u7684\\u7b14\\u8bb0\\u3002\"}, \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\", \"platforms\": [\"win32\"]}, \"runtime\": {\"kind\": \"python\", \"requires_python\": \">=3.12\"}, \"contributes\": {\"mcp_server\": [{\"id\": \"notes\", \"contract\": \"amadeus.mcp-server/v0\", \"transport\": \"stdio\", \"command\": \"${python}\", \"args\": [\"-I\", \"-m\", \"amadeus_example_notes\"], \"env\": {\"AMADEUS_NOTES_DIR\": \"${data}/notes\"}}]}}", + "+{\"license\": \"MIT-0\", \"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.notes-mcp\", \"version\": \"0.2.0\", \"display_name\": {\"en\": \"Local notes MCP\", \"zh-CN\": \"\\u672c\\u5730\\u7b14\\u8bb0 MCP\"}, \"description\": {\"en\": \"List, read and save notes in this package's own data directory through selected Work Providers.\", \"zh-CN\": \"\\u901a\\u8fc7\\u9009\\u5b9a\\u7684 Work Provider \\u5217\\u51fa\\u3001\\u8bfb\\u53d6\\u548c\\u4fdd\\u5b58\\u6b64\\u6269\\u5c55\\u6570\\u636e\\u76ee\\u5f55\\u4e2d\\u7684\\u7b14\\u8bb0\\u3002\"}, \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\", \"platforms\": [\"win32\"]}, \"runtime\": {\"kind\": \"python\", \"requires_python\": \">=3.12\"}, \"contributes\": {\"mcp_server\": [{\"id\": \"notes\", \"contract\": \"amadeus.mcp-server/v0\", \"transport\": \"stdio\", \"command\": \"${python}\", \"args\": [\"-I\", \"-m\", \"amadeus_example_notes\"], \"env\": {\"AMADEUS_NOTES_DIR\": \"${data}/notes\"}}]}}" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.1.0.dist-info/METADATA", + "status": "removed", + "before": "0f4026b7c7603e2f64a976236a7b64cd85515111157601276f7590fa3c1979d7", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,8 +0,0 @@", + "-Metadata-Version: 2.4", + "-Name: amadeus-example-notes-mcp", + "-Version: 0.1.0", + "-Summary: Independent local notes MCP package for Amadeus Work Providers", + "-License-Expression: MIT-0", + "-Requires-Python: >=3.12", + "-License-File: LICENSE", + "-Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.1.0.dist-info/RECORD", + "status": "removed", + "before": "66a144c87ed61659ed85b310b2720236e9c5b29a894cc8a1b8c35a91f31ca9ca", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,9 +0,0 @@", + "-amadeus_example_notes/__init__.py,sha256=Ic6eSPUP_li7z_2ko324t-rZZrFQAiQWXLKf68kL2e8,85", + "-amadeus_example_notes/__main__.py,sha256=fbfpkgotB-0902nXhx1QbdY5Cf2LMQo8LiGT-WMo4WE,4465", + "-amadeus_example_notes/amadeus-extension.json,sha256=9HSCukZVgnGzLx5aVw23dDmhajw7PFH4mQCaGMczr1I,891", + "-amadeus_example_notes_mcp-0.1.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "-amadeus_example_notes_mcp-0.1.0.dist-info/METADATA,sha256=D0Amt8dgPi9kqXYjantkzYVRUREVdgEnb3WQ-jwZedc,243", + "-amadeus_example_notes_mcp-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "-amadeus_example_notes_mcp-0.1.0.dist-info/entry_points.txt,sha256=CVYh8FjHbezFO-SnQAeqvbegwj-Hc43390-sH-yr5ZI,67", + "-amadeus_example_notes_mcp-0.1.0.dist-info/top_level.txt,sha256=3wwSxqp4Genc66JSRkb15-JAPKg5ua-XmSBJ9b4sYEs,22", + "-amadeus_example_notes_mcp-0.1.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.1.0.dist-info/WHEEL", + "status": "removed", + "before": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,5 +0,0 @@", + "-Wheel-Version: 1.0", + "-Generator: setuptools (83.0.0)", + "-Root-Is-Purelib: true", + "-Tag: py3-none-any", + "-" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.1.0.dist-info/entry_points.txt", + "status": "removed", + "before": "095621f058c76decc53be4a74007aabdb7a0c23f87738df7f74fac1fecabe592", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,2 +0,0 @@", + "-[amadeus.extensions]", + "-com.example.notes-mcp = amadeus_example_notes" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.1.0.dist-info/licenses/LICENSE", + "status": "removed", + "before": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,7 +0,0 @@", + "-MIT No Attribution", + "-", + "-Copyright (c) 2026 Amadeus contributors", + "-", + "-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "-", + "-THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.1.0.dist-info/top_level.txt", + "status": "removed", + "before": "df0c12c6aa7819e9dceba2524646f5e7e2403ca839b9af97992049f5be2c604b", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1 +0,0 @@", + "-amadeus_example_notes" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.2.0.dist-info/METADATA", + "status": "added", + "before": null, + "after": "9458579149fc0deebdaf70c951d5fb65f7db53bca57ded719dc242b0128313db", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+Metadata-Version: 2.4", + "+Name: amadeus-example-notes-mcp", + "+Version: 0.2.0", + "+Summary: Independent local notes MCP package for Amadeus Work Providers", + "+License-Expression: MIT-0", + "+Requires-Python: >=3.12", + "+License-File: LICENSE", + "+Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.2.0.dist-info/RECORD", + "status": "added", + "before": null, + "after": "b8f397bbe3eb5043197b59170b5c63f17e1800a603b7e18a40dc0e0b12270fa3", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,9 @@", + "+amadeus_example_notes/__init__.py,sha256=Ic6eSPUP_li7z_2ko324t-rZZrFQAiQWXLKf68kL2e8,85", + "+amadeus_example_notes/__main__.py,sha256=fbfpkgotB-0902nXhx1QbdY5Cf2LMQo8LiGT-WMo4WE,4465", + "+amadeus_example_notes/amadeus-extension.json,sha256=Q_SB755KUMQJGSHXCraMD3IoxYA_Pu2_Zbhcr8sp93w,891", + "+amadeus_example_notes_mcp-0.2.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "+amadeus_example_notes_mcp-0.2.0.dist-info/METADATA,sha256=lFhXkUn8De69r3DJUdX7ZffbU7ylfe1xncJCsBKDE9s,243", + "+amadeus_example_notes_mcp-0.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "+amadeus_example_notes_mcp-0.2.0.dist-info/entry_points.txt,sha256=CVYh8FjHbezFO-SnQAeqvbegwj-Hc43390-sH-yr5ZI,67", + "+amadeus_example_notes_mcp-0.2.0.dist-info/top_level.txt,sha256=3wwSxqp4Genc66JSRkb15-JAPKg5ua-XmSBJ9b4sYEs,22", + "+amadeus_example_notes_mcp-0.2.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.2.0.dist-info/WHEEL", + "status": "added", + "before": null, + "after": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+Wheel-Version: 1.0", + "+Generator: setuptools (83.0.0)", + "+Root-Is-Purelib: true", + "+Tag: py3-none-any", + "+" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.2.0.dist-info/entry_points.txt", + "status": "added", + "before": null, + "after": "095621f058c76decc53be4a74007aabdb7a0c23f87738df7f74fac1fecabe592", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,2 @@", + "+[amadeus.extensions]", + "+com.example.notes-mcp = amadeus_example_notes" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.2.0.dist-info/licenses/LICENSE", + "status": "added", + "before": null, + "after": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,7 @@", + "+MIT No Attribution", + "+", + "+Copyright (c) 2026 Amadeus contributors", + "+", + "+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "+", + "+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_notes_mcp-0.2.0.dist-info/top_level.txt", + "status": "added", + "before": null, + "after": "df0c12c6aa7819e9dceba2524646f5e7e2403ca839b9af97992049f5be2c604b", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+amadeus_example_notes" + ], + "diff_truncated": false + } + ] + }, + { + "id": "com.example.desktop-theme", + "version": "0.1.0", + "filename": "amadeus_example_theme-0.1.0-py3-none-any.whl", + "sha256": "d70397a19c8698f0352cfe57df385ab648a9d7e8f0b8f7facf514ee429cf852e", + "size": 5596, + "host_static_check": "passed", + "catalog_static_policy": "passed", + "license": "MIT-0", + "review_status": "not_reviewed", + "inventory": [ + { + "path": "amadeus_example_theme-0.1.0.dist-info/METADATA", + "status": "added", + "before": null, + "after": "a9131fe7f0b6c1ff5f30da8d8a148dbb8e19cc480a020f9941fc42dcb7f0e51d", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+Metadata-Version: 2.4", + "+Name: amadeus-example-theme", + "+Version: 0.1.0", + "+Summary: Two declarative desktop palettes for the extension pilot", + "+License-Expression: MIT-0", + "+Requires-Python: >=3.11", + "+License-File: LICENSE", + "+Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.1.0.dist-info/RECORD", + "status": "added", + "before": null, + "after": "4f958fd6d55823abbce4bca1d9f07c436f59d285e96f630b9ad51b7182dad805", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,9 @@", + "+amadeus_example_theme/__init__.py,sha256=9YU498boHfeiLKIPKJboJcnwwniRltnGENEdAYffMM0,66", + "+amadeus_example_theme/amadeus-extension.json,sha256=nm4n1Ng1qfJMeMIt4rcUqFvewmXdfYtysJftxOxSI_E,1468", + "+amadeus_example_theme/background.png,sha256=oJ1eOa7qkgUgx6YDzHXzubA5uzB4i_-YQsBm9wmT6QA,2762", + "+amadeus_example_theme-0.1.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "+amadeus_example_theme-0.1.0.dist-info/METADATA,sha256=qRMf5_C2wf9fMNqNihSNu44ZzEgKAg-ZQfxC3Lfw5R0,233", + "+amadeus_example_theme-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "+amadeus_example_theme-0.1.0.dist-info/entry_points.txt,sha256=UvpUNu06rRUQGvKT2t0zzVX0_9wsbXuymSJX0EtmGLU,71", + "+amadeus_example_theme-0.1.0.dist-info/top_level.txt,sha256=4ffmZWk0eKXndFUZP5X6A-ZXwHD_4qavFgktjVZUSIE,22", + "+amadeus_example_theme-0.1.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.1.0.dist-info/WHEEL", + "status": "added", + "before": null, + "after": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+Wheel-Version: 1.0", + "+Generator: setuptools (83.0.0)", + "+Root-Is-Purelib: true", + "+Tag: py3-none-any", + "+" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.1.0.dist-info/entry_points.txt", + "status": "added", + "before": null, + "after": "52fa5436ed3aad15101af293dadd33cd55f4ffdc2c6d7bb2992257d04b6618b5", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,2 @@", + "+[amadeus.extensions]", + "+com.example.desktop-theme = amadeus_example_theme" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.1.0.dist-info/licenses/LICENSE", + "status": "added", + "before": null, + "after": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,7 @@", + "+MIT No Attribution", + "+", + "+Copyright (c) 2026 Amadeus contributors", + "+", + "+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "+", + "+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.1.0.dist-info/top_level.txt", + "status": "added", + "before": null, + "after": "e1f7e665693478a5e77455193f95fa03e657c070ffe2a6af16092d8d56544881", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+amadeus_example_theme" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme/__init__.py", + "status": "added", + "before": null, + "after": "f58538f7c6e81df7a22ca20f2896e825c9f0c2789196d9c610d11d0187df30cd", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+\"\"\"Declarative resources; the Host never imports this package.\"\"\"" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme/amadeus-extension.json", + "status": "added", + "before": null, + "after": "9e6e27d4d835a9f24c78c22de2b714a85bdec265dd7d8b72b097edc4ec5223f1", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+{\"license\": \"MIT-0\", \"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.desktop-theme\", \"version\": \"0.1.0\", \"display_name\": {\"en\": \"Quiet desktop\", \"zh-CN\": \"\\u9759\\u8c27\\u684c\\u9762\"}, \"description\": {\"en\": \"Two local palettes with no package code or network access.\", \"zh-CN\": \"\\u4e24\\u6b3e\\u672c\\u5730\\u914d\\u8272\\uff0c\\u4e0d\\u8fd0\\u884c\\u5305\\u4ee3\\u7801\\uff0c\\u4e5f\\u4e0d\\u8054\\u7f51\\u3002\"}, \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\"}, \"runtime\": {\"kind\": \"none\"}, \"contributes\": {\"theme\": [{\"id\": \"day\", \"contract\": \"amadeus.ui.theme/v0\", \"base\": \"classic\", \"title\": {\"en\": \"Quiet day\", \"zh-CN\": \"\\u9759\\u8c27\\u65e5\\u5149\"}, \"tokens\": {\"--bg\": \"#EAF1F7\", \"--surface\": \"#FFFFFF\", \"--surface-alt\": \"#F4F7FA\", \"--text\": \"#132B3D\", \"--muted\": \"#526B7F\", \"--accent\": \"#154A70\", \"--accent-hover\": \"#0C3957\", \"--faint\": \"#526B7F\", \"--selection-bg\": \"#F2F4F7\", \"--subtle-fill\": \"#F2F4F7\", \"--selected-fill\": \"#EAECF0\", \"--focus-fill\": \"#F2F4F7\"}}, {\"id\": \"night\", \"contract\": \"amadeus.ui.theme/v0\", \"base\": \"wallpaper-slice\", \"title\": {\"en\": \"Quiet night\", \"zh-CN\": \"\\u9759\\u8c27\\u591c\\u8272\"}, \"tokens\": {\"--bg\": \"rgba(6,17,22,.85)\", \"--surface\": \"#0A1D22\", \"--surface-alt\": \"#0D252A\", \"--text\": \"#E0F8F1\", \"--muted\": \"#A3CFC4\", \"--accent\": \"#7DE0CC\", \"--accent-hover\": \"#62BDA8\", \"--faint\": \"#A3CFC4\", \"--selection-bg\": \"rgba(84,255,221,.10)\"}, \"background\": {\"image\": \"background.png\", \"fit\": \"cover\", \"dim\": 0.8}}]}}" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme/background.png", + "status": "added", + "before": null, + "after": "a09d5e39aeea920520c7a603cc75f3b9b039bb30788bff9842c066f70993e900" + } + ], + "previous_candidate_diff": null + }, + { + "id": "com.example.desktop-theme", + "version": "0.2.0", + "filename": "amadeus_example_theme-0.2.0-py3-none-any.whl", + "sha256": "da1201d51ade2a15843973c9aa0c12a1bc8bcd279906c63cac5582066bfe66c6", + "size": 5598, + "host_static_check": "passed", + "catalog_static_policy": "passed", + "license": "MIT-0", + "review_status": "not_reviewed", + "inventory": [ + { + "path": "amadeus_example_theme-0.2.0.dist-info/METADATA", + "status": "added", + "before": null, + "after": "9efd055c3e90af01678321fa60aa2a9c45b2925c022e3dfd791cd7ebf6d0c4e5", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+Metadata-Version: 2.4", + "+Name: amadeus-example-theme", + "+Version: 0.2.0", + "+Summary: Two declarative desktop palettes for the extension pilot", + "+License-Expression: MIT-0", + "+Requires-Python: >=3.11", + "+License-File: LICENSE", + "+Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.2.0.dist-info/RECORD", + "status": "added", + "before": null, + "after": "bb7f4e5d11ff0a025d966b3347eb04125feba9b590ae8b218d4165146d3088bf", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,9 @@", + "+amadeus_example_theme/__init__.py,sha256=9YU498boHfeiLKIPKJboJcnwwniRltnGENEdAYffMM0,66", + "+amadeus_example_theme/amadeus-extension.json,sha256=QL-qROteoxb7JsVzk_Q9v-e5g8VLZcWShfI10M6w-UI,1468", + "+amadeus_example_theme/background.png,sha256=oJ1eOa7qkgUgx6YDzHXzubA5uzB4i_-YQsBm9wmT6QA,2762", + "+amadeus_example_theme-0.2.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "+amadeus_example_theme-0.2.0.dist-info/METADATA,sha256=nv0FXD6QrwFngyH6YKoqnEWyklwCLj39eRzX6_bQxOU,233", + "+amadeus_example_theme-0.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "+amadeus_example_theme-0.2.0.dist-info/entry_points.txt,sha256=UvpUNu06rRUQGvKT2t0zzVX0_9wsbXuymSJX0EtmGLU,71", + "+amadeus_example_theme-0.2.0.dist-info/top_level.txt,sha256=4ffmZWk0eKXndFUZP5X6A-ZXwHD_4qavFgktjVZUSIE,22", + "+amadeus_example_theme-0.2.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.2.0.dist-info/WHEEL", + "status": "added", + "before": null, + "after": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+Wheel-Version: 1.0", + "+Generator: setuptools (83.0.0)", + "+Root-Is-Purelib: true", + "+Tag: py3-none-any", + "+" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.2.0.dist-info/entry_points.txt", + "status": "added", + "before": null, + "after": "52fa5436ed3aad15101af293dadd33cd55f4ffdc2c6d7bb2992257d04b6618b5", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,2 @@", + "+[amadeus.extensions]", + "+com.example.desktop-theme = amadeus_example_theme" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.2.0.dist-info/licenses/LICENSE", + "status": "added", + "before": null, + "after": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,7 @@", + "+MIT No Attribution", + "+", + "+Copyright (c) 2026 Amadeus contributors", + "+", + "+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "+", + "+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.2.0.dist-info/top_level.txt", + "status": "added", + "before": null, + "after": "e1f7e665693478a5e77455193f95fa03e657c070ffe2a6af16092d8d56544881", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+amadeus_example_theme" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme/__init__.py", + "status": "added", + "before": null, + "after": "f58538f7c6e81df7a22ca20f2896e825c9f0c2789196d9c610d11d0187df30cd", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+\"\"\"Declarative resources; the Host never imports this package.\"\"\"" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme/amadeus-extension.json", + "status": "added", + "before": null, + "after": "40bfaa44eb5ea316fb26c57393f43dbfe7b983c54b65c59285f235d0ceb0f942", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+{\"license\": \"MIT-0\", \"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.desktop-theme\", \"version\": \"0.2.0\", \"display_name\": {\"en\": \"Quiet desktop\", \"zh-CN\": \"\\u9759\\u8c27\\u684c\\u9762\"}, \"description\": {\"en\": \"Two local palettes with no package code or network access.\", \"zh-CN\": \"\\u4e24\\u6b3e\\u672c\\u5730\\u914d\\u8272\\uff0c\\u4e0d\\u8fd0\\u884c\\u5305\\u4ee3\\u7801\\uff0c\\u4e5f\\u4e0d\\u8054\\u7f51\\u3002\"}, \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\"}, \"runtime\": {\"kind\": \"none\"}, \"contributes\": {\"theme\": [{\"id\": \"day\", \"contract\": \"amadeus.ui.theme/v0\", \"base\": \"classic\", \"title\": {\"en\": \"Quiet day\", \"zh-CN\": \"\\u9759\\u8c27\\u65e5\\u5149\"}, \"tokens\": {\"--bg\": \"#EAF1F7\", \"--surface\": \"#FFFFFF\", \"--surface-alt\": \"#F4F7FA\", \"--text\": \"#132B3D\", \"--muted\": \"#526B7F\", \"--accent\": \"#154A70\", \"--accent-hover\": \"#0C3957\", \"--faint\": \"#526B7F\", \"--selection-bg\": \"#F2F4F7\", \"--subtle-fill\": \"#F2F4F7\", \"--selected-fill\": \"#EAECF0\", \"--focus-fill\": \"#F2F4F7\"}}, {\"id\": \"night\", \"contract\": \"amadeus.ui.theme/v0\", \"base\": \"wallpaper-slice\", \"title\": {\"en\": \"Quiet night\", \"zh-CN\": \"\\u9759\\u8c27\\u591c\\u8272\"}, \"tokens\": {\"--bg\": \"rgba(6,17,22,.85)\", \"--surface\": \"#0A1D22\", \"--surface-alt\": \"#0D252A\", \"--text\": \"#E0F8F1\", \"--muted\": \"#A3CFC4\", \"--accent\": \"#7DE0CC\", \"--accent-hover\": \"#62BDA8\", \"--faint\": \"#A3CFC4\", \"--selection-bg\": \"rgba(84,255,221,.10)\"}, \"background\": {\"image\": \"background.png\", \"fit\": \"cover\", \"dim\": 0.8}}]}}" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme/background.png", + "status": "added", + "before": null, + "after": "a09d5e39aeea920520c7a603cc75f3b9b039bb30788bff9842c066f70993e900" + } + ], + "previous_candidate_diff": [ + { + "path": "amadeus_example_theme-0.1.0.dist-info/METADATA", + "status": "removed", + "before": "a9131fe7f0b6c1ff5f30da8d8a148dbb8e19cc480a020f9941fc42dcb7f0e51d", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,8 +0,0 @@", + "-Metadata-Version: 2.4", + "-Name: amadeus-example-theme", + "-Version: 0.1.0", + "-Summary: Two declarative desktop palettes for the extension pilot", + "-License-Expression: MIT-0", + "-Requires-Python: >=3.11", + "-License-File: LICENSE", + "-Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.1.0.dist-info/RECORD", + "status": "removed", + "before": "4f958fd6d55823abbce4bca1d9f07c436f59d285e96f630b9ad51b7182dad805", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,9 +0,0 @@", + "-amadeus_example_theme/__init__.py,sha256=9YU498boHfeiLKIPKJboJcnwwniRltnGENEdAYffMM0,66", + "-amadeus_example_theme/amadeus-extension.json,sha256=nm4n1Ng1qfJMeMIt4rcUqFvewmXdfYtysJftxOxSI_E,1468", + "-amadeus_example_theme/background.png,sha256=oJ1eOa7qkgUgx6YDzHXzubA5uzB4i_-YQsBm9wmT6QA,2762", + "-amadeus_example_theme-0.1.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "-amadeus_example_theme-0.1.0.dist-info/METADATA,sha256=qRMf5_C2wf9fMNqNihSNu44ZzEgKAg-ZQfxC3Lfw5R0,233", + "-amadeus_example_theme-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "-amadeus_example_theme-0.1.0.dist-info/entry_points.txt,sha256=UvpUNu06rRUQGvKT2t0zzVX0_9wsbXuymSJX0EtmGLU,71", + "-amadeus_example_theme-0.1.0.dist-info/top_level.txt,sha256=4ffmZWk0eKXndFUZP5X6A-ZXwHD_4qavFgktjVZUSIE,22", + "-amadeus_example_theme-0.1.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.1.0.dist-info/WHEEL", + "status": "removed", + "before": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,5 +0,0 @@", + "-Wheel-Version: 1.0", + "-Generator: setuptools (83.0.0)", + "-Root-Is-Purelib: true", + "-Tag: py3-none-any", + "-" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.1.0.dist-info/entry_points.txt", + "status": "removed", + "before": "52fa5436ed3aad15101af293dadd33cd55f4ffdc2c6d7bb2992257d04b6618b5", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,2 +0,0 @@", + "-[amadeus.extensions]", + "-com.example.desktop-theme = amadeus_example_theme" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.1.0.dist-info/licenses/LICENSE", + "status": "removed", + "before": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,7 +0,0 @@", + "-MIT No Attribution", + "-", + "-Copyright (c) 2026 Amadeus contributors", + "-", + "-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "-", + "-THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.1.0.dist-info/top_level.txt", + "status": "removed", + "before": "e1f7e665693478a5e77455193f95fa03e657c070ffe2a6af16092d8d56544881", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1 +0,0 @@", + "-amadeus_example_theme" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.2.0.dist-info/METADATA", + "status": "added", + "before": null, + "after": "9efd055c3e90af01678321fa60aa2a9c45b2925c022e3dfd791cd7ebf6d0c4e5", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+Metadata-Version: 2.4", + "+Name: amadeus-example-theme", + "+Version: 0.2.0", + "+Summary: Two declarative desktop palettes for the extension pilot", + "+License-Expression: MIT-0", + "+Requires-Python: >=3.11", + "+License-File: LICENSE", + "+Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.2.0.dist-info/RECORD", + "status": "added", + "before": null, + "after": "bb7f4e5d11ff0a025d966b3347eb04125feba9b590ae8b218d4165146d3088bf", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,9 @@", + "+amadeus_example_theme/__init__.py,sha256=9YU498boHfeiLKIPKJboJcnwwniRltnGENEdAYffMM0,66", + "+amadeus_example_theme/amadeus-extension.json,sha256=QL-qROteoxb7JsVzk_Q9v-e5g8VLZcWShfI10M6w-UI,1468", + "+amadeus_example_theme/background.png,sha256=oJ1eOa7qkgUgx6YDzHXzubA5uzB4i_-YQsBm9wmT6QA,2762", + "+amadeus_example_theme-0.2.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "+amadeus_example_theme-0.2.0.dist-info/METADATA,sha256=nv0FXD6QrwFngyH6YKoqnEWyklwCLj39eRzX6_bQxOU,233", + "+amadeus_example_theme-0.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "+amadeus_example_theme-0.2.0.dist-info/entry_points.txt,sha256=UvpUNu06rRUQGvKT2t0zzVX0_9wsbXuymSJX0EtmGLU,71", + "+amadeus_example_theme-0.2.0.dist-info/top_level.txt,sha256=4ffmZWk0eKXndFUZP5X6A-ZXwHD_4qavFgktjVZUSIE,22", + "+amadeus_example_theme-0.2.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.2.0.dist-info/WHEEL", + "status": "added", + "before": null, + "after": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+Wheel-Version: 1.0", + "+Generator: setuptools (83.0.0)", + "+Root-Is-Purelib: true", + "+Tag: py3-none-any", + "+" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.2.0.dist-info/entry_points.txt", + "status": "added", + "before": null, + "after": "52fa5436ed3aad15101af293dadd33cd55f4ffdc2c6d7bb2992257d04b6618b5", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,2 @@", + "+[amadeus.extensions]", + "+com.example.desktop-theme = amadeus_example_theme" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.2.0.dist-info/licenses/LICENSE", + "status": "added", + "before": null, + "after": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,7 @@", + "+MIT No Attribution", + "+", + "+Copyright (c) 2026 Amadeus contributors", + "+", + "+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "+", + "+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme-0.2.0.dist-info/top_level.txt", + "status": "added", + "before": null, + "after": "e1f7e665693478a5e77455193f95fa03e657c070ffe2a6af16092d8d56544881", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+amadeus_example_theme" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_theme/amadeus-extension.json", + "status": "modified", + "before": "9e6e27d4d835a9f24c78c22de2b714a85bdec265dd7d8b72b097edc4ec5223f1", + "after": "40bfaa44eb5ea316fb26c57393f43dbfe7b983c54b65c59285f235d0ceb0f942", + "text_diff": [ + "--- ", + "+++ ", + "@@ -1 +1 @@", + "-{\"license\": \"MIT-0\", \"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.desktop-theme\", \"version\": \"0.1.0\", \"display_name\": {\"en\": \"Quiet desktop\", \"zh-CN\": \"\\u9759\\u8c27\\u684c\\u9762\"}, \"description\": {\"en\": \"Two local palettes with no package code or network access.\", \"zh-CN\": \"\\u4e24\\u6b3e\\u672c\\u5730\\u914d\\u8272\\uff0c\\u4e0d\\u8fd0\\u884c\\u5305\\u4ee3\\u7801\\uff0c\\u4e5f\\u4e0d\\u8054\\u7f51\\u3002\"}, \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\"}, \"runtime\": {\"kind\": \"none\"}, \"contributes\": {\"theme\": [{\"id\": \"day\", \"contract\": \"amadeus.ui.theme/v0\", \"base\": \"classic\", \"title\": {\"en\": \"Quiet day\", \"zh-CN\": \"\\u9759\\u8c27\\u65e5\\u5149\"}, \"tokens\": {\"--bg\": \"#EAF1F7\", \"--surface\": \"#FFFFFF\", \"--surface-alt\": \"#F4F7FA\", \"--text\": \"#132B3D\", \"--muted\": \"#526B7F\", \"--accent\": \"#154A70\", \"--accent-hover\": \"#0C3957\", \"--faint\": \"#526B7F\", \"--selection-bg\": \"#F2F4F7\", \"--subtle-fill\": \"#F2F4F7\", \"--selected-fill\": \"#EAECF0\", \"--focus-fill\": \"#F2F4F7\"}}, {\"id\": \"night\", \"contract\": \"amadeus.ui.theme/v0\", \"base\": \"wallpaper-slice\", \"title\": {\"en\": \"Quiet night\", \"zh-CN\": \"\\u9759\\u8c27\\u591c\\u8272\"}, \"tokens\": {\"--bg\": \"rgba(6,17,22,.85)\", \"--surface\": \"#0A1D22\", \"--surface-alt\": \"#0D252A\", \"--text\": \"#E0F8F1\", \"--muted\": \"#A3CFC4\", \"--accent\": \"#7DE0CC\", \"--accent-hover\": \"#62BDA8\", \"--faint\": \"#A3CFC4\", \"--selection-bg\": \"rgba(84,255,221,.10)\"}, \"background\": {\"image\": \"background.png\", \"fit\": \"cover\", \"dim\": 0.8}}]}}", + "+{\"license\": \"MIT-0\", \"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.desktop-theme\", \"version\": \"0.2.0\", \"display_name\": {\"en\": \"Quiet desktop\", \"zh-CN\": \"\\u9759\\u8c27\\u684c\\u9762\"}, \"description\": {\"en\": \"Two local palettes with no package code or network access.\", \"zh-CN\": \"\\u4e24\\u6b3e\\u672c\\u5730\\u914d\\u8272\\uff0c\\u4e0d\\u8fd0\\u884c\\u5305\\u4ee3\\u7801\\uff0c\\u4e5f\\u4e0d\\u8054\\u7f51\\u3002\"}, \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\"}, \"runtime\": {\"kind\": \"none\"}, \"contributes\": {\"theme\": [{\"id\": \"day\", \"contract\": \"amadeus.ui.theme/v0\", \"base\": \"classic\", \"title\": {\"en\": \"Quiet day\", \"zh-CN\": \"\\u9759\\u8c27\\u65e5\\u5149\"}, \"tokens\": {\"--bg\": \"#EAF1F7\", \"--surface\": \"#FFFFFF\", \"--surface-alt\": \"#F4F7FA\", \"--text\": \"#132B3D\", \"--muted\": \"#526B7F\", \"--accent\": \"#154A70\", \"--accent-hover\": \"#0C3957\", \"--faint\": \"#526B7F\", \"--selection-bg\": \"#F2F4F7\", \"--subtle-fill\": \"#F2F4F7\", \"--selected-fill\": \"#EAECF0\", \"--focus-fill\": \"#F2F4F7\"}}, {\"id\": \"night\", \"contract\": \"amadeus.ui.theme/v0\", \"base\": \"wallpaper-slice\", \"title\": {\"en\": \"Quiet night\", \"zh-CN\": \"\\u9759\\u8c27\\u591c\\u8272\"}, \"tokens\": {\"--bg\": \"rgba(6,17,22,.85)\", \"--surface\": \"#0A1D22\", \"--surface-alt\": \"#0D252A\", \"--text\": \"#E0F8F1\", \"--muted\": \"#A3CFC4\", \"--accent\": \"#7DE0CC\", \"--accent-hover\": \"#62BDA8\", \"--faint\": \"#A3CFC4\", \"--selection-bg\": \"rgba(84,255,221,.10)\"}, \"background\": {\"image\": \"background.png\", \"fit\": \"cover\", \"dim\": 0.8}}]}}" + ], + "diff_truncated": false + } + ] + }, + { + "id": "com.example.tone-tts", + "version": "0.1.0", + "filename": "amadeus_example_tone_tts-0.1.0-py3-none-any.whl", + "sha256": "cdbea2a6b70654952d9aeda97fec8085134ce6ecccfac2395f0b8467a7b2f0c1", + "size": 3393, + "host_static_check": "passed", + "catalog_static_policy": "passed", + "license": "MIT-0", + "review_status": "not_reviewed", + "inventory": [ + { + "path": "amadeus_example_tone/__init__.py", + "status": "added", + "before": null, + "after": "e029241158a40e54d89a01f03b60df312b2f51fac80a91b4e3c3d7c5f6c7bdaf", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,19 @@", + "+\"\"\"MIT-0 offline author example, using only the delivered SDK and standard library.\"\"\"", + "+", + "+import math", + "+import struct", + "+import time", + "+from amadeus_extension import AudioChunk, Extension", + "+", + "+extension = Extension()", + "+", + "+", + "+@extension.tts(\"tone\")", + "+def synthesize(request, context):", + "+ pcm = b\"\".join(", + "+ struct.pack(\"=0.1,<0.2\", \"platforms\": [\"win32\", \"darwin\", \"linux\"]}, \"runtime\": {\"kind\": \"python\", \"entry\": \"amadeus_example_tone:extension\", \"requires_python\": \">=3.12\"}, \"contributes\": {\"voice_backend\": [{\"id\": \"tone\", \"role\": \"tts\", \"contract\": \"amadeus.voice.tts/v0\", \"deployment\": \"local\", \"languages\": [\"en\"], \"streaming\": true}]}}" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.1.0.dist-info/METADATA", + "status": "added", + "before": null, + "after": "20f0497a970c55655140c4b733dd0dee20d35f6e05d4358db116042a71801f31", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+Metadata-Version: 2.4", + "+Name: amadeus-example-tone-tts", + "+Version: 0.1.0", + "+License-Expression: MIT-0", + "+Requires-Python: >=3.12", + "+License-File: LICENSE", + "+Requires-Dist: amadeus-extension-sdk==0.1.0", + "+Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.1.0.dist-info/RECORD", + "status": "added", + "before": null, + "after": "7d665fd42f3fd61dbc390256bb4f9c657c6d29bd209e367043d63a8f779a654d", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+amadeus_example_tone/__init__.py,sha256=4CkkEVikDlTYmgHwO2DfMSsvUfrICpG048PXxfbHva8,549", + "+amadeus_example_tone/amadeus-extension.json,sha256=XzJ3drMa24wqeLUZYSB--30Ylx38MKGhw27jqe669cM,625", + "+amadeus_example_tone_tts-0.1.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "+amadeus_example_tone_tts-0.1.0.dist-info/METADATA,sha256=IPBJepcMVWVRQMS3M90N7iDTX24F1DWNsRYEKnGAHzE,214", + "+amadeus_example_tone_tts-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "+amadeus_example_tone_tts-0.1.0.dist-info/entry_points.txt,sha256=6bCApIsyG35RA3tVo-7bjTK6L2wYJa_KN8v1EqeZ_KY,65", + "+amadeus_example_tone_tts-0.1.0.dist-info/top_level.txt,sha256=oNub5mAzbTaRzUUp5EMM-EqZ5DMPdnbEW-2nulE5OVs,21", + "+amadeus_example_tone_tts-0.1.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.1.0.dist-info/WHEEL", + "status": "added", + "before": null, + "after": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+Wheel-Version: 1.0", + "+Generator: setuptools (83.0.0)", + "+Root-Is-Purelib: true", + "+Tag: py3-none-any", + "+" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.1.0.dist-info/entry_points.txt", + "status": "added", + "before": null, + "after": "e9b080a48b321b7e51037b55a3eedb8d32ba2f6c1825afca37cbf512a799fca6", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,2 @@", + "+[amadeus.extensions]", + "+com.example.tone-tts = amadeus_example_tone" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.1.0.dist-info/licenses/LICENSE", + "status": "added", + "before": null, + "after": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,7 @@", + "+MIT No Attribution", + "+", + "+Copyright (c) 2026 Amadeus contributors", + "+", + "+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "+", + "+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.1.0.dist-info/top_level.txt", + "status": "added", + "before": null, + "after": "a0db9be660336d3691cd4529e4430cf84a99e4330f7676c45beda7ba5139395b", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+amadeus_example_tone" + ], + "diff_truncated": false + } + ], + "previous_candidate_diff": null + }, + { + "id": "com.example.tone-tts", + "version": "0.2.0", + "filename": "amadeus_example_tone_tts-0.2.0-py3-none-any.whl", + "sha256": "46ec319336a9a7bb511026b6b416fc1d01cdd1a725d6c53aa6547d46e353cb8e", + "size": 3394, + "host_static_check": "passed", + "catalog_static_policy": "passed", + "license": "MIT-0", + "review_status": "not_reviewed", + "inventory": [ + { + "path": "amadeus_example_tone/__init__.py", + "status": "added", + "before": null, + "after": "e029241158a40e54d89a01f03b60df312b2f51fac80a91b4e3c3d7c5f6c7bdaf", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,19 @@", + "+\"\"\"MIT-0 offline author example, using only the delivered SDK and standard library.\"\"\"", + "+", + "+import math", + "+import struct", + "+import time", + "+from amadeus_extension import AudioChunk, Extension", + "+", + "+extension = Extension()", + "+", + "+", + "+@extension.tts(\"tone\")", + "+def synthesize(request, context):", + "+ pcm = b\"\".join(", + "+ struct.pack(\"=0.1,<0.2\", \"platforms\": [\"win32\", \"darwin\", \"linux\"]}, \"runtime\": {\"kind\": \"python\", \"entry\": \"amadeus_example_tone:extension\", \"requires_python\": \">=3.12\"}, \"contributes\": {\"voice_backend\": [{\"id\": \"tone\", \"role\": \"tts\", \"contract\": \"amadeus.voice.tts/v0\", \"deployment\": \"local\", \"languages\": [\"en\"], \"streaming\": true}]}}" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.2.0.dist-info/METADATA", + "status": "added", + "before": null, + "after": "c5a055857f0b3801a038e69ad93fd81877c33d5420ea28be7e7636e9529b2e9b", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+Metadata-Version: 2.4", + "+Name: amadeus-example-tone-tts", + "+Version: 0.2.0", + "+License-Expression: MIT-0", + "+Requires-Python: >=3.12", + "+License-File: LICENSE", + "+Requires-Dist: amadeus-extension-sdk==0.1.0", + "+Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.2.0.dist-info/RECORD", + "status": "added", + "before": null, + "after": "34c64f585f18fe85346c1267bca0a349565f54581758dfd68c3db81472a62f87", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+amadeus_example_tone/__init__.py,sha256=4CkkEVikDlTYmgHwO2DfMSsvUfrICpG048PXxfbHva8,549", + "+amadeus_example_tone/amadeus-extension.json,sha256=BXt3UKTRKfWYzbUfNYBep62fVdTlLfMtIsHoq2ztK8s,625", + "+amadeus_example_tone_tts-0.2.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "+amadeus_example_tone_tts-0.2.0.dist-info/METADATA,sha256=xaBVhX8LOAGgOOaa2T_YGHfDPVQg6ii-fnY26VKbLps,214", + "+amadeus_example_tone_tts-0.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "+amadeus_example_tone_tts-0.2.0.dist-info/entry_points.txt,sha256=6bCApIsyG35RA3tVo-7bjTK6L2wYJa_KN8v1EqeZ_KY,65", + "+amadeus_example_tone_tts-0.2.0.dist-info/top_level.txt,sha256=oNub5mAzbTaRzUUp5EMM-EqZ5DMPdnbEW-2nulE5OVs,21", + "+amadeus_example_tone_tts-0.2.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.2.0.dist-info/WHEEL", + "status": "added", + "before": null, + "after": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+Wheel-Version: 1.0", + "+Generator: setuptools (83.0.0)", + "+Root-Is-Purelib: true", + "+Tag: py3-none-any", + "+" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.2.0.dist-info/entry_points.txt", + "status": "added", + "before": null, + "after": "e9b080a48b321b7e51037b55a3eedb8d32ba2f6c1825afca37cbf512a799fca6", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,2 @@", + "+[amadeus.extensions]", + "+com.example.tone-tts = amadeus_example_tone" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.2.0.dist-info/licenses/LICENSE", + "status": "added", + "before": null, + "after": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,7 @@", + "+MIT No Attribution", + "+", + "+Copyright (c) 2026 Amadeus contributors", + "+", + "+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "+", + "+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.2.0.dist-info/top_level.txt", + "status": "added", + "before": null, + "after": "a0db9be660336d3691cd4529e4430cf84a99e4330f7676c45beda7ba5139395b", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+amadeus_example_tone" + ], + "diff_truncated": false + } + ], + "previous_candidate_diff": [ + { + "path": "amadeus_example_tone/amadeus-extension.json", + "status": "modified", + "before": "5f327776b31adb8c2a78b51961207efb7d18971dfc30a1a1c36ee3a9eebaf5c3", + "after": "057b7750a4d129f598cdb51f35805ea7ad9f55d4e52df32d22c1e8ab6ced2bcb", + "text_diff": [ + "--- ", + "+++ ", + "@@ -1 +1 @@", + "-{\"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.tone-tts\", \"version\": \"0.1.0\", \"display_name\": \"Offline SDK tone\", \"description\": \"Synthetic PCM for author contract checks, not a speech service.\", \"license\": \"MIT-0\", \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\", \"platforms\": [\"win32\", \"darwin\", \"linux\"]}, \"runtime\": {\"kind\": \"python\", \"entry\": \"amadeus_example_tone:extension\", \"requires_python\": \">=3.12\"}, \"contributes\": {\"voice_backend\": [{\"id\": \"tone\", \"role\": \"tts\", \"contract\": \"amadeus.voice.tts/v0\", \"deployment\": \"local\", \"languages\": [\"en\"], \"streaming\": true}]}}", + "+{\"schema\": \"amadeus.extension/v0\", \"id\": \"com.example.tone-tts\", \"version\": \"0.2.0\", \"display_name\": \"Offline SDK tone\", \"description\": \"Synthetic PCM for author contract checks, not a speech service.\", \"license\": \"MIT-0\", \"author\": {\"name\": \"Amadeus contributors\"}, \"engines\": {\"amadeus_host_api\": \">=0.1,<0.2\", \"platforms\": [\"win32\", \"darwin\", \"linux\"]}, \"runtime\": {\"kind\": \"python\", \"entry\": \"amadeus_example_tone:extension\", \"requires_python\": \">=3.12\"}, \"contributes\": {\"voice_backend\": [{\"id\": \"tone\", \"role\": \"tts\", \"contract\": \"amadeus.voice.tts/v0\", \"deployment\": \"local\", \"languages\": [\"en\"], \"streaming\": true}]}}" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.1.0.dist-info/METADATA", + "status": "removed", + "before": "20f0497a970c55655140c4b733dd0dee20d35f6e05d4358db116042a71801f31", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,8 +0,0 @@", + "-Metadata-Version: 2.4", + "-Name: amadeus-example-tone-tts", + "-Version: 0.1.0", + "-License-Expression: MIT-0", + "-Requires-Python: >=3.12", + "-License-File: LICENSE", + "-Requires-Dist: amadeus-extension-sdk==0.1.0", + "-Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.1.0.dist-info/RECORD", + "status": "removed", + "before": "7d665fd42f3fd61dbc390256bb4f9c657c6d29bd209e367043d63a8f779a654d", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,8 +0,0 @@", + "-amadeus_example_tone/__init__.py,sha256=4CkkEVikDlTYmgHwO2DfMSsvUfrICpG048PXxfbHva8,549", + "-amadeus_example_tone/amadeus-extension.json,sha256=XzJ3drMa24wqeLUZYSB--30Ylx38MKGhw27jqe669cM,625", + "-amadeus_example_tone_tts-0.1.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "-amadeus_example_tone_tts-0.1.0.dist-info/METADATA,sha256=IPBJepcMVWVRQMS3M90N7iDTX24F1DWNsRYEKnGAHzE,214", + "-amadeus_example_tone_tts-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "-amadeus_example_tone_tts-0.1.0.dist-info/entry_points.txt,sha256=6bCApIsyG35RA3tVo-7bjTK6L2wYJa_KN8v1EqeZ_KY,65", + "-amadeus_example_tone_tts-0.1.0.dist-info/top_level.txt,sha256=oNub5mAzbTaRzUUp5EMM-EqZ5DMPdnbEW-2nulE5OVs,21", + "-amadeus_example_tone_tts-0.1.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.1.0.dist-info/WHEEL", + "status": "removed", + "before": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,5 +0,0 @@", + "-Wheel-Version: 1.0", + "-Generator: setuptools (83.0.0)", + "-Root-Is-Purelib: true", + "-Tag: py3-none-any", + "-" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.1.0.dist-info/entry_points.txt", + "status": "removed", + "before": "e9b080a48b321b7e51037b55a3eedb8d32ba2f6c1825afca37cbf512a799fca6", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,2 +0,0 @@", + "-[amadeus.extensions]", + "-com.example.tone-tts = amadeus_example_tone" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.1.0.dist-info/licenses/LICENSE", + "status": "removed", + "before": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1,7 +0,0 @@", + "-MIT No Attribution", + "-", + "-Copyright (c) 2026 Amadeus contributors", + "-", + "-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "-", + "-THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.1.0.dist-info/top_level.txt", + "status": "removed", + "before": "a0db9be660336d3691cd4529e4430cf84a99e4330f7676c45beda7ba5139395b", + "after": null, + "text_diff": [ + "--- ", + "+++ ", + "@@ -1 +0,0 @@", + "-amadeus_example_tone" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.2.0.dist-info/METADATA", + "status": "added", + "before": null, + "after": "c5a055857f0b3801a038e69ad93fd81877c33d5420ea28be7e7636e9529b2e9b", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+Metadata-Version: 2.4", + "+Name: amadeus-example-tone-tts", + "+Version: 0.2.0", + "+License-Expression: MIT-0", + "+Requires-Python: >=3.12", + "+License-File: LICENSE", + "+Requires-Dist: amadeus-extension-sdk==0.1.0", + "+Dynamic: license-file" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.2.0.dist-info/RECORD", + "status": "added", + "before": null, + "after": "34c64f585f18fe85346c1267bca0a349565f54581758dfd68c3db81472a62f87", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,8 @@", + "+amadeus_example_tone/__init__.py,sha256=4CkkEVikDlTYmgHwO2DfMSsvUfrICpG048PXxfbHva8,549", + "+amadeus_example_tone/amadeus-extension.json,sha256=BXt3UKTRKfWYzbUfNYBep62fVdTlLfMtIsHoq2ztK8s,625", + "+amadeus_example_tone_tts-0.2.0.dist-info/licenses/LICENSE,sha256=wcy0Jht7HLgqRqcE5Mjhrvxmvb_GaFK2rXWzM-MYKVM,919", + "+amadeus_example_tone_tts-0.2.0.dist-info/METADATA,sha256=xaBVhX8LOAGgOOaa2T_YGHfDPVQg6ii-fnY26VKbLps,214", + "+amadeus_example_tone_tts-0.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91", + "+amadeus_example_tone_tts-0.2.0.dist-info/entry_points.txt,sha256=6bCApIsyG35RA3tVo-7bjTK6L2wYJa_KN8v1EqeZ_KY,65", + "+amadeus_example_tone_tts-0.2.0.dist-info/top_level.txt,sha256=oNub5mAzbTaRzUUp5EMM-EqZ5DMPdnbEW-2nulE5OVs,21", + "+amadeus_example_tone_tts-0.2.0.dist-info/RECORD,," + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.2.0.dist-info/WHEEL", + "status": "added", + "before": null, + "after": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,5 @@", + "+Wheel-Version: 1.0", + "+Generator: setuptools (83.0.0)", + "+Root-Is-Purelib: true", + "+Tag: py3-none-any", + "+" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.2.0.dist-info/entry_points.txt", + "status": "added", + "before": null, + "after": "e9b080a48b321b7e51037b55a3eedb8d32ba2f6c1825afca37cbf512a799fca6", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,2 @@", + "+[amadeus.extensions]", + "+com.example.tone-tts = amadeus_example_tone" + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.2.0.dist-info/licenses/LICENSE", + "status": "added", + "before": null, + "after": "c1ccb4261b7b1cb82a46a704e4c8e1aefc66bdbfc66852b6ad75b333e3182953", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1,7 @@", + "+MIT No Attribution", + "+", + "+Copyright (c) 2026 Amadeus contributors", + "+", + "+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.", + "+", + "+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "diff_truncated": false + }, + { + "path": "amadeus_example_tone_tts-0.2.0.dist-info/top_level.txt", + "status": "added", + "before": null, + "after": "a0db9be660336d3691cd4529e4430cf84a99e4330f7676c45beda7ba5139395b", + "text_diff": [ + "--- ", + "+++ ", + "@@ -0,0 +1 @@", + "+amadeus_example_tone" + ], + "diff_truncated": false + } + ] + } + ] +} diff --git a/catalog/schema.json b/catalog/schema.json new file mode 100644 index 0000000..fad1e24 --- /dev/null +++ b/catalog/schema.json @@ -0,0 +1,337 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "format": { + "const": "amadeus.catalog/v1" + }, + "sequence": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "source_commit": { + "type": "string", + "pattern": "^[a-f0-9]{40}$" + }, + "source_time": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "generator_commit": { + "type": "string", + "pattern": "^[a-f0-9]{40}$" + }, + "host_commit": { + "type": "string", + "pattern": "^[a-f0-9]{40}$" + }, + "entries": { + "type": "array", + "maxItems": 4096, + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "version": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "url": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "size": { + "type": "integer", + "minimum": 1, + "maximum": 268435456 + }, + "display": { + "type": "object", + "maxProperties": 16 + }, + "compatibility": { + "type": "object", + "properties": { + "host_api": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "python": { + "type": "string", + "maxLength": 256 + }, + "platforms": { + "type": "array", + "maxItems": 8, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + "sdk": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "dependencies": { + "type": "array", + "maxItems": 32, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + "contributions": { + "type": "array", + "minItems": 1, + "maxItems": 256, + "items": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "role": { + "type": "string", + "maxLength": 128 + }, + "contract": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + "required": [ + "kind", + "role", + "contract" + ], + "additionalProperties": false + } + }, + "delivery": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "runtime": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + "required": [ + "host_api", + "python", + "platforms", + "sdk", + "dependencies", + "contributions", + "delivery", + "runtime" + ], + "additionalProperties": false + }, + "publisher": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "maintainers": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + "package_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + "evidence": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + "required": [ + "id", + "maintainers", + "package_ids", + "evidence" + ], + "additionalProperties": false + }, + "review": { + "type": "object", + "properties": { + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "policy": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "reviewer": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "evidence": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + "required": [ + "sha256", + "policy", + "reviewer", + "date", + "evidence" + ], + "additionalProperties": false + }, + "withdrawal": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "object", + "properties": { + "reason": { + "enum": [ + "maintainer", + "copyright" + ] + }, + "evidence": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + "required": [ + "reason", + "evidence" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "id", + "version", + "sha256", + "url", + "size", + "display", + "compatibility", + "publisher", + "review", + "withdrawal" + ], + "additionalProperties": false + } + }, + "security": { + "type": "array", + "maxItems": 16384, + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "version": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "status": { + "enum": [ + "blocked", + "allowed" + ] + }, + "sequence": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991 + }, + "reason": { + "enum": [ + "security", + "security-resolved" + ] + }, + "evidence": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + "required": [ + "id", + "version", + "sha256", + "status", + "sequence", + "reason", + "evidence" + ], + "additionalProperties": false + } + } + }, + "required": [ + "format", + "sequence", + "source_commit", + "source_time", + "generator_commit", + "host_commit", + "entries", + "security" + ], + "additionalProperties": false +} diff --git a/catalog/security.json b/catalog/security.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/catalog/security.json @@ -0,0 +1 @@ +[] diff --git a/catalog/toolchain.json b/catalog/toolchain.json new file mode 100644 index 0000000..ee3a9ec --- /dev/null +++ b/catalog/toolchain.json @@ -0,0 +1,7 @@ +{ + "host_repository": "https://github.com/Code-Amadeus/Amadeus", + "host_commit": "1840c8e4f646992ddaebea3139c8895f29743a8f", + "python": "3.12.10", + "platform": "win32", + "requirements_sha256": "eca122787362dc7d39b309b931c5e17ce7fb0507f79524589f2978bb018c17e7" +} diff --git a/catalog/v1/index.json b/catalog/v1/index.json new file mode 100644 index 0000000..9f476eb --- /dev/null +++ b/catalog/v1/index.json @@ -0,0 +1,10 @@ +{ + "entries": [], + "format": "amadeus.catalog/v1", + "generator_commit": "85f49315dd01818748e4079b69bdf67d47eb7170", + "host_commit": "1840c8e4f646992ddaebea3139c8895f29743a8f", + "security": [], + "sequence": 0, + "source_commit": "85f49315dd01818748e4079b69bdf67d47eb7170", + "source_time": "2026-10-21T09:03:48+13:00" +} diff --git a/requirements-dev.in b/requirements-dev.in index 1802d60..d5aa302 100644 --- a/requirements-dev.in +++ b/requirements-dev.in @@ -3,3 +3,4 @@ jsonschema==4.26.0 pytest==9.0.3 setuptools==83.0.0 wheel==0.47.0 +packaging==26.3 diff --git a/requirements-dev.txt b/requirements-dev.txt index 8d0fda4..d47b49f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -26,6 +26,7 @@ packaging==26.3 \ --hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \ --hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c # via + # -r requirements-dev.in # pytest # wheel pluggy==1.6.0 \ diff --git a/tests/test_catalog.py b/tests/test_catalog.py new file mode 100644 index 0000000..92dfa8d --- /dev/null +++ b/tests/test_catalog.py @@ -0,0 +1,537 @@ +"""Catalog semantics and actual Host-policy separation; no submitted code executes.""" + +import copy +import hashlib +import json +import os +import subprocess +import sys +import zipfile +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "tools")) +from catalog import ( + PIN, + ROOT, + build_entry, + canonical, + check_policy, + https_url, + incompatible_reasons, + parse_json, + retained_entry, + validate_index, + validate_transition, + verify_host, + wheel_diff, +) + + +def entry(): + return { + "id": "org.example.test", + "version": "1.0.0", + "sha256": "a" * 64, + "url": "https://example.test/example-1.0.0-py3-none-any.whl", + "size": 100, + "display": {"display_name": "Fixture"}, + "compatibility": { + "host_api": ">=0.1,<0.2", + "python": ">=3.12", + "platforms": ["win32"], + "sdk": "0.1.0", + "dependencies": [], + "delivery": "python-wheel", + "runtime": "none", + "contributions": [ + {"kind": "skill", "role": "", "contract": "amadeus.skill/v0"} + ], + }, + "publisher": { + "id": "fixture-author", + "maintainers": ["fixture-author"], + "package_ids": ["org.example.test"], + "evidence": "https://example.test/ownership", + }, + "review": { + "sha256": "a" * 64, + "policy": "curated-v1", + "reviewer": "independent-fixture-reviewer", + "date": "2026-10-21", + "evidence": "https://example.test/review", + }, + "withdrawal": None, + } + + +def index(): + return { + "format": "amadeus.catalog/v1", + "sequence": 1, + "source_commit": "a" * 40, + "source_time": "2026-10-21T00:00:00+00:00", + "generator_commit": "b" * 40, + "host_commit": PIN["host_commit"], + "entries": [entry()], + "security": [], + } + + +def test_compatibility_is_per_package_while_safety_is_independent(): + source = index() + capabilities = { + "delivery": ["python-wheel"], + "runtime": ["none", "python"], + "contributions": source["entries"][0]["compatibility"]["contributions"], + } + for kind, contract in [("future", "future/v0"), ("skill", "amadeus.skill/v99")]: + future = copy.deepcopy(entry()) + future["id"] += "." + kind + future["publisher"]["package_ids"] = [future["id"]] + future["compatibility"]["contributions"].append( + {"kind": kind, "role": "", "contract": contract} + ) + source["entries"].append(future) + source["security"] = [ + { + "id": "org.example.future", + "version": "1.0.0", + "sha256": "c" * 64, + "status": "blocked", + "reason": "security", + "sequence": 1, + "evidence": "https://example.test/ban", + } + ] + validate_index(source) + assert [bool(incompatible_reasons(e, capabilities)) for e in source["entries"]] == [ + False, + True, + True, + ] + assert source["security"][0]["status"] == "blocked" + future = copy.deepcopy(entry()) + future["compatibility"]["delivery"] = "future-delivery" + assert incompatible_reasons(future, capabilities) + + +@pytest.mark.parametrize( + "mutation", ["format", "duplicate", "safety", "review", "owner", "self_review"] +) +def test_unsafe_structure_rejects_snapshot(mutation): + data = index() + if mutation == "format": + data["format"] = "amadeus.catalog/v2" + if mutation == "duplicate": + data["entries"].append(copy.deepcopy(data["entries"][0])) + if mutation == "safety": + data["security"] = [{"status": "unknown"}] + if mutation == "review": + data["entries"][0]["review"]["sha256"] = "b" * 64 + if mutation == "owner": + data["entries"][0]["publisher"]["package_ids"] = ["org.other.package"] + if mutation == "self_review": + data["entries"][0]["review"]["reviewer"] = "fixture-author" + with pytest.raises(ValueError): + validate_index(data) + + +def test_publications_are_monotone_immutable_and_preserve_explicit_safety(): + first = index() + first["security"] = [ + { + "id": "org.example.test", + "version": "1.0.0", + "sha256": "a" * 64, + "status": "blocked", + "reason": "security", + "sequence": 1, + "evidence": "https://example.test/ban", + } + ] + validate_transition(first, copy.deepcopy(first)) + for mutation in [ + "sequence", + "bytes", + "missing", + "ban_missing", + "ban_changed_old_sequence", + ]: + next_index = copy.deepcopy(first) + next_index["sequence"] = 2 + if mutation == "sequence": + next_index["sequence"] = 0 + if mutation == "bytes": + next_index["entries"][0]["sha256"] = "b" * 64 + if mutation == "missing": + next_index["entries"] = [] + if mutation == "ban_missing": + next_index["security"] = [] + if mutation == "ban_changed_old_sequence": + next_index["security"][0].update( + status="allowed", reason="security-resolved" + ) + with pytest.raises(ValueError): + validate_transition(first, next_index) + for reason in ["maintainer", "copyright"]: + next_index = copy.deepcopy(first) + next_index["sequence"] = 2 + next_index["entries"][0]["withdrawal"] = { + "reason": reason, + "evidence": "https://example.test/withdraw", + } + validate_transition(first, next_index) + assert next_index["security"] == first["security"] + next_index["security"][0].update( + status="allowed", reason="security-resolved", sequence=2 + ) + validate_transition(first, next_index) + + +@pytest.mark.parametrize( + "url", + [ + "http://example.test/a", + "https://user:secret@example.test/a", + "https://example.test/a\n", + "file:///tmp/a", + ], +) +def test_urls_cannot_carry_credentials_or_downgrade(url): + with pytest.raises(ValueError): + https_url(url) + + +def test_diff_contains_full_initial_inventory_and_changed_binary_hashes(tmp_path): + old, new = tmp_path / "old.whl", tmp_path / "new.whl" + for path, items in [ + (old, {"a.py": b"old\n", "old.txt": b"x", "image.png": b"\xffa"}), + (new, {"a.py": b"new\n", "new.txt": b"x", "image.png": b"\xffb"}), + ]: + with zipfile.ZipFile(path, "w") as z: + for name, body in items.items(): + z.writestr(name, body) + changes = {e["path"]: e for e in wheel_diff(old, new)} + assert ( + changes["old.txt"]["status"] == "removed" + and changes["new.txt"]["status"] == "added" + ) + assert ( + "-old" in changes["a.py"]["text_diff"] + and "+new" in changes["a.py"]["text_diff"] + ) + assert changes["image.png"]["before"] != changes["image.png"]["after"] + assert "text_diff" not in changes["image.png"] + assert len(wheel_diff(None, new)) == 3 + + +@pytest.fixture +def host_environment(): + value = os.environ.get("AMADEUS_CATALOG_HOST") + if not value: + pytest.skip("explicit pinned Host checkout required for real artifact checks") + host = Path(value) + python = Path(os.environ.get("AMADEUS_CATALOG_PYTHON", sys.executable)) + profile = verify_host(host, python) + return host, python, profile + + +def artifact_source(wheel, artifacts): + digest = hashlib.sha256(wheel.read_bytes()).hexdigest() + target = artifacts / digest / wheel.name + target.parent.mkdir(parents=True) + target.write_bytes(wheel.read_bytes()) + source = entry() + source["publisher"]["package_ids"] = [ + "com.example.handoff-skill", + "com.example.csv-audit-skill", + "com.example.notes-mcp", + "com.example.desktop-theme", + "com.example.mimo-tts", + "com.example.fish-audio", + ] + source["review"]["sha256"] = digest + return { + "wheel": {"url": "https://example.test/" + wheel.name, "sha256": digest}, + **{k: source[k] for k in ["publisher", "review", "withdrawal"]}, + } + + +def test_actual_reference_wheels_and_third_party_policy_boundary( + host_environment, tmp_path +): + host, python, profile = host_environment + wheels = list((host / "build/extension-authoring-review-wheels").glob("*.whl")) + assert len(wheels) == 6 + for wheel in wheels: + source = artifact_source(wheel, tmp_path) + if "fish_audio" in wheel.name: + with pytest.raises(ValueError, match="catalog policy: third-party"): + build_entry(source, tmp_path, host, python, profile) + else: + built = build_entry(source, tmp_path, host, python, profile) + assert ( + not built["compatibility"]["platforms"] + or "win32" in built["compatibility"]["platforms"] + ) + assert built["display"]["author"] + assert canonical(built) == canonical( + build_entry(source, tmp_path, host, python, profile) + ) + # Even Fish remains Host-admissible: catalog policy never changes local rules. + checked = subprocess.run( + [ + str(python), + "-I", + str(host / "tools/extension_package.py"), + "check-wheel", + str(wheel), + ], + capture_output=True, + text=True, + check=False, + ) + assert checked.returncode == 0 + + +@pytest.mark.parametrize( + "defect", + [ + "tag", + "native_suffix", + "native_magic", + "version", + "command", + "module", + "code", + "sdk_url", + "dependency", + ], +) +def test_catalog_exclusions_on_actual_host_inspected_bytes( + host_environment, tmp_path, defect +): + host, python, _ = host_environment + original = next( + (host / "build/extension-authoring-review-wheels").glob("*notes_mcp*.whl") + ) + with zipfile.ZipFile(original) as z: + content = {n: z.read(n) for n in z.namelist()} + manifest_path = next(n for n in content if n.endswith("amadeus-extension.json")) + manifest = json.loads(content[manifest_path]) + metadata_path = next(n for n in content if n.endswith(".dist-info/METADATA")) + header_path = next(n for n in content if n.endswith(".dist-info/WHEEL")) + if defect == "tag": + content[header_path] = ( + b"Wheel-Version: 1.0\nRoot-Is-Purelib: false\nTag: cp312-cp312-win_amd64\n" + ) + if defect == "native_suffix": + content["payload.dll"] = b"not even a real DLL" + if defect == "native_magic": + content["payload.dat"] = b"MZbinary" + if defect == "version": + manifest["version"] = "0.1.0+build" + content[metadata_path] = content[metadata_path].replace( + b"Version: 0.1.0", b"Version: 0.1.0+build" + ) + server = manifest["contributes"]["mcp_server"][0] + if defect == "command": + server.update(command="npx", args=["-y", "unreviewed"]) + manifest["permissions"] = {"os": {"process": ["npx"]}} + if defect == "module": + server["args"] = ["-m", "pip", "install", "unreviewed"] + if defect == "code": + server["args"] = ["-c", "print(1)"] + if defect in {"sdk_url", "dependency"}: + # Add a header before the blank metadata/body separator. + requirement = ( + b"other @ https://example.test/unreviewed.whl" + if defect == "sdk_url" + else b"other==1.0" + ) + content[metadata_path] = ( + b"Requires-Dist: " + requirement + b"\n" + content[metadata_path] + ) + content[manifest_path] = json.dumps(manifest).encode() + wheel = tmp_path / original.name + with zipfile.ZipFile(wheel, "w") as z: + for name, body in content.items(): + z.writestr(name, body) + checked = subprocess.run( + [ + str(python), + "-I", + str(host / "tools/extension_package.py"), + "check-wheel", + str(wheel), + ], + capture_output=True, + text=True, + check=False, + ) + assert checked.returncode == 0, ( + checked.stdout + ) # legitimate local behavior is preserved + with pytest.raises(ValueError, match="catalog policy:"): + check_policy(wheel, json.loads(checked.stdout)) + + +def test_shared_future_client_fixtures(): + fixture = json.loads( + (ROOT / "catalog/fixtures/compatibility.json").read_text(encoding="utf-8") + ) + for case in fixture["cases"]: + assert ( + not incompatible_reasons( + {"compatibility": case["compatibility"]}, fixture["capabilities"] + ) + ) == case["compatible"], case["name"] + + +def test_safety_publication_reuses_reviewed_main_facts_without_redownloading(): + previous = index() + old = previous["entries"][0] + source = { + "wheel": {"url": old["url"], "sha256": old["sha256"]}, + "publisher": old["publisher"], + "review": old["review"], + "withdrawal": { + "reason": "copyright", + "evidence": "https://example.test/takedown", + }, + } + retained = retained_entry(source, previous) + assert retained["withdrawal"]["reason"] == "copyright" + assert retained["compatibility"] == old["compatibility"] + source["wheel"]["sha256"] = "b" * 64 + assert retained_entry(source, previous) is None + + +def test_duplicate_keys_and_invalid_review_dates_are_rejected(): + with pytest.raises(ValueError): + parse_json(b'{"sequence":1,"sequence":2}') + data = index() + data["entries"][0]["review"]["date"] = "2026-99-99" + with pytest.raises(ValueError): + validate_index(data) + + +def test_committed_generator_is_reproducible_and_checks_actual_digest( + host_environment, tmp_path +): + from build_catalog import generate + + host, python, _ = host_environment + source_repo = tmp_path / "inputs" + source_repo.mkdir() + subprocess.run(["git", "init", str(source_repo)], check=True, capture_output=True) + source = artifact_source( + next( + (host / "build/extension-authoring-review-wheels").glob("*notes_mcp*.whl") + ), + tmp_path / "artifacts", + ) + packages = source_repo / "catalog/packages" + packages.mkdir(parents=True) + (packages / "notes.json").write_text(json.dumps(source), encoding="utf-8") + (source_repo / "catalog/security.json").write_text("[]", encoding="utf-8") + subprocess.run( + ["git", "-C", str(source_repo), "add", "."], check=True, capture_output=True + ) + environment = { + **os.environ, + "GIT_AUTHOR_DATE": "2026-10-21T12:00:00+13:00", + "GIT_COMMITTER_DATE": "2026-10-21T12:00:00+13:00", + } + subprocess.run( + [ + "git", + "-C", + str(source_repo), + "-c", + "user.name=Catalog fixture", + "-c", + "user.email=fixture@example.invalid", + "commit", + "-m", + "Synthetic input only", + ], + check=True, + capture_output=True, + env=environment, + ) + previous = index() + previous.update(sequence=0, entries=[]) + first = generate( + source_repo, + "HEAD", + "HEAD", + 1, + tmp_path / "artifacts", + host, + python, + previous=previous, + ) + second = generate( + source_repo, + "HEAD", + "HEAD", + 1, + tmp_path / "artifacts", + host, + python, + previous=previous, + ) + assert canonical(first) == canonical(second) + assert first["source_time"] == "2026-10-21T12:00:00+13:00" + validate_transition(previous, first) + wheel = next((tmp_path / "artifacts").glob("*/*.whl")) + wheel.write_bytes(b"wrong artifact") + with pytest.raises(ValueError, match="digest mismatch"): + generate( + source_repo, + "HEAD", + "HEAD", + 1, + tmp_path / "artifacts", + host, + python, + previous=previous, + ) + + +def test_github_reviewer_identity_is_case_insensitive(): + data = index() + data["entries"][0]["review"]["reviewer"] = "FIXTURE-AUTHOR" + with pytest.raises(ValueError, match="independent reviewer"): + validate_index(data) + + +def test_display_is_derived_without_config_defaults_or_environment_values(): + from catalog import artifact_display + + manifest = { + "display_name": "Fixture", + "description": "", + "config": {"credential": {"default": "synthetic-private-value"}}, + "permissions": {"os": {"network": ["example.test"]}}, + "contributes": { + "mcp_server": [ + { + "id": "notes", + "transport": "stdio", + "command": "${python}", + "args": ["-m", "notes"], + "env": {"TOKEN": "synthetic-private-value"}, + } + ] + }, + } + result = artifact_display(manifest) + assert result["access"]["services"][0]["command"] == "${python}" + assert result["access"]["os"] == manifest["permissions"]["os"] + assert "synthetic-private-value" not in json.dumps(result) + assert result["description"] == manifest["description"] diff --git a/tests/test_catalog_submission.py b/tests/test_catalog_submission.py new file mode 100644 index 0000000..08e1a07 --- /dev/null +++ b/tests/test_catalog_submission.py @@ -0,0 +1,94 @@ +import copy +import io +from pathlib import Path + +import pytest +import test_catalog +import verify_publication as delivery +from preflight_submission import preflight +from test_catalog import index + +from catalog import canonical + +host_environment = test_catalog.host_environment + + +@pytest.mark.parametrize("fragment,admitted", [("notes_mcp", True), ("fish_audio", False)]) +def test_real_author_preflight_separates_host_policy_and_review(host_environment, fragment, admitted): + host, python, _ = host_environment + wheel = next((host / "build/extension-authoring-review-wheels").glob("*"+fragment+"*.whl")) + report = preflight(wheel, host, python) + assert report["ok"] == admitted + assert report["checks"]["target_host"] == report["checks"]["package_format"] == "passed" + assert report["checks"]["catalog_policy"] == ("passed" if admitted else "failed") + assert report["checks"]["human_and_runtime_review"] == "pending" + assert report["review_status"] == "not_reviewed" + + +@pytest.fixture +def served(monkeypatch): + state = {"bytes": b"", "fetches": [], "fail_assets": False} + class Opener: + def open(self, *_args, **_kwargs): + return io.BytesIO(state["bytes"]) + monkeypatch.setattr(delivery, "build_opener", lambda *_: Opener()) + def fetch(source, directory): + state["fetches"].append(source["wheel"]) + if state["fail_assets"]: + raise ValueError("artifact digest mismatch") + path = Path(directory) / "wheel" + path.write_bytes(b"x"*100) + return path + monkeypatch.setattr(delivery, "fetch_artifact", fetch) + return state + + +def run_check(tmp_path, served, before, after): + expected, previous = tmp_path/"index.json", tmp_path/"previous.json" + expected.write_bytes(canonical(after)) + previous.write_bytes(canonical(before)) + served["bytes"] = canonical(after) + return delivery.verify("https://catalog.test/index.json", expected, previous) + + +@pytest.mark.parametrize("change", ["new", "restored", "unblocked", "relocated"]) +def test_fresh_check_required_when_distribution_becomes_available(tmp_path, served, change): + after = index() + before = copy.deepcopy(after) + if change == "new": + before["entries"] = [] + if change == "restored": + before["entries"][0]["withdrawal"] = {"reason":"copyright","evidence":"https://review.test/"} + if change == "relocated": + before["entries"][0]["url"] = "https://old.test/old-1.0.0-py3-none-any.whl" + if change == "unblocked": + e = after["entries"][0] + before["security"] = [{**{k:e[k] for k in ("id","version","sha256")}, + "status":"blocked","sequence":1,"reason":"security","evidence":"https://review.test/"}] + report = run_check(tmp_path, served, before, after) + assert report["ok"] and len(served["fetches"]) == 1 + + +def test_unavailable_old_assets_do_not_block_safety_publication(tmp_path, served): + after = index() + after["entries"][0]["withdrawal"] = {"reason":"copyright","evidence":"https://review.test/"} + served["fail_assets"] = True + report = run_check(tmp_path, served, {"entries":[], **{k:v for k,v in after.items() if k!="entries"}}, after) + assert report["ok"] and not served["fetches"] + + +def test_missing_or_changed_new_asset_is_not_live(tmp_path, served): + after = index(); before = copy.deepcopy(after); before["entries"] = [] + served["fail_assets"] = True + with pytest.raises(ValueError, match="digest mismatch"): + run_check(tmp_path, served, before, after) + + +def test_valid_but_stale_served_index_is_rejected(tmp_path, served): + expected = tmp_path/"index.json" + expected.write_bytes(canonical(index())) + stale = index(); stale["sequence"] = 0; stale["entries"] = [] + served["bytes"] = canonical(stale) + with pytest.raises(ValueError, match="served index differs"): + delivery.verify("https://catalog.test/index.json", expected) + assert not served["fetches"] diff --git a/tools/build_catalog.py b/tools/build_catalog.py new file mode 100644 index 0000000..6eef34d --- /dev/null +++ b/tools/build_catalog.py @@ -0,0 +1,188 @@ +"""Generate an index from a reviewed Git input commit and digest-addressed wheels.""" + +from __future__ import annotations + +import argparse +import json +import subprocess +from pathlib import Path + +from catalog import ( + PIN, + ROOT, + build_entry, + canonical, + fetch_artifact, + parse_json, + read_json, + retained_entry, + validate_index, + validate_transition, + verify_host, +) + + +def git(repo, *args): + return subprocess.check_output( + ["git", "-C", str(repo), *args], text=True, encoding="utf-8" + ).strip() + + +def generate( + source_repo, + source_commit, + approved_ref, + sequence, + artifacts, + host, + python, + fetch=False, + previous=None, +): + commit = git(source_repo, "rev-parse", source_commit + "^{commit}") + subprocess.run( + [ + "git", + "-C", + str(source_repo), + "merge-base", + "--is-ancestor", + commit, + approved_ref, + ], + check=True, + capture_output=True, + ) + generator = git(ROOT, "rev-parse", "HEAD") + if git( + ROOT, + "status", + "--porcelain", + "--", + "tools", + "catalog/policy.json", + "catalog/schema.json", + "catalog/toolchain.json", + ): + raise ValueError( + "commit the trusted generator inputs before generating an index" + ) + profile = verify_host(host, python) + paths = git( + source_repo, + "ls-tree", + "-r", + "--name-only", + commit, + "catalog/packages", + "catalog/security.json", + ).splitlines() + entries, safety = [], [] + for path in paths: + if not path.endswith(".json"): + continue + # Git tree content, never a submitter-selected executable or working-tree file. + raw = git(source_repo, "show", f"{commit}:{path}") + if len(raw.encode()) > 4 * 1024 * 1024: + raise ValueError("catalog input exceeds size limit") + source = parse_json(raw.encode("utf-8")) + if path == "catalog/security.json": + safety = source + else: + retained = retained_entry(source, previous) + if retained is not None: + entries.append(retained) + else: + if fetch: + fetch_artifact(source, artifacts) + entries.append(build_entry(source, artifacts, host, python, profile)) + result = { + "format": "amadeus.catalog/v1", + "sequence": sequence, + "source_commit": commit, + "source_time": git(source_repo, "show", "-s", "--format=%cI", commit), + "generator_commit": generator, + "host_commit": PIN["host_commit"], + "entries": sorted(entries, key=lambda e: (e["id"], e["version"])), + "security": sorted(safety, key=lambda e: (e["id"], e["version"], e["sha256"])), + } + return validate_index(result) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source-repo", type=Path, default=ROOT) + parser.add_argument("--source-commit", required=True) + parser.add_argument( + "--approved-ref", + default="origin/main", + help="CI pins this to the current protected main commit", + ) + parser.add_argument("--sequence", type=int, required=True) + parser.add_argument( + "--previous", + type=Path, + help="Required except for an empty sequence-0 bootstrap", + ) + parser.add_argument("--host", type=Path, required=True) + parser.add_argument("--python", type=Path, required=True) + parser.add_argument("--artifacts", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument( + "--fetch", + action="store_true", + help="Fetch missing wheel bytes over bounded HTTPS; never execute them", + ) + parser.add_argument( + "--check", action="store_true", help="Compare output instead of writing it" + ) + args = parser.parse_args() + try: + previous = validate_index(read_json(args.previous)) if args.previous else None + index = generate( + args.source_repo, + args.source_commit, + args.approved_ref, + args.sequence, + args.artifacts.resolve(), + args.host.resolve(), + args.python.resolve(), + args.fetch, + previous, + ) + if args.previous: + validate_transition(previous, index) + elif index["sequence"] != 0 or index["entries"] or index["security"]: + raise ValueError( + "only an empty sequence-0 bootstrap may omit the previous index" + ) + data = canonical(index) + if args.check: + if args.output.read_bytes() != data: + raise ValueError("generated index differs from committed bytes") + else: + args.output.write_bytes(data) + print( + json.dumps( + { + "ok": True, + "sequence": index["sequence"], + "entries": len(index["entries"]), + } + ) + ) + return 0 + except (OSError, ValueError, KeyError, TypeError, subprocess.SubprocessError): + print( + json.dumps( + { + "ok": False, + "error": "catalog generation failed; check pins, reviewed input, artifacts and sequence", + } + ) + ) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/catalog.py b/tools/catalog.py new file mode 100644 index 0000000..44e3d87 --- /dev/null +++ b/tools/catalog.py @@ -0,0 +1,541 @@ +"""Curated catalog policy and deterministic generation, separate from Host admission. + +Only the fixed Host checker inspects Host contracts. No wheel is installed/imported. +""" + +from __future__ import annotations + +import difflib +import hashlib +import json +import re +import shutil +import subprocess +import tempfile +import zipfile +from datetime import date +from email.parser import Parser +from pathlib import Path +from urllib.parse import unquote, urlsplit +from urllib.request import HTTPRedirectHandler, build_opener + +from jsonschema import Draft202012Validator +from packaging.requirements import Requirement +from packaging.specifiers import SpecifierSet +from packaging.utils import canonicalize_name, parse_wheel_filename + +ROOT = Path(__file__).resolve().parents[1] +POLICY = json.loads((ROOT / "catalog/policy.json").read_text()) +PIN = json.loads((ROOT / "catalog/toolchain.json").read_text()) + + +def fail(message): + raise ValueError(message) + + +def canonical(value): + return ( + json.dumps(value, ensure_ascii=True, sort_keys=True, indent=2) + "\n" + ).encode() + + +def parse_json(data, limit=4 * 1024 * 1024): + if len(data) > limit: + fail("JSON exceeds size limit") + + def pairs(items): + result = {} + for key, value in items: + if key in result: + fail("duplicate JSON key") + result[key] = value + return result + + return json.loads(data, object_pairs_hook=pairs) + + +def read_json(path, limit=4 * 1024 * 1024): + with Path(path).open("rb") as source: + return parse_json(source.read(limit + 1), limit) + + +def https_url(value): + if ( + not isinstance(value, str) + or len(value) > 2048 + or any(ord(c) < 33 or ord(c) == 127 for c in value) + ): + fail("URL must be bounded HTTPS without credentials") + parsed = urlsplit(value) + if ( + parsed.scheme != "https" + or not parsed.hostname + or parsed.username is not None + or parsed.password is not None + or parsed.fragment + ): + fail("URL must be HTTPS without credentials or fragments") + _ = parsed.port # reject invalid ports + return value + + +def wheel_filename(url): + https_url(url) + name = unquote(urlsplit(url).path.rsplit("/", 1)[-1]) + if "/" in name or "\\" in name or ":" in name or not name.endswith(".whl"): + fail("wheel URL must identify a concrete wheel filename") + parse_wheel_filename(name) + return name + + +def identity(entry): + return entry["id"], entry["version"], entry["sha256"] + + +def validate_index(index): + if len(canonical(index)) > POLICY["max_index_bytes"]: + fail("catalog exceeds size limit") + validator = Draft202012Validator(read_json(ROOT / "catalog/schema.json")) + if next(validator.iter_errors(index), None): + fail("unsupported catalog format or invalid required structure") + versions = set() + for entry in index["entries"]: + key = entry["id"], entry["version"] + if key in versions: + fail("duplicate or conflicting package identity") + versions.add(key) + https_url(entry["url"]) + validate_review(entry, entry["id"], entry["sha256"]) + if entry["withdrawal"]: + https_url(entry["withdrawal"]["evidence"]) + facts = set() + for fact in index["security"]: + key = identity(fact) + if key in facts or fact["sequence"] > index["sequence"]: + fail("duplicate safety identity or invalid decision sequence") + facts.add(key) + if (fact["status"], fact["reason"]) not in { + ("blocked", "security"), + ("allowed", "security-resolved"), + }: + fail("inconsistent safety decision") + https_url(fact["evidence"]) + return index + + +def validate_transition(previous, current): + validate_index(previous) + validate_index(current) + if current["sequence"] == previous["sequence"] and canonical(current) == canonical( + previous + ): + return # exact rebuild only + if current["sequence"] != previous["sequence"] + 1: + fail("publication sequence must increase by exactly one") + old = {(e["id"], e["version"]): e for e in previous["entries"]} + new = {(e["id"], e["version"]): e for e in current["entries"]} + for key, entry in old.items(): + if key not in new or entry["sha256"] != new[key]["sha256"]: + fail( + "released identity cannot disappear or change bytes; withdraw it explicitly" + ) + old_safety = {identity(e): e for e in previous["security"]} + new_safety = {identity(e): e for e in current["security"]} + if not old_safety.keys() <= new_safety.keys(): + fail("known safety facts cannot disappear") + for key, fact in new_safety.items(): + if old_safety.get(key) != fact and fact["sequence"] != current["sequence"]: + fail("changed safety decision requires the new publication sequence") + + +def incompatible_reasons(entry, capabilities): + """Reference consumer semantics; never call the full manifest validator here.""" + summary = entry["compatibility"] + reasons = [] + if ( + summary["delivery"] not in capabilities["delivery"] + or summary["runtime"] not in capabilities["runtime"] + ): + reasons.append("unsupported delivery or runtime") + allowed = { + (c["kind"], c.get("role", ""), c["contract"]) + for c in capabilities["contributions"] + } + if any( + (c["kind"], c["role"], c["contract"]) not in allowed + for c in summary["contributions"] + ): + reasons.append("unsupported contribution contract") + return reasons + + +def validate_review(source, package_id, digest): + publisher, review = source["publisher"], source["review"] + date.fromisoformat(review["date"]) + if package_id not in publisher["package_ids"] or not publisher["maintainers"]: + fail("publisher ownership does not cover this package ID") + if review["sha256"] != digest or review["policy"] != POLICY["version"]: + fail("this artifact needs its own current-policy review") + if review["reviewer"].casefold() in { + person.casefold() for person in [*publisher["maintainers"], publisher["id"]] + }: + fail("independent reviewer is required, including reference packages") + https_url(publisher["evidence"]) + https_url(review["evidence"]) + + +def check_policy(wheel, inspection): + """Distribution exclusions only: Host/local install rules are unchanged.""" + manifest = inspection["manifest"] + if not re.fullmatch(POLICY["version_pattern"], manifest["version"]): + fail("catalog policy: version must be X.Y.Z without pre-release/build suffix") + distribution, version, build, tags = parse_wheel_filename(wheel.name) + if ( + canonicalize_name(inspection["distribution"]) != distribution + or str(version) != manifest["version"] + ): + fail("catalog policy: wheel filename identity differs from metadata") + if build or {str(t) for t in tags} != {POLICY["wheel_tag"]}: + fail("catalog policy: wheel filename must be py3-none-any without a build tag") + if PIN["python"] not in SpecifierSet(inspection.get("requires_python", "")): + fail("catalog policy: wheel Python requirement excludes the fixed target") + for raw in inspection["requires_dist"]: + requirement = Requirement(raw) + if requirement.url: + fail("catalog policy: direct URL dependencies are excluded") + if requirement.marker and not requirement.marker.evaluate({"extra": ""}): + continue + if ( + canonicalize_name(requirement.name) not in POLICY["allowed_dependencies"] + or requirement.extras + ): + fail("catalog policy: third-party dependencies are excluded from v1") + with zipfile.ZipFile(wheel) as archive: + names = set(archive.namelist()) + headers = [name for name in names if name.endswith(".dist-info/WHEEL")] + if len(headers) != 1 or archive.getinfo(headers[0]).file_size > 65536: + fail("catalog policy: missing or oversized WHEEL metadata") + if headers[0].removesuffix("WHEEL") + "METADATA" not in names: + fail("catalog policy: WHEEL must belong to the distribution metadata") + metadata = Parser().parsestr(archive.read(headers[0]).decode("utf-8")) + if metadata.get_all("Tag") != [POLICY["wheel_tag"]] or metadata.get_all( + "Root-Is-Purelib" + ) != ["true"]: + fail("catalog policy: WHEEL must declare purelib and exactly py3-none-any") + for info in archive.infolist(): + if info.is_dir(): + continue + with archive.open(info) as member: + magic = member.read(8) + if Path(info.filename).suffix.lower() in POLICY[ + "native_suffixes" + ] or magic.startswith( + ( + b"MZ", + b"\x7fELF", + b"\0asm", + b"\xcf\xfa\xed\xfe", + b"\xfe\xed\xfa\xcf", + b"\xca\xfe\xba\xbe", + b"\xce\xfa\xed\xfe", + b"\xfe\xed\xfa\xce", + ) + ): + fail("catalog policy: native/executable binary payload is excluded") + package = inspection["package_root"] + + def owned_script(value): + if not value.startswith("${package}/"): + return False + suffix = value[len("${package}/") :] + return ( + bool(suffix) + and not any(p in {"", ".", ".."} for p in suffix.split("/")) + and package + "/" + suffix in names + ) + + for server in manifest["contributes"].get("mcp_server", []): + if server["transport"] != "stdio": + continue + command, args = server["command"], server.get("args", []) + if command == "${python}": + args = list(args) + while args and args[0] in {"-I", "-B", "-s", "-E", "-u"}: + args.pop(0) + if len(args) >= 2 and args[0] == "-m": + module = args[1] + location = module.replace(".", "/") + if ( + not re.fullmatch(r"[A-Za-z_]\w*(\.[A-Za-z_]\w*)*", module) + or module.split(".")[0] != package + or not ({location + ".py", location + "/__main__.py"} & names) + ): + fail( + "catalog policy: Python module must belong to the reviewed package" + ) + elif ( + not args or not owned_script(args[0]) or not args[0].endswith(".py") + ): + fail( + "catalog policy: Python must run a reviewed package module or .py script" + ) + elif not owned_script(command) or not command.endswith(".py"): + fail( + "catalog policy: MCP command must be Python or a reviewed package script" + ) + # Actual executable/platform behavior and arbitrary code downloads remain human review. + + +def verify_host(host, python): + def git(*args): + return subprocess.check_output( + ["git", "-C", str(host), *args], text=True + ).strip() + + if git("rev-parse", "HEAD") != PIN["host_commit"]: + fail("Host validator commit does not match toolchain pin") + paths = [ + "tools/extension_package.py", + "server/extensions", + "agent_host/skills.py", + "pyproject.toml", + "vendor/extension-sdk", + ] + if git("status", "--porcelain", "--", *paths): + fail("pinned Host validator inputs are modified") + lock = host / "tools/requirements-extension-authoring.txt" + if hashlib.sha256(lock.read_bytes()).hexdigest() != PIN["requirements_sha256"]: + fail("Host authoring dependency export differs from pin") + result = subprocess.run( + [str(python), "-I", str(host / "tools/extension_package.py"), "profile"], + capture_output=True, + text=True, + timeout=30, + check=False, + ) + if result.returncode: + fail("fixed Host checker environment is unavailable") + profile = json.loads(result.stdout)["host_profile"] + if (profile["python"], profile["platform"]) != (PIN["python"], PIN["platform"]): + fail("catalog qualification requires pinned Windows/Python environment") + return profile + + +class BoundedRedirect(HTTPRedirectHandler): + max_redirections = 5 + + def redirect_request(self, req, fp, code, msg, headers, newurl): + https_url(newurl) + return super().redirect_request(req, fp, code, msg, headers, newurl) + + +def fetch_artifact(source, artifacts): + digest, url = source["wheel"]["sha256"], source["wheel"]["url"] + if not re.fullmatch("[a-f0-9]{64}", digest): + fail("invalid artifact digest") + target = artifacts / digest / wheel_filename(url) + if target.is_file(): + return target + target.parent.mkdir(parents=True, exist_ok=True) + temporary = target.with_suffix(".download") + try: + with ( + build_opener(BoundedRedirect()).open( + https_url(url), timeout=30 + ) as response, + temporary.open("xb") as output, + ): + size, hasher = 0, hashlib.sha256() + while block := response.read(1024 * 1024): + size += len(block) + if size > POLICY["max_wheel_bytes"]: + fail("download exceeds wheel size limit") + hasher.update(block) + output.write(block) + if hasher.hexdigest() != digest: + fail("artifact digest mismatch") + temporary.replace(target) + finally: + temporary.unlink(missing_ok=True) + return target + + +def retained_entry(source, previous): + """Reuse immutable reviewed facts from protected main, never a display/download cache. + + Safety/withdrawal publication must not depend on old asset URLs staying online. + New bytes, publisher facts, reviews or checker pins still need full inspection. + """ + if set(source) != {"wheel", "publisher", "review", "withdrawal"} or set( + source["wheel"] + ) != {"url", "sha256"}: + fail("source contains derived metadata or unknown manual facts") + if previous is None or previous["host_commit"] != PIN["host_commit"]: + return None + for entry in previous["entries"]: + if ( + source["wheel"] == {"url": entry["url"], "sha256": entry["sha256"]} + and source["publisher"] == entry["publisher"] + and source["review"] == entry["review"] + and entry["review"]["policy"] == POLICY["version"] + ): + return {**entry, "withdrawal": source["withdrawal"]} + return None + + +def build_entry(source, artifacts, host, python, profile): + if set(source) != {"wheel", "publisher", "review", "withdrawal"} or set( + source["wheel"] + ) != {"url", "sha256"}: + fail("source contains derived metadata or unknown manual facts") + digest = source["wheel"]["sha256"] + if not re.fullmatch("[a-f0-9]{64}", digest): + fail("invalid artifact digest") + wheel = artifacts / digest / wheel_filename(source["wheel"]["url"]) + if wheel.stat().st_size > POLICY["max_wheel_bytes"]: + fail("artifact exceeds size limit") + with tempfile.TemporaryDirectory(prefix="catalog-artifact-") as temporary: + snapshot = Path(temporary) / wheel.name + shutil.copyfile(wheel, snapshot) + return inspect_entry(source, snapshot, host, python, profile) + + +def artifact_display(manifest): + """Public artifact facts only; never copy configuration defaults or environment values.""" + return { + **{ + k: manifest[k] + for k in ( + "display_name", + "description", + "author", + "license", + "homepage", + "repository", + ) + if k in manifest + }, + "access": { + "os": manifest.get("permissions", {}).get("os", {}), + "services": [ + { + "kind": kind, + **{ + key: item[key] + for key in ( + "id", + "deployment", + "transport", + "command", + "args", + "url", + ) + if key in item + }, + } + for kind in ("voice_backend", "mcp_server") + for item in manifest["contributes"].get(kind, []) + ], + }, + } + + +def inspect_entry(source, wheel, host, python, profile): + digest = source["wheel"]["sha256"] + with wheel.open("rb") as stream: + if hashlib.file_digest(stream, "sha256").hexdigest() != digest: + fail("artifact digest mismatch") + result = subprocess.run( + [ + str(python), + "-I", + str(host / "tools/extension_package.py"), + "check-wheel", + str(wheel), + ], + capture_output=True, + text=True, + timeout=60, + check=False, + ) + if result.returncode: + fail( + "fixed Host check-wheel rejected artifact (run it locally for its sanitized diagnostic)" + ) + inspection = json.loads(result.stdout) + if inspection["digest"] != digest: + fail("artifact changed during inspection") + check_policy(wheel, inspection) + manifest = inspection["manifest"] + validate_review(source, manifest["id"], digest) + contributions = [ + {"kind": kind, "role": item.get("role", ""), "contract": item["contract"]} + for kind, items in manifest["contributes"].items() + for item in items + ] + return { + **{k: source[k] for k in ("publisher", "review", "withdrawal")}, + "id": manifest["id"], + "version": manifest["version"], + "sha256": digest, + "url": source["wheel"]["url"], + "size": wheel.stat().st_size, + "display": artifact_display(manifest), + "compatibility": { + "host_api": manifest["engines"]["amadeus_host_api"], + "python": str( + SpecifierSet(manifest["runtime"].get("requires_python", "")) + & SpecifierSet(inspection.get("requires_python", "")) + ), + "platforms": manifest["engines"].get("platforms", []), + "sdk": profile["sdk_version"], + "dependencies": inspection["requires_dist"], + "contributions": sorted(contributions, key=lambda c: tuple(c.values())), + "delivery": "python-wheel", + "runtime": manifest["runtime"]["kind"], + }, + } + + +def wheel_diff(before, after): + """Bounded per-file text diffs and hashes over the actual reviewed wheel bytes.""" + + def files(path): + if path is None: + return {} + with zipfile.ZipFile(path) as archive: + infos = archive.infolist() + if ( + len(infos) > 10000 + or sum(i.file_size for i in infos) > POLICY["max_wheel_bytes"] + ): + fail("diff archive exceeds limits") + return {i.filename: archive.read(i) for i in infos if not i.is_dir()} + + old, new = files(before), files(after) + changes = [] + for name in sorted(old.keys() | new.keys()): + a, b = old.get(name), new.get(name) + if a == b: + continue + row = { + "path": name, + "status": "added" if a is None else "removed" if b is None else "modified", + "before": hashlib.sha256(a).hexdigest() if a is not None else None, + "after": hashlib.sha256(b).hexdigest() if b is not None else None, + } + try: + if max(len(a or b""), len(b or b"")) <= 65536: + lines = list( + difflib.unified_diff( + (a or b"").decode("utf-8").splitlines(), + (b or b"").decode("utf-8").splitlines(), + lineterm="", + ) + ) + row["text_diff"] = lines[:400] + row["diff_truncated"] = len(lines) > 400 + except UnicodeError: + pass + changes.append(row) + return changes diff --git a/tools/check_catalog.py b/tools/check_catalog.py new file mode 100644 index 0000000..9adc327 --- /dev/null +++ b/tools/check_catalog.py @@ -0,0 +1,61 @@ +"""Check index transitions or emit actual wheel content review evidence (no execution).""" + +import argparse +import json +import zipfile +from pathlib import Path + +from catalog import ( + canonical, + read_json, + validate_index, + validate_transition, + wheel_diff, +) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--index", type=Path) + parser.add_argument("--previous", type=Path) + parser.add_argument( + "--wheel", type=Path, help="Digest-verified current artifact to review" + ) + parser.add_argument( + "--previous-wheel", + type=Path, + help="Previous reviewed artifact; omit for initial full inventory", + ) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + try: + if bool(args.index) == bool(args.wheel): + raise ValueError("choose an index or a wheel") + if args.index: + result = validate_index(read_json(args.index)) + if args.previous: + validate_transition(read_json(args.previous), result) + payload = { + "ok": True, + "sequence": result["sequence"], + "entries": len(result["entries"]), + } + else: + payload = { + "ok": True, + "changes": wheel_diff(args.previous_wheel, args.wheel), + "review_status": "not_reviewed", + "note": "Diff is evidence only; every version needs independent full review.", + } + if args.output: + args.output.write_bytes(canonical(payload)) + else: + print(json.dumps(payload, ensure_ascii=True)) + return 0 + except (OSError, ValueError, KeyError, TypeError, zipfile.BadZipFile): + print(json.dumps({"ok": False, "error": "invalid catalog or review artifact"})) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/check_catalog_ci.py b/tools/check_catalog_ci.py new file mode 100644 index 0000000..97e24b0 --- /dev/null +++ b/tools/check_catalog_ci.py @@ -0,0 +1,157 @@ +"""Run from protected base, consuming a proposed tree as data; never execute its wheels.""" + +import argparse +import json +import subprocess +import tempfile +from pathlib import Path + +from catalog import ( + PIN, + ROOT, + build_entry, + canonical, + fetch_artifact, + read_json, + retained_entry, + validate_index, + validate_transition, + verify_host, + wheel_diff, +) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--proposal", type=Path, required=True) + parser.add_argument("--base", required=True) + parser.add_argument("--host", type=Path, required=True) + parser.add_argument("--python", type=Path, required=True) + parser.add_argument("--artifacts", type=Path, required=True) + parser.add_argument("--evidence", type=Path, required=True) + args = parser.parse_args() + args.evidence.mkdir(parents=True, exist_ok=True) + profile = verify_host(args.host, args.python) + previous = read_json(ROOT / "catalog/v1/index.json") + candidate_safety = { + **previous, + "sequence": previous["sequence"] + 1, + "security": read_json(args.proposal / "catalog/security.json"), + } + validate_transition(previous, candidate_safety) + # Input PR: check actual wheels and emit diffs. The PR does not grant publication authority. + for path in sorted((args.proposal / "catalog/packages").rglob("*.json")): + source = read_json(path) + if retained_entry(source, previous) is not None: + continue + wheel = fetch_artifact(source, args.artifacts) + entry = build_entry(source, args.artifacts, args.host, args.python, profile) + older = [ + e + for e in previous["entries"] + if e["id"] == entry["id"] and e["sha256"] != entry["sha256"] + ] + older.sort(key=lambda e: tuple(int(n) for n in e["version"].split("."))) + before = None + if older: + old = older[-1] + before = fetch_artifact( + {"wheel": {"url": old["url"], "sha256": old["sha256"]}}, args.artifacts + ) + # Digest is the filename; a submitted package ID never becomes a local path. + (args.evidence / (entry["sha256"] + ".json")).write_bytes( + canonical( + { + "identity": entry["id"], + "review_status": "requires_human_review", + "changes": wheel_diff(before, wheel), + } + ) + ) + proposed = validate_index(read_json(args.proposal / "catalog/v1/index.json")) + validate_transition(previous, proposed) + if canonical(previous) == canonical(proposed): + print(json.dumps({"ok": True, "publication": False})) + return 0 + # Generator/input commits must both already belong to protected main. + for commit in (proposed["source_commit"], proposed["generator_commit"]): + subprocess.run( + [ + "git", + "-C", + str(args.proposal), + "merge-base", + "--is-ancestor", + commit, + args.base, + ], + check=True, + capture_output=True, + ) + # A separate disposable checkout runs exactly the approved generator version. + with tempfile.TemporaryDirectory(prefix="catalog-generator-") as temporary: + checkout = Path(temporary) / "generator" + subprocess.run( + ["git", "clone", "--shared", "--no-checkout", str(ROOT), str(checkout)], + check=True, + capture_output=True, + ) + subprocess.run( + [ + "git", + "-C", + str(checkout), + "checkout", + "--detach", + proposed["generator_commit"], + ], + check=True, + capture_output=True, + ) + generator_pin = read_json(checkout / "catalog/toolchain.json") + if generator_pin != PIN: + raise ValueError("qualify a changed Host pin before publication") + old = Path(temporary) / "previous.json" + old.write_bytes(canonical(previous)) + command = [ + str(args.python), + str(checkout / "tools/build_catalog.py"), + "--source-repo", + str(args.proposal.resolve()), + "--source-commit", + proposed["source_commit"], + "--approved-ref", + args.base, + "--sequence", + str(proposed["sequence"]), + "--previous", + str(old), + "--host", + str(args.host.resolve()), + "--python", + str(args.python.resolve()), + "--artifacts", + str(args.artifacts.resolve()), + "--output", + str((args.proposal / "catalog/v1/index.json").resolve()), + "--fetch", + "--check", + ] + subprocess.run(command, check=True) + print(json.dumps({"ok": True, "publication": True})) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (OSError, ValueError, KeyError, TypeError, subprocess.SubprocessError): + print( + json.dumps( + { + "ok": False, + "error": "catalog CI rejected input, review, pin or publication transition", + } + ) + ) + raise SystemExit(1) diff --git a/tools/preflight_submission.py b/tools/preflight_submission.py new file mode 100644 index 0000000..afc5c5a --- /dev/null +++ b/tools/preflight_submission.py @@ -0,0 +1,77 @@ +"""Author preflight: reuse pinned Host admission and curated policy without approval.""" +import argparse +import hashlib +import json +import shutil +import subprocess +import tempfile +from pathlib import Path + +from catalog import PIN, canonical, check_policy, verify_host, wheel_diff + + +def preflight(wheel, host, python): + report = { + "ok": False, "review_status": "not_reviewed", "host_commit": PIN["host_commit"], + "checks": {"package_format": "not_established", "target_host": "not_established", + "catalog_policy": "not_run", "human_and_runtime_review": "pending"}, + } + try: + profile = verify_host(host, python) + with tempfile.TemporaryDirectory(prefix="submission-preflight-") as temporary: + snapshot = Path(temporary) / wheel.name + shutil.copyfile(wheel, snapshot) + digest = hashlib.sha256(snapshot.read_bytes()).hexdigest() + result = subprocess.run( + [str(python), "-I", str(host / "tools/extension_package.py"), + "check-wheel", str(snapshot)], + capture_output=True, text=True, encoding="utf-8", timeout=60, check=False, + ) + inspection = json.loads(result.stdout) + if result.returncode or not inspection.get("ok"): + report["checks"]["target_host"] = "failed" + # Fixed Host's JSON is sanitized; never include process stderr/config. + report["host_diagnostic"] = {key: inspection[key] for key in + ("error", "errors", "checked", "not_checked", "unavailable") if key in inspection} + report["next_step"] = "Resolve the fixed Host check-wheel diagnostic, then repeat preflight." + return report + if inspection["digest"] != digest: + raise ValueError("wheel changed during inspection") + report.update(identity={"id": inspection["manifest"]["id"], + "version": inspection["manifest"]["version"], "sha256": digest}, + environment={"python": profile["python"], "platform": profile["platform"]}, + checked=inspection["checked"], not_checked=inspection["not_checked"]) + # One owner checks both boundaries; a failure is not guessed to be format-valid. + report["checks"].update(package_format="passed", target_host="passed") + try: + check_policy(snapshot, inspection) + except ValueError as exc: + report["checks"]["catalog_policy"] = "failed" + report["policy_diagnostic"] = str(exc) + report["next_step"] = "Fix this curated-policy violation; Host/local admission may still be valid." + return report + report["checks"]["catalog_policy"] = "passed" + report["inventory"] = wheel_diff(None, snapshot) + report["ok"] = True + report["next_step"] = "Attach this report to the submission PR; ownership, rights, runtime and independent review remain required." + except (OSError, ValueError, KeyError, subprocess.SubprocessError) as exc: + report["error"] = str(exc) + report["next_step"] = "Check the wheel path and pinned Windows/Python/checker environment." + return report + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--wheel", type=Path, required=True) + parser.add_argument("--host", type=Path, required=True) + parser.add_argument("--python", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + report = preflight(args.wheel.resolve(), args.host.resolve(), args.python.resolve()) + args.output.write_bytes(canonical(report)) + print(json.dumps({"ok": report["ok"], "checks": report["checks"], "review_status": "not_reviewed"})) + return 0 if report["ok"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/verify_publication.py b/tools/verify_publication.py new file mode 100644 index 0000000..9cca539 --- /dev/null +++ b/tools/verify_publication.py @@ -0,0 +1,69 @@ +"""Verify served index bytes and selected artifacts after publication, without execution.""" +import argparse +import hashlib +import json +import tempfile +from pathlib import Path +from urllib.request import build_opener + +from catalog import ( + POLICY, + BoundedRedirect, + canonical, + fetch_artifact, + https_url, + parse_json, + validate_index, +) + + +def verify(index_url, expected_path, previous_path=None): + expected_bytes = expected_path.read_bytes() + expected = validate_index(parse_json(expected_bytes)) + previous = validate_index(parse_json(previous_path.read_bytes())) if previous_path else {"entries": []} + with build_opener(BoundedRedirect()).open(https_url(index_url), timeout=30) as response: + actual = response.read(4 * 1024 * 1024 + 1) + validate_index(parse_json(actual)) + if actual != expected_bytes: + raise ValueError("served index differs from the checked publication") + previous_blocks = {(d["id"], d["version"], d["sha256"]) for d in previous.get("security", []) if d["status"] == "blocked"} + old = {(e["id"], e["version"], e["sha256"], e["url"]) for e in previous["entries"] + if not e["withdrawal"] and (e["id"], e["version"], e["sha256"]) not in previous_blocks} + blocked = {(d["id"], d["version"], d["sha256"]) for d in expected["security"] if d["status"] == "blocked"} + checked, skipped = [], [] + with tempfile.TemporaryDirectory(prefix="publication-availability-") as temporary: + for entry in expected["entries"]: + identity = {key: entry[key] for key in ("id", "version", "sha256")} + key = (entry["id"], entry["version"], entry["sha256"], entry["url"]) + # Withdrawal/security publication must survive unavailable retired assets. + if entry["withdrawal"] or (entry["id"], entry["version"], entry["sha256"]) in blocked or key in old: + skipped.append({**identity, "reason": "withdrawn, blocked or unchanged published artifact"}) + continue + wheel = fetch_artifact({"wheel": {"url": entry["url"], "sha256": entry["sha256"]}}, Path(temporary)) + if wheel.stat().st_size != entry["size"] or wheel.stat().st_size > POLICY["max_wheel_bytes"]: + raise ValueError("served artifact size differs from checked publication") + checked.append(identity) + return {"ok": True, "sequence": expected["sequence"], + "index_sha256": hashlib.sha256(actual).hexdigest(), + "checked_artifacts": checked, "skipped_artifacts": skipped, + "scope": "exact index and newly distributed/relocated artifacts; not human approval"} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--index-url", required=True) + parser.add_argument("--expected", type=Path, required=True) + parser.add_argument("--previous", type=Path) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + try: + result = verify(args.index_url, args.expected, args.previous) + except (OSError, ValueError, KeyError) as exc: + result = {"ok": False, "error": str(exc), "next_step": "Do not mark this publication live; inspect endpoint and immutable artifacts."} + args.output.write_bytes(canonical(result)) + print(json.dumps({"ok": result["ok"]})) + return 0 if result["ok"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main())