@unrdf/hooks 26.4.2 → 26.4.4
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/LICENSE +24 -0
- package/README.md +562 -53
- package/examples/atomvm-fibo-hooks-demo.mjs +323 -0
- package/examples/delta-monitoring-example.mjs +213 -0
- package/examples/fibo-jtbd-governance.mjs +388 -0
- package/examples/hook-chains/node_modules/.bin/jiti +21 -0
- package/examples/hook-chains/node_modules/.bin/msw +21 -0
- package/examples/hook-chains/node_modules/.bin/terser +21 -0
- package/examples/hook-chains/node_modules/.bin/tsc +21 -0
- package/examples/hook-chains/node_modules/.bin/tsserver +21 -0
- package/examples/hook-chains/node_modules/.bin/tsx +21 -0
- package/examples/hook-chains/node_modules/.bin/vite +21 -0
- package/examples/hook-chains/node_modules/.bin/vitest +2 -2
- package/examples/hook-chains/node_modules/.bin/yaml +21 -0
- package/examples/hook-chains/package.json +2 -2
- package/examples/hook-chains/unrdf-hooks-example-chains-5.0.0.tgz +0 -0
- package/examples/hooks-marketplace.mjs +261 -0
- package/examples/n3-reasoning-example.mjs +279 -0
- package/examples/policy-hooks/node_modules/.bin/jiti +21 -0
- package/examples/policy-hooks/node_modules/.bin/msw +21 -0
- package/examples/policy-hooks/node_modules/.bin/terser +21 -0
- package/examples/policy-hooks/node_modules/.bin/tsc +21 -0
- package/examples/policy-hooks/node_modules/.bin/tsserver +21 -0
- package/examples/policy-hooks/node_modules/.bin/tsx +21 -0
- package/examples/policy-hooks/node_modules/.bin/vite +21 -0
- package/examples/policy-hooks/node_modules/.bin/vitest +2 -2
- package/examples/policy-hooks/node_modules/.bin/yaml +21 -0
- package/examples/policy-hooks/package.json +2 -2
- package/examples/policy-hooks/unrdf-hooks-example-policy-5.0.0.tgz +0 -0
- package/examples/shacl-repair-example.mjs +191 -0
- package/examples/window-condition-example.mjs +285 -0
- package/package.json +6 -3
- package/src/atomvm.mjs +9 -0
- package/src/define.mjs +114 -0
- package/src/executor.mjs +23 -0
- package/src/hooks/atomvm-bridge.mjs +332 -0
- package/src/hooks/builtin-hooks.mjs +13 -7
- package/src/hooks/condition-evaluator.mjs +684 -77
- package/src/hooks/define-hook.mjs +23 -21
- package/src/hooks/effect-executor.mjs +630 -0
- package/src/hooks/effect-sandbox.mjs +19 -9
- package/src/hooks/file-resolver.mjs +155 -1
- package/src/hooks/hook-chain-compiler.mjs +11 -1
- package/src/hooks/hook-executor.mjs +98 -73
- package/src/hooks/knowledge-hook-engine.mjs +133 -7
- package/src/hooks/ontology-learner.mjs +190 -0
- package/src/hooks/policy-pack.mjs +7 -1
- package/src/hooks/query-optimizer.mjs +1 -5
- package/src/hooks/query.mjs +3 -3
- package/src/hooks/schemas.mjs +55 -5
- package/src/hooks/security/error-sanitizer.mjs +46 -24
- package/src/hooks/self-play-autonomics.mjs +423 -0
- package/src/hooks/telemetry.mjs +32 -9
- package/src/hooks/validate.mjs +100 -33
- package/src/index.mjs +2 -0
- package/src/lib/admit-hook.mjs +615 -0
- package/src/policy-compiler.mjs +23 -13
- package/dist/index.d.mts +0 -1738
- package/dist/index.d.ts +0 -1738
- package/dist/index.mjs +0 -1738
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @unrdf/hooks - AtomVM/Erlang Integration Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the HooksBridge for executing knowledge hooks
|
|
5
|
+
* from Erlang/BEAM processes via AtomVM.
|
|
6
|
+
*
|
|
7
|
+
* This bridges two distributed systems:
|
|
8
|
+
* - Erlang: Financial transaction processing (hot path, concurrent)
|
|
9
|
+
* - JavaScript (Node.js + oxigraph): Knowledge hooks (governance, validation)
|
|
10
|
+
*
|
|
11
|
+
* Use Case: Real-time trade validation in Erlang financial exchange
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createStore, namedNode, literal } from '@unrdf/oxigraph';
|
|
15
|
+
import { createHooksBridge } from '../src/atomvm.mjs';
|
|
16
|
+
import { createContext } from '@unrdf/v6-core/receipt-pattern';
|
|
17
|
+
|
|
18
|
+
const FIBO_NS = 'https://spec.edmcouncil.org/fibo/ontology/';
|
|
19
|
+
const EX_NS = 'http://example.org/';
|
|
20
|
+
|
|
21
|
+
console.log('═'.repeat(70));
|
|
22
|
+
console.log('AtomVM/Erlang Integration - FIBO Hooks Demo');
|
|
23
|
+
console.log('═'.repeat(70));
|
|
24
|
+
|
|
25
|
+
// Initialize store and bridge
|
|
26
|
+
console.log('\n1️⃣ Initializing HooksBridge...');
|
|
27
|
+
const store = createStore();
|
|
28
|
+
const bridge = createHooksBridge(store, {
|
|
29
|
+
persistReceipts: true,
|
|
30
|
+
maxHooks: 100,
|
|
31
|
+
});
|
|
32
|
+
console.log('✅ Bridge initialized');
|
|
33
|
+
|
|
34
|
+
// Simulate registration from Erlang processes
|
|
35
|
+
console.log('\n2️⃣ Registering Hooks (Erlang → Bridge)...');
|
|
36
|
+
|
|
37
|
+
(async () => {
|
|
38
|
+
try {
|
|
39
|
+
// Hook 1: Basic ASK condition for any trade
|
|
40
|
+
const hook1Id = await bridge.registerHook({
|
|
41
|
+
name: 'erlang-verify-trade',
|
|
42
|
+
condition: {
|
|
43
|
+
kind: 'sparql-ask',
|
|
44
|
+
query: `
|
|
45
|
+
PREFIX ex: <${EX_NS}>
|
|
46
|
+
ASK {
|
|
47
|
+
?trade a ex:Trade ;
|
|
48
|
+
ex:amount ?amt .
|
|
49
|
+
FILTER (?amt > 0)
|
|
50
|
+
}
|
|
51
|
+
`,
|
|
52
|
+
},
|
|
53
|
+
effects: [
|
|
54
|
+
{
|
|
55
|
+
kind: 'sparql-construct',
|
|
56
|
+
query: `
|
|
57
|
+
PREFIX ex: <${EX_NS}>
|
|
58
|
+
CONSTRUCT {
|
|
59
|
+
?trade ex:bridgeVerified true ;
|
|
60
|
+
ex:verificationTime ?now .
|
|
61
|
+
}
|
|
62
|
+
WHERE {
|
|
63
|
+
?trade a ex:Trade .
|
|
64
|
+
BIND (NOW() as ?now)
|
|
65
|
+
}
|
|
66
|
+
`,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
metadata: {
|
|
70
|
+
source: 'erlang-process-1',
|
|
71
|
+
version: '1.0',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
console.log(`✅ Registered hook 1: ID=${hook1Id}`);
|
|
76
|
+
|
|
77
|
+
// Hook 2: Datalog-based risk assessment
|
|
78
|
+
const hook2Id = await bridge.registerHook({
|
|
79
|
+
name: 'erlang-assess-risk',
|
|
80
|
+
condition: {
|
|
81
|
+
kind: 'datalog',
|
|
82
|
+
facts: [
|
|
83
|
+
'trader("alice")',
|
|
84
|
+
'trader("bob")',
|
|
85
|
+
'authorized("alice")',
|
|
86
|
+
'position("alice", 100000)',
|
|
87
|
+
'position("bob", 50000)',
|
|
88
|
+
],
|
|
89
|
+
rules: [
|
|
90
|
+
'can_trade(T) :- trader(T), authorized(T)',
|
|
91
|
+
'large_position(T) :- position(T, P), P >= 100000',
|
|
92
|
+
'needs_review(T) :- large_position(T), can_trade(T)',
|
|
93
|
+
],
|
|
94
|
+
goal: 'needs_review("alice")',
|
|
95
|
+
},
|
|
96
|
+
effects: [
|
|
97
|
+
{
|
|
98
|
+
kind: 'sparql-construct',
|
|
99
|
+
query: `
|
|
100
|
+
PREFIX ex: <${EX_NS}>
|
|
101
|
+
CONSTRUCT {
|
|
102
|
+
?trader ex:riskReviewRequired true ;
|
|
103
|
+
ex:riskLevel ex:High .
|
|
104
|
+
}
|
|
105
|
+
WHERE {
|
|
106
|
+
?trader a ex:Trader .
|
|
107
|
+
}
|
|
108
|
+
`,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
metadata: {
|
|
112
|
+
source: 'erlang-process-2',
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
console.log(`✅ Registered hook 2: ID=${hook2Id}`);
|
|
117
|
+
|
|
118
|
+
// Hook 3: N3 inference rules for compliance
|
|
119
|
+
const hook3Id = await bridge.registerHook({
|
|
120
|
+
name: 'erlang-infer-compliance',
|
|
121
|
+
condition: {
|
|
122
|
+
kind: 'n3',
|
|
123
|
+
rules: `
|
|
124
|
+
PREFIX ex: <${EX_NS}>
|
|
125
|
+
|
|
126
|
+
{ ?trade ex:amount ?amt . ?amt math:lessThan 1000000 }
|
|
127
|
+
=>
|
|
128
|
+
{ ?trade ex:complianceLevel ex:Standard } .
|
|
129
|
+
|
|
130
|
+
{ ?trade ex:amount ?amt . ?amt math:greaterThanOrEqual 1000000 }
|
|
131
|
+
=>
|
|
132
|
+
{ ?trade ex:complianceLevel ex:Enhanced } .
|
|
133
|
+
|
|
134
|
+
{ ?trade ex:complianceLevel ex:Enhanced }
|
|
135
|
+
=>
|
|
136
|
+
{ ?trade ex:requiresAudit true } .
|
|
137
|
+
`,
|
|
138
|
+
askQuery: `
|
|
139
|
+
PREFIX ex: <${EX_NS}>
|
|
140
|
+
ASK { ?trade ex:requiresAudit true }
|
|
141
|
+
`,
|
|
142
|
+
},
|
|
143
|
+
effects: [
|
|
144
|
+
{
|
|
145
|
+
kind: 'sparql-construct',
|
|
146
|
+
query: `
|
|
147
|
+
PREFIX ex: <${EX_NS}>
|
|
148
|
+
CONSTRUCT {
|
|
149
|
+
?trade ex:auditInitiated true ;
|
|
150
|
+
ex:auditTimestamp ?now .
|
|
151
|
+
}
|
|
152
|
+
WHERE {
|
|
153
|
+
?trade a ex:Trade .
|
|
154
|
+
BIND (NOW() as ?now)
|
|
155
|
+
}
|
|
156
|
+
`,
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
metadata: {
|
|
160
|
+
source: 'erlang-process-3',
|
|
161
|
+
reasoning: 'n3-eye-reasoner',
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
console.log(`✅ Registered hook 3: ID=${hook3Id}`);
|
|
166
|
+
|
|
167
|
+
// List all registered hooks
|
|
168
|
+
console.log('\n3️⃣ Listing Registered Hooks...');
|
|
169
|
+
const hooks = bridge.listHooks();
|
|
170
|
+
console.log(`📋 Total hooks registered: ${hooks.length}`);
|
|
171
|
+
hooks.forEach((h, i) => {
|
|
172
|
+
console.log(` ${i + 1}. ${h.name} (ID: ${h.id})`);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Simulate data from Erlang
|
|
176
|
+
console.log('\n4️⃣ Loading Trade Data...');
|
|
177
|
+
const tradeQuads = [
|
|
178
|
+
[
|
|
179
|
+
namedNode(`${EX_NS}trade/erlang-trade-001`),
|
|
180
|
+
namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
|
|
181
|
+
namedNode(`${EX_NS}Trade`),
|
|
182
|
+
],
|
|
183
|
+
[
|
|
184
|
+
namedNode(`${EX_NS}trade/erlang-trade-001`),
|
|
185
|
+
namedNode(`${EX_NS}amount`),
|
|
186
|
+
literal('500000'),
|
|
187
|
+
],
|
|
188
|
+
[
|
|
189
|
+
namedNode(`${EX_NS}trade/erlang-trade-001`),
|
|
190
|
+
namedNode(`${EX_NS}status`),
|
|
191
|
+
namedNode(`${EX_NS}Active`),
|
|
192
|
+
],
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
tradeQuads.forEach(([s, p, o]) => {
|
|
196
|
+
store.insert([s, p, o], namedNode(`${EX_NS}default`));
|
|
197
|
+
});
|
|
198
|
+
console.log(`✅ Loaded ${tradeQuads.length} quads`);
|
|
199
|
+
|
|
200
|
+
// Evaluate conditions from Erlang
|
|
201
|
+
console.log('\n5️⃣ Evaluating Conditions from Erlang...');
|
|
202
|
+
|
|
203
|
+
const sparqlAskResult = await bridge.evaluateCondition({
|
|
204
|
+
kind: 'sparql-ask',
|
|
205
|
+
query: `
|
|
206
|
+
PREFIX ex: <${EX_NS}>
|
|
207
|
+
ASK {
|
|
208
|
+
?trade a ex:Trade ;
|
|
209
|
+
ex:amount ?amt .
|
|
210
|
+
FILTER (?amt > 0)
|
|
211
|
+
}
|
|
212
|
+
`,
|
|
213
|
+
});
|
|
214
|
+
console.log(`✅ SPARQL ASK result: ${sparqlAskResult ? '✓ True' : '✗ False'}`);
|
|
215
|
+
|
|
216
|
+
const datalogResult = await bridge.evaluateCondition({
|
|
217
|
+
kind: 'datalog',
|
|
218
|
+
facts: ['trade("erlang-trade-001")'],
|
|
219
|
+
rules: ['valid(T) :- trade(T)'],
|
|
220
|
+
goal: 'valid("erlang-trade-001")',
|
|
221
|
+
});
|
|
222
|
+
console.log(`✅ Datalog result: ${datalogResult ? '✓ True' : '✗ False'}`);
|
|
223
|
+
|
|
224
|
+
// Execute hooks with receipt chaining
|
|
225
|
+
console.log('\n6️⃣ Executing Hooks (Erlang → Bridge → Store)...');
|
|
226
|
+
|
|
227
|
+
const executionContext = createContext({
|
|
228
|
+
nodeId: 'erlang-trade-processor',
|
|
229
|
+
t_ns: BigInt(Date.now() * 1000000),
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const result = await bridge.executeHooks(executionContext, [
|
|
233
|
+
hook1Id,
|
|
234
|
+
hook2Id,
|
|
235
|
+
hook3Id,
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
console.log(`✅ Execution complete:`);
|
|
239
|
+
console.log(` Successful: ${result.successful}`);
|
|
240
|
+
console.log(` Failed: ${result.failed}`);
|
|
241
|
+
|
|
242
|
+
// Receipt chain (Priority 1 & 4)
|
|
243
|
+
console.log('\n7️⃣ Receipt Chain Verification...');
|
|
244
|
+
const receipt = result.receipt;
|
|
245
|
+
console.log(`✅ Receipt Hash: ${receipt.receiptHash.substring(0, 32)}...`);
|
|
246
|
+
console.log(` Input Hash: ${receipt.input_hash.substring(0, 32)}...`);
|
|
247
|
+
console.log(` Output Hash: ${receipt.output_hash.substring(0, 32)}...`);
|
|
248
|
+
console.log(` Node ID: ${receipt.nodeId}`);
|
|
249
|
+
|
|
250
|
+
// Verify receipt chain
|
|
251
|
+
const chain = bridge.getReceiptChain();
|
|
252
|
+
console.log(`\n8️⃣ Receipt Chain Status...`);
|
|
253
|
+
console.log(`📊 Total receipts: ${chain.length}`);
|
|
254
|
+
|
|
255
|
+
const verification = bridge.verifyReceiptChain();
|
|
256
|
+
if (verification.valid) {
|
|
257
|
+
console.log(`✅ Chain integrity verified`);
|
|
258
|
+
} else {
|
|
259
|
+
console.log(`❌ Chain broken at receipt ${verification.brokenAt}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// State changes
|
|
263
|
+
if (receipt.delta) {
|
|
264
|
+
console.log(`\n9️⃣ State Changes (Delta)`);
|
|
265
|
+
console.log(`📝 Additions: ${receipt.delta.adds.length} quads`);
|
|
266
|
+
console.log(`🗑️ Deletions: ${receipt.delta.deletes.length} quads`);
|
|
267
|
+
|
|
268
|
+
if (receipt.delta.adds.length > 0) {
|
|
269
|
+
console.log('\nSample additions:');
|
|
270
|
+
receipt.delta.adds.slice(0, 3).forEach((add) => {
|
|
271
|
+
console.log(` + ${add.subject} ${add.predicate} ${add.object}`);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Bridge statistics
|
|
277
|
+
console.log('\n🔟 Bridge Statistics');
|
|
278
|
+
const stats = bridge.getStats();
|
|
279
|
+
console.log(`📊 Hooks registered: ${stats.hookCount}`);
|
|
280
|
+
console.log(`📊 Receipt history: ${stats.receiptCount}`);
|
|
281
|
+
console.log(`📊 Memory usage: ${(stats.memoryBytes / 1024).toFixed(2)} KB`);
|
|
282
|
+
|
|
283
|
+
// Simulate multiple execution cycles (as would happen in Erlang)
|
|
284
|
+
console.log('\n1️⃣1️⃣ Simulating Multiple Execution Cycles...');
|
|
285
|
+
|
|
286
|
+
for (let i = 0; i < 2; i++) {
|
|
287
|
+
const ctx = createContext({
|
|
288
|
+
nodeId: 'erlang-trade-processor',
|
|
289
|
+
t_ns: BigInt(Date.now() * 1000000),
|
|
290
|
+
previousReceiptHash: i > 0 ? chain[chain.length - 1].receiptHash : undefined,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
await bridge.executeHooks(ctx, [hook1Id]);
|
|
294
|
+
const updatedChain = bridge.getReceiptChain();
|
|
295
|
+
console.log(`✅ Cycle ${i + 1}: ${updatedChain.length} receipts in chain`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Final summary
|
|
299
|
+
console.log('\n' + '═'.repeat(70));
|
|
300
|
+
console.log('✨ AtomVM Bridge Demo Complete!');
|
|
301
|
+
console.log('═'.repeat(70));
|
|
302
|
+
|
|
303
|
+
console.log('\n📋 Summary:');
|
|
304
|
+
console.log(' ✅ Hooks registered from Erlang');
|
|
305
|
+
console.log(' ✅ Conditions evaluated dynamically');
|
|
306
|
+
console.log(' ✅ Hooks executed with receipt chaining');
|
|
307
|
+
console.log(' ✅ Receipt chain integrity verified');
|
|
308
|
+
console.log(' ✅ State changes tracked (delta)');
|
|
309
|
+
console.log(' ✅ Multi-cycle execution demonstrates deterministic receipts');
|
|
310
|
+
|
|
311
|
+
console.log('\n💡 Next Steps:');
|
|
312
|
+
console.log(' 1. Connect via HTTP gateway to Erlang processes');
|
|
313
|
+
console.log(' 2. Implement Erlang gen_server for async hook evaluation');
|
|
314
|
+
console.log(' 3. Persist receipts to distributed ledger');
|
|
315
|
+
console.log(' 4. Scale to thousands of concurrent trades');
|
|
316
|
+
|
|
317
|
+
process.exit(0);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error('❌ Error:', error.message);
|
|
320
|
+
console.error(error.stack);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
})();
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @unrdf/hooks - Delta Monitoring Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates change detection using Delta conditions.
|
|
5
|
+
* Triggers rules when data grows, shrinks, or changes significantly.
|
|
6
|
+
*
|
|
7
|
+
* Use cases:
|
|
8
|
+
* - Validation on large imports (increase > 10%)
|
|
9
|
+
* - Anomaly detection on suspicious deletes
|
|
10
|
+
* - Rate limiting on rapid changes
|
|
11
|
+
* - Archive triggers on data growth
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createStore } from '@unrdf/oxigraph';
|
|
15
|
+
import { evaluateCondition } from '../src/hooks/condition-evaluator.mjs';
|
|
16
|
+
import { UnrdfDataFactory as DataFactory } from '@unrdf/core/rdf/n3-justified-only';
|
|
17
|
+
|
|
18
|
+
const { namedNode, literal, quad } = DataFactory;
|
|
19
|
+
|
|
20
|
+
console.log('=== Delta Monitoring Example ===\n');
|
|
21
|
+
|
|
22
|
+
// Utility: create a hook condition with delta spec
|
|
23
|
+
function createDeltaCondition(changeType, threshold = 0.1) {
|
|
24
|
+
return {
|
|
25
|
+
kind: 'delta',
|
|
26
|
+
spec: {
|
|
27
|
+
change: changeType,
|
|
28
|
+
threshold: threshold
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Example 1: Detect any change
|
|
34
|
+
console.log('1. Detect Any Change\n');
|
|
35
|
+
const anyChangeCondition = createDeltaCondition('any');
|
|
36
|
+
|
|
37
|
+
console.log('Condition:');
|
|
38
|
+
console.log(JSON.stringify(anyChangeCondition, null, 2));
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log('Triggers when: any quad is added or deleted');
|
|
41
|
+
console.log('Use for: audit trails, change notifications');
|
|
42
|
+
console.log('');
|
|
43
|
+
|
|
44
|
+
// Example 2: Detect data growth
|
|
45
|
+
console.log('2. Detect Data Growth (Increase)\n');
|
|
46
|
+
const growthCondition = createDeltaCondition('increase', 0.1);
|
|
47
|
+
|
|
48
|
+
console.log('Condition:');
|
|
49
|
+
console.log(JSON.stringify(growthCondition, null, 2));
|
|
50
|
+
console.log('');
|
|
51
|
+
console.log('Triggers when: net adds > 10% of total quads');
|
|
52
|
+
console.log('Example: If store has 100 quads, triggers if 11+ net adds');
|
|
53
|
+
console.log('Use for: validation after imports, reindexing\n');
|
|
54
|
+
|
|
55
|
+
// Example 3: Detect data loss
|
|
56
|
+
console.log('3. Detect Data Loss (Decrease)\n');
|
|
57
|
+
const shrinkCondition = createDeltaCondition('decrease', 0.05);
|
|
58
|
+
|
|
59
|
+
console.log('Condition:');
|
|
60
|
+
console.log(JSON.stringify(shrinkCondition, null, 2));
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log('Triggers when: net deletes > 5% of total quads');
|
|
63
|
+
console.log('Example: If store has 100 quads, triggers if 6+ net deletes');
|
|
64
|
+
console.log('Use for: anomaly detection, suspicious deletion alerts\n');
|
|
65
|
+
|
|
66
|
+
// Example 4: Detect significant modifications
|
|
67
|
+
console.log('4. Detect Significant Changes (Modify)\n');
|
|
68
|
+
const modifyCondition = createDeltaCondition('modify', 0.15);
|
|
69
|
+
|
|
70
|
+
console.log('Condition:');
|
|
71
|
+
console.log(JSON.stringify(modifyCondition, null, 2));
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log('Triggers when: |adds - deletes| > 15% of total quads');
|
|
74
|
+
console.log('Example: 20 adds + 5 deletes = 25 net, triggers if > 15 quads');
|
|
75
|
+
console.log('Use for: detecting bulk replace/sync operations\n');
|
|
76
|
+
|
|
77
|
+
// Practical scenarios
|
|
78
|
+
console.log('5. Real-World Scenarios\n');
|
|
79
|
+
|
|
80
|
+
console.log('Scenario A: Data Import Validation');
|
|
81
|
+
console.log('-'.repeat(40));
|
|
82
|
+
console.log(`
|
|
83
|
+
Hook: {
|
|
84
|
+
condition: {
|
|
85
|
+
kind: 'delta',
|
|
86
|
+
spec: { change: 'increase', threshold: 0.2 } // 20% growth
|
|
87
|
+
},
|
|
88
|
+
effects: [{
|
|
89
|
+
kind: 'sparql-construct',
|
|
90
|
+
query: 'CONSTRUCT { ?s ex:imported true } WHERE { ?s a ex:NewData }'
|
|
91
|
+
}]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
Trigger: When bulk data import adds > 20% new quads
|
|
95
|
+
Action: Mark imported entities, run compliance checks
|
|
96
|
+
`);
|
|
97
|
+
|
|
98
|
+
console.log('Scenario B: Suspicious Deletion Alert');
|
|
99
|
+
console.log('-'.repeat(40));
|
|
100
|
+
console.log(`
|
|
101
|
+
Hook: {
|
|
102
|
+
condition: {
|
|
103
|
+
kind: 'delta',
|
|
104
|
+
spec: { change: 'decrease', threshold: 0.02 } // 2% loss
|
|
105
|
+
},
|
|
106
|
+
effects: [{
|
|
107
|
+
kind: 'sparql-construct',
|
|
108
|
+
query: 'CONSTRUCT { ?s ex:audit ex:DeleteAlert } WHERE { ?s ?p ?o }'
|
|
109
|
+
}]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Trigger: When deletions exceed 2% (unusual for stable data)
|
|
113
|
+
Action: Create audit record for investigation
|
|
114
|
+
`);
|
|
115
|
+
|
|
116
|
+
console.log('Scenario C: Archive on Growth');
|
|
117
|
+
console.log('-'.repeat(40));
|
|
118
|
+
console.log(`
|
|
119
|
+
Hook: {
|
|
120
|
+
condition: {
|
|
121
|
+
kind: 'delta',
|
|
122
|
+
spec: { change: 'increase', threshold: 0.5 } // 50% growth
|
|
123
|
+
},
|
|
124
|
+
effects: [{
|
|
125
|
+
kind: 'function',
|
|
126
|
+
inline: async (store) => {
|
|
127
|
+
// Archive cold data to external storage
|
|
128
|
+
return { archived: true, count: store.size };
|
|
129
|
+
}
|
|
130
|
+
}]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Trigger: When data doubles (50% growth)
|
|
134
|
+
Action: Archive historical data to long-term storage
|
|
135
|
+
`);
|
|
136
|
+
|
|
137
|
+
// Change calculation logic
|
|
138
|
+
console.log('\n6. Delta Calculation Details\n');
|
|
139
|
+
|
|
140
|
+
console.log('Formula for change magnitude:');
|
|
141
|
+
console.log(' changeMagnitude = (additions - deletions) / totalQuads\n');
|
|
142
|
+
|
|
143
|
+
console.log('Example calculation:');
|
|
144
|
+
console.log(' Store size: 1000 quads');
|
|
145
|
+
console.log(' Additions: 250 quads');
|
|
146
|
+
console.log(' Deletions: 50 quads');
|
|
147
|
+
console.log(' Net change: 250 - 50 = 200');
|
|
148
|
+
console.log(' Magnitude: 200 / 1000 = 0.2 (20%)\n');
|
|
149
|
+
|
|
150
|
+
console.log(' Condition evaluation:');
|
|
151
|
+
console.log(' - increase (threshold 0.1): 0.2 > 0.1 ✓ TRIGGERS');
|
|
152
|
+
console.log(' - increase (threshold 0.3): 0.2 > 0.3 ✗ no trigger');
|
|
153
|
+
console.log(' - decrease (threshold 0.1): -0.2 < -0.1 ✗ no trigger');
|
|
154
|
+
console.log(' - modify (threshold 0.15): |0.2| > 0.15 ✓ TRIGGERS\n');
|
|
155
|
+
|
|
156
|
+
// Threshold recommendations
|
|
157
|
+
console.log('7. Recommended Thresholds\n');
|
|
158
|
+
|
|
159
|
+
console.log('Data Size Increase Decrease Modify Use Case');
|
|
160
|
+
console.log('-' * 60);
|
|
161
|
+
console.log('< 100 quads 0.20 0.20 0.20 Development');
|
|
162
|
+
console.log('100-1K quads 0.15 0.10 0.15 Small datasets');
|
|
163
|
+
console.log('1K-100K quads 0.10 0.05 0.10 Medium datasets');
|
|
164
|
+
console.log('> 100K quads 0.05 0.02 0.05 Large graphs\n');
|
|
165
|
+
|
|
166
|
+
// Combining with effects
|
|
167
|
+
console.log('8. Combined Example: Monitor + React\n');
|
|
168
|
+
|
|
169
|
+
console.log(`
|
|
170
|
+
const importValidationHook = {
|
|
171
|
+
name: 'validate-large-import',
|
|
172
|
+
|
|
173
|
+
condition: {
|
|
174
|
+
kind: 'delta',
|
|
175
|
+
spec: { change: 'increase', threshold: 0.1 } // 10% growth trigger
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
effects: [
|
|
179
|
+
// Effect 1: Mark newly added entities
|
|
180
|
+
{
|
|
181
|
+
kind: 'sparql-construct',
|
|
182
|
+
query: \`CONSTRUCT {
|
|
183
|
+
?s ex:addedAt ?now ;
|
|
184
|
+
ex:source ex:Import .
|
|
185
|
+
} WHERE {
|
|
186
|
+
?s a ex:Trade .
|
|
187
|
+
BIND (NOW() as ?now)
|
|
188
|
+
}\`
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
// Effect 2: Run compliance check
|
|
192
|
+
{
|
|
193
|
+
kind: 'sparql-construct',
|
|
194
|
+
query: \`CONSTRUCT {
|
|
195
|
+
?s ex:complianceStatus ex:PendingReview .
|
|
196
|
+
} WHERE {
|
|
197
|
+
?s a ex:Trade ;
|
|
198
|
+
ex:source ex:Import .
|
|
199
|
+
}\`
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
};
|
|
203
|
+
`);
|
|
204
|
+
|
|
205
|
+
console.log('\nExecution flow:');
|
|
206
|
+
console.log('1. New data added to store (e.g., 250 of 1000 quads)');
|
|
207
|
+
console.log('2. Delta condition evaluates: 0.25 > 0.10 ✓');
|
|
208
|
+
console.log('3. First effect executes: mark new trades with timestamp');
|
|
209
|
+
console.log('4. Second effect executes: set compliance status');
|
|
210
|
+
console.log('5. Hook completes successfully');
|
|
211
|
+
console.log('6. Compliance team notified for batch review\n');
|
|
212
|
+
|
|
213
|
+
console.log('=== Example Complete ===');
|