Skip to content

audit: add optional WSDL field gate (#239)#248

Merged
axisrow merged 5 commits into
mainfrom
feat/issue-239-optional-fields-gate
May 23, 2026
Merged

audit: add optional WSDL field gate (#239)#248
axisrow merged 5 commits into
mainfrom
feat/issue-239-optional-fields-gate

Conversation

@axisrow

@axisrow axisrow commented May 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds recursive WSDL item-field traversal so parity tooling can see optional minOccurs=0 fields and nested subtype fields.
  • Adds an optional-field audit gate in tests/test_wsdl_parity_gate.py plus scripts/build_wsdl_optional_field_audit.py.
  • Tightens the audit after review: it now reads cached WSDL and imported XSD files only, uses exact WSDL path to CLI option mappings instead of leaf-name heuristics, audits all nested depths, and fails on any unclassified row.
  • Commits tests/WSDL_OPTIONAL_FIELD_AUDIT.md as the generated audit snapshot: 3241 field rows, 384 supported rows, 2857 missing_followup rows, 0 unclassified rows.
  • Creates and links follow-up issues ads update: complete WSDL subtype parity — add 12 missing ad subtypes #240 and adgroups add/update: add TrackingParams typed flags #242 through sitelinks add: expose TurboPageId with typed CLI input #257 for confirmed optional-field gaps.
  • Documents the fifth soft WSDL gate in CLAUDE.md and CHANGELOG.md.

Closes #239.

Follow-up issues filed

Test plan

  • python3 -m black --check direct_cli/wsdl_coverage.py tests/test_wsdl_parity_gate.py scripts/build_wsdl_optional_field_audit.py
  • python3 scripts/build_wsdl_optional_field_audit.py --check
  • python3 -m pytest -q tests/test_wsdl_parity_gate.py
  • python3 -m pytest -q tests/test_api_coverage.py tests/test_cli.py
  • python3 scripts/build_api_coverage_report.py
  • python3 -m pytest -q - 1015 passed, 45 skipped, 30 subtests passed

Notes

A full black --check direct_cli tests scripts still reports pre-existing formatting drift in unrelated vendor/legacy files. This PR formats only the files it changes.

@axisrow

axisrow commented May 23, 2026

Copy link
Copy Markdown
Owner Author

@claude review. Focus on critical issues: bugs, security vulnerabilities, logical errors, data loss risks, performance problems, stale audit links, and false closure of #239. Do NOT nitpick style, naming conventions, minor formatting, or subjective preferences — only flag issues that could break functionality, make the optional-field audit misleading, or cause real harm in production.

@claude

claude Bot commented May 23, 2026

Copy link
Copy Markdown

Claude finished @axisrow's task in 4m 54s —— View job


Claude Code Review — PR #248

Todo


Summary

This is a well-scoped, soft-gate audit. No bugs, security issues, data‑loss risks, or production hazards found. The changes are read-only (audit + tests + docs) and cannot affect any mutating CLI path. Below are a few real risks that could make the audit misleading over time, plus one minor non-issue.

Critical-ish findings

1. _camel_to_option heuristic produces false-positive supported rows when leaf names collide. scripts/build_wsdl_optional_field_audit.py:51-54 looks up by leaf field name only, so multiple WSDL paths that share a leaf name map to the same Click option. Example from tests/WSDL_OPTIONAL_FIELD_AUDIT.md:

  • adgroups.add DynamicTextFeedAdGroup.FeedIdsupported (--feed-id)
  • adgroups.add SmartAdGroup.FeedIdsupported (--feed-id)
  • adgroups.add TextAdGroupFeedParams.FeedIdsupported (--feed-id)

In reality there is only one --feed-id Click option on adgroups add, and it's wired into exactly one subtype block. The audit currently asserts all three are covered, which masks any subtype that isn't actually wired. Today this is mitigated by the parent block being flagged missing_followup (#247), but as those follow-ups are closed, the leaf rows will silently flip from "supported (true)" to "supported (false)" without anything noticing. Consider matching on path as well as leaf name in the heuristic, or extending WSDL_FIELD_TO_CLI_OPTION to accept full paths.

2. _issue_link hardcodes the repo slug (scripts/build_wsdl_optional_field_audit.py:57-60):

return f"[{issue}](https://github.com/axisrow/direct-cli/issues/{issue[1:]})"

If the repo is ever renamed/transferred, every missing_followup link in WSDL_OPTIONAL_FIELD_AUDIT.md becomes stale and the regenerated audit will keep emitting dead links — test_optional_field_audit_report_is_current won't catch it because both sides are wrong. Pull the slug from git remote get-url origin or a constant at top of file so it's a single point of failure. Fix this →

3. Soft cache fallback to live WSDL inside the audit gate. fetch_wsdl() at direct_cli/wsdl_coverage.py:164 silently falls back to a network fetch + cache write when the cache file is missing. The new test test_optional_field_audit_report_is_current calls it via subprocess; if a fresh CI checkout (or a future cache cleanup) removes any tests/wsdl_cache/*.xml, the test will go to the network, potentially generate a different audit than the committed Markdown, and either (a) flake on transient network errors or (b) cause a spurious "audit stale" failure that masks the real cause. Not introduced by this PR, but the new soft-gate is the first thing that runs the full container walk, so it's the most likely surface for this. Worth either passing use_cache=True, allow_network=False semantics or asserting cache presence before walking. Fix this →

Minor / non-blocking

4. OPTIONAL_FIELD_AUDIT override precedence is unconditional. In _classify_row (scripts/build_wsdl_optional_field_audit.py:69-74), a manual entry wins even if the field has since been implemented. That's the intended behavior (manual ledger is authoritative), but it does mean once a follow-up is shipped, you have to remember to remove the override or the audit will keep reporting missing_followup for a field that's actually live. The _audited_wsdl_paths invariant only catches stale paths, not stale statuses. Consider a separate test that fails when an override says missing_followup but the heuristic detects a typed CLI flag — that flips the audit into a continuous reminder to close follow-ups.

5. iter_container_item_fields recursion is bounded by max_depth (direct_cli/wsdl_coverage.py:564-619), so no cycle risk even for a self-referential WSDL type — good. Shallow-copy of each field excluding item_fields is correct.

6. Determinism of render_markdown. Iteration order is sorted(COMMAND_WSDL_MAP.items()) then WSDL-XML field order; confirmed filter preserves that order. Reproducible across runs. ✓

7. Hard-gate coverage of nested required fields is still missing, e.g. MobileAppAdGroup.StoreUrl (min=1, depth=2) shows as missing_candidate, not required_hard_gate, because get_required_item_fields only walks depth=1. This PR doesn't regress the hard gate — it just newly exposes those rows in the soft audit — but the message "covered by minOccurs>=1 parity gate" is only emitted for depth=1, which is the right call. No action needed; calling out for the next maintainer reading the table.

#239 closure

The PR description claims Closes #239 and what it actually ships is the audit gate itself plus six follow-up issues for the concrete CLI gaps. That's a legitimate scoping decision — making the gaps visible and tracked is exactly what a "missing optional fields" audit should land. Closure looks accurate given the audit registry covers the 29 confirmed gaps and the soft gate prevents future drift. (I could not directly fetch the #239 body in this sandbox to triple-check phrasing; if the issue body explicitly asks for the missing fields to be implemented rather than catalogued, the PR description should be reworded to "Refs #239" and keep #239 open until #242#247 ship.)

Things I checked and found no issue with

  • No security-sensitive surface introduced (no eval, no shell, no auth, no IO outside repo).
  • subprocess.run in test_optional_field_audit_report_is_current uses sys.executable + check=False and inspects returncode — no injection, no hang.
  • No mutation paths touched; smoke-matrix categories unchanged.
  • WSDL field walk has no cycle risk thanks to max_depth=2.
  • The new public function iter_container_item_fields is purely additive and doesn't shadow get_required_item_fields.

· Branch: feat/issue-239-optional-fields-gate

@axisrow
axisrow marked this pull request as ready for review May 23, 2026 18:27
Copilot AI review requested due to automatic review settings May 23, 2026 18:27
@axisrow
axisrow merged commit 14b047a into main May 23, 2026
7 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a soft “optional WSDL field” visibility gate to the existing WSDL parity tooling so minOccurs=0 and nested item fields are enumerated, snapshotted, and tracked via follow-up issues instead of being silently skipped.

Changes:

  • Add recursive WSDL item-field traversal (iter_container_item_fields) and cache-only WSDL/XSD readers for offline parity gates.
  • Introduce scripts/build_wsdl_optional_field_audit.py to generate/check tests/WSDL_OPTIONAL_FIELD_AUDIT.md and fail on any unclassified row.
  • Extend tests/test_wsdl_parity_gate.py with optional-field audit registries/mappings plus tests that validate audit entries, Click option references, cache-only imports, and snapshot freshness.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/test_wsdl_parity_gate.py Adds optional-field audit registries and new tests to validate audit correctness, cache-only behavior, and report freshness.
scripts/build_wsdl_optional_field_audit.py New script to build/check the optional-field audit markdown snapshot and enforce “no unclassified rows”.
direct_cli/wsdl_coverage.py Adds cache-only WSDL/XSD fetch helpers and a recursive item-field traversal helper used by the audit.
CLAUDE.md Documents the new optional-field soft gate as part of the WSDL parity gate description.
CHANGELOG.md Notes the addition of the optional-field audit in 0.3.12.

Comment thread CLAUDE.md
2. *Silent data loss* — a typed flag that does not belong to the chosen `--type` must raise `UsageError`, not be dropped.
3. *WSDL `minOccurs=1` not validated* — every required WSDL item field must be enforced either via Click `required=True` *or* a documented `UsageError` body check (listed in `INTERNAL_VALIDATION`).
4. *Strategy enum drift* — `STRATEGY_TYPES` (`direct_cli/commands/strategies.py`) must equal the subtype-of-one field names in `StrategyAddItem`.
5. *Optional-field visibility* — `scripts/build_wsdl_optional_field_audit.py --check` compares cached WSDL item fields (including `minOccurs=0`, nested to depth 2) with `tests/WSDL_OPTIONAL_FIELD_AUDIT.md`. Confirmed misses stay soft-gated as `missing_followup` rows linked to GitHub issues; they do not fail CI as missing CLI flags until implemented.
Comment thread CHANGELOG.md
- WSDL parity now includes a soft optional-field audit for issue #239.
`scripts/build_wsdl_optional_field_audit.py --check` regenerates and
compares `tests/WSDL_OPTIONAL_FIELD_AUDIT.md`, covering cached mutating
WSDL item fields through nesting depth 2. Confirmed `minOccurs=0`
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.

audit: WSDL parity gate skips optional fields — audit all *Update/*Add types for missed CLI flags (CalloutSetting pattern)

2 participants