The guest SDK is unversioned, GUEST_ABI_MAJOR is bracketed by no manifest field, and plugin pack --check-reproducible does not exist #153

Closed
opened 2026-09-05 12:55:26 +02:00 by buildagent · 5 comments
Member

RETITLED AND CORRECTED IN PLACE 2026-09-06 (packaged-language lane, master 4f866e5).
The old title — "A third party cannot pin the guest SDK…" — asserts something the tree refutes, which is the exact failure mode this repo has already paid for twice (an issue cited as a blocker after it shipped). A third party CAN pin the SDK, by rev, and the pin is kept true by a test. Two of the four blockers are closed, one was refuted with a repro, and three items remain. Original body preserved below.

Verified in this lane, at master 4f866e5

item state how it was checked here
The dependency stanza CLOSED _prdoc/guides/80-package-authoring.md:57+, §1 "Start here: your extractor crate" — read directly
§9's "There is no SDK crate" CLOSED struck and corrected in the guide
"cannot pin the SDK" REFUTED crates/guest/tests/sdk_pin.rs exists: it refuses a branch pin and reddens when crates/guest/src moves and the guide's rev does not
GUEST_ABI_MAJOR surfaces as grammar.kind_table_mismatch REFUTED with a repro plugin-host/tests/guest_globals.rs::a_stale_guest_abi_is_not_a_kind_table_mismatch — the refusal is Verdict::GuestAbiViolation → Reason::AbiDecodeFailed
crates/guest is in no tagged release STILL TRUE git ls-tree -d <tag> crates/guest is empty for all six most recent tags (v0.26.1, v0.26.0, v0.25.0, v0.24.1, v0.24.0, v0.23.1) — re-run here, not quoted
plugin pack --check-reproducible STILL ABSENT check-reproducible and check_reproducible: 0 hits repo-wide — re-run here

The distinction the old title missed: "in no tagged release" is true; "cannot pin" is not. A maintained rev pin with a gate behind it is a pin. The reasoning recorded for choosing it is sound — crates.io needs a publish nothing here can perform or verify, and a tag is not an edit to the tree, so it would be a promise recorded as a fact.

What remains, precisely

  1. SDK versioning. _prdoc/guides/80-abi-support-policy.md:311 scores this row STILL NOT MET: publish = false workspace-wide, no crates.io, no semver, no docs.rs, no tag naming the crate.
  2. GUEST_ABI_MAJOR is bracketed by no manifest field. [abi].host_min/host_max pin the fact-ABI major, which has never moved; GUEST_ABI_MAJOR is 2 and has already moved 1→2. 80-abi-support-policy.md:303,310. The corrected refusal (abi.decode_failed) is arguably worse than filed: it names the fact ABI, which has never moved, so it still points an author at the wrong repair.
  3. plugin pack --check-reproducible is not implemented. The CARGO_INCREMENTAL trap reproduces on a real guest (2883 vs 2887 bytes, different package_digest AND extraction_identity) and pack/validate/inspect say nothing. The guide's own "Nothing warns you" is still exactly true, and a rebuild-and-compare would close a class the docs can only warn about.

Also still open and worth keeping: nothing generalises the grammar build. tests/grammars/build-tree-sitter-{xml,ruby}.sh are 196 and 172 lines of near-duplicate. #79's "no Docker/emscripten ritual" IS honoured; the script is not a tool.


ORIGINAL BODY, 2026-09-05 — blockers 2 and 3 are fixed, blocker 4 was refuted, and blocker 1's conclusion is false

Found by a reviewer who authored a package as a stranger, in /tmp, using only the two published guides. They got from nothing to indexed rows in ~15 minutes — git dep → cargo build --target wasm32-unknown-unknown → plugin-host --strip-name-section → --inject-globals → plugin pack → validate → key generate → sign → trust add → plugin add → index, producing symbols: [(1,'root','module','com.example.mylang/mylang')], refs: 10, tier1_unique=2. It packed and validated first try from the guide's canonical manifest, and every step printed the literal next command.

The architecture works. The distribution channel does not. Four blockers, all cheap:

1. The SDK exists in no tagged release

The premise is TRUE and the conclusion is FALSE (2026-09-06). crates/guest is in no tag — re-verified. But a maintained rev pin shipped, gated by crates/guest/tests/sdk_pin.rs, so a third party can pin it. What remains is versioning, item 1 above.

crates/guest landed on master in 1aa6514, after v0.26.1. The line a third party types first —

code-index-guest = { git = "https://git.h-dv.de/h-dv/code-index.git", tag = "v0.26.1" }

— fails with no matching package named code-index-guest found. Only branch = "master" or a raw rev works. publish = false workspace-wide, so no crates.io, no semver, no docs.rs. The repo IS anonymously cloneable over HTTPS (verified), so the channel exists; the pin does not.

2. Neither guide gives the dependency line

FIXED. _prdoc/guides/80-package-authoring.md:57-100.

All of _prdoc/guides/ contains four mentions of code-index-guest/crates/guest and not one Cargo.toml stanza, git URL, crate-type = ["cdylib"], panic = "abort", [workspace] escape, or rustup target add wasm32-unknown-unknown. The reviewer had to copy crates/guest/example/Cargo.toml from a checkout. A stranger without the repo cloned cannot start.

3. 80-package-authoring.md §9 (line 660) says "There is no SDK crate."

FIXED. Struck and corrected at 80-package-authoring.md:834-853; the walkthrough and signing bullets are marked EXECUTED.

The same file at lines 85, 430 and 441 calls crates/guest "the only supported way to write an extractor." A stale bullet contradicting its own §1, in the document that is the front door.

4. GUEST_ABI_MAJOR is 2, has already moved 1→2, and no manifest field brackets it

The bracket half stands (item 2 above). The refusal-shape half was REFUTED with a repro: the guest loads, the kind table is Verified, and the refusal arrives as Verdict::GuestAbiViolation → Reason::AbiDecodeFailed, not grammar.kind_table_mismatch. crates/plugin-host/tests/guest_globals.rs:907-940.

[abi].host_min/host_max pin the fact-ABI major, which has never moved. The repo's own scorecard says this and calls it NOT MET.

The failure was tested. Good: an author rebuilding hits it at their desk — --inject-globals refuses with "the guest exports no extract(i32,i32,i32,i32) -> i64". Bad, and this is the finding: the package still packs and signs, and the user-facing refusal is

verdict FAILED
  fixtures/sample.oldabi  REFUSED grammar.kind_table_mismatch

The grammar and its kind table are fine. An operator or author reading that goes and rebuilds their grammar — the wrong repair entirely. (Bounded: the repro conflates the wrong extract shape with unpatched globals.)

Also missing, and it is the real surprise

A guest has zero wasm imports, so it cannot ask the grammar for a node's kind NAME. crates/guest/ruby/gen-kinds.sh records the consequence: tree-sitter-ruby spells call with four distinct ids and assignment with two. A builtin comparing node.kind() strings folds them for free; a guest comparing one id silently misses three quarters of Ruby's call sites. Every predicate must be a generated bit table over the whole id space. gen_kinds.py (167 lines) does exactly this, is genuinely reusable, and is mentioned in no guide. (Now referenced at 80-package-authoring.md:135.)

And there is no reproducibility tooling: the reviewer reproduced the CARGO_INCREMENTAL trap on their own guest (2883 vs 2887 bytes, different package_digest AND extraction_identity) and pack/validate/inspect said nothing. plugin pack --check-reproducible (rebuild, compare) would close the class the docs can only warn about.

Cost of a new language, measured

crates/guest/ruby/src/ is 1,993 lines against the compiled crates/plugins/src/ruby.rs's 1,859 — the wasm port is ~7% larger. So: about the same as writing a builtin, plus the kind-table generator, plus a grammar build. Artifacts 24 KB extractor + 2.1 MB grammar, both far under the 8 MiB ceilings.

The grammar half is a copy-paste, not a tool: tests/grammars/build-tree-sitter-{xml,ruby}.sh are 196 and 172 lines of near-duplicate.

  • #87 (a package cannot replace a builtin) — the owner has now decided this gets an operator-consented override
  • #86 (ABI gaps)
> **RETITLED AND CORRECTED IN PLACE 2026-09-06** (packaged-language lane, master `4f866e5`). > The old title — *"A third party cannot pin the guest SDK…"* — **asserts something the tree refutes**, which is the exact failure mode this repo has already paid for twice (an issue cited as a blocker after it shipped). A third party CAN pin the SDK, by rev, and the pin is kept true by a test. Two of the four blockers are closed, one was refuted with a repro, and three items remain. Original body preserved below. ## Verified in this lane, at master `4f866e5` | item | state | how it was checked here | |---|---|---| | **The dependency stanza** | **CLOSED** | `_prdoc/guides/80-package-authoring.md:57+`, §1 *"Start here: your extractor crate"* — read directly | | **§9's "There is no SDK crate"** | **CLOSED** | struck and corrected in the guide | | **"cannot pin the SDK"** | **REFUTED** | `crates/guest/tests/sdk_pin.rs` exists: it refuses a `branch` pin and reddens when `crates/guest/src` moves and the guide's rev does not | | **`GUEST_ABI_MAJOR` surfaces as `grammar.kind_table_mismatch`** | **REFUTED with a repro** | `plugin-host/tests/guest_globals.rs::a_stale_guest_abi_is_not_a_kind_table_mismatch` — the refusal is `Verdict::GuestAbiViolation` → `Reason::AbiDecodeFailed` | | **`crates/guest` is in no tagged release** | **STILL TRUE** | `git ls-tree -d <tag> crates/guest` is **empty for all six most recent tags** (v0.26.1, v0.26.0, v0.25.0, v0.24.1, v0.24.0, v0.23.1) — re-run here, not quoted | | **`plugin pack --check-reproducible`** | **STILL ABSENT** | `check-reproducible` and `check_reproducible`: **0 hits repo-wide** — re-run here | The distinction the old title missed: **"in no tagged release" is true; "cannot pin" is not.** A maintained `rev` pin with a gate behind it is a pin. The reasoning recorded for choosing it is sound — crates.io needs a publish nothing here can perform *or verify*, and a tag is not an edit to the tree, so it would be a promise recorded as a fact. ## What remains, precisely 1. **SDK versioning.** `_prdoc/guides/80-abi-support-policy.md:311` scores this row **STILL NOT MET**: `publish = false` workspace-wide, no crates.io, no semver, no docs.rs, no tag naming the crate. 2. **`GUEST_ABI_MAJOR` is bracketed by no manifest field.** `[abi].host_min`/`host_max` pin the **fact**-ABI major, which has never moved; `GUEST_ABI_MAJOR` is 2 and has already moved 1→2. `80-abi-support-policy.md:303,310`. The corrected refusal (`abi.decode_failed`) is **arguably worse than filed**: it names the fact ABI, which has never moved, so it still points an author at the wrong repair. 3. **`plugin pack --check-reproducible` is not implemented.** The `CARGO_INCREMENTAL` trap reproduces on a real guest (2883 vs 2887 bytes, different `package_digest` AND `extraction_identity`) and `pack`/`validate`/`inspect` say nothing. The guide's own *"Nothing warns you"* is still exactly true, and a rebuild-and-compare would close a class the docs can only warn about. Also still open and worth keeping: **nothing generalises the grammar build.** `tests/grammars/build-tree-sitter-{xml,ruby}.sh` are 196 and 172 lines of near-duplicate. #79's "no Docker/emscripten ritual" IS honoured; the script is not a tool. --- <details> <summary>ORIGINAL BODY, 2026-09-05 — blockers 2 and 3 are fixed, blocker 4 was refuted, and blocker 1's conclusion is false</summary> Found by a reviewer who authored a package **as a stranger**, in `/tmp`, using only the two published guides. They got from nothing to indexed rows in ~15 minutes — `git dep → cargo build --target wasm32-unknown-unknown → plugin-host --strip-name-section → --inject-globals → plugin pack → validate → key generate → sign → trust add → plugin add → index`, producing `symbols: [(1,'root','module','com.example.mylang/mylang')]`, `refs: 10`, `tier1_unique=2`. It packed and validated **first try** from the guide's canonical manifest, and every step printed the literal next command. **The architecture works. The distribution channel does not.** Four blockers, all cheap: ## 1. The SDK exists in no tagged release > **The premise is TRUE and the conclusion is FALSE (2026-09-06).** `crates/guest` is in no tag — re-verified. But a maintained `rev` pin shipped, gated by `crates/guest/tests/sdk_pin.rs`, so a third party *can* pin it. What remains is versioning, item 1 above. `crates/guest` landed on master in `1aa6514`, **after** `v0.26.1`. The line a third party types first — ```toml code-index-guest = { git = "https://git.h-dv.de/h-dv/code-index.git", tag = "v0.26.1" } ``` — fails with `no matching package named code-index-guest found`. Only `branch = "master"` or a raw `rev` works. `publish = false` workspace-wide, so no crates.io, no semver, no docs.rs. The repo IS anonymously cloneable over HTTPS (verified), so the channel exists; the pin does not. ## 2. Neither guide gives the dependency line > **FIXED.** `_prdoc/guides/80-package-authoring.md:57-100`. ~~All of `_prdoc/guides/` contains four mentions of `code-index-guest`/`crates/guest` and **not one** `Cargo.toml` stanza, git URL, `crate-type = ["cdylib"]`, `panic = "abort"`, `[workspace]` escape, or `rustup target add wasm32-unknown-unknown`. The reviewer had to copy `crates/guest/example/Cargo.toml` from a checkout. **A stranger without the repo cloned cannot start.**~~ ## 3. `80-package-authoring.md` §9 (line 660) says "There is no SDK crate." > **FIXED.** Struck and corrected at `80-package-authoring.md:834-853`; the walkthrough and signing bullets are marked EXECUTED. ~~The same file at lines 85, 430 and 441 calls `crates/guest` "the only supported way to write an extractor." A stale bullet contradicting its own §1, in the document that is the front door.~~ ## 4. `GUEST_ABI_MAJOR` is 2, has already moved 1→2, and no manifest field brackets it > **The bracket half stands (item 2 above). The refusal-shape half was REFUTED with a repro:** the guest loads, the kind table is `Verified`, and the refusal arrives as `Verdict::GuestAbiViolation` → `Reason::AbiDecodeFailed`, not `grammar.kind_table_mismatch`. `crates/plugin-host/tests/guest_globals.rs:907-940`. `[abi].host_min`/`host_max` pin the **fact**-ABI major, which has never moved. The repo's own scorecard says this and calls it NOT MET. The failure was tested. Good: an author rebuilding hits it at their desk — `--inject-globals` refuses with *"the guest exports no extract(i32,i32,i32,i32) -> i64"*. Bad, and this is the finding: the package still packs and signs, and the **user-facing** refusal is ``` verdict FAILED fixtures/sample.oldabi REFUSED grammar.kind_table_mismatch ``` ~~The grammar and its kind table are fine. An operator or author reading that goes and rebuilds their grammar — **the wrong repair entirely.**~~ (Bounded: the repro conflates the wrong `extract` shape with unpatched globals.) ## Also missing, and it is the real surprise **A guest has zero wasm imports, so it cannot ask the grammar for a node's kind NAME.** `crates/guest/ruby/gen-kinds.sh` records the consequence: `tree-sitter-ruby` spells `call` with **four** distinct ids and `assignment` with two. A builtin comparing `node.kind()` strings folds them for free; a guest comparing one id silently misses three quarters of Ruby's call sites. Every predicate must be a generated bit table over the whole id space. `gen_kinds.py` (167 lines) does exactly this, is genuinely reusable, and is **mentioned in no guide**. *(Now referenced at `80-package-authoring.md:135`.)* And there is no reproducibility tooling: the reviewer reproduced the `CARGO_INCREMENTAL` trap on their own guest (2883 vs 2887 bytes, different `package_digest` AND `extraction_identity`) and `pack`/`validate`/`inspect` said nothing. `plugin pack --check-reproducible` (rebuild, compare) would close the class the docs can only warn about. ## Cost of a new language, measured `crates/guest/ruby/src/` is **1,993 lines** against the compiled `crates/plugins/src/ruby.rs`'s **1,859** — the wasm port is ~7% *larger*. So: about the same as writing a builtin, plus the kind-table generator, plus a grammar build. Artifacts 24 KB extractor + 2.1 MB grammar, both far under the 8 MiB ceilings. The grammar half is a copy-paste, not a tool: `tests/grammars/build-tree-sitter-{xml,ruby}.sh` are 196 and 172 lines of near-duplicate. ## Related - #87 (a package cannot replace a builtin) — the owner has now decided this gets an operator-consented override - #86 (ABI gaps) </details>
Author
Member

Triage 2026-09-06: LEFT OPEN, but the title is now factually wrong — a third party can pin the guest SDK. Recommend retitling rather than closing.

Two of four blockers are closed, one is answered by a different channel than the title assumes, and one was refuted with a repro.

Closed

(2) The dependency stanza — FIXED. New §1 "Start here: your extractor crate" with the full Cargo.toml: git URL, rev, crate-type = ["cdylib"], panic = "abort", [workspace] escape — _prdoc/guides/80-package-authoring.md:57-100.

(3) §9's "There is no SDK crate" — FIXED. The bullet is struck through and corrected at _prdoc/guides/80-package-authoring.md:834-853; the walkthrough and signing bullets are marked EXECUTED.

Also: gen_kinds.py is now referenced in a guide — 80-package-authoring.md:135. That closes the loose end #87's last comment left (the answer to its blocker 2 being named nowhere).

(1) — addressed by a different channel, and gated

Not a tag. git tag shows the newest is v0.26.1, and git ls-tree -d <tag> crates/guest is empty for all six most recent tags — the SDK is still in no tagged release, exactly as this issue says.

What shipped instead is a maintained rev pin, with a gate: crates/guest/tests/sdk_pin.rs:1-60 refuses a branch pin, goes RED when crates/guest/src moves and the guide's rev does not, and where git history is present compares the SDK at that rev against the working tree file by file — printing when it cannot, rather than skipping silently.

The reasoning is recorded and is sound: crates.io was rejected because it needs a publish nothing here can perform or verify ("publishable" is not "published"), and a tag was rejected because a tag is not an edit to the tree, so it would be a promise recorded as a fact.

So the title's claim — "a third party cannot pin the guest SDK" — is false. They can, by rev, and the pin is kept true by a test.

(4) — REFUTED, with a repro, and the correction survives

The filing said a stale GUEST_ABI_MAJOR would surface as grammar.kind_table_mismatch. It does not. a_stale_guest_abi_is_not_a_kind_table_mismatch (crates/plugin-host/tests/guest_globals.rs:907-940) shows the guest loads, the kind table is Verified, and the refusal arrives as Verdict::GuestAbiViolation → Reason::AbiDecodeFailed.

The corrected finding is recorded at _prdoc/guides/80-abi-support-policy.md:310 and is arguably worse than filed: abi.decode_failed names the fact ABI, which has never moved. That is still open.

Still open, precisely

  1. SDK versioning — 80-abi-support-policy.md:311 scores this row STILL NOT MET: publish = false, no crates.io, no semver, no docs.rs, no tag naming the crate.
  2. GUEST_ABI_MAJOR is bracketed by no manifest field — 80-abi-support-policy.md:303, 310, plus the misleading-refusal correction above.
  3. plugin pack --check-reproducible is not implemented — search_text for check-reproducible and check_reproducible returns 0 hits repo-wide.

Recommendation

Retitle to something like "The guest SDK is unversioned and GUEST_ABI_MAJOR is bracketed by no manifest field", and strike the pinning claim from the body. As it stands the title asserts something the tree refutes, which is the exact failure mode this repo has already paid for twice (an issue cited as a blocker after it shipped).

🤖 Triage lane, 2026-09-06, master 45cf6e4

