Skip to content

Latest commit

 

History

History
642 lines (581 loc) · 34.4 KB

File metadata and controls

642 lines (581 loc) · 34.4 KB

Launch Readiness Checklist

Context. Implementation companion to docs/launch-strategy.md. This file is the concrete, actionable backlog for the 2-week pre-launch sprint.

Status. Pending kickoff. Each item has an effort estimate and a blocker/blocked-by relationship. Total: ~80 hours of focused work.

How to read this

  • Items marked [BLOCKER] must ship before launch.
  • Items marked [STRONG] materially improve launch outcomes; ship if time allows.
  • Items marked [OPTIONAL] are 1-day improvements with diminishing returns.
  • Effort is in person-hours assuming a single developer with Claude assistance.

Track A — Public surface (the things strangers see first)

A1. Demo URL — demo.keren-analytics.dev or equivalent [BLOCKER, 6h]

  • Stand up a public hosted instance using mock mode (no Azure auth needed).
  • Pin a deterministic mock dataset that tells a complete product story (visitors trending up, one anomaly, one slow endpoint, readiness score 68).
  • Subdomain on a domain we control. Not a Render-generated URL (looks amateur).
  • Cloudflare in front for caching + DDoS during launch spike.
  • Health check + uptime monitoring (free tier of UptimeRobot or BetterStack).

A2. Root README rewrite [BLOCKER, 6h] — DONE (asset placeholders await A3 / A1)

The current README is informative but reads like docs, not like a pitch. Rewrite around this structure:

  1. One-line tagline that includes "Azure" + "AI" + a number. Example: "Turn Azure App Insights into shareable Marketing & Technical dashboards in under 2 minutes — AI-powered, MIT-licensed."
  2. Hero GIF (see A3).
  3. Why it exists (3 lines max — the Azure portal pain).
  4. Try it now — single docker run … line + demo URL.
  5. What's inside — bulleted feature list with one screenshot per group.
  6. Comparison table (vs Azure portal, vs Datadog, vs Power BI).
  7. Privacy & security (the trust paragraph: no raw data leaves tenant, here's the auditable code path).
  8. Roadmap — link to docs/backlog/.
  9. Contributing + license + ⭐ ask.

The first screen of the README determines 70% of stargazers vs. bouncers.

  • Shipped: README rewritten end to end. Tagline as suggested ("Turn Azure App Insights into shareable Marketing & Technical dashboards in under 2 minutes — AI-mapped schema, deterministic KQL, nothing raw ever leaves your tenant. MIT."). All 9 sections in order; the "What's inside" list highlights the just-shipped B1-B4 surfaces (alias/regex schema mapping, narration panel, first-run banner, period-over-period chips) so a HN visitor can see the product is real, not slideware. Comparison table includes Azure Portal / Datadog / Power BI with a one-line honesty disclaimer underneath ("the columns we're least kind to are also the most mature and have features we don't"). Privacy & security paragraph links the SECURITY.md auditable controls and the seven encoded checks. Roadmap links the per-track backlog and the launch-strategy traction-gate doc. The old "API endpoints" + "Env variables" wall-of-text trimmed to a short Configuration reference pointing at src/server.js for the full surface.
  • Asset placeholders: the hero GIF block is an HTML comment pointing at docs/assets/hero.gif — needs A3. The "What's inside" bullet list has a sibling HTML comment marking where one screenshot per group would let it breathe. The "Live demo · coming with the public launch" line waits on A1 for the real URL. All three are now tracked in docs/maintainer-todo.md as discrete TODOs so the maintainer can swap them in without spelunking.
  • Validation: README renders cleanly (Markdown only, no broken links to the existing tracked files). Tests still 74/74 — README changes are pure-doc and don't touch any code path.

A3. Hero GIF / Loom recording [BLOCKER, 4h]

  • 30-45 second screen recording: connect → dashboard renders → switch tabs → see readiness score → copy a prompt.
  • Use the demo dataset from A1 so it always looks the same.
  • Annotate with overlay text ("2 minutes from zero to dashboard", "AI maps your custom dimensions automatically", "no raw data leaves your tenant").
  • Compress with Gifski or upload as MP4 to GitHub Issues for inline embed.

A4. OG image / social unfurl [BLOCKER, 3h]

  • 1200×630 image with logo + tagline + dashboard preview.
  • Set OG meta on root URL, demo URL, and GitHub repo (description + topics).
  • Test with the OG debuggers (Twitter card validator, LinkedIn post inspector, Slack).
  • Every share link must visually look like a real product, not a GitHub fallback card.

A5. One-page landing on the demo URL [STRONG, 5h] — DONE (hero shot + Plausible await maintainer)

  • Above the fold: tagline + screenshot + two CTAs ("Try the demo", "Star on GitHub").
  • Below: comparison table, security paragraph, FAQ (3 questions: "does it store my data?", "how do you connect to Azure?", "is the AI required?").
  • Footer: GitHub link, license, contact email.
  • No tracking beyond Plausible/Umami self-hosted (privacy-aligned with product positioning).
  • Shipped: public/index.html #landingPage rewritten with the spec's structure end to end. Tagline now matches the README ("Azure App Insights → Marketing & Technical dashboards in 2 minutes. AI-mapped schema. 22 KQL templates. Nothing raw ever leaves your tenant. MIT."). CTAs reordered so the demo button is primary, Connect-Azure is ghost, and a third button links the repo with a ★ icon. Below-the-fold sections, in order: feature cards (lightly updated copy to mention period-over-period chips and the paste-into-Cursor framing), comparison table (Keren Analytics vs Azure Portal / Datadog / Power BI; 6 rows; honesty disclaimer underneath), security paragraph (links the seven encoded controls
    • the SECURITY.md reporting path), 3-question FAQ (data storage / Azure connection / AI required), docs link, and a footer with GitHub / MIT License / Security / Contact. All sections styled in public/styles.css using the existing accent palette so the page works in both light and dark mode without a second pass.
  • No new tracking: the landing footer copy promises "no tracking cookies; the only analytics is whatever Plausible / Umami snippet the operator pastes in." An HTML-comment slot at the bottom of #landingPage marks the exact insertion point so the snippet doesn't leak into the dashboard. Tracked in docs/maintainer-todo.md under §3 Plausible / Umami.
  • Tests: new supertest case in tests/api.test.js asserts the landing copy is wired (tagline, CTAs, comparison table headers, FAQ questions, footer email). 74 → 75 tests; security audit clean.
  • Asset placeholders (maintainer-side): the hero screenshot above the fold is an HTML-comment block expecting <img class="landing -hero-shot">; the Plausible/Umami slot is a similar comment near the bottom. Both land in docs/maintainer-todo.md.

