root and Claude Opus 5.5
e5e42b1f0d
feat(web): W3-1 shared console routes, nav, api/sse/query-keys and shared components
...
- routes.ts registers all workspaces under /console (+ detail routes, engine, audit, login)
- api.ts: cookie session first (credentials include, CSRF header), Bearer only with stored key, 401 -> /login
- sse.ts: shared EventSource with ?after=seq catch-up, backoff reconnect, per-type subscribe
- shared components: data-table, filter-bar, detail-sheet, timeline, json-view, confirm-dialog,
form-field, kv-list, stat-tile, error/loading states; ui tabs and textarea
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 20:21:29 +08:00
root and Claude Opus 5.5
a24bda952b
feat(server): W2-B sources, identity resolution, metadata and site workbench
...
- Sites / accounts / feeds: registry view (/site-definitions), site CRUD with
enable/disable (history stays queryable, marked site_disabled), accounts and
feed URLs stored as secrets (only secret_ref / redacted urlDisplay exposed),
feed enable/disable/reset/fetch; one shared rate limiter + adapter per site.
- Tasks: ingestFeed (FeedFetchState/FeedCursor on feed_source, observation
feed+guid upsert with digest-change updates, source_release site+id
aggregation across feeds, auth failure -> account invalid + decision, no
retry, gap -> feed.gap_suspected -> budgeted backfill snapshot),
resolveObservation (parseRelease -> local recall via title_alias pg_trgm +
accepted identities -> TMDB -> resolveIdentity; auto binds entity /
provisional units / identities / aliases, otherwise identity_candidate pool
with evidence version and bounded review backoff; manual lock respected),
refreshMetadata (metadata_field + field_override, user locks win, paths
untouched), snapshotSite (site_snapshot coverage records).
- Events: feed.due, feed.gap_suspected, release.observed (self-consumed),
release.resolved, decision.created/updated; scheduler in worker start().
- Identity API: entity detail with redirects, manual mapping correction with
identity_revision, merge with identity_redirect, pool list/assign/dismiss/
retry, content unit numbering lock, aliases, external identities.
- Decisions: list/summary (by entity and kind)/detail/resolve; identity
options executed directly.
- Workbench: /sites/{id}/releases (snapshot first, coalesced live search with
backoff, origin + freshness) and /sites/{id}/releases/{rid} (volatile field
recheck times, refresh keeps data on failure).
- Capabilities: catalog.lookup, release.search, release.inspect,
identity.resolve.
- Migrations 0018-0020: site.adapter api, source_release.resolution_locked,
site_snapshot, identity_candidate, resolution_attempt, identity_redirect,
metadata_field, field_override.
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 20:12:01 +08:00
root and Claude Opus 5.5
829ca4f7ac
feat(server): W2-C rules, downloads, import, organization records, library and dashboard
...
- Rules API: subscriptions, acquisition slots (revision lock), policy revisions
(draft -> publish = automation_grant, rollback as new revision), preference
tags with suppression records (agent cannot re-add suppressed fingerprints).
- Tasks: evaluateAcquisition (mode/evaluate/budget/cache volume -> frozen plan
with slot reservation, candidate reasons as evidence artifacts),
submitDownload (withAttemptToken fencing, already_exists adoption),
verifyAndImport (frozen organization_record + file results, resumable
copy/move/cross-volume steps, assets/locations/bindings in one tx),
cleanupDownloadCache (verified+retention+H&R only, assertSafeToDeleteData),
reconcileLibrary (root sentinel, suspected -> confirmed missing).
- Worker loop: qB sync with tag adoption, no-progress evidence, hard threshold
pause / resume-watermark resume, sweepers for deferred plans and imports.
- Public organization records API incl. changes cursor (change_xid guard),
media library list/detail, inventory overlay, Emby inventory, dashboard.
- Contracts: SlotTemplate, AcquisitionPolicyRules, CandidateEvaluation,
library/inventory/dashboard read models.
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 20:08:47 +08:00
root and Claude Opus 5.5
1cd523d3e6
feat(server): wire PgEventBus and event-driven settings cache into process assembly
...
main.ts starts the PG-backed SSE bus (api/worker processes now see each other's events)
and stops it on shutdown; bootstrap passes ctx.events to the settings reader so
settings.changed invalidates every process's cache.
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 19:59:54 +08:00
root and Claude Opus 5.5
ff230e5da7
feat(server): W2-A login gate, secrets, settings, instances, audit, engine panel API and PG-backed SSE bus
...
- Admin login gate (docs/design/auth-reference.md): admin_user/admin_session/login_attempt
(migration 0015), one-time setup token logged once + conditional UPDATE, argon2id via
hash-wasm (WASM; compiled binary has no --allow-ffi), sha256-stored sessions in HttpOnly
SameSite=Lax cookie umt_session (fixed 12h TTL, soft revoke), HMAC-derived double-submit
CSRF, IP (5) + account (15) failure limits per 15 min, password change revokes other
sessions, optional TOTP with replay counter, AuditAction enum.
- Unified auth middleware: cookie session or Bearer API key -> c.var.actor
{type: admin|api_key, id, name, scopes}; requireScope works for both; requireAdminSession.
- ctx.secrets: AES-256-GCM, per-secret HKDF-SHA256 key from SECRET_MASTER_KEY, AAD bound
to (id, purpose); ctx.settings: 30s cache invalidated by settings.changed events.
- Endpoints: /auth/*, /settings, /api-keys, /secrets, /audit-events, library-roots,
downloader/media-server instances (+test), path-mappings (+verify),
PUT /sites/{id}/credentials, /engine/runs|stats|cancel (read-only openworkflow schema).
- SSE: PgEventBus (sse_event table, migration 0016, LISTEN/NOTIFY umt_events, catch-up by
seq, pruning, resync); EventBusPort is now async.
- Tests: login/rate limit/revocation/CSRF/TOTP, secrets round trip and no log leakage,
admin APIs against mock qB/Emby, SSE across two Deno subprocesses.
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 19:59:54 +08:00
root and Claude Opus 5.5
07db0dac5b
docs: list @umt/mapping and server modules in repo README
...
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 19:31:30 +08:00
root and Claude Opus 5.5
724c360a75
docs(plan): mark W1 review B3-B9 and S2 status after W2-0
...
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 19:31:24 +08:00
root and Claude Opus 5.5
9858b8494d
feat(server): ServerModule skeleton with shared ServerContext, worker runs engine + outbox relay (B6)
...
modules/{auth-settings,sources-identity,acquisition-organize,agent} placeholders listed in
SERVER_MODULES; ServerModule registers routes (under /api/v1, authenticated), tasks,
outbox routes, capabilities and background loops. bootstrap() builds ServerContext (db,
config, logger, events, engine, outbox writer, secrets placeholder, settings reader,
capability registry), fills unregistered tasks with stubs and rejects duplicate task or
outbox ownership. worker starts the OpenWorkflow worker, OutboxRelay and module start().
--role migrate also migrates the engine schema; api/worker connect without migrating.
tasks: TaskRegistry.has, migrateEngineSchema.
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 19:30:55 +08:00
root and Claude Opus 5.5
ef3998767c
style(adapters): throw instead of returning never in assertSafeToDeleteData
...
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 19:30:55 +08:00
root and Claude Opus 5.5
fa9221086b
feat(adapters,testkit): guarded qB data deletion (B7) and mock qB file materialization (B8)
...
adapters: assertSafeToDeleteData({contentPath, libraryRoots, importVerified}) rejects
paths inside/equal to/above any library root, relative or root paths, empty roots and
unverified imports; QbClient.delete({deleteFiles:true}) requires a per-hash
SafeDeleteCheck and throws DownloaderError('unsafe_delete') before sending anything.
testkit: mock qB complete(hash, {files, writeFiles}) writes random-content files under
save_path (opt-in, size-capped, traversal-safe); deleteFiles=true removes them.
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 19:22:56 +08:00
root and Claude Opus 5.5
42ecc93fa5
feat(mapping): centralize domain <-> contracts mappings (B5)
...
New @umt/mapping (depends on domain + contracts only): download status + pause reason,
evidence level / media kind, identity resolution status, slot status / spec columns /
evaluation, ParsedRelease <-> source_release.parsed, SiteRelease <-> source_release,
PathBinding, OrganizePlan -> organization_record / file results / organize.plan output.
Tables are Record<union, ...> so adding a value on either side fails typecheck or tests.
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 19:20:30 +08:00
root and Claude Opus 5.5
da8e53bbca
feat(db): 0013 tasks tables and 0014 agent_run_event; tasks uses db migrations
...
tasks_run_meta/tasks_fact/tasks_op_ledger move into packages/db (if not exists, old
tasks_migration tables dropped); packages/tasks/migrations and migrateTasksTables
removed, tests migrate their schema with @umt/db. agent_run_event(run_id, seq, type,
at, payload) with (run_id, seq) primary key; types regenerated, db-sync covers it.
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 19:15:50 +08:00
root and Claude Opus 5.5
db5352eecd
refactor(contracts,agent): single capability spec and agent run event definitions in contracts
...
B3: CapabilityDescriptor gains replay; CapabilitySpec + capabilitySpec(id) assemble
CAPABILITY_CATALOG + CAPABILITY_IO; drop CapabilityDefinition/CapabilityRisk. agent
registry registers contracts CapabilitySpec (kind/allowedKinds instead of its own
scope enum; approve also requires authorize). AgentRun port renamed AgentRunHandle.
B9: contracts AgentRunEvent/AgentRunEventBody/AgentRunEventRow/AgentUsageTotals with
row and SSE mapping helpers; agent onEvent receives AgentRunEvent, delivered serially
in seq order, firstSeq continues numbering across processes.
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 19:15:49 +08:00
root and Claude Fable 5.1
8f59bf3772
style(contracts): sort events.ts imports
...
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 19:04:32 +08:00
root and Claude Fable 5.1
bc3ff4fb25
docs(plan): W1 wave-end independent review
...
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 19:03:08 +08:00
root and Claude Fable 5.1
c33034a289
fix(adapters): crossDeviceMove verifies pre-existing target with sha256 before deleting source
...
With the default hash="size", a target that already existed with the same
size but different content was treated as "already copied" and the source
was deleted (data loss; REQ §2.1 cleanup needs verified import). The
pre-existing-target comparison now always uses sha256; test covers the
same-size/different-content case with default options.
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 19:01:46 +08:00
root and Claude Fable 5.1
931081459e
fix(contracts): add release.resolved event consumed across W2-B/W2-C
...
Plan §5 has W2-B emit and W2-C consume `release.resolved`, but EventType /
EVENT_PAYLOADS lacked it, so the two parallel subtasks would each invent a
payload. Minimal payload: sourceReleaseId, observationId, mediaEntityId,
contentUnitIds, evidenceLevel (contracts EvidenceLevel).
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 18:59:37 +08:00
root and Claude Fable 5.1
31729f2bac
fix(tasks): align OutboxRelay with outbox_event status/next_attempt_at/idempotency_key
...
The relay only looked at published_at and ignored the ADR-002 §2.1 columns
added in db 0002_foundation: rows stayed status='pending' forever after
dispatch, failures were retried every tick without backoff, and the
row-level idempotency_key was never used as the engine enqueue key.
- claim status='pending' AND next_attempt_at<=now()
- enqueue with idempotency_key (fallback outbox:<id>)
- success: status=dispatched, published_at, attempts+1
- failure: attempts+1, exponential backoff (cap 5 min), last_error; dead at maxAttempts
- test helper table now mirrors db 0001+0002; tests cover backoff and dead
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 18:59:06 +08:00
root and Claude Fable 5.1
608e567edf
chore(deno): finish import extensions for sites/mp-sync paths
...
- sites/mp-sync relative imports already carry extensions; drop the biome
useImportExtensions override and --unstable-sloppy-imports in typecheck:deno
- mp-sync scripts: deno shebang (+x), static `import YAML from "yaml"`,
README/usage switched to `deno run -A`
- verified: check.mjs --to e6820a8 (49 unchanged), movieclaw-import --check (0 diff)
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 18:56:41 +08:00
root and Claude Fable 5.1
569bb29664
docs(plan): W2 service orchestration split
...
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 18:53:44 +08:00
root and Claude Fable 5.1
304feea141
chore(deno): migrate toolchain from Node 24 + pnpm to Deno 2.9.7 + TypeScript 7.0.2 (W1.5)
...
- root deno.json workspace (nodeModulesDir auto), npm versions registered once in imports, deno.lock;
remove pnpm-lock/pnpm-workspace/root package.json; members keep minimal package.json (name/type/exports)
- tasks: dev, typecheck (tsc 7.0.2 per member + deno check), lint/fmt (biome, useImportExtensions),
test (vitest via deno), check:tokens, build (web + deno compile), ci, db:migrate/reset, gen:types
- tsconfig base moved to config/ (no root tsconfig.json); deno types -> types/deno.d.ts; web drops baseUrl
- server: Deno.serve entry with graceful shutdown, --role migrate|preflight|probe, WEB_ROOT static SPA hosting;
drop @hono/node-server, tsx, esbuild build.mjs
- logger: redact child() bindings and serialize err by message/stack; sync stdout destination; tests
- relative imports get explicit extensions (sites/mp-sync paths left for follow-up)
- crash-recovery tests spawn `deno run -A`; tasks tests use unique per-file schemas
- single-container Dockerfile + entrypoint (migrate -> preflight -> api+worker watchdog), docker-compose.yml
- docs: README, runbook, ADR-001 superseded, plan §1 validation row
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 18:53:06 +08:00
root and Claude Opus 5.5
d9f2af2f3c
feat(adapters): port MoviePilot open site framework (7 spiders, 20 user parsers) + MovieClaw v0.27.0 site definitions + mp-sync tools (W1-3c)
...
- sites/spiders: Haidan/HDDolby/RousiPro/SunnyPT/TNode/TorrentLeech/Yema (+mTorrent wrapper), specialized SiteAdapter, indirect download resolved at download time
- sites/user-parsers: SiteParserBase + all 20 MP SiteSchema parsers (lxml XPath via xpath+xmldom), refreshSiteUserData with schema fallback
- sites/definitions: zod SiteDefinition schemas, 23 MovieClaw v0.27.0 (MIT) definitions via scripts/mp-sync/movieclaw-import.mjs, registry getSiteDefinition/listSiteDefinitions, NOTICE
- scripts/mp-sync: ledger.json (upstream sha256), check.mjs, build-ledger.mjs; docs/porting/mp-sync-ledger.md
- license-and-provenance: add copied-mit kind, re-anchor MovieClaw rows to v0.27.0
Co-Authored-By: Claude Opus 5.5 <[email protected] >
2026-09-27 18:39:27 +08:00
root and Claude Fable 5.1
cf85481d95
docs(spikes): Deno 2.9.7 + TS 7 compatibility spike and ADR-004
...
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 18:24:08 +08:00
root and Claude Fable 5.1
fe3613ee8f
docs(porting): MovieClaw v0.27.0 MIT asset assessment
...
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 18:19:46 +08:00
root and Claude Fable 5.1
007992d283
docs(design): OpenWorkflow dashboard reference for console engine panel
...
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 18:15:30 +08:00
root and Claude Fable 5.1
9853d11a19
chore: sync lockfile/workspace after W1; add auth reference and plan updates
...
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 18:12:40 +08:00
root and Claude Fable 5.1
cc8b3a7df4
feat(db,contracts): full v0.1 data model migrations and shared zod contracts (W1-1)
...
- migrations 0002-0012 split by domain (foundation/instances/content/identity/
sources/assets/policy/execution/agent/ops/organization): 41 tables, uuid pk,
created_at/updated_at + touch trigger, text+CHECK status sets, pgcrypto/pg_trgm
- key constraints: source_release(site_id, site_release_id), observation(feed_id, guid),
external_identity accepted partial unique, download_task(instance_id, torrent_hash),
one active acquisition_plan per slot, one active path_binding per entity/season,
asset_location root+path uniqueness, organization_record change_seq/change_xid cursor
- secret table (ciphertext/nonce/kdf) + secret_ref columns; outbox dispatch fields (ADR-002)
- src/types.ts generated from the migrated schema (scripts/gen-types.ts), drift-checked in tests;
int8 -> number, date -> string parsers; migrator accepts migrationTableSchema
- contracts: Row/Create/Update schemas per domain, public organization-record API
(changeSeq cursor), GlobalSettings, CapabilityDescriptor + 14 capability IO schemas,
typed SSE events, Api endpoint table
- tests: migration up/down round trip on real PG in an isolated schema, constraint checks,
contracts<->db column/enum sync, positive/negative parse for every exported schema
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 18:11:37 +08:00
root and Claude Fable 5.1
7ba712ea55
feat(domain): pure business logic for parsing, identity, slots, acquisition, paths, organize, download state (W1-2)
...
- release-parser: title/file/dir parsing with MP parity + deviations (airDate/issue,
cut, release group suffix fallback, dir+file merge, safe episode offset)
- identity: title normalization (NFKC, trad->simp map, season suffix) and
evidence-level candidate ranking (year tolerance ±1, no probabilities)
- slot: hard/soft/upgrade evaluation (missing vs unmet fields), state machine,
movie/unit completion (season pack must be covered by actual file list)
- acquisition: new_title/catch_up/upgrade modes, priority order, budget,
cache volume hard threshold with resume watermark (no double counting)
- path: immutable path bindings, restricted naming templates, sanitize,
conflict detection, downloader<->host path mapping
- organize: planOrganize with sidecar pairing, sample exclusion, existing
target policy, crossDevice marking, H&R -> conflict for move
- download-state: qB state mapping, completion, no-progress evidence
230 tests, ~97% statement coverage.
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 18:11:29 +08:00
root and Claude Fable 5.1
a6d049db81
feat(adapters): RSS and PT site adapters ported from MoviePilot v3 + mocks (W1-3b)
...
- rss: port of MP RssHelper._parse_impl/get_rss_link semantics with safe
XML (entities off, DTD/ENTITY rejected, size/item caps), ETag/Last-Modified,
backoff + Retry-After, circuit breaker with manual reset, passkey redaction,
overlap-window GUID+digest dedupe
- sites/spider: TS port of MP SiteSpider (indexer schema, search URL builder,
field DSL, filters, Jinja templates via nunjucks, login-page detection);
audiences definition hand-written as JSON; raw category id preserved;
shared per-site token bucket
- sites/mteam: port of MP MTorrentSpider (params, result mapping, promo
factors, genDlToken flow) without embedding the API key in enclosures;
business code/message checks
- testkit: mock RSS server, mock NexusPHP site, mock M-Team API
- docs: W1-3b migration mapping appended to porting ledger
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 18:10:34 +08:00
root and Claude Fable 5.1
ef2e504a94
feat(adapters): qBittorrent, filesystem, TMDB and Emby adapters with testkit mocks (W1-3a)
...
- qbittorrent: WebUI API v2 client (cookie/API-key auth, add/info/files/filePrio,
pause/resume with 5.x stop/start fallback, tags, delete with explicit deleteFiles,
maindata), error classes, idempotent submit with local infohash (own bencode)
and post-timeout hash/probe-tag lookup; state mapping per MoviePilot
- fs: copyWithVerify (tmp + fsync + size/sha256/sample + no-clobber commit),
moveSafe (EXDEV surfaced), resumable crossDeviceMove, statSafe/checkRoot with
root sentinel, budgeted incremental scan with cursor, ffprobe, statfs usage
- tmdb: multi-language search/details/seasons, LRU+TTL cache with coalescing,
rate-limit backoff, MoviePilot match chain (name normalization, year +-1)
- emby: Emby/Jellyfin items/seasons/episodes/MediaSources, failures -> unknown
- testkit: mock qB / TMDB / Emby servers, torrent builder, temp-tree fixtures
- docs: W1-3a porting table appended to downloader-and-sites ledger
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 18:07:48 +08:00
root and Claude Fable 5.1
9c881e737f
feat(agent): capability registry, Pi AgentRuntimePort, fake model provider and redaction guard (W1-5)
...
- CapabilityRegistry: descriptor(id/version/schema/scope/risk/idempotent/preview/replay),
invoke = scope limit -> input schema -> server-side authorize -> idempotency key -> handler
-> output schema -> audit; capability id <-> tool name mapping (download.submit <-> download__submit)
- PiAgentRuntime (pi-agent-core AgentHarness 0.87.1): whitelist tools + before_tool re-check,
redacted event stream, JSONL session at sessionDir/<runId>.jsonl, resume/abort/compact,
tool-call/duration/token/cost budgets -> abort + run_failed(budget)
- AgentSettings -> pi-ai createProvider (anthropic / openai-compatible), compaction mapping
- FakeModelProvider + createFakeRuntime (scripted, transcript-derived, survives crash/resume)
- buildSystemPrompt, redactForModel; 27 tests incl. SIGKILL + resume
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 17:55:28 +08:00
root and Claude Fable 5.1
1262340856
feat(tasks): OpenWorkflow-backed TaskEngine, task registry, outbox relay and op-ledger fencing (W1-4)
...
- TaskEngine port + OpenWorkflowEngine ([email protected] .1, schema openworkflow):
enqueue/signal/cancel/get/list/startWorker/stop
- defineTask registry with zod input schema; 9 stub task definitions (§3.1-8)
- TaskContext: step.run/sleep/waitForFact, redacting logger, traceId,
attemptToken, abortSignal driven by PG cancel flag
- signal persists fact to tasks_fact first; waitForFact checks DB before waiting
- OutboxRelay: FOR UPDATE SKIP LOCKED, idempotencyKey outbox:<id>
- withAttemptToken fencing on tasks_op_ledger (own migration, merged in W2)
- tests: kill -9 crash recovery (child worker), cancel, idempotency,
concurrent relays, early fact, lease-loss fencing
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 17:51:21 +08:00
root and Claude Fable 5.1
ec023a8405
docs(spikes): OpenWorkflow and Pi SDK compatibility spikes + ADR-002/003 (W0-B)
...
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 17:39:35 +08:00
root and Claude Fable 5.1
8b90d396b2
docs(porting): MoviePilot/MovieClaw porting ledger (W0-C)
...
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 17:37:14 +08:00
umt and Claude Fable 5.1
b22c440139
chore: scaffold monorepo and migrate uhd_web design system (W0-A)
...
- pnpm workspace: apps/{web,server}, packages/{contracts,db,domain,adapters,tasks,agent,testkit}
- server: Hono + zod-openapi, API Key auth with scopes, redacting pino logger, SSE event bus with replay, worker heartbeat
- db: Kysely + pg, file migrator, 0001_init (settings/api_key/audit_event/outbox_event)
- web: React Router 7 SPA console shell with 12 workspace placeholders, uhd_web tokens/ui/guard scripts
- docs: ADR-001 (Node over Deno), dev runbook, design system rules
Co-Authored-By: Claude Fable 5.1 <[email protected] >
2026-09-27 17:36:26 +08:00