Team
Forwarding decisions
The forwarder runs on the developer's machine, next to the sandbox runtime. It sends decisions and requests up to the console, and brings reviews back down. Every connection starts on the machine. The control plane never connects in.
Run it
fenzi forward■ Forward
✓ Decisions 12
✓ Requests 1
✓ Applied 0
■ Forward
✓ Decisions 3
✓ Requests 0
✓ Applied 1On a machine signed in to a team, fenzi run and fenzi connect also forward in the background while the agent runs, every five seconds, and make one last pass when it exits, so a request the agent files reaches the console without anything else running. A Free machine does not forward. When another forwarder is already running, such as the Mac app's, the run leaves the passes to it. fenzi forward is for the time between runs, or a sandbox kept running with no terminal attached.
It runs until you press Ctrl-C, and looks every five seconds. --every 30s changes that. --once does one pass and exits, which is what a scheduled job or a test wants.
What one pass does
Sync. If the team published a host-only revision, load it onto running sandboxes. A path change is left for the next run, and the note says so.
Ship decisions. Read new allow and deny decisions for each sandbox Fenzi started, turn each into a decision record, and upload them.
Ship requests. Send pending requests for more access, with the agent's reason and any notes the runtime attached.
Apply reviews. For each request a reviewer decided in the console, make the same approve or deny call the terminal would, and mark it applied.
What it sends
One record per decision, the pending requests, and a report on the machine. Every field is listed once, in What our cloud receives.
Reading the runtime's stream
The forwarder watches the runtime's log stream for each sandbox and keeps the position of the last event it read. After a restart, or a dropped connection, it resumes from that position, so nothing is sent twice and nothing in the runtime's buffer is skipped.
If the runtime says events were lost, for example because its buffer filled or it restarted, the forwarder starts again from the current position and sends a gap record: the sandbox and the time span it could not read. The console shows the gap as a row. Those decisions are not recovered from anywhere else, and the console does not pretend otherwise.
When the console is unreachable
Records wait on the machine, in ~/.config/fenzi/spool.jsonl, and upload when the control plane answers again. The queue has a limit. If it fills, records are dropped and the forwarder prints how many, so a hole in the console always has an explanation on the machine.
Requests stay pending while the console is unreachable, and the blocked call stays blocked. The sandbox keeps enforcing its policy the whole time. See When something is down.
When a request changed after review
The runtime can refresh a request after it was filed, or replace the sandbox's draft with the agent's own request for the same host. If a reviewer decided the old version, the runtime refuses the decision. The forwarder does not retry it. It puts the request back on the console as pending, or, if the runtime replaced it, asks for a review of the new one. A person always decides the version that gets applied.
Run it as a service
To keep forwarding between runs, start fenzi forward with your login. The two files below are examples to copy and edit, not packages Fenzi installs. There is no packaged launchd or systemd unit and no MDM package yet; see Rolling out to many machines. The Mac app already forwards while it is open, so you need this only for the time it is closed.
Both run as your own user and need no administrator rights. The forwarder talks to the local runtime the same way fenzi run does, and to the control plane with the machine's token, so sign the machine in with fenzi login first. Until then it stops with a message and the service manager starts it again.
The path. The installer puts fenzi in /usr/local/bin when it can write there, otherwise in ~/.local/bin. FENZI_INSTALL_DIR puts it in the folder you name instead. command -v fenzi prints where yours is. If you installed it elsewhere, change the path in the file. Each file also sets PATH, because the forwarder runs the sandbox runtime's own commands and a service does not get your shell's PATH. Keep the folder the runtime was installed in on it.
macOS: a LaunchAgent
Save this as ~/Library/LaunchAgents/dev.fenzi.forward.plist. Replace you with your user name: launchd does not expand ~.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>dev.fenzi.forward</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/fenzi</string> <!-- or /Users/you/.local/bin/fenzi -->
<string>forward</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/Users/you/.local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/you/Library/Logs/fenzi-forward.log</string>
<key>StandardErrorPath</key>
<string>/Users/you/Library/Logs/fenzi-forward.log</string>
</dict>
</plist>Load it now. It also starts at every login from then on.
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/dev.fenzi.forward.plistRead what it did in ~/Library/Logs/fenzi-forward.log. Each pass writes a short report there, so the file grows; empty it now and then. To stop it and keep it from starting again, unload it and delete the file:
launchctl bootout gui/$(id -u)/dev.fenzi.forwardLinux: a systemd user unit
Save this as ~/.config/systemd/user/fenzi-forward.service. %h is your home folder. If fenzi is in /usr/local/bin, use that path in ExecStart.
[Unit]
Description=Fenzi forwarder
[Service]
ExecStart=%h/.local/bin/fenzi forward
Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin
Restart=on-failure
RestartSec=30
[Install]
WantedBy=default.targetLoad the file, then start it now and at every login:
systemctl --user daemon-reloadsystemctl --user enable --now fenzi-forwardIts output goes to the journal:
journalctl --user -u fenzi-forward -f