eyeling 1.21.4 → 1.21.5

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.
@@ -56,8 +56,8 @@
56
56
  },
57
57
  "integrity": {
58
58
  "canonicalEnvelope": "{\"insight\":{\"createdAt\":\"2026-04-08T07:00:00+00:00\",\"expiresAt\":\"2026-04-08T19:00:00+00:00\",\"id\":\"https://example.org/insight/flandor\",\"metric\":\"regional_retooling_priority\",\"region\":\"Flanders\",\"scopeDevice\":\"economic-resilience-board\",\"scopeEvent\":\"budget-prep-window\",\"suggestionPolicy\":\"lowest_cost_package_covering_all_active_needs\",\"threshold\":3,\"type\":\"ins:Insight\"},\"policy\":{\"duty\":{\"action\":\"odrl:delete\",\"constraint\":{\"leftOperand\":\"odrl:dateTime\",\"operator\":\"odrl:eq\",\"rightOperand\":\"2026-04-08T19:00:00+00:00\"}},\"permission\":{\"action\":\"odrl:use\",\"constraint\":{\"leftOperand\":\"odrl:purpose\",\"operator\":\"odrl:eq\",\"rightOperand\":\"regional_stabilization\"},\"target\":\"https://example.org/insight/flandor\"},\"profile\":\"Flandor-Insight-Policy\",\"prohibition\":{\"action\":\"odrl:distribute\",\"constraint\":{\"leftOperand\":\"odrl:purpose\",\"operator\":\"odrl:eq\",\"rightOperand\":\"firm_surveillance\"},\"target\":\"https://example.org/insight/flandor\"},\"type\":\"odrl:Policy\"}}",
59
- "payloadHashSHA256": "718f5b17d07ab6a95503bc04a1000ddb132409f600659c03d21def81914b780b",
60
- "envelopeHmacSHA256": "955968ca99a191783bc00cba068128ccb9ff40a5e6114fda13a52c74ee27329e",
59
+ "payloadHashSHA256": "78d840cbc98e98d07d97498e8bc1bab716df0fc0c7c221aeaa86644157cfcfb8",
60
+ "envelopeHmacSHA256": "a907547046c46af6550635d786c1ea82c05e8c119c0c302c132eb799710df96e",
61
61
  "verificationMode": "trustedPrecomputedInput"
62
62
  },
63
63
  "answer": {
@@ -69,15 +69,15 @@
69
69
  "recommendedPackage": "Flandor Retooling Pulse",
70
70
  "budgetCapMEUR": 140,
71
71
  "packageCostMEUR": 120,
72
- "payloadHashSHA256": "718f5b17d07ab6a95503bc04a1000ddb132409f600659c03d21def81914b780b",
73
- "envelopeHmacSHA256": "955968ca99a191783bc00cba068128ccb9ff40a5e6114fda13a52c74ee27329e"
72
+ "payloadHashSHA256": "78d840cbc98e98d07d97498e8bc1bab716df0fc0c7c221aeaa86644157cfcfb8",
73
+ "envelopeHmacSHA256": "a907547046c46af6550635d786c1ea82c05e8c119c0c302c132eb799710df96e"
74
74
  },
75
75
  "reasonWhy": [
76
76
  "ExportWeakness holds because at least one cluster has exportOrdersIndex < 90 (Antwerp chemicals=84, Ghent manufacturing=87).",
77
77
  "SkillsStrain holds because the technical vacancy rate is 4.6% and the threshold is > 3.9%.",
78
78
  "GridStress holds because congestion hours = 19 and the threshold is > 11.",
79
79
  "The recommendation rule selects the least-cost package that covers every active need and remains within budget.",
80
- "The selected package is \"Flandor Retooling Pulse\" with cost \u20ac120M, workerCoverage=1200, gridReliefMW=85.",
80
+ "The selected package is \"Flandor Retooling Pulse\" with cost €120M, workerCoverage=1200, gridReliefMW=85.",
81
81
  "Use is permitted only for purpose \"regional_stabilization\" and expires at 2026-04-08T19:00:00+00:00."
82
82
  ],
83
83
  "checks": {
@@ -94,5 +94,5 @@
94
94
  "lowestCostEligiblePackageChosen": true
95
95
  },
96
96
  "allChecksPass": true,
97
- "arcText": "=== Answer ===\nName: Flandor\nRegion: Flanders\nMetric: regional_retooling_priority\nActive need count: 3/3\nRecommended package: Flandor Retooling Pulse\nBudget cap: \u20ac140M\nPackage cost: \u20ac120M\nPayload SHA-256: 718f5b17d07ab6a95503bc04a1000ddb132409f600659c03d21def81914b780b\nEnvelope HMAC-SHA-256: 955968ca99a191783bc00cba068128ccb9ff40a5e6114fda13a52c74ee27329e\n\n=== Reason Why ===\nExportWeakness holds because at least one cluster has exportOrdersIndex < 90 (Antwerp chemicals=84, Ghent manufacturing=87).\nSkillsStrain holds because the technical vacancy rate is 4.6% and the threshold is > 3.9%.\nGridStress holds because congestion hours = 19 and the threshold is > 11.\nThe recommendation rule selects the least-cost package that covers every active need and remains within budget.\nThe selected package is \"Flandor Retooling Pulse\" with cost \u20ac120M, workerCoverage=1200, gridReliefMW=85.\nUse is permitted only for purpose \"regional_stabilization\" and expires at 2026-04-08T19:00:00+00:00.\n\n=== Check ===\n- PASS: payloadHashMatches\n- PASS: signatureVerifies\n- PASS: thresholdReached\n- PASS: scopeComplete\n- PASS: minimizationRespected\n- PASS: authorizationAllowed\n- PASS: dutyTimely\n- PASS: surveillanceReuseProhibited\n- PASS: packageWithinBudget\n- PASS: packageCoversAllActiveNeeds\n- PASS: lowestCostEligiblePackageChosen"
97
+ "arcText": "=== Answer ===\nName: Flandor\nRegion: Flanders\nMetric: regional_retooling_priority\nActive need count: 3/3\nRecommended package: Flandor Retooling Pulse\nBudget cap: €140M\nPackage cost: €120M\nPayload SHA-256: 78d840cbc98e98d07d97498e8bc1bab716df0fc0c7c221aeaa86644157cfcfb8\nEnvelope HMAC-SHA-256: a907547046c46af6550635d786c1ea82c05e8c119c0c302c132eb799710df96e\n\n=== Reason Why ===\nExportWeakness holds because at least one cluster has exportOrdersIndex < 90 (Antwerp chemicals=84, Ghent manufacturing=87).\nSkillsStrain holds because the technical vacancy rate is 4.6% and the threshold is > 3.9%.\nGridStress holds because congestion hours = 19 and the threshold is > 11.\nThe recommendation rule selects the least-cost package that covers every active need and remains within budget.\nThe selected package is \"Flandor Retooling Pulse\" with cost €120M, workerCoverage=1200, gridReliefMW=85.\nUse is permitted only for purpose \"regional_stabilization\" and expires at 2026-04-08T19:00:00+00:00.\n\n=== Check ===\n- PASS: payloadHashMatches\n- PASS: signatureVerifies\n- PASS: thresholdReached\n- PASS: scopeComplete\n- PASS: minimizationRespected\n- PASS: authorizationAllowed\n- PASS: dutyTimely\n- PASS: surveillanceReuseProhibited\n- PASS: packageWithinBudget\n- PASS: packageCoversAllActiveNeeds\n- PASS: lowestCostEligiblePackageChosen"
98
98
  }
@@ -6,8 +6,8 @@ Active need count: 3/3
6
6
  Recommended package: Flandor Retooling Pulse
7
7
  Budget cap: €140M
8
8
  Package cost: €120M
9
- Payload SHA-256: 718f5b17d07ab6a95503bc04a1000ddb132409f600659c03d21def81914b780b
10
- Envelope HMAC-SHA-256: 955968ca99a191783bc00cba068128ccb9ff40a5e6114fda13a52c74ee27329e
9
+ Payload SHA-256: 78d840cbc98e98d07d97498e8bc1bab716df0fc0c7c221aeaa86644157cfcfb8
10
+ Envelope HMAC-SHA-256: a907547046c46af6550635d786c1ea82c05e8c119c0c302c132eb799710df96e
11
11
 
12
12
  === Reason Why ===
13
13
  Export weakness is active because at least one cluster has exportOrdersIndex < 90 (Antwerp chemicals=84, Ghent manufacturing=87).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.21.4",
3
+ "version": "1.21.5",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
@@ -45,12 +45,12 @@
45
45
  "test:n3gen": "node test/n3gen.test.js",
46
46
  "test:examples": "node test/examples.test.js",
47
47
  "test:extra": "node test/extra.test.js",
48
+ "test:bridge": "node test/bridge.test.js",
48
49
  "test:manifest": "node test/manifest.test.js",
49
50
  "test:playground": "node test/playground.test.js",
50
51
  "test:package": "node test/package.test.js",
51
- "test:all": "npm run test:api && npm run test:builtins && npm run test:n3gen && npm run test:examples && npm run test:extra && npm run test:manifest && npm run test:playground",
52
52
  "pretest": "npm run build && npm run test:packlist",
53
- "test": "npm run test:all",
53
+ "test": "npm run test:api && npm run test:builtins && npm run test:n3gen && npm run test:examples && npm run test:extra && npm run test:bridge && npm run test:manifest && npm run test:playground",
54
54
  "posttest": "npm run test:package",
55
55
  "preversion": "npm test",
56
56
  "postversion": "git push origin HEAD --follow-tags"
