Skip to content

docs(internal): forkd snapshot registry truthfulness root-cause + operator runbook (fabro-123) - #32

Open
zenprocess wants to merge 5 commits into
mainfrom
ao/fabro-123/snapshot-truthfulness
Open

docs(internal): forkd snapshot registry truthfulness root-cause + operator runbook (fabro-123)#32
zenprocess wants to merge 5 commits into
mainfrom
ao/fabro-123/snapshot-truthfulness

Conversation

@zenprocess

Copy link
Copy Markdown
Owner

Writing-FIRST deliverable for fabro-123.

The forkd controller on dellsrv is the live service that runs the
fabro GitHub gate's hermetic microVM. Issue fabro-sh#123 asks for a fix that
makes GET /v1/snapshots truthful AND auto re-registers snapshots on
forkd boot. The brief is explicit: root-cause first, in writing,
before any fix
.

This PR contains ONLY the root-cause + design note. It does NOT
contain a code change to the controller, because:

  1. The forkd controller source is not in the fabro repo. The repo
    contains the client (lib/crates/fabro-sandbox/src/forkd/mod.rs),
    the provider (lib/crates/fabro-sandbox/src/provider/forkd.rs),
    and the e2e BDD spec (specs/forkd-e2e/behavior.feature). The
    controller itself is a separate service running on dellsrv; its
    source is not local to this checkout.

  2. dellsrv / *.zp.digital / 10.0.201.x are NOT reachable from this
    sandbox. The brokered zen-gates path that the 2026-07-11 anchor
    references is not currently available: which zen-gates returns
    nothing, the zen MCP server is registered but not connected,
    codebase-memory has no forkd projects. I made one attempt to
    discover a brokered path, failed, and stopped, per the boundary.

What the doc records

  • Live evidence from Refactor fabro-model: OOP redesign with Catalog, LanguageModel trait, ModelRef fabro-sh/fabro#121 (PR Silent model fallback when model doesn't support agent mode fabro-sh/fabro#140, the descriptor-driven gate
    poller): the failure observed on 2026-08-01T08:32:27Z is a firecracker
    PUT /snapshot/load 400 on zen-gate-big, NOT the exec EAGAIN of
    Update tar crate to 0.4.45 (security fix) fabro-sh/fabro#122. This reframes the priority: snapshot registry work, not exec
    EAGAIN work, is the blocker.

  • What the evidence proves (the listing endpoint lies, the boot
    path can fail with HTTP 500 on restore_many, the forkd shim does
    not consume GET /v1/snapshots).

  • What the evidence does NOT prove (registry storage backing,
    whether zen-gate-big is missing entirely or present-but-broken,
    whether zen-gate-base is still bootable today, the fault_ JSON
    in the 400). I refused to invent answers to any of these.

  • Design choice for the controller-side fix (§6): boot-time
    re-registration via a REAL restore-boot canary (NOT a files-exist
    check), golden re-commit on missing, one
    snapshot re-registered tag=... log line per canary-passing
    snapshot. The design cites the 2026-07-11 anchor as the standing
    reason the canary cannot be skipped.

  • Operator runbook (§7): the exact commands the operator must run
    inside a zenctl maint on window to restart the controller, observe
    the post-boot listing, and run a canary exec on both zen-gate-base
    and zen-gate-big to determine which golden is missing, which is
    present-but-broken, and which is healthy. The data the runbook feeds
    back is the design input the controller-side implementation needs.

What this PR does NOT do

  • The controller fix itself. That code is on dellsrv and the source is
    not in this repo. The design in §6 is mine to write (this doc); the
    implementation is the operator's to do against the real controller
    source.
  • A restart of the controller. T3, operator only.

Companion PR

The gate-side preflight lives in
zenprocess/ao-company#143 — it asserts the requested snapshot tag
appears in GET /v1/snapshots before any in-VM work and posts a
distinct snapshot-not-registered infra verdict on mismatch. Until
the controller is fixed, the preflight is what makes the gate silence
on a missing tag observable as its own class.

Refs zenprocess/ao-company#123

Val and others added 5 commits July 11, 2026 12:48
The forkd exec wrapper assembles `sh -lc` bodies with a leading `cd
<working_dir> && ...` so subsequent commands run in the VM workspace.
When `[run.clone] enabled = false` the workspace dir is never created
and every command emits:

    sh: 1: cd: can't cd to /home/fabro/workspace

Guard the `cd` with `2>/dev/null || true` so a missing directory is
silently skipped while preserving the chained command's exit code.
The directory path is still passed through shell_quote() so spaces and
metacharacters stay safe.

Co-Authored-By: Claude <noreply@anthropic.com>
GET /api/v1/runs/<id>/sandbox still returned state:'running' for forkd
microVMs after the run had reached a terminal outcome. forkd destroys
its VM at run completion so the persisted sandbox record cannot be
introspected anymore — the persisted record would otherwise project a
stale 'running' value.

