@dlovans/tenet-core 0.1.4 → 0.2.1
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.
- package/README.md +41 -39
- package/dist/core/engine.d.ts +23 -0
- package/dist/core/engine.js +367 -0
- package/dist/core/operators.d.ts +30 -0
- package/dist/core/operators.js +449 -0
- package/dist/core/resolver.d.ts +11 -0
- package/dist/core/resolver.js +34 -0
- package/dist/core/temporal.d.ts +20 -0
- package/dist/core/temporal.js +109 -0
- package/dist/core/types.d.ts +33 -0
- package/dist/core/types.js +5 -0
- package/dist/core/validate.d.ts +21 -0
- package/dist/core/validate.js +200 -0
- package/dist/index.d.ts +9 -18
- package/dist/index.js +15 -80
- package/dist/validation-fixes.test.d.ts +7 -0
- package/dist/validation-fixes.test.js +399 -0
- package/package.json +5 -14
- package/dist/lint.d.ts +0 -31
- package/dist/lint.js +0 -160
- package/wasm/tenet.wasm +0 -0
- package/wasm/wasm_exec.js +0 -575
|
@@ -0,0 +1,200 @@
|
|
|
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) {
|
|
124
|
+
if (def.value === undefined || def.value === null) {
|
|
125
|
+
addError(state, id, '', `Required field '${id}' is missing`);
|
|
126
|
+
}
|
|
127
|
+
else if ((def.type === 'string' || def.type === 'select') && def.value === '') {
|
|
128
|
+
// Empty string is also considered "missing" for required string/select fields
|
|
129
|
+
addError(state, id, '', `Required field '${id}' is missing`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Validate type if value is present
|
|
133
|
+
if (def.value !== undefined && def.value !== null) {
|
|
134
|
+
validateType(state, id, def);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check attestations for required signatures.
|
|
140
|
+
*/
|
|
141
|
+
export function checkAttestations(state, applyAction) {
|
|
142
|
+
// Check legacy attestations in definitions (simple type: attestation)
|
|
143
|
+
for (const [id, def] of Object.entries(state.schema.definitions)) {
|
|
144
|
+
if (!def || def.type !== 'attestation') {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (def.required && def.value !== true) {
|
|
148
|
+
addError(state, id, '', `Required attestation '${id}' not confirmed`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Check rich attestations
|
|
152
|
+
if (!state.schema.attestations) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
for (const [id, att] of Object.entries(state.schema.attestations)) {
|
|
156
|
+
if (!att) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// Process on_sign if signed is true
|
|
160
|
+
if (att.signed && att.on_sign) {
|
|
161
|
+
applyAction(att.on_sign, `attestation_${id}`, att.law_ref || '');
|
|
162
|
+
}
|
|
163
|
+
// Validate required attestations
|
|
164
|
+
if (att.required) {
|
|
165
|
+
if (!att.signed) {
|
|
166
|
+
addError(state, id, '', `Required attestation '${id}' not signed`, att.law_ref);
|
|
167
|
+
}
|
|
168
|
+
else if (!att.evidence || !att.evidence.provider_audit_id) {
|
|
169
|
+
addError(state, id, '', `Attestation '${id}' signed but missing evidence`, att.law_ref);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Determine document status based on validation errors.
|
|
176
|
+
*/
|
|
177
|
+
export function determineStatus(state) {
|
|
178
|
+
let hasTypeErrors = false;
|
|
179
|
+
let hasMissingRequired = false;
|
|
180
|
+
let hasMissingAttestations = false;
|
|
181
|
+
for (const err of state.errors) {
|
|
182
|
+
const msg = err.message;
|
|
183
|
+
if (msg.includes('must be a')) {
|
|
184
|
+
hasTypeErrors = true;
|
|
185
|
+
}
|
|
186
|
+
else if (msg.includes('missing') || msg.includes('Required field')) {
|
|
187
|
+
hasMissingRequired = true;
|
|
188
|
+
}
|
|
189
|
+
else if (msg.includes('attestation')) {
|
|
190
|
+
hasMissingAttestations = true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (hasTypeErrors) {
|
|
194
|
+
return 'INVALID';
|
|
195
|
+
}
|
|
196
|
+
if (hasMissingRequired || hasMissingAttestations) {
|
|
197
|
+
return 'INCOMPLETE';
|
|
198
|
+
}
|
|
199
|
+
return 'READY';
|
|
200
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tenet - Declarative Logic VM for JSON Schemas
|
|
3
3
|
*
|
|
4
|
-
* This module provides a
|
|
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
|
-
export { lint, isTenetSchema, SCHEMA_URL } from './lint.js';
|
|
8
|
-
export type { LintIssue, LintResult } from './lint.js';
|
|
9
7
|
export interface TenetResult {
|
|
10
8
|
result?: TenetSchema;
|
|
11
9
|
error?: string;
|
|
@@ -49,6 +47,7 @@ export interface Definition {
|
|
|
49
47
|
options?: string[];
|
|
50
48
|
label?: string;
|
|
51
49
|
required?: boolean;
|
|
50
|
+
readonly?: boolean;
|
|
52
51
|
visible?: boolean;
|
|
53
52
|
min?: number;
|
|
54
53
|
max?: number;
|
|
@@ -90,22 +89,13 @@ export interface ValidationError {
|
|
|
90
89
|
message: string;
|
|
91
90
|
law_ref?: string;
|
|
92
91
|
}
|
|
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
92
|
/**
|
|
103
|
-
* Initialize the Tenet
|
|
104
|
-
*
|
|
93
|
+
* Initialize the Tenet VM.
|
|
94
|
+
* This is a no-op in the pure TypeScript implementation (kept for backwards compatibility).
|
|
105
95
|
*
|
|
106
|
-
* @
|
|
96
|
+
* @deprecated No longer needed - the VM is ready immediately after import.
|
|
107
97
|
*/
|
|
108
|
-
export declare function init(
|
|
98
|
+
export declare function init(_wasmPath?: string): Promise<void>;
|
|
109
99
|
/**
|
|
110
100
|
* Run the Tenet VM on a schema.
|
|
111
101
|
*
|
|
@@ -124,6 +114,7 @@ export declare function run(schema: TenetSchema | string, date?: Date | string):
|
|
|
124
114
|
*/
|
|
125
115
|
export declare function verify(newSchema: TenetSchema | string, oldSchema: TenetSchema | string): TenetVerifyResult;
|
|
126
116
|
/**
|
|
127
|
-
* Check if the
|
|
117
|
+
* Check if the VM is ready.
|
|
118
|
+
* Always returns true in the pure TypeScript implementation.
|
|
128
119
|
*/
|
|
129
120
|
export declare function isReady(): boolean;
|
package/dist/index.js
CHANGED
|
@@ -1,76 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tenet - Declarative Logic VM for JSON Schemas
|
|
3
3
|
*
|
|
4
|
-
* This module provides a
|
|
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
|
-
//
|
|
8
|
-
|
|
9
|
-
let wasmReady = false;
|
|
10
|
-
let wasmReadyPromise = null;
|
|
7
|
+
// Import core engine functions
|
|
8
|
+
import { run as coreRun, verify as coreVerify } from './core/engine.js';
|
|
11
9
|
/**
|
|
12
|
-
* Initialize the Tenet
|
|
13
|
-
*
|
|
10
|
+
* Initialize the Tenet VM.
|
|
11
|
+
* This is a no-op in the pure TypeScript implementation (kept for backwards compatibility).
|
|
14
12
|
*
|
|
15
|
-
* @
|
|
13
|
+
* @deprecated No longer needed - the VM is ready immediately after import.
|
|
16
14
|
*/
|
|
17
|
-
export async function init(
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
// Resolve paths relative to this module
|
|
33
|
-
const moduleUrl = new URL(import.meta.url);
|
|
34
|
-
const baseUrl = moduleUrl.href.substring(0, moduleUrl.href.lastIndexOf('/'));
|
|
35
|
-
// Load wasm_exec.js if Go is not defined
|
|
36
|
-
if (typeof Go === 'undefined') {
|
|
37
|
-
await new Promise((resolve, reject) => {
|
|
38
|
-
const script = document.createElement('script');
|
|
39
|
-
script.src = `${baseUrl}/../wasm/wasm_exec.js`;
|
|
40
|
-
script.onload = () => resolve();
|
|
41
|
-
script.onerror = () => reject(new Error('Failed to load wasm_exec.js'));
|
|
42
|
-
document.head.appendChild(script);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
// Auto-resolve WASM path if default
|
|
46
|
-
const resolvedWasmPath = wasmPath === './tenet.wasm'
|
|
47
|
-
? `${baseUrl}/../wasm/tenet.wasm`
|
|
48
|
-
: wasmPath;
|
|
49
|
-
const go = new Go();
|
|
50
|
-
const result = await WebAssembly.instantiateStreaming(fetch(resolvedWasmPath), go.importObject);
|
|
51
|
-
go.run(result.instance);
|
|
52
|
-
}
|
|
53
|
-
else if (isNode) {
|
|
54
|
-
// Node.js environment
|
|
55
|
-
const fs = await import('fs');
|
|
56
|
-
const path = await import('path');
|
|
57
|
-
const { fileURLToPath } = await import('url');
|
|
58
|
-
const { createRequire } = await import('module');
|
|
59
|
-
// ESM-compatible __dirname and require
|
|
60
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
61
|
-
const __dirname = path.dirname(__filename);
|
|
62
|
-
const require = createRequire(import.meta.url);
|
|
63
|
-
// Load wasm_exec.js (Go's JS runtime)
|
|
64
|
-
const wasmExecPath = path.resolve(__dirname, '../wasm/wasm_exec.js');
|
|
65
|
-
require(wasmExecPath);
|
|
66
|
-
const go = new Go();
|
|
67
|
-
const wasmBuffer = fs.readFileSync(wasmPath);
|
|
68
|
-
const result = await WebAssembly.instantiate(wasmBuffer, go.importObject);
|
|
69
|
-
go.run(result.instance);
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
throw new Error('Unsupported environment');
|
|
73
|
-
}
|
|
15
|
+
export async function init(_wasmPath) {
|
|
16
|
+
// No-op: pure TypeScript implementation doesn't need initialization
|
|
17
|
+
return Promise.resolve();
|
|
74
18
|
}
|
|
75
19
|
/**
|
|
76
20
|
* Run the Tenet VM on a schema.
|
|
@@ -80,12 +24,7 @@ async function loadWasm(wasmPath) {
|
|
|
80
24
|
* @returns The transformed schema with computed state, errors, and status
|
|
81
25
|
*/
|
|
82
26
|
export function run(schema, date = new Date()) {
|
|
83
|
-
|
|
84
|
-
throw new Error('Tenet not initialized. Call init() first.');
|
|
85
|
-
}
|
|
86
|
-
const jsonStr = typeof schema === 'string' ? schema : JSON.stringify(schema);
|
|
87
|
-
const dateStr = date instanceof Date ? date.toISOString() : date;
|
|
88
|
-
return globalThis.TenetRun(jsonStr, dateStr);
|
|
27
|
+
return coreRun(schema, date);
|
|
89
28
|
}
|
|
90
29
|
/**
|
|
91
30
|
* Verify that a schema transformation is legal.
|
|
@@ -96,16 +35,12 @@ export function run(schema, date = new Date()) {
|
|
|
96
35
|
* @returns Whether the transformation is valid
|
|
97
36
|
*/
|
|
98
37
|
export function verify(newSchema, oldSchema) {
|
|
99
|
-
|
|
100
|
-
throw new Error('Tenet not initialized. Call init() first.');
|
|
101
|
-
}
|
|
102
|
-
const newJson = typeof newSchema === 'string' ? newSchema : JSON.stringify(newSchema);
|
|
103
|
-
const oldJson = typeof oldSchema === 'string' ? oldSchema : JSON.stringify(oldSchema);
|
|
104
|
-
return globalThis.TenetVerify(newJson, oldJson);
|
|
38
|
+
return coreVerify(newSchema, oldSchema);
|
|
105
39
|
}
|
|
106
40
|
/**
|
|
107
|
-
* Check if the
|
|
41
|
+
* Check if the VM is ready.
|
|
42
|
+
* Always returns true in the pure TypeScript implementation.
|
|
108
43
|
*/
|
|
109
44
|
export function isReady() {
|
|
110
|
-
return
|
|
45
|
+
return true;
|
|
111
46
|
}
|