Skip to content
V5PerceptionPhase 6 · transparency

Perception.Ambient context, never identity.

This page describes the perception layer end-to-end: the closed schema, the three gates between a visitor action and a stored count, the privacy invariants the architecture enforces, and the live aggregate the layer is currently producing. Opt-in default-off, aggregate- only, revocable in one click. Phase 6 lands its observers across five sub-PRs; this page is the single place every one of them is audit-able.

01 · Your consent

The toggle below sets a same-origin cookie named v5_perception_consent with a 14-day lifetime and a paired localStorage flag. The cookie is the single gate the edge endpoint checks; without it, any incoming event in any category other than “adoption” is dropped at the door without a KV write. Revoking clears both the cookie and the flag immediately.

Loading current state…

02 · What can be collected

Six categories. Each one carries a closed set of bucket labels — the bucket is the level of detail the storage holds. Raw measurements (px/s, ms, scroll positions) are never persisted; the bucketization happens client-side before the event ever reaches the endpoint.

  • scroll-velocityHow fast the page scrolls

    Average pixels per second over a sampling window. Bucketed into four coarse states — idle, browsing, scanning, skimming. The raw px/s number is never persisted.

    idlebrowsingscanningskimming
  • dwell-timeHow long a page is kept open

    Wall-clock milliseconds between page open and page close, bucketed into six progressively wider slots. Granularity widens with time because a 12s vs 14s difference is meaningless and a 12s vs 4min difference is.

    0-10s10-30s30-60s60-180s180-600s600s+
  • tab-visibilityHow long the tab spent backgrounded

    Time the visitor had this tab in the background between focus events. Three slots — short, medium, long. No record is kept of when the focus event fired.

    shortmediumlong
  • section-engagementWhich on-page section drew attention

    Section identifiers as they appear in the page source (e.g. hero, projects, contact). The bucket IS the section name; no scroll position, no time spent in section, no order.

    (kebab-case section slug)
  • navigation-flowWhich page-to-page transition fired

    Ordered pair of route slugs (e.g. home>about). Records the transition itself, not the visitor making it. Counts compose with one another into a Markov-shaped graph the operator can read; no individual visitor's path is reconstructible.

    (from-slug>to-slug)
  • cognition-signalInferred attention state at the moment of a navigation

    A three-state qualitative bucket derived from the per-session page counter — arrival on first navigation, exploring through 2-4 routes, engaged from 5 onward. The state never regresses within a session and is computed entirely client-side; only the bucket label reaches the endpoint.

    arrivalexploringengaged
  • pacing-transitionHow many navigations the session reached before backgrounding

    Fired exactly once per session via sendBeacon when the tab is first backgrounded — captures the visitor's departure-time depth. Four progressively widening buckets (first / few / many / deep) match the cognition taxonomy's boundaries. No raw count is persisted; the bucket label is the level of detail.

    firstfewmanydeep
  • adoptionOpt-in / revoke / deny events

    The three states the consent decision can take. Recorded WITHOUT a prior consent gate because the decision IS the consent. The only events the layer is allowed to record before consent.

    opt_in_grantedopt_in_revokedopt_in_denied

03 · How it works

Every recorded count passes through three gates in a fixed order. The endpoint short-circuits at the first gate that fails — a request that crosses none of them still resolves with the same 204 No Content as a request that crosses all three, so the presence-or-absence of telemetry is never an attack surface a network observer can probe.

  1. Gate 1 · operator master switchprocess.env[V5_PERCEPTION_ENABLED] === "1"

    Cheapest check. Without this env set on the deployment, the endpoint silently no-ops every event before reading the body. The operator can dark-launch the entire perception subsystem by leaving the variable unset, which is the default in production right now.

  2. Gate 2 · closed schemaisPerceptionCategory ∧ isValidBucket

    The inbound category must be one of the eight allow-listed values in section 02. The inbound bucket must be either a member of that category's closed list (for fixed categories) or pass the kebab-case shape check (for dynamic categories like navigation-flow). Anything else drops at the door.

  3. Gate 3 · visitor consentCookie: v5_perception_consent=granted

    The endpoint reads the perception consent cookie from the inbound Cookie header. Without granted the event drops — except for the adoption category, which bypasses this gate because recording the consent decision cannot itself require prior consent. The bypass is the only exception architecturally, and it's explicit in the endpoint source.

The flow

opt-in toggled ─► cookie + localStorage written │ ▼ client observer ──► POST { category, bucket } │ ▼ edge endpoint ──► gate 1: env switch on? ─► gate 2: category + bucket valid? ─► gate 3: consent cookie present? (or category = adoption) │ ▼ KV HINCRBY ──► v5:perception:<category> → { <bucket>: count + 1 } │ ▼ this page (ISR) ──► HGETALL × 8 → section 07 below

Every byte that lands in storage is one of the closed bucket labels documented in section 02. The HINCRBY primitive is atomic and stateless — there is no session reference, no timestamp, no identifier anywhere on the persistence path.

