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,140 @@
1
+ # Task: Strict Mode
2
+
3
+ ## Goal
4
+
5
+ Implement the strict-mode transformation utilities for converting JSON Schemas to OpenAI-compatible strict mode format (Algorithm A23). This includes `toStrictSchema()` for full strict conversion, `stripExtensions()` for removing vendor extensions and defaults, and `applyLlmDescriptions()` for substituting `x-llm-description` into `description` fields.
6
+
7
+ ## Files Involved
8
+
9
+ - `src/schema/strict.ts` -- `toStrictSchema()`, `stripExtensions()`, `applyLlmDescriptions()`, internal `convertToStrict()`
10
+ - `tests/schema/test-strict.test.ts` -- Unit tests
11
+
12
+ ## Steps
13
+
14
+ ### 1. Implement deepCopy utility
15
+
16
+ ```typescript
17
+ function deepCopy<T>(obj: T): T {
18
+ return JSON.parse(JSON.stringify(obj));
19
+ }
20
+ ```
21
+
22
+ Used internally to ensure all transformations operate on copies, never mutating the original schema.
23
+
24
+ ### 2. Implement applyLlmDescriptions()
25
+
26
+ Recursively walk the schema tree. At each object node, if both `x-llm-description` and `description` exist, replace `description` with the value of `x-llm-description`. Recurse into `properties`, `items`, `oneOf`/`anyOf`/`allOf`, and `definitions`/`$defs`.
27
+
28
+ ```typescript
29
+ export function applyLlmDescriptions(node: unknown): void {
30
+ if (typeof node !== 'object' || node === null || Array.isArray(node)) return;
31
+
32
+ const obj = node as Record<string, unknown>;
33
+ if ('x-llm-description' in obj && 'description' in obj) {
34
+ obj['description'] = obj['x-llm-description'];
35
+ }
36
+
37
+ if ('properties' in obj && typeof obj['properties'] === 'object' && obj['properties'] !== null) {
38
+ for (const prop of Object.values(obj['properties'] as Record<string, unknown>)) {
39
+ applyLlmDescriptions(prop);
40
+ }
41
+ }
42
+ // ... recurse into items, oneOf, anyOf, allOf, definitions, $defs
43
+ }
44
+ ```
45
+
46
+ TDD: Test root-level description replacement. Test nested property description replacement. Test no-op when `x-llm-description` is absent.
47
+
48
+ ### 3. Implement stripExtensions()
49
+
50
+ Recursively remove all keys starting with `x-` and the `default` key from every object node. Recurse into all nested objects and arrays.
51
+
52
+ ```typescript
53
+ export function stripExtensions(node: unknown): void {
54
+ if (typeof node !== 'object' || node === null || Array.isArray(node)) return;
55
+
56
+ const obj = node as Record<string, unknown>;
57
+ const keysToRemove = Object.keys(obj).filter(
58
+ (k) => (typeof k === 'string' && k.startsWith('x-')) || k === 'default',
59
+ );
60
+ for (const k of keysToRemove) {
61
+ delete obj[k];
62
+ }
63
+
64
+ for (const value of Object.values(obj)) {
65
+ if (typeof value === 'object' && value !== null) {
66
+ if (Array.isArray(value)) {
67
+ for (const item of value) {
68
+ if (typeof item === 'object' && item !== null) {
69
+ stripExtensions(item);
70
+ }
71
+ }
72
+ } else {
73
+ stripExtensions(value);
74
+ }
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ TDD: Test `x-sensitive`, `x-custom`, `x-llm-description` are removed. Test `default` is removed. Test nested structures are processed. Test non-extension keys survive.
81
+
82
+ ### 4. Implement convertToStrict() (internal)
83
+
84
+ For each object node with `type: "object"` and `properties`:
85
+ 1. Set `additionalProperties: false`
86
+ 2. Collect existing `required` array into a Set
87
+ 3. Find optional properties (not in `required`)
88
+ 4. For optional properties with a `type` field: wrap type in array with `"null"` (e.g., `"string"` -> `["string", "null"]`)
89
+ 5. For optional properties without a `type` field: wrap in `{ oneOf: [originalSchema, { type: "null" }] }`
90
+ 6. Set `required` to sorted array of ALL property names
91
+
92
+ Recurse into `properties`, `items`, `oneOf`/`anyOf`/`allOf`, and `definitions`/`$defs`.
93
+
94
+ ```typescript
95
+ function convertToStrict(node: unknown): void {
96
+ // ... see implementation in src/schema/strict.ts
97
+ }
98
+ ```
99
+
100
+ TDD: Test `additionalProperties: false` is added. Test all properties become required. Test optional properties get null-union wrapping. Test already-required properties keep their original type.
101
+
102
+ ### 5. Implement toStrictSchema() composition
103
+
104
+ Compose the three operations: deep-copy, strip extensions, then convert to strict.
105
+
106
+ ```typescript
107
+ export function toStrictSchema(schema: Record<string, unknown>): Record<string, unknown> {
108
+ const result = deepCopy(schema);
109
+ stripExtensions(result);
110
+ convertToStrict(result);
111
+ return result;
112
+ }
113
+ ```
114
+
115
+ TDD: Test that the original schema is not mutated. Test full pipeline: extensions removed, `additionalProperties: false` set, all required, optionals nullable.
116
+
117
+ ## Acceptance Criteria
118
+
119
+ - [x] `applyLlmDescriptions()` replaces `description` with `x-llm-description` where both exist
120
+ - [x] `applyLlmDescriptions()` recurses into `properties`, `items`, `oneOf`/`anyOf`/`allOf`, `definitions`/`$defs`
121
+ - [x] `applyLlmDescriptions()` is a no-op when `x-llm-description` is absent
122
+ - [x] `stripExtensions()` removes all `x-` prefixed keys recursively
123
+ - [x] `stripExtensions()` removes `default` keys recursively
124
+ - [x] `stripExtensions()` preserves non-extension keys
125
+ - [x] `stripExtensions()` handles nested objects and arrays
126
+ - [x] `toStrictSchema()` sets `additionalProperties: false` on all object schemas
127
+ - [x] `toStrictSchema()` makes all properties required (sorted alphabetically)
128
+ - [x] `toStrictSchema()` wraps optional type-bearing properties with null type array
129
+ - [x] `toStrictSchema()` wraps optional non-type properties with `oneOf` null union
130
+ - [x] `toStrictSchema()` does not mutate the original schema (returns deep copy)
131
+ - [x] `toStrictSchema()` strips extensions before strict conversion
132
+ - [x] All tests pass with `vitest`
133
+
134
+ ## Dependencies
135
+
136
+ - None (standalone utility, no internal dependencies)
137
+
138
+ ## Estimated Time
139
+
140
+ 2 hours
@@ -0,0 +1,133 @@
1
+ # Task: TypeBox Generation
2
+
3
+ ## Goal
4
+
5
+ Implement the `jsonSchemaToTypeBox()` function that recursively converts a JSON Schema dictionary to a TypeBox `TSchema` object. This is the TypeScript equivalent of the Python `create_model()` approach, leveraging the fact that TypeBox schemas ARE valid JSON Schema.
6
+
7
+ ## Files Involved
8
+
9
+ - `src/schema/loader.ts` -- `jsonSchemaToTypeBox()` function (exported, co-located with SchemaLoader)
10
+ - `tests/schema/test-loader.test.ts` -- Unit tests for `jsonSchemaToTypeBox()`
11
+
12
+ ## Steps
13
+
14
+ ### 1. Handle object type with properties
15
+
16
+ Convert `{ type: "object", properties: {...}, required: [...] }` to `Type.Object({...})`. Recursively convert each property. Wrap non-required properties with `Type.Optional()`.
17
+
18
+ ```typescript
19
+ if (schemaType === 'object') {
20
+ const properties = schema['properties'] as Record<string, Record<string, unknown>> | undefined;
21
+ const required = new Set((schema['required'] as string[]) ?? []);
22
+
23
+ if (properties) {
24
+ const typeboxProps: Record<string, TSchema> = {};
25
+ for (const [name, propSchema] of Object.entries(properties)) {
26
+ const propType = jsonSchemaToTypeBox(propSchema);
27
+ typeboxProps[name] = required.has(name) ? propType : Type.Optional(propType);
28
+ }
29
+ return Type.Object(typeboxProps);
30
+ }
31
+ return Type.Record(Type.String(), Type.Unknown());
32
+ }
33
+ ```
34
+
35
+ TDD: Test object with required and optional properties. Verify `Value.Check()` accepts valid data and rejects missing required fields.
36
+
37
+ ### 2. Handle object type without properties
38
+
39
+ Convert `{ type: "object" }` (no properties) to `Type.Record(Type.String(), Type.Unknown())`, accepting any string-keyed object.
40
+
41
+ TDD: Test that `{ any: "value" }` passes validation.
42
+
43
+ ### 3. Handle array type
44
+
45
+ Convert `{ type: "array", items: {...} }` to `Type.Array(jsonSchemaToTypeBox(items))`. Without items, use `Type.Array(Type.Unknown())`.
46
+
47
+ ```typescript
48
+ if (schemaType === 'array') {
49
+ const items = schema['items'] as Record<string, unknown> | undefined;
50
+ return items ? Type.Array(jsonSchemaToTypeBox(items)) : Type.Array(Type.Unknown());
51
+ }
52
+ ```
53
+
54
+ TDD: Test typed array (string items), untyped array, valid and invalid data.
55
+
56
+ ### 4. Handle string type with constraints
57
+
58
+ Convert `{ type: "string" }` to `Type.String()`. Pass through constraint options: `minLength`, `maxLength`, `pattern`, `format`.
59
+
60
+ ```typescript
61
+ if (schemaType === 'string') {
62
+ const opts: Record<string, unknown> = {};
63
+ if ('minLength' in schema) opts['minLength'] = schema['minLength'];
64
+ if ('maxLength' in schema) opts['maxLength'] = schema['maxLength'];
65
+ if ('pattern' in schema) opts['pattern'] = schema['pattern'];
66
+ if ('format' in schema) opts['format'] = schema['format'];
67
+ return Type.String(opts);
68
+ }
69
+ ```
70
+
71
+ TDD: Test plain string, string with `minLength`/`maxLength`, verify constraint enforcement.
72
+
73
+ ### 5. Handle integer and number types with constraints
74
+
75
+ Convert `{ type: "integer" }` to `Type.Integer()` and `{ type: "number" }` to `Type.Number()`. Pass through: `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf`.
76
+
77
+ TDD: Test integer rejects floats, number accepts both, constraint boundaries work.
78
+
79
+ ### 6. Handle boolean and null types
80
+
81
+ Convert `{ type: "boolean" }` to `Type.Boolean()` and `{ type: "null" }` to `Type.Null()`.
82
+
83
+ TDD: Test boolean accepts `true`/`false`, rejects strings. Null accepts `null`, rejects `undefined`.
84
+
85
+ ### 7. Handle enum
86
+
87
+ Convert `{ enum: ["a", "b", "c"] }` to `Type.Union(values.map(v => Type.Literal(v)))`.
88
+
89
+ TDD: Test valid enum values accepted, invalid values rejected.
90
+
91
+ ### 8. Handle oneOf and anyOf
92
+
93
+ Convert `{ oneOf: [...] }` and `{ anyOf: [...] }` to `Type.Union(schemas.map(s => jsonSchemaToTypeBox(s)))`.
94
+
95
+ TDD: Test union of string and number accepts both types, rejects others.
96
+
97
+ ### 9. Handle allOf
98
+
99
+ Convert `{ allOf: [...] }` to `Type.Intersect(schemas.map(s => jsonSchemaToTypeBox(s)))`.
100
+
101
+ TDD: Test intersection of two object schemas requires properties from both.
102
+
103
+ ### 10. Handle unknown/unsupported schemas
104
+
105
+ Any schema that does not match a known type falls through to `Type.Unknown()`, which accepts any value.
106
+
107
+ TDD: Test empty schema `{}` accepts any value.
108
+
109
+ ## Acceptance Criteria
110
+
111
+ - [x] `object` with properties converts to `Type.Object()` with correct required/optional handling
112
+ - [x] `object` without properties converts to `Type.Record(Type.String(), Type.Unknown())`
113
+ - [x] `array` with items converts to `Type.Array()` with recursive item conversion
114
+ - [x] `array` without items converts to `Type.Array(Type.Unknown())`
115
+ - [x] `string` converts to `Type.String()` with `minLength`/`maxLength`/`pattern`/`format` passthrough
116
+ - [x] `integer` converts to `Type.Integer()` with numeric constraint passthrough
117
+ - [x] `number` converts to `Type.Number()` with numeric constraint passthrough
118
+ - [x] `boolean` converts to `Type.Boolean()`
119
+ - [x] `null` converts to `Type.Null()`
120
+ - [x] `enum` converts to `Type.Union(Type.Literal(...))` for each value
121
+ - [x] `oneOf`/`anyOf` convert to `Type.Union()`
122
+ - [x] `allOf` converts to `Type.Intersect()`
123
+ - [x] Unknown schemas convert to `Type.Unknown()`
124
+ - [x] All generated TypeBox schemas pass `Value.Check()` validation correctly
125
+ - [x] All tests pass with `vitest`
126
+
127
+ ## Dependencies
128
+
129
+ - types-and-annotations (for `TSchema` type from TypeBox)
130
+
131
+ ## Estimated Time
132
+
133
+ 3 hours
@@ -0,0 +1,160 @@
1
+ # Task: Types and Annotations
2
+
3
+ ## Goal
4
+
5
+ Define the core type interfaces, enums, and annotation merging utilities that form the foundation of the schema system. All other schema tasks depend on these types.
6
+
7
+ ## Files Involved
8
+
9
+ - `src/schema/types.ts` -- `SchemaDefinition`, `ResolvedSchema`, `SchemaStrategy`, `ExportProfile`, `SchemaValidationErrorDetail`, `SchemaValidationResult`, `LLMExtensions`, `validationResultToError()`
10
+ - `src/schema/annotations.ts` -- `mergeAnnotations()`, `mergeExamples()`, `mergeMetadata()`
11
+
12
+ ## Steps
13
+
14
+ ### 1. Define SchemaStrategy enum
15
+
16
+ ```typescript
17
+ export enum SchemaStrategy {
18
+ YamlFirst = 'yaml_first',
19
+ NativeFirst = 'native_first',
20
+ YamlOnly = 'yaml_only',
21
+ }
22
+ ```
23
+
24
+ TDD: Write tests asserting enum values match the expected string representations.
25
+
26
+ ### 2. Define ExportProfile enum
27
+
28
+ ```typescript
29
+ export enum ExportProfile {
30
+ MCP = 'mcp',
31
+ OpenAI = 'openai',
32
+ Anthropic = 'anthropic',
33
+ Generic = 'generic',
34
+ }
35
+ ```
36
+
37
+ TDD: Write tests asserting all four profiles are available and have correct string values.
38
+
39
+ ### 3. Define SchemaDefinition interface
40
+
41
+ ```typescript
42
+ export interface SchemaDefinition {
43
+ moduleId: string;
44
+ description: string;
45
+ inputSchema: Record<string, unknown>;
46
+ outputSchema: Record<string, unknown>;
47
+ errorSchema?: Record<string, unknown> | null;
48
+ definitions: Record<string, unknown>;
49
+ version: string;
50
+ documentation?: string | null;
51
+ schemaUrl?: string | null;
52
+ }
53
+ ```
54
+
55
+ TDD: Create a conforming object and verify all fields are accessible with correct types.
56
+
57
+ ### 4. Define ResolvedSchema interface
58
+
59
+ ```typescript
60
+ import type { TSchema } from '@sinclair/typebox';
61
+
62
+ export interface ResolvedSchema {
63
+ jsonSchema: Record<string, unknown>;
64
+ schema: TSchema;
65
+ moduleId: string;
66
+ direction: string;
67
+ }
68
+ ```
69
+
70
+ TDD: Create a `ResolvedSchema` with `Type.Object({})` and verify `schema` field is a valid `TSchema`.
71
+
72
+ ### 5. Define validation result types
73
+
74
+ ```typescript
75
+ export interface SchemaValidationErrorDetail {
76
+ path: string;
77
+ message: string;
78
+ constraint?: string | null;
79
+ expected?: unknown;
80
+ actual?: unknown;
81
+ }
82
+
83
+ export interface SchemaValidationResult {
84
+ valid: boolean;
85
+ errors: SchemaValidationErrorDetail[];
86
+ }
87
+ ```
88
+
89
+ TDD: Create valid and invalid results, verify field access.
90
+
91
+ ### 6. Implement validationResultToError()
92
+
93
+ ```typescript
94
+ export function validationResultToError(result: SchemaValidationResult): SchemaValidationError {
95
+ if (result.valid) {
96
+ throw new Error('Cannot convert valid result to error');
97
+ }
98
+ const errorDicts = result.errors.map((e) => ({
99
+ path: e.path,
100
+ message: e.message,
101
+ constraint: e.constraint ?? null,
102
+ expected: e.expected ?? null,
103
+ actual: e.actual ?? null,
104
+ }));
105
+ return new SchemaValidationError('Schema validation failed', errorDicts);
106
+ }
107
+ ```
108
+
109
+ TDD: Verify conversion from `SchemaValidationResult` to `SchemaValidationError`, and verify throwing on valid result.
110
+
111
+ ### 7. Define LLMExtensions interface
112
+
113
+ ```typescript
114
+ export interface LLMExtensions {
115
+ llmDescription?: string | null;
116
+ examples?: unknown[] | null;
117
+ sensitive: boolean;
118
+ constraints?: string | null;
119
+ deprecated?: Record<string, unknown> | null;
120
+ }
121
+ ```
122
+
123
+ ### 8. Implement mergeAnnotations()
124
+
125
+ Merges YAML-defined and code-defined module annotations with YAML-wins precedence over code, and code-wins precedence over defaults. Operates on the `ANNOTATION_FIELDS` list: `readonly`, `destructive`, `idempotent`, `requiresApproval`, `openWorld`.
126
+
127
+ TDD: Test default-only, code-only, YAML-only, and YAML-overrides-code scenarios.
128
+
129
+ ### 9. Implement mergeExamples()
130
+
131
+ YAML examples override code examples entirely. If no YAML examples, fall back to code examples. If neither, return empty array. YAML examples are mapped from raw records to `ModuleExample` objects.
132
+
133
+ TDD: Test YAML-wins, code-fallback, and empty scenarios.
134
+
135
+ ### 10. Implement mergeMetadata()
136
+
137
+ Shallow merge with YAML overrides. Start from code metadata (or empty), then `Object.assign()` YAML metadata on top.
138
+
139
+ TDD: Test merge behavior with overlapping keys.
140
+
141
+ ## Acceptance Criteria
142
+
143
+ - [x] `SchemaStrategy` enum has `YamlFirst`, `NativeFirst`, `YamlOnly` members
144
+ - [x] `ExportProfile` enum has `MCP`, `OpenAI`, `Anthropic`, `Generic` members
145
+ - [x] `SchemaDefinition` interface includes all required and optional fields
146
+ - [x] `ResolvedSchema` interface includes `jsonSchema`, `schema` (TSchema), `moduleId`, `direction`
147
+ - [x] `SchemaValidationErrorDetail` includes `path`, `message`, optional `constraint`/`expected`/`actual`
148
+ - [x] `validationResultToError()` converts invalid results and throws on valid results
149
+ - [x] `mergeAnnotations()` respects YAML > code > defaults precedence
150
+ - [x] `mergeExamples()` respects YAML-wins-all precedence
151
+ - [x] `mergeMetadata()` performs shallow merge with YAML overrides
152
+ - [x] All types compile with `tsc --noEmit` under strict mode
153
+
154
+ ## Dependencies
155
+
156
+ - None (foundation task)
157
+
158
+ ## Estimated Time
159
+
160
+ 2 hours
@@ -0,0 +1,149 @@
1
+ # Task: SchemaValidator
2
+
3
+ ## Goal
4
+
5
+ Implement the `SchemaValidator` class for runtime validation of data against TypeBox `TSchema` objects. Supports a coercion toggle (`Value.Decode` vs `Value.Check`), detailed error collection via `Value.Errors()`, and separate `validateInput()`/`validateOutput()` entry points that return validated data or throw `SchemaValidationError`.
6
+
7
+ ## Files Involved
8
+
9
+ - `src/schema/validator.ts` -- `SchemaValidator` class
10
+ - `src/schema/types.ts` -- `SchemaValidationResult`, `SchemaValidationErrorDetail`, `validationResultToError()`
11
+ - `src/errors.ts` -- `SchemaValidationError`
12
+ - `tests/schema/test-validator.test.ts` -- Unit tests
13
+
14
+ ## Steps
15
+
16
+ ### 1. Implement constructor with coercion toggle
17
+
18
+ ```typescript
19
+ export class SchemaValidator {
20
+ private _coerceTypes: boolean;
21
+
22
+ constructor(coerceTypes: boolean = true) {
23
+ this._coerceTypes = coerceTypes;
24
+ }
25
+ }
26
+ ```
27
+
28
+ TDD: Verify default `coerceTypes` is `true`. Verify explicit `false` disables coercion.
29
+
30
+ ### 2. Implement validate() method
31
+
32
+ When coercion is enabled, attempt `Value.Decode()`. If it succeeds, return `{ valid: true, errors: [] }`. If it throws, collect errors and return `{ valid: false, errors: [...] }`.
33
+
34
+ When coercion is disabled, use `Value.Check()`. If it returns `true`, return valid result. Otherwise, collect errors.
35
+
36
+ ```typescript
37
+ validate(data: Record<string, unknown>, schema: TSchema): SchemaValidationResult {
38
+ if (this._coerceTypes) {
39
+ try {
40
+ Value.Decode(schema, data);
41
+ return { valid: true, errors: [] };
42
+ } catch {
43
+ return { valid: false, errors: this._collectErrors(schema, data) };
44
+ }
45
+ }
46
+
47
+ if (Value.Check(schema, data)) {
48
+ return { valid: true, errors: [] };
49
+ }
50
+ return { valid: false, errors: this._collectErrors(schema, data) };
51
+ }
52
+ ```
53
+
54
+ TDD: Test valid data returns `{ valid: true }`. Test invalid data returns `{ valid: false }` with non-empty errors array.
55
+
56
+ ### 3. Implement _collectErrors() private method
57
+
58
+ Iterate over `Value.Errors(schema, data)` and map each `ValueError` to a `SchemaValidationErrorDetail`.
59
+
60
+ ```typescript
61
+ private _collectErrors(schema: TSchema, data: unknown): SchemaValidationErrorDetail[] {
62
+ const errors: SchemaValidationErrorDetail[] = [];
63
+ for (const error of Value.Errors(schema, data)) {
64
+ errors.push(this._typeboxErrorToDetail(error));
65
+ }
66
+ return errors;
67
+ }
68
+ ```
69
+
70
+ TDD: Verify errors have populated `path` and `message` fields.
71
+
72
+ ### 4. Implement _typeboxErrorToDetail() mapping
73
+
74
+ Map TypeBox `ValueError` fields to `SchemaValidationErrorDetail`:
75
+ - `error.path` -> `path` (defaults to `'/'` if empty)
76
+ - `error.message` -> `message`
77
+ - `error.type` -> `constraint` (stringified)
78
+ - `error.schema` -> `expected`
79
+ - `error.value` -> `actual`
80
+
81
+ TDD: Verify detail mapping for nested object errors includes correct JSON path.
82
+
83
+ ### 5. Implement validateInput() method
84
+
85
+ Call `_validateAndReturn()`. On success, return the (potentially coerced) data. On failure, throw `SchemaValidationError` via `validationResultToError()`.
86
+
87
+ ```typescript
88
+ validateInput(data: Record<string, unknown>, schema: TSchema): Record<string, unknown> {
89
+ return this._validateAndReturn(data, schema);
90
+ }
91
+ ```
92
+
93
+ TDD: Test valid input returns data. Test invalid input throws `SchemaValidationError`.
94
+
95
+ ### 6. Implement validateOutput() method
96
+
97
+ Same as `validateInput()` -- delegates to `_validateAndReturn()`. Separate method for semantic clarity in the executor pipeline (step 5 vs step 8).
98
+
99
+ TDD: Test valid output returns data. Test invalid output throws `SchemaValidationError`.
100
+
101
+ ### 7. Implement _validateAndReturn() private method
102
+
103
+ Shared logic for `validateInput()` and `validateOutput()`. With coercion, use `Value.Decode()` and return the decoded value. Without coercion, use `Value.Check()` and return the original data. On failure, collect errors and throw.
104
+
105
+ ```typescript
106
+ private _validateAndReturn(data: Record<string, unknown>, schema: TSchema): Record<string, unknown> {
107
+ if (this._coerceTypes) {
108
+ try {
109
+ return Value.Decode(schema, data) as Record<string, unknown>;
110
+ } catch {
111
+ const result: SchemaValidationResult = {
112
+ valid: false,
113
+ errors: this._collectErrors(schema, data),
114
+ };
115
+ throw validationResultToError(result);
116
+ }
117
+ }
118
+ if (Value.Check(schema, data)) {
119
+ return data;
120
+ }
121
+ const result: SchemaValidationResult = {
122
+ valid: false,
123
+ errors: this._collectErrors(schema, data),
124
+ };
125
+ throw validationResultToError(result);
126
+ }
127
+ ```
128
+
129
+ TDD: Test both coercion and strict paths for valid and invalid data.
130
+
131
+ ## Acceptance Criteria
132
+
133
+ - [x] `validate()` returns `{ valid: true, errors: [] }` for conforming data
134
+ - [x] `validate()` returns `{ valid: false, errors: [...] }` for non-conforming data with detailed errors
135
+ - [x] Coercion mode (`coerceTypes: true`) uses `Value.Decode()` for type coercion
136
+ - [x] Strict mode (`coerceTypes: false`) uses `Value.Check()` for exact type matching
137
+ - [x] `validateInput()` returns data on success, throws `SchemaValidationError` on failure
138
+ - [x] `validateOutput()` returns data on success, throws `SchemaValidationError` on failure
139
+ - [x] Error details include `path`, `message`, `constraint`, `expected`, `actual`
140
+ - [x] Nested object errors include correct JSON path (e.g., `/nested/x`)
141
+ - [x] All tests pass with `vitest`
142
+
143
+ ## Dependencies
144
+
145
+ - types-and-annotations (for `SchemaValidationResult`, `SchemaValidationErrorDetail`, `validationResultToError()`)
146
+
147
+ ## Estimated Time
148
+
149
+ 2 hours