@pdtf/schemas 3.5.0 → 3.5.1-2
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/.claude/settings.local.json +12 -2
- package/README.md +75 -56
- package/package.json +1 -1
- package/src/examples/v3/exampleTransaction.json +4 -0
- package/src/schemas/v3/combined.json +79 -17
- package/src/schemas/v3/compactSkeleton.txt +749 -1
- package/src/schemas/v3/overlays/lpe1.json +0 -57
- package/src/schemas/v3/overlays/ta6.json +42 -1
- package/src/schemas/v3/overlays/ta7.json +46 -3
- package/src/schemas/v3/pdtf-transaction.json +76 -4
- package/src/schemas/v3/skeleton.json +860 -1
- package/src/schemas/verifiedClaims/README.md +12 -3
- package/src/schemas/verifiedClaims/pdtf-verified-claims.json +16 -0
- package/src/tests/v3/transactionSchema.test.js +79 -60
- package/src/tests/v3/verifiedClaimsValidator.test.js +67 -0
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
## README
|
|
2
2
|
|
|
3
3
|
### About verified claims
|
|
4
|
-
`pdtf-verified-claims.json` describes the schema container in which we store property `claims` and their corresponding `verification`.
|
|
4
|
+
`pdtf-verified-claims.json` describes the schema container in which we store property `claims` and their corresponding `verification`.
|
|
5
5
|
|
|
6
|
-
This schema structure is strongly influenced by OpenID Connect for Identity Assurance 1.0's way of storing "verified claims".
|
|
6
|
+
This schema structure is strongly influenced by OpenID Connect for Identity Assurance 1.0's way of storing "verified claims".
|
|
7
7
|
|
|
8
8
|
More Details from the OIDC page which describes the details the nature of `verification` and `claims` objects: https://openid.net/specs/openid-connect-4-identity-assurance-1_0.html#name-representing-verified-claim
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
### Terms of Use
|
|
11
|
+
The schema supports an optional `terms_of_use` object to control access to verified claims based on confidentiality levels:
|
|
12
|
+
|
|
13
|
+
* **Public**: No access restrictions (`allowed_roles: []`)
|
|
14
|
+
* **Restricted**: Requires some participant role but not specifically defined (`allowed_roles: []`)
|
|
15
|
+
* **Confidential**: Specific roles defined for access control (e.g., `allowed_roles: ["Seller's Conveyancer", "Buyer's Conveyancer"]`)
|
|
16
|
+
|
|
17
|
+
The `allowed_roles` array accepts any of the participant roles defined in [pdtf-transaction.json](../v3/pdtf-transaction.json).
|
|
18
|
+
|
|
19
|
+
Notes:
|
|
11
20
|
* For PDTF Schema, all data included in the `claims` object must adhere to [pdtf-transaction.json (v3)](../v3/pdtf-transaction.json)
|
|
12
21
|
* For verification, there is more freedom in terms of the type of data that can currently be provided, see the documentation in the OIDC link above for details and examples.
|
|
@@ -411,6 +411,22 @@
|
|
|
411
411
|
},
|
|
412
412
|
"minProperties": 1,
|
|
413
413
|
"additionalProperties": false
|
|
414
|
+
},
|
|
415
|
+
"terms_of_use": {
|
|
416
|
+
"type": "object",
|
|
417
|
+
"properties": {
|
|
418
|
+
"confidentiality_level": {
|
|
419
|
+
"type": "string",
|
|
420
|
+
"enum": ["public", "restricted", "confidential"]
|
|
421
|
+
},
|
|
422
|
+
"allowed_roles": {
|
|
423
|
+
"type": "array",
|
|
424
|
+
"items": {
|
|
425
|
+
"type": "string"
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
"additionalProperties": false
|
|
414
430
|
}
|
|
415
431
|
},
|
|
416
432
|
"required": ["verification", "claims"],
|
|
@@ -314,9 +314,11 @@ test("correctly gets a subschema validator for a TA6 overlay which validates wit
|
|
|
314
314
|
);
|
|
315
315
|
const data = jp.get(exampleTransaction, path);
|
|
316
316
|
data.uprn = undefined;
|
|
317
|
-
const validator = getSubschemaValidator(
|
|
318
|
-
|
|
319
|
-
|
|
317
|
+
const validator = getSubschemaValidator(
|
|
318
|
+
path,
|
|
319
|
+
clonedExampleTransaction.$schema,
|
|
320
|
+
["ta6ed4"]
|
|
321
|
+
);
|
|
320
322
|
let isValid = validator(data);
|
|
321
323
|
expect(isValid).toBe(true);
|
|
322
324
|
});
|
|
@@ -458,7 +460,7 @@ test("ntsl2025 requires parking and listingAndConservation fields", () => {
|
|
|
458
460
|
const clonedExampleTransaction = JSON.parse(
|
|
459
461
|
JSON.stringify(exampleTransaction)
|
|
460
462
|
);
|
|
461
|
-
|
|
463
|
+
|
|
462
464
|
// Set up lettings-specific fields
|
|
463
465
|
clonedExampleTransaction.propertyPack.lettingInformation = {
|
|
464
466
|
rent: 3500,
|
|
@@ -467,32 +469,47 @@ test("ntsl2025 requires parking and listingAndConservation fields", () => {
|
|
|
467
469
|
};
|
|
468
470
|
delete clonedExampleTransaction.propertyPack.priceInformation;
|
|
469
471
|
delete clonedExampleTransaction.propertyPack.ownership;
|
|
470
|
-
|
|
472
|
+
|
|
471
473
|
// Remove ALL parking fields - this should make validation fail
|
|
472
474
|
delete clonedExampleTransaction.propertyPack.parking.parkingArrangements;
|
|
473
475
|
delete clonedExampleTransaction.propertyPack.parking.disabledParking;
|
|
474
476
|
delete clonedExampleTransaction.propertyPack.parking.controlledParking;
|
|
475
|
-
delete clonedExampleTransaction.propertyPack.parking
|
|
476
|
-
|
|
477
|
+
delete clonedExampleTransaction.propertyPack.parking
|
|
478
|
+
.electricVehicleChargingPoint;
|
|
479
|
+
|
|
477
480
|
// Remove ALL listingAndConservation fields - this should make validation fail
|
|
478
481
|
delete clonedExampleTransaction.propertyPack.listingAndConservation.isListed;
|
|
479
|
-
delete clonedExampleTransaction.propertyPack.listingAndConservation
|
|
480
|
-
|
|
481
|
-
|
|
482
|
+
delete clonedExampleTransaction.propertyPack.listingAndConservation
|
|
483
|
+
.isConservationArea;
|
|
484
|
+
delete clonedExampleTransaction.propertyPack.listingAndConservation
|
|
485
|
+
.hasTreePreservationOrder;
|
|
486
|
+
|
|
482
487
|
const isValid = validator(clonedExampleTransaction);
|
|
483
|
-
|
|
488
|
+
|
|
484
489
|
// The data should be INVALID because required fields are missing
|
|
485
490
|
expect(isValid).toBe(false);
|
|
486
|
-
|
|
491
|
+
|
|
487
492
|
// Specifically check that parking and listingAndConservation fields are reported as missing
|
|
488
|
-
const errorMessages = validator.errors.map(e => e.message);
|
|
489
|
-
expect(errorMessages).toContain(
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
expect(errorMessages).toContain(
|
|
493
|
+
const errorMessages = validator.errors.map((e) => e.message);
|
|
494
|
+
expect(errorMessages).toContain(
|
|
495
|
+
"must have required property 'parkingArrangements'"
|
|
496
|
+
);
|
|
497
|
+
expect(errorMessages).toContain(
|
|
498
|
+
"must have required property 'disabledParking'"
|
|
499
|
+
);
|
|
500
|
+
expect(errorMessages).toContain(
|
|
501
|
+
"must have required property 'controlledParking'"
|
|
502
|
+
);
|
|
503
|
+
expect(errorMessages).toContain(
|
|
504
|
+
"must have required property 'electricVehicleChargingPoint'"
|
|
505
|
+
);
|
|
493
506
|
expect(errorMessages).toContain("must have required property 'isListed'");
|
|
494
|
-
expect(errorMessages).toContain(
|
|
495
|
-
|
|
507
|
+
expect(errorMessages).toContain(
|
|
508
|
+
"must have required property 'isConservationArea'"
|
|
509
|
+
);
|
|
510
|
+
expect(errorMessages).toContain(
|
|
511
|
+
"must have required property 'hasTreePreservationOrder'"
|
|
512
|
+
);
|
|
496
513
|
});
|
|
497
514
|
|
|
498
515
|
test("waterAndDrainage state is invalid under nts2023 overlay when mainsFoulDrainage yesNo is Not known", () => {
|
|
@@ -500,36 +517,37 @@ test("waterAndDrainage state is invalid under nts2023 overlay when mainsFoulDrai
|
|
|
500
517
|
const clonedExampleTransaction = JSON.parse(
|
|
501
518
|
JSON.stringify(exampleTransaction)
|
|
502
519
|
);
|
|
503
|
-
|
|
520
|
+
|
|
504
521
|
// Set the waterAndDrainage to an actually invalid state for nts2023
|
|
505
|
-
// The nts overlay restricts mainsFoulDrainage.yesNo to only "Yes" or "No"
|
|
522
|
+
// The nts overlay restricts mainsFoulDrainage.yesNo to only "Yes" or "No"
|
|
506
523
|
// (not "Not known" like the base schema allows)
|
|
507
524
|
clonedExampleTransaction.propertyPack.waterAndDrainage = {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
514
|
-
}
|
|
525
|
+
water: {
|
|
526
|
+
mainsWater: {
|
|
527
|
+
yesNo: "Yes",
|
|
528
|
+
waterMeter: {
|
|
529
|
+
isSupplyMetered: "No",
|
|
530
|
+
},
|
|
531
|
+
},
|
|
515
532
|
},
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
533
|
+
drainage: {
|
|
534
|
+
mainsSurfaceWaterDrainage: {
|
|
535
|
+
yesNo: "Yes",
|
|
519
536
|
},
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
523
|
-
}
|
|
537
|
+
mainsFoulDrainage: {
|
|
538
|
+
yesNo: "Not known", // This is invalid under nts2023 overlay
|
|
539
|
+
},
|
|
540
|
+
},
|
|
524
541
|
};
|
|
525
|
-
|
|
542
|
+
|
|
526
543
|
const isValid = validator(clonedExampleTransaction);
|
|
527
544
|
expect(isValid).toBe(false);
|
|
528
|
-
|
|
545
|
+
|
|
529
546
|
// Check that the validation error is about the invalid enum value
|
|
530
|
-
const relevantError = validator.errors.find(
|
|
531
|
-
error
|
|
532
|
-
|
|
547
|
+
const relevantError = validator.errors.find(
|
|
548
|
+
(error) =>
|
|
549
|
+
error.instancePath.includes("mainsFoulDrainage") &&
|
|
550
|
+
error.message.includes("must be equal to one of the allowed values")
|
|
533
551
|
);
|
|
534
552
|
expect(relevantError).toBeDefined();
|
|
535
553
|
});
|
|
@@ -539,36 +557,37 @@ test("waterAndDrainage state with mainsFoulDrainage No missing offMainsDrainageS
|
|
|
539
557
|
const clonedExampleTransaction = JSON.parse(
|
|
540
558
|
JSON.stringify(exampleTransaction)
|
|
541
559
|
);
|
|
542
|
-
|
|
560
|
+
|
|
543
561
|
// Set the waterAndDrainage to the state from the user request
|
|
544
562
|
// This SHOULD be invalid because offMainsDrainageSystem is required when mainsFoulDrainage.yesNo is "No"
|
|
545
563
|
clonedExampleTransaction.propertyPack.waterAndDrainage = {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
}
|
|
564
|
+
water: {
|
|
565
|
+
mainsWater: {
|
|
566
|
+
yesNo: "Yes",
|
|
567
|
+
waterMeter: {
|
|
568
|
+
isSupplyMetered: "No",
|
|
569
|
+
},
|
|
570
|
+
},
|
|
553
571
|
},
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
572
|
+
drainage: {
|
|
573
|
+
mainsSurfaceWaterDrainage: {
|
|
574
|
+
yesNo: "Yes",
|
|
557
575
|
},
|
|
558
|
-
|
|
559
|
-
|
|
576
|
+
mainsFoulDrainage: {
|
|
577
|
+
yesNo: "No",
|
|
560
578
|
// Missing offMainsDrainageSystem - this should make validation fail
|
|
561
|
-
}
|
|
562
|
-
}
|
|
579
|
+
},
|
|
580
|
+
},
|
|
563
581
|
};
|
|
564
|
-
|
|
582
|
+
|
|
565
583
|
const isValid = validator(clonedExampleTransaction);
|
|
566
584
|
expect(isValid).toBe(false);
|
|
567
|
-
|
|
585
|
+
|
|
568
586
|
// Check that the validation error is about missing offMainsDrainageSystem
|
|
569
|
-
const relevantError = validator.errors.find(
|
|
570
|
-
error
|
|
571
|
-
|
|
587
|
+
const relevantError = validator.errors.find(
|
|
588
|
+
(error) =>
|
|
589
|
+
error.instancePath.includes("mainsFoulDrainage") &&
|
|
590
|
+
error.message.includes("offMainsDrainageSystem")
|
|
572
591
|
);
|
|
573
592
|
expect(relevantError).toBeDefined();
|
|
574
593
|
});
|
|
@@ -126,3 +126,70 @@ test("returns an empty array of errors for verified claim with multiple valid pa
|
|
|
126
126
|
};
|
|
127
127
|
expect(validateVerifiedClaims([clonedVouch], v3SchemaId, null)).toEqual([]);
|
|
128
128
|
});
|
|
129
|
+
|
|
130
|
+
test("returns an empty array for valid terms_of_use object with confidential level", () => {
|
|
131
|
+
const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
|
|
132
|
+
clonedVouch.terms_of_use = {
|
|
133
|
+
confidentiality_level: "confidential",
|
|
134
|
+
allowed_roles: ["Seller's Conveyancer", "Buyer's Conveyancer"]
|
|
135
|
+
};
|
|
136
|
+
expect(validateVerifiedClaims([clonedVouch])).toEqual([]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("returns an empty array for valid terms_of_use with public confidentiality", () => {
|
|
140
|
+
const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
|
|
141
|
+
clonedVouch.terms_of_use = {
|
|
142
|
+
confidentiality_level: "public",
|
|
143
|
+
allowed_roles: []
|
|
144
|
+
};
|
|
145
|
+
expect(validateVerifiedClaims([clonedVouch])).toEqual([]);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("returns an empty array for valid terms_of_use with restricted level", () => {
|
|
149
|
+
const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
|
|
150
|
+
clonedVouch.terms_of_use = {
|
|
151
|
+
confidentiality_level: "restricted",
|
|
152
|
+
allowed_roles: []
|
|
153
|
+
};
|
|
154
|
+
expect(validateVerifiedClaims([clonedVouch])).toEqual([]);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("returns errors for invalid confidentiality_level in terms_of_use", () => {
|
|
158
|
+
const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
|
|
159
|
+
clonedVouch.terms_of_use = {
|
|
160
|
+
confidentiality_level: "invalid_level",
|
|
161
|
+
allowed_roles: ["Seller's Conveyancer"]
|
|
162
|
+
};
|
|
163
|
+
const errors = validateVerifiedClaims([clonedVouch]);
|
|
164
|
+
expect(errors).toHaveLength(1);
|
|
165
|
+
expect(errors[0]).toHaveLength(1);
|
|
166
|
+
expect(errors[0][0].instancePath).toBe("/verified_claims/0/terms_of_use/confidentiality_level");
|
|
167
|
+
expect(errors[0][0].keyword).toBe("enum");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("returns errors for invalid allowed_roles type in terms_of_use", () => {
|
|
171
|
+
const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
|
|
172
|
+
clonedVouch.terms_of_use = {
|
|
173
|
+
confidentiality_level: "confidential",
|
|
174
|
+
allowed_roles: "not_an_array"
|
|
175
|
+
};
|
|
176
|
+
const errors = validateVerifiedClaims([clonedVouch]);
|
|
177
|
+
expect(errors).toHaveLength(1);
|
|
178
|
+
expect(errors[0]).toHaveLength(1);
|
|
179
|
+
expect(errors[0][0].instancePath).toBe("/verified_claims/0/terms_of_use/allowed_roles");
|
|
180
|
+
expect(errors[0][0].keyword).toBe("type");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("returns errors for additional properties in terms_of_use", () => {
|
|
184
|
+
const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
|
|
185
|
+
clonedVouch.terms_of_use = {
|
|
186
|
+
confidentiality_level: "confidential",
|
|
187
|
+
allowed_roles: ["Estate Agent"],
|
|
188
|
+
extra_property: "should_not_be_allowed"
|
|
189
|
+
};
|
|
190
|
+
const errors = validateVerifiedClaims([clonedVouch]);
|
|
191
|
+
expect(errors).toHaveLength(1);
|
|
192
|
+
expect(errors[0]).toHaveLength(1);
|
|
193
|
+
expect(errors[0][0].instancePath).toBe("/verified_claims/0/terms_of_use");
|
|
194
|
+
expect(errors[0][0].keyword).toBe("additionalProperties");
|
|
195
|
+
});
|