Skip to content

Show signed-in identity and authorized teammates’ Task/Card work #164

Description

@alexeygrigorev

Show signed-in identity and authorized teammates’ Task/Card work

Status: blocked overall — backend Stage A is locally eligible; frontend waits for #161/#180 and release waits for #166 cleanup
Tags: enhancement, portal, frontend, backend, work-engine, data, testing, design, P1
Depends on: #161, #166, #180
Blocks: the #162 access/system and work-execution redesign slices
Next owner: Software Engineer for isolated backend Stage A; do not merge or push it

Product outcome

An authenticated operator can always answer “who am I?” from the canonical shell, deliberately inspect work owned by another active teammate, and help execute that teammate’s Tasks. The authenticated actor never changes: the Task keeps its assignee, while completion, waiting, follow-up, response, proof, file, and other ordinary Task-work history records the real teammate who performed the action.

This issue owns the missing identity, team-directory, owner-projection, filtering, and owner-aware authorization contract. It does not redesign every Tasks/Cards screen; #162 later applies the shared design system while preserving this behavior.

Accepted foundations and contract corrections

Current contract and gaps

  • /api/me returns the authenticated user and the portal cookie is propagated to /work/api/me, but the shell exposes only a generic Settings gear. A disabled or deleted user already fails closed in portal mode; every supported cookie/bearer seam must behave the same way.
  • GET /api/users supplies the existing role-aware Users surface and strips passwordHash. It is not an appropriate work projection because it includes account-management fields such as email.
  • Tasks persist assigneeId, createdBy, and completion/history actor IDs. The UI can resolve an assignee name after loading /api/users, but the task list APIs have no owner filter and may expose raw/unresolved IDs.
  • Cards have no persisted owner. Their Tasks can have different assignees, so Task assignees alone are not a durable Card owner contract.
  • Today, any authenticated operator can call the generic Task and Card write routes for any record. Client-supplied x-user-id is stripped outside the explicit test bypass, but task collaboration, assignment changes, deletion, and direct Card administration are not distinguished. Teammate execution therefore needs a deliberate server-side action boundary, not a client-side identity switch.

Access model

The workspace currently has two supported roles, admin and operator.

Actor / record Read identity/team directory Read Task/Card Mutate Task Administer Card Reassign owner
Active operator, own record Yes Yes Yes Yes for a Card they own No
Active operator, active peer record Safe teammate projection Yes Yes: ordinary Task execution, real actor attribution No direct Card administration No
Active operator, unassigned/disabled/stale owner Safe state only Yes Yes: ordinary Task execution when otherwise valid No No
Active admin Yes Yes Yes Yes Yes, to an active user or unassigned
Disabled, deleted, expired-session, missing-role, or unknown-role actor No team access No peer access No No No

Completing or reopening any visible Task may update the accepted #168 Card counters, lifecycle, completion metadata, and public-safe lifecycle audit in the same transaction. That is the consequence of an authorized Task action, not permission to change the teammate-owned Card title, dates, owner, references, or active stage. Card completion/archive remains system-owned and is never a manual administration permission. The task assignee remains unchanged and history records the verified actor who performed the work.

An operator-created manual Task defaults to that operator as assignee when assigneeId is omitted. An operator-created Card defaults to that operator as owner. Operators cannot create work assigned to a peer, change assigneeId/ownerId, administratively edit unassigned work, or claim it implicitly. They may still perform the explicitly allowed ordinary execution actions on an otherwise visible unassigned Task without changing its assignee or Card owner. Admins retain explicit assignment and recovery authority. Existing and system-created unowned Cards remain Unassigned; do not guess or backfill an owner from historical text or Task data.

Staged implementation and release sequencing

Stage A — backend identity, projection, ownership, and authorization (locally eligible now)

Implement in a clean isolated worktree based on accepted #179/#168 source:

  • one shared verified interactive actor resolver that loads the current User for cookie/session/API-token requests and fails closed for deleted, disabled, missing-role, or unsupported-role actors before team reads or Task/Card writes;
  • the safe /api/team-members projection, safe Task/Card/actor projections, owner filters, Card ownerId, and the server-enforced Task execution versus Task/Card administration boundary below;
  • focused backend domain/handler/API tests under backend/tests, including both canonical /api and rewritten /work/api behavior. Do not create a broad Playwright request/CRUD suite;
  • no frontend, Playwright, deploy-workflow, SAM-template, Replace the transitional Tasks table with the final cardId schema #166 preparer/verifier, import/export/restore, migration, table-creation, or data-mutation work.

Stage A may receive independent Tester and PM review and an explicit-path Refs #164 checkpoint commit. Keep it on its isolated branch: do not merge it into local main, push it, or include it in #166 Phase D.

Stage B — accepted frontend behavior and real browser journeys (blocked on #161 and #180 acceptance)

Start from the final accepted #161/#180 source, then connect the existing account/work-scope prototype to /api/me, /api/team-members, canonical owner deep links, Task/Card projections, delegated Task execution, honest partial failure, and role-derived controls. Remove routine /api/users use from account/work views; keep that API only for the Admin/Users surface.

Playwright owns real operator journeys, navigation, focus, accessibility, and responsive evidence. Backend tests own request matrices, authorization, filters, conflicts, and privacy. Preserve #180’s independent fixtures/diagnostics and the independent non-deploy-blocking browser workflow; do not reintroduce serial coupling, broad API CRUD specs, fixed sleeps, exact incidental copy, raw IDs, or screenshot-pixel assertions.

Stage B may be reviewed and committed on the held #164 branch, but remains unmerged/unpushed while #166 is active.

Stage C — integrate and release from steady state (blocked on #166 cleanup)

After repaired #166 A → B → C, the in-sequence #182 Cards preflight, Phase D production evidence, and accepted cleanup have restored ordinary main-push OIDC deployment:

  • rebase or reconstruct the accepted Show signed-in identity and authorized teammates’ Task/Card work #164 changes on the exact clean steady-state main without preserving temporary compatibility;
  • rerun the complete backend/frontend/browser/SAM verification and source/SAM parity against that final combined source;
  • merge and push through the ordinary DataOps lifecycle, then let On-Call monitor the normal workflow.

No manual app deploy, workflow dispatch for #164, AWS call, owner backfill, source-data import, or production data edit is part of any stage.

API and data contract

Identity and safe teammate projection

  • GET /api/me and GET /work/api/me return the same authenticated { user } identity for a real cookie or bearer session. The user contains id, name, email, role, and disabled: false; it never contains passwordHash or session material.
  • Missing, expired, deleted-user, or disabled-user sessions return JSON 401 at both prefixes and never redirect an API request to HTML. A missing or unsupported role cannot use team reads or work mutations.
  • Add a purpose-built authenticated GET /api/team-members projection for active admin and operator actors. Each entry contains only stable ID, display name, supported role, and active/disabled availability. It omits email, password/session data, private profile data, and external avatar URLs. Active members are valid assignment targets; disabled members exist only to resolve historical ownership.
  • Task responses retain canonical assigneeId and add a safe assignee projection. Card responses persist optional administrative ownerId, add a safe owner projection, and expose a deduplicated safe taskAssignees summary derived from their Tasks. Task history actor display is resolved through the same safe projection. Routine UI never displays raw user IDs.
  • No new image storage is introduced. The shell avatar is deterministic initials derived from the authenticated display name, with a neutral fallback when identity is unavailable.

Owner filters and deep links

  • GET /api/tasks accepts owner=me|team|unassigned|<user-id> alone or in combination with one existing date/range/cardId/status filter. me is resolved from the verified server actor, team means records assigned to any active teammate, and a concrete active/disabled/stale reference returns only that owner’s records with its honest availability projection. Existing filter priority must not silently discard owner.
  • GET /api/cards accepts the same owner values against persisted Card ownerId; GET /api/cards/:id and /api/cards/:id/tasks return the safe owner/team projections. Opening a teammate Card may expose its Task execution actions without granting direct Card administration. No /api/bundles alias is added.
  • Canonical UI routes preserve owner=me|team|unassigned|<user-id> on /#/tasks and /#/cards through direct load, refresh, Back, Forward, Task/Card relationship navigation, and close/return. The visible picker labels are My work, Team work, Unassigned, and teammate display names; a raw ID is never used as visible fallback copy.
  • A disabled owner remains selectable only when referenced by retained work and is labelled Disabled teammate. A missing user reference renders Teammate unavailable, retains the exact deep link and any readable work, and offers a clear-filter recovery action. It never falls back silently to My work or Team work.
  • Owner list/filter failures are honest partial failures: already loaded work stays visible, the owner filter is marked unavailable, and no false empty or fabricated teammate is shown.

Server-enforced collaboration boundary

  • Every ordinary task-execution path uses the verified actor and works for visible own, teammate, or unassigned tasks: atomic waiting/follow-up/response/completion/reopen actions plus task-bound proof/file evidence. These actions retain assigneeId and append the verified actor to history; an owner filter, form value, or browser header cannot impersonate the assignee.
  • Task create and general metadata update use the verified actor. An operator-created Task with omitted assignee defaults to that actor; explicit peer assignment, assignee changes, ownership claims, Task deletion, and Task-Card membership changes affecting a Card the operator does not own are admin-only. A routine delegated action cannot silently reassign the Task, move it between Cards, rewrite its creator, erase history, or bypass Prevent lost Task updates and history with optimistic concurrency #179 expected-version conflicts.
  • Every existing Card administration path enforces the table above: create, metadata update, active-stage edit, and single/batch Template-update apply. An operator may administer only a Card whose persisted ownerId matches them; ownerId changes are admin-only and unassigned Cards are admin-managed. Do not restore the removed manual Card archive/delete routes or direct lifecycle-field writes. Derived Complete and archive cards automatically when all tasks are done #168 counters/lifecycle/audit from a valid teammate-Task action remain allowed and retain the Card owner.
  • A forbidden assignment, deletion, membership change, or direct Card-administration write returns JSON 403 with stable code work_admin_forbidden; unsupported-role team reads return 403 with team_read_forbidden. A stale record remains 404, validation remains 400, and an ownership/version race remains 409; no denial is represented as success.
  • Interactive Task/Card routes never fall back to system, portal-admin, a requested owner, or an assignee when the verified actor is absent. Existing authenticated CLI/channel paths resolve the same active supported User; genuinely scheduled/internal mutations retain their explicit stable system actor and do not become an impersonation route.
  • Card ownerId is administrative metadata outside the Complete and archive cards automatically when all tasks are done #168 aggregate calculation. Authorized Task completion/reopen/membership transactions retain it unchanged while atomically applying the accepted Task history, Card counters/lifecycle/version, and lifecycle audit with the verified action actor.
  • Browser-supplied identity headers, owner query values, form fields, and stale client state cannot change the actor. Re-fetching after a role/disabled/ownership change immediately applies the new server decision.
  • Existing admin-only Users mutations and domain-specific approval/finance/assistant policies remain unchanged. This issue does not grant peer approval or administration rights on any other surface.

Shell and responsive behavior

  • Replace the generic desktop/mobile Settings trigger with a persistent account control backed by /api/me. Desktop shows initials plus the current display name; mobile shows the initials control without consuming the page title/action area. Both expose an accessible name such as Account for Avery Operator, aria-haspopup, and expanded state.
  • The existing Settings surface opens from that control and begins with the signed-in user’s name, own email, and role, followed by the already-authorized appearance/version/Admin/Users/Sign out actions. Operator controls remain role-derived from the server; the account control is not an admin shortcut.
  • Escape, outside click where applicable, close, route change, and sign out preserve the accepted focus/session behavior. Closing restores focus to the account control. Identity loading shows a neutral skeleton; failure shows Identity unavailable with Retry and Sign out rather than a guessed name or role.
  • At 1440x900 the account control, work-scope picker, selected teammate state, and Task/Card heading do not crowd the shell. At 390x844 the control remains at least 44×44 CSS px, the account view is keyboard/touch operable, and the work-scope picker plus first work item fit without horizontal overflow or clipped actions.
  • A teammate Task detail identifies its assignee and keeps the state-valid task execution controls. After a delegated action, visible history says which signed-in operator performed it and the assignee remains unchanged. Assignment, deletion, and direct Card-administration controls remain role-derived and separate.
  • Changed ready, empty, loading, partial-failure, forbidden, disabled-owner, stale-owner, and Settings-open states have correct headings/list/form/popover semantics, visible focus, and zero critical or serious WCAG A/AA findings in automated scans.

Real behavior tests

Use synthetic users such as Avery Operator, Morgan Teammate, Casey Disabled, and Riley Admin with example.test addresses. Do not use real names, production IDs, contact details, private avatars, or operational work.

  • Add focused backend handler/domain contract coverage (suggested backend/tests/team-visibility.test.ts) for both API prefixes, cookie and bearer identity, safe team projections, filter combinations, Card owner persistence, delegated Task-action success with stable assignee and real actor history, assignment/deletion/direct-Card restrictions, spoofed-header rejection, disabled/deleted/role-change fail-closed behavior, and stable 400/403/404/409 responses. Do not put this request matrix in Playwright.
  • Add a normal real-server Playwright behavior suite without request interception (suggested backend/e2e/team-visibility-behavior.spec.js) with exact titled journeys for: desktop/mobile identity and account-menu focus; own/peer/team/unassigned filters and deep-link history; delegated teammate-Task completion/proof/waiting with unchanged assignee and real actor history; direct Card-administration denial; and disabled/stale owner plus partial-source recovery.
  • Assert API payload privacy: no passwordHash, token, cookie, session value, peer email, private avatar URL, or raw identifier copy appears in the team/work projections, DOM, screenshots, logs, or accessibility tree.
  • Update the durable capability/state mapping with exact passing normal-browser titles, including the retained Home-ready pointer affected by identity hydration; update the source/SAM parity state contract for all changed shell/Task/Card states.

Tester captures and reads exactly these synthetic images under .tmp/screenshots/issue-164/:

  • source-identity-settings-desktop-1440x900.png
  • sam-identity-settings-desktop-1440x900.png
  • source-owner-filter-mobile-390x844.png
  • sam-owner-filter-mobile-390x844.png
  • source-teammate-Task-action-desktop-1440x900.png
  • sam-teammate-Task-action-desktop-1440x900.png
  • source-disabled-owner-mobile-390x844.png
  • sam-disabled-owner-mobile-390x844.png

Source/SAM pairs use identical fixtures and prove matching route/query, signed-in actor, assignee, and Card-owner labels, delegated mutation result, unchanged ownership, accessible-name summary, console/page-error result, and asset hashes. Pixel similarity is supporting evidence only. The directory contains exactly these eight PNGs at native dimensions and no stale extras; screenshots are evidence, not behavior proof or tracked artifacts.

Run and report exact commands, exit codes, test counts, and evidence paths in this order:

npm --prefix backend test
npm --prefix backend run typecheck
npm --prefix backend run build
node backend/scripts/verify-frontend-artifact.mjs --source frontend --artifact backend/dist
npm --prefix backend run test:e2e
npm --prefix backend run test:e2e:frontend-parity
make sam-validate
make sam-build
node backend/scripts/verify-frontend-artifact.mjs --source frontend --artifact .aws-sam/build/BackendFunction
make ci
git diff --check

Dependency and sequencing

Lifecycle gates

Out of scope

  • External avatar providers, image upload/storage, profile editing, presence, chat, comments, mentions, notifications, teams/groups, invitations, organization/workspace membership, or more roles/ACL dimensions.
  • Broad Converge canonical DataOps surfaces on one operator design system #162 navigation/visual redesign, changes to Finalize the canonical scan-first Operations Home #161 queue prioritization, a second shell/router/frontend, framework migration, alternate routes, or a legacy bundle API.
  • Identity impersonation; teammate reassignment/deletion; peer account/role/approval/administration; implicit ownership claims; ownership inference/backfill from text; or silent reassignment of unassigned/stale records. Ordinary teammate-Task execution with real actor attribution is explicitly in scope.
  • Changes to assistant, artifact, sponsor, finance, bookkeeping, newsletter, calendar, mailing-export, Docs knowledge, Git, or external provider behavior.
  • Raw SOPs, private operational content, real people/emails, private images/links, production identifiers, credentials-adjacent setup, generated artifacts, or edits to source repos/private knowledge/AWS infra.
  • Manual deploy, workflow dispatch/rerun, AWS/IAM/CloudFormation mutation, external account/provider call, production write, or destructive migration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1ImportantbackendBackend/APIdataData model, migration, storagedesignDesign and UXenhancementNew or improved functionalityfrontendFrontend UIportalShared portal shell and UXtestingTests and QAwork-engineDataTasks task execution engine

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions