Contents

Events

Read workbench telemetry, collector admission and durable visitor attribution.

Events

Event query, count, anomalies and tail read all retained events the actor may read. The checkout does not add a project filter; use --project P to select one explicitly. A project filter requires events.read on that project. Organization admins can also read project-less events when no project filter is selected. Event attribution uses context project, then item project, then session project for every event type, then the supplied project token; unknown ownership stays project-less. Events are disposable telemetry and never establish required operational state.

Preview disposable universe records with yoke universe prune --dry-run. An organization administrator can review defaults with yoke universe retention get; universe retention owns the windows, protections, apply command and interrupted-run recovery.

The workbench collects usage analytics from first load in every mode — local, self-hosted, and hosted — with no consent prompt, banner, or toggle; nothing it collects is personal data, and a local or self-hosted workbench collects into its own universe. The first load captures the visitor's attribution cookie, and that load and each client navigation that changes the path emit PageViewed with event_type=page_view. Query-only, fragment-only, and state-only navigation (filters, ?selection=all rewrites) emits no view. The workbench names each screen in the browser title before the URL changes, most specific part first (PREFIX-12 · Items · Yoke, Runs · Deployments · Yoke), so each view's page_title tells screens apart; a machine-approval code never enters the title. The installed Structured Events Pack removes ?token=, ?user_code=, other sensitive query keys, userinfo, and fragments from page_url and referrer, and stores /machine-approval/<code> as /machine-approval/redacted in page_url, page_path, and referrer, in the browser and again in the collector. Only one page-view tracker runs per document, so a host page that tracks its own views and mounts the workbench records each navigation once.

The engine exposes anonymous GET /api/events/config, POST /api/events, and GET/POST /api/events/attribution. The collector accepts only frontend analytics, requires an exact match between browser Origin and the serving scheme/host/port, checks X-Events-Key against its publishable key, and shares its rate budget through Postgres. Remote serving requires HTTPS. For self-host, set YOKE_API_TRUSTED_PROXIES in the bundle .env to the TLS proxy IPs/CIDRs, preserve Host, and forward scheme/client headers; restart with yoke self-host init --dir PATH --protect-existing --start. Backend and operational writes retain their authenticated boundary.

Each workbench sends to its own universe's collector. A local or self-hosted workbench uses the serving universe's /api/events. A hosted shell serves several universes from one origin, so it mounts the workbench with eventsEndpoint set to the organization's own collector route (/api/orgs/<slug>/api/events) and forwards that route, with the viewer's session, to the tenant engine. A hosted mount without eventsEndpoint sends nothing and warns collector_endpoint_unconfigured; it never falls back to a shared collector.

Configuration returns only a publishable digest. The private cookie signing key belongs to the organization in organizations.events_signing_key. Signed, HttpOnly attribution cookies use tenant-specific names; HTTPS uses __Host- cookies and Secure, while the loopback HTTP door uses port-specific HttpOnly cookies. The attribution site domain is the serving host's registrable domain on the Public Suffix List (app.upyoke.com counts upyoke.com as internal; an IP or localhost is its own site), and sign-in provider returns such as accounts.google.com never become a touch. frontend_event_rate_limits stores disposable request counts (60 requests per client/organization per minute), independently of event retention. Collector event times require qualified RFC3339 with at most six fractional digits and normalize to fixed-six UTC strings before native storage. Signed cookie and handoff instants use that same representation; old numeric-expiry payloads are discarded and attribution is captured again. Rate-window starts and redemption expiries are native PostgreSQL instants; rate windows preserve their 60-second UTC epoch alignment. The server stamps organization, environment (YOKE_ENVIRONMENT), and the viewer's verified actor: a bearer token, the self-hosted yoke_web_session cookie, or, on the Local view, the per-run token that admits the local operator. A page view without one stays anonymous and carries only the browser's visitor_id. The emitter's service and project are stored as sent (web and yoke for the workbench); browser-supplied identity and project/work-item references cannot choose their durable owners, so the row indexes as global. Accepted batches deduplicate on event UUIDs through the existing event sink. Collector envelopes use INFO severity and are acknowledged only after sink completion; failures return collector_unavailable, never false success.

Refusals include origin_not_allowed, publishable_key_invalid, rate_limited (with Retry-After), envelope_invalid, and size limits. Each gives a recovery step. The Pack retries failed batches with their original event IDs. Telemetry remains disposable: using Yoke never depends on its successful delivery.

Each refusal also records one FrontendCollectorRefused backend event (reason, status, route, truncated Origin, serving host; no body, cookie, key or client address), at most once per reason, status and route per minute, so floods stay bounded. Organization admins can read these project-less rows with yoke events query --event-name FrontendCollectorRefused --limit 20. Accepted frontend events take created_at from the collector's receipt time; the envelope keeps the client event_time with received_at and client_time_offset_seconds, and rows more than 300 seconds off carry anomaly_flags = 'client_time_skew'.

Source maintainers install the project-owned Structured Events Pack, then run yoke dev run -- python3 -m yoke_core.tools.build_frontend_events from the lane. This derives browser JavaScript and package-relative Python helpers from the installed events/ files; it does not change the Pack or its baseline receipt. Generated helpers and rule data ship in the engine wheel. Rebuild them after an accepted Pack update; --check reports stale outputs. Node >=22.13 is needed only to build, while installed engines need no Node runtime for collection.

Workbench Events is the audit stream: lifecycle, claims, deploy, doctor findings, function calls. Filter by name and time when debugging "what happened."

Account acquisition is durable state: a signed-in flow creating an actor stores the verified attribution cookie's visitor identity, first touch, and last touch in actors.attribution. Later sign-ins preserve that acquisition snapshot and add a visitor link for the browser they come from. A flow without an attribution cookie creates an actor with no attribution. The event ledger is never used to reconstruct this account fact and may be pruned independently.

Search loaded events searches the entries already loaded and offers observed names as suggestions. Advanced filters keep the precise server-side event name, source, severity and time constraints. The loaded scope remains explicit; text search does not promise matches outside it.

actor_visitor_links is the durable list of browsers each actor has signed in from: one row per visitor_id, owned by its actor_id. Every web sign-in (company OIDC or yoke ui up browser admission) links the browser's verified visitor id to the signed-in actor, so an actor gains one link per browser or device. A visitor id already linked to another actor is never re-linked: the sign-in proceeds, the server logs visitor_linked_to_other_actor, and the row records refused_actor_id/refused_at. actors.attribution remains the signup snapshot. Sign-out (Profile → Sign out, POST /v1/auth/sign-out) revokes the web session and clears the attribution cookie, so the next person on a shared browser starts under a fresh visitor id.

Events are never rewritten. Tie a browser's anonymous page views to their actor at query time:

SELECT e.* FROM events e
JOIN actor_visitor_links l ON l.visitor_id = (e.envelope::jsonb ->> 'visitor_id')
WHERE e.source_type = 'frontend' AND l.actor_id = <actor id>;
Events · Yoke