diff --git a/README.md b/README.md index 5a7e7ed..3d62592 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Codex Document Format +[![Validate Schemas](https://github.com/gvonness-apolitical/codex-file-format-spec/actions/workflows/validate-schemas.yml/badge.svg)](https://github.com/gvonness-apolitical/codex-file-format-spec/actions/workflows/validate-schemas.yml) +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) + **An open specification for documents that unify viewing and editing, with modern security and machine readability.** > Status: Draft Specification (v0.1) diff --git a/examples/academic-document/academic/numbering.json b/examples/academic-document/academic/numbering.json new file mode 100644 index 0000000..3b4bfc6 --- /dev/null +++ b/examples/academic-document/academic/numbering.json @@ -0,0 +1,22 @@ +{ + "version": "0.1", + "equations": { + "style": "number", + "resetOn": "chapter" + }, + "theorems": { + "style": "chapter.number", + "counters": { + "theorem": { "share": ["lemma", "proposition", "corollary"] }, + "definition": { "share": [] } + } + }, + "algorithms": { + "style": "number", + "resetOn": "none" + }, + "exercises": { + "style": "chapter.number", + "resetOn": "chapter" + } +} diff --git a/examples/academic-document/content/document.json b/examples/academic-document/content/document.json new file mode 100644 index 0000000..acf4924 --- /dev/null +++ b/examples/academic-document/content/document.json @@ -0,0 +1,392 @@ +{ + "version": "0.1", + "blocks": [ + { + "type": "heading", + "id": "title", + "level": 1, + "children": [ + { "type": "text", "value": "Introduction to Analysis" } + ] + }, + { + "type": "paragraph", + "id": "intro", + "children": [ + { "type": "text", "value": "This document demonstrates the academic extension with theorems, proofs, exercises, equations, and algorithms." } + ] + }, + { + "type": "heading", + "id": "ch1", + "level": 2, + "children": [ + { "type": "text", "value": "Chapter 1: Foundations" } + ] + }, + { + "type": "academic:theorem", + "variant": "definition", + "id": "def-continuous", + "number": "1.1", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "A function " }, + { "type": "text", "value": "f: X -> Y", "marks": [{ "type": "math", "format": "latex", "source": "f: X \\to Y" }] }, + { "type": "text", "value": " is " }, + { "type": "text", "value": "continuous", "marks": ["bold"] }, + { "type": "text", "value": " at a point " }, + { "type": "text", "value": "x_0", "marks": [{ "type": "math", "format": "latex", "source": "x_0" }] }, + { "type": "text", "value": " if for every " }, + { "type": "text", "value": "epsilon > 0", "marks": [{ "type": "math", "format": "latex", "source": "\\epsilon > 0" }] }, + { "type": "text", "value": " there exists " }, + { "type": "text", "value": "delta > 0", "marks": [{ "type": "math", "format": "latex", "source": "\\delta > 0" }] }, + { "type": "text", "value": " such that " }, + { "type": "text", "value": "|x - x_0| < delta implies |f(x) - f(x_0)| < epsilon", "marks": [{ "type": "math", "format": "latex", "source": "|x - x_0| < \\delta \\implies |f(x) - f(x_0)| < \\epsilon" }] }, + { "type": "text", "value": "." } + ] + } + ] + }, + { + "type": "academic:theorem", + "variant": "theorem", + "id": "thm-ivt", + "number": "1.2", + "title": "Intermediate Value Theorem", + "uses": ["#def-continuous"], + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Let " }, + { "type": "text", "value": "f: [a,b] -> R", "marks": [{ "type": "math", "format": "latex", "source": "f: [a,b] \\to \\mathbb{R}" }] }, + { "type": "text", "value": " be continuous. If " }, + { "type": "text", "value": "y", "marks": [{ "type": "math", "format": "latex", "source": "y" }] }, + { "type": "text", "value": " is between " }, + { "type": "text", "value": "f(a)", "marks": [{ "type": "math", "format": "latex", "source": "f(a)" }] }, + { "type": "text", "value": " and " }, + { "type": "text", "value": "f(b)", "marks": [{ "type": "math", "format": "latex", "source": "f(b)" }] }, + { "type": "text", "value": ", then there exists " }, + { "type": "text", "value": "c in (a,b)", "marks": [{ "type": "math", "format": "latex", "source": "c \\in (a,b)" }] }, + { "type": "text", "value": " such that " }, + { "type": "text", "value": "f(c) = y", "marks": [{ "type": "math", "format": "latex", "source": "f(c) = y" }] }, + { "type": "text", "value": "." } + ] + } + ] + }, + { + "type": "academic:proof", + "of": "#thm-ivt", + "method": "construction", + "qed": "symbol", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Without loss of generality, assume " }, + { "type": "text", "value": "f(a) < y < f(b)", "marks": [{ "type": "math", "format": "latex", "source": "f(a) < y < f(b)" }] }, + { "type": "text", "value": ". Define " }, + { "type": "text", "value": "S = {x in [a,b] : f(x) < y}", "marks": [{ "type": "math", "format": "latex", "source": "S = \\{x \\in [a,b] : f(x) < y\\}" }] }, + { "type": "text", "value": ". The set " }, + { "type": "text", "value": "S", "marks": [{ "type": "math", "format": "latex", "source": "S" }] }, + { "type": "text", "value": " is non-empty (contains " }, + { "type": "text", "value": "a", "marks": [{ "type": "math", "format": "latex", "source": "a" }] }, + { "type": "text", "value": ") and bounded above by " }, + { "type": "text", "value": "b", "marks": [{ "type": "math", "format": "latex", "source": "b" }] }, + { "type": "text", "value": ". Let " }, + { "type": "text", "value": "c = sup S", "marks": [{ "type": "math", "format": "latex", "source": "c = \\sup S" }] }, + { "type": "text", "value": ". By continuity, " }, + { "type": "text", "value": "f(c) = y", "marks": [{ "type": "math", "format": "latex", "source": "f(c) = y" }] }, + { "type": "text", "value": "." } + ] + } + ] + }, + { + "type": "paragraph", + "id": "thm-ref-example", + "children": [ + { "type": "text", "value": "By " }, + { + "type": "text", + "value": "Theorem 1.2", + "marks": [ + { + "type": "theorem-ref", + "target": "#thm-ivt", + "format": "Theorem {number}" + } + ] + }, + { "type": "text", "value": ", every polynomial of odd degree has a real root." } + ] + }, + { + "type": "heading", + "id": "sec-equations", + "level": 2, + "children": [ + { "type": "text", "value": "Multi-Line Equations" } + ] + }, + { + "type": "academic:equation-group", + "environment": "align", + "id": "eq-taylor", + "lines": [ + { + "value": "e^x &= \\sum_{n=0}^{\\infty} \\frac{x^n}{n!}", + "number": "1", + "id": "eq-exp" + }, + { + "value": "&= 1 + x + \\frac{x^2}{2!} + \\frac{x^3}{3!} + \\cdots", + "number": null + }, + { + "value": "\\sin x &= \\sum_{n=0}^{\\infty} \\frac{(-1)^n x^{2n+1}}{(2n+1)!}", + "number": "2", + "id": "eq-sin" + }, + { + "value": "\\cos x &= \\sum_{n=0}^{\\infty} \\frac{(-1)^n x^{2n}}{(2n)!}", + "number": "3", + "id": "eq-cos" + } + ] + }, + { + "type": "paragraph", + "id": "eq-ref-example", + "children": [ + { "type": "text", "value": "From " }, + { + "type": "text", + "value": "(1)", + "marks": [ + { + "type": "equation-ref", + "target": "#eq-exp", + "format": "({number})" + } + ] + }, + { "type": "text", "value": " and " }, + { + "type": "text", + "value": "(2)", + "marks": [ + { + "type": "equation-ref", + "target": "#eq-sin", + "format": "({number})" + } + ] + }, + { "type": "text", "value": ", we can derive Euler's formula." } + ] + }, + { + "type": "heading", + "id": "sec-algorithms", + "level": 2, + "children": [ + { "type": "text", "value": "Algorithms" } + ] + }, + { + "type": "academic:algorithm", + "id": "alg-bisection", + "number": "1", + "title": "Bisection", + "caption": "Root finding by bisection method", + "lineNumbering": true, + "inputs": [ + { "name": "f", "description": "Continuous function" }, + { "name": "a, b", "description": "Interval endpoints with f(a)f(b) < 0" }, + { "name": "tol", "description": "Tolerance" } + ], + "outputs": [ + { "name": "c", "description": "Approximate root" } + ], + "lines": [ + { "content": "\\textbf{while} |b - a| > \\text{tol} \\textbf{do}", "label": "loop" }, + { "content": "c \\gets (a + b) / 2", "indent": 1, "label": "midpoint" }, + { "content": "\\textbf{if} f(c) = 0 \\textbf{then}", "indent": 1 }, + { "content": "\\textbf{return} c", "indent": 2 }, + { "content": "\\textbf{else if} f(a) \\cdot f(c) < 0 \\textbf{then}", "indent": 1 }, + { "content": "b \\gets c", "indent": 2 }, + { "content": "\\textbf{else}", "indent": 1 }, + { "content": "a \\gets c", "indent": 2 }, + { "content": "\\textbf{end if}", "indent": 1 }, + { "content": "\\textbf{end while}", "label": "endloop" }, + { "content": "\\textbf{return} (a + b) / 2", "comment": "approximate root" } + ] + }, + { + "type": "paragraph", + "id": "alg-ref-example", + "children": [ + { "type": "text", "value": "The loop at " }, + { + "type": "text", + "value": "line 1", + "marks": [ + { + "type": "algorithm-ref", + "target": "#alg-bisection", + "line": "loop", + "format": "line {line}" + } + ] + }, + { "type": "text", "value": " of " }, + { + "type": "text", + "value": "Algorithm 1", + "marks": [ + { + "type": "algorithm-ref", + "target": "#alg-bisection", + "format": "Algorithm {number}" + } + ] + }, + { "type": "text", "value": " converges linearly." } + ] + }, + { + "type": "heading", + "id": "sec-exercises", + "level": 2, + "children": [ + { "type": "text", "value": "Exercises" } + ] + }, + { + "type": "academic:exercise", + "id": "ex-1", + "number": "1.1", + "difficulty": "easy", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Prove that " }, + { "type": "text", "value": "f(x) = x^2", "marks": [{ "type": "math", "format": "latex", "source": "f(x) = x^2" }] }, + { "type": "text", "value": " is continuous at " }, + { "type": "text", "value": "x = 0", "marks": [{ "type": "math", "format": "latex", "source": "x = 0" }] }, + { "type": "text", "value": " using the epsilon-delta definition." } + ] + } + ], + "hints": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Try " }, + { "type": "text", "value": "delta = sqrt(epsilon)", "marks": [{ "type": "math", "format": "latex", "source": "\\delta = \\sqrt{\\epsilon}" }] }, + { "type": "text", "value": "." } + ] + } + ], + "solution": { + "visibility": "spoiler", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Given " }, + { "type": "text", "value": "epsilon > 0", "marks": [{ "type": "math", "format": "latex", "source": "\\epsilon > 0" }] }, + { "type": "text", "value": ", choose " }, + { "type": "text", "value": "delta = sqrt(epsilon)", "marks": [{ "type": "math", "format": "latex", "source": "\\delta = \\sqrt{\\epsilon}" }] }, + { "type": "text", "value": ". Then " }, + { "type": "text", "value": "|x| < delta implies |x^2| < delta^2 = epsilon", "marks": [{ "type": "math", "format": "latex", "source": "|x| < \\delta \\implies |x^2| < \\delta^2 = \\epsilon" }] }, + { "type": "text", "value": "." } + ] + } + ] + } + }, + { + "type": "academic:exercise", + "id": "ex-2", + "number": "1.2", + "difficulty": "medium", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Let " }, + { "type": "text", "value": "f(x) = x^3 - x - 1", "marks": [{ "type": "math", "format": "latex", "source": "f(x) = x^3 - x - 1" }] }, + { "type": "text", "value": "." } + ] + } + ], + "parts": [ + { + "label": "a", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Show that " }, + { "type": "text", "value": "f", "marks": [{ "type": "math", "format": "latex", "source": "f" }] }, + { "type": "text", "value": " has a root in " }, + { "type": "text", "value": "(1, 2)", "marks": [{ "type": "math", "format": "latex", "source": "(1, 2)" }] }, + { "type": "text", "value": "." } + ] + } + ], + "solution": { + "visibility": "hidden", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "We have " }, + { "type": "text", "value": "f(1) = -1 < 0", "marks": [{ "type": "math", "format": "latex", "source": "f(1) = -1 < 0" }] }, + { "type": "text", "value": " and " }, + { "type": "text", "value": "f(2) = 5 > 0", "marks": [{ "type": "math", "format": "latex", "source": "f(2) = 5 > 0" }] }, + { "type": "text", "value": ". By the IVT, there exists a root in " }, + { "type": "text", "value": "(1, 2)", "marks": [{ "type": "math", "format": "latex", "source": "(1, 2)" }] }, + { "type": "text", "value": "." } + ] + } + ] + } + }, + { + "label": "b", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Apply three iterations of the bisection method." } + ] + } + ], + "solution": { + "visibility": "hidden", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Iteration 1: " }, + { "type": "text", "value": "c = 1.5, f(1.5) = 0.875 > 0", "marks": [{ "type": "math", "format": "latex", "source": "c = 1.5, f(1.5) = 0.875 > 0" }] }, + { "type": "text", "value": ", so root is in " }, + { "type": "text", "value": "(1, 1.5)", "marks": [{ "type": "math", "format": "latex", "source": "(1, 1.5)" }] }, + { "type": "text", "value": "." } + ] + } + ] + } + } + ] + } + ] +} diff --git a/examples/academic-document/manifest.json b/examples/academic-document/manifest.json new file mode 100644 index 0000000..e83f999 --- /dev/null +++ b/examples/academic-document/manifest.json @@ -0,0 +1,24 @@ +{ + "codex": "0.1", + "id": "pending", + "state": "draft", + "created": "2025-01-28T14:00:00Z", + "modified": "2025-01-28T14:00:00Z", + "extensions": [ + { + "id": "codex.academic", + "version": "0.1", + "required": false + } + ], + "content": { + "path": "content/document.json", + "hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "metadata": { + "dublinCore": "metadata/dublin-core.json" + }, + "academic": { + "numbering": "academic/numbering.json" + } +} diff --git a/examples/academic-document/metadata/dublin-core.json b/examples/academic-document/metadata/dublin-core.json new file mode 100644 index 0000000..124a6c7 --- /dev/null +++ b/examples/academic-document/metadata/dublin-core.json @@ -0,0 +1,14 @@ +{ + "version": "1.1", + "terms": { + "title": "Introduction to Analysis", + "creator": ["Dr. Jane Smith"], + "subject": ["mathematics", "analysis", "calculus"], + "description": "A demonstration of academic extension features including theorems, proofs, exercises, and algorithms.", + "date": "2025-01-28", + "type": "Text", + "format": "application/vnd.codex+zip", + "language": "en", + "rights": "CC BY 4.0" + } +} diff --git a/examples/collaboration-document/collaboration/changes.json b/examples/collaboration-document/collaboration/changes.json new file mode 100644 index 0000000..97c3376 --- /dev/null +++ b/examples/collaboration-document/collaboration/changes.json @@ -0,0 +1,80 @@ +{ + "version": "0.2", + "baseVersion": "sha256:abc123def456789012345678901234567890123456789012345678901234", + "crdtFormat": "yjs", + "changes": [ + { + "id": "change-1", + "type": "insert", + "anchor": { "blockId": "obj-4" }, + "position": { "after": "obj-3" }, + "author": { + "name": "Bob Martinez", + "email": "bob@example.com", + "userId": "user-002", + "color": "#4ecdc4" + }, + "timestamp": "2025-01-28T09:00:00Z", + "status": "accepted" + }, + { + "id": "change-2", + "type": "modify", + "anchor": { "blockId": "para-timeline" }, + "before": { + "type": "paragraph", + "children": [ + { "type": "text", "value": "The project is estimated to require 6 months for initial deployment, with an estimated budget of $100,000." } + ] + }, + "after": { + "type": "paragraph", + "children": [ + { "type": "text", "value": "The project is estimated to require 6 months for initial deployment, with an estimated budget of $150,000. Phase 1 will focus on core functionality, with subsequent phases adding advanced features." } + ] + }, + "author": { + "name": "David Lee", + "email": "david@example.com", + "userId": "user-004", + "color": "#dda0dd" + }, + "timestamp": "2025-01-28T11:00:00Z", + "status": "accepted" + }, + { + "id": "change-3", + "type": "format", + "anchor": { "blockId": "title" }, + "before": { "level": 2 }, + "after": { "level": 1 }, + "author": { + "name": "Alice Chen", + "email": "alice@example.com", + "userId": "user-001", + "color": "#ff6b6b" + }, + "timestamp": "2025-01-28T09:30:00Z", + "status": "accepted" + }, + { + "id": "change-4", + "type": "delete", + "anchor": { "blockId": "removed-section" }, + "before": { + "type": "paragraph", + "children": [ + { "type": "text", "value": "This section was removed during editing." } + ] + }, + "author": { + "name": "Carol Davis", + "email": "carol@example.com", + "userId": "user-003", + "color": "#95e1d3" + }, + "timestamp": "2025-01-28T10:00:00Z", + "status": "pending" + } + ] +} diff --git a/examples/collaboration-document/collaboration/comments.json b/examples/collaboration-document/collaboration/comments.json new file mode 100644 index 0000000..2452f1e --- /dev/null +++ b/examples/collaboration-document/collaboration/comments.json @@ -0,0 +1,114 @@ +{ + "version": "0.2", + "crdtFormat": "yjs", + "comments": [ + { + "id": "comment-1", + "type": "comment", + "anchor": { "blockId": "para-1", "start": 78, "end": 100 }, + "author": { + "name": "Alice Chen", + "email": "alice@example.com", + "userId": "user-001", + "color": "#ff6b6b" + }, + "created": "2025-01-28T10:15:00Z", + "content": "Can we be more specific about what 'organizational efficiency' means in this context?", + "resolved": false, + "replies": [ + { + "id": "reply-1-1", + "author": { + "name": "Bob Martinez", + "email": "bob@example.com", + "userId": "user-002", + "color": "#4ecdc4" + }, + "created": "2025-01-28T10:30:00Z", + "content": "Good point. I'll add some metrics in the next revision." + }, + { + "id": "reply-1-2", + "author": { + "name": "Alice Chen", + "email": "alice@example.com", + "userId": "user-001", + "color": "#ff6b6b" + }, + "created": "2025-01-28T10:45:00Z", + "content": "Thanks! Consider including time savings and error reduction rates." + } + ] + }, + { + "id": "comment-2", + "type": "highlight", + "anchor": { "blockId": "obj-2" }, + "author": { + "name": "Carol Davis", + "email": "carol@example.com", + "userId": "user-003", + "color": "#95e1d3" + }, + "created": "2025-01-28T11:00:00Z", + "content": "This is a key differentiator from our current system.", + "resolved": false + }, + { + "id": "suggestion-1", + "type": "suggestion", + "anchor": { "blockId": "para-timeline", "start": 47, "end": 56 }, + "author": { + "name": "David Lee", + "email": "david@example.com", + "userId": "user-004", + "color": "#dda0dd" + }, + "created": "2025-01-28T11:30:00Z", + "originalText": "6 months", + "suggestedText": "8 months", + "status": "pending" + }, + { + "id": "suggestion-2", + "type": "suggestion", + "anchor": { "blockId": "para-timeline", "start": 106, "end": 114 }, + "author": { + "name": "David Lee", + "email": "david@example.com", + "userId": "user-004", + "color": "#dda0dd" + }, + "created": "2025-01-28T11:32:00Z", + "originalText": "$150,000", + "suggestedText": "$200,000", + "status": "accepted" + }, + { + "id": "reaction-1", + "type": "reaction", + "anchor": { "blockId": "para-conclusion" }, + "author": { + "name": "Eve Wilson", + "email": "eve@example.com", + "userId": "user-005", + "color": "#ffd93d" + }, + "created": "2025-01-28T12:00:00Z", + "emoji": "thumbsup" + }, + { + "id": "reaction-2", + "type": "reaction", + "anchor": { "blockId": "para-conclusion" }, + "author": { + "name": "Frank Brown", + "email": "frank@example.com", + "userId": "user-006", + "color": "#6c5ce7" + }, + "created": "2025-01-28T12:05:00Z", + "emoji": "rocket" + } + ] +} diff --git a/examples/collaboration-document/content/document.json b/examples/collaboration-document/content/document.json new file mode 100644 index 0000000..0c439fd --- /dev/null +++ b/examples/collaboration-document/content/document.json @@ -0,0 +1,121 @@ +{ + "version": "0.1", + "blocks": [ + { + "type": "heading", + "id": "title", + "level": 1, + "children": [ + { "type": "text", "value": "Project Proposal: Document Management System" } + ] + }, + { + "type": "heading", + "id": "exec-summary", + "level": 2, + "children": [ + { "type": "text", "value": "Executive Summary" } + ] + }, + { + "type": "paragraph", + "id": "para-1", + "children": [ + { "type": "text", "value": "This proposal outlines a new document management system designed to improve organizational efficiency and collaboration. The system will provide version control, access management, and real-time collaboration features." } + ] + }, + { + "type": "heading", + "id": "objectives", + "level": 2, + "children": [ + { "type": "text", "value": "Project Objectives" } + ] + }, + { + "type": "list", + "id": "obj-list", + "ordered": true, + "children": [ + { + "type": "listItem", + "id": "obj-1", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Implement secure document storage with encryption" } + ] + } + ] + }, + { + "type": "listItem", + "id": "obj-2", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Enable real-time collaborative editing" } + ] + } + ] + }, + { + "type": "listItem", + "id": "obj-3", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Provide comprehensive audit trails" } + ] + } + ] + }, + { + "type": "listItem", + "id": "obj-4", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Support integration with existing enterprise systems" } + ] + } + ] + } + ] + }, + { + "type": "heading", + "id": "timeline", + "level": 2, + "children": [ + { "type": "text", "value": "Timeline and Budget" } + ] + }, + { + "type": "paragraph", + "id": "para-timeline", + "children": [ + { "type": "text", "value": "The project is estimated to require 6 months for initial deployment, with an estimated budget of $150,000. Phase 1 will focus on core functionality, with subsequent phases adding advanced features." } + ] + }, + { + "type": "heading", + "id": "conclusion", + "level": 2, + "children": [ + { "type": "text", "value": "Conclusion" } + ] + }, + { + "type": "paragraph", + "id": "para-conclusion", + "children": [ + { "type": "text", "value": "We believe this system will significantly improve document workflow and reduce operational costs. We recommend proceeding with Phase 1 immediately." } + ] + } + ] +} diff --git a/examples/collaboration-document/manifest.json b/examples/collaboration-document/manifest.json new file mode 100644 index 0000000..43f82f5 --- /dev/null +++ b/examples/collaboration-document/manifest.json @@ -0,0 +1,25 @@ +{ + "codex": "0.1", + "id": "pending", + "state": "draft", + "created": "2025-01-28T14:00:00Z", + "modified": "2025-01-28T14:00:00Z", + "extensions": [ + { + "id": "codex.collaboration", + "version": "0.2", + "required": false + } + ], + "content": { + "path": "content/document.json", + "hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "metadata": { + "dublinCore": "metadata/dublin-core.json" + }, + "collaboration": { + "comments": "collaboration/comments.json", + "changes": "collaboration/changes.json" + } +} diff --git a/examples/collaboration-document/metadata/dublin-core.json b/examples/collaboration-document/metadata/dublin-core.json new file mode 100644 index 0000000..15fb143 --- /dev/null +++ b/examples/collaboration-document/metadata/dublin-core.json @@ -0,0 +1,14 @@ +{ + "version": "1.1", + "terms": { + "title": "Project Proposal: Document Management System", + "creator": ["Bob Martinez", "Alice Chen", "Carol Davis", "David Lee"], + "subject": ["project proposal", "document management", "collaboration"], + "description": "A demonstration of collaboration extension features including comments, suggestions, reactions, and tracked changes with multiple authors.", + "date": "2025-01-28", + "type": "Text", + "format": "application/vnd.codex+zip", + "language": "en", + "rights": "Confidential" + } +} diff --git a/examples/forms-document/content/document.json b/examples/forms-document/content/document.json new file mode 100644 index 0000000..200931b --- /dev/null +++ b/examples/forms-document/content/document.json @@ -0,0 +1,251 @@ +{ + "version": "0.1", + "blocks": [ + { + "type": "heading", + "id": "title", + "level": 1, + "children": [ + { "type": "text", "value": "Conference Registration Form" } + ] + }, + { + "type": "paragraph", + "id": "intro", + "children": [ + { "type": "text", "value": "Please complete the following form to register for the Annual Technology Conference 2025." } + ] + }, + { + "type": "forms:form", + "id": "registration-form", + "children": [ + { + "type": "heading", + "id": "personal-info", + "level": 2, + "children": [ + { "type": "text", "value": "Personal Information" } + ] + }, + { + "type": "forms:textInput", + "id": "field-name", + "name": "fullName", + "label": "Full Name", + "placeholder": "Enter your full name", + "required": true, + "validation": { + "minLength": 2, + "maxLength": 100, + "message": "Please enter your full name (2-100 characters)" + }, + "fallback": { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Full Name: _________________________________" } + ] + } + }, + { + "type": "forms:textInput", + "id": "field-email", + "name": "email", + "label": "Email Address", + "placeholder": "you@example.com", + "required": true, + "inputType": "email", + "validation": { + "email": true, + "message": "Please enter a valid email address" + } + }, + { + "type": "forms:textInput", + "id": "field-phone", + "name": "phone", + "label": "Phone Number", + "placeholder": "+1 (555) 123-4567", + "inputType": "tel", + "validation": { + "pattern": "^\\+?[0-9\\s\\-\\(\\)]+$", + "message": "Please enter a valid phone number" + } + }, + { + "type": "heading", + "id": "employment-section", + "level": 2, + "children": [ + { "type": "text", "value": "Employment Information" } + ] + }, + { + "type": "forms:dropdown", + "id": "field-employment", + "name": "employmentType", + "label": "Employment Status", + "required": true, + "options": [ + { "value": "employed", "label": "Employed" }, + { "value": "self-employed", "label": "Self-Employed" }, + { "value": "student", "label": "Student" }, + { "value": "retired", "label": "Retired" }, + { "value": "other", "label": "Other" } + ] + }, + { + "type": "forms:textInput", + "id": "field-company", + "name": "companyName", + "label": "Company/Organization Name", + "placeholder": "Enter company name", + "conditionalValidation": { + "when": { "field": "employmentType", "equals": "employed" }, + "then": { + "required": true, + "minLength": 2, + "message": "Company name is required for employed attendees" + } + } + }, + { + "type": "forms:textInput", + "id": "field-job-title", + "name": "jobTitle", + "label": "Job Title", + "placeholder": "e.g., Software Engineer" + }, + { + "type": "heading", + "id": "registration-section", + "level": 2, + "children": [ + { "type": "text", "value": "Registration Options" } + ] + }, + { + "type": "forms:radioGroup", + "id": "field-ticket", + "name": "ticketType", + "label": "Ticket Type", + "required": true, + "options": [ + { "value": "standard", "label": "Standard ($299)" }, + { "value": "premium", "label": "Premium with Workshops ($499)" }, + { "value": "student", "label": "Student ($99)" }, + { "value": "virtual", "label": "Virtual Attendance ($49)" } + ] + }, + { + "type": "forms:dropdown", + "id": "field-sessions", + "name": "sessions", + "label": "Preferred Sessions", + "multiple": true, + "searchable": true, + "options": [ + { "value": "ai-ml", "label": "AI & Machine Learning" }, + { "value": "web-dev", "label": "Web Development" }, + { "value": "security", "label": "Cybersecurity" }, + { "value": "cloud", "label": "Cloud Computing" }, + { "value": "mobile", "label": "Mobile Development" }, + { "value": "data", "label": "Data Science" } + ] + }, + { + "type": "forms:checkbox", + "id": "field-lunch", + "name": "includeLunch", + "label": "Include lunch package (+$50)" + }, + { + "type": "heading", + "id": "dietary-section", + "level": 2, + "children": [ + { "type": "text", "value": "Dietary Requirements" } + ] + }, + { + "type": "forms:dropdown", + "id": "field-dietary", + "name": "dietaryRequirements", + "label": "Dietary Restrictions", + "options": [ + { "value": "none", "label": "No restrictions" }, + { "value": "vegetarian", "label": "Vegetarian" }, + { "value": "vegan", "label": "Vegan" }, + { "value": "halal", "label": "Halal" }, + { "value": "kosher", "label": "Kosher" }, + { "value": "gluten-free", "label": "Gluten-free" }, + { "value": "other", "label": "Other (please specify)" } + ] + }, + { + "type": "forms:textArea", + "id": "field-dietary-notes", + "name": "dietaryNotes", + "label": "Additional Dietary Notes", + "placeholder": "Please describe any allergies or special requirements", + "rows": 3, + "conditionalValidation": { + "when": { "field": "dietaryRequirements", "equals": "other" }, + "then": { + "required": true, + "message": "Please specify your dietary requirements" + } + } + }, + { + "type": "heading", + "id": "terms-section", + "level": 2, + "children": [ + { "type": "text", "value": "Terms and Conditions" } + ] + }, + { + "type": "forms:checkbox", + "id": "field-terms", + "name": "agreeTerms", + "label": "I agree to the terms and conditions", + "required": true, + "validation": { + "required": true, + "message": "You must agree to the terms and conditions to register" + } + }, + { + "type": "forms:checkbox", + "id": "field-newsletter", + "name": "subscribeNewsletter", + "label": "Subscribe to our newsletter for updates" + }, + { + "type": "forms:signature", + "id": "field-signature", + "name": "signature", + "label": "Signature", + "required": true, + "width": 400, + "height": 150 + }, + { + "type": "forms:datePicker", + "id": "field-date", + "name": "signatureDate", + "label": "Date", + "format": "YYYY-MM-DD", + "maxDate": "today", + "required": true + }, + { + "type": "forms:submit", + "id": "submit-btn", + "label": "Complete Registration" + } + ] + } + ] +} diff --git a/examples/forms-document/forms/data.json b/examples/forms-document/forms/data.json new file mode 100644 index 0000000..82617ee --- /dev/null +++ b/examples/forms-document/forms/data.json @@ -0,0 +1,22 @@ +{ + "version": "0.1", + "values": { + "fullName": "Jane Doe", + "email": "jane.doe@example.com", + "phone": "+1 (555) 123-4567", + "employmentType": "employed", + "companyName": "Acme Corporation", + "jobTitle": "Senior Software Engineer", + "ticketType": "premium", + "sessions": ["ai-ml", "cloud", "security"], + "includeLunch": true, + "dietaryRequirements": "vegetarian", + "dietaryNotes": "", + "agreeTerms": true, + "subscribeNewsletter": true, + "signature": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", + "signatureDate": "2025-01-28" + }, + "submitted": false, + "lastModified": "2025-01-28T15:30:00Z" +} diff --git a/examples/forms-document/manifest.json b/examples/forms-document/manifest.json new file mode 100644 index 0000000..fc8db7c --- /dev/null +++ b/examples/forms-document/manifest.json @@ -0,0 +1,21 @@ +{ + "codex": "0.1", + "id": "pending", + "state": "draft", + "created": "2025-01-28T14:00:00Z", + "modified": "2025-01-28T14:00:00Z", + "extensions": [ + { + "id": "codex.forms", + "version": "0.1", + "required": false + } + ], + "content": { + "path": "content/document.json", + "hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "metadata": { + "dublinCore": "metadata/dublin-core.json" + } +} diff --git a/examples/forms-document/metadata/dublin-core.json b/examples/forms-document/metadata/dublin-core.json new file mode 100644 index 0000000..0f207fa --- /dev/null +++ b/examples/forms-document/metadata/dublin-core.json @@ -0,0 +1,14 @@ +{ + "version": "1.1", + "terms": { + "title": "Conference Registration Form", + "creator": "Conference Organizers", + "subject": ["registration", "forms", "conference"], + "description": "A demonstration of forms extension features including text inputs, dropdowns, checkboxes, validation, conditional validation, and signatures.", + "date": "2025-01-28", + "type": "InteractiveResource", + "format": "application/vnd.codex+zip", + "language": "en", + "rights": "All rights reserved" + } +} diff --git a/examples/phantoms-document/content/document.json b/examples/phantoms-document/content/document.json new file mode 100644 index 0000000..ce7a10c --- /dev/null +++ b/examples/phantoms-document/content/document.json @@ -0,0 +1,95 @@ +{ + "version": "0.1", + "blocks": [ + { + "type": "heading", + "id": "title", + "level": 1, + "children": [ + { "type": "text", "value": "Software Architecture Overview" } + ] + }, + { + "type": "paragraph", + "id": "intro", + "children": [ + { "type": "text", "value": "This document provides an overview of the system architecture, including key components and their interactions." } + ] + }, + { + "type": "heading", + "id": "components", + "level": 2, + "children": [ + { "type": "text", "value": "System Components" } + ] + }, + { + "type": "paragraph", + "id": "para-components", + "children": [ + { "type": "text", "value": "The system consists of three main components: the API Gateway, the Application Server, and the Database Layer. Each component is designed for horizontal scalability." } + ] + }, + { + "type": "heading", + "id": "api-section", + "level": 3, + "children": [ + { "type": "text", "value": "API Gateway" } + ] + }, + { + "type": "paragraph", + "id": "para-api", + "children": [ + { "type": "text", "value": "The API Gateway handles all incoming requests, performs authentication, rate limiting, and routes requests to appropriate services." } + ] + }, + { + "type": "heading", + "id": "app-section", + "level": 3, + "children": [ + { "type": "text", "value": "Application Server" } + ] + }, + { + "type": "paragraph", + "id": "para-app", + "children": [ + { "type": "text", "value": "The Application Server contains the business logic and processes requests from the API Gateway. It communicates with the Database Layer for data persistence." } + ] + }, + { + "type": "heading", + "id": "db-section", + "level": 3, + "children": [ + { "type": "text", "value": "Database Layer" } + ] + }, + { + "type": "paragraph", + "id": "para-db", + "children": [ + { "type": "text", "value": "The Database Layer provides data storage using a combination of PostgreSQL for relational data and Redis for caching." } + ] + }, + { + "type": "heading", + "id": "deployment", + "level": 2, + "children": [ + { "type": "text", "value": "Deployment Architecture" } + ] + }, + { + "type": "paragraph", + "id": "para-deployment", + "children": [ + { "type": "text", "value": "The system is deployed on Kubernetes, with each component running in its own pod. Auto-scaling is configured based on CPU and memory utilization." } + ] + } + ] +} diff --git a/examples/phantoms-document/manifest.json b/examples/phantoms-document/manifest.json new file mode 100644 index 0000000..1d6bf6f --- /dev/null +++ b/examples/phantoms-document/manifest.json @@ -0,0 +1,24 @@ +{ + "codex": "0.1", + "id": "pending", + "state": "draft", + "created": "2025-01-28T14:00:00Z", + "modified": "2025-01-28T14:00:00Z", + "extensions": [ + { + "id": "codex.phantoms", + "version": "0.1", + "required": false + } + ], + "content": { + "path": "content/document.json", + "hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "metadata": { + "dublinCore": "metadata/dublin-core.json" + }, + "phantoms": { + "clusters": "phantoms/clusters.json" + } +} diff --git a/examples/phantoms-document/metadata/dublin-core.json b/examples/phantoms-document/metadata/dublin-core.json new file mode 100644 index 0000000..22f8771 --- /dev/null +++ b/examples/phantoms-document/metadata/dublin-core.json @@ -0,0 +1,14 @@ +{ + "version": "1.1", + "terms": { + "title": "Software Architecture Overview", + "creator": "Engineering Team", + "subject": ["software architecture", "system design", "documentation"], + "description": "A demonstration of phantoms extension features including annotation clusters with different scopes (shared, role-based, private), phantom connections, and nested content.", + "date": "2025-01-28", + "type": "Text", + "format": "application/vnd.codex+zip", + "language": "en", + "rights": "Internal Use Only" + } +} diff --git a/examples/phantoms-document/phantoms/clusters.json b/examples/phantoms-document/phantoms/clusters.json new file mode 100644 index 0000000..62d9a45 --- /dev/null +++ b/examples/phantoms-document/phantoms/clusters.json @@ -0,0 +1,191 @@ +{ + "version": "0.1", + "clusters": [ + { + "id": "cluster-architecture-notes", + "anchor": { "blockId": "para-components" }, + "label": "Architecture Notes", + "scope": "shared", + "author": { + "name": "Sarah Johnson", + "email": "sarah@example.com", + "identifier": "https://orcid.org/0000-0002-1234-5678" + }, + "created": "2025-01-28T09:00:00Z", + "metadata": { + "color": "#4a90d9", + "collapsed": false + }, + "phantoms": [ + { + "id": "phantom-1", + "position": { "x": 0, "y": 0 }, + "size": { "width": 200, "height": 100 }, + "content": { + "blocks": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Consider adding a message queue between API Gateway and App Server for async processing." } + ] + } + ] + }, + "created": "2025-01-28T09:15:00Z", + "author": { + "name": "Sarah Johnson", + "email": "sarah@example.com" + } + }, + { + "id": "phantom-2", + "position": { "x": 250, "y": 0 }, + "size": { "width": 180, "height": 80 }, + "content": { + "blocks": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "RabbitMQ or Kafka?" } + ] + } + ] + }, + "connections": [ + { "target": "phantom-1", "style": "arrow", "label": "relates to" } + ], + "created": "2025-01-28T09:20:00Z", + "author": { + "name": "Mike Chen", + "email": "mike@example.com" + } + }, + { + "id": "phantom-3", + "position": { "x": 125, "y": 130 }, + "size": { "width": 200, "height": 60 }, + "content": { + "blocks": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Kafka for high throughput, RabbitMQ for simpler setup" } + ] + } + ] + }, + "connections": [ + { "target": "phantom-2", "style": "dashed" } + ], + "created": "2025-01-28T09:25:00Z", + "author": { + "name": "Sarah Johnson", + "email": "sarah@example.com" + } + } + ] + }, + { + "id": "cluster-security-review", + "anchor": { "blockId": "para-api" }, + "label": "Security Review", + "scope": "role:security-team", + "author": { + "name": "Alex Security", + "email": "alex.sec@example.com" + }, + "created": "2025-01-28T10:00:00Z", + "metadata": { + "color": "#e74c3c", + "collapsed": true + }, + "phantoms": [ + { + "id": "phantom-sec-1", + "position": { "x": 0, "y": 0 }, + "size": { "width": 250, "height": 120 }, + "content": { + "blocks": [ + { + "type": "heading", + "level": 3, + "children": [ + { "type": "text", "value": "Security Checklist" } + ] + }, + { + "type": "list", + "ordered": false, + "children": [ + { + "type": "listItem", + "checked": true, + "children": [ + { "type": "paragraph", "children": [{ "type": "text", "value": "OAuth 2.0 implementation" }] } + ] + }, + { + "type": "listItem", + "checked": true, + "children": [ + { "type": "paragraph", "children": [{ "type": "text", "value": "Rate limiting configured" }] } + ] + }, + { + "type": "listItem", + "checked": false, + "children": [ + { "type": "paragraph", "children": [{ "type": "text", "value": "WAF rules reviewed" }] } + ] + } + ] + } + ] + }, + "created": "2025-01-28T10:05:00Z", + "author": { + "name": "Alex Security", + "email": "alex.sec@example.com" + } + } + ] + }, + { + "id": "cluster-personal-notes", + "anchor": { "blockId": "para-db" }, + "label": "My Notes", + "scope": "private", + "author": { + "name": "Developer", + "email": "dev@example.com" + }, + "created": "2025-01-28T11:00:00Z", + "metadata": { + "color": "#9b59b6", + "collapsed": false + }, + "phantoms": [ + { + "id": "phantom-private-1", + "position": { "x": 0, "y": 0 }, + "size": { "width": 180, "height": 80 }, + "content": { + "blocks": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "TODO: Research TimescaleDB for time-series data" } + ] + } + ] + }, + "created": "2025-01-28T11:05:00Z", + "author": { + "name": "Developer", + "email": "dev@example.com" + } + } + ] + } + ] +} diff --git a/examples/semantic-document/content/document.json b/examples/semantic-document/content/document.json new file mode 100644 index 0000000..ca85213 --- /dev/null +++ b/examples/semantic-document/content/document.json @@ -0,0 +1,184 @@ +{ + "version": "0.1", + "blocks": [ + { + "type": "heading", + "id": "title", + "level": 1, + "children": [ + { "type": "text", "value": "The History of Computing" } + ] + }, + { + "type": "paragraph", + "id": "intro", + "children": [ + { "type": "text", "value": "The development of modern computing began with theoretical work by " }, + { + "type": "text", + "value": "Alan Turing", + "marks": [ + { + "type": "entity", + "uri": "https://www.wikidata.org/wiki/Q7251", + "entityType": "Person", + "source": "wikidata" + } + ] + }, + { "type": "text", "value": " in the 1930s. His concept of the " }, + { + "type": "text", + "value": "Turing machine", + "marks": [ + { "type": "glossary", "ref": "turing-machine" } + ] + }, + { "type": "text", "value": " laid the foundation for all modern computers" }, + { + "type": "text", + "value": "[1]", + "marks": [ + { + "type": "citation", + "refs": ["turing1936"], + "locator": "pp. 230-265" + } + ] + }, + { "type": "text", "value": "." } + ] + }, + { + "type": "paragraph", + "id": "para-2", + "children": [ + { "type": "text", "value": "The first electronic general-purpose computer, " }, + { + "type": "text", + "value": "ENIAC", + "marks": [ + { + "type": "entity", + "uri": "https://www.wikidata.org/wiki/Q12637", + "entityType": "Product", + "source": "wikidata" + } + ] + }, + { "type": "text", "value": ", was completed in 1945" }, + { + "type": "text", + "value": "[2]", + "marks": [ + { "type": "citation", "refs": ["goldstine1972"] } + ] + }, + { "type": "text", "value": ". This marked the beginning of the digital age" }, + { + "type": "text", + "value": "1", + "marks": [ + { "type": "footnote", "number": 1, "id": "fn1" } + ] + }, + { "type": "text", "value": "." } + ] + }, + { + "type": "semantic:footnote", + "number": 1, + "id": "fn1", + "children": [ + { + "type": "paragraph", + "children": [ + { "type": "text", "value": "Some historians argue that earlier mechanical computers, such as Babbage's Analytical Engine, should be considered the true beginning of the computing era. See " }, + { + "type": "text", + "value": "[3]", + "marks": [ + { "type": "citation", "refs": ["swade2001"] } + ] + }, + { "type": "text", "value": " for a detailed discussion." } + ] + } + ] + }, + { + "type": "heading", + "id": "sec-algorithm", + "level": 2, + "children": [ + { "type": "text", "value": "The Concept of an Algorithm" } + ] + }, + { + "type": "paragraph", + "id": "para-3", + "children": [ + { "type": "text", "value": "An " }, + { + "type": "text", + "value": "algorithm", + "marks": [ + { "type": "glossary", "ref": "algorithm" } + ] + }, + { "type": "text", "value": " is a fundamental concept in computer science. Multiple researchers have contributed to its formalization, as noted by various scholars" }, + { + "type": "text", + "value": "[1, 4]", + "marks": [ + { + "type": "citation", + "refs": ["turing1936", "knuth1997"], + "prefix": "see" + } + ] + }, + { "type": "text", "value": "." } + ] + }, + { + "type": "semantic:term", + "id": "term-algorithm", + "term": "Algorithm", + "definition": "A finite sequence of well-defined instructions for solving a class of problems or performing a computation.", + "see": ["turing-machine"] + }, + { + "type": "semantic:term", + "id": "term-turing-machine", + "term": "Turing Machine", + "definition": "A mathematical model of computation describing an abstract machine that manipulates symbols on a strip of tape according to a table of rules." + }, + { + "type": "heading", + "id": "sec-glossary", + "level": 2, + "children": [ + { "type": "text", "value": "Glossary" } + ] + }, + { + "type": "semantic:glossary", + "title": "Terms", + "sort": "alphabetical" + }, + { + "type": "heading", + "id": "sec-references", + "level": 2, + "children": [ + { "type": "text", "value": "References" } + ] + }, + { + "type": "semantic:bibliography", + "style": "apa", + "title": null + } + ] +} diff --git a/examples/semantic-document/manifest.json b/examples/semantic-document/manifest.json new file mode 100644 index 0000000..d40048e --- /dev/null +++ b/examples/semantic-document/manifest.json @@ -0,0 +1,25 @@ +{ + "codex": "0.1", + "id": "pending", + "state": "draft", + "created": "2025-01-28T14:00:00Z", + "modified": "2025-01-28T14:00:00Z", + "extensions": [ + { + "id": "codex.semantic", + "version": "0.1", + "required": false + } + ], + "content": { + "path": "content/document.json", + "hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "metadata": { + "dublinCore": "metadata/dublin-core.json" + }, + "semantic": { + "bibliography": "semantic/bibliography.json", + "glossary": "semantic/glossary.json" + } +} diff --git a/examples/semantic-document/metadata/dublin-core.json b/examples/semantic-document/metadata/dublin-core.json new file mode 100644 index 0000000..090ea1b --- /dev/null +++ b/examples/semantic-document/metadata/dublin-core.json @@ -0,0 +1,14 @@ +{ + "version": "1.1", + "terms": { + "title": "The History of Computing", + "creator": "John Smith", + "subject": ["computer science", "history", "computing"], + "description": "A demonstration of semantic extension features including citations, footnotes, glossary terms, and entity annotations.", + "date": "2025-01-28", + "type": "Text", + "format": "application/vnd.codex+zip", + "language": "en", + "rights": "CC BY 4.0" + } +} diff --git a/examples/semantic-document/semantic/bibliography.json b/examples/semantic-document/semantic/bibliography.json new file mode 100644 index 0000000..b1aa6b3 --- /dev/null +++ b/examples/semantic-document/semantic/bibliography.json @@ -0,0 +1,64 @@ +{ + "version": "0.1", + "entries": [ + { + "id": "turing1936", + "type": "article-journal", + "title": "On Computable Numbers, with an Application to the Entscheidungsproblem", + "author": [ + { "family": "Turing", "given": "Alan M." } + ], + "issued": { + "date-parts": [[1936]] + }, + "container-title": "Proceedings of the London Mathematical Society", + "volume": "42", + "issue": "1", + "page": "230-265", + "DOI": "10.1112/plms/s2-42.1.230" + }, + { + "id": "goldstine1972", + "type": "book", + "title": "The Computer from Pascal to von Neumann", + "author": [ + { "family": "Goldstine", "given": "Herman H." } + ], + "issued": { + "date-parts": [[1972]] + }, + "publisher": "Princeton University Press", + "publisher-place": "Princeton, NJ", + "ISBN": "978-0691023670" + }, + { + "id": "swade2001", + "type": "book", + "title": "The Difference Engine: Charles Babbage and the Quest to Build the First Computer", + "author": [ + { "family": "Swade", "given": "Doron" } + ], + "issued": { + "date-parts": [[2001]] + }, + "publisher": "Viking Press", + "publisher-place": "New York", + "ISBN": "978-0670910205" + }, + { + "id": "knuth1997", + "type": "book", + "title": "The Art of Computer Programming, Volume 1: Fundamental Algorithms", + "author": [ + { "family": "Knuth", "given": "Donald E." } + ], + "issued": { + "date-parts": [[1997]] + }, + "edition": "3rd", + "publisher": "Addison-Wesley", + "publisher-place": "Reading, MA", + "ISBN": "978-0201896831" + } + ] +} diff --git a/examples/semantic-document/semantic/glossary.json b/examples/semantic-document/semantic/glossary.json new file mode 100644 index 0000000..f829547 --- /dev/null +++ b/examples/semantic-document/semantic/glossary.json @@ -0,0 +1,16 @@ +{ + "version": "0.1", + "terms": [ + { + "id": "algorithm", + "term": "Algorithm", + "definition": "A finite sequence of well-defined instructions for solving a class of problems or performing a computation.", + "see": ["turing-machine"] + }, + { + "id": "turing-machine", + "term": "Turing Machine", + "definition": "A mathematical model of computation describing an abstract machine that manipulates symbols on a strip of tape according to a table of rules." + } + ] +} diff --git a/schemas/README.md b/schemas/README.md index eb7adf8..baab9fb 100644 --- a/schemas/README.md +++ b/schemas/README.md @@ -23,6 +23,9 @@ This directory contains JSON Schema definitions for validating Codex document co |--------|---------|-----------| | `semantic.schema.json` | Semantic extension | `semantic:*` blocks and marks | | `academic.schema.json` | Academic extension | `academic:*` blocks and marks | +| `collaboration.schema.json` | Collaboration extension | `collaboration/comments.json`, `collaboration/changes.json` | +| `forms.schema.json` | Forms extension | `forms/data.json`, `forms:*` blocks | +| `security.schema.json` | Security extension | `security/signatures.json`, `security/encryption.json` | ## Schema Dependencies @@ -32,11 +35,18 @@ Some schemas reference definitions from other schema files: annotations.schema.json └── $ref: anchor.schema.json#/$defs/contentAnchor +collaboration.schema.json + └── $ref: anchor.schema.json#/$defs/contentAnchor + +content.schema.json + ├── $ref: semantic.schema.json#/$defs/*Mark + └── $ref: academic.schema.json#/$defs/*Mark + phantoms.schema.json └── $ref: anchor.schema.json#/$defs/contentAnchor ``` -The `anchor.schema.json` file provides shared definitions for `ContentAnchor` and `ContentAnchorUri` that are used by core annotations, phantoms, collaboration, and other extensions. +The `anchor.schema.json` file provides shared definitions for `ContentAnchor`, `ContentAnchorUri`, and `person` that are used by core annotations, phantoms, collaboration, and other extensions. ## Using These Schemas @@ -100,3 +110,41 @@ All schemas are written for JSON Schema Draft 2020-12 (`https://json-schema.org/ - Schemas use `additionalProperties: false` on most objects to catch typos and invalid properties - The `metadata` object in `phantoms.schema.json` intentionally allows additional properties for application-specific extensibility - The `styles` object in `presentation.schema.json` allows arbitrary named styles, but each style definition is strictly validated + +## Extension Policy + +### additionalProperties Defaults + +- **Default**: `additionalProperties: false` for strict validation +- **Exceptions**: + - `metadata` objects allow custom properties (application extensibility) + - CSL bibliography entries allow additional fields (CSL spec compatibility) + - JSON-LD annotations allow arbitrary properties (linked data) + - Base Person object allows extension-specific fields via `allOf` composition + +### Shared Type Composition + +Extension schemas that need Person/author objects use `allOf` composition with the base definition from `anchor.schema.json`: + +```json +{ + "author": { + "allOf": [ + { "$ref": "anchor.schema.json#/$defs/person" }, + { + "type": "object", + "properties": { + "extensionSpecificField": { "type": "string" } + } + } + ] + } +} +``` + +### Versioning + +- Extension READMEs declare version in header (e.g., `Version: 0.1`) +- Data files include `version` field for file format versioning +- Schema `$id` URIs don't include versions (versioned by spec release) +- collaboration v0.2 is intentional (migration from v0.1 with `blockRef`/`range` to `anchor`) diff --git a/schemas/anchor.schema.json b/schemas/anchor.schema.json index 5d63673..68f3c01 100644 --- a/schemas/anchor.schema.json +++ b/schemas/anchor.schema.json @@ -1,9 +1,30 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://codex.document/schemas/anchor.schema.json", - "title": "Codex Content Anchor Definitions", - "description": "Reusable schema definitions for ContentAnchor and ContentAnchorUri, used by collaboration, phantoms, and other extensions.", + "title": "Codex Content Anchor and Person Definitions", + "description": "Reusable schema definitions for ContentAnchor, ContentAnchorUri, and Person, used by collaboration, phantoms, security, and other extensions.", "$defs": { + "person": { + "type": "object", + "description": "Base person/agent identity. Extensions may add additional properties via allOf composition.", + "required": ["name"], + "properties": { + "name": { + "type": "string", + "description": "Display name" + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address" + }, + "identifier": { + "type": "string", + "description": "Persistent identifier (ORCID, DID, URL, institutional ID). For scholarly documents, ORCID format is recommended: https://orcid.org/0000-0002-1825-0097" + } + }, + "additionalProperties": true + }, "contentAnchor": { "type": "object", "description": "Structured content anchor referencing a block, point, or range within document content.", diff --git a/schemas/collaboration.schema.json b/schemas/collaboration.schema.json new file mode 100644 index 0000000..2e5afd2 --- /dev/null +++ b/schemas/collaboration.schema.json @@ -0,0 +1,341 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://codex.document/schemas/collaboration.schema.json", + "title": "Codex Collaboration Extension", + "description": "Schema for collaboration extension files in a Codex document", + "$defs": { + "crdtFormat": { + "type": "string", + "description": "CRDT library format for interoperability", + "enum": ["yjs", "automerge", "diamond-types", "custom"] + }, + "author": { + "description": "Comment/change author information extending base person", + "allOf": [ + { "$ref": "https://codex.document/schemas/anchor.schema.json#/$defs/person" }, + { + "type": "object", + "properties": { + "userId": { + "type": "string", + "description": "User identifier in an external system" + }, + "avatar": { + "type": "string", + "format": "uri", + "description": "URL to avatar image" + }, + "color": { + "type": "string", + "description": "Color for real-time cursor/highlight display (CSS color value)" + } + } + } + ], + "unevaluatedProperties": false + }, + "commentType": { + "type": "string", + "description": "Type of comment annotation", + "enum": ["comment", "highlight", "suggestion", "reaction"] + }, + "suggestionStatus": { + "type": "string", + "description": "Status of a suggestion", + "enum": ["pending", "accepted", "rejected"] + }, + "reply": { + "type": "object", + "description": "A reply to a comment", + "required": ["id", "author", "created", "content"], + "properties": { + "id": { + "type": "string", + "description": "Unique reply identifier" + }, + "author": { + "$ref": "#/$defs/author" + }, + "created": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 creation timestamp" + }, + "content": { + "type": "string", + "description": "Reply text content" + } + }, + "additionalProperties": false + }, + "baseComment": { + "type": "object", + "description": "Base comment properties shared by all comment types", + "required": ["id", "type", "anchor", "author", "created"], + "properties": { + "id": { + "type": "string", + "description": "Unique comment identifier" + }, + "type": { + "$ref": "#/$defs/commentType" + }, + "anchor": { + "$ref": "https://codex.document/schemas/anchor.schema.json#/$defs/contentAnchor", + "description": "Anchor to content (see Anchors and References spec)" + }, + "author": { + "$ref": "#/$defs/author" + }, + "created": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 creation timestamp" + }, + "modified": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 last modification timestamp" + }, + "resolved": { + "type": "boolean", + "description": "Whether the comment is resolved", + "default": false + }, + "replies": { + "type": "array", + "description": "Reply comments", + "items": { + "$ref": "#/$defs/reply" + } + } + } + }, + "comment": { + "allOf": [ + { "$ref": "#/$defs/baseComment" }, + { + "type": "object", + "required": ["content"], + "properties": { + "type": { "const": "comment" }, + "content": { + "type": "string", + "description": "Comment text content" + } + } + } + ] + }, + "highlight": { + "allOf": [ + { "$ref": "#/$defs/baseComment" }, + { + "type": "object", + "properties": { + "type": { "const": "highlight" }, + "content": { + "type": "string", + "description": "Optional note for the highlight" + } + } + } + ] + }, + "suggestion": { + "allOf": [ + { "$ref": "#/$defs/baseComment" }, + { + "type": "object", + "required": ["originalText", "suggestedText"], + "properties": { + "type": { "const": "suggestion" }, + "originalText": { + "type": "string", + "description": "Original text being replaced" + }, + "suggestedText": { + "type": "string", + "description": "Suggested replacement text" + }, + "status": { + "$ref": "#/$defs/suggestionStatus", + "default": "pending" + } + } + } + ] + }, + "reaction": { + "allOf": [ + { "$ref": "#/$defs/baseComment" }, + { + "type": "object", + "required": ["emoji"], + "properties": { + "type": { "const": "reaction" }, + "emoji": { + "type": "string", + "description": "Unicode CLDR short name (e.g., 'thumbsup', 'heart')" + } + } + } + ] + }, + "commentEntry": { + "oneOf": [ + { "$ref": "#/$defs/comment" }, + { "$ref": "#/$defs/highlight" }, + { "$ref": "#/$defs/suggestion" }, + { "$ref": "#/$defs/reaction" } + ] + }, + "changeType": { + "type": "string", + "description": "Type of tracked change", + "enum": ["insert", "delete", "modify", "move", "format"] + }, + "changeStatus": { + "type": "string", + "description": "Status of a tracked change", + "enum": ["pending", "accepted", "rejected"] + }, + "changePosition": { + "type": "object", + "description": "Position for inserted/moved blocks", + "properties": { + "after": { + "type": "string", + "description": "Block ID to insert after" + }, + "before": { + "type": "string", + "description": "Block ID to insert before" + } + }, + "additionalProperties": false + }, + "change": { + "type": "object", + "description": "A tracked change record", + "required": ["id", "type", "anchor", "author", "timestamp"], + "properties": { + "id": { + "type": "string", + "description": "Unique change identifier" + }, + "type": { + "$ref": "#/$defs/changeType" + }, + "anchor": { + "$ref": "https://codex.document/schemas/anchor.schema.json#/$defs/contentAnchor", + "description": "Anchor to affected content" + }, + "position": { + "$ref": "#/$defs/changePosition", + "description": "Position for insert/move operations" + }, + "before": { + "type": "object", + "description": "Content state before the change (for modify/delete)" + }, + "after": { + "type": "object", + "description": "Content state after the change (for modify/insert)" + }, + "author": { + "$ref": "#/$defs/author" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 change timestamp" + }, + "status": { + "$ref": "#/$defs/changeStatus", + "default": "pending" + } + }, + "additionalProperties": false + }, + "crdtMetadata": { + "type": "object", + "description": "CRDT metadata for block-level synchronization", + "properties": { + "clock": { + "type": "object", + "description": "Vector clock for causal ordering", + "additionalProperties": { + "type": "integer" + } + }, + "origin": { + "type": "string", + "description": "Site/peer ID where this version originated" + }, + "seq": { + "type": "integer", + "description": "Local sequence number" + } + }, + "additionalProperties": false + }, + "commentsFile": { + "type": "object", + "description": "Schema for collaboration/comments.json", + "required": ["version", "comments"], + "properties": { + "version": { + "type": "string", + "description": "Collaboration extension version", + "pattern": "^\\d+\\.\\d+$" + }, + "crdtFormat": { + "$ref": "#/$defs/crdtFormat", + "description": "CRDT library format hint for interoperability" + }, + "comments": { + "type": "array", + "description": "Array of comment entries", + "items": { + "$ref": "#/$defs/commentEntry" + } + } + }, + "additionalProperties": false + }, + "changesFile": { + "type": "object", + "description": "Schema for collaboration/changes.json", + "required": ["version", "changes"], + "properties": { + "version": { + "type": "string", + "description": "Collaboration extension version", + "pattern": "^\\d+\\.\\d+$" + }, + "baseVersion": { + "type": "string", + "description": "Document ID (hash) of the base version", + "pattern": "^(sha256|sha384|sha512|sha3-256|sha3-512|blake3):[a-f0-9]+$" + }, + "crdtFormat": { + "$ref": "#/$defs/crdtFormat", + "description": "CRDT library format hint for interoperability" + }, + "changes": { + "type": "array", + "description": "Array of tracked changes", + "items": { + "$ref": "#/$defs/change" + } + } + }, + "additionalProperties": false + } + }, + "oneOf": [ + { "$ref": "#/$defs/commentsFile" }, + { "$ref": "#/$defs/changesFile" } + ] +} diff --git a/schemas/content.schema.json b/schemas/content.schema.json index d94de0a..63e613a 100644 --- a/schemas/content.schema.json +++ b/schemas/content.schema.json @@ -171,7 +171,14 @@ }, { "$ref": "#/$defs/linkMark" }, { "$ref": "#/$defs/anchorMark" }, - { "$ref": "#/$defs/mathMark" } + { "$ref": "#/$defs/mathMark" }, + { "$ref": "https://codex.document/schemas/semantic.schema.json#/$defs/citationMark" }, + { "$ref": "https://codex.document/schemas/semantic.schema.json#/$defs/footnoteMark" }, + { "$ref": "https://codex.document/schemas/semantic.schema.json#/$defs/entityMark" }, + { "$ref": "https://codex.document/schemas/semantic.schema.json#/$defs/glossaryMark" }, + { "$ref": "https://codex.document/schemas/academic.schema.json#/$defs/theoremRefMark" }, + { "$ref": "https://codex.document/schemas/academic.schema.json#/$defs/equationRefMark" }, + { "$ref": "https://codex.document/schemas/academic.schema.json#/$defs/algorithmRefMark" } ] } } diff --git a/schemas/forms.schema.json b/schemas/forms.schema.json new file mode 100644 index 0000000..2fe5f62 --- /dev/null +++ b/schemas/forms.schema.json @@ -0,0 +1,487 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://codex.document/schemas/forms.schema.json", + "title": "Codex Forms Extension", + "description": "Schema for forms extension blocks and data files in a Codex document", + "$defs": { + "validatorType": { + "type": "string", + "description": "Built-in validator types", + "enum": [ + "required", + "minLength", + "maxLength", + "min", + "max", + "pattern", + "email", + "url", + "containsUppercase", + "containsLowercase", + "containsDigit", + "containsSpecial", + "matchesField" + ] + }, + "validation": { + "type": "object", + "description": "Validation rules for a form field", + "properties": { + "required": { + "type": "boolean", + "description": "Field must have a value" + }, + "minLength": { + "type": "integer", + "minimum": 0, + "description": "Minimum string length" + }, + "maxLength": { + "type": "integer", + "minimum": 0, + "description": "Maximum string length" + }, + "min": { + "type": "number", + "description": "Minimum numeric value" + }, + "max": { + "type": "number", + "description": "Maximum numeric value" + }, + "pattern": { + "type": "string", + "description": "Regular expression pattern to match" + }, + "email": { + "type": "boolean", + "description": "Must be a valid email format" + }, + "url": { + "type": "boolean", + "description": "Must be a valid URL format" + }, + "containsUppercase": { + "type": "boolean", + "description": "Must contain at least one uppercase letter" + }, + "containsLowercase": { + "type": "boolean", + "description": "Must contain at least one lowercase letter" + }, + "containsDigit": { + "type": "boolean", + "description": "Must contain at least one digit" + }, + "containsSpecial": { + "type": "boolean", + "description": "Must contain at least one special character" + }, + "matchesField": { + "type": "string", + "description": "Must match the value of another named field" + }, + "message": { + "type": "string", + "description": "Custom error message for validation failure" + } + }, + "additionalProperties": false + }, + "conditionalValidation": { + "type": "object", + "description": "Conditional validation rules based on other field values", + "required": ["when", "then"], + "properties": { + "when": { + "type": "object", + "description": "Condition to evaluate", + "required": ["field"], + "properties": { + "field": { + "type": "string", + "description": "Name of the field to check" + }, + "equals": { + "description": "Condition is true when field equals this value" + }, + "notEquals": { + "description": "Condition is true when field does not equal this value" + }, + "isEmpty": { + "type": "boolean", + "description": "Condition is true when field is empty" + }, + "isNotEmpty": { + "type": "boolean", + "description": "Condition is true when field is not empty" + } + }, + "additionalProperties": false + }, + "then": { + "$ref": "#/$defs/validation", + "description": "Validation rules to apply when condition is met" + } + }, + "additionalProperties": false + }, + "option": { + "type": "object", + "description": "Option for dropdown, radio group, or checkbox group", + "required": ["value", "label"], + "properties": { + "value": { + "type": "string", + "description": "Option value" + }, + "label": { + "type": "string", + "description": "Display label" + }, + "disabled": { + "type": "boolean", + "description": "Whether option is disabled", + "default": false + } + }, + "additionalProperties": false + }, + "fallbackBlock": { + "type": "object", + "description": "Fallback content for viewers that don't support forms", + "required": ["type"], + "properties": { + "type": { + "type": "string", + "description": "Block type (typically 'paragraph')" + }, + "children": { + "type": "array", + "description": "Child content" + } + } + }, + "baseFormField": { + "type": "object", + "description": "Base properties shared by all form fields", + "properties": { + "id": { + "type": "string", + "description": "Unique block identifier" + }, + "name": { + "type": "string", + "description": "Field name for form data" + }, + "label": { + "type": "string", + "description": "Display label" + }, + "placeholder": { + "type": "string", + "description": "Placeholder text" + }, + "required": { + "type": "boolean", + "description": "Whether field is required", + "default": false + }, + "disabled": { + "type": "boolean", + "description": "Whether field is disabled", + "default": false + }, + "validation": { + "$ref": "#/$defs/validation" + }, + "conditionalValidation": { + "$ref": "#/$defs/conditionalValidation" + }, + "fallback": { + "$ref": "#/$defs/fallbackBlock" + } + } + }, + "formContainer": { + "type": "object", + "description": "Form container block", + "required": ["type", "children"], + "properties": { + "type": { "const": "forms:form" }, + "id": { + "type": "string", + "description": "Unique form identifier" + }, + "action": { + "type": "string", + "format": "uri", + "description": "Form submission URL" + }, + "method": { + "type": "string", + "enum": ["GET", "POST"], + "description": "HTTP method for submission", + "default": "POST" + }, + "encoding": { + "type": "string", + "description": "Form encoding type", + "default": "application/json" + }, + "children": { + "type": "array", + "description": "Form field blocks" + } + }, + "additionalProperties": false + }, + "textInputBlock": { + "allOf": [ + { "$ref": "#/$defs/baseFormField" }, + { + "type": "object", + "required": ["type", "name"], + "properties": { + "type": { "const": "forms:textInput" }, + "inputType": { + "type": "string", + "enum": ["text", "email", "password", "tel", "number"], + "description": "HTML input type", + "default": "text" + }, + "maxLength": { + "type": "integer", + "minimum": 1, + "description": "Maximum character length" + }, + "autocomplete": { + "type": "string", + "description": "Autocomplete hint" + } + }, + "additionalProperties": false + } + ] + }, + "textAreaBlock": { + "allOf": [ + { "$ref": "#/$defs/baseFormField" }, + { + "type": "object", + "required": ["type", "name"], + "properties": { + "type": { "const": "forms:textArea" }, + "rows": { + "type": "integer", + "minimum": 1, + "description": "Number of visible text rows", + "default": 4 + }, + "maxLength": { + "type": "integer", + "minimum": 1, + "description": "Maximum character length" + } + }, + "additionalProperties": false + } + ] + }, + "checkboxBlock": { + "allOf": [ + { "$ref": "#/$defs/baseFormField" }, + { + "type": "object", + "required": ["type", "name"], + "properties": { + "type": { "const": "forms:checkbox" }, + "defaultChecked": { + "type": "boolean", + "description": "Initial checked state", + "default": false + } + }, + "additionalProperties": false + } + ] + }, + "radioGroupBlock": { + "allOf": [ + { "$ref": "#/$defs/baseFormField" }, + { + "type": "object", + "required": ["type", "name", "options"], + "properties": { + "type": { "const": "forms:radioGroup" }, + "options": { + "type": "array", + "description": "Radio button options", + "items": { "$ref": "#/$defs/option" }, + "minItems": 1 + }, + "defaultValue": { + "type": "string", + "description": "Default selected value" + } + }, + "additionalProperties": false + } + ] + }, + "dropdownBlock": { + "allOf": [ + { "$ref": "#/$defs/baseFormField" }, + { + "type": "object", + "required": ["type", "name", "options"], + "properties": { + "type": { "const": "forms:dropdown" }, + "options": { + "type": "array", + "description": "Dropdown options", + "items": { "$ref": "#/$defs/option" }, + "minItems": 1 + }, + "defaultValue": { + "type": "string", + "description": "Default selected value" + }, + "searchable": { + "type": "boolean", + "description": "Enable search/filter functionality", + "default": false + }, + "multiple": { + "type": "boolean", + "description": "Allow multiple selections", + "default": false + } + }, + "additionalProperties": false + } + ] + }, + "datePickerBlock": { + "allOf": [ + { "$ref": "#/$defs/baseFormField" }, + { + "type": "object", + "required": ["type", "name"], + "properties": { + "type": { "const": "forms:datePicker" }, + "format": { + "type": "string", + "description": "Date format pattern (e.g., 'YYYY-MM-DD')", + "default": "YYYY-MM-DD" + }, + "minDate": { + "type": "string", + "description": "Minimum selectable date (ISO date or 'today')" + }, + "maxDate": { + "type": "string", + "description": "Maximum selectable date (ISO date or 'today')" + }, + "includeTime": { + "type": "boolean", + "description": "Include time selection", + "default": false + } + }, + "additionalProperties": false + } + ] + }, + "signatureBlock": { + "allOf": [ + { "$ref": "#/$defs/baseFormField" }, + { + "type": "object", + "required": ["type", "name"], + "properties": { + "type": { "const": "forms:signature" }, + "width": { + "type": "integer", + "minimum": 100, + "description": "Signature pad width in pixels" + }, + "height": { + "type": "integer", + "minimum": 50, + "description": "Signature pad height in pixels" + } + }, + "additionalProperties": false + } + ] + }, + "submitBlock": { + "type": "object", + "description": "Form submit button", + "required": ["type"], + "properties": { + "type": { "const": "forms:submit" }, + "id": { + "type": "string", + "description": "Unique block identifier" + }, + "label": { + "type": "string", + "description": "Button label", + "default": "Submit" + } + }, + "additionalProperties": false + }, + "formBlock": { + "oneOf": [ + { "$ref": "#/$defs/formContainer" }, + { "$ref": "#/$defs/textInputBlock" }, + { "$ref": "#/$defs/textAreaBlock" }, + { "$ref": "#/$defs/checkboxBlock" }, + { "$ref": "#/$defs/radioGroupBlock" }, + { "$ref": "#/$defs/dropdownBlock" }, + { "$ref": "#/$defs/datePickerBlock" }, + { "$ref": "#/$defs/signatureBlock" }, + { "$ref": "#/$defs/submitBlock" } + ] + }, + "dataFile": { + "type": "object", + "description": "Schema for forms/data.json", + "required": ["version", "values"], + "properties": { + "version": { + "type": "string", + "description": "Forms extension version", + "pattern": "^\\d+\\.\\d+$" + }, + "values": { + "type": "object", + "description": "Map of field names to values", + "additionalProperties": true + }, + "submitted": { + "type": "boolean", + "description": "Whether the form has been submitted", + "default": false + }, + "submittedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 submission timestamp" + }, + "lastModified": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 last modification timestamp" + } + }, + "additionalProperties": false + } + }, + "type": "object", + "description": "Forms data file", + "$ref": "#/$defs/dataFile" +} diff --git a/schemas/phantoms.schema.json b/schemas/phantoms.schema.json index 6bc5c1d..db01f93 100644 --- a/schemas/phantoms.schema.json +++ b/schemas/phantoms.schema.json @@ -187,20 +187,11 @@ "additionalProperties": false }, "author": { - "type": "object", - "required": ["name"], - "properties": { - "name": { - "type": "string", - "description": "Author display name" - }, - "email": { - "type": "string", - "format": "email", - "description": "Author email address" - } - }, - "additionalProperties": false + "description": "Author identity extending base person", + "allOf": [ + { "$ref": "https://codex.document/schemas/anchor.schema.json#/$defs/person" } + ], + "unevaluatedProperties": false } } } diff --git a/schemas/security.schema.json b/schemas/security.schema.json new file mode 100644 index 0000000..9e442c6 --- /dev/null +++ b/schemas/security.schema.json @@ -0,0 +1,389 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://codex.document/schemas/security.schema.json", + "title": "Codex Security Extension", + "description": "Schema for security extension files in a Codex document", + "$defs": { + "signatureAlgorithm": { + "type": "string", + "description": "Supported signature algorithms", + "enum": ["ES256", "ES384", "EdDSA", "PS256", "ML-DSA-65"] + }, + "algorithmStatus": { + "type": "object", + "description": "Algorithm status metadata for implementation guidance", + "properties": { + "ES256": { + "type": "object", + "properties": { + "status": { "const": "required" }, + "description": { "type": "string" } + } + }, + "ES384": { + "type": "object", + "properties": { + "status": { "const": "recommended" }, + "description": { "type": "string" } + } + }, + "EdDSA": { + "type": "object", + "properties": { + "status": { "const": "recommended" }, + "description": { "type": "string" } + } + }, + "PS256": { + "type": "object", + "properties": { + "status": { "const": "optional" }, + "description": { "type": "string" } + } + }, + "ML-DSA-65": { + "type": "object", + "properties": { + "status": { "const": "experimental" }, + "note": { "type": "string" }, + "description": { "type": "string" } + } + } + }, + "additionalProperties": false + }, + "contentEncryptionAlgorithm": { + "type": "string", + "description": "Content encryption algorithms", + "enum": ["A256GCM", "C20P"] + }, + "keyManagementAlgorithm": { + "type": "string", + "description": "Key wrapping/management algorithms", + "enum": ["ECDH-ES+A256KW", "RSA-OAEP-256", "PBES2-HS256+A256KW"] + }, + "signatureState": { + "type": "string", + "description": "Signature verification state", + "enum": ["valid", "invalid", "expired", "revoked", "untrusted", "unknown"] + }, + "signer": { + "description": "Signer identity information extending base person", + "allOf": [ + { "$ref": "https://codex.document/schemas/anchor.schema.json#/$defs/person" }, + { + "type": "object", + "properties": { + "organization": { + "type": "string", + "description": "Signer's organization" + }, + "certificate": { + "type": "string", + "description": "X.509 certificate in PEM format" + }, + "keyId": { + "type": "string", + "description": "Key identifier (DID, URL, or other identifier)" + } + } + } + ], + "unevaluatedProperties": false + }, + "trustedTimestamp": { + "type": "object", + "description": "Trusted timestamp from a timestamping authority", + "required": ["authority", "time", "token"], + "properties": { + "authority": { + "type": "string", + "format": "uri", + "description": "Timestamp authority URL" + }, + "time": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp from the authority" + }, + "token": { + "type": "string", + "description": "Base64-encoded timestamp token" + }, + "algorithm": { + "type": "string", + "description": "Hash algorithm used for timestamping", + "enum": ["SHA256", "SHA384", "SHA512"] + } + }, + "additionalProperties": false + }, + "signatureScope": { + "type": "object", + "description": "Scoped signature covering content and optional layouts", + "required": ["documentId"], + "properties": { + "documentId": { + "type": "string", + "description": "Content hash (must match top-level documentId)", + "pattern": "^(sha256|sha384|sha512|sha3-256|sha3-512|blake3):[a-f0-9]+$" + }, + "layouts": { + "type": "object", + "description": "Map of layout path to layout file hash for visual attestation", + "additionalProperties": { + "type": "string", + "pattern": "^(sha256|sha384|sha512|sha3-256|sha3-512|blake3):[a-f0-9]+$" + } + } + }, + "additionalProperties": true + }, + "webauthnSignature": { + "type": "object", + "description": "WebAuthn/FIDO2 signature data", + "required": ["credentialId", "authenticatorData", "clientDataJSON", "signature"], + "properties": { + "credentialId": { + "type": "string", + "description": "Base64-encoded credential ID" + }, + "authenticatorData": { + "type": "string", + "description": "Base64-encoded authenticator data" + }, + "clientDataJSON": { + "type": "string", + "description": "Base64-encoded client data JSON" + }, + "signature": { + "type": "string", + "description": "Base64-encoded signature" + } + }, + "additionalProperties": false + }, + "signature": { + "type": "object", + "description": "A digital signature entry", + "required": ["id", "algorithm", "signedAt", "signer", "value"], + "properties": { + "id": { + "type": "string", + "description": "Unique signature identifier" + }, + "algorithm": { + "$ref": "#/$defs/signatureAlgorithm" + }, + "signedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 signing timestamp" + }, + "signer": { + "$ref": "#/$defs/signer" + }, + "value": { + "type": "string", + "description": "Base64-encoded signature value" + }, + "certificateChain": { + "type": "array", + "description": "Certificate chain for validation", + "items": { + "type": "string", + "description": "X.509 certificate in PEM format" + } + }, + "timestamp": { + "$ref": "#/$defs/trustedTimestamp" + }, + "scope": { + "$ref": "#/$defs/signatureScope", + "description": "Scoped signature attestation (content + layout)" + }, + "webauthn": { + "$ref": "#/$defs/webauthnSignature", + "description": "WebAuthn signature data (alternative to value)" + } + }, + "additionalProperties": false + }, + "signaturesFile": { + "type": "object", + "description": "Schema for security/signatures.json", + "required": ["version", "documentId", "signatures"], + "properties": { + "version": { + "type": "string", + "description": "Security extension version", + "pattern": "^\\d+\\.\\d+$" + }, + "documentId": { + "type": "string", + "description": "Document content hash being signed", + "pattern": "^(sha256|sha384|sha512|sha3-256|sha3-512|blake3):[a-f0-9]+$" + }, + "signatures": { + "type": "array", + "description": "Array of signatures", + "items": { + "$ref": "#/$defs/signature" + } + } + }, + "additionalProperties": false + }, + "encryptionRecipient": { + "type": "object", + "description": "Encryption recipient with wrapped key", + "required": ["id", "encryptedKey"], + "properties": { + "id": { + "type": "string", + "description": "Recipient identifier" + }, + "name": { + "type": "string", + "description": "Recipient display name" + }, + "keyId": { + "type": "string", + "description": "Recipient's public key identifier" + }, + "encryptedKey": { + "type": "string", + "description": "Base64-encoded encrypted content encryption key" + }, + "ephemeralPublicKey": { + "type": "string", + "description": "Base64-encoded ephemeral public key (for ECDH)" + } + }, + "additionalProperties": false + }, + "encryptedContent": { + "type": "object", + "description": "Encrypted content reference", + "required": ["iv", "tag", "path"], + "properties": { + "iv": { + "type": "string", + "description": "Base64-encoded initialization vector" + }, + "tag": { + "type": "string", + "description": "Base64-encoded authentication tag" + }, + "path": { + "type": "string", + "description": "Path to encrypted file (typically with .enc suffix)" + } + }, + "additionalProperties": false + }, + "encryptionFile": { + "type": "object", + "description": "Schema for security/encryption.json", + "required": ["version", "algorithm", "keyManagement", "recipients", "encryptedContent"], + "properties": { + "version": { + "type": "string", + "description": "Security extension version", + "pattern": "^\\d+\\.\\d+$" + }, + "algorithm": { + "$ref": "#/$defs/contentEncryptionAlgorithm" + }, + "keyManagement": { + "$ref": "#/$defs/keyManagementAlgorithm" + }, + "recipients": { + "type": "array", + "description": "Array of recipients who can decrypt", + "items": { + "$ref": "#/$defs/encryptionRecipient" + }, + "minItems": 1 + }, + "encryptedContent": { + "$ref": "#/$defs/encryptedContent" + } + }, + "additionalProperties": false + }, + "permission": { + "type": "object", + "description": "Permission grants", + "properties": { + "view": { + "type": "boolean", + "description": "Can view document content" + }, + "print": { + "type": "boolean", + "description": "Can print document" + }, + "copy": { + "type": "boolean", + "description": "Can copy text to clipboard" + }, + "annotate": { + "type": "boolean", + "description": "Can add comments/annotations" + }, + "edit": { + "type": "boolean", + "description": "Can edit content (draft only)" + }, + "sign": { + "type": "boolean", + "description": "Can add signatures" + }, + "decrypt": { + "type": "boolean", + "description": "Can decrypt if encrypted" + } + }, + "additionalProperties": false + }, + "principalPermission": { + "type": "object", + "description": "Permission assignment to a principal", + "required": ["principal", "grants"], + "properties": { + "principal": { + "type": "string", + "description": "Principal identifier (user:email, group:name, role:name, or *)", + "pattern": "^(user:[^:]+|group:[^:]+|role:[^:]+|\\*)$" + }, + "grants": { + "$ref": "#/$defs/permission" + } + }, + "additionalProperties": false + }, + "accessControl": { + "type": "object", + "description": "Access control configuration", + "properties": { + "default": { + "$ref": "#/$defs/permission", + "description": "Default permissions for unauthenticated users" + }, + "permissions": { + "type": "array", + "description": "Permission assignments to principals", + "items": { + "$ref": "#/$defs/principalPermission" + } + } + }, + "additionalProperties": false + } + }, + "oneOf": [ + { "$ref": "#/$defs/signaturesFile" }, + { "$ref": "#/$defs/encryptionFile" } + ] +} diff --git a/scripts/validate-examples.ts b/scripts/validate-examples.ts index e71056a..b1ec768 100644 --- a/scripts/validate-examples.ts +++ b/scripts/validate-examples.ts @@ -40,22 +40,44 @@ function loadJson(filepath: string): unknown { return JSON.parse(content); } +// Schema dependencies (schemas that need other schemas loaded first) +const schemaDependencies: Record = { + 'content.schema.json': ['semantic.schema.json', 'academic.schema.json'], + 'collaboration.schema.json': ['anchor.schema.json'], + 'phantoms.schema.json': ['anchor.schema.json'], + 'security.schema.json': ['anchor.schema.json'], +}; + function getValidator(schemaName: string): ValidateFunction { if (!validators[schemaName]) { const ajv = createAjv(); + // Load dependency schemas first + const deps = schemaDependencies[schemaName] || []; + for (const dep of deps) { + const depSchema = loadSchema(dep); + ajv.addSchema(depSchema); + } const schema = loadSchema(schemaName); validators[schemaName] = ajv.compile(schema); } return validators[schemaName]; } -// Document validations to perform +// Document validations to perform (common files) const validations: Validation[] = [ { schema: 'manifest.schema.json', file: 'manifest.json' }, { schema: 'content.schema.json', file: 'content/document.json' }, { schema: 'dublin-core.schema.json', file: 'metadata/dublin-core.json' }, ]; +// Extension-specific validations (only if files exist) +const extensionValidations: Validation[] = [ + { schema: 'collaboration.schema.json', file: 'collaboration/comments.json' }, + { schema: 'collaboration.schema.json', file: 'collaboration/changes.json' }, + { schema: 'forms.schema.json', file: 'forms/data.json' }, + { schema: 'phantoms.schema.json', file: 'phantoms/clusters.json' }, +]; + let hasErrors = false; console.log('Validating example documents...\n'); @@ -69,6 +91,7 @@ for (const exampleName of exampleDirs) { console.log(`${exampleName}/`); const examplePath = path.join(examplesDir, exampleName); + // Validate common files for (const { schema, file } of validations) { const filepath = path.join(examplePath, file); @@ -97,6 +120,35 @@ for (const exampleName of exampleDirs) { hasErrors = true; } } + + // Validate extension-specific files (silently skip if not present) + for (const { schema, file } of extensionValidations) { + const filepath = path.join(examplePath, file); + + if (!fs.existsSync(filepath)) { + continue; + } + + try { + const validate = getValidator(schema); + const data = loadJson(filepath); + const valid = validate(data); + + if (valid) { + console.log(` ✓ ${file}`); + } else { + console.log(` ✗ ${file}`); + for (const err of validate.errors ?? []) { + console.log(` - ${err.instancePath || '/'}: ${err.message}`); + } + hasErrors = true; + } + } catch (err) { + console.log(` ✗ ${file}`); + console.log(` Error: ${err instanceof Error ? err.message : String(err)}`); + hasErrors = true; + } + } console.log(''); } diff --git a/scripts/validate-schemas.ts b/scripts/validate-schemas.ts index b9662fd..a000737 100644 --- a/scripts/validate-schemas.ts +++ b/scripts/validate-schemas.ts @@ -21,8 +21,8 @@ const standaloneSchemas: string[] = [ 'academic.schema.json', 'anchor.schema.json', 'asset-index.schema.json', - 'content.schema.json', 'dublin-core.schema.json', + 'forms.schema.json', 'manifest.schema.json', 'precise-layout.schema.json', 'presentation.schema.json', @@ -33,7 +33,10 @@ const standaloneSchemas: string[] = [ // Schemas that reference other schemas const dependentSchemas: DependentSchema[] = [ { schema: 'annotations.schema.json', refs: ['anchor.schema.json'] }, + { schema: 'collaboration.schema.json', refs: ['anchor.schema.json'] }, + { schema: 'content.schema.json', refs: ['semantic.schema.json', 'academic.schema.json'] }, { schema: 'phantoms.schema.json', refs: ['anchor.schema.json'] }, + { schema: 'security.schema.json', refs: ['anchor.schema.json'] }, ]; let hasErrors = false; diff --git a/spec/core/03a-anchors-and-references.md b/spec/core/03a-anchors-and-references.md index 99e89a3..0b692c8 100644 --- a/spec/core/03a-anchors-and-references.md +++ b/spec/core/03a-anchors-and-references.md @@ -237,39 +237,65 @@ Implementations SHOULD validate that anchor targets (block IDs, named anchor IDs - `offset` MUST be non-negative - For valid ranges, `end` SHOULD NOT exceed the target block's text content length -## 8. Shared Types +## 8. Terminology Glossary -### 8.1 Person Object +This section defines anchor-related terminology used throughout the specification: -The Person object is a base type used across multiple extensions to represent a person (author, signer, creator). Defining it once ensures consistency across the specification. +| Term | Usage Context | Definition | +|------|---------------|------------| +| Content Anchor URI | Formal name | URI string referencing a content location (e.g., `#blockId/10-25`) | +| ContentAnchor | Schema object type | JSON object representing an anchor with `blockId`, `offset`, `start`, `end` fields | +| anchor | Shorthand | Informal reference to either Content Anchor URI or ContentAnchor object | +| anchor mark | Mark type | A named anchor point within text (`{ "type": "anchor", "id": "..." }`) | +| Content Anchor URI syntax | Pattern description | The `#id[/offset[-end]]` format | + +### 8.1 Content Terminology + +| Term | Description | +|------|-------------| +| `blocks` | Top-level array of block objects (used in root document and phantom content) | +| `children` | Nested content within a block (paragraphs, list items, etc.) | +| `content` | Plain text shorthand in specific contexts (e.g., footnote simple form) | + +The distinction is intentional: `blocks` represents a document-level content array, while `children` represents nested block content. + +## 9. Shared Types + +### 9.1 Person Object + +The Person object is a base type used across multiple extensions to represent a person (author, signer, creator). It is defined in `anchor.schema.json` for reuse across extensions. **Base Person fields:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Display name | -| `email` | string | No | Email address | +| `email` | string | No | Email address (format: email) | +| `identifier` | string | No | Persistent identifier (ORCID, DID, URL, institutional ID) | **Base example:** ```json { "name": "Jane Doe", - "email": "jane@example.com" + "email": "jane@example.com", + "identifier": "https://orcid.org/0000-0002-1825-0097" } ``` -Extensions extend the base Person type with additional fields as needed: +For scholarly documents, the `identifier` field SHOULD use ORCID format (e.g., `https://orcid.org/0000-0002-1825-0097`). + +Extensions extend the base Person type with additional fields via schema composition (`allOf`): -| Extension | Additional Fields | Description | -|-----------|------------------|-------------| -| Security (signer) | `organization`, `certificate`, `keyId` | Cryptographic identity | -| Provenance (creator) | `identifier` | DID or URI-based identifier | -| Collaboration (presence) | `userId`, `color` | Real-time collaboration identity | +| Extension | Object Name | Additional Fields | Description | +|-----------|-------------|------------------|-------------| +| Collaboration | `author` | `userId`, `avatar`, `color` | Real-time collaboration identity | +| Security | `signer` | `organization`, `certificate`, `keyId` | Cryptographic identity | +| Phantoms | `author` | (none) | Basic author attribution | -All Person objects MUST include at minimum the `name` field. Extensions SHOULD include the base fields alongside their extension-specific fields. +All Person objects MUST include at minimum the `name` field. Extensions SHOULD include the base fields alongside their extension-specific fields. The naming distinction between "author" and "signer" is intentional to reflect the semantic difference in their contexts. -## 9. Relationship to Extensions +## 10. Relationship to Extensions The anchor system is defined in the core specification but is primarily consumed by extensions: diff --git a/spec/extensions/README.md b/spec/extensions/README.md new file mode 100644 index 0000000..d1716a3 --- /dev/null +++ b/spec/extensions/README.md @@ -0,0 +1,111 @@ +# Codex Extensions + +This directory contains specifications for Codex extensions. Each extension adds specialized functionality to the core Codex document format. + +## Available Extensions + +| Extension | ID | Version | Status | Purpose | +|-----------|----|---------|----|---------| +| [Semantic](semantic/README.md) | `codex.semantic` | 0.1 | Draft | Citations, footnotes, glossary, entity annotations | +| [Academic](academic/README.md) | `codex.academic` | 0.1 | Draft | Theorems, proofs, exercises, algorithms, equations | +| [Forms](forms/README.md) | `codex.forms` | 0.1 | Draft | Interactive form fields and validation | +| [Collaboration](collaboration/README.md) | `codex.collaboration` | 0.2 | Draft | Comments, track changes, real-time collaboration | +| [Security](security/README.md) | `codex.security` | 0.1 | Draft | Digital signatures, encryption, access control | +| [Phantoms](phantoms/README.md) | `codex.phantoms` | 0.1 | Draft | Off-page annotation clusters | +| [Presentation](presentation/README.md) | `codex.presentation` | 0.1 | Draft | Layout templates and rendering hints | + +## Extension Compatibility + +Extensions are designed to work together. The following matrix shows compatibility between extensions: + +| Extension | semantic | academic | forms | collaboration | security | phantoms | presentation | +|-----------|----------|----------|-------|---------------|----------|----------|--------------| +| semantic | - | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| academic | ✓ | - | △* | ✓ | ✓ | ✓ | ✓ | +| forms | ✓ | △* | - | ✓ | ✓ | ✓ | ✓ | +| collaboration | ✓ | ✓ | ✓ | - | ✓ | ✓ | ✓ | +| security | ✓ | ✓ | ✓ | ✓ | - | ✓ | ✓ | +| phantoms | ✓ | ✓ | ✓ | ✓ | ✓ | - | ✓ | +| presentation | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | - | + +**Legend:** +- ✓ = Fully compatible +- △ = Technically compatible but unusual combination +- ✗ = Incompatible + +*Academic and forms are both content-heavy extensions; combining them is technically possible but unusual in practice. + +## Common Patterns + +### Extension Declaration + +Extensions are declared in the manifest: + +```json +{ + "extensions": [ + { + "id": "codex.semantic", + "version": "0.1", + "required": false + } + ] +} +``` + +The `required` field indicates whether the document can be meaningfully viewed without the extension: +- `false`: Document degrades gracefully without extension support +- `true`: Extension is essential to document content + +### Extension Data Files + +Most extensions store data in dedicated directories: + +| Extension | Directory | Primary Files | +|-----------|-----------|---------------| +| semantic | `semantic/` | `bibliography.json`, `glossary.json` | +| academic | `academic/` | `numbering.json` | +| forms | `forms/` | `data.json` | +| collaboration | `collaboration/` | `comments.json`, `changes.json` | +| security | `security/` | `signatures.json`, `encryption.json` | +| phantoms | `phantoms/` | `clusters.json` | + +### Shared Definitions + +Extensions share common definitions from the core specification: + +- **ContentAnchor** (`anchor.schema.json`): Position references used by collaboration, phantoms, annotations +- **Person** (`anchor.schema.json`): Base identity object extended by collaboration (author), security (signer), phantoms (author) + +## Implementation Guidance + +When implementing Codex support, consider the following priority order: + +### Required for Basic Support +1. Core content blocks +2. Dublin Core metadata +3. Manifest parsing + +### Recommended Extensions +1. **Security** - For document integrity verification +2. **Presentation** - For proper rendering + +### Content Extensions (as needed) +- **Semantic** - For scholarly documents with citations +- **Academic** - For mathematical/scientific content +- **Forms** - For fillable documents + +### Collaboration Extensions +- **Collaboration** - For multi-user editing +- **Phantoms** - For advanced annotation workflows + +## Versioning + +- Each extension specifies its version in the manifest declaration +- Data files include a `version` field matching the extension version +- Version changes follow semantic versioning principles: + - Patch: Bug fixes, clarifications + - Minor: New optional features, backward-compatible + - Major: Breaking changes + +See individual extension READMEs for version history and migration notes. diff --git a/spec/extensions/academic/README.md b/spec/extensions/academic/README.md index 0ae3cdf..607f6b5 100644 --- a/spec/extensions/academic/README.md +++ b/spec/extensions/academic/README.md @@ -897,3 +897,49 @@ This produces: Theorem 1, Lemma 2, Proposition 3, Theorem 4, etc. } ] ``` + +## 11. Author Identification + +For scholarly documents, author identification is critical for attribution and citation. The Codex specification uses a base `person` object defined in `anchor.schema.json` that includes an `identifier` field for persistent identifiers. + +### 11.1 ORCID Recommendation + +For academic documents, the `identifier` field SHOULD use ORCID (Open Researcher and Contributor ID) format: + +```json +{ + "name": "Jane Doe", + "email": "jane.doe@university.edu", + "identifier": "https://orcid.org/0000-0002-1825-0097" +} +``` + +ORCID provides: +- Unique, persistent identification across all scholarly outputs +- Automatic disambiguation of authors with similar names +- Links to affiliations, grants, and publications +- Integration with major publishers and funding agencies + +### 11.2 Other Identifier Formats + +The `identifier` field also supports: + +| Format | Example | Use Case | +|--------|---------|----------| +| ORCID | `https://orcid.org/0000-0002-1825-0097` | Researchers (recommended) | +| ISNI | `https://isni.org/isni/0000000121032683` | Name authority records | +| DID | `did:web:example.com:jane` | Decentralized identity | +| Institutional | `https://university.edu/faculty/jdoe` | University profiles | + +### 11.3 Dublin Core Integration + +Author identifiers should also be included in Dublin Core metadata (`metadata/dublin-core.json`) for discoverability: + +```json +{ + "creator": ["Jane Doe", "John Smith"], + "contributor": ["Research Assistant"] +} +``` + +For richer author metadata, use the semantic extension's JSON-LD annotations to provide Schema.org `Person` objects with ORCID identifiers. diff --git a/spec/extensions/forms/README.md b/spec/extensions/forms/README.md index dc79cc5..1171285 100644 --- a/spec/extensions/forms/README.md +++ b/spec/extensions/forms/README.md @@ -192,6 +192,51 @@ For cross-field validation (e.g., password confirmation): } ``` +### 4.3 Conditional Validation + +Apply validation rules based on other field values using `conditionalValidation`: + +```json +{ + "type": "forms:textInput", + "name": "state", + "label": "State/Province", + "conditionalValidation": { + "when": { "field": "country", "equals": "us" }, + "then": { "required": true } + } +} +``` + +The `when` condition supports the following operators: + +| Operator | Description | +|----------|-------------| +| `equals` | Condition is true when the field equals the specified value | +| `notEquals` | Condition is true when the field does not equal the specified value | +| `isEmpty` | Condition is true when the field is empty (set to `true`) | +| `isNotEmpty` | Condition is true when the field has a value (set to `true`) | + +Only one operator should be used per condition. When the condition evaluates to true, all validation rules in `then` are applied to the field. + +Example with multiple conditional rules: + +```json +{ + "type": "forms:textInput", + "name": "companyName", + "label": "Company Name", + "conditionalValidation": { + "when": { "field": "employmentType", "equals": "employed" }, + "then": { + "required": true, + "minLength": 2, + "message": "Company name is required for employed individuals" + } + } +} +``` + ## 5. Form Data ### 5.1 Storage