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
9 changes: 9 additions & 0 deletions docs/EDITOR.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,12 @@ schema's entry point, even though `flows check` can also ingest it.
4. Hover `timeoutMs` and inspect completion after `type: agent`.

Do not correct `identitty` to `identity` in this dialect: neither is supported.


`flows check --json` also reports `validation`: `mode: "daemon"` includes the
validator binary, protocol and spec version; `mode: "local"` includes a reason
why daemon acceptance is unproven. Editor/schema acceptance alone does not prove
runtime compatibility. Set `FLOWS_NO_DAEMON_CHECK=1` for spawn-free local checks,
or use `--against-daemon` to require the installed binary's stateless validator.
Authored TypeScript bodies remain local header checks. See SURFACE.md's
“What `flows check` proves about the installed runtime” for scope and old binaries.
40 changes: 40 additions & 0 deletions docs/SURFACE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2002,3 +2002,43 @@ captured, hash-verified authored source bytes at their original module URLs.
Successful root output records the Node version, executable SHA256, and embedded
payload SHA256 as `executionRuntime`. The payload remains part of the existing
artifact hash, and completed child effects remain journal results on resume.


### What `flows check` proves about the installed runtime

For declarative YAML/JSON specs, `flows check` now also pipes the compiled kernel
spec to the installed `relayflowd validate-spec` when available. This performs
`RunSpec::parse` and `validate`, just like `run.start` (and is stricter than
`event.submit`, which currently only parses). No socket is opened, run created,
or journal written. The input is the same spec value `run.start` submits, in
canonical form.

- Default: automatic validation when the installed binary supports it; otherwise
local compile and preflight, with a reason on the `CHECK PASSED` line.
- `--against-daemon`: require a verdict; unavailable or incompatible validators
refuse with `daemon_validation_unavailable` and exit 2.
- `--no-daemon-check`: local compile and preflight only, without binary discovery.

`FLOWS_NO_DAEMON_CHECK=1` and `FLOWS_CHECK_AGAINST_DAEMON=1` select the same modes
for an environment; explicit flags win. If both environment variables are set,
the offline opt-out wins. Watch mode preserves the selection.

A daemon refusal uses `invalid_spec`, preserving the kernel field and step
location. A refusal on a generated gate step identifies an SDK lowering bug,
not an authoring mistake. JSON reports carry `validation` on success and refusal;
human output annotates the pass line, without adding a refusal terminator.

The annotation names the binary that judged the spec. An already running daemon
from a different build may disagree; this check does not contact that service.
Older binaries (including 2.0.19) lack `validate-spec`: automatic mode warns
`daemon_unvalidated`, naming the binary and the unproven acceptance, while
required mode refuses. The same applies to a validator that exits nonzero or
is killed after printing an accepting verdict (`abnormal_exit`): acceptance
requires both the verdict and exit 0. A daemon refusal is reported whatever the
exit status. This catches skew going forward, not retroactively.

Authored TypeScript bodies are not lowered until execution. Their header checks
remain local, with `authored_body` as the validation reason (required mode
refuses). This change therefore does not cover the authored-body case reported
in #489. Body capability probing and requiring daemon checks in release artifact
packaging are follow-on work.
93 changes: 93 additions & 0 deletions evidence/daemon-check/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
Commands were run from the repository root unless the command includes `cd`.
All `.txt` files contain captured output, not reconstructed output.

`cd packages/sdk && npx vitest run tests/check-daemon-validation.test.ts tests/cli.test.ts tests/cli-watch.test.ts tests/relay-cli-surface.test.ts`

[Captured output](sdk-final.txt)

`cd packages/sdk && npx vitest run tests/check-daemon-validation.test.ts tests/cli.test.ts tests/cli-watch.test.ts`

[Captured output](sdk-targeted-final.txt)

`cd packages/sdk && npx vitest run tests/relay-cli-surface.test.ts`

[Captured output](cli-surface-final.txt)

`cd packages/sdk && npx vitest run tests/plugin-extension.test.ts`

[Captured output](plugin-extension-retry.txt)

`cd packages/sdk && RELAYFLOWD_BIN=/tmp/relayflows-daemon-check-target/debug/relayflowd npx vitest run tests/live-kernel.test.ts -t 'validates the canonical ladder'`

[Captured output](live-ladder.txt)

`CARGO_TARGET_DIR=/tmp/relayflows-daemon-check-target CARGO_PROFILE_DEV_DEBUG=0 CARGO_PROFILE_TEST_DEBUG=0 CARGO_INCREMENTAL=0 /home/daytona/.cargo/bin/cargo test --manifest-path kernel/Cargo.toml -p relayflowd --test validate_spec`

[Captured output](rust-targeted-final.txt)

`CARGO_TARGET_DIR=/tmp/relayflows-daemon-check-target CARGO_PROFILE_DEV_DEBUG=0 CARGO_PROFILE_TEST_DEBUG=0 CARGO_INCREMENTAL=0 /home/daytona/.cargo/bin/cargo test --manifest-path kernel/Cargo.toml -j 2`

[Captured output](rust-workspace-retry.txt)

`cd packages/sdk && RELAYFLOWS_TOOLCHAIN_HOME=/tmp/daemon-check-toolchain CARGO_TARGET_DIR=/tmp/relayflows-daemon-check-target npm test`

[Captured output](sdk-full.txt)

`cd packages/sdk && PATH=/usr/bin:/bin /usr/local/share/nvm/versions/node/v25.6.0/bin/node node_modules/vitest/vitest.mjs run tests/check-daemon-validation.test.ts tests/cli.test.ts`

[Captured output](sdk-minimal-path.txt)

`node scripts/cli-package-gate.mjs`

[Captured output](package-gate.txt)

`node scripts/build-standalone-cli.mjs bun-linux-x64 /tmp/flows-daemon-check`

[Captured output](standalone-build.txt)

`RELAYFLOWD_BIN=/tmp/stub-relayflowd-check /tmp/flows-daemon-check check --against-daemon --json testdata/hello-deterministic.flow.yaml`

[Captured output](standalone-check.txt)

`cd packages/sdk && npm run typecheck && npm run typecheck:tests`

[Captured output](typecheck-final.txt)

`RELAYFLOWD_BIN=/tmp/relayflows-daemon-check-target/debug/relayflowd node packages/sdk/dist/cli.js check --against-daemon testdata/hello-agent.flow.yaml`

[Captured output](live-cli.txt)

`cd packages/sdk && RELAYFLOWD_BIN=<repo>/kernel/target/release/relayflowd ./node_modules/.bin/vitest run tests/live-kernel.test.ts -t 'naming the step the daemon named'`

Mutation verification for the live skew case: `stepId` mapping removed from
`daemon-spec-validation.ts`, the test run and its failure captured, the file
restored byte-for-byte (`git diff --exit-code` clean) and the test re-run.

[Captured output, mutated](live-skew-mutated.txt) ·
[Captured output, restored](live-skew-restored.txt)

`python3 evidence/daemon-check/mutation-run.py`

[Captured output](mutations.txt)

`git diff --check`

[Captured output](diff-check.txt)

The standalone stub consumed stdin and returned this envelope without running a flow:

```sh
#!/bin/sh
cat >/dev/null
printf '%s\n' '{"ok":true,"protocol":0,"spec_version":"0.1.0"}'
```

Additional historical captures: `rust-targeted.txt` is the private-toolchain
failure; `rust-targeted-retry.txt` contains the initial incorrect test assertion
for the cwd message; `rust-workspace.txt` contains the disk-full linker failure.
`sdk-build*.txt`, `typecheck-tests-final.txt`, and `sdk-targeted.txt` are earlier
build/typecheck/targeted captures. `rustfmt-setup.txt` records tool installation.
`timing.txt` captures shell `time` for the deterministic fixture in auto mode
with RELAYFLOWD_BIN pinned to the built binary, then with --no-daemon-check.
Each `mutation-*.txt` embeds its exact command and exit status. Temporary mutation
working directories are removed by the script; the captured outputs here persist.
10 changes: 10 additions & 0 deletions evidence/daemon-check/cli-surface-final.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

RUN v2.1.9 /home/daytona/.relayflow-v2-supervisor/durable/repository/packages/sdk

✓ tests/relay-cli-surface.test.ts (89 tests) 83ms

