feat(flows): publish the generated Software Garden as a pinned artifact (1/2 for #174)

Merged
KH
13d ago
+573-2

Part 1 of 2 for #174, where option (a) was decided: the /flows generator is the single source of Software Garden.

What

  • web/lib/software-garden-artifact.ts defines the published draft and softwareGardenSource(), which is factorySource(draft, 'cloud').
  • web/public/flows/software-garden/v1.flow.ts is the generator output, 64,355 bytes, sha256:b89a36e2cf9054036a7fc53fba20011a42ef0275ffbaaa907b080738ab46ad27.
  • web/public/flows/software-garden/manifest.json records {version, sha256, bytes, path, url, target, draft, versions[]}.
  • web/scripts/publish-software-garden.mts cuts the next immutable version: cd web && npx -y tsx@4 scripts/publish-software-garden.mts.
  • public/ is now excluded from tsc. The artifact imports @relayflows/surface, which this app does not install.

Which variant, and why

The draft is {sources:['github'], sourceSettings:{}, agents:['claude'], otherAgent:'', task:'', workflow:'traditional', step:3} with target cloud.

  • Agents ['claude']. Cloud's prebuilt wizard (loadPrebuiltFlowHandoff in cloud flow-catalog-client.ts) deploys the catalog source verbatim and passes agents: entry.inputs.defaults.agents. That is ["claude"], and allowedAgents is ["claude"]. Agent selection changes the bytes (claude,codex gives db1b9ed2…), so the artifact has to be the Claude-only variant.
  • Sources ['github'] with empty settings. This is the entry's defaultTrigger. Repository and label filters do not change the cloud source (verified: identical hash with {repository, labels} set), because Cloud's listener applies them.
  • Workflow Traditional. This is the workflow the entry describes: implement, run checks outside the agent, adversarial review, then a PR for a human decision.
  • Task empty. The catalog wizard hands off task: "".

Tests (red, then green)

web/lib/test/software-garden-artifact.test.ts:

  1. The artifact equals softwareGardenSource() byte for byte.
  2. The manifest pins the version's sha256, size, path and url, and its draft equals the module's.
  3. Published versions are immutable: every versions[] entry's file hashes to its recorded sha.
  4. /flows handoff identity. cloudConnectionsHref() for a GitHub + Claude Code + Traditional draft, with and without repo/label filters, carries payload.source equal to the artifact bytes.
  5. The draft equals the catalog entry's default agents and trigger.

Checked locally:

  • Before the artifact existed: 4 failed.
  • After publishing: 5 passed.
  • After changing generator output: tests 1 and 4 fail.
  • After overwriting v1 in place: tests 2 and 3 fail.
  • After running the publish script (writes v2): 5 pass.
  • Full suite: 45 files / 522 tests plus the router's 133 pass, and tsc --noEmit is clean.

Why two PRs

The catalog's source is kind: "github", and Cloud fetches it browser-side from raw.githubusercontent.com at a full commit SHA. The pinned commit must therefore be on main before the catalog references it. PR 2/2 points flows.0 at AgentWorkforce/agentrelay.com@<this PR's merge commit>/web/public/flows/software-garden/v1.flow.ts with this sha256. It needs no Cloud change, since its contract already accepts any public GitHub owner/repo.

🤖 Generated with Claude Code


[!NOTE]
Low Risk
Adds static assets, a publish script, and tests without changing runtime deploy paths in this PR; catalog wiring is deferred to a follow-up.

Overview
Introduces a versioned, sha256-pinned Software Garden flow artifact so /flows onboarding, Cloud prebuilt wizards, and the recommended-flow catalog can all deploy the same generator bytes from public/flows/software-garden/.

Adds software-garden-artifact.ts with the canonical FactoryDraft (GitHub, Claude-only, Traditional, empty task) and softwareGardenSource() via factorySource(draft, 'cloud'), plus v1 (manifest.json + v1.flow.ts) and publish-software-garden.mts to cut the next immutable version when generator output changes. Tests assert byte identity with the generator, manifest pinning, per-version immutability, parity with cloudConnectionsHref handoff (including repo/label filters), and alignment with the software-factory catalog entry.

TypeScript now includes **/*.mts and excludes public/ so the published flow (which imports @relayflows/surface) does not break tsc.

Reviewed by Cursor Bugbot for commit 2044744cd0bbf5f3bcdbbf82f9ca6646d313f1f4. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Publishes the /flows generator's Software Garden output as a pinned, immutable artifact so every surface that deploys it shares the same bytes, and makes the publish script retry-safe.

  • Adds the generator module, the published v1 file and manifest, a test suite, and the publish script; public/ and .mts are now covered or excluded from TypeScript appropriately.
  • The manifest pins version, sha256, size, path, URL, target, and the draft that produced it; softwareGardenSource() is factorySource(draft, 'cloud') for the catalog's default selection (GitHub, Claude Code only, Traditional).
  • Five tests pin the artifact's hash, bytes, path, URL, and the catalog draft, verify it's byte-identical to what /flows onboarding hands to Cloud, and fail on a generator change until republished as a new version.
  • Publishing refuses to proceed while a recorded version is missing or edited, reuses an interrupted publish's file only when it matches, rewrites the manifest when bytes are unchanged, allows a digest to recur as a new version, and stages every write beside its target so an interrupted publish leaves no truncated file.

Agent Relay sessions

  • claude session eae07e25 · opened via gh pr create · last active 2026-10-08