A6. docs/setup-entra-id.md slim version [BLOCKER, 4h] — DONE

  • Today: 13 manual portal steps. Hard wall for non-IT users.
  • Goal: 3-5 steps OR one Bicep one-click.
  • Ship a deploy/azure-app-registration.bicep that creates the app registration, sets redirect URI, generates secret, optionally assigns Reader + Log Analytics Reader roles.
  • README install path becomes:
    az deployment sub create -f deploy/azure-app-registration.bicep \
      -p redirectUri=...
    docker run -p 3000:3000 -e AZURE_CLIENT_ID=... keren-analytics
  • Keep the long manual guide as a fallback section.
  • Shipped: went with a single bash script (deploy/azure-app-registration.sh) rather than Bicep — the Microsoft.Graph Bicep extension can't surface a client-secret value, so a pure-IaC path would still need an out-of-band step. The script is idempotent (re-runs reuse the app and append a fresh secret), GNU/BSD-portable, and prints the exact env vars to paste. README install path now reads az login → run script → docker compose up -d. docs/setup-entra-id.md keeps the manual portal flow as a fallback for tenants where CLI app-registration is restricted.

Track B — Product polish (what they see after they install)

B1. Layer 1 of ai-environment-analysis.md (alias heuristics) [BLOCKER, 12h] — DONE

  • Implement the alias table + regex patterns for userId / sessionId / pagePath / referrer in src/core/mapping.js.
  • Add matchType field (builtin | alias | pattern) to mapping output.
  • Cross-table consistency bonus.
  • 4-6 unit tests covering the new resolution chain.
  • Why it's a blocker: the current mapping is exact-match-only. Half of HN visitors trying their own tenant will see "no userId mapping" because they used uid or visitorId. That's a launch-killing first impression.
  • Shipped: ALIASES table + regex per canonical field, cross-table consistency boost (confidence: high on 2+ tables), matchType / matchedKey / tablesSeen exposed on each canonical mapping, custom keys sanitized before injection, allowedKqlExpressions(mapping) extends the renderer whitelist with alias-derived exprs. +11 tests.

