Skip to content

(MOT-4156) feat(fp,harness,console): scoped pipe steps under the harness fs_scope + trigger visualization - #559

Merged
andersonleal merged 9 commits into
mainfrom
feat/pipe-improvements
Jul 22, 2026
Merged

(MOT-4156) feat(fp,harness,console): scoped pipe steps under the harness fs_scope + trigger visualization#559
andersonleal merged 9 commits into
mainfrom
feat/pipe-improvements

Conversation

@andersonleal

Copy link
Copy Markdown
Collaborator

What

Lets shell::*/coder::* run as fp::pipe steps under the session's trusted filesystem scope — closing the biggest token sink the pipe could not cover (coder::search → fp::get → fp::map → state::set with the results never entering the chat) — plus the console rendering to make cron and pipe-reaction bindings legible.

How

Scope forwarding (fail-closed at every hop)

  • harness stamps the same trusted fs_scope it puts on direct shell/coder calls onto fp::pipe call args (is_stamped_function); all six inject() sites ride the one guard change, including approval release. filesystem_boundary() probes fp::pipe as a representative scoped call so pipe steps get the same jail as direct calls.
  • fp forwards the stamp per scoped step as the last write before dispatch (bus_step_args, after into threading, full key replace) — neither an authored payload.fs_scope nor a value threaded to /fs_scope survives. No stamp (cron, worker-to-worker, no working directory) → scoped steps refused with a teachable error. into pointers targeting /fs_scope are statically refused.
  • harness::react call dispatch strips authored scopes from stamped targets (a registered reaction dispatches outside the turn loop; the direct-shell variant of that hole pre-dated this branch).
  • Shell control-plane ids inside the scoped prefix (shell::config-status, shell::workspace::*) stay statically forbidden as steps.

fp usability (from live-session evidence)

  • Object-input transform errors name the object's keys and the fp::get fix — the top live failure was piping a producer's object response straight into an array transform.
  • Injected guidance: recipe now includes the select step, carries transform arg signatures (kills ~30k-token engine::functions::info dumps), and names producer response shapes.

Console

  • Cron bindings: WHEN pane reads "at 17:00 on Jul 21" beside the expression chip; describeCron covers common shapes and returns null (raw expression) for anything a translation would get wrong — ranges, L/#, dom+dow OR semantics, wild/nonzero seconds, and dialect-correct weekday ordinals (Rust cron crate is Quartz-style 1=Sun, verified against cron-0.12.1).
  • Call-mode reactions (metadata.callfp::pipe): THEN pane names the real target and renders the pipeline step route (PipeStepList, shared with the fp::pipe chat card). PipeView surfaces the stamped scope so approvers see where relative steps anchor.

Security posture

The pipe call remains the single needs_approval surface; the approver sees through[] plus the harness-stamped scope (stamped before hooks). Permissions comments now state that allowlisting fp::pipe grants un-approved shell/coder execution. Documented residual: a direct bus caller can still hand-craft a scope — but such a caller can already call shell directly; shell-side caller verification is tracked follow-up.

Verified

  • cargo test + clippy + fmt clean: harness (245), fp (34); console web: 1052 tests, typecheck clean.
  • Live end-to-end: coder::search → fp::map → fp::uniq → state::set (3225ch → 360ch stored, receipt only), shell::exec → fp::get /stdout → fp::split → fp::when → state::set anchored in the session root, forged payload scopes overwritten, into:"/fs_scope" statically refused, unstamped callers fail closed.
  • Rollout-order safe: either half shipping alone keeps scoped steps refused.

fp::pipe steps dispatch under the fp worker's authority, so shell::*/
coder::* steps never received the per-session filesystem scope and had to
be refused outright. Stamp the same trusted fs_scope object onto fp::pipe
call args (is_stamped_function beside is_scoped_function); fp forwards it
per scoped step. All six inject() call sites — turn loop pre/post-hook,
function_trigger, deferred approval release — ride the one guard change.

filesystem_boundary() probes fp::pipe as a representative scoped call
(shell::exec) so pipe steps get the same jail a direct call would — the
access watch binds shell::*/coder::* only, and without the probe a pipe
would run under the wider configured_roots boundary wherever approval-gate
is live. The watch itself deliberately stays unbound from fp::pipe: a
grant release re-invokes the whole pipe, re-running completed steps.
Lift the static shell::*/coder::* refusal: scoped steps now ride the
harness-stamped filesystem scope, closing the biggest token sink the pipe
could not cover (coder::search → transform → state::set without the
results ever entering the chat).

Fail-closed trust chain:
- PipeRequest.fs_scope (schemars-skipped) captures the harness stamp; the
  harness overwrites or strips anything model-supplied at that key.
- validate() refuses scoped steps when no stamp arrived (cron and
  worker-to-worker callers, sessions without a working directory) and
  statically refuses `into` pointers targeting /fs_scope.
- bus_step_args() re-stamps each scoped step as the LAST write before
  dispatch — after `into` threading — as a full key replace, so neither an
  authored payload.fs_scope nor a value threaded to /fs_scope survives.
  Non-scoped steps pass through untouched; the scope never leaks.

is_scoped_step mirrors harness filesystem_scope::is_scoped_function
byte-for-byte (divergence would open a stamp/strip gap). Permissions
comments now spell out that allowlisting fp::pipe grants un-approved
shell/coder execution — the pipe call stays the approval surface.

Live-verified: search→map→uniq→set (3225ch → 360ch stored, receipt only),
exec→get→split→when→set anchored in the session root, forged scopes
overwritten, unstamped callers refused.
The top live failure (hit twice in one session): a producer's OBJECT
response (coder::search {content_matches,…}, shell::fs::grep {matches,…})
piped straight into an array/string transform, answered by a bare
"map needs an array value (got object)". One shared needs() helper now
names the object's top-level keys and the fp::get fix across all fifteen
collection transforms, so a failed step self-corrects in one round trip —
fp::get's miss message was already the model for this.

