feat(errors): add SysModulesDisabledError typed error class#29
Merged
Conversation
Per apcore/CLAUDE.md, implementation repos contain only code and a README — feature specs, test-case matrices, and design notes live in the apcore protocol-spec repo. Removes: - docs/features/async-task-evolution.md - docs/features/middleware-architecture-hardening.md - docs/async-task-evolution/test-cases.md These files were also stale (referenced the deprecated TaskStore.put method and the removed TaskStatus.RETRYING enum value); the canonical sources are the implementation in src/apcore/async_task.py and the upstream spec at apcore/docs/features/async-tasks.md. Signed-off-by: tercel <tercel.yi@gmail.com>
…nager surface
Implements four sync-audit findings against v0.22.0:
* A-D-AT-04 / D-17 — TaskStore Protocol methods (save, get, delete,
list, list_expired) are now async on the Protocol and on the default
InMemoryTaskStore. AsyncTaskManager awaits all store calls; a
transitional shim still drives sync stores so external custom stores
get one deprecation window to migrate. cleanup() is async because
list/delete now are.
* A-D-AT-03 / D-11 — ReaperHandle.stop and AsyncTaskManager.stop_reaper
are async and drain the reaper task before returning. Callers no
longer need a manual `await asyncio.sleep(0)` after `handle.stop()`.
* A-D-AT-06 — get_status / list_tasks return shallow copies via
dataclasses.replace so callers cannot mutate the live store record.
New get_status_async / list_tasks_async surfaces for I/O-backed
stores that genuinely suspend.
* A-D-AT-09 / D-14 — Legacy RetryPolicy now defaults max_retries to 0
(retries are strictly opt-in across all SDKs) and emits a
DeprecationWarning on instantiation steering callers to RetryConfig.
Adds tests/test_async_task_sync_audit_v022.py pinning each invariant
plus an async-only custom-store smoke test. Updates existing tests to
the new async surface (await store ops, await stop_reaper, await
cleanup) and registers an existing sync test as `@pytest.mark.asyncio`
where required.
Signed-off-by: tercel <tercel.yi@gmail.com>
…wrap
Two normative-decision fixes for the executor pipeline:
* A-D-EXEC-002 / D-21 — BuiltinCallChainGuard now observes
cancel_token.is_cancelled before any guard work and short-circuits
with ExecutionCancelledError. Combined with the existing Step 8
check this satisfies the two-point cancel-token invariant; the
earlier single-check implementation leaked compute through
ACL/middleware/validation even when the caller had already
cancelled.
* A-D-EXEC-005 / D-22 — Executor._recover_from_call_error now unwraps
MiddlewareChainError and propagates its `.original` typed cause
unchanged. Previously the wrapper was collapsed to a generic
ModuleExecuteError, breaking callers that dispatch on typed errors
(notably MCP/A2A bridges keying on APPROVAL_DENIED vs
MODULE_EXECUTE_ERROR). Mirrors the TypeScript and Rust SDKs.
Adds tests/test_executor_sync_audit_v022.py with regression coverage
for both decisions. Drops the now-unused ModuleExecuteError import
from executor.py.
Signed-off-by: tercel <tercel.yi@gmail.com>
…ll paths
Four sync-audit findings clamp the Issue #65 invariants so they hold
uniformly across every registration path, not just the public
register() API:
* A-D-REG-002 — register_internal's ephemeral-namespace check now
uses the shared _is_ephemeral helper instead of bare .startswith,
so the bare ID "ephemeral" is rejected too (it previously slipped
through the .startswith("ephemeral.") guard).
* A-D-REG-003 — _register_in_order (discover path) now follows the
same three-phase deferred-publish protocol as register(): reserve
in-flight slot → run on_load outside the lock → publish on
success. Previously the discover path published into _modules
BEFORE invoking on_load, leaving a window in which registry.get()
callers could observe a module whose on_load-installed state was
incomplete.
* A-D-REG-004 — register_internal now uses the same three-phase
protocol. The Issue #65 invariant ("modules invisible until
on_load completes") now holds for sys-modules too.
* A-D-REG-005 — _invoke_on_load now emits
apcore.registry.module_load_failed when on_load fails, mirroring
the public register() path so subscribers have a single hook for
partial-init detection regardless of which registration route was
used.
Adds tests/test_registry_sync_audit_v022.py with regression coverage
for all four findings, including a probe module whose on_load asserts
the module is NOT yet visible in the registry while the callback runs.
Signed-off-by: tercel <tercel.yi@gmail.com>
Replaces bare RuntimeError raises in APCore.on/off/disable/enable with a typed error class so callers can dispatch on error.code == "SYS_MODULES_DISABLED" rather than catching the generic Exception base. Cross-language equivalent of Rust's ErrorCode::SysModulesDisabled and the parallel TypeScript SysModulesDisabledError landing alongside this PR. The class defaults retryable=False — sys_modules state is a config-time decision, not a transient condition retries can fix. Test plan - pytest tests/test_client.py tests/test_public_api.py -q → 111 passed in 0.15s Signed-off-by: tercel <tercel.yi@gmail.com>
Merged
4 tasks
Signed-off-by: tercel <tercel.yi@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces bare
RuntimeErrorraises inAPCore.on/off/disable/enablewith a typedSysModulesDisabledError(codeSYS_MODULES_DISABLED,retryable=False).Callers can now dispatch on
error.code == "SYS_MODULES_DISABLED"orexcept SysModulesDisabledErrorinstead ofexcept Exception+ string matching.Cross-language equivalent of Rust's
ErrorCode::SysModulesDisabledand a parallel TypeScriptSysModulesDisabledErrorlanding alongside this PR in apcore-typescript.Test plan
pytest tests/test_client.py tests/test_public_api.py -q→ 111 passed in 0.15sapcoretop-level (__init__.py)retryable=Falsedefault — sys_modules state is config-time, not retry-fixable