Files
waggle-os/docs/ux-refactor/p4-launch-integrity-record.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

125 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.map` untracked + gitignored (joining node.exe/native/ under the
existing "generated at build time" section).
- `tauri.conf.json` `beforeBuildCommand` regenerates the SIDECAR BUNDLE (+ vite build) — a raw
`npx tauri build` produces a current server instead of a stale (or, post-untracking, missing)
one. cwd=app/ on every invocation path (npm scripts, CI `cd app && npx @tauri-apps/cli@2 build`).
**Scope deliberately excludes bundle-native-deps/bundle-node (review HIGH):** those scripts are
arch-parameterized via `TARGET_ARCH`, which the hook env doesn't carry — in the cross-arch macOS
release matrix (arm64 runner, `--target x86_64-apple-darwin`) the hook's `process.arch` fallback
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/shared` and
`@waggle/hive-mind-core` export only gitignored `dist/` (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 to `src/index.ts` (no subpath
imports exist — verified), so the bundle always compiles from source; clean checkouts build
without `build:packages`. Verified: post-alias bundle has 59 src files, 0 dist files for the two.
- Dev mode unaffected: `service.rs` debug branch spawns `service.ts` via 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.flag` exists (the
SAME file the Tauri `mark_first_launch_complete` command 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) from
`useOnboarding.update()` whenever completion flips, alongside the Tauri IPC flag.
- The FE auto-complete effect now keys on this status; `?forceWizard` DEV 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 <dataDir> — 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):
- `resolveDataDir` env leg used `??` — a set-but-EMPTY `WAGGLE_DATA_DIR` yielded `''`. 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 > 0` never
auto-completes (pinned).
- D11 re-homing note: an install that previously had implicit `~/.waggle` data while the
launcher set `WAGGLE_DATA_DIR` elsewhere 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.