@longarc/mdash 3.0.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/README.md +278 -0
- package/dist/checkpoint/engine.d.ts +208 -0
- package/dist/checkpoint/engine.d.ts.map +1 -0
- package/dist/checkpoint/engine.js +369 -0
- package/dist/checkpoint/engine.js.map +1 -0
- package/dist/context/engine.d.ts +197 -0
- package/dist/context/engine.d.ts.map +1 -0
- package/dist/context/engine.js +392 -0
- package/dist/context/engine.js.map +1 -0
- package/dist/core/commitment.d.ts +154 -0
- package/dist/core/commitment.d.ts.map +1 -0
- package/dist/core/commitment.js +305 -0
- package/dist/core/commitment.js.map +1 -0
- package/dist/core/crypto.d.ts +100 -0
- package/dist/core/crypto.d.ts.map +1 -0
- package/dist/core/crypto.js +243 -0
- package/dist/core/crypto.js.map +1 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +234 -0
- package/dist/index.js.map +1 -0
- package/dist/mcca/engine.d.ts +260 -0
- package/dist/mcca/engine.d.ts.map +1 -0
- package/dist/mcca/engine.js +518 -0
- package/dist/mcca/engine.js.map +1 -0
- package/dist/physics/engine.d.ts +165 -0
- package/dist/physics/engine.d.ts.map +1 -0
- package/dist/physics/engine.js +371 -0
- package/dist/physics/engine.js.map +1 -0
- package/dist/tee/engine.d.ts +285 -0
- package/dist/tee/engine.d.ts.map +1 -0
- package/dist/tee/engine.js +505 -0
- package/dist/tee/engine.js.map +1 -0
- package/dist/warrant/engine.d.ts +195 -0
- package/dist/warrant/engine.d.ts.map +1 -0
- package/dist/warrant/engine.js +409 -0
- package/dist/warrant/engine.js.map +1 -0
- package/dist/zk/engine.d.ts +243 -0
- package/dist/zk/engine.d.ts.map +1 -0
- package/dist/zk/engine.js +489 -0
- package/dist/zk/engine.js.map +1 -0
- package/package.json +25 -0
- package/src/__tests__/phase1.test.ts +1120 -0
- package/src/__tests__/phase2-4.test.ts +898 -0
- package/src/checkpoint/engine.ts +532 -0
- package/src/context/engine.ts +598 -0
- package/src/core/commitment.ts +438 -0
- package/src/core/crypto.ts +304 -0
- package/src/index.ts +320 -0
- package/src/mcca/engine.ts +778 -0
- package/src/physics/engine.ts +563 -0
- package/src/tee/engine.ts +810 -0
- package/src/warrant/engine.ts +625 -0
- package/src/zk/engine.ts +730 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mdash v3.0 - Physics Engine
|
|
3
|
+
*
|
|
4
|
+
* "Physics" = the laws of agent behavior that cannot be violated.
|
|
5
|
+
* Validates actions against constraints with signed artifacts.
|
|
6
|
+
*
|
|
7
|
+
* Key Concept: Plausibility scoring determines if an action
|
|
8
|
+
* is physically possible given the warrant and context.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
Hash,
|
|
13
|
+
Seal,
|
|
14
|
+
Timestamp,
|
|
15
|
+
WarrantId,
|
|
16
|
+
CheckpointId,
|
|
17
|
+
sha256Object,
|
|
18
|
+
hmacSeal,
|
|
19
|
+
deriveKey,
|
|
20
|
+
generateTimestamp,
|
|
21
|
+
} from '../core/crypto';
|
|
22
|
+
|
|
23
|
+
import { Warrant, WarrantConstraints } from '../warrant/engine';
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// PHYSICS TYPES
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
export type PhysicsViolationType =
|
|
30
|
+
| 'amount_exceeded'
|
|
31
|
+
| 'rate_exceeded'
|
|
32
|
+
| 'domain_forbidden'
|
|
33
|
+
| 'time_forbidden'
|
|
34
|
+
| 'delegation_forbidden'
|
|
35
|
+
| 'scope_violation'
|
|
36
|
+
| 'causality_violation'
|
|
37
|
+
| 'invariant_violation';
|
|
38
|
+
|
|
39
|
+
export interface PhysicsConstraint {
|
|
40
|
+
/** Constraint type */
|
|
41
|
+
type: string;
|
|
42
|
+
/** Constraint parameters */
|
|
43
|
+
params: Record<string, unknown>;
|
|
44
|
+
/** Whether violation should block or warn */
|
|
45
|
+
severity: 'block' | 'warn';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface PhysicsAction {
|
|
49
|
+
/** Action identifier */
|
|
50
|
+
action_id: string;
|
|
51
|
+
/** Action type */
|
|
52
|
+
type: string;
|
|
53
|
+
/** Agent performing the action */
|
|
54
|
+
agent_id: string;
|
|
55
|
+
/** Warrant authorizing the action */
|
|
56
|
+
warrant_id: WarrantId;
|
|
57
|
+
/** Action parameters */
|
|
58
|
+
params: Record<string, unknown>;
|
|
59
|
+
/** Timestamp */
|
|
60
|
+
timestamp: Timestamp;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface PhysicsValidation {
|
|
64
|
+
/** Action being validated */
|
|
65
|
+
action: PhysicsAction;
|
|
66
|
+
/** Whether action is valid */
|
|
67
|
+
valid: boolean;
|
|
68
|
+
/** Plausibility score (0-1) */
|
|
69
|
+
score: number;
|
|
70
|
+
/** Violations found */
|
|
71
|
+
violations: PhysicsViolation[];
|
|
72
|
+
/** Validation timestamp */
|
|
73
|
+
validated_at: Timestamp;
|
|
74
|
+
/** Validation hash (for audit) */
|
|
75
|
+
hash: Hash;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface PhysicsViolation {
|
|
79
|
+
/** Violation type */
|
|
80
|
+
type: PhysicsViolationType;
|
|
81
|
+
/** Constraint that was violated */
|
|
82
|
+
constraint: PhysicsConstraint;
|
|
83
|
+
/** Human-readable message */
|
|
84
|
+
message: string;
|
|
85
|
+
/** Severity */
|
|
86
|
+
severity: 'block' | 'warn';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface SignedArtifact {
|
|
90
|
+
/** Artifact type */
|
|
91
|
+
type: 'validation' | 'constraint' | 'policy';
|
|
92
|
+
/** Artifact content */
|
|
93
|
+
content: unknown;
|
|
94
|
+
/** Content hash */
|
|
95
|
+
hash: Hash;
|
|
96
|
+
/** HMAC seal */
|
|
97
|
+
seal: Seal;
|
|
98
|
+
/** Creation timestamp */
|
|
99
|
+
created_at: Timestamp;
|
|
100
|
+
/** Version for hot-swap */
|
|
101
|
+
version: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// PHYSICS POLICIES
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
export interface PhysicsPolicy {
|
|
109
|
+
/** Policy identifier */
|
|
110
|
+
id: string;
|
|
111
|
+
/** Policy name */
|
|
112
|
+
name: string;
|
|
113
|
+
/** Policy version */
|
|
114
|
+
version: string;
|
|
115
|
+
/** Constraints defined by this policy */
|
|
116
|
+
constraints: PhysicsConstraint[];
|
|
117
|
+
/** Whether policy is active */
|
|
118
|
+
active: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Default policies
|
|
122
|
+
export const DEFAULT_POLICIES: PhysicsPolicy[] = [
|
|
123
|
+
{
|
|
124
|
+
id: 'financial-transfer-v2',
|
|
125
|
+
name: 'Financial Transfer',
|
|
126
|
+
version: '2.0',
|
|
127
|
+
active: true,
|
|
128
|
+
constraints: [
|
|
129
|
+
{
|
|
130
|
+
type: 'max_amount',
|
|
131
|
+
params: { limit: 100000, currency: 'USD' },
|
|
132
|
+
severity: 'block',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: 'rate_limit',
|
|
136
|
+
params: { max_per_hour: 10, max_per_day: 50 },
|
|
137
|
+
severity: 'block',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
type: 'allowlist',
|
|
141
|
+
params: { field: 'destination', required: true },
|
|
142
|
+
severity: 'block',
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'data-access-v1',
|
|
148
|
+
name: 'Data Access',
|
|
149
|
+
version: '1.0',
|
|
150
|
+
active: true,
|
|
151
|
+
constraints: [
|
|
152
|
+
{
|
|
153
|
+
type: 'scope_restriction',
|
|
154
|
+
params: { allowed_tables: [], require_explicit: true },
|
|
155
|
+
severity: 'block',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
type: 'rate_limit',
|
|
159
|
+
params: { max_per_minute: 100 },
|
|
160
|
+
severity: 'warn',
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: 'external-api-v1',
|
|
166
|
+
name: 'External API',
|
|
167
|
+
version: '1.0',
|
|
168
|
+
active: true,
|
|
169
|
+
constraints: [
|
|
170
|
+
{
|
|
171
|
+
type: 'domain_allowlist',
|
|
172
|
+
params: { domains: [], require_explicit: true },
|
|
173
|
+
severity: 'block',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
type: 'rate_limit',
|
|
177
|
+
params: { max_per_minute: 60 },
|
|
178
|
+
severity: 'warn',
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
// ============================================================================
|
|
185
|
+
// PHYSICS ENGINE
|
|
186
|
+
// ============================================================================
|
|
187
|
+
|
|
188
|
+
export class PhysicsEngine {
|
|
189
|
+
private key: CryptoKey | null = null;
|
|
190
|
+
private policies: Map<string, PhysicsPolicy> = new Map();
|
|
191
|
+
private validationHistory: PhysicsValidation[] = [];
|
|
192
|
+
private rateCounters: Map<string, { count: number; windowStart: number }> = new Map();
|
|
193
|
+
|
|
194
|
+
constructor() {
|
|
195
|
+
// Load default policies
|
|
196
|
+
for (const policy of DEFAULT_POLICIES) {
|
|
197
|
+
this.policies.set(policy.id, policy);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Initialize the engine with a seal key
|
|
203
|
+
*/
|
|
204
|
+
async initialize(sealKey: string): Promise<void> {
|
|
205
|
+
this.key = await deriveKey(sealKey);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Validate an action against physics constraints
|
|
210
|
+
*/
|
|
211
|
+
async validate(
|
|
212
|
+
action: PhysicsAction,
|
|
213
|
+
warrant: Warrant
|
|
214
|
+
): Promise<PhysicsValidation> {
|
|
215
|
+
if (!this.key) {
|
|
216
|
+
throw new Error('Engine not initialized. Call initialize() first.');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const startTime = performance.now();
|
|
220
|
+
const violations: PhysicsViolation[] = [];
|
|
221
|
+
|
|
222
|
+
// Get policy for the warrant
|
|
223
|
+
const policy = this.policies.get(warrant.policy_id);
|
|
224
|
+
if (!policy) {
|
|
225
|
+
violations.push({
|
|
226
|
+
type: 'scope_violation',
|
|
227
|
+
constraint: { type: 'policy_required', params: {}, severity: 'block' },
|
|
228
|
+
message: `Unknown policy: ${warrant.policy_id}`,
|
|
229
|
+
severity: 'block',
|
|
230
|
+
});
|
|
231
|
+
} else {
|
|
232
|
+
// Check each constraint
|
|
233
|
+
for (const constraint of policy.constraints) {
|
|
234
|
+
const violation = await this.checkConstraint(
|
|
235
|
+
constraint,
|
|
236
|
+
action,
|
|
237
|
+
warrant.constraints
|
|
238
|
+
);
|
|
239
|
+
if (violation) {
|
|
240
|
+
violations.push(violation);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Check warrant-level constraints
|
|
246
|
+
const warrantViolations = this.checkWarrantConstraints(action, warrant.constraints);
|
|
247
|
+
violations.push(...warrantViolations);
|
|
248
|
+
|
|
249
|
+
// Check rate limits
|
|
250
|
+
const rateViolation = this.checkRateLimit(action);
|
|
251
|
+
if (rateViolation) {
|
|
252
|
+
violations.push(rateViolation);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Calculate plausibility score
|
|
256
|
+
const blockingViolations = violations.filter(v => v.severity === 'block');
|
|
257
|
+
const score = blockingViolations.length === 0
|
|
258
|
+
? Math.max(0, 1 - violations.length * 0.1)
|
|
259
|
+
: 0;
|
|
260
|
+
|
|
261
|
+
const now = generateTimestamp();
|
|
262
|
+
|
|
263
|
+
// Create validation record
|
|
264
|
+
const validationData = {
|
|
265
|
+
action,
|
|
266
|
+
valid: blockingViolations.length === 0,
|
|
267
|
+
score,
|
|
268
|
+
violations,
|
|
269
|
+
validated_at: now,
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const hash = await sha256Object(validationData);
|
|
273
|
+
|
|
274
|
+
const validation: PhysicsValidation = {
|
|
275
|
+
...validationData,
|
|
276
|
+
hash,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// Store in history
|
|
280
|
+
this.validationHistory.push(validation);
|
|
281
|
+
|
|
282
|
+
const elapsed = performance.now() - startTime;
|
|
283
|
+
if (elapsed > 5) {
|
|
284
|
+
console.warn(`Physics validation exceeded target: ${elapsed.toFixed(2)}ms`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return validation;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Check a specific constraint
|
|
292
|
+
*/
|
|
293
|
+
private async checkConstraint(
|
|
294
|
+
constraint: PhysicsConstraint,
|
|
295
|
+
action: PhysicsAction,
|
|
296
|
+
warrantConstraints: WarrantConstraints
|
|
297
|
+
): Promise<PhysicsViolation | null> {
|
|
298
|
+
switch (constraint.type) {
|
|
299
|
+
case 'max_amount': {
|
|
300
|
+
const amount = action.params.amount as number | undefined;
|
|
301
|
+
const limit = warrantConstraints.maxAmount ?? (constraint.params.limit as number);
|
|
302
|
+
|
|
303
|
+
if (amount !== undefined && amount > limit) {
|
|
304
|
+
return {
|
|
305
|
+
type: 'amount_exceeded',
|
|
306
|
+
constraint,
|
|
307
|
+
message: `Amount ${amount} exceeds limit ${limit}`,
|
|
308
|
+
severity: constraint.severity,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
case 'domain_allowlist': {
|
|
315
|
+
const domain = action.params.domain as string | undefined;
|
|
316
|
+
const allowed = warrantConstraints.allowedDomains ?? (constraint.params.domains as string[]);
|
|
317
|
+
|
|
318
|
+
if (domain && allowed.length > 0 && !allowed.includes(domain)) {
|
|
319
|
+
return {
|
|
320
|
+
type: 'domain_forbidden',
|
|
321
|
+
constraint,
|
|
322
|
+
message: `Domain ${domain} not in allowlist`,
|
|
323
|
+
severity: constraint.severity,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
case 'allowlist': {
|
|
330
|
+
const field = constraint.params.field as string;
|
|
331
|
+
const value = action.params[field] as string | undefined;
|
|
332
|
+
const allowed = warrantConstraints.allowedAccounts ?? [];
|
|
333
|
+
|
|
334
|
+
if (constraint.params.required && value && allowed.length > 0) {
|
|
335
|
+
// Check suffix matching for account numbers
|
|
336
|
+
const matches = allowed.some(pattern => {
|
|
337
|
+
if (pattern.startsWith('*')) {
|
|
338
|
+
return value.endsWith(pattern.slice(1));
|
|
339
|
+
}
|
|
340
|
+
return value === pattern;
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
if (!matches) {
|
|
344
|
+
return {
|
|
345
|
+
type: 'scope_violation',
|
|
346
|
+
constraint,
|
|
347
|
+
message: `${field} "${value}" not in allowlist`,
|
|
348
|
+
severity: constraint.severity,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Check warrant-level constraints
|
|
361
|
+
*/
|
|
362
|
+
private checkWarrantConstraints(
|
|
363
|
+
action: PhysicsAction,
|
|
364
|
+
constraints: WarrantConstraints
|
|
365
|
+
): PhysicsViolation[] {
|
|
366
|
+
const violations: PhysicsViolation[] = [];
|
|
367
|
+
|
|
368
|
+
// Check 2FA requirement
|
|
369
|
+
if (constraints.require2FA && !action.params.has2FA) {
|
|
370
|
+
violations.push({
|
|
371
|
+
type: 'scope_violation',
|
|
372
|
+
constraint: { type: 'require_2fa', params: {}, severity: 'block' },
|
|
373
|
+
message: '2FA required but not provided',
|
|
374
|
+
severity: 'block',
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return violations;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Check rate limits
|
|
383
|
+
*/
|
|
384
|
+
private checkRateLimit(action: PhysicsAction): PhysicsViolation | null {
|
|
385
|
+
const key = `${action.agent_id}:${action.type}`;
|
|
386
|
+
const now = Date.now();
|
|
387
|
+
const windowMs = 60 * 1000; // 1 minute window
|
|
388
|
+
|
|
389
|
+
let counter = this.rateCounters.get(key);
|
|
390
|
+
if (!counter || now - counter.windowStart > windowMs) {
|
|
391
|
+
counter = { count: 0, windowStart: now };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
counter.count++;
|
|
395
|
+
this.rateCounters.set(key, counter);
|
|
396
|
+
|
|
397
|
+
// Default rate limit: 100 actions per minute
|
|
398
|
+
const limit = 100;
|
|
399
|
+
if (counter.count > limit) {
|
|
400
|
+
return {
|
|
401
|
+
type: 'rate_exceeded',
|
|
402
|
+
constraint: { type: 'rate_limit', params: { limit }, severity: 'block' },
|
|
403
|
+
message: `Rate limit exceeded: ${counter.count}/${limit} per minute`,
|
|
404
|
+
severity: 'block',
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Create a signed artifact
|
|
413
|
+
*/
|
|
414
|
+
async createSignedArtifact(
|
|
415
|
+
type: SignedArtifact['type'],
|
|
416
|
+
content: unknown,
|
|
417
|
+
version: string
|
|
418
|
+
): Promise<SignedArtifact> {
|
|
419
|
+
if (!this.key) {
|
|
420
|
+
throw new Error('Engine not initialized. Call initialize() first.');
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const now = generateTimestamp();
|
|
424
|
+
const hash = await sha256Object({ type, content, version, created_at: now });
|
|
425
|
+
const seal = await hmacSeal({ hash, type, version }, this.key);
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
type,
|
|
429
|
+
content,
|
|
430
|
+
hash,
|
|
431
|
+
seal,
|
|
432
|
+
created_at: now,
|
|
433
|
+
version,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Hot-swap a policy (for runtime updates)
|
|
439
|
+
*/
|
|
440
|
+
async hotSwapPolicy(policy: PhysicsPolicy): Promise<SignedArtifact> {
|
|
441
|
+
// Create signed artifact for audit
|
|
442
|
+
const artifact = await this.createSignedArtifact(
|
|
443
|
+
'policy',
|
|
444
|
+
policy,
|
|
445
|
+
policy.version
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
// Replace policy
|
|
449
|
+
this.policies.set(policy.id, policy);
|
|
450
|
+
|
|
451
|
+
return artifact;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Get policy by ID
|
|
456
|
+
*/
|
|
457
|
+
getPolicy(id: string): PhysicsPolicy | null {
|
|
458
|
+
return this.policies.get(id) || null;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Get all policies
|
|
463
|
+
*/
|
|
464
|
+
getAllPolicies(): PhysicsPolicy[] {
|
|
465
|
+
return Array.from(this.policies.values());
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Get validation history for an agent
|
|
470
|
+
*/
|
|
471
|
+
getValidationHistory(agentId?: string, limit: number = 100): PhysicsValidation[] {
|
|
472
|
+
let history = this.validationHistory;
|
|
473
|
+
|
|
474
|
+
if (agentId) {
|
|
475
|
+
history = history.filter(v => v.action.agent_id === agentId);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return history.slice(-limit);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Get plausibility statistics
|
|
483
|
+
*/
|
|
484
|
+
getStats(): {
|
|
485
|
+
totalValidations: number;
|
|
486
|
+
validCount: number;
|
|
487
|
+
invalidCount: number;
|
|
488
|
+
averageScore: number;
|
|
489
|
+
violationsByType: Record<string, number>;
|
|
490
|
+
} {
|
|
491
|
+
const total = this.validationHistory.length;
|
|
492
|
+
const valid = this.validationHistory.filter(v => v.valid).length;
|
|
493
|
+
const scores = this.validationHistory.map(v => v.score);
|
|
494
|
+
const avgScore = scores.length > 0
|
|
495
|
+
? scores.reduce((a, b) => a + b, 0) / scores.length
|
|
496
|
+
: 0;
|
|
497
|
+
|
|
498
|
+
const violationsByType: Record<string, number> = {};
|
|
499
|
+
for (const validation of this.validationHistory) {
|
|
500
|
+
for (const violation of validation.violations) {
|
|
501
|
+
violationsByType[violation.type] = (violationsByType[violation.type] || 0) + 1;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
totalValidations: total,
|
|
507
|
+
validCount: valid,
|
|
508
|
+
invalidCount: total - valid,
|
|
509
|
+
averageScore: avgScore,
|
|
510
|
+
violationsByType,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ============================================================================
|
|
516
|
+
// CAUSALITY CHECKER
|
|
517
|
+
// ============================================================================
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Ensures actions respect causal ordering
|
|
521
|
+
* "Effect cannot precede cause"
|
|
522
|
+
*/
|
|
523
|
+
export class CausalityChecker {
|
|
524
|
+
private actionLog: Array<{ action: PhysicsAction; checkpoint: CheckpointId }> = [];
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Record an action with its checkpoint
|
|
528
|
+
*/
|
|
529
|
+
record(action: PhysicsAction, checkpoint: CheckpointId): void {
|
|
530
|
+
this.actionLog.push({ action, checkpoint });
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Check if an action would violate causality
|
|
535
|
+
*/
|
|
536
|
+
checkCausality(
|
|
537
|
+
_action: PhysicsAction,
|
|
538
|
+
dependencies: CheckpointId[]
|
|
539
|
+
): { valid: boolean; violation?: string } {
|
|
540
|
+
// All dependencies must be in the log
|
|
541
|
+
for (const dep of dependencies) {
|
|
542
|
+
const found = this.actionLog.some(entry => entry.checkpoint === dep);
|
|
543
|
+
if (!found) {
|
|
544
|
+
return {
|
|
545
|
+
valid: false,
|
|
546
|
+
violation: `Dependency checkpoint ${dep} not found in causal history`,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return { valid: true };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Get causal chain for an action
|
|
556
|
+
*/
|
|
557
|
+
getCausalChain(checkpointId: CheckpointId): PhysicsAction[] {
|
|
558
|
+
const index = this.actionLog.findIndex(e => e.checkpoint === checkpointId);
|
|
559
|
+
if (index === -1) return [];
|
|
560
|
+
|
|
561
|
+
return this.actionLog.slice(0, index + 1).map(e => e.action);
|
|
562
|
+
}
|
|
563
|
+
}
|