The guest SDK is unversioned, GUEST_ABI_MAJOR is bracketed by no manifest field, and plugin pack --check-reproducible does not exist #153
Labels
No labels
code-review
correctness
dos
performance
security
severity/high
severity/low
severity/medium
tech-debt
Kind/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
h-dv/code-index#153
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Verified in this lane, at master
4f866e5_prdoc/guides/80-package-authoring.md:57+, §1 "Start here: your extractor crate" — read directlycrates/guest/tests/sdk_pin.rsexists: it refuses abranchpin and reddens whencrates/guest/srcmoves and the guide's rev does notGUEST_ABI_MAJORsurfaces asgrammar.kind_table_mismatchplugin-host/tests/guest_globals.rs::a_stale_guest_abi_is_not_a_kind_table_mismatch— the refusal isVerdict::GuestAbiViolation→Reason::AbiDecodeFailedcrates/guestis in no tagged releasegit ls-tree -d <tag> crates/guestis 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 quotedplugin pack --check-reproduciblecheck-reproducibleandcheck_reproducible: 0 hits repo-wide — re-run hereThe distinction the old title missed: "in no tagged release" is true; "cannot pin" is not. A maintained
revpin 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
_prdoc/guides/80-abi-support-policy.md:311scores this row STILL NOT MET:publish = falseworkspace-wide, no crates.io, no semver, no docs.rs, no tag naming the crate.GUEST_ABI_MAJORis bracketed by no manifest field.[abi].host_min/host_maxpin the fact-ABI major, which has never moved;GUEST_ABI_MAJORis 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.plugin pack --check-reproducibleis not implemented. TheCARGO_INCREMENTALtrap reproduces on a real guest (2883 vs 2887 bytes, differentpackage_digestANDextraction_identity) andpack/validate/inspectsay 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}.share 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, producingsymbols: [(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
crates/guestlanded on master in1aa6514, afterv0.26.1. The line a third party types first —— fails with
no matching package named code-index-guest found. Onlybranch = "master"or a rawrevworks.publish = falseworkspace-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
All of_prdoc/guides/contains four mentions ofcode-index-guest/crates/guestand not oneCargo.tomlstanza, git URL,crate-type = ["cdylib"],panic = "abort",[workspace]escape, orrustup target add wasm32-unknown-unknown. The reviewer had to copycrates/guest/example/Cargo.tomlfrom a checkout. A stranger without the repo cloned cannot start.3.
80-package-authoring.md§9 (line 660) says "There is no SDK crate."The same file at lines 85, 430 and 441 callscrates/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_MAJORis 2, has already moved 1→2, and no manifest field brackets it[abi].host_min/host_maxpin 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-globalsrefuses 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 isThe 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 wrongextractshape 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.shrecords the consequence:tree-sitter-rubyspellscallwith four distinct ids andassignmentwith two. A builtin comparingnode.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 at80-package-authoring.md:135.)And there is no reproducibility tooling: the reviewer reproduced the
CARGO_INCREMENTALtrap on their own guest (2883 vs 2887 bytes, differentpackage_digestANDextraction_identity) andpack/validate/inspectsaid 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 compiledcrates/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}.share 196 and 172 lines of near-duplicate.Related
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.pyis 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 tagshows the newest isv0.26.1, andgit ls-tree -d <tag> crates/guestis 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
revpin, with a gate:crates/guest/tests/sdk_pin.rs:1-60refuses abranchpin, goes RED whencrates/guest/srcmoves 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_MAJORwould surface asgrammar.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 isVerified, and the refusal arrives asVerdict::GuestAbiViolation→Reason::AbiDecodeFailed.The corrected finding is recorded at
_prdoc/guides/80-abi-support-policy.md:310and is arguably worse than filed:abi.decode_failednames the fact ABI, which has never moved. That is still open.Still open, precisely
80-abi-support-policy.md:311scores this row STILL NOT MET:publish = false, no crates.io, no semver, no docs.rs, no tag naming the crate.GUEST_ABI_MAJORis bracketed by no manifest field —80-abi-support-policy.md:303, 310, plus the misleading-refusal correction above.plugin pack --check-reproducibleis not implemented —search_textforcheck-reproducibleandcheck_reproduciblereturns 0 hits repo-wide.Recommendation
Retitle to something like "The guest SDK is unversioned and
GUEST_ABI_MAJORis 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
45cf6e4code-index://docs/reason-codesis at 3,979 of its 4,000-token cap, so the next reason code this project mints cannot be documented #184A 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 existto The guest SDK is unversioned,GUEST_ABI_MAJORis bracketed by no manifest field, andplugin pack --check-reproducibledoes not existRetitled 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 commentgit ls-tree -d <tag> crates/guestover the six newest tagscrates/guest/tests/sdk_pin.rspresent_prdoc/guides/80-package-authoring.md:55-62plugin pack --check-reproduciblecheck-reproducibleandcheck_reproducible, repo-wideThe 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
publish = falseworkspace-wide, no crates.io, no semver, no docs.rs, no tag naming the crate.80-abi-support-policy.md:311scores 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.GUEST_ABI_MAJORis bracketed by no manifest field.[abi].host_min/host_maxpin the fact-ABI major, which has never moved;GUEST_ABI_MAJORis 2 and has already moved 1→2. The corrected refusal (Reason::AbiDecodeFailed, per the repro inguest_globals.rs:907-940) is arguably worse than the one originally filed:abi.decode_failednames 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.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 — theCARGO_INCREMENTALtrap movespackage_digestANDextraction_identityand 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-reproducibleis 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 differentCARGO_INCREMENTAL, assert the command notices.🤖 Packaged-language lane, 2026-09-06, master
4f866e5Correction to my own comment, same session:
plugin pack --check-reproducibleas 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 packtakes 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-reproduciblein the "pack twice and compare" form is vacuous by construction: the second pack reads the sameextractor.wasmbytes 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-unknownruns under differentCARGO_INCREMENTALproduced 2883 vs 2887 bytes. By the timepacksees 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:
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.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.--inject-globalspost-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_extractsentence into #86 and kept it there for five days.🤖 Packaged-language lane, 2026-09-06, master
4f866e5Fixed by
59c658a, merged at110199d. Verified part by part in the tree, not from the commit subject:The SDK is versioned.
crates/guest/tests/sdk_pin.rscarries three tests —the_guide_pins_the_sdk_by_rev_and_the_pin_matches_this_tree,the_pinned_rev_holds_the_sdk_this_tree_ships, andthe_sdk_is_inside_at_least_one_release_tag— so the pin cannot drift from the tree, anda pin that names a rev outside every release tag is refused.
GUEST_ABI_MAJORis bracketed by a manifest field.[abi].guest_min/guest_maxexist (
manifest.rs:230,232), andManifest::validateenforces the bracket atmanifest.rs:1072: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, andcode-index-plugin-supervisordepends oncode-index-package— the old home would havebeen 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::GuestAbiViolationnamesGUEST_ABI_MAJOR; before #153 a guest built against 1 and run by a 2 host reportedReason::AbiDecodeFailed, which names the fact ABI — the one version that had not moved.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:
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 nondeterminism an author actually hits is upstream of
pack— theCARGO_INCREMENTALtrap this issue itself reproduced (2883 vs 2887 bytes). By the timepackruns, the divergence has already happened. Sopack --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 bothpackage_digestandextraction_identityso the author can see which moved. It usespackage_digestbecause 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.