fix(bitdefender): errorlog rule-compliance + moveCustomGroup param + ASCII-clean code

Finalizing the skill to "done, no errors, all skill rules":
- errorlog compliance: gz.py no longer logs EXPECTED API responses (validation,
  method-not-found, not-configured, rate-limit, expected state) or `raw`/selftest
  runs to errorlog.md. Per CLAUDE.md "do not log expected/handled conditions".
  Verified: selftest + probes leave errorlog unchanged.
- moveCustomGroup: param is `parentId`, not `newParentId` (6th doc-vs-live fix
  caught by a full param-shape audit).
- ASCII-clean code: removed all non-ASCII (em-dashes, U+21D2 arrow) from scripts
  (avoids cp1252 encode errors; aligns with the ASCII-markers rule).
- api-reference updated.

Verified: 18/18 read commands rc=0 live; selftest 75/75; parser builds; ASCII
markers + vault load + errorlog helper present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 17:06:46 -07:00
parent f55feb07fa
commit 8f17c17258
4 changed files with 90 additions and 46 deletions

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""CLI for the bitdefender skill GravityZone Cloud Public API.
"""CLI for the bitdefender skill - GravityZone Cloud Public API.
Read-only subcommands run freely. Destructive subcommands (delete-endpoint,
delete-package, delete-group) refuse to run unless --confirm is passed; without
@@ -62,6 +62,45 @@ def _log_skill_error(skill, msg, context=""):
pass
# Substrings that mark an error as an EXPECTED API response (validation, probing,
# not-configured, transient rate-limit, expected state) rather than a genuine skill
# failure. The errorlog rule (CLAUDE.md) forbids logging expected/handled
# conditions - only real failures worth pattern-spotting. These are NOT logged.
_EXPECTED_ERROR_MARKERS = (
"required parameter is missing",
"invalid value",
"not expected",
"method not found",
"is not available",
"not available yet",
"were not set",
"not configured",
"should not be used with",
"you must specify a value",
"cannot be restored from isolation",
"429",
"too many requests",
)
def _is_expected_error(msg: str) -> bool:
m = (msg or "").lower()
return any(marker in m for marker in _EXPECTED_ERROR_MARKERS)
def _should_log_error(command: str, msg: str) -> bool:
"""Decide whether a GravityZoneError is a genuine skill failure worth logging.
Skips: explicit suppression (selftest/probes set GZ_SUPPRESS_ERRORLOG), the
exploratory `raw` subcommand, and expected API validation/probe responses.
"""
if os.environ.get("GZ_SUPPRESS_ERRORLOG"):
return False
if command == "raw":
return False
return not _is_expected_error(msg)
def _emit(obj, as_json: bool, table_fn=None) -> None:
if as_json or table_fn is None:
print(json.dumps(obj, indent=2, default=_json_default))
@@ -676,7 +715,7 @@ def cmd_make_group(client, args):
# Substrings that mark a JSON-RPC method as state-destroying. `raw` can reach
# any method (incl. UNVERIFIED ones), so gate these behind --confirm too.
# isolate / blocklist add+remove are NEW destructive verbs from the incidents
# (EDR) module gate them in `raw` as well as via the dedicated subcommands.
# (EDR) module - gate them in `raw` as well as via the dedicated subcommands.
DESTRUCTIVE_RAW_PATTERNS = ("delete", "createuninstall", "createremove",
"createreconfigure", "isolat", "addtoblocklist",
"removefromblocklist", "assignpolicy",
@@ -1019,7 +1058,7 @@ def build_parser() -> argparse.ArgumentParser:
help="Remove one blocklist entry (gated).",
parents=[common])
sp.add_argument("--id", required=True,
help="hashItemId the 'id' from `blocklist` output.")
help="hashItemId - the 'id' from `blocklist` output.")
sp.add_argument("--confirm", action="store_true")
sp = sub.add_parser("assign-policy",
@@ -1208,8 +1247,9 @@ def main(argv=None) -> int:
return rc if isinstance(rc, int) else 0
except GravityZoneError as exc:
print(f"[ERROR] {exc}", file=sys.stderr)
_log_skill_error("bitdefender", f"{exc}",
context=f"cmd={getattr(args, 'command', '?')}")
cmd = getattr(args, "command", "?")
if _should_log_error(cmd, str(exc)):
_log_skill_error("bitdefender", f"{exc}", context=f"cmd={cmd}")
return 1
except KeyboardInterrupt:
return 130