@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.
@@ -0,0 +1,399 @@
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
+ import { describe, it } from 'node:test';
8
+ import assert from 'node:assert';
9
+ import { run } from './index.js';
10
+ // ===========================================================================
11
+ // Issue 1: Empty String Required Validation
12
+ // ===========================================================================
13
+ describe('Issue 1: Empty String Required Validation', () => {
14
+ it('should treat empty string as missing for required string fields', () => {
15
+ const schema = {
16
+ protocol: 'Test_v1',
17
+ schema_id: 'test',
18
+ definitions: {
19
+ name: { type: 'string', value: '', required: true },
20
+ },
21
+ };
22
+ const result = run(schema);
23
+ assert.ok(!result.error, `Run failed: ${result.error}`);
24
+ assert.ok(result.result);
25
+ // Status should be INCOMPLETE because empty string is "missing"
26
+ assert.strictEqual(result.result.status, 'INCOMPLETE', `Expected status INCOMPLETE for empty required string, got ${result.result.status}`);
27
+ // Should have an error for the missing field
28
+ assert.ok(result.result.errors && result.result.errors.length > 0, 'Expected error for empty required string');
29
+ });
30
+ it('should accept empty string for non-required fields', () => {
31
+ const schema = {
32
+ protocol: 'Test_v1',
33
+ schema_id: 'test',
34
+ definitions: {
35
+ notes: { type: 'string', value: '', required: false },
36
+ },
37
+ };
38
+ const result = run(schema);
39
+ assert.ok(!result.error);
40
+ assert.ok(result.result);
41
+ // Status should be READY because field is not required
42
+ assert.strictEqual(result.result.status, 'READY');
43
+ });
44
+ it('should accept zero for required number fields', () => {
45
+ const schema = {
46
+ protocol: 'Test_v1',
47
+ schema_id: 'test',
48
+ definitions: {
49
+ quantity: { type: 'number', value: 0, required: true },
50
+ },
51
+ };
52
+ const result = run(schema);
53
+ assert.ok(!result.error);
54
+ assert.ok(result.result);
55
+ // Status should be READY (0 is a valid value for required number)
56
+ assert.strictEqual(result.result.status, 'READY');
57
+ });
58
+ it('should catch empty allergy_note in survey schema', () => {
59
+ const schema = {
60
+ protocol: 'CoffeePreferenceSurvey_v1',
61
+ schema_id: 'coffee-pref-001',
62
+ definitions: {
63
+ respondent_name: {
64
+ type: 'string',
65
+ label: 'Your Name',
66
+ required: true,
67
+ value: 'Jane Doe',
68
+ },
69
+ allergy_note: {
70
+ type: 'string',
71
+ label: 'Please describe your allergy',
72
+ required: true,
73
+ visible: true,
74
+ value: '',
75
+ },
76
+ },
77
+ };
78
+ const result = run(schema);
79
+ assert.ok(!result.error);
80
+ assert.ok(result.result);
81
+ // Should be INCOMPLETE because allergy_note is required but empty
82
+ assert.strictEqual(result.result.status, 'INCOMPLETE');
83
+ });
84
+ });
85
+ // ===========================================================================
86
+ // Issue 2: Derived Fields Shadowing
87
+ // ===========================================================================
88
+ describe('Issue 2: Derived Fields Take Precedence', () => {
89
+ it('should compute derived value even when field exists in definitions', () => {
90
+ const schema = {
91
+ protocol: 'Test_v1',
92
+ schema_id: 'test',
93
+ definitions: {
94
+ gross: { type: 'number', value: 100 },
95
+ tax: { type: 'number', value: null, readonly: true },
96
+ },
97
+ state_model: {
98
+ inputs: ['gross'],
99
+ derived: {
100
+ tax: { eval: { '*': [{ var: 'gross' }, 0.1] } },
101
+ },
102
+ },
103
+ };
104
+ const result = run(schema);
105
+ assert.ok(!result.error);
106
+ assert.ok(result.result);
107
+ // Tax should be computed as 10 (100 * 0.1)
108
+ const taxDef = result.result.definitions.tax;
109
+ assert.ok(taxDef, 'Expected tax definition to exist');
110
+ assert.strictEqual(taxDef.value, 10, `Expected tax = 10, got ${taxDef.value}`);
111
+ });
112
+ it('should allow logic tree to use derived values', () => {
113
+ const schema = {
114
+ protocol: 'Test_v1',
115
+ schema_id: 'test',
116
+ definitions: {
117
+ gross: { type: 'number', value: 100 },
118
+ },
119
+ state_model: {
120
+ inputs: ['gross'],
121
+ derived: {
122
+ tax: { eval: { '*': [{ var: 'gross' }, 0.1] } },
123
+ },
124
+ },
125
+ logic_tree: [
126
+ {
127
+ id: 'check_tax',
128
+ when: { '>': [{ var: 'tax' }, 5] },
129
+ then: { set: { high_tax: true } },
130
+ },
131
+ ],
132
+ };
133
+ const result = run(schema);
134
+ assert.ok(!result.error);
135
+ assert.ok(result.result);
136
+ // high_tax should be set because tax (10) > 5
137
+ const highTaxDef = result.result.definitions.high_tax;
138
+ assert.ok(highTaxDef, 'Expected high_tax definition to be created');
139
+ assert.strictEqual(highTaxDef.value, true, `Expected high_tax = true, got ${highTaxDef.value}`);
140
+ });
141
+ });
142
+ // ===========================================================================
143
+ // Issue 3: Execution Order (Logic Before Derived)
144
+ // ===========================================================================
145
+ describe('Issue 3: Logic Can Use Derived Values', () => {
146
+ it('should compute derived values before logic tree evaluation', () => {
147
+ const schema = {
148
+ protocol: 'Test_v1',
149
+ schema_id: 'test',
150
+ definitions: {
151
+ income: { type: 'number', value: 50000 },
152
+ deductions: { type: 'number', value: 10000 },
153
+ },
154
+ state_model: {
155
+ inputs: ['income', 'deductions'],
156
+ derived: {
157
+ taxable_income: { eval: { '-': [{ var: 'income' }, { var: 'deductions' }] } },
158
+ },
159
+ },
160
+ logic_tree: [
161
+ {
162
+ id: 'high_income_bracket',
163
+ when: { '>': [{ var: 'taxable_income' }, 30000] },
164
+ then: { set: { tax_bracket: 'high' } },
165
+ },
166
+ ],
167
+ };
168
+ const result = run(schema);
169
+ assert.ok(!result.error);
170
+ assert.ok(result.result);
171
+ // taxable_income should be 40000 (50000 - 10000)
172
+ const taxableIncomeDef = result.result.definitions.taxable_income;
173
+ assert.ok(taxableIncomeDef, 'Expected taxable_income definition');
174
+ assert.strictEqual(taxableIncomeDef.value, 40000);
175
+ // tax_bracket should be "high" because taxable_income (40000) > 30000
176
+ const taxBracketDef = result.result.definitions.tax_bracket;
177
+ assert.ok(taxBracketDef, 'Expected tax_bracket to be set by logic rule');
178
+ assert.strictEqual(taxBracketDef.value, 'high');
179
+ });
180
+ it('should calculate effective tax rate in tax calculator schema', () => {
181
+ const schema = {
182
+ protocol: 'IncomeTaxCalculator_v1',
183
+ schema_id: 'tax-calc-001',
184
+ definitions: {
185
+ gross_annual_income: { type: 'currency', value: 85000 },
186
+ filing_status: {
187
+ type: 'select',
188
+ options: ['single', 'married_joint', 'married_separate'],
189
+ value: 'single',
190
+ },
191
+ standard_deduction: { type: 'currency', readonly: true, value: null },
192
+ taxable_income: { type: 'currency', readonly: true, value: null },
193
+ effective_tax_rate: { type: 'number', readonly: true, value: null },
194
+ },
195
+ state_model: {
196
+ inputs: ['gross_annual_income', 'filing_status'],
197
+ derived: {
198
+ standard_deduction: {
199
+ eval: {
200
+ if: [
201
+ { '==': [{ var: 'filing_status' }, 'single'] },
202
+ 14600,
203
+ { '==': [{ var: 'filing_status' }, 'married_joint'] },
204
+ 29200,
205
+ 21900,
206
+ ],
207
+ },
208
+ },
209
+ taxable_income: {
210
+ eval: {
211
+ '-': [{ var: 'gross_annual_income' }, { var: 'standard_deduction' }],
212
+ },
213
+ },
214
+ },
215
+ },
216
+ logic_tree: [
217
+ {
218
+ id: 'calc_effective_rate',
219
+ when: { '>': [{ var: 'taxable_income' }, 0] },
220
+ then: {
221
+ set: {
222
+ effective_tax_rate: {
223
+ '/': [
224
+ { '*': [{ var: 'taxable_income' }, 0.22] },
225
+ { var: 'gross_annual_income' },
226
+ ],
227
+ },
228
+ },
229
+ },
230
+ },
231
+ ],
232
+ };
233
+ const result = run(schema);
234
+ assert.ok(!result.error);
235
+ assert.ok(result.result);
236
+ // Check standard_deduction is 14600 for single
237
+ const stdDedDef = result.result.definitions.standard_deduction;
238
+ assert.ok(stdDedDef, 'Expected standard_deduction definition');
239
+ assert.strictEqual(stdDedDef.value, 14600);
240
+ // Check taxable_income is 70400 (85000 - 14600)
241
+ const taxableIncomeDef = result.result.definitions.taxable_income;
242
+ assert.ok(taxableIncomeDef, 'Expected taxable_income definition');
243
+ assert.strictEqual(taxableIncomeDef.value, 70400);
244
+ // Check effective_tax_rate is calculated (70400 * 0.22 / 85000 = ~0.182)
245
+ const effectiveRateDef = result.result.definitions.effective_tax_rate;
246
+ assert.ok(effectiveRateDef, 'Expected effective_tax_rate to be calculated');
247
+ const effectiveVal = effectiveRateDef.value;
248
+ assert.ok(effectiveVal > 0.18 && effectiveVal < 0.19, `Expected effective_tax_rate around 0.182, got ${effectiveVal}`);
249
+ });
250
+ });
251
+ // ===========================================================================
252
+ // Edge Cases & Complex Scenarios
253
+ // ===========================================================================
254
+ describe('Edge Cases', () => {
255
+ it('should handle chained derived fields', () => {
256
+ const schema = {
257
+ protocol: 'Test_v1',
258
+ schema_id: 'test',
259
+ definitions: {
260
+ base: { type: 'number', value: 100 },
261
+ },
262
+ state_model: {
263
+ inputs: ['base'],
264
+ derived: {
265
+ level1: { eval: { '*': [{ var: 'base' }, 2] } },
266
+ level2: { eval: { '*': [{ var: 'level1' }, 3] } },
267
+ },
268
+ },
269
+ };
270
+ const result = run(schema);
271
+ assert.ok(!result.error);
272
+ assert.ok(result.result);
273
+ // level1 = 100 * 2 = 200
274
+ const level1Def = result.result.definitions.level1;
275
+ assert.ok(level1Def);
276
+ assert.strictEqual(level1Def.value, 200);
277
+ // level2 = 200 * 3 = 600
278
+ const level2Def = result.result.definitions.level2;
279
+ assert.ok(level2Def);
280
+ assert.strictEqual(level2Def.value, 600);
281
+ });
282
+ it('should re-compute derived after logic modifies inputs', () => {
283
+ const schema = {
284
+ protocol: 'Test_v1',
285
+ schema_id: 'test',
286
+ definitions: {
287
+ discount_eligible: { type: 'boolean', value: false },
288
+ base_price: { type: 'number', value: 100 },
289
+ },
290
+ state_model: {
291
+ inputs: ['discount_eligible', 'base_price'],
292
+ derived: {
293
+ final_price: {
294
+ eval: {
295
+ if: [
296
+ { var: 'discount_eligible' },
297
+ { '*': [{ var: 'base_price' }, 0.9] },
298
+ { var: 'base_price' },
299
+ ],
300
+ },
301
+ },
302
+ },
303
+ },
304
+ logic_tree: [
305
+ {
306
+ id: 'apply_discount',
307
+ when: { '>': [{ var: 'base_price' }, 50] },
308
+ then: { set: { discount_eligible: true } },
309
+ },
310
+ ],
311
+ };
312
+ const result = run(schema);
313
+ assert.ok(!result.error);
314
+ assert.ok(result.result);
315
+ // discount_eligible should be true
316
+ const discountDef = result.result.definitions.discount_eligible;
317
+ assert.ok(discountDef);
318
+ assert.strictEqual(discountDef.value, true);
319
+ // final_price should be 90 (100 * 0.9) because discount_eligible was set by logic
320
+ const finalPriceDef = result.result.definitions.final_price;
321
+ assert.ok(finalPriceDef);
322
+ assert.strictEqual(finalPriceDef.value, 90);
323
+ });
324
+ it('should catch multiple required empty strings', () => {
325
+ const schema = {
326
+ protocol: 'Test_v1',
327
+ schema_id: 'test',
328
+ definitions: {
329
+ first_name: { type: 'string', value: '', required: true },
330
+ last_name: { type: 'string', value: '', required: true },
331
+ nickname: { type: 'string', value: '', required: false },
332
+ },
333
+ };
334
+ const result = run(schema);
335
+ assert.ok(!result.error);
336
+ assert.ok(result.result);
337
+ // Status should be INCOMPLETE
338
+ assert.strictEqual(result.result.status, 'INCOMPLETE');
339
+ // Should have errors for both first_name and last_name
340
+ assert.ok(result.result.errors && result.result.errors.length >= 2, 'Expected at least 2 errors');
341
+ });
342
+ it('should use derived value when definition has null value', () => {
343
+ const schema = {
344
+ protocol: 'Test_v1',
345
+ schema_id: 'test',
346
+ definitions: {
347
+ input_a: { type: 'number', value: 10 },
348
+ input_b: { type: 'number', value: 20 },
349
+ result: { type: 'number', value: null, readonly: true },
350
+ },
351
+ state_model: {
352
+ inputs: ['input_a', 'input_b'],
353
+ derived: {
354
+ result: { eval: { '+': [{ var: 'input_a' }, { var: 'input_b' }] } },
355
+ },
356
+ },
357
+ };
358
+ const result = run(schema);
359
+ assert.ok(!result.error);
360
+ assert.ok(result.result);
361
+ // result should be 30 (10 + 20), not null
362
+ const resultDef = result.result.definitions.result;
363
+ assert.ok(resultDef);
364
+ assert.strictEqual(resultDef.value, 30);
365
+ });
366
+ it('should compare two derived values in logic', () => {
367
+ const schema = {
368
+ protocol: 'Test_v1',
369
+ schema_id: 'test',
370
+ definitions: {
371
+ price_a: { type: 'number', value: 100 },
372
+ price_b: { type: 'number', value: 80 },
373
+ },
374
+ state_model: {
375
+ inputs: ['price_a', 'price_b'],
376
+ derived: {
377
+ discounted_a: { eval: { '*': [{ var: 'price_a' }, 0.8] } },
378
+ discounted_b: { eval: { '*': [{ var: 'price_b' }, 0.9] } },
379
+ },
380
+ },
381
+ logic_tree: [
382
+ {
383
+ id: 'compare_prices',
384
+ when: { '>': [{ var: 'discounted_a' }, { var: 'discounted_b' }] },
385
+ then: { set: { best_deal: 'B' } },
386
+ },
387
+ ],
388
+ };
389
+ const result = run(schema);
390
+ assert.ok(!result.error);
391
+ assert.ok(result.result);
392
+ // discounted_a = 100 * 0.8 = 80
393
+ // discounted_b = 80 * 0.9 = 72
394
+ // 80 > 72, so best_deal = "B"
395
+ const bestDealDef = result.result.definitions.best_deal;
396
+ assert.ok(bestDealDef, 'Expected best_deal to be set');
397
+ assert.strictEqual(bestDealDef.value, 'B');
398
+ });
399
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dlovans/tenet-core",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
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",
@@ -9,20 +9,13 @@
9
9
  ".": {
10
10
  "import": "./dist/index.js",
11
11
  "types": "./dist/index.d.ts"
12
- },
13
- "./lint": {
14
- "import": "./dist/lint.js",
15
- "types": "./dist/lint.d.ts"
16
12
  }
17
13
  },
18
14
  "files": [
19
- "dist",
20
- "wasm"
15
+ "dist"
21
16
  ],
22
17
  "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",
18
+ "build": "tsc",
26
19
  "test": "node --test dist/*.test.js",
27
20
  "prepublishOnly": "npm run build"
28
21
  },
@@ -32,10 +25,8 @@
32
25
  "reactive",
33
26
  "schema",
34
27
  "form",
35
- "wasm",
36
28
  "compliance",
37
- "temporal",
38
- "linter"
29
+ "temporal"
39
30
  ],
40
31
  "author": "Dlovan Sharif",
41
32
  "license": "MIT",
@@ -54,4 +45,4 @@
54
45
  "@types/node": "^25.0.9",
55
46
  "typescript": "^5.0.0"
56
47
  }
57
- }
48
+ }
package/dist/lint.d.ts DELETED
@@ -1,31 +0,0 @@
1
- /**
2
- * Tenet Linter - Static Analysis for Tenet Schemas
3
- *
4
- * Pure TypeScript implementation - no WASM required.
5
- * Can be used in browsers, Node.js, and edge runtimes.
6
- */
7
- import type { TenetSchema } from './index';
8
- export declare const SCHEMA_URL = "https://tenet.dev/schema/v1.json";
9
- export interface LintIssue {
10
- severity: 'error' | 'warning' | 'info';
11
- field?: string;
12
- rule?: string;
13
- message: string;
14
- }
15
- export interface LintResult {
16
- valid: boolean;
17
- issues: LintIssue[];
18
- }
19
- /**
20
- * Perform static analysis on a Tenet schema without executing it.
21
- * Detects potential issues like undefined variables, cycles, and missing fields.
22
- *
23
- * @param schema - The schema object or JSON string
24
- * @returns Lint result with issues found
25
- */
26
- export declare function lint(schema: TenetSchema | string): LintResult;
27
- /**
28
- * Check if a schema is a valid Tenet schema (basic detection).
29
- * Useful for IDE integration to detect Tenet files.
30
- */
31
- export declare function isTenetSchema(schema: unknown): schema is TenetSchema;
package/dist/lint.js DELETED
@@ -1,160 +0,0 @@
1
- /**
2
- * Tenet Linter - Static Analysis for Tenet Schemas
3
- *
4
- * Pure TypeScript implementation - no WASM required.
5
- * Can be used in browsers, Node.js, and edge runtimes.
6
- */
7
- // JSON Schema URL for IDE integration
8
- export const SCHEMA_URL = 'https://tenet.dev/schema/v1.json';
9
- /**
10
- * Perform static analysis on a Tenet schema without executing it.
11
- * Detects potential issues like undefined variables, cycles, and missing fields.
12
- *
13
- * @param schema - The schema object or JSON string
14
- * @returns Lint result with issues found
15
- */
16
- export function lint(schema) {
17
- let parsed;
18
- try {
19
- parsed = typeof schema === 'string' ? JSON.parse(schema) : schema;
20
- }
21
- catch (e) {
22
- return {
23
- valid: false,
24
- issues: [{ severity: 'error', message: `Parse error: ${e}` }]
25
- };
26
- }
27
- const result = {
28
- valid: true,
29
- issues: []
30
- };
31
- // Collect all defined field names
32
- const definedFields = new Set();
33
- if (parsed.definitions) {
34
- for (const name of Object.keys(parsed.definitions)) {
35
- definedFields.add(name);
36
- }
37
- }
38
- // Add derived fields
39
- if (parsed.state_model?.derived) {
40
- for (const name of Object.keys(parsed.state_model.derived)) {
41
- definedFields.add(name);
42
- }
43
- }
44
- // Check 1: Schema identification
45
- if (!parsed.protocol && !parsed['$schema']) {
46
- result.issues.push({
47
- severity: 'info',
48
- message: `Consider adding "protocol": "Tenet_v1.0" or "$schema": "${SCHEMA_URL}" for IDE support`
49
- });
50
- }
51
- // Check 2: Undefined variables in logic tree
52
- if (parsed.logic_tree) {
53
- for (const rule of parsed.logic_tree) {
54
- if (!rule)
55
- continue;
56
- const varsInWhen = extractVars(rule.when);
57
- for (const v of varsInWhen) {
58
- if (!definedFields.has(v)) {
59
- addError(result, v, rule.id, `Undefined variable '${v}' in rule condition`);
60
- }
61
- }
62
- }
63
- }
64
- // Check 3: Potential cycles (fields set by multiple rules)
65
- const fieldSetBy = new Map();
66
- if (parsed.logic_tree) {
67
- for (const rule of parsed.logic_tree) {
68
- if (!rule?.then?.set)
69
- continue;
70
- for (const field of Object.keys(rule.then.set)) {
71
- const rules = fieldSetBy.get(field) || [];
72
- rules.push(rule.id);
73
- fieldSetBy.set(field, rules);
74
- }
75
- }
76
- }
77
- for (const [field, rules] of fieldSetBy) {
78
- if (rules.length > 1) {
79
- addWarning(result, field, '', `Field '${field}' may be set by multiple rules: [${rules.sort().join(', ')}] (potential cycle)`);
80
- }
81
- }
82
- // Check 4: Temporal map validation
83
- if (parsed.temporal_map) {
84
- for (let i = 0; i < parsed.temporal_map.length; i++) {
85
- const branch = parsed.temporal_map[i];
86
- if (!branch)
87
- continue;
88
- if (!branch.logic_version) {
89
- addWarning(result, '', '', `Temporal branch ${i} has no logic_version`);
90
- }
91
- }
92
- }
93
- // Check 5: Empty type in definitions
94
- if (parsed.definitions) {
95
- for (const [name, def] of Object.entries(parsed.definitions)) {
96
- if (!def)
97
- continue;
98
- if (!def.type) {
99
- addWarning(result, name, '', `Definition '${name}' has no type specified`);
100
- }
101
- }
102
- }
103
- return result;
104
- }
105
- /**
106
- * Check if a schema is a valid Tenet schema (basic detection).
107
- * Useful for IDE integration to detect Tenet files.
108
- */
109
- export function isTenetSchema(schema) {
110
- if (typeof schema !== 'object' || schema === null)
111
- return false;
112
- const obj = schema;
113
- // Check for $schema URL
114
- if (obj['$schema'] === SCHEMA_URL)
115
- return true;
116
- // Check for protocol field
117
- if (typeof obj.protocol === 'string' && obj.protocol.startsWith('Tenet'))
118
- return true;
119
- // Check for definitions + logic_tree structure
120
- if (obj.definitions && typeof obj.definitions === 'object')
121
- return true;
122
- return false;
123
- }
124
- // Helper functions
125
- function addError(result, field, rule, message) {
126
- result.valid = false;
127
- result.issues.push({ severity: 'error', field, rule, message });
128
- }
129
- function addWarning(result, field, rule, message) {
130
- result.issues.push({ severity: 'warning', field, rule, message });
131
- }
132
- /**
133
- * Extract all variable references from a JSON-logic expression.
134
- */
135
- function extractVars(node) {
136
- if (node === null || node === undefined)
137
- return [];
138
- const vars = [];
139
- if (typeof node === 'object') {
140
- if (Array.isArray(node)) {
141
- for (const elem of node) {
142
- vars.push(...extractVars(elem));
143
- }
144
- }
145
- else {
146
- const obj = node;
147
- // Check if this is a var reference
148
- if ('var' in obj && typeof obj.var === 'string') {
149
- // Get root variable name (before any dot notation)
150
- const varName = obj.var.split('.')[0];
151
- vars.push(varName);
152
- }
153
- // Recurse into all values
154
- for (const val of Object.values(obj)) {
155
- vars.push(...extractVars(val));
156
- }
157
- }
158
- }
159
- return vars;
160
- }
package/wasm/tenet.wasm DELETED
Binary file