Skip to content

Commit f54d2f6

Browse files
tercelclaude
andcommitted
feat: Bump to v0.12.0 — add Module.preflight()/describe(), ExecutionCancelledError extends ModuleError
- Add optional preflight() and describe() methods to Module interface (spec §5.6) - Add ModuleDescription interface and export from package index - ExecutionCancelledError now extends ModuleError with code EXECUTION_CANCELLED (was bare Error) - Add EXECUTION_CANCELLED to ErrorCodes constants - Remove phantom batchProcessing CHANGELOG entry (never implemented) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 47b86b4 commit f54d2f6

23 files changed

+118
-67
lines changed

CHANGELOG.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.12.0] - 2026-03-10
9+
10+
### Added
11+
- **`Module.preflight()`** — Optional method for domain-specific pre-execution warnings (spec §5.6)
12+
- **`Module.describe()`** — Optional method returning `ModuleDescription` for LLM/AI tool discovery (spec §5.6)
13+
- **`ModuleDescription`** interface — Typed return type for `Module.describe()`, exported from package index
14+
15+
### Changed
16+
- **`ExecutionCancelledError`** now extends `ModuleError` (was bare `Error`) with error code `EXECUTION_CANCELLED`, aligning with PROTOCOL_SPEC §8.7 error hierarchy
17+
- **`ErrorCodes`** — Added `EXECUTION_CANCELLED` constant
18+
19+
### Fixed
20+
- **Removed phantom CHANGELOG entry**`ModuleAnnotations.batchProcessing` (v0.4.0) was never implemented
21+
22+
---
23+
824
## [0.11.0] - 2026-03-08
925

1026
### Added
@@ -235,7 +251,6 @@ Built-in `system.*` modules that allow AI agents to query, monitor
235251
- Improved performance of `Executor.stream()` with optimized buffering.
236252

237253
### Added
238-
- Introduced `ModuleAnnotations.batchProcessing` for enhanced batch processing capabilities.
239254
- Added new logging features for better observability in the execution pipeline.
240255
- **ExtensionManager** and **ExtensionPoint** exports for unified extension point management (discoverer, middleware, acl, span_exporter, module_validator)
241256
- **AsyncTaskManager**, **TaskStatus**, **TaskInfo** exports for async task execution with status tracking (PENDING, RUNNING, COMPLETED, FAILED, CANCELLED) and cancellation

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "apcore-js",
3-
"version": "0.11.0",
3+
"version": "0.12.0",
44
"description": "AI-Perceivable Core — schema-driven module development framework",
55
"type": "module",
66
"main": "./dist/index.js",

src/cancel.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
* Cooperative cancellation support for apcore module execution.
33
*/
44

5-
export class ExecutionCancelledError extends Error {
5+
import { ModuleError } from './errors.js';
6+
7+
export class ExecutionCancelledError extends ModuleError {
68
constructor(message: string = "Execution was cancelled") {
7-
super(message);
9+
super("EXECUTION_CANCELLED", message);
810
this.name = "ExecutionCancelledError";
911
}
1012
}

src/errors.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class ACLDeniedError extends ModuleError {
131131
options?.cause,
132132
options?.traceId,
133133
options?.retryable,
134-
options?.aiGuidance,
134+
options?.aiGuidance ?? `Access denied for '${callerId}' calling '${targetId}'. Verify the caller has the required role or permission, or try an alternative module with similar functionality.`,
135135
options?.userFixable,
136136
options?.suggestion,
137137
);
@@ -158,7 +158,7 @@ export class ModuleNotFoundError extends ModuleError {
158158
options?.cause,
159159
options?.traceId,
160160
options?.retryable,
161-
options?.aiGuidance,
161+
options?.aiGuidance ?? `Module '${moduleId}' does not exist in the registry. Verify the module ID spelling. Use system.manifest.full to list available modules.`,
162162
options?.userFixable,
163163
options?.suggestion,
164164
);
@@ -177,7 +177,7 @@ export class ModuleDisabledError extends ModuleError {
177177
options?.cause,
178178
options?.traceId,
179179
options?.retryable,
180-
options?.aiGuidance,
180+
options?.aiGuidance ?? `Module '${moduleId}' is currently disabled. Use system.control.toggle_feature to re-enable it, or find an alternative module.`,
181181
options?.userFixable,
182182
options?.suggestion,
183183
);
@@ -196,7 +196,7 @@ export class ModuleTimeoutError extends ModuleError {
196196
options?.cause,
197197
options?.traceId,
198198
options?.retryable,
199-
options?.aiGuidance,
199+
options?.aiGuidance ?? `Module '${moduleId}' timed out after ${timeoutMs}ms. Consider: 1) Breaking the operation into smaller steps. 2) Reducing the input data size. 3) Asking the user if a longer timeout is acceptable.`,
200200
options?.userFixable,
201201
options?.suggestion,
202202
);
@@ -227,7 +227,7 @@ export class SchemaValidationError extends ModuleError {
227227
options?.cause,
228228
options?.traceId,
229229
options?.retryable,
230-
options?.aiGuidance,
230+
options?.aiGuidance ?? 'Input validation failed. Review the error details to identify which fields have invalid values, then correct them or ask the user for valid input.',
231231
options?.userFixable,
232232
options?.suggestion,
233233
);
@@ -293,7 +293,7 @@ export class CallDepthExceededError extends ModuleError {
293293
options?.cause,
294294
options?.traceId,
295295
options?.retryable,
296-
options?.aiGuidance,
296+
options?.aiGuidance ?? `Call depth ${depth} exceeds maximum ${maxDepth}. Simplify the module call chain or restructure to reduce nesting depth.`,
297297
options?.userFixable,
298298
options?.suggestion,
299299
);
@@ -320,7 +320,7 @@ export class CircularCallError extends ModuleError {
320320
options?.cause,
321321
options?.traceId,
322322
options?.retryable,
323-
options?.aiGuidance,
323+
options?.aiGuidance ?? 'A circular call was detected in the module call chain. Review the call_chain in error details and restructure to eliminate the cycle.',
324324
options?.userFixable,
325325
options?.suggestion,
326326
);
@@ -736,6 +736,7 @@ export const ErrorCodes = Object.freeze({
736736
MODULE_TIMEOUT: "MODULE_TIMEOUT",
737737
MODULE_LOAD_ERROR: "MODULE_LOAD_ERROR",
738738
RELOAD_FAILED: "RELOAD_FAILED",
739+
EXECUTION_CANCELLED: "EXECUTION_CANCELLED",
739740
MODULE_EXECUTE_ERROR: "MODULE_EXECUTE_ERROR",
740741
SCHEMA_VALIDATION_ERROR: "SCHEMA_VALIDATION_ERROR",
741742
SCHEMA_NOT_FOUND: "SCHEMA_NOT_FOUND",

src/executor.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ import type { Registry } from './registry/registry.js';
3535
export const REDACTED_VALUE: string = '***REDACTED***';
3636

3737
/** Well-known context.data keys used internally by the framework. */
38-
export const CTX_GLOBAL_DEADLINE = '_global_deadline';
39-
export const CTX_TRACING_SPANS = '_tracing_spans';
38+
export const CTX_GLOBAL_DEADLINE = '_apcore.executor.global_deadline';
39+
export const CTX_TRACING_SPANS = '_apcore.mw.tracing.spans';
4040

4141
export function redactSensitive(
4242
data: Record<string, unknown>,
@@ -553,6 +553,26 @@ export class Executor {
553553
checks.push({ check: 'schema', passed: true });
554554
}
555555

556+
// Check 7: module-level preflight (optional)
557+
if (typeof (mod as any).preflight === 'function') {
558+
try {
559+
const preflightWarnings = (mod as any).preflight(effectiveInputs, ctx);
560+
if (Array.isArray(preflightWarnings) && preflightWarnings.length > 0) {
561+
checks.push({ check: 'module_preflight', passed: true, warnings: preflightWarnings });
562+
} else {
563+
checks.push({ check: 'module_preflight', passed: true });
564+
}
565+
} catch (exc: unknown) {
566+
const excName = exc instanceof Error ? exc.constructor.name : 'Error';
567+
const excMsg = exc instanceof Error ? exc.message : String(exc);
568+
checks.push({
569+
check: 'module_preflight',
570+
passed: true,
571+
warnings: [`preflight() raised ${excName}: ${excMsg}`],
572+
});
573+
}
574+
}
575+
556576
return createPreflightResult(checks, requiresApproval);
557577
}
558578

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export { Executor, redactSensitive, REDACTED_VALUE, CTX_GLOBAL_DEADLINE, CTX_TRA
1616

1717
// Module types
1818
export { DEFAULT_ANNOTATIONS, createPreflightResult } from './module.js';
19-
export type { ModuleAnnotations, ModuleExample, ValidationResult, PreflightCheckResult, PreflightResult, Module } from './module.js';
19+
export type { ModuleAnnotations, ModuleExample, ModuleDescription, ValidationResult, PreflightCheckResult, PreflightResult, Module } from './module.js';
2020

2121
// Config
2222
export { Config } from './config.js';
@@ -160,4 +160,4 @@ export type { UsageRecord, CallerUsageSummary, HourlyBucket, ModuleUsageSummary,
160160
export { TraceContext } from './trace-context.js';
161161
export type { TraceParent } from './trace-context.js';
162162

163-
export const VERSION = '0.11.0';
163+
export const VERSION = '0.12.0';

src/middleware/logging.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export class LoggingMiddleware extends Middleware {
4343
inputs: Record<string, unknown>,
4444
context: Context,
4545
): null {
46-
context.data['_logging_mw_start'] = performance.now();
46+
context.data['_apcore.mw.logging.start_time'] = performance.now();
4747

4848
if (this._logInputs) {
4949
const redacted = context.redactedInputs ?? inputs;
@@ -64,7 +64,7 @@ export class LoggingMiddleware extends Middleware {
6464
output: Record<string, unknown>,
6565
context: Context,
6666
): null {
67-
const startTime = (context.data['_logging_mw_start'] as number) ?? performance.now();
67+
const startTime = (context.data['_apcore.mw.logging.start_time'] as number) ?? performance.now();
6868
const durationMs = performance.now() - startTime;
6969

7070
if (this._logOutputs) {

src/middleware/retry.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import type { ModuleError } from '../errors.js';
77
import { Middleware } from './base.js';
88

99
/** Well-known context.data key prefixes for retry state. */
10-
export const CTX_RETRY_COUNT_PREFIX = '_retry_count_';
11-
export const CTX_RETRY_DELAY_PREFIX = '_retry_delay_ms_';
10+
export const CTX_RETRY_COUNT_PREFIX = '_apcore.mw.retry.count.';
11+
export const CTX_RETRY_DELAY_PREFIX = '_apcore.mw.retry.delay_ms.';
1212

1313
export interface RetryConfig {
1414
maxRetries: number;

src/module.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface PreflightCheckResult {
3939
readonly check: string;
4040
readonly passed: boolean;
4141
readonly error?: Record<string, unknown>;
42+
readonly warnings?: string[];
4243
}
4344

4445
export interface PreflightResult {
@@ -68,8 +69,20 @@ export interface Module {
6869
stream?(inputs: Record<string, unknown>, context: Context): AsyncGenerator<Record<string, unknown>>;
6970
/** Optional: Custom input validation without execution. */
7071
validate?(inputs: Record<string, unknown>): ValidationResult | Promise<ValidationResult>;
72+
/** Optional: Domain-specific pre-execution warnings (called by Executor.validate() Check 7). Advisory only — warnings do NOT block execution. */
73+
preflight?(inputs: Record<string, unknown>, context: Context): string[] | Promise<string[]>;
74+
/** Optional: Return module description for LLM/AI tool discovery. */
75+
describe?(): ModuleDescription | Promise<ModuleDescription>;
7176
/** Optional: Called when module is loaded into the registry. */
7277
onLoad?(): void | Promise<void>;
7378
/** Optional: Called when module is unloaded from the registry. */
7479
onUnload?(): void | Promise<void>;
7580
}
81+
82+
export interface ModuleDescription {
83+
readonly description: string;
84+
readonly inputSchema: Record<string, unknown>;
85+
readonly outputSchema: Record<string, unknown>;
86+
readonly annotations: ModuleAnnotations;
87+
readonly examples: ModuleExample[];
88+
}

src/observability/context-logger.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,9 @@ export class ObsLoggingMiddleware extends Middleware {
145145
inputs: Record<string, unknown>,
146146
context: Context,
147147
): null {
148-
const starts = (context.data['_obs_logging_starts'] as number[]) ?? [];
148+
const starts = (context.data['_apcore.mw.logging.obs_starts'] as number[]) ?? [];
149149
starts.push(performance.now());
150-
context.data['_obs_logging_starts'] = starts;
150+
context.data['_apcore.mw.logging.obs_starts'] = starts;
151151

152152
const extra: Record<string, unknown> = {
153153
module_id: moduleId,
@@ -166,7 +166,7 @@ export class ObsLoggingMiddleware extends Middleware {
166166
output: Record<string, unknown>,
167167
context: Context,
168168
): null {
169-
const starts = context.data['_obs_logging_starts'] as number[] | undefined;
169+
const starts = context.data['_apcore.mw.logging.obs_starts'] as number[] | undefined;
170170
if (!starts || starts.length === 0) return null;
171171
const startTime = starts.pop()!;
172172
const durationMs = performance.now() - startTime;
@@ -188,7 +188,7 @@ export class ObsLoggingMiddleware extends Middleware {
188188
error: Error,
189189
context: Context,
190190
): null {
191-
const starts = context.data['_obs_logging_starts'] as number[] | undefined;
191+
const starts = context.data['_apcore.mw.logging.obs_starts'] as number[] | undefined;
192192
if (!starts || starts.length === 0) return null;
193193
const startTime = starts.pop()!;
194194
const durationMs = performance.now() - startTime;

0 commit comments

Comments
 (0)