Add flightcheck developer guide (AGENTS.md) - #38
Add flightcheck developer guide (AGENTS.md)#38Graham McMynn (GrahamMcMynn) wants to merge 37 commits into
Conversation
- Add standard Microsoft MIT license (closes #1) - Allow LICENSE and SECURITY.md through .gitignore allowlist
…fd-c784-48eb-b13c-715927704369 Adding Microsoft SECURITY.MD
- Add componenttype 19 (Test Case) support to extraction pipeline
- setup.py: evaluations/ folder, parentbotcomponentid tracking, snapshot section, evaluationCount in config
- fetch_and_setup.py: include _parentbotcomponentid_value in REST query
- push.py: two-pass CREATE (parent EvaluationSet first, then children with ParentBotComponentId@odata.bind), child-before-parent DELETE ordering, mspva_{uuid} schema naming
- Restructure skills: evaluate/ -> evaluations/create/, add evaluations/update/ and evaluations/delete/
- Update copilot-instructions.md routing table
- Add structured ISU auth debugging reference doc with error-to-config lookup tables - Create troubleshoot skill for diagnosing connectivity/auth failures - Wire up agent routing with trigger phrases for common error strings - Cross-link from workday.md and known-issues-limitations.md
- Add scripts/discover.py for agent discovery via REST API + MSAL auth - Rewrite step1b.md: replace ~250 lines of MCP tool juggling with simple script calls - Remove MCP server start as prerequisite for discovery (step1.md) - Make MCP server start a blocking step 4 in setup (step2.md) - Add 4th checklist item: MCP server started - Rename checklist step 1 from 'Dataverse MCP connected' to 'Dataverse configured' Fixes setup failures on lesser-capable models (Haiku, GPT-4o-mini) that could not reliably load deferred MCP tools or navigate branching recovery logic.
- Add ServiceNow MCP server (src/mcp/servicenow/) with Table API client, ITSM, HRSD, CMDB, catalog, user, and OAuth/OIDC tools - Add /connect skill with auth-specific step files: step1 (instance info + MCP setup), step2-oauth2 (automated OAuth app creation), step3-oauth2/step3-basic (extension pack install), step4 (verify) - Automate OAuth2 setup: pre-generate client secret, create app registry via MCP, set token lifespans, set redirect URL by PP region - Add connect.prompt.md for /connect command - Update copilot-instructions.md with connect skill routing + triggers - Use vscode_askQuestions for onboarding and connect question flows - Add no-leak rule to all step files (no internal variable names in chat)
- New shared azure/ skills: login.md (device code flow), app-registration.md (6-step Entra app setup) - New servicenow/step2-entra.md: orchestrates Azure login + app reg + ServiceNow OIDC config - New servicenow/step3-entra.md: extension pack install with Entra creds (3 fields) - Modified step1.md: removed Entra downgrade gate, added entra routing branch
…bscriptions to prevent terminal prompt
…e), fix oidc_provider oidc_provider_configuration field name
…st-class section with full steps, reorder auth methods
…ntro to explain we're pulling new ServiceNow components
- New step2-graph.md: OIDC provider, integration user, roles, ACL setup via temp Scripted REST API - New step3-graph.md: M365 Admin Center + Copilot Studio knowledge source walkthrough - Modified step1.md: connector type question (Actions/Knowledge/Both), routing for graph path - New call_api tool in server.py: generic REST call using existing MCP auth session - Reference docs: servicenow-knowledge-deployment.md, servicenow-knowledge-troubleshooting.md, servicenow-connector-setup-scripts.md
…ding flows Phase 1 - Consent gates: - app-registration.md: consent before Entra app creation (B.2) and config (B.3-B.6) - step2-entra.md: consent before SNOW user creation (2.3) and OIDC setup (2.4) - step2-oauth2.md: consent before OAuth app creation (2.3) - step2-graph.md: comprehensive consent before Graph Connector setup (2.3-2.8) Phase 2 - Progress narration: - All progress messages annotated with 'do NOT wait for user response' - Timing estimates added to discovery, extraction, and re-extraction steps - Summary tables added after multi-step sequences Phase 4 - Rollback and cleanup: - app-registration.md: B.8 cleanup section with az ad app delete - step2-entra.md: 2.8 cleanup for test user and OIDC entity - step2-graph.md: 2.8e fixed to run on failure; 2.10 full cleanup reference - Incremental config saves after each environment mutation
- Add step2-certificate.md: full certificate auth setup (two Entra apps, OIDC provider, system user, self-signed cert generation) - Add step3-certificate.md: extension pack install with certificate fields, auto-open File Explorer for PFX upload, ITSM field name clarification - Update step1.md (connect router): add reconfigure/reconnect options when ServiceNow is already connected, support PRE_SELECTED_INTEGRATION arg - Update servicenow/step1.md: add certificate option to auth question and routing table - Update SKILL.md: add integration arg passthrough, complete routing table with all auth-specific step files - Update servicenow-hrsd-itsm.md: clarify HRMS plugin activation and OIDC table create behavior
…tall - Add Workday MCP server (SOAP, RaaS, worker data, connection testing) - Add /connect workday flow (step1: gather info, step2: admin setup, step3: verify + extension pack) - Verify-first pattern: run API checks before portal tasks, skip pre-configured items - Two API client paths: SAML Bearer Grant for Entra SSO, Auth Code Grant for Basic - Extension pack install: connections, env vars, cloud flows, topic redirect - Entra app role assignment check before connection creation - Update SKILL.md with Workday routing and key principles - Update README with ServiceNow and Workday integration docs
Replace real tenant/domain references (microsoft_dpt6, EmployeeHub.onmicrosoft.com) with generic placeholders to avoid GitHub secret scanner false positives.
Key changes: - Entra SSO is now mandatory (not optional) — OAuthUser connection uses runtimeSource:invoker - Three connections require three different auth types (Basic/Basic/Entra SSO) - Step 1: Auto-detects existing state (Entra app, extension pack, RaaS report) via Azure CLI + Dataverse MCP - Step 2: Removed flawed pre-flight that tested with MCP admin creds instead of ISU creds. Idempotent ISU creation. Never skip domain permissions. Full permission list including Worker Data: Compensation - Step 3: Explicit connection creation table with naming guidance and auto-fill warning. Topic redirect auto-pushed via push.py. 7-point post-install verification. End-to-end test prompts - SKILL.md: Updated routing and key principles - copilot-instructions.md: Added SSO requirement and 3-connection-types rule
Port ESS Pre-flight Validator into the kit as a Python-native skill. Runs 41+ automated checks across 8 categories: prerequisites, environment, authentication, external systems, Workday deep validation (17 SOAP workflow tests), multi-agent local file validation, and publishing readiness. New files: - scripts/flightcheck/ — Python CLI + check modules (Graph, PP Admin, Workday SOAP, local files) - src/skills/flightcheck/SKILL.md — conversational skill with HTML report + auto-fix offer - .github/prompts/flightcheck.prompt.md — /flightcheck command - src/reference/ess-docs/flightcheck/ — validation matrix, remediation guide, permissions, report template Integration: - /setup Step 5 (optional) — offers readiness check after agent extraction - /menu — added /flightcheck command - /troubleshoot — cross-link to suggest flightcheck first - /connect workday — post-install suggestion to verify with flightcheck Features: - Multi-agent: scans all agents under my/agents/ - HTML report: auto-opens in browser, archived to history/ - Workday creds: reads base URL + tenant from .vscode/mcp.json, prompts for ISU password (never saved) - Graceful degradation: runs whatever permissions allow, skips the rest - Remediation links: clickable portal/docs links in every result
setup.py now maintains an agents[] array in my/config.json alongside the backward-compatible agent field. Each /setup run adds the agent to the array and sets activeAgent. Existing scripts (push, checkpoint, fetch_and_setup) and all 15 skill files continue reading agent.* with zero changes. FlightCheck banner shows all discovered agents.
- Add CONTRIBUTING.md with Microsoft CLA boilerplate - Add CODE_OF_CONDUCT.md referencing Microsoft Open Source Code of Conduct - Add SUPPORT.md with GitHub Issues guidance - Add copyright headers to all 26 Python source files - Update .gitignore allowlist for new boilerplate files
- Update README.md: replace 'Microsoft Internal' license with MIT, add Trademarks section, update Contributing section to link CONTRIBUTING.md and CLA - Add .github/workflows/codeql.yml for CodeQL security scanning (Python) - Add .github/workflows/ci.yml for lint and syntax check - Add .github/dependabot.yml for weekly Python and GitHub Actions updates - Scrub: replace internal Workday tenant 'microsoft_dpt6' with 'contoso_impl' in connect docs
- Add .github/ISSUE_TEMPLATE/bug_report.yml (structured bug report form) - Add .github/ISSUE_TEMPLATE/feature_request.yml (feature request form) - Add .github/ISSUE_TEMPLATE/question.yml (question/help form) - Add .github/ISSUE_TEMPLATE/config.yml (disable blank issues, link to MSRC for security) - Add .github/PULL_REQUEST_TEMPLATE.md (PR checklist with CLA reminder) - Add .github/labels.yml (canonical label set documentation) Labels also applied to repo via gh CLI (16 total).
…r non-production samples-style repos. Closes ADO 7314650.
…CONTRIBUTING.md (closes ADO 7313407, 7313408)
…UTING.md; add Supported/Unsupported scenarios to README. Closes ADO 7313434, 7313435, 7313437, 7313438.
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](actions/setup-python@v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Documents the API architecture, Island Gateway reference, Dataverse entity gotchas, design principles, and checklist for adding new checks. Scoped to scripts/flightcheck/ so it's discoverable when working on the flightcheck tool specifically. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
John Nguyen (johnguy0)
left a comment
There was a problem hiding this comment.
Doc is genuinely useful — it captures the gotchas you spent CONFIG-013 hours on, and that's exactly the kind of thing that pays for itself the next time someone adds a check. Two structural things worth tightening before merge:
-
The "Dataverse = configured, Gateway = working" framing in the Architecture Overview doesn't match what the kit actually does today. Configured state in this kit comes from local YAML files (
scripts/flightcheck/checks/local_files.py), not from Dataverse. If a future check author follows this guide literally, we'll get a duplicate Dataverse query for something already validated locally — two sources of truth that can disagree. -
The "attached to
runner" pattern is documented but the shape ofrunnerisn't declared anywhere. New contributors have to readcli.pyto know what's on it. Either enumerate the attributes here (runner.config,runner.env_url,runner.dv_token,runner.env_id,runner.graph,runner.pp_admin,runner.pva) or — better — declare them as typed Optional fields onFlightCheckRunnerinrunner.pyso they're discoverable from the type.
The "No misleading results" principle is the most valuable thing in this doc. Don't lose it in the cleanup. Inline nits on file paths.
John Nguyen (johnguy0)
left a comment
There was a problem hiding this comment.
Nothing security-relevant in this PR. Worth noting for the record:
- The shared MSAL token cache at
my/.token_cache.binis referenced as a contributor pattern. Already covered bymy/**in.gitignore, so no leak risk. 96ff4394-9197-43aa-b393-6a41652e21f8is the public Power Virtual Agents 1P app ID, not a secret.- No untrusted input flowing anywhere. No new dependencies.
Deferring to other personas.
John Nguyen (johnguy0)
left a comment
There was a problem hiding this comment.
Doc-only PR, no perf angle. The closest thing is the documented "Read all bot components" call which has no pagination, but that's a faithful description of how the Island Gateway endpoint actually behaves — not something this doc introduced. Deferring.
John Nguyen (johnguy0)
left a comment
There was a problem hiding this comment.
Solid reference material — exactly the kind of doc that pays for itself the next time. Two things worth tightening so a contributor can actually act on the steps:
-
Status taxonomy is under-specified. Principle 1 only mentions
SKIPPEDvsPASSED, but the runner exposes five statuses (SKIPPED,NOT_CONFIGURED,WARNING,FAILED,ERROR) and existing checks split between them in ways this doc doesn't capture. Inline comment with the decision tree. -
"Adding a New Check" assumes you know the
CheckResultshape. Steps 3–5 say "add a check function" without showing one. Drop in a 5-line minimal example so a contributor doesn't have to grep around — that's the trial-and-error this doc is meant to prevent. Inline.
- Clarify auth.py path (lives at scripts/auth.py, imported via sys.path) - Rewrite data layer framing as three-way split: Local YAML / Dataverse / Island Gateway - Fix componenttype values (9=Topic, 12=Variable, not 1/68) - Add full status decision tree (PASSED/FAILED/WARNING/SKIPPED/NOT_CONFIGURED/ERROR) - Add runner attributes table with types and descriptions - Add minimal code example showing CheckResult construction - Soften list to 'common kinds include' (API may return others) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
John Nguyen (johnguy0)
left a comment
There was a problem hiding this comment.
I re-reviewed the delta since 91e4784. The earlier feedback is addressed. One new docs accuracy issue remains in the runner attributes table because it documents a client that is not present or wired. Not ready yet.
| | `runner.env_id` | `str` | Power Platform (BAP) environment ID | | ||
| | `runner.graph` | `GraphClient \| None` | Microsoft Graph client | | ||
| | `runner.pp_admin` | `PowerPlatformAdminClient \| None` | BAP admin client | | ||
| | `runner.pva` | `PVAClient \| None` | Island Gateway client | |
There was a problem hiding this comment.
runner.pva is not wired today: cli.py only attaches graph and pp_admin, and this branch has no pva_client.py. Remove this row or add the client and runner wiring before using it in the guide.
There was a problem hiding this comment.
Graham McMynn (@GrahamMcMynn) - confirmed: this branch has no pva_client.py (it lands in PR #37) and cli.py here only wires runner.graph + runner.pp_admin. There's a second doc-accuracy bug in the same table while we're here: runner.pp_admin is documented as PowerPlatformAdminClient, but the actual class in pp_admin_client.py is PPAdminClient.
Suggested fix:
| `runner.graph` | `GraphClient \| None` | Microsoft Graph client |
-| `runner.pp_admin` | `PowerPlatformAdminClient \| None` | BAP admin client |
-| `runner.pva` | `PVAClient \| None` | Island Gateway client |
+| `runner.pp_admin` | `PPAdminClient \| None` | BAP admin client |
| `runner.config` | `dict` | Parsed `my/config.json` |And the matching reference at line 126:
- same structure as `graph_client.py` / `pp_admin_client.py` / `pva_client.py`
+ same structure as `graph_client.py` / `pp_admin_client.py`Add runner.pva and the pva_client.py reference back when PR #37 merges (or in the same merge if they go in together).
There was a problem hiding this comment.
I will not address this comment. The issue is I need to merge #37 first.
saengland
left a comment
There was a problem hiding this comment.
Reviewed AGENTS.md as the contract for future check authors. The structure (architecture, runner attributes, design principles, checklist) is right, but it documents the world that exists in PR #37, not the world that exists on main, and the security section has a critical gap. 5 findings:
| # | Sev | Issue |
|---|---|---|
| 1 | HIGH | pva_client.py referenced as if it exists on main; only lives in PR #37 |
| 2 | HIGH | runner.pva: PVAClient | None documented but cli.py never sets it |
| 3 | HIGH | Zero security guidance for the bug classes round-8 of PR #29 just fixed (defusedxml, retry adapter, CWE-209 scrubbing, HTTPS validation on operator URLs) |
| 4 | MEDIUM | Checklist step 4 doesn''t actually tell authors how to wire a new scope (FULL_SCOPE / SCOPE_MAP in cli.py) |
| 5 | LOW | 12 em-dash chars throughout - kit convention is ASCII only |
#1, #2, #3 are blockers. The security section gap is the biggest one - this guide will be the first thing the next check author reads, and it should encode the lessons we just paid for.
| | Dataverse | `../auth.py` | `{env_url}/user_impersonation` | Bot components (topics, variables, knowledge source *config*), template configs, solution metadata, statecode (enabled/disabled) | | ||
| | Microsoft Graph | `graph_client.py` | `https://graph.microsoft.com/.default` | Licenses, user roles, Entra app registrations, CA policies | | ||
| | Power Platform Admin (BAP) | `pp_admin_client.py` | `https://service.powerapps.com//.default` | Environments, cloud flows, connections, DLP policies | | ||
| | Island Gateway (Copilot Studio) | `pva_client.py` | `96ff4394-9197-43aa-b393-6a41652e21f8/.default` | Live bot component status, model config, knowledge source *runtime state* | |
There was a problem hiding this comment.
HIGH - Architecture table documents pva_client.py (Island Gateway client) as if it exists, but the file is not on main or in this PR's diff - it lives only on PR #37 (users/grahamc/260504UpdateFlightCheckerForNewChecks). A future check author following this guide before PR #37 merges will look for a file that isn't there. The same dangling reference also appears at lines 63-64 (See pva_client.py _discover_gateway() for the implementation pattern) and line 126 (lists pva_client.py as one of the existing client patterns to follow).
Fix: either (a) make PR #38 dependent on PR #37 and rebase it onto pr-37 so pva_client.py lands first, or (b) drop the Island Gateway row from the table and remove every pva_client.py / _discover_gateway reference (lines 16, 41-81, 63-64, 126) and add it back in a follow-up PR after PR #37 merges. Until then the only valid clients to document are auth.py, graph_client.py, and pp_admin_client.py.
| | `runner.env_id` | `str` | Power Platform (BAP) environment ID | | ||
| | `runner.graph` | `GraphClient \| None` | Microsoft Graph client | | ||
| | `runner.pp_admin` | `PowerPlatformAdminClient \| None` | BAP admin client | | ||
| | `runner.pva` | `PVAClient \| None` | Island Gateway client | |
There was a problem hiding this comment.
HIGH - Runner attributes table claims runner.pva: PVAClient | None is available to check functions, but cli.py (lines 162-167 on main) only sets runner.config, runner.env_url, runner.dv_token, runner.env_id, runner.graph, runner.pp_admin. There is no runner.pva. The example at line 163 (# ... possibly using runner.graph / runner.pva / etc. ...) propagates the same error.
A check author who writes if not runner.pva: will hit AttributeError, not the documented None sentinel.
Fix: remove the runner.pva row from the table and the runner.pva mention from the example comment until the attribute is actually attached in cli.py (which happens in PR #37). After PR #37 merges, also document that tenant_id is implicitly needed by any new client following the existing MSAL pattern but is currently not exposed as a runner attribute - so authors must accept it as a separate arg or compute it from env_url.
| - **`statecode`/`statuscode`** are standard Dataverse record status (Active=0/1), | ||
| NOT the runtime crawl/index status. | ||
|
|
||
| ## Design Principles |
There was a problem hiding this comment.
HIGH - The Design Principles section (lines 99-141) has zero security guidance for the exact bug classes that landed in PR #29 round 8 (commits 97e9708, cb22c88):
xml.etreeinstead ofdefusedxmlfor any XML parsing -> XXE- No
urllib3.util.retry.Retryadapter onrequests.Sessionfor transient 5xx/429 against BAP/Graph -> intermittent flake - Raw exception text echoed into
CheckResult.result-> CWE-209 leak of bearer tokens / SAS URLs / PII when an upstream API fails - No scheme validation on operator-provided URLs (e.g.,
dataverseEndpointfrommy/config.json) -> http:// or file:// could be accepted
Future check authors will repeat these because the guide doesn't warn them. This guide is the first thing the next contributor reads.
Fix: add a ## Security Requirements section after Design Principles with four bullets:
- XML parsing: Use
defusedxml.ElementTreenotxml.etreefor any XML payload (Dataverse SOAP, federation metadata). Catch bothET.ParseErrorANDdefusedxml.common.DefusedXmlExceptionso attack-path payloads (EntitiesForbidden,ExternalReferenceForbidden,DTDForbidden,NotSupportedError) fall through to a structured result instead of an unhandled traceback. - HTTP retry: Mount a
urllib3.util.retry.Retry(total=3, backoff_factor=1, status_forcelist=[429,500,502,503,504], allowed_methods=GET/HEAD/OPTIONS only, respect_retry_after_header=True)adapter on a module-levelrequests.Session. Do NOT include POST/PUT/DELETE inallowed_methods- replays of mutating verbs on transient 5xx are unsafe. - CWE-209 scrubbing: Never put raw exception text or
error_descriptionintoCheckResult.resultorCheckResult.remediation. Bearer tokens, SAS URLs, and tenant IDs leak throughrequests.HTTPError.response.textand MSAL error_description. Echo only theerrorcode and HTTP status; log full detail to stderr only. - URL validation: When accepting a URL from
my/config.jsonor any operator-provided source, validateurlparse(url).scheme == "https"before sending bearer tokens to it. Seeauth._validate_https_urlandpp_admin_client.derive_environment_idfor the pattern.
| 2. If a new client is needed, create it following existing patterns and add it | ||
| to the table above | ||
| 3. Add the check function to the appropriate module in `checks/` | ||
| 4. Wire it into `_check_single_agent()` (for per-agent checks) or the |
There was a problem hiding this comment.
MEDIUM - Checklist step 4 says "Wire it into _check_single_agent() (for per-agent checks) or the appropriate scope runner" but never tells the author HOW to wire a brand-new scope. The actual mechanism is: import the function in cli.py, add a tuple (category_name, function) to FULL_SCOPE, and add an entry to SCOPE_MAP if the check should also be runnable in isolation via --scope <name>. Without that, the runner.register(...) call never happens for the new check and it silently never executes.
Fix: replace step 4 with two explicit steps:
4. For per-agent checks (each agent under `my/agents/`), call your check from `_check_single_agent()` in `checks/local_files.py`.
5. For a new scope category, in `scripts/flightcheck/cli.py`:
a. Import the check: `from flightcheck.checks.<module> import run_<name>_checks`
b. Append `("<Category Name>", run_<name>_checks)` to `FULL_SCOPE`.
c. If the scope should also run in isolation, add `"<scope_name>": [("<Category Name>", run_<name>_checks)]` to `SCOPE_MAP`.
d. The runner picks it up via `runner.register(category, fn)`; no further wiring needed.
| @@ -0,0 +1,202 @@ | |||
| # FlightCheck — Developer Guide | |||
There was a problem hiding this comment.
LOW - Document uses Unicode em-dash (U+2014) in 12 places: lines 1, 22, 33, 87, 105, 107, 108, 109, 111, 113, 115, 180. Rest of the kit's docs and source comments use ASCII hyphen. Mixed usage is a churn signal in git grep and breaks the kit's ASCII-only convention (Outlook COM mojibake history - same root cause when authors copy from rendered markdown into other surfaces).
Fix: replace every — with - (or -- if you want to preserve the visual weight). PowerShell from repo root:
(Get-Content scripts/flightcheck/AGENTS.md -Raw).Replace([char]0x2014, '-') | Set-Content scripts/flightcheck/AGENTS.md -NoNewline -Encoding utf8|
Heads-up Graham McMynn (@GrahamMcMynn) Srikanth Deshpande (@srideshpande): retargeted this PR from
Action needed on your side:
Once rebased, the existing review comments stay attached and CI re-runs. If the rebase is messy, ping me and I'll help. |
|
Reopened as #58 against main. The previous review (this PR) is preserved here for context. |
Summary
Adds a developer guide scoped to the flightcheck tool at \scripts/flightcheck/AGENTS.md. This documents the knowledge needed to efficiently add new checks without trial-and-error against live APIs.
Motivation
While implementing CONFIG-013 (PR #37), significant time was spent:
This guide captures those learnings so the next check can be implemented correctly on the first attempt.
Contents