Home

Patch format

A patch is one JSON file with two nested layers. Both are checked in full before anything is applied, and an envelope that does not match is refused rather than partially landed.

Write the file, then hand it to your operator. It goes into their Downloads folder, where the connector notices it within a few seconds.

What you need before writing one

Three of these come from the operator and cannot be guessed; the fourth you choose yourself.

interaction_id and authorization
Minted together on the Agent access panel at llambda.net. They are checked as a pair, so a correct token beside an unfilled id fails exactly like a wrong token. Minting again revokes the previous pair.
The collection id
A symbolic name the operator registered, such as tests — never a filesystem path. They can read it from collections at the llambda> prompt.
The gizmo and gadget names
Both name the project inside that collection, and for a scaffolded gizmo they are the same word. Neither has to exist yet: ask for new-gadget in the same patch and it is created first.
A patch id you have not used
Any unique string. Reusing one is how a corrected patch is mistaken for the patch it corrects, and the second is then ignored as already seen.

A patch that changes one line

This is the whole of an ordinary patch. It assumes a collection registered as tests holding a gizmo hello-patch scaffolded by new-gadget, and it changes what the --hello route prints:


{
  "schema": "LLAMBDA_DOWNLOAD_PATCH_V1",
  "interaction_id": "<from the Agent access panel>",
  "patch_id": "hello-greeting-1",
  "authorization": "<from the same panel>",
  "target": {
    "collection": "tests",
    "gizmo": "hello-patch",
    "gadget": "hello-patch",
    "agent": "edd"
  },
  "patch": {
    "format": "LS_FORK_JSON_PATCH_V1",
    "agent": "edd",
    "title": "Change the hello greeting",
    "files": [
      {
        "op": "upsert",
        "path": "hello-patch.gizmo/hello-patch.wires/projects/hello-patch/src/hello-patch/lib/HelloPatch.hs",
        "content": "module HelloPatch where\n\ngo :: String -> [String] -> IO String\ngo \"--hello\" _ = pure \"hello patches!\"\ngo route _ = pure (\"unknown route: \" ++ route)\n"
      }
    ]
  }
}

Three things in it are worth copying exactly. collection is the registered id, not a path. path is relative to the collection root with forward slashes, and names the module the scaffold wrote. content is the entire replacement file — there is no diff operation, so a patch that means to change one line still carries every other line with it.

Drop it in the operator's Downloads folder. The connector validates it, applies and commits the edit, then compiles and installs. What the compiler said comes back in the result.

Ask the operator to run it. A result saying the patch compiled is not the same as the program doing what you meant, and this is the only step that tells you which you got:


> cd C:\projects\hello-patch.gizmo\hello-patch.wires
> onepush.bat
...
Test suite hello-patch-cog: PASS

> bin\hello-patch.exe --hello
hello patches!

The envelope


{
  "schema": "LLAMBDA_DOWNLOAD_PATCH_V1",
  "interaction_id": "<your interaction id>",
  "patch_id": "<unique per patch>",
  "authorization": "<token>",
  "target": {
    "collection": "<registered collection id, e.g. work>",
    "gizmo": "<gizmo>",
    "gadget": "<gadget>",
    "agent": "<your name>"
  },
  "patch": {
    "format": "LS_FORK_JSON_PATCH_V1",
    "agent": "<your name>",
    "title": "<one line>",
    "files": [
      { "op": "upsert", "path": "<collection-relative>", "content": "<whole file>" },
      { "op": "move", "from": "<old path>", "path": "<new path>" },
      { "op": "delete", "path": "<collection-relative>" }
    ]
  }
}

File operations

Every entry in files names an op. Each one is decided entirely by the patch: nothing depends on what the file currently holds, which is why there is no diff or search-and-replace operation. The same patch is replayed later when lanes are amalgamated, and an operation that read the existing text would produce something different on replay and conflict on a file nobody edited.

upsert
path and content. Writes the file, creating parent directories. The content is the whole file, not a diff and not a fragment, and its bytes are written exactly as sent — so send a file in one line-ending form, never part CRLF and part LF.
delete
path. Removes one file. An already-absent path is not an error: the operation states the end result, and a replay over a tree that has since dropped the file is ordinary.
copy
from and path. Copies bytes from one path to another without sending the content. The source must exist when the patch lands.
move
from and path. Copies then removes the source. This is how a module is renamed: one entry, no content over the wire, and the rename stays legible as a rename in the history.
delete-tree
path. Removes a directory and everything under it. This is how a gizmo is abandoned — listing its files individually requires knowing the whole tree, and quietly leaves behind anything the author did not know about.

Operations

Files are not the whole of what a patch can ask for. Scaffolding a new gizmo means running the generators, which no arrangement of file writes expresses. An operations array, beside patch in the envelope, asks the connector to do it:


"target": {
  "collection": "<registered collection id>",
  "gizmo": "widget",
  "gadget": "widget",
  "agent": "<your name>"
},
"operations": [
  { "op": "new-gadget", "name": "widget" }
],
"patch": { ... files that edit widget.gizmo ... }
new-collection
name. Establishes a new collection of that name beside the one the envelope targets, using the toolchain the connector ships, and registers it so later patches can target it by the same name. The directory must not already exist — that is what keeps this safe: the operation only ever works in a folder it just created, so nothing already on the machine can be read or written. A name that collides with anything is refused, and a failure part-way removes what it made rather than leaving a half-built tree behind.
new-gadget
name. Scaffolds a buildable gizmo of that name in the collection named by target. The name is symbolic — letters, digits, dash and underscore — and becomes both the gizmo directory and its module. Succeeds without changing anything if the gizmo is already there.

This is not a shell. The connector runs a fixed set of its own commands, and every argument is a symbolic name rather than a path: a gizmo name is letters, digits, dash and underscore, and the collection is the one already named in target. A patch cannot name a directory, cannot name a program, and cannot reach a collection its authorization did not admit it to.

Operations run before the files land, so one patch can create a gizmo and populate it. They are also idempotent: asking for a gizmo that already exists succeeds and changes nothing, which is what lets a patch be resent after a failure further along.

Starting a new gizmo

Name the new gizmo and gadget in target — neither has to exist. Ask for new-gadget, and the scaffold it writes is a gizmo that compiles, installs and passes its tests before you have edited anything:


<name>.gizmo/
  <name>.gadgets                  authority: the gadget and its junction
  <name>.wires/
    <name>.wire                   name, projects folder, bin folder
    onepush.bat                   build, install, test
    projects/
      wire.project                which packages this gadget holds
      <name>/src/<name>/
        package.nut               dependencies, modules, go-is entry point
        package.garrage           the build-stage view of the same
        package.cog               the test suite
        package.bolt              what the executable links
        buttons.plan              the lifecycle phases
        lib/<Module>.hs           your code

Then upsert your own module over lib/<Module>.hs and add whatever else the project needs in the same patch. Everything else in that tree is generated on the first onepush and regenerated on every one after, so leave the .cabal, the entry point and the test cog alone.

Landing builds it for you. Once the files are applied and committed, the gadget is compiled and installed, and what the compiler said comes back in the result — so a patch that creates a gizmo tells you in one round whether it compiles. The build is install-only, so run onepush.bat yourself when you want the tests too.

Rules that cost a round when broken

collection is a name, not a path
It is the id the operator registered a collection under — work, say — and the connector resolves it to a directory on their machine. A filesystem path here is refused, and so is an id nobody has registered. Ask your operator which id to use: they can read it from collections at the llambda> prompt. Note that one machine usually holds several collections side by side, so the folder containing them is not itself a collection.
interaction_id is not the token
Both are checked, and they are checked together. A correct token with an unfilled placeholder id fails exactly like a wrong token, so resealing the token never helps. Fill in the id.
Every file entry needs op
Either upsert or delete. Nothing else is accepted, and an entry without one is rejected before the rest of the envelope is read.
Paths are relative to the collection root
Not to the gadget. A file in the paint gadget starts paint.gizmo/paint.wires/. Forward slashes only: a path containing a backslash or a colon is refused, as is one that is absolute or that contains a . or .. segment.
content is the whole file
Not a diff, and not a fragment. delete entries carry no content.
patch_id must be new each time
Reusing one is how a corrected patch gets mistaken for the patch it corrects, and the second one is then ignored as already seen.
No extra top-level fields
Unknown fields are refused by name. The refusal reads as though the whole patch is wrong, when usually one field is misspelled or one layer is missing its wrapper.
semantic is optional
It names the kind of edit, for a person and for Elliot's templates. The lander does not read it, so leaving it out is fine and including it is accepted.

Limits

Adding a Haskell module

The build is descriptor-driven. A module that is not declared will compile on your machine and fail in the collection, so a patch adding one must also patch package.nut — the module under exposes:, and any new package under depends:. Do not patch the generated .cabal file: it is written by wires cast from package.nut, and a hand-edited one is skipped by the generator from then on.

What comes back

land_ok: false
It never landed. The patch did not apply, the target was wrong, or the envelope was malformed. Nothing was built, so there are no compiler diagnostics to read.
land_ok: true, build_ok: false
It landed and would not compile. Read diagnostics; they are the compiler's own words, capped at the first ten useful lines.
ok: true
Landed, compiled and installed. The build that runs on landing is install-only, so this does not say the test cog passed — run onepush for that.