Skip to content

docs(api): daily audit 2026-09-16 — repair the investigation_targets drill-down contract, cap the Automation rule list page size - #405

Merged
ysyneu merged 1 commit into
mainfrom
api-review/20260916T080734Z
Sep 16, 2026
Merged

ysyneu merged 1 commit into
mainfrom
api-review/20260916T080734Z

Conversation

@flashduty

@flashduty flashduty Bot commented Sep 16, 2026

Copy link
Copy Markdown

Daily api-review run (--mode generate --scope all --auto), docs root flashduty-docs @ main (f5d7601).
Window: since the last successful run, 2026-09-15T08:18Z. Source repos pinned to origin/main
(fc-pgy 4c050af8, fc-event 6bcc251a, fc-oncall c590121, fc-rum badb4e0, fc-statuspage 0a4e799,
fc-datasource c8051b6, monit-webapi b693f8e, fc-safari c846a24e, go-pkg e196752) — each working tree
verified clean and byte-equal to its origin/main tip before any read.

Verification

Check Result
Registry public surface vs specs 339 auth=all non-/event/push/ rows vs 338 spec paths — 1 registry row with no spec path (the unresolved item below), 0 spec paths with no registry row
python3 -c "import json; json.load(...)" on all changed spec files ok (monitors.en/zh, safari.en/zh, openapi.en/zh)
scripts/lint_openapi.py OK: 12 spec files, no violations
Deep compare vs HEAD, key by key only the additions/renames listed below
Key-order integrity order preserved in all 4 rewritten files: the relative order of every pre-existing key is identical to HEAD (checked programmatically) — 0 reordering
Dangling $ref after schema removal 0 dangling refs in all 12 spec files
EN/ZH parity (paths, methods, operationIds, schema keys, property keys, types) holds in all 5 module pairs (excl. human-text fields, as designed)
docs.json nav ↔ spec paths, {en,zh}/openapi/api-catalog.mdx ↔ spec paths 338/338 reachable, 0 missing in all three checks
Diff shape 384 insertions / 72 deletions across 6 files

Operation changes

No operation added, none removed. 338 public operations before and after, so docs.json and both
api-catalog.mdx files are deliberately untouched.

1. Monitors — investigation_targets contract repaired (hard breakage)

monit-webapi reworked 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:94InvestigationTarget.UnmarshalJSON calls decoder.DisallowUnknownFields(): the target is a closed union, so the variable_bindings object the spec documented is now a hard InvalidParameter for anyone following the docs.
  • :121-125time_range is 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-review item (it must not be hand-edited, or the next regeneration would clobber it).

Schema Change Operations affected
InvestigationTarget kind enum [dashboard][dashboard, query]; time_range added and required; query payload added (query kind); description now states the closed-union strictness /monit/rule/v2/create, /monit/rule/v2/update, /monit/rule/v2/info (shared AlertRuleV2)
DashboardInvestigationTarget variable_bindings (object of InvestigationVariableBinding) → variables (map[string]string, + required) same three
InvestigationTimeRange newbefore_seconds, after_seconds (both required, ≥ 0, ≤ 9007199254740) reached via InvestigationTarget.time_range
QueryInvestigationTarget newdatasource_id (1 … 9007199254740991), query reached via kind: query
DashboardQuery newmode (instant / range / window), expr, args, min_step_seconds (nullable) reached via QueryInvestigationTarget.query
InvestigationVariableBinding removed — orphaned by the rewrite (0 remaining $refs)
AlertRuleV2.investigation_targets maxItems: 20 added (description already said "at most 20 items") same three

Evidence (every constraint from Go tags or explicit handler code; none inferred from business branches)

  • Envelope — types/alert_rule_investigation_target.go:30-44: InvestigationTarget{Kind, TimeRange, Query *…, Dashboard *…}; Kind/TimeRange are non-pointer → required: [kind, time_range]; Query/Dashboard are omitempty pointers → optional, required conditionally on kind (stated in their descriptions).
  • MaxInvestigationTargets = 20 (:17), enforced at :115maxItems: 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 to Timestamp instead of a plain integer.
  • QueryInvestigationTarget{DatasourceID, Query} (:47); types/alert_rule_investigation_query.go:13-16 bounds datasource_id to (0, MaxJavaScriptSafeInteger]minimum: 1, maximum: 9007199254740991.
  • DashboardQuery = types/dashboard/contract.go:77 Query{Mode, Expr, Args map[string]string, MinStepSeconds *int64}; all non-pointer except MinStepSecondsrequired: [mode, expr, args] and min_step_seconds as a nullable integer. alert_rule_investigation_query.go:20 only permits min_step_seconds when mode == "range", in (0, MaxJavaScriptSafeInteger/1000]minimum: 1, maximum: 9007199254740 + the mode caveat in the description.
  • mode enum instant / range / window: alert_rule_investigation_query.go:39-44 branches on exactly instant and range and otherwise passes the mode through as the Explore execution kind — the same enum already carried by QueryExploreExecution.kind on /monit/query/explore.
  • DashboardInvestigationTarget{Variables map[string]string, DashboardID, TargetID *} (:64-68); :157 defaults 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.
  • Request/response identity — types/alert_rule_config_v2.go:36 puts InvestigationTargets []InvestigationTarget on AlertRuleV2, which is exactly the schema /monit/rule/v2/{create,update,info} all reference, so the fix propagates to both directions in one place.
  • The top-level rule body still tolerates unknown fields (UnmarshalJSON at alert_rule_config_v2.go:91 uses plain json.Unmarshal), so the existing "tolerates additional unknown fields" bullet on /monit/rule/v2/update remains accurate; only the target objects are closed.

2. AI SRE — POST /safari/automation/rule/list page-size cap

Property Change
AutomationRuleListRequest.limit maximum: 200 added; description now states both bounds

fc-safari/logic/automation/service.go: const maxListRulesLimit = 200, and ListRules floors limit < 1 to 20 and clamps limit > 200 to 200; cmd/api/common/common.go:108 ParsePagination supplies the 20 already documented as default. cmd/api/automation/automation.go:83 ListRulesRequest.limit carries no binding: tag, so the cap is explicit service code, which is what makes it documentable. Effective range [1, 200]. Single $ref site, 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 on fc-event origin/main, nor on any origin branch: git log --all -S daily-counts finds it only on origin/dev and origin/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.
  • Registry row matching only on path notation (not drift, no action): /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 struct in 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:

  • Already covered by open PR docs(api): daily audit 2026-09-15 — surface template closed-card action apps, session and feed fields #400 (same drift, do not duplicate): FeedItem.{via, agent_session_id}, SessionItem.{creator_name, standing_tasks}, WarRoomItem.{chat_name, integration_unavailable}, TemplateItem.incident_card_closed_action_apps, SessionGetResponse.pending_messages.
  • Known-deliberate, re-checked this run, left out: 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).
  • Genuine but deferred, flagged for the next round: 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).
  • False positives of the audit method (stated so nobody re-chases them): DSMySQLConfig / DSClickHouseConfig / DSMongoDBConfig "missing" tls_* — the Go structs embed TLSClientConfig with json:",inline" (monit-webapi/model/data_source.go:343), which the name-matching audit does not resolve; the spec is correct. suggest_init on SessionGetResponse / SessionListResponse is 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.cloud was not attempted — this runner cannot reference the credential environment variable.

Process notes / blockers

  • Skill pipeline gap (third consecutive round). The team knowledge pack still contains no runbooks/api-review-daily.md and no runbooks/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, --apply to 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, so scripts/generate_openapi.py cannot 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.
  • Stale knowledge corrected. The note that monit-webapi / monit-edge are not in the GitHub org is wrong as of this run: both exist (monit-webapi b693f8e, monit-edge pushed 2026-09-16T02:17Z) and were cloned, so the Monitors module was audited against real source rather than reusing HEAD content.

Open-PR coordination

#400 (yesterday's audit, branch api-review/20260915T083034Z) is still open and unmerged; its delta is not on main. This branch is cut from today's main and 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.

…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.
@ysyneu
ysyneu merged commit 3c905ba into main Sep 16, 2026
2 checks passed
@ysyneu
ysyneu deleted the api-review/20260916T080734Z branch September 22, 2026 09:46
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