@dlovans/tenet-core 0.2.0 → 0.3.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.
@@ -1,8 +1,112 @@
1
1
  /**
2
- * Internal types for the Tenet VM core.
3
- * Public types are re-exported from index.ts
2
+ * All type definitions for the Tenet VM.
3
+ * This is the single source of truth — other modules re-export from here.
4
4
  */
5
- export type { TenetSchema, TenetResult, TenetVerifyResult, Definition, Rule, Action, TemporalBranch, StateModel, DerivedDef, ValidationError, Attestation, Evidence, } from '../index.js';
5
+ export interface TenetResult {
6
+ result?: TenetSchema;
7
+ error?: string;
8
+ }
9
+ /**
10
+ * Machine-parseable codes for verification issues.
11
+ * UI layers map these to customer-friendly messages; the VM never decides presentation.
12
+ */
13
+ export type VerifyIssueCode = 'unknown_field' | 'computed_mismatch' | 'attestation_unsigned' | 'attestation_no_evidence' | 'attestation_no_timestamp' | 'status_mismatch' | 'convergence_failed' | 'internal_error';
14
+ /**
15
+ * A single structured problem found during verification.
16
+ */
17
+ export interface VerifyIssue {
18
+ code: VerifyIssueCode;
19
+ field_id?: string;
20
+ message: string;
21
+ expected?: unknown;
22
+ claimed?: unknown;
23
+ }
24
+ export interface TenetVerifyResult {
25
+ valid: boolean;
26
+ status?: string;
27
+ issues?: VerifyIssue[];
28
+ schema?: TenetSchema;
29
+ error?: string;
30
+ }
31
+ export interface Evidence {
32
+ provider_audit_id?: string;
33
+ timestamp?: string;
34
+ signer_id?: string;
35
+ logic_version?: string;
36
+ }
37
+ export interface Attestation {
38
+ statement: string;
39
+ law_ref?: string;
40
+ required_role?: string;
41
+ provider?: string;
42
+ required?: boolean;
43
+ signed?: boolean;
44
+ evidence?: Evidence;
45
+ on_sign?: Action;
46
+ }
47
+ export interface TenetSchema {
48
+ protocol?: string;
49
+ schema_id?: string;
50
+ version?: string;
51
+ valid_from?: string;
52
+ definitions: Record<string, Definition>;
53
+ logic_tree?: Rule[];
54
+ temporal_map?: TemporalBranch[];
55
+ state_model?: StateModel;
56
+ errors?: ValidationError[];
57
+ status?: 'READY' | 'INCOMPLETE' | 'INVALID';
58
+ attestations?: Record<string, Attestation>;
59
+ }
60
+ export interface Definition {
61
+ type: 'string' | 'number' | 'boolean' | 'select' | 'date' | 'attestation' | 'currency';
62
+ value?: unknown;
63
+ options?: string[];
64
+ label?: string;
65
+ required?: boolean;
66
+ readonly?: boolean;
67
+ visible?: boolean;
68
+ min?: number;
69
+ max?: number;
70
+ step?: number;
71
+ min_length?: number;
72
+ max_length?: number;
73
+ pattern?: string;
74
+ ui_class?: string;
75
+ ui_message?: string;
76
+ }
77
+ export interface Rule {
78
+ id: string;
79
+ law_ref?: string;
80
+ logic_version?: string;
81
+ when: Record<string, unknown>;
82
+ then: Action;
83
+ disabled?: boolean;
84
+ }
85
+ export interface Action {
86
+ set?: Record<string, unknown>;
87
+ ui_modify?: Record<string, unknown>;
88
+ error_msg?: string;
89
+ }
90
+ export interface TemporalBranch {
91
+ valid_range: [string | null, string | null];
92
+ logic_version: string;
93
+ status: 'ACTIVE' | 'ARCHIVED';
94
+ }
95
+ export interface StateModel {
96
+ inputs: string[];
97
+ derived: Record<string, DerivedDef>;
98
+ }
99
+ export interface DerivedDef {
100
+ eval: Record<string, unknown>;
101
+ }
102
+ export type ErrorKind = 'type_mismatch' | 'missing_required' | 'constraint_violation' | 'attestation_incomplete' | 'runtime_warning' | 'cycle_detected';
103
+ export interface ValidationError {
104
+ field_id?: string;
105
+ rule_id?: string;
106
+ kind: ErrorKind;
107
+ message: string;
108
+ law_ref?: string;
109
+ }
6
110
  /**
7
111
  * Evaluation context for collection operators (some/all/none).
8
112
  * When iterating over an array, provides access to the current element.
@@ -17,7 +121,7 @@ export interface EvalContext {
17
121
  */
18
122
  export interface EvalState {
19
123
  /** The schema being evaluated (mutable copy) */
20
- schema: import('../index.js').TenetSchema;
124
+ schema: TenetSchema;
21
125
  /** Effective date for temporal routing */
22
126
  effectiveDate: Date;
23
127
  /** Tracks which fields were set by which rule (cycle detection) */
@@ -25,7 +129,9 @@ export interface EvalState {
25
129
  /** Current element context for some/all/none operators */
26
130
  currentElement?: unknown;
27
131
  /** Accumulated validation errors */
28
- errors: import('../index.js').ValidationError[];
132
+ errors: ValidationError[];
133
+ /** Cycle detection for derived fields */
134
+ derivedInProgress: Set<string>;
29
135
  }
30
136
  /**
31
137
  * Document status values
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Internal types for the Tenet VM core.
3
- * Public types are re-exported from index.ts
2
+ * All type definitions for the Tenet VM.
3
+ * This is the single source of truth — other modules re-export from here.
4
4
  */
5
5
  export {};
@@ -2,11 +2,11 @@
2
2
  * Definition validation and status determination.
3
3
  * Validates types, constraints, and required fields.
4
4
  */
5
- import type { EvalState, DocStatus } from './types.js';
5
+ import type { EvalState, ErrorKind, DocStatus, Action } from './types.js';
6
6
  /**
7
7
  * Add an error to the state's error list.
8
8
  */
9
- export declare function addError(state: EvalState, fieldId: string, ruleId: string, message: string, lawRef?: string): void;
9
+ export declare function addError(state: EvalState, fieldId: string, ruleId: string, kind: ErrorKind, message: string, lawRef?: string): void;
10
10
  /**
11
11
  * Validate all definitions for type correctness and required fields.
12
12
  */
@@ -14,8 +14,8 @@ export declare function validateDefinitions(state: EvalState): void;
14
14
  /**
15
15
  * Check attestations for required signatures.
16
16
  */
17
- export declare function checkAttestations(state: EvalState, applyAction: (action: any, ruleId: string, lawRef: string) => void): void;
17
+ export declare function checkAttestations(state: EvalState, applyAction: (action: Action, ruleId: string, lawRef: string) => void): void;
18
18
  /**
19
- * Determine document status based on validation errors.
19
+ * Determine document status based on ErrorKind.
20
20
  */
21
21
  export declare function determineStatus(state: EvalState): DocStatus;
@@ -6,10 +6,11 @@ import { toFloat, parseDate } from './operators.js';
6
6
  /**
7
7
  * Add an error to the state's error list.
8
8
  */
9
- export function addError(state, fieldId, ruleId, message, lawRef) {
9
+ export function addError(state, fieldId, ruleId, kind, message, lawRef) {
10
10
  state.errors.push({
11
11
  field_id: fieldId || undefined,
12
12
  rule_id: ruleId || undefined,
13
+ kind,
13
14
  message,
14
15
  law_ref: lawRef || undefined,
15
16
  });
@@ -28,10 +29,10 @@ function isValidOption(value, options) {
28
29
  */
29
30
  function validateNumericConstraints(state, id, value, def) {
30
31
  if (def.min !== undefined && value < def.min) {
31
- addError(state, id, '', `Field '${id}' value ${value.toFixed(2)} is below minimum ${def.min.toFixed(2)}`);
32
+ addError(state, id, '', 'constraint_violation', `Field '${id}' value ${value.toFixed(2)} is below minimum ${def.min.toFixed(2)}`);
32
33
  }
33
34
  if (def.max !== undefined && value > def.max) {
34
- addError(state, id, '', `Field '${id}' value ${value.toFixed(2)} exceeds maximum ${def.max.toFixed(2)}`);
35
+ addError(state, id, '', 'constraint_violation', `Field '${id}' value ${value.toFixed(2)} exceeds maximum ${def.max.toFixed(2)}`);
35
36
  }
36
37
  }
37
38
  /**
@@ -39,16 +40,16 @@ function validateNumericConstraints(state, id, value, def) {
39
40
  */
40
41
  function validateStringConstraints(state, id, value, def) {
41
42
  if (def.min_length !== undefined && value.length < def.min_length) {
42
- addError(state, id, '', `Field '${id}' is too short (minimum ${def.min_length} characters)`);
43
+ addError(state, id, '', 'constraint_violation', `Field '${id}' is too short (minimum ${def.min_length} characters)`);
43
44
  }
44
45
  if (def.max_length !== undefined && value.length > def.max_length) {
45
- addError(state, id, '', `Field '${id}' is too long (maximum ${def.max_length} characters)`);
46
+ addError(state, id, '', 'constraint_violation', `Field '${id}' is too long (maximum ${def.max_length} characters)`);
46
47
  }
47
48
  if (def.pattern) {
48
49
  try {
49
50
  const regex = new RegExp(def.pattern);
50
51
  if (!regex.test(value)) {
51
- addError(state, id, '', `Field '${id}' does not match required pattern`);
52
+ addError(state, id, '', 'constraint_violation', `Field '${id}' does not match required pattern`);
52
53
  }
53
54
  }
54
55
  catch {
@@ -58,13 +59,19 @@ function validateStringConstraints(state, id, value, def) {
58
59
  }
59
60
  /**
60
61
  * Validate a single definition's type and constraints.
62
+ * Array values are allowed — the declared type describes the element type,
63
+ * used by collection operators (some/all/none). Scalar validation is skipped for arrays.
61
64
  */
62
65
  function validateType(state, id, def) {
63
66
  const value = def.value;
67
+ // Skip scalar validation for array values (used with some/all/none operators)
68
+ if (Array.isArray(value)) {
69
+ return;
70
+ }
64
71
  switch (def.type) {
65
72
  case 'string': {
66
73
  if (typeof value !== 'string') {
67
- addError(state, id, '', `Field '${id}' must be a string`);
74
+ addError(state, id, '', 'type_mismatch', `Field '${id}' must be a string`);
68
75
  return;
69
76
  }
70
77
  validateStringConstraints(state, id, value, def);
@@ -74,7 +81,7 @@ function validateType(state, id, def) {
74
81
  case 'currency': {
75
82
  const [numVal, ok] = toFloat(value);
76
83
  if (!ok) {
77
- addError(state, id, '', `Field '${id}' must be a number`);
84
+ addError(state, id, '', 'type_mismatch', `Field '${id}' must be a number`);
78
85
  return;
79
86
  }
80
87
  validateNumericConstraints(state, id, numVal, def);
@@ -82,30 +89,30 @@ function validateType(state, id, def) {
82
89
  }
83
90
  case 'boolean': {
84
91
  if (typeof value !== 'boolean') {
85
- addError(state, id, '', `Field '${id}' must be a boolean`);
92
+ addError(state, id, '', 'type_mismatch', `Field '${id}' must be a boolean`);
86
93
  }
87
94
  break;
88
95
  }
89
96
  case 'select': {
90
97
  if (typeof value !== 'string') {
91
- addError(state, id, '', `Field '${id}' must be a string`);
98
+ addError(state, id, '', 'type_mismatch', `Field '${id}' must be a string`);
92
99
  return;
93
100
  }
94
101
  if (!isValidOption(value, def.options)) {
95
- addError(state, id, '', `Field '${id}' value '${value}' is not a valid option`);
102
+ addError(state, id, '', 'constraint_violation', `Field '${id}' value '${value}' is not a valid option`);
96
103
  }
97
104
  break;
98
105
  }
99
106
  case 'attestation': {
100
107
  if (typeof value !== 'boolean') {
101
- addError(state, id, '', `Attestation '${id}' must be a boolean`);
108
+ addError(state, id, '', 'type_mismatch', `Attestation '${id}' must be a boolean`);
102
109
  }
103
110
  break;
104
111
  }
105
112
  case 'date': {
106
113
  const [, ok] = parseDate(value);
107
114
  if (!ok) {
108
- addError(state, id, '', `Field '${id}' must be a valid date`);
115
+ addError(state, id, '', 'type_mismatch', `Field '${id}' must be a valid date`);
109
116
  }
110
117
  break;
111
118
  }
@@ -120,8 +127,14 @@ export function validateDefinitions(state) {
120
127
  continue;
121
128
  }
122
129
  // Check required fields
123
- if (def.required && (def.value === undefined || def.value === null)) {
124
- addError(state, id, '', `Required field '${id}' is missing`);
130
+ if (def.required) {
131
+ if (def.value === undefined || def.value === null) {
132
+ addError(state, id, '', 'missing_required', `Required field '${id}' is missing`);
133
+ }
134
+ else if ((def.type === 'string' || def.type === 'select') && def.value === '') {
135
+ // Empty string is also considered "missing" for required string/select fields
136
+ addError(state, id, '', 'missing_required', `Required field '${id}' is missing`);
137
+ }
125
138
  }
126
139
  // Validate type if value is present
127
140
  if (def.value !== undefined && def.value !== null) {
@@ -139,7 +152,7 @@ export function checkAttestations(state, applyAction) {
139
152
  continue;
140
153
  }
141
154
  if (def.required && def.value !== true) {
142
- addError(state, id, '', `Required attestation '${id}' not confirmed`);
155
+ addError(state, id, '', 'attestation_incomplete', `Required attestation '${id}' not confirmed`);
143
156
  }
144
157
  }
145
158
  // Check rich attestations
@@ -157,38 +170,30 @@ export function checkAttestations(state, applyAction) {
157
170
  // Validate required attestations
158
171
  if (att.required) {
159
172
  if (!att.signed) {
160
- addError(state, id, '', `Required attestation '${id}' not signed`, att.law_ref);
173
+ addError(state, id, '', 'attestation_incomplete', `Required attestation '${id}' not signed`, att.law_ref);
161
174
  }
162
175
  else if (!att.evidence || !att.evidence.provider_audit_id) {
163
- addError(state, id, '', `Attestation '${id}' signed but missing evidence`, att.law_ref);
176
+ addError(state, id, '', 'attestation_incomplete', `Attestation '${id}' signed but missing evidence`, att.law_ref);
164
177
  }
165
178
  }
166
179
  }
167
180
  }
168
181
  /**
169
- * Determine document status based on validation errors.
182
+ * Determine document status based on ErrorKind.
170
183
  */
171
184
  export function determineStatus(state) {
172
- let hasTypeErrors = false;
173
- let hasMissingRequired = false;
174
- let hasMissingAttestations = false;
175
185
  for (const err of state.errors) {
176
- const msg = err.message;
177
- if (msg.includes('must be a')) {
178
- hasTypeErrors = true;
179
- }
180
- else if (msg.includes('missing') || msg.includes('Required field')) {
181
- hasMissingRequired = true;
182
- }
183
- else if (msg.includes('attestation')) {
184
- hasMissingAttestations = true;
185
- }
186
+ if (err.kind === 'type_mismatch')
187
+ return 'INVALID';
186
188
  }
187
- if (hasTypeErrors) {
188
- return 'INVALID';
189
+ for (const err of state.errors) {
190
+ if (err.kind === 'missing_required' || err.kind === 'attestation_incomplete') {
191
+ return 'INCOMPLETE';
192
+ }
189
193
  }
190
- if (hasMissingRequired || hasMissingAttestations) {
191
- return 'INCOMPLETE';
194
+ for (const err of state.errors) {
195
+ if (err.kind === 'constraint_violation')
196
+ return 'INVALID';
192
197
  }
193
198
  return 'READY';
194
199
  }
package/dist/index.d.ts CHANGED
@@ -4,93 +4,8 @@
4
4
  * This module provides a pure TypeScript implementation of the Tenet VM.
5
5
  * Works in both browser and Node.js environments with no WASM dependencies.
6
6
  */
7
- export { lint, isTenetSchema, SCHEMA_URL } from './lint.js';
8
- export type { LintIssue, LintResult } from './lint.js';
9
- export interface TenetResult {
10
- result?: TenetSchema;
11
- error?: string;
12
- }
13
- export interface TenetVerifyResult {
14
- valid: boolean;
15
- error?: string;
16
- }
17
- export interface Evidence {
18
- provider_audit_id?: string;
19
- timestamp?: string;
20
- signer_id?: string;
21
- logic_version?: string;
22
- }
23
- export interface Attestation {
24
- statement: string;
25
- law_ref?: string;
26
- required_role?: string;
27
- provider?: string;
28
- required?: boolean;
29
- signed?: boolean;
30
- evidence?: Evidence;
31
- on_sign?: Action;
32
- }
33
- export interface TenetSchema {
34
- protocol?: string;
35
- schema_id?: string;
36
- version?: string;
37
- valid_from?: string;
38
- definitions: Record<string, Definition>;
39
- logic_tree?: Rule[];
40
- temporal_map?: TemporalBranch[];
41
- state_model?: StateModel;
42
- errors?: ValidationError[];
43
- status?: 'READY' | 'INCOMPLETE' | 'INVALID';
44
- attestations?: Record<string, Attestation>;
45
- }
46
- export interface Definition {
47
- type: 'string' | 'number' | 'boolean' | 'select' | 'date' | 'attestation' | 'currency';
48
- value?: unknown;
49
- options?: string[];
50
- label?: string;
51
- required?: boolean;
52
- readonly?: boolean;
53
- visible?: boolean;
54
- min?: number;
55
- max?: number;
56
- step?: number;
57
- min_length?: number;
58
- max_length?: number;
59
- pattern?: string;
60
- ui_class?: string;
61
- ui_message?: string;
62
- }
63
- export interface Rule {
64
- id: string;
65
- law_ref?: string;
66
- logic_version?: string;
67
- when: Record<string, unknown>;
68
- then: Action;
69
- disabled?: boolean;
70
- }
71
- export interface Action {
72
- set?: Record<string, unknown>;
73
- ui_modify?: Record<string, unknown>;
74
- error_msg?: string;
75
- }
76
- export interface TemporalBranch {
77
- valid_range: [string | null, string | null];
78
- logic_version: string;
79
- status: 'ACTIVE' | 'ARCHIVED';
80
- }
81
- export interface StateModel {
82
- inputs: string[];
83
- derived: Record<string, DerivedDef>;
84
- }
85
- export interface DerivedDef {
86
- eval: Record<string, unknown>;
87
- }
88
- export interface ValidationError {
89
- field_id?: string;
90
- rule_id?: string;
91
- message: string;
92
- law_ref?: string;
93
- }
7
+ export type { TenetSchema, TenetResult, TenetVerifyResult, VerifyIssue, VerifyIssueCode, Definition, Rule, Action, TemporalBranch, StateModel, DerivedDef, ValidationError, Evidence, Attestation, ErrorKind, } from './core/types.js';
8
+ import type { TenetSchema, TenetResult, TenetVerifyResult } from './core/types.js';
94
9
  /**
95
10
  * Initialize the Tenet VM.
96
11
  * This is a no-op in the pure TypeScript implementation (kept for backwards compatibility).
package/dist/index.js CHANGED
@@ -4,9 +4,6 @@
4
4
  * This module provides a pure TypeScript implementation of the Tenet VM.
5
5
  * Works in both browser and Node.js environments with no WASM dependencies.
6
6
  */
7
- // Re-export lint functions (pure TypeScript)
8
- export { lint, isTenetSchema, SCHEMA_URL } from './lint.js';
9
- // Import core engine functions
10
7
  import { run as coreRun, verify as coreVerify } from './core/engine.js';
11
8
  /**
12
9
  * Initialize the Tenet VM.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tests for validation fixes:
3
+ * - Issue 1: Empty string passes required validation
4
+ * - Issue 2: Derived fields shadowed by definitions
5
+ * - Issue 3: Execution order - logic before derived
6
+ */
7
+ export {};