Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ jobs:
- name: Install OpenTofu
uses: opentofu/setup-opentofu@a1320f892987e89d278cc92dc5adc984fb93aca4 # v2.0.2
with:
tofu_version: "1.8"
tofu_version: "1.13"
tofu_wrapper: false

- name: Format check
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1537,3 +1537,14 @@ The disclosure input parses a form's body as JSON, as it does for a JSON body.
**Decision**: For Overslash-managed sign-in (no `org_idp_configs` row claims the provider), login uses the deployment's `{PROVIDER}_AUTH_*` app even when the org has saved OAuth App Credentials (`OAUTH_{PROVIDER}_CLIENT_ID/SECRET` org secrets) for that provider. Those credentials still override env for service connections (SPEC §7). An admin can opt a provider in: a row in `org_oauth_signin_overrides (org_id, provider_key)`, off by default, makes the org's credentials serve sign-in too. It is set from the *Use for sign-in* action on a Google/GitHub row in Org Settings → OAuth App Credentials (`PATCH /v1/org-oauth-credentials/{provider} {override_signin}`), shown only when the org override exists and the env sign-in app is configured, or from the same checkbox on the save form (`PUT … {override_signin}`). Deleting the credentials clears the opt-in. Dedicated IdP rows, including those with `use_org_credentials`, are unchanged: they are already an explicit sign-in choice. There is no backfill, so orgs that had saved Google/GitHub credentials go back to the built-in sign-in app until an admin opts in. `services::org_signin::resolve_org_signin_credentials`; tests in `tests/org_oauth_credentials.rs`.
**Rationale**: Admins save org OAuth credentials to brand or scope service connections, and the old precedence quietly moved every member's login onto that app as well. That app may have a narrower consent screen, test-user limits or unverified status, so login could break for reasons unrelated to sign-in. Making the sign-in takeover a separate, visible opt-in keeps "configure connections" from changing "how people log in".

## D141: `/health` fails when Postgres is down; Cloud Run probes are TCP

**Date**: 2026-10-27
**Decision**: `GET /health` returns 503 `status: degraded` when its bounded `SELECT 1` fails, and 200 `status: ok` otherwise. `/ready` keeps the same gate. The Cloud Run startup and liveness probes on the API are `tcp_socket` probes on the container port, not HTTP probes on `/health`. The GCM uptime check behind `[P0] API Down` and the Better Stack monitor stay on `/health`, so a database outage pages in prod. Dev's nightly Cloud SQL stop therefore fires dev's `API Down` every night, and that is accepted: it emails only, and it proves the path end to end. `routes/health.rs`, `infra/modules/cloud-run/main.tf`.
**Rationale**: `/health` was always-200 so that a Cloud SQL blip would not make the probes restart containers. That never worked. The probe's default 1 s timeout is shorter than `/health`'s 2 s database timeout, so with the database gone the liveness probe failed anyway. Dev's instance was shut down about 90 s after every nightly stop, and a replacement cannot boot without the database (`create_app` connects and migrates eagerly), so Cloud Run answered 500 until morning. The design gave neither a surviving container nor an honest status. Splitting the two questions fixes both. Whether the process is up is a TCP connect, which nothing upstream can fail. Whether the API can serve is `/health`, which the monitors read and which no longer restarts anything. A TCP startup probe gives up nothing, because the listener binds only after `create_app` has connected, migrated and preflighted. No new endpoint was added, since a TCP probe needs none.

## D142: "Allow & Remember" expires after an org-set default, 7 days out of the box; an omitted `ttl` means that default, not forever

**Date**: 2026-10-28
**Decision**: Orgs have a `default_remember_ttl_secs` setting (migration 139). It is nullable, NULL means forever, it is bounded to 60 s to 365 d, and the column default is 604800 (7 days). The column default also backfills every existing org. Admins set it on `PATCH /v1/orgs/{id}/execution-settings`, and Org Settings shows it as a select below the call timeouts. When `POST /v1/approvals/{id}/resolve` gets `allow_remember` with no `ttl`, the rule expires `now + default`. A permanent rule now needs an explicit `ttl: "forever"`, which `/resolve` accepts (it used to 400 on it). The dashboard's Allow split button still starts on Once (#844), tags the org default "· default" in its duration menu, and always sends `ttl`. The MCP remember dialog preselects the org default; if that default is not one of the dialog's menu values, it is added as a `"{n}s"` option, and the dialog forwards `forever` verbatim. The SDK approval card gets an "Org default" choice, selected by default, that leaves `ttl` out. One parser, `overslash_core::types::duration::parse_rule_ttl`, serves `/resolve` and `PATCH /v1/permissions/{id}`.
**Rationale**: Before this, a remembered rule was permanent unless the reviewer changed the picker. "Never" was the preselection, and leaving the field out meant forever. So the cheapest action created the longest-lived grant. The org default had to be applied on the server, when `ttl` is missing, rather than only preselected in the dashboard, because API, MCP and SDK resolvers never see the dashboard. This changes behaviour for API callers that relied on omitting `ttl` to get forever. That trade is deliberate: any caller that wants a permanent grant can still ask for one, and now has to ask explicitly. Backfilling existing orgs instead of grandfathering them to forever is also deliberate, because the safer default should reach the orgs that never chose. Rules already written keep the expiry they were written with.
13 changes: 11 additions & 2 deletions SCHEMA.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
-- PostgreSQL database dump
--

\restrict IU7gGYzzNTd60ELJcMnX68xk6MwIMaSwIk56fgOPGg3Phe56vVK1sb2CM7pgn1c
\restrict Ima6egLcgrnJ79TnxwblysVaIC9DjWdCHPDn0FnJXFFtcuWvCqfvYirq9Tx4HXW

-- Dumped from database version 16.14 (Debian 16.14-1.pgdg12+1)
-- Dumped by pg_dump version 16.15 (Ubuntu 16.15-0ubuntu0.24.04.1)
Expand Down Expand Up @@ -1064,9 +1064,11 @@ CREATE TABLE public.orgs (
call_timeout_ms integer,
max_call_timeout_ms integer,
default_agent_self_setup boolean DEFAULT true NOT NULL,
default_remember_ttl_secs integer DEFAULT 604800,
CONSTRAINT orgs_approval_auto_bubble_secs_check CHECK ((approval_auto_bubble_secs >= 0)),
CONSTRAINT orgs_audit_response_body_mode_check CHECK ((audit_response_body_mode = ANY (ARRAY['off'::text, 'errors_only'::text, 'all'::text]))),
CONSTRAINT orgs_call_timeout_bounds CHECK ((((call_timeout_ms IS NULL) OR ((call_timeout_ms >= 1000) AND (call_timeout_ms <= 600000))) AND ((max_call_timeout_ms IS NULL) OR ((max_call_timeout_ms >= 1000) AND (max_call_timeout_ms <= 600000))) AND ((call_timeout_ms IS NULL) OR (max_call_timeout_ms IS NULL) OR (call_timeout_ms <= max_call_timeout_ms)))),
CONSTRAINT orgs_default_remember_ttl_bounds CHECK (((default_remember_ttl_secs IS NULL) OR ((default_remember_ttl_secs >= 60) AND (default_remember_ttl_secs <= 31536000)))),
CONSTRAINT orgs_plan_check CHECK ((plan = ANY (ARRAY['standard'::text, 'free_unlimited'::text, 'trial'::text]))),
CONSTRAINT orgs_user_template_policy_check CHECK ((user_template_policy = ANY (ARRAY['none'::text, 'restrictive'::text, 'full'::text])))
);
Expand Down Expand Up @@ -1107,6 +1109,13 @@ COMMENT ON COLUMN public.orgs.max_call_timeout_ms IS 'Ceiling on any resolved ca
COMMENT ON COLUMN public.orgs.default_agent_self_setup IS 'When true (default), a newly-created first-level agent (depth = 1) is seeded with the four overslash:*_own self-setup permission rules: manage_services_own, manage_templates_own, manage_connections_own, request_secrets_own. Existing agents are not touched when this flips.';


--
-- Name: COLUMN orgs.default_remember_ttl_secs; Type: COMMENT; Schema: public; Owner: -
--

COMMENT ON COLUMN public.orgs.default_remember_ttl_secs IS 'Default expiry in seconds for rules written by Allow & Remember when the resolver does not pass a ttl. NULL = forever.';


--
-- Name: pending_checkouts; Type: TABLE; Schema: public; Owner: -
--
Expand Down Expand Up @@ -4046,5 +4055,5 @@ ALTER TABLE ONLY public.webhook_subscriptions
-- PostgreSQL database dump complete
--

\unrestrict IU7gGYzzNTd60ELJcMnX68xk6MwIMaSwIk56fgOPGg3Phe56vVK1sb2CM7pgn1c
\unrestrict Ima6egLcgrnJ79TnxwblysVaIC9DjWdCHPDn0FnJXFFtcuWvCqfvYirq9Tx4HXW

Loading
Loading