Two inference layers above storage

Cognition signal
A per-session counter in sessionStorage advances by one on every route mount the client observer sees. The counter maps to one of three states — arrival (1), exploring (2-4), engaged (5+) — and fires one cognition-signal event per transition. The state never regresses within a session.
Pacing multiplier
The cognition state plus the OS reduced-motion preference resolve to one of four duration multipliers — FULL (1.0), MID (0.85), SNAPPY (0.65), STILL (0.0). Reduced-motion is an unconditional override. Animation consumers read the multiplier through a React Context; the layer ships no spring physics by type-system enforcement.

04 · What is never collected

  • No IP address, no User-Agent, no Accept-Language, no Referer beyond what the platform logs at the edge.
  • No mouse trails, no keystroke timings, no biometric- shaped signals. No session replay tooling.
  • No identifier — anonymous or otherwise — minted by this layer. The Lumina session memory (separately documented at /lumina/brain) is the only place visitor state persists, and even that is anonymous + opt-out + a 14-day TTL (operator- configurable to 30 days; see the brain page for the live value).
  • No timestamp on individual events. The aggregate hash holds a count, not a sequence.
  • No cross-device linking. No third-party trackers. No analytics SDK beyond Vercel Analytics, which itself is cookieless and IP-anonymised at the platform layer.

05 · Privacy invariants

Aggregate-only
Every recorded event lands in a count keyed by a bucket label. The bucket is the level of detail the storage holds — nothing else. There is no per-visitor record, no session-scoped aggregate, no time-of-event field.
No fingerprint
The endpoint reads no IP, no User-Agent, no Accept-Language, no Referer beyond what Vercel logs at the platform layer. The only header consulted is Cookie, and only the perception consent token within it.
No identity persistence
No cross-session identifier is minted. The consent cookie expires after 14 days of inactivity. There is no linkage between this layer and the Lumina session memory (which is separately documented at /lumina/brain).
Opt-in default-off
The subsystem is dark unless the operator has flipped V5_PERCEPTION_ENABLED=1 AND the visitor has opted in via the toggle below. Either gate closed means no event records.
No surfacing
Nothing about the visitor's perception data is ever shown back to that visitor. The layer is invisible by construction — its output is a public aggregate snapshot that anyone can read, not a personalised message the visitor receives.
Graceful no-op
When KV is unavailable, every record helper returns silently and every read helper returns an empty object. The layer never blocks a page render or chat turn.

06 · Retention & aggregation

Aggregated counts live in six Vercel KV hashes — one per category. Each hash maps a bucket label to a count. There is no TTL on the hashes themselves; the counts are cumulative across the lifetime of the layer. The data persisted is, end-to-end, the count itself — nothing else.

Aggregation is monotonically additive. The endpoint increments a single field by 1 per qualifying event; no other write shape exists. There is no decrement, no re-attribution, no per-visitor bucketing. Removing a visitor's contribution to the aggregate is mathematically impossible — but the aggregate also contains no reference to which contributions came from whom, which is the point.

The consent cookie expires after 14 days of inactivity, at which point the visitor returns to the default-OFF state without action.

07 · Live aggregate snapshot

Read from KV at this page's hourly ISR cadence. The number against each bucket is the cumulative count since the perception layer was first enabled. Empty categories below mean no event of that kind has been recorded yet — which, at 6.1 foundation time, is every category that isn't the consent decision itself.

$ perception.snapshot

No events recorded yet.

The subsystem is either dark (V5_PERCEPTION_ENABLED unset), or no visitor has opted in since the layer began recording. Both are valid steady states for Phase 6.1.

08 · Why this exists

Subsequent V5 phases — temporal architecture playback, cinematic topology, operational digital twin — share a need to know the SHAPE of how visitors engage, not the identity of any one visitor. A page that loads fast for someone skimming should still feel cinematic for someone reading; the layer that distinguishes those modes is this one.

The site will never address the visitor about their perception data. There is no “we noticed you spent 8 minutes on architecture” greeting, no “your usual section” section, no implicit profile. The opt-in is a contribution to the aggregate — nothing more.

09 · Related transparency

The perception layer is one of two V5 surfaces that persist any visitor state. The other is the Lumina session memory — the conversation history that the chat embedded across this portfolio uses to maintain context across turns. Each has its own transparency page; together they describe every byte the platform stores about a visit.

10 · Source files

Every claim above is grounded in code. Click any row to read the file on GitHub.

V5 · Phase 6 · Sensory Awakening·Opt-in default-off·Aggregate-only·Revocable in one click

Phase 6 closes with this page. Five sub-PRs landed the foundation: perception endpoint + schema (6.1), cognition observer (6.2), pacing engine (6.3), memory layer extensions (6.4), and this transparency page (6.5). A 60-90 day observation window now opens before Phase 7 (Temporal Architecture) begins; no further perception surface ships during that window.