Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,8 @@
]
}
]
},
"enabledPlugins": {
"mattpocock-skills@claude-plugins-official": true
}
}
9 changes: 9 additions & 0 deletions .claude/skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,12 @@ you changed a skill on purpose, run `scripts/readme.sh --lock` and commit the lo
| after an edit of `README.md`, and before the session ends | `.claude/settings.json` → `scripts/readme.sh --hook` | tells the agent to use `easy-pt-br` while the easy version is older than `README.md` |
| before a commit | `.githooks/pre-commit` (turn on with `just hooks`) | runs `scripts/readme.sh --check` when a README or a skill is staged |
| in CI | `just check` | the same check, for every pull request |

## Plugins

`.claude/settings.json` turns on these Claude Code plugins for everyone who opens this
repository. Claude Code asks each reader to trust and install them once.

| Plugin | From |
|---|---|
| `mattpocock-skills` | the `claude-plugins-official` marketplace |
2 changes: 1 addition & 1 deletion .claude/skills/skills.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"version": 1,
"note": "every skill here was written for this repository; none is a copy. SOURCES in each SKILL.md and in README.md say what each one read. sha256 of each file, checked by scripts/readme.sh --check.",
"files": {
"README.md": "d6cc5605db95be46fd2e1b077739cdd8e5d897573ee755c03399c281dbe8fceb",
"README.md": "d776fed8202f51b478effe71862d1ee1f10b9949c4789af02def06c1c614d83b",
"card-style/SKILL.md": "42755313375b690efb8f47fd3fd73f29eba21e07c04612c980cd2abbdfd70941",
"citation-score/SKILL.md": "7f58cb381bc9dc0d0a6536e5b56f3bd826b61c057c80ada2f95349a0abba0a24",
"easy-pt-br/SKILL.md": "5d7d5383721c3cd38694a425d9b449f8f70ad064ffd4179636bcbeb24eada7ba",
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ graphify-out/
# The Unison codebase of `just unison-install` (unison/refpulse/README.md)
unison/.codebase/
unison/refpulse/install.output.md
.unisonHistory
14 changes: 10 additions & 4 deletions site/src/closed_source_site/app.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -713,10 +713,11 @@ fn timeline() -> Element(Msg) {
}
}

/// The refpulse service's URL on Unison Cloud, printed by `just unison-deploy`. Empty until it is
/// deployed: then the button says so, unless the page is opened with `?refpulse=<url>` (a local
/// `just unison-serve`, for example).
pub const refpulse_url = ""
/// The refpulse service's URL on Unison Cloud, printed by `just unison-deploy`. A page opened with
/// `?refpulse=<url>` asks that URL instead (a local `just unison-serve`, for example).
/// shortcut: the hash URL, because naming the service failed (401); use .../s/refpulse once it is named.
pub const refpulse_url =
"https://h0ffmann.unison-services.cloud/h/uidn3rn5oag2ww53c5h4tltjlje76o3nlm44ct7tyjarcn24j2eq"