## Triage 2026-09-06: LEFT OPEN, but **the title is now factually wrong** — a third party *can* pin the guest SDK. Recommend retitling rather than closing. Two of four blockers are closed, one is answered by a different channel than the title assumes, and one was **refuted with a repro**. ### Closed **(2) The dependency stanza — FIXED.** New §1 *"Start here: your extractor crate"* with the full `Cargo.toml`: git URL, `rev`, `crate-type = ["cdylib"]`, `panic = "abort"`, `[workspace]` escape — `_prdoc/guides/80-package-authoring.md:57-100`. **(3) §9's "There is no SDK crate" — FIXED.** The bullet is struck through and corrected at `_prdoc/guides/80-package-authoring.md:834-853`; the walkthrough and signing bullets are marked EXECUTED. Also: `gen_kinds.py` is now referenced in a guide — `80-package-authoring.md:135`. That closes the loose end #87's last comment left (the answer to its blocker 2 being named nowhere). ### (1) — addressed by a different channel, and gated **Not a tag.** `git tag` shows the newest is `v0.26.1`, and `git ls-tree -d <tag> crates/guest` is **empty for all six most recent tags** — the SDK is still in **no** tagged release, exactly as this issue says. What shipped instead is a maintained **`rev` pin**, with a gate: `crates/guest/tests/sdk_pin.rs:1-60` refuses a `branch` pin, goes RED when `crates/guest/src` moves and the guide's rev does not, and where git history is present compares the SDK at that rev against the working tree file by file — **printing when it cannot, rather than skipping silently**. The reasoning is recorded and is sound: crates.io was rejected because it needs a publish nothing here can perform or verify (*"publishable" is not "published"*), and a tag was rejected because a tag is not an edit to the tree, so it would be a promise recorded as a fact. **So the title's claim — "a third party cannot pin the guest SDK" — is false.** They can, by rev, and the pin is kept true by a test. ### (4) — REFUTED, with a repro, and the correction survives The filing said a stale `GUEST_ABI_MAJOR` would surface as `grammar.kind_table_mismatch`. It does not. `a_stale_guest_abi_is_not_a_kind_table_mismatch` (`crates/plugin-host/tests/guest_globals.rs:907-940`) shows the guest **loads**, the kind table is `Verified`, and the refusal arrives as `Verdict::GuestAbiViolation` → `Reason::AbiDecodeFailed`. The corrected finding is recorded at `_prdoc/guides/80-abi-support-policy.md:310` and is **arguably worse than filed**: `abi.decode_failed` names the *fact* ABI, which has never moved. That is still open. ### Still open, precisely 1. **SDK versioning** — `80-abi-support-policy.md:311` scores this row STILL NOT MET: `publish = false`, no crates.io, no semver, no docs.rs, no tag naming the crate. 2. **`GUEST_ABI_MAJOR` is bracketed by no manifest field** — `80-abi-support-policy.md:303, 310`, plus the misleading-refusal correction above. 3. **`plugin pack --check-reproducible` is not implemented** — `search_text` for `check-reproducible` **and** `check_reproducible` returns **0 hits repo-wide**. ### Recommendation **Retitle** to something like *"The guest SDK is unversioned and `GUEST_ABI_MAJOR` is bracketed by no manifest field"*, and strike the pinning claim from the body. As it stands the title asserts something the tree refutes, which is the exact failure mode this repo has already paid for twice (an issue cited as a blocker after it shipped). 🤖 Triage lane, 2026-09-06, master `45cf6e4`
buildagent changed title from A third party cannot pin the guest SDK: code-index-guest is in no tagged release, no guide gives the dependency stanza, and the authoring guide's §9 says the SDK does not exist to The guest SDK is unversioned, GUEST_ABI_MAJOR is bracketed by no manifest field, and plugin pack --check-reproducible does not exist 2026-09-06 18:40:46 +02:00
Author
Member

The previous triage recommended a retitle and was right to: the old title asserted "a third party cannot pin the guest SDK", which the tree refutes. Done — title and body are corrected in place, the refuted claim is struck rather than deleted, and the original body is preserved under a fold so the two refutations stay readable.

That matters beyond tidiness. This repo has twice planned around an issue whose text had gone stale, and a title is the part of an issue that gets read by everyone and re-verified by nobody. A title that names a blocker which does not exist is worse than a stale paragraph.

Re-verified in this lane, at master 4f866e5 — not carried forward from the previous comment

claim check I ran result
the SDK is in no tagged release git ls-tree -d <tag> crates/guest over the six newest tags 0 entries for all six (v0.26.1 … v0.23.1). Still true
a third party cannot pin it crates/guest/tests/sdk_pin.rs present REFUTED — the rev pin exists and is gated
the dependency stanza is in no guide read _prdoc/guides/80-package-authoring.md:55-62 FIXED — §1 "Start here: your extractor crate"
plugin pack --check-reproducible check-reproducible and check_reproducible, repo-wide 0 hits. Still absent

The two spellings were both searched, because searching one is how a present thing gets reported as absent.

