From cbbcba7d90ec95fcf1db84e29f34e145f4272496 Mon Sep 17 00:00:00 2001 From: Greg von Nessi Date: Sun, 15 Feb 2026 13:34:15 +0000 Subject: [PATCH] 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) --- .github/workflows/validate-schemas.yml | 11 ++ .../comprehensive-document/assets/index.json | 111 ++++++++++++++++++ .../provenance/lineage.json | 36 ++++++ .../signed-document/security/annotations.json | 38 ++++++ package-lock.json | 12 +- scripts/check-spec-schema-sync.ts | 7 +- scripts/lib/ajv-utils.ts | 25 ++++ scripts/validate-examples.ts | 24 +--- scripts/validate-schemas.ts | 22 +--- 9 files changed, 239 insertions(+), 47 deletions(-) create mode 100644 examples/comprehensive-document/assets/index.json create mode 100644 examples/comprehensive-document/provenance/lineage.json create mode 100644 examples/signed-document/security/annotations.json create mode 100644 scripts/lib/ajv-utils.ts diff --git a/.github/workflows/validate-schemas.yml b/.github/workflows/validate-schemas.yml index 183f70b..d8d8176 100644 --- a/.github/workflows/validate-schemas.yml +++ b/.github/workflows/validate-schemas.yml @@ -6,6 +6,7 @@ on: - 'schemas/**' - 'examples/**' - 'scripts/**' + - 'spec/**' - 'package.json' - '.github/workflows/validate-schemas.yml' pull_request: @@ -13,6 +14,7 @@ on: - 'schemas/**' - 'examples/**' - 'scripts/**' + - 'spec/**' - 'package.json' - '.github/workflows/validate-schemas.yml' @@ -30,8 +32,17 @@ jobs: - name: Install dependencies run: npm ci + - name: TypeScript type check + run: npx tsc --noEmit + - name: Validate schemas and examples run: npm test - name: Check spec-schema sync run: npm run check:sync + + - name: Validate cross-references + run: npm run check:refs + + - name: Check example coverage + run: npm run check:coverage diff --git a/examples/comprehensive-document/assets/index.json b/examples/comprehensive-document/assets/index.json new file mode 100644 index 0000000..f0c7328 --- /dev/null +++ b/examples/comprehensive-document/assets/index.json @@ -0,0 +1,111 @@ +{ + "version": "0.1", + "families": [ + { + "name": "Inter", + "fonts": [ + { + "id": "font-inter-regular", + "weight": 400, + "style": "normal" + }, + { + "id": "font-inter-bold", + "weight": 700, + "style": "normal" + }, + { + "id": "font-inter-italic", + "weight": 400, + "style": "italic" + } + ] + } + ], + "assets": [ + { + "id": "hero-image", + "path": "images/hero.png", + "type": "image/png", + "size": 245760, + "hash": "sha256:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", + "metadata": { + "width": 1920, + "height": 1080, + "colorSpace": "sRGB", + "hasAlpha": false, + "dpi": 72 + }, + "license": { + "name": "CC BY 4.0", + "url": "https://creativecommons.org/licenses/by/4.0/" + }, + "variants": [ + { + "path": "images/hero-thumb.png", + "width": 480, + "size": 20480 + }, + { + "path": "images/hero-medium.png", + "width": 960, + "size": 81920 + } + ] + }, + { + "id": "diagram-svg", + "path": "images/architecture.svg", + "type": "image/svg+xml", + "size": 8192, + "hash": "sha256:b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3" + }, + { + "id": "font-inter-regular", + "path": "fonts/Inter-Regular.woff2", + "type": "font/woff2", + "size": 98304, + "hash": "sha256:c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", + "metadata": { + "family": "Inter", + "weight": 400, + "style": "normal" + } + }, + { + "id": "font-inter-bold", + "path": "fonts/Inter-Bold.woff2", + "type": "font/woff2", + "size": 102400, + "hash": "sha256:d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5", + "metadata": { + "family": "Inter", + "weight": 700, + "style": "normal" + } + }, + { + "id": "font-inter-italic", + "path": "fonts/Inter-Italic.woff2", + "type": "font/woff2", + "size": 100352, + "hash": "sha256:e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6", + "metadata": { + "family": "Inter", + "weight": 400, + "style": "italic" + } + }, + { + "id": "supplementary-pdf", + "path": "embeds/appendix-data.pdf", + "type": "application/pdf", + "size": 524288, + "hash": "sha256:f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1", + "metadata": { + "filename": "appendix-data.pdf", + "description": "Supplementary data tables" + } + } + ] +} diff --git a/examples/comprehensive-document/provenance/lineage.json b/examples/comprehensive-document/provenance/lineage.json new file mode 100644 index 0000000..fabf2a5 --- /dev/null +++ b/examples/comprehensive-document/provenance/lineage.json @@ -0,0 +1,36 @@ +{ + "version": "0.1", + "documentId": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "created": "2025-01-28T14:00:00Z", + "creator": { + "name": "Document Author", + "identifier": "https://orcid.org/0000-0002-1825-0097", + "organization": "Example University" + }, + "lineage": { + "parent": null, + "ancestors": [], + "depth": 1 + }, + "merkle": { + "root": "sha256:f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3", + "blockCount": 24, + "algorithm": "sha256" + }, + "timestamps": [ + { + "type": "rfc3161", + "time": "2025-01-28T14:05:00Z", + "hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "authority": "http://timestamp.digicert.com", + "token": "MIIEjDAVBgkqhkiG9w0BCQUxCDAGBgQBMjM0..." + } + ], + "derivedFrom": [ + { + "documentId": "sha256:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", + "relationship": "revision", + "blocks": ["section-2", "section-3"] + } + ] +} diff --git a/examples/signed-document/security/annotations.json b/examples/signed-document/security/annotations.json new file mode 100644 index 0000000..5db0b1a --- /dev/null +++ b/examples/signed-document/security/annotations.json @@ -0,0 +1,38 @@ +{ + "version": "0.1", + "annotations": [ + { + "id": "ann-001", + "type": "comment", + "anchor": { + "blockId": "para-1", + "start": 0, + "end": 45 + }, + "author": "Jane Reviewer", + "created": "2025-01-12T09:15:00Z", + "content": "This section needs a stronger opening statement." + }, + { + "id": "ann-002", + "type": "highlight", + "anchor": { + "blockId": "para-3" + }, + "author": "John Editor", + "created": "2025-01-13T11:30:00Z", + "content": "Key conclusion paragraph" + }, + { + "id": "ann-003", + "type": "note", + "anchor": { + "blockId": "heading-2", + "offset": 0 + }, + "author": "Jane Reviewer", + "created": "2025-01-14T08:00:00Z", + "content": "Consider restructuring this section before signing." + } + ] +} diff --git a/package-lock.json b/package-lock.json index a2aec5f..a4d3ffd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -435,18 +435,18 @@ } }, "node_modules/@types/node": { - "version": "20.19.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", - "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "dependencies": { "undici-types": "~6.21.0" } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.3", diff --git a/scripts/check-spec-schema-sync.ts b/scripts/check-spec-schema-sync.ts index 0065340..37b5589 100644 --- a/scripts/check-spec-schema-sync.ts +++ b/scripts/check-spec-schema-sync.ts @@ -171,10 +171,13 @@ function extractTypesFromSchema(filePath: string): BlockType[] { // Also check allOf conditionals in block definitions if (schema.$defs?.block?.allOf) { - const allOf = schema.$defs.block.allOf as Array>; + const allOf = schema.$defs.block.allOf as Array<{ + if?: { properties?: { type?: { const?: string } } }; + then?: unknown; + }>; for (const condition of allOf) { if (condition.if?.properties?.type?.const) { - const typeName = condition.if.properties.type.const as string; + const typeName = condition.if.properties.type.const; // Skip 'text' as it's ubiquitous (matches spec extraction behavior) if (typeName !== 'text' && !extractedTypes.includes(typeName)) { extractedTypes.push(typeName); diff --git a/scripts/lib/ajv-utils.ts b/scripts/lib/ajv-utils.ts new file mode 100644 index 0000000..e6a21c7 --- /dev/null +++ b/scripts/lib/ajv-utils.ts @@ -0,0 +1,25 @@ +/** + * Shared AJV utilities for schema validation scripts. + */ + +import Ajv2020 from 'ajv/dist/2020'; +import addFormats from 'ajv-formats'; +import * as fs from 'fs'; +import * as path from 'path'; + +const schemasDir = path.join(__dirname, '..', '..', 'schemas'); + +export function createAjv(): Ajv2020 { + const ajv = new Ajv2020({ + strict: false, + allErrors: true, + }); + addFormats(ajv); + return ajv; +} + +export function loadSchema(filename: string): object { + const filepath = path.join(schemasDir, filename); + const content = fs.readFileSync(filepath, 'utf8'); + return JSON.parse(content); +} diff --git a/scripts/validate-examples.ts b/scripts/validate-examples.ts index d5c15cc..88ba80e 100644 --- a/scripts/validate-examples.ts +++ b/scripts/validate-examples.ts @@ -4,12 +4,11 @@ * Validates example documents against their corresponding schemas. */ -import Ajv2020, { ValidateFunction } from 'ajv/dist/2020'; -import addFormats from 'ajv-formats'; +import { ValidateFunction } from 'ajv/dist/2020'; import * as fs from 'fs'; import * as path from 'path'; +import { createAjv, loadSchema } from './lib/ajv-utils.js'; -const schemasDir = path.join(__dirname, '..', 'schemas'); const examplesDir = path.join(__dirname, '..', 'examples'); interface Validation { @@ -20,21 +19,6 @@ interface Validation { // Schema validators (compiled once) const validators: Record = {}; -function createAjv(): Ajv2020 { - const ajv = new Ajv2020({ - strict: false, - allErrors: true, - }); - addFormats(ajv); - return ajv; -} - -function loadSchema(filename: string): object { - const filepath = path.join(schemasDir, filename); - const content = fs.readFileSync(filepath, 'utf8'); - return JSON.parse(content); -} - function loadJson(filepath: string): unknown { const content = fs.readFileSync(filepath, 'utf8'); return JSON.parse(content); @@ -46,6 +30,7 @@ const schemaDependencies: Record = { 'collaboration.schema.json': ['anchor.schema.json'], 'phantoms.schema.json': ['anchor.schema.json'], 'security.schema.json': ['anchor.schema.json'], + 'annotations.schema.json': ['anchor.schema.json'], }; function getValidator(schemaName: string): ValidateFunction { @@ -76,6 +61,9 @@ const extensionValidations: Validation[] = [ { schema: 'collaboration.schema.json', file: 'collaboration/changes.json' }, { schema: 'forms.schema.json', file: 'forms/data.json' }, { schema: 'phantoms.schema.json', file: 'phantoms/clusters.json' }, + { schema: 'annotations.schema.json', file: 'security/annotations.json' }, + { schema: 'asset-index.schema.json', file: 'assets/index.json' }, + { schema: 'provenance.schema.json', file: 'provenance/lineage.json' }, ]; let hasErrors = false; diff --git a/scripts/validate-schemas.ts b/scripts/validate-schemas.ts index dcddbd3..56b38ec 100644 --- a/scripts/validate-schemas.ts +++ b/scripts/validate-schemas.ts @@ -4,12 +4,7 @@ * Validates that all JSON schemas compile correctly. */ -import Ajv2020 from 'ajv/dist/2020'; -import addFormats from 'ajv-formats'; -import * as fs from 'fs'; -import * as path from 'path'; - -const schemasDir = path.join(__dirname, '..', 'schemas'); +import { createAjv, loadSchema } from './lib/ajv-utils.js'; interface DependentSchema { schema: string; @@ -42,21 +37,6 @@ const dependentSchemas: DependentSchema[] = [ let hasErrors = false; -function createAjv(): Ajv2020 { - const ajv = new Ajv2020({ - strict: false, - allErrors: true, - }); - addFormats(ajv); - return ajv; -} - -function loadSchema(filename: string): object { - const filepath = path.join(schemasDir, filename); - const content = fs.readFileSync(filepath, 'utf8'); - return JSON.parse(content); -} - console.log('Validating JSON schemas...\n'); // Validate standalone schemas