Skip to content

feat: add PATCH /v1/web/settings with per-workspace retention#44

Merged
isuttell merged 2 commits into
mainfrom
agents/web-settings-patch
May 24, 2026
Merged

feat: add PATCH /v1/web/settings with per-workspace retention#44
isuttell merged 2 commits into
mainfrom
agents/web-settings-patch

Conversation

@isuttell

@isuttell isuttell commented May 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 3 backlog item #2 (docs/ops/project-status.md, docs/ops/web-app-todo.md). Adds the dashboard settings write path and makes retention a real per-workspace value instead of the global default.

  • workspaces gains auto_deletion_days (int, default 30, CHECK between 1 and 90) via migration 0007; Drizzle schema + snapshot updated, db:check green.
  • GET /v1/web/settings now reads the persisted per-workspace value (was deriving from the global USAGE_POLICY default).
  • PATCH /v1/web/settings (workos access token, admin scope, idempotent, actor rate limit) updates workspace name + retention through runCommand and writes a workspace.settings.updated audit event.

Changes

  • contracts: UpdateWebSettingsRequest (bounds derived from mvpUsagePolicy.min/max_ttl_seconds = 1..90 days; name 1..120); web.settings.update route; HttpMethod gains PATCH; OpenAPI path + golden regenerated; MVP route-registry test updated.
  • db: auto_deletion_days on schema/type/snapshot + migration 0007; workspaces.update port + Postgres/local adapters; updateWebSettings in RepositoryCore; toWebSettings dedups the GET/PATCH shape; both workspace-provisioning paths set the default.
  • api: webUpdateSettings handler + mount, mirroring the web.apiKeys.create mutation pattern.

Risk

Low. Additive column with a safe default (30) and an idempotent, re-runnable migration; pre-launch so no back-compat surface. Bounds enforced at both the Zod contract and the DB check constraint. New route is admin-scoped and goes through the existing idempotent command/audit path.

Test plan

  • pnpm verify green (70/70 turbo tasks): lint, typecheck, test, openapi:check, db:check.
  • api worker tests +8 (PATCH happy path, out-of-range -> 400, forbidden/non-member, idempotency); db repo tests +2 (persisted update + audit, GET reflects persisted value).
  • Local CodeRabbit review run before opening (no actionable findings).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added PATCH /v1/web/settings to update workspace name and auto-deletion days (requires idempotency key).
    • Workspace auto-deletion days configurable between 1 and 90; default is 30 days.
  • Database

    • Persisted auto-deletion setting in workspace records and enforced with a DB constraint/migration.
  • Tests

    • Expanded coverage for success, validation, idempotency and authorization failure scenarios.

Review Change Stack

Backlog item #2 (Phase 3 dashboard). Persist auto-deletion retention
per workspace and let admins edit workspace name + retention from the
dashboard.

- workspaces gains auto_deletion_days (int, default 30, CHECK 1..90) via
  migration 0007; schema + snapshot updated.
- GET /v1/web/settings now reads the persisted value instead of the
  global USAGE_POLICY default.
- PATCH /v1/web/settings (workos_access_token, admin scope, idempotent,
  actor rate limit) updates name + retention through runCommand and
  writes a workspace.settings.updated audit event. Bounds (1..90 days,
  name 1..120) enforced at the Zod contract and the DB check constraint.
- Repo: workspaces.update port + both adapters; updateWebSettings in
  RepositoryCore; toWebSettings dedups the GET/PATCH shape.
- Contract + OpenAPI golden regenerated; api + db tests extended.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@isuttell isuttell temporarily deployed to pr-preview-44 May 24, 2026 19:08 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 13fd70f8-0f19-4f4a-9ac6-1d8cfb59e1f6

📥 Commits

Reviewing files that changed from the base of the PR and between d1f93d1 and 850e365.

📒 Files selected for processing (2)
  • packages/db/src/index.test.ts
  • packages/db/src/repository/core.ts

Walkthrough

This PR adds a complete PATCH /v1/web/settings endpoint for updating workspace settings. It introduces an auto_deletion_days column to the workspaces table with a 1–90 day range derived from the USAGE_POLICY TTL bounds. The feature includes database migrations, Drizzle schema updates, Zod/OpenAPI contract definitions, Repository interface and RepositoryCore implementation with idempotency support, entity adapter wiring, an API handler with member authorization and request validation, and extensive test coverage for success cases, validation errors, idempotency, and scope requirements.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Handler as webUpdateSettings
  participant Validation as UpdateWebSettingsRequest
  participant Idempotent as runIdempotent
  participant Core as RepositoryCore
  participant DB as Database
  Client->>Handler: PATCH /v1/web/settings + Idempotency-Key
  Handler->>Handler: verify member actor with admin scope
  Handler->>Validation: safeParse(workspace_name, auto_deletion_days)
  Handler->>Idempotent: call with idempotency key
  Idempotent->>Core: updateWebSettings(actor, workspaceName, autoDeletionDays)
  Core->>DB: update workspaces set name, auto_deletion_days, updated_at
  Core->>DB: insert workspace.settings.updated operation event
  Core-->>Idempotent: return WebSettings
  Idempotent-->>Handler: return result (or replay if key reused)
  Handler-->>Client: 200 WebSettingsResponse
Loading

Possibly Related PRs

  • zaks-io/agent-paste#24: Adds/extends web API framework and web settings read endpoint that this PR builds upon.
  • zaks-io/agent-paste#15: Introduces the Zod→OpenAPI generation pipeline used to register UpdateWebSettingsRequest and OpenAPI paths.
  • zaks-io/agent-paste#4: Adds idempotency handling (runIdempotent) and Idempotency-Key wiring reused by this PATCH endpoint.

Poem

🐰 I hopped to change a workspace's tune,
I stored the days beneath the moon,
An idempotent patch with care —
Your settings saved, safe in my lair. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main feature: adding a PATCH endpoint for web settings with per-workspace retention configuration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agents/web-settings-patch

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/contracts/openapi/api.json`:
- Around line 7598-8070: The web.settings.update operation is missing a 403
forbidden response for admin-scope denial; add a "403" response object to the
responses map for operationId "web.settings.update" that mirrors the existing
error-envelope shape (an object with required "error" containing
"code","message", optional "docs" and "request_id") and include a descriptive
"description" like "Forbidden — insufficient scope" so clients can
programmatically handle authorization failures.

In `@packages/db/src/repository/core.ts`:
- Around line 479-503: The updateWebSettings path must validate
input.autoDeletionDays in repository core before calling
entities.workspaces.update to prevent local-adapter persistence of out-of-range
values; add a guard in updateWebSettings (inside the uow.command callback,
before calling entities.workspaces.update) that checks input.autoDeletionDays
against the same min/max bounds used by the DB (or a shared constant like
MIN_AUTO_DELETION_DAYS / MAX_AUTO_DELETION_DAYS), and throw a clear error (e.g.,
`invalid_auto_deletion_days`) when outside bounds so invalid values are rejected
before persisting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9e2d7f58-6c0f-4c2d-ae4a-62e4611692ba

📥 Commits

Reviewing files that changed from the base of the PR and between d8b7c7f and d1f93d1.

📒 Files selected for processing (21)
  • apps/api/src/index.test.ts
  • apps/api/src/index.ts
  • packages/contracts/openapi/api.json
  • packages/contracts/src/mvp-contracts.test.ts
  • packages/contracts/src/openapi/api.ts
  • packages/contracts/src/openapi/shared.ts
  • packages/contracts/src/routes.ts
  • packages/contracts/src/web.ts
  • packages/db/migrations/0007_workspace_auto_deletion_days.sql
  • packages/db/snapshot/schema.sql
  • packages/db/src/index.test.ts
  • packages/db/src/policy.ts
  • packages/db/src/queries/workspaces.ts
  • packages/db/src/repository/core.ts
  • packages/db/src/repository/interface.ts
  • packages/db/src/repository/local-entities.ts
  • packages/db/src/repository/ports.ts
  • packages/db/src/repository/postgres-entities.ts
  • packages/db/src/schema.ts
  • packages/db/src/types.ts
  • scripts/local-mvp-server.mjs

Comment thread packages/contracts/openapi/api.json
Comment thread packages/db/src/repository/core.ts
Address CodeRabbit review on #44. The local adapter has no DB CHECK
constraint, so a direct updateWebSettings call could persist an
auto_deletion_days value Postgres would reject. Guard against it in
RepositoryCore using the shared MIN/MAX_AUTO_DELETION_DAYS bounds and
lock the behavior with a repo test.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@isuttell isuttell temporarily deployed to pr-preview-44 May 24, 2026 19:15 — with GitHub Actions Inactive
@isuttell isuttell merged commit 0ba041d into main May 24, 2026
4 checks passed
@isuttell isuttell deleted the agents/web-settings-patch branch May 24, 2026 19:19
@github-actions

Copy link
Copy Markdown

agent-paste PR preview resources were cleaned up. The pr-preview-${context.issue.number} environment is left in place; remove it from the GitHub UI if desired.

isuttell added a commit that referenced this pull request May 24, 2026
)

* docs: mark PATCH /v1/web/settings (#44) done in status + web todo

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat: add operator platform lockdown endpoints behind requireOperator()

POST /v1/web/admin/lockdowns and DELETE /v1/web/admin/lockdowns/{scope}/{target_id}
set and lift reversible platform lockdowns, per ADR 0040/0046/0057.

- New `operator` auth requirement + resolver accepting exactly two identities:
  a WorkOS session whose verified email is in OPERATOR_EMAILS, or a Cloudflare
  Access service-token JWT (RS256, aud-checked, common_name required). Every
  auth failure (incl. API-key bearer, non-operator email) collapses to a
  generic not_found (404) so the surface stays non-enumerable.
- New `platform` actor type across contracts/db/commands; migration 0008 widens
  both actor_type CHECKs and creates `platform_lockdowns` (partial unique index
  on effective rows, forced RLS via app.platform).
- setLockdown/liftLockdown run through runCommand; KV denylist wsd:/ad: keys are
  written on set and deleted on lift, after the Postgres commit (best-effort).
- Operator principals rate-limit by platform:{id} with no workspace dimension.

GET list endpoint deferred (tracked in web-app-todo.md). pnpm verify 70/70,
db:check + openapi:check green, smoke:local green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: handle lockdown set/lift races and require DENYLIST.delete

Address CodeRabbit review on PR #45:
- Make KVNamespace.delete required so lockdown reversibility cannot be
  silently dropped; deleteDenylistEntry guards on env.DENYLIST presence.
- platformLockdowns.insert returns a boolean via ON CONFLICT DO NOTHING
  RETURNING; setLockdown treats a lost partial-unique race as a replay
  instead of aborting the transaction.
- platformLockdowns.markLifted returns a boolean via RETURNING; liftLockdown
  throws not_found (and emits no audit event) when it loses a lift race.
- Normalize Cf-Access common_name (trim + lowercase) before use.
- Assert platform actor on the lift handler and lifted audit event.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: throw on inconsistent lockdown insert conflict

CodeRabbit re-review: when setLockdown's insert is rejected by the partial
unique index but no effective row can be found, throw instead of emitting a
misleading platform.lockdown.set audit event for a row that was never written.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant