Harness pool: quarantine and scheduled refresh
The developer harness pool ($XDG_DATA_HOME/wraptool/harness-pool) installs a coding harness once and shares it across every project (see Getting started and Dev Containers for the mount mechanics). This page covers its update-safety story — quarantine — and the one piece of upkeep it needs from you: a periodic wraptool harness refresh.
Why quarantine, and why refresh matters
wraptool harness update does not just grab the newest release. Before a candidate version can be selected it must clear two independent age checks (design/harness/unified-harness-pool.md §9):
now - published_at >= minimum_release_age # registry publish time
now - first_seen_at >= minimum_observation_age # this pool's own clock
minimum_release_age entirely
minimum_release_age only means something measured against a real upstream publication timestamp. Some sources don’t have one: the agy (Antigravity) archive channel carries no publish-history field at all, and occasionally an npm package is missing its registry time metadata too. For exactly these candidates the pool synthesizes published_at from first_seen_at (its own first-refresh clock read) instead — and, because that synthesized value isn’t a real publish timestamp, pool.Eligible (and the list quarantine countdown) skip the minimum_release_age check entirely for them. Their only quarantine is minimum_observation_age, measured against the real first_seen_at.
The shipped default for minimum_observation_age is 0h. Put together: for agy today, out of the box, that is zero quarantine — a freshly refreshed candidate is eligible immediately, with no age check of any kind standing between it and install/update. This is deliberate (a source with no publish history would otherwise hit an unconditional wall on every first install — see internal/pool/eligibility.go’s PublishedAtSynthesized doc comment for the full ruling), but it means the scheduled-refresh habit this page argues for is not just about making minimum_observation_age count sooner for npm-sourced harnesses — for a synthesized-timestamp source like agy it is the entire quarantine story. Set a nonzero minimum_observation_age if you want any time-based protection at all there.
minimum_release_age (default seven days, policy set --minimum-release-age) protects against a drive-by release published on compromised maintainer credentials: a version pulled or reported during its quarantine window is never downloaded. It only needs the registry’s own publish timestamp, so it works even against a pool that has never run before.
minimum_observation_age (default 0h, opt-in) is different: it is not about when upstream published a release, it is about how long this pool has known the release exists.
# Require 48h of this pool's own observation before a candidate is eligible —
# the same syntax as minimum-release-age above (--harness scopes it).
wraptool harness policy set --minimum-observation-age 48hfirst_seen_at is recorded the first time refresh observes a given (source, package, version) triple, and it is write-once — repeated refreshes never move it forward or back. A pool that never runs refresh never records a first_seen_at, which means minimum_observation_age never starts counting for anything new: the countdown can only begin once metadata has actually been fetched at least once. If you set minimum_observation_age above zero and only ever refresh right before an update, you get the friction (a release looks freshly observed and stays quarantined) without the benefit (an attacker who compromises a release right after you observed it still slips through on your very next update). Refreshing on a schedule — independent of when you actually intend to update — is what makes the observation window mean anything.
Metadata-only refresh is exactly the right shape for a timer: it takes the pool’s advisory lock briefly (§6.5), talks only to the registry (no container/backend, no npm/docker/guix involved for an npm-sourced harness’s metadata, and none at all for the archive-sourced ones), and never downloads a package payload or changes what is active. Running it daily or weekly costs nothing at update time and only helps.
# Metadata only for every harness the manifest already lists; safe for a
# timer, and required for minimum_observation_age to ever start counting.
wraptool harness refreshWith no harness argument this only touches harnesses the pool manifest already lists (i.e. that wraptool harness install has recorded intent for) — nothing to refresh before a first install.
Scheduling it
Linux: a systemd user timer
Two unit files under ~/.config/systemd/user/:
~/.config/systemd/user/wraptool-harness-refresh.service:
[Unit]
Description=Refresh wraptool harness pool candidate metadata
[Service]
Type=oneshot
ExecStart=%h/go/bin/wraptool harness refresh(Adjust ExecStart to wherever your wraptool binary actually lives — %h expands to your home directory.)
~/.config/systemd/user/wraptool-harness-refresh.timer:
[Unit]
Description=Run wraptool harness refresh daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.targetEnable it:
systemctl --user daemon-reload
systemctl --user enable --now wraptool-harness-refresh.timerPersistent=true catches up a missed run (laptop suspended overnight) on the next login rather than silently skipping it. A weekly cadence (OnCalendar=weekly) is just as reasonable if minimum_observation_age is short or unset — the point is regularity, not frequency.
macOS: launchd
~/Library/LaunchAgents/com.wraptool.harness-refresh.plist:
<?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>com.wraptool.harness-refresh</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/wraptool</string>
<string>harness</string>
<string>refresh</string>
</array>
<key>StartInterval</key>
<integer>86400</integer>
<key>RunAtLoad</key>
<false/>
</dict>
</plist>(Adjust the ProgramArguments path to wherever wraptool is installed — which wraptool on the machine you’re loading this on.) Load it with the modern launchctl bootstrap form (targets your per-user GUI domain explicitly, unlike the older, ambiguous load):
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.wraptool.harness-refresh.plistOn an older macOS without bootstrap, fall back to launchctl load ~/Library/LaunchAgents/com.wraptool.harness-refresh.plist.
StartInterval is in seconds (86400 = daily); launchd runs the job roughly on that cadence and catches up a missed run once the machine wakes, similar in spirit to systemd’s Persistent=true.
What’s already shipped versus what’s still deliberately missing
The rest of §16 Phase 5 (“quarantine automation”) in design/harness/unified-harness-pool.md is implemented, not just planned:
- Local observations and release-age eligibility (item 2):
refreshrecords first-seen metadata inobservations.json;wraptool harness list’sELIGIBLE/QUARANTINEDcolumns andupdate/installthemselves enforce bothminimum_release_ageandminimum_observation_agebefore a candidate is selected. - Bypass audit events and candidate warnings (item 3):
--allow-young --reason '...'oninstall/updateexplicitly bypasses quarantine, prints the candidate’s age, and appends anallow_youngevent to the pool-localaudit.jsonl— it is never implied by any other flag.wraptool harness listshows a live countdown (e.g.2.1.221 (3d left)) for anything still quarantined, so the wait is visible before you reach for the bypass.
Item 1 (this page) is scheduling guidance — refresh itself has run the observation clock since Phase 1; a systemd timer or launchd job is the missing piece that makes minimum_observation_age actually protective instead of accidentally decorative.
Item 4, optional upstream signature/transparency adapters, remains deliberately unimplemented. Quarantine’s time delay is a real, working mitigation on its own (§9) — it complements digest/integrity verification, exact dependency locking, no-network realization, and rollback — but it does not require or wait on any particular registry’s signing scheme. If a source you use gains verifiable signatures or a transparency log, that is a future addition, not a prerequisite for anything documented here.