Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ops/BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,10 @@ gate's needs. Not commitments; ordering is the Lead's call with evidence.
and the relayflow review swarm (hoopsheet pattern), pushes fixes, replies,
and closes itself at merge. The flow IS the PR's lifecycle. Khaliq,
2026-08-27 — natural first real consumer of `on()` + relayfile PR trees.
- **Regression suite (`regressions/`, dormant):** red/green flow pairs for the
four platform bugs found 2026-08-27 — enrollment-token bearer auth
(cloud#3202), the `--daemon` `$bunfs` argv re-exec, RelayCron's `succeeded`
into a void (covenant 2), and the cross-account 404 rendered as a permissions
error (covenant 1). Written in the v2 dialect against a surface that does not
exist yet; nothing runs until gates 1/2/6/7/8 close per `regressions/MANIFEST.json`.
The Garden should adopt them once flows run in cloud.
138 changes: 138 additions & 0 deletions regressions/MANIFEST.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
{
"version": "0.1.0",
"suite": "regressions",
"authoredOn": "2026-08-27",
"dialect": "flows-v2 (docs/SURFACE.md)",
"dormant": true,
"dormancy": "No flow here declares an `on()` trigger, no schedule is registered, nothing is deployed, and nothing references this directory from a drive loop or CI job. The suite runs only when a human or the Garden invokes `flows run` explicitly.",
"adoptBy": "software-garden",
"adoptWhen": "flows run in cloud and the gates listed per pair have closed; the Garden should then run each red case (expect pass) and each green case (expect fail) as the standing proof the bug is still open, and flip the pair to green-only once the fix lands.",
"typecheck": {
"command": "cd sdk && npx tsc -p ../regressions/tsconfig.json",
"optIn": true,
"note": "Not part of `npm test`. Types come from regressions/surface.d.ts, a declaration-only slice of the v2 surface; delete it once @relayflows/surface exists."
},
"surfaceGaps": [
{
"id": "declared-failure-assertion",
"need": "A red case must assert that an operation fails in a declared way. The surface has no `expect(kind)` form, so every red probe is a deterministic step that always exits 0 and prints the observed status/body for a postfix gate to judge.",
"blocks": ["enrollment-token-bearer-auth", "cross-account-workspace-404"]
},
{
"id": "rendered-ui-assertion",
"need": "No browser helper, so `the UI shows this string` is not assertable. The cross-account pair asserts the API-level cause instead: the response body carries no code the client could branch on.",
"blocks": ["cross-account-workspace-404"]
},
{
"id": "darwin-arm64-placement",
"need": "gate 7's sandbox router has no macOS pool; the bun-compiled argv bug does not reproduce under node or `bun run`.",
"blocks": ["worker-daemon-bun-argv"]
},
{
"id": "multi-principal-run",
"need": "Two or three credential principals inside one run (CLI bearer, browser session, in-account non-owner). Expressed as `principals/<name>` mount paths and an `as:` argument; gate 8 must make that real and non-ambient.",
"blocks": ["enrollment-token-bearer-auth", "cross-account-workspace-404"]
}
],
"pairs": [
{
"slug": "enrollment-token-bearer-auth",
"bug": "POST /api/v1/workers/enrollment-tokens gates on requireSessionAuth, so a valid CLI bearer is answered 403 and worker enrollment has no headless/self-host/CI path.",
"red": "enrollment-token-bearer-auth.red.flow.ts",
"green": "enrollment-token-bearer-auth.green.flow.ts",
"upstream": "cloud#3202",
"requiredGates": ["gate-1", "gate-6", "gate-8"],
"blockedUntil": [
"gate-1: a relayflow can run at all",
"gate-6: a relayfile adapter for AgentWorkforce cloud (f.cloud helper namespace)",
"gate-8: per-principal scoped credentials resolved through the proxy, not ambient env"
],
"dependsOn": [],
"evidence": {
"files": [
"AgentWorkforce/cloud packages/web/app/api/v1/workers/enrollment-tokens/route.ts:113-115"
],
"strings": ["{\"error\":\"Forbidden\"} (HTTP 403)"],
"observedOn": "2026-08-27"
}
},
{
"slug": "worker-daemon-bun-argv",
"bug": "`cloud worker start --daemon` re-execs the bun-compiled binary with argv[1] = /$bunfs/root/agent-relay-darwin-arm64; the child dies instantly while the CLI prints 'Cloud worker daemon started: <pid>'.",
"red": "worker-daemon-bun-argv.red.flow.ts",
"green": "worker-daemon-bun-argv.green.flow.ts",
"upstream": "to file — relay CLI",
"requiredGates": ["gate-1", "gate-6", "gate-7"],
"blockedUntil": [
"gate-1: a relayflow can run at all",
"gate-6: cloud helper for worker liveness/heartbeat reads",
"gate-7: a darwin-arm64 sandbox profile — the bug is invisible under node or `bun run`"
],
"dependsOn": ["enrollment-token-bearer-auth"],
"evidence": {
"files": [
"AgentWorkforce/relay packages/cli/src/cli/commands/cloud-worker.ts:266",
"AgentWorkforce/relay packages/cli/src/cli/commands/cloud-worker.ts:279",
"AgentWorkforce/relay packages/cli/src/cli/commands/cloud-worker.ts:396"
],
"strings": [
"error: unknown command '/$bunfs/root/agent-relay-darwin-arm64'",
"Cloud worker daemon started: <pid>"
],
"observedOn": "2026-08-27"
}
},
{
"slug": "cron-succeeded-into-void",
"bug": "RelayCron marks a fired schedule lastTriggerStatus: 'succeeded' as soon as the launch POST returns a runId, even with no worker to claim it — a covenant-2 'succeeded' that did nothing.",
"red": "cron-succeeded-into-void.red.flow.ts",
"green": "cron-succeeded-into-void.green.flow.ts",
"upstream": "to file — cloud RelayCron",
"requiredGates": ["gate-1", "gate-2", "gate-6"],
"blockedUntil": [
"gate-1: a relayflow can run at all",
"gate-2: the trigger plane, its executor-liveness check and sweep",
"gate-6: cloud helper for schedules, workers and run journals"
],
"dependsOn": ["enrollment-token-bearer-auth"],
"evidence": {
"files": [
"AgentWorkforce/cloud packages/web/app/api/v1/workflows/schedules/trigger/route.ts:178-192"
],
"runIds": ["8e3e5916", "740c3a27"],
"schedule": "flows-drive",
"strings": ["lastTriggerStatus: \"succeeded\" with no run artifacts, no branch, no PR"],
"observedOn": "2026-08-27"
}
},
{
"slug": "cross-account-workspace-404",
"bug": "A workspace belonging to another account answers 404 with no code and no user-facing message; the client maps every non-ok response to 'Check your workspace permissions', so a not-found condition is reported as a permissions problem.",
"red": "cross-account-workspace-404.red.flow.ts",
"green": "cross-account-workspace-404.green.flow.ts",
"upstream": "to file — cloud",
"requiredGates": ["gate-1", "gate-6", "gate-8"],
"blockedUntil": [
"gate-1: a relayflow can run at all",
"gate-6: a relayfile adapter for AgentWorkforce cloud",
"gate-8: three principals in one run (owner session, other-account session, in-account non-owner)"
],
"dependsOn": [],
"evidence": {
"files": [
"AgentWorkforce/cloud packages/web/app/api/v1/workers/enrollment-tokens/route.ts:29-32",
"AgentWorkforce/cloud packages/web/components/workers/NewWorkerForm.tsx:91-92"
],
"workspaces": {
"khaliq@agentrelay.com": "50587328-441d-4acb-b8f3-dbe1b3c5de99",
"khaliqgant@gmail.com": "0fb35c2e-861f-4d44-848e-fa3f5a3e192e"
},
"strings": [
"{\"error\":\"Workspace not found\"} (HTTP 404)",
"Could not create an enrollment token. Check your workspace permissions and try again."
],
"observedOn": "2026-08-27"
}
}
]
}
88 changes: 88 additions & 0 deletions regressions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# regressions — executable bug reports, red then green

Four platform bugs were found on 2026-08-27, during the first dogfood runs. Each
one is written here twice, in the flows v2 dialect (`docs/SURFACE.md`):

- **`<slug>.red.flow.ts`** — reproduces the failure. Its gates assert the
**broken** behaviour, so it **passes while the bug is present**. This is the
bug report, in a form a machine can re-run.
- **`<slug>.green.flow.ts`** — asserts the **corrected** behaviour. It **fails
while the bug is present** and passes once it is fixed. This is the acceptance
test.

A bug is closed when its red case starts failing and its green case starts
passing, in the same run. Either one alone can lie: a green test that never ran
red proves nothing about the bug it claims to cover.

## These flows do not run yet, and must not

Nothing in this directory is wired into a drive loop, a schedule, or CI. No flow
declares an `on()` trigger, none is deployed, and nothing outside `regressions/`
references it except one backlog line in `ops/BACKLOG.md`. They are written
against `@relayflows/surface` — the v2 authoring surface, which does not exist
yet. `regressions/surface.d.ts` is a declaration-only slice of it: the exact
shapes these four pairs need, so the file doubles as a requirements list for
gate-1 SDK work. Delete it when the real surface ships.

## Running them, once the kernel can

```sh
flows check regressions/<slug>.red.flow.ts # preflight: refuses on a missing gate
flows run regressions/<slug>.red.flow.ts # expected: PASS while the bug is open
flows run regressions/<slug>.green.flow.ts # expected: FAIL while the bug is open
```

Opt-in typecheck (deliberately *not* part of `cd sdk && npm test`):

```sh
cd sdk && npx tsc -p ../regressions/tsconfig.json
```

`MANIFEST.json` carries the same table in machine-readable form — slug, required
gates, `blockedUntil`, `dependsOn` — so the Garden can pick each pair up
automatically the moment its gates close.

## The suite

| slug | bug | evidence | gates required to run | upstream |
|---|---|---|---|---|
| `enrollment-token-bearer-auth` | Minting an enrollment token gates on `requireSessionAuth`, so a valid CLI bearer gets 403 — worker enrollment has no headless, self-host, or CI path. | `cloud packages/web/app/api/v1/workers/enrollment-tokens/route.ts:113-115`; response `{"error":"Forbidden"}` (403) | gate-1, gate-6, gate-8 | cloud#3202 |
| `worker-daemon-bun-argv` | `cloud worker start --daemon` re-execs the bun-compiled binary with `process.argv[1]`, which is a virtual `$bunfs` path; the child dies at once while the CLI reports success. | `relay packages/cli/src/cli/commands/cloud-worker.ts:266` (`process.argv[1] ?? 'agent-relay'`), `:279` (spawn `process.execPath`), `:396` (success log); `error: unknown command '/$bunfs/root/agent-relay-darwin-arm64'` | gate-1, gate-6, gate-7 | to file — relay CLI |
| `cron-succeeded-into-void` | A fired schedule is marked `lastTriggerStatus: "succeeded"` as soon as the launch POST returns a runId, even with no worker to claim it. A schedule can be silently zero forever. | `cloud packages/web/app/api/v1/workflows/schedules/trigger/route.ts:178-192`; runs `8e3e5916` and `740c3a27` on schedule `flows-drive` — both succeeded, nothing executed, no branch, no PR | gate-1, gate-2, gate-6 | to file — cloud RelayCron |
| `cross-account-workspace-404` | Another account's workspace answers 404 with no code and no user message; the client renders every non-ok response as a permissions problem. | `cloud .../enrollment-tokens/route.ts:29-32` (404 `{"error":"Workspace not found"}`), `cloud packages/web/components/workers/NewWorkerForm.tsx:91-92`; workspaces `50587328-…` (khaliq@agentrelay.com) vs `0fb35c2e-…` (khaliqgant@gmail.com) | gate-1, gate-6, gate-8 | to file — cloud |

Two of these are covenant violations, not merely defects:
`cron-succeeded-into-void` is covenant 2 verbatim — *"a 'succeeded' that did
nothing is by definition a kernel bug"* — and so, in its own way, is
`worker-daemon-bun-argv`: the CLI reports a pid for a process that is already
gone. `cross-account-workspace-404` is covenant 1: the error names the wrong
condition in the user's vocabulary, so the user retries with permissions they
already have.

## What the dialect cannot say yet

Written down here rather than worked around silently, because each one is a real
gap in the surface:

1. **No declared-failure assertion.** A red case must say "this operation fails,
in this declared way". The surface has `.gate()` — which *causes* a typed
failure — but no `expect(kind)` which *asserts* one. Every red probe is
therefore a deterministic step that always exits 0 and prints
`<body>\n<status>` (see `probe.ts`), letting a postfix gate judge the observed
response. It works, but the shape it wants is a first-class negative gate.
This belongs with the gate-1 verification work.
2. **No browser helper**, so "the UI shows this string" is not assertable.
`cross-account-workspace-404` asserts the API-level cause instead — the body
carries no `code` the client could branch on — and its green case moves the
user-facing message to the API, where the condition is actually known.
3. **No macOS placement.** `worker-daemon-bun-argv` only reproduces on the
darwin-arm64 compiled binary; the sandbox router (gate 7) has no such pool.
The pair declares `workspace: "sandbox:darwin-arm64"` and waits.
4. **Multi-principal runs are declarative only.** These flows need two or three
credential principals in one run. They are expressed as
`mnt/agentworkforce-cloud/principals/<name>/token` mount reads plus an `as:`
argument on each helper verb — the filesystem path *is* the permission — but
gate 8 has to make that real and non-ambient before any of it holds.
5. **Durable waiting is a helper, not a verb.** `f.cloud.workers.awaitHeartbeat`
compiles to a kernel wait, which the plugin contract permits. If waiting
turns out to be common enough in authored flows, it wants a name of its own.
103 changes: 103 additions & 0 deletions regressions/cron-succeeded-into-void.green.flow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// GREEN — a schedule reports success only when work actually executed.
//
// BUG see cron-succeeded-into-void.red.flow.ts
// EVIDENCE packages/web/app/api/v1/workflows/schedules/trigger/route.ts:178-192;
// runs 8e3e5916 and 740c3a27 (schedule flows-drive, 2026-08-27)
// UPSTREAM to file — cloud RelayCron
//
// Two phases, because the fix has two halves:
// 1. with no eligible worker, the trigger reports a distinct non-success state
// (`no_executor`) — covenant 2's preflight: a trigger with no executor is
// refused before the run starts, not blessed after it;
// 2. with a worker, it reports `succeeded` AND the run's journal shows steps.
//
// This flow FAILS while the bug is present (phase 1 sees `succeeded`) and PASSES
// once both halves hold.
//
// RUN-WHEN: gate-1, gate-2, gate-6
// DEPENDS-ON: enrollment-token-bearer-auth (phase 2 needs headless enrolment)

import { flow } from "@relayflows/surface";

const WORKSPACE = "50587328-441d-4acb-b8f3-dbe1b3c5de99";

export default flow(
"regressions/cron-succeeded-into-void.green",
{
identity: "regressions/cron-succeeded-into-void",
tools: { relayfile: ["agentworkforce-cloud"] },
budget: "$0.20/run",
},
async (f) => {
const schedule = await f.cloud.schedules
.create({
workspaceId: WORKSPACE,
workflow: "regressions/noop",
cron: "0 0 1 1 *",
name: "regression-cron-green",
as: "cli-bearer",
})
.gate((s) => s.id.length > 0, "a disposable schedule");

// Phase 1 — no executor.
await f.cloud.workers
.list({ workspaceId: WORKSPACE, as: "cli-bearer" })
.gate((w) => w.online.length === 0, "precondition: no executor");

await f.cloud.schedules.fire({ scheduleId: schedule.id, as: "cli-bearer" });

await f.cloud.schedules
.get({ scheduleId: schedule.id, as: "cli-bearer" })
.gate(
(s) => s.lastTriggerStatus === "no_executor",
"with no eligible worker the trigger names that condition",
)
.gate(
(s) => s.lastTriggerStatus !== "succeeded",
"and never claims success for work that could not start",
)
.gate(
(s) => (s.lastTriggerError ?? "").length > 0,
"and leaves an error a human can read",
);

// Phase 2 — a real executor. Enrolment is headless, which is why this flow
// depends on cloud#3202 being fixed first.
const enrolment = await f.cloud.workers.mintEnrollmentToken({
workspaceId: WORKSPACE,
name: "regression-cron-worker",
as: "cli-bearer",
});

await f
.run(
`agent-relay cloud worker register --workspace ${WORKSPACE}` +
` --token-file ${enrolment.tokenPath} --name regression-cron-worker --json` +
" && agent-relay cloud worker start --daemon",
)
.gate((out) => out.includes("daemon started"), "an executor now exists");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wait for the executor heartbeat before firing the schedule

When daemon startup returns before its first cloud heartbeat, this gate only proves that the CLI printed a success line, not that an eligible executor exists; the immediately following fire can consequently report no_executor on a fully fixed platform. This is especially relevant because the adjacent daemon regression is explicitly about the success message preceding real liveness. Wait for the newly registered worker to become online before triggering phase 2.

Useful? React with 👍 / 👎.


await f.cloud.schedules.fire({ scheduleId: schedule.id, as: "cli-bearer" });

const state = await f.cloud.schedules
.get({ scheduleId: schedule.id, as: "cli-bearer" })
.gate(
(s) => s.lastTriggerStatus === "succeeded",
"with an executor, succeeded is now truthful",
);

await f.cloud.runs
.journal({ runId: state.lastTriggeredRunId ?? "", as: "cli-bearer" })
.gate(
(j) => j.steps.length > 0,
"and succeeded is backed by a journal with executed steps",
)
.gate(
(j) => j.steps.every((step) => step.completionReason !== null),
"every step carrying a completionReason (AGENTS.md rule 4)",
);

await f.cloud.schedules.remove({ scheduleId: schedule.id, as: "cli-bearer" });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clean up schedules after expected gate failures

This cleanup is reached only when every preceding gate passes, but the documented red/green protocol deliberately expects one twin to fail in either platform state. While the bug is open, the green flow stops at the no_executor assertion; after the fix, the red twin stops at its succeeded assertion, leaving their fixed-name disposable schedules behind on every suite run. Subsequent creates can conflict, and leaked schedules can later fire; arrange cleanup in a failure-safe finalizer or use a lifecycle that removes the schedule even when a gate fails.

Useful? React with 👍 / 👎.

return f.done("bug_fixed");
},
);
Loading