Skip to content

Release 0.9.0

Choose a tag to compare

@tercel tercel released this 13 May 03:20
· 4 commits to main since this release

Added

  • tests/conformance/test_snake_case_kwargs.py — runs the cross-language Algorithm C-SNAKE fixture (apcore-cli/conformance/fixtures/snake-case-kwargs/cases.json) against build_module_command via click.testing.CliRunner. Five cases verify that schema property names with underscores (has_solution, sort_by, sort_order) survive the round trip from CLI parse to the input dict received by executor.call. No source change required — click natively maps --has-solution to has_solution; the Python SDK is the parity reference for the parallel TypeScript fix. Surfaced as part of the cross-SDK regression coverage gap audit.

Fixed (2026-05-13 — cross-SDK audit D10/D11/D1)

  • ConfigEncryptor LOGNAME key-derivation chain (D10-001 / D11-003) — PBKDF2 username fallback was USER → USERNAME → "unknown" (3-tier); now USER → LOGNAME → USERNAME → "unknown" (4-tier) matching the spec and Rust. On hosts where USER is unset but LOGNAME is set (cron, sudo -i, container init), ciphertext written by the Python SDK now round-trips correctly with the Rust SDK. src/apcore_cli/security/config_encryptor.py:96, 165.
  • ConfigEncryptor.store keyring write-failure not wrapped (D11-004) — raw keyring.set_password exceptions now caught and re-raised as ConfigDecryptionError, matching TypeScript and Rust. src/apcore_cli/security/config_encryptor.py:31.
  • ref_resolver only descended into properties (D11-001) — recursive schema walk now visits every dict-valued child (items, additionalProperties, patternProperties, if/then/else, not, contains, propertyNames), matching TypeScript and Rust. $ref under array schemas and conditional schemas is now resolved. src/apcore_cli/ref_resolver.py:142.
  • ref_resolver copy-on-write visited-set (D11-002) — now uses a single mutable set with remove-on-unwind, allowing diamond $ref patterns (two sibling schemas referencing the same $def) to resolve correctly. src/apcore_cli/ref_resolver.py:71.
  • AuditLogger._get_user uses real UID instead of effective UID (D11-010) — switched from os.getuid() to os.geteuid() so audit records reflect the privileges the process actually runs with under sudo / setuid binaries. Matches Rust (geteuid) and TypeScript (os.userInfo). src/apcore_cli/security/audit.py:77.
  • check_approval ignores APCORE_CLI_APPROVAL_TIMEOUT env var (D11-012) — CliApprovalHandler and the legacy check_approval() wrapper now honor the env var when no explicit timeout is passed (precedence: constructor arg > env var > 60 s default). Matches TypeScript. src/apcore_cli/approval.py.
  • exec --dry-run crashes with AttributeError when executor lacks validate (D11-013) — guarded with hasattr(executor, "validate"); falls back to synthetic {"valid": True} matching TypeScript. src/apcore_cli/discovery.py:378.
  • CliApprovalHandler.request_approval missing requires_approval=False short-circuit (D11-014) — now returns approved/not_required when the request explicitly carries requires_approval=False, matching Rust. src/apcore_cli/approval.py:66.
  • CLI brand string in auth error messages (D11-006) — remediation strings now say apcli config set auth.api_key (canonical FE-13 name) instead of apcore-cli config set auth.api_key. src/apcore_cli/security/auth.py.
  • reconvert_enum_values missing from public re-export (D1-W2) — added to __init__.py import block and __all__. Embedders can now from apcore_cli import reconvert_enum_values for parity with TypeScript and Rust.
  • Ref-resolver error hierarchy missing from public re-export (D1-W3) — CircularRefError, MaxDepthExceededError, UnresolvableRefError, RefResolverError added to __init__.py import block and __all__. Parity with TypeScript index.ts:82-84.
  • DEFAULT_BUILTIN_GROUP_NAME missing from public re-export (D1 re-audit) — added to __init__.py. Parity with Rust lib.rs:190.
  • Dead exit-code constants removed (D9-W1) — EXIT_CONFIG_ENV_PREFIX_CONFLICT and EXIT_CONFIG_ENV_MAP_CONFLICT (both = 78, zero callers) deleted from src/apcore_cli/exit_codes.py.
  • Unused pytest-asyncio dev dependency removed (D6) — the package was declared but never exercised; no async tests exist. Removed from [project.optional-dependencies].dev.

Fixed

  • CSV --format csv Python-repr bugcsv.DictWriter was called with {k: str(v) for k, v in row.items()} which emitted Python repr {'k': 'v'} (single quotes) for nested dict/list values. The output was not valid JSON and any downstream JSON parser would fail. Now delegates to apcore_toolkit.format_csv(rows) which emits canonical compact JSON. src/apcore_cli/output.py:149, 378.
  • CSV heterogeneous-keys data loss — header is now the union of keys across all rows (was first-row only via list(rows[0].keys())).
  • CSV line terminator — now \r\n per RFC 4180.
  • JSONL canonical form — now compact (no spaces between separators), matching the cross-SDK contract. Tests updated.

Changed

  • User-visible help/man/completion text no longer leaks the apcore framework name to end users of downstream CLIs built on apcore-cli. Affected strings: init group description (Scaffold new apcore modulesScaffold new modules, init_cmd.py:45), --extensions-dir option help (Path to apcore extensions directory.Path to extensions directory., factory.py:460), zsh/fish completion descriptions for exec (Execute an apcore moduleExecute a module, shell.py:130, 211), and man-page ENVIRONMENT section text (shell.py:299, 314, 319, 458) — drops apcore from the descriptive copy (Path to the apcore extensions directoryPath to the extensions directory, Global apcore logging verbosityGlobal logging verbosity, API key for authenticating with the apcore registryAPI key for authenticating with the registry). Logger names, source comments, module docstrings, and environment-variable identifiers (APCORE_*) are unchanged — only descriptive copy that appears in --help, shell completion, and man output. Cross-SDK parity with TypeScript 0.8.2 and Rust 0.8.1.

Changed (breaking CLI surface)

  • Global --verbose flag renamed to --all-options — The help-display flag is now --all-options; use apcore-cli module --help --all-options to reveal hidden built-in options. verbose is removed from the reserved schema property names set — module schemas may now freely define verbose: boolean for runtime output control. Internal API: set_verbose_help() renamed to set_all_options_help(); module-level global _verbose_help renamed to _all_options_help. Tracked in apcore-cli#21.

Changed (breaking dependency semantics)

  • apcore-toolkit promoted from optional extra to REQUIRED runtime dependency (>=0.7.0). The previous pip install 'apcore-cli[toolkit]' extras pattern is retained as a no-op for backward compat with install scripts, but the toolkit is now always installed alongside apcore-cli. All --format operations route through the toolkit's reference implementation for csv/jsonl/markdown/skill.

Why

See ADR-09 in apcore-cli/docs/tech-design.md for the byte-equivalent toolkit-delegated tier rationale.