Releases: aiperceivable/apcore-typescript
Release 0.19.0
Added
DependencyNotFoundError(error codeDEPENDENCY_NOT_FOUND) — thrown byresolveDependencieswhen a module's required dependency is not registered. Aligns TypeScript with PROTOCOL_SPEC §5.15.2 which has always mandated this error code. Details includemoduleIdanddependencyId. Exported fromapcore.DependencyVersionMismatchError(error codeDEPENDENCY_VERSION_MISMATCH) — thrown byresolveDependencieswhen a declaredversionconstraint is not satisfied by the registered version of the target module. Details includemoduleId,dependencyId,required,actual. Exported fromapcore.resolveDependencies(modules, knownIds, moduleVersions)— new optional third argument acceptingMap<string, string>orRecord<string, string>mapping module id → version. When provided, declared dependency version constraints are enforced per PROTOCOL_SPEC §5.3. When absent, theDependencyInfo.versionfield is silently ignored.ModuleRegistry._resolveLoadOrdernow populates this map from YAML version / classversion/"1.0.0"fallback, and includes already-registered modules so inter-batch constraints resolve against the live registry.- Caret (
^) and tilde (~) constraint support inmatchesVersionHint/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.matchesVersionHintis now exported. - Auto-schema multi-adapter chain (
src/schema/extractor.ts) —SchemaExtractorRegistrywith pluggable adapters. Built-in: TypeBox (priority 100, detectsSymbol.for('TypeBox.Kind')), JsonSchema (priority 30, detectstype/properties). Custom adapters (zod, class-validator, typia) registered viaSchemaExtractorRegistry.register(). See DECLARATIVE_CONFIG_SPEC.md §6.3. auto_schema: true | permissive | strictin binding YAML — triggers module export scanning (inputSchema/outputSchemanamed exports, or<symbolName>InputSchema/<symbolName>OutputSchemacompanion naming). Implicit default when no schema mode specified.BindingSchemaInferenceFailedErrorandBindingSchemaModeConflictError— canonical errors per DECLARATIVE_CONFIG_SPEC.md §7.1.BindingSchemaMissingErroris now a deprecated alias.spec_versionfield support in binding YAML with deprecation warning when absent.documentation,annotations,metadatafields pass throughBindingLoader→FunctionModule. Annotations converted from YAML snake_case to TypeScript camelCase viaparseAnnotations().- Pipeline
handler:dynamic import —_resolveStepandbuildStrategyFromConfigare nowasync. Handler modules loaded viaawait import()with security checks (rejects..segments,file:URLs). See DECLARATIVE_CONFIG_SPEC.md §4.4. - Cross-SDK conformance fixtures in
apcore/conformance/fixtures/.
Fixed
resolveDependenciescycle path accuracy —extractCyclepreviously returned a phantom path (all remaining nodes plus the first one re-appended) when the arbitrarily-picked start node had no outgoing edge insideremaining. This could happen when a module is blocked on an externalknownIdsdependency 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 tosortedRemainingonly when no back-edge exists.
Changed
- Missing required dependencies now throw
DependencyNotFoundError(codeDEPENDENCY_NOT_FOUND) instead ofModuleLoadError(codeMODULE_LOAD_ERROR). Brings TypeScript into compliance with PROTOCOL_SPEC §5.15.2. Upgrade path: catchDependencyNotFoundErrorspecifically, or catch theModuleErrorbase 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 emitsconsole.warn(previously silent). No auto-normalization (dashed-UUID stripping or case folding) is performed atContext.create; such normalization is the caller's ContextFactory responsibility. Valid 32-hex inputs remain accepted verbatim. Covered by new conformance fixturecontext_trace_parent.json.
Changed (BREAKING)
buildStrategyFromConfig()is nowasync— returnsPromise<ExecutionStrategy>. Callers mustawaitit. Necessary becausehandler:resolution usesawait import()._resolveStep()is nowasync— returnsPromise<Step>.BindingSchemaMissingErrorrenamed toBindingSchemaInferenceFailedError. Constructor signature changed:(target, moduleId?, filePath?, remediation?, options?). Old name kept as alias.
Release 0.18.0
Added
- Registry length boundary tests —
tests/registry/test-registry.test.tsnow coversMAX_MODULE_ID_LENGTHconstant equality, exact-length registration acceptance, and over-length rejection (parity withapcore-python'sTestRegisterConstants). - 8 new parity tests in
tests/registry/test-registry.test.tscovering: invalid pattern rejection (uppercase, hyphens, leading digit, etc.), reserved word in any segment rejection,registerInternalaccepting 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 (commit2c204fb) and apcore-rust (plural-only since initial implementation). module()factory now throwsInvalidInputErrorwhenidis not provided, per PROTOCOL_SPEC §5.11.6. JavaScript cannot derive{module_path}.{name}at runtime (unlike Python's__module__), so explicitidis required. Previously defaulted to'anonymous'. Aligned with apcore-rust which also requires explicit name.MAX_MODULE_ID_LENGTHraised 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()andRegistry.registerInternal()now share a privatevalidateModuleId()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}`). Bothregister()andregisterInternal()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 viaRegExp.toString()).
Changed (cross-language sync)
Executor.listStrategies()now returnsStrategyInfo[]instead ofstring[]— Provides step count, step names, and description alongside the strategy name. Aligned with apcore-pythonlist_strategies() -> list[StrategyInfo]and apcore-rustlist_strategies() -> Vec<StrategyInfo>.
Removed
FeatureNotImplementedErrorandDependencyNotFoundError— zero throw-sites across the codebase. Error codesGENERAL_NOT_IMPLEMENTEDandDEPENDENCY_NOT_FOUNDremain inErrorCodesfor use via the genericModuleErrorconstructor. Aligned with apcore-python (commit91e951a).
Fixed
-
README Quick Start — missing
awaitonclient.validate()call.validate()is async and returnsPromise<PreflightResult>; the example assigned the Promise directly instead of awaiting it. -
Dead fallback in
getDefinitiondropped (registry.ts:516-530). Amodule.description ?? metadata.descriptionchain was unreachable becausemodule.descriptionis always set by theModulebase 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:mergeModuleMetadataandregistry/registry.ts:getDefinition. The merge step was doing whole-replacement of theannotationsfield instead of the field-level merge mandated by §4.13 ("If YAML only definesreadonly: true, other fields must retain values from code or defaults."), andgetDefinitionwas reading directly from the module class object even when the merge result was available. The fix wiresmergeAnnotationsandmergeExamplesfromschema/annotations.ts(defined and unit-tested but never previously called from production) into the registry pipeline, and updatesgetDefinitionto consume the merged metadata. User-observable behavior change: modules that suppliedannotations:in their*_meta.yamlcompanion 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 toapcore-pythoncommit9c0fde9. Adds 5 regression tests covering field-level merge, YAML-only, neither-defined, examples-yaml-wins, and unknown-key-drop scenarios. -
annotationsFromJSONprecedence inversion — Per PROTOCOL_SPEC §4.4.1 rule 7, when the same key appears both in a nestedextraobject 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 intoextrafor backward compatibility.
Release 0.17.0
Added
- Step Metadata: Four optional fields on
Stepinterface:matchModules(glob patterns),ignoreErrors(fault-tolerant),pure(safe for validate dry-run),timeoutMs(per-step timeout viaPromise.race). - YAML Pipeline Configuration:
registerStepType(),unregisterStepType(),registeredStepTypes(),buildStrategyFromConfig()in newpipeline-config.tsmodule. - PipelineContext fields:
dryRun,versionHint,executedMiddlewares. - StepTrace:
skipReasonfield.
Changed
- Step order:
BuiltinMiddlewareBeforenow runs BEFOREBuiltinInputValidation. Middleware transforms are validated. - Executor delegation:
callAsync(),validate(), andstream()fully delegate toPipelineEngine.run(). Removed inline step code. - Renamed:
safety_check→call_chain_guard,BuiltinSafetyCheck→BuiltinCallChainGuard. - Removed
builtin.prefix: All step names changed frombuiltin.context_creationtocontext_creation. validate()is now async: ReturnsPromise<PreflightResult>.
Fixed
- Middleware input transforms were never validated against schema.
validate()now uses pipeline dry-run mode — user-addedpure=truesteps automatically participate.
Release 0.16.0
Added
- Config Bus:
envStyle(auto/nested/flat),maxDepth,envPrefixauto-derivation,envMap(namespace + global),Config.envMap(),ConfigEnvMapConflictError. - Context:
ContextKey<T>typed accessor withget()/set()/delete()/exists()/scoped(). Built-in key constants.globalDeadline: number | nullfield.Context.serialize()/deserialize()with_context_version: 1. - Annotations:
extra: Readonly<Record<string, unknown>>extension field.paginationStylechanged from union tostring. All optional fields now required with defaults.createAnnotations()factory.annotationsToJSON()/annotationsFromJSON()wire format. - ACL:
ACLConditionHandlerinterface (boolean | Promise<boolean>).ACL.registerCondition().$or/$notcompound operators.asyncCheck()method. Fail-closed for unknown conditions.removeRulefixed to element-wise comparison. - Pipeline:
Stepinterface,StepResult,PipelineContext,PipelineTrace,ExecutionStrategy,PipelineEngine. 11BuiltinStepclasses. Preset strategies (standard/internal/testing/performance).Executor.strategyoption.callWithTrace().registerStrategy()/listStrategies()/describePipeline().
Changed
- Toggle system module now has PROTOCOL_SPEC reference comment.
Release 0.15.1
Changed
- Env prefix convention simplified — Removed the
^APCORE_[A-Z0-9]reservation rule fromConfig.registerNamespace(). Sub-packages now use single-underscore prefixes (APCORE_MCP,APCORE_OBSERVABILITY,APCORE_SYS) instead of the double-underscore form. Only the exactAPCOREprefix is reserved for the core namespace. - Built-in namespace env prefixes:
APCORE__OBSERVABILITY→APCORE_OBSERVABILITY,APCORE__SYS→APCORE_SYS.
Release 0.15.0
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 allConfiginstances. Options:schema?— JSON Schema object for namespace-level validationenvPrefix?— 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_DUPLICATEif the name is already registered - Throws
CONFIG_NAMESPACE_RESERVEDfor 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 subtreeconfig.namespace(name)— Returns the full subtree for a registered namespace as a plain objectconfig.bind<T>(namespace, type)— Returns a typed view of a namespace subtree; throwsCONFIG_BIND_ERRORon schema mismatchconfig.getTyped<T>(path, type)— Typed single-value accessor with runtime type guardconfig.mount(namespace, options)— Attach an external configuration source to a namespace without requiring a unified YAML file.optionsacceptsfromFile(path string) orfromDict(plain object). ThrowsCONFIG_MOUNT_ERRORon failureConfig.registeredNamespaces()— Returns a string array of all currently registered namespace namesconfig.reload()— Extended: re-reads YAML (when loaded viaConfig.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 existingapcore.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) — Promotesapcore.sys_modules.*flat keys into a dedicated namespace.registerSysModules()prefersconfig.namespace("sysModules")in namespace mode and falls back toconfig.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'). ThrowsERROR_FORMATTER_DUPLICATEif already registered.ErrorFormatterRegistry.get(surface)— Retrieve a registered formatter by surface name.ErrorFormatterRegistry.format(surface, error)— Format aModuleErrorusing the registered formatter; falls back toerror.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
Fixed
- Executor schema validation —
Executor.call()now accepts raw JSON Schema (e.g. fromzodToJsonSchema) asinputSchema/outputSchema, not just TypeBoxTSchema. Previously, passing raw JSON Schema caused TypeBoxValue.Check()to throw "Unknown type". The fix auto-converts viajsonSchemaToTypeBox()on first use and caches the result on the module object to avoid repeated conversion.
Release 0.14.0
Breaking Changes
- Middleware default priority changed from
0to100per PROTOCOL_SPEC §11.2. Middleware without explicit priority will now execute before priority-0 middleware.
Added
- Middleware priority —
Middlewarebase class now acceptspriority: number(default 0). Higher priority executes first; equal priority preserves registration order.BeforeMiddlewareandAfterMiddlewareadapters also acceptpriority. - Priority range validation —
RangeErrorthrown for values outside 0-1000
Release 0.13.1
Changed
- Rebrand: aipartnerup → aiperceivable
Release 0.13.0
Added
- Caching/pagination annotations —
ModuleAnnotationsgains 5 optional fields:cacheable,cacheTtl,cacheKeyFields,paginated,paginationStyle(backward compatible) paginationStyleunion — Typed as'cursor' | 'offset' | 'page'matching Python SDK and specsunsetDate— New field onModuleDescriptorandLLMExtensionsfor module deprecation lifecycleonSuspend()/onResume()lifecycle hooks — Optional methods onModuleinterface for state preservation during hot-reload; integrated into control module reload flow- MCP
_metaexport — Schema exporter includescacheable,cacheTtl,cacheKeyFields,paginated,paginationStylein_metasub-dict - Suspend/resume tests — 5 test cases in
test-control.test.tscovering 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)