Test Files 1 passed (1)
Tests 89 passed (89)
Start at 14:36:15
Duration 3.18s (transform 1.91s, setup 23ms, collect 2.88s, tests 83ms, environment 0ms, prepare 44ms)

Empty file.
11 changes: 11 additions & 0 deletions evidence/daemon-check/live-cli.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
WARNING [permissions_unenforced] Step "edit" declares permissions (fileGlobs, accessPreset). This declaration is validated and recorded with the step spec; it is not currently enforced (gate 8 / #442). These permissions do not restrict file or network access.
WARNING [unprovable_effects] Step "greet" command "printf" resolves, but its effects cannot be proven before execution.
WARNING [unprovable_effects] Step "finish" command "printf" resolves, but its effects cannot be proven before execution.
WARNING [editor_schema_missing] For editor validation, add this first line: # yaml-language-server: $schema=https://schema.relayflows.dev/v0.1/flows.schema.json
GATE step "greet" exit_code+output_contains from data (kernel, journal-replayable)
GATE step "edit" output_contains from data (kernel, journal-replayable)
GATE step "finish" exit_code from data (kernel, journal-replayable)
RESOLVED step "edit" cli "./preflight/authenticated-cli" from project (/home/daytona/.relayflow-v2-supervisor/durable/repository/testdata/flows.json) model "test-model-v1" from step
REQUIRES claude (step "edit")
WARNING [agent_worker_unresolved] 1 agent step ("edit") requires an attached worker. For a local run, use `flows run --local-agent <flow>`, unless you already attach an agent worker for this daemon. `flows check` does not verify worker attachment: with no worker attached the run parks at the first agent step.
CHECK PASSED testdata/hello-agent.flow.yaml [validated by relayflowd at /tmp/relayflows-daemon-check-target/debug/relayflowd, protocol 0]
15 changes: 15 additions & 0 deletions evidence/daemon-check/live-ladder.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

RUN v2.1.9 /home/daytona/.relayflow-v2-supervisor/durable/repository/packages/sdk

stdout | tests/live-kernel.test.ts
LIVE_KERNEL relayflowd=/tmp/relayflows-daemon-check-target/debug/relayflowd
LIVE_KERNEL flows=/home/daytona/.relayflow-v2-supervisor/durable/repository/packages/sdk/dist/cli.js

✓ tests/live-kernel.test.ts (33 tests | 32 skipped) 306ms
✓ validates the canonical ladder with the built daemon without running it 303ms

Test Files 1 passed (1)
Tests 1 passed | 32 skipped (33)
Start at 14:31:05
Duration 4.41s (transform 2.31s, setup 62ms, collect 3.80s, tests 306ms, environment 0ms, prepare 46ms)

44 changes: 44 additions & 0 deletions evidence/daemon-check/live-skew-mutated.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
$ sed -i "s/const stepId = index === undefined ? undefined : submitted.steps[Number(index)]?.id;/const stepId: string | undefined = undefined;/" packages/sdk/src/daemon-spec-validation.ts
$ cd packages/sdk && RELAYFLOWD_BIN=$PWD/../../kernel/target/release/relayflowd ./node_modules/.bin/vitest run tests/live-kernel.test.ts -t "naming the step the daemon named"

RUN v2.1.9 /home/daytona/.relayflow-v2-supervisor/durable/repository/packages/sdk

stdout | tests/live-kernel.test.ts
LIVE_KERNEL relayflowd=/home/daytona/.relayflow-v2-supervisor/durable/repository/kernel/target/release/relayflowd
LIVE_KERNEL flows=/home/daytona/.relayflow-v2-supervisor/durable/repository/packages/sdk/dist/cli.js

❯ tests/live-kernel.test.ts (34 tests | 1 failed | 33 skipped) 61ms
× refuses a field the built daemon does not know, naming the step the daemon named 59ms
→ expected { severity: 'refusal', …(3) } to match object { Object (kind, stepId) }
(3 matching properties omitted from actual)

⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯

FAIL tests/live-kernel.test.ts > refuses a field the built daemon does not know, naming the step the daemon named
AssertionError: expected { severity: 'refusal', …(3) } to match object { Object (kind, stepId) }
(3 matching properties omitted from actual)

- Expected
+ Received

Object {
"kind": "invalid_spec",
- "stepId": "edit",
}

❯ tests/live-kernel.test.ts:2101:19
2099| // The daemon names the field and its own index; the report adds the…
2100| // step id for that index, and claims no compiler bug for an authore…
2101| expect(refusal).toMatchObject({ kind: 'invalid_spec', stepId: 'edit'…
| ^
2102| expect(refusal?.message).toContain('unknown field "from_a_newer_sdk"…
2103| expect(refusal?.message).not.toContain('generated by this compiler');

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

Test Files 1 failed (1)
Tests 1 failed | 33 skipped (34)
Start at 15:16:07
Duration 1.92s (transform 1.06s, setup 20ms, collect 1.67s, tests 61ms, environment 0ms, prepare 44ms)

exit=1
17 changes: 17 additions & 0 deletions evidence/daemon-check/live-skew-restored.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
$ git checkout -- packages/sdk/src/daemon-spec-validation.ts # restored byte-for-byte; git diff --exit-code is clean
$ cd packages/sdk && RELAYFLOWD_BIN=$PWD/../../kernel/target/release/relayflowd ./node_modules/.bin/vitest run tests/live-kernel.test.ts -t "naming the step the daemon named"

RUN v2.1.9 /home/daytona/.relayflow-v2-supervisor/durable/repository/packages/sdk

stdout | tests/live-kernel.test.ts
LIVE_KERNEL relayflowd=/home/daytona/.relayflow-v2-supervisor/durable/repository/kernel/target/release/relayflowd
LIVE_KERNEL flows=/home/daytona/.relayflow-v2-supervisor/durable/repository/packages/sdk/dist/cli.js

✓ tests/live-kernel.test.ts (34 tests | 33 skipped) 61ms

Test Files 1 passed (1)
Tests 1 passed | 33 skipped (34)
Start at 15:16:15
Duration 2.59s (transform 1.45s, setup 24ms, collect 2.31s, tests 61ms, environment 0ms, prepare 50ms)

exit=0
66 changes: 66 additions & 0 deletions evidence/daemon-check/mutation-indices-authored-mutated.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
$ 'node' '/home/daytona/.relayflow-v2-supervisor/durable/repository/packages/sdk/node_modules/vitest/vitest.mjs' 'run' 'tests/check-daemon-validation.test.ts' '-t' 'maps authored steps following generated steps'
cwd: /tmp/daemon-check-mutation-avim9yyh/packages/sdk

RUN v2.1.9 /tmp/daemon-check-mutation-avim9yyh/packages/sdk

❯ tests/check-daemon-validation.test.ts (20 tests | 1 failed | 19 skipped) 24ms
× maps authored steps following generated steps without blaming the compiler 23ms
→ expected [ { severity: 'warning', …(3) }, …(2) ] to deep equally contain { severity: 'refusal', …(4) }

⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯

FAIL tests/check-daemon-validation.test.ts > maps authored steps following generated steps without blaming the compiler
AssertionError: expected [ { severity: 'warning', …(3) }, …(2) ] to deep equally contain { severity: 'refusal', …(4) }

- Expected:
Object {
"errors": Array [
"unknown field \"nope\" at steps[2]",
],
"kind": "invalid_spec",
"message": "unknown field \"nope\" at steps[2]",
"severity": "refusal",
"stepId": "second",
}

+ Received:
Array [
Object {
"kind": "unprovable_effects",
"message": "Step \"first\" command \"echo\" resolves, but its effects cannot be proven before execution.",
"severity": "warning",
"stepId": "first",
},
Object {
"kind": "unprovable_effects",
"message": "Step \"second\" command \"echo\" resolves, but its effects cannot be proven before execution.",
"severity": "warning",
"stepId": "second",
},
Object {
"errors": Array [
"unknown field \"nope\" at steps[2]",
],
"kind": "invalid_spec",
"message": "unknown field \"nope\" at steps[2]",
"severity": "refusal",
},
]

❯ tests/check-daemon-validation.test.ts:178:38
176| { daemonValidation: 'required', daemonValidationDeps: deps('unknow…
177| expect(checked.report.ok).toBe(false);
178| expect(checked.report.diagnostics).toContainEqual({ severity: 'refus…
| ^
179| message: 'unknown field "nope" at steps[2]', errors: ['unknown fie…
180| });

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

Test Files 1 failed (1)
Tests 1 failed | 19 skipped (20)
Start at 14:32:45
Duration 2.04s (transform 1.15s, setup 17ms, collect 1.80s, tests 24ms, environment 0ms, prepare 71ms)


exit=1
14 changes: 14 additions & 0 deletions evidence/daemon-check/mutation-indices-authored-restored.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
$ 'node' '/home/daytona/.relayflow-v2-supervisor/durable/repository/packages/sdk/node_modules/vitest/vitest.mjs' 'run' 'tests/check-daemon-validation.test.ts' '-t' 'maps authored steps following generated steps'
cwd: /tmp/daemon-check-mutation-avim9yyh/packages/sdk

RUN v2.1.9 /tmp/daemon-check-mutation-avim9yyh/packages/sdk

✓ tests/check-daemon-validation.test.ts (20 tests | 19 skipped) 30ms

Test Files 1 passed (1)
Tests 1 passed | 19 skipped (20)
Start at 14:32:50
Duration 2.38s (transform 1.35s, setup 16ms, collect 2.02s, tests 30ms, environment 0ms, prepare 85ms)


exit=0
42 changes: 42 additions & 0 deletions evidence/daemon-check/mutation-indices-generated-mutated.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
$ 'node' '/home/daytona/.relayflow-v2-supervisor/durable/repository/packages/sdk/node_modules/vitest/vitest.mjs' 'run' 'tests/check-daemon-validation.test.ts' '-t' 'maps generated-step indices'
cwd: /tmp/daemon-check-mutation-avim9yyh/packages/sdk

RUN v2.1.9 /tmp/daemon-check-mutation-avim9yyh/packages/sdk

❯ tests/check-daemon-validation.test.ts (20 tests | 1 failed | 19 skipped) 12ms
× maps generated-step indices to submitted steps and identifies compiler bugs 11ms
→ expected { severity: 'refusal', …(4) } to match object { stepId: 'first.gate', …(1) }
(3 matching properties omitted from actual)

⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯

FAIL tests/check-daemon-validation.test.ts > maps generated-step indices to submitted steps and identifies compiler bugs
AssertionError: expected { severity: 'refusal', …(4) } to match object { stepId: 'first.gate', …(1) }
(3 matching properties omitted from actual)

- Expected
+ Received

Object {
"kind": "invalid_spec",
- "stepId": "first.gate",
+ "stepId": "second",
}

❯ tests/check-daemon-validation.test.ts:41:33
39| verification: { type: 'artifact_exists', path: 'answer.txt' } }, f…
40| const result = validateSpecWithDaemon(gated, 'required', deps('unkno…
41| expect(result.diagnostics[0]).toMatchObject({ stepId: 'first.gate', …
| ^
42| expect(result.diagnostics[0]?.message).toContain('generated by this …
43| expect(result.diagnostics[0]?.message).toContain('Report this rather…

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

Test Files 1 failed (1)
Tests 1 failed | 19 skipped (20)
Start at 14:32:43
Duration 2.09s (transform 1.18s, setup 14ms, collect 1.86s, tests 12ms, environment 0ms, prepare 84ms)


exit=1
14 changes: 14 additions & 0 deletions evidence/daemon-check/mutation-indices-generated-restored.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
$ 'node' '/home/daytona/.relayflow-v2-supervisor/durable/repository/packages/sdk/node_modules/vitest/vitest.mjs' 'run' 'tests/check-daemon-validation.test.ts' '-t' 'maps generated-step indices'
cwd: /tmp/daemon-check-mutation-avim9yyh/packages/sdk

RUN v2.1.9 /tmp/daemon-check-mutation-avim9yyh/packages/sdk

✓ tests/check-daemon-validation.test.ts (20 tests | 19 skipped) 6ms

Test Files 1 passed (1)
Tests 1 passed | 19 skipped (20)
Start at 14:32:47
Duration 2.55s (transform 1.49s, setup 14ms, collect 2.28s, tests 6ms, environment 0ms, prepare 47ms)


exit=0
Loading
Loading