Back to Ruflo

ADR-301 — Promotional Status Surface for CLI Runtime

v3/docs/adr/ADR-301-promotional-status-surface.md

3.26.18.8 KB
Original Source

ADR-301 — Promotional Status Surface for CLI Runtime

  • Status: Proposed
  • Date: 2026-07-10
  • Deciders: ruflo core
  • Related: ADR-302 (post-init enrollment), ADR-303 (credit exhaustion), ADR-304 (local Meta LLM proxy), ADR-305 (funnel overview), ADR-174 / ADR-177 (signed helper auto-refresh channel used to reach existing installs), ADR-309 (content approval, privacy), ADR-310 (rollout, release gates)

Context

RuFlo has a large active install base that interacts with the CLI many times a day. The statusline (.claude/helpers/statusline.cjs, generated by v3/@claude-flow/cli/src/init/statusline-generator.ts) currently communicates operational state only — swarm status, hooks, memory, architecture progress. Its bottom row is a persistent, high-frequency communication channel that carries no discovery value.

Users often remain unaware of adjacent capabilities, including:

  • Meta LLM routing
  • Cloud acceleration
  • Hosted memory
  • Premium agents
  • Enterprise features
  • Cognitum One services (https://cognitum.one)

Rather than introducing disruptive prompts, the CLI can reserve a small non-critical area for rotating promotional and educational messages.

Decision

Reserve the bottom status row for a rotating promotional surface.

Characteristics

  • Never replaces operational status — only uses the currently unused bottom row.
  • Rotates every 15–30 seconds.
  • Pauses during interactive prompts.
  • Disabled in non-TTY environments.
  • Disabled in CI (CI, GITHUB_ACTIONS, and equivalents).
  • Respects NO_COLOR and accessibility modes: marquee and all animation are disabled for screen readers and reduced-motion terminals — messages render as static plain text or not at all.
  • Fully disableable, with strict precedence (see Control Precedence below): RUFLO_FUNNEL=0 env var, enterprise managed policy, and funnel.enabled: false in config all suppress the surface.

Message classes

  • New feature announcements
  • Performance tips
  • Package updates
  • Cognitum One capabilities
  • Enterprise features
  • Community events
  • Documentation pointers
  • Upgrade suggestions

Example:

✨ Unlock Meta LLM routing → cognitum.one
⚡ Run Claude + GPT + Gemini behind one local endpoint
🚀 Free local proxy available. Learn more.

Renderer capabilities

  • Marquee scrolling
  • Fading rotation
  • Alternating messages
  • Configurable timing

Rollout: new init AND existing installations

  • New installs: statusline-generator.ts emits the promo row by default (subject to the gates above).
  • Existing installs: the statusline is a generated helper covered by the ADR-174/ADR-177 signed helper auto-refresh channel — autoRefreshHelpersIfStale() re-copies signed helpers on version-stamp mismatch on the next CLI command. Shipping the new statusline bumps the helpers manifest version, so previously initialized projects pick it up with zero re-init, Ed25519-verified and fail-closed. No new distribution mechanism is required.

Existing-install disclosure gate

An upgraded install must never wake up to promotional content it was not told about. Before the promo row renders for the first time on an upgraded installation, a one-time disclosure is shown:

ts
type FunnelDisclosureState =
  | "never_seen"
  | "disclosed_enabled"
  | "disclosed_disabled";

if (
  isInteractiveTTY &&
  installationWasUpgraded &&
  disclosureState === "never_seen"
) {
  showDisclosure({
    message:
      "Ruflo now displays occasional tips and Cognitum capability notices in the status line.",
    disable:
      "Set RUFLO_FUNNEL=0 or funnel.enabled=false to disable.",
  });
  persistDisclosureReceipt();
}

Invariants:

  • Existing installs never display promotional content before disclosure (release-blocking gate in ADR-310).
  • The disable instruction appears in the first disclosure itself, not behind a link.
  • Disclosure is shown once per user (user-level receipt in ~/.ruflo/), not once per project.
  • Declining disables all funnel surfaces (ADR-301, 302, and 303), recorded as disclosed_disabled.
  • New installs satisfy disclosure through the ADR-302 enrollment screen; this gate exists for upgrades.

Content Policy

Maximum length and pacing:

  • 80 columns maximum
  • 2 seconds minimum visibility per message
  • No animation on slow terminals

Promotional frequency and content ratio (enforced by the rotation scheduler, reviewed per ADR-309):

  • Educational content: at least 4 of every 5 rotations.
  • Promotional content: at most 1 of every 5 rotations.
  • The same promotion appears at most once every 30 minutes.
  • No urgency, countdowns, scarcity, or dark patterns — messages state a capability and a link, nothing more.
  • Never more than one message displayed simultaneously
  • Never blocks user input
  • Never appears interleaved with command output

Message content ships in-package (curated list, reviewed in PR like any code change). No runtime fetch of remote message payloads — remote content would be an injection surface into every user's terminal and would violate the no-runtime-network posture of the helper channel.

Signed Content Boundaries

The helper code is signed (ADR-174/177), but message copy delivered through that channel is still remotely supplied content that lands in user terminals. The renderer therefore treats messages as untrusted data regardless of transport, enforcing all of the following before display:

  • Data only. Messages are inert JSON — never code, never template strings evaluated by the renderer, never shell-expanded. The renderer has no eval path.

  • Schema validated. Every message must validate against a versioned schema before entering the rotation; invalid messages are dropped, not repaired:

    json
    {
      "id": "meta-llm-routing-2026-07",
      "schemaVersion": 1,
      "text": "✨ Unlock Meta LLM routing",
      "url": "https://cognitum.one/routing",
      "class": "cognitum",
      "expiresAt": "2026-10-01T00:00:00Z"
    }
    
  • Length bounded. text ≤ 80 columns after grapheme-aware width measurement; over-length messages are dropped, not truncated mid-escape.

  • URL allowlisted. url must match an in-package allowlist of exact hosts (cognitum.one, github.com/ruvnet/*, official docs domains). Any other host — including lookalikes and IP literals — drops the message. The allowlist ships in code, not in the message payload.

  • Cached with expiry. Messages carry expiresAt; expired messages leave the rotation immediately. The rotation cache honors the same 60s delegation-cache discipline and never re-fetches.

  • No terminal control sequences. text is sanitized to printable characters plus emoji: all C0/C1 control bytes, ANSI/OSC/DCS escape sequences, and bidirectional override characters are stripped or the message is dropped. Styling (color, marquee) is applied exclusively by the renderer from its own fixed styles — copy can never emit its own escapes.

A message that fails any check is skipped silently; the promo row falls back to the next valid message or renders nothing. Failure to validate never produces a visible error in the statusline.

Telemetry

Only anonymous aggregate metrics, and only when telemetry is already enabled:

  • Impressions
  • Dismissals
  • Sign-up conversions (attributed via URL campaign parameter, not client identity)
  • Click/open events

No prompt content or user data is collected. Telemetry-off means zero promo-related network calls.

Expected Benefits

  • Increased awareness of adjacent capabilities
  • Low-friction education
  • Continuous feature discovery
  • Higher Cognitum conversion
  • Zero workflow interruption

Control Precedence

The promo surface honors the funnel-wide precedence chain (normative definition in ADR-305):

1. RUFLO_FUNNEL=0            (environment)
2. Enterprise managed policy
3. User config: funnel.enabled
4. Package default
5. Remote signed policy       (only when freshness feed is enabled)

A lower-precedence source can never re-enable what a higher-precedence source disabled.

Guardrails / Consequences

  • Statusline render cost must stay within the existing cache discipline (60s delegation cache, #2337); promo rotation is purely local string selection and adds no CLI invocations.
  • CLI startup latency target (<500ms) unchanged.
  • Dismissal and opt-out state persists per user (not per project) so a user who opts out is never re-prompted across projects.
  • If the promo row ever conflicts with an operational message, the operational message wins unconditionally.