Skip to main content

AD-014: Translator Workflow

Summary

Bowrain's translator workflow coordinates the handoff between automation and humans. An immutable activity feed records what happened; tasks assign work; notifications reach the right people on the right channel. A PushCompletionTracker emits push.automations.completed when prep-work automations finish, gating task fan-out so translators engage only on content that is ready. The source-review task is where a convergence run that holds on an under-ready source lands: the source ship-gate — enforced by the convergence engine (AD-022), not a bypassable option — routes source that has not cleared the project's source_gate here, so the same source issue is fixed once rather than once per locale.

Context

Translation is collaborative. Automation can translate, extract entities, and enforce quality gates, but humans still review, translate, and sign off. The gap to close is between "automation has produced content" and "a human sees it in their inbox and acts on it". Everything that fills that gap — activity, tasks, notifications, and the events that drive them — lives in one coherent subsystem so the data model, UI, and event flow stay consistent.

Decision

Three concepts, one substrate

ConceptRecord ofAudiencePersistence
ActivitySomething that happenedEveryone in the project/workspaceactivities table
TaskAn actionable work item assigned to a personAssignee + project memberstasks table
NotificationA user-targeted alertIndividual usernotifications table

Activities are the source of truth — every significant event produces an activity. Tasks and notifications derive from activities (or from human-initiated assignments).

Activity feed