Guidance tightened from the same evidence: the recipe it taught
(coder::search → fp::map) was itself the trap — it now includes the
fp::get select step; transform arg signatures ride along ("no schema
lookup needed", one live session burned ~30k tokens on
engine::functions::info dumps before its first pipe); producer response
shapes (search/grep/exec/fetch) are named so the right field is selected
first try.
…ions

Two register_trigger card gaps, both of which fell through to raw JSON:

- cron bindings: the WHEN pane now reads "at 17:00 on Jul 21" beside an
  expression chip. describeCron() covers the common shapes (fixed time,
  */N steps, hourly, weekday lists, day-of-month/month; 5-field classic
  and the seconds-first 6/7-field form) and returns null for anything a
  translation would get wrong — ranges, L/#, dom+dow OR semantics, pinned
  years — which keep the raw expression. The config pane is suppressed
  when the config is only the expression.

- call-mode reactions (metadata.call, e.g. turn-completed → fp::pipe):
  reactSpecSchema now models CallSpec (model/task optional — the view
  decides the mode, and metadata with neither call nor task still falls
  back to raw). The THEN pane names the binding's real effect (CALL
  fp::pipe + event-into chip) and renders a PIPELINE pane via
  PipeStepList, extracted from the fp::pipe chat card for reuse; non-pipe
  call targets get a compact payload pane.

Storybook fixture engineRegisterTriggerCallPipe mirrors the live binding.
Local path-dep versions (harness 1.4.3, iii-helpers 1.3.0) moved in
earlier release commits without regenerating dependents' lockfiles;
cargo refreshed them on the next build here. No dependency changes.
A call-mode harness::react payload is model-authored at registration time
and dispatches OUTSIDE the turn loop, so it never passed the trusted
fs_scope stamp path — a registered reaction could carry an arbitrary
root/grants to shell::*/coder::* directly, or to fp::pipe (which trusts
its top-level scope as the harness stamp). Strip the key from stamped
targets after event injection (so a scope threaded via event_into cannot
survive either) using the existing fail-closed inject(root=None) path.

Scoped calls from reactions are now refused downstream until a trusted
per-reaction scope source exists (stamping the registering session's
scope is possible follow-up). The direct-shell variant of this hole
pre-dates the pipe feature.
shell::config-status (agent-policy hard-denied — can surface operator
paths) and shell::workspace::* (console picker plumbing) sit inside the
scoped shell::* prefix, so the fs_scope stamp gate would have admitted
them as pipe steps under fp worker authority. Both ignore fs_scope
entirely; refuse them statically in forbidden_step, per the rule that
every agent-policy hard-denied class must be re-refused in a pipe.

Also formats pipe.rs/util.rs — cargo fmt --check gates the per-worker CI.
…cope

describeCron corrections:
- Seconds are honored, not ignored: every description except "every
  second" speaks at minute granularity, so wild or nonzero seconds now
  return null instead of claiming "at 17:00" for a schedule that fires
  sixty times in that minute.
- Weekday ordinals follow the dialect: the seconds-first form is the Rust
  cron crate's Quartz-style numbering (1=Sun..7=Sat, verified against
  cron-0.12.1 DaysOfWeek — 0 invalid), while classic five-field cron
  stays 0=Sun..6=Sat with 7 as Sunday. Numeric weekdays previously
  rendered one day late in the Rust dialect.

PipeView now surfaces the harness-stamped fs_scope (root chip + grants
count) — the pipe call is the approval surface, and the custom view
replaces the raw request pane, so an approver reviewing relative
shell/coder steps could not previously see where they would run.
@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jul 22, 2026 1:26pm
workers-tech-spec Ready Ready Preview, Comment Jul 22, 2026 1:26pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 47 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@andersonleal andersonleal changed the title feat(fp,harness,console): scoped pipe steps under the harness fs_scope + trigger visualization (MOT-4156) feat(fp,harness,console): scoped pipe steps under the harness fs_scope + trigger visualization Jul 22, 2026
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@andersonleal, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a46b9fea-c624-4eee-86fe-f61bacf95e18

📥 Commits

Reviewing files that changed from the base of the PR and between 28f44dc and c9d89a5.

⛔ Files ignored due to path filters (8)
  • approval-gate/Cargo.lock is excluded by !**/*.lock
  • provider-anthropic/Cargo.lock is excluded by !**/*.lock
  • provider-kimi/Cargo.lock is excluded by !**/*.lock
  • provider-llamacpp/Cargo.lock is excluded by !**/*.lock
  • provider-openai-codex/Cargo.lock is excluded by !**/*.lock
  • provider-openai/Cargo.lock is excluded by !**/*.lock
  • provider-xai/Cargo.lock is excluded by !**/*.lock
  • provider-zai/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • console/web/src/components/chat/engine/RegisterTriggerView.tsx
  • console/web/src/components/chat/engine/__tests__/parsers.test.ts
  • console/web/src/components/chat/engine/parsers.ts
  • console/web/src/components/chat/fp/PipeView.tsx
  • console/web/src/components/chat/fp/parsers.ts
  • console/web/src/stories/fixtures/engine-fixtures.ts
  • fp/README.md
  • fp/iii-permissions.yaml
  • fp/src/guidance.rs
  • fp/src/pipe.rs
  • fp/src/util.rs
  • harness/src/filesystem_scope.rs
  • harness/src/functions/react.rs
  • harness/src/hooks/mod.rs
  • iii-permissions.yaml
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/pipe-improvements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@andersonleal
andersonleal merged commit c3f7b3e into main Jul 22, 2026
69 of 70 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant