1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! **holger's package-handler plugin contract** — the one wire format a handler
//! speaks whether it is linked into the server or loaded from a `.wasm` at
//! startup.
//!
//! Rickard's ask was that *every package handler should be writable as native or
//! WASM*. Before this crate holger had no wasm path at all: the sixteen
//! repository backends are unconditional path dependencies of `server/lib`, so
//! adding a package handler meant editing `server/lib/Cargo.toml` and rebuilding
//! the server.
//!
//! # The two seams
//!
//! A `RepositoryBackendTrait` is a Rust trait object and cannot cross a wasm
//! boundary, so this crate splits a handler in two along the line that actually
//! matters:
//!
//! * [`PackageHandler`] — **the format logic**: coordinate parsing, the HTTP
//! path scheme, listing, content types. This is what varies per ecosystem and
//! what a plugin author writes. It is pure and target-independent, so the one
//! implementation compiles for the host *and* for `wasm32-unknown-unknown`.
//! * [`BlobStore`] — **the bytes**. A wasm module has no filesystem and no
//! socket. The host owns storage and hands it in.
//!
//! That split is what makes "the wasm crate is ABI glue only" true rather than
//! aspirational: the native backend implements `BlobStore` over the filesystem,
//! the wasm shim implements it over four host imports, and *both* call the same
//! `PackageHandler`. It is the same shape as znippy's maven pair, where
//! `host-decompressors` compiles the threaded `ljar` fan-out out of the wasm
//! build and leaves the single-threaded `linflate` both sides share — same
//! logic, different plumbing underneath.
//!
//! # Which verbs cross, and why
//!
//! `RepositoryBackendTrait` (`traits/src/lib.rs:404`) has twelve methods. Six
//! cross:
//!
//! | verb | why it is in |
//! |---|---|
//! | `plugin_manifest` | the module must name itself, or registration needs a config file and the whole point of scanning a directory is lost |
//! | `fetch` | the read. Non-defaulted on the trait. |
//! | `put` | the write. Non-defaulted on the trait. |
//! | `list` | enumeration; the UI and retention planner both read it |
//! | `coordinate_for_path` | the serve-time quarantine gate calls it before bytes go out, and it is pure path logic — the cheapest and most format-specific thing a handler owns |
//! | `handle_http2_request` | non-defaulted, and it is the actual door a package client knocks on. A handler that could not answer it would not be a package handler. |
//!
//! Six do not, and the host supplies the trait's own default for each:
//!
//! | verb | why it is out |
//! |---|---|
//! | `name` / `format` / `is_writable` | constants. Read once from the manifest at load, not per call. |
//! | `archive_files` / `archive_info` / `has_archive` | these describe a *znippy archive handle*. A wasm module has none, and the trait already defaults them to "no archive" — which is the truth here, not a stub. |
//! | `delete_artifact` | the trait default fails **closed**, and the contract requires the implementer to recompute the stored digest and refuse a mismatch. A sandboxed module cannot be trusted to have done that, and a wrong answer deletes bytes. Failing closed is the correct answer, not a missing feature. |
//!
//! # ABI shape
//!
//! Every exported verb takes a `(ptr, len)` pair into linear memory and returns
//! a pointer; the byte length of that result is read back with `result_len()`,
//! exactly as znippy's loader does. Payloads are length-prefixed
//! ([`codec`]) — never JSON, whose separators and missing null representation
//! are a corruption source rather than a parser bug.
pub use ;
pub use ;
/// Exports a `.wasm` package handler must provide. The host names the missing
/// one when a module falls short, so a half-built module is a loud load failure
/// rather than a backend that answers `None` to everything.
/// Host functions a module imports from `env` to reach storage. The host owns
/// every byte; the module owns only the format logic.
///
/// All four return a packed `u64`: `ptr << 32 | len`, and **`0` means the host
/// call itself failed** — never "absent". Absence is carried inside the payload
/// as an encoded `Option`, so a stored empty blob and a missing key stay
/// distinguishable (the exact distinction znippy's JSON row format cannot make).
/// The bytes behind a handler. Implemented **natively** over a directory and
/// **in wasm** over the [`imports`] host functions — the handler logic above it
/// never learns which.
///
/// Keys are handler-chosen store paths (`bundles/tillsynia-1.4.znippy`), not
/// filesystem paths: the host is free to place them wherever it likes and a
/// module never sees an absolute path.
/// **The format logic of one package handler.** Everything here is pure over a
/// [`BlobStore`], so the single implementation compiles for the host and for
/// `wasm32-unknown-unknown` unchanged. A handler crate implements this once; the
/// wasm crate beside it adds no logic, only the ABI shim.