zz_

app/worker/src/index.ts

zzmq.dev@gmail.com/zz / app / worker / src / index.ts @ acd49583 Download
Ladder Summary
  • file The module implements a Cloudflare‑Worker test harness that (1) seeds a fresh Git repository in an in‑memory filesystem and pushes it to an Artifacts repo (`seedRepo`), and (2) runs the complete “zz” protocol end‑to‑end—proposing a lane, posting and claiming work and review asks, granting the review, and submitting a changeset—by invoking methods on a real `MaintainerDO` durable object (`testFullProtocol`). It also re‑exports the `MaintainerDO` class so that other modules can instantiate or reference the durable object. **Exported identifiers** - `MaintainerDO` (class) (the only symbol exported from this file)
 // Proof-of-mechanism Worker for the Cloudflare backend
 // (site/content/remote-backend/index.md). /full-protocol-test is the
 // real target: the actual agent/tools.ts tool surface, through a real
 // MaintainerDO, doing the real protocol end to end (propose -> work ->
 // changeset_review -> submit_changeset -> commit -> Ladder write ->
 // read back) against a real Artifacts repo. The other routes are the
 // smaller, lower-level proofs built incrementally to get there, kept
 // as regression checks for the mechanisms underneath (native reads,
 // isomorphic-git writes, the Ladder's scoped notes checkout).
 import * as git from "isomorphic-git";
 import http from "isomorphic-git/http/web";
 import { createMemFs } from "./memFs.js";
 import { MaintainerDO } from "./MaintainerDO.js";
 import { commitChangesetOnArtifacts, readFileAtCommit } from "./cloudflareGitBackend.js";
 import { writeRung, readRung, appendRepoChildren } from "./cloudflareLadderBackend.js";
 import { ensureMeta, readRepoMeta, readRepoRoles, writeRepoRoles } from "./repoMeta.js";
 import { handleWebRequest } from "./webApp.js";
 import { decryptSecret, type OAuthEnv } from "./auth.js";
 
 export { MaintainerDO };
 
 // Artifacts/ArtifactsRepo/ArtifactsCreateRepoResult are ambient globals
 // from @cloudflare/workers-types (tsconfig's "types" pulls them in) --
 // no hand-rolled copy needed, and no import either; a hand-rolled copy
 // used to live here (and drifted from the real shape -- see
 // cloudflareGitBackend.ts's header for the same fix made there).
 interface Env extends OAuthEnv {
  ARTIFACTS: Artifacts;
  MAINTAINER_DO: DurableObjectNamespace<MaintainerDO>;
  DB: D1Database;
 }
 
 function tokenString(token: { plaintext: string } | string): string {
  return typeof token === "string" ? token.split("?")[0] : token.plaintext;
 }
 
 async function seedRepo(env: Env, repoName: string, files: { path: string; content: string }[]): Promise<{ remote: string; commitOid: string }> {
  const created = await env.ARTIFACTS.create(repoName, { setDefaultBranch: "main" });
  const fs = createMemFs();
  const dir = "/seed";
  await git.init({ fs, dir, defaultBranch: "main" });
  const entries = [];
  for (const f of files) {
  const oid = await git.writeBlob({ fs, dir, blob: new TextEncoder().encode(f.content) });
  entries.push({ mode: "100644", path: f.path, oid, type: "blob" as const });
  }
  const tree = await git.writeTree({ fs, dir, tree: entries });
  const person = { name: "seed", email: "seed@local", timestamp: Math.floor(Date.now() / 1000), timezoneOffset: 0 };
  const commitOid = await git.writeCommit({ fs, dir, commit: { message: "seed", tree, parent: [], author: person, committer: person } });
  await git.writeRef({ fs, dir, ref: "refs/heads/main", value: commitOid, force: true });
  await git.push({ fs, http, dir, url: created.remote, onAuth: () => ({ username: "x-token-auth", password: tokenString(created.token) }), ref: "main", remoteRef: "main" });
  return { remote: created.remote, commitOid };
 }
 
 // The real tool surface, through a real DO, doing the real protocol
 // end to end -- the thing all the other routes were building toward.
 // Skips run_in_sandbox (can't run on a Worker, by design) and composes
 // file content directly instead, same as the rest of this test's
 // seeding does; everything else is the actual zz protocol, unmodified.
 async function testFullProtocol(env: Env): Promise<Response> {
  const repoName = "full-protocol-" + Date.now();
  const steps: Record<string, unknown> = {};
  const actor = "zz-worker-test";
  try {
  const { commitOid: seedCommitOid } = await seedRepo(env, repoName, [{ path: "clamp.js", content: "export function clamp(v,min,max){return Math.min(Math.max(v,min),max);}\n" }]);
  steps.seeded = seedCommitOid;
 
  const id = env.MAINTAINER_DO.idFromName(repoName);
  const stub = env.MAINTAINER_DO.get(id);
  await stub.configure(repoName);
  const call = (name: string, args: Record<string, unknown>) => stub.callTool(name, args);
 
  const lane = (await call("lane_propose", { repo: repoName, name: "full-protocol-test", objective: "prove the real tool surface end to end", scope: ["**"], actor })) as { id: string };
  steps.lane = lane;
 
  const workAsk = (await call("ask_post", { repo: repoName, lane_id: lane.id, kind: "work", from: "human", payload: { description: "add isOdd to index.js" } })) as { id: string };
  await call("ask_claim", { repo: repoName, lane_id: lane.id, ask_id: workAsk.id, actor });
 
  const proposedFiles = [
  { path: "clamp.js", content: "export function clamp(v,min,max){return Math.min(Math.max(v,min),max);}\n" },
  { path: "index.js", content: "export { clamp } from \"./clamp.js\";\n\nexport function isOdd(n) { return n % 2 !== 0; }\n" },
  ];
 
  // The Maintainer-role gate from earlier this session: a review has
  // to exist and be granted before submit_changeset is even
  // claimable (enforced in engine/lane.ts's answer(), not just
  // hoped for) -- so post and grant it first, same order
  // agent/scriptedWork.ts's real flow uses.
  const review = (await call("ask_post", { repo: repoName, lane_id: lane.id, kind: "changeset_review", from: actor, payload: { taskDescription: "add isOdd", files: proposedFiles, message: "add isOdd", summary: "Add isOdd(n)" } })) as { id: string };
  await call("ask_claim", { repo: repoName, lane_id: lane.id, ask_id: review.id, actor });
  await call("ask_answer", { repo: repoName, lane_id: lane.id, ask_id: review.id, actor, action: "grant" });
  steps.reviewGranted = true;
 
  const submit = (await call("ask_post", {
  repo: repoName,
  lane_id: lane.id,
  kind: "submit_changeset",
  from: actor,
  payload: { files: proposedFiles, message: "add isOdd", summary: "Add isOdd(n)" },
  depends_on: [workAsk.id, review.id],
  })) as { id: string };
  await call("ask_answer", { repo: repoName, lane_id: lane.id, ask_id: workAsk.id, actor, action: "complete", payload: { summary: "proposed" } });
 
  await call("ask_claim", { repo: repoName, lane_id: lane.id, ask_id: submit.id, actor });
  const commitResult = (await call("commit_changeset", { repo: repoName, files: proposedFiles, message: "add isOdd", author_name: actor, author_email: `${actor}@local` })) as { commitOid: string };
  steps.commitOid = commitResult.commitOid;
 
  const now = new Date().toISOString();
  const source = [{ kind: "commit", ref: commitResult.commitOid, title: "add isOdd", author: actor, at: now }];
  // appendRepoChildren (called by mergeLaneLadder after every
  // non-root rung write) requires the "_repo" root note to already
  // exist -- demo/greenfield.ts's own bootstrap writes this once,
  // first, before anything else; this test has to do the same.
  await call("ladder_write_rung", { repo: repoName, rung: { path: "_repo", kind: "repo", summary: "A test repo for the full protocol.", sources: source, children: [] } });
  steps.rootRungWritten = true;
  await call("ladder_write_rung", { repo: repoName, rung: { path: "index.js", kind: "file", summary: "Re-exports clamp and adds isOdd.", sources: source, children: [] } });
  steps.fileRungWritten = true;
  await call("ladder_write_rung", {
  repo: repoName,
  rung: { path: `changesets/${commitResult.commitOid}`, kind: "changeset", summary: "Add isOdd.", sources: source, children: [{ path: `changesets/${commitResult.commitOid}/index.js`, kind: "filechange", summary: "Added isOdd.", sources: source, children: [] }] },
  });
  steps.changesetRungWritten = true;
  await call("ask_answer", { repo: repoName, lane_id: lane.id, ask_id: submit.id, actor, action: "complete", payload: { commitOid: commitResult.commitOid } });
  steps.answered = true;
 
  steps.readIndexRung = await call("ladder_read", { repo: repoName, path: "index.js" });
  steps.readLane = await call("lane_get", { repo: repoName, lane_id: lane.id });
  steps.readFileAtCommit = await call("git_show_file", { repo: repoName, sha: commitResult.commitOid, path: "index.js" });
 
  await env.ARTIFACTS.delete(repoName);
  steps.cleanedUp = true;
  return Response.json({ ok: true, steps });
  } catch (err) {
  await env.ARTIFACTS.delete(repoName).catch(() => {});
  return Response.json({ ok: false, error: err instanceof Error ? (err.stack ?? err.message) : String(err), steps }, { status: 500 });
  }
 }
 
 async function testChangesetCommit(env: Env): Promise<Response> {
  const repoName = "changeset-proof-" + Date.now();
  const steps: Record<string, unknown> = {};
  try {
  const { remote, commitOid: seedCommitOid } = await seedRepo(env, repoName, [
  { path: "index.js", content: "export { clamp } from \"./clamp.js\";\n" },
  { path: "clamp.js", content: "export function clamp(v,min,max){return Math.min(Math.max(v,min),max);}\n" },
  ]);
  steps.seeded = seedCommitOid;
 
  using repo = await env.ARTIFACTS.get(repoName);
  const newToken = await repo.createToken("write", 3600);
  const commitOid = await commitChangesetOnArtifacts(
  repoName,
  remote,
  newToken,
  repo,
  { files: [{ path: "index.js", content: "export { clamp } from \"./clamp.js\";\n\nexport function isOdd(n) { return n % 2 !== 0; }\n" }], message: "add isOdd", author: { name: "zz-worker", email: "zz-worker@local" } },
  );
  steps.changesetCommit = commitOid;
  steps.indexJsAfter = await readFileAtCommit(repo, commitOid, "index.js");
  steps.clampJsStillThere = await readFileAtCommit(repo, commitOid, "clamp.js");
 
  await env.ARTIFACTS.delete(repoName);
  steps.cleanedUp = true;
  return Response.json({ ok: true, steps });
  } catch (err) {
  await env.ARTIFACTS.delete(repoName).catch(() => {});
  return Response.json({ ok: false, error: err instanceof Error ? (err.stack ?? err.message) : String(err), steps }, { status: 500 });
  }
 }
 
 async function testLadder(env: Env): Promise<Response> {
  const repoName = "ladder-proof-" + Date.now();
  const steps: Record<string, unknown> = {};
  try {
  const { remote, commitOid: seedCommitOid } = await seedRepo(env, repoName, [{ path: "clamp.js", content: "export function clamp(v,min,max){return Math.min(Math.max(v,min),max);}\n" }]);
  steps.seeded = seedCommitOid;
 
  using repo = await env.ARTIFACTS.get(repoName);
  const writeToken = await repo.createToken("write", 3600);
  await writeRung(remote, writeToken, repo, {
  path: "clamp.js",
  kind: "file",
  summary: "Exports clamp(value, min, max).",
  sources: [{ kind: "commit", ref: seedCommitOid, title: "seed", author: "seed", at: new Date().toISOString() }],
  children: [],
  });
  steps.wroteFileRung = true;
 
  using repo2 = await env.ARTIFACTS.get(repoName);
  const readToken = await repo2.createToken("read", 3600);
  steps.readFileRung = await readRung(remote, readToken, repo2, "file", "clamp.js");
 
  await env.ARTIFACTS.delete(repoName);
  steps.cleanedUp = true;
  return Response.json({ ok: true, steps });
  } catch (err) {
  await env.ARTIFACTS.delete(repoName).catch(() => {});
  return Response.json({ ok: false, error: err instanceof Error ? (err.stack ?? err.message) : String(err), steps }, { status: 500 });
  }
 }
 
 // Diagnostic only: what does git.fetch() actually return for a ref
 // that genuinely doesn't exist on the remote yet? scopedNotesCheckout
 // (cloudflareLadderBackend.ts) assumes it throws or returns a falsy
 // fetchHead -- this checks that assumption directly against a real
 // Artifacts repo instead of guessing from a confusing downstream error.
 // Diagnostic only: the "repo"-kind rung (anchored to rootCommitOid
 // rather than a file blob) and appendRepoChildren were both added
 // speculatively in this final phase and never exercised live --
 // testLadder only covers a "file" rung. Isolates each step so a
 // failure here points at exactly one of: writing the root note,
 // reading it back, or appending a child to it.
 async function testRepoRungAndAppend(env: Env): Promise<Response> {
  const repoName = "repo-rung-proof-" + Date.now();
  const steps: Record<string, unknown> = {};
  try {
  const { remote, commitOid: seedCommitOid } = await seedRepo(env, repoName, [{ path: "a.txt", content: "a\n" }]);
  steps.seeded = seedCommitOid;
  const source = [{ kind: "commit" as const, ref: seedCommitOid, title: "seed", author: "seed", at: new Date().toISOString() }];
 
  using repo1 = await env.ARTIFACTS.get(repoName);
  const writeToken1 = await repo1.createToken("write", 3600);
  await writeRung(remote, writeToken1, repo1, { path: "_repo", kind: "repo", summary: "root", sources: source, children: [] });
  steps.wroteRootRung = true;
 
  using repo2 = await env.ARTIFACTS.get(repoName);
  const readToken1 = await repo2.createToken("read", 3600);
  steps.readRootAfterWrite = await readRung(remote, readToken1, repo2, "repo", "_repo");
 
  using repo3 = await env.ARTIFACTS.get(repoName);
  const writeToken2 = await repo3.createToken("write", 3600);
  await appendRepoChildren(remote, writeToken2, repo3, [{ path: "a.txt", kind: "file" }]);
  steps.appended = true;
 
  using repo4 = await env.ARTIFACTS.get(repoName);
  const readToken2 = await repo4.createToken("read", 3600);
  steps.readRootAfterAppend = await readRung(remote, readToken2, repo4, "repo", "_repo");
 
  await env.ARTIFACTS.delete(repoName);
  return Response.json({ ok: true, steps });
  } catch (err) {
  await env.ARTIFACTS.delete(repoName).catch(() => {});
  return Response.json({ ok: false, error: err instanceof Error ? (err.stack ?? err.message) : String(err), steps }, { status: 500 });
  }
 }
 
 // Diagnostic only: the zz-meta orphan branch is brand new this phase
 // -- verify ensureMeta's bootstrap, a role-grant write, and reads of
 // both files against a real Artifacts repo before anything else is
 // built on top of it (D1, the web app).
 async function testRepoMeta(env: Env): Promise<Response> {
  const repoName = "repo-meta-proof-" + Date.now();
  const steps: Record<string, unknown> = {};
  try {
  const { remote } = await seedRepo(env, repoName, [{ path: "a.txt", content: "a\n" }]);
  using repo = await env.ARTIFACTS.get(repoName);
  const token = await repo.createToken("write", 3600);
 
  await ensureMeta(remote, token, { description: "a test repo", owner: "acct-test" });
  steps.ensuredOnce = await readRepoMeta(remote, token);
 
  // Idempotency: calling ensureMeta again must not clobber the existing meta.json.
  await ensureMeta(remote, token, { description: "SHOULD NOT APPEAR", owner: "acct-other" });
  steps.ensuredTwice = await readRepoMeta(remote, token);
 
  const rolesBefore = await readRepoRoles(remote, token);
  steps.rolesBefore = rolesBefore;
  await writeRepoRoles(remote, token, { grants: [...(rolesBefore?.grants ?? []), { principal: "agent:agent-test", role: "contributor", granted_by: "acct-test", at: new Date().toISOString() }] });
  steps.rolesAfter = await readRepoRoles(remote, token);
 
  // meta.json must survive the roles.json-only write untouched.
  steps.metaAfterRolesWrite = await readRepoMeta(remote, token);
 
  await env.ARTIFACTS.delete(repoName);
  return Response.json({ ok: true, steps });
  } catch (err) {
  await env.ARTIFACTS.delete(repoName).catch(() => {});
  return Response.json({ ok: false, error: err instanceof Error ? (err.stack ?? err.message) : String(err), steps }, { status: 500 });
  }
 }
 
 async function testFetchMissingRef(env: Env): Promise<Response> {
  const repoName = "fetch-missing-ref-" + Date.now();
  const steps: Record<string, unknown> = {};
  try {
  const { remote } = await seedRepo(env, repoName, [{ path: "a.txt", content: "a\n" }]);
  using repo = await env.ARTIFACTS.get(repoName);
  const token = await repo.createToken("write", 3600);
  const fs = createMemFs();
  const dir = "/notes";
  await git.init({ fs, dir, defaultBranch: "main" });
  await git.addRemote({ fs, dir, remote: "origin", url: remote });
  const onAuth = () => ({ username: "x-token-auth", password: tokenString(token) });
  const ref = "refs/notes/zz/ladder/code";
  try {
  const result = await git.fetch({ fs, http, dir, remote: "origin", onAuth, ref, remoteRef: ref, singleBranch: true, tags: false });
  steps.fetchResolved = true;
  steps.fetchResult = { fetchHead: result.fetchHead, headers: result.headers ? Object.fromEntries(Object.entries(result.headers)) : null, pruned: result.pruned ?? null };
  } catch (fetchErr) {
  steps.fetchThrew = true;
  steps.fetchError = fetchErr instanceof Error ? (fetchErr.stack ?? fetchErr.message) : String(fetchErr);
  }
  await env.ARTIFACTS.delete(repoName);
  return Response.json({ ok: true, steps });
  } catch (err) {
  await env.ARTIFACTS.delete(repoName).catch(() => {});
  return Response.json({ ok: false, error: err instanceof Error ? (err.stack ?? err.message) : String(err), steps }, { status: 500 });
  }
 }
 
 // The original proof-of-mechanism test (see this file's own header):
 // create a real Artifacts repo, write/push a commit via isomorphic-git,
 // read it back through the native binding, delete it. Only ever runs
 // on its own explicit path now -- see the fetch handler's own comment
 // for why it used to run on every unmatched request instead.
 async function testProofOfMechanism(env: Env): Promise<Response> {
  const repoName = "proof-" + Date.now();
  const steps: Record<string, unknown> = {};
  try {
  const created = await env.ARTIFACTS.create(repoName, { setDefaultBranch: "main" });
  steps.created = { name: created.name, remote: created.remote };
  const token = tokenString(created.token);
 
  const fs = createMemFs();
  const dir = "/repo";
  await git.init({ fs, dir, defaultBranch: "main" });
  const blobOid = await git.writeBlob({ fs, dir, blob: new TextEncoder().encode("hello from a real Worker\n") });
  const treeOid = await git.writeTree({ fs, dir, tree: [{ mode: "100644", path: "README.md", oid: blobOid, type: "blob" }] });
  const person = { name: "zz-worker", email: "zz-worker@local", timestamp: Math.floor(Date.now() / 1000), timezoneOffset: 0 };
  const commitOid = await git.writeCommit({ fs, dir, commit: { message: "seed from Worker", tree: treeOid, parent: [], author: person, committer: person } });
  await git.writeRef({ fs, dir, ref: "refs/heads/main", value: commitOid, force: true });
  const onAuth = () => ({ username: "x-token-auth", password: token });
  const pushResult = await git.push({ fs, http, dir, url: created.remote, onAuth, ref: "main", remoteRef: "main" });
  steps.push = pushResult;
 
  const repo = await env.ARTIFACTS.get(repoName);
  const log = await repo.log({ ref: "main", limit: 5 });
  steps.log = log;
  const nativeCommit = await repo.readCommit(commitOid);
  const nativeTree = nativeCommit ? await repo.readTree(nativeCommit.treeHash) : null;
  const nativeBlob = nativeTree ? await repo.readBlob(nativeTree[0].hash) : null;
  steps.nativeRead = { blobText: nativeBlob ? await nativeBlob.text() : null };
 
  await env.ARTIFACTS.delete(repoName);
  steps.cleanedUp = true;
  return Response.json({ ok: true, steps });
  } catch (err) {
  await env.ARTIFACTS.delete(repoName).catch(() => {});
  return Response.json({ ok: false, error: err instanceof Error ? (err.stack ?? err.message) : String(err), steps }, { status: 500 });
  }
 }
 
 export default {
  async fetch(req: Request, env: Env): Promise<Response> {
  const path = new URL(req.url).pathname;
  if (path === "/full-protocol-test") return testFullProtocol(env);
  if (path === "/ladder-test") return testLadder(env);
  if (path === "/changeset-test") return testChangesetCommit(env);
  if (path === "/fetch-missing-ref-test") return testFetchMissingRef(env);
  if (path === "/repo-rung-test") return testRepoRungAndAppend(env);
  if (path === "/repo-meta-test") return testRepoMeta(env);
  if (path === "/dev-seed-content") {
  const artifactsName = new URL(req.url).searchParams.get("repo");
  if (!artifactsName) return new Response("missing ?repo=", { status: 400 });
  const stub = env.MAINTAINER_DO.get(env.MAINTAINER_DO.idFromName(artifactsName));
  await stub.configure(artifactsName);
  const commitResult = await stub.callTool("commit_changeset", {
  repo: artifactsName,
  files: [
  { path: "README.md", content: "# Demo widget\n\nA tiny demo repo for testing the web app.\n" },
  { path: "index.js", content: "export function widget() { return 42; }\n" },
  ],
  message: "seed content",
  author_name: "dev-seed",
  author_email: "dev-seed@local",
  });
  return Response.json(commitResult);
  }
 
  if (path === "/dev-cf-userinfo-raw") {
  // One-off diagnostic, extended: does GET /client/v4/user with the
  // OAuth access token as a bearer actually return email/name,
  // the way a REST API token's Bearer auth would? (user-details.read
  // is an API-token-permission-shaped scope, not an OIDC one, so
  // it's plausible the access token doubles as a regular API
  // token for that one permission -- or it may not, API tokens and
  // OAuth access tokens aren't always interchangeable. Checking
  // directly rather than assuming either way.)
  const row = await env.DB.prepare("SELECT access_token_enc FROM agent_identities WHERE provider = 'cloudflare' ORDER BY created_at DESC LIMIT 1").first<{ access_token_enc: string }>();
  if (!row) return new Response("no cloudflare identity stored yet", { status: 404 });
  const accessToken = await decryptSecret(env, row.access_token_enc);
  const [userinfoResp, apiUserResp] = await Promise.all([
  fetch("https://dash.cloudflare.com/oauth2/userinfo", { headers: { authorization: `Bearer ${accessToken}` } }),
  fetch("https://api.cloudflare.com/client/v4/user", { headers: { authorization: `Bearer ${accessToken}` } }),
  ]);
  const [userinfoText, apiUserText] = await Promise.all([userinfoResp.text(), apiUserResp.text()]);
  return new Response(
  `GET /oauth2/userinfo -> ${userinfoResp.status}\n${userinfoText}\n\nGET /client/v4/user -> ${apiUserResp.status}\n${apiUserText}`,
  { headers: { "content-type": "text/plain" } },
  );
  }
 
  const webResponse = await handleWebRequest(req, env);
  if (webResponse) return webResponse;
 
  // Was, for a long time, an unguarded live-Artifacts proof-of-
  // mechanism test (create a real repo, commit, push, read back,
  // delete) run on EVERY request handleWebRequest didn't itself
  // claim -- including, once the web app grew enough real routes
  // that most of the URL space belongs to it, totally ordinary
  // unmatched requests in production (confirmed live: a Workers
  // Routes precedence issue sent "/" here instead of the docs
  // worker, and this ran a real create/push/delete cycle on every
  // hit instead of just 404ing). The actual proof-of-mechanism test
  // this used to BE still exists, just moved behind its own explicit
  // path so it only ever runs on purpose.
  if (path === "/proof-of-mechanism-test") return testProofOfMechanism(env);
  return new Response("not found", { status: 404 });
  },
 };