docs(api): daily audit 2026-09-16 — repair the investigation_targets drill-down contract, cap the Automation rule list page size - #405
Merged
Conversation
…drill-down contract, cap the Automation rule list page size
Two evidence-backed fixes, no operation added or removed (338 public
operations before and after).
1. Monitors, investigation_targets (hard breakage). monit-webapi reworked the
drill-down contract after this spec was last regenerated (2026-09-10), so the
documented shape is now rejected by the server: InvestigationTarget
unmarshals with DisallowUnknownFields, and time_range is mandatory.
- InvestigationTarget: kind enum gains query; time_range added and required;
query payload added (query kind).
- DashboardInvestigationTarget: variable_bindings replaced by the variables
string map that now carries the same event-label templates.
- New: InvestigationTimeRange, QueryInvestigationTarget, DashboardQuery.
- Removed: InvestigationVariableBinding (orphaned by the change).
- AlertRuleV2.investigation_targets: maxItems 20.
This was the flag raised by the earlier Monitors doc review as a
must-fix-by-api-review item; doc-following requests were being rejected.
2. AI SRE, POST /safari/automation/rule/list. AutomationRuleListRequest
.properties.limit gains maximum 200 and a description stating the real
bounds, matching the explicit cap in fc-safari logic/automation/service.go.
Diff: 4 files, +376/-68. Key order preserved everywhere (verified: the
relative order of every pre-existing key is byte-identical to HEAD; no
reordering noise). docs.json and both api-catalog.mdx untouched.
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.
Daily api-review run (
--mode generate --scope all --auto), docs rootflashduty-docs@main(f5d7601).Window: since the last successful run,
2026-09-15T08:18Z. Source repos pinned toorigin/main(fc-pgy
4c050af8, fc-event6bcc251a, fc-oncallc590121, fc-rumbadb4e0, fc-statuspage0a4e799,fc-datasource
c8051b6, monit-webapib693f8e, fc-safaric846a24e, go-pkge196752) — each working treeverified clean and byte-equal to its
origin/maintip before any read.Verification
auth=allnon-/event/push/rows vs 338 spec paths — 1 registry row with no spec path (theunresolveditem below), 0 spec paths with no registry rowpython3 -c "import json; json.load(...)"on all changed spec filesmonitors.en/zh,safari.en/zh,openapi.en/zh)scripts/lint_openapi.pyOK: 12 spec files, no violationsHEAD, key by keyHEAD(checked programmatically) — 0 reordering$refafter schema removaldocs.jsonnav ↔ spec paths,{en,zh}/openapi/api-catalog.mdx↔ spec paths384 insertions / 72 deletionsacross 6 filesOperation changes
No operation added, none removed. 338 public operations before and after, so
docs.jsonand bothapi-catalog.mdxfiles are deliberately untouched.1. Monitors —
investigation_targetscontract repaired (hard breakage)monit-webapireworked the drill-down contract after this spec was last regenerated (2026-09-10T08:24Z),so the documented shape was rejected by the server. Two independent first-hand confirmations:
types/alert_rule_investigation_target.go:94—InvestigationTarget.UnmarshalJSONcallsdecoder.DisallowUnknownFields(): the target is a closed union, so thevariable_bindingsobject the spec documented is now a hardInvalidParameterfor anyone following the docs.:121-125—time_rangeis validated as mandatory on every target and is absent from the spec entirely.An earlier Monitors doc review raised exactly this as a must-fix-by-
/api-reviewitem (it must not be hand-edited, or the next regeneration would clobber it).InvestigationTargetkindenum[dashboard]→[dashboard, query];time_rangeadded and required;querypayload added (querykind); description now states the closed-union strictness/monit/rule/v2/create,/monit/rule/v2/update,/monit/rule/v2/info(sharedAlertRuleV2)DashboardInvestigationTargetvariable_bindings(object ofInvestigationVariableBinding) →variables(map[string]string, + required)InvestigationTimeRangebefore_seconds,after_seconds(both required, ≥ 0, ≤ 9007199254740)InvestigationTarget.time_rangeQueryInvestigationTargetdatasource_id(1 … 9007199254740991),querykind: queryDashboardQuerymode(instant/range/window),expr,args,min_step_seconds(nullable)QueryInvestigationTarget.queryInvestigationVariableBinding$refs)AlertRuleV2.investigation_targetsmaxItems: 20added (description already said "at most 20 items")Evidence (every constraint from Go tags or explicit handler code; none inferred from business branches)
types/alert_rule_investigation_target.go:30-44:InvestigationTarget{Kind, TimeRange, Query *…, Dashboard *…};Kind/TimeRangeare non-pointer →required: [kind, time_range];Query/Dashboardareomitemptypointers → optional, required conditionally onkind(stated in their descriptions).MaxInvestigationTargets = 20(:17), enforced at:115→maxItems: 20.InvestigationTimeRange{before_seconds, after_seconds int64}(:50) with the bound check at:123-125(non-negative, both ≤MaxJavaScriptSafeInteger/1000=9007199254740, not both zero) →minimum: 0,maximum: 9007199254740. These are durations, not timestamps, so their descriptions deliberately avoid Unix/epoch wording — epoch wording here would make the downstream SDK map them toTimestampinstead of a plain integer.QueryInvestigationTarget{DatasourceID, Query}(:47);types/alert_rule_investigation_query.go:13-16boundsdatasource_idto(0, MaxJavaScriptSafeInteger]→minimum: 1,maximum: 9007199254740991.DashboardQuery=types/dashboard/contract.go:77Query{Mode, Expr, Args map[string]string, MinStepSeconds *int64}; all non-pointer exceptMinStepSeconds→required: [mode, expr, args]andmin_step_secondsas a nullable integer.alert_rule_investigation_query.go:20only permitsmin_step_secondswhenmode == "range", in(0, MaxJavaScriptSafeInteger/1000]→minimum: 1,maximum: 9007199254740+ the mode caveat in the description.modeenuminstant/range/window:alert_rule_investigation_query.go:39-44branches on exactlyinstantandrangeand otherwise passes the mode through as the Explore execution kind — the same enum already carried byQueryExploreExecution.kindon/monit/query/explore.DashboardInvestigationTarget{Variables map[string]string, DashboardID, TargetID *}(:64-68);:157defaults a nil map to{}, so the field is always present on the wire →required; values are Go templates rendered from event labels at:200-210, hence the{{ }}wording.types/alert_rule_config_v2.go:36putsInvestigationTargets []InvestigationTargetonAlertRuleV2, which is exactly the schema/monit/rule/v2/{create,update,info}all reference, so the fix propagates to both directions in one place.UnmarshalJSONatalert_rule_config_v2.go:91uses plainjson.Unmarshal), so the existing "tolerates additional unknown fields" bullet on/monit/rule/v2/updateremains accurate; only the target objects are closed.2. AI SRE —
POST /safari/automation/rule/listpage-size capAutomationRuleListRequest.limitmaximum: 200added; description now states both boundsfc-safari/logic/automation/service.go:const maxListRulesLimit = 200, andListRulesfloorslimit < 1to20and clampslimit > 200to200;cmd/api/common/common.go:108ParsePaginationsupplies the20already documented asdefault.cmd/api/automation/automation.go:83ListRulesRequest.limitcarries nobinding:tag, so the cap is explicit service code, which is what makes it documentable. Effective range[1, 200]. Single$refsite, so the change cannot leak into another operation.unresolved
POST /channel/incident/daily-counts(channel:read:incidentDailyCounts,Auth: "all") — registered in the pgy ledger (fc-pgy/logic/api/api_test.go:609) but no handler exists onfc-eventorigin/main, nor on any origin branch:git log --all -S daily-countsfinds it only onorigin/devandorigin/feat/channel-daily-incident-counts(954fdf49,2dfdd0ba). With no handler on the pinned branch there is no input/output struct to extract, so the operation was not fabricated. Second consecutive day unresolved — re-run this audit once that branch merges./monit/prometheus/api/v1/label/:label_name/values↔ spec/monit/prometheus/api/v1/label/{label_name}/values(Gin:vs OpenAPI{}). The operation is documented, in the nav and in both catalogs.Field-level drift audit — what was found and deliberately deferred
Because the generator's inputs are unavailable (see below), a full-tree audit was run instead: every
type X structin the pinned repos was matched to the schema of the same name (116 → 135 schemas resolved by name; ambiguous same-name types rejected). Beyond the two fixes above, it surfaced 17 output-side gaps. Everything not fixed here is listed with a reason — none was silently dropped:FeedItem.{via, agent_session_id},SessionItem.{creator_name, standing_tasks},WarRoomItem.{chat_name, integration_unavailable},TemplateItem.incident_card_closed_action_apps,SessionGetResponse.pending_messages.A2AAgentItem.description(source comment: kept only for pre-GA clients, deliberately absent from docs);MemberItem.password(populated on the create/import path only, never on/member/list);MetricsBase.account_id(in-process grouping key, no serialization site);AssignedTo.layer_{person,webhook}_{reset,append}(types exist nowhere in the specs — documenting them would mean inventing two schemas for push/ServiceNow plumbing).SkillItem.examples(the field is now populated from its own DB column —fc-safari/model/skill/skill.go:40— so it does reach the wire);MCPServerItem.{test_error, tested_at};PostMortemItem.has_active_incident;AutomationRuleItem.last_run;SessionItem.{ref, automation};TemplateItem.{dingtalk,slack}_app_war_room_enabled;AutomationTemplateItem(6 fields, only a 0.45 name-overlap so the match itself needs confirming).DSMySQLConfig/DSClickHouseConfig/DSMongoDBConfig"missing"tls_*— the Go structs embedTLSClientConfigwithjson:",inline"(monit-webapi/model/data_source.go:343), which the name-matching audit does not resolve; the spec is correct.suggest_initonSessionGetResponse/SessionListResponseis present in the spec but not on the inline struct the audit matched, so it needs a handler read before being judged stale.Examples
No operation was added or removed, so no new request/response example was required and none was constructed. The new fields are not injected into existing examples either: those examples already omit the optional fields of the same schemas, so adding only the new ones would make each example inconsistent with its own convention. Real 200-response capture from
api-dev.flashcat.cloudwas not attempted — this runner cannot reference the credential environment variable.Process notes / blockers
runbooks/api-review-daily.mdand norunbooks/api-review-apply-patches.py, so the mandatory patch-first step could not run; the anchor-asserted patch scripts for the two fixes above were written and run only inside this run's ephemeral sandbox (patch_automation_cap.py,patch_investigation_targets.py; dry-run by default,--applyto write), so they are not durable — the diff in this PR is the reviewable artefact. Additionally.api-review/modules/*.json(the generator's inputs) are gitignored and absent, soscripts/generate_openapi.pycannot be re-run from a clean checkout. This PR was produced by the evidence-driven audit described above, with byte-faithful serialization (json.dumps(..., indent=2, ensure_ascii=False), verified to round-trip every committed file byte-identically) so the minimal-diff baseline holds without the patch. Restoring the runbook + patch script + module inputs is the single change that would put this job back on its designed pipeline.monit-webapi/monit-edgeare not in the GitHub org is wrong as of this run: both exist (monit-webapib693f8e,monit-edgepushed2026-09-16T02:17Z) and were cloned, so the Monitors module was audited against real source rather than reusingHEADcontent.Open-PR coordination
#400 (yesterday's audit, branch
api-review/20260915T083034Z) is still open and unmerged; its delta is not onmain. This branch is cut from today'smainand contains none of #400's content — the two changes here are disjoint from it (different schemas/regions), so both can be reviewed and merged in either order. The overlapping fields found by the audit were deliberately left to #400 rather than duplicated.