# knotless: runs on an atlibs composition A knotless repository picks an **atlibs composition**; `knotless-run` runs the repo's CI on it: rent a VM on the socialweb.computer market → check out the commit → run the steps → release. Status 2026-11-10: **works end to end, live**, on atlibs 0.3.0. ## Where | What | Where | |---|---| | Deployed | https://knotless.socialweb.computer (serve + pages), https://knotless-runner.socialweb.computer (runner, per-composition OAuth); deploy: this repo | | Runner + Executor UI | fork `johnandersen777.bsky.social/knotless`, branch `runner-atlibs-composition` @ `f47f4e62` (24 commits on upstream `main`; deployed; fork only, no PRs) | | Design + decisions | that branch: `docs/design/atlibs-compositions.md` | | The composition | `bobvmbuilder.bsky.social/socialweb-computer-cicd` | | Its source | `atlibs-socialweb-computer-requester`: `compositions/socialweb-computer-cicd.compose.ts` → committed `composition.json` | | Scratch experiment | branch `runner-socialweb-computer` (kept, not developed) | ```bash git clone https://knotless.dev/johnandersen777.bsky.social/knotless git -C knotless checkout runner-atlibs-composition less knotless/docs/design/atlibs-compositions.md ``` ## How a run goes ``` push → knotless-run getRecord com.fedfork.exec.binding/ (else /self) ← the owner's binding, pinned by CID trust check: composition + every module publisher ∈ --trust-publisher load composition at its CID (atlibs-host, in process, Rust) prepare (once per owner) → vm.provision → workload.run (checkout + steps via the process contract) → vm.release (always) → dev.knotless.check success | failure ``` ## The composition (atlibs 0.3.0) ```ts // compositions/socialweb-computer-cicd.compose.ts (excerpt) const machine = c.instance("machine", requester, { session: "requester", input: { bidders, kind: "vm", cpus, mem, ssh_key: host("sshPublicKey"), shell: true, iroh: true, via: "iroh", release: false }, }); const workload = c.instance("workload", cicdWorkload); for (const step of ["start", "write", "read"] as const) c.route(workload, `computer.socialweb.cicd.process.${step}`, process_, `the job's ${step} on the VM`); c.export("computer.socialweb.vm.prepare", machine, "bind_key"); c.export("computer.socialweb.vm.provision", machine); // the runner calls the exports c.export("computer.socialweb.vm.release", machine); c.export("computer.socialweb.cicd.workload.run", workload); ``` ```bash K=https://knotless.dev/johnandersen777.bsky.social git clone -q $K/atlibs && git -C atlibs checkout -q 88b2ba8 # v0.3.0, the sibling compose.ts imports git clone -q $K/atlibs-socialweb-computer-requester && cd atlibs-socialweb-computer-requester node --test compositions/compose.test.mjs # fails if a composition.json isn't what its compose.ts writes atlibs compose check compositions/socialweb-computer-cicd.composition.json ``` Routes reach the target's `implements` (no `entry`); instance input is typed by `module.json` `config`. ## Bind a repository Copy-paste block (validated): design doc § **Bind a repository**. Result: `com.fedfork.exec.binding/` in the owner's repo (an org's repo: the org's account; `self` = every repo). Settings › **Executor** (repo or account) does the same from the UI. ## Live evidence | Run | Push | VM | Result | |---|---|---|---| | first green (OAuth per composition) | `7ebc5c5` | hat-9 | steps ok; release bug → fixed | | release fixed | `00df7b3` | hat-9 | success 59 s, `vm.terminated` on its own, records deleted | | atlibs 0.3.0 model | `861f3813` | hat-9 | success 57 s, released, records deleted | | final composition `bafyreiag5yjb…` | `ef96ee9f` | hat-7 | success 46 s, released, records deleted | | deployed runner, composition `bafyreifzmo3b…` (requester `dab378f8`, user-data `c605eb61`) | `325f96d` | hat-7 | success 52 s, released (`fc-lpfuqerea3de-ed3fca28`), no round records in the owner's account | Rows 1–4 as `aliceoa.bsky.social/kc-ci-test`, runner on hat-8; row 5 as `aliceoa.bsky.social/kc-ci-demo` on https://knotless-runner.socialweb.computer. ## Decided - NSIDs: `com.fedfork.exec.*` (atlibs, sandboxed: binding, composition); `computer.socialweb.*` (external compute: `vm.provision/release/prepare`, `cicd.process.*`). - Rents with the **repo owner's own session** (they pay; their vouches count). OAuth **per composition**; app-password fallback. - Binding lookup: getRecord at the repo key, then `self`. Never `listRecords`. Precedence: workflow > repo > account. - Publisher allowlist (`--trust-publisher`, DIDs), compositions pinned by CID; default net denylist (loopback, link-local, metadata, private ranges). - **Workload modules run the steps** over a generic process contract (`exec/start/read/write/kill/list`); MCP is only an adapter. - The workload fetches the commit from the owner's PDS; private repos get a narrower read-only per-repo grant. - In process Rust: `atlibs-host` + `atlibs-iroh` as rev-pinned crates; no Node, no CLI, no extra binary. - One attestation key per **owner**, not per composition. - Compositions are **written in code** (TS / Rust), JSON generated + committed (atlibs 0.3.0). ## Open - Pulls / merge queue: refused (their merged commit is on no remote). - OAuth session is per composition **CID**: a republish means signing in again (live runs used the app-password fallback after repins). - Orgs that delegated before `c9b05996` must delegate again to bind (`repo:com.fedfork.exec.binding`). - The runner doesn't log the release result; termination is seen in the bidder's log. - ~~Runner deploy is manual~~: deployed as a systemd-managed container on the gateway (this repo). - ~~`/oauth/bot` had no `repo:dev.knotless.runnerKey`~~: fixed in the fork (`6879ad7e`, bots only, no one else's login behind); a runner's client-side warn on the 25 s `awaitSpaceWrites` long poll fixed (`25b8fb0c`). - Shots don't cover the Executor page's lookup view. - Workloads can fetch only `iroh://` (covers the net denylist). - Upstream: none (fork only, by policy). - The runner image needs `dumbpipe` (0.40.1) and an `ssh` client: `atlibs-iroh` reaches a rented machine with them. The fork's Dockerfile ships neither; this repo's `Dockerfile` adds both. - ~~The fork's `atlibs-host`/`atlibs-iroh` turn on `serde_json/preserve_order` for the whole workspace, so triage asked its questions in label order~~: fixed in the fork (`95899bec`, triage sorts with a `BTreeMap`; atlibs keeps its ordered maps). - ~~A merge right after a push or another merge said ` has no branch `~~: a single pull's reads (`Target::around`) took its state records (merged, retargeted) from the appview's index only, which hadn't heard of one written a moment before; they now also read the writers' newest state records, as the merge queue does (fork `f47f4e62`). Verified live: stacked `demo-low` → `demo-base`, then `demo-mid` (was on `demo-low`) merged right after it from the pages. - With those crates beside it, rustls had both ring and aws-lc-rs and knotless-serve's Jetstream panicked: fixed in the fork (`ed991eb5`, ring picked in `Ctx::new`). ## Fork tests `cargo test --workspace` alone fails ~100 tests, the same at the base: each test binary starts its own PDS with 32 accounts and the big ones run out (`index out of bounds: the len is 32`, then `PoisonError`). Run them as the Justfile does, against one shared PDS (Node 24, `.node-version`; `zoekt-webserver` on PATH for code search): ```sh just pds & # harness/pds.mjs, accounts made as asked just t --workspace --no-fail-fast ``` Code search and `binary_lifecycle` (which starts serve with `--code-index`) need zoekt at the Dockerfile's commit: ```sh GOBIN=$PWD/.tools go install github.com/sourcegraph/zoekt/cmd/zoekt-git-index@e1fafb15177040d792bd7c8c41a24a0fb39aada4 github.com/sourcegraph/zoekt/cmd/zoekt-webserver@e1fafb15177040d792bd7c8c41a24a0fb39aada4 export PATH=$PWD/.tools:$PATH ``` On hat-8 at `f47f4e62`, that way: 733 passed, 0 failed; `forks_pulls_reviews_and_merges` and `merges_wait_for_their_checks` 10/10 (before: about 4/5 and 2/5 failing, the same at upstream `bc4a8cef`). CI runs them through `harness/cov.mjs` against the spaces alpha.