v0.1.0
First feature-complete stable. Closes the round-2 hardening review
(45 items across two passes), ships clean-laptop onboarding, and
publishes 14 ready-to-copy jarvy.toml project templates.
Telemetry-enabled deployments now actually export records — four
compounding OTLP bugs that left env-only opt-in silently emitting
zero records are fixed (see ### Fixed below). The public surface
from v0.0.5 is preserved; everything below is either additive,
fail-closed by default, or a tightening of internal invariants.
Upgrading from v0.0.5
jarvy update --channel beta (and jarvy update in general) is broken in
v0.0.5 — it exits 0 without actually upgrading. Two pre-existing bugs in
v0.0.5: a hardcoded version = "0.2" clap string that makes v0.0.5 think
it is newer than v0.1.0, plus an update path that never triggers an
artifact download. Both are fixed in v0.1.0 but cannot be patched
retroactively. v0.0.5 users must upgrade by reinstalling via their
package manager, not via jarvy update:
- macOS (Homebrew tap restored):
brew upgrade jarvy - Debian/Ubuntu:
sudo apt install ./jarvy_0.1.0_amd64.deb - Fedora/RHEL:
sudo dnf install ./jarvy-0.1.0-1.x86_64.rpm - Arch (AUR):
yay -Syu jarvy-bin - Windows (winget):
winget upgrade Jarvy.Jarvy - Cargo:
cargo install jarvy --force
From v0.1.0 onward, jarvy update --channel beta and jarvy update
work as documented.
Added
- Project templates.
examples/<stack>/jarvy.tomlships 14
validated drop-in configs (node-npm/pnpm/bun, deno, python-api/uv,
go-api, rust-cli/workspace, ruby-rails, java-spring, react-app,
fullstack, k8s-platform). Companion docs at
docs/templates-index.mdgive an AI-agent decision table mapping
detect-by signals (lockfiles, manifests) to template URLs. - Clean-laptop onboarding. New
Makefile+ idempotent
scripts/bootstrap.shgive contributors a two-command setup
(curl install.sh | bashthenmake setup). Bootstrap script
honorsJARVY_CHANNELfor stable/beta/nightly, falls back to
wgetifcurlis missing, and forwards extra args to
jarvy setup. shellcheck-clean. jarvy validaterecognizes the full top-level surface.
[npm],[pip],[cargo],[commands],[drift],[git],
[network],[logging]no longer trigger
"unknown configuration section" warnings. Toolchain channel
aliases (stable,beta,nightly,lts,current) are
accepted as valid version strings —rust = "stable"validates
cleanly.SecretError::PathEscapesProject+JARVY_ALLOW_EXTERNAL_SECRETS
override.[env.secrets] from_filepaths that resolve outside
the project root and$HOMEafter symlink-resolving
canonicalization are refused by default. Common legitimate paths
(~/.aws/credentials,<project>/.env.secret) keep working.
Override withJARVY_ALLOW_EXTERNAL_SECRETS=1.tools::pinned_installer::PinnedInstallerhelper for the
curl-bash class of installers. arctl, kmcp, and ollama (Linux
fallback only) now fetch their installer scripts at a pinned
commit, sha256-verify the body, and refuse to exec on mismatch —
same pattern Homebrew already used. Refreshing a pinned installer
requires updating the commit + sha256 constants together.- POSIX env-var grammar validation before writing
[env.vars]to shell rc files. Keys not matching
^[A-Za-z_][A-Za-z0-9_]*$are skipped with a structured
event="env.refused_invalid_key"warning instead of corrupting
~/.bashrc/~/.zshrc. tools::install_methodcanonical classifier
(Brew/Cargo/Nvm/Pyenv/Rustup/Snap/System/
NotFound/Unknown).commands::diagnose,commands::drift,
andobservability::bundleall delegate here instead of
hand-rolling three near-identical detectors.- Unsupported-tool feedback loop with telemetry-first delivery.
When a user (or AI agent) hits a tool Jarvy doesn't support, the
run now surfaces a structured request payload — fuzzy Levenshtein
suggestions with prefix-match boost, adefine_tool!scaffold
snippet, exit codeTOOL_UNSUPPORTED(8), and a delivery channel.
Telemetry is canonical: no GitHub account needed and zero triage
work for the maintainer. The pre-filledtool_request.ymlissue
URL is surfaced only when telemetry is off, with
jarvy telemetry enableoffered as a one-time alternative. New
jarvy tools --request <name> [--open]flag with pretty / JSON /
YAML / TOML output. Setup-path returns exit 8 only when every
configured tool was unknown — mixed runs still return 0 so partial
setups succeed. Canonicaltool.unsupportedevent with uniform
field shape across both call sites; OTEL counter
jarvy.tool.unsupportedrenamed from…not_supportedto match. crates/jarvy-templatesworkspace member — dep-free crate
shippingvalidate_tool_name,render_tool_template,
MAX_TOOL_NAME_LEN, and the embeddeddefine_tool!template.
cargo-jarvydepends only on this crate now; clean-build time
drops from minutes (full jarvy lib) to ~7s.
Changed
- Logging pipeline rewired to
tracing_appender::rollingfor
daily rotation +tracing_appender::non_blockingfor buffered
writes.analytics::shutdown_logging()flushes both the
SdkLoggerProviderand the fileWorkerGuardbefore
process::exit, so buffered records aren't lost on early
termination.EnvFilternow has a default-on floor of
warn,jarvy=infoifRUST_LOGis unset. Hook::run_with_policycollapsed from a 3-stateHookOutcome
enum toResult<(), HookError>. Production callers only ever
checkedFailvs not-Fail; the warning-on-continue_on_error
side effect already conveyed the difference. The newErrcase
returns the underlyingHookErrorsoerror_codes::HOOK_FAILED
callers keep working.Sanitizer::sanitize_borrowedreturnsCow<'_, str>so the
no-match path skips allocation entirely.Sanitizer::sanitize
preserves the same fast path internally.tracing::warn!→tracing::error!ontool.failed,
hook.failed,hook.timeout,config.parse_error, and
telemetry.endpoint.refused. These are operator-actionable
conditions, not advisory.- Subprocess spans.
services::run_commandand
tools::common::run_captureare now wrapped in
tracing::info_span!("subprocess.exec", cmd, args_count, ...)
with start/duration/exit_code events. paths.rscleanup.cache_dirinlined into
remote_config_cache_dir(only caller);#![allow(dead_code)]
removed since every public function has external callers now.
Security
- CA-bundle trust check tightened.
network::propagateno
longer accepts paths under the broad~/.jarvy/cache prefix —
only~/.jarvy/ca/is trusted, with a trailing-slash anchor so
~/.jarvy/ca-attacker/...can't slip through. - Cross-origin redirects refused on
remote::validated_get/fetch_remote_config.ureqagent
now uses.max_redirects(0); redirects must be revalidated
through the policy gate. - Sigstore companion verification.
update::releasereturns
Nonefor cosign companion files when the.sig/.pemaren't
exact-match siblings — a substring-match bug that would have let
a malicious tarball claim sibling signatures was closed. exec.rsdeleted (zero-caller speculative seam).team::inheritance::transform_github_urlduplicate deleted;
callers route through the canonicalremote::transform_github_url
so URL hardening lives in one place.
Fixed
validate_getrejected URLs with empty hosts underfile://
scheme but didn't match the documented "scheme not allowed"
error string. Test relaxed to accept any error variant; behavior
unchanged.paths::remote_config_cache_dirnow readsJARVY_HOME
consistently with the rest ofpaths.rs(was hand-rolling the
override before).update_rc_contentargument order documented; previously the
test suite caller had(content, &vars, &ctx, ShellType)instead
of the actual(content, ShellType, &vars, &ctx).- OTLP env-only opt-in now actually exports. Four compounding
bugs causedJARVY_TELEMETRY=1+JARVY_OTLP_ENDPOINT=…to
silently produce zero records, and even file-flag opt-in lost
every metric point on short-lived commands:
(1)init_logginggated on the file flag, missing the env
override — the OTEL log layer was excluded from the
subscriber whenever telemetry was opt-in via env only;
(2)opentelemetry-otlp0.31'swith_endpoint()is the FULL URL
not a base — a barehttp://localhost:4318producedPOST /
and the collector 404'd every batch. New
analytics::resolve_otlp_endpoint(base, signal)appends
/v1/{logs|metrics|traces}idempotently;
(3)otlp_logs_endpoint()ignored the file config's
[telemetry] endpoint— setting it via
jarvy telemetry set-endpointsilently failed to reroute
logs. The logger builder now reads the merged
TelemetryConfig;
(4)telemetry::shutdown()was defined but never called from
main, so theSdkMeterProvider's 60sPeriodicReaderhad
no chance to flush onjarvy setup-length runs.
Now called alongsideanalytics::shutdown_logging()in
the exit path. host.nameemitted as resource attribute, not per-event
field. Grafana Cloud was receiving plaintext
hostname=<machine>.localfrom thesetup.inventoryevent,
defeating the chart-side anonymize pipeline (which only operated
on resource-context attrs). Build a shared
opentelemetry_sdk::Resourceonce at telemetry init with
service.name,service.version,host.name,os.type,
os.description; attach to bothSdkLoggerProviderand
SdkMeterProvider. Previouslyservice.namedefaulted to
unknown_service, which broke stack-level filtering and made
"where did this record come from" guesswork. Local file logger
and stderr layers still print plaintext (those are operator-
owned sinks, not the egress channel).emit_telemetry_hint_if_undecidednow consults
telemetry::is_enabled()first so a user running with
JARVY_TELEMETRY=1doesn't see "telemetry is opt-in and
currently off" right after a run that just emitted records.- Drift hash respects
--file.jarvy drifthashed
<project_dir>/jarvy.tomlregardless of the--fileflag, so
drift detection silently used the wrong file when a non-default
config path was supplied. set_up_osmatchesenv::consts::OScasing. A capitalization
mismatch in the platform-dispatch table caused setup to fall
through to the unknown-OS path on some platforms.
Tests
- 1,633+ tests passing across lib + binary + integration suites
(was ~1,580). Highlights of the new coverage:validated_getrejection tests for HTTP-to-remote, disallowed
host,file://scheme, missing scheme.Hook::run_with_policyoutcome matrix (dry-run / success /
failure × continue_on_error true|false).verify_no_tar_escapecontainment tests + symlink-escape
refusal.- Cosign companion exact-match (no substring) regression.
- Path-containment refusal +
JARVY_ALLOW_EXTERNAL_SECRETS=1
override path for[env.secrets] from_file. - Shell-interpreted-key table-driven test
(every_shell_interpreted_key_refuses_bang_prefix) so adding
a new shell-interpreted git config key lights up the test
suite immediately.
#[serial_test::serial]annotations added for
JARVY_ALLOW_*env mutations to keep parallel runs isolated.
Docs
CLAUDE.mdLogging section rewritten to match the actual
src/logging/(thin re-export layer) andsrc/observability/
(where rotation + sanitizer + analytics live) split.examples/README.md+docs/templates-index.mdpublished as
the human/AI-facing template indexes.llms-full.txt"Project Templates" section added (with
docs/llms.txt+docs/llms-full.txtsymlinks for the published
docs site).
Full Changelog: v0.1.0-rc.15...v0.1.0
Installation
Quick Install (Unix)
curl -fsSL https://raw.githubusercontent.com/bearbinary/jarvy/main/dist/scripts/install.sh | bashQuick Install (Windows PowerShell)
irm https://raw.githubusercontent.com/bearbinary/jarvy/main/dist/scripts/install.ps1 | iexHomebrew
brew install bearbinary/tap/jarvyCargo
cargo install jarvyEarly-Release Channel
Pre-release tags (-rc.N, -beta.N) are routed through the beta channel.
Opt in: JARVY_CHANNEL=beta on the install script, or jarvy update --channel beta.
See docs/release-testing.md.
See installation docs for more options.
Security
Verify Signatures
All release artifacts are signed with Sigstore keyless OIDC. Verify any artifact:
ARTIFACT=jarvy-linux-x86_64.tar.gz
BASE=https://github.com/bearbinary/jarvy/releases/download/v0.1.0
curl -LO $BASE/$ARTIFACT
curl -LO $BASE/$ARTIFACT.sig
curl -LO $BASE/$ARTIFACT.pem
cosign verify-blob \
--signature $ARTIFACT.sig \
--certificate $ARTIFACT.pem \
--certificate-identity-regexp '^https://github\.com/bearbinary/Jarvy/\.github/workflows/release\.yml@refs/tags/v[0-9.]+(-[A-Za-z0-9.]+)?$' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
$ARTIFACTTag Signature
This release was cut from a signed git tag. Verify with:
git -c gpg.ssh.allowedSignersFile=.github/allowed_signers tag --verify v0.1.0SBOM
Software Bill of Materials in SPDX 2.3 (sbom.spdx.json) and CycloneDX 1.4 (sbom.cdx.json).
Checksums
SHA256 checksums for all artifacts are in SHA256SUMS.txt.