/// The book's own DOIs: the concept DOI, then each version's.
pub fn book_dois() -> List(String) {
Expand Down Expand Up @@ -762,6 +763,11 @@ fn live_section(model: Model) -> Element(Msg) {
"This page is static, on GitHub Pages. The button asks a Unison service, refpulse, to check now that each of the book's DOIs resolves at doi.org, and shows its answer here. A live answer, not a fact of the book.",
),
]),
html.p([attribute.class("sub")], [
html.text(
"Unison gives each piece of code a fingerprint, a hash of the code itself. So refpulse was not installed on a server: its code was sent to Unison Cloud, which runs it, and the long part of its address is that fingerprint. The click itself is an ordinary web request. Checking the DOIs on many machines at once is the next step.",
),
]),
html.button(
[
attribute.class("pulse-button"),
Expand Down
53 changes: 47 additions & 6 deletions unison/refpulse/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,35 @@ browser (h0ffmann.github.io) refpulse (Unison) doi.
draw ◄── 200 JSON, CORS: * ◄── {"checked","ran","results":[…]}
```

## In plain words

**What is Unison?** A programming language in which each piece of code has a fingerprint. The
fingerprint is a hash of the code itself: "Each Unison definition is identified by a hash of its
syntax tree" (*The big idea*). Two computers that agree on a fingerprint agree on the code, to
the last letter. So a computer can send code to another computer, and the other computer asks
only for the parts that it does not have yet. Unison's own words: "arbitrary computations can
just be moved from one location to another, with missing dependencies deployed on the fly".

**Like you are five.** Most web services are a shop: someone builds a kitchen (a server, a
container, a build), and you phone in an order. With Unison, you post the recipe itself, and the
cloud cooks it. The recipe has a fingerprint, so nobody can mix it up with another recipe.

**Is the button a remote computation, or a simple API call?** Both, at two different steps.

1. **The deploy is a remote computation.** `just unison-deploy` does not upload a program to a
server that we keep. It sends the function `Route.run refpulse.routes` to Unison Cloud, and
Unison Cloud runs it. Nothing is built: no container, no virtual machine, no YAML. Unison
Cloud says: "The hash is created from the code being deployed; it uniquely identifies the
service deployment". That hash is the long part of refpulse's address,
`/h/uidn3rn5…j2eq`. The address names the code, not a machine.
2. **The click is an API call.** The page sends `GET /check?doi=…` and receives JSON, as with any
web service. The browser does not know that Unison is at the other end.

**What is not done yet.** refpulse checks the DOIs one after another, on one machine. Unison can
also move a computation to other machines while it runs (`Remote.fork`; "the functions are
'moved to the data'", *Spark-like distributed datasets*). With that step, refpulse would check
all the DOIs at the same time, on different machines. It is the "Next" item below.

## The contract

| Request | Answer |
Expand Down Expand Up @@ -92,14 +121,19 @@ call `localhost`, after the prompt. A tunnel's public HTTPS address does not nee
in headless Chrome. The click went out, the answer came back, and the page drew "1 of 1 DOIs
resolve · 302". With the service down, it showed "refpulse did not answer". With no URL, it
showed "refpulse is not deployed yet".
- **Not yet checked: `refpulse.u` itself.** It was written against the names and types that
Unison Share's API gives for `@unison/cloud` 27.3.1, `@unison/routes` 7.0.2, `@unison/http`
16.1.0, `@unison/json` 1.4.2 and `@unison/base` 7.20.0. It was not compiled, because the agent's
sandbox could not download the libraries (`lib.install` stopped with `IncompleteHeaders`).
`just unison-install` compiles it; `install.output.md` shows any error.
- **Checked (15 October 2026): `refpulse.u` itself.** `just unison-install` compiles it against
`@unison/base` 7.20.0, `@unison/cloud` 27.3.1 and `@unison/routes` 7.0.2. `just unison-serve`
ran it in the local runtime. `GET /health` gave 200. `GET /check` gave 302 for the book's DOI
and 404 for a made-up one, with `Access-Control-Allow-Origin: *`.
- **Deployed (15 October 2026).** `just unison-deploy` put refpulse on Unison Cloud at
`https://h0ffmann.unison-services.cloud/h/uidn3rn5oag2ww53c5h4tltjlje76o3nlm44ct7tyjarcn24j2eq`.
It answered `/health` 200, and `/check` 302 and 404, with the CORS header. The site's
`refpulse_url` is this address. The step after it, `ServiceName.assign` (the stable name
`/s/refpulse`), failed with 401 Unauthorized. When the name works, put `/s/refpulse` in
`refpulse_url`: a name stays the same when the code changes, and a hash does not.
- **Next.** The checks run one after another, which takes about 0.5 s per DOI. `Remote.fork` and
`Remote.await` could run them at the same time, on many machines, if the forked task may call
`Http`. That is the "distributed" step, after this one compiles.
`Http`. That is the "distributed" step.

## Sources

Expand All @@ -110,3 +144,10 @@ call `localhost`, after the prompt. A tunnel's public HTTPS address does not nee
`response.headers.add`, the CORS middleware example.
- `@unison/cloud` 27.3.1 README, <https://share.unison-lang.org/@unison/cloud>, read the same
way: `deployHttp`, `ServiceName.assign`, `parMap`, `Remote`.
- Unison, "The big idea", <https://www.unison-lang.org/docs/the-big-idea/>, read 15 October
2026: definitions identified by a hash; computations moved with their dependencies.
- Unison Cloud, home page, <https://www.unison.cloud/>, read 15 October 2026: deployments get
"a unique content-addressed hash"; no containers, VMs or YAML.
- Unison, "Spark-like distributed datasets in under 100 lines of Unison",
<https://www.unison-lang.org/articles/distributed-datasets/>, read 15 October 2026: functions
"moved to the data".
1 change: 1 addition & 0 deletions unison/refpulse/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ A UCM transcript (`just unison-install`): it makes the `refpulse` project in the

```ucm
scratch/main> project.create-empty refpulse
refpulse/main> lib.install @unison/base
refpulse/main> lib.install @unison/cloud
refpulse/main> lib.install @unison/routes
refpulse/main> load unison/refpulse/refpulse.u
Expand Down
Binary file added unison/refpulse/live-check.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion unison/refpulse/refpulse.u
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ refpulse.routes = do
, ("results", Json.array results)
])
refpulse.cors()
health <|> check
(health <|> check)()

-- On this machine: the Cloud runtime, locally. It prints the service's URL and serves until Enter.
refpulse.deploy.local : '{IO, Exception} ()
Expand Down
Loading