Resolve the reported state from the run's already-loaded status at the
point the sandbox object is built for the endpoint. Read the run-state
projection before loading the sandbox record, pass its terminal flag
down through sandbox_details(), and let forkd_details() choose between
SandboxState::Running (active) and SandboxState::Deleted (terminal —
the existing variant that maps to a destroyed microVM).

No HTTP call to forkd is added and no new SandboxState variant is
invented; other providers (docker/daytona/local) still source their
state from their own introspection and ignore the new parameter.

Co-Authored-By: Claude <noreply@anthropic.com>
…uming Deleted

A terminal forkd run does NOT imply a torn-down microVM: ForkdSandbox::stop is
the trait-default no-op and teardown only happens on the run-DELETION path
(cleanup() / DELETE /v1/sandboxes/{id}). A run finished with --preserve-sandbox
(stop_on_terminal=false) leaves the microVM alive, yet forkd_details reported
SandboxState::Deleted for any terminal run, so the UI hid ssh/terminal for a
box that was still reachable.

Replace the is_run_terminal -> Deleted mapping with a real liveness query:
- Add ForkdSandbox::get_sandbox_status (GET /v1/sandboxes/{id}) reusing the
  existing bearer-auth + is_retryable_status + HTTP_RETRY_LIMIT retry policy.
  It never errors: 200 -> Alive, 404/410 -> Gone, and any indeterminate outcome
  (5xx-after-retries, 405/501 endpoint-unsupported, connect/timeout) -> Unknown.
- forkd_details is now async: non-terminal runs still report Running without a
  round-trip; terminal runs map Alive/Unknown -> Running and only a
  controller-confirmed Gone -> Deleted. This is correct even if the forkd
  controller has no status endpoint yet (it simply never claims Deleted).
- The persisted runtime.id carries the "forkd:" sandbox_info prefix; strip it
  to recover the raw controller id for the URL.

Also make the forkd feature build/lint standalone (pre-existing gaps that kept
`--features forkd` from compiling): add dep:fabro-github and widen the
docker/daytona cfg gates on clone_source / RunId / runtime_layout_metadata to
include forkd. Correct two cd-guard test assertions that over-specified shlex
quoting, and rustfmt the crate. Local/Docker/Daytona behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… design

The comment claimed forkd tears down the microVM at terminal, which contradicts
the fix in 7a008fb (terminal != gone; --preserve keeps it alive). Prevents a
future maintainer reintroducing the terminal->Deleted shortcut.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…o-123)

Writing-FIRST deliverable for fabro-123. Records the evidence from fabro-sh#121
that surfaced the real blocker (restore_many 400 from firecracker on
zen-gate-big), separates what the evidence proves from what only a live
controller probe can prove, and gives the operator the exact commands to
run inside a zenctl maint window to determine which golden is missing,
which is present-but-broken, and which is healthy.

The controller fix itself is NOT in this commit: the forkd controller
source is not in the fabro repo (the client is; the controller lives on
dellsrv and its source is not local). Section 6 captures the design for
the operator to implement against the real controller source: boot-time
re-registration via a real restore-boot canary (NOT a files-exist check),
golden re-commit on missing, and a single 'snapshot re-registered tag=...'
log line per canary-passing snapshot. The preflight in fabro-github-gate.sh
lands in a separate commit on the ao-company branch.

I did not reach dellsrv to verify any of the open questions live: the
brokered zen-gates path is not currently available (the zen MCP server is
not connected, zen-gates/godkb binaries are not installed, and the
codebase-memory index has no forkd projects). I made one attempt to
discover a brokered path, failed, and stopped, per the brief's boundary.
The doc lists the four questions that only the operator's live probe can
answer and that the design depends on.
zenprocess pushed a commit that referenced this pull request Aug 1, 2026
…p, softer token rules (fabro-123 + fabro-122)

Orchestrator review of revision 1 caught three errors in the design note. All three are corrected and the most important is promoted to a new §1 Headline section.

Design note changes (docs/internal/forkd-snapshot-registry.md):

