Skip to main content

Audit Notes: Agent Creation (Phase 6.7)

Last Updated: 2025-01-XX
Endpoint: POST /api/v1/admin/agent-profiles/create
Service: payroll-backend-prod (System of Record)

Invariants

1. No Client-Side UUID Generation

Invariant: Backend owns all UUID generation for agent_entity_id. No client-side UUID generation is allowed. Verification:
# From repo root
cd dashboard
grep -r "crypto.randomUUID\|uuid4\|uuid5" src/features/agentMapping/components/CreateAgentModal.tsx
Expected: Only idempotency key generation (for UUIDv4 path), NO agent_entity_id generation. Violation Impact: Duplicate identities, data inconsistency, audit trail corruption.

2. No Org Leak

Invariant: Cross-org requests return generic NOT_FOUND. No information leak about agent existence in other orgs. Verification:
  1. Create agent in Org A (get agent_entity_id)
  2. Call endpoint from Org B with same agent_entity_id
  3. Verify: error.stage="identity", code="NOT_FOUND", generic message (no indication agent exists in Org A)
Violation Impact: Data privacy breach, RBAC bypass, compliance violation.

3. Deterministic UUIDv5 Path Stability

Invariant: Same agent_id (within same tenant+org) always produces same agent_entity_id (UUIDv5). Verification:
  1. Call endpoint with agent_id="721995" (Org A, Tenant X)
  2. Capture agent_entity_id (UUIDv5)
  3. Call endpoint again with same agent_id="721995" (Org A, Tenant X)
  4. Verify: Same agent_entity_id returned, identity_created=false
Violation Impact: Identity fragmentation, duplicate records, inconsistent analytics.

4. Org/Tenant Scoping Enforcement

Invariant: All operations are scoped to tenant_id + org_id. No cross-scope data access. Verification:
  • All BigQuery queries include tenant_id and org_id filters
  • All identity lookups require matching tenant_id + org_id
  • All contact operations require matching tenant_id + org_id
Violation Impact: Data leakage, incorrect attribution, compliance violation.

Expected Failure Modes

1. Contact Creation Fails After Identity Created

Scenario: Identity creation succeeds, but contact creation fails (e.g., invalid email format, BigQuery timeout). Expected Behavior:
  • Response: 200 OK with error.stage="contact", code="CONTACT_CREATE_FAILED"
  • identity_created=true, contact_created=false
  • agent_entity_id and identity_id returned (usable for retry)
  • contact_id=null
UI Behavior:
  • Warning shown: “Identity created but contact creation failed”
  • “Retry Contact Creation” button available
  • Retry uses stored agent_entity_id (skips identity creation)
Healing Path:
  • User clicks “Retry Contact Creation”
  • UI sends request with agent_entity_id (no agent_id)
  • Backend skips identity creation, retries contact creation

2. Partial Idempotency Response

Scenario: Idempotency key lookup succeeds, but response is incomplete (e.g., contact_id missing). Expected Behavior:
  • Backend checks config_agent_profile_idempotency table
  • If idempotency key exists, returns stored response
  • If stored response is incomplete, logs warning, continues with normal flow
Graceful Degradation:
  • If idempotency table missing, logs warning, continues (UUIDv4 path still works)
  • Option A (idempotency-key heal) requires table; Option B (retry via agent_entity_id) does not

3. Identity NOT_FOUND on Retry

Scenario: User provides agent_entity_id for retry, but identity doesn’t exist (wrong org, deleted, etc.). Expected Behavior:
  • Response: 200 OK with error.stage="identity", code="NOT_FOUND"
  • Generic message: “Agent identity not found” (no internal values echoed)
  • No indication that agent exists in different org
UI Behavior:
  • Error shown: “Agent identity not found. Please create a new agent.”
  • Form remains editable, user can create new agent

4. Agent ID Mismatch

Scenario: User provides agent_entity_id + agent_id, but existing identity has different agent_id. Expected Behavior:
  • Response: 200 OK with error.stage="identity", code="VALIDATION_ERROR"
  • Message: “Agent ID mismatch detected” (generic, no internal values)
UI Behavior:
  • Error shown: “Agent ID mismatch. Please verify the agent ID or use a different agent entity ID.”
  • Form remains editable

Production Verification Checklist

Pre-Deploy Verification

  • BigQuery table config_agent_profile_idempotency exists (if using Option A)
  • Backend code includes idempotency-key handling
  • Frontend does NOT generate agent_entity_id (only idempotency keys for UUIDv4 path)
  • All BigQuery queries include tenant_id + org_id filters

Post-Deploy Smoke Tests

Test 1: Deterministic Path Idempotency

# Call endpoint twice with same agent_id
curl -X POST "https://payroll-backend-prod-evndxpcirq-uc.a.run.app/api/v1/admin/agent-profiles/create" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Org-Id: test-org" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "test-123",
    "is_active": true,
    "effective_start_date": "2024-01-15",
    "contact": {
      "first_name": "Test",
      "last_name": "Agent"
    }
  }'

# Capture agent_entity_id from first call
# Call again with same agent_id
# Verify: Same agent_entity_id returned, identity_created=false
Expected: Same agent_entity_id on both calls.

Test 2: Bootstrap Path (UUIDv4)

# Call endpoint without agent_id
curl -X POST "https://payroll-backend-prod-evndxpcirq-uc.a.run.app/api/v1/admin/agent-profiles/create" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Org-Id: test-org" \
  -H "Idempotency-Key: smoke-test-$(date +%s)" \
  -H "Content-Type: application/json" \
  -d '{
    "is_active": true,
    "effective_start_date": "2024-01-15",
    "contact": {
      "first_name": "Bootstrap",
      "last_name": "Test"
    }
  }'
Expected: UUIDv4 agent_entity_id returned, agent_id stored as NULL.

Test 3: Idempotency Key (UUIDv4 Path)

# Call endpoint twice with same Idempotency-Key (no agent_id)
IDEMPOTENCY_KEY="smoke-test-$(date +%s)"

# First call
curl -X POST "https://payroll-backend-prod-evndxpcirq-uc.a.run.app/api/v1/admin/agent-profiles/create" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Org-Id: test-org" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{...}'

# Second call (same Idempotency-Key)
curl -X POST "https://payroll-backend-prod-evndxpcirq-uc.a.run.app/api/v1/admin/agent-profiles/create" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Org-Id: test-org" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{...}'
Expected: Same response returned both times (no duplicates).

Test 4: Retry Path (agent_entity_id)

# Create agent (get agent_entity_id)
RESPONSE=$(curl -X POST "https://payroll-backend-prod-evndxpcirq-uc.a.run.app/api/v1/admin/agent-profiles/create" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Org-Id: test-org" \
  -H "Content-Type: application/json" \
  -d '{...}')

AGENT_ENTITY_ID=$(echo $RESPONSE | jq -r '.agent_entity_id')

# Retry with agent_entity_id (different contact data)
curl -X POST "https://payroll-backend-prod-evndxpcirq-uc.a.run.app/api/v1/admin/agent-profiles/create" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Org-Id: test-org" \
  -H "Content-Type: application/json" \
  -d "{
    \"agent_entity_id\": \"$AGENT_ENTITY_ID\",
    \"is_active\": true,
    \"effective_start_date\": \"2024-01-15\",
    \"contact\": {
      \"first_name\": \"Updated\",
      \"last_name\": \"Contact\"
    }
  }"
Expected: identity_created=false, contact updated, same agent_entity_id returned.

Test 5: Cross-Org Security

# Create agent in Org A
RESPONSE_A=$(curl -X POST "https://payroll-backend-prod-evndxpcirq-uc.a.run.app/api/v1/admin/agent-profiles/create" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Org-Id: org-a" \
  -H "Content-Type: application/json" \
  -d '{...}')

AGENT_ENTITY_ID=$(echo $RESPONSE_A | jq -r '.agent_entity_id')

# Try to access from Org B
curl -X POST "https://payroll-backend-prod-evndxpcirq-uc.a.run.app/api/v1/admin/agent-profiles/create" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Org-Id: org-b" \
  -H "Content-Type: application/json" \
  -d "{
    \"agent_entity_id\": \"$AGENT_ENTITY_ID\",
    \"is_active\": true,
    \"effective_start_date\": \"2024-01-15\",
    \"contact\": {
      \"first_name\": \"Test\",
      \"last_name\": \"Agent\"
    }
  }"
Expected: error.stage="identity", code="NOT_FOUND", generic message (no leak).

BigQuery Verification

# Verify idempotency table exists (if using Option A)
bq ls payroll-bi-gauntlet:payroll_analytics | grep config_agent_profile_idempotency

# Verify agent identity created
bq query --use_legacy_sql=false \
  "SELECT agent_entity_id, agent_id, tenant_id, org_id, created_at
   FROM \`payroll-bi-gauntlet.payroll_analytics.config_agent_profile_identity\`
   WHERE tenant_id = 'test-tenant' AND org_id = 'test-org'
   ORDER BY created_at DESC
   LIMIT 10"

# Verify contact created
bq query --use_legacy_sql=false \
  "SELECT contact_id, agent_entity_id, first_name, last_name, created_at
   FROM \`payroll-bi-gauntlet.payroll_analytics.config_agent_profile_contact\`
   WHERE agent_entity_id = '<agent_entity_id>'
   ORDER BY created_at DESC
   LIMIT 1"
  • Checkpoint Document: PHASE_6_7_CHECKPOINT.md - Full API contract, smoke tests
  • Deployment Guide: docs/DEPLOYMENT.md - Manual deploy instructions
  • Phase Index: docs/PHASES.md - Phase 6.7 details and locked decisions