feat: Step 3 - AI-Powered Batch Suggestions#2
Conversation
- Add ADR 0006 for OpenAI gpt-5-mini via AI Gateway - Add ADR 0007 for candidate suggestions + D1 cache - Add ADR 0008 for accept-all idempotency pointers - Make docs/vertical-slice.md explicit for Steps 1–7 - Extend API error codes and optional details - Add TASK tracker for Step 3 plan
- Add suggestion columns to candidate table: suggestedBucket, suggestedText, suggestionStatus, suggestionError, suggestionAttempts, suggestionModel, suggestionPromptVersion, suggestionUpdatedAt - Create suggestion_cache table with unique index on (user_id, normalized_term, model, prompt_version) - Generate and apply migration 0002_busy_vulcan.sql - Implement POST /api/batch/:id/suggest endpoint with fill-missing (default) and regenerate modes - Add stub and OpenAI suggestion providers (configurable via SUGGESTIONS_PROVIDER env var) - Extend GET /api/batch/:id to return all suggestion fields per candidate - Add per-candidate claim/lock pattern using conditional DB update to prevent race conditions - Add multi-level caching: in-batch Map + D1 suggestion_cache - Add 15 comprehensive tests covering auth, ownership, retry/skip semantics, cache hits, and concurrency safety - Update wrangler.jsonc with test environment config for stub provider - Update vitest.config.mts with SUGGESTIONS_PROVIDER: 'stub' binding 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add ai.binding configuration to wrangler.jsonc
- Use env.AI.gateway().getUrl('openai') for dynamic URL generation
- Remove CF_ACCOUNT_ID requirement (auto-injected by binding)
- Update OpenAIConfig interface to accept gatewayBaseUrl
- Add AI binding to test environment config
- Regenerate TypeScript types with AI binding
All 35 tests passing. Follows official Cloudflare AI Gateway docs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add AI binding to Bindings type in batch.ts (fixes TS7053) - Import eq operator and user table in auth/index.ts - Add explicit type annotation to Drizzle where callback (fixes TS7006, TS7031) All tests passing, typecheck clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The AI binding pattern automatically injects the account ID, so we no longer need CF_ACCOUNT_ID as an environment variable. This commit: - Removes CF_ACCOUNT_ID validation check in suggest endpoint - Removes CF_ACCOUNT_ID from Bindings type definition - Updates vertical-slice.md to document AI binding requirement - Updates ADR 0006 to reflect AI binding URL generation pattern All 35 tests pass. TypeScript compilation succeeds. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
append-api | 7175e38 | Commit Preview URL Branch Preview URL |
Dec 24 2025, 02:21 PM |
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
||||||||||||||||||||||||||
PR Code Suggestions ✨Explore these optional code suggestions:
|
|||||||||||||||||||
All 403s were classified as blocked_auth, causing origin validation failures to be parked as "sign-in required". Since re-authentication can't fix an origin mismatch, these items would be stuck indefinitely. Changes: - Add ORIGIN_FORBIDDEN error code for preview origin validation - Update error classifier to treat ORIGIN_FORBIDDEN as permanent failure - Other 403s remain blocked_auth (may recover after sign-in) Fixes PR review feedback item #2.
All 403s were classified as blocked_auth, causing origin validation failures to be parked as "sign-in required". Since re-authentication can't fix an origin mismatch, these items would be stuck indefinitely. Changes: - Add ORIGIN_FORBIDDEN error code for preview origin validation - Update error classifier to treat ORIGIN_FORBIDDEN as permanent failure - Other 403s remain blocked_auth (may recover after sign-in) Fixes PR review feedback item #2.
Tighten export date validation to reject impossible dates like 2026-02-30 or 2026-13-01 that previously passed regex validation and got silently normalized by Date.UTC. - Add isValidDayKey() to validate date components match after round-tripping through Date - Update DayKeySchema to use stricter validation - Add regression tests for impossible dates Addresses Codex PR review issue #2.
feat: Step 3 - AI-Powered Batch Suggestions
All 403s were classified as blocked_auth, causing origin validation failures to be parked as "sign-in required". Since re-authentication can't fix an origin mismatch, these items would be stuck indefinitely. Changes: - Add ORIGIN_FORBIDDEN error code for preview origin validation - Update error classifier to treat ORIGIN_FORBIDDEN as permanent failure - Other 403s remain blocked_auth (may recover after sign-in) Fixes PR review feedback item #2.
Tighten export date validation to reject impossible dates like 2026-02-30 or 2026-13-01 that previously passed regex validation and got silently normalized by Date.UTC. - Add isValidDayKey() to validate date components match after round-tripping through Date - Update DayKeySchema to use stricter validation - Add regression tests for impossible dates Addresses Codex PR review issue #2.
User description
Summary
Implements Step 3 of the vertical slice: AI-powered batch suggestions endpoint with OpenAI
gpt-5-minivia Cloudflare AI Gateway.This feature enables the Worker API to generate bucket assignments and one-liner descriptions for candidate terms, with built-in caching and retry logic.
Key Features
fill-missing(default): Fills missing suggestions, consults cache, retries errors up to 3 attemptsregenerate: Re-generates suggestions, bypasses cache, overwrites existing suggestionsDatabase Changes
candidatetable:suggestedBucket,suggestedText,suggestionStatus,suggestionError,suggestionAttempts,suggestionModel,suggestionPromptVersion,suggestionUpdatedAtsuggestion_cachetable with unique index on(user_id, normalized_term, model, prompt_version)0002_busy_vulcan.sqlapplied to production ✅Configuration Requirements
Production Secrets (Manual Action Required)
You need to set the OpenAI API key:
AI Gateway Setup (Manual Verification Required)
Ensure an AI Gateway exists in your Cloudflare dashboard with ID
append-gateway:append-gateway)Environment Variables (Already Configured)
AI_GATEWAY_ID: Set to"append-gateway"inwrangler.jsoncSUGGESTIONS_PROVIDER: Set to"openai"inwrangler.jsoncImplementation Details
Architecture Decisions
gpt-5-minivia Cloudflare AI Gateway (updated to reflect AI binding pattern)candidatetable + D1 cache for cost controlData Flow
suggestedKey Behaviors
chosenBucket,chosenText)candidate.version(reserved for user edits)candidate.status = 'suggested'after any attempt (success or error)batch.status = 'suggested'after suggest run (even with partial errors)Test Coverage
All 35 tests passing:
Tests use stub provider (deterministic, no external API calls).
Commits
b80b60bLock vertical slice contracts and add ADRs23196edfeat: implement step 3 ai-powered batch suggestions4850381refactor: migrate to AI Gateway binding patterna7b5161fix: add missing type annotations for AI binding and Drizzle query7175e38fix: remove CF_ACCOUNT_ID requirement for AI Gateway bindingDocumentation Updates
docs/vertical-slice.mdto document AI binding requirementdocs/adr/0006-openai-gpt-5-mini-via-ai-gateway.mdto reflect AI binding patternNext Steps (Post-Merge)
After merging, you'll need to manually:
OPENAI_API_KEYsecret in production (see command above)append-gateway🤖 Generated with Claude Code
PR Type
Enhancement, Tests, Documentation
Description
Implements Step 3 of the vertical slice: AI-powered batch suggestions endpoint (
POST /api/batch/:id/suggest) with OpenAIgpt-5-minivia Cloudflare AI GatewayAdds comprehensive suggestion generation module with stub and OpenAI providers, multi-level caching (in-batch Map + D1 cache table), and retry logic (up to 3 attempts)
Supports two modes:
fill-missing(default, consults cache) andregenerate(bypasses cache, overwrites suggestions)Extends database schema with 8 new suggestion fields on
candidatetable and newsuggestionCachetable with unique index on(user_id, normalized_term, model, prompt_version)Adds 15 comprehensive tests covering authentication, authorization, validation, caching behavior, and error handling
Includes new error codes (
VERSION_CONFLICT,BATCH_NOT_READY,SERVICE_UNAVAILABLE,CONFIGURATION_ERROR) with optionaldetailsfield for structured error metadataConfigures Cloudflare AI Gateway binding and environment variables for both production and test environments
Documents all implementation decisions via three new ADRs (0006: OpenAI via AI Gateway, 0007: suggestions storage strategy, 0008: accept-all idempotency)
Expands vertical slice specification with detailed Step 3 endpoint contract and Steps 4-7 specifications
Diagram Walkthrough
flowchart LR A["POST /api/batch/:id/suggest"] --> B["Validate ownership & mode"] B --> C["Process candidates sequentially"] C --> D["Check in-batch cache"] D --> E{Cache hit?} E -->|Yes| F["Use cached suggestion"] E -->|No| G["Check D1 cache<br/>fill-missing mode only"] G --> H{D1 hit?} H -->|Yes| I["Use cached suggestion"] H -->|No| J["Call OpenAI<br/>15s timeout"] J --> K["Validate & store result"] K --> L["Upsert to D1 cache<br/>fill-missing mode only"] F --> M["Update candidate<br/>never increment version"] I --> M L --> M M --> N["Update batch status<br/>to suggested"] N --> O["Return results breakdown<br/>suggested/cached/skipped/errors"]File Walkthrough
1 files
suggestions.spec.ts
Add comprehensive test suite for batch suggestions endpointpackages/api/test/suggestions.spec.ts
endpoint with 15 tests covering authentication, authorization,
validation, and suggestion generation
fill-missing(default) andregeneratemodes with propercaching behavior verification
batches, and candidate versions are not incremented
getAuthCookie(),generateTerms(),createBatch(), and database cleanup4 files
batch.ts
Implement POST /api/batch/:id/suggest endpoint with cachingpackages/api/src/routes/batch.ts
POST /api/batch/:id/suggestendpoint with query params forlimit(1-200, default 50) andregeneratemode flagcache (Map) and D1 cache table for cost control
conditions; never increments
candidate.versionor overwriteschosen_*fields
suggestedafter processing; returns detailedresults breakdown (suggested, cached, skipped, errors)
suggestions.ts
Add suggestion generation module with stub and OpenAI providerspackages/api/src/lib/suggestions.ts
validation, error handling, and timeout support
testing; OpenAI provider integrates with Cloudflare AI Gateway
SUGGESTION_MODEL(gpt-5-mini),PROMPT_VERSION(1),MAX_SUGGESTION_ATTEMPTS(3),SUGGESTION_TIMEOUT_MS(15s), limit boundsvalidateSuggestion()for response validation,normalizeText()for text sanitization, and
processConcurrently()utilitydomain.schema.ts
Add suggestion fields and cache table to database schemapackages/api/src/db/domain.schema.ts
SuggestionStatusenum type (in_progress | done | error) fortracking suggestion generation state
candidatetable with 8 new suggestion fields:suggestedBucket,suggestedText,suggestionStatus,suggestionError,suggestionAttempts,suggestionModel,suggestionPromptVersion,suggestionUpdatedAtmaterializedTermId,materializedTermSenseIdfor Step 5 accept-all idempotency (ADR 0008)suggestionCachetable with unique index on(user_id,normalized_term, model, prompt_version)for per-user, per-term cachingapi-error.ts
Extend API error types with new codes and details fieldpackages/api/src/lib/api-error.ts
VERSION_CONFLICT,BATCH_NOT_READY,SERVICE_UNAVAILABLE,CONFIGURATION_ERRORfor Step 3-5 endpointsdetailsfield toApiErrorResponsefor structured errormetadata (e.g., current version on conflict, reason on batch not
ready)
apiError()function signature to accept optionaldetailsparameter
5 files
worker-configuration.d.ts
Regenerate worker configuration types with AI bindingpackages/api/worker-configuration.d.ts
wrangler typescommand reflectingnew bindings and environment variables
SUGGESTIONS_PROVIDER(openai | stub),AI_GATEWAY_ID(append-gateway | test-gateway), and
AI(Ai binding) to Cloudflare.EnvENABLE_TEST_EMAIL_PASSWORD_AUTHfor test environmentconfiguration
0002_snapshot.json
Add Drizzle migration snapshot for Step 3 schemapackages/api/drizzle/meta/0002_snapshot.json
new suggestion fields on
candidate, newsuggestionCachetable,indexes, and check constraints
types, foreign keys, and constraints
0002_busy_vulcan.sql
Database migration for suggestions and cache tablespackages/api/drizzle/0002_busy_vulcan.sql
suggestion_cachetable with unique index on(user_id,normalized_term, model, prompt_version)candidatetable:suggested_bucket,suggested_text,suggestion_status,suggestion_error,suggestion_attempts,suggestion_model,suggestion_prompt_version,suggestion_updated_atenum values
candidate_suggestion_lookup_idxindex on(batch_id,suggestion_status, suggestion_attempts)for efficient eligibilityqueries
wrangler.jsonc
Worker configuration for AI Gateway and suggestionspackages/api/wrangler.jsonc
"ai": { "binding": "AI" }for both default and test environments
SUGGESTIONS_PROVIDER(set to"openai"indefault,
"stub"in test) andAI_GATEWAY_ID(set to"append-gateway")OPENAI_API_KEYin the list of secrets to beconfigured
account ID injection
vitest.config.mts
Test configuration for stub suggestions providerpackages/api/vitest.config.mts
SUGGESTIONS_PROVIDER: 'stub'to test environment variables fordeterministic suggestion generation
provider is used
1 files
index.ts
Fix type annotation in Better Auth user lookuppackages/api/src/lib/auth/index.ts
(u, { eq })destructuring to
(u: typeof user)for proper Drizzle query buildertyping
9 files
vertical-slice.md
Expand vertical slice specification with all step detailsdocs/vertical-slice.md
requirements, and API error contract details
request/response contracts, validation rules, idempotency behavior
response format, fill-missing vs regenerate modes, and cost control
via caching
preconditions, bucket feed with pagination, and export with
deterministic ordering
SUMMARY_step3-suggestions.md
Add session handoff summary for Step 3 and AI binding workSUMMARY_step3-suggestions.md
and AI Gateway binding migration work
multi-level caching, AI binding pattern) and what was tried but failed
(concurrent processing, auto-generated migration, direct AI binding
mock)
production, set secrets, test OpenAI provider, proceed to Step 4
design.md
Update design document with Step 3 and ADR referencesdocs/design.md
Gateway) and ADR 0007 (suggestion storage on Candidate + cache)
Candidatetable rather thanseparate
Suggestiontable per ADR 0007via materialization pointers (ADR 0008) and notes that suggestions
never overwrite
chosen_*fieldschoice
0008-accept-all-idempotency-via-candidate-materialization-pointers.md
Add ADR 0008 for accept-all idempotency via pointersdocs/adr/0008-accept-all-idempotency-via-candidate-materialization-pointers.md
pointers (
materialized_term_id,materialized_term_sense_id) foraccept-all idempotency
idempotency keys and enable partial success recovery
(insufficient) and unique constraints on
term_sense(rejected due toappend-only design)
TASK_PLAN_vertical-slice-step-3-suggestions.md
Task plan for Step 3 suggestions implementationTASK_PLAN_vertical-slice-step-3-suggestions.md
AI-powered batch suggestions
database schema, caching, and testing
requirement
sequential processing
PLAN_vertical-slice-step-3-suggestions.md
Comprehensive Step 3 suggestions implementation planPLAN_vertical-slice-step-3-suggestions.md
and full API contract
gpt-5-minivia Cloudflare AI Gateway withnon-streaming responses
bucketandtextfields), validation rules, and retry/regenerate semantics
testing approach with stub provider, and risk mitigation
0006-openai-gpt-5-mini-via-ai-gateway.md
ADR for OpenAI model and AI Gateway routingdocs/adr/0006-openai-gpt-5-mini-via-ai-gateway.md
gpt-5-minias the default modelrouted through Cloudflare AI Gateway
OPENAI_API_KEY), andautomatic account ID injection
Authorization header
choice) and alternatives considered
0007-step-3-suggestions-on-candidate-plus-cache.md
ADR for suggestions storage and caching strategydocs/adr/0007-step-3-suggestions-on-candidate-plus-cache.md
candidatetablewith metadata columns rather than separate entity
(user_id,normalized_term, model, prompt_version)chosen_*fields orincrement
versionconstraints
README.md
Update ADR index with new decisionsdocs/adr/README.md
Gateway), ADR 0007 (suggestions on candidate + cache), and ADR 0008
(accept-all idempotency)
1 files