Tour 1 of 2
Review a change a model wrote
A model has edited a function that cleans up display names. This tour follows the change the way a reviewer meets it, and shows which questions Jacquard answers before you read a function body.
1. The diff you are asked to approve
--- a/normalize.jac
+++ b/normalize.jac
@@ -1,3 +1,6 @@
-- Clean up a display name before it is stored.
-normalize-name : (Text) ->{} Text
-normalize-name(name) = text.trim(name)
+normalize-name : (Text) ->{Net} Text
+normalize-name(name) = text.trim(lookup-alias(name))
+
+lookup-alias : (Text) ->{Net} Text
+lookup-alias(name) = net.get(text.concat("https://aliases.example/", name))
In most languages, finding out whether this can reach the network means
reading lookup-alias and everything it calls. Here the answer is
in the diff: the signature of normalize-name changed from
->{} to ->{Net}.
2. The signature is checked, not written down
The checker infers every row and reports them. Nobody can forget to add one.
$ jac check after.jac --print-sigs
lookup-alias : (Text) ->{Net} Text
normalize-name : (Text) ->{Net} Text Suppose the model had left the old signature in place. The program would not check, because a closed row cannot absorb an effect the body performs:
$ jac check stale.jac
stale.jac:3:1-53: error[E0804]: The value does not satisfy its annotation
Cause: equation definition `normalize-name` does not match its signature: expected (text) ->{} text, got (text) ->{net | e} text (a closed effect row cannot absorb extra effects; a stored definition passed as a thunk can be eta-expanded at the use site: (lam () (app (var f))))
Next step: Change the value or its annotation so the two types agree.
[exit status 1] 3. What changed, semantically
jac diff compares the two versions' canonical structure rather
than their text. It names the added definition, and for the changed one it
shows the authority the change asks for:
$ jac diff before.jac after.jac
added lookup-alias
changed normalize-name
at normalize-name/defterm[0]/group[0]/binding[1]/group[0]/tarrow[1]:
- authority {}
+ authority {Net [world/high] — reach a network endpoint through the granted handler}
at normalize-name/defterm[0]/group[0]/binding[2]/lam[1]/app[1]:
- name
+ #84ba72f2f9241987421fc893d2d75926f5aa398b39fe4aa3098b0df6b2fbf92e:term(name)
The output is still close to the kernel. Each at line is a path
into the definition's kernel tree: tarrow[1] is the row of its
type, and lam[1] is its body. Inside the body, the new call to
lookup-alias appears as that definition's hash, because the
body refers to the definition by identity, not by name. The first change is
the one a reviewer cares about: authority went from nothing to
Net. A more readable default for this output is planned.
4. Nothing happens without a grant
A program that calls normalize-name now needs network
authority. Without a grant the runtime stops before anything runs:
$ jac run run.jac
error[E0814]: The program requires an effect that was not granted
Cause: This program requires net [world/high] — reach a network endpoint through the granted handler, which is not granted (performed via `normalize-name`).
Next step: grant it with --allow net, or handle the effect in the program
[exit status 3] A dry run shows what it would do, without granting anything. Network requests are recorded and answered by a simulation:
$ jac run --dry-run run.jac
"<dry-run response>"
dry-run dispositions: console=forwarded clock=forwarded fs.read=forwarded fs.write=audited net.fetch=audited+simulated infer.complete=audited+simulated dist=simulated(seed 0) eval=refused
dry-run: this run WOULD have:
fetched https://aliases.example/ ada 5. Approve by hash
An approval can name the exact canonical hash of what you reviewed.
Comments, layout, local names and the order of definitions are not part of
it. Here is the same change after an agent reformatted it, renamed its
parameters and moved lookup-alias first:
-- Look the name up, then trim it.
lookup-alias : (Text) ->{Net} Text
lookup-alias(n) =
net.get(text.concat("https://aliases.example/", n))
normalize-name : (Text) ->{Net} Text
normalize-name(display) = text.trim(lookup-alias(display)) $ jac diff after.jac after-reformatted.jac
no semantic changes | Version | File | Hash |
|---|---|---|
| Before the change | before.jac | ccf28f9616ad |
| After the change | after.jac | 9066d9579e71 |
| After, reformatted and renamed | after-reformatted.jac | 9066d9579e71 |
An approval of 9066d9579e71… still holds after
the reformat. Any structural change produces a new hash, which needs a new
approval. Equal hashes mean equal canonical structure; they say nothing about
whether two different programs behave alike.
6. An approval pins what lies beneath it
A definition refers to everything it calls by hash, so its own hash covers
them. Suppose a later change edits only lookup-alias, sending
names to a different host:
$ jac diff after.jac dep-edit.jac
changed lookup-alias
at lookup-alias/defterm[0]/group[0]/binding[2]/lam[1]/app[1]/app[1]/lit[0]:
- "https://aliases.example/"
+ "https://names.example/"
dependents: normalize-name
changed normalize-name
at normalize-name/defterm[0]/group[0]/binding[2]/lam[1]/app[1]/app[0]/ref[0]:
- #84ba72f2f9241987421fc893d2d75926f5aa398b39fe4aa3098b0df6b2fbf92e
+ #0320bcfd0739cce280ba0aea01841f6255752ce9f37b3c3efc98b58ffd1daaa1 normalize-name was not touched, yet its hash moved from
9066d9579e71 to d198087ca873,
and jac diff lists it as a dependent. Nothing can be swapped in
underneath an approval without changing the hash it names.
An approval record can be a single line. The escrow demo that ships with
the release keeps one in a plain file,
APPROVAL,
naming who approved which hash.
What the reviewer did not have to do
- Read the bodies of every function the change calls.
- Trust that a hand-written annotation was kept up to date.
- Run the change to find out what it would reach.
- Re-review a change that an agent only reformatted.
- Wonder whether something beneath an approved definition changed.
Every output on this page is recorded from Jacquard 0.3.0 and
re-checked by scripts/verify_examples.sh.
Next
- Tour 2: one policy, three worlds: what a change will do in situations you cannot reproduce.
- Why a language? Common objections, answered.