apcore-js 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/.gitmessage +60 -0
  3. package/.pre-commit-config.yaml +28 -0
  4. package/CHANGELOG.md +47 -0
  5. package/CLAUDE.md +68 -0
  6. package/README.md +131 -0
  7. package/apcore-logo.svg +79 -0
  8. package/package.json +37 -0
  9. package/planning/acl-system/overview.md +54 -0
  10. package/planning/acl-system/plan.md +92 -0
  11. package/planning/acl-system/state.json +76 -0
  12. package/planning/acl-system/tasks/acl-core.md +226 -0
  13. package/planning/acl-system/tasks/acl-rule.md +92 -0
  14. package/planning/acl-system/tasks/conditional-rules.md +259 -0
  15. package/planning/acl-system/tasks/pattern-matching.md +152 -0
  16. package/planning/acl-system/tasks/yaml-loading.md +271 -0
  17. package/planning/core-executor/overview.md +53 -0
  18. package/planning/core-executor/plan.md +88 -0
  19. package/planning/core-executor/state.json +76 -0
  20. package/planning/core-executor/tasks/async-support.md +106 -0
  21. package/planning/core-executor/tasks/execution-pipeline.md +113 -0
  22. package/planning/core-executor/tasks/redaction.md +85 -0
  23. package/planning/core-executor/tasks/safety-checks.md +65 -0
  24. package/planning/core-executor/tasks/setup.md +75 -0
  25. package/planning/decorator-bindings/overview.md +62 -0
  26. package/planning/decorator-bindings/plan.md +104 -0
  27. package/planning/decorator-bindings/state.json +87 -0
  28. package/planning/decorator-bindings/tasks/binding-directory.md +79 -0
  29. package/planning/decorator-bindings/tasks/binding-loader.md +148 -0
  30. package/planning/decorator-bindings/tasks/explicit-schemas.md +85 -0
  31. package/planning/decorator-bindings/tasks/function-module.md +127 -0
  32. package/planning/decorator-bindings/tasks/module-factory.md +89 -0
  33. package/planning/decorator-bindings/tasks/schema-modes.md +142 -0
  34. package/planning/middleware-system/overview.md +48 -0
  35. package/planning/middleware-system/plan.md +102 -0
  36. package/planning/middleware-system/state.json +65 -0
  37. package/planning/middleware-system/tasks/adapters.md +170 -0
  38. package/planning/middleware-system/tasks/base.md +115 -0
  39. package/planning/middleware-system/tasks/logging-middleware.md +304 -0
  40. package/planning/middleware-system/tasks/manager.md +313 -0
  41. package/planning/observability/overview.md +53 -0
  42. package/planning/observability/plan.md +119 -0
  43. package/planning/observability/state.json +98 -0
  44. package/planning/observability/tasks/context-logger.md +201 -0
  45. package/planning/observability/tasks/exporters.md +121 -0
  46. package/planning/observability/tasks/metrics-collector.md +162 -0
  47. package/planning/observability/tasks/metrics-middleware.md +141 -0
  48. package/planning/observability/tasks/obs-logging-middleware.md +179 -0
  49. package/planning/observability/tasks/span-model.md +120 -0
  50. package/planning/observability/tasks/tracing-middleware.md +179 -0
  51. package/planning/overview.md +81 -0
  52. package/planning/registry-system/overview.md +57 -0
  53. package/planning/registry-system/plan.md +114 -0
  54. package/planning/registry-system/state.json +109 -0
  55. package/planning/registry-system/tasks/dependencies.md +157 -0
  56. package/planning/registry-system/tasks/entry-point.md +148 -0
  57. package/planning/registry-system/tasks/metadata.md +198 -0
  58. package/planning/registry-system/tasks/registry-core.md +323 -0
  59. package/planning/registry-system/tasks/scanner.md +172 -0
  60. package/planning/registry-system/tasks/schema-export.md +261 -0
  61. package/planning/registry-system/tasks/types.md +124 -0
  62. package/planning/registry-system/tasks/validation.md +177 -0
  63. package/planning/schema-system/overview.md +56 -0
  64. package/planning/schema-system/plan.md +121 -0
  65. package/planning/schema-system/state.json +98 -0
  66. package/planning/schema-system/tasks/exporter.md +153 -0
  67. package/planning/schema-system/tasks/loader.md +106 -0
  68. package/planning/schema-system/tasks/ref-resolver.md +133 -0
  69. package/planning/schema-system/tasks/strict-mode.md +140 -0
  70. package/planning/schema-system/tasks/typebox-generation.md +133 -0
  71. package/planning/schema-system/tasks/types-and-annotations.md +160 -0
  72. package/planning/schema-system/tasks/validator.md +149 -0
  73. package/src/acl.ts +188 -0
  74. package/src/bindings.ts +208 -0
  75. package/src/config.ts +24 -0
  76. package/src/context.ts +75 -0
  77. package/src/decorator.ts +110 -0
  78. package/src/errors.ts +369 -0
  79. package/src/executor.ts +348 -0
  80. package/src/index.ts +81 -0
  81. package/src/middleware/adapters.ts +54 -0
  82. package/src/middleware/base.ts +33 -0
  83. package/src/middleware/index.ts +6 -0
  84. package/src/middleware/logging.ts +103 -0
  85. package/src/middleware/manager.ts +105 -0
  86. package/src/module.ts +41 -0
  87. package/src/observability/context-logger.ts +201 -0
  88. package/src/observability/index.ts +4 -0
  89. package/src/observability/metrics.ts +212 -0
  90. package/src/observability/tracing.ts +187 -0
  91. package/src/registry/dependencies.ts +99 -0
  92. package/src/registry/entry-point.ts +64 -0
  93. package/src/registry/index.ts +8 -0
  94. package/src/registry/metadata.ts +111 -0
  95. package/src/registry/registry.ts +314 -0
  96. package/src/registry/scanner.ts +150 -0
  97. package/src/registry/schema-export.ts +177 -0
  98. package/src/registry/types.ts +32 -0
  99. package/src/registry/validation.ts +38 -0
  100. package/src/schema/annotations.ts +67 -0
  101. package/src/schema/exporter.ts +93 -0
  102. package/src/schema/index.ts +14 -0
  103. package/src/schema/loader.ts +270 -0
  104. package/src/schema/ref-resolver.ts +235 -0
  105. package/src/schema/strict.ts +128 -0
  106. package/src/schema/types.ts +73 -0
  107. package/src/schema/validator.ts +82 -0
  108. package/src/utils/index.ts +1 -0
  109. package/src/utils/pattern.ts +30 -0
  110. package/tests/helpers.ts +30 -0
  111. package/tests/integration/test-acl-safety.test.ts +268 -0
  112. package/tests/integration/test-binding-executor.test.ts +194 -0
  113. package/tests/integration/test-e2e-flow.test.ts +117 -0
  114. package/tests/integration/test-error-propagation.test.ts +259 -0
  115. package/tests/integration/test-middleware-chain.test.ts +120 -0
  116. package/tests/integration/test-observability-integration.test.ts +438 -0
  117. package/tests/observability/test-context-logger.test.ts +123 -0
  118. package/tests/observability/test-metrics.test.ts +89 -0
  119. package/tests/observability/test-tracing.test.ts +131 -0
  120. package/tests/registry/test-dependencies.test.ts +70 -0
  121. package/tests/registry/test-entry-point.test.ts +133 -0
  122. package/tests/registry/test-metadata.test.ts +265 -0
  123. package/tests/registry/test-registry.test.ts +140 -0
  124. package/tests/registry/test-scanner.test.ts +257 -0
  125. package/tests/registry/test-schema-export.test.ts +224 -0
  126. package/tests/registry/test-validation.test.ts +75 -0
  127. package/tests/schema/test-loader.test.ts +97 -0
  128. package/tests/schema/test-ref-resolver.test.ts +105 -0
  129. package/tests/schema/test-strict.test.ts +139 -0
  130. package/tests/schema/test-validator.test.ts +64 -0
  131. package/tests/test-acl.test.ts +206 -0
  132. package/tests/test-bindings.test.ts +227 -0
  133. package/tests/test-config.test.ts +76 -0
  134. package/tests/test-context.test.ts +151 -0
  135. package/tests/test-decorator.test.ts +173 -0
  136. package/tests/test-errors.test.ts +204 -0
  137. package/tests/test-executor.test.ts +252 -0
  138. package/tests/test-middleware-manager.test.ts +185 -0
  139. package/tests/test-middleware.test.ts +86 -0
  140. package/tsconfig.build.json +8 -0
  141. package/tsconfig.json +20 -0
  142. package/vitest.config.ts +18 -0