- §1 NEW: Headline — TWO distinct live infra failures with a ~91:1 ratio. Exec-stage EAGAIN (zenprocess/ao-company#122, 1092 occurrences) dominates restore-stage 400 (this issue, 12 occurrences). Both are real; they are different bugs with different fix paths.
- §3 NEW: What this worker got wrong in the previous revision (the orchestrator's three corrections, explicit).
  (a) gated=1 is a gate-attempt count, NOT a success verdict. The authoritative verdict lives in GitHub + fabro-gate-health.json.
  (b) The trader head 3ed3b8a4 pairing IS verified — gh api returns state=error at 2026-08-01T08:32:33Z with the restore_many 400 description.
  (c) The "log does not record 3ed3b8a" finding was misleadingly worded — the poll log's posted lines for this slice are uniformly for uniforme, but the trader/foundry verdicts live on GitHub, not in the file.
- All section numbers shifted by one (added §1 headline + §3 corrections).
- References updated to point at gh api + fabro-gate-health.json as authoritative.

Runbook changes (docs/internal/forkd-snapshot-registry-runbook.md):

- New §2 covers the exec-stage EAGAIN diagnostic (zenprocess/ao-company#122). Captures controller baseline (ps, /proc/PID/limits, cgroup) and EAGAIN-triggered samples of fd / thread / memory counters. Asks the operator to characterize which resource is exhausted.
- §3 (per-tag bootability, was §2) updated; the new EAGAIN step is §2.
- §4 (token handling, was §3) softened. The previous revision asserted the token "never lands on a log line because the bash subshell is the only place it is expanded." That was overconfident — shell tracing, error paths, and command capture can expose it. New rules: reference by file path only, confirm shell tracing is OFF, disable shell history capture for the maintenance window, do not paste commands into chat windows or pastebins, do not use curl -v or --trace.
- All section numbers shifted.

Doc header notes that this PR supersedes zenprocess/fabro PR #32. PR #32 also touches lib/crates/fabro-sandbox/* (DEAD paths post crate reorg; the sandbox crate now lives under lib/components/fabro-sandbox/) — those file changes are NOT carried forward.

Co-Authored-By: Claude <noreply@anthropic.com>
zenprocess added a commit that referenced this pull request Aug 1, 2026
… (fabro-123) (#33)

* docs(internal): forkd snapshot-registry root-cause + operator runbook (fabro-123)

Writing-FIRST deliverable for zenprocess/ao-company#123 acceptance
criterion #4. Records the verified evidence (restore_many 400 on earlier
uniforme heads, exec EAGAIN at 2026-08-01T08:32, blast radius from
fabro-gate-health.json) and explicitly marks every UNVERIFIED claim
with the reason (forkd controller source not local, dellsrv behind
egress boundary from this sandbox). The companion operator runbook
hands off items 1-2 of the issue to the operator, with T3 maint-window
discipline and a token-path-only reference (no token values).

The code half (PR fabro-sh#143, gate-side preflight that emits a distinct
snapshot-not-registered infra verdict) is already landed on
ao/fabro-123/snapshot-preflight in zenprocess/ao-company and is NOT
re-implemented here. Cross-referenced in §5 layer 1 and §7 of the
design note.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(internal): revision 2 — two-failure headline, EAGAIN runbook step, softer token rules (fabro-123 + fabro-122)

Orchestrator review of revision 1 caught three errors in the design note. All three are corrected and the most important is promoted to a new §1 Headline section.

Design note changes (docs/internal/forkd-snapshot-registry.md):

- §1 NEW: Headline — TWO distinct live infra failures with a ~91:1 ratio. Exec-stage EAGAIN (zenprocess/ao-company#122, 1092 occurrences) dominates restore-stage 400 (this issue, 12 occurrences). Both are real; they are different bugs with different fix paths.
- §3 NEW: What this worker got wrong in the previous revision (the orchestrator's three corrections, explicit).
  (a) gated=1 is a gate-attempt count, NOT a success verdict. The authoritative verdict lives in GitHub + fabro-gate-health.json.
  (b) The trader head 3ed3b8a4 pairing IS verified — gh api returns state=error at 2026-08-01T08:32:33Z with the restore_many 400 description.
  (c) The "log does not record 3ed3b8a" finding was misleadingly worded — the poll log's posted lines for this slice are uniformly for uniforme, but the trader/foundry verdicts live on GitHub, not in the file.
- All section numbers shifted by one (added §1 headline + §3 corrections).
- References updated to point at gh api + fabro-gate-health.json as authoritative.

Runbook changes (docs/internal/forkd-snapshot-registry-runbook.md):

- New §2 covers the exec-stage EAGAIN diagnostic (zenprocess/ao-company#122). Captures controller baseline (ps, /proc/PID/limits, cgroup) and EAGAIN-triggered samples of fd / thread / memory counters. Asks the operator to characterize which resource is exhausted.
- §3 (per-tag bootability, was §2) updated; the new EAGAIN step is §2.
- §4 (token handling, was §3) softened. The previous revision asserted the token "never lands on a log line because the bash subshell is the only place it is expanded." That was overconfident — shell tracing, error paths, and command capture can expose it. New rules: reference by file path only, confirm shell tracing is OFF, disable shell history capture for the maintenance window, do not paste commands into chat windows or pastebins, do not use curl -v or --trace.
- All section numbers shifted.

Doc header notes that this PR supersedes zenprocess/fabro PR #32. PR #32 also touches lib/crates/fabro-sandbox/* (DEAD paths post crate reorg; the sandbox crate now lives under lib/components/fabro-sandbox/) — those file changes are NOT carried forward.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Referee <ralukacostache@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
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