16 KiB
Implementation Status
Last updated: 2026-09-09
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.
- Public search is available at
GET /api/v1/public/searchthrough a normalized SearchService boundary with pagination, price filters, text matching, and sold-item visibility controls. - Public search also supports active category and condition lookup options, URL-backed filters, and allowlisted recent/price sorting.
- Public search responses include
itemspluspaginationmetadata (page,limit,total, andtotalPages); pending listings are visible by default and sold listings require an explicit filter. - Public product pages are available at
GET /api/v1/public/products/:slug; responses use a public DTO and require an active physical item with an available or pending listing. - Public listing collection and detail reads are available at
GET /api/v1/public/listingsandGET /api/v1/public/listings/:id; collection responses include pagination metadata and detail reads hide internal inventory identity. - Public listing detail responses include safe condition labels, latest market value, and public image URLs; unavailable image states are rendered accessibly in the gallery.
- Public inventory collection and detail reads are available at
GET /api/v1/public/inventoryandGET /api/v1/public/inventory/:id; responses join only active physical items to available or pending listings and exclude private item notes. - The public seller directory is available at
GET /api/v1/public/sellers; it returns only sellers with visible inventory and exposes display identity without user or contact fields. - The web workspace now has a Vue/Vite public catalog shell with Vue Router, a catalog API service, search composable, loading/error/empty states, and responsive search-first styling.
- The web catalog now provides keyboard-accessible listing cards with stable
/listings/:idlinks, a public listing detail page, and service-level detail loading/error handling. - Catalog search and pagination are synchronized to the browser URL (
qandpage) with accessible previous/next controls; the web port5173is declared in the project port registry. - The web catalog includes a stable
/products/:slugproduct detail route with API-backed loading, not-found/error states, and semantic product facts. - The visual design foundation is centralized in web tokens with light/dark/system runtime theme support; the fixture composition has moved to Storybook at port
6006with the accessibility addon and component stories for available, pending, and sold states. 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.
- Acquisition, sales, dashboard, pricing observations, market valuations, audited sale voiding, and manual market-value overrides are implemented with seller-scoped API and admin UI workflows.
- Seller-scoped bulk archive is implemented through the CSRF-protected
POST /api/v1/admin/bulkroute; it validates active inventory lifecycle transitions and never substitutes for the dedicated sale workflow.
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 |
Verified locally: npm test passes 116 tests with no failures; worker tests, web tests, web build, Storybook build, and Storybook interaction/accessibility checks pass. The integration suite and authenticated E2E workflow are defined in CI; they require the isolated test database/Redis services and should not be described as locally verified unless those services are available.
Next Milestone
Complete production configuration and operational maturity around the implemented vertical slices.
- Configure and validate an approved external pricing provider and provider-specific mapping data.
- Expand broader accessibility/manual coverage and address any remaining integration edge cases.
Acceptance criteria: production provider credentials are secret-managed, migrations and backups are exercised in the target environment, and CI runs integration, authenticated E2E, web, build, Storybook build, and Storybook interaction/accessibility gates.
Overall Roadmap
Work through one bounded vertical slice at a time. The linked documentation remains authoritative when this roadmap is less detailed.
Completed vertical slices include authentication, listings and image prerequisites, acquisitions, sales and audited corrections, dashboard metrics/alerts, pricing observations and quality handling, provider mappings, durable pricing refresh jobs, public search/detail/image states, admin workflows, CI integration/E2E gates, production Docker packaging, rollback/health checks, PostgreSQL backup verification, restore drill scripts, image-storage restore support, and deployment smoke checks.
Remaining roadmap areas are approved external provider credentials/adapters, broader accessibility/manual coverage, and deployment-specific NAS/TLS validation.
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.
- The provider abstraction, fixture provider, HTTP provider adapter, Product mapping workflow, durable refresh queue, worker execution, polling contract, retry policy, and completed-job purge operation are implemented. Production readiness still requires approved external provider credentials and provider-specific mapping data.
- API and worker production startup now reject fixture pricing, missing provider endpoints, and non-HTTPS provider endpoints before opening runtime connections.
- Pricing refresh requests now enqueue seller-scoped jobs and expose a safe
GET /api/v1/admin/jobs/:idpolling contract instead of doing provider work inline. - Product-level provider mappings now persist seller-scoped
PRODUCT/SEARCHrelationships and support active listing and controlled disabling. - Pricing refresh enqueue now resolves its provider from an active Product mapping and rejects unmapped Products explicitly.
- Local development and CI can enable a deterministic fixture provider for end-to-end refresh coverage; production provider adapters remain explicitly configured deployment boundaries.
- Pricing refresh jobs have a bounded three-attempt retry policy with exponential backoff. The worker schedules completed-job cleanup using configurable retention days and cleanup interval settings.
- Bulk archive, asynchronous bulk pricing refresh, and Product-level bulk tag updates are implemented through the seller-scoped API boundary. Inventory
SOLDremains intentionally exclusive to the audited sale workflow. - Admin inventory JSON export is implemented at
GET /api/v1/admin/exports/inventory.jsonand serializes seller-scoped admin DTOs as a downloadable attachment. - Admin inventory CSV export is implemented at
GET /api/v1/admin/exports/inventory.csvwith escaped spreadsheet-safe fields and seller-scoped admin DTOs. - Admin inventory Excel-compatible export is implemented at
GET /api/v1/admin/exports/inventory.xlsusing the XML workbook format and seller-scoped admin DTOs. - Seller-scoped admin settings are implemented through
GET/PATCH /api/v1/admin/settingswith strict category allowlists and server-side partial-update merging. - Seller-scoped saved-view CRUD is implemented through the documented admin API with validated filter maps and repository-level ownership checks.
- Large inventory JSON exports can be enqueued through
POST /api/v1/admin/exports/inventory.json/jobs, polled throughGET /api/v1/admin/exports/jobs/:id, and downloaded after completion; the worker owns claim, completion, and failure processing. - Completed asynchronous inventory JSON exports can be downloaded through
GET /api/v1/admin/exports/jobs/:id/download. - PostgreSQL restore verification and image snapshot restore scripts are implemented and covered by shell tests as part of the deployment drill workflow.
npm audit --omit=devcurrently reports zero production vulnerabilities. Re-run the audit after dependency changes and review development-dependency findings separately before production releases.
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.