Skip to content

feat(users): introduce discipline (developer / conception / business)#100

Merged
studert merged 2 commits into
mainfrom
worktree-introduce-roles
May 22, 2026
Merged

feat(users): introduce discipline (developer / conception / business)#100
studert merged 2 commits into
mainfrom
worktree-introduce-roles

Conversation

@studert
Copy link
Copy Markdown
Member

@studert studert commented May 22, 2026

Summary

Adds a mandatory discipline field (developer / conception / business) to every user so the tool can attribute Conception and Business colleagues alongside the existing Developer-only population. Avoids colliding with the existing security role column.

Spec docs live in specs/032-user-disciplines/ — proposal, plan, and a running implementation-notes log of decisions / deviations / tradeoffs / open questions.

  • New user_discipline enum + NOT NULL column with 'developer' default. Migration 0021_wise_vindicator.sql is metadata-only on PG11+ — all 184 existing users backfill to developer atomically.
  • Required at creation (new-user form, GitHub-sync inline-create form). Editable everywhere users are edited (kebab dialog, detail page).
  • Visible in the users table (sortable + faceted filter), user detail header, /profile page, and assignment detail.
  • CSV export gains a discipline column between circle and role. Bulk import accepts the column as optional; existing rows preserve their value when the column is blank.
  • Profile API (/api/profile) returns the new field. NextAuth session deliberately does not include it.

Test plan

  • pnpm typecheck clean
  • pnpm lint clean (zero warnings)
  • pnpm test — 339 tests pass
  • Manual: created a user via /users/new with discipline=Conception, edited to Business; change-history records discipline from "conception" to "business"
  • Manual: validation fires (Please select a discipline) when submitting without picking
  • Manual: CSV export at /api/export/users includes discipline column with backfilled values for all users
  • Manual: /profile shows the Developer badge alongside the Admin role badge
  • Migration verified against a Neon branch (wt/introduce-roles)

Known follow-ups

Captured in specs/032-user-disciplines/implementation-notes.html:

  • Claude analytics pages (/claude/users, UserListRow, UserDetail) still hide discipline; queue a reporting follow-up to surface discipline as a filter / breakdown.
  • GitHub sync "import as-is" path defaults silently to developer (no per-row UI to pick); admin reclassifies in /users after the sync. The inline-create path captures discipline explicitly.
  • CSV export column order changed — any external positional consumer needs to switch to header-based parsing.

🤖 Generated with Claude Code

Every user now carries a mandatory discipline alongside the existing security
role, organizational circle, and Anthropic profile. Required at creation,
editable, and visible across users table, detail page, profile, and assignment
detail. Bulk CSV import accepts an optional discipline column; existing rows
preserve their value when the column is blank.

Plan, proposal, and implementation notes live in specs/032-user-disciplines/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 22, 2026 08:57
@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ai-developer-hub Ready Ready Preview, Comment May 22, 2026 9:11am

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a new mandatory discipline attribute on users (developer / conception / business) to support attribution and reporting across non-developer colleagues, without overloading the existing security role.

Changes:

  • Adds user_discipline Postgres enum + users.discipline NOT NULL column with default developer (and updates seeds/migration metadata).
  • Extends server actions, validators, and types to require/persist discipline on create, allow edits, and preserve values during CSV upserts when the column is blank.
  • Surfaces discipline across UI (create/edit forms, users table + filter, profile header, assignment detail) and exposes it via /api/profile + user CSV export.

Reviewed changes

