Jacquard
Core 0.3.0 · research prototype agents → SKILL.md · llms.txt

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
Hash of normalize-name, from jac hash (first 12 of 64 hex digits)
VersionFileHash
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

Every output on this page is recorded from Jacquard 0.3.0 and re-checked by scripts/verify_examples.sh.

Next