Skip to Content

← All archived runs

Run: admin-escalation-config

run.md

Run: admin-escalation-config

  • request: Admin dashboard — escalation rules configuration (epic admin-dashboard-data, feature 5/7)
  • issue: #444
  • branch: claude/gracious-ramanujan-yxdrje
  • pr: #445 (draft) — https://github.com/sustentus/sustentus/pull/445
  • stage: ship
  • gates:
    • define approved: yes
    • merge approved: no
  • updated: 2026-06-10

Log

  • 2026-06-09 define: spec drafted from intake stub (pipeline/intake/admin-dashboard-data/admin-escalation-config.md); decisions — dedicated /admin/escalation config page, derived active status, assumes admin-data-foundation server-fetch
  • 2026-06-10 build: spec approved by owner (gate:spec-approved); stage:define → stage:build. Implemented TenantSetting escalation helpers, /admin/escalation config page, wired EscalationRulesCard to real per-tenant state, fixed config-gap link target.
  • 2026-06-10 ship: docs impact assessed (none, technical or business); investor + changelog notes drafted, changelog published to apps/help; /code-review clean; route policy fix verified on preview. PR readied; awaiting merge approval.

00_intake/stub.md

Stub: Admin dashboard — escalation rules configuration

  • feature-slug: admin-escalation-config
  • epic: admin-dashboard-data
  • personas: Admin
  • initiative: Build the Bridge / objective: Q2 2026 Objective 3 — Validate Technical Infrastructure & Payout Flow
  • depends-on: admin-data-foundation
  • sequence: 5 of 7

Problem

The EscalationRulesCard (rules active, default-escalation enabled) is dummy. There is no stored escalation configuration, so the dashboard cannot show whether escalations are set up. Per the storage decision, this simple config lives in the existing TenantSetting key/value collection.

Proposed change

  • Define escalation tenant-settings keys (e.g. default-escalation enabled, rules active) under an escalation category in TenantSetting.
  • Wire the EscalationRulesCard to read those settings.
  • Build the config UI: admin can toggle default escalation and mark escalation rules active.

Acceptance criteria (rough)

  • Admin can enable/disable default escalation; the value persists per tenant.
  • EscalationRulesCard reflects the real saved state (not mock false).
  • Never-configured state renders as inactive/off rather than a mock value.

Out of scope (this feature)

  • Authoring complex per-stage escalation rule logic or firing escalations — this is a config flag this round. (Per-stage escalation toggles live with SLA in admin-sla-config.)

Notes for Define

  • Decide whether "escalation rules active" is purely a tenant-setting flag or derived from the SLA stages' escalation toggles. admin-readiness-gaps does not currently require an escalation check, but keep the keys reusable in case it adds one.
  • touches: packages/services/src/db/models/tenant-setting.ts (keys, read/write), apps/web/components/admin/dashboard/escalation-rules-card.tsx.

01_define/output/spec.md

Spec: Admin escalation rules configuration

  • slug: admin-escalation-config
  • issue: #444
  • personas: Admin
  • touches: packages/services/src/db/services/tenant-setting, apps/web/app/(app)/admin/escalation, apps/web/components/admin/dashboard/escalation-rules-card.tsx, apps/web/app/(app)/admin/dashboard/page.tsx, apps/web/lib/mock/admin.ts
  • complexity: standard

Problem

On the admin "Platform setup" dashboard, the EscalationRulesCard (Active/Missing status, "Default escalation enabled" yes/no) is driven entirely by dummy data in lib/mock/admin.ts — there is no stored escalation configuration, so the card can never reflect whether a tenant has actually set escalation up. This is feature 5 of the admin-dashboard-data epic, which advances Build the Bridge / Q2 2026 Objective 3 — validate technical infrastructure & payout flow by making each dashboard section operational from real, per-tenant data rather than a mock. Per the epic's storage decision, this simple config lives in the existing TenantSetting key/value collection rather than a dedicated model.

Proposed change

  • Escalation tenant-settings key. Store escalation config under an escalation category in TenantSetting, with a single flag this round: escalation.defaultEnabled (boolean). The category is kept as a namespace so future per-stage escalation keys (e.g. escalation.<stage>.enabled) can slot in without a schema change.
  • Typed service helpers. Add getEscalationConfig(tenantId) and setEscalationConfig(tenantId, { defaultEnabled }) to TenantSettingService, following the existing getMinFitScore / setMinFitScore precedent (upsert under the escalation category; getEscalationConfig returns { defaultEnabled: false } when nothing is stored).
  • Config UI — a dedicated /admin/escalation page. A server component reads the current config; a client form (backed by a server action, following the /admin/settings actions pattern) lets the admin toggle "default escalation enabled" and save. Sentence-case copy throughout.
  • Wire EscalationRulesCard to real state. The dashboard passes the real config slice instead of d.escalationRules. defaultEscalationEnabled comes straight from the stored flag; the card's active status is derived — active (status "Active") when default escalation is enabled, otherwise "Missing". A never-configured tenant therefore renders as Missing / off, not a mock value. The card links to /admin/escalation, and the matching "No escalation rule" entry in the config-gaps panel points its action at /admin/escalation (currently /admin/automations, which does not exist).

Acceptance criteria

  • An admin can enable or disable default escalation on /admin/escalation, and the value persists per tenant in TenantSetting under the escalation category (escalation.defaultEnabled).
  • getEscalationConfig / setEscalationConfig exist on TenantSettingService; get returns { defaultEnabled: false } when no setting is stored.
  • The EscalationRulesCard on the admin dashboard reflects the real saved state — its "Default escalation enabled" badge and Active/Missing status both come from the stored config, not the mock.
  • The card's status is derived: "Active" when default escalation is enabled, otherwise "Missing".
  • A tenant that has never configured escalation renders as Missing / off (no stored value is treated as false), not the old mock value.
  • The dashboard card links to /admin/escalation, and the "No escalation rule" config-gap action targets /admin/escalation.

Out of scope

  • Authoring per-stage escalation rule logic, escalation timers, or actually firing/escalating incidents — this round is a tenant-wide config flag only. (Per-stage escalation toggles live with SLA in admin-sla-config.)
  • A separate admin-set "rules active" flag — status is derived from defaultEnabled this round, not stored independently.
  • The derived readiness / config-gaps aggregation layer (that is the capstone admin-readiness-gaps feature). This feature only fixes the single "No escalation rule" gap's link target.
  • Replicating the change into apps/dashboards — that app stays the dummy source of truth.

Open questions

  • none. Prerequisite (not a blocker on this spec): this feature assumes admin-data-foundation (feature 1) has converted /admin/dashboard to a server-fetched per-tenant page; the escalation slice plugs into that server fetch. Build should adopt the foundation's data-loading seam rather than re-introduce client-side fetching. If the foundation has not merged when Build starts, land it first — Build does not gather requirements, it implements this settled spec on top of the foundation.

02_build/output/notes.md

Build notes: admin-escalation-config

  • branch: claude/gracious-ramanujan-yxdrje
  • commits:
    • feat: admin-escalation-config — store escalation config in TenantSetting + service helpers
    • feat: admin-escalation-config — /admin/escalation config page + wire EscalationRulesCard to real state

What changed

  • packages/services/src/db/services/tenant-setting/index.ts: added EscalationConfig type and getEscalationConfig / setEscalationConfig helpers on TenantSettingService, following the existing getMinFitScore / setMinFitScore precedent. Config is stored under key escalation.defaultEnabled in the escalation category (a namespace, so future per-stage keys slot in without a schema change). get returns { defaultEnabled: false } when nothing is stored.
  • packages/services/src/db/services/index.ts: re-export the EscalationConfig type from the services server barrel.
  • apps/web/lib/admin-dashboard-data.ts: added an escalation slice to the dashboard data seam — fetches the real config and exposes { defaultEnabled, active }, where active is derived (active === defaultEnabled, no separately stored flag).
  • apps/web/app/(app)/admin/dashboard/page.tsx: EscalationRulesCard now reads the real escalation slice instead of d.escalationRules.
  • apps/web/components/admin/dashboard/escalation-rules-card.tsx: added a footer link to /admin/escalation (mirrors the SLA card's footer-link pattern).
  • apps/web/app/(app)/admin/escalation/: new config page — server component reads the current config; a client form (Switch + server action saveEscalationConfig, following the matching-settings / /admin/settings actions pattern) lets an admin toggle and save. Saving revalidates both /admin/escalation and /admin/dashboard.
  • apps/web/lib/mock/admin.ts: pointed the "No escalation rule" config-gap action at /admin/escalation (was /admin/automations, which does not exist).
  • apps/web/lib/route-policies.ts: registered /admin/escalation as admin-only. The proxy.ts Clerk middleware denies unmatched routes by default (redirects to the role home), so without a policy entry every admin hitting /admin/escalation was bounced to /admin/dashboard — the page was unreachable. Added alongside the other deep admin routes.

Acceptance criteria status

  • Admin can enable/disable default escalation on /admin/escalation, persisted per tenant in TenantSetting under the escalation category (escalation.defaultEnabled) — page + form + setEscalationConfig upsert.
  • getEscalationConfig / setEscalationConfig exist on TenantSettingService; get returns { defaultEnabled: false } when no setting is stored.
  • EscalationRulesCard reflects the real saved state — both the "Default escalation enabled" badge and the Active/Missing status come from the stored config via the dashboard data seam, not the mock.
  • Card status is derived: "Active" when default escalation is enabled, otherwise "Missing" (active = defaultEnabled in the data seam → ConfigCard renders Active/Missing).
  • A never-configured tenant renders Missing / off — getEscalationConfig treats no stored value as false.
  • The card links to /admin/escalation, and the "No escalation rule" config-gap action targets /admin/escalation.

Verify result

  • mechanical checks (format · lint · typecheck · build) run in CI + the Vercel preview, not here. Ship reads them back from gh pr checks. No check is expected to fail.
  • Note: Typography from @sustentus/ui has a closed prop type (no htmlFor), so the form associates its label via the Switch's aria-label, matching the existing min-fit-score form.

Notes for review

  • d.escalationRules is left in lib/mock/admin.ts (now unused) to keep the diff minimal, in line with the file's other kitchen-sink mock entries; it can be pruned in a later cleanup.
  • Out of scope per spec and not touched: per-stage escalation logic/timers, a separate stored "rules active" flag, the readiness/config-gaps aggregation layer, and replicating into apps/dashboards.

03_ship/output/changelog.md

Changelog: admin-escalation-config

Persona: admin

Configure escalation for your organisation

Admins can now set up escalation from the platform setup dashboard:

  • Turn default escalation on or off for your organisation from the new escalation settings page, and your choice is saved.
  • The escalation card on your setup dashboard now shows your real status — active when default escalation is on, or flagged as missing when it is not yet set up.

This is part of making your platform setup dashboard reflect your organisation's real configuration.

03_ship/output/investor-update.md

Admins can now configure escalation for their organisation

We've made escalation handling configurable per organisation — the first operational control in the admin "platform setup" area to move from placeholder content to real, saved data. It's a small but foundational step in turning the admin dashboard into a working control panel rather than a preview.

  • Admins can switch default escalation on or off for their organisation, and the choice persists.
  • The setup dashboard now reflects each organisation's real escalation status instead of sample content.
  • The setting is stored on our existing tenant-settings foundation, so further escalation controls can build on it without new infrastructure.

This advances the Build the Bridge initiative and our Q2 2026 objective to validate technical infrastructure & payout flow — making the admin control surface something operators can genuinely configure.

03_ship/output/pr.md

Ship: admin-escalation-config

  • PR: #445 — https://github.com/sustentus/sustentus/pull/445
  • branch: claude/gracious-ramanujan-yxdrje
  • CI: format pass · lint pass · typecheck pass · pr-issue-link pass · vercel preview pass
  • technical docs: no technical docs impact (no apps/docs/technical page enumerates admin routes or tenant-setting keys; demo-environment mentions "Tenant Settings" only generically)
  • business docs: no business docs impact (no feature-role-matrix entity covers admin platform config; the escalation concepts in initiatives/refine-the-bridge describe the future per-stage escalation engine, which this config flag deliberately does not build)
  • release notes: both — investor draft + changelog entry included in this PR (changelog published to apps/help)

Review summary

  • /code-review (high effort) found no correctness bugs. Verified the service helpers, route policy, server action, dashboard-data slice, and card wiring against their call sites.
  • Refuted: toast feedback (the Toaster is mounted in app/layout.tsx; toast() is the established pattern); <Link><button> nesting in the card (identical to the existing SlaConfigCard footer-link — house convention, not introduced here).
  • One low-severity note (not blocking): loadAdminDashboardData fetches escalation config and the setup checklist sequentially; they could run via Promise.all. Matches the foundation's existing style; left as-is.
  • Route-policy fix: /admin/escalation had no ROUTE_POLICIES entry, so the deny-by-default proxy.ts middleware redirected every admin to their home. Added it as admin-only (commit 654b8f9); verified the page now loads on the preview.

Acceptance check (vs spec)

  • Admin can enable/disable default escalation on /admin/escalation; persisted per tenant in TenantSetting under the escalation category (escalation.defaultEnabled).
  • getEscalationConfig / setEscalationConfig exist on TenantSettingService; get returns { defaultEnabled: false } when unset.
  • EscalationRulesCard reflects real saved state (badge + Active/Missing status from the server-fetched slice, not mock).
  • Status derived: Active when default escalation enabled, else Missing.
  • Never-configured tenant renders Missing / off.
  • Card links to /admin/escalation; the "No escalation rule" config-gap action targets it too.
  • /admin/escalation is reachable (route policy registered).

Merge & deploy

  • merged: no — awaiting explicit human approval at the merge gate.
  • deploy: pending merge.

04_release/output/release.md

Release: admin-escalation-config

  • live: yes — dashboards production deploy dpl_HhXenyQ3XXxaDAB8YhpnamPyAHgg (READY, target=production) for merge commit 1692cb1 on main; https://dashboards-git-main-sustentus.vercel.app
  • audience: both (changelog already live in apps/help via the Ship merge; investor update now sent)
  • sent: investor update sent to jamie.nisbet@outlook.be, 9e95db73.apoyar.eu@emea.teams.ms on 2026-06-10, after explicit owner approval. Delivered via Resend with confirmed message ids (f7f05510-d3ae-4a0d-8789-8113083037d5, 51c9617c-2065-4563-adcf-79697d6e9ccd) — each a real HTTP 200 + id, not an inferred success.

Note on the earlier send

A first --send (recorded above before this fix) reported "2 sent" but was a false success: this environment's egress to api.resend.com was blocked by the network allowlist (proxy Host not in allowlist, HTTP 403), so the request never reached Resend. The script's success check was hardened to require a real 2xx and a Resend id (commit on the run branch), and the actual delivery above was made once egress to Resend was available. The investor update went out verbatim from 03_ship/output/investor-update.md — no last-minute edits.