Skip to content

Commit cbbcba7

Browse files
Fix type error, improve CI pipeline, and complete example coverage
- Fix TS2339 type error in check-spec-schema-sync.ts with proper type assertion for allOf conditional array - Bump ajv 8.17.1->8.18.0 and @types/node 20.19.30->20.19.33 - Add spec/** path trigger to CI so spec changes run validation - Add tsc --noEmit, check:refs, and check:coverage to CI pipeline - Extract shared AJV utilities into scripts/lib/ajv-utils.ts to remove duplication between validate-schemas.ts and validate-examples.ts - Add annotations, asset-index, and provenance example files to achieve 15/15 schema coverage (was 12/15)
1 parent cc39fb6 commit cbbcba7

9 files changed

Lines changed: 239 additions & 47 deletions

File tree

.github/workflows/validate-schemas.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ on:
66
- 'schemas/**'
77
- 'examples/**'
88
- 'scripts/**'
9+
- 'spec/**'
910
- 'package.json'
1011
- '.github/workflows/validate-schemas.yml'
1112
pull_request:
1213
paths:
1314
- 'schemas/**'
1415
- 'examples/**'
1516
- 'scripts/**'
17+
- 'spec/**'
1618
- 'package.json'
1719
- '.github/workflows/validate-schemas.yml'
1820

@@ -30,8 +32,17 @@ jobs:
3032
- name: Install dependencies
3133
run: npm ci
3234

35+
- name: TypeScript type check
36+
run: npx tsc --noEmit
37+
3338
- name: Validate schemas and examples
3439
run: npm test
3540

3641
- name: Check spec-schema sync
3742
run: npm run check:sync
43+
44+
- name: Validate cross-references
45+
run: npm run check:refs
46+
47+
- name: Check example coverage
48+
run: npm run check:coverage
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
{
2+
"version": "0.1",
3+
"families": [
4+
{
5+
"name": "Inter",
6+
"fonts": [
7+
{
8+
"id": "font-inter-regular",
9+
"weight": 400,
10+
"style": "normal"
11+
},
12+
{
13+
"id": "font-inter-bold",
14+
"weight": 700,
15+
"style": "normal"
16+
},
17+
{
18+
"id": "font-inter-italic",
19+
"weight": 400,
20+
"style": "italic"
21+
}
22+
]
23+
}
24+
],
25+
"assets": [
26+
{
27+
"id": "hero-image",
28+
"path": "images/hero.png",
29+
"type": "image/png",
30+
"size": 245760,
31+
"hash": "sha256:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
32+
"metadata": {
33+
"width": 1920,
34+
"height": 1080,
35+
"colorSpace": "sRGB",
36+
"hasAlpha": false,
37+
"dpi": 72
38+
},
39+
"license": {
40+
"name": "CC BY 4.0",
41+
"url": "https://creativecommons.org/licenses/by/4.0/"
42+
},
43+
"variants": [
44+
{
45+
"path": "images/hero-thumb.png",
46+
"width": 480,
47+
"size": 20480
48+
},
49+
{
50+
"path": "images/hero-medium.png",
51+
"width": 960,
52+
"size": 81920
53+
}
54+
]
55+
},
56+
{
57+
"id": "diagram-svg",
58+
"path": "images/architecture.svg",
59+
"type": "image/svg+xml",
60+
"size": 8192,
61+
"hash": "sha256:b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3"
62+
},
63+
{
64+
"id": "font-inter-regular",
65+
"path": "fonts/Inter-Regular.woff2",
66+
"type": "font/woff2",
67+
"size": 98304,
68+
"hash": "sha256:c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
69+
"metadata": {
70+
"family": "Inter",
71+
"weight": 400,
72+
"style": "normal"
73+
}
74+
},
75+
{
76+
"id": "font-inter-bold",
77+
"path": "fonts/Inter-Bold.woff2",
78+
"type": "font/woff2",
79+
"size": 102400,
80+
"hash": "sha256:d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5",
81+
"metadata": {
82+
"family": "Inter",
83+
"weight": 700,
84+
"style": "normal"
85+
}
86+
},
87+
{
88+
"id": "font-inter-italic",
89+
"path": "fonts/Inter-Italic.woff2",
90+
"type": "font/woff2",
91+
"size": 100352,
92+
"hash": "sha256:e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6",
93+
"metadata": {
94+
"family": "Inter",
95+
"weight": 400,
96+
"style": "italic"
97+
}
98+
},
99+
{
100+
"id": "supplementary-pdf",
101+
"path": "embeds/appendix-data.pdf",
102+
"type": "application/pdf",
103+
"size": 524288,
104+
"hash": "sha256:f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1",
105+
"metadata": {
106+
"filename": "appendix-data.pdf",
107+
"description": "Supplementary data tables"
108+
}
109+
}
110+
]
111+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"version": "0.1",
3+
"documentId": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
4+
"created": "2025-01-28T14:00:00Z",
5+
"creator": {
6+
"name": "Document Author",
7+
"identifier": "https://orcid.org/0000-0002-1825-0097",
8+
"organization": "Example University"
9+
},
10+
"lineage": {
11+
"parent": null,
12+
"ancestors": [],
13+
"depth": 1
14+
},
15+
"merkle": {
16+
"root": "sha256:f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3",
17+
"blockCount": 24,
18+
"algorithm": "sha256"
19+
},
20+
"timestamps": [
21+
{
22+
"type": "rfc3161",
23+
"time": "2025-01-28T14:05:00Z",
24+
"hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
25+
"authority": "http://timestamp.digicert.com",
26+
"token": "MIIEjDAVBgkqhkiG9w0BCQUxCDAGBgQBMjM0..."
27+
}
28+
],
29+
"derivedFrom": [
30+
{
31+
"documentId": "sha256:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
32+
"relationship": "revision",
33+
"blocks": ["section-2", "section-3"]
34+
}
35+
]
36+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"version": "0.1",
3+
"annotations": [
4+
{
5+
"id": "ann-001",
6+
"type": "comment",
7+
"anchor": {
8+
"blockId": "para-1",
9+
"start": 0,
10+
"end": 45
11+
},
12+
"author": "Jane Reviewer",
13+
"created": "2025-01-12T09:15:00Z",
14+
"content": "This section needs a stronger opening statement."
15+
},
16+
{
17+
"id": "ann-002",
18+
"type": "highlight",
19+
"anchor": {
20+
"blockId": "para-3"
21+
},
22+
"author": "John Editor",
23+
"created": "2025-01-13T11:30:00Z",
24+
"content": "Key conclusion paragraph"
25+
},
26+
{
27+
"id": "ann-003",
28+
"type": "note",
29+
"anchor": {
30+
"blockId": "heading-2",
31+
"offset": 0
32+
},
33+
"author": "Jane Reviewer",
34+
"created": "2025-01-14T08:00:00Z",
35+
"content": "Consider restructuring this section before signing."
36+
}
37+
]
38+
}

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/check-spec-schema-sync.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,13 @@ function extractTypesFromSchema(filePath: string): BlockType[] {
171171

172172
// Also check allOf conditionals in block definitions
173173
if (schema.$defs?.block?.allOf) {
174-
const allOf = schema.$defs.block.allOf as Array<Record<string, unknown>>;
174+
const allOf = schema.$defs.block.allOf as Array<{
175+
if?: { properties?: { type?: { const?: string } } };
176+
then?: unknown;
177+
}>;
175178
for (const condition of allOf) {
176179
if (condition.if?.properties?.type?.const) {
177-
const typeName = condition.if.properties.type.const as string;
180+
const typeName = condition.if.properties.type.const;
178181
// Skip 'text' as it's ubiquitous (matches spec extraction behavior)
179182
if (typeName !== 'text' && !extractedTypes.includes(typeName)) {
180183
extractedTypes.push(typeName);

scripts/lib/ajv-utils.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Shared AJV utilities for schema validation scripts.
3+
*/
4+
5+
import Ajv2020 from 'ajv/dist/2020';
6+
import addFormats from 'ajv-formats';
7+
import * as fs from 'fs';
8+
import * as path from 'path';
9+
10+
const schemasDir = path.join(__dirname, '..', '..', 'schemas');
11+
12+
export function createAjv(): Ajv2020 {
13+
const ajv = new Ajv2020({
14+
strict: false,
15+
allErrors: true,
16+
});
17+
addFormats(ajv);
18+
return ajv;
19+
}
20+
21+
export function loadSchema(filename: string): object {
22+
const filepath = path.join(schemasDir, filename);
23+
const content = fs.readFileSync(filepath, 'utf8');
24+
return JSON.parse(content);
25+
}

scripts/validate-examples.ts

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
* Validates example documents against their corresponding schemas.
55
*/
66

7-
import Ajv2020, { ValidateFunction } from 'ajv/dist/2020';
8-
import addFormats from 'ajv-formats';
7+
import { ValidateFunction } from 'ajv/dist/2020';
98
import * as fs from 'fs';
109
import * as path from 'path';
10+
import { createAjv, loadSchema } from './lib/ajv-utils.js';
1111

12-
const schemasDir = path.join(__dirname, '..', 'schemas');
1312
const examplesDir = path.join(__dirname, '..', 'examples');
1413

1514
interface Validation {
@@ -20,21 +19,6 @@ interface Validation {
2019
// Schema validators (compiled once)
2120
const validators: Record<string, ValidateFunction> = {};
2221

23-
function createAjv(): Ajv2020 {
24-
const ajv = new Ajv2020({
25-
strict: false,
26-
allErrors: true,
27-
});
28-
addFormats(ajv);
29-
return ajv;
30-
}
31-
32-
function loadSchema(filename: string): object {
33-
const filepath = path.join(schemasDir, filename);
34-
const content = fs.readFileSync(filepath, 'utf8');
35-
return JSON.parse(content);
36-
}
37-
3822
function loadJson(filepath: string): unknown {
3923
const content = fs.readFileSync(filepath, 'utf8');
4024
return JSON.parse(content);
@@ -46,6 +30,7 @@ const schemaDependencies: Record<string, string[]> = {
4630
'collaboration.schema.json': ['anchor.schema.json'],
4731
'phantoms.schema.json': ['anchor.schema.json'],
4832
'security.schema.json': ['anchor.schema.json'],
33+
'annotations.schema.json': ['anchor.schema.json'],
4934
};
5035

5136
function getValidator(schemaName: string): ValidateFunction {
@@ -76,6 +61,9 @@ const extensionValidations: Validation[] = [
7661
{ schema: 'collaboration.schema.json', file: 'collaboration/changes.json' },
7762
{ schema: 'forms.schema.json', file: 'forms/data.json' },
7863
{ schema: 'phantoms.schema.json', file: 'phantoms/clusters.json' },
64+
{ schema: 'annotations.schema.json', file: 'security/annotations.json' },
65+
{ schema: 'asset-index.schema.json', file: 'assets/index.json' },
66+
{ schema: 'provenance.schema.json', file: 'provenance/lineage.json' },
7967
];
8068

8169
let hasErrors = false;

scripts/validate-schemas.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,7 @@
44
* Validates that all JSON schemas compile correctly.
55
*/
66

7-
import Ajv2020 from 'ajv/dist/2020';
8-
import addFormats from 'ajv-formats';
9-
import * as fs from 'fs';
10-
import * as path from 'path';
11-
12-
const schemasDir = path.join(__dirname, '..', 'schemas');
7+
import { createAjv, loadSchema } from './lib/ajv-utils.js';
138

149
interface DependentSchema {
1510
schema: string;
@@ -42,21 +37,6 @@ const dependentSchemas: DependentSchema[] = [
4237

4338
let hasErrors = false;
4439

45-
function createAjv(): Ajv2020 {
46-
const ajv = new Ajv2020({
47-
strict: false,
48-
allErrors: true,
49-
});
50-
addFormats(ajv);
51-
return ajv;
52-
}
53-
54-
function loadSchema(filename: string): object {
55-
const filepath = path.join(schemasDir, filename);
56-
const content = fs.readFileSync(filepath, 'utf8');
57-
return JSON.parse(content);
58-
}
59-
6040
console.log('Validating JSON schemas...\n');
6141

6242
// Validate standalone schemas

0 commit comments

Comments
 (0)