CREATE TABLE activities (
id TEXT PRIMARY KEY,
workspace_id TEXT NOT NULL,
project_id TEXT NOT NULL DEFAULT '',
stream TEXT NOT NULL DEFAULT 'main',
actor_id TEXT NOT NULL,
actor_name TEXT NOT NULL DEFAULT '',
type TEXT NOT NULL,
entity_type TEXT NOT NULL DEFAULT '',
entity_id TEXT NOT NULL DEFAULT '',
summary TEXT NOT NULL,
data JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Activity types cover content lifecycle (item.pushed, block.translated, block.reviewed), project management (project.updated, member.added), streams (stream.created, stream.merged), automation and AI (flow.completed — recorded once the flow lifecycle events are emitted, which they are not yet — job.completed, extraction.completed), quality (gate.passed, gate.failed, brand.drift), review queue (review.assigned, review.decided), connectors, tasks, and versions.

The ActivityRecorder subscribes to the event bus (AD-012). Unlike the audit log (raw events for compliance), activities are curated for humans — they aggregate related events (a push that updates 50 blocks produces one item.pushed activity, not 50 block.updated activities).

Activity feed by role

The UI defaults a role-based filter when a user opens the feed, and users can always override. Defaults:

RoleDefault filter
PM / AdminAll
DeveloperTechnical (connectors, flows, pushes, streams)
TranslatorMy work + content
ReviewerReview pipeline
ObserverMilestones only

Tasks

CREATE TABLE tasks (
id TEXT PRIMARY KEY,
workspace_id TEXT NOT NULL,
project_id TEXT NOT NULL,
stream TEXT NOT NULL DEFAULT 'main',
type TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'open',
priority TEXT NOT NULL DEFAULT 'normal',
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
assignee_id TEXT NOT NULL DEFAULT '',
created_by TEXT NOT NULL,
completed_by TEXT NOT NULL DEFAULT '',
data JSONB NOT NULL DEFAULT '{}',
due_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ
);

Task types:

TypeWhat it means
translateTranslate blocks for a locale
reviewReview translated blocks
review_termsDecide on term candidates in the review queue
source_reviewInspect source content before language fan-out
fix_qualityResolve QA issues
fix_brand_voiceResolve brand voice violations
fix_terminologyResolve terminology violations
connector_setupConfigure a connector
customFree-form

Task lifecycle: openin_progress → (done | cancelled). A blocked status is available for explicit dependencies.

Tasks originate from three sources: manual creation by a project manager, automation actions (create_review_tasks, create_source_review), or system-generated (quality gate failures, brand voice drift, extraction completion).

Source review gate

Fixing a source-language issue once per locale is expensive, so the convergence engine is source-first: it settles the source and holds at a source ship-gate before it translates any locale (AD-022). A TaskSourceReview is the human half of that gate — the task a run opens when it holds on source rather than fanning out:

convergence run (on push, kapi up, or Run now)
→ settle source · evaluate source_gate
→ source below the gate → run holds (stall_reason = source_not_ready)
→ create_source_review
→ single TaskSourceReview for a source reviewer
→ reviewer completes task
→ source.review.completed
→ the held run fans out per-locale on its next pass

The source reviewer inspects placeholder correctness, terminology consistency, DNT identification, and context notes before language fan-out. HandleCompleteTask emits source.review.completed when the task closes; the run that was holding lifts the now-approved source to the gate and produces the per-locale translations (and their review tasks) it was waiting on. A project that wants raw fan-out sets source_gate: none to opt out of the gate entirely; the governed default (checked) is to settle first.

PushCompletionTracker

A server component bridges individual job completion and collective push readiness:

  • Subscribes to push.completed.
  • Polls JobStore.ListJobsByPushID() and ExtractionJobStore.ListByPushID() every five seconds.
  • When all jobs reach terminal status (completed or failed) it emits push.automations.completed carrying push_id, items, workspace_slug, project_id, translation_status, and extraction_status.
  • Emits immediately if the project has neither AI translation nor extraction enabled (0 jobs).
  • Times out after 30 minutes with status: timeout.

Task fan-out actions

Two automation actions (AD-013) drive fan-out:

create_review_tasks — for each target locale, find project members with matching language scope and PermReview or PermTranslate, create one task per (locale, assignee). If no member is assigned to a locale, create an unassigned task and notify project admins. Config: mode (review or translate), priority.

create_source_review — create a single source review task. Config: reviewer (user ID or fallback to the first member with PermEditSource).

Notifications

The existing notification store extends with categories, grouping, and multi-channel preferences.

CREATE TABLE notification_preferences (
user_id TEXT NOT NULL,
workspace_id TEXT NOT NULL,
category TEXT NOT NULL,
channel_web BOOLEAN NOT NULL DEFAULT TRUE,
channel_email BOOLEAN NOT NULL DEFAULT FALSE,
channel_push BOOLEAN NOT NULL DEFAULT FALSE,
channel_desktop BOOLEAN NOT NULL DEFAULT TRUE,
PRIMARY KEY (user_id, workspace_id, category)
);

Categories: task, review, quality, automation, mention, project, system.

Channels

ChannelTransport
WebWebSocket via the existing notificationHub + cross-instance Redis Pub/Sub
DesktopgRPC WatchProject stream → Wails event → OS notification
EmailImmediate or digest via the Mailer
PushFCM / APNs via a PushService abstraction and push_tokens table

The NotificationDispatcher resolves recipients (project membership + assignee + watchers), checks per-user preferences per category, persists the notification, and fans out to enabled channels.

Role-based defaults

Notification defaults are seeded from the user's project role when they first join:

Notification typePM / AdminDeveloperTranslatorReviewer
task.assignedwebwebweb, email, desktopweb, email, desktop
task.overdueweb, emailwebweb, email, desktopweb, email, desktop
review.assignedwebweb, email, desktop
quality.gate.failedweb, emailweb, emailwebweb, desktop
brand.driftweb, emailwebweb, desktop
mentionweb, email, desktopweb, email, desktopweb, email, desktopweb, email, desktop
flow.failedweb, emailweb, email
content.availablewebweb, email, desktopweb, desktop
progress.milestoneweb, emailwebwebweb
quota.warningweb, email

Users override per category.

Digest and quiet hours

A DigestWorker runs daily (and weekly for executives). For each user with digest_frequency != off it fetches unread notifications since last_sent_at, groups by category, renders the template, sends via Mailer, and updates digest_state.

Smart delivery rules:

  • Seen on web, skip email — if the batch is all read, skip. If the user has been actively connected in the last hour, defer non-urgent emails.
  • Quiet hours — per-user quiet window with timezone; non-urgent email, push, and desktop are queued until it ends. Urgent (priority: high) always delivers.
  • Escalationtask.assigned (normal) → task.due_soon (email even if off for the category) → task.overdue (priority: high, immediate email). Connector errors escalate on three consecutive failures.
  • Auto-mute resolved issues — when a gate that previously failed now passes, related failure notifications mark as read. When a task completes, its due_soon and overdue notifications auto-dismiss.
  • @mentions — comments and notes scan for @username, resolve, and dispatch the mention notification type, always via web, email, and desktop.

MCP integration

The bravo agent (AD-016) and other persona agents consume tasks through MCP tools: list_my_tasks, claim_task, complete_task. Agents appear as project members with language scope and the appropriate permission, so task assignment treats them identically to human translators.

UI

  • Activity Feed — timeline appearing on project dashboards, the workspace overview, and block detail. Role-based default filter.
  • Task Board — Kanban by status, drill-down to editor. Accessible from the project sidebar, the "My Tasks" cross-project view, and the dashboard.
  • Notification Panel — grouped cards with category badges, inline actions ("Go to task", "Open in editor"), and direct access to preferences.

All three are shared React components in packages/ui/ so the web app, desktop app, and future mobile surfaces render the same primitives.

Consequences

  • Translator engagement is deterministic — tasks appear exactly when prep work finishes, not when content arrives.
  • Source review prevents duplicated fixes and upgrades the quality of downstream translations.
  • Notifications reach the right channel without flooding inboxes. Preferences, quiet hours, and digest batching keep alert fatigue low.
  • Role-based defaults make the system usable without configuration; the first time a user opens their inbox they see only what matters for their role.
  • Agents and humans share one task queue, so hybrid teams scale without special cases.