@@ -0,0 +1,261 @@
1
+ # Task: Schema Export
2
+
3
+ ## Goal
4
+
5
+ Implement schema query and export functions that extract module schema information from the registry and serialize it in JSON or YAML formats. Supports strict mode (via `toStrictSchema()` for OpenAI function calling compatibility), compact mode (truncated descriptions, stripped extensions), and LLM export profiles (MCP, OpenAI, Anthropic, Generic) through the `SchemaExporter` class. Functions operate on individual modules or across all registered modules.
6
+
7
+ ## Files Involved
8
+
9
+ - `src/registry/schema-export.ts` -- Schema export function implementations
10
+ - `src/registry/registry.ts` -- `Registry` class (consumed as dependency)
11
+ - `src/schema/exporter.ts` -- `SchemaExporter` class
12
+ - `src/schema/strict.ts` -- `toStrictSchema()`, `stripExtensions()`
13
+ - `src/schema/types.ts` -- `ExportProfile`, `SchemaDefinition`
14
+ - `src/module.ts` -- `ModuleAnnotations`, `ModuleExample`
15
+ - `src/errors.ts` -- `ModuleNotFoundError`
16
+ - `tests/registry/test-schema-export.test.ts` -- Schema export tests
17
+
18
+ ## Steps (TDD)
19
+
20
+ ### Step 1: Implement getSchema() for single module
21
+
22
+ ```typescript
23
+ import { describe, it, expect } from 'vitest';
24
+ import { Registry } from '../../src/registry/registry.js';
25
+ import { getSchema } from '../../src/registry/schema-export.js';
26
+
27
+ describe('getSchema', () => {
28
+ it('should extract schema record from registered module', () => {
29
+ const reg = new Registry();
30
+ const mod = {
31
+ name: 'Adder',
32
+ description: 'Adds two numbers',
33
+ version: '1.0.0',
34
+ tags: ['math'],
35
+ inputSchema: { type: 'object', properties: { a: { type: 'number' } } },
36
+ outputSchema: { type: 'object', properties: { result: { type: 'number' } } },
37
+ annotations: null,
38
+ examples: [],
39
+ };
40
+ reg.register('math.add', mod);
41
+ const schema = getSchema(reg, 'math.add');
42
+
43
+ expect(schema).not.toBeNull();
44
+ expect(schema!['module_id']).toBe('math.add');
45
+ expect(schema!['name']).toBe('Adder');
46
+ expect(schema!['description']).toBe('Adds two numbers');
47
+ expect(schema!['version']).toBe('1.0.0');
48
+ expect(schema!['tags']).toEqual(['math']);
49
+ expect(schema!['input_schema']).toEqual(mod.inputSchema);
50
+ expect(schema!['output_schema']).toEqual(mod.outputSchema);
51
+ });
52
+
53
+ it('should return null for non-existent module', () => {
54
+ const reg = new Registry();
55
+ expect(getSchema(reg, 'nonexistent')).toBeNull();
56
+ });
57
+ });
58
+ ```
59
+
60
+ ### Step 2: Implement exportSchema() with JSON serialization
61
+
62
+ ```typescript
63
+ import { exportSchema } from '../../src/registry/schema-export.js';
64
+
65
+ describe('exportSchema', () => {
66
+ it('should export schema as formatted JSON string', () => {
67
+ const reg = new Registry();
68
+ reg.register('test.mod', {
69
+ description: 'Test',
70
+ inputSchema: { type: 'object' },
71
+ outputSchema: { type: 'object' },
72
+ execute: async () => ({}),
73
+ });
74
+ const json = exportSchema(reg, 'test.mod', 'json');
75
+ const parsed = JSON.parse(json);
76
+ expect(parsed['module_id']).toBe('test.mod');
77
+ expect(parsed['description']).toBe('Test');
78
+ });
79
+
80
+ it('should throw ModuleNotFoundError for missing module', () => {
81
+ const reg = new Registry();
82
+ expect(() => exportSchema(reg, 'missing')).toThrow();
83
+ });
84
+ });
85
+ ```
86
+
87
+ ### Step 3: YAML format serialization
88
+
89
+ ```typescript
90
+ it('should export schema as YAML string', () => {
91
+ const reg = new Registry();
92
+ reg.register('yaml.mod', {
93
+ description: 'YAML test',
94
+ inputSchema: { type: 'object' },
95
+ outputSchema: { type: 'object' },
96
+ execute: async () => ({}),
97
+ });
98
+ const yamlStr = exportSchema(reg, 'yaml.mod', 'yaml');
99
+ expect(yamlStr).toContain('module_id');
100
+ expect(yamlStr).toContain('yaml.mod');
101
+ });
102
+ ```
103
+
104
+ ### Step 4: Strict mode with toStrictSchema()
105
+
106
+ ```typescript
107
+ it('should apply strict mode transformations', () => {
108
+ const reg = new Registry();
109
+ reg.register('strict.mod', {
110
+ description: 'Strict test',
111
+ inputSchema: {
112
+ type: 'object',
113
+ properties: { a: { type: 'number' } },
114
+ },
115
+ outputSchema: { type: 'object' },
116
+ execute: async () => ({}),
117
+ });
118
+ const json = exportSchema(reg, 'strict.mod', 'json', true);
119
+ const parsed = JSON.parse(json);
120
+ // Strict mode adds additionalProperties: false, required arrays
121
+ expect(parsed['input_schema']).toBeDefined();
122
+ });
123
+ ```
124
+
125
+ ### Step 5: Compact mode with truncated descriptions
126
+
127
+ ```typescript
128
+ it('should apply compact mode transformations', () => {
129
+ const reg = new Registry();
130
+ reg.register('compact.mod', {
131
+ description: 'First sentence. Second sentence with more details.',
132
+ inputSchema: { type: 'object' },
133
+ outputSchema: { type: 'object' },
134
+ execute: async () => ({}),
135
+ });
136
+ const json = exportSchema(reg, 'compact.mod', 'json', false, true);
137
+ const parsed = JSON.parse(json);
138
+ expect(parsed['description']).toBe('First sentence.');
139
+ expect(parsed['documentation']).toBeUndefined();
140
+ expect(parsed['examples']).toBeUndefined();
141
+ });
142
+ ```
143
+
144
+ ### Step 6: LLM export profile support
145
+
146
+ ```typescript
147
+ it('should export with LLM profile', () => {
148
+ const reg = new Registry();
149
+ reg.register('profile.mod', {
150
+ description: 'Profile test',
151
+ inputSchema: { type: 'object', properties: {} },
152
+ outputSchema: { type: 'object' },
153
+ execute: async () => ({}),
154
+ });
155
+ const json = exportSchema(reg, 'profile.mod', 'json', false, false, 'generic');
156
+ const parsed = JSON.parse(json);
157
+ expect(parsed).toBeDefined();
158
+ });
159
+ ```
160
+
161
+ ### Step 7: Implement getAllSchemas()
162
+
163
+ ```typescript
164
+ import { getAllSchemas } from '../../src/registry/schema-export.js';
165
+
166
+ describe('getAllSchemas', () => {
167
+ it('should return schema records for all registered modules', () => {
168
+ const reg = new Registry();
169
+ reg.register('mod.a', {
170
+ description: 'A',
171
+ inputSchema: { type: 'object' },
172
+ outputSchema: { type: 'object' },
173
+ execute: async () => ({}),
174
+ });
175
+ reg.register('mod.b', {
176
+ description: 'B',
177
+ inputSchema: { type: 'object' },
178
+ outputSchema: { type: 'object' },
179
+ execute: async () => ({}),
180
+ });
181
+ const schemas = getAllSchemas(reg);
182
+ expect(Object.keys(schemas)).toHaveLength(2);
183
+ expect(schemas['mod.a']['module_id']).toBe('mod.a');
184
+ expect(schemas['mod.b']['module_id']).toBe('mod.b');
185
+ });
186
+
187
+ it('should return empty object for empty registry', () => {
188
+ const reg = new Registry();
189
+ expect(getAllSchemas(reg)).toEqual({});
190
+ });
191
+ });
192
+ ```
193
+
194
+ ### Step 8: Implement exportAllSchemas()
195
+
196
+ ```typescript
197
+ import { exportAllSchemas } from '../../src/registry/schema-export.js';
198
+
199
+ describe('exportAllSchemas', () => {
200
+ it('should export all schemas as JSON string', () => {
201
+ const reg = new Registry();
202
+ reg.register('all.a', {
203
+ description: 'A',
204
+ inputSchema: { type: 'object' },
205
+ outputSchema: { type: 'object' },
206
+ execute: async () => ({}),
207
+ });
208
+ const json = exportAllSchemas(reg);
209
+ const parsed = JSON.parse(json);
210
+ expect(parsed['all.a']).toBeDefined();
211
+ });
212
+
213
+ it('should apply strict mode to all schemas', () => {
214
+ const reg = new Registry();
215
+ reg.register('strict.all', {
216
+ description: 'Strict all',
217
+ inputSchema: { type: 'object', properties: { x: { type: 'string' } } },
218
+ outputSchema: { type: 'object' },
219
+ execute: async () => ({}),
220
+ });
221
+ const json = exportAllSchemas(reg, 'json', true);
222
+ const parsed = JSON.parse(json);
223
+ expect(parsed['strict.all']).toBeDefined();
224
+ });
225
+
226
+ it('should export all schemas as YAML', () => {
227
+ const reg = new Registry();
228
+ reg.register('yaml.all', {
229
+ description: 'YAML',
230
+ inputSchema: { type: 'object' },
231
+ outputSchema: { type: 'object' },
232
+ execute: async () => ({}),
233
+ });
234
+ const yamlStr = exportAllSchemas(reg, 'yaml');
235
+ expect(yamlStr).toContain('yaml.all');
236
+ });
237
+ });
238
+ ```
239
+
240
+ ## Acceptance Criteria
241
+
242
+ - [x] `getSchema()` extracts module_id, name, description, version, tags, input_schema, output_schema, annotations, examples
243
+ - [x] `getSchema()` returns null for non-existent modules
244
+ - [x] `exportSchema()` serializes to JSON (default) and YAML formats
245
+ - [x] `exportSchema()` throws `ModuleNotFoundError` for missing modules
246
+ - [x] Strict mode applies `toStrictSchema()` to input and output schemas
247
+ - [x] Compact mode truncates description at first sentence boundary and strips extensions/documentation/examples
248
+ - [x] LLM export profiles (MCP, OpenAI, Anthropic, Generic) are delegated to `SchemaExporter`
249
+ - [x] `getAllSchemas()` returns schema records keyed by module ID for all registered modules
250
+ - [x] `exportAllSchemas()` serializes all schemas with optional strict/compact/profile modes
251
+ - [x] Strict and compact modes are mutually exclusive (strict takes precedence)
252
+ - [x] `deepCopy()` prevents mutation of source module data during transformation
253
+ - [x] All tests pass with `vitest`
254
+
255
+ ## Dependencies
256
+
257
+ - `registry-core` task (Registry class)
258
+
259
+ ## Estimated Time
260
+
261
+ 3 hours
@@ -0,0 +1,124 @@
1
+ # Task: Types
2
+
3
+ ## Goal
4
+
5
+ Define the core TypeScript interfaces for the registry system: `ModuleDescriptor` (complete module definition record), `DiscoveredModule` (scanner output representing a found file), and `DependencyInfo` (parsed dependency entry). These interfaces are consumed by every other registry component and provide the type-safe foundation for the entire module lifecycle.
6
+
7
+ ## Files Involved
8
+
9
+ - `src/registry/types.ts` -- Interface definitions
10
+ - `src/module.ts` -- Imports `ModuleAnnotations` and `ModuleExample` types
11
+ - `tests/registry/test-types.test.ts` -- Type-level and runtime tests
12
+
13
+ ## Steps (TDD)
14
+
15
+ ### Step 1: Define DiscoveredModule interface
16
+
17
+ Write a test that creates a `DiscoveredModule` object and asserts all fields are accessible:
18
+
19
+ ```typescript
20
+ import { describe, it, expect } from 'vitest';
21
+ import type { DiscoveredModule } from '../../src/registry/types.js';
22
+
23
+ describe('DiscoveredModule', () => {
24
+ it('should represent a discovered file with canonical ID', () => {
25
+ const dm: DiscoveredModule = {
26
+ filePath: '/extensions/math/add.ts',
27
+ canonicalId: 'math.add',
28
+ metaPath: '/extensions/math/add_meta.yaml',
29
+ namespace: null,
30
+ };
31
+ expect(dm.filePath).toBe('/extensions/math/add.ts');
32
+ expect(dm.canonicalId).toBe('math.add');
33
+ expect(dm.metaPath).toBe('/extensions/math/add_meta.yaml');
34
+ expect(dm.namespace).toBeNull();
35
+ });
36
+
37
+ it('should support namespace for multi-root scanning', () => {
38
+ const dm: DiscoveredModule = {
39
+ filePath: '/plugins/greet.ts',
40
+ canonicalId: 'plugins.greet',
41
+ metaPath: null,
42
+ namespace: 'plugins',
43
+ };
44
+ expect(dm.namespace).toBe('plugins');
45
+ });
46
+ });
47
+ ```
48
+
49
+ Implement the interface with `filePath: string`, `canonicalId: string`, `metaPath: string | null`, `namespace: string | null`.
50
+
51
+ ### Step 2: Define DependencyInfo interface
52
+
53
+ Write a test for `DependencyInfo`:
54
+
55
+ ```typescript
56
+ describe('DependencyInfo', () => {
57
+ it('should represent a module dependency', () => {
58
+ const dep: DependencyInfo = {
59
+ moduleId: 'core.auth',
60
+ version: '2.0.0',
61
+ optional: false,
62
+ };
63
+ expect(dep.moduleId).toBe('core.auth');
64
+ expect(dep.version).toBe('2.0.0');
65
+ expect(dep.optional).toBe(false);
66
+ });
67
+
68
+ it('should allow null version for unversioned dependencies', () => {
69
+ const dep: DependencyInfo = {
70
+ moduleId: 'utils.logger',
71
+ version: null,
72
+ optional: true,
73
+ };
74
+ expect(dep.version).toBeNull();
75
+ expect(dep.optional).toBe(true);
76
+ });
77
+ });
78
+ ```
79
+
80
+ Implement with `moduleId: string`, `version: string | null`, `optional: boolean`.
81
+
82
+ ### Step 3: Define ModuleDescriptor interface
83
+
84
+ Write a test for `ModuleDescriptor`:
85
+
86
+ ```typescript
87
+ describe('ModuleDescriptor', () => {
88
+ it('should represent a complete module definition', () => {
89
+ const desc: ModuleDescriptor = {
90
+ moduleId: 'math.add',
91
+ name: 'Add Numbers',
92
+ description: 'Adds two numbers together',
93
+ documentation: 'Detailed usage guide...',
94
+ inputSchema: { type: 'object', properties: { a: { type: 'number' } } },
95
+ outputSchema: { type: 'object', properties: { result: { type: 'number' } } },
96
+ version: '1.0.0',
97
+ tags: ['math', 'arithmetic'],
98
+ annotations: null,
99
+ examples: [],
100
+ metadata: {},
101
+ };
102
+ expect(desc.moduleId).toBe('math.add');
103
+ expect(desc.tags).toContain('math');
104
+ });
105
+ });
106
+ ```
107
+
108
+ Implement with all fields including `annotations: ModuleAnnotations | null` and `examples: ModuleExample[]` imported from `../module.js`.
109
+
110
+ ## Acceptance Criteria
111
+
112
+ - [x] `ModuleDescriptor` interface has all 11 fields: moduleId, name, description, documentation, inputSchema, outputSchema, version, tags, annotations, examples, metadata
113
+ - [x] `DiscoveredModule` interface has 4 fields: filePath, canonicalId, metaPath, namespace
114
+ - [x] `DependencyInfo` interface has 3 fields: moduleId, version, optional
115
+ - [x] All interfaces compile under `tsc --noEmit` with strict mode
116
+ - [x] Types are re-exported from `src/registry/index.ts`
117
+
118
+ ## Dependencies
119
+
120
+ - `src/module.ts` must define `ModuleAnnotations` and `ModuleExample` types
121
+
122
+ ## Estimated Time
123
+
124
+ 1 hour
@@ -0,0 +1,177 @@
1
+ # Task: Validation
2
+
3
+ ## Goal
4
+
5
+ Implement `validateModule()` which performs duck-type structural validation on a module object. Checks that the module has `inputSchema` (non-null object), `outputSchema` (non-null object), `description` (non-empty string), and `execute` (function). Returns an array of error strings -- empty array means valid. Unlike Python's `issubclass` checks, TypeScript uses duck typing to validate structural conformance.
6
+
7
+ ## Files Involved
8
+
9
+ - `src/registry/validation.ts` -- Validation implementation
10
+ - `tests/registry/test-validation.test.ts` -- Validation tests
11
+
12
+ ## Steps (TDD)
13
+
14
+ ### Step 1: Valid module returns empty error array
15
+
16
+ ```typescript
17
+ import { describe, it, expect } from 'vitest';
18
+ import { validateModule } from '../../src/registry/validation.js';
19
+
20
+ describe('validateModule', () => {
21
+ it('should return empty array for a valid module', () => {
22
+ const validModule = {
23
+ inputSchema: { type: 'object', properties: {} },
24
+ outputSchema: { type: 'object', properties: {} },
25
+ description: 'A valid module',
26
+ execute: async () => ({}),
27
+ };
28
+ const errors = validateModule(validModule);
29
+ expect(errors).toEqual([]);
30
+ });
31
+ });
32
+ ```
33
+
34
+ ### Step 2: Missing inputSchema
35
+
36
+ ```typescript
37
+ it('should report missing inputSchema', () => {
38
+ const mod = {
39
+ outputSchema: { type: 'object' },
40
+ description: 'Test',
41
+ execute: async () => ({}),
42
+ };
43
+ const errors = validateModule(mod);
44
+ expect(errors).toHaveLength(1);
45
+ expect(errors[0]).toContain('inputSchema');
46
+ });
47
+ ```
48
+
49
+ ### Step 3: Missing outputSchema
50
+
51
+ ```typescript
52
+ it('should report missing outputSchema', () => {
53
+ const mod = {
54
+ inputSchema: { type: 'object' },
55
+ description: 'Test',
56
+ execute: async () => ({}),
57
+ };
58
+ const errors = validateModule(mod);
59
+ expect(errors).toHaveLength(1);
60
+ expect(errors[0]).toContain('outputSchema');
61
+ });
62
+ ```
63
+
64
+ ### Step 4: Missing or empty description
65
+
66
+ ```typescript
67
+ it('should report missing description', () => {
68
+ const mod = {
69
+ inputSchema: { type: 'object' },
70
+ outputSchema: { type: 'object' },
71
+ execute: async () => ({}),
72
+ };
73
+ const errors = validateModule(mod);
74
+ expect(errors.some((e) => e.includes('description'))).toBe(true);
75
+ });
76
+
77
+ it('should report empty string description', () => {
78
+ const mod = {
79
+ inputSchema: { type: 'object' },
80
+ outputSchema: { type: 'object' },
81
+ description: '',
82
+ execute: async () => ({}),
83
+ };
84
+ const errors = validateModule(mod);
85
+ expect(errors.some((e) => e.includes('description'))).toBe(true);
86
+ });
87
+ ```
88
+
89
+ ### Step 5: Missing execute method
90
+
91
+ ```typescript
92
+ it('should report missing execute method', () => {
93
+ const mod = {
94
+ inputSchema: { type: 'object' },
95
+ outputSchema: { type: 'object' },
96
+ description: 'Test module',
97
+ };
98
+ const errors = validateModule(mod);
99
+ expect(errors.some((e) => e.includes('execute'))).toBe(true);
100
+ });
101
+ ```
102
+
103
+ ### Step 6: Multiple validation errors
104
+
105
+ ```typescript
106
+ it('should accumulate all validation errors', () => {
107
+ const errors = validateModule({});
108
+ expect(errors.length).toBeGreaterThanOrEqual(3);
109
+ expect(errors.some((e) => e.includes('inputSchema'))).toBe(true);
110
+ expect(errors.some((e) => e.includes('outputSchema'))).toBe(true);
111
+ expect(errors.some((e) => e.includes('description'))).toBe(true);
112
+ expect(errors.some((e) => e.includes('execute'))).toBe(true);
113
+ });
114
+ ```
115
+
116
+ ### Step 7: Non-object inputSchema/outputSchema
117
+
118
+ ```typescript
119
+ it('should reject non-object inputSchema', () => {
120
+ const mod = {
121
+ inputSchema: 'not an object',
122
+ outputSchema: { type: 'object' },
123
+ description: 'Test',
124
+ execute: async () => ({}),
125
+ };
126
+ const errors = validateModule(mod);
127
+ expect(errors.some((e) => e.includes('inputSchema'))).toBe(true);
128
+ });
129
+
130
+ it('should reject null inputSchema', () => {
131
+ const mod = {
132
+ inputSchema: null,
133
+ outputSchema: { type: 'object' },
134
+ description: 'Test',
135
+ execute: async () => ({}),
136
+ };
137
+ const errors = validateModule(mod);
138
+ expect(errors.some((e) => e.includes('inputSchema'))).toBe(true);
139
+ });
140
+ ```
141
+
142
+ ### Step 8: Constructor fallback for class-based modules
143
+
144
+ ```typescript
145
+ it('should check constructor for schema when instance lacks it', () => {
146
+ class MyModule {
147
+ static inputSchema = { type: 'object' };
148
+ static outputSchema = { type: 'object' };
149
+ description = 'Class-based module';
150
+ execute = async () => ({});
151
+ }
152
+ const instance = new MyModule();
153
+ const errors = validateModule(instance);
154
+ expect(errors).toEqual([]);
155
+ });
156
+ ```
157
+
158
+ ## Acceptance Criteria
159
+
160
+ - [x] Valid module with inputSchema (object), outputSchema (object), description (string), execute (function) returns `[]`
161
+ - [x] Missing `inputSchema` produces descriptive error string
162
+ - [x] Missing `outputSchema` produces descriptive error string
163
+ - [x] Missing or empty `description` produces descriptive error string
164
+ - [x] Missing `execute` method produces descriptive error string
165
+ - [x] Non-object `inputSchema`/`outputSchema` (string, null, number) produces error
166
+ - [x] All errors are accumulated and returned together
167
+ - [x] Constructor properties are checked as fallback for class instances
168
+ - [x] Function accepts `unknown` type parameter for flexibility
169
+ - [x] All tests pass with `vitest`
170
+
171
+ ## Dependencies
172
+
173
+ - `types` task (interfaces for context)
174
+
175
+ ## Estimated Time
176
+
177
+ 1 hour
@@ -0,0 +1,56 @@
1
+ # Feature: Schema System
2
+
3
+ ## Overview
4
+
5
+ The Schema System is the foundational type infrastructure for apcore. It handles schema loading from YAML files, `$ref` resolution across documents, conversion to TypeBox `TSchema` objects, runtime validation via TypeBox `Value.Check()`/`Value.Decode()`, and export to LLM provider formats (MCP, OpenAI, Anthropic, Generic). The system supports three schema resolution strategies (`yaml_first`, `native_first`, `yaml_only`), annotation merging between YAML and code-defined metadata, and strict-mode transformations for OpenAI function calling compatibility.
6
+
7
+ ## Scope
8
+
9
+ ### Included
10
+
11
+ - `SchemaDefinition`, `ResolvedSchema`, `SchemaValidationResult`, and `LLMExtensions` interfaces
12
+ - `SchemaStrategy` and `ExportProfile` enums for runtime configuration
13
+ - `SchemaLoader` class with YAML file loading, `jsonSchemaToTypeBox()` conversion, two-level caching (definition + resolved), and strategy-based schema selection
14
+ - `RefResolver` class with `$ref` resolution supporting local pointers, relative file paths, `apcore://` canonical URIs, circular reference detection, and configurable max depth
15
+ - `jsonSchemaToTypeBox()` recursive converter from JSON Schema dictionaries to TypeBox `TSchema` objects
16
+ - `SchemaValidator` class with coercion toggle, `validate()`, `validateInput()`, and `validateOutput()` methods backed by TypeBox `Value.Check()`/`Value.Decode()`/`Value.Errors()`
17
+ - `SchemaExporter` class with four export profiles: MCP (with annotations), OpenAI (strict mode), Anthropic (with examples), Generic (passthrough)
18
+ - `toStrictSchema()`, `stripExtensions()`, `applyLlmDescriptions()` strict-mode utilities
19
+ - `mergeAnnotations()`, `mergeExamples()`, `mergeMetadata()` for YAML/code conflict resolution
20
+
21
+ ### Excluded
22
+
23
+ - Executor pipeline integration (consumed by `core-executor`)
24
+ - Registry module discovery (consumed by `registry-system`)
25
+ - Decorator-based schema generation (consumed by `decorator-bindings`)
26
+ - YAML schema file authoring and schema directory management
27
+
28
+ ## Technology Stack
29
+
30
+ - **TypeScript 5.5+** with strict mode
31
+ - **@sinclair/typebox >= 0.34.0** for schema representation and runtime validation
32
+ - **js-yaml** for YAML file parsing
33
+ - **Node.js >= 18.0.0** with ES Module support (`node:fs`, `node:path`)
34
+ - **vitest** for unit testing
35
+
36
+ ## Task Execution Order
37
+
38
+ | # | Task File | Description | Status |
39
+ |---|-----------|-------------|--------|
40
+ | 1 | [types-and-annotations](./tasks/types-and-annotations.md) | Core interfaces, enums, validation result types, annotation merging | completed |
41
+ | 2 | [loader](./tasks/loader.md) | SchemaLoader with YAML loading, caching, strategy selection | completed |
42
+ | 3 | [ref-resolver](./tasks/ref-resolver.md) | RefResolver with $ref resolution, apcore:// URIs, circular detection | completed |
43
+ | 4 | [typebox-generation](./tasks/typebox-generation.md) | jsonSchemaToTypeBox() recursive JSON Schema to TypeBox conversion | completed |
44
+ | 5 | [validator](./tasks/validator.md) | SchemaValidator with TypeBox validation, coercion toggle, error mapping | completed |
45
+ | 6 | [exporter](./tasks/exporter.md) | SchemaExporter with MCP, OpenAI, Anthropic, Generic export profiles | completed |
46
+ | 7 | [strict-mode](./tasks/strict-mode.md) | toStrictSchema(), stripExtensions(), applyLlmDescriptions() utilities | completed |
47
+
48
+ ## Progress
49
+
50
+ | Total | Completed | In Progress | Pending |
51
+ |-------|-----------|-------------|---------|
52
+ | 7 | 7 | 0 | 0 |
53
+
54
+ ## Reference Documents
55
+
56
+ - [Schema System Feature Specification](../../features/schema-system.md)