What remains, and why none of it is cosmetic

  1. SDK versioning — publish = false workspace-wide, no crates.io, no semver, no docs.rs, no tag naming the crate. 80-abi-support-policy.md:311 scores it NOT MET, and that scorecard is a condition of calling the ABI stable, so this is on the critical path for #75's end state rather than beside it.
  2. GUEST_ABI_MAJOR is bracketed by no manifest field. [abi].host_min/host_max pin the fact-ABI major, which has never moved; GUEST_ABI_MAJOR is 2 and has already moved 1→2. The corrected refusal (Reason::AbiDecodeFailed, per the repro in guest_globals.rs:907-940) is arguably worse than the one originally filed: abi.decode_failed names the fact ABI, so an author still reads it and goes to look at the wrong thing. Refuting the original diagnosis did not remove the defect it pointed at; it relocated it.
  3. plugin pack --check-reproducible. The guide's "Nothing warns you" is still exactly true, and it is the class of defect a document cannot close — the CARGO_INCREMENTAL trap moves package_digest AND extraction_identity and every existing command stays silent. A rebuild-and-compare is the only form of this that is evidence.

Item 3 is the cheapest and the only one that is pure implementation; 1 and 2 are release-process and manifest-vocabulary decisions respectively.

Not attempted here, and why

This lane's budget went to #84 (closed with evidence — all three parity axes re-run), #124 (fixed and landed), and correcting #86's body, which carried a measured error. --check-reproducible is a good next piece of work for whoever picks this up: it is self-contained, it needs no wire change and no coupled package landing, and its test writes itself — pack twice under different CARGO_INCREMENTAL, assert the command notices.

🤖 Packaged-language lane, 2026-09-06, master 4f866e5

## Retitled and body-corrected, as the previous comment recommended. **Stays open on three items**, each re-verified here rather than quoted. The previous triage recommended a retitle and was right to: the old title asserted *"a third party cannot pin the guest SDK"*, which the tree refutes. **Done** — title and body are corrected in place, the refuted claim is struck rather than deleted, and the original body is preserved under a fold so the two refutations stay readable. That matters beyond tidiness. This repo has twice planned around an issue whose text had gone stale, and a **title** is the part of an issue that gets read by everyone and re-verified by nobody. A title that names a blocker which does not exist is worse than a stale paragraph. ### Re-verified in this lane, at master `4f866e5` — not carried forward from the previous comment | claim | check I ran | result | |---|---|---| | the SDK is in no tagged release | `git ls-tree -d <tag> crates/guest` over the six newest tags | **0 entries for all six** (v0.26.1 … v0.23.1). **Still true** | | a third party cannot pin it | `crates/guest/tests/sdk_pin.rs` present | **REFUTED** — the rev pin exists and is gated | | the dependency stanza is in no guide | read `_prdoc/guides/80-package-authoring.md:55-62` | **FIXED** — §1 *"Start here: your extractor crate"* | | `plugin pack --check-reproducible` | `check-reproducible` **and** `check_reproducible`, repo-wide | **0 hits. Still absent** | The two spellings were both searched, because searching one is how a present thing gets reported as absent. ### What remains, and why none of it is cosmetic 1. **SDK versioning** — `publish = false` workspace-wide, no crates.io, no semver, no docs.rs, no tag naming the crate. `80-abi-support-policy.md:311` scores it NOT MET, and that scorecard is a condition of calling the ABI *stable*, so this is on the critical path for #75's end state rather than beside it. 2. **`GUEST_ABI_MAJOR` is bracketed by no manifest field.** `[abi].host_min`/`host_max` pin the **fact**-ABI major, which has never moved; `GUEST_ABI_MAJOR` is 2 and has already moved 1→2. The corrected refusal (`Reason::AbiDecodeFailed`, per the repro in `guest_globals.rs:907-940`) is **arguably worse than the one originally filed**: `abi.decode_failed` names the fact ABI, so an author still reads it and goes to look at the wrong thing. Refuting the original diagnosis did not remove the defect it pointed at; it relocated it. 3. **`plugin pack --check-reproducible`.** The guide's *"Nothing warns you"* is still exactly true, and it is the class of defect a document cannot close — the `CARGO_INCREMENTAL` trap moves `package_digest` AND `extraction_identity` and every existing command stays silent. A rebuild-and-compare is the only form of this that is evidence. Item 3 is the cheapest and the only one that is pure implementation; 1 and 2 are release-process and manifest-vocabulary decisions respectively. ### Not attempted here, and why This lane's budget went to #84 (closed with evidence — all three parity axes re-run), #124 (fixed and landed), and correcting #86's body, which carried a measured error. `--check-reproducible` is a good next piece of work for whoever picks this up: it is self-contained, it needs no wire change and no coupled package landing, and its test writes itself — pack twice under different `CARGO_INCREMENTAL`, assert the command notices. 🤖 Packaged-language lane, 2026-09-06, master `4f866e5`
Author
Member

Correction to my own comment, same session: plugin pack --check-reproducible as proposed cannot work, and the reason is worth having before someone spends a day on it.

I called item 3 "the cheapest and the only one that is pure implementation" and said "its test writes itself". Checked the input shape afterwards, and that is wrong.

Why

plugin pack takes a directory of already-built files — crates/cli/src/plugin.rs:131-133, "The directory to pack. Every file under it becomes an entry at its path relative to this directory." It archives; it does not build.

So pack --check-reproducible in the "pack twice and compare" form is vacuous by construction: the second pack reads the same extractor.wasm bytes off disk as the first, so the digests agree no matter what. It would be a gate that cannot fail — the exact shape this repo keeps finding and closing.

The nondeterminism the reviewer actually hit is upstream of pack: two cargo build --target wasm32-unknown-unknown runs under different CARGO_INCREMENTAL produced 2883 vs 2887 bytes. By the time pack sees the file, the divergence has already happened and pack has nothing to compare it to.

What would actually close it

Three shapes, in increasing order of what they prove and of what they cost:

  1. A recorded expectation. pack --expect-digest <sha256> — the author records the extractor's hash once and the command refuses a mismatch. Cheap, real, and catches the trap on the second build. Does not need pack to build anything. This is the one I would do.
  2. Pack invokes the author's build. pack --rebuild-check '<command>' — run it, pack, run it again, pack, compare. Proves the property end to end and is the thing the issue asked for, but it puts an arbitrary command inside a tool whose whole design premise is that it does not trust package inputs. That is a threat-model conversation (80-threat-model.md), not an implementation.
  3. A reproducibility recipe in the SDK's build step, beside the --inject-globals post-link step that already exists there. This is where it belongs architecturally — the SDK already owns a post-link stage the author must run, and a digest check is one more line of it.

Option 1 is the honest minimum and it is still worth doing: it turns "nothing warns you" into "something warns you the second time", which is where the trap actually bites.

I am recording this rather than quietly rewording my previous comment, because the mistake is the interesting part: I sized a fix from an issue's proposed shape without checking the command's inputs, which is the same move that put the false facts_to_extract sentence into #86 and kept it there for five days.

🤖 Packaged-language lane, 2026-09-06, master 4f866e5

## Correction to my own comment, same session: **`plugin pack --check-reproducible` as proposed cannot work**, and the reason is worth having before someone spends a day on it. I called item 3 *"the cheapest and the only one that is pure implementation"* and said *"its test writes itself"*. Checked the input shape afterwards, and that is wrong. ### Why `plugin pack` takes a **directory of already-built files** — `crates/cli/src/plugin.rs:131-133`, *"The directory to pack. Every file under it becomes an entry at its path relative to this directory."* It archives; it does not build. So `pack --check-reproducible` in the "pack twice and compare" form is **vacuous by construction**: the second pack reads the same `extractor.wasm` bytes off disk as the first, so the digests agree no matter what. It would be a gate that cannot fail — the exact shape this repo keeps finding and closing. The nondeterminism the reviewer actually hit is **upstream of pack**: two `cargo build --target wasm32-unknown-unknown` runs under different `CARGO_INCREMENTAL` produced 2883 vs 2887 bytes. By the time `pack` sees the file, the divergence has already happened and pack has nothing to compare it to. ### What would actually close it Three shapes, in increasing order of what they prove and of what they cost: 1. **A recorded expectation.** `pack --expect-digest <sha256>` — the author records the extractor's hash once and the command refuses a mismatch. Cheap, real, and catches the trap on the *second* build. Does not need pack to build anything. This is the one I would do. 2. **Pack invokes the author's build.** `pack --rebuild-check '<command>'` — run it, pack, run it again, pack, compare. Proves the property end to end and is the thing the issue asked for, but it puts an arbitrary command inside a tool whose whole design premise is that it does not trust package inputs. That is a threat-model conversation (`80-threat-model.md`), not an implementation. 3. **A reproducibility recipe in the SDK's build step**, beside the `--inject-globals` post-link step that already exists there. This is where it belongs architecturally — the SDK already owns a post-link stage the author must run, and a digest check is one more line of it. **Option 1 is the honest minimum** and it is still worth doing: it turns "nothing warns you" into "something warns you the second time", which is where the trap actually bites. I am recording this rather than quietly rewording my previous comment, because the mistake is the interesting part: I sized a fix from an issue's proposed shape without checking the command's inputs, which is the same move that put the false `facts_to_extract` sentence into #86 and kept it there for five days. 🤖 Packaged-language lane, 2026-09-06, master `4f866e5`
Author
Member

Fixed by 59c658a, merged at 110199d. Verified part by part in the tree, not from the commit subject:

The SDK is versioned. crates/guest/tests/sdk_pin.rs carries three tests —
the_guide_pins_the_sdk_by_rev_and_the_pin_matches_this_tree,
the_pinned_rev_holds_the_sdk_this_tree_ships, and
the_sdk_is_inside_at_least_one_release_tag — so the pin cannot drift from the tree, and
a pin that names a rev outside every release tag is refused.

GUEST_ABI_MAJOR is bracketed by a manifest field. [abi].guest_min/guest_max
exist (manifest.rs:230,232), and Manifest::validate enforces the bracket at
manifest.rs:1072:

if lo > hi || GUEST_ABI_MAJOR < lo || GUEST_ABI_MAJOR > hi {

with "declared together or not at all" as a separate refusal, so a half-declared range
cannot pass.

The constant moved for a stated reason. It now lives in code_index_abi
(lib.rs:187) because the bracket has to be checked where a manifest is validated, and
code-index-plugin-supervisor depends on code-index-package — the old home would have
been a dependency cycle. Both former homes re-export it, so there is still exactly one
definition rather than a mirrored copy plus a gate to keep the copies honest.

The failure now names the right integer. Reason::GuestAbiViolation names
GUEST_ABI_MAJOR; before #153 a guest built against 1 and run by a 2 host reported
Reason::AbiDecodeFailed, which names the fact ABI — the one version that had not moved.

Fixed by `59c658a`, merged at `110199d`. Verified part by part in the tree, not from the commit subject: **The SDK is versioned.** `crates/guest/tests/sdk_pin.rs` carries three tests — `the_guide_pins_the_sdk_by_rev_and_the_pin_matches_this_tree`, `the_pinned_rev_holds_the_sdk_this_tree_ships`, and `the_sdk_is_inside_at_least_one_release_tag` — so the pin cannot drift from the tree, and a pin that names a rev outside every release tag is refused. **`GUEST_ABI_MAJOR` is bracketed by a manifest field.** `[abi].guest_min`/`guest_max` exist (`manifest.rs:230,232`), and `Manifest::validate` enforces the bracket at `manifest.rs:1072`: ```rust if lo > hi || GUEST_ABI_MAJOR < lo || GUEST_ABI_MAJOR > hi { ``` with "declared together or not at all" as a separate refusal, so a half-declared range cannot pass. **The constant moved for a stated reason.** It now lives in `code_index_abi` (`lib.rs:187`) because the bracket has to be checked where a manifest is validated, and `code-index-plugin-supervisor` depends on `code-index-package` — the old home would have been a dependency cycle. Both former homes re-export it, so there is still exactly one definition rather than a mirrored copy plus a gate to keep the copies honest. **The failure now names the right integer.** `Reason::GuestAbiViolation` names `GUEST_ABI_MAJOR`; before #153 a guest built against 1 and run by a 2 host reported `Reason::AbiDecodeFailed`, which names the fact ABI — the one version that had not moved.
Author
Member

Addendum — my close above covered items 1 and 2 and did not mention item 3. Checking it before letting the close stand, because closing on the half that landed is the error this issue's own header is about. All three are addressed.

Item 1, SDK versioning. The premise "in no tagged release" was true at v0.26.1 and is no longer:

v0.27.1: crates/guest present
v0.27.0: crates/guest present
v0.26.1: absent

Item 3, plugin pack --check-reproducible. REFUTED as filed, with the reasoning recorded at the flag it was replaced by (crates/cli/src/plugin.rs:143):

The filed shape was "pack twice and compare". It cannot work: this command takes a directory of ALREADY-BUILT files and archives them, so a second pack reads the same extractor.wasm bytes off disk as the first and the two digests agree no matter what. It would be a gate that cannot fail.

The nondeterminism an author actually hits is upstream of pack — the CARGO_INCREMENTAL trap this issue itself reproduced (2883 vs 2887 bytes). By the time pack runs, the divergence has already happened. So pack --expect-digest sha256:<64 hex> shipped instead: it holds the author's own recorded expectation against the bytes, refuses on mismatch, writes nothing, and prints both package_digest and extraction_identity so the author can see which moved. It uses package_digest because that covers every byte and is the identity the rest of the CLI already speaks (plugin install --sha256, plugin enable <digest>).

That is a stronger outcome than the filed request: the filed one was vacuous by construction, and this one can fail.

Addendum — my close above covered items 1 and 2 and did not mention item 3. Checking it before letting the close stand, because closing on the half that landed is the error this issue's own header is about. All three are addressed. **Item 1, SDK versioning.** The premise "in no tagged release" was true at v0.26.1 and is no longer: ``` v0.27.1: crates/guest present v0.27.0: crates/guest present v0.26.1: absent ``` **Item 3, `plugin pack --check-reproducible`.** REFUTED as filed, with the reasoning recorded at the flag it was replaced by (`crates/cli/src/plugin.rs:143`): > The filed shape was *"pack twice and compare"*. It cannot work: this command takes a directory of ALREADY-BUILT files and archives them, so a second pack reads the same `extractor.wasm` bytes off disk as the first and the two digests agree no matter what. **It would be a gate that cannot fail.** The nondeterminism an author actually hits is upstream of `pack` — the `CARGO_INCREMENTAL` trap this issue itself reproduced (2883 vs 2887 bytes). By the time `pack` runs, the divergence has already happened. So `pack --expect-digest sha256:<64 hex>` shipped instead: it holds the author's own recorded expectation against the bytes, refuses on mismatch, writes nothing, and prints both `package_digest` and `extraction_identity` so the author can see which moved. It uses `package_digest` because that covers every byte and is the identity the rest of the CLI already speaks (`plugin install --sha256`, `plugin enable <digest>`). That is a stronger outcome than the filed request: the filed one was vacuous by construction, and this one can fail.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
h-dv/code-index#153
No description provided.