Editions matrix (technical)¶
Cross-cutting map of WorkWingman build / runtime / distribution / reader surfaces. Facts only from this tree. Axes are orthogonal unless a cell says otherwise: process edition ≠ session audience ≠ deployment rung ≠ data world ≠ docs reader tier.
Missing requested source: docs/technical/tester-laptop-distribution-spec.md does not exist in this repo. TODO(verify) whether it lives elsewhere or was never written.
Related fixtures (not full edition specs):
docs/technical/team-only.md— body is a single line; listed for audienceteam-testonly indocs/cloud-docs-manifest.jsondocs/plain/yc-only.md— same pattern foryc-reviewer
1. Primary matrix¶
| Surface / “edition” | What it is | How selected (flags / env / keys) | Capability / audience string | Distribution channel | Notes / gaps |
|---|---|---|---|---|---|
| Public desktop (default product) | Local-first Windows app: Electron + self-contained .NET API + Angular + bundled Playwright Chromium | Default when WorkWingman:Edition is not server (WorkWingman__Edition env). Loopback API + per-launch token (%LOCALAPPDATA%\WorkWingman\api-token). No server invitation audience. |
Runtime: edition=desktop, audience=local-user (RuntimeCapabilities.From when serverEdition=false) |
Single NSIS installer WorkWingman-Setup-<version>.exe via pwsh electron/build-installer.ps1 / npm run dist. Optional portable: npm run dist:portable. Auto-update feed configured as generic provider https://updates.workwingman.app/beta, channel latest (emits latest.yml). Package marks workWingman.updateFeedState: "unpublished-build" — checks fail closed until feed is published. One product name (WorkWingman / dev.workwingman.desktop); no separate “public vs internal” desktop artifact name found. |
Chromium bundled (PLAYWRIGHT_BROWSERS_PATH). Per-user NSIS (perMachine: false, no UAC). Update modes: Manual / Notify (default) / Automatic. Signing recommended (Azure Trusted Signing); build currently forces unsigned discovery off. TODO(verify): live hosting of latest.yml + installer at the feed URL. |
| Internal (Google cohort) | Hosted server session for staff / internal testers | Server: WorkWingman__Edition=server. Google SSO allowlist cohort internal-testing (WorkWingman:TenantSession:Cohorts, typically file mount WORKWINGMAN_COHORT_CONFIG_FILE → Secret Manager workwingman-cohort-config on prod). TenantId example internal. Private workspace per member by default (CreatePrivateWorkspacePerMember default true → {TenantId}-{sha256(email)[..16]}). |
Session audience = cohort name internal-testing. Not a special case in RuntimeCapabilities — unmatched audiences resolve to edition=server-locked, audience=unconfigured. UI label: “Internal testing”. |
Cloud Run app ladder: prod ww-team @ app.workwingman.ai; qa ww-team-qa @ app.qa.workwingman.ai. Entry via Google sign-in paths admitted in pilot allowlist. MPA door “Internal team” is signposting only — cohort is resolved from email allowlist, not which door was clicked. |
Orthogonal to invitation audiences. Multi-mode emails may also belong to other cohorts. |
| team-test (invitation audience) | Hosted server audience for team testers | Server + successful redemption of invitation whose configured Audience=team-test (SHA-256 of code in Secret Manager; plaintext out-of-band). Prod ladder: WorkWingman__TenantSession__Invitations__0__Audience=team-test, TenantId=team, private workspace per redemption, max redemptions configured on ladder (e.g. 200). |
Runtime after auth: edition=team-cloud, audience=team-test |
Same Cloud Run app surface as other server sessions (ww-team / ww-team-qa). Codes never in image/source; only hashes. |
Spec design also describes team-test retention variants (e.g. 30-day default in yc-security-disclosures.md) — treat retention UI details as design contract until proven live. TODO(verify) live retention defaults vs design. |
| yc-reviewer (invitation audience + docs tier) | Hosted server audience for YC review; also a cloud in-app docs audience label | Same invitation machinery; since 2026-08-06 (WING-316) the yc invitation lives only on the dedicated ww-yc pilot (TenantId=ycrev, database ww-yc, rung pilot) — the prod ww-team ladder no longer carries it. Capabilities and media exclusions differ from team-test. |
Runtime after auth: edition=yc-cloud, audience=yc-reviewer. Docs: pages whose audiences include yc-reviewer in docs/cloud-docs-manifest.json |
ww-yc @ yc.workwingman.ai behind the shared load balancer, digest-pinned via WorkWingman.CloudDeploy (verified live 2026-08-06). |
YC excludes several media capabilities (“Not included in this YC test”). Client must not infer edition from URL/build flag; post-auth /api/tenant/session supplies capabilities. |
QA Test Bench (cohort qa) |
Google cohort for QA; design text calls dual-role work “Test Bench QA” vs real user testing | Server + cohort qa in cohort config. Example: TenantId=qa-shared, CreatePrivateWorkspacePerMember: false (shared workspace) — only qa may set this false. Dual-role: email in MultiModeEmails and in more than one cohort → choose mode at sign-in (POST /api/auth/google/mode); nothing minted until choice. |
Session audience qa. UI label “QA testing”. Not mapped to yc-cloud/team-cloud in RuntimeCapabilities → falls through to server-locked unless remapped elsewhere (none found). |
Same hosted app services; telemetry stamps cohort for BigQuery filtering (exclude QA from user-testing analysis). | Mock/live dual product editions: not defined in repo. No TestBench, mock, or live edition flags found. Closest dual axes are: (1) multi-mode cohort choice (qa vs user-testing), (2) desktop real vs sandbox data worlds (WorkWingman:Environment), (3) deployment prod vs qa ladder. TODO(verify) any external “Test Bench mock/live” naming. |
| user-testing (cohort; related) | Real-use tester cohort (not named in the user title list, but on the same axis as internal/QA) | Cohort user-testing / TenantId=usertest; private workspaces per member. Surveys/insights also gated by WorkWingman:UserTesting=true (set on prod and qa app ladder rungs). |
Session audience user-testing. UI: “User testing”. Runtime capabilities: same gap as other non-invitation audiences (server-locked in RuntimeCapabilities). |
Hosted app; site may route “User testing” door through NDA/notice page before Google. | Flag is orthogonal: same code ships; UserTesting enables survey arm + usage-insights operator path. |
| qa-test (QA ladder invitation) | Invitation audience on qa app rung only | env-ladder.json qa: WorkWingman__TenantSession__Invitations__0__Audience=qa-test, TenantId=qateam |
No RuntimeCapabilities case → server-locked / unconfigured if used as authenticated audience |
ww-team-qa |
TODO(verify) intended capability mapping for qa-test. |
| Cloud site (marketing MPA + CMS) | Public multi-page site (Razor + htmx), not the Angular product shell | Separate image/service: ww-site / ww-site-qa. Env: SITE_CANONICAL_HOST, WingCMS dirs, Google client for CMS staff. Staff allowlist WINGCMS_STAFF_EMAILS. Admin host cms.workwingman.ai (prod). Content bucket prod ww-site-content vs qa ww-site-content-qa (must not cross). |
Marketing content “editions” cards: desktop (BYOK free) and cloud (subscription) in site-content.json — content/SKU narrative, not WorkWingman:Edition. |
Cloud Run + HTTPS (load balancer / Cloudflare edge per site docs). Public host workwingman.ai; qa site.qa.workwingman.ai. |
App product is app.workwingman.ai. Doors on /App are entry signposts, not edition switches. |
| yc-reviewer docs tier (in-app hosted docs) | Per-document audience gate inside server edition docs API | Manifest docs/cloud-docs-manifest.json lists each path’s audiences array (yc-reviewer, team-test). Access fail-closed: no match → no doc. Session audience from authenticated tenant session. Docs endpoints only when server edition enabled. |
Audience strings only: yc-reviewer, team-test (current manifest). Fixtures: plain/yc-only.md → yc-reviewer; technical/team-only.md → team-test. |
Served via API routes admitted in pilot allowlist: GET /api/docs/manifest, GET /api/docs/content on the hosted app origin. |
Separate system from marketing-site reader tiers (below). |
| Marketing-site docs reader tiers | Cumulative email tiers for site docs cards | Env lists WINGSITE_DOCS_TIER1_EMAILS, TIER2, TIER3 → Team / Internal / Tester (DocsReaderSignInService). Google OAuth + PKCE; Cloudflare Access as perimeter layer. |
Tiers: Team (all), Internal (Internal+Tester), Tester (Tester only). Not the strings yc-reviewer / team-test. |
ww-site surface |
Seven reader-related settings called out as live-only / undeclared on env ladder hazard — TODO(verify) exact setting names. |
2. Orthogonal axes (not separate “editions” but select behavior)¶
| Axis | Values | Selection | Effect |
|---|---|---|---|
| Process edition | desktop (default) / server | WorkWingman:Edition == server |
Loopback token vs tenant session + CSRF; pilot route allowlist when server |
| Capability edition | desktop / yc-cloud / team-cloud / server-locked |
Derived from process edition + authenticated audience | Feature lock/exclude matrix; frontend branches desktop vs cloud session on capabilities.edition === 'desktop' |
| Invitation audience | yc-reviewer, team-test, (qa) qa-test |
Invitation hash config | Entry gate + (for yc/team) capability special-case |
| Google cohort audience | internal-testing, user-testing, qa |
Cohort file + optional multi-mode pick | Session audience, tenant/workspace, telemetry cohort |
| Deployment rung | prod, qa (live ladder); uat/dev documented unbuilt |
WORKWINGMAN_DEPLOYMENT_ENVIRONMENT |
Separate Cloud Run service, Firestore DB, buckets, OAuth — same image |
| Data world (desktop) | real (default) / sandbox |
WorkWingman:Environment |
Isolated data/vault/browser paths; sandbox disables env API-key fallback |
| User-testing build | off / on | WorkWingman:UserTesting=true |
Surveys + insights; telemetry capture exists more broadly |
| Dev tools | off / on | WorkWingman:DevTools=true (dev profiles) |
Sandbox reset, SANDBOX pill, etc. Packaged app never sets it |
| Ordinary data routes unlock | locked / unlocked | WorkWingman:Server:UnlockOrdinaryDataRoutes=true |
Pilot/demo: unlocks legacy routes; admitted tenants share process-wide stores — never for hard multi-tenant isolation |
| Product feature flags | e.g. audience.veteranTools |
contracts/feature-flags.defaults.json |
Product features, not runtime edition (yc-security-disclosures.md) |
3. Packaging / electron-updater (desktop distribution detail)¶
| Item | Fact |
|---|---|
| Builder | electron-builder ^25.1.8; target Windows NSIS |
| Updater | electron-updater ^6.8.9; guarded when app.isPackaged |
| Bundle | Electron main/preload + published self-contained API + Angular frontend/dist + Playwright Chromium |
| Artifact | WorkWingman-Setup-${version}.exe → electron/dist/ |
| Install | Interactive wizard or silent /S; per-user under %LOCALAPPDATA%\Programs\WorkWingman |
| Publish at build | --publish never (no auto-upload) |
| Feed | provider: generic, url: https://updates.workwingman.app/beta, channel: latest |
| Feed readiness | unpublished-build until real feed hosted |
4. Cloud server selection (deploy sketch)¶
Server is selected only by configuration, not by client build flavor:
WorkWingman__Edition=server- Tenant store (e.g. Firestore project/provider)
- Invitation entries with
Audience,TenantId,CodeSha256(secret), redemption limits - Optional Google SSO client + cohort file
- Optional
WorkWingman__UserTesting=true - Optional
WorkWingman__Server__UnlockOrdinaryDataRoutes=true(ladder currently sets true on prod/qa)
Pre-auth GET /capabilities remains content-free server-locked (no audience disclosure).
5. Source map¶
| Path | Role |
|---|---|
src/WorkWingman.Api/RuntimeCapabilities.cs |
Capability edition strings |
src/WorkWingman.Api/Security/ServerEditionSecurity.cs |
Server gate + pilot route allowlist |
ops/cloud-run/env-ladder.json |
Prod/qa env for app + site |
ops/cloud-run/cohorts.example.json |
Cohort shape + MultiModeEmails |
docs/technical/cloud-run-server-edition.md |
Server contract |
docs/technical/packaging.md |
NSIS / updater |
docs/design/wing-214-google-sso.md |
Cohorts; Test Bench dual-role mention |
docs/cloud-docs-manifest.json |
In-app docs audiences |
docs/technical/oauth-and-reader-tiers.md |
Docs OAuth + fail-closed audiences |
docs/testing/user-testing-telemetry-spec.md |
UserTesting flag |
docs/business/cloud-edition-value.md |
Business case sources |
electron/package.json |
productName, NSIS, publish URL |
src/WorkWingman.Site/Content/site-content.json |
Marketing edition cards |
src/WorkWingman.Site/Services/DocsReaderSignInService.cs |
Site docs Team/Internal/Tester |
6. TODO(verify)¶
- Existence/location of
tester-laptop-distribution-spec.md(absent here). - Whether production still routes a frozen
ww-ycalongside rollingww-team. - Whether invitation/cohort audiences other than
yc-reviewer/team-testshould gainRuntimeCapabilitiesbranches (today →server-locked). - Any official “QA Test Bench mock vs live” edition names outside this tree.
- Live status of desktop update feed at
updates.workwingman.app/beta. - Absolute names of seven marketing-docs reader settings (env-ladder hazard).
- Whether marketing cloud “Subscription” SKU is live pricing vs aspirational content.