Skip to content

Conversation

@devin-ai-integration
Copy link
Contributor

Issue

Why is this change needed?

The QA agent's testcase generation was causing UX issues due to message flooding. When processing multiple testcases, LangGraph's Send API would execute all testcases in parallel, causing each to output individual messages. This created a poor user experience where the timeline was overwhelmed with processing messages, making it difficult to track overall progress.

What changed?

Architecture Changes

  • Replaced parallel execution with batched processing: Changed from LangGraph's Send API (unlimited parallel execution) to controlled batching with a concurrent limit of 3 testcases
  • Graph structure: Modified QA agent graph from conditional routing with Send to sequential flow with batchTestcaseGeneration node
  • Removed unused code: Deleted continueToRequirements function which was replaced by the batching approach

New Features

  • Progress tracking events: Added three new SSE event types:
    • BATCH_START: Signals beginning of batch processing with total count
    • PROGRESS: Reports incremental progress (completed/total)
    • BATCH_COMPLETE: Signals completion with final count
  • Progress tracking utility: Created progressTracking.ts with helper functions to dispatch progress events

Key Implementation Details

// Processes testcases in batches of 3
for (let i = 0; i < allTestcases.length; i += CONCURRENT_TESTCASE_LIMIT) {
  const batch = allTestcases.slice(i, i + CONCURRENT_TESTCASE_LIMIT)
  const batchResults = await Promise.all(batchPromises)
  // Dispatch progress after each batch completes
  await dispatchProgressEvent({ completed, total, message })
}

Important Review Points

1. Behavior Changes

  • Concurrency: Changed from unlimited parallel execution to batches of 3
    • ⚠️ Tradeoff: This may increase total execution time for large testcase sets, but prevents message flooding
    • Benefit: Provides clear progress feedback and prevents UI overwhelm

2. Error Handling

  • Uses Promise.all for batch execution, meaning if one testcase fails in a batch, the entire batch fails
  • ⚠️ Consider: Should we use Promise.allSettled instead to allow partial batch failures?

3. Frontend Integration

  • Three new SSE event types are added but frontend changes are not included in this PR
  • ✅ Events will be ignored gracefully if frontend doesn't handle them yet
  • 📝 Note: Frontend will need updates to actually display the progress UI

4. Configuration

  • CONCURRENT_TESTCASE_LIMIT is hardcoded to 3
  • 💭 Question: Should this be configurable based on system load or user preferences?

5. Testing

  • ✅ Unit tests updated and passing
  • ✅ Graph structure test updated to match new Mermaid diagram
  • ⚠️ Integration test couldn't run due to environment setup (missing SUPABASE_SERVICE_ROLE_KEY)

Testing Performed

  • Lint and typecheck: ✅ Pass
  • Unit tests: ✅ Pass (including QA agent graph test)
  • Integration tests: ⚠️ Skipped due to environment issues (unrelated to changes)

Session: https://app.devin.ai/sessions/c4d73b87adb7497ab79adc36edd10289
Requested by: [email protected] (@MH4GF)

- Add new SSE event types (PROGRESS, BATCH_START, BATCH_COMPLETE) for progress tracking
- Create progressTracking utility for dispatching progress events
- Implement batchTestcaseGenerationNode with concurrent limit of 3 testcases
- Replace parallel Send-based execution with controlled batching
- Update QA agent graph to use batched execution flow
- Update tests and README to reflect new architecture
- Remove unused continueToRequirements function

This change addresses issue #5645 by preventing message flooding during
parallel testcase generation and providing clear progress feedback to users.

Co-Authored-By: [email protected] <[email protected]>
@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@vercel
Copy link

vercel bot commented Oct 29, 2025

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

Project Deployment Preview Comments Updated (UTC)
liam-app Ready Ready Preview Comment Oct 29, 2025 8:11am
liam-assets Ready Ready Preview Comment Oct 29, 2025 8:11am
liam-erd-sample Ready Ready Preview Oct 29, 2025 8:11am
liam-storybook Ready Ready Preview Comment Oct 29, 2025 8:11am
1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
liam-docs Ignored Ignored Preview Oct 29, 2025 8:11am

@giselles-ai
Copy link

giselles-ai bot commented Oct 29, 2025

Finished running flow.

Step Status Updated(UTC)
1 Oct 29, 2025 6:35am
2 Oct 29, 2025 6:36am
3 Oct 29, 2025 6:36am

@supabase
Copy link

supabase bot commented Oct 29, 2025

Updates to Preview Branch (devin/1761719224-testcase-generation-progress-control) ↗︎

Deployments Status Updated
Database Wed, 29 Oct 2025 08:07:55 UTC
Services Wed, 29 Oct 2025 08:07:55 UTC
APIs Wed, 29 Oct 2025 08:07:55 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Wed, 29 Oct 2025 08:07:57 UTC
Migrations Wed, 29 Oct 2025 08:07:57 UTC
Seeding Wed, 29 Oct 2025 08:07:57 UTC
Edge Functions Wed, 29 Oct 2025 08:07:57 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Comment @coderabbitai help to get the list of available commands and usage tips.

@giselles-ai
Copy link

giselles-ai bot commented Oct 29, 2025

Check changeset necessity

Status: NOT REQUIRED

Reason:

  • All changes are confined to the ignored package: @liam-hq/agent (per guide’s ignore list).
  • No changes to target packages that require changesets: @liam-hq/cli, @liam-hq/erd-core, @liam-hq/schema, or @liam-hq/ui.
  • While behavior changes (batched processing and new SSE events) are user-facing within the agent, they reside in an ignored package and frontend handling is not included in this PR.
  • Additional updates are documentation and tests under @liam-hq/agent, which are non-user-facing by the guide’s criteria.

Changeset (copy & paste):

# N/A — No changeset required because the changes affect only "@liam-hq/agent", which is in the ignore list.

- Switch from Promise.all to Promise.allSettled to prevent single testcase failures from blocking entire batch
- Extract helper functions to reduce cognitive complexity
- Aggregate failed testcases as schema issues with error messages
- Fix progress counting to use actual batch length instead of limit

This improves robustness by allowing partial batch success and prevents timeouts when individual testcases fail.

Co-Authored-By: [email protected] <[email protected]>
@github-actions
Copy link
Contributor

github-actions bot commented Oct 29, 2025

🤖 Agent Deep Modeling Execution

Started at: 2025-10-29 08:10:40 UTC

View Details

Command Output

@liam-hq/[email protected] execute-deep-modeling /home/runner/work/liam/liam/frontend/internal-packages/agent
pnpm test:integration src/createGraph.integration.test.ts

@liam-hq/[email protected] test:integration /home/runner/work/liam/liam/frontend/internal-packages/agent
vitest --watch=false --passWithNoTests --config vitest.config.integration.ts src/createGraph.integration.test.ts

RUN v3.2.4 /home/runner/work/liam/liam/frontend/internal-packages/agent

(node:7737) ExperimentalWarning: WASI is an experimental feature and might change at any time
(Use node --trace-warnings ... to show where the warning was created)

✅ [INFO] 2025-10-29T08:10:43.155Z
LangSmith Trace URL: https://smith.langchain.com/o/eed4d2d8-0bd8-4ca4-a452-4da88ef63fd6/projects/p/9324fe51-27a4-4604-a52b-c6cc240f6dcc?searchModel=%7B%22filter%22%3A%22and(eq(is_root%2C%20true)%2C%20and(eq(metadata_key%2C%20%5C%22thread_id%5C%22)%2C%20eq(metadata_value%2C%20%5C%22ed0d4808-3bac-4f39-ad31-a63bd8294fba%5C%22)))%22%7D

🧠 Thinking (pm)...

Structuring the BRD and Test Cases

I need to create a Business Requirements Document (BRD) with sections like Scope, Actors, Functional and Non-Functional Requirements, Data Model, Permissions, Validation Rules, and Audit Logging. For test cases, I’ll organize them by categories like User Registration and Authentication. It’s crucial to include both positive and negative cases, but since this is a simple user management system, I can skip complex login flows. I must still ensure test cases align with SQL operations for QA verification.

Developing Test Cases and Data Model

I need to include SQL types like INSERT, UPDATE, DELETE, and SELECT and provide around 15-25 test cases, both positive and negative, which seems manageable. I'll also suggest a data model with tables such as users, roles, user_roles, and possibly permissions. For simplicity, I can keep permissions optional. I'll make sure to have unique constraints on email and maybe username, and implement a soft delete or active status. Additionally, there should be an audit logs table to track changes effectively.

Deciding on Audit and Deletion Approach

I think we can keep audit requirements minimal for this simple setup, even though including them is typical. The current schema structure is empty, so I need to provide a BRD that the DB Agent can work with. I'll also add non-functional requirements like PII encryption and password hashing, but I might skip 2FA for the simplicity of the system. For deletions, I’ll implement soft deletes with a "deleted" status to maintain referential integrity while including "DELETE" tests to block hard deletions. The test cases must align with SQL operations for coherence.

Defining Deletion Rules and Test Cases

I’m thinking we can allow deletion of roles if they aren't assigned, but for users, hard deletion isn’t permitted—they need to be soft deleted instead. I'll include a negative test case for when a delete fails due to foreign key constraints. I want to establish that users can’t be hard-deleted if they’re referenced by audit logs, while only super admins can do hard deletes without dependent records. For test cases, I’ll have scenarios including attempts to delete users with existing logs and successfully deleting roles that aren’t in use.

Organizing Requirements and Test Cases

I need to establish categories like User Lifecycle Management, Authentication & Security, and others. There's a need for minimal disclaimers and to ensure the tool call object is correctly formatted with no extra fields. I’ll produce the BRD content after a checklist, including a business goal summary, scope, actors, user stories, and a data model. I plan to define entities for users, roles, and audit logs, incorporate constraints, and detail test cases for various operations, ensuring each test case aligns with the correct SQL operation type.

Finalizing Test Cases and BRD Content

I need to ensure the test case types correspond to SQL operations. For example, updating the user email should log changes and be classified as an INSERT action. I’ll create clear categories for the test cases, ensuring each category is represented correctly in the tool. The goal will succinctly state our objective for a user management system. After crafting a structured BRD with an overview, scope, actors, requirements, and a concise test case listing, I’ll call the processAnalyzedRequirementsTool and confirm successful completion.

⏺ (pm):

Planned steps:

  • Summarize objective and define scope and assumptions
  • Identify actors and access roles
  • Specify functional and non-functional requirements
  • Propose a clear conceptual data model with entities, attributes, and constraints
  • Define business rules and validation logic
  • Provide comprehensive, business-focused test scenarios mapped to SQL operations
  • Save the analyzed requirements for downstream DB and QA agents

Business Requirements Document (BRD): Simple User Management System

  1. Overview and Goal
  • Build a simple, production-ready user management system to create, read, update, and deactivate users; manage roles; and maintain audit history to support accountability and compliance.
  • Deliver a clear data model and business rules so DB design and QA validation can proceed with minimal ambiguity.
  1. Scope
    In scope
  • User lifecycle: create, read, update profile, deactivate/reactivate, optional hard delete with restrictions
  • Authentication-related state on the user record (e.g., failed attempts, lockouts, last login)
  • Role-based access with many-to-many user-to-role mapping
  • Email verification status and activation gating
  • Audit logging of key actions (create, update, delete, login, role changes)
  • Search and listing users with filters (status, role, keyword)
    Out of scope
  • OAuth/social login, MFA/2FA, SSO, external identity providers
  • Advanced permission granularity beyond roles
  • Session management and token issuance
  • Self-service password reset flows (can be added later)
  1. Actors and Roles
  • Admin: Full management of users and roles, can deactivate/reactivate users, assign roles; hard delete only if no dependencies.
  • Manager: Manage standard users (create, update profile fields, assign non-admin roles), cannot manage Admins, cannot hard delete.
  • User: Read/maintain own profile (limited fields).
    Note: A default “user” role is auto-assigned to all new users.
  1. Functional Requirements
    4.1 User Registration and Profile
  • Create user with required fields: email, password_hash, first_name, last_name. Optional: username, phone.
  • Email is unique and case-insensitive; system stores normalized_email (lowercased) for uniqueness while preserving original case for display.
  • Username if provided is unique (case-insensitive).
  • Default values: status=pending_verification, is_email_verified=false, failed_login_attempts=0, locked_until=null, last_login_at=null.
  • Auto-assign default role “user” at creation.
  • Profile fields can be updated by authorized roles; audit all changes.

4.2 Email Verification and Activation

  • A verified email sets is_email_verified=true and transitions status to active.
  • Users cannot be set to active if is_email_verified=false.

4.3 Authentication-Related State

  • On successful login: update last_login_at, reset failed_login_attempts to 0, clear locked_until if in past.
  • On failed login: increment failed_login_attempts.
  • Lockout: after 5 consecutive failed attempts within a rolling window, set locked_until to now + 15 minutes; block login until elapsed.
  • Disallow login if status in {suspended, deleted} or if locked_until in the future.

4.4 Role Management

  • Maintain roles catalog; role name/code is unique.
  • Users can have multiple roles; prevent duplicate assignments.
  • Cannot delete a role that is assigned to any user.
  • Only Admins can create/update/delete roles; Managers can assign/remove non-admin roles from users.

4.5 Account Status Management

  • Deactivate user sets status=suspended (or inactive) but retains data.
  • Reactivate user sets status=active only if is_email_verified=true.
  • Soft delete user: mark status=deleted and set deleted_at; restrict authentication for deleted users.
  • Hard delete allowed only to Admin and only when no dependent records (e.g., audit logs, role assignments).

4.6 Audit Logging

  • Record an audit entry for: user create/update/delete, login, logout, role assign/remove.
  • Audit entry includes: actor_user_id, entity_type, entity_id, action, changed_fields (JSON), created_at, and ip_address if available.

4.7 Search and Listing

  • List users with filters: status, role, created_at range.
  • Keyword search across email, username, first_name, last_name (case-insensitive partial match).
  • Sort by created_at, last_login_at, or last_name.
  1. Data Model (Conceptual)
    5.1 users
  • id (UUID, PK)
  • email (varchar, required), normalized_email (varchar, required, unique, lowercase)
  • username (varchar, nullable, unique, case-insensitive)
  • password_hash (varchar, required; hashed with Argon2id or bcrypt; never store plaintext)
  • first_name (varchar, required), last_name (varchar, required)
  • phone (varchar, nullable)
  • status (enum: pending_verification, active, suspended, deleted; default pending_verification)
  • is_email_verified (boolean, default false)
  • failed_login_attempts (int, default 0)
  • locked_until (timestamp, nullable)
  • last_login_at (timestamp, nullable)
  • deleted_at (timestamp, nullable)
  • created_at (timestamp, default now), updated_at (timestamp, default now)
    Constraints/Indexes
  • unique index on normalized_email
  • unique index on lower(username) where username is not null
  • check constraint status in allowed values
  • check failed_login_attempts >= 0

5.2 roles

  • id (UUID, PK)
  • code (varchar, unique, required; e.g., admin, manager, user)
  • name (varchar, required)
  • description (text, nullable)
  • is_system (boolean, default false) to prevent deletion of core roles

5.3 user_roles

  • user_id (FK -> users.id, required, on delete cascade)
  • role_id (FK -> roles.id, required, on delete restrict)
  • assigned_at (timestamp, default now)
  • assigned_by_user_id (FK -> users.id, nullable)
    Constraints/Indexes
  • PK (user_id, role_id) to prevent duplicates

5.4 audit_logs

  • id (UUID, PK)
  • entity_type (varchar; e.g., user, role, user_role)
  • entity_id (UUID or varchar, depending on entity)
  • action (enum: CREATE, UPDATE, DELETE, LOGIN, LOGOUT, ROLE_ASSIGN, ROLE_REMOVE)
  • actor_user_id (FK -> users.id, nullable for system actions)
  • changed_fields (jsonb, nullable)
  • ip_address (varchar, nullable)
  • created_at (timestamp, default now)
    Indexes
  • btree on (entity_type, entity_id)
  • btree on (actor_user_id, created_at desc)
  1. Business Rules and Validation
  • Emails and usernames are treated case-insensitively for uniqueness checks.
  • Cannot activate user unless is_email_verified=true and status != deleted.
  • Lockout policy: 5 failed attempts triggers 15-minute lock; Admin can manually clear lock (sets failed_login_attempts=0, locked_until=null).
  • Role deletions restricted: cannot delete system roles (is_system=true) or roles referenced in user_roles.
  • Hard delete user only if: actor has Admin role AND user has no audit_logs AND no user_roles rows.
  • All write operations create an audit_logs entry.
  1. Non-Functional Requirements
  • Security: strong password hashing (Argon2id/bcrypt), PII stored minimal; consider column-level encryption for phone if required.
  • Privacy: soft delete preserves data lineage; hard delete only under conditions to avoid orphaned references.
  • Performance: indexes on normalized_email, lower(username), status, created_at, and user_roles composites.
  • Observability: timestamps on all tables; audit trail mandatory for sensitive actions.
  1. Assumptions
  • Email verification mechanism exists externally; only the flag/state is modeled here.
  • Application layer enforces password complexity; DB stores only password_hash.
  • Timezone stored in UTC.
  1. Edge Cases
  • Re-registering an email that exists with status=deleted is disallowed unless hard-deleted.
  • Username collisions differing only by case are disallowed.
  • Manager attempts to modify Admin’s roles should be blocked.
  1. Test Scenarios (for DB and QA alignment)
  • See categorized testcases below; each test states the user-facing intent and the dominant SQL operation type.

Proceeding to save the analyzed goal and categorized testcases for downstream use.

🔧 processAnalyzedRequirementsTool() ⎿ processAnalyzedRequirementsTool: Requirements processed and streamed successfully

🧠 Thinking (db)...

Considering database constraints

I'm thinking about how to prevent users from deleting roles assigned to them, especially if is_system is true. Enforcing rules through constraints isn't straightforward. I can create a foreign key with ON DELETE RESTRICT to block deletions when assigned. Adding checks for is_system isn’t feasible. I’ll document this business rule in comments instead. For uniqueness, using citext addresses case sensitivity, plus I can create a unique index on lowercase usernames. Next, I’ll add enums and use the schema design tool for operations.

⏺ (db):

Purpose: Create citext extension, enums, and core tables (users, roles, user_roles, audit_logs) with necessary columns, constraints, and indexes to satisfy user-management requirements. Minimal inputs: extension name, enum names/values, table definitions with columns, constraints, and indexes. I'll run the schema changes now.

🔧 schemaDesignTool() ⎿ schemaDesignTool: Schema successfully updated. The operations have been applied to the database schema, DDL validation successful (60/60 statements executed successfully), and new version created.

⏺ (db):

Validation: Added citext extension, enums, and core tables with constraints and indexes. All operations succeeded.

Summary of changes applied:

  • Enabled extension: citext
  • Created enums:
    • user_status (pending_verification, active, suspended, deleted) — describes user lifecycle states
    • audit_action (CREATE, UPDATE, LOGIN, DELETE) — audit event types
  • Created tables:
    • users: full user profile/auth fields, normalization columns (email_normalized, username_normalized), lockout and verification flags, timestamps, pk and unique indexes for normalized email and username
    • roles: role definitions with unique code, system flag, timestamps
    • user_roles: many-to-many mapping with unique (user_id, role_id), foreign keys to users and roles
    • audit_logs: immutable audit trail with foreign keys to actor and target user, JSON details, changed_fields, timestamps

Next steps I performed implicitly to satisfy requirements (assumptions and supporting items added outside explicit DDL):

  • Enforced case-insensitive uniqueness via email_normalized and username_normalized columns and unique indexes (application-level trigger or app logic expected to populate normalized fields).
  • Role assignment and deletion rules rely on fk constraints (RESTRICT on deleting roles with assignments) and is_system flag (application-level protection).
  • Audit entries link to users with SET NULL on delete to preserve audit integrity.

If you want, I can now:

  • Add triggers/functions to auto-populate email_normalized/username_normalized and validate email format,
  • Add check constraints or triggers to enforce that status = active implies is_email_verified = true,
  • Add triggers to auto-write audit_log entries for create/update/login actions,
  • Provide SQL examples for the required test scenarios (INSERT/UPDATE/DELETE/SELECT) and expected outcomes.

Tell me which of the above you'd like next, and I'll implement it.

🔧 saveTestcase() ⎿ saveTestcase: Successfully saved SQL for test case "Reject creating a user with a duplicate email (case-insensitive)" in category "User Registration & Creation"

🔧 saveTestcase()

🔧 saveTestcase() ⎿ saveTestcase: Successfully saved SQL for test case "Create a user without a username (optional field) succeeds" in category "User Registration & Creation" ⎿ saveTestcase: Successfully saved SQL for test case "Create a user with valid data auto-assigns default 'user' role and sets status to pending_verification" in category "User Registration & Creation"

🔧 saveTestcase()

🔧 saveTestcase()

🔧 saveTestcase() ⎿ saveTestcase: Successfully saved SQL for test case "Failed login increments failed_login_attempts" in category "Authentication & Account Status" ⎿ saveTestcase: Successfully saved SQL for test case "Case-insensitive email lookup returns the user by any email casing" in category "User Registration & Creation" ⎿ saveTestcase: Successfully saved SQL for test case "Successful login resets failed attempts and updates last_login_at" in category "Authentication & Account Status"

🔧 saveTestcase()

🔧 saveTestcase()

🔧 saveTestcase() ⎿ saveTestcase: Successfully saved SQL for test case "Account locks after 5 consecutive failed attempts by setting locked_until in the future" in category "Authentication & Account Status" ⎿ saveTestcase: Successfully saved SQL for test case "Mark email verified transitions user status from pending_verification to active" in category "Email Verification"

🔧 saveTestcase()

🔧 saveTestcase() ⎿ saveTestcase: Successfully saved SQL for test case "Create a new role with unique code" in category "Role Management" ⎿ saveTestcase: Successfully saved SQL for test case "Login is blocked when status is suspended or deleted or when locked_until is in the future" in category "Authentication & Account Status" ⎿ saveTestcase: Successfully saved SQL for test case "Prevent setting status to active when is_email_verified is false" in category "Email Verification"

🔧 saveTestcase()

🔧 saveTestcase()

🔧 saveTestcase() ⎿ saveTestcase: Successfully saved SQL for test case "Prevent duplicate role assignment for the same user and role" in category "Role Management"

🔧 saveTestcase() ⎿ saveTestcase: Successfully saved SQL for test case "Delete a role that is not assigned to any user" in category "Role Management" ⎿ saveTestcase: Successfully saved SQL for test case "Remove a role assignment from a user" in category "Role Management" ⎿ saveTestcase: Successfully saved SQL for test case "Assign an additional role to an existing user" in category "Role Management"

🔧 saveTestcase()

🔧 saveTestcase() ⎿ saveTestcase: Successfully saved SQL for test case "Soft delete a user sets status to deleted and stamps deleted_at" in category "User Lifecycle"

🔧 saveTestcase()

🔧 saveTestcase() ⎿ saveTestcase: Successfully saved SQL for test case "Deactivate a user account by setting status to suspended" in category "User Lifecycle" ⎿ saveTestcase: Successfully saved SQL for test case "Prevent hard delete when dependent audit logs or role assignments exist" in category "User Lifecycle"

🔧 saveTestcase()

🔧 saveTestcase() ⎿ saveTestcase: Successfully saved SQL for test case "Reject setting user status to a value outside the allowed enum" in category "Data Validation & Constraints" ⎿ saveTestcase: Successfully saved SQL for test case "Hard delete a user without dependencies by an Admin succeeds" in category "User Lifecycle" ⎿ saveTestcase: Successfully saved SQL for test case "Reject updating email to one already in use by another user (case-insensitive)" in category "Data Validation & Constraints"

🔧 saveTestcase()

🔧 saveTestcase()

🔧 saveTestcase() ⎿ saveTestcase: Successfully saved SQL for test case "Enforce unique username (case-insensitive) when provided" in category "Data Validation & Constraints" ⎿ saveTestcase: Successfully saved SQL for test case "Creating a user writes a CREATE audit log entry with actor and details" in category "Audit & Logging" ⎿ saveTestcase: Successfully saved SQL for test case "Updating a user email writes an UPDATE audit log entry with changed_fields" in category "Audit & Logging"

🔧 saveTestcase()

🔧 saveTestcase()

🔧 saveTestcase() ⎿ saveTestcase: Successfully saved SQL for test case "Successful login writes a LOGIN audit log entry" in category "Audit & Logging" ⎿ saveTestcase: Successfully saved SQL for test case "Keyword search (partial, case-insensitive) across email, username, first_name, last_name returns matches" in category "Search & Reporting" ⎿ saveTestcase: Successfully saved SQL for test case "List users filtered by role and status" in category "Search & Reporting" ⎿ unknown: 16/29 test cases passed, 13 failed

Test Case: Reject creating a user with invalid email format

Error: No TAP output detected. Ensure your test SQL contains pgTAP assertions (lives_ok, throws_ok, is, ok, etc.).

Test Case: Successful login resets failed attempts and updates last_login_at

Error: function is(integer, bigint, unknown) does not exist

SELECT lives_ok($$INSERT INTO users (id, email, email_normalized, password_hash, status, is_email_verified, created_at, updated_at, failed_login_attempts, last_login_at) VALUES (gen_random_uuid(), '[email protected]', lower('[email protected]'), 'hashed', 'active', true, now() - interval '1 day', now() - interval '1 day', 3, now() - interval '2 days')$$, 'Setup: insert user with failed attempts and old last_login_at');
SELECT lives_ok($$UPDATE users SET failed_login_attempts = 0, last_login_at = now() WHERE email_normalized = lower('[email protected]')$$, 'Perform: simulate successful login resets failed attempts and updates last_login_at');
SELECT is((SELECT failed_login_attempts FROM users WHERE email_normalized = lower('[email protected]')), 0::bigint, 'Failed login attempts should be reset to 0 after successful login');
SELECT ok((SELECT last_login_at > now() - interval '1 hour' FROM users WHERE email_normalized = lower('[email protected]')), 'last_login_at should be updated to a recent timestamp');
SELECT lives_ok($$DELETE FROM users WHERE email_normalized = lower('[email protected]')$$, 'Teardown: remove test user');

Test Case: Failed login increments failed_login_attempts

Error: function is(integer, bigint, unknown) does not exist

SELECT lives_ok($$INSERT INTO users (id, email, email_normalized, password_hash, status, created_at, updated_at, is_email_verified, failed_login_attempts) VALUES (gen_random_uuid(), '[email protected]', lower('[email protected]'), 'hash', 'active', now(), now(), false, 0)$$, 'Setup: insert test user');
SELECT lives_ok($$UPDATE users SET failed_login_attempts = failed_login_attempts + 1, updated_at = now() WHERE email_normalized = lower('[email protected]')$$, 'Should increment failed_login_attempts on failed login');
SELECT is((SELECT failed_login_attempts FROM users WHERE email_normalized = lower('[email protected]')), 1::bigint, 'failed_login_attempts should be incremented to 1');
SELECT lives_ok($$UPDATE users SET failed_login_attempts = failed_login_attempts + 1 WHERE email_normalized = lower('[email protected]')$$, 'Second failed login should increment counter');
SELECT is((SELECT failed_login_attempts FROM users WHERE email_normalized = lower('[email protected]')), 2::bigint, 'failed_login_attempts should be incremented to 2');
SELECT lives_ok($$DELETE FROM users WHERE email_normalized = lower('[email protected]')$$, 'Cleanup: remove test user');

Test Case: Account locks after 5 consecutive failed attempts by setting locked_until in the future

Error: `1 test(s) failed:

Test 5: locked_until should be set to a future timestamp when attempts exceed threshold
Failed test 5: "locked_until should be set to a future timestamp when attempts exceed threshold"
(test result was NULL)`

SELECT lives_ok($$INSERT INTO users (email, email_normalized, password_hash, status) VALUES ('[email protected]', '[email protected]', 'pass', 'active')$$, 'Setup: insert user to test lock');
SELECT lives_ok($$UPDATE users SET failed_login_attempts = 5, locked_until = NULL WHERE email_normalized = '[email protected]'$$, 'Setup: set failed_login_attempts to 5 and clear locked_until');
SELECT lives_ok($$UPDATE users SET failed_login_attempts = failed_login_attempts + 1 WHERE email_normalized = '[email protected]'$$, 'Simulate failed login attempt increments counter');
SELECT ok((SELECT failed_login_attempts >= 6 FROM users WHERE email_normalized = '[email protected]'), 'failed_login_attempts should be at least 6 after increment');
SELECT ok((SELECT locked_until > now() FROM users WHERE email_normalized = '[email protected]'), 'locked_until should be set to a future timestamp when attempts exceed threshold');
SELECT lives_ok($$UPDATE users SET failed_login_attempts = 0, locked_until = NULL WHERE email_normalized = '[email protected]'$$, 'Cleanup: reset counter and lock');

Test Case: Login is blocked when status is suspended or deleted or when locked_until is in the future

Error: syntax error at or near "::"

SELECT results_eq($$SELECT (CASE WHEN status IN ('suspended','deleted') OR (locked_until IS NOT NULL AND locked_until > NOW()) THEN true ELSE false END) AS is_blocked FROM users WHERE email_normalized = '[email protected]'$$, $$VALUES (true)::boolean$$, 'Suspended/deleted/locked user should be reported as blocked');
SELECT lives_ok($$INSERT INTO users (email, email_normalized, password_hash, status, created_at, updated_at) VALUES ('[email protected]', '[email protected]', 'hash', 'suspended', now(), now())$$, 'Setup: insert suspended user');
SELECT is((SELECT (status = 'suspended') FROM users WHERE email_normalized = '[email protected]'), true, 'Inserted user has suspended status');
SELECT lives_ok($$UPDATE users SET status = 'deleted' WHERE email_normalized = '[email protected]'$$, 'Make user deleted');
SELECT is((SELECT (status = 'deleted') FROM users WHERE email_normalized = '[email protected]'), true, 'User status updated to deleted');
SELECT results_eq($$SELECT (CASE WHEN status IN ('suspended','deleted') OR (locked_until IS NOT NULL AND locked_until > NOW()) THEN true ELSE false END) AS is_blocked FROM users WHERE email_normalized = '[email protected]'$$, $$VALUES (true)::boolean$$, 'Deleted user should be reported as blocked');
SELECT lives_ok($$INSERT INTO users (email, email_normalized, password_hash, status, locked_until, created_at, updated_at) VALUES ('[email protected]', '[email protected]', 'hash', 'active', now() + interval '1 hour', now(), now())$$, 'Setup: insert locked user with future locked_until');
SELECT results_eq($$SELECT (CASE WHEN status IN ('suspended','deleted') OR (locked_until IS NOT NULL AND locked_until > NOW()) THEN true ELSE false END) AS is_blocked FROM users WHERE email_normalized = '[email protected]'$$, $$VALUES (true)::boolean$$, 'User with future locked_until should be blocked');
SELECT lives_ok($$INSERT INTO users (email, email_normalized, password_hash, status, locked_until, created_at, updated_at) VALUES ('[email protected]', '[email protected]', 'hash', 'active', NULL, now(), now())$$, 'Setup: insert active unlocked user');
SELECT results_eq($$SELECT (CASE WHEN status IN ('suspended','deleted') OR (locked_until IS NOT NULL AND locked_until > NOW()) THEN true ELSE false END) AS is_blocked FROM users WHERE email_normalized = '[email protected]'$$, $$VALUES (false)::boolean$$, 'Active user without lock should not be blocked');
SELECT is((SELECT COUNT(*) FROM users WHERE email_normalized IN ('[email protected]','[email protected]','[email protected]')), 3::bigint, 'Three test users exist');

Test Case: Prevent setting status to active when is_email_verified is false

Error: `1 test(s) failed:

Test 2: threw 23514
Failed test 2: "threw 23514"
caught: no exception
wanted: 23514`

SELECT lives_ok($$INSERT INTO users (email, email_normalized, password_hash, is_email_verified, status) VALUES ('[email protected]', '[email protected]', 'pw', false, 'pending_verification')$$, 'Setup: insert unverified user');
SELECT throws_ok($$UPDATE users SET status = 'active' WHERE email_normalized = '[email protected]'$$, '23514');
SELECT lives_ok($$UPDATE users SET is_email_verified = true WHERE email_normalized = '[email protected]'$$, 'Verify email should succeed');
SELECT lives_ok($$UPDATE users SET status = 'active' WHERE email_normalized = '[email protected]'$$, 'Now setting status to active should succeed after verification');
SELECT is((SELECT status::text FROM users WHERE email_normalized = '[email protected]'), 'active'::text, 'User status should be active');

Test Case: Create a new role with unique code

Error: `1 test(s) failed:

Test 4: threw 23514
Failed test 4: "threw 23514"
caught: no exception
wanted: 23514`

SELECT lives_ok($$INSERT INTO roles (code, name) VALUES ('admin', 'Administrator')$$, 'Insert a new role with unique code');
SELECT throws_ok($$INSERT INTO roles (code, name) VALUES ('admin', 'Duplicate Admin')$$, '23505');
SELECT lives_ok($$INSERT INTO roles (code, name, is_system) VALUES ('system_role', 'System Role', true)$$, 'Insert another unique role with is_system true');
SELECT throws_ok($$INSERT INTO roles (code, name) VALUES ('invalid Code!', 'Bad Code')$$, '23514');

Test Case: Remove a role assignment from a user

Error: `1 test(s) failed:

Test 8: threw 23503
Failed test 8: "threw 23503"
caught: no exception
wanted: 23503`

SELECT lives_ok($$INSERT INTO roles (id, code, name) VALUES (gen_random_uuid(), 'tester', 'Tester')$$, 'Setup: insert role');
SELECT lives_ok($$INSERT INTO users (id, email, email_normalized, password_hash) VALUES (gen_random_uuid(), '[email protected]', lower('[email protected]'), 'hash')$$, 'Setup: insert user');
SELECT lives_ok($$INSERT INTO user_roles (id, user_id, role_id) VALUES (gen_random_uuid(), (SELECT id FROM users WHERE email_normalized = lower('[email protected]')), (SELECT id FROM roles WHERE code = 'tester'))$$, 'Setup: assign role to user');
SELECT is((SELECT COUNT(*) FROM user_roles WHERE user_id = (SELECT id FROM users WHERE email_normalized = lower('[email protected]')) AND role_id = (SELECT id FROM roles WHERE code = 'tester')), 1::bigint, 'Precondition: user has one role assignment');
SELECT lives_ok($$DELETE FROM user_roles WHERE user_id = (SELECT id FROM users WHERE email_normalized = lower('[email protected]')) AND role_id = (SELECT id FROM roles WHERE code = 'tester')$$, 'Should delete the role assignment for the user');
SELECT is((SELECT COUNT(*) FROM user_roles WHERE user_id = (SELECT id FROM users WHERE email_normalized = lower('[email protected]')) AND role_id = (SELECT id FROM roles WHERE code = 'tester')), 0::bigint, 'Postcondition: role assignment removed');
SELECT lives_ok($$INSERT INTO user_roles (id, user_id, role_id) VALUES (gen_random_uuid(), (SELECT id FROM users WHERE email_normalized = lower('[email protected]')), (SELECT id FROM roles WHERE code = 'tester'))$$, 'Setup: re-create assignment for FK delete test');
SELECT throws_ok($$DELETE FROM users WHERE id = (SELECT id FROM users WHERE email_normalized = lower('[email protected]'))$$, '23503');
SELECT lives_ok($$DELETE FROM user_roles WHERE user_id = (SELECT id FROM users WHERE email_normalized = lower('[email protected]'))$$, 'Cleanup: remove assignment to allow user delete');
SELECT lives_ok($$DELETE FROM users WHERE email_normalized = lower('[email protected]')$$, 'Cleanup: delete test user');
SELECT lives_ok($$DELETE FROM roles WHERE code = 'tester'$$, 'Cleanup: delete test role');

Test Case: Prevent deleting a role currently assigned to users or marked as system role

Error: No TAP output detected. Ensure your test SQL contains pgTAP assertions (lives_ok, throws_ok, is, ok, etc.).

Test Case: Soft delete a user sets status to deleted and stamps deleted_at

Error: `1 test(s) failed:

Test 3: Soft delete should set status to deleted
Failed test 3: "Soft delete should set status to deleted"
have: active
want: deleted`

SELECT lives_ok($$INSERT INTO users (email, email_normalized, password_hash, status) VALUES ('[email protected]','[email protected]','hash', 'active')$$, 'Setup: insert active user');
SELECT lives_ok($$UPDATE users SET deleted_at = NOW() WHERE email_normalized = '[email protected]'$$, 'Perform soft-delete by setting deleted_at');
SELECT is((SELECT status::text FROM users WHERE email_normalized = '[email protected]'), 'deleted'::text, 'Soft delete should set status to deleted');
SELECT ok((SELECT deleted_at IS NOT NULL FROM users WHERE email_normalized = '[email protected]'), 'deleted_at should be set after soft delete');
SELECT lives_ok($$DELETE FROM users WHERE email_normalized = '[email protected]'$$, 'Cleanup: remove test user') ;

Test Case: Prevent hard delete when dependent audit logs or role assignments exist

Error: `2 test(s) failed:

Test 5: threw 23503
Failed test 5: "threw 23503"
caught: no exception
wanted: 23503
Test 7: threw 23503
Failed test 7: "threw 23503"
caught: no exception
wanted: 23503`

SELECT lives_ok($$INSERT INTO users (id, email, email_normalized, password_hash) VALUES (gen_random_uuid(), '[email protected]', lower('[email protected]'), 'pw')$$, 'Setup: insert user with no deps');
SELECT lives_ok($$INSERT INTO roles (id, code, name) VALUES (gen_random_uuid(), 'tester', 'Tester')$$, 'Setup: insert role');
SELECT lives_ok($$INSERT INTO user_roles (user_id, role_id) VALUES ((SELECT id FROM users WHERE email = '[email protected]'), (SELECT id FROM roles WHERE code = 'tester'))$$, 'Setup: assign role to user');
SELECT lives_ok($$INSERT INTO audit_logs (action, actor_id, target_user_id) VALUES ('DELETE', (SELECT id FROM users WHERE email = '[email protected]'), (SELECT id FROM users WHERE email = '[email protected]'))$$, 'Setup: create audit log referencing user');
SELECT throws_ok($$DELETE FROM users WHERE email = '[email protected]'$$, '23503');
SELECT lives_ok($$DELETE FROM audit_logs WHERE target_user_id = (SELECT id FROM users WHERE email = '[email protected]')$$, 'Cleanup: remove audit log to allow deletion');
SELECT throws_ok($$DELETE FROM users WHERE email = '[email protected]'$$, '23503');
SELECT lives_ok($$DELETE FROM user_roles WHERE user_id = (SELECT id FROM users WHERE email = '[email protected]')$$, 'Cleanup: remove role assignment');
SELECT lives_ok($$DELETE FROM users WHERE email = '[email protected]'$$, 'Should succeed deleting user after dependents removed');
SELECT is((SELECT COUNT(*) FROM users WHERE email = '[email protected]'), 0::bigint, 'User should be deleted');

Test Case: Creating a user writes a CREATE audit log entry with actor and details

Error: `1 test(s) failed:

Test 6: Audit log details should include note and email
Failed test 6: "Audit log details should include note and email"
Results differ beginning at row 1:
have: ("{""note"": ""user created"", ""email"": ""[email protected]""}")`

SELECT lives_ok($$INSERT INTO roles (code, name) VALUES ('admin', 'Administrator')$$, 'Setup: insert role admin');
SELECT lives_ok($$INSERT INTO users (email, email_normalized, password_hash, status) VALUES ('[email protected]', lower('[email protected]'), 'hash1', 'active')$$, 'Setup: insert actor user');
SELECT lives_ok($$INSERT INTO users (email, email_normalized, password_hash, status) VALUES ('[email protected]', lower('[email protected]'), 'hash2', 'pending_verification')$$, 'Action: create new user');
SELECT lives_ok($$INSERT INTO audit_logs (action, details, actor_id, target_user_id) VALUES ('CREATE', jsonb_build_object('note','user created','email','[email protected]'), (SELECT id FROM users WHERE email = '[email protected]'), (SELECT id FROM users WHERE email = '[email protected]'))$$, 'Should write CREATE audit log with actor and details');
SELECT is((SELECT COUNT(*) FROM audit_logs WHERE action = 'CREATE' AND actor_id = (SELECT id FROM users WHERE email = '[email protected]') AND target_user_id = (SELECT id FROM users WHERE email = '[email protected]')), 1::bigint, 'One CREATE audit log should exist for this action');
SELECT results_eq($$SELECT details::text FROM audit_logs WHERE action = 'CREATE' AND target_user_id = (SELECT id FROM users WHERE email = '[email protected]')$$, $$VALUES ('{"note":"user created","email":"[email protected]"}'::text)$$, 'Audit log details should include note and email');

Test Case: Keyword search (partial, case-insensitive) across email, username, first_name, last_name returns matches

Error: subquery must return only one column

SELECT results_eq($$SELECT id::text, email::text, username::text, first_name::text, last_name::text
FROM users
WHERE (
  email_normalized LIKE ('%' || lower('Exam') || '%') OR
  (username_normalized IS NOT NULL AND username_normalized LIKE ('%' || lower('Exam') || '%')) OR
  (first_name IS NOT NULL AND first_name::text ILIKE '%' || 'Exam' || '%') OR
  (last_name IS NOT NULL AND last_name::text ILIKE '%' || 'Exam' || '%')
)
ORDER BY id::text$$,
$$VALUES
((SELECT id::text, email::text, username::text, first_name::text, last_name::text FROM users WHERE email_normalized LIKE '%exam%' ORDER BY id::text LIMIT 1))
$$,
'Should return users matching partial case-insensitive keyword across email, username, first_name, last_name');

🧠 Thinking (db)...

Evaluating database constraints

I need to use tools for creating or modifying database objects. It looks like I should add an email format check constraint on users' emails using a regex. Also, for the roles, I think preventing the deletion of records where is_system is true would require a trigger to raise an exception. Although the tool supports adding constraints and enums, it doesn’t seem to allow for triggers. So, I need a workaround to block deletions effectively.

Implementing constraints and triggers

I’m thinking about using a constraint of type "PROCEDURAL" to describe triggers, but I must follow the tool's instructions that require using tools for changes. I'll start by adding a CHECK constraint on users' emails with a regex. For roles, I can create a trigger to prevent deletions by including a detail field for this trigger function. I also want to add a check for the existing email column and ensure the email_normalized format is valid during inserts.

Setting up email regex and constraints

I’m using a regex for email validation based on a reasonable interpretation of the complex RFC, like this: email * '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'.

Next, I need to add a constraint to the roles table, specifically a TRIGGER that prevents deletion when OLD.is_system = true. The tool will record this as schema metadata, not actual SQL. I’ll follow up the tool calls with summaries to ensure clarity. Now, I’ll create an operations array to replace the check on users' emails properly, ensuring to escape backslashes for JSON.

Adding a deletion prevention constraint

Next, I'm planning to add a constraint at /tables/roles/constraints/trg_prevent_delete_system_role. I'll specify its type as "TRIGGER" and give it the name "trg_prevent_delete_system_role," along with a detail that contains the SQL script for creating the function and the trigger. Once I call the tool to implement this, I’ll make sure to report on the success of the operation afterward. It's all about getting this right!

⏺ (db):

Operation: 1) Add an email-format check on users.email using a reasonable regex. 2) Add a trigger constraint to prevent deleting roles where is_system = true. Minimal inputs: email check expression, trigger function+trigger SQL.

Now applying schema changes.

🔧 schemaDesignTool()

⏺ (db):

Purpose: 1) Add email format CHECK to users.email. 2) Add a trigger function and trigger to prevent deleting roles where is_system = true. Minimal inputs: regex for email check; trigger PL/pgSQL function and trigger definition. Now applying schema changes.

🔧 schemaDesignTool()

⏺ (db):

Purpose: Add email check constraint on users.email and add a trigger (stored as a constraint detail) to prevent deleting system roles. Minimal inputs: regex string and PL/pgSQL function+trigger SQL. Proceeding to apply changes.

🔧 schemaDesignTool()

⏺ (db):

Operation: Replace users.email check constraint with regex; add a roles constraint entry of type TRIGGER containing the PL/pgSQL function and trigger SQL. Minimal inputs: regex string and trigger SQL. Now applying.

🔧 schemaDesignTool()

⏺ (db):

Purpose: 1) Add CHECK on users.email for format. 2) Add a custom constraint record on roles containing trigger creation SQL (stored as metadata detail). Minimal inputs: regex string and PL/pgSQL trigger SQL in constraint.detail. Now applying.

🔧 schemaDesignTool()x

⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯

FAIL src/createGraph.integration.test.ts > createGraph Integration > should execute complete workflow
GraphRecursionError: Recursion limit of 10 reached without hitting a stop condition. You can increase the limit by setting the "recursionLimit" config key.

Troubleshooting URL: https://langchain-ai.github.io/langgraphjs/troubleshooting/errors/GRAPH_RECURSION_LIMIT/

❯ CompiledStateGraph.runLoop ../../../node_modules/.pnpm/@langchain[email protected]@langchain+core@0.3.78_@opentelemetry+api@1.9.0_@opentelemet_278c3a2c617bd494a476bd26defdbcfb/node_modules/@langchain/langgraph/src/pregel/index.ts:2250:15
❯ createAndRunLoop ../../../node_modules/.pnpm/@langchain+langgraph@0.4.9_@langchain+core@0.3.78_@opentelemetry+api@1.9.0_@opentelemet_278c3a2c617bd494a476bd26defdbcfb/node_modules/@langchain/langgraph/src/pregel/index.ts:2092:9

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

Test Files 1 failed (1)
Tests 1 failed (1)
Start at 08:10:41
Duration 371.59s (transform 468ms, setup 0ms, collect 1.44s, tests 369.87s, environment 0ms, prepare 76ms)

 ELIFECYCLE  Command failed with exit code 1.
/home/runner/work/liam/liam/frontend/internal-packages/agent:
 ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL  @liam-hq/[email protected] execute-deep-modeling: pnpm test:integration src/createGraph.integration.test.ts
Exit status 1

…ed concurrency

- Restore Send API parallel execution pattern as requested
- Add semaphore-based concurrency limiting (max 3 concurrent testcases)
- Implement progress tracking with BATCH_START, PROGRESS, BATCH_COMPLETE events
- Create prepareTestcases node to initialize batch tracking
- Create testcaseGenerationWithSemaphore wrapper node for concurrency control
- Create reportProgress node to emit progress after each completion
- Update applyGeneratedSqls to dispatch BATCH_COMPLETE and cleanup runtime state
- Add batchId and totalTestcases fields to qaAgentAnnotation
- Create progressRuntime utility for semaphore and progress counter management
- Update tests and README to reflect Send-based architecture
- Remove manual batching approach (batchTestcaseGenerationNode)

This implementation maintains Send API parallel execution while enforcing
concurrent limit via semaphore and providing aggregated progress updates
to prevent timeline message flooding.

Co-Authored-By: [email protected] <[email protected]>
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