plan: 0012-macos-bidder-vz paused: > PAUSED by the user on 2026-10-16: no macOS work until the user resumes it. Done: M0-REVISIT (the plan now follows the code at socialweb-computer-kcp 79f40adb; docs/ARCHITECTURE.md section 15 holds the reconciliation and one new item, M1-BOOTSTRAP-DARWIN), M0-HOST-BASELINE and M0-VFKIT-ASSET (vfkit v0.6.4, the Red Hat universal file, pinned in versions.json and shipped as is). Not started: every other M0 item. Learned: kcp v0.33.0, a cgo kine built from source, bao v2.6.2 and deno 2.9.7 run on darwin/arm64; a system-domain LaunchDaemon with a non-admin UserName started a VM through Virtualization.framework (a guest-less EFI probe, see section 15); a Setpgid child outlives a SIGKILLed launchd job and its bootout, so the darwin DenoPod runner must not give each run its own process group; the unix socket path limit is 103 bytes, not 107; a home directory is mode 750, so daemon assets live in a root-owned directory; this Mac has FileVault on and an open console session and may not be rebooted, signed out or put to sleep, so the reboot, logout and no-login proofs stay blocked until the user does them. The next worker needs: a way to run a market network on the Mac (a darwin devsup, or a plan to join a remote network, see section 15), the launchd hostsvc.Manager, the vz-arm64 guest image (a kernel with virtio-pci, built on a Linux host), and a clean Mac state: nothing of this work is left outside ~/.tmp on the Mac. written: 2026-10-11 queued: > QUEUED. Starts only after plan 0006 (docs/plans/0006-socialweb-computer-unified-binary.yaml) and plan 0007 (docs/plans/0007-compute-provider-kcp.yaml) are complete on Linux, every item done with evidence and their acceptance checks ACC6-* and ACC7-* recorded, and after the plan 0008 (docs/plans/0008-embedded-deno-and-typescript-denopods.yaml) items M1-DENO, M1-TS-CLOSURE, M2-DENOKCP-FORK, M2-DENOKCP-INPROC, M2-SERVICE-TEMPLATE and M4-MARKET-NETWORK-PORT are done. Then M0-REVISIT runs first; no other item may move to doing before it is done. Every item is executed ON a macOS host with Apple Silicon (decided by the user 2026-10-11); the executor works on that host, not on hat-8. M0-REVISIT ran on 2026-10-16 against socialweb-computer-kcp origin/main 79f40adb (the vendored hono-compute-provider and atproto-market are the ts/ trees at that commit); every prerequisite item above is done, and docs/ARCHITECTURE.md section 15, "Plan 0012 M0-REVISIT", holds the reconciliation. source: > The user's decisions of 2026-10-09..11: macOS and Windows bidder work is deferred out of plans 0006, 0007 and 0008 into their own plans (0011 for Windows, this one for macOS). Plans 0006 and 0007 build the cross-OS seams on Linux: Go interfaces, GOOS build-tagged files, and per-OS embedded assets compiled in only for the target OS. Expected seam names (they may differ as landed; M0-REVISIT reconciles): a VM host/runtime interface (Start, Inspect returning a Verdict Running|Lost|Unknown, Stop with a grace, List by owner) with one backend per OS; a host-unit/service-manager interface; an OS-neutral ComputeVM status.runtime {backend, ref, instance, pid?, startTime?}; a content-addressed binary install versions//; asset dirs runtime// garbage-collected only when nothing references them. As landed: the VM seam is internal/vmruntime.Runtime over hostsvc.Manager (Verdict is hostsvc.Verdict; status.runtime is {backend, ref, instance, pid, startTime, hostBootID, vmmID}); the binary install is bin// with bin/current and bin/previous; asset dirs are runtime//. Headless servers only. A VM survives a crash or upgrade of the core binary and any logout; a host reboot makes every VM Lost, replaced through a new RFP. The mechanism is the native one per OS. The invariant: no socialweb-computer code is in a running VM's process tree; the OS service manager or VMM owns the VM directly. On macOS that is a launchd job whose program is the VMM itself (vfkit), with networking either Virtualization.framework NAT or a separate gvproxy job (open; M0-NETWORK-VZ); our code only does one-shot preparation (disks, the job's plist, its submission) and later inspects and stops the job. All code, docs, architecture and plan items are changeable; the best solution wins. state_rules: - TypeScript lives in this repository (decided by the user 2026-10-12): the closure's ten repositories were vendored with their history under ts// and the originals are frozen; a path this plan names under ts/ is changed here, never in the original. - > Decided by the user 2026-10-16: the Mac this plan runs on is never rebooted, power-cycled, signed out of or put to sleep by an executor, not even after asking. A proof that needs one of those is split: the part that a no-reboot equivalent can show (bootout of the launchd domain or job, kill of the VMM owner, a stale reference Inspected to Lost, a console user who stays logged in) is run and recorded; the reboot-, logout- and sleep-only part is written down with what it would add and stays blocked until the user does it. - This file is the only state. Every step updates status and evidence. - status is one of todo, doing, done, blocked. - An item is done only when its evidence is recorded (commit SHAs, test names watched to fail and then pass, readings from the macOS host with UTC timestamps, the host's model, chip and macOS version and build named in each reading). - Open items are mirrored in NEXT.yaml (P12-*). - A decision marked "decided by Claude, user may veto" was not confirmed by the user; one marked "decided by the user 2026-10-11" was. - An item names its dependencies in depends_on; an item may not start while one of them is not done. Cross-plan dependencies are written :. - > M0-REVISIT must run before any other item moves to doing, and must be redone (its status back to doing, every item that depends on it re-checked) whenever plan 0006, 0007 or 0008 changes a seam, the ComputeVM schema, the asset pipeline, the controller or the install/upgrade flow after M0-REVISIT's recorded commit SHA. - > RFP flow is the spine (org-root CLAUDE.md). Every test or spike that needs a live guest drives the requester (runComputeContract) against a real local bidder through RFP, bid, accept and cloud-init user_data. No test, spike or code boots a guest any other way: no hand-run vfkit with a hand-made disk, no `container run`, no hand-written authorized_keys. Zero ssh sessions to any guest. ownership: owned: > All code in socialweb-computer-kcp (including the darwin backends and build-tagged files this plan adds), the forked kcp, deno-kcp and kcp-libs copies plans 0006 and 0008 vendor, and the macOS host the user provides for this plan. hono-compute-provider and atproto-market may be changed by commit; never pushed. not_owned: > The market protocol records and lexicons (so any arch field for RFPs or bids is the user's decision, questions_for_user), and the core architecture: nodes and guests only ever born through requester -> RFP -> bid -> accept -> cloud-init user_data, reached only through the relay. vfkit and gvproxy are pinned third-party assets, used unmodified; macOS itself (launchd, Virtualization.framework) is measured, never patched. research: measured_on: > 2026-10-11 on hat-8 (Linux) from this repo at f0460fd (main) and from published documentation only. NOTHING in this block was observed on a Mac; every macOS behaviour below is a claim the M0 items measure. The lifecycle notes this plan was to be written from (scratchpad lifecycle-macos.md, lifecycle-synthesis.md, lifecycle-linux.md) were not on disk when it was written; M0-REVISIT re-reads whatever 0006/0007 recorded in their place. in_repo_today: - "internal/hostos has darwin files since 0006 M0-XOS-GATE: proc_darwin.go (ProcStart, Cmdline, Alive, Signal, Reload, Terminate), bootid_darwin.go and portrange_darwin.go refuse with ErrUnsupported (plan 0012); sysproc_darwin.go gives Setpgid and an empty DieWithParent; lock_unix.go (build tag unix) already serves darwin. docs/ARCHITECTURE.md 'The host platform' has a darwin column that says 'refuses'. GOOS=darwin GOARCH=arm64 go build ./... and go vet ./... are clean at 79f40adb." - "plan 0006 M0-SIZE-BUDGET (as written 2026-10-07) gives darwin client modes only and refuses run bidder by name; superseded by the user's 2026-10-11 decision that this plan builds the macOS bidder. M0-REVISIT reads what 0006 landed." - "internal/kcplibs/impl/execrunner/runner_linux.go:19 Setpgid per DenoPod run, killGroup/termGroup signal that group (runner_darwin.go refuses today): on darwin a per-run process group escapes launchd's kill of the job's process group when the bidder dies (launchd.plist AbandonProcessGroup semantics), so deno children would outlive a crashed bidder" - "cmd/socialweb-nodeimage, socialweb-nodeinit and socialweb-nodeboot are gone. Guest images are built on Linux by scripts/build-guest-image.sh into kernel + rootfs.ext4 + image.json per hypervisor and arch, pinned in internal/versions/versions.json guestImages.targets, never embedded, fetched and verified by internal/guestimage (the name vz-arm64 is reserved there for this plan); the firecracker-aarch64 kernel config (deploy/guest-kernel/firecracker-aarch64.config) has virtio-mmio and no virtio-pci, and VZ presents virtio over PCI, so VZ needs its own kernel config or a distribution kernel" - "user_data reaches the guest on a read-only ext4 disk labelled cidata (cloud-init NoCloud), written in pure Go by internal/guestdisk WriteSeed over internal/guestseed; boot args come from image.json; the rootfs is cloned by internal/guestdisk Clone (clone_linux.go FICLONE; clone_other.go plain copy)" - "atproto-market lib/common/cloud-init-common/mod.ts: UserDataModule at :70, registry at :90, registered modules are secrets, k3s, iroh and poweroff-ends-vm (:838-841, after 0008 M1-STRIP-LEGACY-TRANSPORTS); guest-side arch mapping is in the iroh module (dumbpipe x86_64|aarch64, :559-566) and k3s uses the upstream installer; every module is checked on arm64 in M0-ARCH-MODULES" - "atproto-market lexicons/com/publicdomainrelay/temp/compute/vm.json:19-45 compute.vm has cpus, mem, disk, network, location, role, user_data and NO arch field: a requester cannot say amd64 and an arm64-only bidder cannot tell" - "ts/hono-compute-provider has no container-backend-container any more (lib/ holds compute-provider-kcp, container-backend-docker and microvm-vmruntime); a guest is a ComputeVM object that the Go controller internal/computevm reconciles through internal/vmruntime, so the VM host seam is Go (microvm-vmruntime only shells out to the socialweb-computer vm verb, cmd/socialweb-computer/vm.go)" - "deno-macos-runner-desktop (org root) is a tray UI for a desktop bidder; this plan is headless only and does not use it" virtualization_framework: - "vfkit: 'The virtual machine will be started when vfkit starts and will be terminated as soon as the vfkit process exits' (https://github.com/crc-org/vfkit/blob/main/doc/usage.md). The VMM process therefore IS the VM's lifetime, which is why the launchd job's program must be vfkit itself and nothing of ours." - "vfkit flags used here (same doc): --bootloader linux,kernel=,initrd=,cmdline= with a kernel that 'must be uncompressed or the VM will hang'; --device virtio-blk,path=; --device virtio-net,nat,mac=...; --device virtio-net,unixSocketPath=... for gvisor-tap-vsock; --device virtio-serial,logFilePath=...; --device rosetta,mountTag=...; --restful-uri unix://... with GET /vm/state; --timesync needs qemu-guest-agent over vsock" - "gvproxy with vfkit needs no helper in the guest: gvproxy -listen unix://... --listen-vfkit unixgram://... and vfkit --device virtio-net,unixSocketPath=... (https://github.com/containers/gvisor-tap-vsock)" - "Every process using Virtualization.framework needs the com.apple.security.virtualization entitlement; an ad-hoc signature carries it: codesign --entitlements vz.entitlements -s - (https://github.com/Code-Hex/vz README). Code-Hex/vz needs CGO_ENABLED=1 and Xcode or the Command Line Tools even though it calls through purego (same README)." - "VZNATNetworkDeviceAttachment (https://developer.apple.com/documentation/virtualization/vznatnetworkdeviceattachment): every NAT-attached VM of the host shares one vmnet shared network, so guest-to-guest isolation is not given; measured in M0-NETWORK-VZ" - "VZLinuxRosettaDirectoryShare, macOS 13+ (https://developer.apple.com/documentation/virtualization/vzlinuxrosettadirectoryshare): amd64 Linux user binaries can run in an arm64 guest through binfmt; a partial answer to the arch question, not a substitute for it" - "An Apple DTS engineer advised against running a (macOS-guest) VZ VM from a root launchd daemon after intermittent Secure Enclave key errors (https://developer.apple.com/forums/thread/841688); Tart's headless guidance requires a logged-in GUI session (https://tart.run/faq/), and a CI installer that relies on auto-login requires FileVault off. None of these is about Linux guests under vfkit with no one logged in; that is M0-LAUNCHD-DOMAIN's measurement and the plan's largest risk." - "Rejected by the invariant, recorded: lima keeps the VZ VM inside its own hostagent process (lima docs internals, vz.pid), and an own cgo VZ helper would be our code as the VMM; krunkit (libkrun) needs macOS 14+ and is a possible second VMM, not evaluated here" launchd_and_platform: - "launchd.plist(5): UserName runs a system-domain job as that user; KeepAlive, RunAtLoad, LaunchOnlyOnce; AbandonProcessGroup false (default) means launchd kills the job's remaining process group when the job exits. Only plists under /Library/LaunchDaemons (and /System) are loaded at boot, so a job bootstrapped by path from elsewhere is gone after a reboot: reboot = Lost by construction, measured in M0-REBOOT-LOST-POWER." - "golang.org/x/sys/unix SysctlKinfoProc(\"kern.proc.pid\", pid) returns KinfoProc whose Proc.P_starttime is the start time (x/sys unix ztypes_darwin_*.go), CGO_ENABLED=0: the darwin twin of /proc//stat start ticks" - "Apple: replacing a signed executable in place (cp keeps the inode) risks a code-signing crash the next time it runs; write a new file and rename (https://developer.apple.com/documentation/security/updating-mac-software). runtime// and bin// never rewrite a file in place." items: - id: M0-REVISIT milestone: M0 title: Re-read what 0006, 0007 and 0008 actually landed and amend this plan to match before anything else depends_on: [] decision: > Decided by the user 2026-10-11: everything is changeable and the plan follows what landed, not what was expected. Before any execution, on the Mac's checkout: re-read the status and evidence of every item of plans 0006, 0007 and 0008, docs/ARCHITECTURE.md (at least sections 5, 9 The host platform, 12, 13), NEXT.yaml, and the code as landed: the VM host/runtime seam (interface, Verdict, Stop grace, List by owner), the host-unit/service-manager seam, the GOOS build-tagged files and which are stubs for darwin, the per-OS asset pipeline (how a darwin-only asset is staged, pinned and, for vfkit, signed), the ComputeVM schema (status.runtime and its field managers), the controller's re-population rule, the install and upgrade flow (versions//, GC of runtime//), the DenoPod runner, the market network command, and 0006 M0-SIZE-BUDGET's platform matrix (darwin as client-only is superseded). Then amend THIS file: ids, decisions, depends_on (real landed cross-plan ids), files (real package paths) and the acceptance checks. A seam gap a darwin backend needs is written as a new item here, never patched silently. files: ["docs/plans/0012-macos-bidder-vz.yaml (this file)"] test_first: > None (reconciliation). The reading is a diff of this file before and after, plus `GOOS=darwin GOARCH=arm64 go build ./... && go vet ./...` on the landed tree and a list of every seam symbol with no darwin implementation. done_when: > This file amended and committed; evidence names the socialweb-computer-kcp commit SHA (and the hono-compute-provider and atproto-market SHAs) it was checked against, the renamed or added items, and the darwin stub list. risks: - "If 0006/0007 change after this ran, the state rule sends this item back to doing; the evidence keeps every SHA it was run against." status: done evidence: [] - id: M0-HOST-BASELINE milestone: M0 title: Record the Mac and run the landed binary's non-VM modes on it depends_on: [M0-REVISIT] decision: > Read and record: model, chip, RAM (sysctl hw.memsize), macOS version and build, SIP state, FileVault state (fdesetup status), pmset -g, whether anyone is logged in at the console, Xcode CLT presence (it must NOT be needed at runtime). Build the landed tree for darwin/arm64 and run as an unprivileged user: `version --json`, `check-host`, in-process kcp on port 0 with its OnListen ports (0006:M2-KCP-PORT0), the embedded deno running a fixture entry with --cached-only (0008:M1-DENO), an OpenBaoServer object with the darwin bao asset (0006:M2-OPENBAO-SERVER), and one DenoPod through the forked runner. Also measure the process-group question: start the runner under a test LaunchDaemon, SIGKILL it, and record whether its deno children (Setpgid per run, execrunner runner_linux.go:19; the darwin runner refuses today, so this reading uses a stand-in parent that sets Setpgid on a child) survive launchd's kill of the job's group. Each failure is an item added here by name. files: ["evidence only; new items appended to this file for every failure"] test_first: "None (measurement). Each command's output and exit status with UTC timestamps." done_when: "Host record committed; each non-VM mode recorded as works or fails-with-item; the deno-survives-bidder reading recorded." risks: [] status: done evidence: [] - id: M0-VFKIT-ASSET milestone: M0 title: vfkit as an ad-hoc-signed embedded asset, extracted to disk, still valid and runnable by a daemon user depends_on: [M0-HOST-BASELINE] decision: > Measure, with a pinned vfkit release (its upstream signature replaced by, or kept with, an ad-hoc signature carrying com.apple.security.virtualization, done at build time on the Mac or in CI): embedded zstd in the darwin/arm64 binary, extracted by internal/assets to runtime//vfkit via temp file + rename (new inode), mode 0755. Read: `codesign --verify --strict` and `codesign -d --entitlements -` on the extracted file; `xattr -l` (no com.apple.quarantine: our writer never sets it; a quarantined copy of the core binary, as a browser download would make, is also tested to see whether extraction propagates it); `spctl --assess` result; `vfkit --version` run as a dedicated non-admin user from a LaunchDaemon. Also: the sha256 the pipeline pins is of the signed file, and re-signing changes it, so signing happens before pinning. Fallback if an ad-hoc signature is refused for a daemon (Gatekeeper or AMFI kill): sign with a Developer ID certificate and notarize the vfkit asset (the user's question on notarization), recorded. files: ["evidence only"] test_first: "None (measurement)." done_when: "codesign, xattr, spctl and run readings recorded for the extracted vfkit; signing step order recorded." risks: ["Never rewrite an extracted asset in place (Apple: in-place replacement risks a code-signing kill); a re-extraction is always a new file plus rename."] status: done evidence: [] - id: M0-SCRATCH-BACKEND milestone: M0 title: A scratch-branch vfkit backend behind the landed VM host seam, renting one arm64 guest through the RFP flow depends_on: [M0-VFKIT-ASSET] decision: > The live spikes need a guest, and a guest is only ever born through the RFP flow, so this item builds the smallest darwin backend that lets `socialweb-computer run bidder` on this Mac win an RFP and boot the guest from its cloud-init user_data. Scratch branch only; M1 items rebuild it test-first. Content: an arm64 kernel built on Linux as an uncompressed Image with the virtio drivers built in (virtio-blk, virtio-net, virtio-console, vsock, ext4), the initramfs and an arm64 rootfs from the Linux image pipeline (raw ext4 images, which vfkit's virtio-blk takes directly), the cidata seed as a raw ext4 labelled cidata (the nodeboot shape) written in pure Go; vfkit started as a launchd job in the system domain with UserName set (bootstrapped with sudo in this scratch item only; the real domain is M0-LAUNCHD-DOMAIN's decision), --restful-uri on a unix socket in the work dir, virtio-serial to a log file, VZ NAT networking. Driven by runComputeContract against the market network or, if the market network does not yet run on macOS, the canonical harness shape of ts/atproto-market/test/bidder_container_integration_test.ts with the bidder pointed at its dispatcher. The guest reaches onNetwork; zero ssh. files: ["scratch branch only: internal/vmhost (vfkit scratch backend), the arm64 kernel config, the raw disk writer; evidence names the branch and SHA"] test_first: > The live rent itself: onNetwork fires within the boot timeout; the virtio-serial log shows cloud-init finishing every module; zero ssh. Recorded red first (e.g. a compressed Image hangs, per vfkit's doc) and then green. done_when: "One guest rented through the RFP flow on this Mac reached onNetwork and was released with vm.delete 200; boot time and console log recorded." risks: [] status: todo evidence: [] - id: M0-VZ-LIFETIME milestone: M0 title: What owns a VZ VM, and what happens to it when that owner dies depends_on: [M0-SCRATCH-BACKEND] decision: > With an RFP-born guest: record the process tree (ps -axo pid,ppid,pgid,user,comm) and find which process holds the guest's memory (vfkit, or a com.apple.Virtualization.VirtualMachine XPC service process, and that process's parent). Then SIGKILL the bidder (the guest must survive: vfkit is a separate launchd job), then SIGKILL vfkit (the guest must die: onNetwork heartbeat stops, the XPC process exits, the REST socket refuses). Decision recorded: the launchd job's program is vfkit and nothing else; the bidder never parents a VMM. If the VM survives vfkit's death (the XPC process lives on), the Verdict needs that process's identity too, recorded and taken into M1-VMHOST-VZ. files: ["evidence only"] test_first: "None (measurement): process tree before and after each kill, guest heartbeat timeline." done_when: "Both kills recorded with outcomes; the memory-owning process named." risks: [] status: todo evidence: [] - id: M0-LAUNCHD-DOMAIN milestone: M0 title: Which launchd domain holds VM jobs, submitted by whom, with no one logged in and across logout depends_on: [M0-VZ-LIFETIME] decision: > The bidder runs as a non-admin user _socialweb. Measure each candidate with RFP-born guests, the Mac rebooted and NO user logged in at the console (and FileVault off for this measurement, or unlocked by fdesetup authrestart): (a) system domain, jobs with UserName _socialweb, submitted by a small root LaunchDaemon broker (socialweb-computer internal launchd-broker) that accepts job specs from _socialweb over a unix socket; (b) the user/ background domain of _socialweb, submitted by the bidder itself with no root: does `launchctl bootstrap user/` work for a uid with no login, does VZ run there, does the domain persist; (c) an install-time pool of N system-domain plists whose program is vfkit; vfkit's argv carries every per-VM path and size, so a slot would need its argv rewritten per VM, which is a submission by root again; recorded as infeasible unless (a) and (b) both fail; (d) the gui/ domain of an auto-login _socialweb (needs FileVault off; violates headless; last resort). For each: VZ starts the VM in that context (the DTS caution concerns macOS guests; this measures Linux guests), the VM survives SIGKILL of the bidder, survives a console user logging in and out, and the bidder can inspect and stop it. Preferred: (b) if it works (no root code at runtime), else (a). The chosen domain is asked of the user (questions_for_user) before M1-LAUNCHD-BROKER is built. files: ["evidence only (domain x property table)"] test_first: "None (measurement): the domain x {starts with no login, survives bidder kill, survives logout, inspectable, stoppable} table with UTC times." done_when: "Table complete for (a), (b) and (d); (c) recorded with its reason; preferred domain written into M1-LAUNCHD-BROKER and M1-HOSTUNIT-LAUNCHD." risks: - "If VZ refuses to start in every non-GUI context, the plan blocks at M0-GATE: the only path left is (d), which needs the user to accept an auto-login account and FileVault off." status: todo evidence: [] - id: M0-REBOOT-LOST-POWER milestone: M0 title: Reboot = Lost by construction, sleep never happens, and what FileVault does to a reboot depends_on: [M0-LAUNCHD-DOMAIN] decision: > Measure: VM job plists written outside /Library/LaunchDaemons (under the state dir) with RunAtLoad true, KeepAlive false and LaunchOnlyOnce true, bootstrapped by path; after `sudo reboot` the job is absent (launchctl print fails) and no vfkit runs, while the bidder's own LaunchDaemon (in /Library/LaunchDaemons) is back. A vfkit crash (SIGKILL) is not restarted by launchd (KeepAlive false), so a dead VM stays dead and reads Lost. Sleep: with pmset -a sleep 0 (and disablesleep 1 on a portable) set at install, an idle Mac with a running guest does not sleep over 2 hours (pmset -g log); compare with a PreventSystemSleep power assertion held by the bidder (dies with the bidder; recorded as the weaker option). FileVault on: after a reboot, record whether LaunchDaemons start before a console unlock (expected: not until unlock on Apple Silicon), and `fdesetup authrestart` for planned reboots. Fallback if a bootstrapped-by-path job survives reboot: delete stale plists at bidder start before re-population, which reads them as Lost. files: ["evidence only"] test_first: "None (measurement)." done_when: "Reboot, vfkit-crash, sleep and FileVault readings recorded." risks: [] status: todo evidence: [] - id: M0-PROC-IDENTITY milestone: M0 title: Process identity and the Verdict on darwin, CGO_ENABLED=0 depends_on: [M0-SCRATCH-BACKEND] decision: > Measure: unix.SysctlKinfoProc("kern.proc.pid", pid).Proc.P_starttime for vfkit, stable across reads and different for a reused pid; launchctl print /