Repository navigation
AI postmortems: Postmortem rules choose which resolved incidents get a draft, and its templates - #5071
Merged
Conversation
…, and its templates AIPostmortemRule (drag-ordered, incident conditions as investigation rules have, and the postmortem templates a draft may follow), its service (at most 10 templates, every reference the project's own), the migration, and the drafting job reading them: no rule = every resolved incident, standard outline; with rules the first match decides, and OneUptime AI picks the best fit when a rule names several templates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ore settings A drag-ordered table beside Investigation rules and Auto remediation rules: Match (All incidents / Only incidents that match conditions) then Draft (the templates a draft may follow, the name filled in from them). The folded header counts its rules; its summary and description say drafts too. Plan below Growth on Cloud: no template picker, and the server follows the standard outline. Strings in all 17 locales. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hey cannot edit PostmortemRules fixture renders the real Incidents -> AI -> Settings page: folded More settings, the rules table in its order, the Match and Draft steps (name following the templates), a keyboard drag and a refused one, a viewer, a project below the Growth plan, phone, dark and German. BaseModelTable: a drag-ordered table greys its grips out, saying why, for someone who may not edit its rows - the server refused their drop anyway. A project below the template plan gets a card description without templates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… drafting job, table and pages New: AIPostmortemRuleModel, PostmortemRuleTemplates, AIPostmortemRuleService, PostmortemRules (no rule = every incident; first match decides; templates the project's own, by name; below Growth none; unreadable = no draft), PostmortemTemplatePick (none/one/several; failures take the first), AIPostmortemRulesTable (Match then Draft, name follows templates, drag order, plan gate, rule count). Runner suite: rules decide before any wait, the template is filled in and named in the feed. Guards updated for the new rule model, drag list, prompt builder and call site; the folded header names four of six cards; viewers' grips are off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The incident lane's More settings names five cards now, the alert lane's four; the summary waits for the postmortem rules to read on incidents. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…plates Postmortems gets a section on the rules under More settings: what a rule matches, its templates (none, one, or the best fit of several, named in the feed), first match wins by drag order, no match means no draft, the switch stays the only off switch, Owner/Admin and the Growth plan. AI SRE names them in Auto-postmortem and in the Cost controls fold; Incident Settings names the third table under More settings. In all 17 languages, with the facts suite holding the English page to the product. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…raft-rules The postmortem rules migration moves to 1804200000000, after master's 1804150000000-AddIncidentVideoCallCalendarEvent; the Dashboard locales take master's keys, with this branch's appended after them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oup 6 Group 6 was the lightest of the groups holding several suites on 2026-10-19's runs (15.3 to 20.5 minutes); the suite adds about 2. Its screenshots and traces go up with the group's artifact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…raft-rules Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
…disabled rule says so on a phone UI review of the Postmortem rules card (Incidents > AI > Settings > More settings), looked at in the real page beside the side menu at 1280, 1440, 1536 and 1920px, on a phone, in dark mode and in German and Japanese: - At 1280px the card is about 940px wide for seven columns. A rule's templates were one line of names, which pushed the table to 1212px: the rows scrolled sideways, Status sat under the row's buttons (a disabled rule looked like the rest), and every name was cut to "Draft with Database...". Templates are now the list every table draws records with - one a line, the first three, then "2 more templates" which shows the rest (PostmortemRuleTemplates) - capped at 11rem (18rem from 1536px) through wrapMaxWidthClassName, and a rule's name runs onto more lines instead of being cut. The table fits from 1280px up in English. - A phone's card left out a yes/no column whose value was false even when the column draws its own element, so a disabled rule had no Status at all (the same for every rules table, two-factor setups and team invitations since the table redesign). TableRow now draws the element; a plain yes/no field that is no is still left out. - TableColumnListComponent: getMoreText lets a page word "2 more templates" with its locale's plural forms; moreText is looked up in the reader's language (the locale files had "weitere Ressourcen" and the others, never shown). - Below the Growth plan the rule's Name showed the column description "Name of this postmortem rule." as its help: hidden. Tests: TableCardBooleanElement (phone card, fails on master's TableRow), TableColumnList (+3, two fail on master's list), PostmortemRuleTemplates (real TableBody in German, desktop and phone), AIPostmortemRulesTable (column shapes, list, singular, standard outline, Name help), PostmortemRulesSettings (wiring and the new plural in 17 locales), and the offline E2E suite: a layout=page fixture mode beside a real SideMenu, a 1280px fit test and a phone Disabled test (both fail on the old code). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ttier wraps it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es the card's arrival changed - The card's description was 252 characters; ShortDescriptionsGuard holds a card description to 210 (two lines beside its title). Now: "With no rule, OneUptime AI drafts a postmortem for every resolved incident. Add rules to draft only for matching incidents, in the templates you choose. The first rule that matches decides - drag to reorder." (207). The locale key is renamed in place in all 17 files, with new words in each: no translation lost, key count and order unchanged. - RuleCriteriaModelTable counts the Dashboard's rule forms with a Match step: 72 with the postmortem rules (no help panel, as the investigation rules). - AIInvestigationSettingsLimits: folded More settings on the incident page now also says a resolved incident is given a postmortem draft; the alerts' words are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
simlarsen
pushed a commit
that referenced
this pull request
Oct 19, 2026
…red list #5071 added AIPostmortemRule, a list put in order by dragging (@ListOrderColumn), without adding its service to NormalizeListOrder. That migration's suite holds it to exactly the drag-ordered lists ("a list added later without being added here would never be healed"), and failed on the 14.0.78 gate (App Test 2/3): AIPostmortemRule expected, not covered. AIPostmortemRuleService joins the rule lists. Its rows were numbered from the start, so on an install where the migration has yet to run it finds nothing to renumber; NormalizeListOrder.test passes (6). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
simlarsen
pushed a commit
that referenced
this pull request
Oct 19, 2026
…phone too, since phone cards draw a yes/no column's element for no The 14.0.78 gate's Common Test 2/8 failed on StatusPageMonitorRuleForm: "says Disabled on a phone too" still showed why its Status column is an Element, by rendering the same column typed Boolean and expecting the disabled rule's status to be missing on a phone. #5071 fixed that in the shared table: a phone's card now draws a yes/no column's own element for no as well (Disabled, Setup incomplete, Invitation Sent; a plain yes/no column still leaves no out), pinned by TableCardBooleanElement.test. The test now expects Disabled from the Boolean-typed column too, and says why the column became an Element. StatusPageMonitorRuleForm (15) and the table and detail layout suites (27, 734 tests) pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
With Draft a postmortem when an incident resolves on, OneUptime AI drafted a postmortem for every resolved incident, always in its standard outline. A project could not keep drafts to the incidents that need one, and could not have a draft follow its own postmortem templates.
What changes
Postmortem rules, a new table under Incidents → AI → Settings → More settings. It sits after Auto remediation rules and before Daily limits, matching the order of the switches above it. The alerts page does not get it, because alerts have no postmortem.
IncidentAlertRuleMatcher.@ListOrderColumn) and shows Name, Match Criteria, Templates and Status. The folded header lists the card and counts enabled rules as a chip. The defaults line now says drafts happen too.IncidentPostmortemRunner) asksPostmortemRules.getIncidentDraftPlanbefore it waits on call transcripts:generatePostmortemFromAI(template). The feed post says ", following the postmortem template Name".PostmortemTemplatePick): with several templates, one small LLM call (16 output tokens, feature "Incident Postmortem", logged in AI Logs) answers with a number. If it names none, or the call fails, the draft uses the first template by name. It is registered in the kill-switch and prompt-text guard registries.AIPostmortemRuleService): every template, monitor, severity and label a rule names must belong to the project. More than 10 templates is refused before any lookup. Only Project Owner and Project Admin can create, edit or delete rules; members and viewers can read them, the same as investigation rules.BaseModelTable): on every drag-ordered table, someone without update permission now sees greyed-out grips with the Edit button's permission tooltip. Before, a viewer could drag a row, the server refused it, and the rows jumped back.1804200000000-AddAIPostmortemRulesaddsAIPostmortemRule, its relation tables and the templates join table. It writes no rows.Decisions I made
What changes for existing customers
Nothing changes until someone adds a rule. The migration writes no rows, and while the switch is on every resolved incident still gets a draft in the standard outline. On the incident AI settings page, folded More settings now shows four names plus "2 more" (it was four plus "1 more"), and the defaults line mentions postmortem drafts. On every drag-ordered table, people without edit rights now see disabled drag grips instead of grips that drag and then bounce back.
Left out
/api/ai-postmortem-rule.Tests added and what each proves
Common:
Tests/Models/DatabaseModels/AIPostmortemRuleModel.test.ts: the model's route, columns, permissions, list-order column, relations and that it has no plan gate.Tests/Types/AI/PostmortemRuleTemplates.test.ts: the template count, however the ids are written, and the 10-template limit.Tests/Server/Services/AIPostmortemRuleService.test.ts: templates are project-scoped on create and update, more than 10 is refused before any lookup, and edits that leave templates alone are not judged on them.Tests/Server/Utils/AI/PostmortemRules.test.ts: the matching itself. Covers no rule, first match wins by order, no match means no draft, disabled rules, unreadable rules or templates mean no draft, deleted or blank templates, project scoping, and the plan gate.Tests/Server/Utils/AI/PostmortemTemplatePick.test.ts: none, one or several templates, the prompt, reading the answer (number or exact name), and the fallbacks.Tests/App/Dashboard/AIPostmortemRulesTable.test.tsx: the real table renders. Covers the Match and Draft steps, name following the templates, order, permissions, the plan-hidden picker, and what is sent.IncidentPostmortemRunner.test.ts: the plan is read before the wait and again after it, the outline is passed, and the feed names the template.AiWrittenTextInFeedsAndChats,AIKillSwitchBackstopandRecordTextInPromptsGuard: the new LLM caller is registered in each.ListOrderColumnModels;RelationListReadCoverage.MoreSettingsOneCardandProjectAiSettingsSwitchPages.BaseModelTableDragReorder: grips are disabled, with a tooltip, when the user cannot update.App:
Tests/Dashboard/PostmortemRulesSettings.test.ts: the card is on the incident page and not the alert page, it counts on the folded header, the table wiring is right, and all 17 locales have the translations.AISettingsSwitchesFirstandAISettingsSeparationfor the per-lane cards.CriteriaGroupsPostmortemsDocsFactsgets a new describe that holds the new section to the product (card title, criteria answers, fields, template pick, first match, roles, Growth plan).MoreSettingsOneCardDocsandAutomaticRemediationSwitchDocspin the new sentences.E2E (offline fixture, real page bundled with esbuild):
packages/E2E/PostmortemRules/PostmortemRules.spec.ts, 16 tests. They cover the switch and the fold, ordering, a viewer, creating a rule with and without templates, a typed name being kept, conditions, keyboard drag and a refused drop, below and on the Growth plan, phone, dark mode and German.npm run test-postmortem-rules-ui.What ran
tsc --noEmit,npm run compile-tests,npx jest Guard(149 suites), and the 19 changed or added suites. All pass; the one skip is the existing Postgres-gated rule criteria test.tsc --noEmit; Apptsc --noEmit(8 GB heap);--listFilesshows nothing outside App and Common.npx jest Tests/FeatureSet/Docs/Docsand every suite that reads the three changed pages (about 100 suites). All pass.npm run docs:check-anchors,npm run i18n:validate,npm run docs:localize-anchors -- --applyon the three pages.ee/moved aside: pass.npm run compile;npm run test-postmortem-rules-ui: 16 passed (about 36 s, after merging master).1804150000000and then this branch's1804200000000.CheckSchemaDrift.shreports no drift.test.app.yaml(E2ESuitesRunInCiand three others): pass.prettier --checkandeslinton changed code files: clean. The E2E fixture.jsfiles show the same project-service parse error as every sibling fixture. The docs.mdfiles are not prettier-formatted on master either.CI
test-postmortem-rules-uijoins Dashboard Offline UI group 6, which took 15.3 to 20.5 minutes on 2026-10-19's runs. That adds about 2 minutes. Its screenshots and traces go up with the group's artifact.UI review
A second agent reviewed every surface of this change in a real browser and polished it before merging.
What I looked at
What I changed, and why
TableColumnListComponent, in the newPostmortemRuleTemplates). Each template is on its own line, the first three are shown, and a 2 more templates button shows the rest. In a row, a long name is cut short with its full name on hover; in a phone's card, it wraps. The column is capped at 11rem, or 18rem from 1536px, throughwrapMaxWidthClassName. That is the only width capTableWrapContentInvariantsallows.TableRownow draws the element. A plain yes/no field that is "no" is still left out, as before.TableColumnListComponent(shared, additive):getMoreText(count)lets a page word "2 more templates" with its language's plural forms.moreTextis now looked up in the reader's language. The locale files already had "weitere Ressourcen" and the other translations, but they were never shown.ShortDescriptionsGuardallows a card description at most 210, so CI would have failed on it. It now reads: "With no rule, OneUptime AI drafts a postmortem for every resolved incident. Add rules to draft only for matching incidents, in the templates you choose. The first rule that matches decides - drag to reorder." That is 207 characters and two lines at 1280px. The locale key is renamed in place in all 17 files, each with its own new words, so no translation is lost and the key count and order stay the same.hideColumnDescription).RuleCriteriaModelTablenow counts 72 rule forms with a Match step.AIInvestigationSettingsLimitsnow expects the incident page's new defaults line; the alerts line is unchanged.{{count}} more templatesand its_oneform, translated in all 16 locales. Only these two keys were added:i18n:extractalso picked up 35 keys from other merged features (SCIM, Vercel, SSO), and I left those out.Tests added or changed in the review
Common/Tests/UI/Components/Table/TableCardBooleanElement.test.tsx(new): a phone's card shows "Disabled" as well as "Enabled". A column that draws nothing is still left out, and so is a plain "no". 2 of its 5 tests fail on master'sTableRow.Common/Tests/UI/Components/TableColumnList.test.tsx(+3):getMoreText, andmoreTextlooked up in German. 2 fail on master's component.Common/Tests/App/Dashboard/PostmortemRuleTemplates.test.tsx(new): the cell inside a realTableBodywith the real German locale. One template per line, cut short in a row and wrapped in a card, "2 weitere Vorlagen" and its singular, and "Standardgliederung" in grey.Common/Tests/App/Dashboard/AIPostmortemRulesTable.test.tsx: the column shapes (Name wraps, Templates capped through the wrap option), the list, sorting and blanks, the more button and its singular, the standard outline, and the hidden Name help.App/Tests/Dashboard/PostmortemRulesSettings.test.ts: the table wiring, and the new plural in all 17 locales.PostmortemRules(18 tests, was 16):layout=pagemode: the page inside Common'sPageandSideMenu, as wide as a person sees it.Left as follow-ups
What the review ran
tsc --noEmitandnpm run compile-tests(the app, ui and rest programs); the Dashboard'stsc --noEmit -p tsconfig.json; Apptsc --noEmit(8 GB heap); and E2Enpm run compile.--listFilesfor App, E2E and Common lists nothing outside the package and Common.ee/moved aside:TableCardBooleanElement,TableColumnList,PostmortemRuleTemplates,AIPostmortemRulesTable,PostmortemRulesSettings,RuleCriteriaModelTableandAIInvestigationSettingsLimits.npx jest Guard(149 suites); the Common table, list and detail component suites (22); and Common's model, schema, Terraform, API reference and infrastructure suites.ShortDescriptionsGuard(which found the 252-character description),TableWrapContentInvariants,ListDeletePolicy,DragOrderedListsGuard,SettingsFormsOwnWordsGuardandNoAlwaysOnInfoBannersGuard; CommonDeleteConsequencesandListDeleteEveryList.npx jest Localein Common (30) and App (9).npm run i18n:checkreports every locale file sound and no translation lost.i18n:hardcodedonComponents/AISettingsfinds 0.test-postmortem-rules-ui: 18 passed in about 40s. The two new tests fail against the builder's table and master'sTableRow.TableRowfails 2 of the 5TableCardBooleanElementtests, and master'sTableColumnListComponentfails 2 of the 5TableColumnListtests.prettier --checkon every code file the branch changes (73), andeslinton its .ts, .tsx and .js files. Both are clean, apart from the fixture.jsparse error that every sibling fixture shows. The regex timing checker finds nothing new.ExpoPushReceiptQueueValkeyneeds a running Valkey server and fails here with "Connection is closed". This branch does not touch it.🤖 Generated with Claude Code