ff505bc752
14 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| ff505bc752 |
#233: prune conversion job payloads for every job kind
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 5m12s
CI / Build container images (pull_request) Successful in 3m28s
CI / Auth e2e pack (pull_request) Successful in 8m33s
CI / Import/export fidelity gate (pull_request) Successful in 1m2s
CD / Build and push images (push) Successful in 29s
CD / Deploy to Test (push) Successful in 12s
CD / Smoke tests against Test (push) Successful in 1m22s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Failing after 5m9s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
The raw input/result bytes of import/export conversion jobs were kept forever; a deleted classified page could live on inside its last export. A new daily conversion-payload-prune job nulls both once a finished (succeeded or failed) job passes conversion.payloadRetentionDays (instance setting, default 30) — the row survives for status/audit. PENDING and RUNNING rows keep their payload, so the worker's stale-lock recovery path is untouched; a hand-requeued pruned job fails finally via conversionInputOf instead of crashing the worker. The input column becomes nullable; the migration backfills by clearing payloads of jobs already finished longer ago than the default period (recent results stay downloadable until they age out). Job-count fence in system.spec: 7 -> 8 (new scheduler registration). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| ed2225bb77 |
#196: audit-trail retention job
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 5m5s
CI / Build container images (pull_request) Successful in 2m48s
CI / Auth e2e pack (pull_request) Successful in 7m50s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 15s
CD / Deploy to Test (push) Successful in 16s
CD / Smoke tests against Test (push) Successful in 1m20s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 5m11s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m38s
CI / Import/export fidelity gate (push) Successful in 56s
audit.retentionDays (instance setting, default 365) bounds the audit_log: the daily audit-retention job deletes entries past the period and records the deletion itself (audit.pruned with count, cutoff and period) so a gap in the trail is always explainable. Lives in its own AuditRetentionService because the settings service audits its writes - folding retention into AuditService would close a constructor cycle. The read-access trail (#222-#225) is deliberately not covered; it gets its own period. security.md gains the Logging section the schema has cited for a while; the maintenance-job fence moves 6 -> 7 (the deliberate new row). Refs #196 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| afef45732a |
#191: feeds.enabled instance switch, feed-token log masking
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m52s
CI / Build container images (pull_request) Successful in 3m55s
CI / Auth e2e pack (pull_request) Successful in 7m52s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 16s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m55s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m34s
CI / Import/export fidelity gate (push) Successful in 58s
Chosen path: an instance master switch following the api.enabled/ mcp.enabled pattern — while off, both feed routes AND the feed-token management answer 404 (existence hidden). Default ON: feeds predate the switch, existing instances and their subscribed readers keep working; the VS-NfD reference configuration (#227) turns it off. Admin UI gets the toggle next to the API/MCP switches (i18n de+en). Moving the token out of the query string is documented as rejected: a path segment lands in the same proxy and request logs, and feed readers cannot send headers — that is why the credential is in the URL at all. What DID leak was our own request log (pino logs req.url): the req serializer now masks ?token= values (common/mask-token-param.ts), so no code path logs the credential. Refs #191 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 9c64166b10 |
Editable landing page for the Site Admin
Some checks failed
CD / Build and push images (push) Successful in 3m50s
CD / Deploy to Test (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 4m13s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Failing after 2m44s
CI / Import/export fidelity gate (push) Has been skipped
The public home page (/) now renders Markdown the Site Admin stores in the new home.content instance setting, through the same sanitizing pipeline as the legal pages; empty falls back to the built-in welcome text. New public GET /home/content, an Admin → Settings editor with live preview, and an e2e test covering default/configured/escaping/ admin-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 04e21a0aac |
Built-in MCP endpoint (Streamable HTTP) on top of the public API (#105)
All checks were successful
CD / Build and push images (push) Successful in 3m50s
CI / Lint, typecheck, test (push) Successful in 4m2s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 11s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 13s
CI / Auth e2e pack (push) Successful in 5m37s
CI / Import/export fidelity gate (push) Successful in 47s
AI clients talk to the instance directly at /api/mcp — under the /api/ path (deviation from the issue's literal /mcp) so every existing reverse proxy already routes it; no deployment changes anywhere. - Transport: official @modelcontextprotocol/sdk server, STATELESS — each POST builds a fresh server+transport pair, no session store, replicas stay trivial; GET/DELETE answer 405. Auth per PAT bearer (#104 tokens), per-token rate limit (429 + Retry-After). - Own switches, independent of REST: instance mcp.enabled (admin settings, default off; off = 404, feature invisible) + pond setting mcpEnabled (pond-settings toggle, default off) — pinned independent in both directions by tests. - Tools (thin wrappers over the #104 services, same permission gates, audit-logged writes): list_ponds, list_pages, read_page, search, create_page, update_page (replace semantics through the collab-owned restore path — open editors converge), add_comment, list_labels, set_page_labels (exact replace), export_pond (link to the REST ZIP). Tool errors carry the api error codes; results carry stable slugs/ids. MCP resources stay the documented stage-2 stretch goal. - Deliberately on the SDK's low-level Server API with a hand-written tool table (mcp-tools.ts): the typed registerTool generics drove tsc out of memory in a program this size; manual Zod validation keeps the wire behavior explicit. - PublicApiService exposure filtering parameterized ('api' | 'mcp', shared pondFeatureEnabled helper) — one implementation, two switches. - Docs: "Connect Claude Code / MCP clients" section in public-api.md (claude mcp add one-liner + mcp-remote bridge for stdio clients). Verification: 8-test e2e pack driving the real MCP SDK client over Streamable HTTP against a listening api (initialize + tools/list, switch independence in both directions, anonymous/garbage 401, opt-in 404 semantics, page roundtrip incl. restore-NOTIFY, labels/comments, read scope blocked from writes with scope_required); live check through the web proxy against the seeded stack (tools list, create, read, update, search — LIVE CHECK PASSED); full api suite 61/61 files green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 0c85293830 |
Public REST API v1: personal access tokens, instance switch + per-pond opt-in (#104)
Some checks failed
CI / Lint, typecheck, test (push) Failing after 1m39s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m51s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m10s
CD / Promote to Int (push) Successful in 11s
Token-authenticated machine access at /api/public/v1 — the foundation for the built-in MCP endpoint (#105). Personal access tokens: - api_tokens table (SHA-256 hash, scope read|write, optional pond restriction, expiry, revocation, throttled last-used) + migration; secrets are dt_pat_<random>, shown exactly once - lifecycle endpoints under /users/me/api-tokens (session-only — a leaked token can never mint more tokens) with audit entries api.token_created/api.token_revoked - settings UI section (create with scope/expiry/pond restriction, one-time reveal with copy, list with status + revoke), de+en Activation (404 semantics per #60 on both levels): - instance setting api.enabled (default off, admin settings switch) - pond setting apiEnabled (default off, pond settings toggle; the PondsService settings-merge learned the key — the #92 lesson) Surface (/api/public/v1, excluded from the SPA's global prefix): - me, ponds, pages (list/read as Markdown+HTML, create from Markdown via the shared pipeline, PATCH title/content, DELETE to trash), search (permission-filtered + narrowed to exposed ponds, highlights as **…**), markdown ZIP export, labels (tree, create/rename/recolour/move/delete, assign/unassign), comments (threads, create, resolve/reopen) - content replacement travels the collab-owned document path: the new state lands as a MANUAL version "API update", then the established restore NOTIFY applies it — open editors converge, history stays append-only, no second lineage (VersionsService.replaceContent) - hand-maintained OpenAPI 3.1 document at /openapi.json, pinned to the controller by a route-coverage test in both directions Enforcement: - PublicApiGuard: instance switch → bearer PAT auth (request.user is the token's user) → per-token rate limit (429 + Retry-After) → scope (403 scope_required) → pond opt-in + token restriction - the shared PermissionGuard then applies the unchanged permission model; PageParamSource gained pondSlugParam for the slug+slug routes - no cookies anywhere → no CSRF surface (pinned by a hostile-Origin test) - every write audit-logged as api.write with the token attributed Tests/verification: - 12-test e2e pack: lifecycle, switches, permission matrix (reader/editor/outsider × scopes), restriction, page roundtrip incl. restore-NOTIFY assertion, labels, comments incl. policy, search narrowing, ZIP export, rate limit; full api suite 60/60 green (quota fixture via per-user override — never the instance default) - new collab-pack test proves an open editor converges onto an API content replacement (green against a local seeded stack) - UI smoke against the built SPA: token create/reveal/revoke, pond opt-in persists, admin switch persists (10/10) - docs/self-hosting/public-api.md + README link Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 5cef359b8f |
Nextcloud backup target: admin-configured, manual + scheduled uploads, in-app restore (#103)
All checks were successful
CI / Lint, typecheck, test (push) Successful in 3m45s
CD / Build and push images (push) Successful in 3m49s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m18s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Successful in 5m35s
CI / Import/export fidelity gate (push) Successful in 47s
Off-host backups for every self-hoster, configured entirely in the admin UI — supersedes the host-specific mirror plan behind #84. shared: - webdav.ts (new package entry like token-crypto): minimal WebDAV client with basic auth — PROPFIND (tolerant multistatus parser), MKCOL, PUT (streamed), GET, DELETE; Nextcloud DAV path derived from the plain server URL, explicit DAV bases pass through - backup-status.ts: additive remote-upload status in status.json, the restore-status.json contract (running/succeeded/failed + staleness bound), the backup_command/backup_maintenance NOTIFY channels, and the one-bundle-per-set naming (dorfteich-backup-<id>.tar.gz) - backup-set.ts moved here from apps/backup (api lists local sets) backup sidecar: - reads the backup.* instance settings directly from the database (admin changes apply next run; local retention row overrides the env) and the app password from the secret store - after each successful set: bundle dump + files archive + manifest into ONE self-contained tar.gz, upload via WebDAV per schedule (off/daily/weekly; manual runs always upload), prune remote bundles — never the newest — and record the outcome in status.json; upload failures alert via a new backupUploadFailed mail (de+en) - command listener on backup_command (run / restore) with a serial queue against the nightly timer - restore orchestrator: restore-status.json → maintenance NOTIFY → grace → (remote: download + manifest-verify bundle) → terminate other DB connections → shared perform-restore path (same code as restore.sh) → final status + maintenance exit api: - MaintenanceGuard (global, registered before the setup gate): 503 maintenance_mode while restore-status says running; health endpoints and the new public GET /backup/restore-status stay exempt; a stale running state (crashed sidecar) unblocks after 30 min - MaintenanceStateService watches the file and restarts the api after a successful restore (fresh caches, migrate-on-start for older dumps); main.ts refuses to touch the database while a restore runs — a container restarting mid-restore must not race pg_restore with migrate deploy - worker sweeps (conversion, mail outbox, scheduler) catch transient database failures instead of dying on an unhandled rejection — the restore's connection termination crashed the api in verification - backup admin endpoints under /admin/system/backup: settings (live connection test before save, password write-only into the secret store), nextcloud/test, sets (local via the ro backups mount + remote via WebDAV), run + restore (type-to-confirm backstop, source validation) — commands travel as NOTIFY payloads; audit actions backup.settings_changed/run_triggered/restore_requested - readyz: new warning-level backup_remote check while a target is configured (26 h daily / 170 h weekly bound) collab: - maintenance listener: on enter, persist + close every live session and refuse new connections until exit (failsafe timeout 30 min) — no in-memory document may write pre-restore content back afterwards web: - Admin → System backup section: status card with remote facts and a "Back up now" button, the Nextcloud settings form with test button, and the restore picker (local + remote sets, type-to-confirm) - global maintenance screen: any 503 maintenance_mode flips the SPA to a status page polling the exempt endpoint, reloading when the instance returns Verified end-to-end against a live stack (fresh DB, native api + sidecar, fake WebDAV server): configure → test → manual backup → bundle upload → readyz/sets/status surfaces → remote restore with maintenance gate, marker rollback and api restart; suites: shared 21, backup 9, collab 11, api 58 files green, lint + i18n:check + typecheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| c8aac13dfb |
Add Site-Admin system panel with persistent audit trail (#86)
All checks were successful
CI / Lint, typecheck, test (push) Successful in 3m14s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m45s
CD / Deploy to Test (push) Successful in 10s
CD / Smoke tests against Test (push) Successful in 1m11s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Successful in 5m20s
CI / Import/export fidelity gate (push) Successful in 45s
New /admin/system panel (operations.md §Maintenance jobs): the maintenance job list shows every registered job with truthful last-run data (new Job.lastDurationMs recorded by the scheduler) and a manual trigger that respects the run-mutex and is itself audit-logged; a backup card mirrors the sidecar's status.json including the freshness verdict; an audit-log viewer filters by actor, action, and time range with pagination; and a storage overview lists the largest ponds. Auth events and admin actions (grants, members, user/quota admin, plugins, settings, setup) now land in a new audit_log table through a central AuditService — which keeps emitting the established stdout log line — while content activity stays log-only by design. All endpoints are Site-Admin-only; covered by API DB tests and a Playwright pack in CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| fd2bdb3fb8 |
Add instance legal pages with public rendering and footer links (#82)
All checks were successful
CI / Lint, typecheck, test (push) Successful in 3m7s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m14s
CD / Deploy to Test (push) Successful in 12s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 9s
CI / Auth e2e pack (push) Successful in 5m9s
CI / Import/export fidelity gate (push) Successful in 45s
Imprint and privacy policy are two new Markdown instance settings (legal.imprint, legal.privacyPolicy), edited by Site Admins in a new "Legal pages" admin section with a toggleable rendered preview. The preview uses the same shared pipeline the server renders with (markdown → schema doc → escaped HTML), so stored markup can never smuggle script to visitors. The pages render publicly at /legal/imprint and /legal/privacy — as an SPA route plus, like #56, a self-contained server-rendered HTML document under /api/v1/legal/:kind. The endpoints are setup-exempt: legal information stays reachable even while the first-run wizard is pending. Unconfigured pages show a localized notice instead of 404ing, and Site Admins additionally get a warning banner linking to the settings. A new footer with both links appears on every SPA view (editor, auth screens, public pages) and in the server-rendered documents, whose shared shell moved to public/html-shell.ts and now renders its chrome in the instance default locale (ADR 0012). docs/self-hosting/legal-template.md ships imprint and privacy-policy templates in English and German whose sections mirror Dorfteich's actual processing activities (accounts, sessions, rate-limit IPs, proxy logs, transactional mail, content, export, deletion, no third-party requests), with a review checklist tied to security.md §Privacy. New `legal` i18n namespace (de+en); api and web e2e coverage including a new CI legal pack (footer navigation, notice vs. admin banner, and the admin form publishing a text end to end). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| f0a82bad20 |
Add the first-run setup wizard API with env-backed secret store (#80)
Some checks failed
CD / Build and push images (push) Successful in 3m16s
CI / Lint, typecheck, test (push) Successful in 3m5s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Failing after 3m35s
CD / Promote to Int (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m6s
CI / Import/export fidelity gate (push) Successful in 43s
CI / Build container images (push) Has been skipped
When the api runs against a database without the setup.completedAt marker, a global SetupGuard answers every non-exempt route with 503 setup_required; only /setup/*, health probes, and the session routes stay reachable. The wizard steps (POST /setup/admin|instance|smtp| registration|complete) write straight to their production homes; the Site Admin step signs its creator in, later steps require that session. Completing sets the marker and locks every step permanently (410, also across restarts, and not reopenable via PATCH /admin/settings). SMTP entered in the wizard is verified with a live delivery test first (failure blocks the step with the transport error as detail) and then persisted to the new env-backed secret store: a mode-600 dotenv file on the new `secrets` volume (SECRETS_FILE). Explicit container env always wins over the store; empty compose-passed strings count as unset. The mail transport now resolves lazily through SmtpConfigService so wizard changes apply without a restart. SETUP_ADMIN_* env pre-seeds the whole wizard at boot for automated deploys; a backfill migration marks instances that already have a Site Admin as completed, and seed/vitest global-setup do the same for fixture databases. The setup e2e suite provisions its own fresh database (CREATE DATABASE + migrate deploy) per run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 30891f99cf |
Add non-image attachments with allowlist, SVG policy, and file managers (#61)
All checks were successful
CD / Build and push images (push) Successful in 4m2s
CI / Lint, typecheck, test (push) Successful in 2m46s
CI / Auth e2e pack (push) Successful in 3m45s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m18s
CD / Promote to Int (push) Successful in 12s
Extend uploads (#27, ADR 0011) beyond images to a configurable general attachment allowlist, plus the page attachments section and the Pond Admin file manager. Backend: - Two instance settings: `upload.allowedExtensions` (lowercase, dot-stripped, images always allowed regardless) and `upload.svgPolicy` (reject | sanitize). - FilesService.resolveUpload: raster images still decided by magic bytes; SVG is sanitized with DOMPurify (scripts, event handlers, foreignObject stripped) or rejected per policy; everything else is admitted only if its extension is on the allowlist. A sanitized SVG's stored bytes are re-accounted so pond_usage matches disk. - Downloads set `Content-Disposition: attachment` for every non-raster type (office files, PDFs, SVG) with `nosniff`, so they can never execute inline; raster images stay inline for page embeds. - New endpoints: `GET /ponds/:id/files` (pond_admin: all files + usage + orphan flag), `POST /pages/:id/files` and `GET /pages/:id/files` (page-write/read: the attachments section). New error code `upload_type_not_allowed` (de+en). Frontend: - Page attachments section (AttachmentsPanel): upload, list with type glyph, size, and uploader, insert-as-link into the document (an internal media link that downloads, never renders inline), and delete. Toggled in the editor. - Pond file manager (PondFileManager) in pond settings for Pond Admins: every file with its referencing page (or an orphan flag) and storage usage. - Admin uploads settings form (allowlist + SVG policy). New `files` i18n namespace (de+en). Tests: - files.e2e.db.test.ts: allowlisted non-image accepted and served as a download; disallowed extension rejected; renamed-.html-as-.png still fails; SVG sanitized (scripts/handlers stripped) and reject-mode rejects; page attachment listing; pond file manager usage/orphan; non-admin denied. - New e2e pack apps/web/e2e/attachments.spec.ts (+ CI step): upload → list → insert link (verified attachment disposition + nosniff), disallowed-type error, pond file manager usage/orphan. Local: typecheck, lint, i18n:check, build all green; api-db 184, shared 121, web 50; attachments pack 3/3, members 3/3, content 5/5. Adds dompurify + jsdom to the api for server-side SVG sanitization. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| a645763679 |
Add page trash: soft delete, restore, and purge job (#31)
All checks were successful
CD / Build and push images (push) Successful in 2m5s
CI / Lint, typecheck, test (push) Successful in 1m45s
CI / Auth e2e pack (push) Successful in 1m50s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m11s
CD / Promote to Int (push) Successful in 10s
Backend: a generic maintenance-job scheduler (SchedulerService, `jobs` table) that any later maintenance job registers with instead of growing its own timer loop. Due-ness and the run-mutex both live in the DB row (`lastRunAt` survives a restart; claiming a due job is one atomic `UPDATE ... WHERE status != 'RUNNING'`), and an injectable ClockService lets tests simulate retention elapsing without waiting or faking the global clock. Trash endpoints: GET /ponds/:id/trash (list), POST /pages/:id/restore, DELETE /pages/:id/purge (manual, bypasses retention) — all sharing the same purge logic as the scheduled daily job (default 30-day retention, new trash.retentionDays instance setting). Purging deletes a page's content cache, update log, and attachment files/quota; page_versions is a placeholder until M3 exists. Direct navigation to a trashed page now 404s with a distinguishable `page_trashed` code for editors (a plain 404 for everyone else) instead of the generic not-found. Attachment.pageId — added in #27 but never wired up — now gets set on every page state save to whichever page's document currently embeds the file, which is what lets purge find a page's files. Frontend: a per-pond trash view (restore/purge), a "move to trash" action with confirmation in the page menu, and a trash link in the sidebar for pond owners. Also fixes react-query retrying 4xx responses for several seconds by default, which was masking the trash-hint 404 in the UI (and would have affected any other not-found/permission error the same way). Closes #31 |
|||
| 64928f0ac0 |
Quota foundation: overrides, resolution, race-safe consumption (#22)
All checks were successful
CD / Build and push images (push) Successful in 1m46s
CI / Lint, typecheck, test (push) Successful in 1m17s
CI / Auth e2e pack (push) Successful in 1m39s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m6s
CD / Promote to Int (push) Successful in 10s
- quota_overrides + pond_usage models (BigInt values, unique per
subject+key); migration 20260705185146_quotas
- instance-default quota keys in the settings registry (editors 5,
readers 50, additional ponds 0, storage 1 GiB, max file 25 MiB)
- QuotaService: getEffective with pond → user → instance resolution
(zero counts as a value, not a gap); assertCanCreateSharedPond and
checkAndConsume serialize via pg_advisory_xact_lock inside the guarded
write's transaction; release never drops below zero
- pond creation enforces additional_ponds (personal ponds don't count);
quota errors carry code quota_exceeded + {quotaKey, limit}, localized
- seed grants fixtures an additional_ponds override (default is 0)
- table-driven resolution tests, parallel-consumption test, e2e for the
pond-creation limit
Closes #22
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpQz6ypHJsLfMf4S6fyQEB
|
|||
| 1314096c94 |
Add account/session endpoints and typed instance settings with admin API
Server halves of #17/#18/#19: PATCH /users/me and change-password (verifies the current password, logs out every other session), GET/DELETE /users/me/sessions with current-session flag and protection against revoking oneself; InstanceSettingsService as a typed, cached, Zod-validated registry over instance_settings (schema-default fallback for invalid stored values, audit-logged writes) consumed by the signup flow; /admin/settings behind the new SiteAdminGuard with strict unknown-key rejection. SessionsService moves to its own module to keep Auth/Users acyclic. Three new e2e suites bring the api to 42 tests. Part of #17, #18, #19 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |