Commit Graph

8 Commits

Author SHA1 Message Date
0a346e69f8 feat(sdk): add idempotency-key delegation to RemoteAgentClient
KI-002: prevent duplicate delegation execution when a remote agent
container restarts mid-delegation. Remote agent clients now compute a
SHA-256 key from task_text + current wall-clock minute and POST it as
idempotency_key to /workspaces/:id/delegate.

The platform deduplicates requests sharing the same key within the
minute window. Callers can also pass an explicit idempotency_key to
override the auto-computed value.

New:
- make_idempotency_key(task_text) → str
- RemoteAgentClient.delegate(task, target_id, idempotency_key=None, timeout=300.0)
- 8 new unit tests covering headers, errors, explicit key override, and
  make_idempotency_key invariants

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 18:18:23 +00:00
d55b2b951c fix(sdk): resolve KI-003 — log warning for skipped symlinks in _safe_extract_tar
_symlink entries in plugin tarballs are skipped (security posture, correct) but
now emit a logger.warning so operators can audit what was dropped:

  "skipping symlink in plugin tarball (not supported for security): <name> -> <target>"

Added test_safe_extract_logs_warning_for_skipped_symlink asserting the warning
is present in caplog records at WARNING level.  All 211 tests pass (+1 new).

known-issues.md updated.
2026-04-21 22:03:13 +00:00
19653f85c8 fix(sdk): resolve KI-004 — fcntl.flock token file race condition
Add fcntl.flock around token read/write in load_token() and save_token():

- load_token(): shared lock (LOCK_SH | LOCK_NB) before reading.
  Returns None if lock is contended rather than blocking.
- save_token(): exclusive lock (LOCK_EX | LOCK_NB) before writing.
  Gracefully degrades (logs warning, skips write) if another writer
  holds the lock; in-memory _token is still updated so this instance
  functions correctly. Releases lock in finally block.

Concurrent readers are safe. Concurrent writers are serialised. The
platform's one-token-per-workspace invariant is preserved.

known-issues.md updated.
2026-04-21 21:36:00 +00:00
beca7db42a fix(sdk): resolve KI-005 and KI-007 — secrets scan + _is_hex guard
KI-007 (High): Add isinstance(value, str) guard to _is_hex() so
non-string arguments return False cleanly instead of raising TypeError.
Updated test_is_hex_non_string to assert False instead of expecting
pytest.raises(TypeError).

KI-005 (High): Add _scan_for_secrets() to manifest.py that walks all
string values in plugin.yaml and reports common credential patterns
(sk-, ghp_, AKIA, bearer tokens, long hex strings, password/api_key
assignments). Call it from validate_manifest(). Skips the sha256
field since it's a content-addressed hash, not a secret.

Run: pytest → 210 passed, 1 skipped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:09:44 +00:00
molecule-ai[bot]
818931f9d3
feat(tests): GAP-05 add _get_with_retry() with 429 back-off + fix broken test_call_peer_errors (#11)
Adds retry-on-429 with exponential back-off (1 s → 2 s → 4 s, ±25% jitter,
30 s cap, Retry-After header honoured) to all idempotent RemoteAgentClient
GET calls: poll_state, pull_secrets, get_peers, discover_peer.

Also fixes the merged test_call_peer_errors.py (PR #7) which was broken:
- Removed pytest-mock dependency (mocker not installed)
- Fixed call_peer(message: str) vs dict
- Fixed non-existent _call_direct/_call_proxy method patches
- Uses FakeResponse + _session.post.side_effect pattern consistently

Adds tests/conftest.py (FakeResponse + client fixture + _CaptureHandler)
and tests/test_retry_backoff.py (18 new tests).

Co-authored-by: Molecule AI SDK-Dev <sdk-dev@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 07:08:01 +00:00
molecule-ai[bot]
4467f8ad89
feat(security): add plugin content integrity verification (SHA256) (#3)
* feat(security): add plugin content integrity verification (SHA256)

SDK-side follow-up to molecule-core PR #1019 (pinned-ref supply-chain fix).

Changes:
- verify_plugin_sha256(plugin_dir, expected_sha) — content-addressed manifest
  hash over sorted (relpath, SHA256(content)) pairs; plugin.yaml excluded
  from its own hash to avoid circular dependency
- _walk_files(root) / _sha256_file(path) — internal helpers
- install_plugin() calls verify_sha256 after atomic rename; on mismatch
  deletes plugin dir and raises ValueError before setup.sh runs
- PLUGIN_YAML_SCHEMA gains optional sha256 field (64-char lowercase hex)
- validate_manifest() validates sha256 format when present

Tests (12 new):
- sha256_file correctness, walk_files ordering, verify_* (match/mismatch/invalid)
- install_plugin sha256 verified: setup.sh runs
- install_plugin sha256 mismatch: raises ValueError, setup.sh NOT run
- install_plugin no sha256: backward-compat, skips verification
- validate_manifest sha256: valid/invalid/non-hex/absent

Pre-existing: 4 async tests in test_sdk.py fail without pytest-asyncio
(not related to this change).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(tests): add pytest-asyncio markers to async adaptor tests

The 4 tests using async def were failing because pytest-asyncio was not
installed and pytest.ini set asyncio_mode=auto (which requires it). Add
@pytest.mark.asyncio to each async test and add pytest-asyncio as a
test optional dependency so CI gets the right extras when installing.

Fixes: 4 FAILED tests in test_sdk.py

* feat(cli): add verify-sha256 command to molecule_agent

Add `python -m molecule_agent verify-sha256 <plugin-dir>` CLI that
computes the content-integrity SHA256 for a plugin directory (the same
manifest hash that verify_plugin_sha256() uses internally). Plugin authors
can run this to generate the hash to put in plugin.yaml's sha256 field.

Also:
- Re-export verify_plugin_sha256 and compute_plugin_sha256 from the
  molecule_agent package root so `from molecule_agent import
  compute_plugin_sha256` works.
- Update CLAUDE.md to document the CLI and content integrity flow.
- Write pr-description-draft.md as a backup for when GH_TOKEN recovers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Molecule AI SDK-Dev <sdk-dev@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 01:00:35 +00:00
molecule-ai[bot]
51ad567fd0
fix(tests): add pytest-asyncio markers to async adaptor tests (#4)
* feat(security): add plugin content integrity verification (SHA256)

SDK-side follow-up to molecule-core PR #1019 (pinned-ref supply-chain fix).

Changes:
- verify_plugin_sha256(plugin_dir, expected_sha) — content-addressed manifest
  hash over sorted (relpath, SHA256(content)) pairs; plugin.yaml excluded
  from its own hash to avoid circular dependency
- _walk_files(root) / _sha256_file(path) — internal helpers
- install_plugin() calls verify_sha256 after atomic rename; on mismatch
  deletes plugin dir and raises ValueError before setup.sh runs
- PLUGIN_YAML_SCHEMA gains optional sha256 field (64-char lowercase hex)
- validate_manifest() validates sha256 format when present

Tests (12 new):
- sha256_file correctness, walk_files ordering, verify_* (match/mismatch/invalid)
- install_plugin sha256 verified: setup.sh runs
- install_plugin sha256 mismatch: raises ValueError, setup.sh NOT run
- install_plugin no sha256: backward-compat, skips verification
- validate_manifest sha256: valid/invalid/non-hex/absent

Pre-existing: 4 async tests in test_sdk.py fail without pytest-asyncio
(not related to this change).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(tests): add pytest-asyncio markers to async adaptor tests

The 4 tests using async def were failing because pytest-asyncio was not
installed and pytest.ini set asyncio_mode=auto (which requires it). Add
@pytest.mark.asyncio to each async test and add pytest-asyncio as a
test optional dependency so CI gets the right extras when installing.

Fixes: 4 FAILED tests in test_sdk.py

---------

Co-authored-by: Molecule AI SDK-Dev <sdk-dev@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 00:54:07 +00:00
Hongming Wang
fefcc38e11 feat: initial Python SDK (extracted from molecule-monorepo/sdk/python)
Workspace, org, channel, memory, delegation client for Molecule AI.
Package renamed to molecule-ai-sdk for PyPI.
2026-04-16 03:15:38 -07:00