10 KiB
P4 — Launch integrity: D11 + D12 + clean-install onboarding (2026-06-11)
Scope (ratified): D11 (WAGGLE_DATA_DIR + startup log), D12 (stale tracked sidecar bundle — release-stopping), the S4 founder flag (clean-install onboarding skip), FREE→Upgrade e2e re-run.
D11 — dataDir split-brain closed
startService ignored WAGGLE_DATA_DIR while local/index.ts config, the marketplace installer,
and memory-mcp honored it — a custom install split state across two directories. Now:
resolveDataDir() = option > WAGGLE_DATA_DIR > ~/.waggle (exported, unit-pinned), plus the
ratified ONE startup line — Data dir: <resolved> · tier: <effective> — via readTierFromDataDir
(extracted from the request-scoped reader; same config.json + getEffectiveTier contract as
GET /api/tier). Stale "default to SOLO" comments fixed in assert-tier.ts and settings.ts (the
canonical vocabulary is TRIAL/FREE/PRO/TEAMS/ENTERPRISE; legacy solo→FREE, basic→PRO via
LEGACY_TIER_MAP, verified in tiers.ts before asserting it in a comment).
Live-verified: sidecar booted with WAGGLE_DATA_DIR=<tmp> and NO option — log line printed the
tmp dir + tier: FREE; install landed in the env-pointed dir.
D12 — the bundle is generated, never tracked
app/src-tauri/resources/service.js (esbuild output of service.ts) was COMMITTED — last refreshed
2026-04-30, six weeks behind the server. Every official build path (app npm scripts, both CI
workflows) already regenerates it, so the tracked copy only ever shipped through a raw
npx tauri build — exactly the stale-binary class the ruling names. Fix per the ratified
preference (build-time generation + untracking, stronger than the minimum CI hash gate):
service.js+service.js.mapuntracked + gitignored (joining node.exe/native/ under the existing "generated at build time" section).tauri.conf.jsonbeforeBuildCommandregenerates the SIDECAR BUNDLE (+ vite build) — a rawnpx tauri buildproduces a current server instead of a stale (or, post-untracking, missing) one. cwd=app/ on every invocation path (npm scripts, CIcd app && npx @tauri-apps/cli@2 build). Scope deliberately excludes bundle-native-deps/bundle-node (review HIGH): those scripts are arch-parameterized viaTARGET_ARCH, which the hook env doesn't carry — in the cross-arch macOS release matrix (arm64 runner,--target x86_64-apple-darwin) the hook'sprocess.archfallback would have CLOBBERED the x64-staged Node runtime + native libs with arm64 binaries, shipping a non-launching Intel DMG with green CI. build-sidecar emits portable JS — safe arch-blind. The arch-parameterized staging stays solely in the explicit npm/CI steps that set TARGET_ARCH.- Stale-dist closure (review MED, verified by metafile probe):
@waggle/sharedand@waggle/hive-mind-coreexport only gitignoreddist/(core/agent/server export src), so the regenerated bundle silently embedded whatever dist was last compiled — the same stale-server class, one layer down. build-sidecar.mjs now ALIASES both packages tosrc/index.ts(no subpath imports exist — verified), so the bundle always compiles from source; clean checkouts build withoutbuild:packages. Verified: post-alias bundle has 59 src files, 0 dist files for the two. - Dev mode unaffected:
service.rsdebug branch spawnsservice.tsvia tsx directly. - Pinned in tauri-config.test.ts (beforeBuildCommand contains build-sidecar; gitignore covers both files). Bundle regenerated locally post-D11 + post-alias to prove the script runs clean (6.5 MB).
Clean-install onboarding skip — CONFIRMED, fixed
S4 flagged it as suspected; P4 confirmed by code: buildLocalServer calls
wsManager.ensureDefault() unconditionally at boot (index.ts:322), and useOnboarding's
returning-user auto-complete fired on getWorkspaces().length > 0 — so every clean production
install auto-completed the wizard before first render. A brand-new user never saw onboarding.
(The Tauri first-launch flag never gated this effect; web/dev had no flag at all.)
Fix — server-authoritative status: new routes/onboarding.ts:
GET /api/onboarding/status→ completed if the flag<dataDir>/first-launch.flagexists (the SAME file the Taurimark_first_launch_completecommand writes on default installs), OR legacy evidence for pre-flag installs: any frame in the personal mind, or >1 workspaces (the seeded default alone is NOT evidence). No flag + no evidence →completed:false→ wizard runs (fail toward showing onboarding, same direction as the Rust command's IO-error fallback).POST /api/onboarding/complete→ idempotent flag stamp; fired (fire-and-forget) fromuseOnboarding.update()whenever completion flips, alongside the Tauri IPC flag.- The FE auto-complete effect now keys on this status;
?forceWizardDEV bypass unchanged; adapter failure keeps the wizard (a truly new user can still set up).
Live-verified on a clean dataDir: seeded default workspace present (count=1) and
{"completed":false,"source":"none"} — the wizard renders for new users.
Accepted edge (documented, not fixed): a legacy returning user with a fresh webview AND zero frames AND only the default workspace (onboarded historically but never used memory) re-sees the wizard once — no durable signal distinguishes them from a clean install; the flag stamps on their re-completion and the class dies out.
FREE→Upgrade e2e re-run
Act 4 "Tier Wall" (user-behavior.spec.ts) against a fresh FREE-tier sidecar: first run 3/6 failed
on 401s — the API-level spec predates the P1b bearer gate (the documented dock-era e2e advisory
band, not a product regression). Re-run under WAGGLE_TRUST_LOCALHOST=1 (the sanctioned local
mode): 6/6 pass — gated 403 carries what's-missing detail, upgrade URL valid, tier ladder
coherent, FREE retains meaningful endpoints, Stripe checkout URL shape valid, cost dashboard
teases value at FREE. Spec-side bearer wiring stays ledgered with the P7 e2e band.
Review — 5-dimension adversarial workflow (run split across a session-limit reset)
First pass: 6 confirmed / 18 refuted, with the onboarding-logic finder + 17 verifiers killed by the session limit (resumed after reset; final tally in the P4 commit message). Dispositions:
| Sev | Finding | Disposition |
|---|---|---|
| HIGH (×2 dims) | beforeBuildCommand re-ran arch-parameterized bundle scripts without TARGET_ARCH — cross-arch macOS release legs clobbered x64 staging with arm64 (non-launching Intel DMG, green CI) | FIXED: hook trimmed to build-sidecar (arch-independent) + vite build |
| MED | Regenerated bundle silently embedded stale @waggle/shared + hive-mind-core dist (verified empirically) | FIXED: esbuild alias → src/index.ts; 0 dist files in bundle post-fix |
| LOW (×2 dims) | Double-run of sidecar/native/node/vite on every scripted build path | FIXED by the HIGH trim (hook now adds only build-sidecar+vite; build-sidecar double-run costs ~2s) |
| LOW | Tauri fs-flag fast-path hardcodes ~/.waggle while the server flag lives in — divergence reachable on custom-WAGGLE_DATA_DIR desktop installs (flag says returning, dataDir un-onboarded) | LEDGERED (P7): requires the Rust shell to learn the dataDir; default installs (the shipped path) share one file. Server status remains the primary signal |
Resumed run (post-limit-reset, against the already-fixed code): 13 confirmed / 16 refuted. The fresh onboarding-logic finder verified the full pre-completion-evidence chain and two more clusters; all fixed:
| Sev | Finding (clustered) | Disposition |
|---|---|---|
| MED ×4 | Wizard/system-origin evidence pollution — step-1 PUT /api/profile writes a 'User identity:' frame pre-completion (verified to the exact line), C33 import frames, workspace-create, and even the monthly-assessment CRON frame all count as "returning user"; mid-wizard abandon + fresh WebView profile (or Back-nav to step 0 + reload) silently skips the wizard |
FIXED: server-durable PENDING latch — the first status call that finds no flag + no evidence stamps <dataDir>/onboarding-pending.flag; thereafter only explicit completion flips the status. Kills every evidence producer at once. Pinned with the exact verified attack chain (identity frame after latch → still completed:false) + a real-returning-user no-latch pin |
| MED ×5 (4 dims) | Tauri fs-flag divergence — flag_path() hardcoded ~/.waggle while D11 makes WAGGLE_DATA_DIR official; a stale home flag auto-skips onboarding against a brand-new custom dataDir (the fast-path bypasses the server status entirely) |
FIXED in Rust: flag_path() honors non-empty WAGGLE_DATA_DIR first (same resolution order as resolveDataDir), so the IPC flag and the server stamp are the same file on EVERY install shape. Sequential cargo test pins the order incl. the empty-env fallthrough |
| MED | D12 raw-path overclaim — after the arch-safety trim, a raw npx tauri build could silently package a binary with NO Node runtime (the resources glob tolerates absent files) |
FIXED: check-sidecar-resources.mjs preflight in beforeBuildCommand — missing runtime/native artifacts now FAIL the build with instructions, never ship silently |
| LOW | D11 startup log line unpinned (deleting it kept gates green) | FIXED: console spy in the D11 integration test asserts the line + dataDir + tier |
| LOW | onboardingRoutes registration on the real server unpinned | FIXED: source-pin (import + register) in onboarding-status.test.ts |
| LOW | Production spawn seam (service.rs → bundled service.js) is smoke-only territory | LEDGERED: binary-smoke class, P7/D15 grid |
Self-triaged from the first run's unverified pile (fixed regardless — trivially real):
resolveDataDirenv leg used??— a set-but-EMPTYWAGGLE_DATA_DIRyielded''. Now||(pinned).- Mid-wizard auto-complete ejection (the question the dead onboarding-logic finder was asked):
a C33 import during the wizard writes personal-mind frames = legacy evidence; a mid-wizard
refresh would auto-complete the wizard out from under the user. Guard added:
step > 0never auto-completes (pinned). - D11 re-homing note: an install that previously had implicit
~/.waggledata while the launcher setWAGGLE_DATA_DIRelsewhere now follows the env var — that is the ratified D11 behavior (the split-brain closing), recorded here as an intentional flip.
Gates
FE 878/878 · server suite + tsc + lint recorded in the P4 commit message · D11 8 pins · onboarding 5 server + 4 FE pins · D12 pin · live: D11 boot, clean-install status, Act 4 6/6.