From 5e05fe17578cbe76ffb77879314812fc58c8a6f4 Mon Sep 17 00:00:00 2001 From: Dr Alexander Mikhalev Date: Wed, 14 Oct 2026 13:37:08 +0100 Subject: [PATCH 1/7] fix: prepare Zed adapter for public LSP RC4 --- README.md | 6 ++- catalog/terraphim-lsp/README.md | 5 +++ docs/marketplace.md | 12 ++--- docs/qualification.md | 9 ++++ docs/rc4-install-qualification.md | 74 +++++++++++++++++++++++++++++++ src/provision.rs | 64 ++++++++++++++++---------- tests/fixtures/README.md | 7 ++- 7 files changed, 144 insertions(+), 33 deletions(-) create mode 100644 docs/rc4-install-qualification.md diff --git a/README.md b/README.md index f8fa336..729c99e 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,10 @@ Rust types and crates. ## Publication status This is scoped extension source with public build/release CI. It is not yet an -installable Zed registry entry. The current pin requires a new public LSP release -with graph export and native qualification before marketplace submission. +installable Zed registry entry. The prepared RC4 pin requires its new public LSP +release and clean native installation qualification before marketplace submission. +No RC4 checksum or successful installation is claimed while those gates are +pending. See the [RC4 installation checklist](docs/rc4-install-qualification.md). Automatic provisioning supports macOS and Linux on x86_64 and ARM64. Windows automatic provisioning is not qualified; an explicit binary path remains possible. diff --git a/catalog/terraphim-lsp/README.md b/catalog/terraphim-lsp/README.md index 511b63c..5335ec5 100644 --- a/catalog/terraphim-lsp/README.md +++ b/catalog/terraphim-lsp/README.md @@ -9,6 +9,11 @@ This package supplies the language server only. It downloads verified release assets from GitHub, or uses the user's explicit `lsp.terraphim-lsp.binary.path`. It does not provide the browser editor's panels or an MCP server. +The prepared dependency is `terraphim_lsp-v1.22.0-rc.4`. Public release downloads +and a clean default installation remain pending; the historical RC3 test +checksums are not evidence for this pin. Follow the +[installation checklist](../../docs/rc4-install-qualification.md) before publishing. + ## Install and migrate For local qualification, choose **Extensions → Install Dev Extension** and select diff --git a/docs/marketplace.md b/docs/marketplace.md index e170512..9ea0b62 100644 --- a/docs/marketplace.md +++ b/docs/marketplace.md @@ -26,10 +26,12 @@ The live official registry had no Terraphim entry when checked on 13 October. Publication still requires these actual dependencies: 1. A publicly downloadable LSP release with authored-synonym export. The - existing `terraphim_lsp-v1.22.0-rc.3` pin returned HTTP 404 on public GitHub. - Do not downgrade it, substitute local bytes or call internal access public - installation proof. Update the shared pin only after the new immutable - release is verified across all supported assets. + prepared shared pin is `terraphim_lsp-v1.22.0-rc.4`; its release and checksum + readback must pass for all six supported assets before this pin is published. + The earlier RC3 GitHub lookup returned HTTP 404; its retained Gitea fixtures + are historical evidence only. Do not substitute local bytes or call internal + access public installation proof. The [RC4 checklist](rc4-install-qualification.md) + keeps dependency download, native installation and catalog acceptance separate. 2. Manual stock-Zed qualification at the exact source commit submitted, then a human-authored catalog PR under the upstream AI policy. Upstream review and registry publication remain distinct from source or release publication. @@ -146,6 +148,6 @@ python3 scripts/marketplace.py --catalog --extension-path catalog/terraphim-lsp The last command requires a clean checkout at the existing tag and a fresh output directory. It is intentionally expected to fail for the current -unpublished public rc.3 dependency. A failed gate is a blocker, not successful +unpublished public RC4 dependency. A failed gate is a blocker, not successful installation evidence. Actual release/catalog commands must be run only after reviewing the exact tag and the successful receipt. diff --git a/docs/qualification.md b/docs/qualification.md index 9d08ad6..9b949a2 100644 --- a/docs/qualification.md +++ b/docs/qualification.md @@ -76,3 +76,12 @@ binary. The exact catalog package installation and public LSP provisioning checks remain pending. All 41 original editor requirements are not certified for stock Zed. The Zed application fork is outside this package. + +## Prepared public RC4 installation (14 October 2026) + +The shared pin update targets `terraphim_lsp-v1.22.0-rc.4`, built from public +crates.io dependencies. Checksums and native default-download results remain +pending until the actual release exists. The retained RC3 fixtures and the +candidate checks above do not qualify the RC4 release. The next run follows the +[isolated installation checklist](rc4-install-qualification.md), using the catalog +package without a binary override and inspecting the actual provisioned path. diff --git a/docs/rc4-install-qualification.md b/docs/rc4-install-qualification.md new file mode 100644 index 0000000..9682809 --- /dev/null +++ b/docs/rc4-install-qualification.md @@ -0,0 +1,74 @@ +# RC4 default-install qualification + +Status: prepared; release checksums and actual public installation are pending. +Do not publish this extension pin or submit its catalog entry before the gates +below pass. No hash in the historical RC3 fixtures is reused as an RC4 hash. + +## Dependency and source identity + +The shared LSP pin is `terraphim_lsp-v1.22.0-rc.4` in the public +`terraphim/terraphim-ai` repository. The catalog package disables the internal +Gitea attempt; the legacy combined adapter preserves its source order and MCP +configuration. Both still verify the selected binary against that release's +`checksums.txt` and store a versioned `.verified` marker. Checksums are downloaded +and checked at runtime, not invented or embedded during this preparation. + +Before an extension tag is published, run the existing `marketplace.py` public +preflight on the exact reviewed/tagged source. It must anonymously download +and hash all six raw assets: macOS ARM64, macOS x86_64, macOS universal, Linux +x86_64 GNU, Linux x86_64 musl and Linux ARM64 musl. Save its actual receipt and +confirm the public LSP manifest binds the public source and crates.io archive. +macOS binaries are unsigned; retain the release's actual qualification scope. + +## Fresh stock-Zed profile + +Use the installed stock application, with a new directory outside existing user +projects. The local CLI supports `--foreground`, `--new` and `--user-data-dir`. +The official [CLI documentation](https://zed.dev/docs/reference/cli) describes +this override. Current [path implementation](https://github.com/zed-industries/zed/blob/main/crates/paths/src/paths.rs) +places settings at `/config/settings.json` when the override is active. +macOS logs may still use the global log directory, so capture this instance's +foreground output separately; do not replace or clear the user's logs. + +1. Create new profile and fixture directories. Record the source commit, actual + WASM hash, Zed version and a pre-install file listing. Start with no extension + cache, no copied user settings and no `binary.path` or `binary.arguments`. +2. Launch `zed --foreground --new --user-data-dir PROFILE FIXTURE_DIRECTORY`. + Verify settings opened by this instance resolve inside `PROFILE/config`. + Never alter the user's existing profile to make this check pass. +3. Use **Extensions → Install Dev Extension** to select the exact reviewed + `catalog/terraphim-lsp` source. This is the documented + [development installation route](https://zed.dev/docs/extensions/developing-extensions). + The isolated profile must contain only one Terraphim language-server adapter. + A dev installation validates the adapter and its default downloader; it is + not evidence that the Zed catalog has accepted or installed the extension. +4. Open a fresh Markdown fixture. Observe the completed default download and + inspect the spawned language-server executable path. Require it to be the + profile's versioned RC4 extension-work binary, not an explicit path or a PATH + fallback. Compare that executable and its `.verified` marker with the actual + anonymous RC4 checksum, then run that exact file's `--version`. +5. Restart the language server and verify the same cached executable and marker + are reused. Retain the instance log and process-path evidence. A candidate + override or a binary found on PATH cannot satisfy these checks. + +## Saved user-visible outputs + +Use the bounded native checks in [qualification.md](qualification.md): swaps of +existing alternatives, save/reopen, undo/redo, Trim Done, clean export, repeated +knowledge-graph export and dirty-destination refusal. Clearly mark preauthored +fixture annotations as input. The current stock adapter does not provide an +arbitrary-alternative text-input UI; the separate LSP authoring protocol requires +an explicit client caller. + +Inspect the actual saved graph and unchanged source, then run released +terraphim-grep against that graph. Keep baseline and graph-enabled outputs +separate. Store the native extension/source/asset hashes with saved files and +screenshots. These checks do not certify all 41 browser-editor requirements. + +## Catalog gate + +After the exact-source native run and extension release succeed, prepare the +existing catalog patch through CI. Zed's upstream AI policy requires a human to +write and submit the catalog PR and maintainer replies. Verify the live registry +listing and a second clean registry installation after upstream acceptance. +The Zed application fork remains outside this work. diff --git a/src/provision.rs b/src/provision.rs index 125e779..034c616 100644 --- a/src/provision.rs +++ b/src/provision.rs @@ -20,18 +20,16 @@ //! Only raw (uncompressed) release assets are supported: the host extracts //! archives before the extension could hash them. //! -//! # Sources (rc.3) +//! # Release sources //! -//! The rc.3 language server is published on the internal Gitea -//! (`git.terraphim.cloud`), in the public release-only repository -//! `terraphim/terraphim-lsp-releases` (no source; the source stays private in -//! `terraphim/terraphim-ai`). A download is tried from that release first -//! (its URLs are the `releases/download//` scheme, so no API -//! lookup is needed: the host's `download_file`, which sends no credentials, -//! is the only call), then from the GitHub release, then `PATH`. Each source -//! is verified against its own `checksums.txt`. Gitea is reachable from the -//! tailnet only, so off the tailnet (or when an asset is missing) the -//! download fails and the next source is used. +//! The public catalog uses GitHub only for the shared rc.4 pin. Its release +//! must exist and pass anonymous download checks before extension publication. +//! The combined legacy adapter retains its Gitea-first source order, then +//! GitHub and finally `PATH`; a missing internal release falls through to +//! GitHub. Each source is checked against its own `checksums.txt`. +//! +//! The captured rc.3 Gitea fixtures remain historical parser/provisioning +//! evidence. They are not checksums or installation evidence for rc.4. use sha2::{Digest, Sha256}; use std::fmt; @@ -64,15 +62,14 @@ pub struct BinarySpec { } /// The internal Gitea repository (tailnet only, public, release assets only) -/// that hosts the rc.3 release channel. The source stays in the private -/// `terraphim/terraphim-ai`. +/// retained by the combined legacy adapter. The catalog never contacts it. pub const GITEA_RELEASES: &str = "https://git.terraphim.cloud/terraphim/terraphim-lsp-releases"; /// The `terraphim-lsp` language server from terraphim-ai. pub const LSP: BinarySpec = BinarySpec { repo: "terraphim/terraphim-ai", gitea: Some(GITEA_RELEASES), - tag: "terraphim_lsp-v1.22.0-rc.3", + tag: "terraphim_lsp-v1.22.0-rc.4", asset_stem: "terraphim-lsp", checksum_file: "checksums.txt", dir_prefix: "terraphim-lsp", @@ -243,7 +240,7 @@ impl std::error::Error for ProvisionError {} /// Release asset names for `platform`, most preferred first. /// /// macOS prefers the universal binary, then the per-architecture one -/// (signing is release-dependent; the rc.3 macOS assets are unsigned); +/// (signing is release-dependent; verify the selected release manifest); /// Linux prefers glibc on x86_64 and musl elsewhere. Windows is deferred /// (issue #16) and reported as unsupported. pub fn candidate_assets( @@ -805,6 +802,11 @@ mod tests { const CHECKSUMS: &str = include_str!("../tests/fixtures/checksums.txt"); const ASSETS_JSON: &str = include_str!("../tests/fixtures/release_assets.json"); + // Immutable historical fixture identity, not the current release pin. + const HISTORICAL_RC3: BinarySpec = BinarySpec { + tag: "terraphim_lsp-v1.22.0-rc.3", + ..LSP + }; /// `LSP` without the Gitea source: the GitHub-only flow. const GH: BinarySpec = BinarySpec { gitea: None, ..LSP }; @@ -915,7 +917,7 @@ mod tests { ); assert_eq!( binary_path(&mcp), - "terraphim-lsp-terraphim_lsp-v1.22.0-rc.3/terraphim_mcp_server" + "terraphim-lsp-terraphim_lsp-v1.22.0-rc.4/terraphim_mcp_server" ); } @@ -961,13 +963,13 @@ mod tests { } } - /// The fixtures are captured from the published pre-release, so the - /// pinned tag must resolve to the real (unsigned) universal binary on Gitea. + /// Preserve the real historical capture without re-labelling it as rc.4. #[test] - fn fixture_is_the_published_pinned_release() { - assert_eq!(LSP.tag, "terraphim_lsp-v1.22.0-rc.3"); + fn fixture_is_the_historical_rc3_release() { + assert_eq!(HISTORICAL_RC3.tag, "terraphim_lsp-v1.22.0-rc.3"); + assert_ne!(HISTORICAL_RC3.tag, LSP.tag); let assets = fixture_assets(); - let candidates = candidate_assets(&LSP, MAC_ARM).unwrap(); + let candidates = candidate_assets(&HISTORICAL_RC3, MAC_ARM).unwrap(); let asset = pick_asset(&candidates, &assets).unwrap(); assert_eq!(asset.name, "terraphim-lsp-universal-apple-darwin"); assert!(asset.download_url.starts_with( @@ -1086,11 +1088,11 @@ mod tests { fn install_layout_is_versioned() { assert_eq!( install_dir(&LSP), - "terraphim-lsp-terraphim_lsp-v1.22.0-rc.3" + "terraphim-lsp-terraphim_lsp-v1.22.0-rc.4" ); assert_eq!( binary_path(&LSP), - "terraphim-lsp-terraphim_lsp-v1.22.0-rc.3/terraphim-lsp" + "terraphim-lsp-terraphim_lsp-v1.22.0-rc.4/terraphim-lsp" ); } @@ -1697,7 +1699,11 @@ mod tests { // The fixture is the Gitea release's own asset list. for asset in fixture_assets() { assert_eq!( - gitea_asset_url(LSP.gitea.unwrap(), LSP.tag, &asset.name), + gitea_asset_url( + HISTORICAL_RC3.gitea.unwrap(), + HISTORICAL_RC3.tag, + &asset.name + ), asset.download_url ); } @@ -1901,6 +1907,16 @@ mod tests { mod catalog_configuration_tests { use super::*; + #[test] + fn shared_public_release_pin_targets_rc4() { + assert_eq!(LSP.tag, "terraphim_lsp-v1.22.0-rc.4"); + assert_eq!(LSP.repo, "terraphim/terraphim-ai"); + assert_eq!(LSP.checksum_file, "checksums.txt"); + if cfg!(feature = "public-catalog") { + assert_eq!(configured_lsp().gitea, None); + } + } + #[test] fn configured_lsp_changes_only_the_private_network_source() { let selected = configured_lsp(); diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md index 93d94d2..0da84da 100644 --- a/tests/fixtures/README.md +++ b/tests/fixtures/README.md @@ -6,8 +6,11 @@ pre-release `terraphim_lsp-v1.22.0-rc.3` of the release-only repository so no credentials are needed): the release's `checksums.txt` (`sha256sum -- *` format) and its asset list (`name`, `download_url` = Gitea's `browser_download_url`, the `releases/download//` scheme the -extension reproduces in `gitea_asset_url`). Recapture both when the pinned tag -in `src/provision.rs` changes: +extension reproduces in `gitea_asset_url`). These files remain an immutable +historical RC3 capture. They are not RC4 checksums or public-install evidence. +The live release gate downloads and verifies the current pin independently. + +The original capture used: ```sh TAG=terraphim_lsp-v1.22.0-rc.3 From ef6b2a6ffb557105cf557b77a2159289700903b4 Mon Sep 17 00:00:00 2001 From: Dr Alexander Mikhalev Date: Wed, 14 Oct 2026 13:53:40 +0100 Subject: [PATCH 2/7] docs: explain native Zed alternative authoring command --- README.md | 13 +++++++--- docs/alternative-authoring.md | 43 +++++++++++++++++++++++++++++++ docs/qualification.md | 26 ++++++++++++++++--- docs/rc4-install-qualification.md | 9 ++++--- 4 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 docs/alternative-authoring.md diff --git a/README.md b/README.md index 729c99e..040c991 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,15 @@ no separate knowledge-graph engine and does not require a Zed application fork. In stock Zed, code actions swap existing KG synonyms and alternatives already saved in a Markdown annotation. Hover, diagnostics and writing actions use the -shared LSP. For arbitrary new alternatives, the LSP exposes -`terraphim.alternative.add`: a client must collect the selection and new text, -invoke the command, and apply its returned edit. The current adapter provides no -input UI for that authoring command or the browser editor's visual panels. +shared LSP. The prepared RC4 server adds `terraphim.alternative.add_and_apply` +for Zed's built-in LSP command picker: supply a JSON object with the document URI, +selection range and new text. The server asks Zed to apply the annotation edit. +This candidate flow has passed native save/reopen and undo/redo checks; the +released RC4 installation gate remains pending. See the +[command-picker walkthrough](docs/alternative-authoring.md). +The original `terraphim.alternative.add` still returns an edit for clients that +apply it themselves. The adapter does not infer the current selection or provide +the browser editor's visual panels. Alternatives and authored `synonyms::` relationships use Terraphim's existing Rust types and crates. diff --git a/docs/alternative-authoring.md b/docs/alternative-authoring.md new file mode 100644 index 0000000..12206a8 --- /dev/null +++ b/docs/alternative-authoring.md @@ -0,0 +1,43 @@ +# Author a new alternative with Zed's command picker + +Status: verified with a candidate RC4 server in stock Zed 1.23.3. The public +RC4 release and default-install verification are still pending. + +Zed's built-in **lsp command selector: toggle** action can pass JSON arguments +to a language server. The prepared RC4 server advertises +`terraphim.alternative.add_and_apply`, which asks Zed to apply the annotation +and confirms that the document changed. This uses the stock application. + +For a Markdown file beginning with `Risk is manageable.`, open the command +palette, run **lsp command selector: toggle**, and select +`terraphim.alternative.add_and_apply`. Press Tab to focus **Arguments** and +enter one JSON object, replacing the URI with your open file's absolute URI: + +```json +{"uri":"file:///path/to/brief.md","range":{"start":{"line":0,"character":0},"end":{"line":0,"character":4}},"text":"Concern"} +``` + +Press Enter, then save the document. The original sentence keeps its wording, while the Markdown annotation records `Concern` as its +alternative. The URI and range are explicit input: the picker does not infer +your selection. LSP line and character positions start at zero; character +offsets use UTF-16. An optional `version` rejects a stale document version. + +You can then select the word and use its alternative code action to swap the +wording. Save/reopen preserves the annotation; normal undo and redo apply to +the edit. Run `terraphim.export_knowledge_graph` through the same picker with: + +```json +{"uri":"file:///path/to/brief.md"} +``` + +Save the generated `brief.knowledge-graph.json`. Its `risk` and `concern` +entries share a concept ID; released `terraphim-grep` can consume that graph. +The source document retains its annotation. + +The original `terraphim.alternative.add` returns a WorkspaceEdit for clients +that apply it themselves. Zed's generic picker only logs a command's return +value, so use the explicit `add_and_apply` variant here. It requires versioned +edits and client apply support. A rejection or stale document is reported as a +failure. If acknowledgement or the matching document change times out, inspect +the document before retrying: the edit may still arrive. A successful command +returns an acknowledgement rather than a second edit to apply. diff --git a/docs/qualification.md b/docs/qualification.md index 9b949a2..8b0b89c 100644 --- a/docs/qualification.md +++ b/docs/qualification.md @@ -26,10 +26,12 @@ public project brief and save each resulting file. 2. Start with a clearly identified preauthored annotation or existing KG term, swap an alternative, and save, close and reopen the Markdown document. Inspect the saved annotations and resulting text; verify undo and redo bytes. - The current stock-Zed adapter has no input UI for an arbitrary new alternative. - The separate `terraphim.alternative.add` protocol requires a caller to collect - the selection/text and apply the returned edit; a seeded fixture does not - qualify that authoring flow. + Separately start from plain Markdown and use the prepared RC4 + `terraphim.alternative.add_and_apply` command in Zed's generic picker with an + explicit URI/range/text object. Inspect the newly created annotation and + repeat save/reopen and undo/redo. A seeded fixture cannot qualify authoring. + The original `terraphim.alternative.add` still returns an edit that the + generic picker only logs; use the explicit apply command for this check. 3. Choose a trim level and **Done**. Save/reopen and inspect persisted ghost anchors, then verify subsequent edits keep the anchors aligned. 4. Export the document knowledge graph twice from the native code action. Verify @@ -75,6 +77,22 @@ binary. The exact catalog package installation and public LSP provisioning checks remain pending. All 41 original editor requirements are not certified for stock Zed. + +## New-alternative candidate check — 14 October 2026 + +Stock Zed 1.23.3 ran the new `terraphim.alternative.add_and_apply` command through +its generic LSP picker. A single JSON object supplied a range over `Risk` and +the new text `Concern`; no annotation or synonym relationship existed in the +initial fixture. Zed applied and saved the generated annotation. Native undo +and save restored the original plain Markdown; redo and save restored the +annotation byte for byte. After close/reopen, native graph export produced +`risk` and `concern` with the same concept ID and preserved the source. + +This used an explicit candidate binary override (SHA-256 +`c7fd75a97184764860b0a7f5a129466e28f817e689f03ca17176d2545a352ba9`). +It qualifies the manual JSON command path, not a selection-aware text prompt, +default release download, or catalog installation. The earlier fixture-based +checks above retain their original scope. See [authoring](alternative-authoring.md). The Zed application fork is outside this package. ## Prepared public RC4 installation (14 October 2026) diff --git a/docs/rc4-install-qualification.md b/docs/rc4-install-qualification.md index 9682809..e73851b 100644 --- a/docs/rc4-install-qualification.md +++ b/docs/rc4-install-qualification.md @@ -56,9 +56,12 @@ foreground output separately; do not replace or clear the user's logs. Use the bounded native checks in [qualification.md](qualification.md): swaps of existing alternatives, save/reopen, undo/redo, Trim Done, clean export, repeated knowledge-graph export and dirty-destination refusal. Clearly mark preauthored -fixture annotations as input. The current stock adapter does not provide an -arbitrary-alternative text-input UI; the separate LSP authoring protocol requires -an explicit client caller. +fixture annotations as input. Separately create a new alternative from plain +Markdown with `terraphim.alternative.add_and_apply` in Zed's generic LSP picker, +as described in [the authoring walkthrough](alternative-authoring.md). Supply +the document URI, selection range and text explicitly; there is no automatic +selection inference. Verify the newly generated annotation, save/reopen and +undo/redo before exporting it. The original return-edit command is unchanged. Inspect the actual saved graph and unchanged source, then run released terraphim-grep against that graph. Keep baseline and graph-enabled outputs From 4a49bea7b9710848f0d0fc217588a7852ab8f7f3 Mon Sep 17 00:00:00 2001 From: Dr Alexander Mikhalev Date: Wed, 14 Oct 2026 14:50:51 +0100 Subject: [PATCH 3/7] fix: prepare Zed provisioning for immutable LSP RC5 --- README.md | 12 ++--- catalog/terraphim-lsp/README.md | 4 +- docs/alternative-authoring.md | 3 +- docs/marketplace.md | 8 ++-- docs/qualification.md | 23 +++++---- docs/rc5-install-qualification.md | 80 +++++++++++++++++++++++++++++++ src/provision.rs | 18 +++---- tests/fixtures/README.md | 2 +- 8 files changed, 120 insertions(+), 30 deletions(-) create mode 100644 docs/rc5-install-qualification.md diff --git a/README.md b/README.md index 040c991..024751d 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ no separate knowledge-graph engine and does not require a Zed application fork. In stock Zed, code actions swap existing KG synonyms and alternatives already saved in a Markdown annotation. Hover, diagnostics and writing actions use the -shared LSP. The prepared RC4 server adds `terraphim.alternative.add_and_apply` +shared LSP. The prospective RC5 server includes `terraphim.alternative.add_and_apply` for Zed's built-in LSP command picker: supply a JSON object with the document URI, selection range and new text. The server asks Zed to apply the annotation edit. -This candidate flow has passed native save/reopen and undo/redo checks; the -released RC4 installation gate remains pending. See the +The earlier RC4 candidate flow passed native save/reopen and undo/redo checks; the +released RC5 installation gate remains pending. See the [command-picker walkthrough](docs/alternative-authoring.md). The original `terraphim.alternative.add` still returns an edit for clients that apply it themselves. The adapter does not infer the current selection or provide @@ -30,10 +30,10 @@ Rust types and crates. ## Publication status This is scoped extension source with public build/release CI. It is not yet an -installable Zed registry entry. The prepared RC4 pin requires its new public LSP +installable Zed registry entry. The prepared RC5 pin requires its new public LSP release and clean native installation qualification before marketplace submission. -No RC4 checksum or successful installation is claimed while those gates are -pending. See the [RC4 installation checklist](docs/rc4-install-qualification.md). +No RC5 checksum or successful installation is claimed while those gates are +pending. See the [RC5 installation checklist](docs/rc5-install-qualification.md). Automatic provisioning supports macOS and Linux on x86_64 and ARM64. Windows automatic provisioning is not qualified; an explicit binary path remains possible. diff --git a/catalog/terraphim-lsp/README.md b/catalog/terraphim-lsp/README.md index 5335ec5..5bdf899 100644 --- a/catalog/terraphim-lsp/README.md +++ b/catalog/terraphim-lsp/README.md @@ -9,10 +9,10 @@ This package supplies the language server only. It downloads verified release assets from GitHub, or uses the user's explicit `lsp.terraphim-lsp.binary.path`. It does not provide the browser editor's panels or an MCP server. -The prepared dependency is `terraphim_lsp-v1.22.0-rc.4`. Public release downloads +The prepared dependency is `terraphim_lsp-v1.22.0-rc.5`. Public release downloads and a clean default installation remain pending; the historical RC3 test checksums are not evidence for this pin. Follow the -[installation checklist](../../docs/rc4-install-qualification.md) before publishing. +[installation checklist](../../docs/rc5-install-qualification.md) before publishing. ## Install and migrate diff --git a/docs/alternative-authoring.md b/docs/alternative-authoring.md index 12206a8..755ef30 100644 --- a/docs/alternative-authoring.md +++ b/docs/alternative-authoring.md @@ -1,7 +1,8 @@ # Author a new alternative with Zed's command picker Status: verified with a candidate RC4 server in stock Zed 1.23.3. The public -RC4 release and default-install verification are still pending. +RC4 GitHub binary release failed in the musl build lanes. The prospective RC5 +release and default-install verification remain pending. Zed's built-in **lsp command selector: toggle** action can pass JSON arguments to a language server. The prepared RC4 server advertises diff --git a/docs/marketplace.md b/docs/marketplace.md index 9ea0b62..d02d0f0 100644 --- a/docs/marketplace.md +++ b/docs/marketplace.md @@ -23,14 +23,16 @@ explicitly records `nativeQualified: false`; a passing build cannot attest to manual Zed behavior or registry acceptance. The live official registry had no Terraphim entry when checked on 13 October. +The RC4 crate was published, but its GitHub binary release failed in the two +musl build lanes. The prospective RC5 pin preserves that immutable history. Publication still requires these actual dependencies: 1. A publicly downloadable LSP release with authored-synonym export. The - prepared shared pin is `terraphim_lsp-v1.22.0-rc.4`; its release and checksum + prepared shared pin is `terraphim_lsp-v1.22.0-rc.5`; its release and checksum readback must pass for all six supported assets before this pin is published. The earlier RC3 GitHub lookup returned HTTP 404; its retained Gitea fixtures are historical evidence only. Do not substitute local bytes or call internal - access public installation proof. The [RC4 checklist](rc4-install-qualification.md) + access public installation proof. The [RC5 checklist](rc5-install-qualification.md) keeps dependency download, native installation and catalog acceptance separate. 2. Manual stock-Zed qualification at the exact source commit submitted, then a human-authored catalog PR under the upstream AI policy. Upstream review @@ -148,6 +150,6 @@ python3 scripts/marketplace.py --catalog --extension-path catalog/terraphim-lsp The last command requires a clean checkout at the existing tag and a fresh output directory. It is intentionally expected to fail for the current -unpublished public RC4 dependency. A failed gate is a blocker, not successful +unpublished public RC5 dependency. A failed gate is a blocker, not successful installation evidence. Actual release/catalog commands must be run only after reviewing the exact tag and the successful receipt. diff --git a/docs/qualification.md b/docs/qualification.md index 8b0b89c..984ed92 100644 --- a/docs/qualification.md +++ b/docs/qualification.md @@ -95,11 +95,18 @@ default release download, or catalog installation. The earlier fixture-based checks above retain their original scope. See [authoring](alternative-authoring.md). The Zed application fork is outside this package. -## Prepared public RC4 installation (14 October 2026) - -The shared pin update targets `terraphim_lsp-v1.22.0-rc.4`, built from public -crates.io dependencies. Checksums and native default-download results remain -pending until the actual release exists. The retained RC3 fixtures and the -candidate checks above do not qualify the RC4 release. The next run follows the -[isolated installation checklist](rc4-install-qualification.md), using the catalog -package without a binary override and inspecting the actual provisioned path. +## Historical RC4 installation preparation (14 October 2026) + +The earlier shared pin targeted `terraphim_lsp-v1.22.0-rc.4`, built from public +crates.io dependencies. Its crate was published, but the GitHub binary release +failed in the two musl lanes. Its [prepared checklist](rc4-install-qualification.md) +and the retained RC3 fixtures do not prove a default release installation. + +## Prospective public RC5 installation (14 October 2026) + +The prepared shared pin is `terraphim_lsp-v1.22.0-rc.5`. Its public assets, +checksums and native default-download results remain pending. The candidate +checks above retain their original identities. The next run follows the +[RC5 isolated installation checklist](rc5-install-qualification.md), using the +catalog package without a binary override and inspecting the actual provisioned +path. A successful local build alone cannot qualify release installation. diff --git a/docs/rc5-install-qualification.md b/docs/rc5-install-qualification.md new file mode 100644 index 0000000..fa1102a --- /dev/null +++ b/docs/rc5-install-qualification.md @@ -0,0 +1,80 @@ +# RC5 default-install qualification + +Status: prospective RC5 preparation; release checksums and actual public +installation are pending. The [earlier RC4 checklist](rc4-install-qualification.md) +is retained as history: its crate was published, but its GitHub binary release +failed in the musl lanes. No RC4 tag or package is modified for this recovery. +Do not publish this extension pin or submit its catalog entry before the gates +below pass. No hash in the historical RC3 fixtures is reused as an RC5 hash. + +## Dependency and source identity + +The shared LSP pin is `terraphim_lsp-v1.22.0-rc.5` in the public +`terraphim/terraphim-ai` repository. The catalog package disables the internal +Gitea attempt; the legacy combined adapter preserves its source order and MCP +configuration. Both still verify the selected binary against that release's +`checksums.txt` and store a versioned `.verified` marker. Checksums are downloaded +and checked at runtime, not invented or embedded during this preparation. + +Before an extension tag is published, run the existing `marketplace.py` public +preflight on the exact reviewed/tagged source. It must anonymously download +and hash all six raw assets: macOS ARM64, macOS x86_64, macOS universal, Linux +x86_64 GNU, Linux x86_64 musl and Linux ARM64 musl. Save its actual receipt and +confirm the public LSP manifest binds the public source and crates.io archive. +macOS binaries are unsigned; retain the release's actual qualification scope. + +## Fresh stock-Zed profile + +Use the installed stock application, with a new directory outside existing user +projects. The local CLI supports `--foreground`, `--new` and `--user-data-dir`. +The official [CLI documentation](https://zed.dev/docs/reference/cli) describes +this override. Current [path implementation](https://github.com/zed-industries/zed/blob/main/crates/paths/src/paths.rs) +places settings at `/config/settings.json` when the override is active. +macOS logs may still use the global log directory, so capture this instance's +foreground output separately; do not replace or clear the user's logs. + +1. Create new profile and fixture directories. Record the source commit, actual + WASM hash, Zed version and a pre-install file listing. Start with no extension + cache, no copied user settings and no `binary.path` or `binary.arguments`. +2. Launch `zed --foreground --new --user-data-dir PROFILE FIXTURE_DIRECTORY`. + Verify settings opened by this instance resolve inside `PROFILE/config`. + Never alter the user's existing profile to make this check pass. +3. Use **Extensions → Install Dev Extension** to select the exact reviewed + `catalog/terraphim-lsp` source. This is the documented + [development installation route](https://zed.dev/docs/extensions/developing-extensions). + The isolated profile must contain only one Terraphim language-server adapter. + A dev installation validates the adapter and its default downloader; it is + not evidence that the Zed catalog has accepted or installed the extension. +4. Open a fresh Markdown fixture. Observe the completed default download and + inspect the spawned language-server executable path. Require it to be the + profile's versioned RC5 extension-work binary, not an explicit path or a PATH + fallback. Compare that executable and its `.verified` marker with the actual + anonymous RC5 checksum, then run that exact file's `--version`. +5. Restart the language server and verify the same cached executable and marker + are reused. Retain the instance log and process-path evidence. A candidate + override or a binary found on PATH cannot satisfy these checks. + +## Saved user-visible outputs + +Use the bounded native checks in [qualification.md](qualification.md): swaps of +existing alternatives, save/reopen, undo/redo, Trim Done, clean export, repeated +knowledge-graph export and dirty-destination refusal. Clearly mark preauthored +fixture annotations as input. Separately create a new alternative from plain +Markdown with `terraphim.alternative.add_and_apply` in Zed's generic LSP picker, +as described in [the authoring walkthrough](alternative-authoring.md). Supply +the document URI, selection range and text explicitly; there is no automatic +selection inference. Verify the newly generated annotation, save/reopen and +undo/redo before exporting it. The original return-edit command is unchanged. + +Inspect the actual saved graph and unchanged source, then run released +terraphim-grep against that graph. Keep baseline and graph-enabled outputs +separate. Store the native extension/source/asset hashes with saved files and +screenshots. These checks do not certify all 41 browser-editor requirements. + +## Catalog gate + +After the exact-source native run and extension release succeed, prepare the +existing catalog patch through CI. Zed's upstream AI policy requires a human to +write and submit the catalog PR and maintainer replies. Verify the live registry +listing and a second clean registry installation after upstream acceptance. +The Zed application fork remains outside this work. diff --git a/src/provision.rs b/src/provision.rs index 034c616..847e31e 100644 --- a/src/provision.rs +++ b/src/provision.rs @@ -22,14 +22,14 @@ //! //! # Release sources //! -//! The public catalog uses GitHub only for the shared rc.4 pin. Its release +//! The public catalog uses GitHub only for the shared rc.5 pin. Its release //! must exist and pass anonymous download checks before extension publication. //! The combined legacy adapter retains its Gitea-first source order, then //! GitHub and finally `PATH`; a missing internal release falls through to //! GitHub. Each source is checked against its own `checksums.txt`. //! //! The captured rc.3 Gitea fixtures remain historical parser/provisioning -//! evidence. They are not checksums or installation evidence for rc.4. +//! evidence. They are not checksums or installation evidence for rc.5. use sha2::{Digest, Sha256}; use std::fmt; @@ -69,7 +69,7 @@ pub const GITEA_RELEASES: &str = "https://git.terraphim.cloud/terraphim/terraphi pub const LSP: BinarySpec = BinarySpec { repo: "terraphim/terraphim-ai", gitea: Some(GITEA_RELEASES), - tag: "terraphim_lsp-v1.22.0-rc.4", + tag: "terraphim_lsp-v1.22.0-rc.5", asset_stem: "terraphim-lsp", checksum_file: "checksums.txt", dir_prefix: "terraphim-lsp", @@ -917,7 +917,7 @@ mod tests { ); assert_eq!( binary_path(&mcp), - "terraphim-lsp-terraphim_lsp-v1.22.0-rc.4/terraphim_mcp_server" + "terraphim-lsp-terraphim_lsp-v1.22.0-rc.5/terraphim_mcp_server" ); } @@ -963,7 +963,7 @@ mod tests { } } - /// Preserve the real historical capture without re-labelling it as rc.4. + /// Preserve the real historical capture without re-labelling it as rc.5. #[test] fn fixture_is_the_historical_rc3_release() { assert_eq!(HISTORICAL_RC3.tag, "terraphim_lsp-v1.22.0-rc.3"); @@ -1088,11 +1088,11 @@ mod tests { fn install_layout_is_versioned() { assert_eq!( install_dir(&LSP), - "terraphim-lsp-terraphim_lsp-v1.22.0-rc.4" + "terraphim-lsp-terraphim_lsp-v1.22.0-rc.5" ); assert_eq!( binary_path(&LSP), - "terraphim-lsp-terraphim_lsp-v1.22.0-rc.4/terraphim-lsp" + "terraphim-lsp-terraphim_lsp-v1.22.0-rc.5/terraphim-lsp" ); } @@ -1908,8 +1908,8 @@ mod catalog_configuration_tests { use super::*; #[test] - fn shared_public_release_pin_targets_rc4() { - assert_eq!(LSP.tag, "terraphim_lsp-v1.22.0-rc.4"); + fn shared_public_release_pin_targets_rc5() { + assert_eq!(LSP.tag, "terraphim_lsp-v1.22.0-rc.5"); assert_eq!(LSP.repo, "terraphim/terraphim-ai"); assert_eq!(LSP.checksum_file, "checksums.txt"); if cfg!(feature = "public-catalog") { diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md index 0da84da..5d8f92f 100644 --- a/tests/fixtures/README.md +++ b/tests/fixtures/README.md @@ -7,7 +7,7 @@ so no credentials are needed): the release's `checksums.txt` (`sha256sum -- *` format) and its asset list (`name`, `download_url` = Gitea's `browser_download_url`, the `releases/download//` scheme the extension reproduces in `gitea_asset_url`). These files remain an immutable -historical RC3 capture. They are not RC4 checksums or public-install evidence. +historical RC3 capture. They are not RC4 or RC5 checksums or public-install evidence. The live release gate downloads and verifies the current pin independently. The original capture used: From aee04b9e04136b23b0f6d1ecac99eabf525f0abb Mon Sep 17 00:00:00 2001 From: Dr Alexander Mikhalev Date: Wed, 14 Oct 2026 15:32:52 +0100 Subject: [PATCH 4/7] fix: prepare stock-Zed adapter for public LSP RC6 --- README.md | 10 ++-- catalog/terraphim-lsp/README.md | 4 +- docs/alternative-authoring.md | 2 +- docs/marketplace.md | 10 ++-- docs/qualification.md | 13 +++-- docs/rc6-install-qualification.md | 82 +++++++++++++++++++++++++++++++ src/provision.rs | 18 +++---- tests/fixtures/README.md | 2 +- 8 files changed, 116 insertions(+), 25 deletions(-) create mode 100644 docs/rc6-install-qualification.md diff --git a/README.md b/README.md index 024751d..8b24aa0 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ no separate knowledge-graph engine and does not require a Zed application fork. In stock Zed, code actions swap existing KG synonyms and alternatives already saved in a Markdown annotation. Hover, diagnostics and writing actions use the -shared LSP. The prospective RC5 server includes `terraphim.alternative.add_and_apply` +shared LSP. The prospective RC6 server includes `terraphim.alternative.add_and_apply` for Zed's built-in LSP command picker: supply a JSON object with the document URI, selection range and new text. The server asks Zed to apply the annotation edit. The earlier RC4 candidate flow passed native save/reopen and undo/redo checks; the -released RC5 installation gate remains pending. See the +released RC6 installation gate remains pending. See the [command-picker walkthrough](docs/alternative-authoring.md). The original `terraphim.alternative.add` still returns an edit for clients that apply it themselves. The adapter does not infer the current selection or provide @@ -30,10 +30,10 @@ Rust types and crates. ## Publication status This is scoped extension source with public build/release CI. It is not yet an -installable Zed registry entry. The prepared RC5 pin requires its new public LSP +installable Zed registry entry. The prepared RC6 pin requires its new public LSP release and clean native installation qualification before marketplace submission. -No RC5 checksum or successful installation is claimed while those gates are -pending. See the [RC5 installation checklist](docs/rc5-install-qualification.md). +No RC6 checksum or successful installation is claimed while those gates are +pending. See the [RC6 installation checklist](docs/rc6-install-qualification.md). Automatic provisioning supports macOS and Linux on x86_64 and ARM64. Windows automatic provisioning is not qualified; an explicit binary path remains possible. diff --git a/catalog/terraphim-lsp/README.md b/catalog/terraphim-lsp/README.md index 5bdf899..d4c500b 100644 --- a/catalog/terraphim-lsp/README.md +++ b/catalog/terraphim-lsp/README.md @@ -9,10 +9,10 @@ This package supplies the language server only. It downloads verified release assets from GitHub, or uses the user's explicit `lsp.terraphim-lsp.binary.path`. It does not provide the browser editor's panels or an MCP server. -The prepared dependency is `terraphim_lsp-v1.22.0-rc.5`. Public release downloads +The prepared dependency is `terraphim_lsp-v1.22.0-rc.6`. Public release downloads and a clean default installation remain pending; the historical RC3 test checksums are not evidence for this pin. Follow the -[installation checklist](../../docs/rc5-install-qualification.md) before publishing. +[installation checklist](../../docs/rc6-install-qualification.md) before publishing. ## Install and migrate diff --git a/docs/alternative-authoring.md b/docs/alternative-authoring.md index 755ef30..e71f23a 100644 --- a/docs/alternative-authoring.md +++ b/docs/alternative-authoring.md @@ -1,7 +1,7 @@ # Author a new alternative with Zed's command picker Status: verified with a candidate RC4 server in stock Zed 1.23.3. The public -RC4 GitHub binary release failed in the musl build lanes. The prospective RC5 +RC4 GitHub binary release failed in the musl build lanes. The prospective RC6 release and default-install verification remain pending. Zed's built-in **lsp command selector: toggle** action can pass JSON arguments diff --git a/docs/marketplace.md b/docs/marketplace.md index d02d0f0..98d62cc 100644 --- a/docs/marketplace.md +++ b/docs/marketplace.md @@ -24,15 +24,17 @@ manual Zed behavior or registry acceptance. The live official registry had no Terraphim entry when checked on 13 October. The RC4 crate was published, but its GitHub binary release failed in the two -musl build lanes. The prospective RC5 pin preserves that immutable history. +musl build lanes. RC5 built all six binaries, but final release assembly rejected +x86_64 log names; no GitHub binary release was published. The prospective RC6 +pin preserves both immutable versions. Publication still requires these actual dependencies: 1. A publicly downloadable LSP release with authored-synonym export. The - prepared shared pin is `terraphim_lsp-v1.22.0-rc.5`; its release and checksum + prepared shared pin is `terraphim_lsp-v1.22.0-rc.6`; its release and checksum readback must pass for all six supported assets before this pin is published. The earlier RC3 GitHub lookup returned HTTP 404; its retained Gitea fixtures are historical evidence only. Do not substitute local bytes or call internal - access public installation proof. The [RC5 checklist](rc5-install-qualification.md) + access public installation proof. The [RC6 checklist](rc6-install-qualification.md) keeps dependency download, native installation and catalog acceptance separate. 2. Manual stock-Zed qualification at the exact source commit submitted, then a human-authored catalog PR under the upstream AI policy. Upstream review @@ -150,6 +152,6 @@ python3 scripts/marketplace.py --catalog --extension-path catalog/terraphim-lsp The last command requires a clean checkout at the existing tag and a fresh output directory. It is intentionally expected to fail for the current -unpublished public RC5 dependency. A failed gate is a blocker, not successful +unpublished public RC6 dependency. A failed gate is a blocker, not successful installation evidence. Actual release/catalog commands must be run only after reviewing the exact tag and the successful receipt. diff --git a/docs/qualification.md b/docs/qualification.md index 984ed92..0f8d7ab 100644 --- a/docs/qualification.md +++ b/docs/qualification.md @@ -102,11 +102,18 @@ crates.io dependencies. Its crate was published, but the GitHub binary release failed in the two musl lanes. Its [prepared checklist](rc4-install-qualification.md) and the retained RC3 fixtures do not prove a default release installation. -## Prospective public RC5 installation (14 October 2026) +## Historical RC5 installation preparation (14 October 2026) -The prepared shared pin is `terraphim_lsp-v1.22.0-rc.5`. Its public assets, +RC5 published its crate and built all six binaries, but final release assembly +rejected x86_64 log names. Its GitHub binary release was not published. The +[RC5 checklist](rc5-install-qualification.md) and untouched isolated input fixture +remain historical preparation, not proof of a default installation. + +## Prospective public RC6 installation (14 October 2026) + +The prepared shared pin is `terraphim_lsp-v1.22.0-rc.6`. Its public assets, checksums and native default-download results remain pending. The candidate checks above retain their original identities. The next run follows the -[RC5 isolated installation checklist](rc5-install-qualification.md), using the +[RC6 isolated installation checklist](rc6-install-qualification.md), using the catalog package without a binary override and inspecting the actual provisioned path. A successful local build alone cannot qualify release installation. diff --git a/docs/rc6-install-qualification.md b/docs/rc6-install-qualification.md new file mode 100644 index 0000000..b90ab55 --- /dev/null +++ b/docs/rc6-install-qualification.md @@ -0,0 +1,82 @@ +# RC6 default-install qualification + +Status: prospective RC6 preparation; release checksums and actual public +installation are pending. The [RC4 checklist](rc4-install-qualification.md) and +[RC5 checklist](rc5-install-qualification.md) are retained as history. RC4 failed +in the musl build lanes; RC5 built all six binaries, but final release assembly +rejected x86_64 log names. Neither GitHub binary release was published. Their +crates and tags are unchanged by this recovery. +Do not publish this extension pin or submit its catalog entry before the gates +below pass. No hash in the historical RC3 fixtures is reused as an RC6 hash. + +## Dependency and source identity + +The shared LSP pin is `terraphim_lsp-v1.22.0-rc.6` in the public +`terraphim/terraphim-ai` repository. The catalog package disables the internal +Gitea attempt; the legacy combined adapter preserves its source order and MCP +configuration. Both still verify the selected binary against that release's +`checksums.txt` and store a versioned `.verified` marker. Checksums are downloaded +and checked at runtime, not invented or embedded during this preparation. + +Before an extension tag is published, run the existing `marketplace.py` public +preflight on the exact reviewed/tagged source. It must anonymously download +and hash all six raw assets: macOS ARM64, macOS x86_64, macOS universal, Linux +x86_64 GNU, Linux x86_64 musl and Linux ARM64 musl. Save its actual receipt and +confirm the public LSP manifest binds the public source and crates.io archive. +macOS binaries are unsigned; retain the release's actual qualification scope. + +## Fresh stock-Zed profile + +Use the installed stock application, with a new directory outside existing user +projects. The local CLI supports `--foreground`, `--new` and `--user-data-dir`. +The official [CLI documentation](https://zed.dev/docs/reference/cli) describes +this override. Current [path implementation](https://github.com/zed-industries/zed/blob/main/crates/paths/src/paths.rs) +places settings at `/config/settings.json` when the override is active. +macOS logs may still use the global log directory, so capture this instance's +foreground output separately; do not replace or clear the user's logs. + +1. Create new profile and fixture directories. Record the source commit, actual + WASM hash, Zed version and a pre-install file listing. Start with no extension + cache, no copied user settings and no `binary.path` or `binary.arguments`. +2. Launch `zed --foreground --new --user-data-dir PROFILE FIXTURE_DIRECTORY`. + Verify settings opened by this instance resolve inside `PROFILE/config`. + Never alter the user's existing profile to make this check pass. +3. Use **Extensions → Install Dev Extension** to select the exact reviewed + `catalog/terraphim-lsp` source. This is the documented + [development installation route](https://zed.dev/docs/extensions/developing-extensions). + The isolated profile must contain only one Terraphim language-server adapter. + A dev installation validates the adapter and its default downloader; it is + not evidence that the Zed catalog has accepted or installed the extension. +4. Open a fresh Markdown fixture. Observe the completed default download and + inspect the spawned language-server executable path. Require it to be the + profile's versioned RC6 extension-work binary, not an explicit path or a PATH + fallback. Compare that executable and its `.verified` marker with the actual + anonymous RC6 checksum, then run that exact file's `--version`. +5. Restart the language server and verify the same cached executable and marker + are reused. Retain the instance log and process-path evidence. A candidate + override or a binary found on PATH cannot satisfy these checks. + +## Saved user-visible outputs + +Use the bounded native checks in [qualification.md](qualification.md): swaps of +existing alternatives, save/reopen, undo/redo, Trim Done, clean export, repeated +knowledge-graph export and dirty-destination refusal. Clearly mark preauthored +fixture annotations as input. Separately create a new alternative from plain +Markdown with `terraphim.alternative.add_and_apply` in Zed's generic LSP picker, +as described in [the authoring walkthrough](alternative-authoring.md). Supply +the document URI, selection range and text explicitly; there is no automatic +selection inference. Verify the newly generated annotation, save/reopen and +undo/redo before exporting it. The original return-edit command is unchanged. + +Inspect the actual saved graph and unchanged source, then run released +terraphim-grep against that graph. Keep baseline and graph-enabled outputs +separate. Store the native extension/source/asset hashes with saved files and +screenshots. These checks do not certify all 41 browser-editor requirements. + +## Catalog gate + +After the exact-source native run and extension release succeed, prepare the +existing catalog patch through CI. Zed's upstream AI policy requires a human to +write and submit the catalog PR and maintainer replies. Verify the live registry +listing and a second clean registry installation after upstream acceptance. +The Zed application fork remains outside this work. diff --git a/src/provision.rs b/src/provision.rs index 847e31e..a2e954d 100644 --- a/src/provision.rs +++ b/src/provision.rs @@ -22,14 +22,14 @@ //! //! # Release sources //! -//! The public catalog uses GitHub only for the shared rc.5 pin. Its release +//! The public catalog uses GitHub only for the shared rc.6 pin. Its release //! must exist and pass anonymous download checks before extension publication. //! The combined legacy adapter retains its Gitea-first source order, then //! GitHub and finally `PATH`; a missing internal release falls through to //! GitHub. Each source is checked against its own `checksums.txt`. //! //! The captured rc.3 Gitea fixtures remain historical parser/provisioning -//! evidence. They are not checksums or installation evidence for rc.5. +//! evidence. They are not checksums or installation evidence for rc.6. use sha2::{Digest, Sha256}; use std::fmt; @@ -69,7 +69,7 @@ pub const GITEA_RELEASES: &str = "https://git.terraphim.cloud/terraphim/terraphi pub const LSP: BinarySpec = BinarySpec { repo: "terraphim/terraphim-ai", gitea: Some(GITEA_RELEASES), - tag: "terraphim_lsp-v1.22.0-rc.5", + tag: "terraphim_lsp-v1.22.0-rc.6", asset_stem: "terraphim-lsp", checksum_file: "checksums.txt", dir_prefix: "terraphim-lsp", @@ -917,7 +917,7 @@ mod tests { ); assert_eq!( binary_path(&mcp), - "terraphim-lsp-terraphim_lsp-v1.22.0-rc.5/terraphim_mcp_server" + "terraphim-lsp-terraphim_lsp-v1.22.0-rc.6/terraphim_mcp_server" ); } @@ -963,7 +963,7 @@ mod tests { } } - /// Preserve the real historical capture without re-labelling it as rc.5. + /// Preserve the real historical capture without re-labelling it as rc.6. #[test] fn fixture_is_the_historical_rc3_release() { assert_eq!(HISTORICAL_RC3.tag, "terraphim_lsp-v1.22.0-rc.3"); @@ -1088,11 +1088,11 @@ mod tests { fn install_layout_is_versioned() { assert_eq!( install_dir(&LSP), - "terraphim-lsp-terraphim_lsp-v1.22.0-rc.5" + "terraphim-lsp-terraphim_lsp-v1.22.0-rc.6" ); assert_eq!( binary_path(&LSP), - "terraphim-lsp-terraphim_lsp-v1.22.0-rc.5/terraphim-lsp" + "terraphim-lsp-terraphim_lsp-v1.22.0-rc.6/terraphim-lsp" ); } @@ -1908,8 +1908,8 @@ mod catalog_configuration_tests { use super::*; #[test] - fn shared_public_release_pin_targets_rc5() { - assert_eq!(LSP.tag, "terraphim_lsp-v1.22.0-rc.5"); + fn shared_public_release_pin_targets_rc6() { + assert_eq!(LSP.tag, "terraphim_lsp-v1.22.0-rc.6"); assert_eq!(LSP.repo, "terraphim/terraphim-ai"); assert_eq!(LSP.checksum_file, "checksums.txt"); if cfg!(feature = "public-catalog") { diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md index 5d8f92f..228b815 100644 --- a/tests/fixtures/README.md +++ b/tests/fixtures/README.md @@ -7,7 +7,7 @@ so no credentials are needed): the release's `checksums.txt` (`sha256sum -- *` format) and its asset list (`name`, `download_url` = Gitea's `browser_download_url`, the `releases/download//` scheme the extension reproduces in `gitea_asset_url`). These files remain an immutable -historical RC3 capture. They are not RC4 or RC5 checksums or public-install evidence. +historical RC3 capture. They are not RC4, RC5 or RC6 checksums or public-install evidence. The live release gate downloads and verifies the current pin independently. The original capture used: From 140e25b573865efa372418ac5475771a12c4785d Mon Sep 17 00:00:00 2001 From: Dr Alexander Mikhalev Date: Wed, 14 Oct 2026 16:06:52 +0100 Subject: [PATCH 5/7] fix: download pinned catalog assets without release metadata API --- README.md | 12 +- catalog/terraphim-lsp/README.md | 8 +- docs/marketplace.md | 13 +- docs/rc6-install-qualification.md | 22 ++- src/provision.rs | 295 +++++++++++++++++++++++++----- 5 files changed, 290 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 8b24aa0..926e23c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ no separate knowledge-graph engine and does not require a Zed application fork. In stock Zed, code actions swap existing KG synonyms and alternatives already saved in a Markdown annotation. Hover, diagnostics and writing actions use the -shared LSP. The prospective RC6 server includes `terraphim.alternative.add_and_apply` +shared LSP. The released RC6 server includes `terraphim.alternative.add_and_apply` for Zed's built-in LSP command picker: supply a JSON object with the document URI, selection range and new text. The server asks Zed to apply the annotation edit. The earlier RC4 candidate flow passed native save/reopen and undo/redo checks; the @@ -30,10 +30,12 @@ Rust types and crates. ## Publication status This is scoped extension source with public build/release CI. It is not yet an -installable Zed registry entry. The prepared RC6 pin requires its new public LSP -release and clean native installation qualification before marketplace submission. -No RC6 checksum or successful installation is claimed while those gates are -pending. See the [RC6 installation checklist](docs/rc6-install-qualification.md). +installable Zed registry entry. The RC6 public LSP release and all six binary +checksums have been verified. The first clean native installation failed at the +GitHub metadata API with HTTP 403. The catalog downloader now uses the pinned +public asset URLs directly, verifies checksums, and disables PATH fallback. +A fresh native installation and saved-output qualification remain required +before marketplace submission. See the [RC6 installation checklist](docs/rc6-install-qualification.md). Automatic provisioning supports macOS and Linux on x86_64 and ARM64. Windows automatic provisioning is not qualified; an explicit binary path remains possible. diff --git a/catalog/terraphim-lsp/README.md b/catalog/terraphim-lsp/README.md index d4c500b..ba06e6f 100644 --- a/catalog/terraphim-lsp/README.md +++ b/catalog/terraphim-lsp/README.md @@ -6,11 +6,13 @@ pinned language server as the existing Terraphim extension. The language server uses Terraphim's shared crates for alternatives and `synonyms::` relationships. This package supplies the language server only. It downloads verified release -assets from GitHub, or uses the user's explicit `lsp.terraphim-lsp.binary.path`. +assets from fixed, pinned GitHub URLs without a metadata API request or PATH +fallback, or uses the user's explicit `lsp.terraphim-lsp.binary.path`. It does not provide the browser editor's panels or an MCP server. -The prepared dependency is `terraphim_lsp-v1.22.0-rc.6`. Public release downloads -and a clean default installation remain pending; the historical RC3 test +The dependency is `terraphim_lsp-v1.22.0-rc.6`. Its public release downloads and +checksums are verified. A fresh default installation remains pending after +fixing a GitHub metadata API rate-limit failure; the historical RC3 test checksums are not evidence for this pin. Follow the [installation checklist](../../docs/rc6-install-qualification.md) before publishing. diff --git a/docs/marketplace.md b/docs/marketplace.md index 98d62cc..fbcf16f 100644 --- a/docs/marketplace.md +++ b/docs/marketplace.md @@ -11,7 +11,11 @@ Cargo library points to the existing `src/lib.rs`; the provisioning, settings and all knowledge-graph behavior remain shared. The root `terraphim` package continues to enable its existing LSP and MCP server. The catalog builds with `public-catalog` and no `mcp` feature, so it registers only the language server -and skips the private-network Gitea download attempt. Both use the same pin and +and skips the private-network Gitea download attempt. The catalog selects from +the pinned public `checksums.txt` using fixed release URLs, without a metadata +API request or PATH fallback. The explicit binary path remains supported. The +legacy adapter retains Gitea-first, GitHub API discovery and PATH fallback; MCP +keeps its independent pin and discovery. Both LSP packages use the same pin and checksum verification. Remove the legacy adapter before enabling the catalog package, because they register the same language-server ID. @@ -25,8 +29,11 @@ manual Zed behavior or registry acceptance. The live official registry had no Terraphim entry when checked on 13 October. The RC4 crate was published, but its GitHub binary release failed in the two musl build lanes. RC5 built all six binaries, but final release assembly rejected -x86_64 log names; no GitHub binary release was published. The prospective RC6 -pin preserves both immutable versions. +x86_64 log names; no GitHub binary release was published. The RC6 release is +public, with all six binaries anonymously downloaded and verified on 14 October. +The first native default installation failed at the GitHub metadata API (403), +so direct pinned-asset provisioning must pass a new clean native run. RC4 and +RC5 remain immutable. Publication still requires these actual dependencies: 1. A publicly downloadable LSP release with authored-synonym export. The diff --git a/docs/rc6-install-qualification.md b/docs/rc6-install-qualification.md index b90ab55..d1396b7 100644 --- a/docs/rc6-install-qualification.md +++ b/docs/rc6-install-qualification.md @@ -1,7 +1,7 @@ # RC6 default-install qualification -Status: prospective RC6 preparation; release checksums and actual public -installation are pending. The [RC4 checklist](rc4-install-qualification.md) and +Status: RC6 public release and all six binary checksums verified on 14 October; +fresh native installation is pending after a real metadata API HTTP 403 failure. The [RC4 checklist](rc4-install-qualification.md) and [RC5 checklist](rc5-install-qualification.md) are retained as history. RC4 failed in the musl build lanes; RC5 built all six binaries, but final release assembly rejected x86_64 log names. Neither GitHub binary release was published. Their @@ -13,8 +13,11 @@ below pass. No hash in the historical RC3 fixtures is reused as an RC6 hash. The shared LSP pin is `terraphim_lsp-v1.22.0-rc.6` in the public `terraphim/terraphim-ai` repository. The catalog package disables the internal -Gitea attempt; the legacy combined adapter preserves its source order and MCP -configuration. Both still verify the selected binary against that release's +Gitea attempt and GitHub metadata API. It downloads fixed URLs under the exact +release tag and selects the first supported asset present in `checksums.txt`. +It never searches PATH after a failed download. Explicit `binary.path` settings +remain supported. The legacy combined adapter preserves its source order, +GitHub API discovery, PATH fallback and independent MCP configuration. Both still verify the selected binary against that release's `checksums.txt` and store a versioned `.verified` marker. Checksums are downloaded and checked at runtime, not invented or embedded during this preparation. @@ -25,6 +28,17 @@ x86_64 GNU, Linux x86_64 musl and Linux ARM64 musl. Save its actual receipt and confirm the public LSP manifest binds the public source and crates.io archive. macOS binaries are unsigned; retain the release's actual qualification scope. +## Preserved failed installation + +The first untouched stock-Zed profile installed the adapter at source +`aee04b9e04136b23b0f6d1ecac99eabf525f0abb`, then failed before obtaining a +binary: GitHub's unauthenticated metadata API returned HTTP 403 (rate limit). +That failed profile and its log are retained as failure evidence. The direct +pinned-asset change must be tested in another empty profile; a populated cache, +manual binary override or PATH binary cannot establish that it fixes this failure. +Pure URL, checksum-selection and fallback tests are local policy evidence, not +proof of a successful network installation. + ## Fresh stock-Zed profile Use the installed stock application, with a new directory outside existing user diff --git a/src/provision.rs b/src/provision.rs index a2e954d..e7ba681 100644 --- a/src/provision.rs +++ b/src/provision.rs @@ -22,8 +22,9 @@ //! //! # Release sources //! -//! The public catalog uses GitHub only for the shared rc.6 pin. Its release -//! must exist and pass anonymous download checks before extension publication. +//! The public catalog uses fixed GitHub asset URLs for the shared rc.6 pin, +//! with no metadata API request or PATH fallback. Its release must exist and +//! pass anonymous download checks before extension publication. //! The combined legacy adapter retains its Gitea-first source order, then //! GitHub and finally `PATH`; a missing internal release falls through to //! GitHub. Each source is checked against its own `checksums.txt`. @@ -37,11 +38,22 @@ use std::fs; use std::io::Read; use std::path::Path; +/// How a pinned GitHub release selects its downloadable assets. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GithubDiscovery { + /// Ask the host to list the release through GitHub's metadata API. + ApiListing, + /// Use fixed tag/target URLs and select the asset from `checksums.txt`. + PinnedChecksums, +} + /// A pinned release of one binary. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct BinarySpec { /// GitHub repository, `owner/name` (also the repository path on Gitea). pub repo: &'static str, + /// Asset-discovery policy for the GitHub source. + pub github_discovery: GithubDiscovery, /// URL of the internal Gitea repository that holds the release assets /// (public on the tailnet, assets only), tried before GitHub; `None` for /// binaries published on GitHub only. @@ -68,6 +80,7 @@ pub const GITEA_RELEASES: &str = "https://git.terraphim.cloud/terraphim/terraphi /// The `terraphim-lsp` language server from terraphim-ai. pub const LSP: BinarySpec = BinarySpec { repo: "terraphim/terraphim-ai", + github_discovery: GithubDiscovery::ApiListing, gitea: Some(GITEA_RELEASES), tag: "terraphim_lsp-v1.22.0-rc.6", asset_stem: "terraphim-lsp", @@ -79,11 +92,16 @@ pub const LSP: BinarySpec = BinarySpec { /// Select the same release pin for legacy and public catalog installations. /// -/// The public catalog package avoids the private-network Gitea attempt; the -/// existing combined adapter retains its original source order. +/// The public catalog uses fixed public asset URLs and no PATH fallback. The +/// existing combined adapter retains its original source order and discovery. pub const fn configured_lsp() -> BinarySpec { if cfg!(feature = "public-catalog") { - BinarySpec { gitea: None, ..LSP } + BinarySpec { + gitea: None, + github_discovery: GithubDiscovery::PinnedChecksums, + path_fallback: false, + ..LSP + } } else { LSP } @@ -92,6 +110,7 @@ pub const fn configured_lsp() -> BinarySpec { /// The `terraphim_mcp_server` context server from terraphim-clients. pub const MCP: BinarySpec = BinarySpec { repo: "terraphim/terraphim-clients", + github_discovery: GithubDiscovery::ApiListing, gitea: None, tag: "terraphim_mcp_server-v1.21.18-rc.1", asset_stem: "terraphim_mcp_server", @@ -289,6 +308,29 @@ pub fn gitea_release(spec: &BinarySpec, platform: Platform) -> Result .collect()) } +/// Known public URLs for the pinned GitHub release and platform. +/// +/// These URLs do not require a GitHub metadata API request. Availability and +/// asset choice are established by the downloaded checksum file; the binary +/// must still pass its exact SHA-256 check before it can be started. +pub fn github_pinned_release( + spec: &BinarySpec, + platform: Platform, +) -> Result, ProvisionError> { + let mut names = candidate_assets(spec, platform)?; + names.push(spec.checksum_file.to_string()); + Ok(names + .into_iter() + .map(|name| Asset { + download_url: format!( + "https://github.com/{}/releases/download/{}/{name}", + spec.repo, spec.tag, + ), + name, + }) + .collect()) +} + /// The first candidate present in the release's asset list. pub fn pick_asset<'a>( candidates: &[String], @@ -512,6 +554,17 @@ pub fn failure_message(spec: &BinarySpec, platform: Platform, error: &ProvisionE ) } +// Keep the lookup lazy: a disabled fallback must never ask the host for PATH. +fn fallback_from_path( + spec: &BinarySpec, + lookup: impl FnOnce() -> Option, +) -> Option { + spec.path_fallback + .then(lookup) + .flatten() + .filter(|path| !path.trim().is_empty()) +} + /// Status text when provisioning failed but a `PATH` binary is used. pub fn path_fallback_message( spec: &BinarySpec, @@ -582,7 +635,7 @@ pub fn resolve( Err(error) => { // Leave nothing executable or half-written behind (I2). let _ = fs::remove_dir_all(root.join(install_dir(spec))); - match host.which(spec.asset_stem).filter(|p| !p.trim().is_empty()) { + match fallback_from_path(spec, || host.which(spec.asset_stem)) { Some(path) => { host.set_status(Status::Failed(path_fallback_message( spec, platform, &error, &path, @@ -621,7 +674,7 @@ fn install_from_sources( candidate_assets(spec, platform)?; let mut announced = false; let Some(base) = spec.gitea else { - return install_listed(spec, platform, root, host, &mut announced); + return install_github(spec, platform, root, host, &mut announced); }; let from_gitea = gitea_release(spec, platform).and_then(|assets| { install( @@ -638,7 +691,7 @@ fn install_from_sources( Ok(path) => return Ok(path), Err(error) => error, }; - install_listed(spec, platform, root, host, &mut announced).map_err(|github| { + install_github(spec, platform, root, host, &mut announced).map_err(|github| { ProvisionError::Sources { gitea_base: base.to_string(), gitea: Box::new(gitea), @@ -647,18 +700,60 @@ fn install_from_sources( }) } -/// Installs from the GitHub release the host lists. -fn install_listed( +/// Installs from GitHub using the binary's explicit discovery policy. +fn install_github( spec: &BinarySpec, platform: Platform, root: &Path, host: &mut impl Host, announced: &mut bool, ) -> Result { - let assets = host - .release_assets(spec.repo, spec.tag) - .map_err(ProvisionError::Host)?; - install(spec, platform, root, host, assets, Pick::Listed, announced) + let (assets, pick) = match spec.github_discovery { + GithubDiscovery::ApiListing => ( + host.release_assets(spec.repo, spec.tag) + .map_err(ProvisionError::Host)?, + Pick::Listed, + ), + GithubDiscovery::PinnedChecksums => { + (github_pinned_release(spec, platform)?, Pick::ByChecksums) + } + }; + install(spec, platform, root, host, assets, pick, announced) +} + +// The same pure selection path serves direct Gitea and pinned GitHub assets. +fn pick_checksummed_asset<'a>( + candidates: &[String], + assets: &'a [Asset], + checksum_text: &str, +) -> Result<(&'a Asset, String), ProvisionError> { + // Only a missing entry moves on to the next candidate; every + // other checksum error (a malformed file, conflicting entries) + // fails closed rather than installing a lower-priority asset. + let mut chosen = None; + for name in candidates { + match lookup_checksum(checksum_text, name) { + Ok(expected) => { + let asset = assets + .iter() + .find(|asset| &asset.name == name) + .ok_or_else(|| ProvisionError::NoMatchingAsset { + candidates: candidates.to_vec(), + })?; + chosen = Some((asset, expected)); + break; + } + // With direct pinned URLs, `checksums.txt` is the + // listing: like `pick_asset` on a GitHub release, the first + // candidate present wins. Whichever is chosen is verified + // against its own entry before it is installed. + Err(ProvisionError::ChecksumEntryMissing { .. }) => continue, + Err(error) => return Err(error), + } + } + chosen.ok_or_else(|| ProvisionError::NoMatchingAsset { + candidates: candidates.to_vec(), + }) } /// Downloads, verifies and installs the pinned release from `assets`; @@ -715,36 +810,7 @@ fn install( })?; let (asset, expected) = match listed { Some(asset) => (asset, lookup_checksum(&checksum_text, &asset.name)?), - None => { - // Only a missing entry moves on to the next candidate; every - // other checksum error (a malformed file, conflicting entries) - // fails closed rather than installing a lower-priority asset. - let mut chosen = None; - for name in &candidates { - match lookup_checksum(&checksum_text, name) { - Ok(expected) => { - let asset = - assets - .iter() - .find(|asset| &asset.name == name) - .ok_or_else(|| ProvisionError::NoMatchingAsset { - candidates: candidates.clone(), - })?; - chosen = Some((asset, expected)); - break; - } - // No listing exists on Gitea, so `checksums.txt` is the - // listing: like `pick_asset` on a GitHub release, the first - // candidate present wins. Whichever is chosen is verified - // against its own entry before it is installed. - Err(ProvisionError::ChecksumEntryMissing { .. }) => continue, - Err(error) => return Err(error), - } - } - chosen.ok_or_else(|| ProvisionError::NoMatchingAsset { - candidates: candidates.clone(), - })? - } + None => pick_checksummed_asset(&candidates, &assets, &checksum_text)?, }; let temporary_rel = format!("{dir}/{}.download", asset.name); @@ -1907,6 +1973,130 @@ mod tests { mod catalog_configuration_tests { use super::*; + #[test] + fn disabled_path_fallback_never_performs_lookup() { + for spec in [ + MCP, + BinarySpec { + path_fallback: false, + ..LSP + }, + ] { + let calls = std::cell::Cell::new(0); + assert_eq!( + fallback_from_path(&spec, || { + calls.set(calls.get() + 1); + Some("/unrelated/terraphim-lsp".into()) + }), + None + ); + assert_eq!(calls.get(), 0); + } + } + + #[test] + fn legacy_path_fallback_preserves_lookup_and_empty_path_refusal() { + let calls = std::cell::Cell::new(0); + assert_eq!( + fallback_from_path(&LSP, || { + calls.set(calls.get() + 1); + Some("/existing/terraphim-lsp".into()) + }), + Some("/existing/terraphim-lsp".into()) + ); + assert_eq!(calls.get(), 1); + assert_eq!(fallback_from_path(&LSP, || Some(" ".into())), None); + assert_eq!(fallback_from_path(&LSP, || None), None); + } + + #[test] + fn pinned_github_urls_use_only_the_exact_release_and_supported_targets() { + let cases: &[(Os, Arch, &[&str])] = &[ + ( + Os::Mac, + Arch::Aarch64, + &["universal-apple-darwin", "aarch64-apple-darwin"], + ), + ( + Os::Mac, + Arch::X8664, + &["universal-apple-darwin", "x86_64-apple-darwin"], + ), + ( + Os::Linux, + Arch::X8664, + &["x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"], + ), + (Os::Linux, Arch::Aarch64, &["aarch64-unknown-linux-musl"]), + ]; + for &(os, arch, targets) in cases { + let assets = github_pinned_release(&configured_lsp(), Platform { os, arch }).unwrap(); + let mut names: Vec<_> = targets + .iter() + .map(|target| format!("terraphim-lsp-{target}")) + .collect(); + names.push("checksums.txt".into()); + assert_eq!(assets.len(), names.len()); + for (asset, expected) in assets.iter().zip(names) { + assert_eq!(asset.name, expected); + assert_eq!(asset.download_url, format!( + "https://github.com/terraphim/terraphim-ai/releases/download/terraphim_lsp-v1.22.0-rc.6/{expected}" + )); + } + } + assert!(matches!( + github_pinned_release( + &LSP, + Platform { + os: Os::Windows, + arch: Arch::X8664 + } + ), + Err(ProvisionError::UnsupportedPlatform { .. }) + )); + } + + #[test] + fn pinned_checksums_select_preferred_asset_and_only_skip_missing_entries() { + let platform = Platform { + os: Os::Mac, + arch: Arch::Aarch64, + }; + let assets = github_pinned_release(&LSP, platform).unwrap(); + let candidates = candidate_assets(&LSP, platform).unwrap(); + let preferred = format!("{} {}\n", "a".repeat(64), candidates[0]); + let alternate = format!("{} {}\n", "b".repeat(64), candidates[1]); + let (asset, hash) = + pick_checksummed_asset(&candidates, &assets, &(alternate.clone() + &preferred)) + .unwrap(); + assert_eq!(asset.name, candidates[0]); + assert_eq!(hash, "a".repeat(64)); + assert_eq!( + pick_checksummed_asset(&candidates, &assets, &alternate) + .unwrap() + .0 + .name, + candidates[1] + ); + let conflict = preferred + &format!("{} {}\n", "c".repeat(64), candidates[0]) + &alternate; + assert!(matches!( + pick_checksummed_asset(&candidates, &assets, &conflict), + Err(ProvisionError::ChecksumAmbiguous { .. }) + )); + assert!(matches!( + pick_checksummed_asset( + &candidates, + &assets, + &("malformed\n".to_owned() + &alternate) + ), + Err(ProvisionError::ChecksumMalformed { .. }) + )); + assert!(matches!( + pick_checksummed_asset(&candidates, &assets, ""), + Err(ProvisionError::NoMatchingAsset { .. }) + )); + } + #[test] fn shared_public_release_pin_targets_rc6() { assert_eq!(LSP.tag, "terraphim_lsp-v1.22.0-rc.6"); @@ -1918,15 +2108,30 @@ mod catalog_configuration_tests { } #[test] - fn configured_lsp_changes_only_the_private_network_source() { + fn configured_lsp_changes_only_catalog_source_and_fallback_policy() { let selected = configured_lsp(); assert_eq!( BinarySpec { gitea: LSP.gitea, + github_discovery: LSP.github_discovery, + path_fallback: LSP.path_fallback, ..selected }, LSP ); + assert_eq!(LSP.github_discovery, GithubDiscovery::ApiListing); + assert_eq!(MCP.github_discovery, GithubDiscovery::ApiListing); + const { assert!(LSP.path_fallback) }; + const { assert!(!MCP.path_fallback) }; + assert_eq!(selected.path_fallback, !cfg!(feature = "public-catalog")); + assert_eq!( + selected.github_discovery, + if cfg!(feature = "public-catalog") { + GithubDiscovery::PinnedChecksums + } else { + GithubDiscovery::ApiListing + } + ); assert_eq!( selected.gitea, if cfg!(feature = "public-catalog") { From 5ec7da1892df6b59a752301e8f83a83b735fc6d8 Mon Sep 17 00:00:00 2001 From: Dr Alexander Mikhalev Date: Wed, 14 Oct 2026 16:58:06 +0100 Subject: [PATCH 6/7] fix: publish the qualified Zed catalog package --- .github/workflows/release.yml | 39 ++++- README.md | 28 ++-- catalog/terraphim-lsp/README.md | 18 ++- docs/marketplace.md | 113 ++++++++------ docs/qualification.md | 66 +++++++- docs/released-package-install.md | 40 +++++ examples/release-contract.rs | 20 ++- scripts/catalog_package.py | 34 ++++- scripts/ci.sh | 3 +- scripts/marketplace.py | 126 ++++++++++++++-- scripts/package.py | 31 +++- scripts/package_qualification.py | 46 +++--- scripts/publish_github.py | 27 ++-- scripts/test_catalog_package.py | 16 ++ scripts/test_catalog_release.py | 208 ++++++++++++++++++++++++++ scripts/test_package_qualification.py | 41 ++++- 16 files changed, 709 insertions(+), 147 deletions(-) create mode 100644 docs/released-package-install.md create mode 100644 scripts/test_catalog_release.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 746a027..1279286 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,9 +9,17 @@ on: required: true type: string publish: - description: Publish after all public download gates pass + description: Publish the catalog package after native source qualification default: false type: boolean + qualified_commit: + description: Exact native-tested source SHA (required to publish) + default: '' + type: string + qualification_url: + description: Immutable public native-qualification receipt URL (required to publish) + default: '' + type: string permissions: contents: read concurrency: @@ -21,10 +29,17 @@ jobs: verify: runs-on: ubuntu-24.04 timeout-minutes: 30 + outputs: + artifact-id: ${{ steps.package-upload.outputs.artifact-id }} env: RELEASE_TAG: ${{ inputs.tag || github.ref_name }} SOURCE_REPO: ${{ github.repository }} CARGO_BUILD_BUILD_DIR: target/build + RELEASE_EVENT: ${{ github.event_name }} + RELEASE_REF: ${{ github.ref }} + PUBLISH_REQUESTED: ${{ inputs.publish && 'true' || 'false' }} + QUALIFIED_COMMIT: ${{ inputs.qualified_commit }} + QUALIFICATION_URL: ${{ inputs.qualification_url }} steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 with: @@ -33,12 +48,18 @@ jobs: persist-credentials: false - run: bash scripts/ci.sh - name: Verify public source and every raw pinned LSP download - run: python3 scripts/marketplace.py --tag "$RELEASE_TAG" --repo "$SOURCE_REPO" --out public-preflight + run: >- + python3 scripts/marketplace.py --tag "$RELEASE_TAG" --repo "$SOURCE_REPO" + --extension-path catalog/terraphim-lsp --out public-preflight + --release-event "$RELEASE_EVENT" --release-ref "$RELEASE_REF" + --release-publish "$PUBLISH_REQUESTED" --qualified-commit "$QUALIFIED_COMMIT" + --qualification-url "$QUALIFICATION_URL" - name: Package the tested WASI module for this tag - run: python3 scripts/package.py --tag "$RELEASE_TAG" --wasm target/wasm32-wasip2/release/zed_terraphim.wasm --out-dir release-dist + run: python3 scripts/catalog_package.py --tag "$RELEASE_TAG" --wasm catalog/terraphim-lsp/target/wasm32-wasip2/release/zed_terraphim_lsp.wasm --out release-dist - name: Bind package bytes to the tested module and tagged source - run: python3 scripts/package_qualification.py --tag "$RELEASE_TAG" --wasm target/wasm32-wasip2/release/zed_terraphim.wasm - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + run: python3 scripts/package_qualification.py --tag "$RELEASE_TAG" --extension-path catalog/terraphim-lsp --wasm catalog/terraphim-lsp/target/wasm32-wasip2/release/zed_terraphim_lsp.wasm + - id: package-upload + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: zed-release-${{ inputs.tag || github.ref_name }} path: | @@ -46,10 +67,11 @@ jobs: release-dist/SHA256SUMS release-dist/package-qualification.json public-preflight/preflight.json + public-preflight/native-qualification.json if-no-files-found: error publish: needs: verify - if: github.event_name == 'push' || inputs.publish + if: github.event_name == 'workflow_dispatch' && inputs.publish runs-on: ubuntu-24.04 environment: zed-release permissions: @@ -65,12 +87,13 @@ jobs: fetch-depth: 0 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: - name: zed-release-${{ inputs.tag || github.ref_name }} + artifact-ids: ${{ needs.verify.outputs.artifact-id }} + merge-multiple: true path: verified-release - name: Publish verified assets without replacing existing bytes env: GH_TOKEN: ${{ github.token }} - run: python3 scripts/publish_github.py --tag "$RELEASE_TAG" --repo "$SOURCE_REPO" --artifacts verified-release + run: python3 scripts/publish_github.py --tag "$RELEASE_TAG" --repo "$SOURCE_REPO" --artifacts verified-release --extension-path catalog/terraphim-lsp - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: zed-publication-${{ inputs.tag || github.ref_name }} diff --git a/README.md b/README.md index 926e23c..4bab4e7 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,10 @@ no separate knowledge-graph engine and does not require a Zed application fork. In stock Zed, code actions swap existing KG synonyms and alternatives already saved in a Markdown annotation. Hover, diagnostics and writing actions use the -shared LSP. The released RC6 server includes `terraphim.alternative.add_and_apply` -for Zed's built-in LSP command picker: supply a JSON object with the document URI, +shared LSP. To create an alternative, use `terraphim.alternative.add_and_apply` +in Zed's built-in LSP command picker: supply a JSON object with the document URI, selection range and new text. The server asks Zed to apply the annotation edit. -The earlier RC4 candidate flow passed native save/reopen and undo/redo checks; the -released RC6 installation gate remains pending. See the -[command-picker walkthrough](docs/alternative-authoring.md). +See the [command-picker walkthrough](docs/alternative-authoring.md). The original `terraphim.alternative.add` still returns an edit for clients that apply it themselves. The adapter does not infer the current selection or provide the browser editor's visual panels. @@ -29,13 +27,14 @@ Rust types and crates. ## Publication status -This is scoped extension source with public build/release CI. It is not yet an -installable Zed registry entry. The RC6 public LSP release and all six binary -checksums have been verified. The first clean native installation failed at the -GitHub metadata API with HTTP 403. The catalog downloader now uses the pinned -public asset URLs directly, verifies checksums, and disables PATH fallback. -A fresh native installation and saved-output qualification remain required -before marketplace submission. See the [RC6 installation checklist](docs/rc6-install-qualification.md). +The public release workflow packages **Terraphim LSP**, the catalog variant. +[Released-package installation](docs/released-package-install.md) explains how +to verify and load its compiled WASM without rebuilding it. Official Zed catalog +availability requires a separate upstream review and registry build. + +[Qualification evidence](docs/qualification.md) distinguishes source tests, +released-package installation and saved editor outputs. Each native receipt +identifies the exact extension source, WASM and language-server binary used. Automatic provisioning supports macOS and Linux on x86_64 and ARM64. Windows automatic provisioning is not qualified; an explicit binary path remains possible. @@ -52,8 +51,9 @@ bash scripts/ci.sh ``` CI checks locked native/WASI builds, warnings-denied Clippy, actual package bytes -and publication regressions. Reviewed immutable tags drive GitHub releases after -anonymous dependency-download checks and artifact/source binding. +and publication regressions. Immutable tags run verification. A manual dispatch at the exact tag publishes +the catalog package after native source qualification, anonymous dependency +checks and artifact/source binding; retries reuse the original CI artifact. [Marketplace instructions](docs/marketplace.md) describe the gates and Zed's human-authored catalog submission requirement. [Qualification](docs/qualification.md) diff --git a/catalog/terraphim-lsp/README.md b/catalog/terraphim-lsp/README.md index ba06e6f..ff44e9a 100644 --- a/catalog/terraphim-lsp/README.md +++ b/catalog/terraphim-lsp/README.md @@ -10,16 +10,20 @@ assets from fixed, pinned GitHub URLs without a metadata API request or PATH fallback, or uses the user's explicit `lsp.terraphim-lsp.binary.path`. It does not provide the browser editor's panels or an MCP server. -The dependency is `terraphim_lsp-v1.22.0-rc.6`. Its public release downloads and -checksums are verified. A fresh default installation remains pending after -fixing a GitHub metadata API rate-limit failure; the historical RC3 test -checksums are not evidence for this pin. Follow the -[installation checklist](../../docs/rc6-install-qualification.md) before publishing. +The language-server version is pinned in the shared adapter source. Each release +binds that dependency, the extension source and compiled package bytes in its +qualification receipt. See the repository's +[qualification evidence](https://github.com/terraphim/zed-terraphim/blob/main/docs/qualification.md). ## Install and migrate -For local qualification, choose **Extensions → Install Dev Extension** and select -this `catalog/terraphim-lsp` directory. A catalog listing is available only after +For a precompiled GitHub release, verify its checksums and select the extracted +four-file directory in **Extensions → Install Dev Extension**. It contains the +released `extension.wasm` and no Cargo source; Zed loads those exact bytes. +Follow the [released-package instructions](https://github.com/terraphim/zed-terraphim/blob/main/docs/released-package-install.md). + +For source development, select this `catalog/terraphim-lsp` source directory +instead; Zed compiles a dev build. A catalog listing is available only after Zed maintainers accept the submission and the registry build completes. Remove/disable the legacy **Terraphim** extension before enabling **Terraphim LSP**: diff --git a/docs/marketplace.md b/docs/marketplace.md index fbcf16f..45c7959 100644 --- a/docs/marketplace.md +++ b/docs/marketplace.md @@ -4,7 +4,7 @@ This repository continues the existing stock-Zed adapter. Gitea remains the source of record; the GitHub mirror is the public build/catalog source. It does not depend on or modify a Zed fork. -## Catalog package and remaining publication gates (2026-10-13) +## Catalog package and publication gates `catalog/terraphim-lsp` is the separate **Terraphim LSP** catalog package. Its Cargo library points to the existing `src/lib.rs`; the provisioning, settings @@ -26,26 +26,17 @@ and saves its actual WASM bytes and hashes in `dist/catalog`. A build receipt explicitly records `nativeQualified: false`; a passing build cannot attest to manual Zed behavior or registry acceptance. -The live official registry had no Terraphim entry when checked on 13 October. -The RC4 crate was published, but its GitHub binary release failed in the two -musl build lanes. RC5 built all six binaries, but final release assembly rejected -x86_64 log names; no GitHub binary release was published. The RC6 release is -public, with all six binaries anonymously downloaded and verified on 14 October. -The first native default installation failed at the GitHub metadata API (403), -so direct pinned-asset provisioning must pass a new clean native run. RC4 and -RC5 remain immutable. -Publication still requires these actual dependencies: - -1. A publicly downloadable LSP release with authored-synonym export. The - prepared shared pin is `terraphim_lsp-v1.22.0-rc.6`; its release and checksum - readback must pass for all six supported assets before this pin is published. - The earlier RC3 GitHub lookup returned HTTP 404; its retained Gitea fixtures - are historical evidence only. Do not substitute local bytes or call internal - access public installation proof. The [RC6 checklist](rc6-install-qualification.md) - keeps dependency download, native installation and catalog acceptance separate. -2. Manual stock-Zed qualification at the exact source commit submitted, then - a human-authored catalog PR under the upstream AI policy. Upstream review - and registry publication remain distinct from source or release publication. +Source builds, native source qualification, GitHub packages and official catalog +installation are distinct checks. Before releasing the catalog package, verify +the exact pinned public language-server assets, complete the native checklist +at the tagged extension source, and retain the saved output files. After release, +install the downloaded compiled package and repeat those checks without a rebuild. +[Qualification](qualification.md) retains the dated candidate and released-binary +results, including discovered failures. [Released-package installation](released-package-install.md) +describes how the artifact bytes are preserved through native testing. + +An upstream catalog PR must be human-authored under Zed's policy. Acceptance and +registry publication remain separate from this repository's source/release CI. The scoped public source prerequisite is now satisfied. On 13 October, all 46 files at [the initial public commit](https://github.com/terraphim/zed-terraphim/tree/1fc332292ea41ae98ed55bd97b2daaa0ce48bcd5) @@ -68,26 +59,45 @@ the existing deterministic `package.py` packager. No publication token is available to this workflow. Existing Gitea workflows retain their own channel. `.github/workflows/release.yml` accepts an existing `vX.Y.Z` or `vX.Y.Z-rc.N` -tag matching `extension.toml`. A tag push publishes only after all checks; -manual dispatch defaults to verification without publication. It never creates -or moves tags. A release source must be on the public mirror's default branch. - -The public preflight compiles `examples/release-contract.rs`, which obtains the -pin and all six raw LSP candidates directly from the existing Rust -`provision::LSP` and `candidate_assets` types/functions. It anonymously downloads -`checksums.txt` and every named raw binary using ordinary GitHub URLs, verifies -SHA-256 and native executable format, and records the source/tag, URLs, sizes -and hashes. It does not execute foreign binaries or claim native-host UAT. -The internal Gitea source and PATH overrides cannot satisfy this gate. - -After preflight, the tested WASI module is packaged again for the exact tag. The -publication job consumes only this run's artifact, validates its receipt, -archive layout, tagged source-file bytes, tested WASM hash and checksums, and verifies the remote tag still names the -source. The post-build package-qualification.json is published alongside the archive and checksums. It creates a draft, uploads missing assets, compares existing assets -byte-for-byte, and only then publishes. Existing bytes are never overwritten. -Three fresh anonymous downloads must match before it reports -`PUBLISHED_PUBLIC_VERIFIED`. A partial public release or different existing -bytes fails closed and requires diagnosis rather than a clobber operation. +tag matching the catalog manifest. Tag pushes verify the catalog package but do +not publish. Manual dispatch defaults to verification. To publish, dispatch at +that exact tag ref with `publish=true`, the exact native-tested `qualified_commit` +and an immutable raw-GitHub `qualification_url` at a full 40-character commit +in this source repository. CI downloads that JSON, validates its catalog/source +identity, named native checks and saved-output hashes, and requires its recorded +server hash/size to match a verified asset from the pinned public LSP release. +Its source-install WASM hash remains distinct from the CI release module; see the +[receipt schema](qualification.md#native-source-receipt-schema). Catalog publication requires a stable +extension tag. The protected `zed-release` environment retains its tag-only policy; +no branch dispatch can authorize a different tag through an input. + +The public preflight executes `examples/release-contract.rs` with the catalog +feature set and obtains the actual `configured_lsp()` contract. It requires fixed +public checksum discovery, no private download and no PATH fallback, then +anonymously downloads and verifies every raw pinned LSP target. It records source, +tag, URLs, sizes and hashes. It does not execute foreign binaries or claim UAT. + +The workflow uses the catalog build from `catalog-ci.sh`. `catalog_package.py` +checks the shared source, manifest, locked dependencies, no-MCP policy and module +size, then reuses the deterministic packager to create `terraphim-lsp-.tar.gz`. +The archive contains the catalog LICENSE, README, manifest and exact compiled +`extension.wasm`; legacy root artifacts remain test outputs and are not published +by this workflow. The qualification receipt binds the catalog source path/ID, +tagged file bytes, tested module, archive and checksum manifest. + +The publish job consumes the verify job's immutable **artifact ID**. It validates +all bytes and the original native source receipt again and verifies the remote tag still resolves to the tested source. +It creates a draft, uploads only missing assets, compares existing assets byte for +byte, and publishes only after they match. No tag moves, rebuilds, clobber uploads +or repairs of incomplete public releases occur. A failed publish job can retry +against the original artifact and draft. Fresh anonymous downloads of the archive, +checksums and qualification receipt must match before publication is reported. + +Freeze generic documentation before native qualification. A normal merge can +preserve the tested PR head as an ancestor of main; tag that exact head. A later +docs-only commit may hold a sanitized UAT receipt while `qualified_commit` still +names the tested source. Finally save a separate receipt for installation of the +actual downloaded CI module; a source dev-build receipt cannot substitute for it. Configure the `zed-release` GitHub environment according to the project's release policy. Its job uses only the repository's `GITHUB_TOKEN` with @@ -154,11 +164,20 @@ bash scripts/ci.sh cargo run --locked --offline --example release-contract python3 scripts/marketplace.py --tag vX.Y.Z --repo OWNER/REPOSITORY --out /tmp/zed-public-preflight-new # Catalog preflight additionally requires exact native qualification evidence: -python3 scripts/marketplace.py --catalog --extension-path catalog/terraphim-lsp --tag vX.Y.Z --repo OWNER/REPOSITORY --qualified-commit FULL_SHA --qualification-url https://example.com/actual-evidence --out /tmp/zed-catalog-preflight-new +python3 scripts/marketplace.py --catalog --extension-path catalog/terraphim-lsp --tag vX.Y.Z --repo OWNER/REPOSITORY --qualified-commit FULL_SHA --qualification-url "https://raw.githubusercontent.com/OWNER/REPOSITORY/<40-character-evidence-commit>/docs/qualifications/native.json" --out /tmp/zed-catalog-preflight-new ``` -The last command requires a clean checkout at the existing tag and a fresh -output directory. It is intentionally expected to fail for the current -unpublished public RC6 dependency. A failed gate is a blocker, not successful -installation evidence. Actual release/catalog commands must be run only after -reviewing the exact tag and the successful receipt. +Replace OWNER/REPOSITORY with this public source repository, FULL_SHA with the +exact native-tested source, and the evidence-commit placeholder with the full +40-character Git commit containing the sanitized JSON receipt. A branch name, +mutable URL or unrelated repository is rejected. The last command requires a +clean checkout at the existing tag and a fresh output directory. + +The intended first catalog package tag is `v0.2.0`, matching the catalog manifest. +It must be created only after the final source checks and native qualification +pass. The repaired language-server dependency will be pinned separately to the +actual public `terraphim_lsp-v1.22.0-rc.7` release; a stable extension version does +not change that server's prerelease status. The existing RC6 pin stays unchanged +until RC7's public assets are verified. Actual release/catalog commands require +the reviewed exact tag and successful receipts; a failed gate is not installation +evidence. diff --git a/docs/qualification.md b/docs/qualification.md index 0f8d7ab..fdd2a05 100644 --- a/docs/qualification.md +++ b/docs/qualification.md @@ -109,11 +109,61 @@ rejected x86_64 log names. Its GitHub binary release was not published. The [RC5 checklist](rc5-install-qualification.md) and untouched isolated input fixture remain historical preparation, not proof of a default installation. -## Prospective public RC6 installation (14 October 2026) - -The prepared shared pin is `terraphim_lsp-v1.22.0-rc.6`. Its public assets, -checksums and native default-download results remain pending. The candidate -checks above retain their original identities. The next run follows the -[RC6 isolated installation checklist](rc6-install-qualification.md), using the -catalog package without a binary override and inspecting the actual provisioned -path. A successful local build alone cannot qualify release installation. +## Released RC6 default-provisioning check (14 October 2026) + +Source `140e25b573865efa372418ac5475771a12c4785d` installed the catalog adapter in +an empty stock-Zed 1.23.3 profile and downloaded RC6 through its pinned public +asset URLs, with no binary override or PATH fallback. The actual universal macOS +server SHA-256 was `3b07976ffde477cbbe2705b078d64188fa2dd27e9a93a6b4052e8f152a4510f3`. +A restart reused the verified cache. Native authoring, swap, saved undo/redo, +reopen, two graph exports, Trim, clean export and released-grep retrieval produced +saved outputs. This was a Zed dev build of the catalog source, not installation +of a GitHub-published extension archive. + +The dirty-JSON regression **failed**: graph export overwrote an unsaved interior +change in an open JSON destination. Native Undo recovered the test edit. A dirty +Markdown clean-export destination was correctly refused; that does not qualify +JSON protection. The earlier append-only JSON test also did not prove refusal. +The discovered JSON failure must be repaired and verified with a new released +server and exact catalog source before release admission. + +The earlier [RC6 checklist](rc6-install-qualification.md) preserves the original +prepared steps. Its earlier metadata-API HTTP 403 profile remains separate from +the successful direct-download profile. RC4/RC5 source and release history above +remain unchanged. + +## Compiled release-package qualification + +Follow [released-package installation](released-package-install.md) in a second +empty profile. Record the anonymous download checksum, exact archive files and +loaded WASM hash, with no Cargo source or compiler invocation. Then perform the +native checks above and save their actual outputs. Keep this receipt separate +from the source dev-build receipt; identify which bytes each run exercised. +Official Zed catalog installation still requires upstream acceptance and an +actual registry install. None of these bounded checks certifies all 41 browser +requirements for stock Zed. + +## Native source receipt schema + +The manually reviewed source-install receipt is JSON at an immutable raw GitHub +URL in this repository: `https://raw.githubusercontent.com/terraphim/zed-terraphim/<40-character-evidence-commit>/docs/qualifications/.json`. +The evidence commit may follow the tested source, provided `qualifiedCommit` +remains the exact tested/tagged source. Publish only sanitized public fixtures; +keep local absolute paths, account details and private original receipts outside +this repository. + +The schema requires: + +- `schema: 1`, `status: "PASS"`, `scope: "stock-zed-source"` and exact `qualifiedCommit`. +- `extensionId: "terraphim-lsp"`, `extensionPath: "catalog/terraphim-lsp"` and the catalog `version`. +- `sourceInstallWasm` containing the actual native dev-install module's SHA-256 and byte count. +- `server` containing `releaseTag`, `sha256` and `bytes`, matching an anonymously verified asset of that pinned public release. +- `checks` with PASS for `defaultProvisioning`, `cacheRestart`, `createApplySaveUndoRedoReopen`, `synonymGraphExportTwice`, `dirtyJsonRefusal`, `trimDoneAnchorShift`, `cleanExportDirtyRefusal` and `releasedGrepSharedSynonym`. +- Nonempty `savedOutputs`, each with a unique public filename `name`, actual `sha256` and `bytes`. Inputs must remain identified separately in the human-reviewed evidence. + +CI checks these declarations and binds the downloaded receipt's exact bytes to +preflight/package qualification. It does not rerun native UI interactions or +substitute the receipt's dev-module hash for the different CI module. Publication +uses the saved receipt from the original verify-job artifact. After publication, +a separate actual downloaded-package installation receipt identifies the CI WASM +loaded in stock Zed and its resulting saved outputs. diff --git a/docs/released-package-install.md b/docs/released-package-install.md new file mode 100644 index 0000000..0346d31 --- /dev/null +++ b/docs/released-package-install.md @@ -0,0 +1,40 @@ +# Install the compiled Terraphim LSP package + +Use a release from [terraphim/zed-terraphim](https://github.com/terraphim/zed-terraphim/releases) +that includes all three files: + +- `terraphim-lsp-.tar.gz` +- `SHA256SUMS` +- `package-qualification.json` + +Check that the receipt names `terraphim-lsp`, source path +`catalog/terraphim-lsp`, the intended tag and archive checksum. Verify the archive +against `SHA256SUMS` before extracting it into a new empty directory. The extracted +directory must contain exactly LICENSE, README.md, extension.toml and extension.wasm. +The manifest ID must be `terraphim-lsp`; compare all four file hashes against the +receipt. Do not replace files with a local build or add Cargo.toml/Rust source. + +In stock Zed, open **Extensions → Install Dev Extension** and choose that extracted +directory. Although the UI calls it a dev extension, this directory contains the +released module and no source to compile. Zed loads the existing extension.wasm. +The source-development route instead selects `catalog/terraphim-lsp` in a Git +checkout and compiles a different dev module; its hashes may differ. + +Disable/remove the combined legacy Terraphim adapter before enabling this package +because both register the same LSP. The language server is downloaded from the +pinned public GitHub release, verified against its checksum, and reused on +restart. Keep `lsp.terraphim-lsp.binary.path` unset to verify default provisioning. +The catalog package has no PATH fallback and no private-network download. + +For release qualification, use a fresh isolated Zed profile and disposable project. +Record the archive/module checksums before installation and again from the actual +installed path afterward. Confirm the module was not rebuilt, record the provisioned +server/version/checksum marker, and save the create/swap/undo/redo/reopen/export, +dirty-destination refusal, Trim and grep outputs described in +[qualification](qualification.md). A downloaded input is not evidence of successful +execution; the receipt must include the actual resulting files. + +This is manual installation of a released package. An official catalog install is +available only after Zed's upstream review and registry build; it is a separate +acceptance check. The [publication instructions](marketplace.md) describe the +CI and human-authored catalog submission gates. diff --git a/examples/release-contract.rs b/examples/release-contract.rs index 3162ef6..f7c4148 100644 --- a/examples/release-contract.rs +++ b/examples/release-contract.rs @@ -1,8 +1,11 @@ //! Emit the actual provisioner's pinned public LSP download contract for CI. use std::collections::BTreeSet; -use zed_terraphim::provision::{candidate_assets, Arch, Os, Platform, LSP}; +use zed_terraphim::provision::{ + candidate_assets, configured_lsp, Arch, GithubDiscovery, Os, Platform, +}; fn main() -> Result<(), Box> { + let spec = configured_lsp(); let mut assets = BTreeSet::new(); for (os, arch) in [ (Os::Mac, Arch::Aarch64), @@ -10,15 +13,22 @@ fn main() -> Result<(), Box> { (Os::Linux, Arch::Aarch64), (Os::Linux, Arch::X8664), ] { - assets.extend(candidate_assets(&LSP, Platform { os, arch })?); + assets.extend(candidate_assets(&spec, Platform { os, arch })?); } println!( "{}", serde_json::json!({ - "repo": LSP.repo, - "tag": LSP.tag, - "checksum_file": LSP.checksum_file, + "repo": spec.repo, + "tag": spec.tag, + "checksum_file": spec.checksum_file, "assets": assets, + "public_catalog": cfg!(feature = "public-catalog"), + "gitea": spec.gitea, + "path_fallback": spec.path_fallback, + "github_discovery": match spec.github_discovery { + GithubDiscovery::ApiListing => "api_listing", + GithubDiscovery::PinnedChecksums => "pinned_checksums", + }, }) ); Ok(()) diff --git a/scripts/catalog_package.py b/scripts/catalog_package.py index 64862ac..d81ad1b 100644 --- a/scripts/catalog_package.py +++ b/scripts/catalog_package.py @@ -2,17 +2,19 @@ """Check and stage the exact compiled LSP-only package for native qualification.""" import argparse +import copy import hashlib import json from pathlib import Path import shutil import subprocess +import tempfile import tomllib import marketplace import package -PATH = "catalog/terraphim-lsp" +PATH = package.CATALOG_PATH def validate_source(root): @@ -33,8 +35,15 @@ def validate_source(root): ) if legacy["id"] != "terraphim" or not legacy.get("context_servers"): raise marketplace.GateError("Preserve the existing combined adapter identity") - if manifest["language_servers"] != legacy["language_servers"]: - raise marketplace.GateError("The two packages must register the same LSP") + servers = manifest["language_servers"] + expected = copy.deepcopy(legacy["language_servers"]) + if servers != expected: + # JSON buffers only protect exported destinations. Preserve the legacy + # registration; never enable other servers/features to satisfy this gate. + expected["terraphim-lsp"]["languages"] = ["Markdown", "JSON", "JSONC"] + expected["terraphim-lsp"]["language_ids"] = {"JSON": "json", "JSONC": "jsonc"} + if servers != expected: + raise marketplace.GateError("Catalog may add only passive JSON/JSONC registration") if cargo["lib"]["path"] != "../../src/lib.rs": raise marketplace.GateError( "The catalog must compile the existing adapter source" @@ -97,12 +106,29 @@ def stage(root, wasm, output): return receipt +def release(root, wasm, output, tag): + """Archive the size/source-checked catalog bytes with the existing packager.""" + # Stage privately so another build cannot replace the module after validation. + with tempfile.TemporaryDirectory(prefix="zed-catalog-release-") as folder: + checked = Path(folder) + receipt = stage(root, wasm, checked) + result = package.package(root, checked / "extension.wasm", output, tag, + extension_path=PATH) + (output / "catalog-build-receipt.json").write_text(json.dumps(receipt, indent=2) + "\n") + return result + + def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--wasm", type=Path, required=True) parser.add_argument("--out", type=Path, default=package.ROOT / "dist/catalog") + parser.add_argument("--tag", help="Create a catalog release archive for this immutable tag") args = parser.parse_args() - print(json.dumps(stage(package.ROOT, args.wasm, args.out), indent=2)) + if args.tag: + result = release(package.ROOT, args.wasm, args.out, args.tag) + print(result.tarball) + else: + print(json.dumps(stage(package.ROOT, args.wasm, args.out), indent=2)) if __name__ == "__main__": diff --git a/scripts/ci.sh b/scripts/ci.sh index 2f14a5a..9e82b12 100644 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -10,7 +10,8 @@ cargo clippy --locked --offline --target wasm32-wasip2 --all-features -- -D warn cargo test --locked --offline --all-targets --all-features cargo build --locked --offline --release --target wasm32-wasip2 sh scripts/guards.sh -python3 -m unittest discover -s scripts -p 'test_*.py' python3 scripts/package.py --wasm "${CARGO_TARGET_DIR:-target}/wasm32-wasip2/release/zed_terraphim.wasm" bash scripts/catalog-ci.sh + +python3 -m unittest discover -s scripts -p 'test_*.py' diff --git a/scripts/marketplace.py b/scripts/marketplace.py index 9b53d61..95847e3 100644 --- a/scripts/marketplace.py +++ b/scripts/marketplace.py @@ -192,6 +192,102 @@ def validate_receipt(receipt, source, tag, catalog=False): raise GateError("Receipt does not authorize catalog submission") +NATIVE_CHECKS = ( + "defaultProvisioning", "cacheRestart", "createApplySaveUndoRedoReopen", + "synonymGraphExportTwice", "dirtyJsonRefusal", "trimDoneAnchorShift", + "cleanExportDirtyRefusal", "releasedGrepSharedSynonym", +) + + +def immutable_qualification_url(url, repo): + """Accept only a raw JSON file at a full commit in the public source repo.""" + if not REPO_RE.fullmatch(repo) or not re.fullmatch( + rf"https://raw\.githubusercontent\.com/{re.escape(repo)}/[0-9a-f]{{40}}/" + r"(?:[A-Za-z0-9_-]+/)*[A-Za-z0-9_.-]+\.json", url + ): + raise GateError("Native qualification must be immutable raw JSON in the source repository") + return url + + +def validate_native_qualification(data, source, manifest, contract, assets): + """Validate declared source-UAT evidence; never equate it to CI package WASM.""" + if not 0 < len(data) <= 1024 * 1024: + raise GateError("Native qualification receipt exceeds the size limit") + value = json.loads(data) + if ( + not isinstance(value, dict) or type(value.get("schema")) is not int + or value.get("schema") != 1 or value.get("status") != "PASS" + or value.get("scope") != "stock-zed-source" + or value.get("qualifiedCommit") != source + or value.get("extensionId") != "terraphim-lsp" + or value.get("extensionPath") != package.CATALOG_PATH + or value.get("version") != manifest["version"] + ): + raise GateError("Native source qualification identity or scope differs") + + def binary_record(record, maximum): + return (isinstance(record, dict) + and isinstance(record.get("sha256"), str) + and re.fullmatch(r"[0-9a-f]{64}", record["sha256"]) + and type(record.get("bytes")) is int + and 0 < record["bytes"] <= maximum) + + if not binary_record(value.get("sourceInstallWasm"), 5 * 1024 * 1024): + raise GateError("Native source-install WASM evidence is missing or invalid") + server = value.get("server") + if (not binary_record(server, 128 * 1024 * 1024) + or server.get("releaseTag") != contract["tag"] + or not any(server["sha256"] == asset["sha256"] + and server["bytes"] == asset["bytes"] for asset in assets)): + raise GateError("Native server differs from the verified public LSP release") + checks = value.get("checks") + if (not isinstance(checks, dict) or any(checks.get(name) != "PASS" for name in NATIVE_CHECKS) + or any(result != "PASS" for result in checks.values())): + raise GateError("Required native saved-output checks have not passed") + outputs = value.get("savedOutputs") + if (not isinstance(outputs, list) or not 1 <= len(outputs) <= 100 + or any(not binary_record(item, 32 * 1024 * 1024) + or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_.-]*", item.get("name", "")) + for item in outputs) + or len({item["name"] for item in outputs}) != len(outputs)): + raise GateError("Native saved-output identities are missing or invalid") + return value + + +def native_evidence_record(data, url, repo, source, manifest, contract, assets): + """Bind immutable receipt bytes and validated declarations to the preflight.""" + immutable_qualification_url(url, repo) + value = validate_native_qualification(data, source, manifest, contract, assets) + return {"url": url, "sha256": hashlib.sha256(data).hexdigest(), "bytes": len(data), + "scope": value["scope"], "qualifiedCommit": value["qualifiedCommit"], + "sourceInstallWasm": value["sourceInstallWasm"], "server": value["server"]} + + +def release_admission(event, ref, tag, publish): + """Publishing requires manual dispatch at the exact immutable tag ref.""" + if publish and (event != "workflow_dispatch" or ref != f"refs/tags/{tag}"): + raise GateError("Publish requires manual dispatch at the exact release tag") + return publish + + +def release_contract(root, extension_path): + """Run the actual shared Rust contract in the selected package feature mode.""" + package.identity(root, extension_path) + command = ["cargo", "run", "--locked", "--offline", "--quiet"] + if extension_path == package.CATALOG_PATH: + command += ["--no-default-features", "--features", "public-catalog"] + command += ["--example", "release-contract"] + contract = json.loads(subprocess.check_output(command, cwd=root, text=True)) + if extension_path == package.CATALOG_PATH and ( + contract.get("public_catalog") is not True + or contract.get("gitea") is not None + or contract.get("path_fallback") is not False + or contract.get("github_discovery") != "pinned_checksums" + ): + raise GateError("Catalog contract must use only pinned public assets") + return contract + + def preflight(args): if not REPO_RE.fullmatch(args.repo): raise GateError("--repo must be owner/name") @@ -212,6 +308,7 @@ def preflight(args): if args.catalog: catalog_manifest(manifest, args.tag) qualification(source, args.qualified_commit, args.qualification_url) + immutable_qualification_url(args.qualification_url, args.repo) if args.out.exists() or args.out.is_symlink(): raise GateError("Use a new output directory for each public preflight") args.out.mkdir(parents=True) @@ -228,22 +325,7 @@ def preflight(args): ) ) source_ancestry(repo, comparison, source) - contract = json.loads( - subprocess.check_output( - [ - "cargo", - "run", - "--locked", - "--offline", - "--quiet", - "--example", - "release-contract", - ], - cwd=ROOT, - text=True, - ) - ) - # These values come from compiled provision::LSP/candidate_assets, not a second pin. + contract = release_contract(ROOT, args.extension_path) if not REPO_RE.fullmatch(contract["repo"]) or not re.fullmatch( r"[A-Za-z0-9_.-]+", contract["tag"] ): @@ -275,6 +357,11 @@ def preflight(args): record = verify_binary(args.out / name, expected[name]) record["url"] = url assets.append(record) + native_evidence = None + if args.catalog: + native_bytes = anonymous_download(args.qualification_url, args.out / "native-qualification.json") + native_evidence = native_evidence_record(native_bytes, args.qualification_url, + args.repo, source, manifest, contract, assets) receipt = { "status": "PASS", "checkedAt": datetime.datetime.now(datetime.timezone.utc).isoformat(), @@ -286,6 +373,7 @@ def preflight(args): "extensionPath": args.extension_path, "catalog": args.catalog, "qualificationUrl": args.qualification_url if args.catalog else None, + "nativeQualification": native_evidence, "publicLsp": contract, "assets": assets, } @@ -300,10 +388,16 @@ def main(): parser.add_argument("--out", type=Path, required=True) parser.add_argument("--catalog", action="store_true") parser.add_argument("--extension-path", default="") + parser.add_argument("--release-event", choices=["push", "workflow_dispatch"]) + parser.add_argument("--release-ref", default="") + parser.add_argument("--release-publish", choices=["true", "false"], default="false") parser.add_argument("--qualified-commit", default="") parser.add_argument("--qualification-url", default="") args = parser.parse_args() try: + if args.release_event is not None: + args.catalog = release_admission(args.release_event, args.release_ref, + args.tag, args.release_publish == "true") result = preflight(args) print( json.dumps( diff --git a/scripts/package.py b/scripts/package.py index d07178d..6c30b2f 100644 --- a/scripts/package.py +++ b/scripts/package.py @@ -30,10 +30,29 @@ import subprocess import sys import tarfile +import tomllib from typing import NamedTuple, Optional ROOT = pathlib.Path(__file__).resolve().parent.parent FILES = ["LICENSE", "README.md", "extension.toml"] +CATALOG_PATH = "catalog/terraphim-lsp" +PACKAGE_IDENTITIES = {"": ("terraphim", "terraphim-zed"), + CATALOG_PATH: ("terraphim-lsp", "terraphim-lsp")} + + +def identity(root: pathlib.Path, extension_path: str = ""): + """Return the fixed package identity, refusing path or manifest substitution.""" + if extension_path not in PACKAGE_IDENTITIES: + raise PackageError("Unsupported extension path") + selected = root / extension_path + if not selected.resolve().is_relative_to(root.resolve()): + raise PackageError("Extension path escapes source checkout") + expected_id, prefix = PACKAGE_IDENTITIES[extension_path] + manifest = tomllib.loads((selected / "extension.toml").read_text()) + if manifest.get("id") != expected_id: + raise PackageError("Extension identity differs from selected source path") + return selected, manifest, prefix + # Fixed timestamp for every archive entry; SOURCE_DATE_EPOCH overrides it. FIXED_MTIME = int(os.environ.get("SOURCE_DATE_EPOCH", "0")) TAG_RE = re.compile(r"v(\d+\.\d+\.\d+)(-rc\.\d+)?") @@ -51,7 +70,7 @@ class Result(NamedTuple): def read_version(text: str, table: Optional[str] = None) -> str: """Return `version = "..."` from the top level of a TOML file, or from - `[table]` when given. Avoids tomllib so Python 3.8+ is enough.""" + `[table]` when given. Retained for existing version-check callers.""" current = None for line in text.splitlines(): stripped = line.strip() @@ -124,24 +143,26 @@ def package( wasm: pathlib.Path, out_dir: pathlib.Path, tag: Optional[str] = None, + extension_path: str = "", ) -> Result: - version = read_version((root / "extension.toml").read_text()) + selected, manifest, prefix = identity(root, extension_path) + version = manifest["version"] prerelease = check_tag(tag, version) if tag else False data = wasm.read_bytes() if not data.startswith(b"\x00asm"): raise PackageError(f"{wasm} is not a WebAssembly module") - name = f"terraphim-zed-{version}" + name = f"{prefix}-{version}" staged = out_dir / name if staged.exists(): shutil.rmtree(staged) staged.mkdir(parents=True) for file in FILES: - shutil.copyfile(root / file, staged / file) + shutil.copyfile(selected / file, staged / file) (staged / "extension.wasm").write_bytes(data) release = tag[1:] if tag else version - tarball = out_dir / f"terraphim-zed-{release}.tar.gz" + tarball = out_dir / f"{prefix}-{release}.tar.gz" write_tarball(staged, tarball) digest = hashlib.sha256(tarball.read_bytes()).hexdigest() sums = out_dir / "SHA256SUMS" diff --git a/scripts/package_qualification.py b/scripts/package_qualification.py index e3fb554..9bc46f2 100644 --- a/scripts/package_qualification.py +++ b/scripts/package_qualification.py @@ -12,7 +12,6 @@ import subprocess import sys import tarfile -import tomllib import marketplace import package @@ -25,10 +24,10 @@ def record(data): return {"sha256": hashlib.sha256(data).hexdigest(), "bytes": len(data)} -def source_state(root, tag): +def source_state(root, tag, extension_path=""): """Require a clean tracked checkout at the existing tag and read its files.""" - files = {name: (root / name).read_bytes() for name in package.FILES} - manifest = tomllib.loads(files["extension.toml"].decode("utf-8")) + selected, manifest, _ = package.identity(root, extension_path) + files = {name: (selected / name).read_bytes() for name in package.FILES} package.check_tag(tag, manifest["version"]) def git(*args): @@ -41,14 +40,16 @@ def git(*args): if git("status", "--porcelain", "--untracked-files=no").strip(): raise marketplace.GateError("Package qualification requires clean tracked source") for name, data in files.items(): - if data != git("show", f"{source}:{name}"): + if data != git("show", f"{source}:{(Path(extension_path) / name).as_posix()}"): raise marketplace.GateError(f"Source file differs from tagged bytes: {name}") return source, manifest, files -def inspect_archive(data, version): +def inspect_archive(data, version, extension_path=""): """Read the four exact regular files, rejecting aliases, links and extras.""" - prefix = f"terraphim-zed-{version}" + if extension_path not in package.PACKAGE_IDENTITIES: + raise package.PackageError("Unsupported extension path") + prefix = f"{package.PACKAGE_IDENTITIES[extension_path][1]}-{version}" file_names = package.FILES + ["extension.wasm"] expected_names = {prefix} | {f"{prefix}/{name}" for name in file_names} with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as archive: @@ -73,22 +74,30 @@ def inspect_archive(data, version): return files -def capture(root, artifacts, tag, expected_source=None): +def capture(root, artifacts, tag, expected_source=None, extension_path=""): """Inspect actual bytes and return the complete deterministic qualification.""" - source, manifest, source_files = source_state(root, tag) + source, manifest, source_files = source_state(root, tag, extension_path) if expected_source is not None and source != expected_source: raise marketplace.GateError("Checked-out source differs from publication source") preflight_bytes = (artifacts / "public-preflight/preflight.json").read_bytes() preflight = json.loads(preflight_bytes) marketplace.validate_receipt(preflight, source, tag) if ( - preflight.get("version") != manifest["version"] + preflight.get("extensionPath", "") != extension_path + or preflight.get("version") != manifest["version"] or preflight.get("extensionId") != manifest["id"] or not marketplace.REPO_RE.fullmatch(preflight.get("repository", "")) ): raise marketplace.GateError("Preflight identity differs from tagged manifest") + if extension_path and preflight.get("catalog"): + native_data = (artifacts / "public-preflight/native-qualification.json").read_bytes() + native_record = marketplace.native_evidence_record(native_data, + preflight["qualificationUrl"], preflight["repository"], source, + manifest, preflight["publicLsp"], preflight["assets"]) + if native_record != preflight.get("nativeQualification"): + raise marketplace.GateError("Native source receipt differs from the checked preflight") folder = artifacts / "release-dist" - archive = folder / f"terraphim-zed-{tag[1:]}.tar.gz" + archive = folder / f"{package.PACKAGE_IDENTITIES[extension_path][1]}-{tag[1:]}.tar.gz" archive_bytes = archive.read_bytes() sums = folder / "SHA256SUMS" sums_bytes = sums.read_bytes() @@ -96,7 +105,7 @@ def capture(root, artifacts, tag, expected_source=None): archive.name: record(archive_bytes)["sha256"] }: raise marketplace.GateError("Release package does not match its checksum manifest") - members = inspect_archive(archive_bytes, manifest["version"]) + members = inspect_archive(archive_bytes, manifest["version"], extension_path) for name, data in source_files.items(): if members[name] != data: raise marketplace.GateError(f"Packaged source differs from tagged bytes: {name}") @@ -110,6 +119,7 @@ def capture(root, artifacts, tag, expected_source=None): "repository": preflight["repository"], "version": manifest["version"], "extensionId": manifest["id"], + "extensionPath": extension_path, "publicPreflight": record(preflight_bytes), "archive": {"name": archive.name, **record(archive_bytes)}, "checksums": {"name": sums.name, **record(sums_bytes)}, @@ -118,9 +128,9 @@ def capture(root, artifacts, tag, expected_source=None): return preflight, report, members, [archive, sums, folder / RECEIPT] -def qualify(root, artifacts, tag, wasm): +def qualify(root, artifacts, tag, wasm, extension_path=""): """Issue a new receipt only when the archive contains the actual CI module.""" - _, report, members, files = capture(root, artifacts, tag) + _, report, members, files = capture(root, artifacts, tag, extension_path=extension_path) if members["extension.wasm"] != wasm.read_bytes(): raise marketplace.GateError("Packaged WASM differs from the tested CI module") # Never replace prior qualification evidence, including an interrupted run. @@ -129,9 +139,10 @@ def qualify(root, artifacts, tag, wasm): return report -def verify(root, artifacts, tag, source): +def verify(root, artifacts, tag, source, extension_path=""): """Require the CI receipt to match all independently re-read source/bytes.""" - preflight, actual, _, files = capture(root, artifacts, tag, source) + preflight, actual, _, files = capture(root, artifacts, tag, source, extension_path) + marketplace.validate_receipt(preflight, source, tag, catalog=bool(extension_path)) qualified = json.loads(files[-1].read_bytes()) if qualified != actual: raise marketplace.GateError("Package bytes or source differ from CI qualification") @@ -141,11 +152,12 @@ def verify(root, artifacts, tag, source): def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--tag", required=True) + parser.add_argument("--extension-path", choices=package.PACKAGE_IDENTITIES, default="") parser.add_argument("--artifacts", type=Path, default=Path(".")) parser.add_argument("--wasm", type=Path, required=True) args = parser.parse_args() try: - report = qualify(package.ROOT, args.artifacts, args.tag, args.wasm) + report = qualify(package.ROOT, args.artifacts, args.tag, args.wasm, args.extension_path) print(json.dumps(report)) except ( marketplace.GateError, package.PackageError, KeyError, ValueError, diff --git a/scripts/publish_github.py b/scripts/publish_github.py index e918b04..bfacfb0 100644 --- a/scripts/publish_github.py +++ b/scripts/publish_github.py @@ -34,27 +34,31 @@ def release(repo, tag): return found[0] if found else None -def verify_package(artifacts, tag, source, root=package.ROOT): - return package_qualification.verify(root, artifacts, tag, source) +def verify_package(artifacts, tag, source, root=package.ROOT, extension_path=""): + return package_qualification.verify(root, artifacts, tag, source, extension_path) @contextmanager -def qualified_snapshot(artifacts, tag, source, root=package.ROOT): +def qualified_snapshot(artifacts, tag, source, root=package.ROOT, extension_path=""): """Validate a private snapshot and use those same bytes through publication.""" - package.check_tag(tag, package.read_version((root / "extension.toml").read_text())) + _, manifest, prefix = package.identity(root, extension_path) + package.check_tag(tag, manifest["version"]) with tempfile.TemporaryDirectory(prefix="zed-qualified-") as tmp: snapshot = Path(tmp) - for relative in [ + paths = [ "public-preflight/preflight.json", - f"release-dist/terraphim-zed-{tag[1:]}.tar.gz", + f"release-dist/{prefix}-{tag[1:]}.tar.gz", "release-dist/SHA256SUMS", f"release-dist/{package_qualification.RECEIPT}", - ]: + ] + if extension_path: + paths.append("public-preflight/native-qualification.json") + for relative in paths: destination = snapshot / relative destination.parent.mkdir(parents=True, exist_ok=True) with destination.open("xb") as output: output.write((artifacts / relative).read_bytes()) - yield verify_package(snapshot, tag, source, root) + yield verify_package(snapshot, tag, source, root, extension_path) def verify_remote_tag(repo, tag, source): @@ -80,6 +84,7 @@ def fresh_readback(artifacts): def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--tag", required=True) + parser.add_argument("--extension-path", choices=package.PACKAGE_IDENTITIES, default="") parser.add_argument("--repo", required=True) parser.add_argument("--artifacts", type=Path, required=True) args = parser.parse_args() @@ -89,7 +94,7 @@ def main(): source = subprocess.check_output( ["git", "rev-parse", "HEAD"], cwd=package.ROOT, text=True ).strip() - with qualified_snapshot(args.artifacts, args.tag, source) as (receipt, files): + with qualified_snapshot(args.artifacts, args.tag, source, extension_path=args.extension_path) as (receipt, files): if receipt["repository"] != args.repo: raise marketplace.GateError("Preflight belongs to another repository") verify_remote_tag(args.repo, args.tag, source) @@ -98,7 +103,7 @@ def main(): if current is None: notes = args.artifacts / "release-notes.md" notes.write_text( - f"Zed extension {args.tag}; source `{source}`.\n\nPackage SHA-256 is in SHA256SUMS; package-qualification.json binds its files and WASM to this source and CI preflight. Public pinned LSP downloads passed the release gate. This package is not a claim of Zed catalog acceptance. See docs/marketplace.md for catalog prerequisites.\n" + f"Zed extension {receipt['extensionId']} {args.tag}; source `{source}`.\n\nPackage SHA-256 is in SHA256SUMS; package-qualification.json binds its files and WASM to this source and CI preflight. Public pinned LSP downloads passed the release gate. This package is not a claim of Zed catalog acceptance. See docs/marketplace.md for catalog prerequisites.\n" ) command = [ "release", @@ -165,6 +170,8 @@ def main(): "status": "PUBLISHED_PUBLIC_VERIFIED", "tag": args.tag, "source": source, + "extensionId": receipt["extensionId"], + "extensionPath": args.extension_path, "assets": [ { "name": path.name, diff --git a/scripts/test_catalog_package.py b/scripts/test_catalog_package.py index e6ffc59..3ad64e3 100644 --- a/scripts/test_catalog_package.py +++ b/scripts/test_catalog_package.py @@ -56,6 +56,22 @@ def test_subdirectory_rejects_traversal_and_absolute_paths(self): with self.subTest(value=value), self.assertRaises(marketplace.GateError): catalog.registry_entry("", "terraphim-lsp", "0.2.0", value) + def test_catalog_can_add_only_passive_json_without_changing_legacy(self): + root = self.fixture() + legacy = (root / "extension.toml").read_bytes() + manifest = root / catalog_package.PATH / "extension.toml" + manifest.write_text(manifest.read_text().replace( + 'languages = ["Markdown"]', + 'languages = ["Markdown", "JSON", "JSONC"]\nlanguage_ids = { JSON = "json", JSONC = "jsonc" }')) + catalog_package.validate_source(root) + self.assertEqual((root / "extension.toml").read_bytes(), legacy) + text = manifest.read_text() + for changed in [text.replace('JSONC = "jsonc"', 'JSONC = "markdown"'), + text.replace('["Markdown", "JSON", "JSONC"]', '["Markdown", "JSON", "Python"]')]: + manifest.write_text(changed) + with self.assertRaisesRegex(marketplace.GateError, "only passive"): + catalog_package.validate_source(root) + def test_catalog_rejects_retargeting_an_existing_manifest_directory(self): text = '[terraphim-lsp]\nsubmodule = "extensions/terraphim-lsp"\npath = "other"\nversion = "0.2.0"\n' with self.assertRaisesRegex( diff --git a/scripts/test_catalog_release.py b/scripts/test_catalog_release.py new file mode 100644 index 0000000..67bc820 --- /dev/null +++ b/scripts/test_catalog_release.py @@ -0,0 +1,208 @@ +"""Cross-variant regressions using actual files, Git and archive processing. + +Minimal modules are archive parser inputs; these tests never attest to native +execution or network publication. +""" +import json +import unittest + +import catalog_package +import marketplace +import package +import package_qualification +import publish_github +from test_package_qualification import Fixture + +CATALOG = "catalog/terraphim-lsp" + + +class CatalogRelease(unittest.TestCase): + def test_catalog_archive_is_distinct_and_bound_to_nested_source(self): + fixture = Fixture(self, extension_path=CATALOG) + report = fixture.qualify() + self.assertEqual(report["extensionId"], "terraphim-lsp") + self.assertEqual(report["extensionPath"], CATALOG) + self.assertEqual(report["archive"]["name"], "terraphim-lsp-0.2.0.tar.gz") + fixture.verify() + second = package.package(fixture.root, fixture.wasm, fixture.base / "again", + fixture.tag, extension_path=CATALOG) + self.assertEqual(second.tarball.read_bytes(), fixture.packaged.tarball.read_bytes()) + members = package_qualification.inspect_archive( + second.tarball.read_bytes(), "0.2.0", extension_path=CATALOG) + for name in package.FILES: + self.assertEqual(members[name], (fixture.root / CATALOG / name).read_bytes()) + self.assertNotIn("build-receipt.json", members) + + def test_actual_compiled_catalog_wasm_cannot_be_replaced_with_legacy(self): + fixture = Fixture(self, extension_path=CATALOG) + catalog_wasm = package.ROOT / CATALOG / "target/wasm32-wasip2/release/zed_terraphim_lsp.wasm" + legacy_wasm = package.ROOT / "target/wasm32-wasip2/release/zed_terraphim.wasm" + self.assertNotEqual(catalog_wasm.read_bytes(), legacy_wasm.read_bytes()) + fixture.wasm.write_bytes(catalog_wasm.read_bytes()) + fixture.packaged = package.package(fixture.root, fixture.wasm, + fixture.artifacts / "release-dist", fixture.tag, extension_path=CATALOG) + report = fixture.qualify() + self.assertGreater(report["entries"]["extension.wasm"]["bytes"], 100_000) + fixture.verify() + fixture.receipt.unlink() + with self.assertRaisesRegex(marketplace.GateError, "tested CI module"): + package_qualification.qualify(fixture.root, fixture.artifacts, fixture.tag, + legacy_wasm, extension_path=CATALOG) + + def test_catalog_stage_archives_actual_checked_module_and_keeps_receipt_separate(self): + fixture = Fixture(self, extension_path=CATALOG) + wasm = package.ROOT / CATALOG / "target/wasm32-wasip2/release/zed_terraphim_lsp.wasm" + output = fixture.base / "catalog-stage" + result = catalog_package.release(package.ROOT, wasm, output, "v0.2.0") + staged = json.loads((output / "catalog-build-receipt.json").read_bytes()) + self.assertEqual(staged["extensionId"], "terraphim-lsp") + self.assertIs(staged["nativeQualified"], False) + members = package_qualification.inspect_archive(result.tarball.read_bytes(), + "0.2.0", CATALOG) + self.assertEqual(members["extension.wasm"], wasm.read_bytes()) + self.assertEqual(set(members), {*package.FILES, "extension.wasm"}) + self.assertEqual(staged["files"]["extension.wasm"], + package_qualification.record(wasm.read_bytes())["sha256"]) + + def test_release_workflow_routes_only_catalog_bytes_and_original_artifact(self): + workflow = (package.ROOT / ".github/workflows/release.yml").read_text() + verify, publish = workflow.split("\n publish:\n", 1) + for program in ["catalog_package.py", "package_qualification.py", "marketplace.py"]: + self.assertIn(f"scripts/{program}", verify) + self.assertNotIn("scripts/package.py", verify) + self.assertNotIn("/release/zed_terraphim.wasm", verify) + self.assertIn("--extension-path catalog/terraphim-lsp", verify) + self.assertIn("--release-publish \"$PUBLISH_REQUESTED\"", verify) + self.assertIn("--qualified-commit \"$QUALIFIED_COMMIT\"", verify) + self.assertIn("--qualification-url \"$QUALIFICATION_URL\"", verify) + self.assertIn("artifact-id: ${{ steps.package-upload.outputs.artifact-id }}", verify) + self.assertIn("if: github.event_name == 'workflow_dispatch' && inputs.publish", publish) + self.assertIn("environment: zed-release", publish) + self.assertIn("artifact-ids: ${{ needs.verify.outputs.artifact-id }}", publish) + self.assertIn("--extension-path catalog/terraphim-lsp", publish) + self.assertNotIn("contents: write", verify) + self.assertEqual(publish.count("contents: write"), 1) + self.assertNotRegex(publish, r"(?m)^\s+run:.*(?:cargo|package_qualification|catalog_package)") + + def test_legacy_preflight_cannot_authorize_catalog_upload(self): + fixture = Fixture(self, extension_path=CATALOG) + fixture.qualify() + for changed in [dict(extensionId="terraphim"), dict(extensionPath=""), + dict(catalog=False)]: + fixture.preflight.write_text(json.dumps({**fixture.proof, **changed})) + with self.subTest(changed=changed), self.assertRaises(marketplace.GateError): + fixture.verify() + + def test_catalog_cannot_be_relabelled_from_legacy_archive(self): + fixture = Fixture(self, extension_path=CATALOG) + legacy = package.package(fixture.root, fixture.wasm, fixture.base / "legacy", fixture.tag) + fixture.packaged.tarball.write_bytes(legacy.tarball.read_bytes()) + fixture.refresh_checksum() + with self.assertRaisesRegex(marketplace.GateError, "layout"): + fixture.qualify() + + def test_catalog_rejects_other_tested_module(self): + fixture = Fixture(self, extension_path=CATALOG) + fixture.wasm.write_bytes(b"\x00asm\x01\x00\x00\x00\x00\x01\x00") + with self.assertRaisesRegex(marketplace.GateError, "tested CI module"): + fixture.qualify() + self.assertFalse(fixture.receipt.exists()) + + def test_unsupported_path_or_wrong_identity_is_rejected(self): + fixture = Fixture(self, extension_path=CATALOG) + for path in ["../other", "/tmp/other", "catalog/../other", "catalog/other"]: + with self.subTest(path=path), self.assertRaises(package.PackageError): + package.package(fixture.root, fixture.wasm, fixture.base / "bad", fixture.tag, + extension_path=path) + manifest = fixture.root / CATALOG / "extension.toml" + manifest.write_text(manifest.read_text().replace('id = "terraphim-lsp"', 'id = "terraphim"')) + with self.assertRaisesRegex(package.PackageError, "identity"): + package.package(fixture.root, fixture.wasm, fixture.base / "bad", fixture.tag, + extension_path=CATALOG) + + def test_tag_build_can_qualify_without_native_claim_but_cannot_publish(self): + fixture = Fixture(self, extension_path=CATALOG) + fixture.proof["catalog"] = False + fixture.preflight.write_text(json.dumps(fixture.proof)) + report = fixture.qualify() + self.assertEqual(report["scope"], "Package integrity after CI; native Zed acceptance is separate") + with self.assertRaisesRegex(marketplace.GateError, "does not authorize catalog"): + fixture.verify() + + def test_manual_publish_requires_matching_tag_context(self): + self.assertFalse(marketplace.release_admission("push", "refs/tags/v0.2.0", "v0.2.0", False)) + self.assertTrue(marketplace.release_admission("workflow_dispatch", "refs/tags/v0.2.0", "v0.2.0", True)) + for event, ref in [("push", "refs/tags/v0.2.0"), + ("workflow_dispatch", "refs/heads/main"), + ("workflow_dispatch", "refs/tags/v0.2.1")]: + with self.subTest(event=event, ref=ref), self.assertRaises(marketplace.GateError): + marketplace.release_admission(event, ref, "v0.2.0", True) + + def test_native_receipt_url_is_same_repo_full_commit_json(self): + good = "https://raw.githubusercontent.com/example/zed-terraphim/" + "a" * 40 + "/docs/native.json" + self.assertEqual(marketplace.immutable_qualification_url(good, "example/zed-terraphim"), good) + for changed in [good.replace("a" * 40, "main"), good.replace("example/", "other/"), + good.replace("https://", "http://"), good + "?download=1", + good.replace("/docs/", "/../"), good.replace(".json", ".md")]: + with self.subTest(url=changed), self.assertRaises(marketplace.GateError): + marketplace.immutable_qualification_url(changed, "example/zed-terraphim") + + def test_native_receipt_checks_source_variant_server_and_required_outputs(self): + fixture = Fixture(self, extension_path=CATALOG) + original = json.loads(fixture.native_data) + changes = [("qualifiedCommit", "b" * 40), ("scope", "stock-zed-released-package"), + ("extensionId", "terraphim"), ("extensionPath", ""), + ("version", "0.3.0"), ("status", "FAIL"), + ("sourceInstallWasm", {"sha256": "bad", "bytes": 562871}), + ("server", {**original["server"], "sha256": "4" * 64}), + ("server", {**original["server"], "releaseTag": "another-version"}), + ("checks", {**original["checks"], "dirtyJsonRefusal": "FAIL"}), + ("savedOutputs", []), + ("savedOutputs", [{"name": "/private/local.md", "sha256": "3" * 64, "bytes": 73}])] + for key, value in changes: + changed = {**original, key: value} + with self.subTest(key=key, value=value), self.assertRaises(marketplace.GateError): + marketplace.validate_native_qualification(json.dumps(changed).encode(), + fixture.source, {"version": "0.2.0"}, fixture.proof["publicLsp"], fixture.proof["assets"]) + for malformed in [b"[]", b"null", b"{}"]: + with self.subTest(malformed=malformed), self.assertRaises(marketplace.GateError): + marketplace.validate_native_qualification(malformed, fixture.source, + {"version": "0.2.0"}, fixture.proof["publicLsp"], fixture.proof["assets"]) + + def test_native_receipt_bytes_are_frozen_separately_from_ci_module(self): + fixture = Fixture(self, extension_path=CATALOG) + report = fixture.qualify() + self.assertNotEqual(report["entries"]["extension.wasm"]["sha256"], + fixture.proof["nativeQualification"]["sourceInstallWasm"]["sha256"]) + fixture.verify() + # Semantically identical JSON has different bytes and cannot reuse proof. + fixture.native_path.write_text(json.dumps(json.loads(fixture.native_data), indent=2)) + with self.assertRaisesRegex(marketplace.GateError, "receipt differs"): + fixture.verify() + fixture.native_path.unlink() + with self.assertRaises(FileNotFoundError): + fixture.verify() + + def test_actual_rust_contract_selects_public_catalog_without_changing_legacy(self): + public = marketplace.release_contract(package.ROOT, CATALOG) + legacy = marketplace.release_contract(package.ROOT, "") + self.assertEqual(public["tag"], legacy["tag"]) + self.assertEqual(public["assets"], legacy["assets"]) + self.assertEqual(len(public["assets"]), 6) + self.assertTrue(legacy["gitea"].startswith("https://git.terraphim.cloud/")) + self.assertIs(legacy["path_fallback"], True) + self.assertEqual(legacy["github_discovery"], "api_listing") + + def test_catalog_snapshot_freezes_exact_qualified_bytes(self): + fixture = Fixture(self, extension_path=CATALOG) + fixture.qualify() + original = fixture.packaged.tarball.read_bytes() + with publish_github.qualified_snapshot(fixture.artifacts, fixture.tag, + fixture.source, fixture.root, extension_path=CATALOG) as (_, files): + fixture.packaged.tarball.write_bytes(b"other") + self.assertEqual(files[0].name, "terraphim-lsp-0.2.0.tar.gz") + self.assertEqual(files[0].read_bytes(), original) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/test_package_qualification.py b/scripts/test_package_qualification.py index b2fb03c..7712248 100644 --- a/scripts/test_package_qualification.py +++ b/scripts/test_package_qualification.py @@ -19,7 +19,7 @@ class Fixture: - def __init__(self, case, tag="v0.2.0"): + def __init__(self, case, tag="v0.2.0", extension_path=""): temporary = tempfile.TemporaryDirectory() case.addCleanup(temporary.cleanup) self.base = Path(temporary.name) @@ -27,6 +27,12 @@ def __init__(self, case, tag="v0.2.0"): self.root.mkdir() for name in package.FILES: (self.root / name).write_bytes((package.ROOT / name).read_bytes()) + self.extension_path = extension_path + if extension_path: + nested = self.root / extension_path + nested.mkdir(parents=True) + for name in package.FILES: + (nested / name).write_bytes((package.ROOT / extension_path / name).read_bytes()) (self.root / "build-source.rs").write_text("// Tracked non-packaged source\n") self.git("init", "-q") self.git("add", ".") @@ -38,15 +44,38 @@ def __init__(self, case, tag="v0.2.0"): (self.artifacts / "public-preflight").mkdir(parents=True) self.proof = { "status": "PASS", "source": self.source, "tag": self.tag, - "version": "0.2.0", "extensionId": "terraphim", "catalog": False, + "version": "0.2.0", "extensionId": "terraphim-lsp" if extension_path else "terraphim", + "extensionPath": extension_path, "catalog": bool(extension_path), "repository": "example/zed-terraphim", } + if extension_path: + # Parser fixtures only: these declarations do not attest to a native run. + native = { + "schema": 1, "status": "PASS", "scope": "stock-zed-source", + "qualifiedCommit": self.source, "extensionId": "terraphim-lsp", + "extensionPath": extension_path, "version": "0.2.0", + "sourceInstallWasm": {"sha256": "1" * 64, "bytes": 562871}, + "server": {"releaseTag": "terraphim_lsp-v1.22.0-rc.7", + "sha256": "2" * 64, "bytes": 13000000}, + "checks": {name: "PASS" for name in marketplace.NATIVE_CHECKS}, + "savedOutputs": [{"name": "saved-fixture.md", "sha256": "3" * 64, "bytes": 73}], + } + self.native_data = json.dumps(native).encode() + self.native_path = self.artifacts / "public-preflight/native-qualification.json" + self.native_path.write_bytes(self.native_data) + self.proof["qualificationUrl"] = "https://raw.githubusercontent.com/example/zed-terraphim/" + "a" * 40 + "/docs/native.json" + self.proof["publicLsp"] = {"tag": native["server"]["releaseTag"]} + self.proof["assets"] = [{"sha256": native["server"]["sha256"], "bytes": native["server"]["bytes"]}] + self.proof["nativeQualification"] = marketplace.native_evidence_record( + self.native_data, self.proof["qualificationUrl"], self.proof["repository"], + self.source, {"version": "0.2.0"}, self.proof["publicLsp"], self.proof["assets"]) self.preflight = self.artifacts / "public-preflight/preflight.json" self.preflight.write_text(json.dumps(self.proof)) self.wasm = self.base / "ci-built.wasm" self.wasm.write_bytes(b"\x00asm\x01\x00\x00\x00") self.packaged = package.package( - self.root, self.wasm, self.artifacts / "release-dist", self.tag + self.root, self.wasm, self.artifacts / "release-dist", self.tag, + **({"extension_path": extension_path} if extension_path else {}) ) self.receipt = self.artifacts / "release-dist" / qualification.RECEIPT @@ -58,11 +87,13 @@ def commit(self, message): "-c", "commit.gpgsign=false", "commit", "-qm", message) def qualify(self): - return qualification.qualify(self.root, self.artifacts, self.tag, self.wasm) + return qualification.qualify(self.root, self.artifacts, self.tag, self.wasm, + **({"extension_path": self.extension_path} if self.extension_path else {})) def verify(self): return publish_github.verify_package( - self.artifacts, self.tag, self.source, root=self.root + self.artifacts, self.tag, self.source, root=self.root, + **({"extension_path": self.extension_path} if self.extension_path else {}) ) def refresh_checksum(self): From b75b475adebe42604ac9ba86023b884c7049bc58 Mon Sep 17 00:00:00 2001 From: Dr Alexander Mikhalev Date: Wed, 14 Oct 2026 17:23:06 +0100 Subject: [PATCH 7/7] fix: protect open JSON graph exports with released RC7 --- catalog/terraphim-lsp/README.md | 4 ++ catalog/terraphim-lsp/extension.toml | 3 +- docs/alternative-authoring.md | 9 ++- docs/marketplace.md | 9 +-- docs/qualification.md | 18 ++++++ docs/rc7-install-qualification.md | 88 ++++++++++++++++++++++++++++ scripts/test_catalog_package.py | 14 +++++ src/provision.rs | 20 +++---- 8 files changed, 145 insertions(+), 20 deletions(-) create mode 100644 docs/rc7-install-qualification.md diff --git a/catalog/terraphim-lsp/README.md b/catalog/terraphim-lsp/README.md index ff44e9a..96a70b8 100644 --- a/catalog/terraphim-lsp/README.md +++ b/catalog/terraphim-lsp/README.md @@ -5,6 +5,10 @@ for Markdown in stock Zed. This catalog package uses the same Rust adapter and pinned language server as the existing Terraphim extension. The language server uses Terraphim's shared crates for alternatives and `synonyms::` relationships. +JSON and JSONC are attached only to track open export destinations. Unsaved +changes cause graph export to refuse the write, preserving the buffer and disk. +They do not receive Markdown alternatives, Lab analysis or diagnostics. + This package supplies the language server only. It downloads verified release assets from fixed, pinned GitHub URLs without a metadata API request or PATH fallback, or uses the user's explicit `lsp.terraphim-lsp.binary.path`. diff --git a/catalog/terraphim-lsp/extension.toml b/catalog/terraphim-lsp/extension.toml index 6a85337..4eedaa4 100644 --- a/catalog/terraphim-lsp/extension.toml +++ b/catalog/terraphim-lsp/extension.toml @@ -8,5 +8,6 @@ repository = "https://github.com/terraphim/zed-terraphim" [language_servers.terraphim-lsp] name = "Terraphim LSP" -languages = ["Markdown"] +languages = ["Markdown", "JSON", "JSONC"] +language_ids = { JSON = "json", JSONC = "jsonc" } diff --git a/docs/alternative-authoring.md b/docs/alternative-authoring.md index e71f23a..ba36aa4 100644 --- a/docs/alternative-authoring.md +++ b/docs/alternative-authoring.md @@ -1,11 +1,7 @@ # Author a new alternative with Zed's command picker -Status: verified with a candidate RC4 server in stock Zed 1.23.3. The public -RC4 GitHub binary release failed in the musl build lanes. The prospective RC6 -release and default-install verification remain pending. - Zed's built-in **lsp command selector: toggle** action can pass JSON arguments -to a language server. The prepared RC4 server advertises +to a language server. Terraphim LSP advertises `terraphim.alternative.add_and_apply`, which asks Zed to apply the annotation and confirms that the document changed. This uses the stock application. @@ -42,3 +38,6 @@ edits and client apply support. A rejection or stale document is reported as a failure. If acknowledgement or the matching document change times out, inspect the document before retrying: the edit may still arrive. A successful command returns an acknowledgement rather than a second edit to apply. + +The pinned server is the public RC7 release. Versioned native and package-install +evidence is recorded in [qualification.md](qualification.md); the [RC7 checklist](rc7-install-qualification.md) describes the release gates. diff --git a/docs/marketplace.md b/docs/marketplace.md index 45c7959..41b3755 100644 --- a/docs/marketplace.md +++ b/docs/marketplace.md @@ -175,9 +175,10 @@ clean checkout at the existing tag and a fresh output directory. The intended first catalog package tag is `v0.2.0`, matching the catalog manifest. It must be created only after the final source checks and native qualification -pass. The repaired language-server dependency will be pinned separately to the -actual public `terraphim_lsp-v1.22.0-rc.7` release; a stable extension version does -not change that server's prerelease status. The existing RC6 pin stays unchanged -until RC7's public assets are verified. Actual release/catalog commands require +pass. The language-server dependency is pinned separately to the verified public +[`terraphim_lsp-v1.22.0-rc.7` release](https://github.com/terraphim/terraphim-ai/releases/tag/terraphim_lsp-v1.22.0-rc.7); +a stable extension version does not change that server's prerelease status. +The catalog attaches JSON and JSONC only for passive export-destination tracking; +Markdown features remain restricted to Markdown. Actual release/catalog commands require the reviewed exact tag and successful receipts; a failed gate is not installation evidence. diff --git a/docs/qualification.md b/docs/qualification.md index fdd2a05..45a8f97 100644 --- a/docs/qualification.md +++ b/docs/qualification.md @@ -132,6 +132,24 @@ prepared steps. Its earlier metadata-API HTTP 403 profile remains separate from the successful direct-download profile. RC4/RC5 source and release history above remain unchanged. +## RC7 dirty-destination repair (14 October 2026) + +The public [RC7 server release](https://github.com/terraphim/terraphim-ai/releases/tag/terraphim_lsp-v1.22.0-rc.7) +is built from source `4fb62d337c306a9105c5e872f3400f3052a2caef`. All six binary +assets were anonymously downloaded and checked against the release checksums. +Actual downloaded macOS ARM64 and universal binaries passed protocol checks for +JSON and JSONC dirty-destination refusal after buffer changes, with a changed +pending graph payload. +Those process-level checks do not establish stock-Zed UI behavior. + +The catalog registers JSON and JSONC as passive buffers for this protection; +they receive no Markdown alternatives, Lab analysis or diagnostics. Follow the +[RC7 checklist](rc7-install-qualification.md) on the final source to reproduce the +previous interior-edit failure, require visible refusal and preserve both the +unsaved buffer and disk. Publication also requires the remaining native checks +and a separately verified compiled-package installation. Historical RC6 evidence +above remains unchanged. + ## Compiled release-package qualification Follow [released-package installation](released-package-install.md) in a second diff --git a/docs/rc7-install-qualification.md b/docs/rc7-install-qualification.md new file mode 100644 index 0000000..e698877 --- /dev/null +++ b/docs/rc7-install-qualification.md @@ -0,0 +1,88 @@ +# RC7 default-install qualification + +The dependency is the public [Terraphim LSP RC7 release](https://github.com/terraphim/terraphim-ai/releases/tag/terraphim_lsp-v1.22.0-rc.7), +source `4fb62d337c306a9105c5e872f3400f3052a2caef`. CI run +[38593509964](https://github.com/terraphim/terraphim-ai/actions/runs/38593509964) +published it on 14 October 2026. All six raw binary assets were independently +downloaded anonymously and verified against that release's `checksums.txt`. + +| Asset | SHA-256 | +| --- | --- | +| `terraphim-lsp-aarch64-apple-darwin` | `faf89155be9cdada9e06d87d4c54279405a13409ee74917469c0bc6756462aae` | +| `terraphim-lsp-universal-apple-darwin` | `ed60697248dfbef1d3b731e5f758fd7f02c00c74b3da8581df11dd78fa2e32cd` | +| `terraphim-lsp-x86_64-apple-darwin` | `9532a4e019631d1ec97b3df956fd18304a92660e99a497bf14e6101f618ec0cb` | +| `terraphim-lsp-aarch64-unknown-linux-musl` | `0ded8432af3863296b123f18e514e20989bdf3b120360d6454552dc39d4e63bf` | +| `terraphim-lsp-x86_64-unknown-linux-gnu` | `a535ff81b9de3658b7803b59dc03c6f048a5f913339b0f2746c21001ae85a062` | +| `terraphim-lsp-x86_64-unknown-linux-musl` | `7e646d7ed10fec3a24be9c42248ae2c1974327be7329eb0157eec57336def0c2` | + +The adapter checks the actual downloaded checksum file at runtime. This table is +release evidence, not a substitute for that check. The catalog uses fixed public +URLs, no GitHub metadata API, no Gitea request and no PATH fallback. Explicit +`binary.path` remains supported but must be absent during default-install tests. +The legacy adapter retains its source ordering and independent MCP pin. + +## Fresh source installation + +Freeze and commit the reviewed catalog source before running these checks. Use +an untouched stock-Zed profile and disposable project, with no copied extension +cache, executable, user settings or binary override. Record input files as inputs. +Use `zed --foreground --new --user-data-dir PROFILE PROJECT`; verify that this +instance's settings resolve to `PROFILE/config/settings.json`. + +Install the reviewed `catalog/terraphim-lsp` directory through **Extensions → +Install Dev Extension**. Record the exact source SHA and dev-built `extension.wasm` +hash. Open the plain Markdown fixture, then identify the actual spawned server +inside the profile's versioned RC7 work directory. Compare the executable and +`.verified` marker with the public release, run that file's `--version`, and +restart the server to prove verified-cache reuse. No manual executable copy or +PATH result satisfies default provisioning. + +## Saved outputs and dirty JSON regression + +1. Start with plain `Risk is manageable.` and create `Concern` through + `terraphim.alternative.add_and_apply`, as in [the authoring walkthrough](alternative-authoring.md). + Save the resulting annotation. Native undo/save must restore the original; + redo/save must restore the generated annotation. Close/reopen and swap using + a code action, then inspect the actual saved text. +2. Export the graph twice and save both actual outputs. Require `risk` and + `concern` to share a concept ID, equal graph content on repeat export and an + unchanged source document. +3. Reproduce the RC6 failure with an **interior** unsaved edit to the existing + graph's `name` string, not an appended suffix. Capture the dirty tab and text + before export, plus the on-disk hash. Export again from the paired Markdown. + Require visible refusal, the unsaved interior text still present, and the + on-disk hash unchanged. Persist a window-only screenshot of the result. + Save the preserved edit afterward and verify its actual bytes. Repeat for a + JSONC destination when exercising the JSONC protocol path; the native default + filename is JSON. An append surviving outside the replaced range is not proof. +4. Verify JSON/JSONC receive no Markdown alternatives, diagnostics or Lab marks. + Their attachment exists only for passive buffer and version tracking. +5. Run Trim Done on the explicitly preauthored fixture. Save its actual ghost + annotations, insert a prefix and verify all retained anchor offsets shift by + the prefix length. Export clean Markdown and verify the saved text. Make an + unsaved edit in that destination, require refusal and preserve its buffer/disk. +6. Run released `terraphim-grep` against a clean brief first without and then with + the actual exported graph. Save its version/hash, commands and returned JSON. + The newly authored synonym must retrieve the canonical passage only when the + graph is supplied. Keep seeded fixtures separate from these generated outputs. + +Save a private detailed receipt and the sanitized public source receipt described +in [qualification.md](qualification.md). A native failure blocks publication; +headless protocol success cannot substitute for this run. Historical RC6 failure +receipts and profiles remain unchanged. These bounded checks do not certify all +41 browser-editor requirements. + +## Compiled package and catalog + +After exact-source acceptance, a normal merge may preserve that tested PR head; +tag the exact tested source as `v0.2.0`. The evidence can be published in a later +docs-only commit while retaining the tested source identity. The protected release +workflow must qualify and publish its exact catalog archive. + +Then use another empty stock-Zed profile to install the **downloaded four-file +archive**, following [released-package installation](released-package-install.md). +Require the loaded WASM hash to equal the released archive's module, with no Cargo +source or Rust rebuild. Save a separate native receipt for these bytes. Catalog +submission remains a human-authored upstream contribution, followed by actual +registry listing and registry-install verification. The Zed application fork is +outside this work. diff --git a/scripts/test_catalog_package.py b/scripts/test_catalog_package.py index 3ad64e3..e9b75fb 100644 --- a/scripts/test_catalog_package.py +++ b/scripts/test_catalog_package.py @@ -31,6 +31,20 @@ def test_shared_source_and_legacy_identity_are_preserved(self): self.assertEqual(manifest["id"], "terraphim-lsp") self.assertFalse(manifest.get("context_servers")) + def test_actual_catalog_attaches_json_only_for_passive_destination_tracking(self): + catalog_manifest = tomllib.loads( + (package.ROOT / catalog_package.PATH / "extension.toml").read_text() + ) + server = catalog_manifest["language_servers"]["terraphim-lsp"] + self.assertEqual(server["languages"], ["Markdown", "JSON", "JSONC"]) + self.assertEqual(server["language_ids"], {"JSON": "json", "JSONC": "jsonc"}) + legacy_manifest = tomllib.loads((package.ROOT / "extension.toml").read_text()) + self.assertEqual( + legacy_manifest["language_servers"]["terraphim-lsp"]["languages"], + ["Markdown"], + ) + self.assertNotIn("language_ids", legacy_manifest["language_servers"]["terraphim-lsp"]) + def test_catalog_entry_points_to_the_manifest_subdirectory(self): changed = catalog.registry_entry( "", "terraphim-lsp", "0.2.0", catalog_package.PATH diff --git a/src/provision.rs b/src/provision.rs index e7ba681..015fb98 100644 --- a/src/provision.rs +++ b/src/provision.rs @@ -22,7 +22,7 @@ //! //! # Release sources //! -//! The public catalog uses fixed GitHub asset URLs for the shared rc.6 pin, +//! The public catalog uses fixed GitHub asset URLs for the shared rc.7 pin, //! with no metadata API request or PATH fallback. Its release must exist and //! pass anonymous download checks before extension publication. //! The combined legacy adapter retains its Gitea-first source order, then @@ -30,7 +30,7 @@ //! GitHub. Each source is checked against its own `checksums.txt`. //! //! The captured rc.3 Gitea fixtures remain historical parser/provisioning -//! evidence. They are not checksums or installation evidence for rc.6. +//! evidence. They are not checksums or installation evidence for rc.7. use sha2::{Digest, Sha256}; use std::fmt; @@ -82,7 +82,7 @@ pub const LSP: BinarySpec = BinarySpec { repo: "terraphim/terraphim-ai", github_discovery: GithubDiscovery::ApiListing, gitea: Some(GITEA_RELEASES), - tag: "terraphim_lsp-v1.22.0-rc.6", + tag: "terraphim_lsp-v1.22.0-rc.7", asset_stem: "terraphim-lsp", checksum_file: "checksums.txt", dir_prefix: "terraphim-lsp", @@ -983,7 +983,7 @@ mod tests { ); assert_eq!( binary_path(&mcp), - "terraphim-lsp-terraphim_lsp-v1.22.0-rc.6/terraphim_mcp_server" + "terraphim-lsp-terraphim_lsp-v1.22.0-rc.7/terraphim_mcp_server" ); } @@ -1029,7 +1029,7 @@ mod tests { } } - /// Preserve the real historical capture without re-labelling it as rc.6. + /// Preserve the real historical capture without re-labelling it as rc.7. #[test] fn fixture_is_the_historical_rc3_release() { assert_eq!(HISTORICAL_RC3.tag, "terraphim_lsp-v1.22.0-rc.3"); @@ -1154,11 +1154,11 @@ mod tests { fn install_layout_is_versioned() { assert_eq!( install_dir(&LSP), - "terraphim-lsp-terraphim_lsp-v1.22.0-rc.6" + "terraphim-lsp-terraphim_lsp-v1.22.0-rc.7" ); assert_eq!( binary_path(&LSP), - "terraphim-lsp-terraphim_lsp-v1.22.0-rc.6/terraphim-lsp" + "terraphim-lsp-terraphim_lsp-v1.22.0-rc.7/terraphim-lsp" ); } @@ -2040,7 +2040,7 @@ mod catalog_configuration_tests { for (asset, expected) in assets.iter().zip(names) { assert_eq!(asset.name, expected); assert_eq!(asset.download_url, format!( - "https://github.com/terraphim/terraphim-ai/releases/download/terraphim_lsp-v1.22.0-rc.6/{expected}" + "https://github.com/terraphim/terraphim-ai/releases/download/terraphim_lsp-v1.22.0-rc.7/{expected}" )); } } @@ -2098,8 +2098,8 @@ mod catalog_configuration_tests { } #[test] - fn shared_public_release_pin_targets_rc6() { - assert_eq!(LSP.tag, "terraphim_lsp-v1.22.0-rc.6"); + fn shared_public_release_pin_targets_rc7() { + assert_eq!(LSP.tag, "terraphim_lsp-v1.22.0-rc.7"); assert_eq!(LSP.repo, "terraphim/terraphim-ai"); assert_eq!(LSP.checksum_file, "checksums.txt"); if cfg!(feature = "public-catalog") {