Skip to content

Releases: aiperceivable/apcore-typescript

Release 0.19.0

19 Apr 09:11

Choose a tag to compare

Added

  • DependencyNotFoundError (error code DEPENDENCY_NOT_FOUND) — thrown by resolveDependencies when a module's required dependency is not registered. Aligns TypeScript with PROTOCOL_SPEC §5.15.2 which has always mandated this error code. Details include moduleId and dependencyId. Exported from apcore.
  • DependencyVersionMismatchError (error code DEPENDENCY_VERSION_MISMATCH) — thrown by resolveDependencies when a declared version constraint is not satisfied by the registered version of the target module. Details include moduleId, dependencyId, required, actual. Exported from apcore.
  • resolveDependencies(modules, knownIds, moduleVersions) — new optional third argument accepting Map<string, string> or Record<string, string> mapping module id → version. When provided, declared dependency version constraints are enforced per PROTOCOL_SPEC §5.3. When absent, the DependencyInfo.version field is silently ignored. ModuleRegistry._resolveLoadOrder now populates this map from YAML version / class version / "1.0.0" fallback, and includes already-registered modules so inter-batch constraints resolve against the live registry.
  • Caret (^) and tilde (~) constraint support in matchesVersionHint / selectBestVersion (npm/Cargo semantics): ^1.2.3 → >=1.2.3,<2.0.0, ^0.2.3 → >=0.2.3,<0.3.0, ^0.0.3 → >=0.0.3,<0.0.4, ~1.2.3 → >=1.2.3,<1.3.0, ~1.2 → >=1.2.0,<1.3.0, ~1 → >=1.0.0,<2.0.0. matchesVersionHint is now exported.
  • Auto-schema multi-adapter chain (src/schema/extractor.ts) — SchemaExtractorRegistry with pluggable adapters. Built-in: TypeBox (priority 100, detects Symbol.for('TypeBox.Kind')), JsonSchema (priority 30, detects type/properties). Custom adapters (zod, class-validator, typia) registered via SchemaExtractorRegistry.register(). See DECLARATIVE_CONFIG_SPEC.md §6.3.
  • auto_schema: true | permissive | strict in binding YAML — triggers module export scanning (inputSchema/outputSchema named exports, or <symbolName>InputSchema/<symbolName>OutputSchema companion naming). Implicit default when no schema mode specified.
  • BindingSchemaInferenceFailedError and BindingSchemaModeConflictError — canonical errors per DECLARATIVE_CONFIG_SPEC.md §7.1. BindingSchemaMissingError is now a deprecated alias.
  • spec_version field support in binding YAML with deprecation warning when absent.
  • documentation, annotations, metadata fields pass through BindingLoaderFunctionModule. Annotations converted from YAML snake_case to TypeScript camelCase via parseAnnotations().
  • Pipeline handler: dynamic import_resolveStep and buildStrategyFromConfig are now async. Handler modules loaded via await import() with security checks (rejects .. segments, file: URLs). See DECLARATIVE_CONFIG_SPEC.md §4.4.
  • Cross-SDK conformance fixtures in apcore/conformance/fixtures/.

Fixed

  • resolveDependencies cycle path accuracyextractCycle previously returned a phantom path (all remaining nodes plus the first one re-appended) when the arbitrarily-picked start node had no outgoing edge inside remaining. This could happen when a module is blocked on an external knownIds dependency while another subset contains a real cycle. Rewritten to DFS from each remaining node (sorted) and return a true back-edge cycle [n0, ..., nk, n0]; falls back to sortedRemaining only when no back-edge exists.

Changed

  • Missing required dependencies now throw DependencyNotFoundError (code DEPENDENCY_NOT_FOUND) instead of ModuleLoadError (code MODULE_LOAD_ERROR). Brings TypeScript into compliance with PROTOCOL_SPEC §5.15.2. Upgrade path: catch DependencyNotFoundError specifically, or catch the ModuleError base class. Code-based dispatch (err.code === 'DEPENDENCY_NOT_FOUND') also works and is recommended for cross-language consumers.
  • Context.create({ traceParent }) — strict input validation per PROTOCOL_SPEC §10.5. trace_ids that are all-zero or all-f (W3C-invalid) now trigger regeneration, and any regeneration now emits console.warn (previously silent). No auto-normalization (dashed-UUID stripping or case folding) is performed at Context.create; such normalization is the caller's ContextFactory responsibility. Valid 32-hex inputs remain accepted verbatim. Covered by new conformance fixture context_trace_parent.json.

Changed (BREAKING)

  • buildStrategyFromConfig() is now async — returns Promise<ExecutionStrategy>. Callers must await it. Necessary because handler: resolution uses await import().
  • _resolveStep() is now async — returns Promise<Step>.
  • BindingSchemaMissingError renamed to BindingSchemaInferenceFailedError. Constructor signature changed: (target, moduleId?, filePath?, remediation?, options?). Old name kept as alias.

Release 0.18.0

15 Apr 02:19

Choose a tag to compare

Added

  • Registry length boundary teststests/registry/test-registry.test.ts now covers MAX_MODULE_ID_LENGTH constant equality, exact-length registration acceptance, and over-length rejection (parity with apcore-python's TestRegisterConstants).
  • 8 new parity tests in tests/registry/test-registry.test.ts covering: invalid pattern rejection (uppercase, hyphens, leading digit, etc.), reserved word in any segment rejection, registerInternal accepting reserved first segment, accepting reserved word in any segment, still rejecting empty, still rejecting invalid pattern, still rejecting over-length, and rejecting duplicate.

Changed

  • ACL singular condition handler aliases removed (identity_type, role, call_depth). Spec §6.1 only defines the plural forms (identity_types, roles, max_call_depth); the singular aliases were a cross-language divergence. Aligned with apcore-python (commit 2c204fb) and apcore-rust (plural-only since initial implementation).
  • module() factory now throws InvalidInputError when id is not provided, per PROTOCOL_SPEC §5.11.6. JavaScript cannot derive {module_path}.{name} at runtime (unlike Python's __module__), so explicit id is required. Previously defaulted to 'anonymous'. Aligned with apcore-rust which also requires explicit name.
  • MAX_MODULE_ID_LENGTH raised from 128 to 192 (registry/registry.ts). Tracks PROTOCOL_SPEC §2.7 EBNF constraint #1 — accommodates Java/.NET deep-namespace FQN-derived IDs while remaining filesystem-safe (192 + ".binding.yaml".length = 205 < 255-byte filename limit on ext4/xfs/NTFS/APFS/btrfs). Module IDs valid before this change remain valid; only the upper bound moved. Forward-compatible relaxation: older 0.17.x/0.18.x readers will reject IDs in the 129–192 range emitted by this version.
  • Registry.register() and Registry.registerInternal() now share a private validateModuleId() helper that runs validation in canonical order (empty → EBNF pattern → length → reserved word per-segment). Deduplicated 2 enforcement sites in the same file. Aligned cross-language with apcore-python and apcore-rust.
  • Duplicate registration error message canonicalized to `Module ID '${moduleId}' is already registered` (was `Module already exists: ${moduleId}`). Both register() and registerInternal() now emit the same message. Aligned with apcore-python and apcore-rust byte-for-byte.
  • Helper error message style aligned with apcore-python / apcore-rust:
  • Empty error: 'module_id must be a non-empty string' (was 'Module ID must be a non-empty string' — now lowercase to match Python/Rust).
  • Pattern error: single quotes around the offending ID (was double quotes).
  • Pattern error format string: uses ${MODULE_ID_PATTERN.source} (bare regex source) instead of ${MODULE_ID_PATTERN} (which produced /.../ slashes via RegExp.toString()).

Changed (cross-language sync)

  • Executor.listStrategies() now returns StrategyInfo[] instead of string[] — Provides step count, step names, and description alongside the strategy name. Aligned with apcore-python list_strategies() -> list[StrategyInfo] and apcore-rust list_strategies() -> Vec<StrategyInfo>.

Removed

  • FeatureNotImplementedError and DependencyNotFoundError — zero throw-sites across the codebase. Error codes GENERAL_NOT_IMPLEMENTED and DEPENDENCY_NOT_FOUND remain in ErrorCodes for use via the generic ModuleError constructor. Aligned with apcore-python (commit 91e951a).

Fixed

  • README Quick Start — missing await on client.validate() call. validate() is async and returns Promise<PreflightResult>; the example assigned the Promise directly instead of awaiting it.

  • Dead fallback in getDefinition dropped (registry.ts:516-530). A module.description ?? metadata.description chain was unreachable because module.description is always set by the Module base class constructor. Removed the dead branch.

  • Spec §4.13 annotation merge — YAML annotations are no longer silently dropped at registration. Two coupled bugs were repaired in registry/metadata.ts:mergeModuleMetadata and registry/registry.ts:getDefinition. The merge step was doing whole-replacement of the annotations field instead of the field-level merge mandated by §4.13 ("If YAML only defines readonly: true, other fields must retain values from code or defaults."), and getDefinition was reading directly from the module class object even when the merge result was available. The fix wires mergeAnnotations and mergeExamples from schema/annotations.ts (defined and unit-tested but never previously called from production) into the registry pipeline, and updates getDefinition to consume the merged metadata. User-observable behavior change: modules that supplied annotations: in their *_meta.yaml companion files were previously seeing those annotations silently ignored; they will now be honored. Modules that relied on the broken behavior should audit their meta files. Identical fix to apcore-python commit 9c0fde9. Adds 5 regression tests covering field-level merge, YAML-only, neither-defined, examples-yaml-wins, and unknown-key-drop scenarios.

  • annotationsFromJSON precedence inversion — Per PROTOCOL_SPEC §4.4.1 rule 7, when the same key appears both in a nested extra object and as a top-level overflow key, the nested value now wins (previously the spread order {...explicitExtra, ...overflow} made overflow win). Behavior change is observable only in the pathological case where an input contains both forms of the same key — no conformant producer emits this. Top-level overflow keys are still tolerated and merged into extra for backward compatibility.

Release 0.17.0

05 Apr 11:50

Choose a tag to compare

Added

  • Step Metadata: Four optional fields on Step interface: matchModules (glob patterns), ignoreErrors (fault-tolerant), pure (safe for validate dry-run), timeoutMs (per-step timeout via Promise.race).
  • YAML Pipeline Configuration: registerStepType(), unregisterStepType(), registeredStepTypes(), buildStrategyFromConfig() in new pipeline-config.ts module.
  • PipelineContext fields: dryRun, versionHint, executedMiddlewares.
  • StepTrace: skipReason field.

Changed

  • Step order: BuiltinMiddlewareBefore now runs BEFORE BuiltinInputValidation. Middleware transforms are validated.
  • Executor delegation: callAsync(), validate(), and stream() fully delegate to PipelineEngine.run(). Removed inline step code.
  • Renamed: safety_checkcall_chain_guard, BuiltinSafetyCheckBuiltinCallChainGuard.
  • Removed builtin. prefix: All step names changed from builtin.context_creation to context_creation.
  • validate() is now async: Returns Promise<PreflightResult>.

Fixed

  • Middleware input transforms were never validated against schema.
  • validate() now uses pipeline dry-run mode — user-added pure=true steps automatically participate.

Release 0.16.0

05 Apr 03:34

Choose a tag to compare

Added

  • Config Bus: envStyle (auto/nested/flat), maxDepth, envPrefix auto-derivation, envMap (namespace + global), Config.envMap(), ConfigEnvMapConflictError.
  • Context: ContextKey<T> typed accessor with get()/set()/delete()/exists()/scoped(). Built-in key constants. globalDeadline: number | null field. Context.serialize()/deserialize() with _context_version: 1.
  • Annotations: extra: Readonly<Record<string, unknown>> extension field. paginationStyle changed from union to string. All optional fields now required with defaults. createAnnotations() factory. annotationsToJSON()/annotationsFromJSON() wire format.
  • ACL: ACLConditionHandler interface (boolean | Promise<boolean>). ACL.registerCondition(). $or/$not compound operators. asyncCheck() method. Fail-closed for unknown conditions. removeRule fixed to element-wise comparison.
  • Pipeline: Step interface, StepResult, PipelineContext, PipelineTrace, ExecutionStrategy, PipelineEngine. 11 BuiltinStep classes. Preset strategies (standard/internal/testing/performance). Executor.strategy option. callWithTrace(). registerStrategy()/listStrategies()/describePipeline().

Changed

  • Toggle system module now has PROTOCOL_SPEC reference comment.

Release 0.15.1

31 Mar 10:17

Choose a tag to compare

Changed

  • Env prefix convention simplified — Removed the ^APCORE_[A-Z0-9] reservation rule from Config.registerNamespace(). Sub-packages now use single-underscore prefixes (APCORE_MCP, APCORE_OBSERVABILITY, APCORE_SYS) instead of the double-underscore form. Only the exact APCORE prefix is reserved for the core namespace.
  • Built-in namespace env prefixes: APCORE__OBSERVABILITYAPCORE_OBSERVABILITY, APCORE__SYSAPCORE_SYS.

Release 0.15.0

31 Mar 06:44

Choose a tag to compare

Added

Config Bus Architecture (§9.4–§9.14)

Config is upgraded from an internal configuration tool to an ecosystem-level Config Bus. Any package — apcore ecosystem or third-party — can register a named namespace with optional JSON Schema validation, environment variable prefix, and default values.

  • Config.registerNamespace(name, options?) — Register a namespace on the global (class-level) registry shared across all Config instances. Options:
  • schema? — JSON Schema object for namespace-level validation
  • envPrefix? — Environment variable prefix for this namespace (e.g. 'APCORE__MCP')
  • defaults? — Default values merged before file and env overrides
  • Late registration is permitted; call config.reload() afterward to apply defaults and env overrides
  • Throws CONFIG_NAMESPACE_DUPLICATE if the name is already registered
  • Throws CONFIG_NAMESPACE_RESERVED for reserved names (e.g. _config)
  • config.get("namespace.key.path") — Dot-path access with namespace resolution. The first segment resolves to a registered namespace; remaining segments traverse its subtree
  • config.namespace(name) — Returns the full subtree for a registered namespace as a plain object
  • config.bind<T>(namespace, type) — Returns a typed view of a namespace subtree; throws CONFIG_BIND_ERROR on schema mismatch
  • config.getTyped<T>(path, type) — Typed single-value accessor with runtime type guard
  • config.mount(namespace, options) — Attach an external configuration source to a namespace without requiring a unified YAML file. options accepts fromFile (path string) or fromDict (plain object). Throws CONFIG_MOUNT_ERROR on failure
  • Config.registeredNamespaces() — Returns a string array of all currently registered namespace names
  • config.reload() — Extended: re-reads YAML (when loaded via Config.load()), re-detects legacy/namespace mode, re-applies namespace defaults and env overrides, re-validates, and re-reads mounted files
Unified YAML with namespace sections

Config files now support a namespace mode when an apcore: top-level key is present. Each registered namespace occupies its own top-level section. The _config reserved meta-namespace controls validation behavior (strict, allowUnknown). Legacy files (no apcore: key) remain fully backward compatible.

Per-namespace environment variable overrides

Each namespace declares its own envPrefix. The loader uses a longest-prefix-match dispatch algorithm to route env vars to the correct namespace. The APCORE__ double-underscore convention distinguishes apcore sub-package prefixes (e.g. APCORE__MCP, APCORE__OBSERVABILITY) from the existing APCORE_ flat-key prefix.

New error codes
Code When thrown
CONFIG_NAMESPACE_DUPLICATE registerNamespace() called with an already-registered name
CONFIG_NAMESPACE_RESERVED registerNamespace() called with a reserved name (e.g. _config)
CONFIG_ENV_PREFIX_CONFLICT Two namespaces declare the same envPrefix
CONFIG_MOUNT_ERROR mount() cannot read or parse the external source
CONFIG_BIND_ERROR bind<T>() or getTyped<T>() type guard fails

Built-in Namespace Registrations (§9.15)

apcore pre-registers two namespaces for its own subsystems:

  • observability (APCORE__OBSERVABILITY) — Wraps the existing apcore.observability.* flat keys (tracing, metrics, logging, errorHistory, platformNotify) into a dedicated namespace. Adapter packages (apcore-mcp, apcore-a2a, apcore-cli) should read from this namespace instead of maintaining independent logging defaults.
  • sysModules (APCORE__SYS) — Promotes apcore.sys_modules.* flat keys into a dedicated namespace. registerSysModules() prefers config.namespace("sysModules") in namespace mode and falls back to config.get("sys_modules.*") in legacy mode.

Error Formatter Registry (§8.8)

New ErrorFormatter interface and ErrorFormatterRegistry singleton for adapter-specific error serialization:

  • ErrorFormatterRegistry.register(surface, formatter) — Register a named formatter (e.g. 'mcp', 'a2a'). Throws ERROR_FORMATTER_DUPLICATE if already registered.
  • ErrorFormatterRegistry.get(surface) — Retrieve a registered formatter by surface name.
  • ErrorFormatterRegistry.format(surface, error) — Format a ModuleError using the registered formatter; falls back to error.toDict() when no formatter is registered for the surface.

New error code: ERROR_FORMATTER_DUPLICATE.

Event Type Naming Convention and Collision Fix (§9.16)

Two confirmed event-type collisions in the emitted event stream are resolved. Canonical dot-namespaced names replace the ambiguous short-form names:

Legacy name (alias, still emitted) Canonical name Meaning
"module_health_changed" apcore.module.toggled Module enabled/disabled toggle
"module_health_changed" apcore.health.recovered Error-rate recovery after spike
"config_changed" apcore.config.updated Config key updated at runtime
"config_changed" apcore.module.reloaded Module reloaded from disk

Naming convention: apcore.* is reserved for core events. Adapter packages use their own prefix (apcore-mcp.*, apcore-a2a.*, apcore-cli.*). All four legacy short-form names remain emitted as aliases during the transition period.


Release 0.14.1

29 Mar 10:53

Choose a tag to compare

Fixed

  • Executor schema validationExecutor.call() now accepts raw JSON Schema (e.g. from zodToJsonSchema) as inputSchema/outputSchema, not just TypeBox TSchema. Previously, passing raw JSON Schema caused TypeBox Value.Check() to throw "Unknown type". The fix auto-converts via jsonSchemaToTypeBox() on first use and caches the result on the module object to avoid repeated conversion.

Release 0.14.0

25 Mar 02:18

Choose a tag to compare

Breaking Changes

  • Middleware default priority changed from 0 to 100 per PROTOCOL_SPEC §11.2. Middleware without explicit priority will now execute before priority-0 middleware.

Added

  • Middleware priorityMiddleware base class now accepts priority: number (default 0). Higher priority executes first; equal priority preserves registration order. BeforeMiddleware and AfterMiddleware adapters also accept priority.
  • Priority range validationRangeError thrown for values outside 0-1000

Release 0.13.1

22 Mar 12:56

Choose a tag to compare

Changed

  • Rebrand: aipartnerup → aiperceivable

Release 0.13.0

12 Mar 06:50

Choose a tag to compare

Added

  • Caching/pagination annotationsModuleAnnotations gains 5 optional fields: cacheable, cacheTtl, cacheKeyFields, paginated, paginationStyle (backward compatible)
  • paginationStyle union — Typed as 'cursor' | 'offset' | 'page' matching Python SDK and spec
  • sunsetDate — New field on ModuleDescriptor and LLMExtensions for module deprecation lifecycle
  • onSuspend() / onResume() lifecycle hooks — Optional methods on Module interface for state preservation during hot-reload; integrated into control module reload flow
  • MCP _meta export — Schema exporter includes cacheable, cacheTtl, cacheKeyFields, paginated, paginationStyle in _meta sub-dict
  • Suspend/resume tests — 5 test cases in test-control.test.ts covering happy path, null return, no hooks, error paths
  • README Links section — Footer with Documentation, Specification, GitHub, npm, Issues links

Changed

  • Rebranded — "module development framework" → "module standard" in package.json, index.ts, README, and internal JSDoc
  • README — Three-tier slogan/subtitle/definition format, annotation features in feature list
  • dictToAnnotations — Snake_case fallbacks for new fields (cache_ttl, cache_key_fields, pagination_style)
  • All sys-module annotations — Updated with new fields (9 modules across 5 files)