arcane-os 0.3.6 → 0.4.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.
@@ -1,995 +0,0 @@
1
- const IDENTIFIER_MAXIMUM_CHARACTERS = 128;
2
-
3
- const SCHEMA = 'arcane.twin-policy-decision';
4
- const VERSION = 1;
5
- const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
6
- const REASON_CODE_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*){2,7}$/;
7
- const REQUIREMENT_TARGET_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*){1,7}$/;
8
- const EXPLICIT_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
9
- const RESERVED_KEYS_VALUES = ['__proto__', 'constructor', 'prototype'];
10
- const LAYER_VALUES = ['advisory', 'organization', 'platform', 'role'];
11
- const OUTCOME_VALUES = ['confirm', 'constrain', 'deny', 'escalate', 'log', 'permit', 'redact'];
12
- const REQUIREMENT_OUTCOME_VALUES = ['confirm', 'constrain', 'escalate', 'log', 'redact'];
13
- const CORE_REASON_CODE_VALUES = {
14
- confirm: 'arcane.policy.confirm',
15
- constrain: 'arcane.policy.constrain',
16
- deny: 'arcane.policy.deny',
17
- escalate: 'arcane.policy.escalate',
18
- log: 'arcane.policy.log',
19
- permit: 'arcane.policy.permit',
20
- redact: 'arcane.policy.redact'
21
- };
22
- const ROOT_KEY_VALUES = [
23
- 'schema',
24
- 'version',
25
- 'id',
26
- 'evaluatedAt',
27
- 'layer',
28
- 'policy',
29
- 'outcome',
30
- 'reasonCodes',
31
- 'requirements'
32
- ];
33
- const PAYLOAD_KEY_VALUES = ['outcome', 'reasonCodes', 'requirements'];
34
- const TRUSTED_CONTEXT_KEY_VALUES = ['id', 'evaluatedAt', 'layer', 'policyId', 'policyVersion'];
35
- const POLICY_KEY_VALUES = ['id', 'version'];
36
- const REQUIREMENT_KEY_VALUES = ['id', 'reasonCode', 'target', 'value'];
37
-
38
- const RESERVED_KEYS = new Set(RESERVED_KEYS_VALUES);
39
- const LAYERS = new Set(LAYER_VALUES);
40
- const OUTCOMES = new Set(OUTCOME_VALUES);
41
- const REQUIREMENT_OUTCOMES = new Set(REQUIREMENT_OUTCOME_VALUES);
42
- const ROOT_KEYS = ROOT_KEY_VALUES;
43
- const PAYLOAD_KEYS = PAYLOAD_KEY_VALUES;
44
- const TRUSTED_CONTEXT_KEYS = TRUSTED_CONTEXT_KEY_VALUES;
45
- const POLICY_KEYS = POLICY_KEY_VALUES;
46
- const REQUIREMENT_KEYS = REQUIREMENT_KEY_VALUES;
47
- const CORE_REASON_CODES = CORE_REASON_CODE_VALUES;
48
- const CORE_REASON_CODE_SET = new Set(Object.values(CORE_REASON_CODES));
49
-
50
- const constructionToken = Symbol('TWiNPolicyDecision construction');
51
- const decisionInstances = new WeakSet();
52
- const validationFailureTokens = new WeakMap();
53
- let activeValidationToken = null;
54
-
55
- export class TWiNPolicyDecisionValidationError extends TypeError {
56
- constructor(code, path, message) {
57
- super(message);
58
- defineDataProperty(this, 'name', 'TWiNPolicyDecisionValidationError');
59
- defineDataProperty(this, 'code', code);
60
- defineDataProperty(this, 'path', path);
61
- }
62
- }
63
-
64
- function fail(code, path, message) {
65
- const error = new TWiNPolicyDecisionValidationError(code, path, message);
66
-
67
- validationFailureTokens.set(error, activeValidationToken);
68
- throw error;
69
- }
70
-
71
- function validationBoundary(operation) {
72
- const previousToken = activeValidationToken;
73
- const operationToken = Symbol('TWiNPolicyDecision validation');
74
-
75
- activeValidationToken = operationToken;
76
- try {
77
- return operation();
78
- } catch (error) {
79
- if (validationFailureTokens.get(error) === operationToken) {
80
- throw error;
81
- }
82
- fail(
83
- 'TWIN_POLICY_DECISION_INVALID',
84
- '$',
85
- 'TWiN policy decision input could not be inspected safely.'
86
- );
87
- } finally {
88
- activeValidationToken = previousToken;
89
- }
90
- }
91
-
92
- function safeRecord() {
93
- return {};
94
- }
95
-
96
- function defineDataProperty(target, key, value) {
97
- Object.defineProperty(
98
- target,
99
- key,
100
- {
101
- configurable: true,
102
- enumerable: true,
103
- value,
104
- writable: true
105
- }
106
- );
107
-
108
- return target;
109
- }
110
-
111
- function appendOwn(array, value) {
112
- defineDataProperty(array, array.length, value);
113
-
114
- return array;
115
- }
116
-
117
- function safeArray(values) {
118
- const array = [];
119
-
120
- for (let index = 0; index < values.length; index += 1) {
121
- appendOwn(array, values[index]);
122
- }
123
- return array;
124
- }
125
-
126
- function sortedCopy(values, compare) {
127
- const sorted = [];
128
-
129
- for (let index = 0; index < values.length; index += 1) {
130
- appendOwn(sorted, values[index]);
131
- }
132
- for (let index = 1; index < sorted.length; index += 1) {
133
- const current = sorted[index];
134
- let destination = index;
135
-
136
- while (destination > 0 && compare(sorted[destination - 1], current) > 0) {
137
- defineDataProperty(sorted, destination, sorted[destination - 1]);
138
- destination -= 1;
139
- }
140
- defineDataProperty(sorted, destination, current);
141
- }
142
-
143
- return sorted;
144
- }
145
-
146
- function validateUnicode(value, path) {
147
- for (let index = 0; index < value.length; index += 1) {
148
- const unit = value.charCodeAt(index);
149
-
150
- if (unit >= 0xD800 && unit <= 0xDBFF) {
151
- const following = value.charCodeAt(index + 1);
152
-
153
- if (!(following >= 0xDC00 && following <= 0xDFFF)) {
154
- fail(
155
- 'TWIN_POLICY_DECISION_INVALID',
156
- path,
157
- 'TWiN policy decision text contains invalid Unicode.'
158
- );
159
- }
160
- index += 1;
161
- } else if (unit >= 0xDC00 && unit <= 0xDFFF) {
162
- fail(
163
- 'TWIN_POLICY_DECISION_INVALID',
164
- path,
165
- 'TWiN policy decision text contains invalid Unicode.'
166
- );
167
- }
168
- }
169
-
170
- return value;
171
- }
172
-
173
- function normalizedString(value, path, options = {}) {
174
- const nullable = options.nullable === true;
175
- const nonempty = options.nonempty !== false;
176
-
177
- if (nullable && value === null) {
178
- return null;
179
- }
180
- if (typeof value !== 'string') {
181
- fail(
182
- 'TWIN_POLICY_DECISION_INVALID',
183
- path,
184
- 'TWiN policy decision field has an invalid type.'
185
- );
186
- }
187
- validateUnicode(value, path);
188
- if (nonempty && !value.trim()) {
189
- fail(
190
- 'TWIN_POLICY_DECISION_INVALID',
191
- path,
192
- 'TWiN policy decision field must contain text.'
193
- );
194
- }
195
- return value;
196
- }
197
-
198
- function identifier(value, path) {
199
- if (typeof value !== 'string') {
200
- fail(
201
- 'TWIN_POLICY_DECISION_INVALID',
202
- path,
203
- 'TWiN policy decision identifier has an invalid type.'
204
- );
205
- }
206
- validateUnicode(value, path);
207
- if (value.length > IDENTIFIER_MAXIMUM_CHARACTERS || !IDENTIFIER_PATTERN.test(value)) {
208
- fail(
209
- 'TWIN_POLICY_DECISION_INVALID',
210
- path,
211
- 'TWiN policy decision identifier is invalid.'
212
- );
213
- }
214
-
215
- return value;
216
- }
217
-
218
- function enumeration(value, path, allowed) {
219
- if (typeof value !== 'string' || !allowed.has(value)) {
220
- fail(
221
- 'TWIN_POLICY_DECISION_INVALID',
222
- path,
223
- 'TWiN policy decision enum value is invalid.'
224
- );
225
- }
226
-
227
- return value;
228
- }
229
-
230
- function isReservedKey(key) {
231
- return typeof key === 'string' && RESERVED_KEYS.has(key);
232
- }
233
-
234
- function plainRecord(value, path, allowedKeys, active) {
235
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
236
- fail(
237
- 'TWIN_POLICY_DECISION_INVALID',
238
- path,
239
- 'TWiN policy decision field must be a plain record.'
240
- );
241
- }
242
- const prototype = Object.getPrototypeOf(value);
243
-
244
- if (
245
- prototype !== Object.prototype
246
- && prototype !== null
247
- && !decisionInstances.has(value)
248
- ) {
249
- fail(
250
- 'TWIN_POLICY_DECISION_INVALID',
251
- path,
252
- 'TWiN policy decision record prototype is invalid.'
253
- );
254
- }
255
- if (active.has(value)) {
256
- fail(
257
- 'TWIN_POLICY_DECISION_INVALID',
258
- path,
259
- 'TWiN policy decision data must not contain cycles.'
260
- );
261
- }
262
- active.add(value);
263
- const allowed = new Set(allowedKeys);
264
-
265
- for (const key of Reflect.ownKeys(value)) {
266
- if (typeof key !== 'string' || isReservedKey(key) || !allowed.has(key)) {
267
- active.delete(value);
268
- fail(
269
- 'TWIN_POLICY_DECISION_UNKNOWN_FIELD',
270
- path,
271
- 'TWiN policy decision contains an unsupported field.'
272
- );
273
- }
274
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
275
-
276
- if (!descriptor || !Object.hasOwn(descriptor, 'value')) {
277
- active.delete(value);
278
- fail(
279
- 'TWIN_POLICY_DECISION_INVALID',
280
- `${path}.${key}`,
281
- 'TWiN policy decision accessors are not allowed.'
282
- );
283
- }
284
- }
285
-
286
- return value;
287
- }
288
-
289
- function finishRecord(value, active) {
290
- active.delete(value);
291
- }
292
-
293
- function ownValue(record, key, path, options = {}) {
294
- const required = options.required !== false;
295
- const descriptor = Object.getOwnPropertyDescriptor(record, key);
296
-
297
- if (!descriptor) {
298
- if (required) {
299
- fail(
300
- 'TWIN_POLICY_DECISION_INVALID',
301
- `${path}.${key}`,
302
- 'TWiN policy decision required field is missing.'
303
- );
304
- }
305
-
306
- return options.defaultValue;
307
- }
308
-
309
- return descriptor.value;
310
- }
311
-
312
- function denseArray(value, path, active) {
313
- if (!Array.isArray(value)) {
314
- fail(
315
- 'TWIN_POLICY_DECISION_INVALID',
316
- path,
317
- 'TWiN policy decision field must be an array.'
318
- );
319
- }
320
- const prototype = Object.getPrototypeOf(value);
321
-
322
- if (prototype !== Array.prototype) {
323
- fail(
324
- 'TWIN_POLICY_DECISION_INVALID',
325
- path,
326
- 'TWiN policy decision array prototype is invalid.'
327
- );
328
- }
329
- if (active.has(value)) {
330
- fail(
331
- 'TWIN_POLICY_DECISION_INVALID',
332
- path,
333
- 'TWiN policy decision data must not contain cycles.'
334
- );
335
- }
336
- active.add(value);
337
-
338
- for (const key of Reflect.ownKeys(value)) {
339
- if (key === 'length') {
340
- continue;
341
- }
342
- if (
343
- typeof key !== 'string'
344
- || isReservedKey(key)
345
- || !/^(0|[1-9]\d*)$/.test(key)
346
- || Number(key) >= value.length
347
- ) {
348
- active.delete(value);
349
- fail(
350
- 'TWIN_POLICY_DECISION_UNKNOWN_FIELD',
351
- path,
352
- 'TWiN policy decision array contains an unsupported field.'
353
- );
354
- }
355
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
356
-
357
- if (!descriptor || !Object.hasOwn(descriptor, 'value')) {
358
- active.delete(value);
359
- fail(
360
- 'TWIN_POLICY_DECISION_INVALID',
361
- `${path}[${key}]`,
362
- 'TWiN policy decision accessors are not allowed.'
363
- );
364
- }
365
- }
366
- for (let index = 0; index < value.length; index += 1) {
367
- if (!Object.hasOwn(value, index)) {
368
- active.delete(value);
369
- fail(
370
- 'TWIN_POLICY_DECISION_INVALID',
371
- `${path}[${index}]`,
372
- 'TWiN policy decision sparse arrays are not allowed.'
373
- );
374
- }
375
- }
376
-
377
- return value;
378
- }
379
-
380
- function finishArray(value, active) {
381
- active.delete(value);
382
- }
383
-
384
- function normalizeTimestamp(value, path) {
385
- if (typeof value !== 'string') {
386
- fail(
387
- 'TWIN_POLICY_DECISION_INVALID',
388
- path,
389
- 'TWiN policy decision timestamp is invalid.'
390
- );
391
- }
392
- const instant = new Date(value);
393
-
394
- if (Number.isNaN(instant.valueOf()) || instant.toISOString() !== value) {
395
- fail(
396
- 'TWIN_POLICY_DECISION_INVALID',
397
- path,
398
- 'TWiN policy decision timestamp is invalid.'
399
- );
400
- }
401
-
402
- return value;
403
- }
404
-
405
- function isLeapYear(year) {
406
- return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
407
- }
408
-
409
- function isValidExplicitTimestamp(value) {
410
- if (!EXPLICIT_TIMESTAMP_PATTERN.test(value)) {
411
- return false;
412
- }
413
- const year = Number(value.slice(0, 4));
414
- const month = Number(value.slice(5, 7));
415
- const day = Number(value.slice(8, 10));
416
- const hour = Number(value.slice(11, 13));
417
- const minute = Number(value.slice(14, 16));
418
- const second = Number(value.slice(17, 19));
419
- const daysByMonth = [
420
- 31,
421
- isLeapYear(year) ? 29 : 28,
422
- 31,
423
- 30,
424
- 31,
425
- 30,
426
- 31,
427
- 31,
428
- 30,
429
- 31,
430
- 30,
431
- 31
432
- ];
433
-
434
- if (
435
- month < 1
436
- || month > 12
437
- || day < 1
438
- || day > daysByMonth[month - 1]
439
- || hour > 23
440
- || minute > 59
441
- || second > 59
442
- ) {
443
- return false;
444
- }
445
- if (!value.endsWith('Z')) {
446
- const offsetHour = Number(value.slice(-5, -3));
447
- const offsetMinute = Number(value.slice(-2));
448
-
449
- if (offsetHour > 23 || offsetMinute > 59) {
450
- return false;
451
- }
452
- }
453
-
454
- return true;
455
- }
456
-
457
- function normalizeTrustedTimestamp(value, path) {
458
- if (!(typeof value === 'string' || (value && typeof value === 'object'))) {
459
- fail(
460
- 'TWIN_POLICY_DECISION_INVALID',
461
- path,
462
- 'TWiN policy decision timestamp is invalid.'
463
- );
464
- }
465
- let instant;
466
-
467
- if (typeof value === 'string' && !isValidExplicitTimestamp(value)) {
468
- fail(
469
- 'TWIN_POLICY_DECISION_INVALID',
470
- path,
471
- 'TWiN policy decision timestamp is invalid.'
472
- );
473
- }
474
- try {
475
- if (typeof value === 'string') {
476
- instant = new Date(value);
477
- } else {
478
- instant = new Date(Date.prototype.getTime.call(value));
479
- }
480
- } catch {
481
- fail(
482
- 'TWIN_POLICY_DECISION_INVALID',
483
- path,
484
- 'TWiN policy decision timestamp is invalid.'
485
- );
486
- }
487
-
488
- if (Number.isNaN(instant.valueOf())) {
489
- fail(
490
- 'TWIN_POLICY_DECISION_INVALID',
491
- path,
492
- 'TWiN policy decision timestamp is invalid.'
493
- );
494
- }
495
-
496
- return instant.toISOString();
497
- }
498
-
499
- function normalizeReasonCode(value, path) {
500
- const code = normalizedString(value, path);
501
-
502
- if (!REASON_CODE_PATTERN.test(code)) {
503
- fail(
504
- 'TWIN_POLICY_DECISION_INVALID',
505
- path,
506
- 'TWiN policy decision reason code is invalid.'
507
- );
508
- }
509
-
510
- return code;
511
- }
512
-
513
- function compareStrings(left, right) {
514
- if (left === right) {
515
- return 0;
516
- }
517
-
518
- return left < right ? -1 : 1;
519
- }
520
-
521
- function normalizeReasonCodes(value, outcome, path, active) {
522
- const list = denseArray(value, path, active);
523
-
524
- if (list.length === 0) {
525
- finishArray(list, active);
526
- fail(
527
- 'TWIN_POLICY_DECISION_INVALID',
528
- path,
529
- 'TWiN policy decision requires a reason code.'
530
- );
531
- }
532
- const normalized = [];
533
- const seen = new Set();
534
- const requiredCode = CORE_REASON_CODES[outcome];
535
-
536
- for (let index = 0; index < list.length; index += 1) {
537
- const code = normalizeReasonCode(list[index], `${path}[${index}]`);
538
-
539
- if (seen.has(code)) {
540
- finishArray(list, active);
541
- fail(
542
- 'TWIN_POLICY_DECISION_INVALID',
543
- `${path}[${index}]`,
544
- 'TWiN policy decision reason codes must be unique.'
545
- );
546
- }
547
- if (index > 0 && CORE_REASON_CODE_SET.has(code)) {
548
- finishArray(list, active);
549
- fail(
550
- 'TWIN_POLICY_DECISION_INCONSISTENT',
551
- `${path}[${index}]`,
552
- 'TWiN policy decision contains a contradictory core reason code.'
553
- );
554
- }
555
- seen.add(code);
556
- appendOwn(normalized, code);
557
- }
558
- finishArray(list, active);
559
-
560
- if (normalized[0] !== requiredCode) {
561
- fail(
562
- 'TWIN_POLICY_DECISION_INCONSISTENT',
563
- `${path}[0]`,
564
- 'TWiN policy decision outcome and core reason code do not agree.'
565
- );
566
- }
567
- const additional = [];
568
-
569
- for (let index = 1; index < normalized.length; index += 1) {
570
- appendOwn(additional, normalized[index]);
571
- }
572
- const sortedAdditional = sortedCopy(additional, compareStrings);
573
- const canonicalReasons = [];
574
-
575
- appendOwn(canonicalReasons, requiredCode);
576
- for (let index = 0; index < sortedAdditional.length; index += 1) {
577
- appendOwn(canonicalReasons, sortedAdditional[index]);
578
- }
579
-
580
- return safeArray(canonicalReasons);
581
- }
582
-
583
- function normalizeRequirementTarget(value, path) {
584
- const target = normalizedString(
585
- value,
586
- path,
587
- {
588
- nullable: true
589
- }
590
- );
591
-
592
- if (target !== null && !REQUIREMENT_TARGET_PATTERN.test(target)) {
593
- fail(
594
- 'TWIN_POLICY_DECISION_INVALID',
595
- path,
596
- 'TWiN policy decision requirement target is invalid.'
597
- );
598
- }
599
-
600
- return target;
601
- }
602
-
603
- function normalizeScalar(value, path) {
604
- if (value === null || typeof value === 'boolean') {
605
- return value;
606
- }
607
- if (typeof value === 'string') {
608
- return normalizedString(
609
- value,
610
- path,
611
- {
612
- nonempty: false
613
- }
614
- );
615
- }
616
- if (typeof value === 'number' && Number.isFinite(value)) {
617
- return Object.is(value, -0) ? 0 : value;
618
- }
619
-
620
- fail(
621
- 'TWIN_POLICY_DECISION_INVALID',
622
- path,
623
- 'TWiN policy decision requirement value must be a JSON scalar.'
624
- );
625
- }
626
-
627
- function normalizeRequirement(value, path, reasonCodes, active) {
628
- const record = plainRecord(value, path, REQUIREMENT_KEYS, active);
629
- const requirement = {
630
- id: identifier(ownValue(record, 'id', path), `${path}.id`),
631
- reasonCode: normalizeReasonCode(
632
- ownValue(record, 'reasonCode', path),
633
- `${path}.reasonCode`
634
- ),
635
- target: normalizeRequirementTarget(
636
- ownValue(record, 'target', path),
637
- `${path}.target`
638
- ),
639
- value: normalizeScalar(
640
- ownValue(record, 'value', path),
641
- `${path}.value`
642
- )
643
- };
644
- finishRecord(record, active);
645
-
646
- if (!reasonCodes.has(requirement.reasonCode)) {
647
- fail(
648
- 'TWIN_POLICY_DECISION_INCONSISTENT',
649
- `${path}.reasonCode`,
650
- 'TWiN policy decision requirement references an undeclared reason code.'
651
- );
652
- }
653
-
654
- return requirement;
655
- }
656
-
657
- function compareRequirements(left, right) {
658
- return compareStrings(left.id, right.id);
659
- }
660
-
661
- function normalizeRequirements(value, outcome, reasonCodes, path, active) {
662
- const list = denseArray(value, path, active);
663
- const normalized = [];
664
- const ids = new Set();
665
-
666
- for (let index = 0; index < list.length; index += 1) {
667
- const requirement = normalizeRequirement(
668
- list[index],
669
- `${path}[${index}]`,
670
- reasonCodes,
671
- active
672
- );
673
-
674
- if (ids.has(requirement.id)) {
675
- finishArray(list, active);
676
- fail(
677
- 'TWIN_POLICY_DECISION_INVALID',
678
- `${path}[${index}].id`,
679
- 'TWiN policy decision requirement identifiers must be unique.'
680
- );
681
- }
682
- ids.add(requirement.id);
683
- appendOwn(normalized, requirement);
684
- }
685
- finishArray(list, active);
686
-
687
- if (REQUIREMENT_OUTCOMES.has(outcome) && normalized.length === 0) {
688
- fail(
689
- 'TWIN_POLICY_DECISION_INCONSISTENT',
690
- path,
691
- 'TWiN policy decision outcome requires at least one requirement.'
692
- );
693
- }
694
- if (!REQUIREMENT_OUTCOMES.has(outcome) && normalized.length !== 0) {
695
- fail(
696
- 'TWIN_POLICY_DECISION_INCONSISTENT',
697
- path,
698
- 'TWiN policy decision outcome does not accept requirements.'
699
- );
700
- }
701
-
702
- return safeArray(sortedCopy(normalized, compareRequirements));
703
- }
704
-
705
- function normalizePolicy(value, path, active) {
706
- const record = plainRecord(value, path, POLICY_KEYS, active);
707
- const policy = {
708
- id: identifier(ownValue(record, 'id', path), `${path}.id`),
709
- version: identifier(ownValue(record, 'version', path), `${path}.version`)
710
- };
711
- finishRecord(record, active);
712
-
713
- return policy;
714
- }
715
-
716
- function clonePolicy(value) {
717
- const policy = safeRecord();
718
-
719
- defineDataProperty(policy, 'id', value.id);
720
- defineDataProperty(policy, 'version', value.version);
721
-
722
- return policy;
723
- }
724
-
725
- function cloneRequirement(value) {
726
- const requirement = safeRecord();
727
-
728
- defineDataProperty(requirement, 'id', value.id);
729
- defineDataProperty(requirement, 'reasonCode', value.reasonCode);
730
- defineDataProperty(requirement, 'target', value.target);
731
- defineDataProperty(requirement, 'value', value.value);
732
-
733
- return requirement;
734
- }
735
-
736
- function canonicalRecord(values) {
737
- const requirements = [];
738
-
739
- for (let index = 0; index < values.requirements.length; index += 1) {
740
- appendOwn(requirements, cloneRequirement(values.requirements[index]));
741
- }
742
- const canonical = safeRecord();
743
-
744
- defineDataProperty(canonical, 'schema', SCHEMA);
745
- defineDataProperty(canonical, 'version', VERSION);
746
- defineDataProperty(canonical, 'id', values.id);
747
- defineDataProperty(canonical, 'evaluatedAt', values.evaluatedAt);
748
- defineDataProperty(canonical, 'layer', values.layer);
749
- defineDataProperty(canonical, 'policy', clonePolicy(values.policy));
750
- defineDataProperty(canonical, 'outcome', values.outcome);
751
- defineDataProperty(canonical, 'reasonCodes', safeArray(values.reasonCodes));
752
- defineDataProperty(canonical, 'requirements', safeArray(requirements));
753
-
754
- return canonical;
755
- }
756
-
757
- function normalizeValues(input) {
758
- const active = new WeakSet();
759
- const record = plainRecord(input, '$', ROOT_KEYS, active);
760
- const schema = ownValue(record, 'schema', '$');
761
-
762
- if (schema !== SCHEMA) {
763
- finishRecord(record, active);
764
- fail(
765
- 'TWIN_POLICY_DECISION_UNSUPPORTED_VERSION',
766
- 'schema',
767
- 'TWiN policy decision schema is unsupported.'
768
- );
769
- }
770
- const version = ownValue(record, 'version', '$');
771
-
772
- if (version !== VERSION) {
773
- finishRecord(record, active);
774
- fail(
775
- 'TWIN_POLICY_DECISION_UNSUPPORTED_VERSION',
776
- 'version',
777
- 'TWiN policy decision version is unsupported.'
778
- );
779
- }
780
- const outcome = enumeration(
781
- ownValue(record, 'outcome', '$'),
782
- 'outcome',
783
- OUTCOMES
784
- );
785
- const reasonCodes = normalizeReasonCodes(
786
- ownValue(record, 'reasonCodes', '$'),
787
- outcome,
788
- 'reasonCodes',
789
- active
790
- );
791
- const values = {
792
- id: identifier(ownValue(record, 'id', '$'), 'id'),
793
- evaluatedAt: normalizeTimestamp(
794
- ownValue(record, 'evaluatedAt', '$'),
795
- 'evaluatedAt'
796
- ),
797
- layer: enumeration(
798
- ownValue(record, 'layer', '$'),
799
- 'layer',
800
- LAYERS
801
- ),
802
- policy: normalizePolicy(
803
- ownValue(record, 'policy', '$'),
804
- 'policy',
805
- active
806
- ),
807
- outcome,
808
- reasonCodes,
809
- requirements: normalizeRequirements(
810
- ownValue(record, 'requirements', '$'),
811
- outcome,
812
- new Set(reasonCodes),
813
- 'requirements',
814
- active
815
- )
816
- };
817
- finishRecord(record, active);
818
-
819
- return values;
820
- }
821
-
822
- function normalizeCreatePayload(payload, trustedContext) {
823
- const active = new WeakSet();
824
- const source = plainRecord(payload, 'payload', PAYLOAD_KEYS, active);
825
- const context = plainRecord(
826
- trustedContext,
827
- 'trustedContext',
828
- TRUSTED_CONTEXT_KEYS,
829
- active
830
- );
831
- const canonical = {
832
- schema: SCHEMA,
833
- version: VERSION,
834
- id: ownValue(context, 'id', 'trustedContext'),
835
- evaluatedAt: normalizeTrustedTimestamp(
836
- ownValue(context, 'evaluatedAt', 'trustedContext'),
837
- 'evaluatedAt'
838
- ),
839
- layer: ownValue(context, 'layer', 'trustedContext'),
840
- policy: {
841
- id: ownValue(context, 'policyId', 'trustedContext'),
842
- version: ownValue(context, 'policyVersion', 'trustedContext')
843
- },
844
- outcome: ownValue(source, 'outcome', 'payload'),
845
- reasonCodes: ownValue(source, 'reasonCodes', 'payload'),
846
- requirements: ownValue(
847
- source,
848
- 'requirements',
849
- 'payload',
850
- {
851
- required: false,
852
- defaultValue: []
853
- }
854
- )
855
- };
856
- finishRecord(context, active);
857
- finishRecord(source, active);
858
-
859
- return canonical;
860
- }
861
-
862
- function auditRequirement(value) {
863
- const requirement = safeRecord();
864
-
865
- defineDataProperty(requirement, 'id', value.id);
866
- defineDataProperty(requirement, 'reasonCode', value.reasonCode);
867
- defineDataProperty(requirement, 'targetPresent', value.target !== null);
868
- defineDataProperty(
869
- requirement,
870
- 'valueType',
871
- value.value === null ? 'null' : typeof value.value
872
- );
873
-
874
- return requirement;
875
- }
876
-
877
- class TWiNPolicyDecision {
878
- constructor(token, values) {
879
- if (token !== constructionToken) {
880
- fail(
881
- 'TWIN_POLICY_DECISION_INVALID',
882
- '$',
883
- 'TWiN policy decisions must be created through the public factory.'
884
- );
885
- }
886
- defineDataProperty(this, 'schema', SCHEMA);
887
- defineDataProperty(this, 'version', VERSION);
888
- defineDataProperty(this, 'id', values.id);
889
- defineDataProperty(this, 'evaluatedAt', values.evaluatedAt);
890
- defineDataProperty(this, 'layer', values.layer);
891
- defineDataProperty(this, 'policy', values.policy);
892
- defineDataProperty(this, 'outcome', values.outcome);
893
- defineDataProperty(this, 'reasonCodes', values.reasonCodes);
894
- defineDataProperty(this, 'requirements', values.requirements);
895
- decisionInstances.add(this);
896
- }
897
-
898
- toJSON() {
899
- return canonicalRecord(normalizeValues(this));
900
- }
901
- }
902
-
903
- function buildDecision(canonical) {
904
- const values = normalizeValues(canonical);
905
-
906
- return new TWiNPolicyDecision(constructionToken, values);
907
- }
908
-
909
- export function createTWiNPolicyDecision(payload, trustedContext) {
910
- return validationBoundary(
911
- function validateCreateInput() {
912
- return buildDecision(normalizeCreatePayload(payload, trustedContext));
913
- }
914
- );
915
- }
916
-
917
- export function rehydrateTWiNPolicyDecision(canonical) {
918
- return validationBoundary(
919
- function validateCanonicalInput() {
920
- let source = canonical;
921
- let canonicalText = null;
922
-
923
- if (typeof source === 'string') {
924
- canonicalText = source;
925
- source = JSON.parse(source);
926
- }
927
- const decision = buildDecision(source);
928
-
929
- if (
930
- canonicalText !== null
931
- && canonicalText !== JSON.stringify(canonicalRecord(decision))
932
- ) {
933
- fail(
934
- 'TWIN_POLICY_DECISION_INVALID',
935
- '$',
936
- 'TWiN policy decision JSON is not canonical.'
937
- );
938
- }
939
-
940
- return decision;
941
- }
942
- );
943
- }
944
-
945
- export function serializeTWiNPolicyDecision(decision) {
946
- return JSON.stringify(canonicalRecord(rehydrateTWiNPolicyDecision(decision)));
947
- }
948
-
949
- export function twinPolicyDecisionAuditProjection(decision) {
950
- const value = rehydrateTWiNPolicyDecision(decision);
951
- const requirements = [];
952
-
953
- for (let index = 0; index < value.requirements.length; index += 1) {
954
- appendOwn(
955
- requirements,
956
- auditRequirement(value.requirements[index])
957
- );
958
- }
959
- const projection = safeRecord();
960
-
961
- defineDataProperty(projection, 'schema', value.schema);
962
- defineDataProperty(projection, 'version', value.version);
963
- defineDataProperty(projection, 'id', value.id);
964
- defineDataProperty(projection, 'evaluatedAt', value.evaluatedAt);
965
- defineDataProperty(projection, 'layer', value.layer);
966
- defineDataProperty(projection, 'policy', clonePolicy(value.policy));
967
- defineDataProperty(projection, 'outcome', value.outcome);
968
- defineDataProperty(
969
- projection,
970
- 'reasonCodes',
971
- safeArray(value.reasonCodes)
972
- );
973
- defineDataProperty(
974
- projection,
975
- 'requirements',
976
- safeArray(requirements)
977
- );
978
-
979
- return projection;
980
- }
981
-
982
- const limits = {
983
- identifierCharacters: IDENTIFIER_MAXIMUM_CHARACTERS
984
- };
985
- const contractValue = {
986
- schema: SCHEMA,
987
- version: VERSION,
988
- layers: [...LAYERS],
989
- outcomes: [...OUTCOMES],
990
- requirementOutcomes: [...REQUIREMENT_OUTCOMES],
991
- coreReasonCodes: CORE_REASON_CODES,
992
- limits
993
- };
994
-
995
- export const twinPolicyDecisionContract = contractValue;