B2. Layer 2 mock LLM narration on demo [STRONG, 8h] — DONE

  • On the demo URL only (mock mode), surface a "What we found" panel that reads like the LLM output described in ai-environment-analysis.md.
  • Canned response, no actual LLM call. Cost: 0 €.
  • Tagline appears in screenshots and the hero GIF: "AI explains what your telemetry looks like."
  • Real LLM integration ships post-launch (ai-setup-wizard.md proper).
  • Shipped: src/core/narration.js — deterministic generator that composes a 3-4 sentence "Environment analysis" paragraph from the dashboard payload (visitors, sessions, top campaign source, peak hour, error-rate band, userId mapping type). Wired into buildOverviewDashboard so the payload now carries narration: { headline, paragraph, badge, tagline, mode }. Frontend renders it in a new panel above the KPIs on the Marketing tab (public/index.html#narrationPanel, public/app.jsrenderNarration, public/styles.css.narration-*).
  • Honesty tweak vs original spec: rather than a fully canned string, the same generator runs in both modes — mock mode shows it without badge, real mode shows it with a "Preview — real LLM coming soon" badge. The numbers come from the dashboard the user already sees, so nothing is invented; the "AI explains" tagline is honest because the panel does interpret the data, just deterministically. When Azure OpenAI integration ships post-launch, the same payload shape is what the frontend consumes — only the paragraph gets richer.
  • Tests: 10 unit tests in tests/narration.test.js (mode toggle, badge presence, KPI presence in paragraph, peak-hour formatting, error-rate threshold branch, mapping-type branches, empty-data fallback, no template-token leakage, length bounds). Plus the api integration test asserts dashboard.narration.mode === "mock" end to end. 56 → 66 tests.
  • Maintainer-side: screenshots for the launch should include the Marketing tab with this panel visible (above the fold). Tracked in docs/maintainer-todo.md under the press-kit / hero-GIF items.

B3. First-run banner on dashboard [STRONG, 6h] — DONE

  • Compose a deterministic banner from existing readiness + mapping data: "Your environment scores 68/100. Two quick wins available: [Add user identity (+15)] [Capture browser timings (+8)]".
  • Click on a quick win → scroll to the matching prompt card.
  • No LLM needed for v1; the AI version is post-launch.
  • Shipped: server-side, computeReadinessScore now returns a quickWins array (top 3 unavailable signals by points; src/core/readinessScore.js). Frontend renders an <aside id="firstRunBanner"> at the top of #dashboardPanel — visible across all three tabs because it sits above the tab-toolbar. Layout: a left-side score chip (68/100 in accent color), a title ("Your environment scores 68/100. 2 quick wins available:"), and pill-shaped buttons for each quick win (User Identity (+15)). Clicking a chip switches to the Readiness tab via the existing activateTab("readiness") helper, scrolls smooth-into-view to #signal-row-${signal} (each score-row wrapper now carries that stable id), and fires a 1.6s background flash to draw the eye.
  • Dismiss + persistence: × button writes eaa.firstRunBanner.dismissed.v1=1 to localStorage. The banner also auto-hides when there are no quick wins (perfect score) or when readinessScore is null/empty. No DB needed (consistent with the in-memory metadataStore).
  • Tests: +3 unit tests in tests/readinessScore.test.js (quickWins shape, perfect-score returns empty, null-report returns empty). 66 → 69 tests, audit clean. UI verification (banner visible
    • chip click + dismiss persistence) is the maintainer's eyeball pass — docs/maintainer-todo.md already has the "open dev server and check the dashboard" item.

B4. Period-over-period comparison (top 3 KPI tiles only) [STRONG, 10h] — DONE

  • Limited scope: only the 3 most prominent KPI tiles get a delta vs. previous period.
  • KQL templates accept a compareTo: previous parameter.
  • UI: small green/red delta chip + "vs last week" caption.
  • Full comparison + deployment markers ship post-launch.
  • Shipped server side: new previousTimeRange(timeRange) helper in core/timeRange.js maps the launch ranges to their predecessors (today → yesterday, 7d → prev7d, 30d → prev30d) and null for anything else (custom range hides the chips). New kql/previous-kpis.kql template runs a single summarize over the prior window returning all three KPIs (uniqueVisitors, sessions, pageViews = count()) — keeps query count to +1 instead of +3. core/dashboard.js runs that query when a predecessor exists, then deltaEntry() builds { current, previous, deltaPct, direction } per KPI with a 0.5% neutral band and an explicit null deltaPct when previous is zero (so a first-time tenant doesn't see misleading +∞%). Final payload: dashboard.kpis.comparison = { previousRangeKey, label, uniqueVisitors, sessions, pageViews } or null. Cache key uses the prev range key, so previous results live in their own cache slot.
  • Shipped UI: the 3 KPI tiles on the Marketing tab gain a .kpi-meta-row with a colored .kpi-delta pill (+13.6% green up, -x% red down, ~0% neutral grey) and a .kpi-compare caption ("vs last week" / "vs yesterday" / "vs last month"). Hidden when comparison is null. No new dependency.
  • Mock data: src/azure/mockData.js gains RANGE_SCALE entries for yesterday: 0.06, prev7d: 0.22, prev30d: 0.88 (slightly lower than current so the demo screenshots show the screenshot- friendly green positive deltas) plus a previousKpis query handler.
  • Tests: new tests/timeRange.test.js (5 cases — predecessor key, window length, today→yesterday, custom/null/unknown returns null, comparisonLabel mapping). tests/api.test.js end-to-end asserts dashboard.kpis.comparison.previousRangeKey === "prev7d" with all three KPIs and a valid direction. 69 → 74 tests, audit clean.
  • Out of scope (post-launch, per spec): the deployment-markers layer and full per-chart comparisons stay deferred.

B5. "Copy share image" button [OPTIONAL, 4h]

  • Server-side render a PNG of the current dashboard view using the existing state. Stores nothing.
  • One button on every dashboard tab.
  • HN demographic loves "screenshot for Slack" workflows. Cheap viral lever.

B6. Demo dataset polish [BLOCKER, 3h] — DONE

  • Audit src/azure/mockData.js: every chart on every tab must have a visually interesting story.
  • Ensure: visible weekly seasonality, one anomaly day, one slow endpoint, meaningful geo distribution, varied browser mix.
  • The demo is also the launch screenshot source — boring data = boring launch.
  • Shipped: visitor traffic spike 4 days ago (×2.4, deterministic so it lands in both 7d and 30d windows, marked anomaly: "traffic_spike"), /api/checkout is the unmistakable slow + error outlier (p99 ~6s, 8.2% error rate), 12-country geo distribution with Spain/Italy added, browser mix expanded to 6 entries (Mobile Safari + Samsung Internet) and OS to 5 (Android added), KPI sparklines have a clearer upward trend on visitors/sessions and a subtle improving-perf trend on response time alongside the existing trailing error spike, lunch dip on hourly trend.

Track C — Trust signals (security + project quality)

C1. Pre-launch security pass [BLOCKER, 4h] — DONE

  • Verify no token / code_verifier / session secret is logged anywhere (grep + manual review of src/server.js, src/azure/realClient.js, src/azure/tokenStore.js).
  • Run npm audit and address criticals.
  • Add a SECURITY.md with reporting policy.
  • CSP review: minimize unsafe-inline, justify CDN allowlist.
  • Make the "no raw data leaves your tenant" claim provable: link to the exact lines in the code from the README/landing FAQ.
  • Shipped: turned the audit into a repeatable check (scripts/security-audit.mjs, npm run audit:security) with 7 encoded controls (sensitive logging, session hardening, CSP, raw telemetry persistence, committed env files, npm audit). GitHub Actions workflow runs it on push/PR plus a Monday cron, so newly disclosed transitive vulnerabilities turn the badge red even without commits. README gains the green badge; SECURITY.md documents the reporting policy, the audited posture, and the two legitimate fs sinks (core/audit.js metadata events, core/metadataStore.js setup-state JSON — no raw rows). One real finding fixed in flight: bumped transitive path-to-regexp 8.3.0 → 8.4.2 and qs 6.14.1 → 6.15.1 via npm audit fix (no breaking changes, tests still 43/43).

C2. GitHub repo polish [BLOCKER, 3h] — DONE (file-tracked items; Settings-side items remain manual)

  • About / topics / website URL / description filled in.
  • Issue templates (bug, feature request, question) in .github/.
  • CONTRIBUTING.md (short — link to docs).
  • CODE_OF_CONDUCT.md (Contributor Covenant).
  • SECURITY.md (pointer to email).
  • Pin the v0.1.0 release with proper notes.
  • Add badges: license, CI status (if CI is set up — see C3), Docker pull count.
  • Shipped: LICENSE (MIT, copyright Lionel Garnier and contributors) with package.json aligned ("license": "MIT", author populated). CONTRIBUTING.md is short and points at CLAUDE.md for invariants and at the per-track backlog for what's deliberately deferred. CODE_OF_CONDUCT.md adopts Contributor Covenant 2.1 by URL reference (avoids inlining the canonical text; reporting goes to the same email as SECURITY.md). .github/ISSUE_TEMPLATE/ ships three structured forms (bug / feature / question) plus config.yml that disables blank issues and surfaces the security reporting path. A short .github/PULL_REQUEST_TEMPLATE.md reminds contributors to read CLAUDE.md and run npm test + npm run audit:security. README gains license + Node-version badges next to the existing security badge. SECURITY.md updated with the real contact email and the closed-as-fixed gaps removed (SESSION_SECRET fail-loud + rate limiting). One adjacent fix carried in flight: src/config.js now throws when NODE_ENV=production and SESSION_SECRET is missing or a known placeholder, with 5 new tests in tests/config.test.js.
  • Deferred (Settings-side, not file-tracked): About / topics / website URL / description on the GitHub repo page; pinning the v0.1.0 release. These need the maintainer to click through GitHub Settings; nothing to commit.
  • Deferred (depends on other tracks): the Docker-pull-count badge needs a published Docker image (Track A6 territory); the CI-status badge needs the npm test workflow from C3.

C3. Minimal CI [STRONG, 3h] — DONE

  • GitHub Actions: run npm install + npm test on push and PR.
  • Status badge in README.
  • Without this, "MIT-licensed" feels less production. Cheap signal.
  • Shipped: .github/workflows/tests.yml runs npm ci + npm test on push and PR to main (also workflow_dispatch for manual reruns) on Node 22 with the npm cache. Status badge added to the README right next to the existing security-audit badge. The redundant npm test step inside security-audit.yml is left in place as a belt-and-braces sanity check (the audit asserts the tests still pass before declaring the security posture green); both workflows are independent so a failing test does not red-badge the audit and vice versa, but a regression that breaks npm test is now surfaced under the unambiguous "Tests" name rather than buried under "Security audit".

C4. CHANGELOG.md [OPTIONAL, 1h] — DONE

  • Document v0.1.0 launch contents.
  • Future commits append.
  • Shipped: CHANGELOG.md follows Keep-a-Changelog 1.1.0 with two sections: [Unreleased] for what's landed since v0.1.0 (the C1-C4 trust track + LICENSE/CoC/templates/CI badge), and [0.1.0] for the initial public release grouped as Added (product), Added (distribution), Security, Documentation, and Tests. Doubles as the v0.1.0 release-notes draft when the maintainer pins the release in GitHub Settings (see docs/maintainer-todo.md §2).

Track D — Distribution prep (the launch ammunition)

D1. Show HN post draft [BLOCKER, 3h]

  • Title format: "Show HN: Keren Analytics — 2-min Azure App Insights dashboards (open source, AI-mapped)".
  • Body: 4-6 paragraphs:
    1. What it is (one paragraph).
    2. Why I built it — concrete pain point with Azure portal.
    3. How the AI angle works (Layer 1 alias + Layer 2 narration).
    4. What's open and what's deliberately left for later.
    5. Tech stack one-liner.
    6. Ask for feedback.
  • Pre-write the first founder-comment that addresses the predictable "but Azure portal already does X" objection.

D2. Reddit posts (r/azure, r/devops, r/selfhosted) [BLOCKER, 2h]

  • Three different angles, one per subreddit. Same product, framed for the audience:
    • r/azure: "I built an OSS alternative to portal analytics — feedback?"
    • r/devops: focus on the readiness score + LLM-suggested instrumentation.
    • r/selfhosted: focus on the Docker one-liner + privacy posture.
  • Avoid "look at my project" framing. Lead with a concrete thing the user cares about.

D3. dev.to / blog post [STRONG, 4h]

  • Long-form (1500-2500 words) on the technical deep dive most likely to catch HN: "Building a natural-language-to-KQL layer for Azure logs (without Azure OpenAI as a hard dep)".
  • Cross-post to dev.to + Hashnode + personal blog.

D4. Outreach list [BLOCKER, 2h]

  • 10-15 named contacts: Microsoft MVPs in Azure data/devops space, MS DevRel folks, well-known Azure newsletter authors, maintainers of Awesome-Azure lists.
  • Personalized 3-line DM template ready to send post-launch (not before).
  • LinkedIn for non-engineers, Twitter/X for engineers, email as fallback.

D5. Launch-day analytics [BLOCKER, 2h]

  • Plausible / Umami self-hosted on the demo URL.
  • Track: source (HN / Reddit / Twitter), conversion to GitHub stars, demo click-through rate, time on page.
  • Real-time dashboard the founder can watch during launch day.

D6. Press kit [OPTIONAL, 2h]

  • One PDF / page with: logo (SVG + PNG), tagline, 280-char pitch, 1500-char pitch, 3 product screenshots, founder photo + bio.
  • Hosted at /press on the demo URL.
  • Send proactively to MVPs / newsletter authors who pick up the post.

Track E — Anti-fragility for launch day

E1. Demo rate limiting [BLOCKER, 3h] — DONE (LLM cap deferred with B2)

  • Per-IP rate limit on the demo (60 req/min hard cap).
  • Friendly "high traffic, try in a few minutes" page rather than 502/504.
  • Hard daily cap on LLM cost (when B2 ships): cut over to canned responses past €10/day on the demo.
  • Shipped: src/core/rateLimit.js — minimal in-memory fixed-window per-IP limiter, no new dep (CLAUDE.md mandates a minimal dep set, demo is single-instance). Wired in src/server.js as two named buckets: api (60 req/min, all dynamic routes) and auth (stricter 20 req/min, on /auth/* so OAuth burning can't also DoS the dashboard). Both bypass NODE_ENV=test. Friendly 429 page (HTML or JSON depending on Accept) plus Retry-After, X-RateLimit-Limit, and X-RateLimit-Remaining headers. +8 unit tests.
  • Deferred (with B2): the daily LLM cost cap. Lives in the same file when the mock-LLM narration ships — needs a separate counter keyed by day and a kill-switch flipping the demo to canned responses past the cap. No mock-LLM = nothing to cap yet.

E2. Pre-warm + cache [STRONG, 2h]

  • Pre-cache the mock dashboard for the demo dataset.
  • All static assets behind Cloudflare with long max-age.

E3. Status page [OPTIONAL, 1h]

  • BetterStack free tier or self-hosted.
  • Linked from the demo footer.
  • During an outage, "we're aware" beats silence by a wide margin.

E4. Launch day runbook [STRONG, 1h] — DONE

  • Single doc: how to roll back the demo, how to rate-limit harder, who to contact at Render/Cloudflare, where the LLM kill switch is.
  • Founder reads it the morning of launch. Saves hours under stress.
  • Shipped: docs/launch-day-runbook.md — single page intentionally short and copy-pasteable: T-2h pre-launch checklist (gated on the same "ready to launch" definition this file has), what to watch during the window (Plausible / GitHub stars / Render / demo URL), triage trees for "it's slow / it's down" and "someone found a security issue", how to rate-limit harder (specific edits to src/server.js:83-98), rollback recipe (revert + push, never force-push to main), HN/Reddit moderation playbook, and a contacts section. The LLM kill-switch section is stubbed with a pointer to where it'll live when B2 ships.

Track F — AI-first setup wizard (ADR 0005, pre-launch blocker)

Added 2026-05-11 after ADR 0005. The "AI-mapped schema / AI explains your telemetry" claims were AI-washing — no LLM was wired. This track closes that gap before launch by shipping a real audit + AI mapping + recommendations flow on Azure AI Foundry, persisted in SQLite. The other AI surfaces (ai-natural-language-queries.md, ai-instrumentation-assistant.md) stay post-launch. See docs/adr/0005-ai-first-scope.md.

F1. Persistance SQLite [BLOCKER, 16-24h]

  • Install better-sqlite3 (sync, simple) or use Node 22.5+ node:sqlite native module. ADR 0005 § Decision 2.
  • Schema: tenants, scans, mappings, signals, validations — with per-tenant scope and timestamps. File at data/keren.db.
  • Rewrite src/core/metadataStore.js to use the SQLite schema. Keep the same exported interface (getTenant, updateTenant, logStateTransition) so callers don't change.
  • Migration script: if data/store.json exists at boot, import it into the SQLite schema, then rename to store.json.legacy (don't delete).
  • Backup policy: hourly VACUUM INTO-based snapshot to data/backups/ (cap 24 snapshots). Production-side: Azure Blob upload via Container Apps Jobs cron (separate task, F1 ships the script).
  • Tests: existing 5 metadataStore tests must keep passing; add 3 new tests for migration path + concurrent transactions.

F2. Schema scan enrichi [BLOCKER, 16-24h]

  • Extend src/core/schemaProfile.js → new src/core/schemaScan.js.
  • Capture per-tenant : event volumes by name, top-N customDimensions keys with cardinality + sample values (PII-scrubbed via regex email|phone|ssn), timestamps span, gaps detected (e.g. no userId-like field present, no campaign tracking, no session duration).
  • Persist scan output as a JSON document in scans table, indexed by tenantId + scannedAt. Latest scan is the active one; history kept.
  • Cap on scan KQL: use existing queryTimeoutMs (12s), summary queries only (no take 10000). Total ≤ 5-6 queries per scan.
  • Tests: schema parse roundtrip, PII scrub coverage, gap detection heuristics.

F3. AI mapping + recommendations service [BLOCKER, 24-32h]

  • New src/ai/azureFoundry.js provider implementing the contract in docs/architecture-ai.md. Authenticate via the existing Managed Identity (no API keys). Endpoint + deployment name in env vars (AZURE_FOUNDRY_ENDPOINT, AZURE_FOUNDRY_DEPLOYMENT).
  • Prompt structuré (JSON schema response) that takes a F2 scan as input and returns: mapping_proposals[] (canonical field → tenant column, with confidence + explanation), missing_signals[] (signal name + recommended KQL to instrument), dashboard_recommendations (which charts to show prominently given what's available).
  • Quota guard: in-memory daily counter; hard cap at 10 €/day worth of tokens (computed from gpt-4o-mini pricing); when exceeded, return a deterministic fallback (existing alias/regex mapping + empty recommendations) plus a degraded: true flag the UI surfaces.
  • Cache scan→AI-output in SQLite (so a re-load doesn't re-spend tokens). Invalidate on re-scan.
  • Mode AI_PROVIDER=none must continue to work (deterministic-only output, no LLM call) — tests run in this mode.
  • Bicep update: extend infra/main.bicep with Azure AI Foundry Hub + Project + connection + model deployment + role assignment (Cognitive Services User to the existing MI).
  • Tests: provider contract test, JSON schema validation, fallback trigger on quota exceeded, cache hit/miss.

F4. Setup wizard UI [BLOCKER, 32-40h]

  • New route /setup (or extend existing post-OAuth flow). Multi-step:
    1. Scanning — spinner + live narration ("Reading custom dimensions… Found 47 event types… Detecting user identity…").
    2. AI findings — proposed mappings with confidence badges, missing signals with recommended KQL (copy button), dashboard recommendations.
    3. Validate/edit — user can accept all, override individual mappings, dismiss recommendations. Persists to validations table.
    4. Save & continue — redirects to the dashboard with the validated mapping active.
  • Re-scan button in settings ("Found new event types? Re-scan now").
  • Empty-state handling: tenant with no events yet → wizard explains what to instrument and waits.
  • Tests: end-to-end supertest covering the 4-step flow + override path.

F5. Documentation + AI specs refresh [STRONG, 8-16h]

  • Update docs/backlog/ai-setup-wizard.md status from "post-launch optional" to "pre-launch, see Track F". Add the concrete F1-F4 scope (replace speculative sections).
  • Update docs/backlog/ai-environment-analysis.md similarly — Layer 1 alias is done (B1), Layer 2 LLM ships as part of F3.
  • Update docs/architecture-ai.md status from DRAFT to ACCEPTED, with the azure-foundry provider as the canonical implementation.
  • Remove the Preview — real LLM coming soon badge from narration.js generator output when AI_PROVIDER=azure-foundry and the call succeeds.

Track F dependencies & sequencing

F1 (SQLite) ─┬─→ F2 (scan) ─→ F3 (AI mapping) ─→ F4 (wizard UI)
             │                       ↑
             └───────────────────────┘
                  (cache + persistence)

F5 in parallel once F1-F4 are conceptually clear (~day 3 of Track F).

Maintainer dependency : Azure AI Foundry workspace + gpt-4o-mini deployment must exist before F3 can run end-to-end. See docs/maintainer-todo.md § "Provisionner Azure AI Foundry".

Effort summary

Track Blockers Strong Optional Total
A — Public surface 23h 5h 0h 28h
B — Product polish 15h 24h 4h 43h
C — Trust signals 7h 3h 1h 11h
D — Distribution prep 9h 4h 2h 15h
E — Anti-fragility 3h 3h 1h 7h
F — AI-first scope (added 2026-05-11) 88-120h 8-16h 0h 96-136h
Total blockers (must ship) 145-177h
Total with strong items 192-232h
Total all-in 200-240h

Realistic launch timeline with all blockers + most strong items: ~5-6 weeks at ~40h/week focused. Most of Phase A blockers already shipped (infra, custom domain, CI/CD, GitHub polish, release v0.1.0). Remaining heavy lift is Track F (AI setup wizard, ~15 jours focus) plus the content/asset drafts in Track D and the Hero GIF / OG image in Track A.

Sequencing — recommended order

Week 1 — product + content

  • Day 1-2: B1 (alias heuristics) + B6 (demo dataset polish).
  • Day 3: A6 (Bicep) + C1 (security pass).
  • Day 4: A1 (demo URL stand-up) + A4 (OG image).
  • Day 5: A2 (README rewrite) + A3 (hero GIF).

Week 2 — polish + launch prep

  • Day 6: B2 (mock LLM narration) + B3 (first-run banner).
  • Day 7: B4 (period comparison) + B6 polish round 2.
  • Day 8: A5 (landing page) + C2 (GitHub repo polish) + C3 (CI).
  • Day 9: D1-D5 (all distribution prep).
  • Day 10: Soft test in 1-2 small communities. Fix anything broken.

Day 11 — launch.

Definition of "ready to launch"

All [BLOCKER] items shipped. Three independent strangers can:

  1. Open the demo URL on a phone and understand the product within 30s.
  2. Run the install command on a fresh machine and reach the dashboard.
  3. Read the README and articulate the security posture without asking questions.

If any of these fails on launch eve, delay the launch by one week. A blown launch is much worse than a 1-week delay.

What's deliberately out of scope for launch

These exist in the broader backlog and explicitly do not block launch:

  • Hosted multi-tenant SaaS (Phase 3).
  • Postgres / Redis persistence (Phase 3).
  • Full LLM-powered setup wizard (ai-setup-wizard.md Layer 3+).
  • Natural-language query explorer (ai-natural-language-queries.md).
  • AI instrumentation assistant (ai-instrumentation-assistant.md).
  • Custom event funnels and full conversion tab.
  • Multi-resource aggregation.
  • Goals + weekly digest.
  • AWS / GCP connectors (Phase 4).

These are post-traction features. Building them pre-launch is the single most common way solo founders waste their launch window.