Files
NianAIGC/progress.md
2026-05-29 15:54:13 +08:00

448 lines
27 KiB
Markdown

# Progress Log
## Session: 2026-05-28
### Phase 1: Repository Survey
- **Status:** complete
- **Started:** 2026-05-28 10:26 CST
- Actions taken:
- Read the planning-with-files skill instructions.
- Confirmed no prior planning files existed.
- Created lightweight planning files for this project understanding pass.
- Inspected top-level directory, Git status, file inventory, and root package/config candidates.
- Learned that this is not a Git repository and that the main app appears under `removed extracted runtime`.
- Read root README, root package metadata, runtime README, runtime package metadata, and a filtered non-media source list.
- Files created/modified:
- `task_plan.md`
- `findings.md`
- `progress.md`
### Phase 2: Architecture Mapping
- **Status:** complete
- Actions taken:
- Started tracing entry points and runtime structure from the extracted standalone bundle.
- Read startup, health-check, runtime-info scripts, environment example, and extraction notes.
- Ran `npm run info` and read bundle/app path manifests plus `.next` server/static file layout.
- Inspected representative compiled API routes for projects, generations, uploads, and prompt assembly.
- Searched compiled server chunks for Seedance/OSS/generation behavior.
- Re-read quoted dynamic API route paths for generation polling/retry and project detail/delete behavior.
- Summarized creation modes, starter catalog, planning cases, and avatar/outfit presets from content JSON.
- Files created/modified:
- `task_plan.md`
- `findings.md`
- `progress.md`
### Phase 3: Runtime & Verification
- **Status:** complete
- Actions taken:
- Started the runtime with `npm start`; Next reported ready on `http://127.0.0.1:3000`.
- Ran `npm run health`; health returned `ok: true`, `desktopManaged: true`, and both Seedance/OSS services unconfigured.
- Verified `/studio`, `/api/projects`, `/api/reference-templates?mode=video_studio`, and `/api/billing` with `curl --noproxy '*'`.
- Inspected the generated `.runtime/data/app-state.json`.
- Stopped the runtime server after verification.
- Files created/modified:
- `.runtime/data/app-state.json`
- `task_plan.md`
- `findings.md`
- `progress.md`
### Phase 4: Delivery
- **Status:** complete
- Actions taken:
- Prepared the final Chinese project overview for the user.
- Files created/modified:
- `task_plan.md`
- `progress.md`
## Test Results
| Test | Input | Expected | Actual | Status |
|------|-------|----------|--------|--------|
| Runtime info | `npm run info` | Prints manifest/routes/content summary | Succeeded | pass |
| Runtime startup | `npm start` | Starts Next standalone app | Ready on `http://127.0.0.1:3000` | pass |
| Health check | `npm run health` | `ok: true` | `ok: true`, services unconfigured | pass |
| Studio page | `curl --noproxy '*' -I /studio` | HTTP 200 | HTTP 200 | pass |
| Projects API | `curl --noproxy '*' /api/projects` | JSON response | `{"projects":[]}` | pass |
## Session: 2026-05-28 - EvoLink Image Engine Settings
### Phase 1: Trace Current Settings and Provider Flow
- **Status:** complete
- Actions taken:
- Restored planning context with the planning-with-files skill.
- Inspected settings persistence in `lib/server/app-settings.ts`.
- Inspected settings API and settings panel rendering.
- Re-read image generation service and dynamic image polling route.
- Findings:
- Settings are persisted into root `.env.local` and applied to `process.env` immediately after saving.
- Settings groups currently cover Jimeng/Volcengine Visual, Seedance, and OSS.
- Image jobs already store `provider`, `providerTaskId`, request/response payloads, and import completed remote image URLs as assets.
- Current provider type only allows `volcengine-visual`, `seedance`, and `mock`.
### Phase 2: Add EvoLink Provider Adapter
- **Status:** complete
- Actions taken:
- Added `lib/evolink/image-client.ts` with image engine selection, EvoLink settings, submit/query calls, payload construction, task id extraction, status mapping, and result URL extraction.
- Added `evolink` to the `GenerationJob.provider` union.
- Updated image job submission to route `image.generate` and `image.inpaint` through EvoLink when `IMAGE_CREATION_ENGINE=evolink`.
- Mapped existing image size presets to EvoLink ratio-style `size` values so `resolution` can control output tier.
- Kept `image.upscale` on Jimeng even when EvoLink is selected.
- Added EvoLink polling support inside the existing image sync flow.
### Phase 3: Expose Engine Settings
- **Status:** complete
- Actions taken:
- Added settings fields for `IMAGE_CREATION_ENGINE` and EvoLink API/model options.
- Updated settings status cards to show the active image engine and effective image interface mode.
- Added EvoLink provider labeling in the asset manager.
- Updated health response, README, and `.env.example`.
### Phase 4: Verification
- **Status:** complete
- Actions taken:
- Added focused tests for EvoLink payload mapping, inpaint mask mapping, task id extraction, status mapping, and result URL extraction.
- Ran `npm test`: 5 files passed, 12 tests passed.
- Ran `npm run build`: production build completed successfully.
- Started the local dev server and checked `/settings` in the in-app browser.
- Confirmed the settings page opens on the new engine tab, the EvoLink tab renders credential/model fields, and the status tab shows active image engine and image interface state.
- Stopped the local dev server after verification.
## Test Results - EvoLink Image Engine Settings
| Test | Input | Expected | Actual | Status |
|------|-------|----------|--------|--------|
| Unit tests | `npm test` | All tests pass | 5 files / 12 tests passed | pass |
| Production build | `npm run build` | Build succeeds | Build succeeded | pass |
| Settings UI | Browser `/settings` | Engine/EvoLink/status tabs render | Confirmed visible content | pass |
## Error Log - EvoLink Image Engine Settings
| Timestamp | Error | Attempt | Resolution |
|-----------|-------|---------|------------|
| 2026-05-28 20:58 CST | Browser wait for `EvoLink API Key` hit a transient detached element after tab click | 1 | Re-read the page body and confirmed the EvoLink tab rendered correctly |
| 2026-05-28 21:03 CST | `npm start` failed because `.next` did not contain a production build after dev-server use | 1 | Added `prestart: next build` and made `npm start` bind to `127.0.0.1:3000` |
### Startup Fix
- **Status:** complete
- Actions taken:
- Reproduced `npm run dev` and confirmed the dev server reaches `Ready`.
- Reproduced `npm start` failure: `next start` could not find a production build in `.next`.
- Updated `package.json` so `npm start` automatically runs `next build` first and then starts on `127.0.0.1:3000`.
- Updated README startup notes.
- Verified `npm start` now builds and starts successfully.
- Verified `GET /api/health` returns `ok: true` and `/settings` returns HTTP 200.
### Status-Based Engine Management
- **Status:** complete
- Actions taken:
- Replaced the standalone engine settings tab with per-capability engine assignments in the settings status tab.
- Added `IMAGE_GENERATE_ENGINE` and `IMAGE_INPAINT_ENGINE` as configurable engine keys.
- Kept legacy `IMAGE_CREATION_ENGINE` / `IMAGE_PROVIDER` as fallback defaults for backward compatibility.
- Updated generation routing and health output to report/use per-capability engines.
- Updated README and `.env.example`.
- Ran `npm test`: 5 files / 12 tests passed.
- Ran `npm run build`: production build succeeded.
- Restarted production server after a rebuild and verified `/settings` renders status-based engine controls.
### Status Page Cleanup
- **Status:** complete
- Actions taken:
- Simplified the status tab after user feedback that the page felt messy.
- Replaced stacked per-capability cards with one compact table: function, engine, interface, model/key.
- Kept API status as four compact badges above the table.
- Ran `npm test`: 5 files / 12 tests passed.
- Ran `npm run build`: production build succeeded.
- Verified `/settings` visually on a temporary dev server at `127.0.0.1:3001`, then stopped that server.
## Error Log
| Timestamp | Error | Attempt | Resolution |
|-----------|-------|---------|------------|
| 2026-05-28 10:26 CST | `git status --short` failed: not a Git repository | 1 | Continue as a plain project folder and inspect files directly |
| 2026-05-28 10:27 CST | zsh `no matches found` for unquoted `[id]` route paths | 1 | Quote bracketed paths in future commands |
| 2026-05-28 10:31 CST | Plain `curl` calls to localhost hit a local proxy and returned 502/empty output | 1 | Used `curl --noproxy '*'` and verification passed |
## Session: 2026-05-29 - UI/UX, Seedance Limits, and Branding
### Full Product UI/UX Polish
- **Status:** complete
- Actions taken:
- Added `gsap` as the only new frontend dependency for motion.
- Added `lib/ui/motion.ts` with scoped reveal, crossfade, modal enter/exit, feedback pulse, cleanup, and `prefers-reduced-motion` handling.
- Reworked global UI tokens and responsive CSS in `app/globals.css`.
- Improved global shell accessibility with skip link, active nav state, focus styling, and GSAP reveal.
- Polished `/create`, `/assets`, `/settings`, and image editing screens while preserving backend API and route semantics.
- Removed visible English module labels, title helper descriptions, and right-side module badges after user feedback.
- Reduced topbar height and tightened mobile controls to avoid horizontal scrolling.
- Files created/modified:
- `package.json`
- `package-lock.json`
- `app/globals.css`
- `components/app-shell.tsx`
- `components/create-studio.tsx`
- `components/asset-manager.tsx`
- `components/image-editor.tsx`
- `components/settings-panel.tsx`
- `lib/ui/motion.ts`
### Seedance API Limits Alignment
- **Status:** complete
- Actions taken:
- Checked official Volcengine/Ark Seedance docs for video generation parameter restrictions.
- Confirmed Seedance 2.0 `duration` supports integer seconds from `4` to `15`, or `-1` for model auto duration.
- Changed the video duration UI from a numeric input to a fixed dropdown of `4 秒` through `15 秒`.
- Added video settings normalization for duration, ratio, and resolution in `lib/video-settings.ts`.
- Added support for `21:9` and `adaptive` ratios.
- Added model-aware resolution normalization so Seedance 2.0 fast falls back away from unsupported `1080p`.
- Updated Seedance client and video service payload normalization before task creation.
- Updated `.env.example`, README, and tests.
- Files created/modified:
- `lib/video-settings.ts`
- `components/create-studio.tsx`
- `lib/seedance/client.ts`
- `lib/server/video-generation-service.ts`
- `tests/video-settings.test.ts`
- `.env.example`
- `README.md`
### Product Branding and Logo
- **Status:** complete
- Actions taken:
- Located logo assets under `/Users/inmanx/Documents/icon/logo`.
- Renamed the product to `智念AIGC平台` across app metadata, topbar, package metadata, README, and app info output.
- Initially used the white transparent logo with a dark frame, then revised after user feedback that the logo was hard to see and the frame changed the brand feel.
- Generated a cropped transparent PNG from the black/blue logo variant and saved it as `public/logo/zhinian-logo.png`.
- Removed topbar logo border, background, and shadow.
- Desktop now shows logo plus `智念AIGC平台`; mobile hides the adjacent title and keeps the logo visible.
- Files created/modified:
- `public/logo/zhinian-logo.png`
- `components/app-shell.tsx`
- `app/globals.css`
- `app/layout.tsx`
- `package.json`
- `README.md`
- `scripts/print-app-info.mjs`
- `lib/server/app-settings.ts`
- `lib/server/generation-service.ts`
## Test Results - 2026-05-29 Product Polish
| Test | Input | Expected | Actual | Status |
|------|-------|----------|--------|--------|
| Unit tests | `npm test` | All tests pass | 6 files / 16 tests passed | pass |
| Production build | `npm run build` | Build succeeds | Build succeeded | pass |
| Health check | `npm run health` and `/api/health` | `ok: true` | `ok: true` | pass |
| Seedance video UI | Browser `/create?mode=video` | Duration choices are `4` to `15` seconds | Confirmed | pass |
| Responsive overflow | Browser widths `375`, `768`, `1024`, `1440` | No horizontal overflow | Confirmed | pass |
| Logo visibility | Browser `/create` | Logo loads without frame/background | Confirmed border `0px`, background `none`, shadow `none` | pass |
## Error Log - 2026-05-29 Product Polish
| Timestamp | Error | Attempt | Resolution |
|-----------|-------|---------|------------|
| 2026-05-29 | Seedance build failed because resolution tuple typing narrowed fast-model choices too far | 1 | Switched resolution membership checks to readonly string arrays and build passed |
| 2026-05-29 | Browser REPL variable names collided across verification cells | 1 | Reused or renamed persistent variables instead of redeclaring constants |
| 2026-05-29 | White logo required a dark frame on the light topbar and looked off-brand | 1 | Switched to black/blue logo, generated a transparent cropped asset, and removed frame styling |
## Session: 2026-05-29 - Account Login and SSO Protection
### Phase 13: Account Login and SSO Protection
- **Status:** in_progress
- Actions taken:
- Restored existing planning context and started a new Phase 13 for account login.
- Read the provided SSO integration guide.
- Confirmed the app currently has no login middleware/session helper and uses a fixed `demo-merchant` owner for first-party data.
- Started tracing routes and data access boundaries.
- Inspected first-party asset, generation, settings, health, upload, and file-serving routes.
- Confirmed public API v1 and worker routes use separate token mechanisms and should be preserved.
- Added auth config parsing, signed session cookies, OAuth2 authorize/callback/logout routes, JWT/JWKS verification, and current-user helpers.
- Added login page and topbar login/user/logout state.
- Added middleware protection for Web pages, first-party APIs, and local file-serving routes.
- Threaded authenticated owner IDs through first-party asset and generation routes.
- Added local file ownership checks through `storagePath`.
- Added SSO settings fields, health/settings status, env examples, README docs, deployment notes, and focused auth tests.
- Ran `npm test`: 8 files / 25 tests passed.
- Ran `npm run build`: production build passed with middleware and auth routes included.
- Browser-checked `/create` and `/auth/login` at desktop and 390px widths: no horizontal overflow, login config-missing state renders correctly.
- Verified forced-auth middleware behavior with a temporary dev server: `/create` redirects to `/auth/login`, first-party `/api/assets` returns 503 when auth is missing config, and `/api/v1/openapi.json` remains public.
- **Status:** complete
## Test Results - Account Login and SSO Protection
| Test | Input | Expected | Actual | Status |
|------|-------|----------|--------|--------|
| Unit tests | `npm test` | All tests pass | 8 files / 25 tests passed | pass |
| Production build | `npm run build` | Build succeeds | Build succeeded | pass |
| Browser layout | `/create`, `/auth/login`, 1280px and 390px | No horizontal overflow | Confirmed | pass |
| Middleware redirect | `GET /create` with `ZHINIAN_AUTH_REQUIRED=1` and missing auth config | Redirect to login | 307 to `/auth/login?next=%2Fcreate&error=auth_not_configured` | pass |
| First-party API guard | `GET /api/assets` with required auth and missing config | 503 JSON | `{"error":"认证配置不完整。"}` | pass |
| Public API preservation | `GET /api/v1/openapi.json` with required auth and missing config | 200 | 200 OK | pass |
## Error Log - Account Login and SSO Protection
| Timestamp | Error | Attempt | Resolution |
|-----------|-------|---------|------------|
| 2026-05-29 | zsh expanded unquoted `[id]` dynamic route paths again while reading route files | 1 | Re-ran the reads with single-quoted route paths |
| 2026-05-29 | `npm run build` failed because DOM `JsonWebKey` type does not include JWKS `kid` | 1 | Switched verifier typing to Node crypto `JsonWebKey` with a local `kid` extension |
## Session: 2026-05-29 - Password Captcha Login
### Phase 14: Password Captcha Login
- **Status:** complete
- Actions taken:
- Safely inspected the provided captcha and password grant response shape without printing tokens.
- Added `components/auth-login-panel.tsx` for account/password/captcha login on `/auth/login`.
- Added `/api/auth/captcha` to proxy image captcha requests to the auth service.
- Added `/api/auth/password` to call password grant server-side, verify the returned JWT, and set the same signed session cookie.
- Kept the original OAuth Authorization Code link as a secondary login option.
- Updated README, Chinese README, and deployment docs.
- Ran `npm test`: 8 files / 25 tests passed.
- Ran `npm run build`: production build passed.
- Restarted the dev server on `127.0.0.1:3001`.
- Browser-tested password captcha login with the user-provided test account; login reached `/create`, the topbar showed the authenticated username, and logout returned to `/auth/login?loggedOut=1`.
## Test Results - Password Captcha Login
| Test | Input | Expected | Actual | Status |
|------|-------|----------|--------|--------|
| Captcha endpoint | `${AUTH_BASE}/code/image?randomStr=...` | Image response | PNG 100x40 | pass |
| Password grant | Provided password grant sample | JWT token response | 200 with access/refresh tokens, sanitized in logs | pass |
| Local password login API | `POST /api/auth/password` | Signed session cookie | 200 and `zhinian_session` set | pass |
| Browser login | `/auth/login` form | Redirect to `/create` | `/create` rendered with authenticated username in topbar | pass |
| Logout | Click topbar logout | Return to login | `/auth/login?loggedOut=1` | pass |
## Session: 2026-05-29 - Server One-Command Deployment Support
### Docker and Script Deployment
- **Status:** complete
- Actions taken:
- Added `.dockerignore` to keep secrets, local runtime data, Next build cache, dependencies, and bulky legacy media out of Docker build context.
- Added a multi-stage `Dockerfile` using Node 22 Alpine, `npm ci`, `next build`, production dependency pruning, and `next start` on `0.0.0.0`.
- Added `docker-compose.yml` with `zhinian-aigc` service, `.env.local` env file, `APP_PORT` host mapping, persistent `./.runtime:/app/.runtime`, restart policy, and HTTP healthcheck.
- Added `scripts/setup.sh` for local preparation.
- Added `scripts/deploy.sh` for server deployment with Docker Compose detection, `.env.local` creation, runtime directory creation, image build, and background startup.
- Added `start:server` npm script for non-Docker Node/PM2 deployment.
- Updated `.env.example` with `APP_PORT`.
- Expanded `README.zh-CN.md` with server deployment, Docker commands, Node deployment fallback, and backup notes.
- Added a short deployment entry to `README.md`.
- Files created/modified:
- `.dockerignore`
- `Dockerfile`
- `docker-compose.yml`
- `scripts/setup.sh`
- `scripts/deploy.sh`
- `.env.example`
- `package.json`
- `README.md`
- `README.zh-CN.md`
## Test Results - Server Deployment Support
| Test | Input | Expected | Actual | Status |
|------|-------|----------|--------|--------|
| Shell syntax | `bash -n scripts/setup.sh scripts/deploy.sh` | No syntax errors | Passed | pass |
| Unit tests | `npm test` | All tests pass | 6 files / 16 tests passed | pass |
| Production build | `npm run build` | Build succeeds | Build succeeded | pass |
| Local health | `curl --noproxy '*' /api/health` | `ok: true` | `ok: true` | pass |
| Docker CLI availability | `docker --version` | Docker version if installed | No Docker CLI output in current environment | not run |
## Session: 2026-05-29 - Deployable Handoff and Integration Surface
### Implementation
- **Status:** complete
- Actions taken:
- Added `docs/DEPLOYMENT.md` for operations deployment, environment variables, health checks, runtime persistence, reverse proxy guidance, and validation checklist.
- Added `docs/API.md` for partner authentication, task lifecycle, job creation, asset upload/register, asset query/download, idempotency, webhook signing, and error handling.
- Linked deployment/API docs and the OpenAPI route from both README files.
- Expanded `/api/v1/openapi.json` to document capabilities, assets, asset download, jobs, job detail, cancel, request schemas, response schemas, and API key auth.
- Added authenticated `/api/v1/assets/:id` and `/api/v1/assets/:id/download` endpoints.
- Restricted public asset listing/detail/download to assets visible to the authenticated API client.
- Added API client tags to uploaded assets and API-generated output assets so long-lived integrations can query/download their own results.
- Added a deploy-script health check and printed API documentation/OpenAPI hints after startup.
### Verification
- **Status:** complete
- Results:
- `npm test`: 7 files / 21 tests passed.
- `npm run build`: production build succeeded and included `/api/v1/assets/:id`, `/api/v1/assets/:id/download`, and `/api/v1/openapi.json`.
- Local HTTP smoke test with a temporary API key returned `/api/v1/capabilities` and expanded OpenAPI paths.
- Local HTTP smoke test uploaded a PNG through `/api/v1/assets`, fetched `/api/v1/assets/:id`, and downloaded matching binary bytes from `/api/v1/assets/:id/download`.
- Mock-provider task flow created a queued job through `/api/v1/jobs`, Worker processed it to `succeeded`, generated output asset carried the API client tag, and download returned an attachment response.
## Session: 2026-05-29 - Engine-Aware Image Tuning
### Implementation
- **Status:** complete
- Actions taken:
- Replaced the image generation text-influence range slider with select options.
- Added create-page engine detection from `/api/health`.
- For Jimeng image generation, the UI now shows `文本影响` options: `创意 35`, `均衡 50`, `贴合 70`, `严格 85`, and submits `scale`.
- For EvoLink image generation, the UI now shows `生成质量` options: `快速`, `标准`, `精细`, and submits `quality`.
- Added per-request EvoLink `quality` support in the payload builder.
- Updated public API request typing, OpenAPI schema, API docs, and focused tests.
### Verification
- **Status:** complete
- Results:
- `npm test`: 7 files / 22 tests passed.
- `npm run build`: production build succeeded.
- Agent-browser snapshot on `/create` showed current EvoLink mode rendering `生成质量` with `快速 / 标准 / 精细`.
- Desktop and mobile screenshots showed the create-page controls fitting cleanly.
- Browser page errors list was empty.
## Session: 2026-05-29 - Standalone Login Page Polish
### Implementation
- **Status:** complete
- Actions taken:
- Updated the app shell so `/auth/*` pages render without the shared topbar and skip link.
- Reworked `/auth/login` into a standalone branded surface with logo, platform name, and account/password/captcha login box.
- Removed the visible `统一认证中心` login action from the login page.
- Kept login motion on the existing GSAP helper layer for scoped reveal and feedback animation.
### Verification
- **Status:** complete
- Results:
- In-app browser mobile-width check confirmed no topbar, no SSO link, logo/platform name present, login panel present, and no horizontal overflow.
- Browser viewport checks at 1280x800 and 390x844 confirmed the same layout invariants.
- `npm test`: 8 files / 25 tests passed.
- `npm run build`: production build succeeded and included `/auth/login`.
- After restarting the dev server on `127.0.0.1:3001`, the login page still rendered without the topbar or SSO entry.
- Earlier auth verification for this session passed form login, redirect to `/create`, and logout back to `/auth/login`.
## Error Log - Server Deployment Support
| Timestamp | Error | Attempt | Resolution |
|-----------|-------|---------|------------|
| 2026-05-29 | First `scripts/deploy.sh` draft had a shell quoting error while stripping quotes from `APP_PORT` | 1 | Simplified quote stripping and verified with `bash -n` |
| 2026-05-29 | Docker CLI is unavailable in the current local environment | 1 | Documented that Docker build should be validated on the target server; local Next build/test/health passed |
## 5-Question Reboot Check
| Question | Answer |
|----------|--------|
| Where am I? | Complete |
| Where am I going? | No remaining planned phases |
| What's the goal? | Understand and explain the whole project |
| What have I learned? | This is an extracted standalone Next.js runtime for 智念创作助手, with local JSON persistence and optional Seedance/OSS integrations |
| What have I done? | Completed repository survey, architecture mapping, and runtime verification |
## Session: 2026-05-29 - Task Management and Public API v1
### Planning and Scope
- **Status:** in progress
- Actions taken:
- Confirmed the current app already has `GenerationJob`, image/video submit routes, polling routes, Supabase/local JSON persistence, and Docker Compose deployment support.
- Accepted the user decision that multi-task support should be implemented as task management logic, not a separate message queue system.
- Set the implementation path: API Key auth, `/api/v1` public routes, task-state/locking fields, provider execution via Worker, and a Docker Compose worker service.
### Implementation
- **Status:** complete
- Actions taken:
- Extended `GenerationJob` with external client, idempotency, priority, retry, lock, timing, and webhook fields.
- Updated local JSON and Supabase mappings, plus `supabase/schema.sql` with queue indexes and `claim_generation_jobs`.
- Changed image/video submit services so creation enqueues jobs only; provider dispatch and polling now happen through `advanceImageJob` / `advanceVideoJob`.
- Added task manager, API Key auth, public idempotency helper, webhook signing/delivery, internal Worker tick route, and `scripts/worker.mjs`.
- Added `/api/v1/capabilities`, `/api/v1/assets`, `/api/v1/jobs`, `/api/v1/jobs/:id`, `/api/v1/jobs/:id/cancel`, and `/api/v1/openapi.json`.
- Added `npm run worker`, `npm run worker:once`, and a `zhinian-worker` Docker Compose service.
- Updated README files and `.env.example` with API/Worker/Webhook configuration.
### Verification
- **Status:** complete
- Results:
- `npm test`: 7 files / 21 tests passed.
- `npm run build`: production build succeeded.
- `npm run health`: returned `ok: true`.
- Local `/api/v1/capabilities` with API Key returned capabilities.
- Local `/api/v1/jobs` created a queued job with idempotency key.
- `npm run worker:once` claimed the queued job and processed it to `succeeded` in mock mode.
- Docker CLI is unavailable in this local environment, so `docker compose up --build` still needs server-side validation.