Add worker-owned image derivative job execution with explicit PROCESSING, COMPLETED, and FAILED outcomes, plus tests and documentation. Verification: npm test passed 70 tests, worker tests passed 2 tests, and integration passed 14 tests. No build script is configured.
10 KiB
Implementation Status
Last updated: 2026-09-08
Resume Here
Before changing code, read this document, AGENTS.md, and the linked source-of-truth documentation. Then confirm the current baseline with:
npm test
For persistence or Redis-backed changes, also run the isolated integration suite after setting TEST_DATABASE_URL to the *_test database and TEST_REDIS_URL to the test Redis instance:
TEST_DATABASE_URL='postgresql://...' TEST_REDIS_URL='redis://...' npm run test:integration
The current local container runtime is OrbStack. If Docker commands point at a stale host, run:
unset DOCKER_HOST
docker context use orbstack
Completed
Workspace And Database
- Npm workspace with the API and shared contracts/validation packages.
- PostgreSQL Compose service on local port
5433; API port3000is registered in.local/project-ports.json. - Redis Compose service on local port
6380supports distributed login throttling. - Drizzle schema and migrations:
0000_core_inventory: users, sellers, categories, condition profiles, conditions, products, and physical inventory items.0001_auth_sessions: hashed server-side sessions, expiry, revocation, IP address, and user agent.0002_session_csrf_tokens: session-bound hashed CSRF tokens.0003_security_audit_events: private append-only authentication security events.0004_password_reset_tokens: hashed, one-time password recovery tokens.0005_mfa_methods: protected TOTP method records and lifecycle state.0006_listings: listing records and lifecycle enum.0007_listing_image_count: persisted image prerequisite for publication.0008_inventory_images: item-owned image metadata and processing state.0009_remove_listing_image_count: publication no longer stores a listing-level image snapshot.
- Isolated integration tests require a database name ending in
_testand truncate only that database's tables.- Local-only
npm run provision:admincreates the first Admin User and linked Seller atomically. It requiresINITIAL_ADMIN_EMAIL,INITIAL_ADMIN_PASSWORD, andINITIAL_SELLER_NAME, hashes the password with scrypt, and refuses once any User exists.
- Local-only
Domain And Persistence
- Product identity requires name, category, and seller.
- Products receive seller-scoped, normalized slugs; duplicate names receive a numeric suffix.
- Each Inventory Item is a separate physical copy.
- Inventory condition must belong to the Product category.
- Inventory lifecycle supports
ACTIVE,SOLD, andARCHIVED; sold and archived items are locked against ordinary edits. - Repositories for products, inventory items, conditions, users, and sessions use Drizzle/PostgreSQL.
- Listing persistence supports draft creation, seller-scoped lookup, status updates, and inventory-item history.
API And Authentication
- Fastify app bootstrap and
GET /healthresponse. - Admin Product endpoints:
GET /api/v1/admin/productsPOST /api/v1/admin/products
- Admin Inventory endpoint:
POST /api/v1/admin/inventory
- Authentication endpoints:
POST /api/v1/auth/loginPOST /api/v1/auth/logoutGET /api/v1/auth/me
- Login uses asynchronous scrypt password verification. Session tokens are random, stored only as SHA-256 hashes, and never returned in JSON.
- Session cookie settings are
HttpOnly,SameSite=Strict, path/, andSecurein production. - Login is limited to five attempts per client IP every 15 minutes and returns
429 AUTH_RATE_LIMITEDwithRetry-Afteronce exhausted. Redis atomically coordinates the limit across API processes. - Login sets a separate same-site CSRF cookie. Authenticated state-changing routes require its matching
x-csrf-tokenheader, validated against the session-bound hash. GET /api/v1/auth/sessionsreturns safe metadata for the current user's active sessions and marks the current session.DELETE /api/v1/auth/sessions/:idrevokes an owned session;DELETE /api/v1/auth/sessionsrevokes all other sessions while retaining the current one. Revoking the current session clears both cookies.- Active sessions renew
last_activity_atafter five minutes, avoiding a database write on every authenticated request. - Authentication events are retained separately from inventory activity.
LOGIN_SUCCESS,LOGIN_FAILURE,SESSION_CREATED, andSESSION_REVOKEDinclude the known actor where available, plus safe request context. Passwords and session/CSRF tokens or hashes are never recorded. - Password recovery uses generic
POST /api/v1/auth/forgot-passwordresponses to prevent account enumeration. Opaque tokens are delivered only through the SMTP adapter, stored only as hashes, expire after one hour, are consumed once, and revoke existing sessions after a password reset. - API startup requires
SMTP_URL,SMTP_FROM, andPASSWORD_RESET_URL; configure these local values before starting the server. - Existing admin routes validate the authenticated session and derive seller ownership server-side.
- Request schemas live in
packages/validation; admin response DTOs live inpackages/contracts. - MFA enrollment, verification, disable, protected secret handling, login verification, and audit events are implemented.
- Admin listing creation and lifecycle routes are implemented:
POST /api/v1/admin/listingsGET /api/v1/admin/listingsGET /api/v1/admin/listings/:idPOST /api/v1/admin/listings/:id/publishPOST /api/v1/admin/listings/:id/unpublishPOST /api/v1/admin/listings/:id/pendingPOST /api/v1/admin/listings/:id/archive
- Listing publication is seller-scoped, CSRF-protected, and gated by persisted identity, price, physical inventory, condition, and image prerequisites.
- Listing publication rejects physical inventory in
SOLDorARCHIVEDstatus with a conflict response. - Listing reads are seller-scoped and return only admin listing DTO fields.
- Item-owned image metadata can be added through a seller-scoped, CSRF-protected admin route; publication uses the persisted image count.
ImageStorage, traversal-safe local storage, multipart upload transport, and binary signature validation are implemented; derivative processing remains.- Derivative orchestration now defines thumbnail, medium, and large WebP storage outputs behind an injected renderer; the concrete worker-backed renderer remains.
- The worker owns an image derivative job runner with explicit processing, completion, and failure outcomes; queue transport and persistent job records remain.
- Image metadata limits are enforced in the service boundary: supported MIME types, 10 MiB default file size, and 10,000-pixel maximum dimensions.
Verified Baseline
| Suite | Current coverage |
|---|---|
| Domain | Product requirements and inventory lifecycle rules |
| Application | Product slug allocation, condition compatibility, password verification/recovery, session issuance, activity renewal, management, and audit events |
| HTTP | Product and inventory creation, validation, authentication/recovery, CSRF, rate limiting, and session management |
| Integration | PostgreSQL catalog/session/audit/password recovery persistence and Redis-shared login throttling |
Latest result: npm test passed 70 tests; worker tests passed 2 tests; npm run test:integration passed 14 tests against OrbStack PostgreSQL and Redis.
Next Milestone
Complete the inventory/listing integrity boundary before starting pricing and acquisition.
- Add a portable decoder/encoder and worker-backed derivative processing lifecycle.
- Begin acquisition and pricing foundations after the listing/image boundary is complete.
Acceptance criteria: listing reads and lifecycle writes preserve seller ownership, CSRF, inventory status, and publication prerequisites.
Overall Roadmap
Work through one bounded vertical slice at a time. The linked documentation remains authoritative when this roadmap is less detailed.
- Authentication hardening.
- Listings and image prerequisites: keep Listing separate from Inventory Item; enforce publish requirements from REQUIREMENTS.md.
- Acquisition, pricing provider mappings, observations, valuations, and private/public DTO boundaries from DATA-MODEL.md and PRICING.md.
- Sales workflow, sold-item locking, corrections, cost basis, and audit history.
- SearchService, public catalog/search endpoints, filtering, pagination, and public product URLs.
- Image storage abstraction, upload validation, processing worker, responsive variants, and alt-text editing.
- Vue admin workflows and public catalog, following DESIGN.md, UX.md, and ACCESSIBILITY.md.
- Dashboard, bulk operations, exports, settings, workers, and provider adapters.
- CI, Docker deployment completion, backups, end-to-end/accessibility coverage, and operational documentation.
Known Gaps
- No initial User/Seller is provisioned in the development database yet. Run
npm run provision:adminafter setting its required local.envvariables. - Listings are partially implemented: draft creation, seller-scoped reads, publication gating, lifecycle writes, archive behavior, sold/archived inventory checks, and item-owned image metadata are complete; storage-backed processing remains.
- Sales, acquisitions, pricing, concrete image rendering, search, queue transport, persistent worker jobs, web UI, CI, and deployment services remain unimplemented.
npm installcurrently reports dependency audit findings. Review and remediate them before production deployment; do not usenpm audit fix --forcewithout reviewing its changes.
Local Commands
npm install
npm run db:up
npm run db:migrate
npm run provision:admin
npm test
The Compose database requires a local .env created from .env.example. To prepare the test database once after bringing PostgreSQL up:
docker compose exec -T postgres sh -c 'createdb -U "$POSTGRES_USER" pokemon_inventory_test'
Then migrate it using its TEST_DATABASE_URL value as DATABASE_URL, and run integration tests with both TEST_DATABASE_URL and TEST_REDIS_URL set. See README.md for the environment-file prerequisites.
Suggested Resume Prompt
Read docs/IMPLEMENTATION-STATUS.md and continue the next bounded milestone. Verify the existing baseline first, follow AGENTS.md, and keep the change focused with tests.