awrtifact 0.2.2__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. {awrtifact-0.2.2 → awrtifact-0.2.3}/PKG-INFO +36 -2
  2. {awrtifact-0.2.2 → awrtifact-0.2.3}/README.md +35 -1
  3. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/__init__.py +1 -1
  4. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/_doctor.py +33 -4
  5. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/ghapi.py +4 -4
  6. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/serve_spec.py +13 -0
  7. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/spec.py +26 -8
  8. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/worker_template.py +112 -2
  9. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact.egg-info/PKG-INFO +36 -2
  10. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact.egg-info/SOURCES.txt +1 -0
  11. {awrtifact-0.2.2 → awrtifact-0.2.3}/pyproject.toml +1 -1
  12. {awrtifact-0.2.2 → awrtifact-0.2.3}/tests/test_serve_spec.py +46 -0
  13. awrtifact-0.2.3/tests/test_share_route.py +133 -0
  14. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/__main__.py +0 -0
  15. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/backup.py +0 -0
  16. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/cli.py +0 -0
  17. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/data/gobbonet/__init__.py +0 -0
  18. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/data/gobbonet/backup-gate.html +0 -0
  19. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/data/gobbonet/gobbonet-backup.js +0 -0
  20. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/data/workflows/__init__.py +0 -0
  21. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/data/workflows/hash-release-object.yml +0 -0
  22. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/data/workflows/mirror-hf-set.yml +0 -0
  23. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/data/workflows/mirror-hf-to-release.yml +0 -0
  24. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/data/workflows/mirror-to-release.yml +0 -0
  25. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/fetch.py +0 -0
  26. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/fetchset.py +0 -0
  27. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/gh.py +0 -0
  28. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/hashes.py +0 -0
  29. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/hf.py +0 -0
  30. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/manifest.py +0 -0
  31. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/mirror.py +0 -0
  32. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/mirrorset.py +0 -0
  33. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/plan.py +0 -0
  34. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/provision.py +0 -0
  35. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/sealing.py +0 -0
  36. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/split.py +0 -0
  37. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/upload.py +0 -0
  38. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact/verify.py +0 -0
  39. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact.egg-info/dependency_links.txt +0 -0
  40. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact.egg-info/entry_points.txt +0 -0
  41. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact.egg-info/requires.txt +0 -0
  42. {awrtifact-0.2.2 → awrtifact-0.2.3}/awrtifact.egg-info/top_level.txt +0 -0
  43. {awrtifact-0.2.2 → awrtifact-0.2.3}/setup.cfg +0 -0
  44. {awrtifact-0.2.2 → awrtifact-0.2.3}/tests/test_fetch.py +0 -0
  45. {awrtifact-0.2.2 → awrtifact-0.2.3}/tests/test_fetchset.py +0 -0
  46. {awrtifact-0.2.2 → awrtifact-0.2.3}/tests/test_ghapi.py +0 -0
  47. {awrtifact-0.2.2 → awrtifact-0.2.3}/tests/test_hf.py +0 -0
  48. {awrtifact-0.2.2 → awrtifact-0.2.3}/tests/test_manifest.py +0 -0
  49. {awrtifact-0.2.2 → awrtifact-0.2.3}/tests/test_mirror_hf.py +0 -0
  50. {awrtifact-0.2.2 → awrtifact-0.2.3}/tests/test_mirrorset.py +0 -0
  51. {awrtifact-0.2.2 → awrtifact-0.2.3}/tests/test_provision.py +0 -0
  52. {awrtifact-0.2.2 → awrtifact-0.2.3}/tests/test_sealing.py +0 -0
  53. {awrtifact-0.2.2 → awrtifact-0.2.3}/tests/test_split_verify.py +0 -0
  54. {awrtifact-0.2.2 → awrtifact-0.2.3}/tests/test_upload.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: awrtifact
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Aither World Artifact — deliberately chunk artifacts into GitHub release assets and fetch them back byte-verified. The productized aitherkvcache mirror lane.
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/Aitherium/awrtifact
@@ -20,7 +20,7 @@ Requires-Dist: awseal>=0.1; extra == "seal"
20
20
 
21
21
  **[Docs](https://aitherium.github.io/awrtifact/)** · [Source](https://github.com/Aitherium/awrtifact) · `pip install awrtifact` · [The Aither World](https://aitherium.github.io/)
22
22
 
23
- > **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awrtifact** is one of its 46 bricks — each installs on its own, runs offline, and needs no account.
23
+ > **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awrtifact** is one of its 65 bricks — each installs on its own, runs offline, and needs no account.
24
24
  >
25
25
  > **Start here:** Store one artifact as a versioned GitHub release and fetch it back byte-verified — `awrtifact mirror <URL|FILE> --release TAG`.
26
26
 
@@ -190,14 +190,17 @@ Each installs on its own, works offline, and needs no account.
190
190
  | [awskills](https://github.com/Aitherium/awskills) | that an agent knows your procedure | the procedure written down, versioned, and loadable by any agent |
191
191
  | [awpack](https://github.com/Aitherium/awpack) | that the pack you want shipped inside somebody's SDK, under whatever licence that SDK happens to carry | the pack as its own versioned artifact, with its own licence, that any agent runtime can install |
192
192
  | [awm](https://github.com/Aitherium/awm) | that memory stayed in its lane | tenant:user:project scopes, so a write cannot cross a boundary |
193
+ | [awdesk](https://github.com/Aitherium/awdesk) | that the agent is somewhere behind a browser tab | a tray icon, a face on your desktop, and the decision card that pops when it needs you |
193
194
  | [awnode](https://github.com/Aitherium/awnode) | a vendor's cloud with every prompt | a local gateway routing to backends you chose |
194
195
  | [awgraph](https://github.com/Aitherium/awgraph) | that grep found everything | an AST + tree-sitter call graph an agent can traverse |
195
196
  | [awgit](https://github.com/Aitherium/awgit) | that no one else is editing this file | a lease, refused at commit time if you do not hold it |
196
197
  | [awdelphi](https://github.com/Aitherium/awdelphi) | one agent's confident take on a decision | the round trace, the anonymity, and who dissents |
198
+ | [awclassify](https://github.com/Aitherium/awclassify) | a filename, a folder, or whoever last touched it | doc_type, visibility, audience and topics, with the evidence lines that decided each |
197
199
  | [awtoll](https://github.com/Aitherium/awtoll) | that your tooling is saving you context | the measured token cost of each tool call, and what the alternative cost |
198
200
  | [awseal](https://github.com/Aitherium/awseal) | that the artifact came from who you think | an Ed25519 seal — the key that verifies is not the key that forges |
199
201
  | [awshare](https://github.com/Aitherium/awshare) | that the download is intact | content-addressed bundles, verified on fetch |
200
202
  | [awnest](https://github.com/Aitherium/awnest) | that there is a person on the other end | a verdict with evidence, where "we could not tell" is not "yes" |
203
+ | [awrena](https://github.com/Aitherium/awrena) | a leaderboard someone can edit, and votes nobody counted | a scored duel with both answers kept, and a result bound to them |
201
204
  | [awnboard](https://github.com/Aitherium/awnboard) | a share link anyone who sees it can use | an invitation addressed to one person, for one gate, revocable |
202
205
  | [awnix](https://github.com/Aitherium/awnix) | that the box is what you left it as | an immutable image you built, with atomic rollback |
203
206
  | [awrecover](https://github.com/Aitherium/awrecover) | that the restore worked | a restore that fully lands or does not land at all |
@@ -205,8 +208,14 @@ Each installs on its own, works offline, and needs no account.
205
208
  | [awrelay](https://github.com/Aitherium/awrelay) | a SaaS in the middle of your agents | findings, alerts and coordination over your own transport |
206
209
  | [awask](https://github.com/Aitherium/awask) | that anyone read the paragraph where you asked | the ask itself, with a button that steers the session that raised it |
207
210
  | [awmail](https://github.com/Aitherium/awmail) | a mailbox somebody else can read | mail your agents send and receive over your own server |
211
+ | [awswarm](https://github.com/Aitherium/awswarm) | that a model either fits your GPU or it doesn't run at all | a placement plan and an acquisition-probability estimate before you spend on a run |
208
212
  | [awfind](https://github.com/Aitherium/awfind) | one vendor's idea of the web | results from whichever providers you configured |
209
213
  | [awbrowse](https://github.com/Aitherium/awbrowse) | that the page said what you were told | the render, the DOM and the requests it made |
214
+ | [awvoice](https://github.com/Aitherium/awvoice) | that a cloud vendor may hold your audio | a transcript and a wav from a service you host |
215
+ | [awvision](https://github.com/Aitherium/awvision) | a filename and a caption somebody wrote | what a model actually reports about the pixels |
216
+ | [awscreen](https://github.com/Aitherium/awscreen) | a selector that was true when the page was written | the elements actually rendered, by what they look like |
217
+ | [awbeads](https://github.com/Aitherium/awbeads) | that a layout your users built survives the next deploy | the arrangement as data you can read back, diff, and hand to another surface |
218
+ | [awbonsai](https://github.com/Aitherium/awbonsai) | that inference always means a request left the machine | a WebGPU model answering on the tab's own GPU, with a consent record logged before it ever loaded |
210
219
  | [gawbbonet](https://github.com/Aitherium/gawbbonet) | the model to keep a 300-message campaign coherent by itself | campaign facts recalled from scoped memory you can list and edit |
211
220
  | [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | a vendor's quantisation defaults | sub-byte KV cache kernels you can benchmark yourself |
212
221
  | **awrtifact** _(you are here)_ | a hand-rolled split script and a hand-edited worker manifest | byte-verified parts in a release, served with Range + CORS, sizes asserted by a live gate |
@@ -216,15 +225,21 @@ Each installs on its own, works offline, and needs no account.
216
225
  | [awrecurse](https://github.com/Aitherium/awrecurse) | that everything you pasted in was actually read | which slices it opened, and what it concluded from each |
217
226
  | [awprism](https://github.com/Aitherium/awprism) | the first explanation that fits | the ranked alternatives, and the observation that separates them |
218
227
  | [awrepl](https://github.com/Aitherium/awrepl) | what the agent believes the value is | the value, printed from the live session |
228
+ | [awreport](https://github.com/Aitherium/awreport) | that the report you pasted carried no token in it | a redacted report, and the duplicate it merged into instead of filing twice |
219
229
  | [awresearch](https://github.com/Aitherium/awresearch) | a summary of pages nobody opened | every claim against the source it came from |
220
230
  | [awfocus](https://github.com/Aitherium/awfocus) | twelve terminal tabs and a bad memory | one command that names every session, finds any transcript, and opens or steers the one you want |
221
231
  | [awgym](https://github.com/Aitherium/awgym) | that a world model learned anything from the games it saw | transitions captured from real play, fed back, and the retrodiction score falling on grids it never saw |
222
232
  | [awpredict](https://github.com/Aitherium/awpredict) | a model because it trained without erroring | its prediction against a self-updating lookup, on the rows that are actually novel |
233
+ | [awevolve](https://github.com/Aitherium/awevolve) | that your optimisation loop is finding anything | every version it kept, the score that version earned, and the edit that produced it |
223
234
  | [awsh](https://github.com/Aitherium/awsh) | that you already know the name of the command | what it decided your line meant, before it acts on it |
235
+ | [awmine](https://github.com/Aitherium/awmine) | that a session's lesson survived the session | a row per outcome, a candidate per lesson, and the transcript line each one came from |
236
+ | [awrise](https://github.com/Aitherium/awrise) | that a scheduled agent ran at all, and ran exactly once | a durable record of every wake -- fired, skipped, overlapped or timed out -- each with its reason |
224
237
  | [awkno](https://github.com/Aitherium/awkno) | that the docs site is up, or that you remember the family | the whole ecosystem in your terminal, with no network at all |
238
+ | [awwall](https://github.com/Aitherium/awwall) | that a service only talks to the hosts you think it talks to | an explicit egress allowlist, where a denial names the rule that denied it |
225
239
  | [awembed](https://github.com/Aitherium/awembed) | a general-purpose embedder that has never seen your code | a held-out split of whole directories, scored teacher vs student vs int8 |
226
240
  | [awtax](https://github.com/Aitherium/awtax) | a closed tax app's sealed file you can never read again | a plain, provider-neutral schema of every figure, with the page it came from |
227
241
  | [awsettings](https://github.com/Aitherium/awsettings) | that you will remember to re-approve the same thing on every box you work from | one profile, unioned rather than overwritten, with the credentials left behind |
242
+ | [awavatar](https://github.com/Aitherium/awavatar) | a cloud 3D vendor's opaque task id | a manifest with a sha256, a licence and a rig-audit verdict per file |
228
243
 
229
244
  [**awnix**](https://github.com/Aitherium/awnix) is the ground floor — A Linux you can hand to an agent — immutable base, capabilities included.
230
245
 
@@ -238,11 +253,13 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
238
253
  | [awskills](https://github.com/Aitherium/awskills) | Portable agent skills — self-contained procedures an agent loads on demand | [docs](https://aitherium.github.io/awskills/) |
239
254
  | [awpack](https://github.com/Aitherium/awpack) | First-party agent packs — the ones we build, versioned and installable on their own | [docs](https://aitherium.github.io/awpack/) |
240
255
  | [awm](https://github.com/Aitherium/awm) | A portable, scoped agent memory | [docs](https://aitherium.github.io/awm/) |
256
+ | [awdesk](https://github.com/Aitherium/awdesk) | Aither World Desk -- the desktop body of AitherOS Online: tray, avatars, decision cards, the Living Desktop as an overlay | [docs](https://aitherium.github.io/awdesk/) |
241
257
  | [awnode](https://github.com/Aitherium/awnode) | A lightweight local gateway — bridges your apps to the AI backends you chose | [docs](https://aitherium.github.io/awnode/) |
242
258
  | [awrun](https://github.com/Aitherium/awrun) | A priority-aware queue and dispatcher for agentic runs and ad-hoc CI builds. It also judges whether the runner pool is big enough for the queue it is draining, and can ask a host to grow it -- reserving capacity is zero-sum, so a saturated pool needs more of it, not a different share of it | [docs](https://aitherium.github.io/awrun/) |
243
259
  | [awgraph](https://github.com/Aitherium/awgraph) | A semantic code graph for agents — AST + tree-sitter, call graphs | [docs](https://aitherium.github.io/awgraph/) |
244
260
  | [awgit](https://github.com/Aitherium/awgit) | Semantic version control on top of git — edit-ops and leases | [docs](https://aitherium.github.io/awgit/) |
245
261
  | [awdelphi](https://github.com/Aitherium/awdelphi) | Anonymous multi-round expert panels — a converged answer with a trace | [docs](https://aitherium.github.io/awdelphi/) |
262
+ | [awclassify](https://github.com/Aitherium/awclassify) | Classify any document -- what it is, who may read it, who it is for, what it is about | — |
246
263
  | [awtoll](https://github.com/Aitherium/awtoll) | What every tool call costs you in context, measured from your own transcripts | [docs](https://aitherium.github.io/awtoll/) |
247
264
  | [awseal](https://github.com/Aitherium/awseal) | Sign an artifact so a stranger can verify it | [docs](https://aitherium.github.io/awseal/) |
248
265
  | [awshare](https://github.com/Aitherium/awshare) | Publish an artifact and fetch it back verified | [docs](https://aitherium.github.io/awshare/) |
@@ -251,6 +268,7 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
251
268
  | [awiam](https://github.com/Aitherium/awiam) | Who is this caller? A directory and session store that fails honestly | [docs](https://aitherium.github.io/awiam/) |
252
269
  | [awtunnel](https://github.com/Aitherium/awtunnel) | Reach a service that has no public address | [docs](https://aitherium.github.io/awtunnel/) |
253
270
  | [awnest](https://github.com/Aitherium/awnest) | Prove there is a human before you let them into the nest | [docs](https://aitherium.github.io/awnest/) |
271
+ | [awrena](https://github.com/Aitherium/awrena) | Put two agents head to head and get a verdict you can check | [docs](https://aitherium.github.io/awrena/) |
254
272
  | [awnboard](https://github.com/Aitherium/awnboard) | A front gate you can put in front of anything, and hand someone the key to | [docs](https://aitherium.github.io/awnboard/) |
255
273
  | [awnix](https://github.com/Aitherium/awnix) | A Linux you can hand to an agent — immutable base, capabilities included | [docs](https://aitherium.github.io/awnix/) |
256
274
  | [awrecover](https://github.com/Aitherium/awrecover) | Labelled snapshots with an all-or-nothing restore | [docs](https://aitherium.github.io/awrecover/) |
@@ -259,9 +277,17 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
259
277
  | [awask](https://github.com/Aitherium/awask) | Your agent asks you a question — and acts on your answer | [docs](https://aitherium.github.io/awask/) |
260
278
  | [awmail](https://github.com/Aitherium/awmail) | Give an agent an email address — send, and actually receive | [docs](https://aitherium.github.io/awmail/) |
261
279
  | [awnet](https://github.com/Aitherium/awnet) | The agentic web — agents host a mesh, and agents join one | [docs](https://aitherium.github.io/awnet/) |
280
+ | [awswarm](https://github.com/Aitherium/awswarm) | Run one model too big for any single GPU across a pool of small ones | — |
262
281
  | [awfind](https://github.com/Aitherium/awfind) | A portable search client — query, results, ranking | [docs](https://aitherium.github.io/awfind/) |
263
282
  | [awbrowse](https://github.com/Aitherium/awbrowse) | A portable browser client — navigate, console, network, DOM, screenshot | [docs](https://aitherium.github.io/awbrowse/) |
283
+ | [awvoice](https://github.com/Aitherium/awvoice) | Hear and speak — transcribe audio, synthesize a voice | [docs](https://aitherium.github.io/awvoice/) |
284
+ | [awvision](https://github.com/Aitherium/awvision) | See an image — describe it, ask it a question, compare two | [docs](https://aitherium.github.io/awvision/) |
285
+ | [awscreen](https://github.com/Aitherium/awscreen) | See this machine — what is on screen, and where to click it | [docs](https://aitherium.github.io/awscreen/) |
286
+ | [awkit](https://github.com/Aitherium/awkit) | Render an agent panel from a tool result — one component, any React app | — |
287
+ | [awbeads](https://github.com/Aitherium/awbeads) | A spatial canvas for a page — arrange things, connect them, and keep the arrangement | — |
288
+ | [awbonsai](https://github.com/Aitherium/awbonsai) | Run a real model in the visitor's own browser — no server round trip, no upload | — |
264
289
  | [awknowledge](https://github.com/Aitherium/awknowledge) | How to run a coding agent so the result survives — the laws, with evidence | [docs](https://aitherium.github.io/awknowledge/) |
290
+ | [awbrain](https://github.com/Aitherium/awbrain) | Your history as a wiki of linked markdown — claims pinned to the evidence | — |
265
291
  | [gawbbonet](https://github.com/Aitherium/gawbbonet) | GobboNet campaigns with a real agent brain — scoped memory, graph recall | [docs](https://aitherium.github.io/gawbbonet/) |
266
292
  | [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | Near-optimal KV cache quantization for LLM inference — sub-byte compression | [docs](https://aitherium.github.io/aitherkvcache/) |
267
293
  | **awrtifact** _(you are here)_ | Deliberately chunk artifacts into GitHub release assets — the productized aitherkvcache mirror lane | [docs](https://aitherium.github.io/awrtifact/) |
@@ -271,15 +297,23 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
271
297
  | [awrecurse](https://github.com/Aitherium/awrecurse) | Answer a question over a context far larger than the window — recursively, with the trace kept | [docs](https://aitherium.github.io/awrecurse/) |
272
298
  | [awprism](https://github.com/Aitherium/awprism) | Turn a failure into ranked hypotheses — and say what would confirm each one | [docs](https://aitherium.github.io/awprism/) |
273
299
  | [awrepl](https://github.com/Aitherium/awrepl) | A REPL an agent can actually use — state that survives between turns | [docs](https://aitherium.github.io/awrepl/) |
300
+ | [awreport](https://github.com/Aitherium/awreport) | File a bug report that has already scrubbed your secrets and collapsed the duplicate | — |
274
301
  | [awresearch](https://github.com/Aitherium/awresearch) | Ask a research question, get a cited report you can check | [docs](https://aitherium.github.io/awresearch/) |
275
302
  | [awfocus](https://github.com/Aitherium/awfocus) | See, search and steer every Claude session from one command | [docs](https://aitherium.github.io/awfocus/) |
276
303
  | [awgym](https://github.com/Aitherium/awgym) | An ARC training gym — a game a world model can watch, and six roles that play through it | [docs](https://aitherium.github.io/awgym/) |
277
304
  | [awpredict](https://github.com/Aitherium/awpredict) | Predict what your environment does next, and how surprised you were | [docs](https://aitherium.github.io/awpredict/) |
305
+ | [awevolve](https://github.com/Aitherium/awevolve) | Point an agent at a file and a command that scores it, and let it improve | — |
278
306
  | [awsh](https://github.com/Aitherium/awsh) | Your terminal answers you -- type a question where a command would go | [docs](https://aitherium.github.io/awsh/) |
307
+ | [awmine](https://github.com/Aitherium/awmine) | Mine what your agents did -- outcomes, lessons and procedures out of the transcripts they left behind | — |
308
+ | [awrise](https://github.com/Aitherium/awrise) | Wake an agent on a schedule, let it do one thing, and put it back to sleep | [docs](https://aitherium.github.io/awrise/) |
279
309
  | [awkno](https://github.com/Aitherium/awkno) | The man page for the Aither World — every brick, stack and law, offline | [docs](https://aitherium.github.io/awkno/) |
310
+ | [awwall](https://github.com/Aitherium/awwall) | Say what a workload may reach, and watch everything else fail closed | [docs](https://aitherium.github.io/awwall/) |
311
+ | [awrouter](https://github.com/Aitherium/awrouter) | OpenRouter for your own fleet: pick a model backend by cost/latency/ capability, fail over, fit the context window, stream. Standalone, OpenAI-compatible, no Aither-specifics required to be valuable | — |
280
312
  | [awembed](https://github.com/Aitherium/awembed) | Train an embedding model that knows your corpus, and prove it beats the big one | [docs](https://aitherium.github.io/awembed/) |
281
313
  | [awtax](https://github.com/Aitherium/awtax) | Turn any tax PDF -- returns, W-2, 1099, statements, even scans -- into structured data you can check | [docs](https://aitherium.github.io/awtax/) |
314
+ | [awflow](https://github.com/Aitherium/awflow) | A deterministic workflow runtime — chain agent calls with journal replay and budget control | [docs](https://aitherium.github.io/awflow/) |
282
315
  | [awsettings](https://github.com/Aitherium/awsettings) | Your agent's permissions and config, following you to the next machine | [docs](https://aitherium.github.io/awsettings/) |
316
+ | [awavatar](https://github.com/Aitherium/awavatar) | One character spec in, a rigged, animated, multi-style avatar pack out | [docs](https://aitherium.github.io/awavatar/) |
283
317
 
284
318
  <div id="aither-constellation" data-self="awrtifact"></div>
285
319
  <script src="aither-constellation.js"></script>
@@ -4,7 +4,7 @@
4
4
 
5
5
  **[Docs](https://aitherium.github.io/awrtifact/)** · [Source](https://github.com/Aitherium/awrtifact) · `pip install awrtifact` · [The Aither World](https://aitherium.github.io/)
6
6
 
7
- > **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awrtifact** is one of its 46 bricks — each installs on its own, runs offline, and needs no account.
7
+ > **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awrtifact** is one of its 65 bricks — each installs on its own, runs offline, and needs no account.
8
8
  >
9
9
  > **Start here:** Store one artifact as a versioned GitHub release and fetch it back byte-verified — `awrtifact mirror <URL|FILE> --release TAG`.
10
10
 
@@ -174,14 +174,17 @@ Each installs on its own, works offline, and needs no account.
174
174
  | [awskills](https://github.com/Aitherium/awskills) | that an agent knows your procedure | the procedure written down, versioned, and loadable by any agent |
175
175
  | [awpack](https://github.com/Aitherium/awpack) | that the pack you want shipped inside somebody's SDK, under whatever licence that SDK happens to carry | the pack as its own versioned artifact, with its own licence, that any agent runtime can install |
176
176
  | [awm](https://github.com/Aitherium/awm) | that memory stayed in its lane | tenant:user:project scopes, so a write cannot cross a boundary |
177
+ | [awdesk](https://github.com/Aitherium/awdesk) | that the agent is somewhere behind a browser tab | a tray icon, a face on your desktop, and the decision card that pops when it needs you |
177
178
  | [awnode](https://github.com/Aitherium/awnode) | a vendor's cloud with every prompt | a local gateway routing to backends you chose |
178
179
  | [awgraph](https://github.com/Aitherium/awgraph) | that grep found everything | an AST + tree-sitter call graph an agent can traverse |
179
180
  | [awgit](https://github.com/Aitherium/awgit) | that no one else is editing this file | a lease, refused at commit time if you do not hold it |
180
181
  | [awdelphi](https://github.com/Aitherium/awdelphi) | one agent's confident take on a decision | the round trace, the anonymity, and who dissents |
182
+ | [awclassify](https://github.com/Aitherium/awclassify) | a filename, a folder, or whoever last touched it | doc_type, visibility, audience and topics, with the evidence lines that decided each |
181
183
  | [awtoll](https://github.com/Aitherium/awtoll) | that your tooling is saving you context | the measured token cost of each tool call, and what the alternative cost |
182
184
  | [awseal](https://github.com/Aitherium/awseal) | that the artifact came from who you think | an Ed25519 seal — the key that verifies is not the key that forges |
183
185
  | [awshare](https://github.com/Aitherium/awshare) | that the download is intact | content-addressed bundles, verified on fetch |
184
186
  | [awnest](https://github.com/Aitherium/awnest) | that there is a person on the other end | a verdict with evidence, where "we could not tell" is not "yes" |
187
+ | [awrena](https://github.com/Aitherium/awrena) | a leaderboard someone can edit, and votes nobody counted | a scored duel with both answers kept, and a result bound to them |
185
188
  | [awnboard](https://github.com/Aitherium/awnboard) | a share link anyone who sees it can use | an invitation addressed to one person, for one gate, revocable |
186
189
  | [awnix](https://github.com/Aitherium/awnix) | that the box is what you left it as | an immutable image you built, with atomic rollback |
187
190
  | [awrecover](https://github.com/Aitherium/awrecover) | that the restore worked | a restore that fully lands or does not land at all |
@@ -189,8 +192,14 @@ Each installs on its own, works offline, and needs no account.
189
192
  | [awrelay](https://github.com/Aitherium/awrelay) | a SaaS in the middle of your agents | findings, alerts and coordination over your own transport |
190
193
  | [awask](https://github.com/Aitherium/awask) | that anyone read the paragraph where you asked | the ask itself, with a button that steers the session that raised it |
191
194
  | [awmail](https://github.com/Aitherium/awmail) | a mailbox somebody else can read | mail your agents send and receive over your own server |
195
+ | [awswarm](https://github.com/Aitherium/awswarm) | that a model either fits your GPU or it doesn't run at all | a placement plan and an acquisition-probability estimate before you spend on a run |
192
196
  | [awfind](https://github.com/Aitherium/awfind) | one vendor's idea of the web | results from whichever providers you configured |
193
197
  | [awbrowse](https://github.com/Aitherium/awbrowse) | that the page said what you were told | the render, the DOM and the requests it made |
198
+ | [awvoice](https://github.com/Aitherium/awvoice) | that a cloud vendor may hold your audio | a transcript and a wav from a service you host |
199
+ | [awvision](https://github.com/Aitherium/awvision) | a filename and a caption somebody wrote | what a model actually reports about the pixels |
200
+ | [awscreen](https://github.com/Aitherium/awscreen) | a selector that was true when the page was written | the elements actually rendered, by what they look like |
201
+ | [awbeads](https://github.com/Aitherium/awbeads) | that a layout your users built survives the next deploy | the arrangement as data you can read back, diff, and hand to another surface |
202
+ | [awbonsai](https://github.com/Aitherium/awbonsai) | that inference always means a request left the machine | a WebGPU model answering on the tab's own GPU, with a consent record logged before it ever loaded |
194
203
  | [gawbbonet](https://github.com/Aitherium/gawbbonet) | the model to keep a 300-message campaign coherent by itself | campaign facts recalled from scoped memory you can list and edit |
195
204
  | [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | a vendor's quantisation defaults | sub-byte KV cache kernels you can benchmark yourself |
196
205
  | **awrtifact** _(you are here)_ | a hand-rolled split script and a hand-edited worker manifest | byte-verified parts in a release, served with Range + CORS, sizes asserted by a live gate |
@@ -200,15 +209,21 @@ Each installs on its own, works offline, and needs no account.
200
209
  | [awrecurse](https://github.com/Aitherium/awrecurse) | that everything you pasted in was actually read | which slices it opened, and what it concluded from each |
201
210
  | [awprism](https://github.com/Aitherium/awprism) | the first explanation that fits | the ranked alternatives, and the observation that separates them |
202
211
  | [awrepl](https://github.com/Aitherium/awrepl) | what the agent believes the value is | the value, printed from the live session |
212
+ | [awreport](https://github.com/Aitherium/awreport) | that the report you pasted carried no token in it | a redacted report, and the duplicate it merged into instead of filing twice |
203
213
  | [awresearch](https://github.com/Aitherium/awresearch) | a summary of pages nobody opened | every claim against the source it came from |
204
214
  | [awfocus](https://github.com/Aitherium/awfocus) | twelve terminal tabs and a bad memory | one command that names every session, finds any transcript, and opens or steers the one you want |
205
215
  | [awgym](https://github.com/Aitherium/awgym) | that a world model learned anything from the games it saw | transitions captured from real play, fed back, and the retrodiction score falling on grids it never saw |
206
216
  | [awpredict](https://github.com/Aitherium/awpredict) | a model because it trained without erroring | its prediction against a self-updating lookup, on the rows that are actually novel |
217
+ | [awevolve](https://github.com/Aitherium/awevolve) | that your optimisation loop is finding anything | every version it kept, the score that version earned, and the edit that produced it |
207
218
  | [awsh](https://github.com/Aitherium/awsh) | that you already know the name of the command | what it decided your line meant, before it acts on it |
219
+ | [awmine](https://github.com/Aitherium/awmine) | that a session's lesson survived the session | a row per outcome, a candidate per lesson, and the transcript line each one came from |
220
+ | [awrise](https://github.com/Aitherium/awrise) | that a scheduled agent ran at all, and ran exactly once | a durable record of every wake -- fired, skipped, overlapped or timed out -- each with its reason |
208
221
  | [awkno](https://github.com/Aitherium/awkno) | that the docs site is up, or that you remember the family | the whole ecosystem in your terminal, with no network at all |
222
+ | [awwall](https://github.com/Aitherium/awwall) | that a service only talks to the hosts you think it talks to | an explicit egress allowlist, where a denial names the rule that denied it |
209
223
  | [awembed](https://github.com/Aitherium/awembed) | a general-purpose embedder that has never seen your code | a held-out split of whole directories, scored teacher vs student vs int8 |
210
224
  | [awtax](https://github.com/Aitherium/awtax) | a closed tax app's sealed file you can never read again | a plain, provider-neutral schema of every figure, with the page it came from |
211
225
  | [awsettings](https://github.com/Aitherium/awsettings) | that you will remember to re-approve the same thing on every box you work from | one profile, unioned rather than overwritten, with the credentials left behind |
226
+ | [awavatar](https://github.com/Aitherium/awavatar) | a cloud 3D vendor's opaque task id | a manifest with a sha256, a licence and a rig-audit verdict per file |
212
227
 
213
228
  [**awnix**](https://github.com/Aitherium/awnix) is the ground floor — A Linux you can hand to an agent — immutable base, capabilities included.
214
229
 
@@ -222,11 +237,13 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
222
237
  | [awskills](https://github.com/Aitherium/awskills) | Portable agent skills — self-contained procedures an agent loads on demand | [docs](https://aitherium.github.io/awskills/) |
223
238
  | [awpack](https://github.com/Aitherium/awpack) | First-party agent packs — the ones we build, versioned and installable on their own | [docs](https://aitherium.github.io/awpack/) |
224
239
  | [awm](https://github.com/Aitherium/awm) | A portable, scoped agent memory | [docs](https://aitherium.github.io/awm/) |
240
+ | [awdesk](https://github.com/Aitherium/awdesk) | Aither World Desk -- the desktop body of AitherOS Online: tray, avatars, decision cards, the Living Desktop as an overlay | [docs](https://aitherium.github.io/awdesk/) |
225
241
  | [awnode](https://github.com/Aitherium/awnode) | A lightweight local gateway — bridges your apps to the AI backends you chose | [docs](https://aitherium.github.io/awnode/) |
226
242
  | [awrun](https://github.com/Aitherium/awrun) | A priority-aware queue and dispatcher for agentic runs and ad-hoc CI builds. It also judges whether the runner pool is big enough for the queue it is draining, and can ask a host to grow it -- reserving capacity is zero-sum, so a saturated pool needs more of it, not a different share of it | [docs](https://aitherium.github.io/awrun/) |
227
243
  | [awgraph](https://github.com/Aitherium/awgraph) | A semantic code graph for agents — AST + tree-sitter, call graphs | [docs](https://aitherium.github.io/awgraph/) |
228
244
  | [awgit](https://github.com/Aitherium/awgit) | Semantic version control on top of git — edit-ops and leases | [docs](https://aitherium.github.io/awgit/) |
229
245
  | [awdelphi](https://github.com/Aitherium/awdelphi) | Anonymous multi-round expert panels — a converged answer with a trace | [docs](https://aitherium.github.io/awdelphi/) |
246
+ | [awclassify](https://github.com/Aitherium/awclassify) | Classify any document -- what it is, who may read it, who it is for, what it is about | — |
230
247
  | [awtoll](https://github.com/Aitherium/awtoll) | What every tool call costs you in context, measured from your own transcripts | [docs](https://aitherium.github.io/awtoll/) |
231
248
  | [awseal](https://github.com/Aitherium/awseal) | Sign an artifact so a stranger can verify it | [docs](https://aitherium.github.io/awseal/) |
232
249
  | [awshare](https://github.com/Aitherium/awshare) | Publish an artifact and fetch it back verified | [docs](https://aitherium.github.io/awshare/) |
@@ -235,6 +252,7 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
235
252
  | [awiam](https://github.com/Aitherium/awiam) | Who is this caller? A directory and session store that fails honestly | [docs](https://aitherium.github.io/awiam/) |
236
253
  | [awtunnel](https://github.com/Aitherium/awtunnel) | Reach a service that has no public address | [docs](https://aitherium.github.io/awtunnel/) |
237
254
  | [awnest](https://github.com/Aitherium/awnest) | Prove there is a human before you let them into the nest | [docs](https://aitherium.github.io/awnest/) |
255
+ | [awrena](https://github.com/Aitherium/awrena) | Put two agents head to head and get a verdict you can check | [docs](https://aitherium.github.io/awrena/) |
238
256
  | [awnboard](https://github.com/Aitherium/awnboard) | A front gate you can put in front of anything, and hand someone the key to | [docs](https://aitherium.github.io/awnboard/) |
239
257
  | [awnix](https://github.com/Aitherium/awnix) | A Linux you can hand to an agent — immutable base, capabilities included | [docs](https://aitherium.github.io/awnix/) |
240
258
  | [awrecover](https://github.com/Aitherium/awrecover) | Labelled snapshots with an all-or-nothing restore | [docs](https://aitherium.github.io/awrecover/) |
@@ -243,9 +261,17 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
243
261
  | [awask](https://github.com/Aitherium/awask) | Your agent asks you a question — and acts on your answer | [docs](https://aitherium.github.io/awask/) |
244
262
  | [awmail](https://github.com/Aitherium/awmail) | Give an agent an email address — send, and actually receive | [docs](https://aitherium.github.io/awmail/) |
245
263
  | [awnet](https://github.com/Aitherium/awnet) | The agentic web — agents host a mesh, and agents join one | [docs](https://aitherium.github.io/awnet/) |
264
+ | [awswarm](https://github.com/Aitherium/awswarm) | Run one model too big for any single GPU across a pool of small ones | — |
246
265
  | [awfind](https://github.com/Aitherium/awfind) | A portable search client — query, results, ranking | [docs](https://aitherium.github.io/awfind/) |
247
266
  | [awbrowse](https://github.com/Aitherium/awbrowse) | A portable browser client — navigate, console, network, DOM, screenshot | [docs](https://aitherium.github.io/awbrowse/) |
267
+ | [awvoice](https://github.com/Aitherium/awvoice) | Hear and speak — transcribe audio, synthesize a voice | [docs](https://aitherium.github.io/awvoice/) |
268
+ | [awvision](https://github.com/Aitherium/awvision) | See an image — describe it, ask it a question, compare two | [docs](https://aitherium.github.io/awvision/) |
269
+ | [awscreen](https://github.com/Aitherium/awscreen) | See this machine — what is on screen, and where to click it | [docs](https://aitherium.github.io/awscreen/) |
270
+ | [awkit](https://github.com/Aitherium/awkit) | Render an agent panel from a tool result — one component, any React app | — |
271
+ | [awbeads](https://github.com/Aitherium/awbeads) | A spatial canvas for a page — arrange things, connect them, and keep the arrangement | — |
272
+ | [awbonsai](https://github.com/Aitherium/awbonsai) | Run a real model in the visitor's own browser — no server round trip, no upload | — |
248
273
  | [awknowledge](https://github.com/Aitherium/awknowledge) | How to run a coding agent so the result survives — the laws, with evidence | [docs](https://aitherium.github.io/awknowledge/) |
274
+ | [awbrain](https://github.com/Aitherium/awbrain) | Your history as a wiki of linked markdown — claims pinned to the evidence | — |
249
275
  | [gawbbonet](https://github.com/Aitherium/gawbbonet) | GobboNet campaigns with a real agent brain — scoped memory, graph recall | [docs](https://aitherium.github.io/gawbbonet/) |
250
276
  | [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | Near-optimal KV cache quantization for LLM inference — sub-byte compression | [docs](https://aitherium.github.io/aitherkvcache/) |
251
277
  | **awrtifact** _(you are here)_ | Deliberately chunk artifacts into GitHub release assets — the productized aitherkvcache mirror lane | [docs](https://aitherium.github.io/awrtifact/) |
@@ -255,15 +281,23 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
255
281
  | [awrecurse](https://github.com/Aitherium/awrecurse) | Answer a question over a context far larger than the window — recursively, with the trace kept | [docs](https://aitherium.github.io/awrecurse/) |
256
282
  | [awprism](https://github.com/Aitherium/awprism) | Turn a failure into ranked hypotheses — and say what would confirm each one | [docs](https://aitherium.github.io/awprism/) |
257
283
  | [awrepl](https://github.com/Aitherium/awrepl) | A REPL an agent can actually use — state that survives between turns | [docs](https://aitherium.github.io/awrepl/) |
284
+ | [awreport](https://github.com/Aitherium/awreport) | File a bug report that has already scrubbed your secrets and collapsed the duplicate | — |
258
285
  | [awresearch](https://github.com/Aitherium/awresearch) | Ask a research question, get a cited report you can check | [docs](https://aitherium.github.io/awresearch/) |
259
286
  | [awfocus](https://github.com/Aitherium/awfocus) | See, search and steer every Claude session from one command | [docs](https://aitherium.github.io/awfocus/) |
260
287
  | [awgym](https://github.com/Aitherium/awgym) | An ARC training gym — a game a world model can watch, and six roles that play through it | [docs](https://aitherium.github.io/awgym/) |
261
288
  | [awpredict](https://github.com/Aitherium/awpredict) | Predict what your environment does next, and how surprised you were | [docs](https://aitherium.github.io/awpredict/) |
289
+ | [awevolve](https://github.com/Aitherium/awevolve) | Point an agent at a file and a command that scores it, and let it improve | — |
262
290
  | [awsh](https://github.com/Aitherium/awsh) | Your terminal answers you -- type a question where a command would go | [docs](https://aitherium.github.io/awsh/) |
291
+ | [awmine](https://github.com/Aitherium/awmine) | Mine what your agents did -- outcomes, lessons and procedures out of the transcripts they left behind | — |
292
+ | [awrise](https://github.com/Aitherium/awrise) | Wake an agent on a schedule, let it do one thing, and put it back to sleep | [docs](https://aitherium.github.io/awrise/) |
263
293
  | [awkno](https://github.com/Aitherium/awkno) | The man page for the Aither World — every brick, stack and law, offline | [docs](https://aitherium.github.io/awkno/) |
294
+ | [awwall](https://github.com/Aitherium/awwall) | Say what a workload may reach, and watch everything else fail closed | [docs](https://aitherium.github.io/awwall/) |
295
+ | [awrouter](https://github.com/Aitherium/awrouter) | OpenRouter for your own fleet: pick a model backend by cost/latency/ capability, fail over, fit the context window, stream. Standalone, OpenAI-compatible, no Aither-specifics required to be valuable | — |
264
296
  | [awembed](https://github.com/Aitherium/awembed) | Train an embedding model that knows your corpus, and prove it beats the big one | [docs](https://aitherium.github.io/awembed/) |
265
297
  | [awtax](https://github.com/Aitherium/awtax) | Turn any tax PDF -- returns, W-2, 1099, statements, even scans -- into structured data you can check | [docs](https://aitherium.github.io/awtax/) |
298
+ | [awflow](https://github.com/Aitherium/awflow) | A deterministic workflow runtime — chain agent calls with journal replay and budget control | [docs](https://aitherium.github.io/awflow/) |
266
299
  | [awsettings](https://github.com/Aitherium/awsettings) | Your agent's permissions and config, following you to the next machine | [docs](https://aitherium.github.io/awsettings/) |
300
+ | [awavatar](https://github.com/Aitherium/awavatar) | One character spec in, a rigged, animated, multi-style avatar pack out | [docs](https://aitherium.github.io/awavatar/) |
267
301
 
268
302
  <div id="aither-constellation" data-self="awrtifact"></div>
269
303
  <script src="aither-constellation.js"></script>
@@ -9,4 +9,4 @@ fetched back byte-verified.
9
9
  The core is stdlib-only; the spec-shaped commands need PyYAML (extra: `spec`).
10
10
  """
11
11
 
12
- __version__ = "0.2.2"
12
+ __version__ = "0.2.3"
@@ -19,12 +19,12 @@ import os
19
19
  import shutil
20
20
  import sys
21
21
 
22
- #: Frozen from AitherOS/config/ecosystem.yaml at generation time. A shipped
22
+ #: Frozen from the Aither World registry at generation time. A shipped
23
23
  #: package cannot read the registry, and a doctor that guessed at the family
24
24
  #: would go stale in silence. Regenerate to update.
25
25
  SELF = 'awrtifact'
26
- FAMILY = ['awask', 'awbac', 'awbrain', 'awbrowse', 'awclassify', 'awdelphi', 'awdit', 'awevolve', 'awfind', 'awfocus', 'awgit', 'awgraph', 'awgym', 'awiam', 'awkno', 'awm', 'awmail', 'awnboard', 'awnest', 'awnet', 'awnode', 'awpool', 'awpredict', 'awprism', 'awreason', 'awrecover', 'awrecurse', 'awrelay', 'awrena', 'awrepl', 'awresearch', 'awrise', 'awrouter', 'awrun', 'awscreen', 'awseal', 'awshare', 'awsprite', 'awswarm', 'awtoll', 'awtunnel', 'awvision', 'awvoice', 'awwall']
27
- PAIRS_WITH = ['adk', 'awgit', 'awnode', 'awrecover', 'awseal', 'awshare']
26
+ FAMILY = ['awask', 'awavatar', 'awbac', 'awbrain', 'awbrowse', 'awclassify', 'awdecide', 'awdeck', 'awdelphi', 'awdit', 'awembed', 'awevolve', 'awfind', 'awflow', 'awfocus', 'awgit', 'awgraph', 'awgym', 'awiam', 'awkno', 'awm', 'awmail', 'awmine', 'awnboard', 'awnest', 'awnet', 'awnode', 'awpool', 'awpredict', 'awprism', 'awprove', 'awreason', 'awrecover', 'awrecurse', 'awrelay', 'awrena', 'awrepl', 'awreport', 'awresearch', 'awrise', 'awrouter', 'awrun', 'awscreen', 'awseal', 'awsettings', 'awshare', 'awsprite', 'awstorage', 'awswarm', 'awtax', 'awtoll', 'awtunnel', 'awvision', 'awvoice', 'awwall', 'gawbbonet']
27
+ PAIRS_WITH = ['adk', 'awgit', 'awnode', 'awrecover', 'awseal', 'awshare', 'awstorage']
28
28
 
29
29
  #: This brick's OWN config, read out of its source at generation time.
30
30
  #: ENV_REQUIRED is `os.environ["X"]` -- absent, that is a KeyError the moment
@@ -33,7 +33,7 @@ PAIRS_WITH = ['adk', 'awgit', 'awnode', 'awrecover', 'awseal', 'awshare']
33
33
  #: platform-wide vars it also touches would be noise, and a doctor that floods
34
34
  #: gets ignored.
35
35
  ENV_REQUIRED = []
36
- ENV_OPTIONAL = []
36
+ ENV_OPTIONAL = ['AWRTIFACT_GH_BACKEND']
37
37
 
38
38
 
39
39
  def _installed(mod: str) -> "str | None":
@@ -91,6 +91,7 @@ def report(out=None) -> int:
91
91
  local = _local_checks()
92
92
  for line in local:
93
93
  print(f" {line}", file=out)
94
+ problems, unjudged = _local_verdict()
94
95
 
95
96
  if mine is None:
96
97
  print(f"\nverdict: {SELF} itself is not importable. Reinstall it before "
@@ -102,6 +103,17 @@ def report(out=None) -> int:
102
103
  f"so the code path that needs them raises rather than degrades.",
103
104
  file=out)
104
105
  return 1
106
+ # A measured NO outranks a missing optional pair: awrise printed
107
+ # "nothing wakes run-due" and still exited 0 because the missing-pairs
108
+ # branch returned first (measured 2026-09-20).
109
+ if problems:
110
+ for p in problems:
111
+ print(f"\nverdict: {p}", file=out)
112
+ return 1
113
+ if unjudged:
114
+ for u in unjudged:
115
+ print(f"\nverdict: UNJUDGED -- could not judge: {u}", file=out)
116
+ return 2
105
117
  if missing_pairs:
106
118
  print(f"\nverdict: {SELF} works, but pairs with "
107
119
  f"{', '.join(sorted(missing_pairs))} which "
@@ -112,6 +124,23 @@ def report(out=None) -> int:
112
124
  return 0
113
125
 
114
126
 
127
+ def _local_verdict() -> tuple:
128
+ """The brick's OWN verdict, folded into the exit code.
129
+
130
+ Without this the local lines are DECORATION. Measured 2026-09-20: awrise
131
+ printed "hostclock NOT INSTALLED -- nothing wakes run-due" and "last tick
132
+ never", then exited 0 -- teaching an operator that a doctor exit code
133
+ carries no information. A brick opts in with `_doctor_local_verdict()`
134
+ returning (problems, unjudged); a brick without one is unaffected.
135
+ """
136
+ try:
137
+ mod = importlib.import_module(f"{SELF}.doctor_local")
138
+ problems, unjudged = mod._doctor_local_verdict()
139
+ except Exception: # noqa: BLE001
140
+ return [], []
141
+ return list(problems or []), list(unjudged or [])
142
+
143
+
115
144
  def _local_checks() -> "list[str]":
116
145
  """Per-brick checks, if this package defines them.
117
146
 
@@ -1,9 +1,9 @@
1
1
  """GitHub REST backend — the same operations `gh.py` shells, over urllib.
2
2
 
3
- Why a second backend exists: the fleet's control plane (the Genesis router,
4
- `aither-worker`) runs in containers that carry no `gh` binary, and the
5
- "mirror anything from Hugging Face to any GitHub repo" story has to work
6
- from there — an MCP tool that can only run on the operator's laptop is not a
3
+ Why a second backend exists: server-side callers (routers, background
4
+ workers) run in containers that carry no `gh` binary, and the "mirror
5
+ anything from Hugging Face to any GitHub repo" story has to work from
6
+ there — a tool that can only run on the operator's laptop is not a
7
7
  platform capability. Everything here is keyed on ONE token
8
8
  (`GH_TOKEN`, then `GITHUB_TOKEN`, or an explicit `token_override` for a
9
9
  server process that must never write into `os.environ`).
@@ -31,6 +31,17 @@ def render(spec: dict, spec_path: Path, worker: dict | None = None) -> tuple[str
31
31
  """Render (index.js, wrangler.toml) for one worker of a validated spec."""
32
32
  worker = worker or spec_mod.workers(spec)[0]
33
33
  chunked = spec_mod.chunked_map(spec)
34
+ share_url = worker.get("share_grant_url")
35
+ if share_url:
36
+ if not isinstance(share_url, str) or not share_url.startswith("https://"):
37
+ raise ValueError(f"worker {worker['name']}: share_grant_url must be an https URL")
38
+ share_js = worker_template.SHARE_ROUTE_JS.replace(
39
+ "__SHARE_GRANT_URL_JSON__", json.dumps(share_url))
40
+ share_dispatch = worker_template.SHARE_ROUTE_DISPATCH
41
+ else:
42
+ # Absent = the worker renders exactly as it did before the share route
43
+ # existed, so the template change alone redeploys nothing.
44
+ share_js = share_dispatch = ""
34
45
  header = (
35
46
  "GENERATED by `awrtifact serve-spec` from the awrtifact spec — DO NOT "
36
47
  "EDIT BY HAND.\n"
@@ -44,6 +55,8 @@ def render(spec: dict, spec_path: Path, worker: dict | None = None) -> tuple[str
44
55
  .replace("__ALLOWED_SRC__", spec.get("allowlist", {}).get("regex", ""))
45
56
  .replace("__WHOLE_JSON__", json.dumps(spec_mod.whole_names(spec)))
46
57
  .replace("__CHUNKED_JSON__", json.dumps(chunked, indent=2))
58
+ .replace("__SHARE_ROUTE_JS__", share_js)
59
+ .replace("__SHARE_ROUTE_DISPATCH__", share_dispatch)
47
60
  .replace("__R2_BINDING__", worker.get("r2_binding", "WEIGHTS"))
48
61
  )
49
62
 
@@ -39,10 +39,16 @@ Shape:
39
39
  release: bonsai-image-v1 # release tag to serve from
40
40
  part_size: 1900000000 # optional; default 1.9 GiB
41
41
  parts: [] # optional explicit sizes; else derived
42
+ part_names: [] # optional asset names for `parts` (else .partN)
43
+ repo: Aitherium/awnix # optional; else store.repo
42
44
 
43
45
  An artifact whose total exceeds GitHub's 2 GiB cap is CHUNKED (parts derived
44
46
  uniformly: N full slices + one tail). An artifact at or under the cap is
45
47
  served WHOLE unless explicit `parts` are given.
48
+
49
+ `repo` lets one store serve releases from another repo (the awnix ISOs live in
50
+ Aitherium/awnix, not the weights repo), and `part_names` serves a file that some
51
+ OTHER tool split -- awnix's ISO lane names its slices `<iso>.00.part`, not `.partN`.
46
52
  """
47
53
 
48
54
  from __future__ import annotations
@@ -133,6 +139,15 @@ def validate(spec: dict) -> dict:
133
139
  )
134
140
  if any(p > GITHUB_ASSET_CAP for p in parts):
135
141
  raise ValueError(f"artifact {name}: a part exceeds the 2 GiB cap")
142
+ pnames = art.get("part_names") or []
143
+ if pnames and (len(pnames) != len(parts) or len(set(pnames)) != len(pnames)
144
+ or any("/" in n or "\\" in n for n in pnames)):
145
+ raise ValueError(f"artifact {name}: part_names must be {len(parts)} "
146
+ f"distinct bare filenames, one per part")
147
+ elif art.get("part_names"):
148
+ raise ValueError(f"artifact {name}: part_names needs explicit parts sizes")
149
+ if "repo" in art and (not isinstance(art["repo"], str) or art["repo"].count("/") != 1):
150
+ raise ValueError(f"artifact {name}: repo must be 'owner/repo'")
136
151
  else:
137
152
  part_size = art.get("part_size") or DEFAULT_PART_SIZE
138
153
  if part_size > GITHUB_ASSET_CAP:
@@ -150,6 +165,11 @@ def artifact_release(spec: dict, art: dict) -> str:
150
165
  raise ValueError(f"artifact {art.get('name')}: no release and no upstreams")
151
166
 
152
167
 
168
+ def artifact_repo(spec: dict, art: dict) -> str:
169
+ """The owner/repo an artifact's release lives in (artifact wins, else the store)."""
170
+ return art.get("repo") or spec["store"]["repo"]
171
+
172
+
153
173
  def upstream_bases(spec: dict) -> list[str]:
154
174
  """ALLOWED-lane base URLs, tried in order: distinct release downloads."""
155
175
  repo = spec["store"]["repo"]
@@ -160,7 +180,7 @@ def upstream_bases(spec: dict) -> list[str]:
160
180
  bases.append(base)
161
181
  for art in spec.get("artifacts") or []:
162
182
  base = (
163
- f"https://github.com/{repo}/releases/download/"
183
+ f"https://github.com/{artifact_repo(spec, art)}/releases/download/"
164
184
  f"{artifact_release(spec, art)}/"
165
185
  )
166
186
  if base not in bases:
@@ -183,7 +203,7 @@ def path_upstreams(spec: dict) -> dict:
183
203
  f"https://github.com/{repo}/releases/download/{up['release']}/")
184
204
  for art in spec.get("artifacts") or []:
185
205
  rel = artifact_release(spec, art)
186
- out[rel] = f"https://github.com/{repo}/releases/download/{rel}/"
206
+ out[rel] = f"https://github.com/{artifact_repo(spec, art)}/releases/download/{rel}/"
187
207
  return out
188
208
 
189
209
 
@@ -200,7 +220,6 @@ def whole_names(spec: dict) -> list[str]:
200
220
 
201
221
  def chunked_map(spec: dict) -> dict:
202
222
  """name → {"upstream": base-url, "parts": [{name, size}]} for big artifacts."""
203
- repo = spec["store"]["repo"]
204
223
  out: dict[str, dict] = {}
205
224
  for art in spec.get("artifacts") or []:
206
225
  name = art["name"]
@@ -210,16 +229,15 @@ def chunked_map(spec: dict) -> dict:
210
229
  continue
211
230
  if explicit:
212
231
  # Explicit parts are SIZES (the spec contract); name them .partN.
213
- sizes = [
214
- {"name": f"{name}.part{idx}", "size": size}
215
- for idx, size in enumerate(explicit)
216
- ]
232
+ names = art.get("part_names") or [f"{name}.part{i}" for i in range(len(explicit))]
233
+ sizes = [{"name": n, "size": size} for n, size in zip(names, explicit)]
217
234
  else:
218
235
  sizes = derive_parts(name, total, art.get("part_size")
219
236
  or DEFAULT_PART_SIZE)
220
237
  release = artifact_release(spec, art)
221
238
  out[name] = {
222
- "upstream": f"https://github.com/{repo}/releases/download/{release}/",
239
+ "upstream": (f"https://github.com/{artifact_repo(spec, art)}"
240
+ f"/releases/download/{release}/"),
223
241
  "parts": [{"name": p["name"], "size": p["size"]} for p in sizes],
224
242
  }
225
243
  return out
@@ -12,6 +12,8 @@ Sentinel tokens (never valid in generated output):
12
12
  __ALLOWED_SRC__ regex source string
13
13
  __WHOLE_JSON__ array of whole-file names
14
14
  __CHUNKED_JSON__ name → {upstream, parts:[{name,size}]}
15
+ __SHARE_ROUTE_JS__ the Aither Share byte route, or "" (opt-in per worker)
16
+ __SHARE_ROUTE_DISPATCH__ its dispatch line in fetch(), or ""
15
17
  """
16
18
 
17
19
  from __future__ import annotations
@@ -44,7 +46,7 @@ const WHOLE = new Set(__WHOLE_JSON__);
44
46
  // client asks for by the original filename. Range requests are translated into
45
47
  // per-part sub-ranges. The manifest is GENERATED from the awrtifact spec — never
46
48
  // hand-edited (a hand-edited entry is exactly how a stale build ships).
47
- const CHUNKED = __CHUNKED_JSON__;
49
+ const CHUNKED = __CHUNKED_JSON__;__SHARE_ROUTE_JS__
48
50
 
49
51
  const cors = {
50
52
  'Access-Control-Allow-Origin': '*',
@@ -271,7 +273,7 @@ export default {
271
273
  status: 200,
272
274
  headers: { 'Content-Type': 'application/json', ...cors },
273
275
  });
274
- }
276
+ }__SHARE_ROUTE_DISPATCH__
275
277
  // Take only the FILENAME, so both surfaces work with one worker:
276
278
  // artifacts.aitherium.com/<name> (custom route, preferred)
277
279
  // <worker>.<account>.workers.dev/<name> (fallback)
@@ -390,3 +392,111 @@ __ROUTE_BLOCK__
390
392
  binding = "__R2_BINDING__"
391
393
  bucket_name = "__R2_BUCKET__"
392
394
  """
395
+
396
+
397
+ #: Aither Share byte route: `/s/<share_id>?t=<ticket>`. Rendered ONLY for a worker
398
+ #: whose spec entry sets `share_grant_url`; every other worker renders byte-for-
399
+ #: byte as before, so adding this template never redeploys a worker by itself.
400
+ #:
401
+ #: What it serves is CIPHERTEXT from R2 (`share/<id>/ciphertext.bin`). The worker
402
+ #: never holds a data key. Every request asks Genesis (through the public grant-
403
+ #: check door) whether the ticket still holds, so a revoke is effective on the
404
+ #: next request: the response is `private, no-store`, and nothing is cached at the
405
+ #: edge that could outlive a revocation.
406
+ SHARE_ROUTE_JS = r"""
407
+
408
+ // ── Aither Share: /s/<share_id>?t=<ticket> ─────────────────────────────────────
409
+ // GENERATED because this worker's spec sets share_grant_url. Grant-checked on
410
+ // EVERY request (never cached), Range-capable, ciphertext only.
411
+ const SHARE_GRANT_URL = __SHARE_GRANT_URL_JSON__;
412
+ const SHARE_ID = /^shr_[A-Za-z0-9_-]{8,64}$/;
413
+ const shareHeaders = {
414
+ 'Access-Control-Allow-Origin': '*',
415
+ 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
416
+ 'Access-Control-Allow-Headers': 'Range, X-Share-Ticket',
417
+ 'Access-Control-Expose-Headers': 'Content-Length, Content-Range, Accept-Ranges, X-Share-Sha256',
418
+ 'Cache-Control': 'private, no-store',
419
+ };
420
+
421
+ function shareRefusal(status, text) {
422
+ return new Response(text + '\n', { status, headers: shareHeaders });
423
+ }
424
+
425
+ async function serveShare(request, env, shareId) {
426
+ if (!SHARE_ID.test(shareId)) return shareRefusal(404, 'not found');
427
+ const url = new URL(request.url);
428
+ // TODO(ticket-in-query): `?t=` lands in edge logs and Referer headers, and the
429
+ // ticket is reusable for its 5-minute life (it unlocks ciphertext only). Accept
430
+ // it from the X-Share-Ticket header only, or have the grant check spend it.
431
+ const ticket = request.headers.get('X-Share-Ticket') || url.searchParams.get('t') || '';
432
+ if (!ticket) return shareRefusal(401, 'this link needs a ticket');
433
+ const bucket = env && env.__R2_BINDING__;
434
+ if (!bucket) return shareRefusal(503, 'share storage is not bound');
435
+
436
+ let grant;
437
+ try {
438
+ const resp = await fetch(SHARE_GRANT_URL, {
439
+ method: 'POST',
440
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
441
+ body: JSON.stringify({ share_id: shareId, ticket }),
442
+ });
443
+ // 410 = revoked or expired, relayed as-is so the page can say so.
444
+ if (resp.status === 410) return shareRefusal(410, 'revoked by sender');
445
+ if (!resp.ok) return shareRefusal(resp.status === 404 ? 404 : 403, 'not allowed');
446
+ grant = await resp.json();
447
+ } catch (_e) {
448
+ // Fail CLOSED: an unreachable grant check never falls through to serving.
449
+ return shareRefusal(503, 'grant check unavailable');
450
+ }
451
+ const prefix = `share/${shareId}/`;
452
+ if (!grant || grant.ok !== true || typeof grant.object_key !== 'string'
453
+ || !grant.object_key.startsWith(prefix)) {
454
+ return shareRefusal(403, 'not allowed');
455
+ }
456
+
457
+ const rangeHeader = request.headers.get('Range');
458
+ let object;
459
+ if (rangeHeader) {
460
+ const m = /^bytes=(\d*)-(\d*)$/.exec(rangeHeader.trim());
461
+ if (!m || (m[1] === '' && m[2] === '')) return shareRefusal(416, 'bad range');
462
+ let r;
463
+ if (m[1] === '') r = { suffix: Number(m[2]) };
464
+ else if (m[2] === '') r = { offset: Number(m[1]) };
465
+ else {
466
+ const start = Number(m[1]);
467
+ const end = Number(m[2]);
468
+ if (end < start) return shareRefusal(416, 'range not satisfiable');
469
+ r = { offset: start, length: end - start + 1 };
470
+ }
471
+ object = await bucket.get(grant.object_key, { range: r });
472
+ } else {
473
+ object = await bucket.get(grant.object_key);
474
+ }
475
+ if (!object) return shareRefusal(404, 'not found');
476
+
477
+ const headers = new Headers(shareHeaders);
478
+ headers.set('Accept-Ranges', 'bytes');
479
+ headers.set('Content-Type', 'application/octet-stream');
480
+ if (typeof grant.sha256 === 'string') headers.set('X-Share-Sha256', grant.sha256);
481
+ if (rangeHeader && object.range && typeof object.range.offset === 'number') {
482
+ const start = object.range.offset;
483
+ const len = object.range.length ?? (object.size - start);
484
+ headers.set('Content-Length', String(len));
485
+ // Whole-object size, never `*`: a client detects truncation against it.
486
+ headers.set('Content-Range', `bytes ${start}-${start + len - 1}/${object.size}`);
487
+ if (request.method === 'HEAD') return new Response(null, { status: 206, headers });
488
+ return new Response(object.body, { status: 206, headers });
489
+ }
490
+ headers.set('Content-Length', String(object.size));
491
+ if (request.method === 'HEAD') return new Response(null, { status: 200, headers });
492
+ return new Response(object.body, { status: 200, headers });
493
+ }"""
494
+
495
+ SHARE_ROUTE_DISPATCH = r"""
496
+ // Aither Share bytes: grant-checked, never matched by the allowlist below.
497
+ {
498
+ const shareSegs = new URL(request.url).pathname.split('/').filter(Boolean);
499
+ if (shareSegs.length === 2 && shareSegs[0] === 's') {
500
+ return serveShare(request, env, shareSegs[1]);
501
+ }
502
+ }"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: awrtifact
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Aither World Artifact — deliberately chunk artifacts into GitHub release assets and fetch them back byte-verified. The productized aitherkvcache mirror lane.
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/Aitherium/awrtifact
@@ -20,7 +20,7 @@ Requires-Dist: awseal>=0.1; extra == "seal"
20
20
 
21
21
  **[Docs](https://aitherium.github.io/awrtifact/)** · [Source](https://github.com/Aitherium/awrtifact) · `pip install awrtifact` · [The Aither World](https://aitherium.github.io/)
22
22
 
23
- > **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awrtifact** is one of its 46 bricks — each installs on its own, runs offline, and needs no account.
23
+ > **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awrtifact** is one of its 65 bricks — each installs on its own, runs offline, and needs no account.
24
24
  >
25
25
  > **Start here:** Store one artifact as a versioned GitHub release and fetch it back byte-verified — `awrtifact mirror <URL|FILE> --release TAG`.
26
26
 
@@ -190,14 +190,17 @@ Each installs on its own, works offline, and needs no account.
190
190
  | [awskills](https://github.com/Aitherium/awskills) | that an agent knows your procedure | the procedure written down, versioned, and loadable by any agent |
191
191
  | [awpack](https://github.com/Aitherium/awpack) | that the pack you want shipped inside somebody's SDK, under whatever licence that SDK happens to carry | the pack as its own versioned artifact, with its own licence, that any agent runtime can install |
192
192
  | [awm](https://github.com/Aitherium/awm) | that memory stayed in its lane | tenant:user:project scopes, so a write cannot cross a boundary |
193
+ | [awdesk](https://github.com/Aitherium/awdesk) | that the agent is somewhere behind a browser tab | a tray icon, a face on your desktop, and the decision card that pops when it needs you |
193
194
  | [awnode](https://github.com/Aitherium/awnode) | a vendor's cloud with every prompt | a local gateway routing to backends you chose |
194
195
  | [awgraph](https://github.com/Aitherium/awgraph) | that grep found everything | an AST + tree-sitter call graph an agent can traverse |
195
196
  | [awgit](https://github.com/Aitherium/awgit) | that no one else is editing this file | a lease, refused at commit time if you do not hold it |
196
197
  | [awdelphi](https://github.com/Aitherium/awdelphi) | one agent's confident take on a decision | the round trace, the anonymity, and who dissents |
198
+ | [awclassify](https://github.com/Aitherium/awclassify) | a filename, a folder, or whoever last touched it | doc_type, visibility, audience and topics, with the evidence lines that decided each |
197
199
  | [awtoll](https://github.com/Aitherium/awtoll) | that your tooling is saving you context | the measured token cost of each tool call, and what the alternative cost |
198
200
  | [awseal](https://github.com/Aitherium/awseal) | that the artifact came from who you think | an Ed25519 seal — the key that verifies is not the key that forges |
199
201
  | [awshare](https://github.com/Aitherium/awshare) | that the download is intact | content-addressed bundles, verified on fetch |
200
202
  | [awnest](https://github.com/Aitherium/awnest) | that there is a person on the other end | a verdict with evidence, where "we could not tell" is not "yes" |
203
+ | [awrena](https://github.com/Aitherium/awrena) | a leaderboard someone can edit, and votes nobody counted | a scored duel with both answers kept, and a result bound to them |
201
204
  | [awnboard](https://github.com/Aitherium/awnboard) | a share link anyone who sees it can use | an invitation addressed to one person, for one gate, revocable |
202
205
  | [awnix](https://github.com/Aitherium/awnix) | that the box is what you left it as | an immutable image you built, with atomic rollback |
203
206
  | [awrecover](https://github.com/Aitherium/awrecover) | that the restore worked | a restore that fully lands or does not land at all |
@@ -205,8 +208,14 @@ Each installs on its own, works offline, and needs no account.
205
208
  | [awrelay](https://github.com/Aitherium/awrelay) | a SaaS in the middle of your agents | findings, alerts and coordination over your own transport |
206
209
  | [awask](https://github.com/Aitherium/awask) | that anyone read the paragraph where you asked | the ask itself, with a button that steers the session that raised it |
207
210
  | [awmail](https://github.com/Aitherium/awmail) | a mailbox somebody else can read | mail your agents send and receive over your own server |
211
+ | [awswarm](https://github.com/Aitherium/awswarm) | that a model either fits your GPU or it doesn't run at all | a placement plan and an acquisition-probability estimate before you spend on a run |
208
212
  | [awfind](https://github.com/Aitherium/awfind) | one vendor's idea of the web | results from whichever providers you configured |
209
213
  | [awbrowse](https://github.com/Aitherium/awbrowse) | that the page said what you were told | the render, the DOM and the requests it made |
214
+ | [awvoice](https://github.com/Aitherium/awvoice) | that a cloud vendor may hold your audio | a transcript and a wav from a service you host |
215
+ | [awvision](https://github.com/Aitherium/awvision) | a filename and a caption somebody wrote | what a model actually reports about the pixels |
216
+ | [awscreen](https://github.com/Aitherium/awscreen) | a selector that was true when the page was written | the elements actually rendered, by what they look like |
217
+ | [awbeads](https://github.com/Aitherium/awbeads) | that a layout your users built survives the next deploy | the arrangement as data you can read back, diff, and hand to another surface |
218
+ | [awbonsai](https://github.com/Aitherium/awbonsai) | that inference always means a request left the machine | a WebGPU model answering on the tab's own GPU, with a consent record logged before it ever loaded |
210
219
  | [gawbbonet](https://github.com/Aitherium/gawbbonet) | the model to keep a 300-message campaign coherent by itself | campaign facts recalled from scoped memory you can list and edit |
211
220
  | [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | a vendor's quantisation defaults | sub-byte KV cache kernels you can benchmark yourself |
212
221
  | **awrtifact** _(you are here)_ | a hand-rolled split script and a hand-edited worker manifest | byte-verified parts in a release, served with Range + CORS, sizes asserted by a live gate |
@@ -216,15 +225,21 @@ Each installs on its own, works offline, and needs no account.
216
225
  | [awrecurse](https://github.com/Aitherium/awrecurse) | that everything you pasted in was actually read | which slices it opened, and what it concluded from each |
217
226
  | [awprism](https://github.com/Aitherium/awprism) | the first explanation that fits | the ranked alternatives, and the observation that separates them |
218
227
  | [awrepl](https://github.com/Aitherium/awrepl) | what the agent believes the value is | the value, printed from the live session |
228
+ | [awreport](https://github.com/Aitherium/awreport) | that the report you pasted carried no token in it | a redacted report, and the duplicate it merged into instead of filing twice |
219
229
  | [awresearch](https://github.com/Aitherium/awresearch) | a summary of pages nobody opened | every claim against the source it came from |
220
230
  | [awfocus](https://github.com/Aitherium/awfocus) | twelve terminal tabs and a bad memory | one command that names every session, finds any transcript, and opens or steers the one you want |
221
231
  | [awgym](https://github.com/Aitherium/awgym) | that a world model learned anything from the games it saw | transitions captured from real play, fed back, and the retrodiction score falling on grids it never saw |
222
232
  | [awpredict](https://github.com/Aitherium/awpredict) | a model because it trained without erroring | its prediction against a self-updating lookup, on the rows that are actually novel |
233
+ | [awevolve](https://github.com/Aitherium/awevolve) | that your optimisation loop is finding anything | every version it kept, the score that version earned, and the edit that produced it |
223
234
  | [awsh](https://github.com/Aitherium/awsh) | that you already know the name of the command | what it decided your line meant, before it acts on it |
235
+ | [awmine](https://github.com/Aitherium/awmine) | that a session's lesson survived the session | a row per outcome, a candidate per lesson, and the transcript line each one came from |
236
+ | [awrise](https://github.com/Aitherium/awrise) | that a scheduled agent ran at all, and ran exactly once | a durable record of every wake -- fired, skipped, overlapped or timed out -- each with its reason |
224
237
  | [awkno](https://github.com/Aitherium/awkno) | that the docs site is up, or that you remember the family | the whole ecosystem in your terminal, with no network at all |
238
+ | [awwall](https://github.com/Aitherium/awwall) | that a service only talks to the hosts you think it talks to | an explicit egress allowlist, where a denial names the rule that denied it |
225
239
  | [awembed](https://github.com/Aitherium/awembed) | a general-purpose embedder that has never seen your code | a held-out split of whole directories, scored teacher vs student vs int8 |
226
240
  | [awtax](https://github.com/Aitherium/awtax) | a closed tax app's sealed file you can never read again | a plain, provider-neutral schema of every figure, with the page it came from |
227
241
  | [awsettings](https://github.com/Aitherium/awsettings) | that you will remember to re-approve the same thing on every box you work from | one profile, unioned rather than overwritten, with the credentials left behind |
242
+ | [awavatar](https://github.com/Aitherium/awavatar) | a cloud 3D vendor's opaque task id | a manifest with a sha256, a licence and a rig-audit verdict per file |
228
243
 
229
244
  [**awnix**](https://github.com/Aitherium/awnix) is the ground floor — A Linux you can hand to an agent — immutable base, capabilities included.
230
245
 
@@ -238,11 +253,13 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
238
253
  | [awskills](https://github.com/Aitherium/awskills) | Portable agent skills — self-contained procedures an agent loads on demand | [docs](https://aitherium.github.io/awskills/) |
239
254
  | [awpack](https://github.com/Aitherium/awpack) | First-party agent packs — the ones we build, versioned and installable on their own | [docs](https://aitherium.github.io/awpack/) |
240
255
  | [awm](https://github.com/Aitherium/awm) | A portable, scoped agent memory | [docs](https://aitherium.github.io/awm/) |
256
+ | [awdesk](https://github.com/Aitherium/awdesk) | Aither World Desk -- the desktop body of AitherOS Online: tray, avatars, decision cards, the Living Desktop as an overlay | [docs](https://aitherium.github.io/awdesk/) |
241
257
  | [awnode](https://github.com/Aitherium/awnode) | A lightweight local gateway — bridges your apps to the AI backends you chose | [docs](https://aitherium.github.io/awnode/) |
242
258
  | [awrun](https://github.com/Aitherium/awrun) | A priority-aware queue and dispatcher for agentic runs and ad-hoc CI builds. It also judges whether the runner pool is big enough for the queue it is draining, and can ask a host to grow it -- reserving capacity is zero-sum, so a saturated pool needs more of it, not a different share of it | [docs](https://aitherium.github.io/awrun/) |
243
259
  | [awgraph](https://github.com/Aitherium/awgraph) | A semantic code graph for agents — AST + tree-sitter, call graphs | [docs](https://aitherium.github.io/awgraph/) |
244
260
  | [awgit](https://github.com/Aitherium/awgit) | Semantic version control on top of git — edit-ops and leases | [docs](https://aitherium.github.io/awgit/) |
245
261
  | [awdelphi](https://github.com/Aitherium/awdelphi) | Anonymous multi-round expert panels — a converged answer with a trace | [docs](https://aitherium.github.io/awdelphi/) |
262
+ | [awclassify](https://github.com/Aitherium/awclassify) | Classify any document -- what it is, who may read it, who it is for, what it is about | — |
246
263
  | [awtoll](https://github.com/Aitherium/awtoll) | What every tool call costs you in context, measured from your own transcripts | [docs](https://aitherium.github.io/awtoll/) |
247
264
  | [awseal](https://github.com/Aitherium/awseal) | Sign an artifact so a stranger can verify it | [docs](https://aitherium.github.io/awseal/) |
248
265
  | [awshare](https://github.com/Aitherium/awshare) | Publish an artifact and fetch it back verified | [docs](https://aitherium.github.io/awshare/) |
@@ -251,6 +268,7 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
251
268
  | [awiam](https://github.com/Aitherium/awiam) | Who is this caller? A directory and session store that fails honestly | [docs](https://aitherium.github.io/awiam/) |
252
269
  | [awtunnel](https://github.com/Aitherium/awtunnel) | Reach a service that has no public address | [docs](https://aitherium.github.io/awtunnel/) |
253
270
  | [awnest](https://github.com/Aitherium/awnest) | Prove there is a human before you let them into the nest | [docs](https://aitherium.github.io/awnest/) |
271
+ | [awrena](https://github.com/Aitherium/awrena) | Put two agents head to head and get a verdict you can check | [docs](https://aitherium.github.io/awrena/) |
254
272
  | [awnboard](https://github.com/Aitherium/awnboard) | A front gate you can put in front of anything, and hand someone the key to | [docs](https://aitherium.github.io/awnboard/) |
255
273
  | [awnix](https://github.com/Aitherium/awnix) | A Linux you can hand to an agent — immutable base, capabilities included | [docs](https://aitherium.github.io/awnix/) |
256
274
  | [awrecover](https://github.com/Aitherium/awrecover) | Labelled snapshots with an all-or-nothing restore | [docs](https://aitherium.github.io/awrecover/) |
@@ -259,9 +277,17 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
259
277
  | [awask](https://github.com/Aitherium/awask) | Your agent asks you a question — and acts on your answer | [docs](https://aitherium.github.io/awask/) |
260
278
  | [awmail](https://github.com/Aitherium/awmail) | Give an agent an email address — send, and actually receive | [docs](https://aitherium.github.io/awmail/) |
261
279
  | [awnet](https://github.com/Aitherium/awnet) | The agentic web — agents host a mesh, and agents join one | [docs](https://aitherium.github.io/awnet/) |
280
+ | [awswarm](https://github.com/Aitherium/awswarm) | Run one model too big for any single GPU across a pool of small ones | — |
262
281
  | [awfind](https://github.com/Aitherium/awfind) | A portable search client — query, results, ranking | [docs](https://aitherium.github.io/awfind/) |
263
282
  | [awbrowse](https://github.com/Aitherium/awbrowse) | A portable browser client — navigate, console, network, DOM, screenshot | [docs](https://aitherium.github.io/awbrowse/) |
283
+ | [awvoice](https://github.com/Aitherium/awvoice) | Hear and speak — transcribe audio, synthesize a voice | [docs](https://aitherium.github.io/awvoice/) |
284
+ | [awvision](https://github.com/Aitherium/awvision) | See an image — describe it, ask it a question, compare two | [docs](https://aitherium.github.io/awvision/) |
285
+ | [awscreen](https://github.com/Aitherium/awscreen) | See this machine — what is on screen, and where to click it | [docs](https://aitherium.github.io/awscreen/) |
286
+ | [awkit](https://github.com/Aitherium/awkit) | Render an agent panel from a tool result — one component, any React app | — |
287
+ | [awbeads](https://github.com/Aitherium/awbeads) | A spatial canvas for a page — arrange things, connect them, and keep the arrangement | — |
288
+ | [awbonsai](https://github.com/Aitherium/awbonsai) | Run a real model in the visitor's own browser — no server round trip, no upload | — |
264
289
  | [awknowledge](https://github.com/Aitherium/awknowledge) | How to run a coding agent so the result survives — the laws, with evidence | [docs](https://aitherium.github.io/awknowledge/) |
290
+ | [awbrain](https://github.com/Aitherium/awbrain) | Your history as a wiki of linked markdown — claims pinned to the evidence | — |
265
291
  | [gawbbonet](https://github.com/Aitherium/gawbbonet) | GobboNet campaigns with a real agent brain — scoped memory, graph recall | [docs](https://aitherium.github.io/gawbbonet/) |
266
292
  | [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | Near-optimal KV cache quantization for LLM inference — sub-byte compression | [docs](https://aitherium.github.io/aitherkvcache/) |
267
293
  | **awrtifact** _(you are here)_ | Deliberately chunk artifacts into GitHub release assets — the productized aitherkvcache mirror lane | [docs](https://aitherium.github.io/awrtifact/) |
@@ -271,15 +297,23 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
271
297
  | [awrecurse](https://github.com/Aitherium/awrecurse) | Answer a question over a context far larger than the window — recursively, with the trace kept | [docs](https://aitherium.github.io/awrecurse/) |
272
298
  | [awprism](https://github.com/Aitherium/awprism) | Turn a failure into ranked hypotheses — and say what would confirm each one | [docs](https://aitherium.github.io/awprism/) |
273
299
  | [awrepl](https://github.com/Aitherium/awrepl) | A REPL an agent can actually use — state that survives between turns | [docs](https://aitherium.github.io/awrepl/) |
300
+ | [awreport](https://github.com/Aitherium/awreport) | File a bug report that has already scrubbed your secrets and collapsed the duplicate | — |
274
301
  | [awresearch](https://github.com/Aitherium/awresearch) | Ask a research question, get a cited report you can check | [docs](https://aitherium.github.io/awresearch/) |
275
302
  | [awfocus](https://github.com/Aitherium/awfocus) | See, search and steer every Claude session from one command | [docs](https://aitherium.github.io/awfocus/) |
276
303
  | [awgym](https://github.com/Aitherium/awgym) | An ARC training gym — a game a world model can watch, and six roles that play through it | [docs](https://aitherium.github.io/awgym/) |
277
304
  | [awpredict](https://github.com/Aitherium/awpredict) | Predict what your environment does next, and how surprised you were | [docs](https://aitherium.github.io/awpredict/) |
305
+ | [awevolve](https://github.com/Aitherium/awevolve) | Point an agent at a file and a command that scores it, and let it improve | — |
278
306
  | [awsh](https://github.com/Aitherium/awsh) | Your terminal answers you -- type a question where a command would go | [docs](https://aitherium.github.io/awsh/) |
307
+ | [awmine](https://github.com/Aitherium/awmine) | Mine what your agents did -- outcomes, lessons and procedures out of the transcripts they left behind | — |
308
+ | [awrise](https://github.com/Aitherium/awrise) | Wake an agent on a schedule, let it do one thing, and put it back to sleep | [docs](https://aitherium.github.io/awrise/) |
279
309
  | [awkno](https://github.com/Aitherium/awkno) | The man page for the Aither World — every brick, stack and law, offline | [docs](https://aitherium.github.io/awkno/) |
310
+ | [awwall](https://github.com/Aitherium/awwall) | Say what a workload may reach, and watch everything else fail closed | [docs](https://aitherium.github.io/awwall/) |
311
+ | [awrouter](https://github.com/Aitherium/awrouter) | OpenRouter for your own fleet: pick a model backend by cost/latency/ capability, fail over, fit the context window, stream. Standalone, OpenAI-compatible, no Aither-specifics required to be valuable | — |
280
312
  | [awembed](https://github.com/Aitherium/awembed) | Train an embedding model that knows your corpus, and prove it beats the big one | [docs](https://aitherium.github.io/awembed/) |
281
313
  | [awtax](https://github.com/Aitherium/awtax) | Turn any tax PDF -- returns, W-2, 1099, statements, even scans -- into structured data you can check | [docs](https://aitherium.github.io/awtax/) |
314
+ | [awflow](https://github.com/Aitherium/awflow) | A deterministic workflow runtime — chain agent calls with journal replay and budget control | [docs](https://aitherium.github.io/awflow/) |
282
315
  | [awsettings](https://github.com/Aitherium/awsettings) | Your agent's permissions and config, following you to the next machine | [docs](https://aitherium.github.io/awsettings/) |
316
+ | [awavatar](https://github.com/Aitherium/awavatar) | One character spec in, a rigged, animated, multi-style avatar pack out | [docs](https://aitherium.github.io/awavatar/) |
283
317
 
284
318
  <div id="aither-constellation" data-self="awrtifact"></div>
285
319
  <script src="aither-constellation.js"></script>
@@ -47,5 +47,6 @@ tests/test_mirrorset.py
47
47
  tests/test_provision.py
48
48
  tests/test_sealing.py
49
49
  tests/test_serve_spec.py
50
+ tests/test_share_route.py
50
51
  tests/test_split_verify.py
51
52
  tests/test_upload.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "awrtifact"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
  description = "Aither World Artifact — deliberately chunk artifacts into GitHub release assets and fetch them back byte-verified. The productized aitherkvcache mirror lane."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -170,3 +170,49 @@ def test_prefix_route_is_a_namespace_for_chunked_and_r2():
170
170
  text = open(src, encoding="utf-8").read()
171
171
  assert "const fromR2 = baseOverride ? null : await serveFromR2(request, env, name);" in text
172
172
  assert "CHUNKED[name].upstream === baseOverride" in text
173
+
174
+
175
+ def _cross_repo_spec() -> dict:
176
+ spec = _spec()
177
+ spec["artifacts"] = [{
178
+ "id": "awnix-ai-full-iso",
179
+ "name": "awnix-ai-full-x86_64.iso",
180
+ "repo": "Aitherium/awnix",
181
+ "release": "awnix-iso-ai-full-2026.09.22",
182
+ "total": 300,
183
+ "parts": [200, 100],
184
+ "part_names": ["awnix-x86_64.iso.00.part", "awnix-x86_64.iso.01.part"],
185
+ }]
186
+ return spec
187
+
188
+
189
+ def test_cross_repo_named_parts_reach_the_chunked_map():
190
+ from awrtifact import spec as spec_mod
191
+
192
+ s = _cross_repo_spec()
193
+ spec_mod.validate(s)
194
+ entry = spec_mod.chunked_map(s)["awnix-ai-full-x86_64.iso"]
195
+ assert entry["upstream"] == (
196
+ "https://github.com/Aitherium/awnix/releases/download/awnix-iso-ai-full-2026.09.22/")
197
+ assert [p["name"] for p in entry["parts"]] == [
198
+ "awnix-x86_64.iso.00.part", "awnix-x86_64.iso.01.part"]
199
+ assert spec_mod.path_upstreams(s)["awnix-iso-ai-full-2026.09.22"].startswith(
200
+ "https://github.com/Aitherium/awnix/")
201
+ # the store repo still owns everything that does not say otherwise
202
+ assert all("aitherkvcache" in b or "awnix" in b for b in spec_mod.upstream_bases(s))
203
+
204
+
205
+ @pytest.mark.parametrize("mutate", [
206
+ lambda a: a.update(part_names=["only-one.part"]),
207
+ lambda a: a.update(part_names=["x.part", "x.part"]),
208
+ lambda a: a.update(part_names=["../x", "y"]),
209
+ lambda a: a.update(repo="not-a-repo"),
210
+ lambda a: (a.pop("parts"), a.update(total=300)),
211
+ ])
212
+ def test_cross_repo_spec_refuses_bad_data(mutate):
213
+ from awrtifact import spec as spec_mod
214
+
215
+ s = _cross_repo_spec()
216
+ mutate(s["artifacts"][0])
217
+ with pytest.raises(ValueError):
218
+ spec_mod.validate(s)
@@ -0,0 +1,133 @@
1
+ """The opt-in `/s/<share>` byte route in the generated worker.
2
+
3
+ Two properties, both of which a green build would hide if broken:
4
+
5
+ 1. OPT-IN IS BYTE-EXACT. A worker whose spec entry has no `share_grant_url`
6
+ renders exactly as before the route existed, so shipping the template does
7
+ not redeploy (or change) any worker by itself.
8
+ 2. THE ROUTE FAILS CLOSED. The rendered JS is executed under Node with a fake
9
+ grant-check and a fake R2 bucket: no ticket is 401, a revoked grant is 410,
10
+ an unreachable grant check is 503 (never a fall-through to serving), an
11
+ object key outside the share's own prefix is refused, and a valid ticket
12
+ streams the ciphertext with Range. Skipped only when Node is absent.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import shutil
19
+ import subprocess
20
+
21
+ import pytest
22
+ from awrtifact import serve_spec, worker_template
23
+
24
+
25
+ def _spec(share_url=None) -> dict:
26
+ worker = {"name": "awrtifact", "dir": "awrtifact", "route": "artifact.aitherium.com/*",
27
+ "r2_binding": "WEIGHTS", "r2_bucket": "aither-artifacts"}
28
+ if share_url:
29
+ worker["share_grant_url"] = share_url
30
+ return {"store": {"name": "awrtifact", "repo": "Aitherium/aitherkvcache"},
31
+ "workers": [worker], "upstreams": [{"release": "artifact-v1"}],
32
+ "allowlist": {"regex": r"^[A-Za-z0-9._-]+\.gguf$"}, "artifacts": []}
33
+
34
+
35
+ def test_absent_share_url_renders_no_share_code(tmp_path, monkeypatch):
36
+ js, _toml = serve_spec.render(_spec(), tmp_path / "awrtifact.yaml")
37
+ assert "serveShare" not in js
38
+ assert "__SHARE" not in js
39
+ # Byte-exact with the pre-route template (the slots removed outright).
40
+ legacy = worker_template.JS_TEMPLATE.replace("__SHARE_ROUTE_JS__", "").replace(
41
+ "__SHARE_ROUTE_DISPATCH__", "")
42
+ monkeypatch.setattr(worker_template, "JS_TEMPLATE", legacy)
43
+ js_legacy, _ = serve_spec.render(_spec(), tmp_path / "awrtifact.yaml")
44
+ assert js == js_legacy
45
+
46
+
47
+ def test_present_share_url_renders_the_route(tmp_path):
48
+ url = "https://api.example.com/api/share/grant-check"
49
+ js, _ = serve_spec.render(_spec(url), tmp_path / "awrtifact.yaml")
50
+ assert f"const SHARE_GRANT_URL = {json.dumps(url)};" in js
51
+ assert "return serveShare(request, env, shareSegs[1]);" in js
52
+ assert "env && env.WEIGHTS" in js
53
+ assert "__SHARE" not in js and "__R2_BINDING__" not in js
54
+
55
+
56
+ def test_non_https_share_url_is_refused(tmp_path):
57
+ with pytest.raises(ValueError, match="https"):
58
+ serve_spec.render(_spec("http://api.example.com/x"), tmp_path / "awrtifact.yaml")
59
+
60
+
61
+ _HARNESS = r"""
62
+ import worker from './index.mjs';
63
+ const CT = new Uint8Array(Array.from({length: 100}, (_, i) => i));
64
+ let grantMode = 'ok';
65
+ globalThis.fetch = async (url, init) => {
66
+ if (grantMode === 'down') throw new Error('unreachable');
67
+ const body = JSON.parse(init.body);
68
+ if (grantMode === 'revoked') return new Response('gone', {status: 410});
69
+ if (body.ticket !== 'good') return new Response('no', {status: 403});
70
+ const key = grantMode === 'escape' ? 'share/shr_OTHER0000/ciphertext.bin'
71
+ : `share/${body.share_id}/ciphertext.bin`;
72
+ return new Response(JSON.stringify({ok: true, object_key: key, size: 100, sha256: 'abc'}),
73
+ {status: 200, headers: {'Content-Type': 'application/json'}});
74
+ };
75
+ const reads = [];
76
+ const bucket = {
77
+ async get(key, opts) {
78
+ reads.push(key);
79
+ if (!key.startsWith('share/shr_abcdefgh12/')) return null;
80
+ if (opts && opts.range) {
81
+ const r = opts.range;
82
+ const off = r.suffix !== undefined ? 100 - r.suffix : r.offset;
83
+ const len = r.length !== undefined ? r.length : 100 - off;
84
+ return {size: 100, range: {offset: off, length: len}, body: CT.slice(off, off + len)};
85
+ }
86
+ return {size: 100, body: CT};
87
+ },
88
+ };
89
+ const env = {WEIGHTS: bucket};
90
+ async function hit(path, headers = {}) {
91
+ const req = new Request('https://artifact.aitherium.com' + path, {headers});
92
+ const r = await worker.fetch(req, env);
93
+ const buf = new Uint8Array(await r.arrayBuffer());
94
+ return {status: r.status, len: buf.length, first: buf[0], cr: r.headers.get('Content-Range'),
95
+ cache: r.headers.get('Cache-Control')};
96
+ }
97
+ const out = {};
98
+ out.noTicket = await hit('/s/shr_abcdefgh12');
99
+ out.badTicket = await hit('/s/shr_abcdefgh12?t=bad');
100
+ out.badId = await hit('/s/..%2Fetc?t=good');
101
+ out.whole = await hit('/s/shr_abcdefgh12?t=good');
102
+ out.range = await hit('/s/shr_abcdefgh12', {'X-Share-Ticket': 'good', Range: 'bytes=10-19'});
103
+ grantMode = 'revoked'; out.revoked = await hit('/s/shr_abcdefgh12?t=good');
104
+ grantMode = 'down'; out.down = await hit('/s/shr_abcdefgh12?t=good');
105
+ grantMode = 'escape'; out.escape = await hit('/s/shr_abcdefgh12?t=good');
106
+ out.reads = reads;
107
+ console.log(JSON.stringify(out));
108
+ """
109
+
110
+
111
+ @pytest.mark.skipif(shutil.which("node") is None, reason="node is not installed")
112
+ def test_rendered_share_route_fails_closed_under_node(tmp_path):
113
+ js, _ = serve_spec.render(_spec("https://api.example.com/api/share/grant-check"),
114
+ tmp_path / "awrtifact.yaml")
115
+ (tmp_path / "index.mjs").write_text(js, encoding="utf-8")
116
+ (tmp_path / "harness.mjs").write_text(_HARNESS, encoding="utf-8")
117
+ proc = subprocess.run(["node", "harness.mjs"], cwd=tmp_path, capture_output=True,
118
+ text=True, encoding="utf-8", timeout=60)
119
+ assert proc.returncode == 0, proc.stderr
120
+ out = json.loads(proc.stdout.strip().splitlines()[-1])
121
+ assert out["noTicket"]["status"] == 401
122
+ assert out["badTicket"]["status"] == 403
123
+ assert out["badId"]["status"] == 404
124
+ assert out["whole"] == {"status": 200, "len": 100, "first": 0, "cr": None,
125
+ "cache": "private, no-store"}
126
+ assert out["range"]["status"] == 206
127
+ assert out["range"]["len"] == 10 and out["range"]["first"] == 10
128
+ assert out["range"]["cr"] == "bytes 10-19/100"
129
+ assert out["revoked"]["status"] == 410
130
+ assert out["down"]["status"] == 503
131
+ assert out["escape"]["status"] == 403
132
+ # Refusals never touched the bucket: exactly the two served requests read it.
133
+ assert out["reads"] == ["share/shr_abcdefgh12/ciphertext.bin"] * 2
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes