@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.
- package/dist/core/engine.d.ts +23 -0
- package/dist/core/engine.js +365 -0
- package/dist/core/operators.d.ts +30 -0
- package/dist/core/operators.js +424 -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 +69 -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 +194 -0
- package/dist/index.d.ts +9 -16
- package/dist/index.js +16 -62
- package/package.json +4 -8
- package/wasm/tenet.wasm +0 -0
- package/wasm/wasm_exec.js +0 -575
|
@@ -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
|
|
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
|
|
104
|
-
*
|
|
95
|
+
* Initialize the Tenet VM.
|
|
96
|
+
* This is a no-op in the pure TypeScript implementation (kept for backwards compatibility).
|
|
105
97
|
*
|
|
106
|
-
* @
|
|
98
|
+
* @deprecated No longer needed - the VM is ready immediately after import.
|
|
107
99
|
*/
|
|
108
|
-
export declare function init(
|
|
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
|
|
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
|
|
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
|
|
7
|
+
// Re-export lint functions (pure TypeScript)
|
|
8
8
|
export { lint, isTenetSchema, SCHEMA_URL } from './lint.js';
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// Import core engine functions
|
|
10
|
+
import { run as coreRun, verify as coreVerify } from './core/engine.js';
|
|
11
11
|
/**
|
|
12
|
-
* Initialize the Tenet
|
|
13
|
-
*
|
|
12
|
+
* Initialize the Tenet VM.
|
|
13
|
+
* This is a no-op in the pure TypeScript implementation (kept for backwards compatibility).
|
|
14
14
|
*
|
|
15
|
-
* @
|
|
15
|
+
* @deprecated No longer needed - the VM is ready immediately after import.
|
|
16
16
|
*/
|
|
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
|
-
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
47
|
+
return true;
|
|
94
48
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dlovans/tenet-core",
|
|
3
|
-
"version": "0.
|
|
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
|
|
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
|