@@ -0,0 +1,134 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const assert = require('node:assert/strict');
6
+ const { pathToFileURL } = require('node:url');
7
+
8
+ const TTY = process.stdout.isTTY;
9
+ const C = TTY
10
+ ? { g: '\x1b[32m', r: '\x1b[31m', y: '\x1b[33m', dim: '\x1b[2m', n: '\x1b[0m' }
11
+ : { g: '', r: '', y: '', dim: '', n: '' };
12
+
13
+ function ok(msg) {
14
+ console.log(`${C.g}OK ${C.n} ${msg}`);
15
+ }
16
+ function info(msg) {
17
+ console.log(`${C.y}==${C.n} ${msg}`);
18
+ }
19
+ function fail(msg) {
20
+ console.error(`${C.r}FAIL${C.n} ${msg}`);
21
+ }
22
+
23
+ const ROOT = path.resolve(__dirname, '..');
24
+ const BRIDGE_DIR = path.join(ROOT, 'examples', 'arc-bridge');
25
+
26
+ function isDirectory(p) {
27
+ try {
28
+ return fs.statSync(p).isDirectory();
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ function readJson(filePath) {
35
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
36
+ }
37
+
38
+ function listCaseDirs(baseDir) {
39
+ if (!isDirectory(baseDir)) {
40
+ throw new Error(`Bridge directory not found: ${baseDir}`);
41
+ }
42
+
43
+ return fs
44
+ .readdirSync(baseDir)
45
+ .map((name) => path.join(baseDir, name))
46
+ .filter(isDirectory)
47
+ .sort();
48
+ }
49
+
50
+ function findCaseFiles(caseDir) {
51
+ const base = path.basename(caseDir);
52
+
53
+ const modelPath = path.join(caseDir, `${base}.model.mjs`);
54
+ const dataPath = path.join(caseDir, `${base}.data.json`);
55
+ const expectedPath = path.join(caseDir, `${base}.expected.json`);
56
+
57
+ for (const required of [modelPath, dataPath, expectedPath]) {
58
+ if (!fs.existsSync(required)) {
59
+ throw new Error(`Missing required bridge artifact: ${required}`);
60
+ }
61
+ }
62
+
63
+ return { base, modelPath, dataPath, expectedPath };
64
+ }
65
+
66
+ async function loadEvaluate(modelPath) {
67
+ const moduleUrl = pathToFileURL(modelPath).href;
68
+ const mod = await import(moduleUrl);
69
+
70
+ if (typeof mod.evaluate !== 'function') {
71
+ throw new Error(`Model does not export evaluate(data): ${modelPath}`);
72
+ }
73
+
74
+ return mod.evaluate;
75
+ }
76
+
77
+ function assertArcTextShape(arcText, label) {
78
+ assert.equal(typeof arcText, 'string', `${label}: arcText must be a string`);
79
+ assert.match(arcText, /=== Answer ===/, `${label}: missing Answer section`);
80
+ assert.match(arcText, /=== Reason Why ===/, `${label}: missing Reason Why section`);
81
+ assert.match(arcText, /=== Check ===/, `${label}: missing Check section`);
82
+ }
83
+
84
+ async function runCase(caseDir) {
85
+ const { base, modelPath, dataPath, expectedPath } = findCaseFiles(caseDir);
86
+
87
+ const evaluate = await loadEvaluate(modelPath);
88
+ const data = readJson(dataPath);
89
+ const expected = readJson(expectedPath);
90
+ const actual = await evaluate(data);
91
+
92
+ assert.equal(actual.allChecksPass, true, `${base}: expected allChecksPass === true`);
93
+
94
+ assertArcTextShape(actual.arcText, base);
95
+
96
+ assert.deepStrictEqual(actual, expected, `${base}: actual result does not match expected JSON`);
97
+
98
+ return base;
99
+ }
100
+
101
+ async function main() {
102
+ const caseDirs = listCaseDirs(BRIDGE_DIR);
103
+
104
+ if (caseDirs.length === 0) {
105
+ throw new Error(`No bridge cases found in ${BRIDGE_DIR}`);
106
+ }
107
+
108
+ info(`bridge tests: ${caseDirs.length} case(s)`);
109
+
110
+ let passed = 0;
111
+
112
+ for (let i = 0; i < caseDirs.length; i += 1) {
113
+ const caseDir = caseDirs[i];
114
+ const n = i + 1;
115
+ const label = path.basename(caseDir);
116
+
117
+ try {
118
+ await runCase(caseDir);
119
+ passed += 1;
120
+ ok(`${n}. ${label}`);
121
+ } catch (error) {
122
+ fail(`${n}. ${label}`);
123
+ fail(error.stack || String(error));
124
+ process.exit(2);
125
+ }
126
+ }
127
+
128
+ info(`all ${passed} bridge test(s) passed`);
129
+ }
130
+
131
+ main().catch((error) => {
132
+ fail(error.stack || String(error));
133
+ process.exit(2);
134
+ });