@dlovans/tenet-core 0.1.3 → 0.2.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.
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Definition validation and status determination.
3
+ * Validates types, constraints, and required fields.
4
+ */
5
+ import { toFloat, parseDate } from './operators.js';
6
+ /**
7
+ * Add an error to the state's error list.
8
+ */
9
+ export function addError(state, fieldId, ruleId, message, lawRef) {
10
+ state.errors.push({
11
+ field_id: fieldId || undefined,
12
+ rule_id: ruleId || undefined,
13
+ message,
14
+ law_ref: lawRef || undefined,
15
+ });
16
+ }
17
+ /**
18
+ * Check if a value is one of the allowed options.
19
+ */
20
+ function isValidOption(value, options) {
21
+ if (!options) {
22
+ return true; // No restrictions
23
+ }
24
+ return options.includes(value);
25
+ }
26
+ /**
27
+ * Validate numeric constraints (min/max).
28
+ */
29
+ function validateNumericConstraints(state, id, value, def) {
30
+ 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
+ }
33
+ if (def.max !== undefined && value > def.max) {
34
+ addError(state, id, '', `Field '${id}' value ${value.toFixed(2)} exceeds maximum ${def.max.toFixed(2)}`);
35
+ }
36
+ }
37
+ /**
38
+ * Validate string constraints (length, pattern).
39
+ */
40
+ function validateStringConstraints(state, id, value, def) {
41
+ 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
+ }
44
+ 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
+ }
47
+ if (def.pattern) {
48
+ try {
49
+ const regex = new RegExp(def.pattern);
50
+ if (!regex.test(value)) {
51
+ addError(state, id, '', `Field '${id}' does not match required pattern`);
52
+ }
53
+ }
54
+ catch {
55
+ // Invalid regex pattern, skip validation
56
+ }
57
+ }
58
+ }
59
+ /**
60
+ * Validate a single definition's type and constraints.
61
+ */
62
+ function validateType(state, id, def) {
63
+ const value = def.value;
64
+ switch (def.type) {
65
+ case 'string': {
66
+ if (typeof value !== 'string') {
67
+ addError(state, id, '', `Field '${id}' must be a string`);
68
+ return;
69
+ }
70
+ validateStringConstraints(state, id, value, def);
71
+ break;
72
+ }
73
+ case 'number':
74
+ case 'currency': {
75
+ const [numVal, ok] = toFloat(value);
76
+ if (!ok) {
77
+ addError(state, id, '', `Field '${id}' must be a number`);
78
+ return;
79
+ }
80
+ validateNumericConstraints(state, id, numVal, def);
81
+ break;
82
+ }
83
+ case 'boolean': {
84
+ if (typeof value !== 'boolean') {
85
+ addError(state, id, '', `Field '${id}' must be a boolean`);
86
+ }
87
+ break;
88
+ }
89
+ case 'select': {
90
+ if (typeof value !== 'string') {
91
+ addError(state, id, '', `Field '${id}' must be a string`);
92
+ return;
93
+ }
94
+ if (!isValidOption(value, def.options)) {
95
+ addError(state, id, '', `Field '${id}' value '${value}' is not a valid option`);
96
+ }
97
+ break;
98
+ }
99
+ case 'attestation': {
100
+ if (typeof value !== 'boolean') {
101
+ addError(state, id, '', `Attestation '${id}' must be a boolean`);
102
+ }
103
+ break;
104
+ }
105
+ case 'date': {
106
+ const [, ok] = parseDate(value);
107
+ if (!ok) {
108
+ addError(state, id, '', `Field '${id}' must be a valid date`);
109
+ }
110
+ break;
111
+ }
112
+ }
113
+ }
114
+ /**
115
+ * Validate all definitions for type correctness and required fields.
116
+ */
117
+ export function validateDefinitions(state) {
118
+ for (const [id, def] of Object.entries(state.schema.definitions)) {
119
+ if (!def) {
120
+ continue;
121
+ }
122
+ // Check required fields
123
+ if (def.required && (def.value === undefined || def.value === null)) {
124
+ addError(state, id, '', `Required field '${id}' is missing`);
125
+ }
126
+ // Validate type if value is present
127
+ if (def.value !== undefined && def.value !== null) {
128
+ validateType(state, id, def);
129
+ }
130
+ }
131
+ }
132
+ /**
133
+ * Check attestations for required signatures.
134
+ */
135
+ export function checkAttestations(state, applyAction) {
136
+ // Check legacy attestations in definitions (simple type: attestation)
137
+ for (const [id, def] of Object.entries(state.schema.definitions)) {
138
+ if (!def || def.type !== 'attestation') {
139
+ continue;
140
+ }
141
+ if (def.required && def.value !== true) {
142
+ addError(state, id, '', `Required attestation '${id}' not confirmed`);
143
+ }
144
+ }
145
+ // Check rich attestations
146
+ if (!state.schema.attestations) {
147
+ return;
148
+ }
149
+ for (const [id, att] of Object.entries(state.schema.attestations)) {
150
+ if (!att) {
151
+ continue;
152
+ }
153
+ // Process on_sign if signed is true
154
+ if (att.signed && att.on_sign) {
155
+ applyAction(att.on_sign, `attestation_${id}`, att.law_ref || '');
156
+ }
157
+ // Validate required attestations
158
+ if (att.required) {
159
+ if (!att.signed) {
160
+ addError(state, id, '', `Required attestation '${id}' not signed`, att.law_ref);
161
+ }
162
+ else if (!att.evidence || !att.evidence.provider_audit_id) {
163
+ addError(state, id, '', `Attestation '${id}' signed but missing evidence`, att.law_ref);
164
+ }
165
+ }
166
+ }
167
+ }
168
+ /**
169
+ * Determine document status based on validation errors.
170
+ */
171
+ export function determineStatus(state) {
172
+ let hasTypeErrors = false;
173
+ let hasMissingRequired = false;
174
+ let hasMissingAttestations = false;
175
+ 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
+ }
187
+ if (hasTypeErrors) {
188
+ return 'INVALID';
189
+ }
190
+ if (hasMissingRequired || hasMissingAttestations) {
191
+ return 'INCOMPLETE';
192
+ }
193
+ return 'READY';
194
+ }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Tenet - Declarative Logic VM for JSON Schemas
3
3
  *
4
- * This module provides a JavaScript/TypeScript wrapper around the Tenet WASM binary.
5
- * Works in both browser and Node.js environments.
4
+ * This module provides a pure TypeScript implementation of the Tenet VM.
5
+ * Works in both browser and Node.js environments with no WASM dependencies.
6
6
  */
7
7
  export { lint, isTenetSchema, SCHEMA_URL } from './lint.js';
8
8
  export type { LintIssue, LintResult } from './lint.js';
@@ -49,6 +49,7 @@ export interface Definition {
49
49
  options?: string[];
50
50
  label?: string;
51
51
  required?: boolean;
52
+ readonly?: boolean;
52
53
  visible?: boolean;
53
54
  min?: number;
54
55
  max?: number;
@@ -90,22 +91,13 @@ export interface ValidationError {
90
91
  message: string;
91
92
  law_ref?: string;
92
93
  }
93
- declare global {
94
- var TenetRun: (json: string, date: string) => TenetResult;
95
- var TenetVerify: (newJson: string, oldJson: string) => TenetVerifyResult;
96
- var Go: new () => GoInstance;
97
- }
98
- interface GoInstance {
99
- importObject: WebAssembly.Imports;
100
- run(instance: WebAssembly.Instance): Promise<void>;
101
- }
102
94
  /**
103
- * Initialize the Tenet WASM module.
104
- * Must be called before using run() or verify().
95
+ * Initialize the Tenet VM.
96
+ * This is a no-op in the pure TypeScript implementation (kept for backwards compatibility).
105
97
  *
106
- * @param wasmPath - Path or URL to tenet.wasm file
98
+ * @deprecated No longer needed - the VM is ready immediately after import.
107
99
  */
108
- export declare function init(wasmPath?: string): Promise<void>;
100
+ export declare function init(_wasmPath?: string): Promise<void>;
109
101
  /**
110
102
  * Run the Tenet VM on a schema.
111
103
  *
@@ -124,6 +116,7 @@ export declare function run(schema: TenetSchema | string, date?: Date | string):
124
116
  */
125
117
  export declare function verify(newSchema: TenetSchema | string, oldSchema: TenetSchema | string): TenetVerifyResult;
126
118
  /**
127
- * Check if the WASM module is ready.
119
+ * Check if the VM is ready.
120
+ * Always returns true in the pure TypeScript implementation.
128
121
  */
129
122
  export declare function isReady(): boolean;
package/dist/index.js CHANGED
@@ -1,59 +1,22 @@
1
1
  /**
2
2
  * Tenet - Declarative Logic VM for JSON Schemas
3
3
  *
4
- * This module provides a JavaScript/TypeScript wrapper around the Tenet WASM binary.
5
- * Works in both browser and Node.js environments.
4
+ * This module provides a pure TypeScript implementation of the Tenet VM.
5
+ * Works in both browser and Node.js environments with no WASM dependencies.
6
6
  */
7
- // Re-export lint functions (pure TypeScript, no WASM needed)
7
+ // Re-export lint functions (pure TypeScript)
8
8
  export { lint, isTenetSchema, SCHEMA_URL } from './lint.js';
9
- let wasmReady = false;
10
- let wasmReadyPromise = null;
9
+ // Import core engine functions
10
+ import { run as coreRun, verify as coreVerify } from './core/engine.js';
11
11
  /**
12
- * Initialize the Tenet WASM module.
13
- * Must be called before using run() or verify().
12
+ * Initialize the Tenet VM.
13
+ * This is a no-op in the pure TypeScript implementation (kept for backwards compatibility).
14
14
  *
15
- * @param wasmPath - Path or URL to tenet.wasm file
15
+ * @deprecated No longer needed - the VM is ready immediately after import.
16
16
  */
17
- export async function init(wasmPath = './tenet.wasm') {
18
- if (wasmReady)
19
- return;
20
- if (wasmReadyPromise)
21
- return wasmReadyPromise;
22
- wasmReadyPromise = loadWasm(wasmPath);
23
- await wasmReadyPromise;
24
- wasmReady = true;
25
- }
26
- async function loadWasm(wasmPath) {
27
- // Detect environment
28
- const isBrowser = typeof window !== 'undefined';
29
- const isNode = typeof process !== 'undefined' && process.versions?.node;
30
- if (isBrowser) {
31
- // Browser environment
32
- const go = new Go();
33
- const result = await WebAssembly.instantiateStreaming(fetch(wasmPath), go.importObject);
34
- go.run(result.instance);
35
- }
36
- else if (isNode) {
37
- // Node.js environment
38
- const fs = await import('fs');
39
- const path = await import('path');
40
- const { fileURLToPath } = await import('url');
41
- const { createRequire } = await import('module');
42
- // ESM-compatible __dirname and require
43
- const __filename = fileURLToPath(import.meta.url);
44
- const __dirname = path.dirname(__filename);
45
- const require = createRequire(import.meta.url);
46
- // Load wasm_exec.js (Go's JS runtime)
47
- const wasmExecPath = path.resolve(__dirname, '../wasm/wasm_exec.js');
48
- require(wasmExecPath);
49
- const go = new Go();
50
- const wasmBuffer = fs.readFileSync(wasmPath);
51
- const result = await WebAssembly.instantiate(wasmBuffer, go.importObject);
52
- go.run(result.instance);
53
- }
54
- else {
55
- throw new Error('Unsupported environment');
56
- }
17
+ export async function init(_wasmPath) {
18
+ // No-op: pure TypeScript implementation doesn't need initialization
19
+ return Promise.resolve();
57
20
  }
58
21
  /**
59
22
  * Run the Tenet VM on a schema.
@@ -63,12 +26,7 @@ async function loadWasm(wasmPath) {
63
26
  * @returns The transformed schema with computed state, errors, and status
64
27
  */
65
28
  export function run(schema, date = new Date()) {
66
- if (!wasmReady) {
67
- throw new Error('Tenet not initialized. Call init() first.');
68
- }
69
- const jsonStr = typeof schema === 'string' ? schema : JSON.stringify(schema);
70
- const dateStr = date instanceof Date ? date.toISOString() : date;
71
- return globalThis.TenetRun(jsonStr, dateStr);
29
+ return coreRun(schema, date);
72
30
  }
73
31
  /**
74
32
  * Verify that a schema transformation is legal.
@@ -79,16 +37,12 @@ export function run(schema, date = new Date()) {
79
37
  * @returns Whether the transformation is valid
80
38
  */
81
39
  export function verify(newSchema, oldSchema) {
82
- if (!wasmReady) {
83
- throw new Error('Tenet not initialized. Call init() first.');
84
- }
85
- const newJson = typeof newSchema === 'string' ? newSchema : JSON.stringify(newSchema);
86
- const oldJson = typeof oldSchema === 'string' ? oldSchema : JSON.stringify(oldSchema);
87
- return globalThis.TenetVerify(newJson, oldJson);
40
+ return coreVerify(newSchema, oldSchema);
88
41
  }
89
42
  /**
90
- * Check if the WASM module is ready.
43
+ * Check if the VM is ready.
44
+ * Always returns true in the pure TypeScript implementation.
91
45
  */
92
46
  export function isReady() {
93
- return wasmReady;
47
+ return true;
94
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dlovans/tenet-core",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Declarative logic VM for JSON schemas - reactive validation, temporal routing, and computed state",
6
6
  "main": "dist/index.js",
@@ -16,13 +16,10 @@
16
16
  }
17
17
  },
18
18
  "files": [
19
- "dist",
20
- "wasm"
19
+ "dist"
21
20
  ],
22
21
  "scripts": {
23
- "build:wasm": "cd .. && GOOS=js GOARCH=wasm go build -o js/wasm/tenet.wasm ./cmd/wasm",
24
- "build:js": "tsc",
25
- "build": "npm run build:wasm && npm run build:js",
22
+ "build": "tsc",
26
23
  "test": "node --test dist/*.test.js",
27
24
  "prepublishOnly": "npm run build"
28
25
  },
@@ -32,7 +29,6 @@
32
29
  "reactive",
33
30
  "schema",
34
31
  "form",
35
- "wasm",
36
32
  "compliance",
37
33
  "temporal",
38
34
  "linter"
@@ -54,4 +50,4 @@
54
50
  "@types/node": "^25.0.9",
55
51
  "typescript": "^5.0.0"
56
52
  }
57
- }
53
+ }
package/wasm/tenet.wasm DELETED
Binary file