feat(flows): pin Software Garden v3 in the catalog (catalogVersion 6) and automate future pin moves

Merged
KH
11d ago
+908-27

Summary

1. The catalog pins Software Garden v3 (catalogVersion 6). #192 published v3, but web/data/recommended-flow-catalog.v1.json still served v1 at catalogVersion 5. Cloud refuses to upgrade an activation onto a different source at the same catalogVersion, so both the catalog and the entry advance:

FieldValue
catalogVersion / entry version6 / 6
pathweb/public/flows/software-garden/v3.flow.ts
releasesoftware-garden-v3
refb6125bfdba33e15b4ad68c9d431fe5630c0894d7 (the commit where v3.flow.ts first landed on main, #192's merge commit; git log --diff-filter=A --format=%H origin/main -- web/public/flows/software-garden/v3.flow.ts)
sha256a54a282e0ac607bf4481aee5c3353c7a5c63178da44080ba72ff872b24fcba73 (git show <ref>:<path>, the local file, the manifest, and the bytes the rawUrl serves all match)
url / rawUrlhttps://github.com/AgentWorkforce/agentrelay.com/blob/b6125bfd…/web/public/flows/software-garden/v3.flow.ts / https://raw.githubusercontent.com/AgentWorkforce/agentrelay.com/b6125bfd…/web/public/flows/software-garden/v3.flow.ts
  • SOURCE_BY_CATALOG_VERSION in web/lib/test/software-garden-artifact.test.ts gains row 6.
  • verify-catalog-gates.mjs and verify-recommended-flow-catalog.mjs no longer hardcode 5. Both scripts call a shared assertRecommendedCatalogEnvelope (recommended-flow-contract.mjs). It requires schemaVersion 1, a positive safe-integer catalogVersion and a flows array, which is what Cloud's recommendedFlowCatalogEnvelopeSchema parses. The artifact test separately holds catalogVersion equal to the newest recorded row. The gates also enforce Babysitter's authority floor of 4: Cloud's PUBLISHED_RECOMMENDED_FLOW_CATALOG_VERSION strips Babysitter from catalogs older than that.
  • recommended-flow-catalog.test.ts no longer hardcodes catalogVersion 5 or the v1 pin. It derives them from the catalog and checks their shape, so the exact pin is recorded once, in the rows above.
  • Docs (recommended.mdx): catalogVersion 6, and the v3 path/release example.
  • Babysitter is unaffected. The data diff touches only the software-factory entry. Babysitter's entry, its extension contract on Garden, and its blocked activation are byte-identical. verify-catalog-gates passes, and catalogVersion 6 is ≥ 4, so Cloud still publishes Babysitter metadata.

2. Automation: .github/workflows/software-garden-catalog-bump.yml + web/scripts/propose-software-garden-catalog-bump.mjs.

  • The workflow runs on a push to main that touches web/public/flows/software-garden/manifest.json, and on workflow_dispatch.
  • If the manifest's latest version is newer than the pinned one, the script does four things:
    • pins the commit where that version's file first landed on main, with the file's sha256 at that commit (it must equal the manifest's digest);
    • edits only the pin's values in the hand-formatted catalog JSON (it proves the result parses to exactly the intended object);
    • appends the SOURCE_BY_CATALOG_VERSION row;
    • moves the catalogVersion, release and path that recommended.mdx names.
  • Two jobs keep the write token away from third-party code:
    • check (read-only token) runs npm ci and --check, which makes the edit and runs npm run verify:recommended-flows, npm test and the web typecheck;
    • propose (contents: write, pull-requests: write) checks out the same SHA, installs nothing (the script uses only Node built-ins), and runs --open-pr.
  • propose pushes automation/software-garden-catalog-bump and opens the PR, or updates the one already open (title and body). The push is skipped when the branch already carries the same tree.
  • If the pin is current, it does nothing. It never merges and never enables auto-merge.
  • Only propose gets contents: write and pull-requests: write. No new secrets.
  • git gets the token from a credential helper that reads GH_TOKEN from the environment, never from argv, a URL or git config. Checkout keeps persist-credentials: false, and any error output is redacted.
  • The automation is documented in recommended.mdx under "Moving the Software Garden pin".

Known limitation

The bump PR is opened with GITHUB_TOKEN, and GitHub does not start other workflows for events that token causes, so CI does not run on the bump PR automatically. This repo has no existing convention for that (the IndexNow snapshot commit uses [skip ci]), so I did not add a PAT or App secret. Instead:

  • the workflow runs the same checks before it opens the PR;
  • the PR body tells the maintainer to run the CI workflow on the branch (gh workflow run test.yml --ref automation/software-garden-catalog-bump) or to close and reopen the PR.

The repo setting that lets Actions create PRs is already enabled (can_approve_pull_request_reviews: true).

Review rounds

  • Round 1 (9d5f423) addressed four threads:
    • Devin: the fixture was tied to catalogVersion 6;
    • Devin: the token was available to npm ci;
    • Devin: the token appeared in git argv;
    • Codex: the docs would go stale on automated bumps.
  • All four are fixed, replied to and resolved.
  • Round 2 (9d443f0) addressed two cubic threads:
    • the API test could not catch a forgotten entry-version bump, so the artifact test now independently requires version == catalogVersion;
    • --open-pr with --git ≠ --root would commit the wrong tree, so it is now refused up front.
  • Both are fixed, replied to and resolved.
  • After round 2: no unresolved threads, and cubic reports all issues addressed.

Verification

  • npm test: web 49 files / 614 tests passed; router 9 files / 97 tests passed.
  • npx tsc --noEmit passed in web and in router.
  • The CI workflow on this PR ("Tests and typecheck") passed.
  • npm run verify:recommended-flows passed. It ran verify:catalog-gates and fetched the v3 rawUrl: software-factory: verified software-garden-v3 (b6125bfd…) sha256:a54a282e…, and Babysitter verified unchanged.
  • npm run verify:setup-skills passed.
  • Script tests (web/lib/test/software-garden-catalog-bump.test.ts, 15 tests) rewind the checked-in files to fixed history points (v1 @ 5, v3 @ 6), so they stay valid after future bumps. They cover:
    • state@6 reports current;
    • state@5 reproduces this PR's catalog, artifact test and docs byte-for-byte;
    • a v3 to v4 bump produces catalogVersion 7;
    • the docs keep naming what the catalog serves;
    • refusals: an unlanded version, a digest mismatch, a manifest missing its own version, an ambiguous value, a non-pin change, a conflicting or non-consecutive row;
    • PR create vs. update vs. force-update, never merging, the token never appearing in any argument, and redaction.
  • Simulated a future catalogVersion 7 tree locally: the affected test files (39 tests) pass.
  • Checked the credential helper with git credential fill.
  • Red first: before the data change, the row 6 assertion, the envelope tests and the script tests failed against the parent.
  • Dry run on this tree: node web/scripts/propose-software-garden-catalog-bump.mjs prints Software Garden pin is current: v3 at catalogVersion 6.
  • Dry run on a scratch copy of origin/main (git archive, pinned to v1 at catalogVersion 5): --root <scratch> --write produced a catalog and an artifact test identical to this PR (diff empty), and changed exactly the three versioned values in that tree's docs. A re-run then reported current.
  • Negative gate check: catalogVersion 3, 0 and "6" fail verify-catalog-gates; 7 passes.

🤖 Generated with Claude Code


Agent Relay sessions

  • claude session 7f40e353 · opened via gh pr create · last active 2026-10-10
  • claude session 5d87b716 · contributor · ran gh pr commands · last active 2026-10-10