Skip to content

fix: provision dependency-only directories safely - #242

Merged
ruby-dlee merged 9 commits into
mainfrom
fm/provision-bare-requirements-p8
Aug 18, 2026
Merged

fix: provision dependency-only directories safely#242
ruby-dlee merged 9 commits into
mainfrom
fm/provision-bare-requirements-p8

Conversation

@ruby-dlee

Copy link
Copy Markdown
Owner

Intent

Fix the shared worktree-provisioning classifier in bin/fm-provision-lib.sh so a directory carrying only a dependency list installs those dependencies and is never treated as a runnable package. Establish and fix the actual failing command rather than assuming the reported requirements.txt diagnosis; preserve an honest capability-gap launch when safe dependency-only installation is unsupported. Cover the direct execution path with regression tests that prove requirements-only directories never become package installs and inspect sibling lockfile-only manager cases. Confirm that pyproject.toml without a dependency lock/list, ambiguous managers, below-scan-depth, and over-budget outcomes are deliberate separate policies. Do not silently change the fleet-wide component budget; include a reasoned, evidence-based recommendation for relvino's observed 23 components in the completion report, with any proposed default change left as a separate change. Validate review, tests, documentation, lint, push, PR, and CI to green.

What Changed

  • Treat bare requirements.txt and resolvable standalone uv.lock files as dependency lists without installing their directories as packages; report unsupported Python and JavaScript lockfile-only cases as capability gaps.
  • Harden Herdr event-reader environment isolation and distinguish unrelated fleet churn from lab-attributable tripwire failures.
  • Document the provisioning policies and expand regression coverage, including isolated test homes and configuration fixtures.

Risk Assessment

✅ Low: The change is well-bounded, preserves requirements.txt precedence and fleet-wide limits, and conservatively converts unsupported standalone lock shapes into explicit capability gaps.

Testing

The reported baseline and focused end-to-end CLI tests all passed, demonstrating dependency-only provisioning, separate pyproject/manager/depth/budget gap policies, guarded Herdr drift classification, and live Herdr spawn/event behavior; reviewer-visible CLI transcripts were captured and the worktree remained clean.

Evidence: Provisioning end-to-end transcript
== tests/fm-spawn-provision.test.sh ==
ok - a worktree declaring no recognized manifest provisions nothing and succeeds
ok - a bare requirements.txt installs only its dependencies
ok - bare requirements take precedence over a standalone uv.lock
ok - a standalone uv.lock installs only its locked dependencies
ok - an unresolved standalone uv.lock records a capability gap without refusing
ok - a path-dependent standalone uv.lock records a capability gap
ok - an npm lock without its required manifest records a capability gap
ok - a pnpm lock without its required manifest records a capability gap
ok - provisioning installs once and reuses an unchanged worktree without install cost
ok - a changed dependency manifest invalidates the cache
ok - installer configuration that changes what is installed invalidates the cache
ok - a fingerprint match with a broken environment is a miss, not a hit
ok - Node installs include declared validation dependencies deterministically
ok - an environment whose recorded runtime no longer matches is reprovisioned
ok - pip directives and includes are parsed, so an unchanged component stays cached
ok - build output written inside node_modules does not invalidate a healthy cache
ok - a declared manifest that cannot be read refuses instead of hashing to nothing
ok - a component nested past the scan depth is reported everywhere, not dropped
ok - a dependency scan that outlives its bound warns, reports, and launches unprovisioned
ok - a uv pip check that could not run is recorded as unverified, never as a verdict
ok - a python project with no committed lock or requirements file is named, not silently skipped
ok - the pyproject declaration read is bounded and degrades to undetermined
ok - a pyproject.toml holding only tool configuration provisions nothing and reports nothing
ok - a reported metadata inconsistency is recorded and launches; an unusable environment still refuses
ok - a failing install refuses the spawn and records no fingerprint
ok - an install that hangs is bounded and refuses instead of wedging the spawn
ok - a signal-killed bounded install is reported as a failure
ok - empty, zero, and nonnumeric provisioning tunables refuse before detection
ok - a traversal failure is distinct from a successful undeclared-worktree no-op
ok - a directory name containing another as a token is not deduped away
ok - a bounded call to a command that cannot be executed is a failure
ok - an environment signature that captures nothing refuses instead of hashing nothing
ok - a host without python3 warns and launches unprovisioned rather than refusing
ok - a host with no bounding mechanism runs nothing and launches unprovisioned
ok - a python project with no uv is left unprovisioned and reported, not refused
ok - a recognized but unsupported package manager is a recorded gap, not a refusal
ok - a worktree over the component budget provisions what it can and reports what it skipped
ok - the component budget is spent on what the task names before detection order
ok - a host capability gap is reported to the lane, not only to the operator
ok - a symlink planted at the report path is replaced rather than written through
ok - a requirements graph past the traversal cap is a gap, never a fingerprint over a prefix
ok - the JS package manager comes from what the project declares, not from lockfile precedence
ok - detection is driven by what the worktree declares and skips installed trees
ok - a uv workspace syncs every member while a single project keeps the plain sync
ok - uv workspace member manifests participate in the cache fingerprint
ok - a Node pin is read from what the project declares, and only when unambiguous
ok - a declared Node runtime that cannot be found leaves the JS components unprovisioned
ok - a declared Node runtime is resolved to its highest installed match and exported for the lane
ok - a Node pin wins for the Node toolchain and changes resolution for nothing else
ok - a shared pinned Node prefix is published once and never rebuilt under a running lane
ok - concurrent publishes each return a complete prefix and never nest one inside the other
ok - a published prefix that stops validating is stepped over, leaving the stale one untouched
ok - a spawn provisions its worktree after verification and before endpoint creation
ok - provisioning failure refuses the spawn instead of launching a lane that cannot validate
ok - a worktree over the component budget launches, warns, and records what it skipped
ok - the crewmate can read, inside its own worktree, what provisioning skipped and why
ok - teardown removes the provisioning log with the rest of the per-task state
ok - a later provisioning failure leaves the worktree clean without writing to the main clone
ok - an unignored install directory refuses before any installer runs, and never writes to the main clone
ok - provisioning is opt-out per spawn and per home, and the choice is recorded
ok - a lease that provisions nothing clears the previous task's report instead of inheriting it
ok - an unparseable provisioning setting refuses rather than silently disabling the gate
ok - a project declaring no recognized manifest spawns exactly as before
# all fm-spawn-provision tests passed
Evidence: Herdr tripwire behavior transcript
== tests/fm-herdr-lab.test.sh ==
ok - fm-herdr-lab: names fail closed and require the lab prefix
ok - fm-herdr-lab: provision timeout is configurable only within a bounded range
ok - fm-herdr-lab: provisioning, scoped calls, guarded teardown, and fleet tripwire are deterministic
ok - fm-herdr-lab: missing tripwire refuses teardown before any Herdr call
ok - fm-herdr-lab: agent argv routing inserts only the owned session before --
ok - fm-herdr-lab: changed default fleet state is a hard failure
ok - fm-herdr-lab: unrelated same-operator fleet churn is audited without blocking teardown
ok - fm-herdr-lab: lab-attributed default fleet drift retains teardown refusal
ok - fm-herdr-lab: transient default runtime activity does not change fleet identity
ok - fm-herdr-lab: provisioning requires a proven-running default session
ok - fm-herdr-lab: default running proof requires the JSON Boolean true
ok - fm-herdr-lab: destructive lab boundaries re-prove the running default session
ok - fm-herdr-lab: an owned stopped lab can re-provision safely
ok - fm-herdr-lab: failed deletion retains ownership until absence is confirmed
ok - fm-herdr-lab: timed-out provisioning cancels the launch before teardown
Evidence: Live Herdr backend smoke transcript
== tests/fm-backend-autodetect-smoke.test.sh ==
ok - real herdr: fm-spawn.sh auto-detects herdr from HERDR_ENV=1 (no explicit config) and prints the loud notice
ok - real herdr: auto-detected spawn records backend=herdr and herdr_session/workspace/tab/pane fields in meta
ok - real herdr: the auto-detected spawn's launch command actually ran in the herdr pane
ok - real herdr: teardown completes the auto-detected spawn/teardown cycle (meta cleared, pane closed)
== tests/fm-backend-herdr-eventwait-smoke.test.sh ==
ok - real herdr (herdr 0.7.3): events.subscribe capability gate passes (protocol >= 16, events surface present in api schema)
ok - real herdr (herdr 0.7.3): a driven idle->blocked transition returns the blocked record in 0.425s (pane w1:p2)
ok - real herdr: the watcher fast-path enqueues a stale wake naming the task window from the live blocked transition
- Outcome: 🔧 1 issue found → auto-fixed (7) ✅ across 8 runs (17h45m53s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed (2) ✅
  • 🚨 bin/fm-provision-lib.sh:1534 - The new standalone-uv.lock path creates a synthetic pyproject containing only the root name/version, then runs uv sync --frozen. A real lock generated from a project with dependencies will not match that synthetic metadata, so uv refuses to update under --frozen; the test fake only checks that pyproject.toml exists and therefore misses the real failure. This also leaves a directory containing both requirements.txt and a standalone uv.lock on the broken uv-lock path because detection prioritizes uv.lock. Preserve the required capability-gap launch for standalone locks unless matching project metadata can be reconstructed safely, and prefer the supported requirements installer when requirements.txt is present without pyproject.toml.

🔧 Fix: Prefer requirements over standalone uv locks
1 error still open:

  • 🚨 bin/fm-provision-lib.sh:1535 - Standalone locks are classified as supported after validating only the root entry. A valid lock with root source = { virtual = "." } and a dependency sourced from a relative workspace/path remains reachable: copying the lock to a cache directory changes the relative-path base, while --no-install-workspace may omit that dependency entirely. The probe does not verify locked packages for uv-lock, so provisioning can either fail the whole spawn or report success with an incomplete environment instead of the required honest capability gap. At the classifier boundary, reject locks containing non-root relative/workspace sources unless their original resolution semantics can be preserved safely.

🔧 Fix: Reject path-dependent standalone uv locks
✅ Re-checked - no issues remain.

🔧 **Test** - 1 issue found → auto-fixed (7) ✅
  • 🚨 tests failed with exit code 1
  • if [ "${FM_AZURE_VALIDATION_CELL:-0}" = 1 ]; then exec "$FM_AZURE_VALIDATION_SHARD_BRIDGE" behavior --count "${FM_AZURE_VALIDATION_SHARD_COUNT:-8}"; else exec bin/fm-no-mistakes-test-command.sh; fi

🔧 Fix: Isolate watcher guard configuration fixture
1 error still open:

  • 🚨 tests failed with exit code 1
  • if [ "${FM_AZURE_VALIDATION_CELL:-0}" = 1 ]; then exec "$FM_AZURE_VALIDATION_SHARD_BRIDGE" behavior --count "${FM_AZURE_VALIDATION_SHARD_COUNT:-8}"; else exec bin/fm-no-mistakes-test-command.sh; fi

🔧 Fix: Isolate watcher fixtures from live configuration
1 error still open:

  • 🚨 tests failed with exit code 1
  • if [ "${FM_AZURE_VALIDATION_CELL:-0}" = 1 ]; then exec "$FM_AZURE_VALIDATION_SHARD_BRIDGE" behavior --count "${FM_AZURE_VALIDATION_SHARD_COUNT:-8}"; else exec bin/fm-no-mistakes-test-command.sh; fi

🔧 Fix: Isolate test homes and reap Herdr event reader
1 error still open:

  • 🚨 tests failed with exit code 1
  • if [ "${FM_AZURE_VALIDATION_CELL:-0}" = 1 ]; then exec "$FM_AZURE_VALIDATION_SHARD_BRIDGE" behavior --count "${FM_AZURE_VALIDATION_SHARD_COUNT:-8}"; else exec bin/fm-no-mistakes-test-command.sh; fi

🔧 Fix: Isolate arm policy fixture home
1 error still open:

  • 🚨 tests failed with exit code 1
  • if [ "${FM_AZURE_VALIDATION_CELL:-0}" = 1 ]; then exec "$FM_AZURE_VALIDATION_SHARD_BRIDGE" behavior --count "${FM_AZURE_VALIDATION_SHARD_COUNT:-8}"; else exec bin/fm-no-mistakes-test-command.sh; fi

🔧 Fix: Confirm AFK failure was ambient Herdr interference
1 error still open:

  • 🚨 tests failed with exit code 1
  • if [ "${FM_AZURE_VALIDATION_CELL:-0}" = 1 ]; then exec "$FM_AZURE_VALIDATION_SHARD_BRIDGE" behavior --count "${FM_AZURE_VALIDATION_SHARD_COUNT:-8}"; else exec bin/fm-no-mistakes-test-command.sh; fi

🔧 Fix: Confirm bounded I/O cleanup passes focused stress
1 error still open:

  • 🚨 tests failed with exit code 1
  • if [ "${FM_AZURE_VALIDATION_CELL:-0}" = 1 ]; then exec "$FM_AZURE_VALIDATION_SHARD_BRIDGE" behavior --count "${FM_AZURE_VALIDATION_SHARD_COUNT:-8}"; else exec bin/fm-no-mistakes-test-command.sh; fi

🔧 Fix: Audit external Herdr churn while preserving attributed tripwire
✅ Re-checked - no issues remain.

  • if [ "${FM_AZURE_VALIDATION_CELL:-0}" = 1 ]; then exec "$FM_AZURE_VALIDATION_SHARD_BRIDGE" behavior --count "${FM_AZURE_VALIDATION_SHARD_COUNT:-8}"; else exec bin/fm-no-mistakes-test-command.sh; fi
  • Outer-executor baseline: if [ "${FM_AZURE_VALIDATION_CELL:-0}" = 1 ]; then exec "$FM_AZURE_VALIDATION_SHARD_BRIDGE" behavior --count "${FM_AZURE_VALIDATION_SHARD_COUNT:-8}"; else exec bin/fm-no-mistakes-test-command.sh; fi (reported successful)
  • bash tests/fm-spawn-provision.test.sh
  • bash tests/fm-herdr-lab.test.sh
  • bash tests/fm-backend-autodetect-smoke.test.sh
  • bash tests/fm-backend-herdr-eventwait-smoke.test.sh
  • Verified git status --short remained empty after testing
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@ruby-dlee
ruby-dlee merged commit f4fac5c into main Aug 18, 2026
13 checks passed
@ruby-dlee
ruby-dlee deleted the fm/provision-bare-requirements-p8 branch August 18, 2026 14:27
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