Copilot reviewed 29 out of 32 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/api/profile.test.ts Updates profile API unit test fixtures to include discipline.
tests/unit/agent-auth.test.ts Updates agent user test fixture to include discipline.
src/types/index.ts Adds UserDiscipline type and threads it through relevant API/input types.
src/lib/validators.ts Adds discipline enum validation for create/update/bulk-import/inline-create schemas.
src/lib/utils.ts Updates CSV diff helper to treat discipline as “only changed when explicitly provided”.
src/lib/profile-data.ts Includes discipline in computed profile payload.
src/lib/disciplines.ts Adds centralized discipline constants, labels, icons, and defensive narrowing helpers.
src/lib/db/seed.ts Seeds admin user with discipline: "developer".
src/lib/db/seed-agent-user.ts Seeds agent user with discipline: "developer".
src/lib/db/schema.ts Adds user_discipline enum and users.discipline column with default.
src/lib/db/migrations/meta/0021_snapshot.json Captures updated Drizzle migration snapshot for the new enum/column.
src/lib/db/migrations/meta/_journal.json Registers migration 0021_wise_vindicator in the migration journal.
src/lib/db/migrations/0021_wise_vindicator.sql Creates enum + adds users.discipline column (default + NOT NULL).
src/components/profile/profile-header.tsx Displays discipline badge (icon + label) on /profile.
src/components/inline-user-form.tsx Requires discipline selection for GitHub sync inline user creation.
src/components/edit-user-dialog.tsx Adds discipline select to the edit-user dialog.
src/app/users/users-table.tsx Adds discipline column (sortable) and faceted filter option for users table.
src/app/users/new/new-user-form.tsx Requires discipline selection when creating a user via /users/new.
src/app/users/import/bulk-import-form.tsx Adds discipline parsing/validation, preview display, and conditional upsert behavior for CSV import.
src/app/users/[id]/user-detail-client.tsx Displays discipline in header and allows editing in the user detail form.
src/app/settings/sync/github-member-sync-sheet.tsx Carries discipline through sync “create user” resolution payload.
src/app/assignments/[id]/page.tsx Passes assignee discipline to client for assignment detail display.
src/app/assignments/[id]/assignment-detail-client.tsx Displays assignee discipline alongside assignment header details.
src/app/api/profile/route.ts Adds discipline to profile API response payload.
src/app/api/export/users/route.ts Adds discipline column to exported users CSV (between circle and role).
src/actions/users.ts Persists discipline on create/update; preserves discipline on CSV upsert when omitted; returns discipline for previews.
src/actions/github-sync.ts Sets discipline on sync-created users (default for import-as-is; explicit from inline-create).
specs/032-user-disciplines/proposal.html Adds spec documentation for the discipline feature proposal.
specs/032-user-disciplines/plan.html Adds implementation plan documentation for discipline rollout.
specs/032-user-disciplines/implementation-notes.html Adds running implementation notes and recorded tradeoffs/deviations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

role: string;
/** Discipline value extracted from CSV. Empty string = CSV did not supply a value. */
discipline: string;
/** True when the CSV had a discipline column AND this row populated it. */
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the docstring in 776a7ed to match the implementation. Treating column missing and column present but blank the same is intentional for the upsert semantics — both should leave the existing discipline untouched, so the parser doesn't need to distinguish them.

Comment on lines 184 to 189
const newUsers: Array<{
githubLogin: string;
name: string;
email: string;
discipline: "developer" | "conception" | "business";
}> = [];
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 776a7ed — added UserDiscipline to the existing type import block and used it in the local newUsers array type so the union no longer drifts if the enum expands.

Comment thread src/components/inline-user-form.tsx Outdated
githubLogin,
name: defaultName,
email: defaultEmail,
discipline: undefined as unknown as InlineUserCreationInput["discipline"],
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 776a7ed — dropped the undefined as unknown as ... cast and omitted the discipline key from defaultValues entirely. zodResolver still enforces it as required at submit time.

Comment thread src/app/users/new/new-user-form.tsx Outdated
circle: undefined,
role: "viewer",
// No default — force a conscious pick.
discipline: undefined as unknown as UserInput["discipline"],
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 776a7ed — same treatment as the inline form: removed the undefined as unknown as UserInput["discipline"] cast and omitted the key from defaultValues. The Zod resolver still enforces discipline as required at submit.

- bulk-import-form: docstring for `disciplineProvided` now matches the
  implementation (treats "column missing" and "row blank" the same).
- github-member-sync-sheet: use `UserDiscipline` instead of re-declaring
  the union literal so the type stays in sync with the enum.
- inline-user-form / new-user-form: drop `undefined as unknown as ...`
  casts in RHF `defaultValues`; the DeepPartial shape accepts omission,
  and zodResolver still enforces a conscious pick on submit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@studert studert merged commit dfaa2af into main May 22, 2026
7 checks passed
@studert studert deleted the worktree-introduce-roles branch May 22, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants